Merge branch 'dev' of github.com:jetlinks/jetlinks-ui-vue into dev

This commit is contained in:
easy 2023-03-08 19:43:58 +08:00
commit 4dcbf18939
74 changed files with 8576 additions and 7662 deletions

View File

@ -2,36 +2,36 @@
<div class="indicator-box">
<template v-if="['int', 'long', 'double', 'float'].includes(type)">
<template v-if="value.range">
<a-input-number v-model:value="value.value[0]" :max="value.value[1]" size="small"
style="width: 100%;"></a-input-number>
<j-input-number v-model:value="value.value[0]" :max="value.value[1]" size="small"
style="width: 100%;"></j-input-number>
~
<a-input-number v-model:value="value.value[1]" :min="value.value[0]" size="small"
style="width: 100%;"></a-input-number>
<j-input-number v-model:value="value.value[1]" :min="value.value[0]" size="small"
style="width: 100%;"></j-input-number>
</template>
<a-input-number v-else v-model:value="value.value" size="small" style="width: 100%;"></a-input-number>
<j-input-number v-else v-model:value="value.value" size="small" style="width: 100%;"></j-input-number>
</template>
<template v-else-if="type === 'date'">
<a-range-picker v-if="value.range" show-time v-model:value="value.value" size="small" />
<a-date-picker v-else show-time v-model:value="value.value" size="small" />
<j-range-picker v-if="value.range" show-time v-model:value="value.value" size="small" />
<j-date-picker v-else show-time v-model:value="value.value" size="small" />
</template>
<template v-else-if="type === 'boolean'">
<a-select v-model:value="value.value[0]" :options="list" size="small" placeholder="请选择"></a-select>
<j-select v-model:value="value.value[0]" :options="list" size="small" placeholder="请选择"></j-select>
</template>
<template v-else-if="type === 'string'">
<a-input v-model:value="value.value" size="small" placeholder="请输入"></a-input>
<j-input v-model:value="value.value" size="small" placeholder="请输入"></j-input>
</template>
<template v-else>
<template v-if="value.range">
<a-input v-model:value="value.value[0]" :max="value.value[1]" size="small" placeholder="请输入"></a-input>
<j-input v-model:value="value.value[0]" :max="value.value[1]" size="small" placeholder="请输入"></j-input>
~
<a-input v-model:value="value.value[1]" :min="value.value[0]" size="small" placeholder="请输入"></a-input>
<j-input v-model:value="value.value[1]" :min="value.value[0]" size="small" placeholder="请输入"></j-input>
</template>
<a-input-number v-else v-model:value="value.value" size="small" placeholder="请输入"></a-input-number>
<j-input-number v-else v-model:value="value.value" size="small" placeholder="请输入"></j-input-number>
</template>
<div v-if="type !== 'boolean' && type !== 'string'">
<a-checkbox style="min-width: 60px; margin-left: 5px;" v-model:checked="value.range" @change="changeChecked">
<j-checkbox style="min-width: 60px; margin-left: 5px;" v-model:checked="value.range" @change="changeChecked">
范围
</a-checkbox>
</j-checkbox>
</div>
</div>
</template>

View File

@ -1,5 +1,5 @@
<template>
<a-popover :visible="visible" placement="left">
<j-popover :visible="visible" placement="left">
<template #title>
<div style="display: flex; justify-content: space-between; align-items: center;">
<div style="width: 150px;">配置元素</div>
@ -10,19 +10,19 @@
<div style="max-width: 400px;">
<div class="ant-form-vertical">
<value-type-form v-model:value="_value" :name="name" isSub key="sub"></value-type-form>
<a-form-item label="说明" :name="name.concat(['description'])" :rules="[
<j-form-item label="说明" :name="name.concat(['description'])" :rules="[
{ max: 200, message: '最多可输入200个字符' },
]">
<a-textarea v-model:value="_value.description" size="small"></a-textarea>
</a-form-item>
<j-textarea v-model:value="_value.description" size="small"></j-textarea>
</j-form-item>
</div>
</div>
</template>
<a-button type="dashed" block @click="visible = true">
<j-button type="dashed" block @click="visible = true">
配置元素
<AIcon type="EditOutlined" class="item-icon" />
</a-button>
</a-popover>
</j-button>
</j-popover>
</template>
<script setup lang="ts" name="ArrayParam">
import ValueTypeForm from '@/views/device/components/Metadata/Base/Edit/ValueTypeForm.vue';
@ -33,7 +33,7 @@ type ValueType = Record<any, any>;
const props = defineProps({
value: {
type: Object as PropType<ValueType>,
default: () => ({ extends: {} })
default: () => ({ expands: {} })
},
name: {
type: Array as PropType<(string | number)[]>,

View File

@ -1,35 +1,35 @@
<template>
<div class="boolean-param">
<a-row :gutter="4">
<a-col :span="12">
<a-form-item label=" " :name="name.concat(['trueText'])" :rules="[
<j-row :gutter="4">
<j-col :span="12">
<j-form-item label=" " :name="name.concat(['trueText'])" :rules="[
{ required: true, message: '请输入trueText' },
]">
<a-input v-model:value="value.trueText" placeholder="trueText" size="small" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="-" :name="name.concat(['trueValue'])" :rules="[
<j-input v-model:value="value.trueText" placeholder="trueText" size="small" />
</j-form-item>
</j-col>
<j-col :span="12">
<j-form-item label="-" :name="name.concat(['trueValue'])" :rules="[
{ required: true, message: '请输入trueValue' },
]">
<a-input v-model:value="value.trueValue" placeholder="trueValue" size="small"/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label=" " :name="name.concat(['falseText'])" :rules="[
<j-input v-model:value="value.trueValue" placeholder="trueValue" size="small"/>
</j-form-item>
</j-col>
<j-col :span="12">
<j-form-item label=" " :name="name.concat(['falseText'])" :rules="[
{ required: true, message: '请输入falseText' },
]">
<a-input v-model:value="value.falseText" placeholder="falseText" size="small" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="-" :name="name.concat(['falseValue'])" :rules="[
<j-input v-model:value="value.falseText" placeholder="falseText" size="small" />
</j-form-item>
</j-col>
<j-col :span="12">
<j-form-item label="-" :name="name.concat(['falseValue'])" :rules="[
{ required: true, message: '请输入falseValue' },
]">
<a-input v-model:value="value.falseValue" placeholder="falseValue" size="small" />
</a-form-item>
</a-col>
</a-row>
<j-input v-model:value="value.falseValue" placeholder="falseValue" size="small" />
</j-form-item>
</j-col>
</j-row>
</div>
</template>
<script setup lang="ts" name="BooleanParam">

View File

@ -1,5 +1,5 @@
<template>
<a-popover placement="left" trigger="click">
<j-popover placement="left" trigger="click">
<template #title>
<div class="edit-title" style="display: flex; justify-content: space-between; align-items: center;">
<div style="width: 150px;">{{ config.name }}</div>
@ -7,18 +7,18 @@
</template>
<template #content>
<div style="max-width: 400px;" class="ant-form-vertical">
<a-form-item v-for="item in config.properties" :name="name.concat([item.property])" :label="item.name">
<a-select v-model:value="value[item.property]" :options="item.type?.elements?.map((e: { 'text': string, 'value': string }) => ({
<j-form-item v-for="item in config.properties" :name="name.concat([item.property])" :label="item.name">
<j-select v-model:value="value[item.property]" :options="item.type?.elements?.map((e: { 'text': string, 'value': string }) => ({
label: e.text,
value: e.value,
}))" size="small"></a-select>
</a-form-item>
}))" size="small"></j-select>
</j-form-item>
</div>
</template>
<a-button type="dashed" block>
<j-button type="dashed" block>
存储配置<AIcon type="EditOutlined" class="item-icon"/>
</a-button>
</a-popover>
</j-button>
</j-popover>
</template>
<script setup lang="ts" name="ConfigParam">
import { PropType } from 'vue';

View File

@ -5,7 +5,7 @@
<AIcon type="MenuOutlined" class="item-drag item-icon" />
</div>
<div class="item-middle item-editable">
<a-popover :visible="editIndex === index" placement="top">
<j-popover :visible="editIndex === index" placement="top">
<template #title>
<div class="edit-title" style="display: flex; justify-content: space-between; align-items: center;">
<div style="width: 150px;">枚举项配置</div>
@ -14,34 +14,34 @@
</template>
<template #content>
<div class="ant-form-vertical">
<a-form-item label="Value" :name="name.concat([index, 'value'])" :rules="[
<j-form-item label="Value" :name="name.concat([index, 'value'])" :rules="[
{ required: true, message: '请输入Value' },
]">
<a-input v-model:value="_value[index].value" size="small"></a-input>
</a-form-item>
<a-form-item label="Text" :name="name.concat([index, 'text'])" :rules="[
<j-input v-model:value="_value[index].value" size="small"></j-input>
</j-form-item>
<j-form-item label="Text" :name="name.concat([index, 'text'])" :rules="[
{ required: true, message: '请输入Text' },
]">
<a-input v-model:value="_value[index].text" size="small"></a-input>
</a-form-item>
<j-input v-model:value="_value[index].text" size="small"></j-input>
</j-form-item>
</div>
</template>
<div class="item-edit" @click="handleEdit(index)">
{{ item.text || '枚举项配置' }}
<AIcon type="EditOutlined" class="item-icon" />
</div>
</a-popover>
</j-popover>
</div>
<div class="item-right">
<AIcon type="DeleteOutlined" @click="handleDelete(index)" />
</div>
</div>
<a-button type="dashed" block @click="handleAdd">
<j-button type="dashed" block @click="handleAdd">
<template #icon>
<AIcon type="PlusOutlined" class="item-icon" />
</template>
新增枚举型
</a-button>
</j-button>
</div>
</template>
<script setup lang="ts" name="BooleanParam">

View File

@ -5,7 +5,7 @@
<AIcon type="MenuOutlined" class="item-drag item-icon" />
</div>
<div class="item-middle item-editable">
<a-popover :visible="editIndex === index" placement="left">
<j-popover :visible="editIndex === index" placement="left">
<template #title>
<div class="edit-title" style="display: flex; justify-content: space-between; align-items: center;">
<div style="width: 150px;">配置参数</div>
@ -14,7 +14,7 @@
</template>
<template #content>
<div style="max-width: 400px;" class="ant-form-vertical">
<a-form-item label="标识" :name="name.concat([index, 'id'])" :rules="[
<j-form-item label="标识" :name="name.concat([index, 'id'])" :rules="[
{ required: true, message: '请输入标识' },
{ max: 64, message: '最多可输入64个字符' },
{
@ -22,14 +22,14 @@
message: 'ID只能由数字、字母、下划线、中划线组成',
},
]">
<a-input v-model:value="_value[index].id" size="small"></a-input>
</a-form-item>
<a-form-item label="名称" :name="name.concat([index, 'name'])" :rules="[
<j-input v-model:value="_value[index].id" size="small"></j-input>
</j-form-item>
<j-form-item label="名称" :name="name.concat([index, 'name'])" :rules="[
{ required: true, message: '请输入名称' },
{ max: 64, message: '最多可输入64个字符' },
]">
<a-input v-model:value="_value[index].name" size="small"></a-input>
</a-form-item>
<j-input v-model:value="_value[index].name" size="small"></j-input>
</j-form-item>
<value-type-form v-model:value="_value[index].valueType" :name="name.concat([index, 'valueType'])" isSub
key="json_sub"></value-type-form>
</div>
@ -38,18 +38,18 @@
{{ item.name || '配置参数' }}
<AIcon type="EditOutlined" class="item-icon" />
</div>
</a-popover>
</j-popover>
</div>
<div class="item-right">
<AIcon type="DeleteOutlined" @click="handleDelete(index)" />
</div>
</div>
<a-button type="dashed" block @click="handleAdd">
<j-button type="dashed" block @click="handleAdd">
<template #icon>
<AIcon type="PlusOutlined" class="item-icon" />
</template>
添加参数
</a-button>
</j-button>
</div>
</template>
<script setup lang="ts" name="JsonParam">

View File

@ -6,7 +6,7 @@
{{ `#${index + 1}.` }}
</div>
<div class="item-middle item-editable">
<a-popover :visible="editIndex === index" placement="top" @visible-change="change" trigger="click">
<j-popover :visible="editIndex === index" placement="top" @visible-change="change" trigger="click">
<template #title>
<div class="edit-title" style="display: flex; justify-content: space-between; align-items: center;">
<div style="width: 150px;">配置参数</div>
@ -16,7 +16,7 @@
<template #content>
<div>
<div class="ant-form-vertical">
<a-form-item label="标识" :name="name.concat([index, 'id'])" :rules="[
<j-form-item label="标识" :name="name.concat([index, 'id'])" :rules="[
{ required: true, message: '请输入标识' },
{ max: 64, message: '最多可输入64个字符' },
{
@ -24,20 +24,19 @@
message: 'ID只能由数字、字母、下划线、中划线组成',
},
]">
<a-input v-model:value="_value[index].id" size="small"></a-input>
</a-form-item>
<a-form-item label="名称" :name="name.concat([index, 'name'])" :rules="[
<j-input v-model:value="_value[index].id" size="small"></j-input>
</j-form-item>
<j-form-item label="名称" :name="name.concat([index, 'name'])" :rules="[
{ required: true, message: '请输入名称' },
{ max: 64, message: '最多可输入64个字符' },
]">
<a-input v-model:value="_value[index].name" size="small"></a-input>
</a-form-item>
<a-form-item label="指标值" :name="name.concat([index, 'value'])" :rules="[
{ required: true, message: '请输入指标值' },
{ validator: () => validateIndicator(_value[index]), message: '请输入指标值' }
<j-input v-model:value="_value[index].name" size="small"></j-input>
</j-form-item>
<j-form-item label="指标值" :name="name.concat([index, 'value'])" :rules="[
{ required: true, validator: () => validateIndicator(_value[index]), message: '请输入指标值' }
]">
<JIndicators v-model:value="_value[index]" :type="type" size="small" :enum="enum" />
</a-form-item>
</j-form-item>
</div>
</div>
</template>
@ -45,18 +44,18 @@
{{ item.name || '配置参数' }}
<AIcon type="EditOutlined" class="item-icon" />
</div>
</a-popover>
</j-popover>
</div>
<div class="item-right">
<AIcon type="DeleteOutlined" @click="handleDelete(index)" />
</div>
</div>
<a-button type="dashed" block @click="handleAdd">
<j-button type="dashed" block @click="handleAdd">
<template #icon>
<AIcon type="PlusOutlined" class="item-icon" />
</template>
添加指标
</a-button>
</j-button>
</div>
</template>
<script setup lang="ts" name="MetricsParam">

View File

@ -1,33 +1,33 @@
<template>
<a-form-item :name="name.concat(['script'])">
<j-form-item :name="name.concat(['script'])">
<f-rule-editor v-model:value="value.script" :id="id"></f-rule-editor>
</a-form-item>
</j-form-item>
<template v-if="showWindow">
<a-form-item label="规则配置" :name="name.concat(['isVirtualRule'])">
<a-switch v-model:checked="value.isVirtualRule" :checked-value="true" :un-checked-value="false"
@change="changeWindow"></a-switch>
</a-form-item>
<j-form-item label="规则配置" :name="name.concat(['isVirtualRule'])">
<j-switch v-model:checked="value.isVirtualRule" :checked-value="true" :un-checked-value="false"
@change="changeWindow"></j-switch>
</j-form-item>
<template v-if="value.isVirtualRule">
<a-form-item label="窗口" :name="name.concat(['windowType'])" :rules="[
<j-form-item label="窗口" :name="name.concat(['windowType'])" :rules="[
{ required: true, message: '请选择窗口' },
]">
<a-select v-model:value="value.windowType" :options="windowTypeEnum" size="small" allow-clear></a-select>
</a-form-item>
<a-form-item label="聚合函数" :name="name.concat(['aggType'])" :rules="[
<j-select v-model:value="value.windowType" :options="windowTypeEnum" size="small" allow-clear></j-select>
</j-form-item>
<j-form-item label="聚合函数" :name="name.concat(['aggType'])" :rules="[
{ required: true, message: '请选择聚合函数' },
]">
<a-select v-model:value="value.aggType" :options="aggTypeOptions" size="small" allow-clear></a-select>
</a-form-item>
<a-form-item :label="spanLabel" :name="name.concat(['window', 'span'])" :rules="[
<j-select v-model:value="value.aggType" :options="aggTypeOptions" size="small" allow-clear></j-select>
</j-form-item>
<j-form-item :label="spanLabel" :name="name.concat(['window', 'span'])" :rules="[
{ required: true, message: '请输入窗口长度' },
]">
<a-input-number v-model:value="value.window.span" size="small" style="width: 100%;"></a-input-number>
</a-form-item>
<a-form-item :label="everyLabel" :name="name.concat(['window', 'every'])" :rules="[
<j-input-number v-model:value="value.window.span" size="small" style="width: 100%;"></j-input-number>
</j-form-item>
<j-form-item :label="everyLabel" :name="name.concat(['window', 'every'])" :rules="[
{ required: true, message: '请输入步长' },
]">
<a-input-number v-model:value="value.window.every" size="small" style="width: 100%;"></a-input-number>
</a-form-item>
<j-input-number v-model:value="value.window.every" size="small" style="width: 100%;"></j-input-number>
</j-form-item>
</template>
</template>
</template>

View File

@ -4,32 +4,32 @@
<div class="live-player-content">
<!-- 工具栏 -->
<div class="player-screen-tool" v-if="showScreen">
<a-radio-group
<j-radio-group
:value="screen"
button-style="solid"
@change="handleScreenChange"
>
<a-radio-button :value="1">单屏</a-radio-button>
<a-radio-button :value="4">四分屏</a-radio-button>
<a-radio-button :value="9">九分屏</a-radio-button>
<a-radio-button :value="0">全屏</a-radio-button>
</a-radio-group>
<j-radio-button :value="1">单屏</j-radio-button>
<j-radio-button :value="4">四分屏</j-radio-button>
<j-radio-button :value="9">九分屏</j-radio-button>
<j-radio-button :value="0">全屏</j-radio-button>
</j-radio-group>
<div class="screen-tool-save">
<a-tooltip title="可保存分屏配置记录">
<j-tooltip title="可保存分屏配置记录">
<AIcon type="QuestionCircleOutlined" />
</a-tooltip>
<a-popover
</j-tooltip>
<j-popover
v-model:visible="visible"
trigger="click"
title="分屏名称"
>
<template #content>
<a-form
<j-form
ref="formRef"
:model="formData"
layout="vertical"
>
<a-form-item
<j-form-item
name="name"
:rules="[
{
@ -42,37 +42,37 @@
},
]"
>
<a-textarea v-model:value="formData.name" />
</a-form-item>
<a-button
<j-textarea v-model:value="formData.name" />
</j-form-item>
<j-button
type="primary"
@click="saveHistory"
:loading="loading"
style="width: 100%; margin-top: 16px"
>
保存
</a-button>
</a-form>
</j-button>
</j-form>
</template>
<a-dropdown-button
<j-dropdown-button
type="primary"
@click="visible = true"
>
保存
<template #overlay>
<a-menu>
<a-empty
<j-menu>
<j-empty
v-if="!historyList.length"
description="暂无数据"
/>
<a-menu-item
<j-menu-item
v-for="(item, index) in historyList"
:key="`his${index}`"
@click="handleHistory(item)"
>
<a-space>
<j-space>
<span>{{ item.name }}</span>
<a-popconfirm
<j-popconfirm
title="确认删除?"
ok-text="确认"
cancel-text="取消"
@ -89,13 +89,13 @@
e?.stopPropagation()
"
/>
</a-popconfirm>
</a-space>
</a-menu-item>
</a-menu>
</j-popconfirm>
</j-space>
</j-menu-item>
</j-menu>
</template>
</a-dropdown-button>
</a-popover>
</j-dropdown-button>
</j-popover>
</div>
</div>
<!-- 播放器 -->

View File

@ -11,7 +11,7 @@
:muted="'muted' in props ? props.muted !== false : true"
:hide-big-play-button="true"
:poster="props.poster || ''"
:timeout="props.timeout || 20"
:timeout="props.timeout || 30"
:video-url="url || ''"
@play="props.onPlay"
@pause="props.onPause"

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

@ -1,13 +1,13 @@
<template>
<div class="wrapper">
<a-tabs v-model="activeKey" tab-position="left">
<a-tab-pane
<j-tabs v-model="activeKey" tab-position="left">
<j-tab-pane
v-for="func in newFunctions"
:key="func.id"
:tab="func.name"
>
<a-row :gutter="30">
<a-col :span="15">
<j-row :gutter="30">
<j-col :span="15">
<MonacoEditor
:ref="`monacoEditor${func.id}`"
v-model="func.json"
@ -15,31 +15,31 @@
style="height: 400px"
/>
<div class="editor-btn">
<a-space>
<a-button
<j-space>
<j-button
type="primary"
@click="handleExecute(func)"
>
执行
</a-button>
<a-button
</j-button>
<j-button
type="default"
@click="handleClear(func)"
>
清空
</a-button>
</a-space>
</j-button>
</j-space>
</div>
</a-col>
<a-col :span="9">
</j-col>
<j-col :span="9">
<h6>执行结果</h6>
<span class="execute-result">
{{ func.executeResult }}
</span>
</a-col>
</a-row>
</a-tab-pane>
</a-tabs>
</j-col>
</j-row>
</j-tab-pane>
</j-tabs>
</div>
</template>

View File

@ -1,21 +1,21 @@
<template>
<div class="wrapper">
<div class="tips">
<a-space>
<j-space>
<AIcon type="QuestionCircleOutlined" />
<span>精简模式下参数只支持输入框的方式录入</span>
</a-space>
</j-space>
</div>
<a-tabs v-model="activeKey" tab-position="left">
<a-tab-pane
<j-tabs v-model="activeKey" tab-position="left">
<j-tab-pane
v-for="func in newFunctions"
:key="func.id"
:tab="func.name"
>
<a-row :gutter="30">
<a-col :span="15">
<a-form :ref="`${func.id}Ref`" :model="func">
<a-table
<j-row :gutter="30">
<j-col :span="15">
<j-form :ref="`${func.id}Ref`" :model="func">
<j-table
:columns="columns"
:data-source="func.table"
:pagination="false"
@ -26,7 +26,7 @@
v-if="column.dataIndex === 'type'"
>
<span>{{ record.type }}</span>
<a-tooltip
<j-tooltip
v-if="record.type === 'object'"
>
<template slot="title">
@ -40,12 +40,12 @@
cursor: 'help',
}"
/>
</a-tooltip>
</j-tooltip>
</template>
<template
v-if="column.dataIndex === 'value'"
>
<a-form-item
<j-form-item
:name="['table', index, 'value']"
:rules="{
required: true,
@ -82,37 +82,37 @@
: undefined
"
/>
</a-form-item>
</j-form-item>
</template>
</template>
</a-table>
</a-form>
</j-table>
</j-form>
<div class="editor-btn">
<a-space>
<a-button
<j-space>
<j-button
type="primary"
@click="handleExecute(func)"
>
执行
</a-button>
<a-button
</j-button>
<j-button
type="default"
@click="handleClear(func)"
>
清空
</a-button>
</a-space>
</j-button>
</j-space>
</div>
</a-col>
<a-col :span="9">
</j-col>
<j-col :span="9">
<h6>执行结果</h6>
<span class="execute-result">
{{ func.executeResult }}
</span>
</a-col>
</a-row>
</a-tab-pane>
</a-tabs>
</j-col>
</j-row>
</j-tab-pane>
</j-tabs>
</div>
</template>

View File

@ -1,6 +1,6 @@
<template>
<a-card>
<a-empty
<j-card>
<j-empty
v-if="!metadata || (metadata && !metadata.functions.length)"
style="margin-top: 50px"
>
@ -9,15 +9,15 @@
<!-- <a @click="emits('onJump', 'Metadata')">物模型属性功能</a> -->
<a @click="onJump">物模型属性功能</a>
</template>
</a-empty>
</j-empty>
<template v-else>
<a-tabs v-model:activeKey="activeKey">
<a-tab-pane key="Simple" tab="精简模式" />
<a-tab-pane key="Advance" tab="高级模式" />
</a-tabs>
<j-tabs v-model:activeKey="activeKey">
<j-tab-pane key="Simple" tab="精简模式" />
<j-tab-pane key="Advance" tab="高级模式" />
</j-tabs>
<component :is="tabs[activeKey]" />
</template>
</a-card>
</j-card>
</template>
<script setup lang="ts">

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,5 +1,5 @@
<template>
<a-form-item label="标识" name="id" :rules="[
<j-form-item label="标识" name="id" :rules="[
{ required: true, message: '请输入标识' },
{ max: 64, message: '最多可输入64个字符' },
{
@ -7,14 +7,14 @@
message: 'ID只能由数字、字母、下划线、中划线组成',
},
]">
<a-input v-model:value="value.id" size="small" @change="asyncOtherConfig" :disabled="metadataStore.model.action === 'edit'"></a-input>
</a-form-item>
<a-form-item label="名称" name="name" :rules="[
<j-input v-model:value="value.id" size="small" @change="asyncOtherConfig" :disabled="metadataStore.model.action === 'edit'"></j-input>
</j-form-item>
<j-form-item label="名称" name="name" :rules="[
{ required: true, message: '请输入名称' },
{ max: 64, message: '最多可输入64个字符' },
]">
<a-input v-model:value="value.name" size="small"></a-input>
</a-form-item>
<j-input v-model:value="value.name" size="small"></j-input>
</j-form-item>
<template v-if="modelType === 'properties'">
<value-type-form :name="['valueType']" v-model:value="value.valueType" key="property" title="数据类型"
@change-type="changeValueType"></value-type-form>
@ -22,42 +22,42 @@
:valueType="value.valueType"></expands-form>
</template>
<template v-if="modelType === 'functions'">
<a-form-item label="是否异步" name="async" :rules="[
<j-form-item label="是否异步" name="async" :rules="[
{ required: true, message: '请选择是否异步' },
]">
<a-radio-group v-model:value="value.async">
<a-radio :value="true"></a-radio>
<a-radio :value="false"></a-radio>
</a-radio-group>
</a-form-item>
<a-form-item label="输入参数" name="inputs" :rules="[
<j-radio-group v-model:value="value.async">
<j-radio :value="true"></j-radio>
<j-radio :value="false"></j-radio>
</j-radio-group>
</j-form-item>
<j-form-item label="输入参数" name="inputs" :rules="[
{ required: true, message: '请输入输入参数' },
]">
<JsonParam v-model:value="value.inputs" :name="['inputs']"></JsonParam>
</a-form-item>
</j-form-item>
<value-type-form :name="['output']" v-model:value="value.output" key="function" title="输出参数"></value-type-form>
</template>
<template v-if="modelType === 'events'">
<a-form-item label="级别" :name="['expands', 'level']" :rules="[
<j-form-item label="级别" :name="['expands', 'level']" :rules="[
{ required: true, message: '请选择级别' },
]">
<a-select v-model:value="value.expands.level" :options="EventLevel" size="small"></a-select>
</a-form-item>
<j-select v-model:value="value.expands.level" :options="EventLevel" size="small"></j-select>
</j-form-item>
<value-type-form :name="['valueType']" v-model:value="value.valueType" key="function" title="输出参数"></value-type-form>
</template>
<template v-if="modelType === 'tags'">
<value-type-form :name="['valueType']" v-model:value="value.valueType" key="property" title="数据类型"></value-type-form>
<a-form-item label="读写类型" :name="['expands', 'type']" :rules="[
<j-form-item label="读写类型" :name="['expands', 'type']" :rules="[
{ required: true, message: '请选择读写类型' },
]">
<a-select v-model:value="value.expands.type" :options="ExpandsTypeList" mode="multiple" size="small"></a-select>
</a-form-item>
<j-select v-model:value="value.expands.type" :options="ExpandsTypeList" mode="multiple" size="small"></j-select>
</j-form-item>
</template>
<a-form-item label="说明" name="description" :rules="[
<j-form-item label="说明" name="description" :rules="[
{ max: 200, message: '最多可输入200个字符' },
]">
<a-textarea v-model:value="value.description" size="small"></a-textarea>
</a-form-item>
<j-textarea v-model:value="value.description" size="small"></j-textarea>
</j-form-item>
</template>
<script setup lang="ts" name="BaseForm">
import { PropType } from 'vue';

View File

@ -1,26 +1,30 @@
<template>
<a-form-item label="来源" :name="name.concat(['source'])" v-if="type === 'product'" :rules="[
<j-form-item label="来源" :name="name.concat(['source'])" v-if="type === 'product'" :rules="[
{ required: true, message: '请选择来源' },
]">
<a-select v-model:value="_value.source" :options="PropertySource" size="small"
:disabled="metadataStore.model.action === 'edit'"></a-select>
</a-form-item>
<j-select v-model:value="_value.source" :options="PropertySource" size="small"
:disabled="metadataStore.model.action === 'edit'"></j-select>
</j-form-item>
<virtual-rule-param v-if="_value.source === 'rule'" v-model:value="_value.virtualRule"
:name="name.concat(['virtualRule'])" :id="id" :showWindow="_value.source === 'rule'"></virtual-rule-param>
<a-form-item label="读写类型" :name="name.concat(['type'])" :rules="[
<j-form-item label="读写类型" :name="name.concat(['type'])" :rules="[
{ required: true, message: '请选择读写类型' },
]">
<a-select v-model:value="_value.type" :options="ExpandsTypeList" mode="multiple" size="small"></a-select>
</a-form-item>
<a-form-item label="其他配置" v-if="config.length > 0">
<a-form-item v-for="(item, index) in config" :key="index">
<j-select v-model:value="_value.type" :options="ExpandsTypeList" mode="multiple" size="small"></j-select>
</j-form-item>
<j-form-item label="其他配置" v-if="config.length > 0">
<j-form-item v-for="(item, index) in config" :key="index">
<config-param v-model:value="_value" :config="item" :name="name"></config-param>
</a-form-item>
</a-form-item>
<a-form-item v-if="type === 'product' && ['int', 'float', 'double', 'long', 'date', 'string', 'boolean'].includes(valueType.type)"
label="指标配置" :name="name.concat(['metrics'])">
<metrics-param v-model:value="_value.metrics" :type="valueType.type" :enum="valueType" :name="name.concat(['metrics'])"></metrics-param>
</a-form-item>
</j-form-item>
</j-form-item>
<j-form-item
v-if="type === 'product' && ['int', 'float', 'double', 'long', 'date', 'string', 'boolean'].includes(valueType.type)"
label="指标配置" :name="name.concat(['metrics'])" :rules="[
{ validator: () => validateMetrics(_value.metrics), message: '请输入指标配置' }
]">
<metrics-param v-model:value="_value.metrics" :type="valueType.type" :enum="valueType"
:name="name.concat(['metrics'])"></metrics-param>
</j-form-item>
</template>
<script setup lang="ts" name="ExpandsForm">
import { useMetadataStore } from '@/store/metadata';
@ -86,5 +90,15 @@ onMounted(() => {
}
})
const validateMetrics = (value: Record<any, any>[]) => {
const flag = value.every((item) => {
return item.id && item.name && item.value;
});
if (!flag) {
return Promise.reject(new Error('请输入指标配置'));
}
return Promise.resolve();
}
</script>
<style lang="less" scoped></style>

View File

@ -1,48 +1,54 @@
<template>
<a-form-item :label="title" :name="name.concat(['type'])" :rules="[
<j-form-item :label="title" :name="name.concat(['type'])" :rules="[
metadataStore.model.type !== 'functions' ? { required: true, message: `请选择${title}` } : {},
]">
<a-select v-model:value="_value.type" :options="metadataStore.model.type === 'events' ? eventDataTypeList : _dataTypeList" size="small" @change="changeType"></a-select>
</a-form-item>
<a-form-item label="单位" :name="name.concat(['unit'])" v-if="['int', 'float', 'long', 'double'].includes(_value.type)">
<j-select v-model:value="_value.type"
:options="metadataStore.model.type === 'events' ? eventDataTypeList : _dataTypeList" size="small"
@change="changeType"></j-select>
</j-form-item>
<j-form-item label="单位" :name="name.concat(['unit'])" v-if="['int', 'float', 'long', 'double'].includes(_value.type)">
<InputSelect v-model:value="_value.unit" :options="unit.unitOptions" size="small"></InputSelect>
</a-form-item>
<a-form-item label="精度" :name="name.concat(['scale'])" v-if="['float', 'double'].includes(_value.type)">
<a-input-number v-model:value="_value.scale" size="small" :min="0" :max="2147483647" :precision="0" :default-value="2"
style="width: 100%"></a-input-number>
</a-form-item>
<a-form-item label="布尔值" name="booleanConfig" v-if="['boolean'].includes(_value.type)">
</j-form-item>
<j-form-item label="精度" :name="name.concat(['scale'])" v-if="['float', 'double'].includes(_value.type)">
<j-input-number v-model:value="_value.scale" size="small" :min="0" :max="2147483647" :precision="0" :default-value="2"
style="width: 100%"></j-input-number>
</j-form-item>
<j-form-item label="布尔值" name="booleanConfig" v-if="['boolean'].includes(_value.type)">
<BooleanParam :name="name" v-model:value="_value"></BooleanParam>
</a-form-item>
<a-form-item label="枚举项" :name="name.concat(['elements'])" v-if="['enum'].includes(_value.type)" :rules="[
{ required: true, message: '请配置枚举项' }
</j-form-item>
<j-form-item label="枚举项" :name="name.concat(['elements'])" v-if="['enum'].includes(_value.type)" :rules="[
{ required: true, validator: validateEnum, message: '请配置枚举项' }
]">
<EnumParam v-model:value="_value.elements" :name="name.concat(['elements'])"></EnumParam>
</a-form-item>
<a-form-item :name="name.concat(['expands', 'maxLength'])" v-if="['string', 'password'].includes(_value.type)">
</j-form-item>
<j-form-item :name="name.concat(['expands', 'maxLength'])" v-if="['string', 'password'].includes(_value.type)">
<template #label>
<a-space>
<j-space>
最大长度
<a-tooltip title="字节">
<j-tooltip title="字节">
<question-circle-outlined style="color: rgb(136, 136, 136); font-size: 12px;" />
</a-tooltip>
</a-space>
</j-tooltip>
</j-space>
</template>
<a-input-number v-model:value="_value.expands.maxLength" size="small" :max="2147483647" :min="1" :precision="0"
style="width: 100%;"></a-input-number>
</a-form-item>
<a-form-item label="元素配置" :name="name.concat(['elementType'])" v-if="['array'].includes(_value.type)">
<j-input-number v-model:value="_value.expands.maxLength" size="small" :max="2147483647" :min="1" :precision="0"
style="width: 100%;"></j-input-number>
</j-form-item>
<j-form-item label="元素配置" :name="name.concat(['elementType'])" v-if="['array'].includes(_value.type)" :rules="[
{ validator: validateArray }
]">
<ArrayParam v-model:value="_value.elementType" :name="name.concat(['elementType'])"></ArrayParam>
</a-form-item>
<a-form-item label="JSON对象" :name="name.concat(['properties'])" v-if="['object'].includes(_value.type)" :rules="[]">
</j-form-item>
<j-form-item label="JSON对象" :name="name.concat(['properties'])" v-if="['object'].includes(_value.type)" :rules="[
{ validator: validateJson }
]">
<JsonParam v-model:value="_value.properties" :name="name.concat(['properties'])"></JsonParam>
</a-form-item>
<a-form-item label="文件类型" :name="name.concat(['fileType'])" v-if="['file'].includes(_value.type)" initialValue="url"
</j-form-item>
<j-form-item label="文件类型" :name="name.concat(['fileType'])" v-if="['file'].includes(_value.type)" initialValue="url"
:rules="[
{ required: true, message: '请选择文件类型' },
]">
<a-select v-model:value="_value.fileType" :options="FileTypeList" size="small"></a-select>
</a-form-item>
<j-select v-model:value="_value.fileType" :options="FileTypeList" size="small"></j-select>
</j-form-item>
</template>
<script lang="ts" setup mame="BaseForm">
import { DataTypeList, FileTypeList } from '@/views/device/data';
@ -56,6 +62,8 @@ import EnumParam from '@/components/Metadata/EnumParam/index.vue'
import ArrayParam from '@/components/Metadata/ArrayParam/index.vue'
import JsonParam from '@/components/Metadata/JsonParam/index.vue'
import { useMetadataStore } from '@/store/metadata';
import { Rule } from 'ant-design-vue/es/form';
import { Form } from 'ant-design-vue/es';
type ValueType = Record<any, any>;
const props = defineProps({
@ -84,15 +92,8 @@ interface Emits {
}
const emit = defineEmits<Emits>()
// emit('update:value', { extends: {}, ...props.value })
const metadataStore = useMetadataStore()
// const _value = computed({
// get: () => props.value,
// set: val => {
// emit('update:value', val)
// }
// })
const _value = ref<ValueType>({})
watchEffect(() => {
_value.value = props.value || {
@ -142,6 +143,79 @@ const eventDataTypeList = [
const changeType = (val: SelectValue) => {
emit('changeType', val as string)
}
const validateEnum = async (_rule: Rule, val: Record<any, any>[]) => {
if (val.length === 0) return Promise.reject(new Error('请配置枚举项'));
const flag = val.every((item) => {
return item.value && item.text;
});
if (!flag) {
return Promise.reject(new Error('请配置枚举项'));
}
return Promise.resolve();
}
const validateArray = async (_rule: Rule, val: Record<any, any>) => {
if (!val) return Promise.reject(new Error('请输入元素配置'));
await validateValueType(_rule, val)
return Promise.resolve();
}
const validateJson = async (_rule: Rule, val: Record<any, any>[]) => {
if (!val || val.length === 0) {
return Promise.reject(new Error('请输入配置参数'));
}
for (let item of val) {
if (!item) return Promise.reject(new Error('请输入配置参数'));
await validateValueType(_rule, item)
}
return Promise.resolve();
}
const validateValueType = async (_rule: Rule, val: Record<any, any>) => {
if (!val) return Promise.reject(new Error('请输入元素配置'));
if (!val.id) {
return Promise.reject(new Error('请输入标识'))
}
if (!val.name) {
return Promise.reject(new Error('请输入名称'))
}
if (metadataStore.model.type !== 'functions' && !val.valueType?.type) {
return Promise.reject(new Error(`请选择${props.title}`))
}
if (['enum'].includes(val.valueType.type)) {
await validateEnum(_rule, val.valueType.elements)
}
if (['array'].includes(val.valueType.type)) {
await validateArray(_rule, val.valueType.elementType)
}
if (['object'].includes(val.valueType.type)) {
await validateJson(_rule, val.valueType.properties)
}
if (['file'].includes(val.valueType.type) && !val.valueType.fileType) {
return Promise.reject(new Error('请选择文件类型'))
}
return Promise.resolve();
}
// const rules = ref({
// type: [
// metadataStore.model.type !== 'functions' ? { required: true, message: `${props.title}` } : {},
// ],
// elements: [
// { required: true, validator: validateEnum, message: '' }
// ],
// elementType: [
// { validator: validateArray, message: '' }
// ],
// properties: [
// { validator: validateJson, message: '' }
// ],
// fileType: [
// { required: true, message: '' },
// ]
// })
</script>
<style lang="less" scoped>
:deep(.ant-form-item-label) {

View File

@ -1,13 +1,13 @@
<template>
<a-drawer :mask-closable="false" width="25vw" visible :title="`${title}-${typeMapping[metadataStore.model.type]}`"
<j-drawer :mask-closable="false" width="25vw" visible :title="`${title}-${typeMapping[metadataStore.model.type]}`"
@close="close" destroy-on-close :z-index="1000" placement="right">
<template #extra>
<a-button :loading="save.loading" type="primary" @click="save.saveMetadata">保存</a-button>
<j-button :loading="save.loading" type="primary" @click="save.saveMetadata">保存</j-button>
</template>
<a-form ref="formRef" :model="form.model" layout="vertical">
<j-form ref="formRef" :model="form.model" layout="vertical">
<BaseForm :model-type="metadataStore.model.type" :type="type" v-model:value="form.model"></BaseForm>
</a-form>
</a-drawer>
</j-form>
</j-drawer>
</template>
<script lang="ts" setup name="Edit">
import { useInstanceStore } from '@/store/instance';
@ -22,6 +22,7 @@ import { DeviceInstance } from '@/views/device/Instance/typings';
import BaseForm from './BaseForm.vue';
import { PropType } from 'vue';
import { _deploy } from '@/api/device/product';
import { cloneDeep } from 'lodash';
const props = defineProps({
type: {
@ -60,7 +61,7 @@ const form = reactive({
model: {} as any,
})
if (metadataStore.model.action === 'edit') {
form.model = metadataStore.model.item
form.model = cloneDeep(metadataStore.model.item)
}
const formRef = ref<FormInstance>()
@ -75,7 +76,9 @@ const save = reactive({
const type = metadataStore.model.type
const _detail: ProductItem | DeviceInstance = props.type === 'device' ? instanceStore.detail : productStore.current
const _metadata = JSON.parse(_detail?.metadata || '{}')
const list = _metadata[type] as any[]
console.log(_metadata)
console.log(type)
const list = (_metadata[type] as any[]) || []
if (formValue.id) {
if (metadataStore.model.action === 'add' && list.some(item => item.id === formValue.id)) {
message.error('标识已存在')

View File

@ -1,7 +1,7 @@
<template>
<j-pro-table :loading="loading" :data-source="data" size="small" :columns="columns" row-key="id" model="TABLE">
<template #headerTitle>
<a-input-search v-model:value="searchValue" placeholder="请输入名称" @search="handleSearch"></a-input-search>
<j-input-search v-model:value="searchValue" placeholder="请输入名称" @search="handleSearch"></j-input-search>
</template>
<template #rightExtraRender>
<PermissionButton type="primary" :uhas-permission="`${permission}:update`" key="add" @click="handleAddClick"
@ -135,6 +135,13 @@ const refreshMetadata = () => {
watch([route.params.id, type], refreshMetadata, { immediate: true })
const metadataStore = useMetadataStore()
watch(() => metadataStore.model.importMetadata,
(val: boolean) => {
if (!!val) {
refreshMetadata()
metadataStore.set('importMetadata', false)
}
})
const handleAddClick = () => {
metadataStore.set('edit', true)
metadataStore.set('item', undefined)
@ -189,6 +196,4 @@ const removeItem = async (record: MetadataItem) => {
}
};
</script>
<style scoped lang="less">
</style>
<style scoped lang="less"></style>

View File

@ -1,13 +1,13 @@
<template>
<a-drawer :mask-closable="false" title="查看物模型" width="700" v-model:visible="_visible" destroy-on-close @close="close">
<j-drawer :mask-closable="false" title="查看物模型" width="700" v-model:visible="_visible" destroy-on-close @close="close">
<template #extra>
<a-space>
<a-button type="primary" @click="handleExport">
<j-space>
<j-button type="primary" @click="handleExport">
导出
</a-button>
</a-space>
</j-button>
</j-space>
</template>
<a-spin :spinning="loading">
<j-spin :spinning="loading">
<div class="cat-content">
<p class="cat-tip">
物模型是对设备在云端的功能描述包括设备的属性服务和事件物联网平台通过定义一种物的描述语言来描述物模型称之为
@ -15,15 +15,15 @@
组装上报设备的数据您可以导出完整物模型用于云端应用开发
</p>
</div>
<a-tabs @change="handleConvertMetadata" destroy-inactive-tab-pane>
<a-tab-pane v-for="item in codecs" :key="item.id" :tab="item.name">
<j-tabs @change="handleConvertMetadata" destroy-inactive-tab-pane>
<j-tab-pane v-for="item in codecs" :key="item.id" :tab="item.name">
<div class="cat-panel">
<MonacoEditor v-model="value" theme="vs" style="height: 100%"></MonacoEditor>
</div>
</a-tab-pane>
</a-tabs>
</a-spin>
</a-drawer>
</j-tab-pane>
</j-tabs>
</j-spin>
</j-drawer>
</template>
<script setup lang="ts" name="Cat">
import { message } from 'ant-design-vue/es';

View File

@ -1,5 +1,5 @@
<template>
<a-modal :mask-closable="false" title="导入物模型" destroy-on-close v-model:visible="_visible" @cancel="close"
<j-modal :mask-closable="false" title="导入物模型" destroy-on-close v-model:visible="_visible" @cancel="close"
@ok="handleImport" :confirm-loading="loading">
<div class="import-content">
<p class="import-tip">
@ -7,46 +7,46 @@
导入的物模型会覆盖原来的属性功能事件标签请谨慎操作
</p>
</div>
<a-form layout="vertical" v-model="formModel">
<a-form-item v-if="type === 'product'" label="导入方式" v-bind="validateInfos.type">
<a-select v-model:value="formModel.type">
<a-select-option value="copy">拷贝产品</a-select-option>
<a-select-option value="import">导入物模型</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="选择产品" v-bind="validateInfos.copy" v-if="formModel.type === 'copy'">
<a-select :options="productList" v-model:value="formModel.copy" option-filter-prop="label"></a-select>
</a-form-item>
<a-form-item label="物模型类型" v-bind="validateInfos.metadata"
<j-form layout="vertical" v-model="formModel">
<j-form-item v-if="type === 'product'" label="导入方式" v-bind="validateInfos.type">
<j-select v-model:value="formModel.type">
<j-select-option value="copy">拷贝产品</j-select-option>
<j-select-option value="import">导入物模型</j-select-option>
</j-select>
</j-form-item>
<j-form-item label="选择产品" v-bind="validateInfos.copy" v-if="formModel.type === 'copy'">
<j-select :options="productList" v-model:value="formModel.copy" option-filter-prop="label"></j-select>
</j-form-item>
<j-form-item label="物模型类型" v-bind="validateInfos.metadata"
v-if="type === 'device' || formModel.type === 'import'">
<a-select v-model:value="formModel.metadata">
<a-select-option value="jetlinks">Jetlinks物模型</a-select-option>
<a-select-option value="alink">阿里云物模型TSL</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="导入类型" v-bind="validateInfos.metadataType"
<j-select v-model:value="formModel.metadata">
<j-select-option value="jetlinks">Jetlinks物模型</j-select-option>
<j-select-option value="alink">阿里云物模型TSL</j-select-option>
</j-select>
</j-form-item>
<j-form-item label="导入类型" v-bind="validateInfos.metadataType"
v-if="type === 'device' || formModel.type === 'import'">
<a-select v-model:value="formModel.metadataType">
<a-select-option value="file">文件上传</a-select-option>
<a-select-option value="script">脚本</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="文件上传" v-bind="validateInfos.upload" v-if="formModel.metadataType === 'file'">
<a-input v-model:value="formModel.upload">
<j-select v-model:value="formModel.metadataType">
<j-select-option value="file">文件上传</j-select-option>
<j-select-option value="script">脚本</j-select-option>
</j-select>
</j-form-item>
<j-form-item label="文件上传" v-bind="validateInfos.upload" v-if="formModel.metadataType === 'file'">
<j-input v-model:value="formModel.upload">
<template #addonAfter>
<a-upload v-model:file-list="fileList" :before-upload="beforeUpload" accept=".json"
<j-upload v-model:file-list="fileList" :before-upload="beforeUpload" accept=".json"
:show-upload-list="false" :action="FILE_UPLOAD" @change="fileChange"
:headers="{ 'X-Access-Token': getToken()}">
<AIcon type="UploadOutlined" class="upload-button" />
</a-upload>
</j-upload>
</template>
</a-input>
</a-form-item>
<a-form-item label="物模型" v-bind="validateInfos.import" v-if="formModel.metadataType === 'script'">
</j-input>
</j-form-item>
<j-form-item label="物模型" v-bind="validateInfos.import" v-if="formModel.metadataType === 'script'">
<MonacoEditor v-model="formModel.import" theme="vs" style="height: 300px"></MonacoEditor>
</a-form-item>
</a-form>
</a-modal>
</j-form-item>
</j-form>
</j-modal>
</template>
<script setup lang="ts" name="Import">
import { useForm } from 'ant-design-vue/es/form';
@ -54,13 +54,14 @@ import { saveMetadata } from '@/api/device/instance'
import { queryNoPagingPost, convertMetadata, modify } from '@/api/device/product'
import type { DefaultOptionType } from 'ant-design-vue/es/select';
import type { UploadProps, UploadFile, UploadChangeParam } from 'ant-design-vue/es';
import type { DeviceMetadata, ProductItem } from '@/views/device/Product/typings'
import { message } from 'ant-design-vue/es';
import type { DeviceMetadata } from '@/views/device/Product/typings'
import { message } from 'jetlinks-ui-components';
import { useInstanceStore } from '@/store/instance'
import { useProductStore } from '@/store/product';
import { FILE_UPLOAD } from '@/api/comm';
import { getToken } from '@/utils/comm';
import MonacoEditor from '@/components/MonacoEditor/index.vue'
import { useMetadataStore } from '@/store/metadata';
const route = useRoute()
const instanceStore = useInstanceStore()
@ -87,7 +88,6 @@ const _visible = computed({
})
const close = () => {
console.log(1)
emits('update:visible', false);
}
@ -196,7 +196,7 @@ const operateLimits = (mdata: DeviceMetadata) => {
});
return obj;
};
const metadataStore = useMetadataStore()
const handleImport = async () => {
validate().then(async (data) => {
loading.value = true
@ -224,6 +224,7 @@ const handleImport = async () => {
} else {
productStore.refresh(id as string)
}
metadataStore.set('importMetadata', true)
// Store.set(SystemConst.GET_METADATA, true)
// Store.set(SystemConst.REFRESH_METADATA_TABLE, true)
close()
@ -263,13 +264,14 @@ const handleImport = async () => {
message.success('导入成功')
}
}
// Store.set(SystemConst.GET_METADATA, true)
// Store.set(SystemConst.REFRESH_METADATA_TABLE, true)
if (props?.type === 'device') {
instanceStore.refresh(id as string)
} else {
productStore.refresh(id as string)
}
metadataStore.set('importMetadata', true)
// Store.set(SystemConst.GET_METADATA, true)
// Store.set(SystemConst.REFRESH_METADATA_TABLE, true)
close();
} catch (e) {
loading.value = false

View File

@ -1,6 +1,6 @@
<!-- 国标级联-绑定通道 -->
<template>
<a-modal
<j-modal
v-model:visible="_vis"
title="绑定通道"
cancelText="取消"
@ -51,19 +51,19 @@
<h3>通道列表</h3>
</template>
<template #status="slotProps">
<a-space>
<a-badge
<j-space>
<j-badge
:status="
slotProps.status.value === 'online'
? 'success'
: 'error'
"
:text="slotProps.status.text"
></a-badge>
</a-space>
></j-badge>
</j-space>
</template>
</JProTable>
</a-modal>
</j-modal>
</template>
<script setup lang="ts">

View File

@ -28,34 +28,34 @@
<h3>通道列表</h3>
</template>
<template #rightExtraRender>
<a-space>
<a-button type="primary" @click="bindVis = true">
<j-space>
<j-button type="primary" @click="bindVis = true">
绑定通道
</a-button>
<a-popconfirm
</j-button>
<j-popconfirm
title="确认解绑?"
@confirm="handleMultipleUnbind"
>
<a-button> 批量解绑 </a-button>
</a-popconfirm>
</a-space>
<j-button> 批量解绑 </j-button>
</j-popconfirm>
</j-space>
</template>
<template #gbChannelIdHeader="title">
<a-tooltip
<j-tooltip
title="国标级联有16位、20位两种格式。在当前页面修改不会修改视频设备-通道页面中的国标ID"
>
<a-space>
<j-space>
<span>{{ title }}</span>
<AIcon type="InfoCircleOutlined" />
</a-space>
</a-tooltip>
</j-space>
</j-tooltip>
</template>
<template #gbChannelId="slotProps">
<a-space>
<j-space>
<Ellipsis>
{{ slotProps.gbChannelId }}
</Ellipsis>
<a-popover
<j-popover
v-model:visible="slotProps.popVis"
trigger="click"
>
@ -70,7 +70,7 @@
</template>
<template #content>
<div class="simple-form">
<a-input
<j-input
v-model:value="gbID"
@change="validField(slotProps)"
/>
@ -82,67 +82,67 @@
该国标ID在同一设备下已存在
</div>
</div>
<a-button
<j-button
type="primary"
@click="handleSave(slotProps)"
:loading="loading"
style="width: 100%"
>
保存
</a-button>
</j-button>
</template>
<a-button type="link" @click="slotProps.popVis = true">
<j-button type="link" @click="slotProps.popVis = true">
<AIcon type="EditOutlined" />
</a-button>
</a-popover>
</a-space>
</j-button>
</j-popover>
</j-space>
</template>
<template #status="slotProps">
<a-space>
<a-badge
<j-space>
<j-badge
:status="
slotProps.status.value === 'online'
? 'success'
: 'error'
"
:text="slotProps.status.text"
></a-badge>
</a-space>
></j-badge>
</j-space>
</template>
<template #action="slotProps">
<a-space :size="16">
<a-tooltip
<j-space :size="16">
<j-tooltip
v-for="i in getActions(slotProps, 'table')"
:key="i.key"
v-bind="i.tooltip"
>
<a-popconfirm
<j-popconfirm
v-if="i.popConfirm"
v-bind="i.popConfirm"
:disabled="i.disabled"
>
<a-button
<j-button
:disabled="i.disabled"
style="padding: 0"
type="link"
><AIcon :type="i.icon"
/></a-button>
</a-popconfirm>
<a-button
/></j-button>
</j-popconfirm>
<j-button
style="padding: 0"
type="link"
v-else
@click="i.onClick && i.onClick(slotProps)"
>
<a-button
<j-button
:disabled="i.disabled"
style="padding: 0"
type="link"
><AIcon :type="i.icon"
/></a-button>
</a-button>
</a-tooltip>
</a-space>
/></j-button>
</j-button>
</j-tooltip>
</j-space>
</template>
</JProTable>

View File

@ -1,6 +1,6 @@
<!-- 国标级联-推送 -->
<template>
<a-modal
<j-modal
v-model:visible="_vis"
title="推送"
cancelText="取消"
@ -9,10 +9,10 @@
@ok="_vis = false"
@cancel="_vis = false"
>
<a-row :gutter="20">
<a-col :span="8">
<j-row :gutter="20">
<j-col :span="8">
<p>成功{{ successCount }}</p>
<a-space>
<j-space>
<p>失败{{ failCount }}</p>
<a
v-if="errMessage.length"
@ -24,19 +24,19 @@
"
>下载</a
>
</a-space>
</a-col>
<a-col :span="8">
</j-space>
</j-col>
<j-col :span="8">
<p>推送通道数量{{ data.count }}</p>
</a-col>
<a-col :span="8">
</j-col>
<j-col :span="8">
<p>已推送通道数量{{ successCount + failCount }}</p>
</a-col>
</a-row>
</j-col>
</j-row>
<div v-if="flag">
<a-textarea :rows="10" v-model:value="errStr" />
<j-textarea :rows="10" v-model:value="errStr" />
</div>
</a-modal>
</j-modal>
</template>
<script setup lang="ts">

View File

@ -1,14 +1,14 @@
<!-- 国标级联新增/编辑 -->
<template>
<page-container>
<a-card>
<a-row :gutter="24">
<a-col :span="12">
<a-form ref="formRef" layout="vertical" :model="formData">
<a-row :gutter="24">
<j-card>
<j-row :gutter="24">
<j-col :span="12">
<j-form ref="formRef" layout="vertical" :model="formData">
<j-row :gutter="24">
<TitleComponent data="基本信息" />
<a-col :span="12">
<a-form-item
<j-col :span="12">
<j-form-item
label="名称"
name="cascadeName"
:rules="[
@ -22,14 +22,14 @@
},
]"
>
<a-input
<j-input
v-model:value="formData.cascadeName"
placeholder="请输入名称"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item
</j-form-item>
</j-col>
<j-col :span="12">
<j-form-item
label="代理视频流"
name="proxyStream"
:rules="[
@ -39,23 +39,23 @@
},
]"
>
<a-radio-group
<j-radio-group
button-style="solid"
v-model:value="formData.proxyStream"
>
<a-radio-button :value="true">
<j-radio-button :value="true">
启用
</a-radio-button>
<a-radio-button :value="false">
</j-radio-button>
<j-radio-button :value="false">
禁用
</a-radio-button>
</a-radio-group>
</a-form-item>
</a-col>
</j-radio-button>
</j-radio-group>
</j-form-item>
</j-col>
<TitleComponent data="信令服务配置" />
<a-col :span="12">
<a-form-item
<j-col :span="12">
<j-form-item
name="clusterNodeId"
:rules="[
{
@ -67,25 +67,25 @@
<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
v-model:value="formData.clusterNodeId"
placeholder="请选择集群节点"
:options="clustersList"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item
</j-form-item>
</j-col>
<j-col :span="12">
<j-form-item
label="信令名称"
name="name"
:rules="[
@ -99,14 +99,14 @@
},
]"
>
<a-input
<j-input
v-model:value="formData.name"
placeholder="请输入信令名称"
/>
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item
</j-form-item>
</j-col>
<j-col :span="24">
<j-form-item
label="上级SIP ID"
name="sipId"
:rules="[
@ -120,14 +120,14 @@
},
]"
>
<a-input
<j-input
v-model:value="formData.sipId"
placeholder="请输入上级SIP ID"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item
</j-form-item>
</j-col>
<j-col :span="12">
<j-form-item
label="上级SIP域"
name="domain"
:rules="[
@ -141,14 +141,14 @@
},
]"
>
<a-input
<j-input
v-model:value="formData.domain"
placeholder="请输入上级平台SIP域"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item
</j-form-item>
</j-col>
<j-col :span="12">
<j-form-item
label="上级SIP 地址"
name="remoteAddress"
:rules="[
@ -161,17 +161,17 @@
},
]"
>
<a-row :gutter="10">
<a-col :span="14">
<a-input
<j-row :gutter="10">
<j-col :span="14">
<j-input
v-model:value="
formData.remoteAddress
"
placeholder="请输入IP地址"
/>
</a-col>
<a-col :span="10">
<a-input-number
</j-col>
<j-col :span="10">
<j-input-number
:min="1"
:max="65535"
v-model:value="
@ -180,13 +180,13 @@
placeholder="请输入端口"
style="width: 100%"
/>
</a-col>
</a-row>
</a-form-item>
</a-col>
</j-col>
</j-row>
</j-form-item>
</j-col>
<a-col :span="24">
<a-form-item
<j-col :span="24">
<j-form-item
label="本地SIP ID"
name="localSipId"
:rules="[
@ -200,14 +200,14 @@
},
]"
>
<a-input
<j-input
v-model:value="formData.localSipId"
placeholder="网关侧的SIP ID"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item
</j-form-item>
</j-col>
<j-col :span="12">
<j-form-item
name="host"
:rules="[
{
@ -222,36 +222,36 @@
<template #label>
<span>
SIP本地地址
<a-tooltip
<j-tooltip
title="使用指定的网卡和端口进行请求"
>
<AIcon
type="QuestionCircleOutlined"
style="margin-left: 2px"
/>
</a-tooltip>
</j-tooltip>
</span>
</template>
<a-row :gutter="10">
<a-col :span="14">
<a-select
<j-row :gutter="10">
<j-col :span="14">
<j-select
v-model:value="formData.host"
placeholder="请选择IP地址"
:options="allList"
/>
</a-col>
<a-col :span="10">
<a-select
</j-col>
<j-col :span="10">
<j-select
v-model:value="formData.port"
placeholder="请选择端口"
:options="allListPorts"
/>
</a-col>
</a-row>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item
</j-col>
</j-row>
</j-form-item>
</j-col>
<j-col :span="12">
<j-form-item
label="SIP远程地址"
name="publicHost"
:rules="[
@ -264,17 +264,17 @@
},
]"
>
<a-row :gutter="10">
<a-col :span="14">
<a-input
<j-row :gutter="10">
<j-col :span="14">
<j-input
v-model:value="
formData.publicHost
"
placeholder="请输入IP地址"
/>
</a-col>
<a-col :span="10">
<a-input-number
</j-col>
<j-col :span="10">
<j-input-number
:min="1"
:max="65535"
v-model:value="
@ -283,12 +283,12 @@
placeholder="请输入端口"
style="width: 100%"
/>
</a-col>
</a-row>
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item
</j-col>
</j-row>
</j-form-item>
</j-col>
<j-col :span="24">
<j-form-item
label="传输协议"
name="transport"
:rules="[
@ -298,22 +298,22 @@
},
]"
>
<a-radio-group
<j-radio-group
button-style="solid"
v-model:value="formData.transport"
@change="setPorts"
>
<a-radio-button value="UDP">
<j-radio-button value="UDP">
UDP
</a-radio-button>
<a-radio-button value="TCP">
</j-radio-button>
<j-radio-button value="TCP">
TCP
</a-radio-button>
</a-radio-group>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item
</j-radio-button>
</j-radio-group>
</j-form-item>
</j-col>
<j-col :span="12">
<j-form-item
label="用户"
name="user"
:rules="[
@ -327,14 +327,14 @@
},
]"
>
<a-input
<j-input
v-model:value="formData.user"
placeholder="请输入用户"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item
</j-form-item>
</j-col>
<j-col :span="12">
<j-form-item
label="接入密码"
name="password"
:rules="[
@ -348,14 +348,14 @@
},
]"
>
<a-input-password
<j-input-password
v-model:value="formData.password"
placeholder="请输入接入密码"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item
</j-form-item>
</j-col>
<j-col :span="12">
<j-form-item
label="厂商"
name="manufacturer"
:rules="[
@ -369,14 +369,14 @@
},
]"
>
<a-input
<j-input
v-model:value="formData.manufacturer"
placeholder="请输入厂商"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item
</j-form-item>
</j-col>
<j-col :span="12">
<j-form-item
label="型号"
name="model"
:rules="[
@ -390,14 +390,14 @@
},
]"
>
<a-input
<j-input
v-model:value="formData.model"
placeholder="请输入型号"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item
</j-form-item>
</j-col>
<j-col :span="12">
<j-form-item
label="版本号"
name="firmware"
:rules="[
@ -411,14 +411,14 @@
},
]"
>
<a-input
<j-input
v-model:value="formData.firmware"
placeholder="请输入版本号"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item
</j-form-item>
</j-col>
<j-col :span="12">
<j-form-item
label="心跳周期(秒)"
name="keepaliveInterval"
:rules="[
@ -428,7 +428,7 @@
},
]"
>
<a-input-number
<j-input-number
:min="1"
:max="10000"
v-model:value="
@ -437,10 +437,10 @@
placeholder="请输入心跳周期"
style="width: 100%"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item
</j-form-item>
</j-col>
<j-col :span="12">
<j-form-item
label="注册间隔(秒)"
name="registerInterval"
:rules="[
@ -450,7 +450,7 @@
},
]"
>
<a-input-number
<j-input-number
:min="1"
:max="10000"
v-model:value="
@ -459,29 +459,29 @@
placeholder="请输入注册间隔"
style="width: 100%"
/>
</a-form-item>
</a-col>
</a-row>
</j-form-item>
</j-col>
</j-row>
<a-form-item>
<a-button
<j-form-item>
<j-button
type="primary"
@click="handleSubmit"
:loading="btnLoading"
>
保存
</a-button>
</a-form-item>
</a-form>
</a-col>
<a-col :span="12">
</j-button>
</j-form-item>
</j-form>
</j-col>
<j-col :span="12">
<div class="doc">
<h1>1.概述</h1>
<div>
配置国标级联平台可以将已经接入到自身的摄像头共享给第三方调用播放
</div>
<div>
<a-alert
<j-alert
message="注该配置只用于将本平台向上级联至第三方平台如需第三方平台向上级联至本平台请在“视频设备”页面新增设备时选择“GB/T28181”接入方式。"
type="info"
show-icon
@ -494,7 +494,7 @@
<h2>1上级SIP ID</h2>
<div>请填写第三方平台中配置的<b>SIP ID</b></div>
<div class="image">
<a-image
<j-image
width="100%"
:src="getImage('/northbound/doc2.png')"
/>
@ -502,7 +502,7 @@
<h2>2上级SIP </h2>
<div>请填写第三方平台中配置的<b>SIP ID域</b></div>
<div class="image">
<a-image
<j-image
width="100%"
:src="getImage('/northbound/doc1.png')"
/>
@ -510,7 +510,7 @@
<h2>3上级SIP 地址</h2>
<div>请填写第三方平台中配置的<b>SIP ID地址</b></div>
<div class="image">
<a-image
<j-image
width="100%"
:src="getImage('/northbound/doc3.png')"
/>
@ -549,9 +549,9 @@
SIP代理与 SIP服务器出现1s误 差所经过的运行时间
</div>
</div>
</a-col>
</a-row>
</a-card>
</j-col>
</j-row>
</j-card>
</page-container>
</template>
@ -652,7 +652,7 @@ onMounted(() => {
});
const regDomain =
/[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+\.?/;
/[j-zA-Z0-9][-j-zA-Z0-9]{0,62}(\.[j-zA-Z0-9][-j-zA-Z0-9]{0,62})+\.?/;
/**
* 上级SIP地址 字段验证
* @param _

View File

@ -53,7 +53,7 @@
</h3>
<p>通道数量{{ slotProps.count }}</p>
<Ellipsis>
<a-badge
<j-badge
:text="`sip:${slotProps.sipConfigs[0]?.sipId}@${slotProps.sipConfigs[0]?.hostAndPort}`"
:status="
slotProps.status?.value === 'enabled'
@ -92,7 +92,7 @@
{{ slotProps.sipConfigs[0]?.publicHost }}
</template>
<template #status="slotProps">
<a-badge
<j-badge
:text="slotProps.status?.text"
:status="
slotProps.status?.value === 'enabled'
@ -102,7 +102,7 @@
/>
</template>
<template #onlineStatus="slotProps">
<a-badge
<j-badge
:text="slotProps.onlineStatus?.text"
:status="
slotProps.onlineStatus?.value === 'online'
@ -112,7 +112,7 @@
/>
</template>
<template #action="slotProps">
<a-space :size="16">
<j-space :size="16">
<template
v-for="i in getActions(slotProps, 'table')"
:key="i.key"
@ -131,7 +131,7 @@
<template #icon><AIcon :type="i.icon" /></template>
</PermissionButton>
</template>
</a-space>
</j-space>
</template>
</JProTable>

View File

@ -3,26 +3,26 @@
<div class="card-header">
<div class="title">{{ title }}</div>
<div class="tools">
<a-space>
<a-radio-group
<j-space>
<j-radio-group
v-model:value="dimension"
button-style="solid"
>
<a-radio-button value="today">今日</a-radio-button>
<a-radio-button value="week">近一周</a-radio-button>
<a-radio-button value="month">近一月</a-radio-button>
<a-radio-button value="year">近一年</a-radio-button>
</a-radio-group>
<a-range-picker
<j-radio-button value="today">今日</j-radio-button>
<j-radio-button value="week">近一周</j-radio-button>
<j-radio-button value="month">近一月</j-radio-button>
<j-radio-button value="year">近一年</j-radio-button>
</j-radio-group>
<j-range-picker
format="YYYY-MM-DD HH:mm:ss"
valueFormat="x"
v-model:value="dateRange"
/>
</a-space>
</j-space>
</div>
</div>
<div v-if="chartData.length" class="chart" ref="chartRef"></div>
<a-empty v-else class="no-data" description="暂无数据"></a-empty>
<j-empty v-else class="no-data" description="暂无数据"></j-empty>
</div>
</template>
@ -56,52 +56,49 @@ const chartRef = ref();
const createChart = () => {
nextTick(() => {
const myChart = echarts.init(chartRef.value as HTMLElement);
const sData: number[] = props.chartData.map(
(m: any) => m.value && m.value.toFixed(2),
);
const maxY = Math.max.apply(null, sData.length ? sData : [0]);
const options = {
grid: {
left: '7%',
left: maxY > 100000 ? 90 : 50,
right: '5%',
top: '5%',
bottom: '5%',
},
tooltip: {
trigger: 'axis',
// formatter: '{a}<br>{b}: {c}',
axisPointer: {
type: 'shadow',
},
formatter: '{b0}<br />{a0}: {c0}',
},
xAxis: {
type: 'category',
data: props.chartData.map((m: any) => m.x),
},
yAxis: {
type: 'value',
// minInterval: 1,
},
xAxis: [
{
data: props.chartData.map((m: any) => m.x),
},
],
yAxis: [
{
show: false,
axisTick: {
show: false,
},
axisLine: {
show: false,
},
splitLine: {
lineStyle: {
type: 'solid',
},
},
},
],
series: [
{
name: '播放数量(人次)',
data: sData,
type: 'bar',
barWidth: 16,
itemStyle: {
color: '#2f54eb',
},
},
{
name: '播放数量(人次)',
type: 'line',
symbol: 'circle',
showSymbol: false,
smooth: true,
data: props.chartData.map(
(m: any) => m.value && m.value.toFixed(2),
),
lineStyle: {
color: '#a5fff9',
},
data: sData,
},
],
};
@ -116,8 +113,6 @@ const createChart = () => {
watch(
() => props.chartData,
(val) => {
console.log('createChart', val);
createChart();
},
{ deep: true },

View File

@ -4,12 +4,12 @@
<div class="content-left">
<div class="content-left-title">
<span>{{ title }}</span>
<a-tooltip placement="top" v-if="tooltip">
<j-tooltip placement="top" v-if="tooltip">
<template #title>
<span>{{ tooltip }}</span>
</template>
<AIcon type="QuestionCircleOutlined" />
</a-tooltip>
</j-tooltip>
</div>
<div class="content-left-value">{{ value }}</div>
</div>
@ -20,7 +20,7 @@
<div class="top-card-footer">
<template v-for="(item, index) in footer" :key="index">
<span v-if="!item.status">{{ item.title }}</span>
<a-badge v-else :text="item.title" :status="item.status" />
<j-badge v-else :text="item.title" :status="item.status" />
<div class="footer-item-value">{{ item.value }}</div>
</template>
</div>

View File

@ -123,6 +123,7 @@ const getAggData = () => {
{
title: '总时长',
value: timestampFormat(res.result.duration),
status: '',
},
];
});
@ -139,6 +140,7 @@ const getAggPlayingData = () => {
{
title: '播放人数',
value: res.result.playerTotal,
status: '',
},
];
});
@ -188,9 +190,11 @@ const getPlayCount = async (params: any) => {
])
.then((res) => {
let result: any = [];
res.result.forEach((item: any) => {
result = [...result, ...item.data];
});
res.result
.sort((a: any, b: any) => b.data.timestamp - a.data.timestamp)
.forEach((item: any) => {
result.push({ group: item.group, ...item.data });
});
chartData.value = result.map((m: any) => ({
x: m.timeString,
value: m.value,

View File

@ -1,6 +1,6 @@
<!-- 视频设备 - 播放 -->
<template>
<a-modal
<j-modal
v-model:visible="_vis"
title="播放"
cancelText="取消"
@ -33,17 +33,17 @@
/>
</div>
<div class="media-live-tool">
<a-radio-group
<j-radio-group
v-model:value="mediaType"
button-style="solid"
@change="mediaStart"
>
<a-radio-button value="mp4">MP4</a-radio-button>
<a-radio-button value="flv">FLV</a-radio-button>
<a-radio-button value="m3u8">HLS</a-radio-button>
</a-radio-group>
<j-radio-button value="mp4">MP4</j-radio-button>
<j-radio-button value="flv">FLV</j-radio-button>
<j-radio-button value="m3u8">HLS</j-radio-button>
</j-radio-group>
</div>
</a-modal>
</j-modal>
</template>
<script setup lang="ts">

View File

@ -1,6 +1,6 @@
<!-- Modal 弹窗用于新增修改数据 -->
<template>
<a-modal
<j-modal
v-model:visible="_vis"
:title="!!formData.id ? '编辑' : '新增'"
width="650px"
@ -9,10 +9,10 @@
@ok="handleSubmit"
@cancel="handleCancel"
>
<a-form ref="formRef" :model="formData" layout="vertical">
<a-row :gutter="10">
<a-col :span="12">
<a-form-item
<j-form ref="formRef" :model="formData" layout="vertical">
<j-row :gutter="10">
<j-col :span="12">
<j-form-item
name="channelId"
:rules="[
{
@ -26,22 +26,22 @@
>
<template #label>
通道ID
<a-tooltip title="若不填写系统将自动生成唯一ID">
<j-tooltip title="若不填写系统将自动生成唯一ID">
<AIcon
type="QuestionCircleOutlined"
style="margin-left: 2px"
/>
</a-tooltip>
</j-tooltip>
</template>
<a-input
<j-input
v-model:value="formData.channelId"
:disabled="!!formData.id"
placeholder="请输入通道ID"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item
</j-form-item>
</j-col>
<j-col :span="12">
<j-form-item
name="name"
label="通道名称"
:rules="[
@ -49,14 +49,14 @@
{ max: 64, message: '最多可输入64个字符' },
]"
>
<a-input
<j-input
v-model:value="formData.name"
placeholder="请输入通道名称"
/>
</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="media_url"
:rules="[
{ required: true, message: '请输入视频地址' },
@ -65,66 +65,66 @@
>
<template #label>
视频地址
<a-tooltip
<j-tooltip
title="不同厂家的RTSP固定地址规则不同请按对应厂家的规则填写"
>
<AIcon
type="QuestionCircleOutlined"
style="margin-left: 2px"
/>
</a-tooltip>
</j-tooltip>
</template>
<a-input
<j-input
v-model:value="formData.media_url"
placeholder="请输入视频地址"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item
</j-form-item>
</j-col>
<j-col :span="12">
<j-form-item
name="media_username"
label="用户名"
:rules="{ max: 64, message: '最多可输入64个字符' }"
>
<a-input
<j-input
v-model:value="formData.media_username"
placeholder="请输入用户名"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item
</j-form-item>
</j-col>
<j-col :span="12">
<j-form-item
name="media_password"
label="密码"
:rules="{ max: 64, message: '最多可输入64个字符' }"
>
<a-input-password
<j-input-password
v-model:value="formData.media_password"
placeholder="请输入密码"
/>
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item name="address" label="安装地址">
<a-input
</j-form-item>
</j-col>
<j-col :span="24">
<j-form-item name="address" label="安装地址">
<j-input
v-model:value="formData.address"
placeholder="请输入安装地址"
/>
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item name="description" label="说明">
<a-textarea
</j-form-item>
</j-col>
<j-col :span="24">
<j-form-item name="description" label="说明">
<j-textarea
v-model:value="formData.description"
:rows="4"
:maxlength="200"
showCount
/>
</a-form-item>
</a-col>
</a-row>
</a-form>
</a-modal>
</j-form-item>
</j-col>
</j-row>
</j-form>
</j-modal>
</template>
<script setup lang="ts">

View File

@ -19,62 +19,62 @@
model="table"
>
<template #headerTitle>
<a-tooltip
<j-tooltip
v-if="route?.query.type === 'gb28181-2016'"
title="接入方式为GB/T28281时不支持新增"
>
<a-button type="primary" disabled> 新增 </a-button>
</a-tooltip>
<a-button type="primary" @click="handleAdd" v-else>
<j-button type="primary" disabled> 新增 </j-button>
</j-tooltip>
<j-button type="primary" @click="handleAdd" v-else>
新增
</a-button>
</j-button>
</template>
<template #status="slotProps">
<a-space>
<a-badge
<j-space>
<j-badge
:status="
slotProps.status.value === 'online'
? 'success'
: 'error'
"
:text="slotProps.status.text"
></a-badge>
</a-space>
></j-badge>
</j-space>
</template>
<template #action="slotProps">
<a-space :size="16">
<a-tooltip
<j-space :size="16">
<j-tooltip
v-for="i in getActions(slotProps, 'table')"
:key="i.key"
v-bind="i.tooltip"
>
<a-popconfirm
<j-popconfirm
v-if="i.popConfirm"
v-bind="i.popConfirm"
:disabled="i.disabled"
>
<a-button
<j-button
:disabled="i.disabled"
style="padding: 0"
type="link"
><AIcon :type="i.icon"
/></a-button>
</a-popconfirm>
<a-button
/></j-button>
</j-popconfirm>
<j-button
style="padding: 0"
type="link"
v-else
@click="i.onClick && i.onClick(slotProps)"
>
<a-button
<j-button
:disabled="i.disabled"
style="padding: 0"
type="link"
><AIcon :type="i.icon"
/></a-button>
</a-button>
</a-tooltip>
</a-space>
/></j-button>
</j-button>
</j-tooltip>
</j-space>
</template>
</JProTable>

View File

@ -53,14 +53,14 @@
/>
</div>
<div class="playback-right">
<a-spin :spinning="loading">
<a-tooltip placement="topLeft">
<j-spin :spinning="loading">
<j-tooltip placement="topLeft">
<template #title>
<div>云端存储在服务器中</div>
<div>本地存储在设备本地</div>
</template>
<div>类型: <AIcon type="QuestionCircleOutlined" /></div>
</a-tooltip>
</j-tooltip>
<RadioCard
layout="horizontal"
:options="[
@ -80,11 +80,11 @@
v-model="type"
/>
<div class="playback-calendar">
<a-calendar
<j-calendar
v-model:value="time"
:fullscreen="false"
:disabledDate="
(currentDate) => currentDate > dayjs(new Date())
(currentDate: Dayjs) => currentDate > dayjs(new Date())
"
@change="handlePanelChange"
/>
@ -93,20 +93,20 @@
class="playback-list"
:class="{ 'no-list': !historyList.length }"
>
<a-empty
<j-empty
v-if="!historyList.length"
description="暂无数据"
/>
<a-list
<j-list
v-else
class="playback-list-items"
itemLayout="horizontal"
:dataSource="historyList"
>
<template #renderItem="{ item }">
<a-list-item>
<j-list-item>
<template #actions>
<a-tooltip
<j-tooltip
key="play-btn"
:title="
(item.startTime ||
@ -136,8 +136,8 @@
"
/>
</a>
</a-tooltip>
<a-tooltip
</j-tooltip>
<j-tooltip
key="download"
:title="
type !== 'local'
@ -155,7 +155,7 @@
() => downloadClick(item)
"
/>
</a-tooltip>
</j-tooltip>
</template>
<div>
@ -173,12 +173,12 @@
).format('HH:mm:ss')
}}
</div>
</a-list-item>
</j-list-item>
</template>
<div></div>
</a-list>
</j-list>
</div>
</a-spin>
</j-spin>
</div>
</div>
</page-container>

View File

@ -1,5 +1,5 @@
<template>
<a-modal
<j-modal
v-model:visible="_vis"
title="快速添加"
cancelText="取消"
@ -9,38 +9,38 @@
:confirmLoading="btnLoading"
width="660px"
>
<a-form layout="vertical">
<a-form-item label="产品名称" v-bind="validateInfos.name">
<a-input
<j-form layout="vertical">
<j-form-item label="产品名称" v-bind="validateInfos.name">
<j-input
v-model:value="formData.name"
placeholder="请输入名称"
/>
</a-form-item>
</j-form-item>
<template v-if="channel === 'gb28181-2016' && formData.accessId">
<a-form-item
<j-form-item
label="接入密码"
v-bind="validateInfos['configuration.access_pwd']"
>
<a-input-password
<j-input-password
v-model:value="formData.configuration.access_pwd"
placeholder="请输入接入密码"
/>
</a-form-item>
<a-form-item label="流传输模式">
<a-select
</j-form-item>
<j-form-item label="流传输模式">
<j-select
v-model:value="formData.configuration.stream_mode"
placeholder="请选择流传输模式"
:options="streamMode"
/>
</a-form-item>
</j-form-item>
</template>
<a-form-item label="接入网关" v-bind="validateInfos.accessId">
<j-form-item label="接入网关" v-bind="validateInfos.accessId">
<div class="gateway-box">
<div v-if="!gatewayList.length">
暂无数据请先
<a-button type="link">
<j-button type="link">
添加{{ providerType[props.channel] }} 接入网关
</a-button>
</j-button>
</div>
<div
class="gateway-item"
@ -71,20 +71,20 @@
{{ item.name }}
</h3>
<div class="desc">{{ item.description }}</div>
<a-row v-if="props.channel === 'gb28181-2016'">
<a-col :span="12">
<j-row v-if="props.channel === 'gb28181-2016'">
<j-col :span="12">
{{ item.channelInfo?.name }}
</a-col>
<a-col :span="12">
</j-col>
<j-col :span="12">
{{ item.protocolDetail.name }}
</a-col>
<a-col :span="12">
</j-col>
<j-col :span="12">
<p
v-for="(i, idx) in item.channelInfo
?.addresses"
:key="`${i.address}_address${idx}`"
>
<a-badge
<j-badge
:text="i.address"
:color="
i.health === -1
@ -93,10 +93,10 @@
"
/>
</p>
</a-col>
</a-row>
<a-row v-else>
<a-col :span="24">
</j-col>
</j-row>
<j-row v-else>
<j-col :span="24">
<div class="subtitle">
{{ item.protocolDetail.name }}
</div>
@ -105,15 +105,15 @@
item.protocolDetail.description
}}
</p>
</a-col>
</a-row>
</j-col>
</j-row>
</template>
</CardBox>
</div>
</div>
</a-form-item>
</a-form>
</a-modal>
</j-form-item>
</j-form>
</j-modal>
</template>
<script setup lang="ts">

View File

@ -1,11 +1,11 @@
<!-- 视频设备新增/编辑 -->
<template>
<page-container>
<a-card>
<a-row :gutter="24">
<a-col :span="12">
<a-form layout="vertical">
<a-form-item
<j-card>
<j-row :gutter="24">
<j-col :span="12">
<j-form layout="vertical">
<j-form-item
label="接入方式"
v-bind="validateInfos.channel"
>
@ -16,134 +16,134 @@
:disabled="!!route.query.id"
v-model="formData.channel"
/>
</a-form-item>
<a-row :gutter="24">
<a-col :span="8">
</j-form-item>
<j-row :gutter="24">
<j-col :span="8">
<JUpload
v-model:modelValue="formData.photoUrl"
:bgImage="formData.photoUrl"
/>
</a-col>
<a-col :span="16">
<a-form-item
</j-col>
<j-col :span="16">
<j-form-item
label="ID"
v-bind="validateInfos.id"
>
<a-input
<j-input
v-model:value="formData.id"
placeholder="请输入"
:disabled="!!route.query.id"
/>
</a-form-item>
<a-form-item
</j-form-item>
<j-form-item
label="设备名称"
v-bind="validateInfos.name"
>
<a-input
<j-input
v-model:value="formData.name"
placeholder="请输入名称"
/>
</a-form-item>
</a-col>
</a-row>
<a-form-item
</j-form-item>
</j-col>
</j-row>
<j-form-item
label="所属产品"
v-bind="validateInfos.productId"
>
<a-row :gutter="[0, 10]">
<a-col :span="!!route.query.id ? 24 : 22">
<a-select
<j-row :gutter="[0, 10]">
<j-col :span="!!route.query.id ? 24 : 22">
<j-select
v-model:value="formData.productId"
placeholder="请选择所属产品"
:disabled="!!route.query.id"
>
<a-select-option
<j-select-option
v-for="(item, index) in productList"
:key="index"
:value="item.id"
>
{{ item.name }}
</a-select-option>
</a-select>
</a-col>
<a-col :span="2" v-if="!route.query.id">
<a-button
</j-select-option>
</j-select>
</j-col>
<j-col :span="2" v-if="!route.query.id">
<j-button
type="link"
@click="saveProductVis = true"
>
<AIcon type="PlusOutlined" />
</a-button>
</a-col>
</a-row>
</a-form-item>
<a-form-item
</j-button>
</j-col>
</j-row>
</j-form-item>
<j-form-item
label="接入密码"
v-bind="validateInfos['others.access_pwd']"
v-if="formData.channel === 'gb28181-2016'"
>
<a-input-password
<j-input-password
v-model:value="formData.others.access_pwd"
placeholder="请输入接入密码"
/>
</a-form-item>
</j-form-item>
<template v-if="!!route.query.id">
<a-form-item
<j-form-item
label="流传输模式"
v-bind="validateInfos.streamMode"
>
<a-radio-group
<j-radio-group
button-style="solid"
v-model:value="formData.streamMode"
>
<a-radio-button value="UDP">
<j-radio-button value="UDP">
UDP
</a-radio-button>
<a-radio-button value="TCP_PASSIVE">
</j-radio-button>
<j-radio-button value="TCP_PASSIVE">
TCP被动
</a-radio-button>
</a-radio-group>
</a-form-item>
<a-form-item label="设备厂商">
<a-input
</j-radio-button>
</j-radio-group>
</j-form-item>
<j-form-item label="设备厂商">
<j-input
v-model:value="formData.manufacturer"
placeholder="请输入设备厂商"
/>
</a-form-item>
<a-form-item label="设备型号">
<a-input
</j-form-item>
<j-form-item label="设备型号">
<j-input
v-model:value="formData.model"
placeholder="请输入设备型号"
/>
</a-form-item>
<a-form-item label="固件版本">
<a-input
</j-form-item>
<j-form-item label="固件版本">
<j-input
v-model:value="formData.firmware"
placeholder="请输入固件版本"
/>
</a-form-item>
</j-form-item>
</template>
<a-form-item label="说明">
<a-textarea
<j-form-item label="说明">
<j-textarea
v-model:value="formData.description"
show-count
:maxlength="200"
:rows="5"
placeholder="请输入说明"
/>
</a-form-item>
<a-form-item>
<a-button
</j-form-item>
<j-form-item>
<j-button
type="primary"
@click="handleSubmit"
:loading="btnLoading"
>
保存
</a-button>
</a-form-item>
</a-form>
</a-col>
<a-col :span="12">
</j-button>
</j-form-item>
</j-form>
</j-col>
<j-col :span="12">
<div v-if="1" class="doc" style="height: 800">
<h1>1.概述</h1>
<div>
@ -166,7 +166,7 @@
各个厂家不同设备型号的设备端配置页面布局存在差异但配置项基本大同小异此处以大华摄像头为例作为接入配置示例
</div>
<div class="image">
<a-image
<j-image
width="100%"
:src="getImage('/media/doc1.png')"
/>
@ -177,7 +177,7 @@
SIP域通常为SIP服务器编号的前10位
</div>
<div class="image">
<a-image
<j-image
width="100%"
:src="getImage('/media/doc2.png')"
/>
@ -187,7 +187,7 @@
SIP服务器IP/端口填入该设备所属产品-接入方式页面中连接信息的IP/端口
</div>
<div class="image">
<a-image
<j-image
width="100%"
:src="getImage('/media/doc3.png')"
/>
@ -201,7 +201,7 @@
填入该设备所属产品-接入方式页面中GB28281配置处的接入密码
</div>
<div class="image">
<a-image
<j-image
width="100%"
:src="getImage('/media/doc4.png')"
/>
@ -230,9 +230,9 @@
只能选择接入方式为固定地址的产品若当前无对应产品可点击右侧快速添加按钮填写产品名称和选择固定地址类型的网关完成产品创建
</div>
</div>
</a-col>
</a-row>
</a-card>
</j-col>
</j-row>
</j-card>
<SaveProduct
v-model:visible="saveProductVis"
@ -285,7 +285,7 @@ const formRules = ref({
},
{ max: 64, message: '最多输入64个字符' },
{
pattern: /^[a-zA-Z0-9_\-]+$/,
pattern: /^[j-zA-Z0-9_\-]+$/,
message: '请输入英文或者数字或者-或者_',
},
],

View File

@ -44,30 +44,30 @@
<h3 class="card-item-content-title">
{{ slotProps.name }}
</h3>
<a-row>
<a-col :span="12">
<j-row>
<j-col :span="12">
<div class="card-item-content-text">厂商</div>
<div>{{ slotProps.manufacturer }}</div>
</a-col>
<a-col :span="12">
</j-col>
<j-col :span="12">
<div class="card-item-content-text">
通道数量
</div>
<div>{{ slotProps.channelNumber }}</div>
</a-col>
<a-col :span="12">
</j-col>
<j-col :span="12">
<div class="card-item-content-text">型号</div>
<div>{{ slotProps.model }}</div>
</a-col>
<a-col :span="12">
</j-col>
<j-col :span="12">
<div class="card-item-content-text">
接入方式
</div>
<div>
{{ providerType[slotProps.provider] }}
</div>
</a-col>
</a-row>
</j-col>
</j-row>
</template>
<template #actions="item">
<PermissionButton
@ -92,7 +92,7 @@
</CardBox>
</template>
<template #action="slotProps">
<a-space :size="16">
<j-space :size="16">
<template
v-for="i in getActions(slotProps, 'table')"
:key="i.key"
@ -111,7 +111,7 @@
<template #icon><AIcon :type="i.icon" /></template>
</PermissionButton>
</template>
</a-space>
</j-space>
</template>
</JProTable>
</page-container>

View File

@ -21,6 +21,56 @@
/>
</j-col>
</j-row>
<!-- 选择设备 -->
<j-modal
title="选择设备"
width="800px"
v-model:visible="visible"
:maskClosable="false"
:destroyOnClose="true"
@cancel="visible = false"
@ok="handleSubmit"
>
<j-advanced-search
type="simple"
:columns="columns"
@search="handleSearch"
/>
<JProTable
ref="tableRef"
model="table"
rowKey="id"
:columns="columns"
:request="deviceApi.list"
:defaultParams="{
sorts: [{ name: 'createTime', order: 'desc' }],
}"
:params="params"
:rowSelection="{
type: 'radio',
selectedRowKeys: deviceItem?.id
? [deviceItem.id]
: undefined,
onSelect: (record: any) => {
deviceItem = record;
}
}"
>
<template #state="slotProps">
<a-space>
<a-badge
:status="
slotProps.state.value === 'online'
? 'success'
: 'error'
"
:text="slotProps.state.text"
/>
</a-space>
</template>
</JProTable>
</j-modal>
</page-container>
</template>
@ -33,6 +83,13 @@ import BasicCountCard from '@/views/media/Home/components/BasicCountCard.vue';
import { usePermissionStore } from '@/store/permission';
import type { bootConfig, recommendList } from '@/views/home/typing';
import deviceApi from '@/api/media/device';
import { message } from 'ant-design-vue';
import { useMenuStore } from 'store/menu';
const menuStory = useMenuStore();
//
const hasPermission = usePermissionStore().hasPermission;
@ -54,6 +111,7 @@ const deviceBootConfig: bootConfig[] = [
link: 'media/Cascade',
},
];
const deviceStepDetails: recommendList[] = [
{
title: '添加视频设备',
@ -66,8 +124,16 @@ const deviceStepDetails: recommendList[] = [
title: '查看通道',
details: '查看设备下的通道数据,可以进行直播、录制等操作。',
iconUrl: '/images/home/bottom-7.png',
linkUrl: 'media/Device/Channel',
// linkUrl: 'media/Device/Channel',
linkUrl: '',
auth: hasPermission('media/Device:view'),
onClick: (row: any) => {
if (hasPermission('media/Device:view')) {
visible.value = true;
} else {
message.warning('暂无权限,请联系管理员');
}
},
},
{
title: '分屏展示',
@ -76,4 +142,71 @@ const deviceStepDetails: recommendList[] = [
linkUrl: 'media/SplitScreen',
},
];
//
const visible = ref(false);
const columns = [
{
title: 'ID',
dataIndex: 'id',
key: 'id',
search: {
type: 'string',
},
},
{
title: '名称',
dataIndex: 'name',
key: 'name',
search: {
type: 'string',
},
},
{
title: '通道数量',
dataIndex: 'channelNumber',
key: 'channelNumber',
},
{
title: '状态',
dataIndex: 'state',
key: 'state',
scopedSlots: true,
search: {
type: 'select',
options: [
{ label: '在线', value: 'online' },
{ label: '离线', value: 'offline' },
],
handleValue: (v: any) => {
return v;
},
},
},
];
const params = ref<Record<string, any>>({});
/**
* 搜索
* @param params
*/
const handleSearch = (e: any) => {
params.value = e;
};
const deviceItem = ref();
const handleSubmit = () => {
if (deviceItem.value && deviceItem.value.id) {
menuStory.jumpPage(
'media/Device/Channel',
{},
{
id: deviceItem.value.id,
type: deviceItem.value.provider,
},
);
} else {
message.warning('请选择设备');
}
};
</script>

View File

@ -1,6 +1,6 @@
<template>
<page-container>
<a-card class="splitScreen">
<j-card class="splitScreen">
<div class="split-screen">
<LeftTree @onSelect="mediaStart" />
<div class="right-content">
@ -17,7 +17,7 @@
/>
</div>
</div>
</a-card>
</j-card>
</page-container>
</template>

View File

@ -1,6 +1,6 @@
<template>
<div class="left-content">
<a-tree
<j-tree
:height="700"
:show-line="{ showLeafIcon: false }"
:show-icon="true"
@ -15,7 +15,7 @@
v-if="!treeData.find((f: any) => f.id === id)"
/>
</template>
</a-tree>
</j-tree>
</div>
</template>

View File

@ -1,5 +1,5 @@
<template>
<a-modal
<j-modal
v-model:visible="_vis"
title="调试"
cancelText="取消"
@ -8,34 +8,34 @@
@cancel="handleCancel"
:confirmLoading="btnLoading"
>
<a-form ref="formRef" layout="vertical" :model="formData">
<a-form-item
<j-form ref="formRef" layout="vertical" :model="formData">
<j-form-item
label="通知模版"
name="templateId"
:rules="{ required: true, message: '该字段为必填字段' }"
>
<a-select
<j-select
v-model:value="formData.templateId"
placeholder="请选择通知模版"
@change="getTemplateDetail"
>
<a-select-option
<j-select-option
v-for="(item, index) in templateList"
:key="index"
:value="item.id"
>
{{ item.name }}
</a-select-option>
</a-select>
</a-form-item>
<a-form-item
</j-select-option>
</j-select>
</j-form-item>
<j-form-item
label="变量"
v-if="
formData.templateDetailTable &&
formData.templateDetailTable.length
"
>
<a-table
<j-table
row-key="id"
:columns="columns"
:data-source="formData.templateDetailTable"
@ -49,7 +49,7 @@
<span>{{ record[column.dataIndex] }}</span>
</template>
<template v-else>
<a-form-item
<j-form-item
:name="['templateDetailTable', index, 'value']"
:rules="{
required: true,
@ -60,13 +60,13 @@
v-model:modelValue="record.value"
:itemType="record.type"
/>
</a-form-item>
</j-form-item>
</template>
</template>
</a-table>
</a-form-item>
</a-form>
</a-modal>
</j-table>
</j-form-item>
</j-form>
</j-modal>
</template>
<script setup lang="ts">

View File

@ -1,7 +1,7 @@
<!-- webhook请求头可编辑表格 -->
<template>
<div class="table-wrapper">
<a-table
<j-table
:columns="columns"
:data-source="dataSource"
bordered
@ -9,18 +9,18 @@
>
<template #bodyCell="{ column, text, record }">
<template v-if="['key', 'value'].includes(column.dataIndex)">
<a-input v-model:value="record[column.dataIndex]" />
<j-input v-model:value="record[column.dataIndex]" />
</template>
<template v-else-if="column.dataIndex === 'operation'">
<a-button type="text">
<j-button type="text">
<template #icon>
<delete-outlined @click="handleDelete(record.id)" />
</template>
</a-button>
</j-button>
</template>
</template>
</a-table>
<a-button
</j-table>
<j-button
type="dashed"
@click="handleAdd"
style="width: 100%; margin-top: 5px"
@ -29,7 +29,7 @@
<plus-outlined />
</template>
添加
</a-button>
</j-button>
</div>
</template>

View File

@ -1,36 +1,36 @@
<!-- 通知配置详情 -->
<template>
<page-container>
<a-card>
<a-row>
<a-col :span="10">
<a-form layout="vertical">
<a-form-item
<j-card>
<j-row>
<j-col :span="10">
<j-form layout="vertical">
<j-form-item
label="通知方式"
v-bind="validateInfos.type"
>
<a-select
<j-select
v-model:value="formData.type"
placeholder="请选择通知方式"
:disabled="!!formData.id"
@change="handleTypeChange"
>
<a-select-option
<j-select-option
v-for="(item, index) in NOTICE_METHOD"
:key="index"
:value="item.value"
>
{{ item.label }}
</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="名称" v-bind="validateInfos.name">
<a-input
</j-select-option>
</j-select>
</j-form-item>
<j-form-item label="名称" v-bind="validateInfos.name">
<j-input
v-model:value="formData.name"
placeholder="请输入名称"
/>
</a-form-item>
<a-form-item
</j-form-item>
<j-form-item
label="类型"
v-bind="validateInfos.provider"
v-if="formData.type !== 'email'"
@ -40,144 +40,144 @@
v-model="formData.provider"
@change="handleProviderChange"
/>
</a-form-item>
</j-form-item>
<!-- 钉钉 -->
<template v-if="formData.type === 'dingTalk'">
<template
v-if="formData.provider === 'dingTalkMessage'"
>
<a-form-item
<j-form-item
label="AppKey"
v-bind="
validateInfos['configuration.appKey']
"
>
<a-input
<j-input
v-model:value="
formData.configuration.appKey
"
placeholder="请输入AppKey"
/>
</a-form-item>
<a-form-item
</j-form-item>
<j-form-item
label="AppSecret"
v-bind="
validateInfos['configuration.appSecret']
"
>
<a-input
<j-input
v-model:value="
formData.configuration.appSecret
"
placeholder="请输入AppSecret"
/>
</a-form-item>
</j-form-item>
</template>
<template
v-if="
formData.provider === 'dingTalkRobotWebHook'
"
>
<a-form-item
<j-form-item
label="webHook"
v-bind="validateInfos['configuration.url']"
>
<a-input
<j-input
v-model:value="
formData.configuration.url
"
placeholder="请输入webHook"
/>
</a-form-item>
</j-form-item>
</template>
</template>
<!-- 微信 -->
<template v-if="formData.type === 'weixin'">
<a-form-item
<j-form-item
label="corpId"
v-bind="validateInfos['configuration.corpId']"
>
<a-input
<j-input
v-model:value="
formData.configuration.corpId
"
placeholder="请输入corpId"
/>
</a-form-item>
<a-form-item
</j-form-item>
<j-form-item
label="corpSecret"
v-bind="
validateInfos['configuration.corpSecret']
"
>
<a-input
<j-input
v-model:value="
formData.configuration.corpSecret
"
placeholder="请输入corpSecret"
/>
</a-form-item>
</j-form-item>
</template>
<!-- 邮件 -->
<template v-if="formData.type === 'email'">
<a-form-item
<j-form-item
label="服务器地址"
v-bind="validateInfos['configuration.host']"
>
<a-space>
<a-input
<j-space>
<j-input
v-model:value="
formData.configuration.host
"
placeholder="请输入服务器地址"
/>
<a-input-number
<j-input-number
v-model:value="
formData.configuration.port
"
/>
<a-checkbox
<j-checkbox
v-model:value="
formData.configuration.ssl
"
>
开启SSL
</a-checkbox>
</a-space>
</a-form-item>
<a-form-item
</j-checkbox>
</j-space>
</j-form-item>
<j-form-item
label="发件人"
v-bind="validateInfos['configuration.sender']"
>
<a-input
<j-input
v-model:value="
formData.configuration.sender
"
placeholder="请输入发件人"
/>
</a-form-item>
<a-form-item
</j-form-item>
<j-form-item
label="用户名"
v-bind="validateInfos['configuration.username']"
>
<a-input
<j-input
v-model:value="
formData.configuration.username
"
placeholder="请输入用户名"
/>
</a-form-item>
<a-form-item
</j-form-item>
<j-form-item
label="密码"
v-bind="validateInfos['configuration.password']"
>
<a-input
<j-input
v-model:value="
formData.configuration.password
"
placeholder="请输入密码"
/>
</a-form-item>
</j-form-item>
</template>
<!-- 语音/短信 -->
<template
@ -186,94 +186,94 @@
formData.type === 'sms'
"
>
<a-form-item
<j-form-item
label="RegionId"
v-bind="validateInfos['configuration.regionId']"
>
<a-select
<j-select
v-model:value="
formData.configuration.regionId
"
placeholder="请选择RegionId"
>
<a-select-option
<j-select-option
v-for="(item, index) in regionList"
:key="index"
:value="item.value"
>
{{ item.label }}
</a-select-option>
</a-select>
</a-form-item>
<a-form-item
</j-select-option>
</j-select>
</j-form-item>
<j-form-item
label="AccessKeyId"
v-bind="
validateInfos['configuration.accessKeyId']
"
>
<a-input
<j-input
v-model:value="
formData.configuration.accessKeyId
"
placeholder="请输入AccessKeyId"
/>
</a-form-item>
<a-form-item
</j-form-item>
<j-form-item
label="Secret"
v-bind="validateInfos['configuration.secret']"
>
<a-input
<j-input
v-model:value="
formData.configuration.secret
"
placeholder="Secret"
/>
</a-form-item>
</j-form-item>
</template>
<!-- webhook -->
<template v-if="formData.type === 'webhook'">
<a-form-item
<j-form-item
label="Webhook"
v-bind="validateInfos['configuration.url']"
>
<a-input
<j-input
v-model:value="formData.configuration.url"
placeholder="请输入Webhook"
/>
</a-form-item>
<a-form-item label="请求头">
</j-form-item>
<j-form-item label="请求头">
<EditTable
v-model:headers="
formData.configuration.headers
"
/>
</a-form-item>
</j-form-item>
</template>
<a-form-item label="说明">
<a-textarea
<j-form-item label="说明">
<j-textarea
v-model:value="formData.description"
show-count
:maxlength="200"
:rows="5"
placeholder="请输入说明"
/>
</a-form-item>
<a-form-item>
<a-button
</j-form-item>
<j-form-item>
<j-button
type="primary"
@click="handleSubmit"
:loading="btnLoading"
>
保存
</a-button>
</a-form-item>
</a-form>
</a-col>
<a-col :span="12" :push="2">
</j-button>
</j-form-item>
</j-form>
</j-col>
<j-col :span="12" :push="2">
<Doc :docData="formData" />
</a-col>
</a-row>
</a-card>
</j-col>
</j-row>
</j-card>
</page-container>
</template>
@ -395,7 +395,7 @@ const formRules = ref({
{ required: true, message: '请输入Webhook' },
{
pattern:
/^(((ht|f)tps?):\/\/)?([^!@#$%^&*?.\s-]([^!@#$%^&*?.\s]{0,63}[^!@#$%^&*?.\s])?\.)+[a-z]{2,6}\/?/,
/^(((ht|f)tps?):\/\/)?([^!@#$%^&*?.\s-]([^!@#$%^&*?.\s]{0,63}[^!@#$%^&*?.\s])?\.)+[j-z]{2,6}\/?/,
message: 'Webhook需要是一个合法的URL',
},
],

View File

@ -1,9 +1,8 @@
<template>
<a-modal v-model:visible="_vis" title="通知记录" :footer="null" width="70%">
<j-search
<j-modal v-model:visible="_vis" title="通知记录" :footer="null" width="70%">
<j-advanced-search
type="simple"
:columns="columns"
target="product"
@search="handleSearch"
/>
@ -22,18 +21,18 @@
{{ moment(slotProps.notifyTime).format('YYYY-MM-DD HH:mm:ss') }}
</template>
<template #state="slotProps">
<a-space>
<a-badge
<j-space>
<j-badge
:status="slotProps.state.value"
:text="slotProps.state.text"
></a-badge>
></j-badge>
<AIcon
v-if="slotProps.state.value === 'error'"
type="ExclamationCircleOutlined"
style="color: #1d39c4; cursor: pointer"
@click="handleError(slotProps.errorStack)"
/>
</a-space>
</j-space>
</template>
<template #action="slotProps">
<AIcon
@ -43,7 +42,7 @@
/>
</template>
</JProTable>
</a-modal>
</j-modal>
</template>
<script setup lang="ts">

View File

@ -1,15 +1,15 @@
<template>
<div>
<a-modal
<j-modal
v-model:visible="_vis"
title="同步用户"
:footer="null"
@cancel="_vis = false"
width="80%"
>
<a-row :gutter="10" class="model-body">
<a-col :span="4">
<a-input
<j-row :gutter="10" class="model-body">
<j-col :span="4">
<j-input
v-model:value="deptName"
@keyup.enter="getDepartment"
allowClear
@ -23,17 +23,17 @@
@click="getDepartment"
/>
</template>
</a-input>
<a-tree
</j-input>
<j-tree
:tree-data="deptTreeData"
:fieldNames="{ title: 'name', key: 'id' }"
:selectedKeys="[deptId]"
@select="onTreeSelect"
>
</a-tree>
<a-empty v-if="!deptTreeData.length" />
</a-col>
<a-col :span="20">
</j-tree>
<j-empty v-if="!deptTreeData.length" />
</j-col>
<j-col :span="20">
<JProTable
ref="tableRef"
:columns="columns"
@ -43,38 +43,38 @@
noPagination
>
<template #headerTitle>
<a-button type="primary" @click="handleAutoBind">
<j-button type="primary" @click="handleAutoBind">
自动绑定
</a-button>
</j-button>
</template>
<template #status="slotProps">
<a-space>
<a-badge
<j-space>
<j-badge
:status="slotProps.status.value"
:text="slotProps.status.text"
></a-badge>
</a-space>
></j-badge>
</j-space>
</template>
<template #action="slotProps">
<a-space :size="16">
<a-tooltip
<j-space :size="16">
<j-tooltip
v-for="i in getActions(slotProps, 'table')"
:key="i.key"
v-bind="i.tooltip"
>
<a-popconfirm
<j-popconfirm
v-if="i.popConfirm"
v-bind="i.popConfirm"
:disabled="i.disabled"
>
<a-button
<j-button
:disabled="i.disabled"
style="padding: 0"
type="link"
><AIcon :type="i.icon"
/></a-button>
</a-popconfirm>
<a-button
/></j-button>
</j-popconfirm>
<j-button
style="padding: 0"
type="link"
v-else
@ -82,23 +82,23 @@
i.onClick && i.onClick(slotProps)
"
>
<a-button
<j-button
:disabled="i.disabled"
style="padding: 0"
type="link"
><AIcon :type="i.icon"
/></a-button>
</a-button>
</a-tooltip>
</a-space>
/></j-button>
</j-button>
</j-tooltip>
</j-space>
</template>
</JProTable>
</a-col>
</a-row>
</a-modal>
</j-col>
</j-row>
</j-modal>
<!-- 绑定用户 -->
<a-modal
<j-modal
v-model:visible="bindVis"
title="绑定用户"
:maskClosable="false"
@ -106,9 +106,9 @@
@cancel="handleCancel"
@ok="handleBindSubmit"
>
<a-form layout="vertical">
<a-form-item label="用户" v-bind="validateInfos.userId">
<a-select
<j-form layout="vertical">
<j-form-item label="用户" v-bind="validateInfos.userId">
<j-select
v-model:value="formData.userId"
:options="allUserList"
allowClear
@ -117,9 +117,9 @@
:filter-option="filterOption"
placeholder="请选择用户"
/>
</a-form-item>
</a-form>
</a-modal>
</j-form-item>
</j-form>
</j-modal>
</div>
</template>

View File

@ -16,7 +16,7 @@
:gridColumn="3"
>
<template #headerTitle>
<a-space>
<j-space>
<PermissionButton
type="primary"
@click="handleAdd"
@ -34,7 +34,7 @@
导入
</PermissionButton>
</a-upload>
<a-popconfirm
<j-popconfirm
title="确认导出?"
ok-text="确定"
cancel-text="取消"
@ -43,8 +43,8 @@
<PermissionButton hasPermission="notice/Config:export">
导出
</PermissionButton>
</a-popconfirm>
</a-space>
</j-popconfirm>
</j-space>
</template>
<template #card="slotProps">
<CardBox
@ -67,39 +67,39 @@
<h3 class="card-item-content-title">
{{ slotProps.name }}
</h3>
<a-row>
<a-col :span="12">
<j-row>
<j-col :span="12">
<div class="card-item-content-text">
通知方式
</div>
<div>
{{ getMethodTxt(slotProps.type) }}
</div>
</a-col>
<a-col :span="12">
</j-col>
<j-col :span="12">
<div class="card-item-content-text">说明</div>
<Ellipsis>
{{ slotProps.description }}
</Ellipsis>
</a-col>
</a-row>
</j-col>
</j-row>
</template>
<template #actions="item">
<a-tooltip
<j-tooltip
v-bind="item.tooltip"
:title="item.disabled && item.tooltip.title"
>
<a-dropdown
<j-dropdown
placement="bottomRight"
v-if="item.key === 'others'"
>
<a-button>
<j-button>
<AIcon :type="item.icon" />
<span>{{ item.text }}</span>
</a-button>
</j-button>
<template #overlay>
<a-menu>
<a-menu-item
<j-menu>
<j-menu-item
v-for="(o, i) in item.children"
:key="i"
>
@ -113,10 +113,10 @@
</template>
<span>{{ o.text }}</span>
</PermissionButton>
</a-menu-item>
</a-menu>
</j-menu-item>
</j-menu>
</template>
</a-dropdown>
</j-dropdown>
<j-popconfirm
v-else-if="item.key === 'delete'"
v-bind="item.popConfirm"
@ -143,12 +143,12 @@
<span>{{ item.text }}</span>
</PermissionButton>
</template>
</a-tooltip>
</j-tooltip>
</template>
</CardBox>
</template>
<template #action="slotProps">
<a-space :size="16">
<j-space :size="16">
<template
v-for="i in getActions(slotProps, 'table')"
:key="i.key"
@ -167,7 +167,7 @@
<template #icon><AIcon :type="i.icon" /></template>
</PermissionButton>
</template>
</a-space>
</j-space>
</template>
</JProTable>

View File

@ -1,5 +1,5 @@
<template>
<a-modal
<j-modal
v-model:visible="_vis"
title="调试"
cancelText="取消"
@ -8,33 +8,33 @@
@cancel="handleCancel"
:confirmLoading="btnLoading"
>
<a-form ref="formRef" layout="vertical" :model="formData">
<a-form-item
<j-form ref="formRef" layout="vertical" :model="formData">
<j-form-item
label="通知配置"
name="configId"
:rules="{ required: true, message: '该字段为必填字段' }"
>
<a-select
<j-select
v-model:value="formData.configId"
placeholder="请选择通知配置"
>
<a-select-option
<j-select-option
v-for="(item, index) in configList"
:key="index"
:value="item.id"
>
{{ item.name }}
</a-select-option>
</a-select>
</a-form-item>
<a-form-item
</j-select-option>
</j-select>
</j-form-item>
<j-form-item
label="变量"
v-if="
formData.templateDetailTable &&
formData.templateDetailTable.length
"
>
<a-table
<j-table
row-key="id"
:columns="columns"
:data-source="formData.templateDetailTable"
@ -48,7 +48,7 @@
<span>{{ record[column.dataIndex] }}</span>
</template>
<template v-else>
<a-form-item
<j-form-item
:name="['templateDetailTable', index, 'value']"
:rules="{
required: record.required,
@ -78,13 +78,13 @@
v-model:modelValue="record.value"
:itemType="record.type"
/>
</a-form-item>
</j-form-item>
</template>
</template>
</a-table>
</a-form-item>
</a-form>
</a-modal>
</j-table>
</j-form-item>
</j-form>
</j-modal>
</template>
<script setup lang="ts">

View File

@ -6,7 +6,7 @@
v-for="(item, index) in fileList"
:key="index"
>
<a-input v-model:value="item.name">
<j-input v-model:value="item.name">
<template #addonAfter>
<a-upload
name="file"
@ -20,14 +20,14 @@
<upload-outlined />
</a-upload>
</template>
</a-input>
</j-input>
<delete-outlined
@click="handleDelete(item.id)"
style="cursor: pointer"
/>
</div>
<a-button
<j-button
type="dashed"
@click="handleAdd"
style="width: 100%; margin-top: 5px"
@ -36,7 +36,7 @@
<plus-outlined />
</template>
添加
</a-button>
</j-button>
</div>
</template>

View File

@ -1,5 +1,5 @@
<template>
<a-select
<j-select
:options="options"
@change="change"
placeholder="请选择收信部门"

View File

@ -1,5 +1,5 @@
<template>
<a-select
<j-select
:options="options"
@change="change"
placeholder="请选择标签推送,多个标签用,号分隔"

View File

@ -1,5 +1,5 @@
<template>
<a-select
<j-select
:options="options"
@change="change"
placeholder="请选择收信人"

View File

@ -1,7 +1,7 @@
<!-- 模板内容-变量列表 -->
<template>
<div class="table-wrapper">
<a-table
<j-table
:columns="columns"
:data-source="dataSource"
bordered
@ -11,52 +11,52 @@
<span v-if="column.dataIndex === 'id'">
{{ record[column.dataIndex] }}
</span>
<a-input
<j-input
v-if="column.dataIndex === 'name'"
v-model:value="record.name"
/>
<a-select
<j-select
v-if="column.dataIndex === 'type'"
v-model:value="record.type"
@change="handleTypeChange(record)"
>
<a-select-option value="string">字符串</a-select-option>
<a-select-option value="date">时间</a-select-option>
<a-select-option value="double">数字</a-select-option>
</a-select>
<j-select-option value="string">字符串</j-select-option>
<j-select-option value="date">时间</j-select-option>
<j-select-option value="double">数字</j-select-option>
</j-select>
<template v-if="column.dataIndex === 'format'">
<span v-if="record.type === 'string'">
{{ record.format }}
</span>
<a-select
<j-select
v-if="record.type === 'date'"
v-model:value="record.format"
>
<a-select-option value="timestamp">
<j-select-option value="timestamp">
timestamp
</a-select-option>
<a-select-option value="yyyy-MM-dd">
</j-select-option>
<j-select-option value="yyyy-MM-dd">
yyyy-MM-dd
</a-select-option>
<a-select-option value="yyyy-MM-dd HH:mm:ss">
</j-select-option>
<j-select-option value="yyyy-MM-dd HH:mm:ss">
yyyy-MM-dd HH:mm:ss
</a-select-option>
</a-select>
<a-input
</j-select-option>
</j-select>
<j-input
v-if="record.type === 'double'"
v-model:value="record.format"
>
<template #suffix>
<a-tooltip
<j-tooltip
title="格式为:%.xf x代表数字保留的小数位数。当x=0时,代表格式为整数"
>
<AIcon type="QuestionCircleOutlined" />
</a-tooltip>
</j-tooltip>
</template>
</a-input>
</j-input>
</template>
</template>
</a-table>
</j-table>
</div>
</template>

View File

@ -1,36 +1,36 @@
<!-- 通知模板详情 -->
<template>
<page-container>
<a-card>
<a-row>
<a-col :span="10">
<a-form layout="vertical">
<a-form-item
<j-card>
<j-row>
<j-col :span="10">
<j-form layout="vertical">
<j-form-item
label="通知方式"
v-bind="validateInfos.type"
>
<a-select
<j-select
v-model:value="formData.type"
placeholder="请选择通知方式"
:disabled="!!formData.id"
@change="handleTypeChange"
>
<a-select-option
<j-select-option
v-for="(item, index) in NOTICE_METHOD"
:key="index"
:value="item.value"
>
{{ item.label }}
</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="名称" v-bind="validateInfos.name">
<a-input
</j-select-option>
</j-select>
</j-form-item>
<j-form-item label="名称" v-bind="validateInfos.name">
<j-input
v-model:value="formData.name"
placeholder="请输入名称"
/>
</a-form-item>
<a-form-item
</j-form-item>
<j-form-item
label="类型"
v-bind="validateInfos.provider"
v-if="
@ -43,67 +43,67 @@
v-model="formData.provider"
@change="handleProviderChange"
/>
</a-form-item>
<a-form-item
</j-form-item>
<j-form-item
v-bind="validateInfos.configId"
v-if="formData.type !== 'email'"
>
<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
v-model:value="formData.configId"
placeholder="请选择绑定配置"
@change="handleConfigChange"
>
<a-select-option
<j-select-option
v-for="(item, index) in configList"
:key="index"
:value="item.id"
>
{{ item.name }}
</a-select-option>
</a-select>
</a-form-item>
</j-select-option>
</j-select>
</j-form-item>
<!-- 钉钉 -->
<template v-if="formData.type === 'dingTalk'">
<template
v-if="formData.provider === 'dingTalkMessage'"
>
<a-form-item
<j-form-item
v-bind="validateInfos['template.agentId']"
>
<template #label>
<span>
AgentID
<a-tooltip title="应用唯一标识">
<j-tooltip title="应用唯一标识">
<AIcon
type="QuestionCircleOutlined"
style="margin-left: 2px"
/>
</a-tooltip>
</j-tooltip>
</span>
</template>
<a-input
<j-input
v-model:value="
formData.template.agentId
"
placeholder="请输入AppSecret"
/>
</a-form-item>
<a-row :gutter="10">
<a-col :span="12">
<a-form-item label="收信部门">
</j-form-item>
<j-row :gutter="10">
<j-col :span="12">
<j-form-item label="收信部门">
<ToOrg
v-model:toParty="
formData.template
@ -112,14 +112,14 @@
:type="formData.type"
:config-id="formData.configId"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item>
</j-form-item>
</j-col>
<j-col :span="12">
<j-form-item>
<template #label>
<span>
收信人
<a-tooltip
<j-tooltip
title="如果不填写该字段,将在使用此模板发送通知时进行指定"
>
<AIcon
@ -128,7 +128,7 @@
margin-left: 2px;
"
/>
</a-tooltip>
</j-tooltip>
</span>
</template>
<ToUser
@ -138,29 +138,29 @@
:type="formData.type"
:config-id="formData.configId"
/>
</a-form-item>
</a-col>
</a-row>
</j-form-item>
</j-col>
</j-row>
</template>
<template
v-if="
formData.provider === 'dingTalkRobotWebHook'
"
>
<a-form-item
<j-form-item
label="消息类型"
v-bind="
validateInfos['template.messageType']
"
>
<a-select
<j-select
v-model:value="
formData.template.messageType
"
placeholder="请选择消息类型"
@change="handleMessageTypeChange"
>
<a-select-option
<j-select-option
v-for="(
item, index
) in ROBOT_MSG_TYPE"
@ -168,16 +168,16 @@
:value="item.value"
>
{{ item.label }}
</a-select-option>
</a-select>
</a-form-item>
</j-select-option>
</j-select>
</j-form-item>
<template
v-if="
formData.template.messageType ===
'markdown'
"
>
<a-form-item
<j-form-item
label="标题"
v-bind="
validateInfos[
@ -185,34 +185,34 @@
]
"
>
<a-input
<j-input
v-model:value="
formData.template.markdown.title
"
placeholder="请输入标题"
/>
</a-form-item>
</j-form-item>
</template>
<template
v-if="
formData.template.messageType === 'link'
"
>
<a-form-item
<j-form-item
label="标题"
v-bind="
validateInfos['template.link.title']
"
>
<a-input
<j-input
v-model:value="
formData.template.link.title
"
placeholder="请输入标题"
/>
</a-form-item>
<a-form-item label="图片链接">
<a-input
</j-form-item>
<j-form-item label="图片链接">
<j-input
v-model:value="
formData.template.link.picUrl
"
@ -239,55 +239,55 @@
/>
</a-upload>
</template>
</a-input>
</a-form-item>
<a-form-item label="内容链接">
<a-input
</j-input>
</j-form-item>
<j-form-item label="内容链接">
<j-input
v-model:value="
formData.template.link
.messageUrl
"
placeholder="请输入内容链接"
/>
</a-form-item>
</j-form-item>
</template>
</template>
</template>
<!-- 微信 -->
<template v-if="formData.type === 'weixin'">
<a-form-item
<j-form-item
v-bind="validateInfos['template.agentId']"
>
<template #label>
<span>
AgentId
<a-tooltip title="应用唯一标识">
<j-tooltip title="应用唯一标识">
<AIcon
type="QuestionCircleOutlined"
style="margin-left: 2px"
/>
</a-tooltip>
</j-tooltip>
</span>
</template>
<a-input
<j-input
v-model:value="formData.template.agentId"
placeholder="请输入agentId"
/>
</a-form-item>
<a-row :gutter="10">
<a-col :span="12">
<a-form-item>
</j-form-item>
<j-row :gutter="10">
<j-col :span="12">
<j-form-item>
<template #label>
<span>
收信人
<a-tooltip
<j-tooltip
title="如果不填写该字段,将在使用此模版发送通知时进行指定。"
>
<AIcon
type="QuestionCircleOutlined"
style="margin-left: 2px"
/>
</a-tooltip>
</j-tooltip>
</span>
</template>
<ToUser
@ -297,10 +297,10 @@
:type="formData.type"
:config-id="formData.configId"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="收信部门">
</j-form-item>
</j-col>
<j-col :span="12">
<j-form-item label="收信部门">
<ToOrg
v-model:toParty="
formData.template.toParty
@ -308,21 +308,21 @@
:type="formData.type"
:config-id="formData.configId"
/>
</a-form-item>
</a-col>
</a-row>
<a-form-item>
</j-form-item>
</j-col>
</j-row>
<j-form-item>
<template #label>
<span>
标签推送
<a-tooltip
<j-tooltip
title="本企业微信的标签ID列表,最多支持100个,如果不填写该字段,将在使用此模版发送通知时进行指定"
>
<AIcon
type="QuestionCircleOutlined"
style="margin-left: 2px"
/>
</a-tooltip>
</j-tooltip>
</span>
</template>
<ToTag
@ -330,62 +330,62 @@
:type="formData.type"
:config-id="formData.configId"
/>
</a-form-item>
</j-form-item>
</template>
<!-- 邮件 -->
<template v-if="formData.type === 'email'">
<a-form-item
<j-form-item
v-bind="validateInfos['template.subject']"
>
<template #label>
<span>
标题
<a-tooltip title="邮件标题">
<j-tooltip title="邮件标题">
<AIcon
type="QuestionCircleOutlined"
style="margin-left: 2px"
/>
</a-tooltip>
</j-tooltip>
</span>
</template>
<a-input
<j-input
v-model:value="formData.template.subject"
placeholder="请输入标题"
/>
</a-form-item>
<a-form-item>
</j-form-item>
<j-form-item>
<template #label>
<span>
收件人
<a-tooltip
<j-tooltip
title="多个收件人用换行分隔 最大支持1000个号码"
>
<AIcon
type="QuestionCircleOutlined"
style="margin-left: 2px"
/>
</a-tooltip>
</j-tooltip>
</span>
</template>
<a-select
<j-select
mode="tags"
:options="[]"
v-model:value="formData.template.sendTo"
placeholder="请选择收件人"
/>
</a-form-item>
<a-form-item>
</j-form-item>
<j-form-item>
<template #label>
<span>
附件信息
<a-tooltip
<j-tooltip
title="附件只输入文件名称将在发送邮件时进行文件上传"
>
<AIcon
type="QuestionCircleOutlined"
style="margin-left: 2px"
/>
</a-tooltip>
</j-tooltip>
</span>
</template>
<Attachments
@ -393,44 +393,44 @@
formData.template.attachments
"
/>
</a-form-item>
</j-form-item>
</template>
<!-- 语音 -->
<template v-if="formData.type === 'voice'">
<a-form-item
<j-form-item
v-bind="validateInfos['template.templateType']"
>
<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
v-model:value="
formData.template.templateType
"
placeholder="请选择类型"
>
<a-select-option
<j-select-option
v-for="(item, index) in VOICE_TYPE"
:key="index"
:value="item.value"
>
{{ item.label }}
</a-select-option>
</a-select>
</a-form-item>
<a-row :gutter="10">
<a-col :span="12">
<a-form-item
</j-select-option>
</j-select>
</j-form-item>
<j-row :gutter="10">
<j-col :span="12">
<j-form-item
v-bind="
validateInfos[
'template.templateCode'
@ -440,49 +440,49 @@
<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
v-model:value="
formData.template.templateCode
"
placeholder="请输入模板ID"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item>
</j-form-item>
</j-col>
<j-col :span="12">
<j-form-item>
<template #label>
<span>
被叫号码
<a-tooltip
<j-tooltip
title="仅支持中国大陆号码"
>
<AIcon
type="QuestionCircleOutlined"
style="margin-left: 2px"
/>
</a-tooltip>
</j-tooltip>
</span>
</template>
<a-input
<j-input
v-model:value="
formData.template.calledNumber
"
placeholder="请输入被叫号码"
/>
</a-form-item>
</a-col>
</a-row>
<a-form-item
</j-form-item>
</j-col>
</j-row>
<j-form-item
v-bind="
validateInfos['template.calledShowNumbers']
"
@ -490,92 +490,92 @@
<template #label>
<span>
被叫显号
<a-tooltip
<j-tooltip
title="必须是已购买的号码,用于呼叫号码显示"
>
<AIcon
type="QuestionCircleOutlined"
style="margin-left: 2px"
/>
</a-tooltip>
</j-tooltip>
</span>
</template>
<a-input
<j-input
v-model:value="
formData.template.calledShowNumbers
"
placeholder="请输入被叫显号"
/>
</a-form-item>
<a-form-item>
</j-form-item>
<j-form-item>
<template #label>
<span>
播放次数
<a-tooltip title="语音文件的播放次数">
<j-tooltip title="语音文件的播放次数">
<AIcon
type="QuestionCircleOutlined"
style="margin-left: 2px"
/>
</a-tooltip>
</j-tooltip>
</span>
</template>
<a-input
<j-input
v-model:value="formData.template.playTimes"
placeholder="请输入播放次数"
/>
</a-form-item>
<a-form-item
</j-form-item>
<j-form-item
v-if="formData.template.templateType === 'tts'"
>
<template #label>
<span>
模版内容
<a-tooltip
<j-tooltip
title="语音验证码内容输入框,用于渲染验语音证码变量。"
>
<AIcon
type="QuestionCircleOutlined"
style="margin-left: 2px"
/>
</a-tooltip>
</j-tooltip>
</span>
</template>
<a-textarea
<j-textarea
v-model:value="formData.template.message"
show-count
:rows="5"
placeholder="内容中的变量将用于阿里云语音验证码"
/>
</a-form-item>
</j-form-item>
</template>
<!-- 短信 -->
<template v-if="formData.type === 'sms'">
<a-row :gutter="10">
<a-col :span="12">
<a-form-item
<j-row :gutter="10">
<j-col :span="12">
<j-form-item
v-bind="validateInfos['template.code']"
>
<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
v-model:value="
formData.template.code
"
placeholder="请选择模板"
@change="handleTemplateChange"
>
<a-select-option
<j-select-option
v-for="(
item, index
) in templateList"
@ -583,77 +583,77 @@
:value="item.templateCode"
>
{{ item.templateName }}
</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item>
</j-select-option>
</j-select>
</j-form-item>
</j-col>
<j-col :span="12">
<j-form-item>
<template #label>
<span>
收信人
<a-tooltip
<j-tooltip
title="仅支持中国大陆号码"
>
<AIcon
type="QuestionCircleOutlined"
style="margin-left: 2px"
/>
</a-tooltip>
</j-tooltip>
</span>
</template>
<a-input
<j-input
v-model:value="
formData.template.phoneNumber
"
placeholder="请输入收信人"
/>
</a-form-item>
</a-col>
</a-row>
<a-form-item
</j-form-item>
</j-col>
</j-row>
<j-form-item
v-bind="validateInfos['template.signName']"
>
<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
v-model:value="formData.template.signName"
placeholder="请选择签名"
>
<a-select-option
<j-select-option
v-for="(item, index) in signsList"
:key="index"
:value="item.signName"
>
{{ item.signName }}
</a-select-option>
</a-select>
</a-form-item>
</j-select-option>
</j-select>
</j-form-item>
</template>
<!-- webhook -->
<template v-if="formData.type === 'webhook'">
<a-form-item label="请求体">
<a-radio-group
<j-form-item label="请求体">
<j-radio-group
v-model:value="
formData.template.contextAsBody
"
style="margin-bottom: 20px"
>
<a-radio :value="true">默认</a-radio>
<a-radio :value="false">自定义</a-radio>
</a-radio-group>
<a-textarea
<j-radio :value="true">默认</j-radio>
<j-radio :value="false">自定义</j-radio>
</j-radio-group>
<j-textarea
v-model:value="formData.template.body"
placeholder="请求体中的数据来自于发送通知时指定的所有变量"
v-if="formData.template.contextAsBody"
@ -668,7 +668,7 @@
"
/>
</div>
</a-form-item>
</j-form-item>
</template>
<template
v-if="
@ -676,23 +676,23 @@
formData.type !== 'voice'
"
>
<a-form-item
<j-form-item
v-bind="validateInfos['template.message']"
>
<template #label>
<span>
模版内容
<a-tooltip
<j-tooltip
title="发送的内容,支持录入变量"
>
<AIcon
type="QuestionCircleOutlined"
style="margin-left: 2px"
/>
</a-tooltip>
</j-tooltip>
</span>
</template>
<a-textarea
<j-textarea
v-model:value="formData.template.message"
:maxlength="200"
:rows="5"
@ -700,9 +700,9 @@
placeholder="变量格式:${name};
示例:尊敬的${name},${time}有设备触发告警,请注意处理"
/>
</a-form-item>
</j-form-item>
</template>
<a-form-item
<j-form-item
label="变量列表"
v-if="
formData.variableDefinitions &&
@ -714,32 +714,32 @@
formData.variableDefinitions
"
/>
</a-form-item>
<a-form-item label="说明">
<a-textarea
</j-form-item>
<j-form-item label="说明">
<j-textarea
v-model:value="formData.description"
show-count
:maxlength="200"
:rows="5"
placeholder="请输入说明"
/>
</a-form-item>
<a-form-item>
<a-button
</j-form-item>
<j-form-item>
<j-button
type="primary"
@click="handleSubmit"
:loading="btnLoading"
>
保存
</a-button>
</a-form-item>
</a-form>
</a-col>
<a-col :span="12" :push="2">
</j-button>
</j-form-item>
</j-form>
</j-col>
<j-col :span="12" :push="2">
<Doc :docData="formData" />
</a-col>
</a-row>
</a-card>
</j-col>
</j-row>
</j-card>
</page-container>
</template>

View File

@ -1,9 +1,8 @@
<template>
<a-modal v-model:visible="_vis" title="通知记录" :footer="null" width="70%">
<j-search
<j-modal v-model:visible="_vis" title="通知记录" :footer="null" width="70%">
<j-advanced-search
type="simple"
:columns="columns"
target="product"
@search="handleSearch"
/>
@ -22,18 +21,18 @@
{{ moment(slotProps.notifyTime).format('YYYY-MM-DD HH:mm:ss') }}
</template>
<template #state="slotProps">
<a-space>
<a-badge
<j-space>
<j-badge
:status="slotProps.state.value"
:text="slotProps.state.text"
></a-badge>
></j-badge>
<AIcon
v-if="slotProps.state.value === 'error'"
type="ExclamationCircleOutlined"
style="color: #1d39c4; cursor: pointer"
@click="handleError(slotProps.errorStack)"
/>
</a-space>
</j-space>
</template>
<template #action="slotProps">
<AIcon
@ -43,7 +42,7 @@
/>
</template>
</JProTable>
</a-modal>
</j-modal>
</template>
<script setup lang="ts">

View File

@ -16,7 +16,7 @@
:gridColumn="3"
>
<template #headerTitle>
<a-space>
<j-space>
<PermissionButton
type="primary"
@click="handleAdd"
@ -36,7 +36,7 @@
导入
</PermissionButton>
</a-upload>
<a-popconfirm
<j-popconfirm
title="确认导出?"
ok-text="确定"
cancel-text="取消"
@ -47,8 +47,8 @@
>
导出
</PermissionButton>
</a-popconfirm>
</a-space>
</j-popconfirm>
</j-space>
</template>
<template #card="slotProps">
<CardBox
@ -71,22 +71,22 @@
<h3 class="card-item-content-title">
{{ slotProps.name }}
</h3>
<a-row>
<a-col :span="12">
<j-row>
<j-col :span="12">
<div class="card-item-content-text">
通知方式
</div>
<div>
{{ getMethodTxt(slotProps.type) }}
</div>
</a-col>
<a-col :span="12">
</j-col>
<j-col :span="12">
<div class="card-item-content-text">说明</div>
<Ellipsis>
{{ slotProps.description }}
</Ellipsis>
</a-col>
</a-row>
</j-col>
</j-row>
</template>
<template #actions="item">
<PermissionButton
@ -119,7 +119,7 @@
</span>
</template>
<template #action="slotProps">
<a-space :size="16">
<j-space :size="16">
<template
v-for="i in getActions(slotProps, 'table')"
:key="i.key"
@ -138,7 +138,7 @@
<template #icon><AIcon :type="i.icon" /></template>
</PermissionButton>
</template>
</a-space>
</j-space>
</template>
</JProTable>

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