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>
<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="left">
<Editor
@ -20,7 +20,7 @@
<Operator :id="id" @add-operator-value="addOperatorValue"/>
</div>
</div>
</a-modal>
</j-modal>
</template>
<script setup lang="ts" name="Advance">
import Editor from '../Editor/index.vue'

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
<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>
<script setup lang="ts" name="InputSelect">
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="value.range">
<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"
style="width: 100%;"></j-input-number>
style="width: 100%;" placeholder="请输入"></j-input-number>
</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 v-else-if="type === 'date'">
<j-range-picker v-if="value.range" show-time v-model:value="value.value" size="small" />
<j-date-picker v-else 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" placeholder="请输入"/>
</template>
<template v-else-if="type === 'boolean'">
<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) => {
if (e.target.checked) {

View File

@ -1,28 +1,30 @@
<template>
<j-popover :visible="visible" placement="left">
<template #title>
<div style="display: flex; justify-content: space-between; align-items: center;">
<div style="width: 150px;">配置元素</div>
<div @click="visible = false"><AIcon type="CloseOutlined" /></div>
</div>
</template>
<template #content>
<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>
<j-button type="dashed" block @click="visible = true">
<j-popover :visible="visible" placement="left">
<template #title>
<div style="display: flex; justify-content: space-between; align-items: center;">
<div style="width: 150px;">配置元素</div>
<div @click="visible = false">
<AIcon type="CloseOutlined" />
</div>
</div>
</div>
</template>
<j-button type="dashed" block @click="visible = true">
</template>
<template #content>
<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" />
</j-button>
</j-popover>
</j-popover>
</j-button>
</template>
<script setup lang="ts" name="ArrayParam">
import ValueTypeForm from '@/views/device/components/Metadata/Base/Edit/ValueTypeForm.vue';

View File

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

View File

@ -18,13 +18,13 @@
{ required: true, message: '请输入Value' },
{ 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 label="Text" :name="name.concat([index, 'text'])" :rules="[
{ required: true, message: '请输入Text' },
{ 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>
</div>
</template>

View File

@ -22,15 +22,15 @@
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 label="名称" :name="name.concat([index, 'name'])" :rules="[
{ required: true, message: '请输入名称' },
{ 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>
<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>
</div>
</template>
@ -69,6 +69,10 @@ const props = defineProps({
name: {
type: Array as PropType<(string | number)[]>,
default: () => ([])
},
isSub: {
type: Boolean,
default: true
}
})

View File

@ -6,7 +6,7 @@
{{ `#${index + 1}.` }}
</div>
<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>
<div class="edit-title" style="display: flex; justify-content: space-between; align-items: center;">
<div style="width: 150px;">配置参数</div>
@ -24,13 +24,13 @@
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 label="名称" :name="name.concat([index, 'name'])" :rules="[
{ required: true, message: '请输入名称' },
{ 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 label="指标值" :name="name.concat([index, 'value'])" :rules="[
{ required: true, validator: () => validateIndicator(_value[index]), message: '请输入指标值' }
@ -41,7 +41,7 @@
</div>
</template>
<div class="item-edit" @click="handleEdit(index)">
{{ item.name || '配置参数' }}
<Ellipsis>{{ item.name || '配置参数' }}</Ellipsis>
<AIcon type="EditOutlined" class="item-icon" />
</div>
</j-popover>
@ -118,7 +118,7 @@ const validateIndicator = (value: any) => {
return Promise.reject(new Error('请输入指标值'));
}
} else {
if (value?.value === '' || value?.value === undefined) {
if (!value?.value) {
return Promise.reject(new Error('请输入指标值'));
}
}
@ -151,6 +151,10 @@ const change = (visible: boolean) => {
// }
.item-edit {
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
max-width: 240px;
}
.item-icon {

View File

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

View File

@ -4,7 +4,7 @@
* @returns {boolean}
*/
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})?$/
return phone.test(value) || mobile.test(value)
}

View File

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

View File

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

View File

@ -47,7 +47,7 @@
</template>
<script setup lang="ts" name="modifyModal">
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 { ValidateErrorEntity } from 'ant-design-vue/es/form/interface';
import { list } from '@/api/iot-card/home';

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -147,7 +147,7 @@
import { category } from '@/api/device/product';
import { Form } from 'ant-design-vue';
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 { useProductStore } from '@/store/product';
import { filterTreeSelectNode, filterSelectNode } from '@/utils/comm';

View File

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

View File

@ -7,13 +7,13 @@
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 label="名称" name="name" :rules="[
{ required: true, message: '请输入名称' },
{ 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>
<template v-if="modelType === 'properties'">
<value-type-form :name="['valueType']" v-model:value="value.valueType" key="property" title="数据类型"
@ -33,7 +33,7 @@
<j-form-item label="输入参数" name="inputs" :rules="[
{ 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>
<value-type-form :name="['output']" v-model:value="value.output" key="function" title="输出参数" :required="false"></value-type-form>
</template>
@ -41,7 +41,7 @@
<j-form-item label="级别" :name="['expands', 'level']" :rules="[
{ 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>
<value-type-form :name="['valueType']" v-model:value="value.valueType" key="function" title="输出参数" only-object></value-type-form>
</template>
@ -50,13 +50,13 @@
<j-form-item label="标签类型" :name="['expands', 'type']" :rules="[
{ 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>
</template>
<j-form-item label="说明" name="description" :rules="[
{ 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>
</template>
<script setup lang="ts" name="BaseForm">

View File

@ -19,9 +19,17 @@
</j-form-item>
<j-form-item
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: '请输入指标配置' }
]">
<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"
:name="name.concat(['metrics'])"></metrics-param>
</j-form-item>

View File

@ -4,14 +4,14 @@
]">
<j-select v-model:value="_value.type" :disabled="onlyObject"
:options="onlyObject ? eventDataTypeList : _dataTypeList" size="small"
@change="changeType"></j-select>
@change="changeType" :placeholder="`请选择${title}`"></j-select>
</j-form-item>
<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>
</j-form-item>
<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"
style="width: 100%"></j-input-number>
style="width: 100%" placeholder="请输入精度"></j-input-number>
</j-form-item>
<j-form-item label="布尔值" name="booleanConfig" v-if="['boolean'].includes(_value.type)">
<BooleanParam :name="name" v-model:value="_value"></BooleanParam>
@ -26,12 +26,13 @@
<j-space>
最大长度
<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-space>
</template>
<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 label="元素配置" :name="name.concat(['elementType'])" v-if="['array'].includes(_value.type)" :rules="[
{ validator: validateArray }
@ -47,7 +48,7 @@
:rules="[
{ 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>
</template>
<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) {
_value.value.scale = 2
}
if (['file'].includes(val as string)) {
_value.value.fileType = _value.value.fileType || 'url'
}
emit('changeType', val as string)
}

View File

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

View File

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

View File

@ -16,7 +16,7 @@
<Edit v-if="metadataStore.model.edit" :type="target" :tabs="type" @refresh="refreshMetadata"></Edit>
</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">
<template #bodyCell="{ column, record }">
<template v-if="column.dataIndex === 'level'">
@ -44,7 +44,7 @@
}">
<AIcon type="EditOutlined" />
</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="{
title: '确认删除?', onConfirm: async () => {
await removeItem(record);
@ -57,7 +57,7 @@
</j-space>
</template>
</template>
</a-table>
</j-table>
</template>
<script setup lang="ts" name="BaseMetadata">
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 { asyncUpdateMetadata, removeMetadata } from '../metadata'
import Edit from './Edit/index.vue'
import { ColumnProps } from 'ant-design-vue/es/table'
interface Props {
type: MetadataType;
target: 'product' | 'device';
@ -97,13 +98,12 @@ const expandsType = ref({
write: '写',
report: '上报',
});
const actions = [
const actions: ColumnProps[] = [
{
title: '操作',
align: 'left',
width: 200,
dataIndex: 'action',
scopedSlots: true,
},
];
const pagination = {
@ -116,14 +116,14 @@ const pagination = {
size: 'small',
} as TablePaginationConfig
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 handleSearch = (searchValue: string) => {
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
} else {
data.value = items.value
data.value = items.value[type]
}
}
@ -215,6 +215,6 @@ const removeItem = async (record: MetadataItem) => {
.table-header {
display: flex;
justify-content: space-between;
padding: 16px 0;
padding: 16px;
}
</style>

View File

@ -18,7 +18,7 @@
<j-tabs @change="handleConvertMetadata" destroy-inactive-tab-pane>
<j-tab-pane v-for="item in codecs" :key="item.id" :tab="item.name">
<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>
</j-tab-pane>
</j-tabs>

View File

@ -17,8 +17,7 @@
<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-form-item>
<j-form-item label="物模型类型" v-bind="validateInfos.metadata"
v-if="type === 'device' || formModel.type === 'import'">
<j-form-item label="物模型类型" v-bind="validateInfos.metadata" v-if="type === 'device' || formModel.type === 'import'">
<j-select v-model:value="formModel.metadata">
<j-select-option value="jetlinks">Jetlinks物模型</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-input v-model:value="formModel.upload">
<template #addonAfter>
<j-upload v-model:file-list="fileList" :before-upload="beforeUpload" accept=".json"
:show-upload-list="false" :action="FILE_UPLOAD" @change="fileChange"
:headers="{ 'X-Access-Token': getToken()}">
<j-upload v-model:file-list="fileList" :before-upload="beforeUpload" accept=".json" :show-upload-list="false"
:action="FILE_UPLOAD" @change="fileChange" :headers="{ 'X-Access-Token': getToken() }">
<AIcon type="UploadOutlined" class="upload-button" />
</j-upload>
</template>
</j-input>
</j-form-item>
<j-form-item label="物模型" v-bind="validateInfos.import" v-if="formModel.metadataType === 'script'">
<MonacoEditor v-model="formModel.import" theme="vs" style="height: 300px"></MonacoEditor>
<j-form-item v-bind="validateInfos.import" v-if="(type === 'device' || formModel.type === 'import') && formModel.metadataType === 'script'">
<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>
</j-modal>
@ -60,7 +66,6 @@ import { useInstanceStore } from '@/store/instance'
import { useProductStore } from '@/store/product';
import { FILE_UPLOAD } from '@/api/comm';
import { getToken } from '@/utils/comm';
import MonacoEditor from '@/components/MonacoEditor/index.vue'
import { useMetadataStore } from '@/store/metadata';
const route = useRoute()
@ -250,7 +255,7 @@ const handleImport = async () => {
resp = await modify(id as string, params)
}
loading.value = false
if (resp.status === 200) {
if (resp.success) {
if (props?.type === 'device') {
const detail = instanceStore.current
detail.metadata = paramsDevice
@ -290,9 +295,10 @@ const handleImport = async () => {
padding: 10px;
}
}
.upload-button {
width: 37px;
height: 30px;
height: 30px;
line-height: 30px;
margin: 0 -11px;
}

View File

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

View File

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

View File

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

View File

@ -65,33 +65,16 @@ import Basic from './Basic/index.vue';
import Role from './Role/index.vue';
import Menu from './Menu/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 { saveInit } from '@/api/initHome';
import { BASE_API_PATH, TOKEN_KEY } from '@/utils/variable';
import { FILE_UPLOAD } from '@/api/comm';
import { LocalStore } from '@/utils/comm';
import { message } from 'ant-design-vue';
import { Form } from 'ant-design-vue';
import { message } from 'jetlinks-ui-components';
const basicRef = ref();
const roleRef = ref();
const initDataRef = ref();
const loading = ref(false);
// const useForm = Form.useForm;
// const { resetFields, validate, validateInfos } = useForm(
// modalForm.value,
// rulesModle.value,
// );
/**
* 默认打开第一个初始菜单
*/

View File

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

View File

@ -38,8 +38,7 @@
:params="params"
:rowSelection="{
selectedRowKeys: _selectedRowKeys,
onSelect: onSelectChange,
onSelectAll: onSelectAllChange,
onChange: onSelectChange,
}"
@cancelSelect="_selectedRowKeys = []"
:pagination="{
@ -165,32 +164,9 @@ const handleSearch = (e: any) => {
const listRef = ref();
const _selectedRowKeys = ref<string[]>([]);
const onSelectChange = (
record: any[],
selected: boolean,
selectedRows: any[],
) => {
_selectedRowKeys.value = selected
? [...getSetRowKey(selectedRows)]
: _selectedRowKeys.value.filter((item: any) => item !== record?.id);
const onSelectChange = (keys: string[]) => {
_selectedRowKeys.value = [...keys];
};
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 handleSave = async () => {

View File

@ -15,8 +15,7 @@
:params="params"
:rowSelection="{
selectedRowKeys: _selectedRowKeys,
onSelect: onSelectChange,
onSelectAll: onSelectAllChange,
onChange: onSelectChange,
}"
@cancelSelect="_selectedRowKeys = []"
:pagination="{
@ -242,32 +241,9 @@ const listRef = ref();
const _selectedRowKeys = ref<string[]>([]);
const bindVis = ref(false);
const onSelectChange = (
record: any[],
selected: boolean,
selectedRows: any[],
) => {
_selectedRowKeys.value = selected
? [...getSetRowKey(selectedRows)]
: _selectedRowKeys.value.filter((item: any) => item !== record?.id);
const onSelectChange = (keys: string[]) => {
_selectedRowKeys.value = [...keys];
};
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"
>
<PermissionButton
:danger="i.key === 'delete'"
:disabled="i.disabled"
:popConfirm="i.popConfirm"
:tooltip="{

View File

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

View File

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

View File

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

View File

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

View File

@ -69,7 +69,7 @@ const getDeviceList = async () => {
const res = await cascadeApi.getMediaTree({ paging: false });
if (res.success) {
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) => {
const extra: any = {};
extra.isLeaf = isLeaf(m);

View File

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

View File

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

View File

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

View File

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

View File

@ -598,7 +598,13 @@
</j-form-item>
</j-col>
<j-col :span="12">
<j-form-item>
<j-form-item
v-bind="
validateInfos[
'template.phoneNumber'
]
"
>
<template #label>
<span>
收信人
@ -828,7 +834,10 @@ const resetPublicFiles = () => {
case 'dingTalkRobotWebHook':
formData.value.template.message = undefined;
formData.value.template.messageType = 'markdown';
formData.value.template.markdown = { text: undefined, title: undefined };
formData.value.template.markdown = {
text: undefined,
title: undefined,
};
break;
case 'corpMessage':
formData.value.template.agentId = undefined;
@ -990,6 +999,17 @@ const formRules = ref({
'template.signName': [
{ 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
description: [{ max: 200, message: '最多可输入200个字符' }],
'template.message': [

View File

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

View File

@ -49,9 +49,8 @@
</template>
<script lang="ts" setup>
import { Form } from 'ant-design-vue';
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 formRef = ref();
const Myprops = defineProps({

View File

@ -59,9 +59,8 @@
</template>
<script lang="ts" setup>
import { Form } from 'ant-design-vue';
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 useForm = Form.useForm;
const Myprops = defineProps({

View File

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

View File

@ -64,7 +64,7 @@
import { getImage } from '@/utils/comm';
import { queryLevel, saveLevel } from '@/api/rule-engine/config';
import { LevelItem } from './typing';
import { message } from 'ant-design-vue/es';
import { message } from 'jetlinks-ui-components';
import Io from './Io/index.vue'
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 { query } from '@/api/rule-engine/scene';
import { getImage } from '@/utils/comm';
import { message } from 'ant-design-vue';
import { message } from 'jetlinks-ui-components';
import { useMenuStore } from '@/store/menu';
import { useRoute } from 'vue-router';
import { useAlarmConfigurationStore } from '@/store/alarm';

View File

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

View File

@ -97,7 +97,7 @@ import { unbindScene } from '@/api/rule-engine/configuration';
import { useRoute } from 'vue-router';
import type { ActionsType } from '@/components/Table';
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 { useAlarmConfigurationStore } from '@/store/alarm';
import { storeToRefs } from 'pinia';

View File

@ -179,7 +179,7 @@ import {
import { queryLevel } from '@/api/rule-engine/config';
import { Store } from 'jetlinks-store';
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 { useMenuStore } from '@/store/menu';
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 moment from 'moment';
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 Info from './info.vue';
import { storeToRefs } from 'pinia';

View File

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

View File

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

View File

@ -148,7 +148,7 @@ import {
} from '@/api/rule-engine/instance';
import type { ActionsType } from '@/components/Table/index.vue';
import { getImage } from '@/utils/comm';
import { message } from 'ant-design-vue';
import { message } from 'jetlinks-ui-components';
import Save from './Save/index.vue';
import { SystemConst } from '@/utils/consts';
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 DeviceSelect from './DeviceSelect.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 = {
(e: 'cancel'): void
(e: 'change', data: TriggerDevice): void
(e: 'save', data: TriggerDevice, options: Record<string, any>): void
}
@ -76,6 +78,7 @@ interface AddModelType extends Omit<TriggerDevice, 'selectorValues'> {
metadata: metadataType,
operator: TriggerDeviceOptions
}
const formItemContext = Form.useInjectFormItemContext();
const emit = defineEmits<Emit>()
const typeRef = ref()
@ -165,26 +168,6 @@ const handleOptions = (data: TriggerDeviceOptions) => {
_options.when = when;
_options.time = time;
_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') {
@ -257,12 +240,13 @@ const save = async (step?: number) => {
optionsCache.value.action = typeData.action
const _options = handleOptions(typeData.data);
const data = {
operator: typeData.data,
operation: typeData.data,
selector: addModel.selector,
selectorValues: addModel.selectorValues,
productId: addModel.productId
}
emit('save', data, _options)
formItemContext.onFieldChange()
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -234,7 +234,7 @@
@click="onType('device')"
>
<template v-if="data?.device?.selector === 'fixed'">
<div>
<div style='display: flex; align-items: center;'>
<AIcon
:type="
typeIconMap[
@ -248,13 +248,16 @@
}}</span>
<AIcon
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(
data?.options?.propertiesValue,
@ -349,7 +352,7 @@
/>
</div>
</template>
<div v-else class="filter-add-button">
<div v-else class="filter-add-button" @click='addFilterParams'>
<AIcon type="PlusOutlined" style="padding-right: 4px" />
<span>添加过滤条件</span>
</div>
@ -394,6 +397,7 @@ import { useSceneStore } from '@/store/scene';
import { storeToRefs } from 'pinia';
import { iconMap, itemNotifyIconMap, typeIconMap } from './util';
import FilterGroup from './FilterGroup.vue';
import { randomString } from '@/utils/utils'
const sceneStore = useSceneStore();
const { data: _data } = storeToRefs(sceneStore);
@ -471,6 +475,30 @@ const onSave = (data: ActionsType, options: any) => {
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 = () => {
visible.value = true;
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -23,7 +23,7 @@
@deleteAll='branchesDeleteAll'
/>
<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 class='actions-terms-options no-when'>
@ -32,6 +32,18 @@
</div>
</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>
</template>
@ -40,14 +52,14 @@ import { storeToRefs } from 'pinia';
import { useSceneStore } from 'store/scene'
import { cloneDeep } from 'lodash-es'
import { provide } from 'vue'
import { ContextKey, handleParamsData } from './util'
import { ContextKey, handleParamsData, thenRules } from './util'
import { getParseTerm } from '@/api/rule-engine/scene'
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 { data } = storeToRefs(sceneStore)
const open = ref(false)
const columnOptions = ref<any>([])
@ -56,6 +68,11 @@ provide(ContextKey, columnOptions)
const change = (e: boolean) => {
open.value = e
if (!e) {
data.value.branches!.length = 1
} else {
data.value.branches!.push(null as any)
}
}
const queryColumn = (dataModel: FormModelType) => {
@ -96,9 +113,8 @@ const branchesDeleteAll = () => {
}
watchEffect(() => {
console.log(data.value.trigger, 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'
:key='item.key'
:name='["branches", branchName, "when", whenName, "terms", index]'
:rules='rules'
>
<ParamsItem
v-model:value='formModel.branches[branchName].when[whenName].terms[index]'
@ -39,7 +40,6 @@
:termsName='name'
:whenName='whenName'
:branchName='branchName'
@change='paramsChange'
/>
</j-form-item>
</div>
@ -60,6 +60,7 @@ import DropdownButton from '../DropdownButton'
import { storeToRefs } from 'pinia';
import { useSceneStore } from 'store/scene'
import ParamsItem from './ParamsItem.vue'
import { isArray } from 'lodash-es'
const sceneStore = useSceneStore()
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 termsData = computed(() => {
@ -125,10 +161,6 @@ const onDelete = () => {
formModel.value.branches?.[props.branchName]?.when?.splice(props.name, 1)
}
const paramsChange = () => {
}
const addTerms = () => {
const terms = {
type: 'and',

View File

@ -106,6 +106,10 @@
}
}
}
.actions-branches-item {
width: 100%;
}
}
.terms-params {
@ -131,11 +135,15 @@
.ant-form-item {
margin-bottom: 8px;
&:not(:first-child) {
&:not(:nth-child(2)) {
.ant-form-item-explain-error {
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)
}
}) || []
}
}
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 type { ActionsType } from '@/components/Table';
import { getImage } from '@/utils/comm';
import SceneCard from './SceneCard.vue';
import BadgeStatus from '@/components/BadgeStatus/index.vue';
const menuStory = useMenuStore();

View File

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

View File

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

View File

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