fix: 修复公网host多次校验提示

* fix: 修改bug

* feat: 修改bug优化ui

* fix: 修改bug

* fix: 修改bug

* fix: 修改bug

* fix: 修改物模型导入bug

* fix: 修改bug

* fix: 修改bug

* fix: 修改bug

* fix: 设备功能

* fix: 设备功能删除console.log

* fix: 修改onenet和ctwing

* fix: 修改bug

* fix: 修改首页视图

* fix: 修复产品跳转设备接口异常

* fix: 17008、16996

* fix: 修复Iframe页面无法访问

* fix: bug#17006

* fix: 修改bug

* feat: 17019

* fix: 修改文档

* fix: ctwing右侧提示

* fix: bug#16975

* fix: 修改17021

* fix: bug#17018

* fix: 优化oauth页面跳转逻辑

* fix: 产品导入提示bug

* fix: 产品导入bug

* fix: bug#17035

* fix: bug#17041

* fix: bug#17041

* fix: bug#17041

* fix: bug#17051

* fix: 初始化页面添加菜单控制

* fix: 修复公网host多次校验提示
This commit is contained in:
XieYongHong 2023-08-07 10:18:10 +08:00 committed by GitHub
parent acc6ba0e6f
commit cee1c99381
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
65 changed files with 764 additions and 452 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 120 KiB

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 116 KiB

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 125 KiB

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

View File

Before

Width:  |  Height:  |  Size: 274 KiB

After

Width:  |  Height:  |  Size: 274 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 307 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

View File

@ -0,0 +1,80 @@
<template>
<div class="view-content">
<div
class="select-item"
v-for="item in list"
:key="item.id"
@click="onChange(item.id)"
:class="{
active: currentView === item.id,
}"
>
<img :src="getImage(`/home/home-view/${item.id}${currentView === item.id ? '-active' : ''}.png`)" alt="" />
</div>
</div>
</template>
<script lang="ts" setup>
import { getImage } from '@/utils/comm';
const list = [
{
id: 'device',
name: '设备接入视图',
},
{
id: 'ops',
name: '运营管理视图',
},
{
id: 'comprehensive',
name: '综合管理视图',
},
];
const props = defineProps({
value: {
type: String,
default: ''
},
})
const emits = defineEmits(['update:value', 'change'])
const currentView = ref<string>('');
const onChange = (id: string) => {
emits('change', id);
emits('update:value', id)
}
watchEffect(() => {
currentView.value = (props.value || '') as string
})
</script>
<style lang="less" scoped>
.view-content {
display: flex;
justify-content: space-between;
.select-item {
cursor: pointer;
width: 30%;
border-radius: 14px;
color: #333333;
overflow: hidden;
img {
width: 100%;
height: 100%;
background-size: cover;
}
&:hover {
box-shadow: 0px 3px 6px -4px rgba(0, 0, 0, 0.12),
0px 6px 16px 0px rgba(0, 0, 0, 0.08),
0px 9px 16px 8px rgba(0, 0, 0, 0.1);
}
}
}
</style>

View File

@ -1,5 +1,10 @@
<template>
<j-form-item name="type" label="读写类型" required>
<j-form-item name="type" label="读写类型" :rules="[
{
required: true,
message: '请选择读写类型'
}
]">
<j-select
v-model:value="myValue"
mode="multiple"

View File

@ -369,7 +369,7 @@ const findComponents = (code: string, level: number, isApp: boolean, components:
if (level === 1) { // BasicLayoutPage
return myComponents ? () => myComponents() : BasicLayoutPage
} else if (isApp){ // iframe
return () => Iframe
return Iframe
} else if (level === 2) { // BlankLayoutPage or components
return myComponents ? () => myComponents() : BlankLayoutPage
} else if(myComponents) { // components

View File

@ -215,11 +215,17 @@ const _channelListAll = computed(() => {
})
const channelList = computed(() => {
return _channelListAll.value.map((item: any) => ({
const list:any = [];
_channelListAll.value.forEach((item: any) => {
if(item?.state?.value !== 'disabled'){
list.push({
provider: item.provider,
value: item.id,
label: item.name,
}));
})
}
});
return list
})
const channelSelect = (key: string, detail: any) => {

View File

@ -56,7 +56,7 @@
class="tree-left-tag"
v-if="data.id !== '*'"
:color="colorMap.get(data?.uniformState?.value)"
>{{ data?.pointNumber === 0 ? '--' : (data?.uniformState?.value === 'normal' ? '运行中' : data?.uniformState?.text) }}</j-tag
>{{ data?.uniformState?.text }}</j-tag
>
<j-tag
class="tree-left-tag2"
@ -67,7 +67,7 @@
"
>
{{
data?.state?.value === 'disabled' ? '禁用' : '运行中'
data?.state?.value === 'disabled' ? data?.state?.text : data?.runningState?.text
}}
</j-tag
>
@ -94,6 +94,7 @@
? '启用'
: '禁用',
}"
:disabled="data?.runningState?.value === 'stopped' && data?.state?.value!== 'disabled'"
hasPermission="DataCollect/Collector:action"
:popConfirm="{
title:

View File

@ -12,54 +12,94 @@
</div>
<h1>1. 概述</h1>
<div>
DuerOS支持家居场景下的云端控制该页面主要将平台的产品与DuerOS支持语音控制的产品进行映射以到达小度平台控制本平台设备的目的
DuerOS支持在家居场景下进行云端控制该页面主要将平台的产品与DuerOS支持语音控制的产品进行映射达到小度平台控制本平台设备的目的
</div>
<h1>2. 操作步骤</h1>
<div>
<h2>1在百度小度技能平台创建技能并授权完成物联网平台与DuerOS的关联</h2>
<div class="image">
<j-image width="100%" :src="getImage('/cloud/dueros-doc.jpg')" />
</div>
<h1>授权地址</h1>
<div>物联网平台的登录地址注意需要为https</div>
<div>请复制并填写: https://{location.host}/#/user/login</div>
<h1>Client_Id</h1>
<div>请填写系统管理-应用管理中的clientId</div>
<div class="image">
<j-image width="100%" :src="getImage('/cloud/dueros-doc1.png')" />
</div>
<h1>回调地址</h1>
<div>请复制DuerOS平台中的值填写到系统管理-应用管理中-redirectUrl中</div>
<div class="image">
<j-image width="100%" :src="getImage('/cloud/dueros-doc2.png')" />
</div>
<h1>Token地址</h1>
<div>请复制并填写HTTPS://{location.host}/api/v1/token</div>
<h1>ClientSecret</h1>
<div>请复制系统管理-应用管理中的secureKey填写到DuerOS平台</div>
<div class="image">
<j-image width="100%" :src="getImage('/cloud/dueros-doc3.png')" />
</div>
<div></div>
<h1>WebService</h1>
<div>请复制并填写/dueros/product/_query</div>
<h2>2登录物联网平台进行平台内产品与DuerOS产品的数据映射</h2>
<h2>
3智能家居用户通过物联网平台中的用户登录小度APP获取平台内当前用户的所属设备获取后即可进行语音控制
</h2>
</div>
<h1>3. 配置说明</h1>
<div>1在百度小度技能平台创建技能并授权</div>
<div>
<h2>
1设备类型为DuerOS平台拟定的标准规范设备类型将决定动作映射动作的下拉选项以及属性映射Dueros属性的下拉选项
</h2>
2在物联网平台 系统管理--应用管理中配置应用完成与DuerOS的关联
</div>
<div class="image">
<j-image
width="100%"
:src="getImage('/cloud/dueros-doc.jpg')"
/>
<div class="desc">新建DuerOS</div>
</div>
<j-descriptions
bordered
size="small"
:column="1"
:labelStyle="{ width: '100px' }"
>
<j-descriptions-item label="参数">说明</j-descriptions-item>
<j-descriptions-item label="授权地址"
>物联网平台的登录地址http://host:port/JetLinks.cn</j-descriptions-item
>
<j-descriptions-item label="Client_Id">
请复制并填写物联网平台的appId
</j-descriptions-item>
<j-descriptions-item label="Scope">
以空格分割的权限列表若不传递此参数代表请求用户的默认权限
</j-descriptions-item>
<j-descriptions-item label="Token地址">
请复制并填写HTTPS://host:port/api/v1/token
</j-descriptions-item>
<j-descriptions-item label="ClientSecret">
请复制并填写物联网平台的secureKey
</j-descriptions-item>
<j-descriptions-item label="WebService">
请复制并填写/dueros/product/_query
</j-descriptions-item>
</j-descriptions>
<div class="image">
<j-image
width="100%"
:src="getImage('/cloud/dueros-doc1.png')"
/>
<div class="desc">新建应用</div>
</div>
<j-descriptions
bordered
size="small"
:column="1"
:labelStyle="{ width: '100px' }"
>
<j-descriptions-item label="参数">说明</j-descriptions-item>
<j-descriptions-item label="appId">
第三方应用唯一标识物联网平台的自动生成
</j-descriptions-item>
<j-descriptions-item label="secureKey">
secureKey 第三方应用唯一标识匹配的秘钥物联网平台的自动生成
</j-descriptions-item>
<j-descriptions-item label="角色">
为应用用户分配角色根据绑定的角色进行系统菜单赋权
</j-descriptions-item>
<j-descriptions-item label="组织">
为应用用户分配所属组织根据绑定的组织进行数据隔离
</j-descriptions-item>
<j-descriptions-item label="redirectUrl">
请复制并填写小度平台的回调地址
</j-descriptions-item>
<j-descriptions-item label="IP白名单">
允许指定IP地址访问
</j-descriptions-item>
</j-descriptions>
<div>3登录物联网平台进行平台内产品与DuerOS产品的数据映射</div>
<div>
4智能家居用户通过物联网平台中的用户登录小度APP获取平台内当前用户的所属设备获取后即可进行语音控制
</div>
</div>
<h1>配置说明</h1>
<div>
设备类型为DuerOS平台拟定的标准规范设备类型将决定动作映射动作的下拉选项以及属性映射Dueros属性的下拉选项
</div>
</div>
</template>
<script lang="ts" setup>
import { getImage } from '@/utils/comm';
</script>
<style lang="less" scoped>
@ -97,6 +137,13 @@ import { getImage } from '@/utils/comm';
.image {
margin: 16px 0;
.desc {
width: 100%;
text-align: center;
color: rgb(138, 143, 141);
margin-top: 10px;
}
}
}
</style>

View File

@ -1,22 +1,6 @@
<template>
<div class="choose-view">
<div class="view-content">
<div
:span="8"
class="select-item"
v-for="item in list"
:key="item.id"
@click="currentView = item.id"
:class="{
active: currentView === item.id,
}"
>
<div class="select-item-box">
<div class="title">{{ item.name }}</div>
</div>
<img :src="getImage(`/home/home-view/${item.id}.png`)" alt="" />
</div>
</div>
<HomeView v-model:value="currentView" />
<div class="btn">
<j-button type="primary" @click="confirm">保存修改</j-button>
</div>
@ -25,24 +9,11 @@
<script lang="ts" setup>
import { getMe_api, getView_api, setView_api } from '@/api/home';
import { getImage, onlyMessage } from '@/utils/comm';
import { onlyMessage } from '@/utils/comm';
import HomeView from '@/components/HomeView/index.vue';
const currentView = ref<string>('');
const isApiUser = ref<boolean>();
const list = [
{
id: 'device',
name: '设备接入视图',
},
{
id: 'ops',
name: '运营管理视图',
},
{
id: 'comprehensive',
name: '综合管理视图',
},
];
function getViews() {
// api
@ -59,10 +30,13 @@ function getViews() {
})
.then((resp: any) => {
if (resp?.status === 200) {
if (resp.result) currentView.value = resp.result?.content;
else if (resp.result?.username === 'admin') {
if (resp.result) {
currentView.value = resp.result?.content;
} else if (resp.result?.username === 'admin') {
currentView.value = 'comprehensive';
} else currentView.value = 'init';
} else {
currentView.value = 'device';
}
}
});
}
@ -84,45 +58,6 @@ onMounted(() => {
width: 100%;
padding: 4% 9% 0 9%;
box-sizing: border-box;
.view-content {
display: flex;
justify-content: space-between;
.select-item {
cursor: pointer;
width: 30%;
border-radius: 14px;
overflow: hidden;
color: #333333;
.select-item-box {
position: relative;
width: 100%;
.title {
position: absolute;
top: 36px;
left: 36px;
font-size: 24px;
}
}
img {
width: 100%;
height: 100%;
background-size: cover;
}
&.active {
border: 1px solid @primary-color-active;
color: @primary-color-active;
}
&:hover {
box-shadow: 0px 3px 6px -4px rgba(0, 0, 0, 0.12),
0px 6px 16px 0px rgba(0, 0, 0, 0.08),
0px 9px 16px 8px rgba(0, 0, 0, 0.1);
}
}
}
.btn {
display: flex;

View File

@ -109,7 +109,7 @@ const query = reactive({
type: 'number',
componentProps:{
precision:0,
min:0
min:1
}
},
scopedSlots: true,

View File

@ -101,7 +101,9 @@
<j-col
:span="24"
v-if="
modelRef.type === 'INVOKE_FUNCTION' && modelRef.function && modelRef.inputs.length
modelRef.type === 'INVOKE_FUNCTION' &&
modelRef.function &&
modelRef.inputs.length
"
>
<!-- <j-form-item
@ -169,7 +171,7 @@ const funcChange = (val: string) => {
name: item.name,
value: undefined,
valueType: item?.valueType?.type,
required: item?.expands?.required
required: item?.expands?.required,
};
});
modelRef.inputs = list;
@ -177,22 +179,25 @@ const funcChange = (val: string) => {
};
const saveBtn = async () => {
const _inputs = await inputsRef.value?.onSave();
console.log(_inputs)
if(!_inputs){
return
}
formRef.value.validate().then(async () => {
const _data = await formRef.value?.validate();
if (!_data) return;
const values = toRaw(modelRef);
let _inputs: any[] = [];
if (values.type === 'READ_PROPERTY') {
await readProperties(instanceStore.current?.id || '', [
values.properties,
]);
} else if (values.type === 'WRITE_PROPERTY') {
await settingProperties(instanceStore.current?.id || '', {
[values.properties || '']: values.propertyValue,
});
} else {
if (modelRef.inputs.length) {
_inputs = modelRef.inputs.filter((i: any) => !i.value && i?.required);
if (_inputs.length) {
const _inputs = await inputsRef.value?.onSave();
if (!_inputs) {
return;
}
}
if (values.type === 'INVOKE_FUNCTION') {
const list = (modelRef?.inputs || [])?.filter((it: any) => !!it.value);
const obj = {};
list.map((it: any) => {
@ -205,18 +210,7 @@ const saveBtn = async () => {
...obj,
},
);
} else {
if (values.type === 'READ_PROPERTY') {
await readProperties(instanceStore.current?.id || '', [
values.properties,
]);
} else {
await settingProperties(instanceStore.current?.id || '', {
[values.properties || '']: values.propertyValue,
});
}
}
});
};
defineExpose({ saveBtn });

View File

@ -8,15 +8,15 @@
<template #title>
<div>
<div style="display: flex; align-items: center">
<a-tooltip>
<j-tooltip>
<template #title>{{
productStore.current.name
}}</template>
<div class="productDetailHead">
{{ productStore.current.name }}
</div>
</a-tooltip>
<div style="margin: -5px 0 0 20px">
</j-tooltip>
<div style="margin: -5px 0 0 20px" v-if="permissionStore.hasPermission('device/Product:action')">
<j-popconfirm
title="确认禁用"
@confirm="handleUndeploy"
@ -50,6 +50,27 @@
/>
</j-popconfirm>
</div>
<div style="margin: -5px 0 0 20px" v-else>
<j-tooltip>
<template #title>暂无权限请联系管理员</template>
<j-switch
v-if="productStore.current.state === 1"
:checked="productStore.current.state === 1"
checked-children="正常"
un-checked-children="禁用"
:disabled="!permissionStore.hasPermission('device/Product:action')"
/>
<j-switch
v-if="productStore.current.state === 0"
:unCheckedValue="
productStore.current.state === 0
"
checked-children="正常"
un-checked-children="禁用"
:disabled="!permissionStore.hasPermission('device/Product:action')"
/>
</j-tooltip>
</div>
</div>
</div>
</template>
@ -176,7 +197,7 @@ const tabs = {
watch(
() => route.params.id,
(newId) => {
if (newId) {
if (newId && route.name === 'device/Product/Detail') {
productStore.reSet();
productStore.tabActiveKey = 'Info';
productStore.refresh(newId as string);

View File

@ -389,29 +389,37 @@ const beforeUpload = (file: any) => {
reader.readAsText(file);
reader.onload = async (result) => {
const text = result.target?.result;
console.log('text: ', text);
console.log(file);
// console.log('text: ', text);
// console.log(file);
if (!file.type.includes('json')) {
onlyMessage('请上传json格式文件', 'error');
return false;
}
if(!text){
onlyMessage('文件内容不能为空','error')
return false;
}
try {
const data = JSON.parse(text || '{}');
const data = JSON.parse(text);
//
data.state = 0;
if (Array.isArray(data)) {
onlyMessage('文件内容不能为空', 'error');
onlyMessage('请上传正确格式文件', 'error');
return false;
}
delete data.state;
if(!data?.name){
data.name = "产品" + Date.now();
}
const res = await updateDevice(data);
if (res.status === 200) {
onlyMessage('操作成功');
tableRef.value?.reload();
}
return true;
} catch {
onlyMessage('请上传json格式文件', 'error');
} catch(e) {
onlyMessage('请上传正确格式文件', 'error');
}
return true;
};

View File

@ -24,6 +24,7 @@
title: hasOperate('add', type)
? '当前的存储方式不支持新增'
: '新增',
getPopupContainer: getPopupContainer,
}"
@click="handleAddClick()"
placement="topRight"
@ -43,8 +44,10 @@
? '当前的存储方式不支持新增'
: !editStatus ? '暂无改动数据': '保存',
placement: hasOperate('add', type) ? 'topRight' : 'top',
getPopupContainer: getPopupContainer,
}"
@click="handleSaveClick()"
placement="topRight"
>
保存
</PermissionButton>
@ -93,11 +96,12 @@
v-else
v-model:value="data.record.expands"
:id="data.record.id"
:type="data.record.valueType.type"
:disabled="target === 'device' && productNoEdit.id?.includes?.(data.record._sortIndex)"
:record="data.record"
:tooltip="target === 'device' && productNoEdit.id?.includes?.(data.record._sortIndex) ? {
title: '继承自产品物模型的数据不支持删除',
} : undefined"
:type="data.record.valueType.type"
/>
</template>
<template #action="{data}">
@ -111,6 +115,7 @@
@click="copyItem(data.record, data.index)"
:tooltip="{
title: operateLimits('add', type) ? '当前的存储方式不支持复制' : '复制',
getPopupContainer: getPopupContainer,
}"
>
<AIcon type="CopyOutlined" />
@ -124,6 +129,7 @@
@click="handleAddClick(null, data.index)"
:tooltip="{
title: operateLimits('add', type) ? '当前的存储方式不支持新增' : '新增',
getPopupContainer: getPopupContainer,
}"
>
<AIcon type="PlusSquareOutlined" />
@ -136,6 +142,7 @@
@click="showDetail(data.record)"
:tooltip="{
title: '详情',
getPopupContainer: getPopupContainer,
}"
>
<AIcon type="FileSearchOutlined" />
@ -152,9 +159,11 @@
onConfirm: async () => {
await removeItem(data.index);
},
getPopupContainer: getPopupContainer
}"
:tooltip="{
placement: 'topRight',
getPopupContainer: getPopupContainer,
title: target === 'device' && productNoEdit.id?.includes?.(data.record._sortIndex) ? '继承自产品物模型的数据不支持删除' :'删除',
}"
:disabled="target === 'device' && productNoEdit.id?.includes?.(data.record._sortIndex)"
@ -167,21 +176,25 @@
<PropertiesModal
v-if="type === 'properties' && detailData.visible"
:data="detailData.data"
:getPopupContainer="getPopupContainer"
@cancel="cancelDetailModal"
/>
<FunctionModal
v-else-if="type === 'functions' && detailData.visible"
:data="detailData.data"
:getPopupContainer="getPopupContainer"
@cancel="cancelDetailModal"
/>
<EventModal
v-else-if="type === 'events' && detailData.visible"
:data="detailData.data"
:getPopupContainer="getPopupContainer"
@cancel="cancelDetailModal"
/>
<TagsModal
v-else-if="type === 'tags' && detailData.visible"
:data="detailData.data"
:getPopupContainer="getPopupContainer"
@cancel="cancelDetailModal"
/>
</template>
@ -210,10 +223,11 @@ import {omit} from "lodash-es";
import { PropertiesModal, FunctionModal, EventModal, TagsModal } from './DetailModal'
import { Modal } from 'jetlinks-ui-components'
import {EventEmitter} from "@/utils/utils";
import {watch} from "vue";
import {computed, watch} from "vue";
import {cloneDeep} from "lodash";
import {useSystem} from "store/system";
import {storeToRefs} from "pinia";
import { FULL_CODE } from 'jetlinks-ui-components/es/DataTable'
const props = defineProps({
target: {
@ -256,13 +270,23 @@ const detailData = reactive({
visible:false
})
const showSave = ref(metadata.value.length !== 0)
const showLastDelete = ref(false)
const dataSourceCache = ref<any[]>(metadata.value)
const fullRef = inject(FULL_CODE);
const getPopupContainer = (node: any) => {
const fullDom = tableRef.value?.fullRef?.()
return fullDom || node
}
const showLastDelete = computed(() => {
return dataSourceCache.value.length === 1
})
provide('_dataSource', dataSourceCache)
const showDetail = (data: any) => {
detailData.data = data
detailData.visible = true
@ -345,15 +369,15 @@ const handleAddClick = async (_data?: any, index?: number) => {
const newObject = _data || getDataByType()
const _addData = await tableRef.value.addItem(newObject, index)
if (_addData.length === 1) {
showLastDelete.value = true
}
// if (_addData.length === 1) {
// showLastDelete.value = true
// }
showSave.value = true
};
const copyItem = (record: any, index: number) => {
const copyData = cloneDeep(omit(record, ['_uuid', '_sortIndex']))
copyData.id = `copy_${copyData.id}`.slice(0,64)
copyData.id = `copy_${copyData.id}`
handleAddClick(copyData, index)
}
@ -362,9 +386,9 @@ const removeItem = (index: number) => {
// data.splice(index, 1);
// dataSource.value = data
const _data = tableRef.value.removeItem(index)
if (_data.length === 1) {
showLastDelete.value = true
}
// if (_data.length === 1) {
// showLastDelete.value = true
// }
if (_data.length === 0) {
showSave.value = false
@ -373,6 +397,7 @@ const removeItem = (index: number) => {
}
const editStatusChange = (status: boolean) => {
console.log('editStatusChange',status)
editStatus.value = status
}
@ -432,6 +457,7 @@ const handleSaveClick = async (next?: Function) => {
if(result.success) {
dataSource.value = resp
tableRef.value.cleanEditStatus()
editStatus.value = false
onlyMessage('操作成功!')
next?.()
}

View File

@ -4,6 +4,7 @@
:maskClosable="false"
title="事件详情"
width="650px"
:getContainer="getPopupContainer"
@cancel="cancel"
@ok="ok"
>
@ -36,6 +37,10 @@ const props = defineProps({
data: {
type: Object,
default: () => ({})
},
getPopupContainer: {
type: Function,
default: undefined
}
})

View File

@ -4,6 +4,7 @@
title="功能详情"
width="650px"
:maskClosable="false"
:getContainer="getPopupContainer"
@cancel="cancel"
@ok="ok"
>
@ -40,6 +41,10 @@ const props = defineProps({
data: {
type: Object,
default: () => ({})
},
getPopupContainer: {
type: Function,
default: undefined
}
})

View File

@ -3,6 +3,7 @@
visible
:maskClosable="false"
title="属性详情"
:getContainer="getPopupContainer"
@cancel="cancel"
@ok="ok"
>
@ -67,6 +68,10 @@ const props = defineProps({
data: {
type: Object,
default: () => ({})
},
getPopupContainer: {
type: Function,
default: undefined
}
})

View File

@ -3,6 +3,7 @@
visible
:maskClosable="false"
title="标签详情"
:getContainer="getPopupContainer"
@cancel="cancel"
@ok="ok"
>
@ -47,6 +48,10 @@ const props = defineProps({
data: {
type: Object,
default: () => ({})
},
getPopupContainer: {
type: Function,
default: undefined
}
})

View File

@ -439,7 +439,7 @@ export const useColumns = (type?: MetadataType, target?: 'device' | 'product', n
const virtualRule = values.expands?.virtualRule
const source = value.source
const ids = (noEdit?.value?.id || []) as any[]
console.log(source, value)
if (source) {
if (source === 'device' && !value.type?.length) {
return Promise.reject('请选择读写类型');
@ -542,13 +542,11 @@ export const useColumns = (type?: MetadataType, target?: 'device' | 'product', n
form: {
required: true,
rules: [
{ required: true, message: '请选择读写类型' },
{
callback(rule:any,value: any, dataSource: any[]) {
const field = rule.field.split('.')
const fieldIndex = Number(field[1])
const values = dataSource.find((item, index) => index === fieldIndex)
if (!values?.expands?.type?.length) {
return Promise.reject('请选择读写类型')
}

View File

@ -150,6 +150,7 @@ const columns = [
dataIndex: 'id',
type: 'text',
width: 100,
placement: 'Left',
form: {
required: true,
rules: [

View File

@ -1,13 +1,15 @@
<template>
<j-select-boolean
<j-select
v-model:value="myValue"
tureTitle="必填"
falseTitle='不必填'
style="width: 100%;"
:options="[
{ label: '不必填', value: 'false'},
{ label: '必填', value: 'true'},
]"
@change="change"
>
</j-select-boolean>
</j-select>
</template>
<script setup name="ConstraintSelect">
@ -32,14 +34,14 @@ const myValue = ref()
const change = (e) => {
const newData = { ...props.value }
set(newData, props.name, myValue.value)
set(newData, props.name, myValue.value === 'true')
console.log(newData, e);
emit('update:value', newData)
}
watch(() => JSON.stringify(props.data), () => {
console.log(props.value, props.name);
myValue.value = get(props.value, props.name) || false
myValue.value = get(props.value, props.name) === true ? 'true' : 'false'
}, { immediate: true })
</script>

View File

@ -86,6 +86,7 @@ const columns = [{
dataIndex: 'id',
type: 'text',
width: 100,
placement: 'Left',
form: {
required: true,
rules: [{
@ -191,6 +192,7 @@ watch(
() => {
type.value = props.value?.valueType?.type;
_valueType.value = props.value?.valueType
console.log(props.value)
// elements.value = props.value?.valueType.elements;
// if (['float', 'double', 'int', 'long'].includes(type.value)) {
// const res = getUnit().then((res) => {

View File

@ -27,6 +27,7 @@ const columns = [
title: '参数标识',
dataIndex: 'id',
type: 'text',
placement: 'Left',
form: {
required: true,
rules: [{

View File

@ -4,7 +4,7 @@
<span>{{ TypeStringMap[data.record.valueType?.type] }}</span>
</template>
<template #required="{ data }">
<span>{{ data.record.expands?.required ? "是": '否' }}</span>
<span>{{ data.record.expands?.required ? "必填": '不必填' }}</span>
</template>
<template #config="{ data }">
<ConfigModal v-model:value="data.record.valueType" :showOther="false" />
@ -64,6 +64,7 @@ const columns = ref([
title: '参数标识',
dataIndex: 'id',
type: 'text',
placement: 'Left',
form: {
required: true,
rules: [

View File

@ -1,7 +1,7 @@
<template>
<div class="metadata-type">
<div class="metadata-type-select">
<DataTableTypeSelect v-model:value="type" @change="typeChange" />
<DataTableTypeSelect v-model:value="type" :allowClear="true" @change="typeChange" />
</div>
<DataTableArray
v-if="type === 'array'"
@ -101,6 +101,7 @@ const columns = [
title: '参数标识',
dataIndex: 'id',
type: 'text',
placement: 'Left',
form: {
required: true,
rules: [{

View File

@ -11,7 +11,9 @@
{{ data.record.range === true ? '范围值' : '固定值'}}
</template>
<template #value="{data}">
{{ data.record.range === true ? data.record.value?.join('-') : data.record.value }}
<j-ellipsis>
{{ data.record.range === true ? data.record.value?.join('-') : showText(data.record.value) }}
</j-ellipsis>
</template>
<template #action="{data}">
<j-button
@ -43,6 +45,10 @@ const props = defineProps({
type: {
type: String,
default: undefined
},
options: {
type: Array,
default: () => []
}
})
@ -53,12 +59,31 @@ const tableRef = ref()
provide('metricsType', props.type)
const showText = (value: any) => {
switch (props.type) {
case 'date':
return value;
case 'boolean':
const item = props.options.find(item => item.value === value)
if (item) {
return item.label
}else if (value) {
return value === 'true' ? '是' : '否'
} else {
return ''
}
default:
return value
}
}
const columns: any = [
{
title: '指标标识',
dataIndex: 'id',
width: 120,
type: 'text',
placement: 'Left',
form: {
required: true,
rules: [{
@ -109,7 +134,10 @@ const columns: any = [
width: 100,
type: 'components',
components: {
name: MetricValueItem
name: MetricValueItem,
props: {
options: props.options
}
},
form: {
required: true,

View File

@ -1,7 +1,9 @@
<template>
<div class="metrics-item-value">
<div class="metrics-item-text">
<j-ellipsis>
{{ showText }}
</j-ellipsis>
</div>
<j-popconfirm-modal
:show-cancel="false"
@ -11,8 +13,8 @@
>
<template #content>
<j-form ref="formRef" :model="formData">
<j-form-item v-if="value.range === false" :rules="[{ required: true, message: '请输入指标值'}]" name="value">
<Item v-model:value="formData.value" />
<j-form-item v-if="value.range === false" :rules="[{ validator: typeValidator}]" name="value">
<Item v-model:value="formData.value" :options="options" />
</j-form-item>
<div v-else class="data-table-boolean-item">
<div class="data-table-boolean-item--value">
@ -41,6 +43,7 @@ import Item from './item.vue'
import {Form} from "jetlinks-ui-components";
import {cloneDeep} from "lodash";
import { FULL_CODE } from 'jetlinks-ui-components/es/DataTable'
import dayjs from "dayjs";
type ValueType = number | Array<number | undefined> | undefined;
@ -53,12 +56,16 @@ const props = defineProps({
type: Object as PropType<any>,
default: undefined,
},
options: {
type: Array,
default: () => []
}
});
const emit = defineEmits<Emit>();
const formItemContext = Form.useInjectFormItemContext();
const fullRef = inject(FULL_CODE);
const type = inject<string>('metricsType')
const formData = reactive<{
value: ValueType;
@ -72,26 +79,81 @@ const formData = reactive<{
const formRef = ref()
const showText = computed(() => {
if (props.value.range === false) {
return props.value?.value || ''
switch (type) {
case 'date':
return props.value?.value;
case 'boolean':
const _value = props.value?.value
const item = props.options.find(item => item.value === props.value?.value)
if (item) {
return item.label
}else if (_value) {
return _value === 'true' ? '是' : '否'
} else {
return ''
}
default:
return props.value?.value
}
} else {
return props.value?.value?.[0] ? props.value.value.join('-') : ''
}
})
const validatorTip = () =>{
let tip = '请输入'
if (['boolean', 'date'].includes(type!)) {
tip = '请选择'
}
return `${tip}指标值`
}
const validator = (_: any, value: any) => {
if (props.value.range && formData.rangeValue![0] >= formData.rangeValue![1]) {
return Promise.reject('需大于左侧数值')
}
return Promise.resolve()
}
const typeValidator = (_: any, value: any) => {
if (value === undefined) {
return Promise.reject(validatorTip())
}
if (type === 'string' && value?.length > 64) {
return Promise.reject('最多可输入64个字符')
}
return Promise.resolve()
}
const handleValueByType = (value: any, isRange: boolean = false) => {
if (isRange) {
return (value as number[]).map(item => {
const itemStr = String(item)
const index = String(item).indexOf('.')
return index === -1 ? item : Number(itemStr.substring(0, index))
})
} else {
const itemStr = String(value)
const index = String(value).indexOf('.')
return index === -1 ? value : Number(itemStr.substring(0, index))
}
}
const confirm = () => {
return new Promise((resolve, reject) => {
formRef.value.validate().then(() => {
const value = props.value.range === true ? formData.rangeValue : formData.value
console.log('confirm',value, props.value)
let value = props.value.range === true ? formData.rangeValue : formData.value
if (['int', 'long'].includes(type)) {
value = handleValueByType(value, props.value.range)
console.log('confirm',value, type)
}
emit('update:value', {
...props.value,
value: value
@ -121,6 +183,7 @@ watch(() => props.value.range,(value, oldValue) => {
display: flex;
gap: 12px;
align-items: center;
width: 100%;
.metrics-item-text {
flex: 1;

View File

@ -2,7 +2,6 @@
<j-input
v-if="type === 'string'"
v-model:value="myValue"
:maxLength="64"
placeholder="请输入"
@change="change"
/>
@ -11,7 +10,7 @@
v-model:value="myValue"
:precision="0"
:max="2147483647"
:min="-2147483647"
:min="-2147483648"
style="width: 100%"
placeholder="请输入"
@change="change"
@ -19,8 +18,8 @@
<j-input-number
v-else-if="type === 'long'"
v-model:value="myValue"
:max="9223372036854775807"
:min="-9223372036854775808"
:max="999999999999999"
:min="-999999999999999"
:precision="0"
placeholder="请输入"
style="width: 100%"
@ -29,8 +28,8 @@
<j-input-number
v-else-if="['float', 'double'].includes(type)"
v-model:value="myValue"
:max="9999999999999999"
:min="-9999999999999999"
:max="999999999999999"
:min="-999999999999999"
placeholder="请输入"
style="width: 100%"
@change="change"
@ -38,18 +37,21 @@
<j-select
v-else-if="type === 'boolean'"
placeholder="请选择"
:options="[
{ label: '否', value: 'false'},
{ label: '是', value: 'true'},
]"
v-model:value="myValue"
style="width: 100%"
:options="options"
:get-popup-container="(node) => fullRef || node"
@change="change"
/>
<j-date-picker
v-else-if="type === 'date' "
v-model:value="myValue"
show-time
format="YYYY-MM-DD HH:mm:ss"
style="width: 100%"
placeholder="请选择"
:get-popup-container="(node) => fullRef || node"
valueFormat="YYYY-MM-DD HH:mm:ss"
@change="change"
/>
</template>
@ -61,6 +63,10 @@ const props = defineProps({
value: {
type: [String, Number, Array],
default: undefined
},
options: {
type: Array,
default: () => []
}
})
@ -72,8 +78,8 @@ const myValue = ref(props.value)
const fullRef = inject(FULL_CODE);
const change = () => {
// formItemContext.onFieldChange()
emit('update:value', myValue.value)
formItemContext.onFieldChange()
}
watch(() => props.value, () => {

View File

@ -8,7 +8,7 @@
@cancel="cancel"
@visibleChange="visibleChange"
>
<template #content>
<template v-if="visible" #content>
<j-scrollbar height="350" v-if="showMetrics || config.length > 0">
<j-collapse v-model:activeKey="activeKey">
<j-collapse-panel v-for="(item, index) in config" :key="'store_'+index" :header="item.name">
@ -40,7 +40,7 @@
<AIcon type="ExclamationCircleOutlined" style="padding-left: 12px;padding-top: 4px;" />
</j-tooltip>
</template>
<Metrics ref="metricsRef" :value="myValue.metrics" :type="props.type"/>
<Metrics ref="metricsRef" :options="booleanOptions" :type="props.type" :value="myValue.metrics"/>
</j-collapse-panel>
</j-collapse>
@ -84,6 +84,10 @@ const props = defineProps({
type: String,
default: undefined
},
record: {
type: Object,
default: () => ({})
},
})
const fullRef = inject(FULL_CODE);
@ -109,6 +113,11 @@ const showMetrics = computed(() => {
return ['int', 'long', 'float', 'double', 'string', 'boolean', 'date'].includes(props.type as any)
})
const booleanOptions = ref([
{ label: '否', value: 'false'},
{ label: '是', value: 'true'},
])
const columns = ref([
{
title: '参数名称',
@ -129,9 +138,16 @@ const columns = ref([
const getConfig = async () => {
const id = type === 'product' ? productStore.current?.id : deviceStore.current.id
console.log(props.id, id, props.type)
console.log(props.id, id, props)
if(!props.id || !id || !props.type) return
if (props.type === 'boolean') {
const booleanValue = props.record.valueType
booleanOptions.value[0] = { label: booleanValue.falseText || '否', value: booleanValue.falseValue || 'false'}
booleanOptions.value[1] = { label: booleanValue.trueText || '是', value: booleanValue.trueValue || 'true'}
}
const params: any = {
deviceId: id,
metadata: {
@ -189,6 +205,7 @@ const confirm = () => {
}
const visibleChange = (e: boolean) => {
visible.value = e
if (e) {
configValue.value = omit(props.value, ['source', 'type', 'metrics', 'required'])
getConfig()
@ -200,6 +217,7 @@ const cancel = () => {
}
watch(() => props.value, () => {
console.log(props.value)
myValue.value = cloneDeep(props.value)
}, {immediate: true, deep: true})

View File

@ -163,7 +163,6 @@ const cancel = () => {
myValue.value = props.value?.expands?.source || '';
}
type.value = props.value?.expands?.type || [];
console.log('取消', myValue.value);
}
watch(

View File

@ -6,7 +6,10 @@
:options="typeOptions"
/>
<template v-if="source === 'rule'">
<j-form-item :name="['virtualRule', 'triggerProperties']" required>
<j-form-item :name="['virtualRule', 'triggerProperties']" :rules="[{
required: true,
message: '请选择触发属性'
}]">
<template #label>
触发属性
<j-tooltip>
@ -23,7 +26,7 @@
<j-select
v-model:value="formData.virtualRule.triggerProperties"
mode="multiple"
placeholder="请选择属性"
placeholder="请选择触发属性"
show-search
max-tag-count="responsive"
>
@ -65,7 +68,10 @@
<j-form-item
label="窗口"
:name="['virtualRule', 'windowType']"
required
:rules="[{
required: true,
message: '请选择窗口类型'
}]"
>
<j-select
v-model:value="formData.virtualRule.windowType"
@ -85,7 +91,10 @@
<j-form-item
label="聚合函数"
:name="['virtualRule', 'aggType']"
required
:rules="[{
required: true,
message: '请选择聚合函数'
}]"
>
<j-select
v-model:value="formData.virtualRule.aggType"
@ -108,7 +117,7 @@
},
{
pattern: /^\d+$/,
message: '请输入0-999999之间的正整数',
message: '请输入1-999999之间的正整数',
},
]"
>
@ -135,7 +144,7 @@
},
{
pattern: /^\d+$/,
message: '请输入0-999999之间的正整数',
message: '请输入1-999999之间的正整数',
},
]"
>
@ -250,11 +259,10 @@ const options = computed(() => {
});
const setInitVirtualRule = () => {
console.log(props.value?.expands?.virtualRule);
formData.virtualRule = {
...initData,
...(props.value?.expands?.virtualRule || {}),
triggerProperties: props.value?.expands?.virtualRule?.triggerProperties || ['*'],
triggerProperties: props.value?.expands?.virtualRule?.triggerProperties?.length ? props.value?.expands?.virtualRule?.triggerProperties : ['*']
}
}
@ -274,8 +282,9 @@ const handleSearch = async () => {
);
}
if (resp && resp.status === 200 && resp.result) {
const _triggerProperties = props.value?.expands?.virtualRule?.triggerProperties?.length ? props.value?.expands?.virtualRule?.triggerProperties : resp.result.triggerProperties
formData.virtualRule = {
triggerProperties: resp.result.triggerProperties,
triggerProperties: _triggerProperties?.length ? _triggerProperties : ['*'],
...resp.result.rule,
}
} else {
@ -318,6 +327,7 @@ watch(
formData.virtualRule = initData;
handleSearch();
setInitVirtualRule()
} else {
formData.virtualRule = undefined;
}

View File

@ -195,7 +195,7 @@ import { useProductStore } from '@/store/product';
import { FILE_UPLOAD } from '@/api/comm';
import { getToken, onlyMessage } from '@/utils/comm';
import { useMetadataStore } from '@/store/metadata';
import { omit, uniqBy } from 'lodash-es';
import { omit } from 'lodash-es';
import { Modal } from 'jetlinks-ui-components';
const route = useRoute();
@ -258,11 +258,20 @@ const loadData = async () => {
loadData();
const beforeUpload: UploadProps['beforeUpload'] = (file) => {
if(file.type === 'application/json') {
const reader = new FileReader();
reader.readAsText(file);
reader.onload = (json) => {
if(json.target?.result){
onlyMessage('操作成功!')
formModel.import = json.target?.result;
} else {
onlyMessage('文件内容不能为空', 'error')
}
};
} else {
onlyMessage('请上传json格式的文件', 'error')
}
};
const fileChange = (info: UploadChangeParam) => {
if (info.file.status === 'done') {
@ -273,10 +282,18 @@ const fileChange = (info: UploadChangeParam) => {
}
};
const uniqArray = (arr: any[]) => {
const _map = new Map();
for(let item of arr) {
_map.set(item.id, item)
}
return [..._map.values()]
}
const operateLimits = (mdata: DeviceMetadata) => {
hasVirtualRule.value = false;
const obj: DeviceMetadata = { ...mdata };
const old = JSON.parse(instanceStore.detail?.metadata || '{}');
const old = JSON.parse((props.type === 'device' ? instanceStore.detail?.metadata : productStore.detail?.metadata) || '{}');
const fid = instanceStore.detail?.features?.map((item) => item.id);
const _data: DeviceMetadata = {
properties: [],
@ -284,10 +301,10 @@ const operateLimits = (mdata: DeviceMetadata) => {
functions: [],
tags: []
}
_data.properties = uniqBy([...(obj?.properties || []), ...(old?.properties || [])], 'id')
_data.events = uniqBy([...(obj?.events || []), ...(old?.events || [])], 'id')
_data.functions = uniqBy([...(obj?.functions || []), ...(old?.functions || [])], 'id')
_data.tags = uniqBy([...(obj?.tags || []), ...(old?.tags || [])], 'id')
_data.properties = uniqArray([...(old?.properties || []), ...uniqArray(obj?.properties || [])])
_data.events = uniqArray([...(old?.events || []), ...uniqArray(obj?.events || [])])
_data.functions = uniqArray([...(old?.functions || []), ...uniqArray(obj?.functions || [])])
_data.tags = uniqArray([...(old?.tags || []), ...uniqArray(obj?.tags || [])])
if (fid?.includes('eventNotModifiable')) {
_data.events = old?.events || [];

View File

@ -3,26 +3,7 @@
<div class="title">请选择首页视图</div>
<div class="choose-view">
<div class="view-content">
<div
:span="8"
class="select-item"
v-for="item in list"
:key="item.id"
@click="selectValue = item.id"
:class="{
active: selectValue === item.id,
}"
>
<div class="select-item-box">
<div class="select-title">{{ item.name }}</div>
</div>
<img
:src="getImage(`/home/home-view/${item.id}.png`)"
alt=""
/>
</div>
</div>
<HomeView v-model:value="selectValue"/>
<div class="btn">
<j-button type="primary" @click="confirm">保存修改</j-button>
</div>
@ -32,23 +13,8 @@
<script lang="ts" setup>
import { setView_api } from '@/api/home';
import { getImage } from '@/utils/comm';
import { useUserInfo } from '@/store/userInfo';
const list = [
{
id: 'device',
name: '设备接入视图',
},
{
id: 'ops',
name: '运营管理视图',
},
{
id: 'comprehensive',
name: '综合管理视图',
},
];
import HomeView from '@/components/HomeView/index.vue';
const user = useUserInfo();
const emits = defineEmits(['refresh']);
@ -95,45 +61,6 @@ watch(
width: 100%;
padding: 0 9%;
box-sizing: border-box;
.view-content {
display: flex;
justify-content: space-between;
.select-item {
cursor: pointer;
width: 30%;
border-radius: 14px;
overflow: hidden;
color: #333333;
.select-item-box {
position: relative;
width: 100%;
.select-title {
position: absolute;
top: 36px;
left: 36px;
font-size: 24px;
}
}
img {
width: 100%;
height: 100%;
background-size: cover;
}
&.active {
border: 1px solid @primary-color-active;
color: @primary-color-active;
}
&:hover {
box-shadow: 0px 3px 6px -4px rgba(0, 0, 0, 0.12),
0px 6px 16px 0px rgba(0, 0, 0, 0.08),
0px 9px 16px 8px rgba(0, 0, 0, 0.1);
}
}
}
.btn {
display: flex;

View File

@ -16,6 +16,8 @@ import BaseMenu, { USER_CENTER_MENU_DATA } from '../data/baseMenu'
import { getSystemPermission, updateMenus } from '@/api/initHome';
import { protocolList } from '@/utils/consts';
import { getProviders } from '@/api/data-collect/channel';
import { isNoCommunity } from '@/utils/utils';
/**
* 获取菜单数据
*/
@ -28,10 +30,14 @@ const menuDatas = reactive({
* 查询支持的协议
*/
const getProvidersFn = async () => {
if (!isNoCommunity) {
return undefined
} else {
const res: any = await getProviders();
const ids = res.result?.map?.(item => item.id) || []
return protocolList.some(item => ids.includes(item.value))
}
}
/**
* 获取当前系统权限信息

View File

@ -31,7 +31,6 @@
]"
>
<j-input
disabled
v-model:value="
formState.apiAddress
"
@ -113,48 +112,78 @@
<div class="doc">
<h1>操作指引</h1>
<div>
1CTWing端创建产品设备以及一个第三方应用
1CTWing端创建应用产品设备配置完成后查看产品详情其中产品ID以及Master-APIkey在JetLinks平台配置时会使用
</div>
<div class="image">
<j-image width="100%" :src="img1" />
</div>
<div>
2CTWing端配置产品/设备/分组级订阅订阅方URL地址请填写:
<div
2IOT端创建类型为CTWing的设备接入网关
<!-- <div
class="url"
style="word-wrap: break-word"
>
{{
`${origin}/api/ctwing/${randomString()}/notify`
}}
</div>
</div>
<div class="image">
<j-image width="100%" :src="img1" />
</div> -->
</div>
<div>
3IOT端创建类型为CTWing的设备接入网关
</div>
<div>
4IOT端创建产品选中接入方式为CTWing,填写CTWing平台中的产品IDMaster-APIkey
3IOT端创建产品选中接入方式为CTWing填写CTWing平台中的产品IDMaster-APIkey
</div>
<div class="image">
<j-image width="100%" :src="img2" />
</div>
<div>
5IOT端添加设备为每一台设备设置唯一的IMEI需与CTWing平台中填写的值一致
4IOT端添加设备设备ID需要配置真实设备的IMEI号配置完成后启用设备如果启用设备提示错误请检查CTWing网关参数是否配置正确
</div>
<div class="image">
<j-image width="100%" :src="img3" />
</div>
<h1>设备接入网关配置说明</h1>
<div>
1.请将CTWing的AEP平台-应用管理中的App
Key和App Secret复制到当前页面
</div>
<div class="image">
<j-image width="100%" :src="img4" />
</div>
<h1>其他说明</h1>
<j-descriptions
bordered
size="small"
:column="1"
:labelStyle="{ width: '100px' }"
>
<j-descriptions-item label="参数"
>说明</j-descriptions-item
>
<j-descriptions-item label="IMEI"
>真实设备的IMEI号需要填写正确否则推送数据会接收失败</j-descriptions-item
>
<j-descriptions-item label="SN">
真实设备的SN号
</j-descriptions-item>
<j-descriptions-item label="IMSI">
真实设备的IMSI号
</j-descriptions-item>
<j-descriptions-item label="PSK">
真实设备的PSK非必填
</j-descriptions-item>
</j-descriptions>
<div>
1.在IOT端启用设备时若CTWing平台没有与之对应的设备则将在CTWing端自动创建新设备
5CTWing端配置产品/设备/分组级订阅订阅方URL地址请填写 {{
`${origin}/api/ctwing/${randomString()}/notify`
}}此处订阅地址可以在JetLinks平台中配置完成CTWing网关后再填写
</div>
<div class="image">
<j-image width="100%" :src="img5" />
</div>
<div>
6以上步骤配置完成后可以触发真实设备进行上数在平台对应设备详情页查看设备状态
</div>
<div class="image">
<j-image width="100%" :src="img6" />
</div>
<h1>
其他说明
</h1>
<div>
在IOT端启用设备时若CTWing平台没有与之对应的设备则将在CTWing端自动创建新设备
</div>
</div>
</j-scrollbar>
@ -330,6 +359,8 @@ const img1 = getImage('/network/01.png');
const img2 = getImage('/network/02.jpg');
const img3 = getImage('/network/03.png');
const img4 = getImage('/network/04.jpg');
const img5 = getImage('/network/05-Ctwing.png')
const img6 = getImage('/network/06-Ctwing.png')
interface FormState {
apiAddress: string;

View File

@ -41,7 +41,6 @@
</j-tooltip>
</template>
<j-input
disabled
v-model:value="
formState.apiAddress
"
@ -163,29 +162,42 @@
<div class="doc">
<h1>操作指引</h1>
<div>
1OneNet端创建产品设备并配置HTTP推送
1登录OneNet平台创建产品设备并配置HTTP的全局推送
</div>
<div>
2IOT端创建类型为OneNet的设备接入网关
</div>
<div>
3IOT端创建产品选中接入方式为OneNet类型的设备接入网关填写Master-APIkeyOneNet端的产品Key
2获取OneNet平台的产品Key
</div>
<div class="image">
<j-image width="100%" :src="img5" />
</div>
<div>
4IOT端添加设备在设备实例页面为每一台设备设置唯一的IMEIIMSI码需与OneNet平台中的值一致
3IOT端创建类型为OneNet的设备接入网关
</div>
<div>
4IOT端创建产品选中接入方式为OneNet类型的设备接入网关填写Master-APIkeyOneNet端的产品Key
</div>
<div class="image">
<j-image width="100%" :src="img6" />
</div>
<h1>HTTP推送配置说明</h1>
<div class="image">
<j-image width="100%" :src="img" />
<div class="desc">填写OneNet网关相关参数</div>
</div>
<div>
HTTP推送配置路径应用开发&gt;数据推送
5IOT端添加设备在设备实例页面为每一台设备设置唯一的IMEIIMSI码需与OneNet平台中的值一致
</div>
<div class="image">
<j-image width="100%" :src="img7" />
</div>
<h1>HTTP推送配置说明</h1>
<div>
1点击<b>数据推送&gt;HTTP推送&gt;添加全局推送</b>
</div>
<div class="image">
<j-image width="100%" :src="img8" />
</div>
<div>
2填写全局推送相关参数
</div>
<div class="image">
<j-image width="100%" :src="img9" />
</div>
<j-descriptions
bordered
@ -196,11 +208,8 @@
<j-descriptions-item label="参数"
>说明</j-descriptions-item
>
<j-descriptions-item label="实例名称"
>推送实例的名称</j-descriptions-item
>
<j-descriptions-item label="推送地址">
用于接收OneNet推送设备数据的地址物联网平台地址:
<j-descriptions-item label="推送地址url">
用于接收OneNet推送设备数据的物联网平台地址:
<div style="word-wrap: break-word">
{{
`${origin}/api/one-net/${randomString()}/notify`
@ -210,12 +219,26 @@
<j-descriptions-item label="Token">
自定义token,可用于验证请求是否来自OneNet
</j-descriptions-item>
<j-descriptions-item label="消息加密">
<j-descriptions-item label="AESKey">
采用AES加密算法对推送的数据进行数据加密AesKey为加密秘钥
</j-descriptions-item>
</j-descriptions>
<j-alert
message="注意:"
style="margin-top: 15px;"
type="warning"
>
<template #description>
<span>
开发者在OneNet物联网平台添加全局推送参数前需要在JetLinks物联网平台添加OneNet接入网关且OneNet接入网关中的通知Token参数需要和OneNet物联网平台全局推送参数中的Token保持一致否则OneNet物联网平台添加全局推送检验不通过提示
<span style="color: red">validate fail: validate token fail.</span>
</span>
</template>
</j-alert>
<h1>设备接入网关配置说明</h1>
<j-descriptions
bordered
size="small"
@ -226,13 +249,13 @@
>说明</j-descriptions-item
>
<j-descriptions-item label="apiKey"
>OneNet平台中具体产品的Key</j-descriptions-item
>OneNet平台中具体产品的Master-APIkey</j-descriptions-item
>
<j-descriptions-item label="通知Token">
填写OneNet数据推送配置中设置的Token
</j-descriptions-item>
<j-descriptions-item label="aesKey">
若OneNet数据推送配置了消息加密此处填写OneNet端数据推送配置中设置的aesKey
若OneNet数据推送配置了消息加密此处填写OneNet端数据推送配置中设置的AESkey
</j-descriptions-item>
</j-descriptions>
<h1>其他说明</h1>
@ -411,9 +434,11 @@ import { useMenuStore } from 'store/menu';
const menuStory = useMenuStore();
const origin = window.location.origin;
const img5 = getImage('/network/05.jpg');
const img6 = getImage('/network/06.jpg');
const img = getImage('/network/OneNet.jpg');
const img5 = getImage('/network/05.jpeg');
const img6 = getImage('/network/06.png');
const img7 = getImage('/network/07.jpeg');
const img8 = getImage('/network/08.png');
const img9 = getImage('/network/09.png');
interface FormState {
apiAddress: string;
@ -450,7 +475,7 @@ const formRef1 = ref<FormInstance>();
const formRef2 = ref<FormInstance>();
const formState = ref<FormState>({
apiAddress: 'https://api.heclouds.com/',
apiAddress: 'http://api.zj.cmcconenet.com/',
apiKey: '',
validateToken: '',
aesKey: '',
@ -627,4 +652,11 @@ watch(
height: 30px;
padding-bottom: 8px;
}
.desc {
width: 100%;
text-align: center;
color: rgb(138, 143, 141);
margin-top: 10px;
}
</style>

View File

@ -333,6 +333,7 @@
index,
'publicHost',
]"
:validateFirst="true"
:rules="[
{
required: true,

View File

@ -63,6 +63,8 @@ const props = defineProps({
},
});
const emits = defineEmits(['refresh'])
const columns = [
{
title: '序号',
@ -101,6 +103,7 @@ const handleSearch = async (id: string, arr: Item[]) => {
if (_item) {
return {
..._item,
name: item.name,
flag: true,
};
}
@ -125,7 +128,7 @@ const saveInfo = async (preset: Item[]) => {
},
});
if (resp.status === 200) {
console.log(resp);
emits('refresh')
}
};

View File

@ -128,7 +128,7 @@
</template>
</MediaTool>
</div>
<Preset :data="data" />
<Preset :data="data" @refresh="onRefresh" />
</div>
</div>
<template #footer>
@ -151,6 +151,7 @@ import Preset from './Preset.vue';
type Emits = {
(e: 'update:visible', data: boolean): void;
(e: 'refresh'): void;
};
const emit = defineEmits<Emits>();
@ -315,6 +316,10 @@ const onShare = () => {
visible.value = true;
};
const onRefresh = () => {
emit('refresh')
}
watch(
() => _vis.value,
(val: boolean) => {

View File

@ -130,7 +130,7 @@
:channelData="channelData"
@submit="listRef.reload()"
/>
<Live v-model:visible="playerVis" :data="playData" />
<Live v-model:visible="playerVis" :data="playData" @refresh="listRef.reload()" />
</page-container>
</template>

View File

@ -151,13 +151,14 @@ const changeAccount = () => {
const getLoginUser = async (data?: any) => {
if (getToken()) { //
const res = await getMe_api()
console.log(params.value, data)
if (res.success) {
userName.value = res.result?.user.name
userName.value = res.result?.user?.name
isLogin.value = true
getApplication(data?.client_id || params.value.client_id)
if (data?.internal === 'true' || internal.value === 'true') { // oauth2
// if (data?.internal === 'true' || internal.value === 'true') { // oauth2
goOAuth2Fn(data)
}
// }
} else if (res.status === 401) {
setTimeout(() => {
spinning.value = false
@ -177,16 +178,19 @@ const getLoginUser = async (data?: any) => {
}
}
const getQueryVariable = (variable: any) => {
const query = window.location.search.substring(1);
const vars = query.split('&');
const getQueryVariable = (): Map<string, string> => {
const index = window.location.href.indexOf('?')
const paramsUrl = window.location.href.substr(index + 1)
const paramsArr = paramsUrl.split('#')?.[0] || ''
const vars = paramsArr.split('&');
const maps = new Map()
for (let i = 0; i < vars.length; i++) {
const pair = vars[i].split('=');
if (pair[0] === variable) {
return pair[1];
const [key, value] = pair
maps.set(key, value)
}
}
return '';
return maps;
}
const doLogin = () => {
@ -210,33 +214,35 @@ const initPage = async () => {
let redirectUrl
// url
const paramsIndex = location.hash.indexOf('?')
const params = new URLSearchParams(location.hash.slice(paramsIndex))
const _params = getQueryVariable()
const items = {
client_id: params.get('client_id'),
state: params.get('state'),
redirect_uri: decodeURIComponent(params.get('redirect_uri')!),
response_type: params.get('response_type'),
scope: params.get('scope'),
client_id: _params.get('client_id'),
state: _params.get('state'),
redirect_uri: decodeURIComponent(_params.get('redirect_uri')!),
response_type: _params.get('response_type'),
scope: _params.get('scope'),
}
const item = params.get('internal');
const item = _params.get('internal');
if (items.redirect_uri) {
const origin = items.redirect_uri.split('/').slice(0, 3)
const url = `${origin.join('/')}${items.redirect_uri?.split('redirect=')[1]}`
// redirectUrl = `${items.redirect_uri?.split('redirect_uri=')[0]}?redirect=${url}`
redirectUrl = items.redirect_uri
}
//
getLoginUser({
...items,
internal: params.get('internal'),
redirect_uri: redirectUrl,
})
internal.value = item!
console.log(params)
internal.value = item
params.value = {
...items,
redirect_uri: redirectUrl,
}
console.log(params.value)
//
getLoginUser({
...items,
internal: _params.get('internal'),
redirect_uri: redirectUrl,
})
}
const getSettingDetail = () => {

View File

@ -60,6 +60,10 @@ const onSelect = (v: any, _label: string, index: number) => {
const tabChange = (e: string) => {
emit('update:source', e)
}
watch(() => props.value, () => {
myValue.value = props.value
})
</script>
<style scoped>

View File

@ -175,7 +175,7 @@ watchEffect(() => {
treeOpenKeys.value = openKeysByTree(_options, props.value, props.valueName)
} else {
if (isMetric) { //
label.value = props.metric !== undefined ? props.value : props.placeholder
label.value = props.metric !== undefined ? props.value || props.placeholder : props.placeholder
} else {
label.value = props.value !== undefined ? props.value : props.placeholder
}

View File

@ -82,7 +82,7 @@ import { ContextKey, arrayParamsKey, timeTypeKeys } from './util'
import { useSceneStore } from 'store/scene'
import { storeToRefs } from 'pinia';
import { Form } from 'jetlinks-ui-components'
import { isArray, isObject, pick } from 'lodash-es'
import {indexOf, isArray, isObject, isString, pick} from 'lodash-es'
import {cloneDeep} from "lodash";
const sceneStore = useSceneStore()
@ -267,7 +267,8 @@ const columnSelect = (option: any) => {
termTypeChange = true
paramsValue.termType = termTypes?.length ? termTypes[0].id : 'eq'
}
if (hasTypeChange) { //
console.log('hasTypeChange', paramsValue.value.source, tabsOptions.value)
if (hasTypeChange || !tabsOptions.value.every(a => a.key === paramsValue.value.source )) { //
paramsValue.termType = termTypes?.length ? termTypes[0].id : 'eq'
paramsValue.value = {
source: tabsOptions.value[0].key,
@ -289,6 +290,7 @@ const columnSelect = (option: any) => {
paramsValue.value = newValue
}
console.log(paramsValue, hasTypeChange)
handOptionByColumn(option)
emit('update:value', { ...paramsValue })
nextTick(() => {
@ -322,8 +324,13 @@ const termsTypeSelect = (e: { key: string, name: string }) => {
if (_source === 'metric') {
newValue.metric = paramsValue.value?.metric
const isArray = isString(paramsValue.value!.value) ? paramsValue.value!.value?.includes?.(',') : false
if (arrayParamsKey.includes(e.key) !== isArray) { //
newValue.value = undefined
}
}
paramsValue.value = newValue
emit('update:value', { ...paramsValue })
formItemContext.onFieldChange()
formModel.value.options!.when[props.branchName].terms[props.whenName].terms[props.termsName][1] = e.name

View File

@ -95,8 +95,8 @@ export default defineConfig(({ mode}) => {
// target: 'http://192.168.32.226:8844',
// target: 'http://192.168.32.244:8881',
// target: 'http://192.168.32.163:8844', //张季本地
target: 'http://120.77.179.54:8844', // 120测试
// target: 'http://47.109.52.230: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/, '')

View File

@ -3755,8 +3755,8 @@ jetlinks-ui-components@^1.0.23:
jetlinks-ui-components@^1.0.28:
version "1.0.28"
resolved "http://registry.jetlinks.cn/jetlinks-ui-components/-/jetlinks-ui-components-1.0.28.tgz#f409bdb62769947bf026a32b98899417d2352d0e"
integrity sha512-SO/04K//MHJ4lAK1KPYf8q+FNaXCnSVsAOhr8YIcaPWlgHuitj9sxdso8xHwypCvNBMEUzTx+kDXB9HAlFqGMA==
resolved "http://registry.jetlinks.cn/jetlinks-ui-components/-/jetlinks-ui-components-1.0.28.tgz#5b26937f9a0dc7e02006d230944d9044ea9fb4ee"
integrity sha512-yuxOswVTAcR5hevPoxtfdZnosiGy+KmoPMWxShr3B2UDAMybNjps10nKKMsn70Tdvm5VnYZFw5GvTktRxyr/JA==
dependencies:
"@vueuse/core" "^9.12.0"
"@vueuse/router" "^9.13.0"