fix: 合并冲突

This commit is contained in:
jackhoo_98 2023-03-21 10:20:57 +08:00
commit d9c2da7f4e
93 changed files with 820 additions and 1038 deletions

View File

@ -1,5 +1,5 @@
<template> <template>
<a-modal :mask-closable="false" visible width="70vw" title="设置属性规则" @cancel="handleCancel" @ok="handleOk"> <j-modal :mask-closable="false" visible width="70vw" title="设置属性规则" @cancel="handleCancel" @ok="handleOk">
<div class="advance-box"> <div class="advance-box">
<div class="left"> <div class="left">
<Editor <Editor
@ -20,7 +20,7 @@
<Operator :id="id" @add-operator-value="addOperatorValue"/> <Operator :id="id" @add-operator-value="addOperatorValue"/>
</div> </div>
</div> </div>
</a-modal> </j-modal>
</template> </template>
<script setup lang="ts" name="Advance"> <script setup lang="ts" name="Advance">
import Editor from '../Editor/index.vue' import Editor from '../Editor/index.vue'

View File

@ -12,28 +12,28 @@
</div> </div>
</div> </div>
</div> </div>
<a-table :columns="columns" :data-source="property" :pagination="false" bordered size="small"> <j-table :columns="columns" :data-source="property" :pagination="false" bordered size="small">
<template #bodyCell="{ column, record, index }"> <template #bodyCell="{ column, record, index }">
<template v-if="column.key === 'id'"> <template v-if="column.key === 'id'">
<j-auto-complete :options="options" v-model:value="record.id" size="small" width="130px"/> <j-auto-complete :options="options" v-model:value="record.id" size="small" width="130px"/>
</template> </template>
<template v-if="column.key === 'current'"> <template v-if="column.key === 'current'">
<a-input v-model:value="record.current" size="small"></a-input> <j-input v-model:value="record.current" size="small"></j-input>
</template> </template>
<template v-if="column.key === 'last'"> <template v-if="column.key === 'last'">
<a-input v-model:value="record.last" size="small"></a-input> <j-input v-model:value="record.last" size="small"></j-input>
</template> </template>
<template v-if="column.key === 'action'"> <template v-if="column.key === 'action'">
<delete-outlined @click="deleteItem(index)" /> <AIcon type="DeleteOutlined" @click="deleteItem(index)" />
</template> </template>
</template> </template>
</a-table> </j-table>
<a-button type="dashed" block style="margin-top: 5px" @click="addItem"> <j-button type="dashed" block style="margin-top: 5px" @click="addItem">
<template #icon> <template #icon>
<plus-outlined /> <AIcon type="PlusOutlined" />
</template> </template>
添加条目 添加条目
</a-button> </j-button>
</div> </div>
<div class="right"> <div class="right">
<div class="header"> <div class="header">
@ -57,15 +57,14 @@
</div> </div>
</div> </div>
<div class="log"> <div class="log">
<a-descriptions> <j-descriptions>
<a-descriptions-item v-for="item in ruleEditorStore.state.log" :label="moment(item.time).format('HH:mm:ss')" <j-descriptions-item v-for="item in ruleEditorStore.state.log" :label="moment(item.time).format('HH:mm:ss')"
:key="item.time" :span="3"> :key="item.time" :span="3">
<a-tooltip placement="top" :title="item.content"> <j-tooltip placement="top" :title="item.content">
{{ item.content }} {{ item.content }}
</a-tooltip> </j-tooltip>
</a-descriptions-item> </j-descriptions-item>
))} </j-descriptions>
</a-descriptions>
</div> </div>
</div> </div>
</div> </div>

View File

@ -7,35 +7,33 @@
{{ item.value }} {{ item.value }}
</span> </span>
<span> <span>
<a-dropdown> <j-dropdown>
<more-outlined /> <AIcon type="MoreOutlined" />
<template #overlay> <template #overlay>
<a-menu> <j-menu>
<a-menu-item v-for="item in symbolList.filter((t: SymbolType, i: number) => i > 6)" :key="item.key" <j-menu-item v-for="item in symbolList.filter((t: SymbolType, i: number) => i > 6)" :key="item.key"
@click="addOperatorValue(item.value)"> @click="addOperatorValue(item.value)">
{{ item.value }} {{ item.value }}
</a-menu-item> </j-menu-item>
</a-menu> </j-menu>
</template> </template>
</a-dropdown> </j-dropdown>
</span> </span>
</div> </div>
<div class="right"> <div class="right">
<span v-if="mode !== 'advance'"> <span v-if="mode !== 'advance'">
<a-tooltip :title="!id ? '请先输入标识' : '设置属性规则'"> <j-tooltip :title="!id ? '请先输入标识' : '设置属性规则'">
<fullscreen-outlined :class="!id ? 'disabled' : ''" @click="fullscreenClick" /> <AIcon type="FullscreenOutlined" :class="!id ? 'disabled' : ''" @click="fullscreenClick" />
</a-tooltip> </j-tooltip>
</span> </span>
</div> </div>
</div> </div>
<div class="editor"> <div class="editor">
<MonacoEditor v-if="loading" v-model:model-value="_value" theme="vs" ref="editor" /> <JMonacoEditor v-if="loading" v-model:model-value="_value" theme="vs" ref="editor" lang="javascript"/>
</div> </div>
</div> </div>
</template> </template>
<script setup lang="ts" name="Editor"> <script setup lang="ts" name="Editor">
import { FullscreenOutlined, MoreOutlined } from '@ant-design/icons-vue';
import MonacoEditor from '@/components/MonacoEditor/index.vue';
interface Props { interface Props {
mode?: 'advance' | 'simple'; mode?: 'advance' | 'simple';

View File

@ -1,30 +1,30 @@
<template> <template>
<div class="operator-box"> <div class="operator-box">
<a-input-search @search="search" allow-clear placeholder="搜索关键字" /> <j-input-search @search="search" allow-clear placeholder="搜索关键字" />
<div class="tree"> <div class="tree">
<a-tree @select="selectTree" :field-names="{ title: 'name', key: 'id', }" auto-expand-parent <j-tree @select="selectTree" :field-names="{ title: 'name', key: 'id', }" auto-expand-parent
:tree-data="data"> :tree-data="data">
<template #title="node"> <template #title="node">
<div class="node"> <div class="node">
<div>{{ node.name }}</div> <div>{{ node.name }}</div>
<div :class="node.children?.length > 0 ? 'parent' : 'add'"> <div :class="node.children?.length > 0 ? 'parent' : 'add'">
<a-popover v-if="node.type === 'property'" placement="right" title="请选择使用值"> <j-popover v-if="node.type === 'property'" placement="right" title="请选择使用值">
<template #content> <template #content>
<a-space direction="vertical"> <j-space direction="vertical">
<a-tooltip placement="right" title="实时值为空时获取上一有效值补齐,实时值不为空则使用实时值"> <j-tooltip placement="right" title="实时值为空时获取上一有效值补齐,实时值不为空则使用实时值">
<a-button type="text" @click="recentClick(node)"> <j-button type="text" @click="recentClick(node)">
$recent实时值 $recent实时值
</a-button> </j-button>
</a-tooltip> </j-tooltip>
<a-tooltip placement="right" title="实时值的上一有效值"> <j-tooltip placement="right" title="实时值的上一有效值">
<a-button @click="lastClick(node)" type="text"> <j-button @click="lastClick(node)" type="text">
上一值 上一值
</a-button> </j-button>
</a-tooltip> </j-tooltip>
</a-space> </j-space>
</template> </template>
<a>添加</a> <a>添加</a>
</a-popover> </j-popover>
<a v-else @click="addClick(node)"> <a v-else @click="addClick(node)">
添加 添加
@ -32,7 +32,7 @@
</div> </div>
</div> </div>
</template> </template>
</a-tree> </j-tree>
</div> </div>
<div class="explain"> <div class="explain">
<Markdown :source="item?.description || ''"></Markdown> <Markdown :source="item?.description || ''"></Markdown>

View File

@ -1,5 +1,5 @@
<template> <template>
<a-select v-model:value="_value" mode="tags" :options="options" :size="size" @change="change"></a-select> <a-select v-model:value="_value" mode="tags" :options="options" :size="size" @change="change" placeholder="请选择单位"></a-select>
</template> </template>
<script setup lang="ts" name="InputSelect"> <script setup lang="ts" name="InputSelect">
import { SizeType } from 'ant-design-vue/es/config-provider'; import { SizeType } from 'ant-design-vue/es/config-provider';

View File

@ -3,16 +3,16 @@
<template v-if="['int', 'long', 'double', 'float'].includes(type)"> <template v-if="['int', 'long', 'double', 'float'].includes(type)">
<template v-if="value.range"> <template v-if="value.range">
<j-input-number v-model:value="value.value[0]" :max="value.value[1]" size="small" <j-input-number v-model:value="value.value[0]" :max="value.value[1]" size="small"
style="width: 100%;"></j-input-number> style="width: 100%;" placeholder="请输入"></j-input-number>
~ ~
<j-input-number v-model:value="value.value[1]" :min="value.value[0]" size="small" <j-input-number v-model:value="value.value[1]" :min="value.value[0]" size="small"
style="width: 100%;"></j-input-number> style="width: 100%;" placeholder="请输入"></j-input-number>
</template> </template>
<j-input-number v-else v-model:value="value.value" size="small" style="width: 100%;"></j-input-number> <j-input-number v-else v-model:value="value.value" size="small" style="width: 100%;" placeholder="请输入"></j-input-number>
</template> </template>
<template v-else-if="type === 'date'"> <template v-else-if="type === 'date'">
<j-range-picker v-if="value.range" show-time v-model:value="value.value" size="small" /> <j-range-picker v-if="value.range" show-time v-model:value="value.value" size="small" placeholder="请输入"/>
<j-date-picker v-else show-time v-model:value="value.value" size="small" /> <j-date-picker v-else show-time v-model:value="value.value" size="small" placeholder="请输入"/>
</template> </template>
<template v-else-if="type === 'boolean'"> <template v-else-if="type === 'boolean'">
<j-select v-model:value="value.value[0]" :options="list" size="small" placeholder="请选择"></j-select> <j-select v-model:value="value.value[0]" :options="list" size="small" placeholder="请选择"></j-select>
@ -57,7 +57,7 @@ const props = defineProps({
} }
}) })
Form.useInjectFormItemContext() // Form.useInjectFormItemContext()
const changeChecked = (e: CheckboxChangeEvent) => { const changeChecked = (e: CheckboxChangeEvent) => {
if (e.target.checked) { if (e.target.checked) {

View File

@ -1,28 +1,30 @@
<template> <template>
<j-popover :visible="visible" placement="left"> <j-button type="dashed" block @click="visible = true">
<template #title> <j-popover :visible="visible" placement="left">
<div style="display: flex; justify-content: space-between; align-items: center;"> <template #title>
<div style="width: 150px;">配置元素</div> <div style="display: flex; justify-content: space-between; align-items: center;">
<div @click="visible = false"><AIcon type="CloseOutlined" /></div> <div style="width: 150px;">配置元素</div>
</div> <div @click="visible = false">
</template> <AIcon type="CloseOutlined" />
<template #content> </div>
<div style="max-width: 400px;">
<div class="ant-form-vertical">
<value-type-form v-model:value="_value" :name="name" isSub key="sub"></value-type-form>
<j-form-item label="说明" :name="name.concat(['description'])" :rules="[
{ max: 200, message: '最多可输入200个字符' },
]">
<j-textarea v-model:value="_value.description" size="small"></j-textarea>
</j-form-item>
</div> </div>
</div> </template>
</template> <template #content>
<j-button type="dashed" block @click="visible = true"> <div style="max-width: 400px;">
<div class="ant-form-vertical">
<value-type-form v-model:value="_value" :name="name" isSub key="sub"></value-type-form>
<j-form-item label="说明" :name="name.concat(['description'])" :rules="[
{ max: 200, message: '最多可输入200个字符' },
]">
<j-textarea v-model:value="_value.description" size="small" placeholder="请输入说明"></j-textarea>
</j-form-item>
</div>
</div>
</template>
配置元素 配置元素
<AIcon type="EditOutlined" class="item-icon" /> <AIcon type="EditOutlined" class="item-icon" />
</j-button> </j-popover>
</j-popover> </j-button>
</template> </template>
<script setup lang="ts" name="ArrayParam"> <script setup lang="ts" name="ArrayParam">
import ValueTypeForm from '@/views/device/components/Metadata/Base/Edit/ValueTypeForm.vue'; import ValueTypeForm from '@/views/device/components/Metadata/Base/Edit/ValueTypeForm.vue';

View File

@ -1,24 +1,25 @@
<template> <template>
<j-popover placement="left" trigger="click"> <j-button type="dashed" block>
<template #title> <j-popover placement="left" trigger="click">
<div class="edit-title" style="display: flex; justify-content: space-between; align-items: center;"> <template #title>
<div style="width: 150px;">{{ config.name }}</div> <div class="edit-title" style="display: flex; justify-content: space-between; align-items: center;">
</div> <div style="width: 150px;">{{ config.name }}</div>
</template> </div>
<template #content> </template>
<div style="max-width: 400px;" class="ant-form-vertical"> <template #content>
<j-form-item v-for="item in config.properties" :name="name.concat([item.property])" :label="item.name"> <div style="max-width: 400px;" class="ant-form-vertical">
<j-select v-model:value="value[item.property]" :options="item.type?.elements?.map((e: { 'text': string, 'value': string }) => ({ <j-form-item v-for="item in config.properties" :name="name.concat([item.property])" :label="item.name">
label: e.text, <j-select v-model:value="value[item.property]" :options="item.type?.elements?.map((e: { 'text': string, 'value': string }) => ({
value: e.value, label: e.text,
}))" size="small"></j-select> value: e.value,
</j-form-item> }))" size="small" :placeholder="`请输入${item.name}`"></j-select>
</div> </j-form-item>
</template> </div>
<j-button type="dashed" block> </template>
存储配置<AIcon type="EditOutlined" class="item-icon"/> 存储配置
</j-button> <AIcon type="EditOutlined" class="item-icon" />
</j-popover> </j-popover>
</j-button>
</template> </template>
<script setup lang="ts" name="ConfigParam"> <script setup lang="ts" name="ConfigParam">
import { PropType } from 'vue'; import { PropType } from 'vue';
@ -30,7 +31,7 @@ const props = defineProps({
default: () => ({}) default: () => ({})
}, },
name: { name: {
type: Array as PropType<(string| number)[]>, type: Array as PropType<(string | number)[]>,
default: () => ([]), default: () => ([]),
required: true required: true
}, },
@ -57,15 +58,17 @@ const props = defineProps({
color: rgb(136, 136, 136); color: rgb(136, 136, 136);
font-size: 12px; font-size: 12px;
} }
:deep(.ant-form-item-label) { :deep(.ant-form-item-label) {
>label { >label {
font-size: 12px; font-size: 12px;
} }
} }
:deep(.ant-select) { :deep(.ant-select) {
font-size: 12px; font-size: 12px;
} }
:deep(input) { :deep(input) {
height: 22px; height: 22px;
} }</style>
</style>

View File

@ -18,13 +18,13 @@
{ required: true, message: '请输入Value' }, { required: true, message: '请输入Value' },
{ max: 64, message: '最多可输入64个字符' }, { max: 64, message: '最多可输入64个字符' },
]"> ]">
<j-input v-model:value="_value[index].value" size="small"></j-input> <j-input v-model:value="_value[index].value" size="small" placeholder="请输入Value"></j-input>
</j-form-item> </j-form-item>
<j-form-item label="Text" :name="name.concat([index, 'text'])" :rules="[ <j-form-item label="Text" :name="name.concat([index, 'text'])" :rules="[
{ required: true, message: '请输入Text' }, { required: true, message: '请输入Text' },
{ max: 64, message: '最多可输入64个字符' }, { max: 64, message: '最多可输入64个字符' },
]"> ]">
<j-input v-model:value="_value[index].text" size="small"></j-input> <j-input v-model:value="_value[index].text" size="small" placeholder="请输入Text"></j-input>
</j-form-item> </j-form-item>
</div> </div>
</template> </template>

View File

@ -22,15 +22,15 @@
message: 'ID只能由数字、字母、下划线、中划线组成', message: 'ID只能由数字、字母、下划线、中划线组成',
}, },
]"> ]">
<j-input v-model:value="_value[index].id" size="small"></j-input> <j-input v-model:value="_value[index].id" size="small" placeholder="请输入标识"></j-input>
</j-form-item> </j-form-item>
<j-form-item label="名称" :name="name.concat([index, 'name'])" :rules="[ <j-form-item label="名称" :name="name.concat([index, 'name'])" :rules="[
{ required: true, message: '请输入名称' }, { required: true, message: '请输入名称' },
{ max: 64, message: '最多可输入64个字符' }, { max: 64, message: '最多可输入64个字符' },
]"> ]">
<j-input v-model:value="_value[index].name" size="small"></j-input> <j-input v-model:value="_value[index].name" size="small" placeholder="请输入名称"></j-input>
</j-form-item> </j-form-item>
<value-type-form v-model:value="_value[index].valueType" :name="name.concat([index, 'valueType'])" isSub <value-type-form v-model:value="_value[index].valueType" :name="name.concat([index, 'valueType'])" :isSub="isSub"
key="json_sub"></value-type-form> key="json_sub"></value-type-form>
</div> </div>
</template> </template>
@ -69,6 +69,10 @@ const props = defineProps({
name: { name: {
type: Array as PropType<(string | number)[]>, type: Array as PropType<(string | number)[]>,
default: () => ([]) default: () => ([])
},
isSub: {
type: Boolean,
default: true
} }
}) })

View File

@ -6,7 +6,7 @@
{{ `#${index + 1}.` }} {{ `#${index + 1}.` }}
</div> </div>
<div class="item-middle item-editable"> <div class="item-middle item-editable">
<j-popover :visible="editIndex === index" placement="top" @visible-change="change" trigger="click"> <j-popover :visible="editIndex === index" placement="left" @visible-change="change" trigger="click">
<template #title> <template #title>
<div class="edit-title" style="display: flex; justify-content: space-between; align-items: center;"> <div class="edit-title" style="display: flex; justify-content: space-between; align-items: center;">
<div style="width: 150px;">配置参数</div> <div style="width: 150px;">配置参数</div>
@ -24,13 +24,13 @@
message: 'ID只能由数字、字母、下划线、中划线组成', message: 'ID只能由数字、字母、下划线、中划线组成',
}, },
]"> ]">
<j-input v-model:value="_value[index].id" size="small"></j-input> <j-input v-model:value="_value[index].id" size="small" placeholder="请输入标识"></j-input>
</j-form-item> </j-form-item>
<j-form-item label="名称" :name="name.concat([index, 'name'])" :rules="[ <j-form-item label="名称" :name="name.concat([index, 'name'])" :rules="[
{ required: true, message: '请输入名称' }, { required: true, message: '请输入名称' },
{ max: 64, message: '最多可输入64个字符' }, { max: 64, message: '最多可输入64个字符' },
]"> ]">
<j-input v-model:value="_value[index].name" size="small"></j-input> <j-input v-model:value="_value[index].name" size="small" placeholder="请输入名称"></j-input>
</j-form-item> </j-form-item>
<j-form-item label="指标值" :name="name.concat([index, 'value'])" :rules="[ <j-form-item label="指标值" :name="name.concat([index, 'value'])" :rules="[
{ required: true, validator: () => validateIndicator(_value[index]), message: '请输入指标值' } { required: true, validator: () => validateIndicator(_value[index]), message: '请输入指标值' }
@ -41,7 +41,7 @@
</div> </div>
</template> </template>
<div class="item-edit" @click="handleEdit(index)"> <div class="item-edit" @click="handleEdit(index)">
{{ item.name || '配置参数' }} <Ellipsis>{{ item.name || '配置参数' }}</Ellipsis>
<AIcon type="EditOutlined" class="item-icon" /> <AIcon type="EditOutlined" class="item-icon" />
</div> </div>
</j-popover> </j-popover>
@ -118,7 +118,7 @@ const validateIndicator = (value: any) => {
return Promise.reject(new Error('请输入指标值')); return Promise.reject(new Error('请输入指标值'));
} }
} else { } else {
if (value?.value === '' || value?.value === undefined) { if (!value?.value) {
return Promise.reject(new Error('请输入指标值')); return Promise.reject(new Error('请输入指标值'));
} }
} }
@ -151,6 +151,10 @@ const change = (visible: boolean) => {
// } // }
.item-edit { .item-edit {
cursor: pointer; cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
max-width: 240px;
} }
.item-icon { .item-icon {

View File

@ -33,7 +33,10 @@ export const defaultBranches = [
terms: [ terms: [
{ {
column: undefined, column: undefined,
value: undefined, value: {
source: 'fixed',
value: undefined
},
termType: undefined, termType: undefined,
key: 'params_1', key: 'params_1',
type: 'and', type: 'and',
@ -89,6 +92,8 @@ export const useSceneStore = defineStore('scene', () => {
branches = cloneDeep(defaultBranches) branches = cloneDeep(defaultBranches)
if (triggerType === 'device') { if (triggerType === 'device') {
branches.push(null) branches.push(null)
} else {
branches[0].when.length = []
} }
} else { } else {
const branchesLength = branches.length; const branchesLength = branches.length;

View File

@ -4,7 +4,7 @@
* @returns {boolean} * @returns {boolean}
*/ */
export const phoneRegEx = (value: string) => { export const phoneRegEx = (value: string) => {
const phone = new RegExp('^(((\\+86)|(\\+86-))|((86)|(86\\-))|((0086)|(0086\\-)))?1[3|5|7|8]\\d{9}$') const phone = new RegExp('^(((\\+86)|(\\+86-))|((86)|(86\\-))|((0086)|(0086\\-)))?1[3|5|7|8|9]\\d{9}$')
const mobile = /(0[0-9]{2,3})([2-9][0-9]{6,7})+([0-9]{8,11})?$/ const mobile = /(0[0-9]{2,3})([2-9][0-9]{6,7})+([0-9]{8,11})?$/
return phone.test(value) || mobile.test(value) return phone.test(value) || mobile.test(value)
} }

View File

@ -28,6 +28,7 @@
</j-form-item> </j-form-item>
</j-col> </j-col>
<j-col <j-col
class="inputs"
:span=" :span="
modelRef.messageType === 'READ_PROPERTY' || modelRef.messageType === 'READ_PROPERTY' ||
actionType === 'latestData' actionType === 'latestData'
@ -68,6 +69,7 @@
</j-col> </j-col>
<j-col <j-col
:span="12" :span="12"
class="inputs"
v-if=" v-if="
modelRef.messageType === 'WRITE_PROPERTY' && modelRef.messageType === 'WRITE_PROPERTY' &&
actionType === 'command' actionType === 'command'
@ -84,11 +86,11 @@
<ValueItem <ValueItem
v-model:modelValue="modelRef.message.value" v-model:modelValue="modelRef.message.value"
:itemType=" :itemType="
property.type || property.valueType?.type || 'int' property.valueType?.type || property.type || 'int'
" "
:options=" :options="
property.valueType?.type === 'enum' property.valueType?.type === 'enum'
? (property?.dataType?.elements || []).map( ? (property?.valueType?.elements || []).map(
(item) => { (item) => {
return { return {
label: item?.text, label: item?.text,
@ -190,9 +192,21 @@ const modelRef = reactive({
properties: undefined, properties: undefined,
functionId: undefined, functionId: undefined,
inputs: [], inputs: [],
value: undefined
}, },
}); });
const property = ref<any>({});
const onPropertyChange = (val: string) => {
if (val) {
const _item = props.metadata?.properties.find(
(item: any) => item.id === val,
);
property.value = _item || {};
}
};
watch( watch(
() => props.modelValue, () => props.modelValue,
(newVal) => { (newVal) => {
@ -208,8 +222,6 @@ watch(
}, },
); );
const property = ref<any>({});
const funcChange = (val: string) => { const funcChange = (val: string) => {
if (val) { if (val) {
const arr = const arr =
@ -227,15 +239,6 @@ const funcChange = (val: string) => {
} }
}; };
const onPropertyChange = (val: string) => {
if (val) {
const _item = props.metadata?.properties.find(
(item: any) => item.id === val,
);
property.value = _item?.[0] || {};
}
};
const saveBtn = () => const saveBtn = () =>
new Promise((resolve) => { new Promise((resolve) => {
formRef.value formRef.value

View File

@ -571,6 +571,7 @@ const getTypes = async () => {
}; };
const getDuerOSProperties = (val: string) => { const getDuerOSProperties = (val: string) => {
console.log(val)
const arr = modelRef.propertyMappings.map((item) => item?.source) || []; const arr = modelRef.propertyMappings.map((item) => item?.source) || [];
const checked = _.cloneDeep(arr); const checked = _.cloneDeep(arr);
const _index = checked.findIndex((i) => i === val); const _index = checked.findIndex((i) => i === val);
@ -672,6 +673,7 @@ watch(
_data.applianceType = _data?.applianceType?.value; _data.applianceType = _data?.applianceType?.value;
} }
Object.assign(modelRef, _data); Object.assign(modelRef, _data);
console.log(modelRef.propertyMappings)
} }
}, },
{ immediate: true, deep: true }, { immediate: true, deep: true },

View File

@ -47,7 +47,7 @@
</template> </template>
<script setup lang="ts" name="modifyModal"> <script setup lang="ts" name="modifyModal">
import { PropType } from 'vue'; import { PropType } from 'vue';
import { Form, message } from 'ant-design-vue'; import { Form, message } from 'jetlinks-ui-components';
import { queryTree, saveTree, updateTree } from '@/api/device/category'; import { queryTree, saveTree, updateTree } from '@/api/device/category';
import { ValidateErrorEntity } from 'ant-design-vue/es/form/interface'; import { ValidateErrorEntity } from 'ant-design-vue/es/form/interface';
import { list } from '@/api/iot-card/home'; import { list } from '@/api/iot-card/home';

View File

@ -75,8 +75,7 @@
import { queryTree, deleteTree } from '@/api/device/category'; import { queryTree, deleteTree } from '@/api/device/category';
import type { ActionsType } from '@/components/Table/index.vue'; import type { ActionsType } from '@/components/Table/index.vue';
import ModifyModal from './components/modifyModal/index.vue'; import ModifyModal from './components/modifyModal/index.vue';
import type { TableColumnType, TableProps } from 'ant-design-vue'; import { message } from 'jetlinks-ui-components';
import { message } from 'ant-design-vue';
const expandedRowKeys = ref<any>([]); const expandedRowKeys = ref<any>([]);
const tableRef = ref<Record<string, any>>({}); const tableRef = ref<Record<string, any>>({});
const modifyRef = ref(); const modifyRef = ref();

View File

@ -119,6 +119,7 @@ const Status = defineComponent({
<span> <span>
<PermissionButton <PermissionButton
type="link" type="link"
style="padding: 0"
hasPermission="link/Type:action" hasPermission="link/Type:action"
popConfirm={{ popConfirm={{
title: '确认启用', title: '确认启用',
@ -288,6 +289,8 @@ const Status = defineComponent({
text={<span> text={<span>
<PermissionButton <PermissionButton
hasPermission="link/Type:action" hasPermission="link/Type:action"
type="link"
style="padding: 0"
popConfirm={{ popConfirm={{
title: '确认启用', title: '确认启用',
onConfirm: async () => { onConfirm: async () => {
@ -416,6 +419,8 @@ const Status = defineComponent({
<PermissionButton <PermissionButton
hasPermission="link/AccessConfig:action" hasPermission="link/AccessConfig:action"
type="link"
style="padding: 0"
popConfirm={{ popConfirm={{
title: '确认启用', title: '确认启用',
onConfirm: async () => { onConfirm: async () => {
@ -528,6 +533,8 @@ const Status = defineComponent({
<PermissionButton <PermissionButton
hasPermission="device/Product:action" hasPermission="device/Product:action"
type="link"
style="padding: 0"
popConfirm={{ popConfirm={{
title: '确认启用', title: '确认启用',
onConfirm: async () => { onConfirm: async () => {
@ -636,6 +643,8 @@ const Status = defineComponent({
<PermissionButton <PermissionButton
hasPermission="device/Product:action" hasPermission="device/Product:action"
type="link"
style="padding: 0"
popConfirm={{ popConfirm={{
title: '确认启用', title: '确认启用',
onConfirm: async () => { onConfirm: async () => {
@ -712,6 +721,8 @@ const Status = defineComponent({
<PermissionButton <PermissionButton
hasPermission="device/Instance:action" hasPermission="device/Instance:action"
type="link"
style="padding: 0"
popConfirm={{ popConfirm={{
title: '确认启用', title: '确认启用',
onConfirm: async () => { onConfirm: async () => {
@ -1720,6 +1731,8 @@ const Status = defineComponent({
<PermissionButton <PermissionButton
hasPermission="device/Product:action" hasPermission="device/Product:action"
type="link"
style="padding: 0"
popConfirm={{ popConfirm={{
title: '确认启用', title: '确认启用',
onConfirm: async () => { onConfirm: async () => {

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="wrapper"> <div class="advance-wrapper">
<j-tabs v-model="activeKey" tab-position="left"> <j-tabs v-model="activeKey" tab-position="left">
<j-tab-pane <j-tab-pane
v-for="func in newFunctions" v-for="func in newFunctions"
@ -129,7 +129,7 @@ const handleClear = (func: any) => {
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
.wrapper { .advance-wrapper {
.editor-btn { .editor-btn {
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="wrapper"> <div class="simple-wrapper">
<div class="tips"> <div class="tips">
<j-space> <j-space>
<AIcon type="QuestionCircleOutlined" /> <AIcon type="QuestionCircleOutlined" />
@ -239,7 +239,7 @@ const handleClear = (func: any) => {
:deep(.ant-form-item-with-help .ant-form-item-explain) { :deep(.ant-form-item-with-help .ant-form-item-explain) {
min-height: 0; min-height: 0;
} }
.wrapper { .simple-wrapper {
.tips { .tips {
margin-bottom: 10px; margin-bottom: 10px;
} }

View File

@ -13,16 +13,16 @@
</PermissionButton> </PermissionButton>
</template> </template>
<j-descriptions-item label="设备ID">{{ <j-descriptions-item label="设备ID">{{
instanceStore.current.id instanceStore.current?.id
}}</j-descriptions-item> }}</j-descriptions-item>
<j-descriptions-item label="产品名称">{{ <j-descriptions-item label="产品名称">{{
instanceStore.current.productName instanceStore.current?.productName
}}</j-descriptions-item> }}</j-descriptions-item>
<j-descriptions-item label="产品分类">{{ <j-descriptions-item label="产品分类">{{
instanceStore.current.classifiedName instanceStore.current?.classifiedName
}}</j-descriptions-item> }}</j-descriptions-item>
<j-descriptions-item label="设备类型">{{ <j-descriptions-item label="设备类型">{{
instanceStore.current.deviceType?.text instanceStore.current?.deviceType?.text
}}</j-descriptions-item> }}</j-descriptions-item>
<j-descriptions-item label="固件版本">{{ <j-descriptions-item label="固件版本">{{
instanceStore.current?.firmwareInfo?.version instanceStore.current?.firmwareInfo?.version
@ -31,25 +31,25 @@
instanceStore.current?.transport instanceStore.current?.transport
}}</j-descriptions-item> }}</j-descriptions-item>
<j-descriptions-item label="消息协议">{{ <j-descriptions-item label="消息协议">{{
instanceStore.current.protocolName instanceStore.current?.protocolName
}}</j-descriptions-item> }}</j-descriptions-item>
<j-descriptions-item label="创建时间">{{ <j-descriptions-item label="创建时间">{{
instanceStore.current.createTime instanceStore.current?.createTime
? moment(instanceStore.current.createTime).format( ? moment(instanceStore.current?.createTime).format(
'YYYY-MM-DD HH:mm:ss', 'YYYY-MM-DD HH:mm:ss',
) )
: '' : ''
}}</j-descriptions-item> }}</j-descriptions-item>
<j-descriptions-item label="注册时间">{{ <j-descriptions-item label="注册时间">{{
instanceStore.current.registerTime instanceStore.current?.registerTime
? moment(instanceStore.current.registerTime).format( ? moment(instanceStore.current?.registerTime).format(
'YYYY-MM-DD HH:mm:ss', 'YYYY-MM-DD HH:mm:ss',
) )
: '' : ''
}}</j-descriptions-item> }}</j-descriptions-item>
<j-descriptions-item label="最后上线时间">{{ <j-descriptions-item label="最后上线时间">{{
instanceStore.current.onlineTime instanceStore.current?.onlineTime
? moment(instanceStore.current.onlineTime).format( ? moment(instanceStore.current?.onlineTime).format(
'YYYY-MM-DD HH:mm:ss', 'YYYY-MM-DD HH:mm:ss',
) )
: '' : ''
@ -57,12 +57,12 @@
<j-descriptions-item <j-descriptions-item
label="父设备" label="父设备"
v-if=" v-if="
instanceStore.current.deviceType?.value === 'childrenDevice' instanceStore.current?.deviceType?.value === 'childrenDevice'
" "
>{{ instanceStore.current.parentId }}</j-descriptions-item >{{ instanceStore.current?.parentId }}</j-descriptions-item
> >
<j-descriptions-item label="说明">{{ <j-descriptions-item label="说明">{{
instanceStore.current.description instanceStore.current?.description
}}</j-descriptions-item> }}</j-descriptions-item>
</j-descriptions> </j-descriptions>
<Config /> <Config />

View File

@ -3,7 +3,11 @@
<!-- <j-spin :spinning="loading"> --> <!-- <j-spin :spinning="loading"> -->
<div class="card-container"> <div class="card-container">
<div class="header"> <div class="header">
<div class="title">{{ _props.data.name }}</div> <div class="title">
<Ellipsis style="width: 100%;">
{{ _props.data.name }}
</Ellipsis>
</div>
<div class="extra"> <div class="extra">
<j-space :size="16"> <j-space :size="16">
<template v-for="i in actions" :key="i.key"> <template v-for="i in actions" :key="i.key">
@ -97,12 +101,9 @@ const _props = defineProps({
.title { .title {
width: 60%; width: 60%;
margin-right: 10px; margin-right: 10px;
overflow: hidden;
color: rgba(0, 0, 0, 0.65); color: rgba(0, 0, 0, 0.65);
font-weight: 400; font-weight: 400;
font-size: 12px; font-size: 12px;
white-space: nowrap;
text-overflow: ellipsis;
} }
} }

View File

@ -1,16 +1,15 @@
<template> <template>
<page-container <page-container
:tabList="list" :tabList="list"
@back="onBack"
:tabActiveKey="instanceStore.tabActiveKey" :tabActiveKey="instanceStore.tabActiveKey"
@tabChange="onTabChange" @tabChange="onTabChange"
> >
<template #title> <template #title>
<div> <div>
<div style="display: flex; align-items: center"> <div style="display: flex; align-items: center">
<j-button @click="onBack" size="small">返回</j-button> <!-- <j-button @click="onBack" size="small">返回</j-button> -->
<div style="margin-left: 20px; font-size: 24px"> <div style="font-size: 24px">
{{ instanceStore.current.name }} {{ instanceStore.current?.name }}
</div> </div>
<j-divider type="vertical" /> <j-divider type="vertical" />
<j-space> <j-space>
@ -21,15 +20,15 @@
<j-badge <j-badge
:status=" :status="
statusMap.get( statusMap.get(
instanceStore.current.state?.value, instanceStore.current?.state?.value,
) )
" "
/> />
{{ instanceStore.current.state?.text }} {{ instanceStore.current?.state?.text }}
</span> </span>
<PermissionButton <PermissionButton
v-if=" v-if="
instanceStore.current.state?.value === instanceStore.current?.state?.value ===
'notActive' 'notActive'
" "
type="link" type="link"
@ -44,7 +43,7 @@
</PermissionButton> </PermissionButton>
<PermissionButton <PermissionButton
v-if=" v-if="
instanceStore.current.state?.value === 'online' instanceStore.current?.state?.value === 'online'
" "
type="link" type="link"
style="margin-top: -5px; padding: 0 20px" style="margin-top: -5px; padding: 0 20px"
@ -65,7 +64,7 @@
" "
:title=" :title="
instanceStore.current?.features?.find( instanceStore.current?.features?.find(
(item) => item.id === 'selfManageState', (item) => item?.id === 'selfManageState',
) )
? '该设备的在线状态与父设备(网关设备)保持一致' ? '该设备的在线状态与父设备(网关设备)保持一致'
: '该设备在线状态由设备自身运行状态决定,不继承父设备(网关设备)的在线状态' : '该设备在线状态由设备自身运行状态决定,不继承父设备(网关设备)的在线状态'
@ -81,7 +80,7 @@
<div style="padding-top: 24px"> <div style="padding-top: 24px">
<j-descriptions size="small" :column="4"> <j-descriptions size="small" :column="4">
<j-descriptions-item label="ID">{{ <j-descriptions-item label="ID">{{
instanceStore.current.id instanceStore.current?.id
}}</j-descriptions-item> }}</j-descriptions-item>
<j-descriptions-item label="所属产品"> <j-descriptions-item label="所属产品">
<PermissionButton <PermissionButton
@ -90,7 +89,7 @@
@click="jumpProduct" @click="jumpProduct"
hasPermission="device/Product:view" hasPermission="device/Product:view"
> >
{{ instanceStore.current.productName }} {{ instanceStore.current?.productName }}
</PermissionButton> </PermissionButton>
</j-descriptions-item> </j-descriptions-item>
</j-descriptions> </j-descriptions>
@ -193,11 +192,12 @@ const getStatus = (id: string) => {
}; };
watch( watch(
() => route.params.id, () => route.params?.id,
(newId) => { (newId) => {
if (newId) { if (newId) {
instanceStore.refresh(String(newId)); instanceStore.refresh(String(newId));
getStatus(String(newId)); getStatus(String(newId));
instanceStore.tabActiveKey = 'Info'
} }
}, },
{ immediate: true, deep: true }, { immediate: true, deep: true },
@ -207,52 +207,52 @@ onMounted(() => {
instanceStore.tabActiveKey = history.state?.params?.tab || 'Info'; instanceStore.tabActiveKey = history.state?.params?.tab || 'Info';
}); });
const onBack = () => { // const onBack = () => {
menuStory.jumpPage('device/Instance'); // menuStory.jumpPage('device/Instance');
}; // };
const onTabChange = (e: string) => { const onTabChange = (e: string) => {
instanceStore.tabActiveKey = e; instanceStore.tabActiveKey = e;
}; };
const handleAction = async () => { const handleAction = async () => {
if (instanceStore.current.id) { if (instanceStore.current?.id) {
const resp = await _deploy(instanceStore.current.id); const resp = await _deploy(instanceStore.current?.id);
if (resp.status === 200) { if (resp.status === 200) {
message.success('操作成功!'); message.success('操作成功!');
instanceStore.refresh(instanceStore.current.id); instanceStore.refresh(instanceStore.current?.id);
} }
} }
}; };
const handleDisconnect = async () => { const handleDisconnect = async () => {
if (instanceStore.current.id) { if (instanceStore.current?.id) {
const resp = await _disconnect(instanceStore.current.id); const resp = await _disconnect(instanceStore.current?.id);
if (resp.status === 200) { if (resp.status === 200) {
message.success('操作成功!'); message.success('操作成功!');
instanceStore.refresh(instanceStore.current.id); instanceStore.refresh(instanceStore.current?.id);
} }
} }
}; };
const handleRefresh = async () => { const handleRefresh = async () => {
if (instanceStore.current.id) { if (instanceStore.current?.id) {
await instanceStore.refresh(instanceStore.current.id); await instanceStore.refresh(instanceStore.current?.id);
message.success('操作成功'); message.success('操作成功');
} }
}; };
const jumpProduct = () => { const jumpProduct = () => {
menuStory.jumpPage('device/Product/Detail', { menuStory.jumpPage('device/Product/Detail', {
id: instanceStore.current.productId, id: instanceStore.current?.productId,
}); });
}; };
watchEffect(() => { watchEffect(() => {
const keys = list.value.map((i) => i.key); const keys = list.value.map((i) => i.key);
if ( if (
instanceStore.current.protocol && instanceStore.current?.protocol &&
!['modbus-tcp', 'opc-ua'].includes(instanceStore.current.protocol) && !['modbus-tcp', 'opc-ua'].includes(instanceStore.current?.protocol) &&
!keys.includes('Diagnose') !keys.includes('Diagnose')
) { ) {
list.value.push({ list.value.push({
@ -261,8 +261,8 @@ watchEffect(() => {
}); });
} }
if ( if (
instanceStore.current.features?.find( instanceStore.current?.features?.find(
(item: any) => item.id === 'transparentCodec', (item: any) => item?.id === 'transparentCodec',
) && ) &&
!keys.includes('Parsing') !keys.includes('Parsing')
) { ) {
@ -272,7 +272,7 @@ watchEffect(() => {
}); });
} }
if ( if (
instanceStore.current.protocol === 'modbus-tcp' && instanceStore.current?.protocol === 'modbus-tcp' &&
!keys.includes('Modbus') !keys.includes('Modbus')
) { ) {
list.value.push({ list.value.push({
@ -281,7 +281,7 @@ watchEffect(() => {
}); });
} }
if ( if (
instanceStore.current.protocol === 'opc-ua' && instanceStore.current?.protocol === 'opc-ua' &&
!keys.includes('OPCUA') !keys.includes('OPCUA')
) { ) {
list.value.push({ list.value.push({
@ -290,7 +290,7 @@ watchEffect(() => {
}); });
} }
if ( if (
instanceStore.current.deviceType?.value === 'gateway' && instanceStore.current?.deviceType?.value === 'gateway' &&
!keys.includes('ChildDevice') !keys.includes('ChildDevice')
) { ) {
// //
@ -300,8 +300,8 @@ watchEffect(() => {
}); });
} }
if ( if (
instanceStore.current.accessProvider === 'edge-child-device' && instanceStore.current?.accessProvider === 'edge-child-device' &&
instanceStore.current.parentId && instanceStore.current?.parentId &&
!keys.includes('EdgeMap') !keys.includes('EdgeMap')
) { ) {
list.value.push({ list.value.push({

View File

@ -335,6 +335,7 @@ const columns = [
key: 'productName', key: 'productName',
search: { search: {
type: 'select', type: 'select',
rename: 'productId',
options: () => options: () =>
new Promise((resolve) => { new Promise((resolve) => {
queryNoPagingPost({ paging: false }).then((resp: any) => { queryNoPagingPost({ paging: false }).then((resp: any) => {

View File

@ -22,7 +22,7 @@
<!-- 勾选 --> <!-- 勾选 -->
<div v-if="active" class="checked-icon"> <div v-if="active" class="checked-icon">
<div> <div>
<CheckOutlined /> <AIcon type="CheckOutlined"></AIcon>
</div> </div>
</div> </div>
</div> </div>
@ -31,11 +31,6 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import {
SearchOutlined,
CheckOutlined,
DeleteOutlined,
} from '@ant-design/icons-vue';
import { StatusColorEnum } from '@/utils/consts.ts'; import { StatusColorEnum } from '@/utils/consts.ts';
import type { ActionsType } from '@/components/Table/index.vue'; import type { ActionsType } from '@/components/Table/index.vue';
import { PropType } from 'vue'; import { PropType } from 'vue';

View File

@ -6,7 +6,7 @@
<div style="display: flex"> <div style="display: flex">
<h3>配置信息</h3> <h3>配置信息</h3>
<div style="margin: 0 0px 0 15px; color: #1d39c4"> <div style="margin: 0 0px 0 15px; color: #1d39c4">
<edit-outlined @click="editConfig" /> <AIcon type="EditOutlined"/>
</div> </div>
</div> </div>
</template> </template>
@ -53,11 +53,6 @@ import { useProductStore } from '@/store/product';
import Save from '../../Save/index.vue'; import Save from '../../Save/index.vue';
import moment from 'moment'; import moment from 'moment';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import {
EditOutlined,
DeleteOutlined,
PlusOutlined,
} from '@ant-design/icons-vue';
const productStore = useProductStore(); const productStore = useProductStore();
const route = useRoute(); const route = useRoute();
const saveRef = ref(); const saveRef = ref();

View File

@ -281,7 +281,7 @@
:columns="query.columns" :columns="query.columns"
target="deviceModal" target="deviceModal"
@search="search" @search="search"
type='simple' type="simple"
/> />
<JProTable <JProTable
:columns="query.columns" :columns="query.columns"
@ -370,11 +370,11 @@
<script lang="ts" setup> <script lang="ts" setup>
import { useProductStore } from '@/store/product'; import { useProductStore } from '@/store/product';
import { ConfigMetadata } from '@/views/device/Product/typings'; import { ConfigMetadata } from '@/views/device/Product/typings';
import { Empty, FormItem, message } from 'ant-design-vue'; import { Empty, message } from 'jetlinks-ui-components';
import { getImage } from '@/utils/comm'; import { getImage } from '@/utils/comm';
import Title from '../Title/index.vue'; import Title from '../Title/index.vue';
import { usePermissionStore } from '@/store/permission'; import { usePermissionStore } from '@/store/permission';
import { steps, steps1 } from './util' import { steps, steps1 } from './util';
import './index.less'; import './index.less';
import { import {
getProviders, getProviders,
@ -396,7 +396,7 @@ const productStore = useProductStore();
import Driver from 'driver.js'; import Driver from 'driver.js';
import 'driver.js/dist/driver.min.css'; import 'driver.js/dist/driver.min.css';
import { marked } from 'marked'; import { marked } from 'marked';
import type { FormInstance, TableColumnType } from 'ant-design-vue'; import type { TableColumnType } from 'ant-design-vue';
import { useMenuStore } from '@/store/menu'; import { useMenuStore } from '@/store/menu';
const formRef = ref(); const formRef = ref();
const menuStore = useMenuStore(); const menuStore = useMenuStore();
@ -590,7 +590,7 @@ const search = (e: any) => {
}; };
}; };
const stepsRef = reactive({current:0}) const stepsRef = reactive({ current: 0 });
/** /**
* 保存引导页数据 * 保存引导页数据
@ -615,10 +615,10 @@ const driver = new Driver({
}, },
onReset: () => { onReset: () => {
if (stepsRef.current !== 3) { if (stepsRef.current !== 3) {
guide({ guide({
name: 'guide', name: 'guide',
content: 'skip', content: 'skip',
}); });
} }
stepsRef.current = 0; stepsRef.current = 0;
}, },
@ -638,10 +638,10 @@ const driver1 = new Driver({
}, },
onReset: () => { onReset: () => {
if (stepsRef.current !== 4) { if (stepsRef.current !== 4) {
guide({ guide({
name: 'guide', name: 'guide',
content: 'skip', content: 'skip',
}); });
} }
stepsRef.current = 0; stepsRef.current = 0;
}, },
@ -808,44 +808,42 @@ const getConfigDetail = (
messageProtocol: string, messageProtocol: string,
transportProtocol: string, transportProtocol: string,
) => { ) => {
getConfigView(messageProtocol, transportProtocol).then( getConfigView(messageProtocol, transportProtocol).then((resp) => {
(resp) => { if (resp.status === 200) {
if (resp.status === 200) { config.value = resp.result;
config.value = resp.result; const Group = {
const Group = { title: '分组',
title: '分组', dataIndex: 'group',
dataIndex: 'group', key: 'group',
key: 'group', ellipsis: true,
ellipsis: true, align: 'center',
align: 'center', width: 100,
width: 100, customCell: (record: any, rowIndex: number) => {
customCell: (record: any, rowIndex: number) => { const obj = {
const obj = { children: record,
children: record, rowSpan: 0,
rowSpan: 0, };
}; const list = config.value?.routes || [];
const list = config.value?.routes || [];
const arr = list.filter( const arr = list.filter(
(res: any) => res.group === record.group, (res: any) => res.group === record.group,
); );
const isRowIndex = const isRowIndex =
rowIndex === 0 || rowIndex === 0 ||
list[rowIndex - 1].group !== record.group; list[rowIndex - 1].group !== record.group;
isRowIndex && (obj.rowSpan = arr.length); isRowIndex && (obj.rowSpan = arr.length);
return obj; return obj;
}, },
}; };
columnsMQTT.value = [Group, ...ColumnsMQTT]; columnsMQTT.value = [Group, ...ColumnsMQTT];
columnsHTTP.value = [Group, ...ColumnsHTTP]; columnsHTTP.value = [Group, ...ColumnsHTTP];
if (config.value?.document) { if (config.value?.document) {
markdownToHtml.value = marked(config.value.document); markdownToHtml.value = marked(config.value.document);
}
} }
}, }
); });
}; };
/** /**
@ -904,7 +902,6 @@ const submitData = async () => {
? await updateDevice(obj) ? await updateDevice(obj)
: await saveDevice(obj); : await saveDevice(obj);
if (resp.status === 200) { if (resp.status === 200) {
detail(productStore.current?.id || '').then((res) => { detail(productStore.current?.id || '').then((res) => {
if (res.status === 200) { if (res.status === 200) {
productStore.current = { ...res.result }; productStore.current = { ...res.result };
@ -913,9 +910,8 @@ const submitData = async () => {
} }
visible.value = false; visible.value = false;
queryParams.value = {}; queryParams.value = {};
}); });
getData(obj.accessId); getData(obj.accessId);
} }
} else { } else {
message.error('请选择接入方式'); message.error('请选择接入方式');
@ -935,47 +931,51 @@ const modifyArray = (oldData: any[], newData: any[]) => {
* *
*/ */
const getGuide = async (isDriver1: boolean = false) => { const getGuide = async (isDriver1: boolean = false) => {
const res: any = await productGuide(); const res: any = await productGuide();
if (res.result && res.result?.content === 'skip') { if (res.result && res.result?.content === 'skip') {
return; return;
} else {
if (isDriver1) {
driver1.defineSteps(steps1);
driver1.start();
} else { } else {
driver.defineSteps(steps); if (isDriver1) {
driver.start(); driver1.defineSteps(steps1);
driver1.start();
} else {
driver.defineSteps(steps);
driver.start();
}
} }
} };
}
/** /**
* 查询保存数据信息 * 查询保存数据信息
*/ */
const getData = async (accessId?: string) => { const getData = async (accessId?: string) => {
const _accessId = accessId || productStore.current?.accessId const _accessId = accessId || productStore.current?.accessId;
if (productStore.current?.id) { if (productStore.current?.id) {
getConfigMetadata(productStore.current?.id).then((resp: any) => { getConfigMetadata(productStore.current?.id).then((resp: any) => {
metadata.value = resp?.result[0] as ConfigMetadata || { properties: [] }; metadata.value = (resp?.result[0] as ConfigMetadata) || {
if (accessId) { // properties: [],
getGuide(!resp?.result.length) // };
} if (accessId) {
}); //
getGuide(resp?.result.length); //
}
});
} }
if (_accessId) { // if (_accessId) {
// const metadataResp = await getConfigMetadata(productStore.current!.id) //
// if (metadataResp.success) { // const metadataResp = await getConfigMetadata(productStore.current!.id)
// metadata.value = (metadataResp.result?.[0] as ConfigMetadata[]) || []; // if (metadataResp.success) {
// } // metadata.value = (metadataResp.result?.[0] as ConfigMetadata[]) || [];
queryAccessDetail(_accessId); // }
getConfigDetail( queryAccessDetail(_accessId);
productStore.current?.messageProtocol || '', getConfigDetail(
productStore.current?.transportProtocol || '', productStore.current?.messageProtocol || '',
); productStore.current?.transportProtocol || '',
getProviders().then((resp) => { );
if (resp.status === 200) { getProviders().then((resp) => {
dataSource.value = resp.result; if (resp.status === 200) {
} dataSource.value = resp.result;
}); }
});
} }
// else { // else {
// if (productStore.current?.id) { // if (productStore.current?.id) {
@ -1009,7 +1009,7 @@ const submitDevice = async () => {
}); });
if (resp.status === 200) { if (resp.status === 200) {
message.success('操作成功!'); message.success('操作成功!');
productStore.current!.storePolicy = storePolicy productStore.current!.storePolicy = storePolicy;
if ((window as any).onTabSaveSuccess) { if ((window as any).onTabSaveSuccess) {
if (resp.result) { if (resp.result) {
(window as any).onTabSaveSuccess(resp); (window as any).onTabSaveSuccess(resp);
@ -1041,14 +1041,14 @@ const add = () => {
* 初始化 * 初始化
*/ */
watchEffect(() => { watchEffect(() => {
if (productStore.current?.storePolicy) { if (productStore.current?.storePolicy) {
form.storePolicy = productStore.current!.storePolicy form.storePolicy = productStore.current!.storePolicy;
} }
}) });
nextTick(() => { nextTick(() => {
getData(); getData();
}) });
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
:deep( :deep(

View File

@ -1,9 +1,9 @@
//引导页数据 //引导页数据
export const steps = [ export const steps = [
{ {
element: '#rc-tabs-0-tab-Metadata', element: '.objectModel',
popover: { popover: {
id: 'driver', className: 'driver',
title: `<div id='title'>配置物模型</div><div id='guide'>1/3</div>`, title: `<div id='title'>配置物模型</div><div id='guide'>1/3</div>`,
description: `配置产品物模型,实现设备在云端的功能描述。`, description: `配置产品物模型,实现设备在云端的功能描述。`,
position: 'bottom', position: 'bottom',
@ -40,7 +40,7 @@ export const steps1 = [
}, },
}, },
{ {
element: '#rc-tabs-0-tab-Metadata', element: '.objectModel',
popover: { popover: {
className: 'driver', className: 'driver',
title: `<div id='title'>配置物模型</div><div id='guide'>2/4</div>`, title: `<div id='title'>配置物模型</div><div id='guide'>2/4</div>`,

View File

@ -112,7 +112,7 @@ import {
getDeviceNumber, getDeviceNumber,
getProtocolDetail, getProtocolDetail,
} from '@/api/device/product'; } from '@/api/device/product';
import { message } from 'ant-design-vue'; import { message } from 'jetlinks-ui-components';
import { getImage } from '@/utils/comm'; import { getImage } from '@/utils/comm';
import encodeQuery from '@/utils/encodeQuery'; import encodeQuery from '@/utils/encodeQuery';
import { useMenuStore } from '@/store/menu'; import { useMenuStore } from '@/store/menu';
@ -141,6 +141,7 @@ const list = ref([
{ {
key: 'Metadata', key: 'Metadata',
tab: '物模型', tab: '物模型',
class:'objectModel'
}, },
{ {
key: 'Device', key: 'Device',

View File

@ -17,13 +17,22 @@
<div class="product-tips"> <div class="product-tips">
<div style="display: flex"> <div style="display: flex">
<div class="product-icon"> <div class="product-icon">
<check-circle-outlined class="icon-style" /> <AIcon
type="CheckCircleOutlined"
class="icon-style"
></AIcon>
</div> </div>
<div class="product-title">产品创建成功</div> <div class="product-title">产品创建成功</div>
</div> </div>
<div style="display: flex"> <div style="display: flex">
<div class="product-id">产品ID: {{ idValue }}</div> <div class="product-id">产品ID: {{ idValue }}</div>
<div class="product-btn" @click="showDetail" style="cursor: pointer;">查看详情</div> <div
class="product-btn"
@click="showDetail"
style="cursor: pointer"
>
查看详情
</div>
</div> </div>
<div>接下来推荐操作:</div> <div>接下来推荐操作:</div>
<div class="product-main">1配置产品接入方式</div> <div class="product-main">1配置产品接入方式</div>
@ -48,7 +57,6 @@
<script lang="ts" setup name="DialogTips"> <script lang="ts" setup name="DialogTips">
import { getImage } from '@/utils/comm.ts'; import { getImage } from '@/utils/comm.ts';
import { useProductStore } from '@/store/product'; import { useProductStore } from '@/store/product';
import { CheckCircleOutlined } from '@ant-design/icons-vue';
import { useMenuStore } from '@/store/menu'; import { useMenuStore } from '@/store/menu';
const visible = ref<boolean>(false); const visible = ref<boolean>(false);
const productStore = useProductStore(); const productStore = useProductStore();
@ -72,12 +80,11 @@ const show = (id: string) => {
* 查看详情 * 查看详情
*/ */
const showDetail = () => { const showDetail = () => {
menuStore.jumpPage('device/Product/Detail',{id:idValue.value}) menuStore.jumpPage('device/Product/Detail', { id: idValue.value });
}; };
defineExpose({ defineExpose({
show: show, show: show,
}); });
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
.product-tips { .product-tips {

View File

@ -147,7 +147,7 @@
import { category } from '@/api/device/product'; import { category } from '@/api/device/product';
import { Form } from 'ant-design-vue'; import { Form } from 'ant-design-vue';
import { getImage } from '@/utils/comm.ts'; import { getImage } from '@/utils/comm.ts';
import { message } from 'ant-design-vue'; import { message } from 'jetlinks-ui-components';
import DialogTips from '../DialogTips/index.vue'; import DialogTips from '../DialogTips/index.vue';
import { useProductStore } from '@/store/product'; import { useProductStore } from '@/store/product';
import { filterTreeSelectNode, filterSelectNode } from '@/utils/comm'; import { filterTreeSelectNode, filterSelectNode } from '@/utils/comm';

View File

@ -160,12 +160,7 @@
import server from '@/utils/request'; import server from '@/utils/request';
import type { ActionsType } from '@/components/Table/index.vue'; import type { ActionsType } from '@/components/Table/index.vue';
import { getImage } from '@/utils/comm'; import { getImage } from '@/utils/comm';
import { import { message } from 'jetlinks-ui-components';
EditOutlined,
DeleteOutlined,
PlusOutlined,
} from '@ant-design/icons-vue';
import { message } from 'ant-design-vue';
import { import {
getProviders, getProviders,
category, category,

View File

@ -7,13 +7,13 @@
message: 'ID只能由数字、字母、下划线、中划线组成', message: 'ID只能由数字、字母、下划线、中划线组成',
}, },
]"> ]">
<j-input v-model:value="value.id" size="small" @change="asyncOtherConfig" :disabled="metadataStore.model.action === 'edit'"></j-input> <j-input v-model:value="value.id" size="small" @change="asyncOtherConfig" :disabled="metadataStore.model.action === 'edit'" placeholder="请输入标识"></j-input>
</j-form-item> </j-form-item>
<j-form-item label="名称" name="name" :rules="[ <j-form-item label="名称" name="name" :rules="[
{ required: true, message: '请输入名称' }, { required: true, message: '请输入名称' },
{ max: 64, message: '最多可输入64个字符' }, { max: 64, message: '最多可输入64个字符' },
]"> ]">
<j-input v-model:value="value.name" size="small"></j-input> <j-input v-model:value="value.name" size="small" placeholder="请输入名称"></j-input>
</j-form-item> </j-form-item>
<template v-if="modelType === 'properties'"> <template v-if="modelType === 'properties'">
<value-type-form :name="['valueType']" v-model:value="value.valueType" key="property" title="数据类型" <value-type-form :name="['valueType']" v-model:value="value.valueType" key="property" title="数据类型"
@ -33,7 +33,7 @@
<j-form-item label="输入参数" name="inputs" :rules="[ <j-form-item label="输入参数" name="inputs" :rules="[
{ validator: (_rule: Rule, val: Record<any, any>[]) => validateJson(_rule, val, '输入参数', false) }, { validator: (_rule: Rule, val: Record<any, any>[]) => validateJson(_rule, val, '输入参数', false) },
]"> ]">
<JsonParam v-model:value="value.inputs" :name="['inputs']"></JsonParam> <JsonParam v-model:value="value.inputs" :name="['inputs']" :is-sub="false"></JsonParam>
</j-form-item> </j-form-item>
<value-type-form :name="['output']" v-model:value="value.output" key="function" title="输出参数" :required="false"></value-type-form> <value-type-form :name="['output']" v-model:value="value.output" key="function" title="输出参数" :required="false"></value-type-form>
</template> </template>
@ -41,7 +41,7 @@
<j-form-item label="级别" :name="['expands', 'level']" :rules="[ <j-form-item label="级别" :name="['expands', 'level']" :rules="[
{ required: true, message: '请选择级别' }, { required: true, message: '请选择级别' },
]"> ]">
<j-select v-model:value="value.expands.level" :options="EventLevel" size="small"></j-select> <j-select v-model:value="value.expands.level" :options="EventLevel" size="small" placeholder="请选择级别"></j-select>
</j-form-item> </j-form-item>
<value-type-form :name="['valueType']" v-model:value="value.valueType" key="function" title="输出参数" only-object></value-type-form> <value-type-form :name="['valueType']" v-model:value="value.valueType" key="function" title="输出参数" only-object></value-type-form>
</template> </template>
@ -50,13 +50,13 @@
<j-form-item label="标签类型" :name="['expands', 'type']" :rules="[ <j-form-item label="标签类型" :name="['expands', 'type']" :rules="[
{ required: true, message: '请选择标签类型' }, { required: true, message: '请选择标签类型' },
]"> ]">
<j-select v-model:value="value.expands.type" :options="ExpandsTypeList" mode="multiple" size="small"></j-select> <j-select v-model:value="value.expands.type" :options="ExpandsTypeList" mode="multiple" size="small" placeholder="请选择标签类型"></j-select>
</j-form-item> </j-form-item>
</template> </template>
<j-form-item label="说明" name="description" :rules="[ <j-form-item label="说明" name="description" :rules="[
{ max: 200, message: '最多可输入200个字符' }, { max: 200, message: '最多可输入200个字符' },
]"> ]">
<j-textarea v-model:value="value.description" size="small"></j-textarea> <j-textarea v-model:value="value.description" size="small" placeholder="请输入说明"></j-textarea>
</j-form-item> </j-form-item>
</template> </template>
<script setup lang="ts" name="BaseForm"> <script setup lang="ts" name="BaseForm">

View File

@ -19,9 +19,17 @@
</j-form-item> </j-form-item>
<j-form-item <j-form-item
v-if="type === 'product' && ['int', 'float', 'double', 'long', 'date', 'string', 'boolean'].includes(valueType.type)" v-if="type === 'product' && ['int', 'float', 'double', 'long', 'date', 'string', 'boolean'].includes(valueType.type)"
label="指标配置" :name="name.concat(['metrics'])" :rules="[ :name="name.concat(['metrics'])" :rules="[
{ validator: () => validateMetrics(_value.metrics), message: '请输入指标配置' } { validator: () => validateMetrics(_value.metrics), message: '请输入指标配置' }
]"> ]">
<template #label>
<j-space>
指标配置
<j-tooltip title="场景联动页面可引用指标配置作为触发条件">
<AIcon type="QuestionCircleOutlined" style="color: rgb(136, 136, 136); font-size: 12px;"/>
</j-tooltip>
</j-space>
</template>
<metrics-param v-model:value="_value.metrics" :type="valueType.type" :enum="valueType" <metrics-param v-model:value="_value.metrics" :type="valueType.type" :enum="valueType"
:name="name.concat(['metrics'])"></metrics-param> :name="name.concat(['metrics'])"></metrics-param>
</j-form-item> </j-form-item>

View File

@ -4,14 +4,14 @@
]"> ]">
<j-select v-model:value="_value.type" :disabled="onlyObject" <j-select v-model:value="_value.type" :disabled="onlyObject"
:options="onlyObject ? eventDataTypeList : _dataTypeList" size="small" :options="onlyObject ? eventDataTypeList : _dataTypeList" size="small"
@change="changeType"></j-select> @change="changeType" :placeholder="`请选择${title}`"></j-select>
</j-form-item> </j-form-item>
<j-form-item label="单位" :name="name.concat(['unit'])" v-if="['int', 'float', 'long', 'double'].includes(_value.type)"> <j-form-item label="单位" :name="name.concat(['unit'])" v-if="['int', 'float', 'long', 'double'].includes(_value.type)">
<InputSelect v-model:value="_value.unit" :options="unit.unitOptions" size="small"></InputSelect> <InputSelect v-model:value="_value.unit" :options="unit.unitOptions" size="small"></InputSelect>
</j-form-item> </j-form-item>
<j-form-item label="精度" :name="name.concat(['scale'])" v-if="['float', 'double'].includes(_value.type)"> <j-form-item label="精度" :name="name.concat(['scale'])" v-if="['float', 'double'].includes(_value.type)">
<j-input-number v-model:value="_value.scale" size="small" :min="0" :max="2147483647" :precision="0" <j-input-number v-model:value="_value.scale" size="small" :min="0" :max="2147483647" :precision="0"
style="width: 100%"></j-input-number> style="width: 100%" placeholder="请输入精度"></j-input-number>
</j-form-item> </j-form-item>
<j-form-item label="布尔值" name="booleanConfig" v-if="['boolean'].includes(_value.type)"> <j-form-item label="布尔值" name="booleanConfig" v-if="['boolean'].includes(_value.type)">
<BooleanParam :name="name" v-model:value="_value"></BooleanParam> <BooleanParam :name="name" v-model:value="_value"></BooleanParam>
@ -26,12 +26,13 @@
<j-space> <j-space>
最大长度 最大长度
<j-tooltip title="字节"> <j-tooltip title="字节">
<question-circle-outlined style="color: rgb(136, 136, 136); font-size: 12px;" /> <AIcon type="QuestionCircleOutlined" style="color: rgb(136, 136, 136); font-size: 12px;" />
<!-- <question-circle-outlined style="color: rgb(136, 136, 136); font-size: 12px;" /> -->
</j-tooltip> </j-tooltip>
</j-space> </j-space>
</template> </template>
<j-input-number v-model:value="_value.expands.maxLength" size="small" :max="2147483647" :min="1" :precision="0" <j-input-number v-model:value="_value.expands.maxLength" size="small" :max="2147483647" :min="1" :precision="0"
style="width: 100%;"></j-input-number> style="width: 100%;" placeholder="请输入最大长度"></j-input-number>
</j-form-item> </j-form-item>
<j-form-item label="元素配置" :name="name.concat(['elementType'])" v-if="['array'].includes(_value.type)" :rules="[ <j-form-item label="元素配置" :name="name.concat(['elementType'])" v-if="['array'].includes(_value.type)" :rules="[
{ validator: validateArray } { validator: validateArray }
@ -47,7 +48,7 @@
:rules="[ :rules="[
{ required: true, message: '请选择文件类型' }, { required: true, message: '请选择文件类型' },
]"> ]">
<j-select v-model:value="_value.fileType" :options="FileTypeList" size="small"></j-select> <j-select v-model:value="_value.fileType" :options="FileTypeList" size="small" placeholder="请选择文件类型"></j-select>
</j-form-item> </j-form-item>
</template> </template>
<script lang="ts" setup mame="BaseForm"> <script lang="ts" setup mame="BaseForm">
@ -152,6 +153,9 @@ const changeType = (val: SelectValue) => {
if (['float', 'double'].includes(_value.value.type) && _value.value.scale === undefined) { if (['float', 'double'].includes(_value.value.type) && _value.value.scale === undefined) {
_value.value.scale = 2 _value.value.scale = 2
} }
if (['file'].includes(val as string)) {
_value.value.fileType = _value.value.fileType || 'url'
}
emit('changeType', val as string) emit('changeType', val as string)
} }

View File

@ -109,7 +109,7 @@ const save = reactive({
if (props?.type === 'device') { if (props?.type === 'device') {
instanceStore.refresh(id as string) instanceStore.refresh(id as string)
} else { } else {
productStore.refresh(id as string) productStore.getDetail(id as string)
} }
// Store.set(SystemConst.REFRESH_METADATA_TABLE, true); // Store.set(SystemConst.REFRESH_METADATA_TABLE, true);
if (deploy) { if (deploy) {
@ -125,7 +125,7 @@ const save = reactive({
} }
// Store.set('product-deploy', deploy); // Store.set('product-deploy', deploy);
} else { } else {
save.resetMetadata(); // save.resetMetadata();
message.success({ message.success({
key: 'metadata', key: 'metadata',
content: '操作成功!', content: '操作成功!',
@ -133,9 +133,9 @@ const save = reactive({
} }
metadataStore.set('edit', false) metadataStore.set('edit', false)
metadataStore.set('item', {}) metadataStore.set('item', {})
if (instanceStore.detail) { // if (props?.type === 'device' && instanceStore.detail) {
instanceStore.detail.independentMetadata = true; // instanceStore.detail.independentMetadata = true;
} // }
} }
} else { } else {
message.error('操作失败!'); message.error('操作失败!');

View File

@ -1,4 +1,4 @@
import { JColumnProps } from "@/components/Table"; import { ColumnProps } from "ant-design-vue/es/table";
const SourceMap = { const SourceMap = {
device: '设备', device: '设备',
@ -12,7 +12,7 @@ const type = {
report: '上报', report: '上报',
}; };
const BaseColumns: JColumnProps[] = [ const BaseColumns: ColumnProps[] = [
{ {
title: '标识', title: '标识',
dataIndex: 'id', dataIndex: 'id',
@ -30,19 +30,17 @@ const BaseColumns: JColumnProps[] = [
}, },
]; ];
const EventColumns: JColumnProps[] = BaseColumns.concat([ const EventColumns: ColumnProps[] = BaseColumns.concat([
{ {
title: '事件级别', title: '事件级别',
dataIndex: 'level', dataIndex: 'level',
scopedSlots: true,
}, },
]); ]);
const FunctionColumns: JColumnProps[] = BaseColumns.concat([ const FunctionColumns: ColumnProps[] = BaseColumns.concat([
{ {
title: '是否异步', title: '是否异步',
dataIndex: 'async', dataIndex: 'async',
scopedSlots: true,
}, },
// { // {
// title: '读写类型', // title: '读写类型',
@ -51,38 +49,33 @@ const FunctionColumns: JColumnProps[] = BaseColumns.concat([
// }, // },
]); ]);
const PropertyColumns: JColumnProps[] = BaseColumns.concat([ const PropertyColumns: ColumnProps[] = BaseColumns.concat([
{ {
title: '数据类型', title: '数据类型',
dataIndex: 'valueType', dataIndex: 'valueType',
scopedSlots: true,
}, },
{ {
title: '属性来源', title: '属性来源',
dataIndex: 'source', dataIndex: 'source',
scopedSlots: true,
}, },
{ {
title: '读写类型', title: '读写类型',
dataIndex: 'type', dataIndex: 'type',
scopedSlots: true,
}, },
]); ]);
const TagColumns: JColumnProps[] = BaseColumns.concat([ const TagColumns: ColumnProps[] = BaseColumns.concat([
{ {
title: '数据类型', title: '数据类型',
dataIndex: 'valueType', dataIndex: 'valueType',
scopedSlots: true,
}, },
{ {
title: '读写类型', title: '读写类型',
dataIndex: 'type', dataIndex: 'type',
scopedSlots: true,
}, },
]); ]);
const MetadataMapping = new Map<string, JColumnProps[]>(); const MetadataMapping = new Map<string, ColumnProps[]>();
MetadataMapping.set('properties', PropertyColumns); MetadataMapping.set('properties', PropertyColumns);
MetadataMapping.set('events', EventColumns); MetadataMapping.set('events', EventColumns);
MetadataMapping.set('tags', TagColumns); MetadataMapping.set('tags', TagColumns);

View File

@ -16,7 +16,7 @@
<Edit v-if="metadataStore.model.edit" :type="target" :tabs="type" @refresh="refreshMetadata"></Edit> <Edit v-if="metadataStore.model.edit" :type="target" :tabs="type" @refresh="refreshMetadata"></Edit>
</div> </div>
</div> </div>
<a-table :loading="loading" :data-source="data" :columns="columns" row-key="id" model="TABLE" size="small" <j-table :loading="loading" :data-source="data" :columns="columns" row-key="id" model="TABLE" size="small"
:pagination="pagination"> :pagination="pagination">
<template #bodyCell="{ column, record }"> <template #bodyCell="{ column, record }">
<template v-if="column.dataIndex === 'level'"> <template v-if="column.dataIndex === 'level'">
@ -44,7 +44,7 @@
}"> }">
<AIcon type="EditOutlined" /> <AIcon type="EditOutlined" />
</PermissionButton> </PermissionButton>
<PermissionButton :has-permission="`${permission}:delete`" type="link" key="delete" style="padding: 0" <PermissionButton :has-permission="`${permission}:delete`" type="link" key="delete" style="padding: 0" danger
:pop-confirm="{ :pop-confirm="{
title: '确认删除?', onConfirm: async () => { title: '确认删除?', onConfirm: async () => {
await removeItem(record); await removeItem(record);
@ -57,7 +57,7 @@
</j-space> </j-space>
</template> </template>
</template> </template>
</a-table> </j-table>
</template> </template>
<script setup lang="ts" name="BaseMetadata"> <script setup lang="ts" name="BaseMetadata">
import type { MetadataItem, MetadataType } from '@/views/device/Product/typings' import type { MetadataItem, MetadataType } from '@/views/device/Product/typings'
@ -69,6 +69,7 @@ import PermissionButton from '@/components/PermissionButton/index.vue'
import { TablePaginationConfig, message } from 'ant-design-vue/es' import { TablePaginationConfig, message } from 'ant-design-vue/es'
import { asyncUpdateMetadata, removeMetadata } from '../metadata' import { asyncUpdateMetadata, removeMetadata } from '../metadata'
import Edit from './Edit/index.vue' import Edit from './Edit/index.vue'
import { ColumnProps } from 'ant-design-vue/es/table'
interface Props { interface Props {
type: MetadataType; type: MetadataType;
target: 'product' | 'device'; target: 'product' | 'device';
@ -97,13 +98,12 @@ const expandsType = ref({
write: '写', write: '写',
report: '上报', report: '上报',
}); });
const actions = [ const actions: ColumnProps[] = [
{ {
title: '操作', title: '操作',
align: 'left', align: 'left',
width: 200, width: 200,
dataIndex: 'action', dataIndex: 'action',
scopedSlots: true,
}, },
]; ];
const pagination = { const pagination = {
@ -116,14 +116,14 @@ const pagination = {
size: 'small', size: 'small',
} as TablePaginationConfig } as TablePaginationConfig
const columns = computed(() => MetadataMapping.get(type)!.concat(actions)) const columns = computed(() => MetadataMapping.get(type)!.concat(actions))
const items = computed(() => JSON.parse((target === 'product' ? productStore.current?.metadata : instanceStore.current.metadata) || '{}') as MetadataItem[]) const items = computed(() => JSON.parse((target === 'product' ? productStore.current?.metadata : instanceStore.current.metadata) || '{}'))
const searchValue = ref<string>() const searchValue = ref<string>()
const handleSearch = (searchValue: string) => { const handleSearch = (searchValue: string) => {
if (searchValue) { if (searchValue) {
const arr = items.value.filter(item => item.name!.indexOf(searchValue) > -1).sort((a, b) => b?.sortsIndex - a?.sortsIndex) const arr = items.value[type].filter((item: MetadataItem) => item.name!.indexOf(searchValue) > -1).sort((a: MetadataItem, b: MetadataItem) => b?.sortsIndex - a?.sortsIndex)
data.value = arr data.value = arr
} else { } else {
data.value = items.value data.value = items.value[type]
} }
} }
@ -215,6 +215,6 @@ const removeItem = async (record: MetadataItem) => {
.table-header { .table-header {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
padding: 16px 0; padding: 16px;
} }
</style> </style>

View File

@ -18,7 +18,7 @@
<j-tabs @change="handleConvertMetadata" destroy-inactive-tab-pane> <j-tabs @change="handleConvertMetadata" destroy-inactive-tab-pane>
<j-tab-pane v-for="item in codecs" :key="item.id" :tab="item.name"> <j-tab-pane v-for="item in codecs" :key="item.id" :tab="item.name">
<div class="cat-panel"> <div class="cat-panel">
<MonacoEditor v-model="value" theme="vs" style="height: 100%"></MonacoEditor> <JMonacoEditor v-model="value" theme="vs" style="height: 100%" lang="javascript"></JMonacoEditor>
</div> </div>
</j-tab-pane> </j-tab-pane>
</j-tabs> </j-tabs>

View File

@ -17,8 +17,7 @@
<j-form-item label="选择产品" v-bind="validateInfos.copy" v-if="formModel.type === 'copy'"> <j-form-item label="选择产品" v-bind="validateInfos.copy" v-if="formModel.type === 'copy'">
<j-select :options="productList" v-model:value="formModel.copy" option-filter-prop="label"></j-select> <j-select :options="productList" v-model:value="formModel.copy" option-filter-prop="label"></j-select>
</j-form-item> </j-form-item>
<j-form-item label="物模型类型" v-bind="validateInfos.metadata" <j-form-item label="物模型类型" v-bind="validateInfos.metadata" v-if="type === 'device' || formModel.type === 'import'">
v-if="type === 'device' || formModel.type === 'import'">
<j-select v-model:value="formModel.metadata"> <j-select v-model:value="formModel.metadata">
<j-select-option value="jetlinks">Jetlinks物模型</j-select-option> <j-select-option value="jetlinks">Jetlinks物模型</j-select-option>
<j-select-option value="alink">阿里云物模型TSL</j-select-option> <j-select-option value="alink">阿里云物模型TSL</j-select-option>
@ -34,16 +33,23 @@
<j-form-item label="文件上传" v-bind="validateInfos.upload" v-if="formModel.metadataType === 'file'"> <j-form-item label="文件上传" v-bind="validateInfos.upload" v-if="formModel.metadataType === 'file'">
<j-input v-model:value="formModel.upload"> <j-input v-model:value="formModel.upload">
<template #addonAfter> <template #addonAfter>
<j-upload v-model:file-list="fileList" :before-upload="beforeUpload" accept=".json" <j-upload v-model:file-list="fileList" :before-upload="beforeUpload" accept=".json" :show-upload-list="false"
:show-upload-list="false" :action="FILE_UPLOAD" @change="fileChange" :action="FILE_UPLOAD" @change="fileChange" :headers="{ 'X-Access-Token': getToken() }">
:headers="{ 'X-Access-Token': getToken()}">
<AIcon type="UploadOutlined" class="upload-button" /> <AIcon type="UploadOutlined" class="upload-button" />
</j-upload> </j-upload>
</template> </template>
</j-input> </j-input>
</j-form-item> </j-form-item>
<j-form-item label="物模型" v-bind="validateInfos.import" v-if="formModel.metadataType === 'script'"> <j-form-item v-bind="validateInfos.import" v-if="(type === 'device' || formModel.type === 'import') && formModel.metadataType === 'script'">
<MonacoEditor v-model="formModel.import" theme="vs" style="height: 300px"></MonacoEditor> <template #label>
<j-space>
物模型
<j-tooltip title="在线编辑器中编写物模型脚本">
<AIcon type="QuestionCircleOutlined" style="color: rgb(136, 136, 136);"/>
</j-tooltip>
</j-space>
</template>
<JMonacoEditor v-model="formModel.import" theme="vs" style="height: 300px" lang="javascript"></JMonacoEditor>
</j-form-item> </j-form-item>
</j-form> </j-form>
</j-modal> </j-modal>
@ -60,7 +66,6 @@ import { useInstanceStore } from '@/store/instance'
import { useProductStore } from '@/store/product'; import { useProductStore } from '@/store/product';
import { FILE_UPLOAD } from '@/api/comm'; import { FILE_UPLOAD } from '@/api/comm';
import { getToken } from '@/utils/comm'; import { getToken } from '@/utils/comm';
import MonacoEditor from '@/components/MonacoEditor/index.vue'
import { useMetadataStore } from '@/store/metadata'; import { useMetadataStore } from '@/store/metadata';
const route = useRoute() const route = useRoute()
@ -250,7 +255,7 @@ const handleImport = async () => {
resp = await modify(id as string, params) resp = await modify(id as string, params)
} }
loading.value = false loading.value = false
if (resp.status === 200) { if (resp.success) {
if (props?.type === 'device') { if (props?.type === 'device') {
const detail = instanceStore.current const detail = instanceStore.current
detail.metadata = paramsDevice detail.metadata = paramsDevice
@ -290,9 +295,10 @@ const handleImport = async () => {
padding: 10px; padding: 10px;
} }
} }
.upload-button { .upload-button {
width: 37px; width: 37px;
height: 30px; height: 30px;
line-height: 30px; line-height: 30px;
margin: 0 -11px; margin: 0 -11px;
} }

View File

@ -2,7 +2,7 @@
<j-card> <j-card>
<div class='device-detail-metadata' style="position: relative;"> <div class='device-detail-metadata' style="position: relative;">
<div class="tips"> <div class="tips">
<j-tooltip v-if="type === 'device'" :title="instanceStore.detail?.independentMetadata && type === 'device' <j-tooltip :title="instanceStore.detail?.independentMetadata && type === 'device'
? '该设备已脱离产品物模型,修改产品物模型对该设备无影响' ? '该设备已脱离产品物模型,修改产品物模型对该设备无影响'
: '设备会默认继承产品的物模型,修改设备物模型后将脱离产品物模型'"> : '设备会默认继承产品的物模型,修改设备物模型后将脱离产品物模型'">
<div class="ellipsis"> <div class="ellipsis">
@ -23,8 +23,8 @@
:tooltip="{ title: '重置后将使用产品的物模型配置' }" key="reload"> :tooltip="{ title: '重置后将使用产品的物模型配置' }" key="reload">
重置操作 重置操作
</PermissionButton> </PermissionButton>
<PermissionButton :hasPermission="`${permission}:update`" @click="visible = true">快速导入</PermissionButton> <PermissionButton :hasPermission="`${permission}:update`" @click="visible = true" key="import">快速导入</PermissionButton>
<PermissionButton :hasPermission="`${permission}:update`" @click="cat = true">物模型TSL</PermissionButton> <PermissionButton :hasPermission="`${permission}:update`" @click="cat = true" key="tsl">物模型TSL</PermissionButton>
</j-space> </j-space>
</template> </template>
@ -54,9 +54,11 @@ import { useInstanceStore } from '@/store/instance'
import Import from './Import/index.vue' import Import from './Import/index.vue'
import Cat from './Cat/index.vue' import Cat from './Cat/index.vue'
import BaseMetadata from './Base/index.vue' import BaseMetadata from './Base/index.vue'
import { useMetadataStore } from '@/store/metadata'
const route = useRoute() const route = useRoute()
const instanceStore = useInstanceStore() const instanceStore = useInstanceStore()
const metadataStore = useMetadataStore()
interface Props { interface Props {
type: 'product' | 'device'; type: 'product' | 'device';
independentMetadata?: boolean; independentMetadata?: boolean;
@ -73,7 +75,9 @@ const resetMetadata = async () => {
const resp = await deleteMetadata(id as string) const resp = await deleteMetadata(id as string)
if (resp.status === 200) { if (resp.status === 200) {
message.info('操作成功') message.info('操作成功')
instanceStore.refresh(id as string) instanceStore.refresh(id as string).then(() => {
metadataStore.set('importMetadata', true)
})
// Store.set(SystemConst.REFRESH_DEVICE, true) // Store.set(SystemConst.REFRESH_DEVICE, true)
// setTimeout(() => { // setTimeout(() => {
// Store.set(SystemConst.REFRESH_METADATA_TABLE, true) // Store.set(SystemConst.REFRESH_METADATA_TABLE, true)
@ -84,11 +88,11 @@ const resetMetadata = async () => {
<style scoped lang="less"> <style scoped lang="less">
.device-detail-metadata { .device-detail-metadata {
.tips { .tips {
width: calc(100% - 670px); // width: calc(100% - 670px);
position: absolute; position: absolute;
top: 12px; top: 12px;
z-index: 1; z-index: 1;
margin-left: 380px; margin-left: 420px;
font-weight: 100; font-weight: 100;
} }

View File

@ -255,7 +255,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { modalState, formState, logoState } from '../data/interface'; import { modalState, formState, logoState } from '../data/interface';
import { getImage } from '@/utils/comm.ts'; import { getImage } from '@/utils/comm.ts';
import { Form } from 'ant-design-vue'; import { Form, message } from 'jetlinks-ui-components';
import { FILE_UPLOAD } from '@/api/comm'; import { FILE_UPLOAD } from '@/api/comm';
import { import {
getSystemPermission, getSystemPermission,
@ -274,8 +274,6 @@ import {
deployDevice, deployDevice,
saveInit, saveInit,
} from '@/api/initHome'; } from '@/api/initHome';
import { ValidateErrorEntity } from 'ant-design-vue/es/form/interface';
import { message } from 'ant-design-vue';
import { LocalStore } from '@/utils/comm'; import { LocalStore } from '@/utils/comm';
import { TOKEN_KEY } from '@/utils/variable'; import { TOKEN_KEY } from '@/utils/variable';
import { SystemConst } from '@/utils/consts' import { SystemConst } from '@/utils/consts'
@ -362,7 +360,7 @@ const saveBasicInfo = () =>{
resolve(false); resolve(false);
} }
}) })
.catch((error: ValidateErrorEntity<formState>) => { .catch(() => {
resolve(false); resolve(false);
}); });
}) })

View File

@ -129,9 +129,8 @@ import {
deployDevice, deployDevice,
} from '@/api/initHome'; } from '@/api/initHome';
import { modalState } from '../data/interface'; import { modalState } from '../data/interface';
import { ValidateErrorEntity } from 'ant-design-vue/es/form/interface';
import type { Rule } from 'ant-design-vue/es/form'; import type { Rule } from 'ant-design-vue/es/form';
import { message } from 'ant-design-vue/es'; import { message } from 'jetlinks-ui-components';
const formRef = ref(); const formRef = ref();
/** /**
* 初始化数据状态 * 初始化数据状态
@ -315,7 +314,7 @@ const saveCurrentData = () => {
resolve(false); resolve(false);
} }
}) })
.catch((error: ValidateErrorEntity<modalState>) => { .catch(() => {
resolve(false); resolve(false);
}); });
}); });

View File

@ -65,33 +65,16 @@ import Basic from './Basic/index.vue';
import Role from './Role/index.vue'; import Role from './Role/index.vue';
import Menu from './Menu/index.vue'; import Menu from './Menu/index.vue';
import InitData from './InitData/index.vue'; import InitData from './InitData/index.vue';
import {
PlusOutlined,
ExclamationCircleOutlined,
LoadingOutlined,
} from '@ant-design/icons-vue';
import type {
FormInstance,
UploadChangeParam,
UploadProps,
} from 'ant-design-vue';
import { modalState, formState, logoState } from './data/interface'; import { modalState, formState, logoState } from './data/interface';
import { saveInit } from '@/api/initHome'; import { saveInit } from '@/api/initHome';
import { BASE_API_PATH, TOKEN_KEY } from '@/utils/variable'; import { BASE_API_PATH, TOKEN_KEY } from '@/utils/variable';
import { FILE_UPLOAD } from '@/api/comm'; import { FILE_UPLOAD } from '@/api/comm';
import { LocalStore } from '@/utils/comm'; import { LocalStore } from '@/utils/comm';
import { message } from 'ant-design-vue'; import { message } from 'jetlinks-ui-components';
import { Form } from 'ant-design-vue';
const basicRef = ref(); const basicRef = ref();
const roleRef = ref(); const roleRef = ref();
const initDataRef = ref(); const initDataRef = ref();
const loading = ref(false); const loading = ref(false);
// const useForm = Form.useForm;
// const { resetFields, validate, validateInfos } = useForm(
// modalForm.value,
// rulesModle.value,
// );
/** /**
* 默认打开第一个初始菜单 * 默认打开第一个初始菜单
*/ */

View File

@ -2,7 +2,7 @@
<template> <template>
<page-container> <page-container>
<pro-search :columns="columns" target="iot-card-management-search" @search="handleSearch" /> <pro-search :columns="columns" target="iot-card-management-search" @search="handleSearch" />
<j-pro-table ref="cardManageRef" :columns="columns" :request="query" <j-pro-table :scroll="{x: 1366}" ref="cardManageRef" :columns="columns" :request="query"
:defaultParams="{ sorts: [{ name: 'createTime', order: 'desc' }] }" :rowSelection="{ :defaultParams="{ sorts: [{ name: 'createTime', order: 'desc' }] }" :rowSelection="{
selectedRowKeys: _selectedRowKeys, selectedRowKeys: _selectedRowKeys,
onChange: onSelectChange, onChange: onSelectChange,
@ -89,7 +89,7 @@
<CardBox :value="slotProps" @click="handleClick" :actions="getActions(slotProps, 'card')" v-bind="slotProps" <CardBox :value="slotProps" @click="handleClick" :actions="getActions(slotProps, 'card')" v-bind="slotProps"
:active="_selectedRowKeys.includes(slotProps.id)" :status="slotProps.cardStateType.value" :active="_selectedRowKeys.includes(slotProps.id)" :status="slotProps.cardStateType.value"
:statusText="slotProps.cardStateType.text" :statusNames="{ :statusText="slotProps.cardStateType.text" :statusNames="{
using: 'success', using: 'processing',
toBeActivated: 'default', toBeActivated: 'default',
deactivate: 'error', deactivate: 'error',
}"> }">
@ -114,12 +114,13 @@
<div>{{ slotProps.cardType.text }}</div> <div>{{ slotProps.cardType.text }}</div>
</j-col> </j-col>
<j-col :span="6"> <j-col :span="6">
<div class="card-item-content-text">提醒</div> <div class="card-item-content-text">绑定设备</div>
<!-- <div>{{ slotProps.cardType.text }}</div> --> <div>{{ slotProps.deviceName }}</div>
</j-col> </j-col>
</j-row> </j-row>
<j-divider style="margin: 12px 0" /> <j-divider style="margin: 12px 0" />
<div v-if="slotProps.usedFlow === 0"> <div class="content-bottom">
<div v-if="slotProps.usedFlow === 0">
<span class="flow-text"> <span class="flow-text">
{{ slotProps.totalFlow }} {{ slotProps.totalFlow }}
</span> </span>
@ -130,7 +131,7 @@
<div class="progress-text"> <div class="progress-text">
<div> <div>
{{ {{
slotProps.totalFlow - slotProps.usedFlow (slotProps.usedFlow / slotProps.totalFlow * 100).toFixed(2)
}} }}
% %
</div> </div>
@ -138,9 +139,8 @@
总共 {{ slotProps.totalFlow }} M 总共 {{ slotProps.totalFlow }} M
</div> </div>
</div> </div>
<j-progress :strokeColor="'#ADC6FF'" :showInfo="false" :percent=" <j-progress :strokeColor="'#ADC6FF'" :showInfo="false" :percent="slotProps.usedFlow / slotProps.totalFlow * 100" />
slotProps.totalFlow - slotProps.usedFlow </div>
" />
</div> </div>
</template> </template>
<template #actions="item"> <template #actions="item">
@ -226,12 +226,20 @@
{{ slotProps.cardType.text }} {{ slotProps.cardType.text }}
</template> </template>
<template #cardStateType="slotProps"> <template #cardStateType="slotProps">
{{ slotProps.cardStateType.text }} <BadgeStatus
:status="slotProps.cardStateType?.value"
:text="slotProps.cardStateType?.text"
:statusNames="{
using: 'processing',
toBeActivated: 'default',
deactivate: 'error',
}"
/>
</template> </template>
<template #activationDate="slotProps"> <template #activationDate="slotProps">
{{ {{
slotProps.activationDate slotProps.activationDate
? moment(slotProps.activationDate).format( ? dayjs(slotProps.activationDate).format(
'YYYY-MM-DD HH:mm:ss', 'YYYY-MM-DD HH:mm:ss',
) )
: '' : ''
@ -240,7 +248,7 @@
<template #updateTime="slotProps"> <template #updateTime="slotProps">
{{ {{
slotProps.updateTime slotProps.updateTime
? moment(slotProps.updateTime).format( ? dayjs(slotProps.updateTime).format(
'YYYY-MM-DD HH:mm:ss', 'YYYY-MM-DD HH:mm:ss',
) )
: '' : ''
@ -274,7 +282,7 @@
<script setup lang="ts"> <script setup lang="ts">
import type { ActionsType } from '@/components/Table'; import type { ActionsType } from '@/components/Table';
import moment from 'moment'; import dayjs from 'dayjs';
import { import {
query, query,
queryPlatformNoPage, queryPlatformNoPage,
@ -297,6 +305,8 @@ import Import from './Import.vue';
import Export from './Export.vue'; import Export from './Export.vue';
import Save from './Save.vue'; import Save from './Save.vue';
import { useMenuStore } from 'store/menu' import { useMenuStore } from 'store/menu'
import BadgeStatus from '@/components/BadgeStatus/index.vue';
const router = useRouter(); const router = useRouter();
const menuStory = useMenuStore() const menuStory = useMenuStore()
const cardManageRef = ref<Record<string, any>>({}); const cardManageRef = ref<Record<string, any>>({});
@ -316,7 +326,7 @@ const columns = [
title: '卡号', title: '卡号',
dataIndex: 'id', dataIndex: 'id',
key: 'id', key: 'id',
width: 300, width: 200,
ellipsis: true, ellipsis: true,
fixed: 'left', fixed: 'left',
search: { search: {
@ -737,6 +747,10 @@ const handelRemove = async () => {
</script> </script>
<style scoped lang="less"> <style scoped lang="less">
.content-bottom {
height: 45px;
}
.flow-text { .flow-text {
font-size: 20px; font-size: 20px;
font-weight: 600; font-weight: 600;

View File

@ -38,8 +38,7 @@
:params="params" :params="params"
:rowSelection="{ :rowSelection="{
selectedRowKeys: _selectedRowKeys, selectedRowKeys: _selectedRowKeys,
onSelect: onSelectChange, onChange: onSelectChange,
onSelectAll: onSelectAllChange,
}" }"
@cancelSelect="_selectedRowKeys = []" @cancelSelect="_selectedRowKeys = []"
:pagination="{ :pagination="{
@ -165,32 +164,9 @@ const handleSearch = (e: any) => {
const listRef = ref(); const listRef = ref();
const _selectedRowKeys = ref<string[]>([]); const _selectedRowKeys = ref<string[]>([]);
const onSelectChange = ( const onSelectChange = (keys: string[]) => {
record: any[], _selectedRowKeys.value = [...keys];
selected: boolean,
selectedRows: any[],
) => {
_selectedRowKeys.value = selected
? [...getSetRowKey(selectedRows)]
: _selectedRowKeys.value.filter((item: any) => item !== record?.id);
}; };
const onSelectAllChange = (
selected: boolean,
selectedRows: any[],
changeRows: any[],
) => {
const unRowsKeys = getSelectedRowsKey(changeRows);
_selectedRowKeys.value = selected
? [...getSetRowKey(selectedRows)]
: _selectedRowKeys.value
.concat(unRowsKeys)
.filter((item) => !unRowsKeys.includes(item));
};
const getSelectedRowsKey = (selectedRows: any[]) =>
selectedRows.map((item) => item?.id).filter((i) => !!i);
const getSetRowKey = (selectedRows: any[]) =>
new Set([..._selectedRowKeys.value, ...getSelectedRowsKey(selectedRows)]);
const loading = ref(false); const loading = ref(false);
const handleSave = async () => { const handleSave = async () => {

View File

@ -15,8 +15,7 @@
:params="params" :params="params"
:rowSelection="{ :rowSelection="{
selectedRowKeys: _selectedRowKeys, selectedRowKeys: _selectedRowKeys,
onSelect: onSelectChange, onChange: onSelectChange,
onSelectAll: onSelectAllChange,
}" }"
@cancelSelect="_selectedRowKeys = []" @cancelSelect="_selectedRowKeys = []"
:pagination="{ :pagination="{
@ -242,32 +241,9 @@ const listRef = ref();
const _selectedRowKeys = ref<string[]>([]); const _selectedRowKeys = ref<string[]>([]);
const bindVis = ref(false); const bindVis = ref(false);
const onSelectChange = ( const onSelectChange = (keys: string[]) => {
record: any[], _selectedRowKeys.value = [...keys];
selected: boolean,
selectedRows: any[],
) => {
_selectedRowKeys.value = selected
? [...getSetRowKey(selectedRows)]
: _selectedRowKeys.value.filter((item: any) => item !== record?.id);
}; };
const onSelectAllChange = (
selected: boolean,
selectedRows: any[],
changeRows: any[],
) => {
const unRowsKeys = getSelectedRowsKey(changeRows);
_selectedRowKeys.value = selected
? [...getSetRowKey(selectedRows)]
: _selectedRowKeys.value
.concat(unRowsKeys)
.filter((item) => !unRowsKeys.includes(item));
};
const getSelectedRowsKey = (selectedRows: any[]) =>
selectedRows.map((item) => item?.id).filter((i) => !!i);
const getSetRowKey = (selectedRows: any[]) =>
new Set([..._selectedRowKeys.value, ...getSelectedRowsKey(selectedRows)]);
/** /**
* 表格操作按钮 * 表格操作按钮

View File

@ -121,6 +121,7 @@
:key="i.key" :key="i.key"
> >
<PermissionButton <PermissionButton
:danger="i.key === 'delete'"
:disabled="i.disabled" :disabled="i.disabled"
:popConfirm="i.popConfirm" :popConfirm="i.popConfirm"
:tooltip="{ :tooltip="{

View File

@ -123,7 +123,11 @@
</j-form-item> </j-form-item>
</j-col> </j-col>
<j-col :span="24"> <j-col :span="24">
<j-form-item name="address" label="安装地址"> <j-form-item
name="address"
label="安装地址"
:rules="{ max: 64, message: '最多可输入64个字符' }"
>
<j-input <j-input
v-model:value="formData.address" v-model:value="formData.address"
placeholder="请输入安装地址" placeholder="请输入安装地址"

View File

@ -79,8 +79,10 @@
:disabled="i.disabled" :disabled="i.disabled"
style="padding: 0" style="padding: 0"
type="link" type="link"
><AIcon :type="i.icon" :danger="i.key === 'delete'"
/></j-button> >
<AIcon :type="i.icon" />
</j-button>
</j-popconfirm> </j-popconfirm>
<j-button <j-button
style="padding: 0" style="padding: 0"
@ -92,8 +94,9 @@
:disabled="i.disabled" :disabled="i.disabled"
style="padding: 0" style="padding: 0"
type="link" type="link"
><AIcon :type="i.icon" >
/></j-button> <AIcon :type="i.icon" />
</j-button>
</j-button> </j-button>
</j-tooltip> </j-tooltip>
</j-space> </j-space>

View File

@ -234,6 +234,8 @@ watch(
if (val) { if (val) {
getGatewayList(); getGatewayList();
} else { } else {
_selectedRowKeys.value = []
extendFormItem.value = []
emit('close'); emit('close');
} }
}, },

View File

@ -124,6 +124,7 @@
:key="i.key" :key="i.key"
> >
<PermissionButton <PermissionButton
:danger="i.key === 'delete'"
:disabled="i.disabled" :disabled="i.disabled"
:popConfirm="i.popConfirm" :popConfirm="i.popConfirm"
:tooltip="{ :tooltip="{

View File

@ -69,7 +69,7 @@ const getDeviceList = async () => {
const res = await cascadeApi.getMediaTree({ paging: false }); const res = await cascadeApi.getMediaTree({ paging: false });
if (res.success) { if (res.success) {
treeData.value = res.result treeData.value = res.result
.sort((a: any, b: any) => a.createTime - b.createTime) .sort((a: any, b: any) => b.createTime - a.createTime)
.map((m: any) => { .map((m: any) => {
const extra: any = {}; const extra: any = {};
extra.isLeaf = isLeaf(m); extra.isLeaf = isLeaf(m);

View File

@ -269,7 +269,7 @@
v-model:value=" v-model:value="
formData.configuration.secret formData.configuration.secret
" "
placeholder="Secret" placeholder="请输入Secret"
/> />
</j-form-item> </j-form-item>
</template> </template>
@ -493,33 +493,33 @@ const handleSslChange = () => {
const resetPublicFiles = () => { const resetPublicFiles = () => {
switch (formData.value.provider) { switch (formData.value.provider) {
case 'dingTalkMessage': case 'dingTalkMessage':
formData.value.configuration.appKey = ''; formData.value.configuration.appKey = undefined;
formData.value.configuration.appSecret = ''; formData.value.configuration.appSecret = undefined;
break; break;
case 'dingTalkRobotWebHook': case 'dingTalkRobotWebHook':
formData.value.configuration.url = ''; formData.value.configuration.url = undefined;
break; break;
case 'corpMessage': case 'corpMessage':
formData.value.configuration.corpId = ''; formData.value.configuration.corpId = undefined;
formData.value.configuration.corpSecret = ''; formData.value.configuration.corpSecret = undefined;
break; break;
case 'embedded': case 'embedded':
formData.value.configuration.host = ''; formData.value.configuration.host = undefined;
formData.value.configuration.port = 25; formData.value.configuration.port = 25;
formData.value.configuration.ssl = false; formData.value.configuration.ssl = false;
formData.value.configuration.sender = ''; formData.value.configuration.sender = undefined;
formData.value.configuration.username = ''; formData.value.configuration.username = undefined;
formData.value.configuration.password = ''; formData.value.configuration.password = undefined;
break; break;
case 'aliyun': case 'aliyun':
formData.value.configuration.regionId = ''; formData.value.configuration.regionId = undefined;
formData.value.configuration.accessKeyId = ''; formData.value.configuration.accessKeyId = undefined;
formData.value.configuration.secret = ''; formData.value.configuration.secret = undefined;
break; break;
case 'aliyunSms': case 'aliyunSms':
formData.value.configuration.regionId = ''; formData.value.configuration.regionId = undefined;
formData.value.configuration.accessKeyId = ''; formData.value.configuration.accessKeyId = undefined;
formData.value.configuration.secret = ''; formData.value.configuration.secret = undefined;
break; break;
case 'http': case 'http':
formData.value.configuration.url = undefined; formData.value.configuration.url = undefined;

View File

@ -49,7 +49,7 @@
pageSize: pageSize, pageSize: pageSize,
pageSizeOptions: ['5', '10', '20', '50', '100'], pageSizeOptions: ['5', '10', '20', '50', '100'],
showSizeChanger: true, showSizeChanger: true,
hideOnSinglePage: true, hideOnSinglePage: false,
showTotal: (total: number, range: number) => `${range[0]} - ${range[1]} 条/总共 ${total}`, showTotal: (total: number, range: number) => `${range[0]} - ${range[1]} 条/总共 ${total}`,
}" }"
@change="handleTableChange" @change="handleTableChange"
@ -196,11 +196,19 @@ const getDepartment = async () => {
deptId.value = _result[0]?.id; deptId.value = _result[0]?.id;
}; };
watch(
() => deptName.value,
(val: any) => {
if (!val) getDepartment();
},
);
/** /**
* 部门点击 * 部门点击
*/ */
const onTreeSelect = (keys: any) => { const onTreeSelect = (keys: any) => {
deptId.value = keys[0]; deptId.value = keys[0];
pageSize.value = 5;
}; };
// //

View File

@ -168,6 +168,7 @@
:key="i.key" :key="i.key"
> >
<PermissionButton <PermissionButton
:danger="i.key === 'delete'"
:disabled="i.disabled" :disabled="i.disabled"
:popConfirm="i.popConfirm" :popConfirm="i.popConfirm"
:tooltip="{ :tooltip="{

View File

@ -2,7 +2,7 @@
<j-select <j-select
:options="options" :options="options"
@change="change" @change="change"
placeholder="请选择标签推送,多个标签用,号分隔" placeholder="请选择标签推送"
style="width: 100%" style="width: 100%"
:allowClear="true" :allowClear="true"
v-model:value="_value" v-model:value="_value"

View File

@ -598,7 +598,13 @@
</j-form-item> </j-form-item>
</j-col> </j-col>
<j-col :span="12"> <j-col :span="12">
<j-form-item> <j-form-item
v-bind="
validateInfos[
'template.phoneNumber'
]
"
>
<template #label> <template #label>
<span> <span>
收信人 收信人
@ -828,7 +834,10 @@ const resetPublicFiles = () => {
case 'dingTalkRobotWebHook': case 'dingTalkRobotWebHook':
formData.value.template.message = undefined; formData.value.template.message = undefined;
formData.value.template.messageType = 'markdown'; formData.value.template.messageType = 'markdown';
formData.value.template.markdown = { text: undefined, title: undefined }; formData.value.template.markdown = {
text: undefined,
title: undefined,
};
break; break;
case 'corpMessage': case 'corpMessage':
formData.value.template.agentId = undefined; formData.value.template.agentId = undefined;
@ -990,6 +999,17 @@ const formRules = ref({
'template.signName': [ 'template.signName': [
{ required: true, message: '请输入签名', trigger: 'blur' }, { required: true, message: '请输入签名', trigger: 'blur' },
], ],
'template.phoneNumber': [
{ max: 64, message: '最多可输入64个字符', trigger: 'change' },
{
trigger: 'change',
validator(_rule: Rule, value: string) {
if (!value) return Promise.resolve();
if (!phoneRegEx(value)) return Promise.reject('该字段不是有效的手机号');
return Promise.resolve();
},
},
],
// webhook // webhook
description: [{ max: 200, message: '最多可输入200个字符' }], description: [{ max: 200, message: '最多可输入200个字符' }],
'template.message': [ 'template.message': [

View File

@ -131,6 +131,7 @@
:key="i.key" :key="i.key"
> >
<PermissionButton <PermissionButton
:danger="i.key === 'delete'"
:disabled="i.disabled" :disabled="i.disabled"
:popConfirm="i.popConfirm" :popConfirm="i.popConfirm"
:tooltip="{ :tooltip="{

View File

@ -49,9 +49,8 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { Form } from 'ant-design-vue';
import { saveOutputData } from '@/api/rule-engine/config'; import { saveOutputData } from '@/api/rule-engine/config';
import { message } from 'ant-design-vue/es'; import { Form, message } from 'jetlinks-ui-components';
const useForm = Form.useForm; const useForm = Form.useForm;
const formRef = ref(); const formRef = ref();
const Myprops = defineProps({ const Myprops = defineProps({

View File

@ -59,9 +59,8 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { Form } from 'ant-design-vue';
import { saveOutputData } from '@/api/rule-engine/config'; import { saveOutputData } from '@/api/rule-engine/config';
import { message } from 'ant-design-vue/es'; import { Form, message } from 'jetlinks-ui-components';
const formRef = ref(); const formRef = ref();
const useForm = Form.useForm; const useForm = Form.useForm;
const Myprops = defineProps({ const Myprops = defineProps({

View File

@ -186,11 +186,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import InputSave from './Save/input.vue'; import InputSave from './Save/input.vue';
import OutputSave from './save/output.vue'; import OutputSave from './save/output.vue';
import {
EditOutlined,
DeleteOutlined,
PlusOutlined,
} from '@ant-design/icons-vue';
import { getDataExchange } from '@/api/rule-engine/config'; import { getDataExchange } from '@/api/rule-engine/config';
import { getImage } from '@/utils/comm'; import { getImage } from '@/utils/comm';
import { marked } from 'marked'; import { marked } from 'marked';

View File

@ -64,7 +64,7 @@
import { getImage } from '@/utils/comm'; import { getImage } from '@/utils/comm';
import { queryLevel, saveLevel } from '@/api/rule-engine/config'; import { queryLevel, saveLevel } from '@/api/rule-engine/config';
import { LevelItem } from './typing'; import { LevelItem } from './typing';
import { message } from 'ant-design-vue/es'; import { message } from 'jetlinks-ui-components';
import Io from './Io/index.vue' import Io from './Io/index.vue'
const list = ref([ const list = ref([
{ {

View File

@ -56,7 +56,7 @@ import { getTargetTypes, save, detail } from '@/api/rule-engine/configuration';
import { queryLevel } from '@/api/rule-engine/config'; import { queryLevel } from '@/api/rule-engine/config';
import { query } from '@/api/rule-engine/scene'; import { query } from '@/api/rule-engine/scene';
import { getImage } from '@/utils/comm'; import { getImage } from '@/utils/comm';
import { message } from 'ant-design-vue'; import { message } from 'jetlinks-ui-components';
import { useMenuStore } from '@/store/menu'; import { useMenuStore } from '@/store/menu';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { useAlarmConfigurationStore } from '@/store/alarm'; import { useAlarmConfigurationStore } from '@/store/alarm';

View File

@ -85,7 +85,7 @@
import { query } from '@/api/rule-engine/scene'; import { query } from '@/api/rule-engine/scene';
import { bindScene } from '@/api/rule-engine/configuration'; import { bindScene } from '@/api/rule-engine/configuration';
import { getImage } from '@/utils/comm'; import { getImage } from '@/utils/comm';
import { message } from 'ant-design-vue'; import { message } from 'jetlinks-ui-components';
const columns = [ const columns = [
{ {
title: '名称', title: '名称',

View File

@ -97,7 +97,7 @@ import { unbindScene } from '@/api/rule-engine/configuration';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import type { ActionsType } from '@/components/Table'; import type { ActionsType } from '@/components/Table';
import { getImage } from '@/utils/comm'; import { getImage } from '@/utils/comm';
import { message } from 'ant-design-vue/es'; import { message } from 'jetlinks-ui-components';
import Save from './save/index.vue'; import Save from './save/index.vue';
import { useAlarmConfigurationStore } from '@/store/alarm'; import { useAlarmConfigurationStore } from '@/store/alarm';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';

View File

@ -179,7 +179,7 @@ import {
import { queryLevel } from '@/api/rule-engine/config'; import { queryLevel } from '@/api/rule-engine/config';
import { Store } from 'jetlinks-store'; import { Store } from 'jetlinks-store';
import type { ActionsType } from '@/components/Table/index.vue'; import type { ActionsType } from '@/components/Table/index.vue';
import { message } from 'ant-design-vue'; import { message } from 'jetlinks-ui-components';
import { getImage } from '@/utils/comm'; import { getImage } from '@/utils/comm';
import { useMenuStore } from '@/store/menu'; import { useMenuStore } from '@/store/menu';
import encodeQuery from '@/utils/encodeQuery'; import encodeQuery from '@/utils/encodeQuery';

View File

@ -49,7 +49,7 @@ import { detail, queryHistoryList } from '@/api/rule-engine/log';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import moment from 'moment'; import moment from 'moment';
import type { ActionsType } from '@/components/Table/index.vue'; import type { ActionsType } from '@/components/Table/index.vue';
import { message } from 'ant-design-vue'; import { message } from 'jetlinks-ui-components';
import { useAlarmStore } from '@/store/alarm'; import { useAlarmStore } from '@/store/alarm';
import Info from './info.vue'; import Info from './info.vue';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';

View File

@ -61,7 +61,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { Empty } from 'ant-design-vue'; import { Empty } from 'jetlinks-ui-components';
import { getImage } from '@/utils/comm'; import { getImage } from '@/utils/comm';
import { useMenuStore } from '@/store/menu'; import { useMenuStore } from '@/store/menu';
import moment from 'moment'; import moment from 'moment';

View File

@ -41,7 +41,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { saveRule, modify } from '@/api/rule-engine/instance'; import { saveRule, modify } from '@/api/rule-engine/instance';
import { getImage } from '@/utils/comm'; import { getImage } from '@/utils/comm';
import { message } from 'ant-design-vue'; import { message } from 'jetlinks-ui-components';
const emit = defineEmits(['success', 'closeSave']); const emit = defineEmits(['success', 'closeSave']);
const props = defineProps({ const props = defineProps({

View File

@ -148,7 +148,7 @@ import {
} from '@/api/rule-engine/instance'; } from '@/api/rule-engine/instance';
import type { ActionsType } from '@/components/Table/index.vue'; import type { ActionsType } from '@/components/Table/index.vue';
import { getImage } from '@/utils/comm'; import { getImage } from '@/utils/comm';
import { message } from 'ant-design-vue'; import { message } from 'jetlinks-ui-components';
import Save from './Save/index.vue'; import Save from './Save/index.vue';
import { SystemConst } from '@/utils/consts'; import { SystemConst } from '@/utils/consts';
const params = ref<Record<string, any>>({}); const params = ref<Record<string, any>>({});

View File

@ -59,10 +59,12 @@ import { detail as deviceDetail } from '@/api/device/instance'
import Product from './Product.vue' import Product from './Product.vue'
import DeviceSelect from './DeviceSelect.vue' import DeviceSelect from './DeviceSelect.vue'
import Type from './Type.vue' import Type from './Type.vue'
import {continuousValue, handleTimerOptions, timeUnitEnum} from '@/views/rule-engine/Scene/Save/components/Timer/util' import { handleTimerOptions } from '@/views/rule-engine/Scene/Save/components/Timer/util'
import { Form } from 'jetlinks-ui-components'
type Emit = { type Emit = {
(e: 'cancel'): void (e: 'cancel'): void
(e: 'change', data: TriggerDevice): void
(e: 'save', data: TriggerDevice, options: Record<string, any>): void (e: 'save', data: TriggerDevice, options: Record<string, any>): void
} }
@ -76,6 +78,7 @@ interface AddModelType extends Omit<TriggerDevice, 'selectorValues'> {
metadata: metadataType, metadata: metadataType,
operator: TriggerDeviceOptions operator: TriggerDeviceOptions
} }
const formItemContext = Form.useInjectFormItemContext();
const emit = defineEmits<Emit>() const emit = defineEmits<Emit>()
const typeRef = ref() const typeRef = ref()
@ -165,26 +168,6 @@ const handleOptions = (data: TriggerDeviceOptions) => {
_options.when = when; _options.when = when;
_options.time = time; _options.time = time;
_options.extraTime = extraTime; _options.extraTime = extraTime;
// if (_timer.trigger === 'cron') {
// _options.time = _timer.cron;
// } else {
// // console.log('continuousValue', continuousValue(_timer.when! || [], _timer!.trigger))
// let whenStr = '';
// if (_timer.when!.length) {
// whenStr = _timer!.trigger === 'week' ? '' : '';
// const whenStrArr = continuousValue(_timer.when! || [], _timer!.trigger);
// const whenStrArr3 = whenStrArr.splice(0, 3);
// whenStr += whenStrArr3.join('');
// whenStr += `${_timer.when!.length}`;
// }
// _options.when = whenStr;
// if (_timer.once) {
// _options.time = _timer.once.time + ' 1';
// } else if (_timer.period) {
// _options.time = _timer.period.from + '-' + _timer.period.to;
// _options.extraTime = `${_timer.period.every}${timeUnitEnum[_timer.period.unit]}1`;
// }
// }
} }
if (data.operator === 'online') { if (data.operator === 'online') {
@ -257,12 +240,13 @@ const save = async (step?: number) => {
optionsCache.value.action = typeData.action optionsCache.value.action = typeData.action
const _options = handleOptions(typeData.data); const _options = handleOptions(typeData.data);
const data = { const data = {
operator: typeData.data, operation: typeData.data,
selector: addModel.selector, selector: addModel.selector,
selectorValues: addModel.selectorValues, selectorValues: addModel.selectorValues,
productId: addModel.productId productId: addModel.productId
} }
emit('save', data, _options) emit('save', data, _options)
formItemContext.onFieldChange()
} }
} }
} }

View File

@ -75,7 +75,6 @@ type Emit = {
} }
const params = ref({}) const params = ref({})
const context = inject('SceneDeviceAddModel')
const props = defineProps({ const props = defineProps({
rowKeys: { rowKeys: {
type: Array as PropType<SelectorValuesItem[]>, type: Array as PropType<SelectorValuesItem[]>,

View File

@ -2,7 +2,7 @@
<div class='device'> <div class='device'>
<j-form-item <j-form-item
:rules='rules' :rules='rules'
name='device' :name='["trigger", "device"]'
> >
<template #label> <template #label>
<TitleComponent data='触发规则' style='font-size: 14px;' /> <TitleComponent data='触发规则' style='font-size: 14px;' />
@ -13,9 +13,9 @@
> >
<Title :options='data.options.trigger' /> <Title :options='data.options.trigger' />
</AddButton> </AddButton>
<AddModel v-if='visible' @cancel='visible = false' @save='save' :value='data.trigger.device' :options='data.options.trigger' />
</j-form-item> </j-form-item>
<Terms /> <Terms />
<AddModel v-if='visible' @cancel='visible = false' @save='save' :value='data.trigger.device' :options='data.options.trigger' />
</div> </div>
</template> </template>
@ -28,6 +28,7 @@ import Title from '../components/Title.vue'
import Terms from '../components/Terms' import Terms from '../components/Terms'
import type { TriggerDevice } from '@/views/rule-engine/Scene/typings' import type { TriggerDevice } from '@/views/rule-engine/Scene/typings'
const sceneStore = useSceneStore() const sceneStore = useSceneStore()
const { data } = storeToRefs(sceneStore) const { data } = storeToRefs(sceneStore)
@ -45,6 +46,7 @@ const rules = [{
const save = (device: TriggerDevice, options: Record<string, any>) => { const save = (device: TriggerDevice, options: Record<string, any>) => {
data.value.trigger!.device = device data.value.trigger!.device = device
data.value.options!.trigger = options data.value.options!.trigger = options
visible.value = false
} }
</script> </script>

View File

@ -1,13 +1,15 @@
<template> <template>
<j-form-item <div class='actions-branches-item'>
:rules="actionRules" <j-form-item
:name="['branches', 0, 'then']" :rules="actionRules"
> :name="['branches', 0, 'then']"
<Action >
:thenOptions="data.branches ? data?.branches[0].then : []" <Action
:name="0" :thenOptions="data.branches ? data?.branches[0].then : []"
/> :name="0"
</j-form-item> />
</j-form-item>
</div>
</template> </template>
<script lang="ts" setup name='SceneSaveManual'> <script lang="ts" setup name='SceneSaveManual'>
@ -21,7 +23,6 @@ const { data } = storeToRefs(sceneStore);
const actionRules = [{ const actionRules = [{
validator(_: any, v?: BranchesThen[]) { validator(_: any, v?: BranchesThen[]) {
console.log(_, v)
if (!v || (v && !v.length) || (v && v.length && !v[0].actions.length)) { if (!v || (v && !v.length) || (v && v.length && !v[0].actions.length)) {
return Promise.reject('至少配置一个执行动作'); return Promise.reject('至少配置一个执行动作');
} }

View File

@ -14,15 +14,17 @@
<Title :options='data.options.trigger' /> <Title :options='data.options.trigger' />
</AddButton> </AddButton>
</j-form-item> </j-form-item>
<j-form-item <div class='actions-branches-item' >
:rules="actionRules" <j-form-item
:name="['branches', 0, 'then']" :rules="actionRules"
> :name="['branches', 0, 'then']"
<Action >
<Action
:thenOptions="data.branches ? data?.branches[0].then : []" :thenOptions="data.branches ? data?.branches[0].then : []"
:name="0" :name="0"
/> />
</j-form-item> </j-form-item>
</div>
<AddModel <AddModel
v-if="visible" v-if="visible"
@cancel='visible = false' @cancel='visible = false'
@ -79,6 +81,7 @@ const onActionUpdate = (_data: any, type: boolean) => {
const save = (_data: OperationTimer, options: Record<string, any>) => { const save = (_data: OperationTimer, options: Record<string, any>) => {
data.value.trigger!.timer = _data data.value.trigger!.timer = _data
data.value.options!.trigger = options data.value.options!.trigger = options
visible.value = false
} }
</script> </script>

View File

@ -83,6 +83,7 @@ import { inject } from 'vue'
import { useSceneStore } from 'store/scene' import { useSceneStore } from 'store/scene'
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { flattenDeep, set } from 'lodash-es' import { flattenDeep, set } from 'lodash-es'
import { Form } from 'jetlinks-ui-components'
const sceneStore = useSceneStore() const sceneStore = useSceneStore()
const { data: formModel } = storeToRefs(sceneStore) const { data: formModel } = storeToRefs(sceneStore)
@ -152,7 +153,7 @@ const paramsValue = reactive<TermsType>({
termType: props.value.termType, termType: props.value.termType,
value: props.value.value value: props.value.value
}) })
const formItemContext = Form.useInjectFormItemContext()
const showDelete = ref(false) const showDelete = ref(false)
const columnOptions: any = inject('filter-params') // const columnOptions: any = inject('filter-params') //
const termTypeOptions = ref<Array<{ id: string, name: string}>>([]) // const termTypeOptions = ref<Array<{ id: string, name: string}>>([]) //
@ -187,8 +188,18 @@ const handOptionByColumn = (option: any) => {
} }
watchEffect(() => { watchEffect(() => {
const option = getOption(columnOptions.value, paramsValue.column, 'id') if (!props.value.error && props.value.column) { // option
handOptionByColumn(option) const option = getOption(columnOptions.value, paramsValue.column, 'id')
if (option) {
handOptionByColumn(option)
} else {
emit('update:value', {
...props.value,
error: true
})
formItemContext.onFieldChange()
}
}
}) })
const showDouble = computed(() => { const showDouble = computed(() => {
@ -235,6 +246,7 @@ const columnSelect = (e: any) => {
) )
handleOptionsColumnsValue(termsColumns, _options) handleOptionsColumnsValue(termsColumns, _options)
emit('update:value', { ...paramsValue }) emit('update:value', { ...paramsValue })
formItemContext.onFieldChange()
} }
const termsTypeSelect = (e: { key: string }) => { const termsTypeSelect = (e: { key: string }) => {
@ -244,10 +256,12 @@ const termsTypeSelect = (e: { key: string }) => {
value: value value: value
} }
emit('update:value', { ...paramsValue }) emit('update:value', { ...paramsValue })
formItemContext.onFieldChange()
} }
const valueSelect = () => { const valueSelect = () => {
emit('update:value', { ...paramsValue }) emit('update:value', { ...paramsValue })
formItemContext.onFieldChange()
} }
const termAdd = () => { const termAdd = () => {

View File

@ -58,7 +58,7 @@ import { storeToRefs } from 'pinia'
import { useSceneStore } from 'store/scene' import { useSceneStore } from 'store/scene'
import DropdownButton from '../../components/DropdownButton' import DropdownButton from '../../components/DropdownButton'
import FilterItem from './FilterCondition.vue' import FilterItem from './FilterCondition.vue'
import { flattenDeep, isArray, set } from 'lodash-es' import { flattenDeep, isArray } from 'lodash-es'
import { provide } from 'vue' import { provide } from 'vue'
import { randomString } from '@/utils/utils' import { randomString } from '@/utils/utils'
import { useParams } from '@/views/rule-engine/Scene/Save/util' import { useParams } from '@/views/rule-engine/Scene/Save/util'
@ -172,8 +172,7 @@ const onDelete = () => {
const rules = [ const rules = [
{ {
validator(_: any, v?: Record<string, any>) { validator(_: any, v?: Record<string, any>) {
console.log('-----v',v) if (v !== undefined && !v.error) {
if (v !== undefined) {
if (!Object.keys(v).length) { if (!Object.keys(v).length) {
return Promise.reject(new Error('该数据已发生变更,请重新配置')) return Promise.reject(new Error('该数据已发生变更,请重新配置'))
} }

View File

@ -234,7 +234,7 @@
@click="onType('device')" @click="onType('device')"
> >
<template v-if="data?.device?.selector === 'fixed'"> <template v-if="data?.device?.selector === 'fixed'">
<div> <div style='display: flex; align-items: center;'>
<AIcon <AIcon
:type=" :type="
typeIconMap[ typeIconMap[
@ -248,13 +248,16 @@
}}</span> }}</span>
<AIcon <AIcon
type="icon-mubiao" type="icon-mubiao"
style="padding-right: 2px" style="padding:0 4px"
/> />
<Ellipsis style='max-width: 200px;margin-right: 12px;'>
{{data?.options?.name}}
</Ellipsis>
<Ellipsis style='max-width: 400px;'>
{{data?.options?.properties}}
</Ellipsis>
{{ {{
`${data?.options?.name} ${ `${
data?.options?.properties
}
${
( (
isBoolean( isBoolean(
data?.options?.propertiesValue, data?.options?.propertiesValue,
@ -349,7 +352,7 @@
/> />
</div> </div>
</template> </template>
<div v-else class="filter-add-button"> <div v-else class="filter-add-button" @click='addFilterParams'>
<AIcon type="PlusOutlined" style="padding-right: 4px" /> <AIcon type="PlusOutlined" style="padding-right: 4px" />
<span>添加过滤条件</span> <span>添加过滤条件</span>
</div> </div>
@ -394,6 +397,7 @@ import { useSceneStore } from '@/store/scene';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { iconMap, itemNotifyIconMap, typeIconMap } from './util'; import { iconMap, itemNotifyIconMap, typeIconMap } from './util';
import FilterGroup from './FilterGroup.vue'; import FilterGroup from './FilterGroup.vue';
import { randomString } from '@/utils/utils'
const sceneStore = useSceneStore(); const sceneStore = useSceneStore();
const { data: _data } = storeToRefs(sceneStore); const { data: _data } = storeToRefs(sceneStore);
@ -471,6 +475,30 @@ const onSave = (data: ActionsType, options: any) => {
visible.value = false; visible.value = false;
}; };
const addFilterParams = () => {
const item: any = {
type: 'and',
key: randomString(),
terms: [
{
column: undefined,
value: {
type: 'fixed',
value: undefined
},
termType: undefined,
type: 'and',
key: randomString()
}
]
}
if (_data.value.branches![props.branchesName].then[props.thenName].actions[props.name].terms) {
_data.value.branches![props.branchesName].then[props.thenName].actions[props.name].terms!.push(item)
} else {
_data.value.branches![props.branchesName].then[props.thenName].actions[props.name].terms = [item]
}
}
const onAdd = () => { const onAdd = () => {
visible.value = true; visible.value = true;
}; };

View File

@ -1,57 +1,57 @@
<template> <template>
<div class="actions"> <div class='actions'>
<div class="actions-title"> <div class='actions-title'>
<span>执行</span> <span>执行</span>
<ShakeLimit <ShakeLimit
v-if="props.openShakeLimit" v-if='props.openShakeLimit'
v-model:value="FormModel.branches[name].shakeLimit" v-model:value='FormModel.branches[name].shakeLimit'
/> />
</div> </div>
<div class="actions-warp"> <div class='actions-warp'>
<j-collapse v-model:activeKey="activeKeys"> <j-collapse v-model:activeKey='activeKeys'>
<j-collapse-panel key="1"> <j-collapse-panel key='1'>
<template #header> <template #header>
<span> <span>
串行 串行
<span class="panel-tip"> <span class='panel-tip'>
按顺序依次执行动作适用于基于动作输出参数判断是否执行后续动作的场景 按顺序依次执行动作适用于基于动作输出参数判断是否执行后续动作的场景
</span> </span>
</span> </span>
</template> </template>
<div class="actions-list"> <div class='actions-list'>
<List <List
type="serial" type='serial'
:branchesName="name" :branchesName='name'
:parallel="false" :parallel='false'
:actions=" :actions='
serialArray.length ? serialArray[0].actions : [] serialArray.length ? serialArray[0].actions : []
" '
@add="(_item) => onAdd(_item, false)" @add='(_item) => onAdd(_item, false)'
@delete="(_key) => onDelete(_key, false)" @delete='(_key) => onDelete(_key, false)'
/> />
</div> </div>
</j-collapse-panel> </j-collapse-panel>
<j-collapse-panel key="2"> <j-collapse-panel key='2'>
<template #header> <template #header>
<span> <span>
并行 并行
<span class="panel-tip"> <span class='panel-tip'>
同时执行所有动作适用于不需要关注执行动作先后顺序和结果的场景 同时执行所有动作适用于不需要关注执行动作先后顺序和结果的场景
</span> </span>
</span> </span>
</template> </template>
<div class="actions-list"> <div class='actions-list'>
<List <List
type="parallel" type='parallel'
:branchesName="name" :branchesName='name'
:parallel="true" :parallel='true'
:actions=" :actions='
parallelArray.length parallelArray.length
? parallelArray[0].actions ? parallelArray[0].actions
: [] : []
" '
@add="(_item) => onAdd(_item, true)" @add='(_item) => onAdd(_item, true)'
@delete="(_key) => onDelete(_key, true)" @delete='(_key) => onDelete(_key, true)'
/> />
</div> </div>
</j-collapse-panel> </j-collapse-panel>
@ -60,64 +60,66 @@
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang='ts' setup>
import ShakeLimit from '../components/ShakeLimit/index.vue'; import ShakeLimit from '../components/ShakeLimit/index.vue'
import { List } from './ListItem'; import { List } from './ListItem'
import { BranchesThen } from '../../typings' import { BranchesThen } from '../../typings'
import { PropType } from 'vue'; import { PropType } from 'vue'
import { randomString } from '@/utils/utils'; import { randomString } from '@/utils/utils'
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia'
import { useSceneStore } from 'store/scene' import { useSceneStore } from 'store/scene'
import { Form } from 'jetlinks-ui-components'
const sceneStore = useSceneStore() const sceneStore = useSceneStore()
const { data: FormModel } = storeToRefs(sceneStore) const { data: FormModel } = storeToRefs(sceneStore)
const formItemContext = Form.useInjectFormItemContext();
const props = defineProps({ const props = defineProps({
name: { name: {
type: Number, type: Number,
default: 0 default: 0
}, },
thenOptions: { thenOptions: {
type: Array as PropType<BranchesThen[]>, type: Array as PropType<BranchesThen[]>,
default: () => [], default: () => []
}, },
openShakeLimit: { openShakeLimit: {
type: Boolean, type: Boolean,
default: false default: false
}, }
}); })
const emit = defineEmits(['update', 'add']); const emit = defineEmits(['update', 'add'])
const activeKeys = ref<string[]>(['1']); const activeKeys = ref<string[]>(['1'])
const parallelArray = ref<BranchesThen[]>([]); const parallelArray = ref<BranchesThen[]>([])
const serialArray = ref<BranchesThen[]>([]); const serialArray = ref<BranchesThen[]>([])
const lock = ref<boolean>(false); const lock = ref<boolean>(false)
watch( watch(
() => props.thenOptions, () => props.thenOptions,
(newVal) => { (newVal) => {
parallelArray.value = newVal.filter((item) => item.parallel); parallelArray.value = newVal.filter((item) => item.parallel)
serialArray.value = newVal.filter((item) => !item.parallel); serialArray.value = newVal.filter((item) => !item.parallel)
const isSerialActions = serialArray.value.some((item) => { const isSerialActions = serialArray.value.some((item) => {
return !!item.actions.length; return !!item.actions.length
}); })
if ( if (
!lock.value && !lock.value &&
parallelArray.value.length && parallelArray.value.length &&
(!serialArray.value.length || !isSerialActions) (!serialArray.value.length || !isSerialActions)
) { ) {
activeKeys.value = ['2']; activeKeys.value = ['2']
lock.value = true; lock.value = true
} }
}, },
{ {
deep: true, deep: true,
immediate: true, immediate: true
}, }
); )
const onDelete = (_key: string, _parallel: boolean) => { const onDelete = (_key: string, _parallel: boolean) => {
const thenName = props.thenOptions.findIndex(item => item.parallel === _parallel) const thenName = props.thenOptions.findIndex(item => item.parallel === _parallel)
@ -125,7 +127,8 @@ const onDelete = (_key: string, _parallel: boolean) => {
if (actionIndex !== -1) { if (actionIndex !== -1) {
FormModel.value.branches?.[props.name].then?.[thenName].actions.splice(actionIndex!, 1) FormModel.value.branches?.[props.name].then?.[thenName].actions.splice(actionIndex!, 1)
} }
}; formItemContext.onFieldChange()
}
const onAdd = (actionItem: any, _parallel: boolean) => { const onAdd = (actionItem: any, _parallel: boolean) => {
const thenName = props.thenOptions.findIndex(item => item.parallel === _parallel) const thenName = props.thenOptions.findIndex(item => item.parallel === _parallel)
@ -146,30 +149,31 @@ const onAdd = (actionItem: any, _parallel: boolean) => {
} }
FormModel.value.branches?.[props.name].then.push(newThenItem) FormModel.value.branches?.[props.name].then.push(newThenItem)
} }
}; formItemContext.onFieldChange()
}
</script> </script>
<style lang="less" scoped> <style lang='less' scoped>
.actions { .actions {
.actions-title { .actions-title {
display: flex; display: flex;
gap: 16px; gap: 16px;
align-items: center; align-items: center;
margin-bottom: 16px; margin-bottom: 16px;
font-weight: 800; font-weight: 800;
font-size: 14px; font-size: 14px;
line-height: 32px; line-height: 32px;
} }
.actions-warp { .actions-warp {
.actions-list { .actions-list {
width: 100%; width: 100%;
}
} }
}
.panel-tip { .panel-tip {
padding-left: 8px; padding-left: 8px;
color: rgba(#000, 0.45); color: rgba(#000, 0.45);
} }
} }
</style> </style>

View File

@ -9,7 +9,9 @@
<slot :label='label'> <slot :label='label'>
<div :class='dropdownButtonClass' > <div :class='dropdownButtonClass' >
<AIcon v-if='!!icon' :type='icon' /> <AIcon v-if='!!icon' :type='icon' />
<Ellipsis style='max-width: 220px;'>
{{ label }} {{ label }}
</Ellipsis>
</div> </div>
</slot> </slot>
</div> </div>

View File

@ -9,7 +9,9 @@
<slot :label='label'> <slot :label='label'>
<div class='dropdown-button value'> <div class='dropdown-button value'>
<AIcon v-if='!!icon' :type='icon' /> <AIcon v-if='!!icon' :type='icon' />
{{ label }} <Ellipsis style='max-width: 220px;'>
{{ label }}
</Ellipsis>
</div> </div>
</slot> </slot>
</div> </div>

View File

@ -56,7 +56,7 @@
<Action <Action
:name='name' :name='name'
:openShakeLimit="true" :openShakeLimit="true"
:thenOptions='FormModel.branches[name].then' :thenOptions='FormModel.branches[name]?.then'
/> />
</j-form-item> </j-form-item>
</div> </div>
@ -64,7 +64,7 @@
</div> </div>
</template> </template>
<script lang='ts' setup name='Branchs'> <script lang='ts' setup name='Branches'>
import type { PropType } from 'vue' import type { PropType } from 'vue'
import type { ActionBranchesProps } from '@/views/rule-engine/Scene/typings' import type { ActionBranchesProps } from '@/views/rule-engine/Scene/typings'
import TermsItem from './TermsItem.vue' import TermsItem from './TermsItem.vue'

View File

@ -44,6 +44,7 @@
:tabsOptions='tabsOptions' :tabsOptions='tabsOptions'
v-model:value='paramsValue.value.value' v-model:value='paramsValue.value.value'
v-model:source='paramsValue.value.source' v-model:source='paramsValue.value.source'
@select='valueSelect'
/> />
<ParamsDropdown <ParamsDropdown
v-else v-else
@ -54,6 +55,7 @@
:tabsOptions='tabsOptions' :tabsOptions='tabsOptions'
v-model:value='paramsValue.value.value' v-model:value='paramsValue.value.value'
v-model:source='paramsValue.value.source' v-model:source='paramsValue.value.source'
@select='valueSelect'
/> />
<j-popconfirm title='确认删除?' @confirm='onDelete'> <j-popconfirm title='确认删除?' @confirm='onDelete'>
<div v-show='showDelete' class='button-delete'> <AIcon type='CloseOutlined' /></div> <div v-show='showDelete' class='button-delete'> <AIcon type='CloseOutlined' /></div>
@ -77,9 +79,11 @@ import { inject } from 'vue'
import { ContextKey } from './util' import { ContextKey } from './util'
import { useSceneStore } from 'store/scene' import { useSceneStore } from 'store/scene'
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { Form } from 'jetlinks-ui-components'
const sceneStore = useSceneStore() const sceneStore = useSceneStore()
const { data: formModel } = storeToRefs(sceneStore) const { data: formModel } = storeToRefs(sceneStore)
const formItemContext = Form.useInjectFormItemContext();
type Emit = { type Emit = {
(e: 'update:value', data: TermsType): void (e: 'update:value', data: TermsType): void
@ -137,10 +141,10 @@ const props = defineProps({
const emit = defineEmits<Emit>() const emit = defineEmits<Emit>()
const paramsValue = reactive<TermsType>({ const paramsValue = reactive<TermsType>({
column: props.value.column, column: props.value?.column,
type: props.value.type, type: props.value?.type,
termType: props.value.termType, termType: props.value?.termType,
value: props.value.value value: props.value?.value
}) })
const showDelete = ref(false) const showDelete = ref(false)
@ -183,8 +187,18 @@ const handOptionByColumn = (option: any) => {
} }
watchEffect(() => { watchEffect(() => {
const option = getOption(columnOptions.value, paramsValue.column, 'column') if (!props.value.error && props.value.column) { // option
handOptionByColumn(option) const option = getOption(columnOptions.value, paramsValue.column, 'column')
if (option) {
handOptionByColumn(option)
} else {
emit('update:value', {
...props.value,
error: true
})
formItemContext.onFieldChange()
}
}
}) })
const showDouble = computed(() => { const showDouble = computed(() => {
@ -216,6 +230,7 @@ const columnSelect = () => {
value: undefined value: undefined
} }
emit('update:value', { ...paramsValue }) emit('update:value', { ...paramsValue })
formItemContext.onFieldChange()
} }
const termsTypeSelect = (e: { key: string }) => { const termsTypeSelect = (e: { key: string }) => {
@ -225,6 +240,11 @@ const termsTypeSelect = (e: { key: string }) => {
value: value value: value
} }
emit('update:value', { ...paramsValue }) emit('update:value', { ...paramsValue })
formItemContext.onFieldChange()
}
const valueSelect = () => {
formItemContext.onFieldChange()
} }
const termAdd = () => { const termAdd = () => {

View File

@ -23,7 +23,7 @@
@deleteAll='branchesDeleteAll' @deleteAll='branchesDeleteAll'
/> />
<div v-else class='actions-terms-warp' :style='{ marginTop: data.branches.length === 2 ? 0 : 24 }'> <div v-else class='actions-terms-warp' :style='{ marginTop: data.branches.length === 2 ? 0 : 24 }'>
<div class='actions-terms-title' style='padding: 0'> <div class='actions-terms-title' style='padding: 0;margin-bottom: 24px;'>
否则 否则
</div> </div>
<div class='actions-terms-options no-when'> <div class='actions-terms-options no-when'>
@ -32,6 +32,18 @@
</div> </div>
</template> </template>
</template> </template>
<div v-else class='actions-branches-item'>
<j-form-item
:name='["branches", 0, "then"]'
:rules='thenRules'
>
<Action
:name='0'
:openShakeLimit="true"
:thenOptions='data.branches[0]?.then'
/>
</j-form-item>
</div>
</div> </div>
</template> </template>
@ -40,14 +52,14 @@ import { storeToRefs } from 'pinia';
import { useSceneStore } from 'store/scene' import { useSceneStore } from 'store/scene'
import { cloneDeep } from 'lodash-es' import { cloneDeep } from 'lodash-es'
import { provide } from 'vue' import { provide } from 'vue'
import { ContextKey, handleParamsData } from './util' import { ContextKey, handleParamsData, thenRules } from './util'
import { getParseTerm } from '@/api/rule-engine/scene' import { getParseTerm } from '@/api/rule-engine/scene'
import type { FormModelType } from '@/views/rule-engine/Scene/typings' import type { FormModelType } from '@/views/rule-engine/Scene/typings'
import Branches from './Branchs.vue' import Branches from './Branches.vue'
import Action from '../../action/index.vue'
const sceneStore = useSceneStore() const sceneStore = useSceneStore()
const { data } = storeToRefs(sceneStore) const { data } = storeToRefs(sceneStore)
const open = ref(false) const open = ref(false)
const columnOptions = ref<any>([]) const columnOptions = ref<any>([])
@ -56,6 +68,11 @@ provide(ContextKey, columnOptions)
const change = (e: boolean) => { const change = (e: boolean) => {
open.value = e open.value = e
if (!e) {
data.value.branches!.length = 1
} else {
data.value.branches!.push(null as any)
}
} }
const queryColumn = (dataModel: FormModelType) => { const queryColumn = (dataModel: FormModelType) => {
@ -96,9 +113,8 @@ const branchesDeleteAll = () => {
} }
watchEffect(() => { watchEffect(() => {
console.log(data.value.trigger, data.value.trigger?.device)
if (data.value.trigger?.device) { if (data.value.trigger?.device) {
queryColumn(data.value) queryColumn({ trigger: data.value.trigger })
} }
}) })

View File

@ -29,6 +29,7 @@
v-for='(item, index) in termsData' v-for='(item, index) in termsData'
:key='item.key' :key='item.key'
:name='["branches", branchName, "when", whenName, "terms", index]' :name='["branches", branchName, "when", whenName, "terms", index]'
:rules='rules'
> >
<ParamsItem <ParamsItem
v-model:value='formModel.branches[branchName].when[whenName].terms[index]' v-model:value='formModel.branches[branchName].when[whenName].terms[index]'
@ -39,7 +40,6 @@
:termsName='name' :termsName='name'
:whenName='whenName' :whenName='whenName'
:branchName='branchName' :branchName='branchName'
@change='paramsChange'
/> />
</j-form-item> </j-form-item>
</div> </div>
@ -60,6 +60,7 @@ import DropdownButton from '../DropdownButton'
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { useSceneStore } from 'store/scene' import { useSceneStore } from 'store/scene'
import ParamsItem from './ParamsItem.vue' import ParamsItem from './ParamsItem.vue'
import { isArray } from 'lodash-es'
const sceneStore = useSceneStore() const sceneStore = useSceneStore()
const { data: formModel } = storeToRefs(sceneStore) const { data: formModel } = storeToRefs(sceneStore)
@ -103,6 +104,41 @@ const props = defineProps({
} }
}) })
const rules = [
{
validator(_: any, v: any) {
if (v !== undefined && !v.error) {
if (!Object.keys(v).length) {
return Promise.reject(new Error('该数据已发生变更,请重新配置'));
}
if (!v.column) {
return Promise.reject(new Error('请选择参数'));
}
if (!v.termType) {
return Promise.reject(new Error('请选择操作符'));
}
if (v.value === undefined) {
return Promise.reject(new Error('请选择或输入参数值'));
} else {
if (
isArray(v.value.value) &&
v.value.value.some((_v: any) => _v === undefined)
) {
return Promise.reject(new Error('请选择或输入参数值'));
} else if (v.value.value === undefined) {
return Promise.reject(new Error('请选择或输入参数值'));
}
}
} else {
return Promise.reject(new Error('请选择参数'));
}
return Promise.resolve();
},
}
]
const showDelete = ref(false) const showDelete = ref(false)
const termsData = computed(() => { const termsData = computed(() => {
@ -125,10 +161,6 @@ const onDelete = () => {
formModel.value.branches?.[props.branchName]?.when?.splice(props.name, 1) formModel.value.branches?.[props.branchName]?.when?.splice(props.name, 1)
} }
const paramsChange = () => {
}
const addTerms = () => { const addTerms = () => {
const terms = { const terms = {
type: 'and', type: 'and',

View File

@ -106,6 +106,10 @@
} }
} }
} }
.actions-branches-item {
width: 100%;
}
} }
.terms-params { .terms-params {
@ -131,11 +135,15 @@
.ant-form-item { .ant-form-item {
margin-bottom: 8px; margin-bottom: 8px;
&:not(:first-child) { &:not(:nth-child(2)) {
.ant-form-item-explain-error { .ant-form-item-explain-error {
padding-left: 80px !important; padding-left: 80px !important;
} }
} }
&.ant-form-item-has-error {
margin-bottom: 0;
}
} }
} }
@ -220,6 +228,9 @@
} }
} }
} }
.actions-branches-item {
width: 75%;
}
} }
} }

View File

@ -9,4 +9,16 @@ export const handleParamsData = (data: any[], key: string = 'column'): any[] =>
children: handleParamsData(item.children, key) children: handleParamsData(item.children, key)
} }
}) || [] }) || []
} }
export const thenRules = [{
validator(_: string, value: any) {
if (!value || (value && !value.length)) {
return Promise.reject('至少配置一个执行动作')
} else {
const isActions = value.some((item: any) => item.actions && item.actions.length)
return isActions ? Promise.resolve() : Promise.reject('至少配置一个执行动作');
}
return Promise.resolve();
}
}]

View File

@ -1,370 +0,0 @@
<template>
<div class="card">
<div
class="card-warp"
:class="{ active: active ? 'active' : '' }"
@click="handleClick"
>
<div class="card-type">
<div class="card-type-text"><slot name="type"></slot></div>
</div>
<div class="card-content">
<div style="display: flex">
<!-- 图片 -->
<div class="card-item-avatar">
<slot name="img"> </slot>
</div>
<!-- 内容 -->
<div class="card-item-body">
<slot name="title"></slot>
<span class="subTitle">
<slot name="subTitle"></slot>
</span>
</div>
</div>
<!-- 勾选 -->
<div v-if="active" class="checked-icon">
<div>
<AIcon type="CheckOutlined" />
</div>
</div>
<!-- 状态 -->
<div
v-if="showStatus"
class="card-state"
:class="statusNames ? statusNames[status] : ''"
>
<div class="card-state-content">
<BadgeStatus
:status="status"
:text="statusText"
:statusNames="statusNames"
></BadgeStatus>
</div>
</div>
</div>
</div>
<!-- 按钮 -->
<slot name="bottom-tool">
<div
v-if="showTool && actions && actions.length"
class="card-tools"
>
<div
v-for="item in actions"
:key="item.key"
class="card-button"
:class="{
delete: item.key === 'delete',
}"
>
<slot name="actions" v-bind="item"></slot>
</div>
</div>
</slot>
</div>
</template>
<script setup lang="ts">
import BadgeStatus from '@/components/BadgeStatus/index.vue';
import { StatusColorEnum } from '@/utils/consts.ts';
import type { ActionsType } from '@/components/Table/index.vue';
import { PropType } from 'vue';
type EmitProps = {
(e: 'click', data: Record<string, any>): void;
};
type TableActionsType = Partial<ActionsType>;
const emit = defineEmits<EmitProps>();
const props = defineProps({
value: {
type: Object as PropType<Record<string, any>>,
default: () => {},
},
showStatus: {
type: Boolean,
default: true,
},
showTool: {
type: Boolean,
default: true,
},
statusText: {
type: String,
default: '正常',
},
status: {
type: [String, Number],
default: 'default',
},
statusNames: {
type: Object,
},
actions: {
type: Array as PropType<TableActionsType[]>,
default: () => [],
},
active: {
type: Boolean,
default: false,
},
});
const handleClick = () => {
emit('click', props.value);
};
</script>
<style lang="less" scoped>
.card {
width: 100%;
background-color: #fff;
.checked-icon {
position: absolute;
right: -22px;
bottom: -22px;
z-index: 2;
width: 44px;
height: 44px;
color: #fff;
background-color: red;
background-color: #2f54eb;
transform: rotate(-45deg);
> div {
position: relative;
height: 100%;
transform: rotate(45deg);
> span {
position: absolute;
top: 6px;
left: 6px;
font-size: 12px;
}
}
}
.card-warp {
position: relative;
border: 1px solid #e6e6e6;
overflow: hidden;
&:hover {
cursor: pointer;
box-shadow: 0 0 24px rgba(#000, 0.1);
.card-mask {
visibility: visible;
}
}
&.active {
position: relative;
border: 1px solid #2f54eb;
}
.card-type {
position: absolute;
top: 0;
left: -14px;
height: 32px;
padding: 0 30px;
color: rgba(0, 0, 0, 0.65);
line-height: 32px;
background-color: rgba(0, 0, 0, 0.06);
transform: skewX(-45deg);
.card-type-text {
display: flex;
align-items: center;
justify-content: center;
transform: skewX(45deg);
}
}
.card-content {
position: relative;
padding: 43px 12px 19px 30px;
overflow: hidden;
.card-item-avatar {
margin-right: 16px;
}
.card-item-body {
display: flex;
flex-direction: column;
flex-grow: 1;
width: 0;
.subTitle {
color: rgba(0, 0, 0, 0.65);
font-size: 14px;
margin-top: 10px;
}
}
.card-state {
position: absolute;
top: 40px;
right: -12px;
display: flex;
justify-content: center;
width: 100px;
padding: 2px 0;
background-color: rgba(#5995f5, 0.15);
transform: skewX(45deg);
&.success {
background-color: @success-color-deprecated-bg;
}
&.warning {
background-color: rgba(#ff9000, 0.1);
}
&.error {
background-color: rgba(#e50012, 0.1);
}
.card-state-content {
transform: skewX(-45deg);
}
}
:deep(.card-item-content-title) {
cursor: pointer;
font-size: 16px;
font-weight: 700;
color: @primary-color;
width: calc(100% - 100px);
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
:deep(.card-item-heard-name) {
font-weight: 700;
font-size: 16px;
margin-bottom: 12px;
}
:deep(.card-item-content-text) {
color: rgba(0, 0, 0, 0.75);
font-size: 12px;
}
}
}
&.item-active {
position: relative;
color: #2f54eb;
.checked-icon {
display: block;
}
.card-warp {
border: 1px solid #2f54eb;
}
}
.card-tools {
display: flex;
margin-top: 8px;
.card-button {
display: flex;
flex-grow: 1;
& > :deep(span, button) {
width: 100%;
border-radius: 0;
}
:deep(button) {
width: 100%;
border-radius: 0;
background: #f6f6f6;
border: 1px solid #e6e6e6;
color: #2f54eb;
&:hover {
background-color: @primary-color-hover;
border-color: @primary-color-hover;
span {
color: #fff !important;
}
}
&:active {
background-color: @primary-color-active;
border-color: @primary-color-active;
span {
color: #fff !important;
}
}
}
&:not(:last-child) {
margin-right: 8px;
}
&.delete {
flex-basis: 60px;
flex-grow: 0;
:deep(button) {
background: @error-color-deprecated-bg;
border: 1px solid @error-color-outline;
span {
color: @error-color !important;
}
&:hover {
background-color: @error-color-hover;
span {
color: #fff !important;
}
}
&:active {
background-color: @error-color-active;
span {
color: #fff !important;
}
}
}
}
:deep(button[disabled]) {
background: @disabled-bg;
border-color: @disabled-color;
span {
color: @disabled-color !important;
}
&:hover {
background-color: @disabled-active-bg;
}
&:active {
background-color: @disabled-active-bg;
}
}
}
}
}
</style>

View File

@ -132,7 +132,6 @@ import { query, _delete, _action, _execute } from '@/api/rule-engine/scene';
import { message } from 'ant-design-vue'; import { message } from 'ant-design-vue';
import type { ActionsType } from '@/components/Table'; import type { ActionsType } from '@/components/Table';
import { getImage } from '@/utils/comm'; import { getImage } from '@/utils/comm';
import SceneCard from './SceneCard.vue';
import BadgeStatus from '@/components/BadgeStatus/index.vue'; import BadgeStatus from '@/components/BadgeStatus/index.vue';
const menuStory = useMenuStore(); const menuStory = useMenuStore();

View File

@ -202,6 +202,7 @@ export type TermsType = {
options?: any[]; options?: any[];
terms?: TermsType[]; terms?: TermsType[];
key?: string; key?: string;
error?: boolean
}; };
export type PlatformRelation = { export type PlatformRelation = {

View File

@ -406,13 +406,16 @@ const uploader: uploaderType = {
'image/pjp', 'image/pjp',
'image/pjpeg', 'image/pjpeg',
], ],
// imageTypes: ['.jpg','.png','.jfif','.pjp','.pjpeg','.jpeg'],
iconTypes: ['image/x-icon'], iconTypes: ['image/x-icon'],
// logo // logo
beforeLogoUpload: ({ size, type }: File) => { // beforeLogoUpload: ({ size, type }: File) => {
beforeLogoUpload: (file: File) => {
console.log('file: ', file);
const typeBool = const typeBool =
uploader.imageTypes.filter((typeStr) => type.includes(typeStr)) uploader.imageTypes.filter((typeStr) => file.type.includes(typeStr))
.length > 0; .length > 0;
const sizeBool = size / 1024 / 1024 < 4; const sizeBool = file.size / 1024 / 1024 < 4;
if (!typeBool) { if (!typeBool) {
message.error(`请上传.jpg.png.jfif.pjp.pjpeg.jpeg格式的图片`); message.error(`请上传.jpg.png.jfif.pjp.pjpeg.jpeg格式的图片`);
} else if (!sizeBool) { } else if (!sizeBool) {

View File

@ -92,11 +92,9 @@ export default defineConfig(({ mode}) => {
[env.VITE_APP_BASE_API]: { [env.VITE_APP_BASE_API]: {
// target: 'http://192.168.33.22:8800', // target: 'http://192.168.33.22:8800',
// target: 'http://192.168.32.244:8881', // target: 'http://192.168.32.244:8881',
// target: 'http://47.112.135.104:5096', // opcua // target: 'http://120.77.179.54:8844', // 120测试
target: 'http://120.77.179.54:8844', // 120测试 target: 'http://192.168.33.46:8844', // 本地开发环境
// target: 'http://47.108.63.174:8845', // 测试 ws: 'ws://192.168.33.46:8844',
// target: 'http://120.77.179.54:8844',
ws: 'ws://120.77.179.54:8844',
changeOrigin: true, changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '') rewrite: (path) => path.replace(/^\/api/, '')
} }