fix: 修改北向输出

This commit is contained in:
100011797 2023-03-08 18:16:59 +08:00
parent 23aff419a3
commit d70956e1e1
21 changed files with 7151 additions and 6464 deletions

View File

@ -29,7 +29,7 @@
v-model:value="myValue"
>
<template #addonAfter>
<form-outlined @click="modalVis = true" />
<AIcon type="FormOutlined" @click="modalVis = true" />
</template>
</j-input>
<GeoComponent
@ -50,7 +50,7 @@
:showUploadList="false"
@change="handleFileChange"
>
<cloud-upload-outlined />
<AIcon type="CloudUploadOutlined" />
</j-upload>
</template>
</j-input>

View File

@ -15,6 +15,7 @@ import Ellipsis from './Ellipsis/index.vue'
import JEmpty from './Empty/index.vue'
import AMapComponent from './AMapComponent/index.vue'
import PathSimplifier from './AMapComponent/PathSimplifier.vue'
import ValueItem from './ValueItem/index.vue'
export default {
install(app: App) {
@ -35,5 +36,6 @@ export default {
.component('JEmpty', JEmpty)
.component('AMapComponent', AMapComponent)
.component('PathSimplifier', PathSimplifier)
.component('ValueItem', ValueItem)
}
}

View File

@ -16,7 +16,7 @@
在特定场景下设备无法直接接入阿里云物联网平台时您可先将设备接入物联网平台再使用阿里云云云对接SDK快速构建桥接服务搭建物联网平台与阿里云物联网平台的双向数据通道
</div>
<div class="image">
<a-image width="100%" :src="getImage('/northbound/aliyun2.png')" />
<j-image width="100%" :src="getImage('/northbound/aliyun2.png')" />
</div>
<h1>2.配置说明</h1>
<div>
@ -26,14 +26,14 @@
</div>
<div>获取路径阿里云物联网平台--服务地址</div>
<div class="image">
<a-image width="100%" :src="getImage('/northbound/aliyun3.png')" />
<j-image width="100%" :src="getImage('/northbound/aliyun3.png')" />
</div>
<h2> 2AccesskeyID/Secret</h2>
<div>
用于程序通知方式调用云服务费API的用户标识和秘钥获取路径阿里云管理控制台--用户头像----AccessKey管理--查看
</div>
<div class="image">
<a-image width="100%" :src="getImage('/northbound/aliyun1.jpg')" />
<j-image width="100%" :src="getImage('/northbound/aliyun1.jpg')" />
</div>
<h2> 3. 网桥产品</h2>
<div>
@ -44,7 +44,7 @@
将阿里云物联网平台中的产品实例与物联网平台的产品实例进行关联关联后需要进入该产品下的每一个设备的实例信息页填入对应的阿里云物联网平台设备的DeviceNameDeviceSecret进行一对一绑定
</div>
<div class="image">
<a-image width="100%" :src="getImage('/northbound/aliyun4.png')" />
<j-image width="100%" :src="getImage('/northbound/aliyun4.png')" />
</div>
</div>
</div>

View File

@ -1,17 +1,17 @@
<template>
<page-container>
<a-card>
<a-row :gutter="24">
<a-col :span="16">
<j-card>
<j-row :gutter="24">
<j-col :span="16">
<TitleComponent data="基本信息" />
<a-form
<j-form
:layout="'vertical'"
ref="formRef"
:model="modelRef"
>
<a-row :gutter="24">
<a-col :span="24">
<a-form-item
<j-row :gutter="24">
<j-col :span="24">
<j-form-item
label="名称"
name="name"
:rules="[
@ -25,14 +25,14 @@
},
]"
>
<a-input
<j-input
placeholder="请输入名称"
v-model:value="modelRef.name"
/>
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item
</j-form-item>
</j-col>
<j-col :span="24">
<j-form-item
:name="['accessConfig', 'regionId']"
:rules="[
{
@ -44,63 +44,62 @@
<template #label>
<span>
服务地址
<a-tooltip
<j-tooltip
title="阿里云内部给每台机器设置的唯一编号"
>
<AIcon
type="QuestionCircleOutlined"
style="margin-left: 2px"
/>
</a-tooltip>
</j-tooltip>
</span>
</template>
<a-select
<j-select
placeholder="请选择服务地址"
v-model:value="
modelRef.accessConfig.regionId
"
show-search
:filter-option="filterOption"
@blur="productChange"
>
<a-select-option
<j-select-option
v-for="item in regionsList"
:key="item.id"
:value="item.id"
:label="item.name"
>{{ item.name }}</a-select-option
>{{ item.name }}</j-select-option
>
</a-select>
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item
</j-select>
</j-form-item>
</j-col>
<j-col :span="24">
<j-form-item
:name="['accessConfig', 'instanceId']"
>
<template #label>
<span>
实例ID
<a-tooltip
<j-tooltip
title="阿里云物联网平台中的实例ID,没有则不填"
>
<AIcon
type="QuestionCircleOutlined"
style="margin-left: 2px"
/>
</a-tooltip>
</j-tooltip>
</span>
</template>
<a-input
<j-input
placeholder="请输入实例ID"
v-model:value="
modelRef.accessConfig.instanceId
"
@blur="productChange"
/>
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item
</j-form-item>
</j-col>
<j-col :span="24">
<j-form-item
:name="['accessConfig', 'accessKeyId']"
:rules="[
{
@ -116,27 +115,27 @@
<template #label>
<span>
accessKey
<a-tooltip
<j-tooltip
title="用于程序通知方式调用云服务API的用户标识"
>
<AIcon
type="QuestionCircleOutlined"
style="margin-left: 2px"
/>
</a-tooltip>
</j-tooltip>
</span>
</template>
<a-input
<j-input
placeholder="请输入accessKey"
v-model:value="
modelRef.accessConfig.accessKeyId
"
@blur="productChange"
/>
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item
</j-form-item>
</j-col>
<j-col :span="24">
<j-form-item
:name="['accessConfig', 'accessSecret']"
:rules="[
{
@ -152,27 +151,27 @@
<template #label>
<span>
accessSecret
<a-tooltip
<j-tooltip
title="用于程序通知方式调用云服务费API的秘钥标识"
>
<AIcon
type="QuestionCircleOutlined"
style="margin-left: 2px"
/>
</a-tooltip>
</j-tooltip>
</span>
</template>
<a-input
<j-input
placeholder="请输入accessSecret"
v-model:value="
modelRef.accessConfig.accessSecret
"
@blur="productChange"
/>
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item
</j-form-item>
</j-col>
<j-col :span="24">
<j-form-item
name="bridgeProductKey"
:rules="{
required: true,
@ -182,44 +181,43 @@
<template #label>
<span>
网桥产品
<a-tooltip
<j-tooltip
title="物联网平台对应的阿里云产品"
>
<AIcon
type="QuestionCircleOutlined"
style="margin-left: 2px"
/>
</a-tooltip>
</j-tooltip>
</span>
</template>
<a-select
<j-select
placeholder="请选择网桥产品"
v-model:value="
modelRef.bridgeProductKey
"
show-search
:filter-option="filterOption"
>
<a-select-option
<j-select-option
v-for="item in aliyunProductList"
:key="item.productKey"
:value="item.productKey"
:label="item.productName"
>{{
item.productName
}}</a-select-option
}}</j-select-option
>
</a-select>
</a-form-item>
</a-col>
<a-col :span="24">
</j-select>
</j-form-item>
</j-col>
<j-col :span="24">
<p>产品映射</p>
<a-collapse
<j-collapse
v-if="modelRef.mappings.length"
:activeKey="activeKey"
@change="onCollChange"
>
<a-collapse-panel
<j-collapse-panel
v-for="(
item, index
) in modelRef.mappings"
@ -239,9 +237,9 @@
type="DeleteOutlined"
@click="delItem(index)"
/></template>
<a-row :gutter="24">
<a-col :span="12">
<a-form-item
<j-row :gutter="24">
<j-col :span="12">
<j-form-item
label="阿里云产品"
:name="[
'mappings',
@ -254,19 +252,16 @@
'请选择阿里云产品',
}"
>
<a-select
<j-select
placeholder="请选择阿里云产品"
v-model:value="
item.productKey
"
show-search
:filter-option="
filterOption
"
>
<a-select-option
<j-select-option
v-for="i in getAliyunProductList(
item.productKey,
item?.productKey || ''
)"
:key="i.productKey"
:value="
@ -277,13 +272,13 @@
"
>{{
i.productName
}}</a-select-option
}}</j-select-option
>
</a-select>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item
</j-select>
</j-form-item>
</j-col>
<j-col :span="12">
<j-form-item
label="平台产品"
:name="[
'mappings',
@ -296,36 +291,36 @@
'请选择平台产品',
}"
>
<a-select
<j-select
placeholder="请选择平台产品"
v-model:value="
item.productId
"
show-search
:filter-option="
filterOption
"
>
<a-select-option
<j-select-option
v-for="i in getPlatProduct(
item.productId,
item.productId || ''
)"
:key="i.id"
:value="item.id"
:value="i?.id"
:label="i.name"
>{{
i.name
}}</a-select-option
}}</j-select-option
>
</a-select>
</a-form-item>
</a-col>
</a-row>
</a-collapse-panel>
</a-collapse>
</a-col>
<a-col :span="24">
<a-button
</j-select>
</j-form-item>
</j-col>
</j-row>
</j-collapse-panel>
</j-collapse>
<j-card v-else>
<j-empty />
</j-card>
</j-col>
<j-col :span="24">
<j-button
type="dashed"
style="width: 100%; margin-top: 10px"
@click="addItem"
@ -334,10 +329,10 @@
type="PlusOutlined"
style="margin-left: 2px"
/>
</a-button>
</a-col>
<a-col :span="24" style="margin-top: 20px">
<a-form-item
</j-button>
</j-col>
<j-col :span="24" style="margin-top: 20px">
<j-form-item
label="说明"
name="description"
:rules="{
@ -345,16 +340,16 @@
message: '最多输入200个字符',
}"
>
<a-textarea
<j-textarea
v-model:value="modelRef.description"
placeholder="请输入说明"
showCount
:maxlength="200"
/>
</a-form-item>
</a-col>
</a-row>
</a-form>
</j-form-item>
</j-col>
</j-row>
</j-form>
<div v-if="type === 'edit'">
<PermissionButton
type="primary"
@ -365,12 +360,12 @@
保存
</PermissionButton>
</div>
</a-col>
<a-col :span="8">
</j-col>
<j-col :span="8">
<Doc />
</a-col>
</a-row>
</a-card>
</j-col>
</j-row>
</j-card>
</page-container>
</template>
@ -384,7 +379,7 @@ import {
queryProductList,
} from '@/api/northbound/alicloud';
import _ from 'lodash';
import { message } from 'ant-design-vue';
import { message } from 'jetlinks-ui-components';
const router = useRouter();
const route = useRoute();
@ -430,10 +425,6 @@ const loading = ref<boolean>(false);
const type = ref<'edit' | 'view'>('edit');
const activeKey = ref<string[]>(['0']);
const filterOption = (input: string, option: any) => {
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
};
const queryRegionsList = async () => {
const resp = await getRegionsList();
if (resp.status === 200) {
@ -503,8 +494,9 @@ const saveBtn = () => {
);
data.bridgeProductName = product?.productName || '';
loading.value = true;
const resp = await savePatch({...toRaw(modelRef), ...data});
loading.value = false;
const resp = await savePatch({...toRaw(modelRef), ...data}).finally(() => {
loading.value = false;
})
if (resp.status === 200) {
message.success('操作成功!');
formRef.value.resetFields();

View File

@ -1,11 +1,11 @@
<template>
<page-container>
<Search
<j-advanced-search
:columns="columns"
target="northbound-aliyun"
@search="handleSearch"
/>
<JTable
<JProTable
ref="instanceRef"
:columns="columns"
:request="query"
@ -13,7 +13,7 @@
:params="params"
>
<template #headerTitle>
<a-space>
<j-space>
<PermissionButton
type="primary"
@click="handleAdd"
@ -22,7 +22,7 @@
<template #icon><AIcon type="PlusOutlined" /></template>
新增
</PermissionButton>
</a-space>
</j-space>
</template>
<template #card="slotProps">
<CardBox
@ -45,20 +45,20 @@
>
{{ slotProps.name }}
</h3>
<a-row>
<a-col :span="12">
<j-row>
<j-col :span="12">
<div class="card-item-content-text">
网桥产品
</div>
<div>{{ slotProps?.bridgeProductName }}</div>
</a-col>
<a-col :span="12">
</j-col>
<j-col :span="12">
<div class="card-item-content-text">
<label>说明</label>
</div>
<div>{{ slotProps?.description }}</div>
</a-col>
</a-row>
</j-col>
</j-row>
</template>
<template #actions="item">
<PermissionButton
@ -81,13 +81,13 @@
</CardBox>
</template>
<template #state="slotProps">
<a-badge
<j-badge
:text="slotProps.state?.text"
:status="statusMap.get(slotProps.state?.value)"
/>
</template>
<template #action="slotProps">
<a-space>
<j-space>
<template
v-for="i in getActions(slotProps, 'table')"
:key="i.key"
@ -104,23 +104,21 @@
<template #icon><AIcon :type="i.icon" /></template>
</PermissionButton>
</template>
</a-space>
</j-space>
</template>
</JTable>
</JProTable>
</page-container>
</template>
<script setup lang="ts">
import { query, _undeploy, _deploy, _delete } from '@/api/northbound/alicloud';
import type { ActionsType } from '@/components/Table/index.vue';
import type { ActionsType } from '@/views/device/Instance/typings'
import { getImage } from '@/utils/comm';
import { message } from 'ant-design-vue';
import { message } from 'jetlinks-ui-components';
import { useMenuStore } from 'store/menu';
const router = useRouter();
const instanceRef = ref<Record<string, any>>({});
const params = ref<Record<string, any>>({});
const current = ref<Record<string, any>>({});
const menuStory = useMenuStore();
@ -149,6 +147,9 @@ const columns = [
title: '说明',
dataIndex: 'describe',
key: 'describe',
search: {
type: 'string',
},
},
{
title: '状态',

View File

@ -40,7 +40,7 @@
</template>
<script lang="ts" setup>
import { PropType } from "vue-demi";
import { PropType } from "vue";
type Emits = {

View File

@ -9,8 +9,8 @@
<j-form-item name="messageType" label="指令类型" :rules="{
required: true,
message: '请选择指令类型',
}">
<j-select placeholder="请选择指令类型" v-model:value="modelRef.messageType" show-search :filter-option="filterOption">
}" class="other">
<j-select placeholder="请选择指令类型" v-model:value="modelRef.messageType" show-search>
<j-select-option value="READ_PROPERTY">读取属性</j-select-option>
<j-select-option value="WRITE_PROPERTY">修改属性</j-select-option>
<j-select-option value="INVOKE_FUNCTION">调用功能</j-select-option>
@ -22,7 +22,7 @@
required: true,
message: '请选择属性',
}">
<j-select placeholder="请选择属性" v-model:value="modelRef.message.properties" show-search :filter-option="filterOption">
<j-select placeholder="请选择属性" v-model:value="modelRef.message.properties" show-search @change="onPropertyChange">
<j-select-option v-for="i in (metadata?.properties) || []" :key="i.id" :value="i.id" :label="i.name">{{i.name}}</j-select-option>
</j-select>
</j-form-item>
@ -32,7 +32,27 @@
required: true,
message: '请输入值',
}">
<j-input />
<ValueItem
v-model:modelValue="modelRef.message.value"
:itemType="property.type || property.valueType?.type || 'int'"
:options="
property.valueType?.type === 'enum'
? (property?.dataType?.elements || []).map(
(item) => {
return {
label: item?.text,
value: item?.value,
};
},
)
: property.valueType?.type === 'boolean'
? [
{ label: '是', value: true },
{ label: '否', value: false },
]
: undefined
"
/>
</j-form-item>
</j-col>
<j-col :span="24" v-if="modelRef.messageType === 'INVOKE_FUNCTION'">
@ -40,7 +60,7 @@
required: true,
message: '请选择功能',
}">
<j-select placeholder="请选择功能" v-model:value="modelRef.message.functionId" show-search :filter-option="filterOption" @change="funcChange">
<j-select placeholder="请选择功能" v-model:value="modelRef.message.functionId" show-search @change="funcChange">
<j-select-option v-for="i in (metadata?.functions) || []" :key="i.id" :value="i.id" :label="i.name">{{i.name}}</j-select-option>
</j-select>
</j-form-item>
@ -62,10 +82,6 @@ import EditTable from './EditTable.vue'
const formRef = ref();
const filterOption = (input: string, option: any) => {
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
};
const props = defineProps({
actionType: {
type: String,
@ -89,10 +105,12 @@ const props = defineProps({
type Emits = {
(e: 'update:modelValue', data: any): void;
};
const emit = defineEmits<Emits>();
const modelRef = computed({
get: () => {
onPropertyChange(props.modelValue?.message?.properties)
return props.modelValue || {
messageType: undefined,
message: {
@ -107,6 +125,8 @@ const modelRef = computed({
}
})
const property = ref<any>({})
const funcChange = (val: string) => {
if(val){
const arr = props.metadata?.functions.find((item: any) => item.id === val)?.inputs || []
@ -122,6 +142,13 @@ 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.validate()
.then(() => {
@ -139,4 +166,13 @@ const saveBtn = () => new Promise((resolve) => {
defineExpose({ saveBtn })
</script>
</script>
<style lang="less" scoped>
:deep(.ant-form-item){
margin-bottom: 0;
}
.other {
margin-bottom: 24px;
}
</style>

View File

@ -51,7 +51,6 @@
placeholder="请选择产品"
v-model:value="modelRef.id"
show-search
:filter-option="filterOption"
@change="productChange"
>
<j-select-option
@ -89,7 +88,6 @@
placeholder="请选择设备类型"
v-model:value="modelRef.applianceType"
show-search
:filter-option="filterOption"
@change="typeChange"
>
<j-select-option
@ -170,13 +168,10 @@
item.action
"
show-search
:filter-option="
filterOption
"
>
<j-select-option
v-for="i in getTypesActions(
item.action,
item.action || ''
)"
:key="i.id"
:value="i.id"
@ -218,9 +213,6 @@
item.actionType
"
show-search
:filter-option="
filterOption
"
>
<j-select-option
value="command"
@ -261,6 +253,9 @@
</j-row>
</j-collapse-panel>
</j-collapse>
<j-card v-else>
<j-empty />
</j-card>
</j-col>
<j-col :span="24">
<j-button
@ -323,13 +318,10 @@
item.source
"
show-search
:filter-option="
filterOption
"
>
<j-select-option
v-for="i in getDuerOSProperties(
item.source,
item.source || '',
)"
:key="i.id"
:value="i.id"
@ -361,16 +353,13 @@
"
mode="tags"
show-search
:filter-option="
filterOption
"
>
<j-select-option
v-for="i in getProductProperties(
item.target,
)"
:key="i.id"
:value="item.id"
:value="i.id"
>{{
i.name
}}</j-select-option
@ -381,6 +370,9 @@
</j-row>
</j-collapse-panel>
</j-collapse>
<j-card v-else>
<j-empty />
</j-card>
</j-col>
<j-col :span="24">
<j-button
@ -494,10 +486,6 @@ const onActionCollChange = (_key: string[]) => {
actionActiveKey.value = _key;
};
const filterOption = (input: string, option: any) => {
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
};
const addItem = () => {
actionActiveKey.value.push(String(modelRef.actionMappings.length));
modelRef.actionMappings.push({
@ -636,8 +624,9 @@ const saveBtn = async () => {
.then(async (data: any) => {
if (tasks.every((item) => item) && data) {
loading.value = true;
const resp = await savePatch(data);
loading.value = false;
const resp = await savePatch(data).finally(() => {
loading.value = false;
})
if (resp.status === 200) {
message.success('操作成功!');
formRef.value.resetFields();

View File

@ -14,7 +14,6 @@
selectedRowKeys: _selectedRowKeys,
onChange: onSelectChange,
}"
@cancelSelect="cancelSelect"
:params="params"
>
<template #headerTitle>
@ -643,10 +642,6 @@ const onSelectChange = (keys: string[]) => {
_selectedRowKeys.value = [...keys];
};
const cancelSelect = () => {
_selectedRowKeys.value = [];
};
const handleClick = (dt: any) => {
if (_selectedRowKeys.value.includes(dt.id)) {
const _index = _selectedRowKeys.value.findIndex((i) => i === dt.id);

View File

@ -1,261 +1,278 @@
<template>
<Search
:columns="columns"
type='simple'
@search="handleSearch"
class='search'
target="scene-triggrt-device-device"
<j-advanced-search
:columns="columns"
type="simple"
@search="handleSearch"
class="search"
target="scene-trigger-device-product"
/>
<a-divider style='margin: 0' />
<a-divider style="margin: 0" />
<j-pro-table
ref='actionRef'
model='CARD'
:columns='columns'
:params='params'
:request='productQuery'
:gridColumn='2'
:gridColumns='[2,2,2]'
:bodyStyle='{
paddingRight: 0,
paddingLeft: 0
}'
ref="actionRef"
model="CARD"
:columns="columns"
:params="params"
:request="productQuery"
:gridColumn="2"
:bodyStyle="{
paddingRight: 0,
paddingLeft: 0,
}"
>
<template #card="slotProps">
<CardBox
:value='slotProps'
:active="rowKey === slotProps.id"
:status="slotProps.state"
:statusText="slotProps.state === 1 ? '正常' : '禁用'"
:statusNames="{ 1: 'success', 0: 'error', }"
@click="handleClick"
>
<template #img>
<slot name="img">
<img width='88' height='88' :src="slotProps.photoUrl || getImage('/device-product.png')" />
</slot>
</template>
<template #content>
<div style='width: calc(100% - 100px)'>
<Ellipsis>
<span style="font-size: 16px;font-weight: 600" >
{{ slotProps.name }}
</span>
</Ellipsis>
</div>
<a-row>
<a-col :span="12">
<div class="card-item-content-text">
设备类型
</div>
<div>直连设备</div>
</a-col>
</a-row>
</template>
</CardBox>
</template>
<template #card="slotProps">
<CardBox
:value="slotProps"
:active="rowKey === slotProps.id"
:status="String(slotProps.state)"
:statusText="slotProps.state === 1 ? '正常' : '禁用'"
:statusNames="{ '1': 'success', '0': 'error' }"
@click="handleClick(slotProps)"
>
<template #img>
<slot name="img">
<img
:width="88"
:height="88"
:src="
slotProps.photoUrl ||
getImage('/device-product.png')
"
/>
</slot>
</template>
<template #content>
<div style="width: calc(100% - 100px)">
<Ellipsis>
<span style="font-size: 16px; font-weight: 600">
{{ slotProps.name }}
</span>
</Ellipsis>
</div>
<a-row>
<a-col :span="12">
<div class="card-item-content-text">设备类型</div>
<Ellipsis>{{ slotProps.deviceType?.text }}</Ellipsis>
</a-col>
<a-col :span="12">
<div class="card-item-content-text">接入方式</div>
<Ellipsis>{{ slotProps?.accessName || '未接入' }}</Ellipsis>
</a-col>
</a-row>
</template>
</CardBox>
</template>
</j-pro-table>
</template>
</template>
<script setup lang='ts' name='Product'>
import { getProviders, queryGatewayList, queryProductList } from '@/api/device/product'
import { queryTree } from '@/api/device/category'
import { getTreeData_api } from '@/api/system/department'
import { isNoCommunity } from '@/utils/utils'
import { getImage } from '@/utils/comm'
type Emit = {
(e: 'update:rowKey', data: string): void
(e: 'update:detail', data: string): void
(e: 'change', data: string): void
}
const actionRef = ref()
const params = ref({})
const props = defineProps({
import {
getProviders,
queryGatewayList,
queryProductList,
} from '@/api/device/product';
import { queryTree } from '@/api/device/category';
import { getTreeData_api } from '@/api/system/department';
import { isNoCommunity } from '@/utils/utils';
import { getImage } from '@/utils/comm';
type Emit = {
(e: 'update:rowKey', data: string): void;
(e: 'update:detail', data: string): void;
(e: 'change', data: string): void;
};
const actionRef = ref();
const params = ref({});
const props = defineProps({
rowKey: {
type: String,
default: ''
type: String,
default: '',
},
detail: {
type: Object,
default: () => ({})
}
})
const emit = defineEmits<Emit>()
const columns = [
type: Object,
default: () => ({}),
},
});
const emit = defineEmits<Emit>();
const columns = [
{
title: 'ID',
dataIndex: 'id',
width: 300,
ellipsis: true,
fixed: 'left',
search: {
type: 'string',
},
title: 'ID',
dataIndex: 'id',
width: 300,
ellipsis: true,
fixed: 'left',
search: {
type: 'string',
},
},
{
title: '名称',
dataIndex: 'name',
width: 200,
ellipsis: true,
search: {
type: 'string',
first: true
}
title: '名称',
dataIndex: 'name',
width: 200,
ellipsis: true,
search: {
type: 'string',
first: true,
},
},
{
title: '网关类型',
dataIndex: 'accessProvider',
width: 150,
ellipsis: true,
hideInTable: true,
search: {
type: 'select',
options: () => getProviders().then((resp: any) => {
if (isNoCommunity) {
return (resp?.result || []).map((item: any) => ({
label: item.name,
value: item.id
}))
} else {
return (resp?.result || []).filter((item: any) => [
'mqtt-server-gateway',
'http-server-gateway',
'mqtt-client-gateway',
'tcp-server-gateway',
].includes(item.id))
.map((item: any) => ({
label: item.name,
value: item.id,
}))
}
})
}
},
{
title: '接入方式',
dataIndex: 'accessName',
width: 150,
ellipsis: true,
search: {
type: 'select',
options: () => queryGatewayList().then((resp: any) =>
resp.result.map((item: any) => ({
label: item.name, value: item.id
}))
)
}
},
{
title: '设备类型',
dataIndex: 'deviceType',
width: 150,
search: {
type: 'select',
options: [
{ label: '直连设备', value: 'device' },
{ label: '网关子设备', value: 'childrenDevice' },
{ label: '网关设备', value: 'gateway' },
]
}
},
{
title: '状态',
dataIndex: 'state',
width: '90px',
search: {
type: 'select',
options: [
{ label: '禁用', value: 0 },
{ label: '正常', value: 1 },
]
}
},
{
title: '说明',
dataIndex: 'describe',
ellipsis: true,
width: 300,
},
{
dataIndex: 'classifiedId',
title: '分类',
hideInTable: true,
search: {
type: 'treeSelect',
options: queryTree({ paging: false }).then(resp => resp.result),
componentProps: {
fieldNames: {
label: 'name',
value: 'id',
}
}
}
},
{
dataIndex: 'id$dim-assets',
title: '所属组织',
hideInTable: true,
search: {
type: 'treeSelect',
options: getTreeData_api({ paging: false }).then((resp: any) => {
const formatValue = (list: any[]) => {
return list.map((item: any) => {
if (item.children) {
item.children = formatValue(item.children);
}
return {
...item,
value: JSON.stringify({
assetType: 'product',
targets: [
{
type: 'org',
id: item.id,
},
],
title: '网关类型',
dataIndex: 'accessProvider',
width: 150,
ellipsis: true,
hideInTable: true,
search: {
type: 'select',
options: () =>
getProviders().then((resp: any) => {
if (isNoCommunity) {
return (resp?.result || []).map((item: any) => ({
label: item.name,
value: item.id,
}));
} else {
return (resp?.result || [])
.filter((item: any) =>
[
'mqtt-server-gateway',
'http-server-gateway',
'mqtt-client-gateway',
'tcp-server-gateway',
].includes(item.id),
)
.map((item: any) => ({
label: item.name,
value: item.id,
}));
}
}),
}
})
}
return formatValue(resp.result)
}),
}
}
]
const handleSearch = (p: any) => {
params.value = p
}
const productQuery = (p: any) => {
},
},
{
title: '接入方式',
dataIndex: 'accessName',
width: 150,
ellipsis: true,
search: {
type: 'select',
options: () =>
queryGatewayList().then((resp: any) =>
resp.result.map((item: any) => ({
label: item.name,
value: item.id,
})),
),
},
},
{
title: '设备类型',
dataIndex: 'deviceType',
width: 150,
search: {
type: 'select',
options: [
{ label: '直连设备', value: 'device' },
{ label: '网关子设备', value: 'childrenDevice' },
{ label: '网关设备', value: 'gateway' },
],
},
},
{
title: '状态',
dataIndex: 'state',
width: '90px',
search: {
type: 'select',
options: [
{ label: '禁用', value: 0 },
{ label: '正常', value: 1 },
],
},
},
{
title: '说明',
dataIndex: 'describe',
ellipsis: true,
width: 300,
},
{
dataIndex: 'classifiedId',
title: '分类',
hideInTable: true,
search: {
type: 'treeSelect',
options: queryTree({ paging: false }).then((resp) => resp.result),
componentProps: {
fieldNames: {
label: 'name',
value: 'id',
},
},
},
},
{
dataIndex: 'id$dim-assets',
title: '所属组织',
hideInTable: true,
search: {
type: 'treeSelect',
options: getTreeData_api({ paging: false }).then((resp: any) => {
const formatValue = (list: any[]) => {
return list.map((item: any) => {
if (item.children) {
item.children = formatValue(item.children);
}
return {
...item,
value: JSON.stringify({
assetType: 'product',
targets: [
{
type: 'org',
id: item.id,
},
],
}),
};
});
};
return formatValue(resp.result);
}),
},
},
];
const handleSearch = (p: any) => {
params.value = p;
};
const productQuery = (p: any) => {
const sorts: any = [];
if (props.rowKey) {
sorts.push({
name: 'id',
value: props.rowKey,
});
sorts.push({
name: 'id',
value: props.rowKey,
});
}
sorts.push({ name: 'createTime', order: 'desc' });
p.sorts = sorts
return queryProductList(p)
}
const handleClick = (detail: any) => {
emit('update:rowKey', detail.id)
emit('update:detail', detail)
emit('change', detail)
}
</script>
p.sorts = sorts;
return queryProductList(p);
};
const handleClick = (detail: any) => {
emit('update:rowKey', detail.id);
emit('update:detail', detail);
emit('change', detail);
};
</script>
<style scoped lang='less'>
.search {
.search {
margin-bottom: 0;
padding-right: 0px;
padding-left: 0px;
}
</style>
}
</style>

View File

@ -0,0 +1,64 @@
<template>
<j-table
rowKey="id"
:columns="columns"
:data-source="dataSource"
bordered
:pagination="false"
>
<template #bodyCell="{ column, text, record }">
<div>
<template v-if="['valueType', 'name'].includes(column.dataIndex)">
<span>{{ text }}</span>
</template>
<template v-else>
<j-input />
<!-- TODO -->
</template>
</div>
</template>
</j-table>
</template>
<script lang="ts" setup>
import { PropType } from "vue";
type Emits = {
(e: 'update:modelValue', data: Record<string, any>[]): void;
};
const _emit = defineEmits<Emits>();
const _props = defineProps({
modelValue: {
type: Array as PropType<Record<string, any>[]>,
default: '',
}
});
const columns = [
{
title: '参数名称',
dataIndex: 'name',
with: '33%',
},
{
title: '类型',
dataIndex: 'valueType',
with: '33%',
},
{
title: '值',
dataIndex: 'value',
with: '34%',
},
];
const dataSource = computed({
get: () => {
return _props.modelValue || []
},
set: (val: any) => {
_emit('update:modelValue', val);
}
})
</script>

View File

@ -0,0 +1,215 @@
<template>
<div>
<a-form :layout="'vertical'" ref="formRef" :model="modelRef">
<j-form-item
:name="['message', 'messageType']"
label="动作类型"
:rules="[{ required: true, message: '请选择动作类型' }]"
>
<TopCard
:typeList="TypeList"
v-model:value="modelRef.message.messageType"
/>
</j-form-item>
<template v-if="deviceMessageType === 'INVOKE_FUNCTION'">
<j-form-item
:name="['message', 'functionId']"
label="功能调用"
:rules="[{ required: true, message: '请选择功能' }]"
>
<j-select
showSearch
placeholder="请选择功能"
v-model:value="modelRef.message.functionId"
@change="onFunctionChange"
>
<j-select-option
v-for="item in metadata?.functions || []"
:value="item?.id"
:key="item?.id"
>{{ item?.name }}</j-select-option
>
</j-select>
</j-form-item>
<j-form-item
v-if="modelRef.message.functionId"
:name="['message', 'inputs']"
:rules="[{ required: true, message: '请输入功能值' }]"
>
<EditTable v-model="modelRef.message.inputs" />
</j-form-item>
</template>
<template v-else-if="deviceMessageType === 'READ_PROPERTY'">
<j-form-item
:name="['message', 'properties']"
label="读取属性"
:rules="[{ required: true, message: '请选择读取属性' }]"
>
<j-select
showSearch
placeholder="请选择属性"
v-model:value="modelRef.message.properties"
>
<j-select-option
v-for="item in metadata?.properties || []"
:value="item?.id"
:key="item?.id"
>{{ item?.name }}</j-select-option
>
</j-select>
</j-form-item>
</template>
<template v-else-if="deviceMessageType === 'WRITE_PROPERTY'">
<j-row>
<j-col :span="11">
<j-form-item
:name="['message', 'properties']"
label="读取属性"
:rules="[
{ required: true, message: '请选择读取属性' },
]"
>
<j-select
showSearch
placeholder="请选择属性"
v-model:value="modelRef.message.properties"
>
<j-select-option
v-for="item in metadata?.properties || []"
:value="item?.id"
:key="item?.id"
>{{ item?.name }}</j-select-option
>
</j-select>
</j-form-item>
</j-col>
<j-col :span="2"></j-col>
<j-col :span="11">
<j-form-item
:name="['message', 'properties']"
label="读取属性"
:rules="[
{ required: true, message: '请选择读取属性' },
]"
>
<j-select placeholder="请选择">
<!-- TODO -->
</j-select>
</j-form-item>
</j-col>
</j-row>
</template>
</a-form>
</div>
</template>
<script lang="ts" setup>
import { getImage } from '@/utils/comm';
import TopCard from '../device/TopCard.vue';
import { detail } from '@/api/device/instance';
import EditTable from './EditTable.vue';
const TypeList = [
{
label: '功能调用',
value: 'INVOKE_FUNCTION',
image: getImage('/scene/invoke-function.png'),
tip: '',
},
{
label: '读取属性',
value: 'READ_PROPERTY',
image: getImage('/scene/read-property.png'),
tip: '',
},
{
label: '设置属性',
value: 'WRITE_PROPERTY',
image: getImage('/scene/write-property.png'),
tip: '',
},
];
const props = defineProps({
values: {
type: Object,
default: () => {},
},
name: {
type: Number,
default: 0,
},
thenName: {
type: Number,
default: 0,
},
branchGroup: {
type: Number,
default: 0,
},
});
const formRef = ref();
const modelRef = reactive({
message: {
messageType: undefined,
functionId: undefined,
properties: undefined,
inputs: [],
},
});
const metadata = ref<{
functions: any[];
properties: any[];
}>({
functions: [],
properties: [],
});
const deviceMessageType = computed(() => {
return modelRef.message.messageType;
});
const onFunctionChange = (val: string) => {
const _item = (metadata.value?.functions || []).find((item: any) => {
return val === item.id;
});
const list = (_item?.inputs || []).map((item: any) => {
return {
id: item.id,
name: item.name,
value: undefined,
valueType: item?.valueType?.type,
};
});
modelRef.message.inputs = list;
};
watchEffect(() => {
// console.log(props.values)
// console.log(metadata.value)
// Object.assign()
});
watch(
() => [props.values?.productDetail, props.values.deviceDetail],
([newVal1, newVal2]) => {
if (newVal1) {
if (props.values?.selector === 'fixed') {
detail(newVal2.id).then((resp) => {
if (resp.status === 200) {
metadata.value = JSON.parse(
resp.result?.metadata || '{}',
);
}
});
} else {
metadata.value = JSON.parse(newVal1?.metadata || '{}');
}
}
},
{ deep: true, immediate: true },
);
</script>

View File

@ -0,0 +1,186 @@
<template>
<!-- <j-advanced-search
:columns="columns"
type="simple"
@search="handleSearch"
class="search"
target="scene-trigger-device-device"
/> -->
<a-divider style="margin: 0" />
<j-pro-table
ref="actionRef"
model="CARD"
:columns="columns"
:params="params"
:request="deviceQuery"
:gridColumn="2"
:bodyStyle="{
paddingRight: 0,
paddingLeft: 0,
}"
>
<template #card="slotProps">
<CardBox
:value="slotProps"
@click="handleClick"
:active="value[0]?.value === slotProps.id"
:status="slotProps.state?.value"
:statusText="slotProps.state?.text"
:statusNames="{
online: 'success',
offline: 'error',
notActive: 'warning',
}"
>
<template #img>
<slot name="img">
<img
:src="getImage('/device/instance/device-card.png')"
/>
</slot>
</template>
<template #content>
<div style="width: calc(100% - 100px)">
<Ellipsis>
<span style="font-size: 16px; font-weight: 600">
{{ slotProps.name }}
</span>
</Ellipsis>
</div>
<j-row style="margin-top: 20px">
<j-col :span="12">
<div class="card-item-content-text">设备类型</div>
<div>{{ slotProps.deviceType?.text }}</div>
</j-col>
<j-col :span="12">
<div class="card-item-content-text">产品名称</div>
<Ellipsis style="width: 100%">
{{ slotProps.productName }}
</Ellipsis>
</j-col>
</j-row>
</template>
</CardBox>
</template>
</j-pro-table>
</template>
<script setup lang='ts' name='Product'>
import { query } from '@/api/device/instance';
import { getImage } from '@/utils/comm';
import { PropType } from 'vue';
type Emit = {
(e: 'update:value', data: any): void;
(e: 'change', data: any): void;
};
const actionRef = ref();
const params = ref({
terms: []
});
const props = defineProps({
value: {
type: Array as PropType<any>,
default: []
},
detail: {
type: Object,
default: () => ({}),
},
productId: {
type: String,
default: ''
}
});
const emit = defineEmits<Emit>();
const columns = [
{
title: 'ID',
dataIndex: 'id',
width: 300,
search: {
type: 'string',
},
},
{
title: '设备名称',
dataIndex: 'name',
search: {
type: 'string',
first: true,
},
},
{
title: '创建时间',
dataIndex: 'createTime',
key: 'createTime',
scopedSlots: true,
search: {
type: 'date',
},
},
{
title: '状态',
dataIndex: 'state',
key: 'state',
scopedSlots: true,
search: {
type: 'select',
options: [
{ label: '禁用', value: 'notActive' },
{ label: '离线', value: 'offline' },
{ label: '在线', value: 'online' },
],
},
},
];
const handleSearch = (p: any) => {
params.value = {
...p,
terms: [
...p.terms,
{terms: [{ column: 'productId', value: props?.productId }]}
]
}
};
const deviceQuery = (p: any) => {
const sorts: any = [];
if (props.value) {
sorts.push({
name: 'id',
value: props.value,
});
}
sorts.push({ name: 'createTime', order: 'desc' });
p.sorts = sorts;
return query(p);
};
const handleClick = (detail: any) => {
emit('update:value', [{ value: detail.id, name: detail.name }]);
emit('change', detail);
};
watchEffect(() => {
params.value = {
...params.value,
terms: params.value?.terms ? [
...(params.value.terms || []),
{terms: [{ column: 'productId', value: props?.productId }]}
] : [{terms: [{ column: 'productId', value: props?.productId }]}]
}
})
</script>
<style scoped lang='less'>
.search {
margin-bottom: 0;
padding-right: 0px;
padding-left: 0px;
}
</style>

View File

@ -0,0 +1,206 @@
<template>
<div>
<template v-for="(item, index) in tagList" :key="item.id">
<j-row :gutter="24" style="margin-bottom: 12px">
<j-col :span="4">
<span v-if="index === 0" class="tagName">标签选择</span>
<j-select
:options="[
{ label: '并且', value: 'and' },
{ label: '或者', value: 'or' },
]"
v-else
:value="item?.type"
style="width: 100%"
@select="(key) => onTypeSelect(key, index)"
/>
</j-col>
<j-col :span="16">
<j-row :gutter="24">
<j-col flex="120px">
<j-select
style="width: 120px"
:value="item.id"
placeholder="请选择标签"
:options="options"
@select="
(_, _data) => onTagSelect(_data, index)
"
/>
</j-col>
<j-col flex="auto">
<ValueItem
v-model:modelValue="item.value"
:itemType="item.valueType"
@change="onValueChange"
:options="
item.valueType === 'enum'
? (item?.dataType?.elements || []).map(
(i) => {
return {
label: i.text,
value: i.value,
};
},
)
: item.valueType === 'boolean'
? [
{ label: '是', value: true },
{ label: '否', value: false },
]
: undefined
"
/>
</j-col>
</j-row>
</j-col>
<j-col :span="4">
<j-space>
<j-button style="padding: 0 8px" @click="addItem">
<AIcon type="PlusOutlined" />
</j-button>
<j-button
danger
v-if="tagData.length > 1"
style="padding: 0 8px"
@click="deleteItem(index)"
>
<AIcon type="DeleteOutlined" />
</j-button>
</j-space>
</j-col>
</j-row>
</template>
</div>
</template>
<script lang="ts" setup>
import { PropType } from 'vue';
const props = defineProps({
value: {
type: Array as PropType<any>,
default: () => [],
},
tagData: {
type: Array as PropType<any[]>,
default: () => [],
},
});
const emits = defineEmits(['update:value', 'change']);
const options = ref<any[]>([]);
const tagList = ref<any[]>([]);
const handleItem = (data: any) => {
return {
...data,
valueType: data.valueType ? data.valueType.type : '-',
format: data.valueType ? data.valueType.format : undefined,
options: data.valueType ? data.valueType.elements : undefined,
value: data.value,
};
};
const addItem = () => {
tagList.value.push({ type: 'and' });
};
const deleteItem = (_index: number) => {
tagList.value.splice(_index, 1);
};
const onTypeSelect = (key: any, _index: number) => {
const indexItem = tagList[_index];
indexItem.type = key;
tagList.value[_index] = indexItem;
};
const onTagSelect = (_data: any, _index: number) => {
const newList = [...unref(tagList)];
const indexType = newList[_index].type;
newList.splice(
_index,
1,
handleItem({ ..._data, value: undefined, type: indexType }),
);
tagList.value = newList;
};
watch(
() => [props.tagData, props.value],
([newTag, newVal]) => {
options.value = newTag.map((item: any) => {
return { label: item.name, value: item.id, ...item };
});
if (newVal && newVal[0] && newVal[0].name && newTag && newTag.length) {
const names: string[] = [];
const newTagList = newVal[0].value
.filter((valueItem: any) => {
return newTag.some(
(item: any) => valueItem.column === item.id,
);
})
.map((valueItem: any) => {
const oldItem = newTag.find(
(item: any) => item.id === valueItem.column,
);
if (oldItem) {
names.push(oldItem.name);
return {
...handleItem(oldItem),
value: valueItem.value,
type: valueItem.type,
};
}
return valueItem;
});
tagList.value = newTagList;
} else {
tagList.value = [{}];
}
},
{
immediate: true,
deep: true,
},
);
const onValueChange = () => {
const newValue = tagList.value
.filter((item) => !!item.value)
.map((item: any) => {
return {
column: item.id,
type: item.type,
value: item.value,
};
});
const arr = newValue
.filter((item) => !!item.value)
.map((item: any) => {
return {
column: item.name,
type: item.type,
value: item.value,
};
});
emits('update:value', [{ value: newValue, name: '标签' }]);
emits('change', [{ value: newValue, name: '标签' }], undefined);
};
</script>
<style lang="less" scoped>
.tagName::before {
position: relative;
left: 70px;
display: inline-block;
margin-right: 4px;
color: #ff4d4f;
font-size: 14px;
font-family: SimSun, sans-serif;
line-height: 1;
content: '*';
}
</style>

View File

@ -0,0 +1,149 @@
<template>
<div class="trigger-way-warp" :class="{ disabled: disabled }">
<div
v-for="item in typeList"
:key="item.value"
class="trigger-way-item"
:class="{
active: _value === item.value,
labelBottom: labelBottom,
}"
@click="onSelect(item.value)"
>
<div class="'way-item-title">
<span class="way-item-label">{{ item.label }}</span>
<j-popover v-if="item.tip">
<AIcon
type="QuestionCircleOutlined"
class="way-item-icon"
/>
</j-popover>
</div>
<div class="way-item-image">
<img :width="48" :src="item.image" />
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { PropType } from 'vue';
const props = defineProps({
typeList: {
type: Array as PropType<any>,
default: () => [],
},
value: {
type: String,
default: '',
},
disabled: {
type: Boolean,
default: false,
},
labelBottom: {
type: Boolean,
default: false,
},
});
const emits = defineEmits(['update:value', 'change']);
const _value = ref(props?.value || '');
watch(
() => props.value,
(newValue) => {
_value.value = newValue || ""
},
{ immediate: true, deep: true },
);
const onSelect = (_type: string) => {
if (!props.disabled) {
_value.value = _type;
emits('update:value', _type);
emits('change', _type)
}
};
</script>
<style lang="less" scoped>
.trigger-way-warp {
display: flex;
flex-wrap: wrap;
gap: 16px 24px;
width: 100%;
.trigger-way-item {
display: flex;
align-items: center;
justify-content: space-between;
width: 237px;
padding: 12px 16px;
border: 1px solid #e0e4e8;
border-radius: 2px;
cursor: pointer;
transition: all 0.3s;
.way-item-title {
span {
font-size: 16px;
}
.way-item-label {
padding-right: 6px;
color: rgba(#000, 0.64);
}
.way-item-icon {
color: rgba(#000, 0.5);
}
}
.way-item-image {
margin: 0 !important;
opacity: 0.6;
}
&:hover {
.way-item-image {
opacity: 0.8;
}
}
&.active {
border-color: @primary-color-active;
.way-item-image {
opacity: 1;
}
}
&.labelBottom {
flex-direction: column-reverse;
grid-gap: 16px;
gap: 0;
align-items: center;
width: auto;
padding: 8px 16px;
p {
margin: 0;
}
}
}
&.disabled {
.trigger-way-item {
cursor: not-allowed;
&:hover {
color: initial;
opacity: 0.6;
}
&.active {
opacity: 1;
}
}
}
}
</style>

View File

@ -1,14 +1,354 @@
<template>
<div>
</div>
<div>
<a-form :layout="'vertical'" ref="formRef" :model="modelRef">
<j-form-item
name="selector"
label="选择方式"
v-show="!(list.length === 1)"
:rules="[{ required: true, message: '请选择方式' }]"
>
<TopCard
:typeList="list"
v-model:value="modelRef.selector"
@change="onSelectorChange"
/>
</j-form-item>
<j-form-item
v-if="modelRef.selector === 'fixed'"
name="selectorValues"
:rules="[{ required: true, message: '请选择设备' }]"
>
<Device
:productId="values.productDetail.id"
v-model:value="modelRef.selectorValues"
@change="onDeviceChange"
/>
</j-form-item>
<j-form-item
v-else-if="modelRef.selector === 'relation'"
label="关系"
name="selectorValues"
:rules="[{ required: true, message: '请选择关系' }]"
>
<j-select
placeholder="请选择关系"
v-model:value="modelRef.selectorValues"
:options="relationList"
show-search
@change="onRelationChange"
/>
</j-form-item>
<j-form-item
v-else-if="modelRef.selector === 'tag'"
name="selectorValues"
:rules="[{ required: true, message: '请选择标签' }]"
>
<Tag
v-model:value="modelRef.selectorValues"
:tagData="tagList"
@change="onTagChange"
/>
</j-form-item>
<j-form-item
v-else
name="selectorValues"
label="变量"
:rules="[{ required: true, message: '请选择' }]"
>
<j-tree-select
style="width: 100%; height: 100%"
:tree-data="builtInList"
v-model:value="modelRef.selectorValues"
placeholder="请选择参数"
@select="onVariableChange"
>
<template #title="{ name, description }">
<a-space>
{{ name }}
<span style="color: grey; margin-left: 5px">{{
description
}}</span>
</a-space>
</template>
</j-tree-select>
</j-form-item>
</a-form>
</div>
</template>
<script setup lang='ts' name="Device">
import { useSceneStore } from '@/store/scene';
import TopCard from './TopCard.vue';
import { storeToRefs } from 'pinia';
import { queryBuiltInParams } from '@/api/rule-engine/scene';
import { getImage } from '@/utils/comm';
import NoticeApi from '@/api/notice/config';
import Device from './Device.vue';
import Tag from './Tag.vue';
const props = defineProps({
values: {
type: Object,
default: () => {},
},
name: {
type: Number,
default: 0,
},
thenName: {
type: Number,
default: 0,
},
branchGroup: {
type: Number,
default: 0,
},
parallel: {
type: Boolean,
},
});
const emits = defineEmits(['save', 'cancel']);
const sceneStore = useSceneStore();
const { data } = storeToRefs(sceneStore);
const formRef = ref();
const modelRef = reactive({
selector: 'fixed',
selectorValues: undefined,
deviceId: '',
source: '',
relationName: '',
upperKey: '',
});
const list = ref<any[]>([]);
const builtInList = ref<any[]>([]);
const tagList = ref<any[]>([]);
const relationList = ref<any[]>([]);
const TypeList = [
{
label: '自定义',
value: 'fixed',
image: getImage('/scene/device-custom.png'),
tip: '自定义选择当前产品下的任意设备',
},
{
label: '按关系',
value: 'relation',
image: getImage('/scene/device-relation.png'),
tip: '选择与触发设备具有相同关系的设备',
},
{
label: '按标签',
value: 'tag',
image: getImage('/scene/device-tag.png'),
tip: '按标签选择产品下具有特定标签的设备',
},
{
label: '按变量',
value: 'variable',
image: getImage('/scene/device-variable.png'),
tip: '选择设备ID为上游变量值的设备',
},
];
const filterTree = (nodes: any[]) => {
if (!nodes?.length) {
return nodes;
}
return nodes.filter((it) => {
if (
it.children.find(
(item: any) =>
item.id.indexOf('deviceId' || 'device_id' || 'device_Id') >
-1,
) &&
!it.children.find((item: any) => item.id.indexOf('boolean') > -1)
) {
return true;
}
return false;
});
};
const treeDataFilter = (arr: any[]) => {
if (Array.isArray(arr) && arr.length) {
const list: any[] = [];
arr.map((item: any) => {
if (item.children) {
const children = treeDataFilter(item.children);
if (children.length) {
list.push({
...item,
title: item.name,
value: item.id,
disabled: true,
children,
});
}
} else {
if (
item.children.find(
(item: any) =>
item.id.indexOf(
'deviceId' || 'device_id' || 'device_Id',
) > -1,
) &&
!item.children.find(
(item: any) => item.id.indexOf('bolaen') > -1,
)
) {
list.push(item);
}
}
});
return list;
} else {
return [];
}
};
const sourceChangeEvent = async () => {
const _params = {
branch: props.thenName,
branchGroup: props.branchGroup,
action: props.name - 1,
};
const resp = await queryBuiltInParams(unref(data), _params);
if (resp.status === 200) {
// const array = filterTree(resp.result as any[]);
//
// if (props.formProductId === DeviceModel.productId)// TODO
console.log(array);
const arr = treeDataFilter(resp.result as any[]);
builtInList.value = array;
}
};
const queryRelationList = () => {
NoticeApi.getRelationUsers({
paging: false,
sorts: [{ name: 'createTime', order: 'desc' }],
terms: [{ termType: 'eq', column: 'objectTypeName', value: '设备' }],
}).then((resp) => {
if (resp.status === 200) {
relationList.value = (resp.result as any[]).map((item) => {
return {
label: item.name,
value: item.relation,
};
});
}
});
};
const filterType = async () => {
const _list = TypeList.filter((item) => item.value === 'fixed');
if (unref(data)?.trigger?.type === 'device') {
//
const res = await NoticeApi.getRelationUsers({
paging: false,
sorts: [{ name: 'createTime', order: 'desc' }],
terms: [
{ termType: 'eq', column: 'objectTypeName', value: '设备' },
],
});
if (res.status === 200 && res.result.length !== 0) {
const array = TypeList.filter((item) => item.value === 'relation');
_list.push(...array);
}
//
const tag = JSON.parse(
props.values.productDetail?.metadata || '{}',
)?.tags;
if (tag && tag.length !== 0) {
const array = TypeList.filter((item) => item.value === 'tag');
_list.push(...array);
}
//
if (
builtInList.value.length !== 0 &&
!props.parallel &&
props.name !== 0
) {
const array = TypeList.filter((item) => item.value === 'variable');
_list.push(...array);
}
list.value = _list;
} else {
if (
builtInList.value.length !== 0 &&
!props.parallel &&
props.name !== 0
) {
const array = TypeList.filter((item) => item.value === 'variable');
_list.push(...array);
}
list.value = _list;
}
};
const onSelectorChange = (val: string) => {
if (val === 'relation') {
queryRelationList();
}
};
const onDeviceChange = (_detail: any) => {
if (_detail) {
emits('save', modelRef, _detail);
}
};
const onRelationChange = (val: any, options: any) => {
modelRef.deviceId = 'deviceId';
modelRef.source = 'upper';
modelRef.selectorValues = val;
modelRef.upperKey = 'scene.deviceId';
modelRef.relationName = options.label;
emits('save', modelRef, {});
};
const onTagChange = (val: any[], arr: any[]) => {
if (val) {
modelRef.deviceId = 'deviceId';
modelRef.source = 'fixed';
}
if (arr) {
tagList.value = arr;
}
};
const onVariableChange = (val: any, node: any) => {
modelRef.deviceId = val;
// modelRef.deviceDetail = node;
modelRef.selectorValues = [{ value: val, name: node.description }] as any;
};
watchEffect(() => {
Object.assign(modelRef, props.values);
});
watch(
() => props.values.productDetail,
async (newVal) => {
await sourceChangeEvent();
if (newVal) {
const metadata = JSON.parse(newVal?.metadata || '{}');
tagList.value = metadata?.tags || [];
filterType();
}
},
{
immediate: true,
deep: true,
},
);
</script>
<style scoped lang='less'>
</style>

View File

@ -1,6 +1,13 @@
<template>
<j-modal title="执行动作" visible :width="860" @cancel="onCancel" @ok="save" :maskClosable="false">
<j-steps :current='DeviceModel.current' @change='stepChange'>
<j-modal
title="执行动作"
visible
:width="860"
@cancel="onCancel"
@ok="save"
:maskClosable="false"
>
<j-steps :current="current" @change="stepChange">
<j-step>
<template #title>选择产品</template>
</j-step>
@ -11,36 +18,82 @@
<template #title>执行动作</template>
</j-step>
</j-steps>
<j-divider style='margin-bottom: 10px;' />
<div class='steps-content'>
<Product v-if='DeviceModel.current === 0' v-model:rowKey='DeviceModel.productId'
v-model:detail='DeviceModel.productDetail' />
<j-divider style="margin-bottom: 10px" />
<div class="steps-content">
<Product
v-if="current === 0"
v-model:rowKey="DeviceModel.productId"
v-model:detail="DeviceModel.productDetail"
/>
<Device
v-else-if="current === 1"
:name="name"
:parallel="parallel"
:branchGroup="branchGroup"
:thenName="thenName"
:values="DeviceModel"
@save="onDeviceSave"
/>
<Action v-else-if="current === 2"
:name="name"
:branchGroup="branchGroup"
:thenName="thenName"
:values="DeviceModel"
/>
</div>
<template #footer>
<div class='steps-action'>
<j-button v-if='DeviceModel.current === 0' @click='cancel'>取消</j-button>
<j-button v-else @click='prev'>上一步</j-button>
<j-button type='primary' v-if='DeviceModel.current < 2' @click='saveClick'>下一步</j-button>
<j-button type='primary' v-else @click='saveClick'>确定</j-button>
</div>
</template>
<div class="steps-action">
<j-button v-if="current === 0" @click="onCancel">取消</j-button>
<j-button v-else @click="prev">上一步</j-button>
<j-button type="primary" v-if="current < 2" @click="saveClick"
>下一步</j-button
>
<j-button type="primary" v-else @click="saveClick"
>确定</j-button
>
</div>
</template>
</j-modal>
</template>
<script lang="ts" setup>
import { DeviceModelType } from './typings';
import Product from './Product.vue';
import Device from './device/index.vue';
import Action from './actions/index.vue';
import { onlyMessage } from '@/utils/comm';
import { detail } from '@/api/device/product'
type Emit = {
(e: 'cancel'): void
(e: 'save', data: any, options: Record<string, any>): void
}
(e: 'cancel'): void;
(e: 'save', data: any, options: Record<string, any>): void;
};
const props = defineProps({
value: {
type: Object,
default: () => {},
},
name: {
type: Number,
default: 0,
},
thenName: {
type: Number,
default: 0,
},
branchGroup: {
type: Number,
default: 0,
},
parallel: {
type: Boolean,
},
});
const current = ref<number>(0);
const DeviceModel = reactive<DeviceModelType>({
steps: [],
current: 0,
productId: '',
deviceId: '',
productDetail: {},
@ -58,38 +111,64 @@ const DeviceModel = reactive<DeviceModelType>({
columns: [],
actionName: '',
tagList: [],
})
});
const emit = defineEmits<Emit>()
const cancel = () => {
const emit = defineEmits<Emit>();
const onCancel = () => {
emit('cancel');
};
const save = async(step?: number) => {
let _step = step !== undefined ? step : DeviceModel.current
if (_step === 0) {
DeviceModel.productId ? DeviceModel.current = 1 : onlyMessage('请选择产品', 'error')
} else if (_step === 1) {
} else {
const save = async (step?: number) => {
let _step = step !== undefined ? step : current.value;
if (_step === 0) {
DeviceModel.productId
? (current.value = 1)
: onlyMessage('请选择产品', 'error');
} else if (_step === 1) {
DeviceModel.deviceId
? (current.value = 2)
: onlyMessage('请选择设备', 'error');
} else {
}
};
const stepChange = (step: number) => {
if (step !== 0) {
save(step - 1)
save(step - 1);
} else {
DeviceModel.current = 0
current.value = 0;
}
}
};
const prev = () => {
DeviceModel.current = DeviceModel.current - 1
}
const saveClick = () => save()
current.value -= 1;
};
const saveClick = () => save();
const onDeviceSave = (_data: any, _detail: any) => {
Object.assign(DeviceModel, _data)
DeviceModel.deviceId = _detail.id
DeviceModel.deviceDetail = _detail
}
watch(
() => props.value,
(newValue) => {
Object.assign(DeviceModel, newValue);
if(newValue?.productId){
detail(newValue.productId).then(resp => {
if(resp.status === 200){
DeviceModel.productDetail = resp.result
}
})
}
},
{ immediate: true, deep: true },
);
</script>
<style lang="less" scoped>
.steps-steps {
width: 100%;
@ -100,5 +179,8 @@ const saveClick = () => save()
.steps-content {
width: 100%;
max-height: 500px;
overflow-y: auto;
overflow-x: hidden;
}
</style>

View File

@ -2,12 +2,12 @@ import { ProductItem } from '@/views/device/Product/typings';
import { ActionsDeviceProps } from '../../../typings';
type DeviceModelType = {
steps: {
key: string;
title: string;
content: React.ReactNode;
}[];
current: number;
// steps: {
// key: string;
// title: string;
// content: React.ReactNode;
// }[];
// current: number;
productId: string;
deviceId: string;
productDetail: ProductItem | any;

View File

@ -1,7 +1,7 @@
<template>
<div>
<template v-if="actionType === 'device'">
<Device v-bind="props" :value="data?.device" @cancel="onCancel" @save="onPropsOk" />
<Device v-bind="props" :value="data?.device" @cancel="onCancel" @save="onPropsOk" :thenName="branchesName" />
</template>
<template v-else-if="actionType === 'notify'">
<Notify v-bind="props" :value="data?.notify" @cancel="onCancel" @save="onPropsOk" />

View File

@ -19,7 +19,7 @@
<template #title="{ name, description }">
<a-space>
{{ name }}
<spn style="color: grey; margin-left: 5px">{{ description }}</spn>
<span style="color: grey; margin-left: 5px">{{ description }}</span>
</a-space>
</template>
</a-tree-select>

11427
yarn.lock

File diff suppressed because it is too large Load Diff