merge: merge dev

* fix: 优化设备接入配置

* fix: 兼容2.1版本创建的菜单数据

* fix: 菜单配置兼容2.1菜单

* fix: bug#28524 【产品】产品选择设备接入网关,有多个配置时,只展示了一个

* fix: bug#28520【设备】当配置未开启时,前端应不展示配置按钮

* fix: bug#28533仪表盘本年流量消耗界面展示需优化

* fix: bug#28520、28534【设备】设备详情-物模型-其他配置 在点击配置时界面提示错误、【设备】当配置未开启时,前端应不展示配置按钮

* fix: bug#28531【设备】详情中查看预处理数据时提示错误

* fix: bug#28545【菜单管理】新增菜单,权限控制处无法正常加载出权限

* fix: 菜单配置->菜单权限同步功能

* fix: bug#28520【设备】当配置未开启时,前端应不展示配置按钮

* fix(media): bug#28595屏蔽固定地址通道列表的厂商字段

* fix(NoticeRule): 文字组织替换部门

* fix(Alarm): bug#28571手动触发展示说明

* fix(media): bug#28553界面展示异常

* fix(iotCard): bug#28567

* fix(media): bug#28595屏蔽固定地址通道列表的厂商字段

* fix: bug#28560【通知配置】微信-企业消息的同步用户页面,列表字段展示错误,【钉钉用户名】需改为【企业微信用户名】

* fix: bug#28544【通知配置】用户自己创建的数据,没有权限调试,报错:暂无权限,请联系管理员!

* fix: bug#28549【采集器】在点位进行批量操作时,开启推送控制,进行保存异常

* fix: bug#28540【组织管理】设备资产分配页,设备列表形式展示,资产权限显示不完整

* fix: bug#28537【产品】新增的网关设备,接入方式选择"边缘网关接入"时,物模型中功能定义界面展示异常

* fix: bug#28550【远程升级】升级任务中的任务详情列表中文案修改

* fix: bug#28551【通知模版】和【通知配置】两者保存的查询条件共用了

* fix: bug#28560【通知配置】微信-企业消息的同步用户页面,列表字段展示错误,【钉钉用户名】需改为【企业微信用户名】

* fix(media): bug#28602屏蔽onvif相关配置

* fix: 物联卡管理样式优化及运营商状态取值

* fix(AliCloud): bug#28581新增阿里云网桥产品下拉值清空

* fix(media): bug#28602屏蔽onvif相关配置

* fix: bug#28578、28597【产品】物模型-属性定义-其他配置 在只进行选择了数据类型时,界面展示异常 、【产品】在进行更换接入网关时,会增加“预处理数据”

* fix: bug#28578、28597【产品】物模型-属性定义-其他配置 在只进行选择了数据类型时,界面展示异常 、【产品】在进行更换接入网关时,会增加“预处理数据”

* fix: bug#28538 【角色管理】【97】进入页面提示权限不足

* fix: bug#28605 【产品】产品物模型点击其它配置时,有时候界面会不出现蒙层

* fix: 【告警配置】兼容2.1绑定与取消绑定场景联动

* fix(region): 地区新增屏蔽重复校验及同步下级区域处理

* fix(system): 数据字典新增选中异常修复

* fix: bug#28611.28613

* fix(media): 仪表盘快捷时间没有取消选中效果

* fix(region): 地区新增屏蔽重复校验及同步下级区域处理

* fix: bug#28616【设备】【日志管理页】列表字段过长时,鼠标悬停【内容】展示数据不友好,和别处不一致

* fix: bug#28560【通知配置】微信-企业消息的同步用户页面,列表字段展示错误,【钉钉用户名】需改为【企业微信用户名】

* fix: bug#28559【通知管理】微信同步用户页面,查询无匹配部门时,后端不需要报错

* fix: bug#28563【通知模版】新增/编辑数据页,【绑定配置】的数据后端校验有错误时,也能正常新增保存,后端校验失败时,应不能新增保存,需要前端把后端的报错处理到校验规则内。

* fix: bug#28568【产品】通过导入的属性,其他配置中的存在方式为正确的展示

* fix: bug#28616【设备】【日志管理页】列表字段过长时,鼠标悬停【内容】展示数据不友好,和别处不一致

* fix: 修改预处理数据页面接口

* fix: 配置枚举项必填按钮放在右方

* fix: 【物模型】修复功能定义名称校验异常

* fix: 【运维管理】仪表盘修复jvm中Echarts无法显示服务名称

* fix: 【物模型】修复功能定义名称校验异常

* fix: 【场景联动】校验接口社区版兼容处理

* fix: bug#28615 【产品】【物模型-属性定义-编辑规则页】在屏幕分辦率【1380* 920】的情况下,属性名称下拉框无法正常获取到值

* fix: bug#28559 【通知管理】微信同步用户页面,查询无匹配部门时,后端不需要报错

* fix: bug#28563【通知模版】新增/编辑数据页,【绑定配置】的数据后端校验有错误时,也能正常新增保存,后端校验失败时,应不能新增保存,需要前端把后端的报错处理到校验规则内。

* fix: bug#28604 【产品】产品指标保存后查看,未回显已填入内容(设备运行状态界面查看是有指标的,但是产品配置查看未回显)

* fix: bug#28634【产品】【物模型-事件定义-输出参数】当数据类型选择枚举值,不配置必填项【枚举项】,直接点击保存,可成功保存,理应提示必填

* fix: bug#28615 【产品】【物模型-属性定义-编辑规则页】在屏幕分辦率【1380* 920】的情况下,属性名称下拉框无法正常获取到值

* fix: 修改预处理数据查询处理结果接口

* fix: bug#28633新增字典后选中新增项

* fix: bug#28628编辑区域报错

* fix: bug#28526 【场景联动】场景联动文案描述错误,并且关闭执行动作后,应该同步隐藏防抖

* fix: 优化过滤条件开关逻辑条件

* fix: bug#【产品】产品(设备、告警记录),告警源不展示设备ID,展示设备名称

* fix: bug#28652【通知配置】通知配置钉钉(微信)进行用户绑定时,展示需优化

* fix: bug#28575 流媒体服务>>>服务商新增可选项内置流媒体

* fix: bug#28618.28619日历维护相关优化

* fix: bug#28648地区管理新增区域后下级消失

* fix: bug#28575 流媒体服务>>>服务商新增可选项内置流媒体

* fix: bug#28672产品物模型>>>功能定义>>>输入参数,选择类型为Object时,无法选择参数(事件也存在)

* fix: bug#28654、28664、28663产品(设备)无效数据修改“告警源”>>>“数据源”,并且设备ID,修改为设备名称、通知模板>>>企业微信和钉钉消息调试时,名称应和模板内变量名称一致

* fix: bug#28663、28659通知模板>>>企业微信进行调试时,需判断收信人、收信人部门,收信人标签三个内容任意填写一个、钉钉消息调试时,需判断收信人或收信人部门任意填写其中一个

* fix: bug#28659【场景联动】场景联动>>>执行动作>>>>消息通知.>>>模板变量需判断收信人或者收信部门任意填写一个

* fix: bug#28661企业微信进行调试时,需判断收信人、收信人部门,收信人标签三个内容任意填写一个

* fix: bug#28672产品物模型>>>功能定义>>>输入参数,选择类型为Object时,无法选择参数(事件也存在)

* fix: 修复钉钉调试选择了收信人过不了校验问题

* fix: 阈值处理方式取值修改

* fix: 场景联动执行动作微信显示部门标签

* fix: 修改场景联通通知图标

* fix: bug#28575内嵌流媒体样式优化

* fix: bug28679.28678

* fix: 修改流媒体详情字段

* fix: 场景联动执行动作微信显示部门标签

* fix: 修改场景联通通知图标

* fix: 无效数据数据源跳转功能

* fix: 流媒体详情修改文字

* fix: 修改流媒体详情字段

* fix: bug#28687 【场景联动】场景联动>>>执行动作>>>消息通知>>>清除了收信信息后(有其余可校验内容)但是无法通过校验(企微、钉钉都存在)

* fix: bug#28688场景联动>>>执行动作>>>消息发送,当钉钉(企业)选择收信人后,切换收信人类型,然后点击保存,未清空切换前的内容

* fix: api配置调试请求体判断条件修改
This commit is contained in:
XieYongHong 2024-08-19 14:18:45 +08:00 committed by GitHub
parent 2b3e3280ac
commit 827b824451
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
61 changed files with 1346 additions and 697 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 457 B

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

@ -31,7 +31,7 @@ export const saveMetadata = (id: string, data: DeviceMetadata) => server.put(`/d
* @param id ID
* @returns
*/
export const detail = (id: string) => server.get<DeviceInstance>(`/device-instance/${id}/detail`)
export const detail = (id: string, _hideError?: any) => server.get<DeviceInstance>(`/device-instance/${id}/detail`, {}, {} , _hideError)
/**
*
@ -682,4 +682,4 @@ export const deleteDeviceThreshold = (productId:string,deviceId:string,propertyI
export const getTemplate = (id: string, format: string) => `${BASE_API_PATH}/device/instance/${id}/property-metadata/template.${format}`
export const uploadAnalyzeMetadata = (data: any) => server.post('/device/instance/property-metadata/file/analyze', data)
export const uploadAnalyzeMetadata = (productId:string,data: any) => server.post(`/device/instance/${productId}/property-metadata/file/analyze`, data)

View File

@ -37,6 +37,12 @@ export const queryByDevice = (data:any) => server.post(`/alarm/record/device/_qu
*/
export const handleLog = (data:any) => server.post('/alarm/record/_handle',data);
/**
*
*
*/
export const handlePreconditioning = (data:any) => server.post(`/alarm/record/device/_handle`,data)
/**
*
*/
@ -58,11 +64,21 @@ export const queryHistoryList = (data:any) => server.post('/alarm/history/_query
*/
export const queryHandleHistory = (data:any) => server.post('/alarm/record/handle-history/_query',data);
/**
*
*/
export const queryPreHandleHistory = (recordId:any,data:any) => server.post(`/alarm/record/handle-history/device/${recordId}/_query`,data)
/**
*
*/
export const queryLogList = (alarmConfigId:any,data:any) => server.post(`/alarm/history/${alarmConfigId}/_query`,data)
/**
*
*/
export const queryPreconditioningLogList = (alarmConfigId:any,data:any) => server.post(`/alarm/history/device/${alarmConfigId}/_query`,data)
/**
*
*/

View File

@ -46,7 +46,7 @@
showSearch
:options="options"
v-model:value="record.id"
:getPopupContainer="(node) => tableWrapperRef || node"
:getPopupContainer="getPopupContainer"
size="small"
style="width: 100%;"
:virtual="true"
@ -105,7 +105,7 @@
size="small"
style="width: 100%;"
:virtual="true"
:getPopupContainer="(node) => tableWrapperRef || node"
:getPopupContainer="getPopupContainer"
:dropdownStyle="{
zIndex: 1072
}"
@ -202,6 +202,7 @@ import { getWebSocket } from '@/utils/websocket';
import {useTableWrapper} from "@/components/Metadata/Table/context";
import { onlyMessage } from '@/utils/comm';
import {message} from "ant-design-vue";
import { useTableFullScreen} from "@/components/Metadata/Table/context";
const props = defineProps({
virtualRule: Object as PropType<Record<any, any>>,
@ -222,7 +223,7 @@ type propertyType = {
const property = ref<propertyType[]>([]);
const tag = ref<Array<any>>([]);
const tableWrapperRef = useTableWrapper()
const fullScreen = useTableFullScreen()
const columns = [
{
title: '属性名称',
@ -287,6 +288,14 @@ const ruleEditorStore = useRuleEditorStore();
const time = ref<number>(0);
const timer = ref<any>(null);
//
const getPopupContainer = (node: any) => {
if (fullScreen.value) {
return tableWrapperRef.value || node
}
return document.body
}
const runScript = () => {
const propertiesList = medataSource?.value || []
const _properties = property.value.map((item: any) => {

View File

@ -105,7 +105,10 @@ const validateRules = () => {
}
const promise = context.validateItem({ [filedName.value]: get(context.dataSource.value, props.name) }, index)
promise.catch(res => {
promise.then(() => {
hideErrorTip()
context.removeFieldError(eventKey.value)
}).catch(res => {
const error = res?.filter(item => item.field === filedName.value) || []
if (error.length === 0) {
hideErrorTip()

View File

@ -1,8 +1,8 @@
<template>
<a-form-item :name="name" :rules="rules" :validate-first="true">
<template #label>
<span style="color: #ff4d4f; padding-right: 4px; padding-top: 2px">*</span>
枚举项
<span style="color: #ff4d4f; padding-right: 4px; padding-top: 2px">*</span>
</template>
<Content ref="tableRef" v-model:value="dataSource" @change="change" />
</a-form-item>

View File

@ -1,232 +1,347 @@
<template>
<PopoverModal
v-model:visible="visible"
:placement="placement"
@ok="onOk"
@cancel="onCancel"
>
<template #content>
<div style="width: 750px">
<EditTable
ref="tableRef"
:columns="myColumns"
:dataSource="dataSource"
:pagination="false"
:height="200"
>
<!-- <template v-for="(_, key) in slots" :key="key" #[key]="slotData">-->
<!-- <slot :name="key" v-bind="slotData"/>-->
<!-- </template>-->
<template #id="{ record, index }">
<EditTableFormItem :name="[index, 'id']">
<a-input v-model:value="record.id" placeholder="请输入标识"/>
</EditTableFormItem>
</template>
<template #name="{ record, index }">
<EditTableFormItem :name="[index, 'name']">
<a-input v-model:value="record.name" placeholder="请输入名称"/>
</EditTableFormItem>
</template>
<template #expands="{ record }">
<BooleanSelect v-model:value="record.expands.required"/>
</template>
<template #valueType="{ record, index }">
<EditTableFormItem :name="[index, 'valueType']">
<div style="display: flex; gap: 12px; align-items: center">
<TypeSelect v-model:value="record.valueType.type" style="flex: 1 1 0;min-width: 0" />
<DoubleParams v-if="['float', 'double'].includes(record.valueType.type)" v-model:value="record.valueType" placement="topRight"/>
<StringParams v-else-if="record.valueType.type === 'string'" v-model:value="record.valueType" placement="topRight"/>
<DateParams v-else-if="record.valueType.type === 'date'" v-model:value="record.valueType.format" placement="topRight"/>
<FileParams v-else-if="record.valueType.type === 'file'" v-model:value="record.valueType.bodyType" placement="topRight"/>
<EnumParams v-else-if="record.valueType.type === 'enum'" v-model:value="record.valueType.elements" placement="topRight"/>
<BooleanParams
v-else-if="record.valueType.type === 'boolean'"
v-model:falseText="record.valueType.falseText"
v-model:falseValue="record.valueType.falseValue"
v-model:trueText="record.valueType.trueText"
v-model:trueValue="record.valueType.trueValue"
placement="topRight"
/>
<ArrayParams v-else-if="record.valueType.type === 'array'" v-model:value="record.valueType.elementType" placement="topRight"/>
</div>
</EditTableFormItem>
</template>
<template #action="{ index }">
<a-button danger type="link" style="padding: 0 5px" @click="() => deleteItem(index)">
<template #icon>
<AIcon type="DeleteOutlined" />
</template>
</a-button>
</template>
</EditTable>
<a-button style="width: 100%;margin-top: 4px" @click="addItem">
<template #icon><AIcon type="PlusOutlined" /></template>
新增
</a-button>
</div>
</template>
<slot>
<a-button type="link" :disabled="disabled" style="padding: 0">
<template #icon>
<AIcon type="EditOutlined" :class="{'table-form-required-aicon': !value.length}"/>
<PopoverModal
v-model:visible="visible"
:placement="placement"
@ok="onOk"
@cancel="onCancel"
>
<template #content>
<div style="width: 750px">
<EditTable
ref="tableRef"
:columns="myColumns"
:dataSource="dataSource"
:pagination="false"
:height="200"
>
<!-- <template v-for="(_, key) in slots" :key="key" #[key]="slotData">-->
<!-- <slot :name="key" v-bind="slotData"/>-->
<!-- </template>-->
<template #id="{ record, index }">
<EditTableFormItem :name="[index, 'id']">
<a-input
v-model:value="record.id"
placeholder="请输入标识"
/>
</EditTableFormItem>
</template>
<template #name="{ record, index }">
<EditTableFormItem :name="[index, 'name']">
<a-input
v-model:value="record.name"
placeholder="请输入名称"
/>
</EditTableFormItem>
</template>
<template #expands="{ record }">
<BooleanSelect
v-model:value="record.expands.required"
/>
</template>
<template #valueType="{ record, index }">
<EditTableFormItem :name="[index, 'valueType']">
<div
style="
display: flex;
gap: 12px;
align-items: center;
"
>
<TypeSelect
v-model:value="record.valueType.type"
style="flex: 1 1 0; min-width: 0"
/>
<DoubleParams
v-if="
['float', 'double'].includes(
record.valueType.type,
)
"
v-model:value="record.valueType"
placement="topRight"
/>
<StringParams
v-else-if="
record.valueType.type === 'string'
"
v-model:value="record.valueType"
placement="topRight"
/>
<DateParams
v-else-if="record.valueType.type === 'date'"
v-model:value="record.valueType.format"
placement="topRight"
/>
<FileParams
v-else-if="record.valueType.type === 'file'"
v-model:value="record.valueType.bodyType"
placement="topRight"
/>
<EnumParams
v-else-if="record.valueType.type === 'enum'"
v-model:value="record.valueType.elements"
placement="topRight"
/>
<BooleanParams
v-else-if="
record.valueType.type === 'boolean'
"
v-model:falseText="
record.valueType.falseText
"
v-model:falseValue="
record.valueType.falseValue
"
v-model:trueText="record.valueType.trueText"
v-model:trueValue="
record.valueType.trueValue
"
placement="topRight"
/>
<ArrayParams
v-else-if="
record.valueType.type === 'array'
"
v-model:value="record.valueType.elementType"
placement="topRight"
/>
</div>
</EditTableFormItem>
</template>
<template #action="{ index }">
<a-button
danger
type="link"
style="padding: 0 5px"
@click="() => deleteItem(index)"
>
<template #icon>
<AIcon type="DeleteOutlined" />
</template>
</a-button>
</template>
</EditTable>
<a-button style="width: 100%; margin-top: 4px" @click="addItem">
<template #icon><AIcon type="PlusOutlined" /></template>
新增
</a-button>
</div>
</template>
</a-button>
</slot>
</PopoverModal>
<slot>
<a-button type="link" :disabled="disabled" style="padding: 0">
<template #icon>
<AIcon
type="EditOutlined"
:class="{ 'table-form-required-aicon': !value.length }"
/>
</template>
</a-button>
</slot>
</PopoverModal>
</template>
<script setup name="MetadataObject">
import { PopoverModal } from '../index'
import BooleanSelect from "../BooleanSelect/index.vue";
import { EditTable, TypeSelect, EditTableFormItem, StringParams, DateParams, FileParams, EnumParams, BooleanParams, ObjectParams, ArrayParams, DoubleParams } from '@/components/Metadata/Table'
import {Form} from "ant-design-vue";
import { PopoverModal } from '../index';
import BooleanSelect from '../BooleanSelect/index.vue';
import {
EditTable,
TypeSelect,
EditTableFormItem,
StringParams,
DateParams,
FileParams,
EnumParams,
BooleanParams,
ObjectParams,
ArrayParams,
DoubleParams,
} from '@/components/Metadata/Table';
import { Form } from 'ant-design-vue';
const props = defineProps({
value: {
type: Array,
default: () => [],
},
placement: {
type: String,
default: 'top',
},
type: {
type: String,
default: 'properties'
},
disabled: {
type: Boolean,
default:false
}
value: {
type: Array,
default: () => [],
},
placement: {
type: String,
default: 'top',
},
type: {
type: String,
default: 'properties',
},
disabled: {
type: Boolean,
default: false,
},
});
const validatorConfig = (value, _isObject = false) => {
if (!value) {
return Promise.resolve();
}
if (value.type === 'enum' && !value.elements?.length) {
return Promise.reject('请添加枚举项');
}
if (value.type === 'array' && !value.elementType?.type) {
return Promise.reject('请选择元素类型');
}
// if (_isObject && value.type === 'object' && !value.properties?.length) {
// return Promise.reject('');
// }
if (value.type === 'date' && !value.format) {
return Promise.reject('请选择时间格式');
}
if (
value.type === 'file' &&
(!value.bodyType ||
(isObject(value.bodyType) && !Object.keys(value.bodyType).length))
) {
return Promise.reject('请选择文件类型');
}
return Promise.resolve();
};
const emit = defineEmits(['update:value', 'confirm', 'cancel']);
const formItemContext = Form.useInjectFormItemContext();
const slots = useSlots()
const slots = useSlots();
const tableRef = ref()
const dataSource = ref([])
const visible = ref(false)
const tableRef = ref();
const dataSource = ref([]);
const visible = ref(false);
const defaultColumns = [
{
title: '参数标识',
dataIndex: 'id',
form: {
rules: [
{
asyncValidator(_, value, ...setting) {
if (value) {
const option = setting[2]
{
title: '参数标识',
dataIndex: 'id',
form: {
rules: [
{
asyncValidator(_, value, ...setting) {
if (value) {
const option = setting[2];
const isSome = dataSource.value.some((item) => {
return item.__dataIndex !== option.index && item.id === value
})
const isSome = dataSource.value.some((item) => {
return (
item.__dataIndex !== option.index &&
item.id === value
);
});
if (isSome) {
return Promise.reject('该标识已存在')
}
return Promise.resolve()
}
return Promise.reject('请输入标识')
}
if (isSome) {
return Promise.reject('该标识已存在');
}
return Promise.resolve();
}
return Promise.reject('请输入标识');
},
},
{ max: 64, message: '最多可输入64个字符' },
{
pattern: /^[a-zA-Z0-9_]+$/,
message: '标识只能由数字、字母、下划线组成',
},
],
},
{ max: 64, message: '最多可输入64个字符' },
{
pattern: /^[a-zA-Z0-9_]+$/,
message: '标识只能由数字、字母、下划线组成',
},
]
}
},
{
title: '参数名称',
dataIndex: 'name',
form: {
rules: [
{
required: true,
message: '请输入名称'
},
{ max: 64, message: '最多可输入64个字符' },
]
}
},
props.type === 'functions' ? {
title: '填写约束',
dataIndex: 'expands',
width: 120,
} : null,
{
title: '数据类型',
dataIndex: 'valueType',
width: 240,
form: {
required: true,
rules: [{
validator(_, value) {
if (!value?.type) {
return Promise.reject('请选择数据类型')
}
return Promise.resolve()
}
}]
},
}
]
{
title: '参数名称',
dataIndex: 'name',
form: {
rules: [
{
required: true,
message: '请输入名称',
},
{ max: 64, message: '最多可输入64个字符' },
],
},
},
props.type === 'functions'
? {
title: '填写约束',
dataIndex: 'expands',
width: 120,
}
: null,
{
title: '数据类型',
dataIndex: 'valueType',
width: 240,
form: {
required: true,
rules: [
{
validator(_, value) {
if (!value?.type) {
return Promise.reject('请选择数据类型');
}
return validatorConfig(value, true);
},
},
],
},
},
];
const myColumns = computed(() => {
return [
...defaultColumns.filter(item => !!item),
{
dataIndex: 'action',
title: '操作',
width: 60,
}
]
})
return [
...defaultColumns.filter((item) => !!item),
{
dataIndex: 'action',
title: '操作',
width: 60,
},
];
});
const onOk = async () => {
const data = await tableRef.value.validate()
if (data) {
visible.value = false
emit('update:value', data)
emit('confirm', data)
formItemContext.onFieldChange()
}
}
const data = await tableRef.value.validate();
if (data) {
visible.value = false;
emit('update:value', data);
emit('confirm', data);
formItemContext.onFieldChange();
}
};
const onCancel = () => {
emit('cancel');
emit('cancel');
};
const deleteItem = (index) => {
dataSource.value.splice(index, 1)
}
dataSource.value.splice(index, 1);
};
const addItem = () => {
dataSource.value.push({
id: undefined,
name: undefined,
expands: {
required: false
dataSource.value.push({
id: undefined,
name: undefined,
expands: {
required: false,
},
valueType: {
expands: {},
},
});
};
watch(
() => [JSON.stringify(props.value), visible.value],
(val) => {
if (visible.value) {
dataSource.value = JSON.parse(val[0] || '[]');
}
},
valueType: {
expands: {}
}
})
}
watch(() => [JSON.stringify(props.value), visible.value], (val) => {
if (visible.value) {
dataSource.value = JSON.parse(val[0] || '[]')
}
}, { immediate: true })
{ immediate: true },
);
</script>
<style scoped>
</style>
<style scoped></style>

View File

@ -48,7 +48,6 @@ const getMaskNode = (id: string, warpClassNames: string) => {
})
const warpNode = document.querySelector(`.${warpClassNames}`) as HTMLDivElement
if (!warpNode) return undefined
warpNode.insertAdjacentElement('beforebegin', maskNode)
@ -64,10 +63,11 @@ export const useMask = (propVisible: boolean, options: { visibleChange: (visible
showMask: Function,
visibleChange: (visible: boolean) => void
} => {
const key = randomString(6)
const visible = ref(propVisible)
const maskDomId = `${maskNodeClassName}-${randomString(6)}`
const maskDomId = `${maskNodeClassName}-${key}`
const warpClassNames = `${maskNodeClassName}-warp-${randomString(4)}`
const warpClassNames = `${maskNodeClassName}-warp-${key}`
const createMask = () => {
if (!maskIds.includes(maskDomId)) {
maskIds.push(maskDomId)
@ -78,13 +78,12 @@ export const useMask = (propVisible: boolean, options: { visibleChange: (visible
const getLastMask = (): HTMLElement | undefined => {
const index = maskIds.findIndex(key => key === maskDomId) // 当前遮罩层下标
let dom = undefined
let lastIndex = 0
if (maskIds.length > 0) {
lastIndex = index < 0 ? 0 : index - 1
lastIndex = index <= 0 ? 0 : index - 1
const lastMaskId = maskIds[lastIndex]
@ -116,7 +115,7 @@ export const useMask = (propVisible: boolean, options: { visibleChange: (visible
updateStyle(maskNode, {
display: 'block'
})
}, 10)
}, 150)
}
const hideMask = () => {

View File

@ -45,7 +45,7 @@ export const defaultBranches = [
key: 'terms_1',
},
],
key: 'branches_1',
key: defaultBranchId,
shakeLimit: {
enabled: false,
time: 1,
@ -69,7 +69,7 @@ const defaultOptions = {
},
],
branchName:'条件',
key: 'branches_1',
key:defaultBranchId,
executeAnyway: true,
groupIndex: 1
},

View File

@ -537,7 +537,10 @@ const getProduct = async () => {
const getAliyunProduct = async (data: any) => {
if (data.regionId && data.accessKeyId && data.accessSecret) {
const resp: any = await getAliyunProductsList(data);
const resp: any = await getAliyunProductsList(data).catch(()=>{
aliyunProductList.value = [];
modelRef.bridgeProductKey = undefined;
});
if (resp.status === 200) {
aliyunProductList.value = resp?.result?.data as Record<
string,
@ -720,6 +723,7 @@ watch(
},
];
modelRef.description = undefined;
aliyunProductList.value = [];
} else if (props.data?.type === 'noData') {
noData.value = true;
}

View File

@ -33,12 +33,13 @@
</div>
</div>
</div>
<Log :currentId="AlarmData.id" :configId="AlarmData.alarmConfigId" />
<Log :currentId="AlarmData.id" :configId="AlarmData.alarmConfigId" :goal="goal"/>
</a-drawer>
<SolveComponent
v-if="solveVisible"
@closeSolve="closeSolve"
@refresh="refresh"
:goal="goal"
:data="AlarmData"
/>
</template>
@ -53,6 +54,10 @@ const props = defineProps({
type: Object,
default: {},
},
goal:{
type:String,
default:''
}
});
const emit = defineEmits(['closeDrawer', 'refreshTable']);
const { levelMap } = useAlarmLevel();

View File

@ -26,10 +26,10 @@
: '--'
}}
</template>
<template #sourceId="slotProps">
<template #sourceName="slotProps">
<Ellipsis>
设备ID
<span class="deviceId" @click="() => gotoDevice(slotProps.sourceId)">{{ slotProps.sourceId }}</span></Ellipsis
设备名称
<span class="deviceId" @click="() => gotoDevice(slotProps.sourceId)">{{ slotProps.sourceName }}</span></Ellipsis
>
</template>
<template #handleType="slotProps">
@ -65,6 +65,7 @@
v-if="solveVisible"
:data="currentAlarm"
:solveType="solveType"
:goal="goal"
:handleDes="handleDescription"
@closeSolve="closeSolve"
@refresh="solveRefresh"
@ -72,6 +73,7 @@
<AlarmLog
v-if="visibleDrawer"
:data="currentAlarm"
:goal="goal"
@closeDrawer="visibleDrawer = false"
@refreshTable="refresh"
/>
@ -80,7 +82,7 @@
<script setup>
import {
queryByDevice as queryAlarmRecord,
queryHandleHistory,
queryPreHandleHistory,
} from '@/api/rule-engine/log';
import { useInstanceStore } from '@/store/instance';
import { useProductStore } from '@/store/product';
@ -204,8 +206,8 @@ const columns =
},
{
title: '告警源',
dataIndex: 'sourceId',
key: 'sourceId',
dataIndex: 'sourceName',
key: 'sourceName',
scopedSlots: true,
search: {
type: 'string',
@ -329,16 +331,8 @@ const handleSearch = (e) => {
params.value = e;
};
const queryHandle = async (id) => {
const res = await queryHandleHistory({
sorts: [{ name: 'createTime', order: 'desc' }],
terms: [
{
column: 'alarmRecordId',
termType: 'eq',
value: id,
type: 'and',
},
],
const res = await queryPreHandleHistory(id,{
sorts: [{ name: 'handleTime', order: 'desc' }],
});
if (res.status === 200 && res.result?.data.length) {
handleDescription.value = res.result.data?.[0]?.description;

View File

@ -31,12 +31,13 @@
><template #createTime="slotProps">
{{ dayjs(slotProps.createTime).format('YYYY-MM-DD HH:mm:ss') }}
</template>
<template #thingId="slotProps">
<template #thingName="slotProps">
<Ellipsis>
设备ID
设备名称
<span
class="deviceId"
>{{ slotProps.thingId }}</span
@click="() => gotoDevice(slotProps.thingId)"
>{{ slotProps.thingName }}</span
></Ellipsis
>
</template>
@ -48,12 +49,14 @@ import { queryInvalidData } from '@/api/rule-engine/log';
import { useInstanceStore } from '@/store/instance';
import { useProductStore } from '@/store/product';
import dayjs from 'dayjs';
import { useMenuStore } from 'store/menu';
const props = defineProps({
goal: {
type: String,
default: 'device',
},
});
const menuStory = useMenuStore();
const { current } =
props.goal === 'device' ? useInstanceStore() : useProductStore();
const columns = props.goal === 'device' ? [
@ -92,9 +95,9 @@ const columns = props.goal === 'device' ? [
scopedSlots: true,
},
{
title: '告警源',
dataIndex: 'thingId',
key: 'thingId',
title: '数据源',
dataIndex: 'thingName',
key: 'thingName',
scopedSlots: true,
search: {
type: 'string',
@ -114,6 +117,10 @@ const columns = props.goal === 'device' ? [
},
},
]
const gotoDevice = (id) => {
menuStory.jumpPage('device/Instance/Detail', { id, tab: 'Running' });
};
const handleSearch = (e) => {
params.value = e;
};

View File

@ -17,6 +17,9 @@
<template #type="slotProps">
{{ slotProps?.type?.text }}
</template>
<template #content="slotProps">
<Ellipsis style="width:calc(100% - 20px)">{{ slotProps?.content }}</Ellipsis>
</template>
<template #timestamp="slotProps">
{{
slotProps.timestamp
@ -87,7 +90,6 @@ const columns = [
},
{
title: '内容',
ellipsis: true,
dataIndex: 'content',
key: 'content',
scopedSlots: true,

View File

@ -163,7 +163,7 @@ import { usePermissionStore } from '@/store/permission';
import { isNoCommunity } from '@/utils/utils';
import { useSystem } from '@/store/system';
const { showThreshold } = useSystem()
const { showThreshold } = useSystem();
const permissionStore = usePermissionStore();
const menuStory = useMenuStore();
const route = useRoute();
@ -272,6 +272,21 @@ const handleUndeploy = () => {
* 是否显示数据解析模块
*/
const getProtocol = async () => {
list.value = [
{
key: 'Info',
tab: '配置信息',
},
{
key: 'Metadata',
tab: '物模型',
class: 'objectModel',
},
{
key: 'Device',
tab: '设备接入',
},
];
if (productStore.current?.messageProtocol) {
const res: any = await getProtocolDetail(
productStore.current?.messageProtocol,
@ -294,7 +309,8 @@ const getProtocol = async () => {
}
if (
supportFirmware &&
permissionStore.hasPermission('device/Firmware:view') && isNoCommunity
permissionStore.hasPermission('device/Firmware:view') &&
isNoCommunity
) {
list.value.push({
key: 'Firmware',
@ -316,7 +332,9 @@ const getProtocol = async () => {
if (
permissionStore.hasPermission(
'rule-engine/Alarm/Configuration:view',
) && isNoCommunity && showThreshold
) &&
isNoCommunity &&
showThreshold
) {
list.value.push({
key: 'AlarmRecord',

View File

@ -260,7 +260,7 @@ export const useColumns = (dataSource: Ref<MetadataItem[]>, type?: MetadataType,
{
asyncValidator: async (rule: any, value: any) => {
const source = value.source
const source = value?.source
if (source) {
if (source === 'device' && !value.type?.length) {
return Promise.reject('请选择读写类型');

View File

@ -69,6 +69,7 @@ import { getTemplate, uploadAnalyzeMetadata} from '@/api/device/instance'
import {getTemplate as getProductTemplate} from '@/api/device/product'
import {downloadFileByUrl} from "@/utils/utils";
import {useGroupActive, useTableWrapper} from "@/components/Metadata/Table/context";
import { useProductStore } from '@/store/product';
const props = defineProps({
target: {
@ -82,7 +83,7 @@ const props = defineProps({
})
const emit = defineEmits(['ok'])
const { current } = useProductStore()
const visible = ref(false);
const successCount = ref(0);
const errorCount = ref(0);
@ -117,7 +118,6 @@ const onCancel = () => {
}
const submitData = async (metadataStr) => {
console.log(metadataStr)
if (metadataStr) {
const _metadataObject = JSON.parse(metadataStr || "{}")
const properties = _metadataObject.properties
@ -176,7 +176,7 @@ const beforeUpload = (file) => {
formData.append('file', file)
step.value = 2
uploadAnalyzeMetadata(formData).then(res => {
uploadAnalyzeMetadata(current.id , formData).then(res => {
if (res.success) {
submitData(res.result)
}
@ -189,7 +189,6 @@ const beforeUpload = (file) => {
const uploadChange = async (info) => {
console.log(info)
if (info.file.status === 'uploading') {
step.value = 2
}

View File

@ -75,9 +75,9 @@ const formData = reactive<{
value: ValueType;
rangeValue: ValueType;
}>({
value: props.value?.range === false ? props.value?.value : undefined,
rangeValue: props.value?.range === true
? cloneDeep(props.value?.value) || [undefined, undefined]
value: props.range === false ? props.value : undefined,
rangeValue: props.range === true
? cloneDeep(props.value) || [undefined, undefined]
: [undefined, undefined],
});
@ -87,10 +87,10 @@ const showText = computed(() => {
if (props.range === false) {
switch (type) {
case 'date':
return props.value?.value;
return props.value;
case 'boolean':
const _value = props.value?.value
const item = props.options.find(item => item.value === props.value?.value)
const _value = props.value
const item = props.options.find(item => item.value === props.value)
if (item) {
return item.label
}else if (_value) {
@ -99,10 +99,10 @@ const showText = computed(() => {
return ''
}
default:
return props.value?.value
return props.value
}
} else {
return props.value?.value?.[0] ? props.value.value.join('-') : ''
return props.value?.[0] ? props.value.join('-') : ''
}
})
@ -169,7 +169,7 @@ const confirm = () => {
watch(() => props.range, (value, oldValue) => {
if (value !== oldValue ) {
formData.rangeValue = value ? [undefined, undefined] : undefined
formData.rangeValue = value ? cloneDeep(props.value) || [undefined, undefined] : undefined
}
}, { immediate: true})
</script>

View File

@ -210,7 +210,10 @@
v-model:value="extraForm.mode"
:options="[
{ label: '忽略', value: 'ignore' },
{ label: '记录', value: 'record' },
{
label: '记录',
value: 'device-record',
},
{
label: '告警',
value: 'device-alarm',
@ -310,7 +313,7 @@ const props = defineProps({
const type = inject('_metadataType');
const { showThreshold } = useSystem()
const { showThreshold } = useSystem();
const productStore = useProductStore();
const deviceStore = useInstanceStore();
const tableWrapperRef = useTableWrapper();
@ -354,7 +357,7 @@ const typeMap = {
const handleTip = computed(() => {
if (extraForm.mode === 'ignore') {
return '平台将忽略超出阈值的数据,无法查看上报记录';
} else if (extraForm.mode === 'record') {
} else if (extraForm.mode === 'device-record') {
return '您可以在预处理数据-无效数据页面查看超出阈值的数据上报记录';
}
return '您可以在预处理数据-告警数据页面查看告警情况';
@ -366,7 +369,7 @@ const showContent = computed(() => {
return showExtra.value;
}
return showMetrics.value || config.value.length > 0;
return (showMetrics.value || config.value.length > 0) && props.id;
});
const showMetrics = computed(() => {
@ -384,7 +387,8 @@ const showMetrics = computed(() => {
const showExtra = computed(() => {
return (
['int', 'long', 'float', 'double'].includes(props.type as any) &&
props.metadataType === 'properties' && showThreshold
props.metadataType === 'properties' &&
showThreshold
);
});
@ -485,8 +489,8 @@ const getConfig = async () => {
} else if (showMetrics.value) {
activeKey.value = ['metrics'];
}
if(showExtra.value){
activeKey.value = ['extra']
if (showExtra.value) {
activeKey.value = ['extra'];
}
if (resp.result.length && !configValue.value) {
resp.result.forEach((a) => {

View File

@ -3844,6 +3844,10 @@ export default [
permission: 'role',
actions: ['query'],
},
{
permission: "role-group",
actions: ["query"]
}
],
buttons: [
{
@ -3912,7 +3916,7 @@ export default [
permissions: [
{
permission: 'role-group',
actions: ['query','save']
actions: ['save']
}
],
},
@ -3922,7 +3926,7 @@ export default [
permissions: [
{
permission: 'role-group',
actions: ['query','delete']
actions: ['delete']
}
],
},

View File

@ -217,7 +217,8 @@
</template>
<template #cardState="slotProps">
<BadgeStatus
:status="slotProps.cardState?.value"
v-if="slotProps.cardState?.state"
:status="slotProps.cardState?.state"
:text="slotProps.cardState?.text"
:statusNames="{
using: 'processing',
@ -508,7 +509,8 @@ const columns = [
title: '运营商状态',
dataIndex: 'operatorState',
key: 'operatorState',
hidden: true,
// hidden: true,
hideInTable: true,
search: {
type: 'select',
options: [

View File

@ -101,7 +101,8 @@ const pickerTimeChange = () => {
const echartsOptions = computed(() => {
const series = serverActive.value.length
? serverActive.value.map((key) => setOptions(serverData.data, key))
: typeDataLine
: typeDataLine;
return {
xAxis: {
type: 'category',

View File

@ -140,7 +140,7 @@ const getJVMEcharts = async (val: any) => {
const setOptions = (optionsData: any, key: string) => ({
data: arrayReverse(optionsData[key]),
// name: key!= 'undefined' ? key : '',
name: key !== 'undefined' ? key : '',
type: 'line',
smooth: true,
symbol: 'none',
@ -158,6 +158,7 @@ const echartsOptions = computed(() => {
const series = serverActive.value.length
? serverActive.value.map((key) => setOptions(serverData.data, key))
: typeDataLine
return {
xAxis: {
type: 'category',

View File

@ -17,6 +17,7 @@
format="YYYY-MM-DD HH:mm:ss"
valueFormat="x"
v-model:value="dateRange"
@change="onChange"
/>
</j-space>
</div>
@ -144,6 +145,10 @@ const createChart = () => {
};
const onChange = ()=>{
dimension.value = 'other'
}
watch(
() => props.chartData,
(val) => {

View File

@ -50,7 +50,7 @@
<div>开始录像</div>
<template #overlay>
<j-menu @click="recordStart">
<j-menu-item key="true" v-if="_type">
<j-menu-item key="true" v-if="_type && route.query.type !== 'onvif'">
<span style="padding-right: 12px"
>本地存储</span
>
@ -129,7 +129,7 @@
</MediaTool>
</div>
<Preset
v-if="data.ptzType.value === 0 || data.ptzType.value === 1"
v-if="(data.ptzType.value === 0 || data.ptzType.value === 1) && route.query.type !== 'onvif'"
:data="data"
@refresh="onRefresh"
/>

View File

@ -58,7 +58,7 @@
</j-tooltip>
<RadioCard
layout="horizontal"
:options="[
:options="deviceType !== 'onvif' ?[
{
label: '云端',
value: 'cloud',
@ -70,7 +70,12 @@
logo: getImage('/local.png'),
disabled: deviceType === 'fixed-media',
},
]"
]:
[{
label: '云端',
value: 'cloud',
logo: getImage('/media/cloud.png'),
}]"
:checkStyle="true"
v-model="type"
/>
@ -319,7 +324,7 @@ onMounted(() => {
deviceType.value = _type;
const _timeStr = dayjs(new Date());
time.value = _timeStr;
if (_type === 'fixed-media') {
if (_type === 'fixed-media' || _type === 'onvif') {
type.value = 'cloud';
queryServiceRecords(_timeStr);
} else {

View File

@ -128,140 +128,404 @@
/>
</j-form-item>
</j-col>
<j-col :span="8" class="form-item">
<j-form-item
:name="['configuration', 'rtpIp']"
:rules="[
{
required: true,
message: '请输入RTP IP',
},
{
validator: validateAddress,
message: '请输入正确的IP地址或者域名',
},
]"
>
<template #label>
RTP IP
<j-tooltip
title="视频设备将流推送到该IP地址下部分设备仅支持IP地址建议全是用IP地址"
>
<AIcon
type="QuestionCircleOutlined"
style="margin-left: 2px"
/>
</j-tooltip>
</template>
<j-input
placeholder="请输入RTP IP"
v-model:value="formData.configuration.rtpIp"
/>
</j-form-item>
</j-col>
<j-col :span="4" v-if="!checked">
<j-form-item
class="form-item"
:name="['configuration', 'rtpPort']"
:rules="[
{
required: true,
message: '请输入端口',
},
]"
>
<div class="form-label"></div>
<j-input-number
style="width: 100%"
:min="1"
:max="65535"
:precision="0"
placeholder="请输入端口"
v-model:value="
formData.configuration.rtpPort
"
/>
</j-form-item>
</j-col>
<j-col :span="4" v-if="checked">
<j-form-item
class="form-item"
:name="[
'configuration',
'dynamicRtpPortRange0',
]"
:rules="[
{
required: true,
message: '请输入起始端口',
},
]"
>
<div class="form-label"></div>
<j-input-number
style="width: 100%"
:min="1"
:max="
formData.configuration
.dynamicRtpPortRange1 || 65535
"
:precision="0"
placeholder="起始端口"
v-model:value="
formData.configuration
.dynamicRtpPortRange0
"
/>
</j-form-item>
</j-col>
<div class="form-item-checked" v-if="checked"></div>
<j-col :span="4" v-if="checked">
<j-form-item
class="form-item"
:name="[
'configuration',
'dynamicRtpPortRange1',
]"
:rules="[
{
required: true,
message: '请输入终止端口',
},
]"
>
<div class="form-label"></div>
<j-input-number
style="width: 100%"
:min="
formData.configuration
.dynamicRtpPortRange0 || 1
"
:max="65535"
:precision="0"
placeholder="终止端口"
v-model:value="
formData.configuration
.dynamicRtpPortRange1
"
/>
</j-form-item>
</j-col>
<j-col :span="4">
<j-form-item
class="form-item-checked2"
:name="['configuration', 'dynamicRtpPort']"
>
<div class="form-label"></div>
<j-checkbox
v-model:checked="
formData.configuration.dynamicRtpPort
"
<template
v-if="formData.provider === 'embedded-zlmedia'"
>
<j-col :span="24">
<j-form-item
:name="['configuration', 'rtpIp']"
:rules="[
{
required: true,
message: '请输入IP',
},
{
validator: validateAddress,
message:
'请输入正确的IP地址或者域名',
},
]"
>
动态端口
</j-checkbox>
</j-form-item>
</j-col>
<template #label>
IP
<j-tooltip
title="视频设备将流推送到该IP地址下部分设备仅支持IP地址建议全是用IP地址"
>
<AIcon
type="QuestionCircleOutlined"
style="margin-left: 2px"
/>
</j-tooltip>
</template>
<j-input
placeholder="请输入IP"
style="width: 50%"
v-model:value="
formData.configuration.rtpIp
"
/>
</j-form-item>
</j-col>
<j-col :span="12" class="item">
<j-row :gutter="[16, 0]">
<j-col :span="20" v-if="!checked">
<j-form-item
class="form-item"
:name="['configuration', 'rtpPort']"
:rules="[
{
required: true,
message: '请输入RTP端口',
},
]"
>
<template #label>
RTP 端口
<j-tooltip
title="视频设备将流推送到该IP地址对应的RTP端口下部分设备仅支持IP地址建议全是用IP地址"
>
<AIcon
type="QuestionCircleOutlined"
style="margin-left: 2px"
/>
</j-tooltip>
</template>
<j-input-number
style="width: 100%"
:min="1"
:max="65535"
:precision="0"
placeholder="请输入RTP端口"
v-model:value="
formData.configuration
.rtpPort
"
/>
</j-form-item>
</j-col>
<j-col :span="8" v-if="checked">
<j-form-item
class="form-item"
:name="[
'configuration',
'dynamicRtpPortRange0',
]"
:rules="[
{
required: true,
message: '请输入起始端口',
},
]"
>
<template #label>
RTP 端口
<j-tooltip
title="视频设备将流推送到该IP地址对应的RTP端口下部分设备仅支持IP地址建议全是用IP地址"
>
<AIcon
type="QuestionCircleOutlined"
style="margin-left: 2px"
/>
</j-tooltip>
</template>
<j-input-number
style="width: 100%"
:min="1"
:max="
formData.configuration
.dynamicRtpPortRange1 ||
65535
"
:precision="0"
placeholder="起始端口"
v-model:value="
formData.configuration
.dynamicRtpPortRange0
"
/>
</j-form-item>
</j-col>
<div
class="form-item-checked"
v-if="checked"
>
</div>
<j-col :span="8" v-if="checked">
<j-form-item
class="form-item"
:name="[
'configuration',
'dynamicRtpPortRange1',
]"
:rules="[
{
required: true,
message: '请输入终止端口',
},
]"
>
<div class="form-label"></div>
<j-input-number
style="width: 100%"
:min="
formData.configuration
.dynamicRtpPortRange0 ||
1
"
:max="65535"
:precision="0"
placeholder="终止端口"
v-model:value="
formData.configuration
.dynamicRtpPortRange1
"
/>
</j-form-item>
</j-col>
<j-col :span="4">
<j-form-item
class="form-item-checked2"
:name="[
'configuration',
'dynamicRtpPort',
]"
>
<div class="form-label"></div>
<j-checkbox
v-model:checked="
formData.configuration
.dynamicRtpPort
"
>
动态端口
</j-checkbox>
</j-form-item>
</j-col>
<j-col :span="24">
<j-form-item
class="form-item"
:name="[
'configuration',
'rtmpPort',
]"
:rules="[
{
required: true,
message: '请输入RTMP端口',
},
]"
>
<template #label>
RTMP 端口
<j-tooltip
title="对外分享的RTMP视频流地址对应端口"
>
<AIcon
type="QuestionCircleOutlined"
style="margin-left: 2px"
/>
</j-tooltip>
</template>
<j-input-number
style="width: 100%"
:min="1"
:max="65535"
:precision="0"
placeholder="RTMP 端口"
v-model:value="
formData.configuration
.rtmpPort
"
/>
</j-form-item>
</j-col>
<j-col :span="24">
<j-form-item
:name="[
'configuration',
'rtspPort',
]"
:rules="[
{
required: true,
message: '请输入RTSP端口',
},
]"
>
<template #label>
RTSP 端口
<j-tooltip
title="对外分享的RTSP视频流地址对应端口"
>
<AIcon
type="QuestionCircleOutlined"
style="margin-left: 2px"
/>
</j-tooltip>
</template>
<j-input-number
style="width: 100%"
:min="1"
:max="65535"
:precision="0"
placeholder="RTSP 端口"
v-model:value="
formData.configuration
.rtspPort
"
/>
</j-form-item>
</j-col>
</j-row>
</j-col>
</template>
<div v-else style="width: 100%; display: flex">
<j-col :span="8" class="form-item">
<j-form-item
:name="['configuration', 'rtpIp']"
:rules="[
{
required: true,
message: '请输入RTP IP',
},
{
validator: validateAddress,
message:
'请输入正确的IP地址或者域名',
},
]"
>
<template #label>
RTP IP
<j-tooltip
title="视频设备将流推送到该IP地址下部分设备仅支持IP地址建议全是用IP地址"
>
<AIcon
type="QuestionCircleOutlined"
style="margin-left: 2px"
/>
</j-tooltip>
</template>
<j-input
placeholder="请输入RTP IP"
v-model:value="
formData.configuration.rtpIp
"
/>
</j-form-item>
</j-col>
<j-col :span="4" v-if="!checked">
<j-form-item
class="form-item"
:name="['configuration', 'rtpPort']"
:rules="[
{
required: true,
message: '请输入端口',
},
]"
>
<div class="form-label"></div>
<j-input-number
style="width: 100%"
:min="1"
:max="65535"
:precision="0"
placeholder="请输入端口"
v-model:value="
formData.configuration.rtpPort
"
/>
</j-form-item>
</j-col>
<j-col :span="4" v-if="checked">
<j-form-item
class="form-item"
:name="[
'configuration',
'dynamicRtpPortRange0',
]"
:rules="[
{
required: true,
message: '请输入起始端口',
},
]"
>
<div class="form-label"></div>
<j-input-number
style="width: 100%"
:min="1"
:max="
formData.configuration
.dynamicRtpPortRange1 || 65535
"
:precision="0"
placeholder="起始端口"
v-model:value="
formData.configuration
.dynamicRtpPortRange0
"
/>
</j-form-item>
</j-col>
<div class="form-item-checked" v-if="checked">
</div>
<j-col :span="4" v-if="checked">
<j-form-item
class="form-item"
:name="[
'configuration',
'dynamicRtpPortRange1',
]"
:rules="[
{
required: true,
message: '请输入终止端口',
},
]"
>
<div class="form-label"></div>
<j-input-number
style="width: 100%"
:min="
formData.configuration
.dynamicRtpPortRange0 || 1
"
:max="65535"
:precision="0"
placeholder="终止端口"
v-model:value="
formData.configuration
.dynamicRtpPortRange1
"
/>
</j-form-item>
</j-col>
<j-col :span="4">
<j-form-item
class="form-item-checked2"
:name="['configuration', 'dynamicRtpPort']"
>
<div class="form-label"></div>
<j-checkbox
v-model:checked="
formData.configuration
.dynamicRtpPort
"
>
动态端口
</j-checkbox>
</j-form-item>
</j-col>
</div>
<j-col :span="24">
<j-form-item>
@ -309,10 +573,7 @@ const Validator = {
const validateAddress = (_rule: any, value: string): Promise<any> =>
new Promise(async (resolve, reject) => {
if (
testIpv4_6(value) ||
Validator.regDomain.test(value)
) {
if (testIpv4_6(value) || Validator.regDomain.test(value)) {
return resolve('');
} else {
return value ? reject('请输入正确的IP地址或者域名') : resolve('');
@ -328,6 +589,8 @@ const formData = ref<FormDataType>({
apiPort: '',
rtpIp: '',
rtpPort: '',
rtspPort: '',
rtmpPort: '',
dynamicRtpPort: false,
// dynamicRtpPortRange: [],
dynamicRtpPortRange0: '',
@ -413,5 +676,12 @@ watch(
height: 30px;
padding-bottom: 8px;
}
.item{
padding-top: 6px;
border: 1px solid #d1d1d1;
background-color: #e0e0e007;
border-radius: 4px;
margin-bottom: 10px;
}
}
</style>

View File

@ -4,6 +4,8 @@ export interface Configuration {
apiPort: number | string;
rtpIp: string | string;
rtpPort: number | string;
rtspPort: number | string;
rtmpPort: number | string;
dynamicRtpPort: boolean;
dynamicRtpPortRange?: array<any>;
dynamicRtpPortRange0?: number | string | undefined;

View File

@ -2,10 +2,10 @@
<template>
<div>
<j-modal
v-model:visible="_vis"
visible
title="同步用户"
:footer="null"
@cancel="_vis = false"
@cancel="$emit('cancel')"
width="80%"
>
<j-row :gutter="10" class="model-body">
@ -131,34 +131,17 @@ import { onlyMessage } from '@/utils/comm';
const useForm = Form.useForm;
type Emits = {
(e: 'update:visible', data: boolean): void;
(e: 'cancel'): void;
};
const emit = defineEmits<Emits>();
const props = defineProps({
visible: { type: Boolean, default: false },
data: {
type: Object as PropType<Partial<Record<string, any>>>,
default: () => ({}),
},
});
const _vis = computed({
get: () => props.visible,
set: (val) => emit('update:visible', val),
});
watch(
() => _vis.value,
(val) => {
if (val) {
getDepartment();
} else {
dataSource.value = [];
}
},
);
//
const deptTreeData = ref([]);
const deptName = ref('');
@ -182,16 +165,11 @@ const getDepartment = async () => {
}
deptTreeData.value = _result;
deptId.value = _result[0]?.id;
if(_result.length){
deptId.value = _result[0]?.id;
}
};
watch(
() => deptName.value,
(val: any) => {
if (!val) getDepartment();
},
);
/**
* 部门点击
*/
@ -253,7 +231,7 @@ const getActions = (
icon: 'DisconnectOutlined',
popConfirm: {
title: '确认解绑?',
onConfirm: () => {
onConfirm: () => {
const response = configApi.unBindUser(
{ bindingId: data.bindId },
data.bindId,
@ -336,7 +314,7 @@ const getAllUsers = async (terms?: any) => {
};
const { result } = await configApi.getPlatformUsers(params);
allUserList.value = result.map((m: any) => ({
label: m.name,
label: m.name + ` (${m.username})`,
value: m.id,
...m,
}));
@ -357,7 +335,7 @@ const getTableData = (terms?: any) => {
(deptUsers || []).forEach((deptUser: any) => {
//
let unBindUser = unBindUsers.find(
(f: any) => f.name === deptUser?.name,
(f: any) => f.id === deptUser?.id,
);
//
const bindUser = bindUsers.find(
@ -399,14 +377,6 @@ const handleTableChange = (pagination: any) => {
pageSize.value = pagination.pageSize;
};
watch(
() => deptId.value,
() => {
getTableData();
},
{ immediate: true },
);
/**
* 绑定用户
*/
@ -487,6 +457,23 @@ const handleCancel = () => {
bindVis.value = false;
resetFields();
};
watch(
() => deptId.value,
() => {
getTableData();
},
// { immediate: true },
);
watch(
() => deptName.value,
(val: any) => {
if (!val) getDepartment();
},
);
onMounted(() => {
getDepartment();
});
</script>
<style lang="less" scoped>

View File

@ -194,7 +194,7 @@
<Debug v-model:visible="debugVis" :data="currentConfig" />
<Log v-if="logVis" :data="currentConfig" @cancel="logVis = false" />
<SyncUser v-model:visible="syncVis" :data="currentConfig" />
<SyncUser v-if="syncVis" :data="currentConfig" @cancel="syncVis = false"/>
</page-container>
</template>

View File

@ -142,62 +142,6 @@ const _vis = computed({
* 获取通知模板
*/
const configList = ref<BindConfig[]>([]);
const getConfigList = async () => {
const params = {
terms: [
{ column: 'type', value: props.data.type },
{ column: 'provider', value: props.data.provider },
],
};
const { result } = await TemplateApi.getConfig(params);
configList.value = result;
//
if (configList.value.length) formData.value.configId = props.data.configId;
};
watch(
() => _vis.value,
(val) => {
if (val) {
getConfigList();
getTemplateDetail();
}
},
);
/**
* 获取模板详情
*/
const getTemplateDetail = async () => {
const { result } = await TemplateApi.getTemplateDetail(props.data.id);
formData.value.templateDetailTable = result.variableDefinitions.map(
(m: any) => ({
...m,
type: m.expands?.businessType ? m.expands.businessType : m.type,
value: undefined,
//
otherRules:
m.id === 'calledNumber' || m.id === 'phoneNumber'
? [
{
max: 64,
message: '最多可输入64个字符',
trigger: 'change',
},
{
trigger: 'change',
validator(_rule: Rule, value: string) {
if (!value) return Promise.resolve();
if (!phoneRegEx(value))
return Promise.reject('请输入有效号码');
return Promise.resolve();
},
},
]
: [],
}),
);
};
const columns = [
{
@ -236,7 +180,74 @@ const formData = ref<{
*/
const formRef = ref();
const btnLoading = ref(false);
const getConfigList = async () => {
const params = {
terms: [
{ column: 'type', value: props.data.type },
{ column: 'provider', value: props.data.provider },
],
};
const { result } = await TemplateApi.getConfig(params);
configList.value = result;
//
if (configList.value.length) formData.value.configId = props.data.configId;
};
/**
* 获取模板详情
*/
const getTemplateDetail = async () => {
const { result } = await TemplateApi.getTemplateDetail(props.data.id);
formData.value.templateDetailTable = result.variableDefinitions.map(
(m: any) => ({
...m,
type: m.expands?.businessType ? m.expands.businessType : m.type,
value: undefined,
//
otherRules:
m.id === 'calledNumber' || m.id === 'phoneNumber'
? [
{
max: 64,
message: '最多可输入64个字符',
trigger: 'change',
},
{
trigger: 'change',
validator(_rule: Rule, value: string) {
if (!value) return Promise.resolve();
if (!phoneRegEx(value))
return Promise.reject('请输入有效号码');
return Promise.resolve();
},
},
]
: [],
}),
);
};
const handleOk = () => {
console.log(formData.value.templateDetailTable)
const filterData = formData.value.templateDetailTable.filter((item: any) =>
['user', 'org', 'tag', 'userIdList', 'departmentIdList'].includes(
item.id,
),
);
const pass = filterData.length
? filterData.some((i: any) => {
return i.value;
})
: true;
if (!pass && props.data.type === 'dingTalk') {
onlyMessage('收信人,收信人部门至少填写一个', 'warning');
return;
}
if (!pass && props.data.type === 'weixin') {
onlyMessage('收信人,收信人部门,收信人标签至少填写一个', 'warning');
return;
}
formRef.value
.validate()
.then(async () => {
@ -267,6 +278,16 @@ const handleCancel = () => {
formRef.value.resetFields();
formData.value.templateDetailTable = [];
};
watch(
() => _vis.value,
(val) => {
if (val) {
getConfigList();
getTemplateDetail();
}
},
);
</script>
<style lang="less" scoped>

View File

@ -14,12 +14,13 @@ import templateApi from '@/api/notice/template';
type Emits = {
(e: 'update:toUser', data: string | undefined): void;
(e: 'update:canSave', data: boolean): void;
};
type Props = {
toUser: string | undefined;
type: string | undefined;
configId: string | undefined;
}
};
const emit = defineEmits<Emits>();
@ -37,14 +38,19 @@ const typeObj = {
const options = ref([]);
const queryData = async () => {
if (!props.configId) return;
const { result } = await templateApi.getUser(
typeObj[props.type],
props.configId,
);
options.value = result.map((item: any) => ({
label: item.name,
value: item.id,
}));
const res: any = await templateApi
.getUser(typeObj[props.type], props.configId)
.catch(() => {
emit('update:canSave', false);
});
if (res.status === 200) {
options.value = res?.result.map((item: any) => ({
label: item.name,
value: item.id,
}));
emit('update:canSave', true);
}
};
queryData();

View File

@ -24,10 +24,10 @@ const AliyunVoice = () => {
<div> /ID标识</div>
<h2> 4</h2>
<div> </div>
<div>使</div>
<div>使</div>
<h2> 5</h2>
<div> </div>
<div>使</div>
<div>使</div>
<h2> 6</h2>
<div> 3</div>
<h2> 7</h2>

View File

@ -105,7 +105,7 @@
</j-form-item>
<j-row :gutter="10">
<j-col :span="12">
<j-form-item label="收信部门">
<j-form-item label="收信部门">
<ToOrg
v-model:toParty="
formData.template
@ -137,6 +137,7 @@
v-model:toUser="
formData.template.userIdList
"
v-model:canSave="canSave"
:type="formData.type"
:config-id="formData.configId"
/>
@ -294,7 +295,7 @@
</j-form-item>
</j-col>
<j-col :span="12">
<j-form-item label="收信部门">
<j-form-item label="收信部门">
<ToOrg
v-model:toParty="
formData.template.toParty
@ -308,7 +309,7 @@
<j-form-item>
<template #label>
<span>
标签推送
收信人标签
<j-tooltip
title="本企业微信的标签ID列表,最多支持100个,如果不填写该字段,将在使用此模板发送通知时进行指定"
>
@ -722,6 +723,7 @@
</j-form-item>
<j-form-item>
<j-button
:disabled="!canSave"
type="primary"
@click="handleSubmit"
:loading="btnLoading"
@ -771,7 +773,7 @@ const router = useRouter();
const route = useRoute();
const useForm = Form.useForm;
const formRef = ref()
const canSave = ref(true)
const flag = ref<boolean>(false)
//
const msgType = ref([
@ -1184,8 +1186,13 @@ const templateList = ref();
const getTemplateList = async () => {
if (!formData.value.configId) return
const id = formData.value.configId || undefined;
const { result } = await templateApi.getAliTemplate(id);
templateList.value = result;
const res:any = await templateApi.getAliTemplate(id).catch(()=>{
canSave.value = false
})
if(res.status === 200){
canSave.value = true
templateList.value = res.result;
}
};
/**

View File

@ -22,7 +22,7 @@
</template>
{{ selectedKeys.some(selectKey => selectKey === item.actionId) ? '已关联' : '关联' }}
</a-button>
<a-button v-else-if="activeKeys.some(active => active === item.actionId)" type="link" @click.stop="onSelect(item)">
<a-button v-else-if="activeKeys.some(active => active === item.actionId || active === -1)" type="link" @click.stop="onSelect(item)">
<template #icon>
<AIcon type="icon-jiebang"/>
</template>
@ -254,8 +254,8 @@ const onBind = (record) => {
}
const onSelect = (record) => {
const selected = props.activeKeys.some(item => item === record.actionId)
emit('select', record.actionId, !selected)
const id = props.activeKeys.find(active => active === record.actionId || active === -1)
emit('select', id, false)
}

View File

@ -188,6 +188,10 @@ const props = defineProps({
type: Boolean,
default: true
},
showHistory: {
type: Boolean,
default: true
},
maskStyle: {
type: Object,
default: undefined
@ -221,7 +225,7 @@ const branchesGroup = computed(() => {
const activeBranches = computed(() => {
const { data, invalid } = handleActiveBranches(branchesGroup.value, props.activeKeys)
isInvalid.value = invalid
isInvalid.value = invalid && props.showHistory
return data
})

View File

@ -38,7 +38,7 @@
<script setup name="SceneDrawer">
import {useRequest} from "@/hook";
import {queryBindScene} from "@/api/rule-engine/configuration";
import {queryBindScene, unbindScene} from "@/api/rule-engine/configuration";
import {handleGroupAndFilter, typeMap} from "./Save/utils";
import BranchesTabs from './Save/BranchesTabs.vue'
import { unBindAlarm, bindScene } from "@/api/rule-engine/configuration";
@ -97,11 +97,13 @@ const handleBind = (id, selected) => {
loading.value = false
})
} else {
unBindAlarm(props.detail.id, props.id, [id]).then(res => {
const request = id === -1 ? unbindScene(props.id, [props.detail.id]) : unBindAlarm(props.detail.id, props.id, [id])
request.then(res => {
activeKeys.value = activeKeys.value.filter(key => key !== id)
}).finally(() => {
loading.value = false
})
}
}

View File

@ -46,6 +46,7 @@
:showBranches="false"
:showBindTags="true"
:showRule="false"
:showHistory="false"
@click="handleView(slotProps)"
>
<div class="scene-view">
@ -116,32 +117,32 @@ const {data: activeKeys, reload} = useRequest(queryBindScene, {
defaultValue: {}
})
const getActions = (
data: Partial<Record<string, any>>,
type: 'card' | 'table',
): ActionsType[] => {
if (!data) return [];
const actions: ActionsType[] = [
{
key: 'action',
text: '解绑',
icon: 'DisconnectOutlined',
popConfirm: {
title: '确认解绑?',
onConfirm: async () => {
// const res = await unbindScene(id, [data.id], data.branchIndex);
const res = await unbindScene(id, [data.id]);
if (res.status === 200) {
onlyMessage('操作成功');
actionRef.value.reload();
}
return
},
},
},
];
return actions;
};
// const getActions = (
// data: Partial<Record<string, any>>,
// type: 'card' | 'table',
// ): ActionsType[] => {
// if (!data) return [];
// const actions: ActionsType[] = [
// {
// key: 'action',
// text: '',
// icon: 'DisconnectOutlined',
// popConfirm: {
// title: '',
// onConfirm: async () => {
// // const res = await unbindScene(id, [data.id], data.branchIndex);
// const res = await unbindScene(id, [data.id]);
// if (res.status === 200) {
// onlyMessage('');
// actionRef.value.reload();
// }
// return
// },
// },
// },
// ];
// return actions;
// };
const queryTable = (_terms: any) => {
return query(_terms, id)

View File

@ -20,11 +20,11 @@
<template #alarmTime="slotProps">{{
dayjs(slotProps.alarmTime).format('YYYY-MM-DD HH:mm:ss')
}}</template>
<template #sourceId="slotProps"
>设备ID<a-button
<template #sourceName="slotProps"
>设备名称<a-button
type="link"
@click="() => gotoDevice(slotProps.sourceId)"
>{{ slotProps.sourceId }}</a-button
>{{ slotProps.sourceName }}</a-button
></template
>
<template #action="slotProps">
@ -213,8 +213,8 @@ watch(
},
{
title: '告警源',
dataIndex: 'sourceId',
key: 'sourceId',
dataIndex: 'sourceName',
key: 'sourceName',
scopedSlots: true,
search: {
type: 'string',

View File

@ -25,7 +25,7 @@
</template>
<script lang="ts" setup>
import { handleLog } from '@/api/rule-engine/log';
import { handleLog, handlePreconditioning } from '@/api/rule-engine/log';
import { onlyMessage } from '@/utils/comm';
const props = defineProps({
data: {
@ -39,6 +39,10 @@ const props = defineProps({
type: String,
default: '',
},
goal: {
type: String,
default: '',
},
});
const loading = ref<boolean>(false);
const formRef = ref();
@ -66,14 +70,17 @@ const handleSave = () => {
formRef.value
.validate()
.then(async () => {
const res = await handleLog({
const params = {
describe: form.describe,
type: 'user',
state: 'normal',
alarmRecordId: props.data?.id || '',
alarmConfigId: props.data?.alarmConfigId || '',
alarmTime: props?.data?.alarmTime || '',
});
};
const res = props.goal
? await handlePreconditioning(params)
: await handleLog(params);
if (res.status === 200) {
onlyMessage('操作成功!');
emit('refresh');
@ -82,13 +89,13 @@ const handleSave = () => {
}
loading.value = false;
})
.catch((error) => {
.catch((error:any) => {
console.log(error);
loading.value = false;
});
};
onMounted(() => {
props.solveType === 'view' ? form.describe = props.handleDes : '';
props.solveType === 'view' ? (form.describe = props.handleDes) : '';
});
</script>
<style lang="less" scoped></style>

View File

@ -12,9 +12,9 @@
>{{ dayjs(text).format('YYYY-MM-DD HH:mm:ss')
}}</span
></template>
<template v-if="column.dataIndex === 'sourceId'">
<template v-if="column.dataIndex === 'sourceName'">
<Ellipsis>
设备ID
设备名称
<span class="deviceId" @click="() => gotoDevice(text)">{{
text
}}</span></Ellipsis
@ -49,7 +49,7 @@
</template>
<script setup>
import { queryLogList } from '@/api/rule-engine/log';
import { queryLogList ,queryPreconditioningLogList } from '@/api/rule-engine/log';
import dayjs from 'dayjs';
import { useMenuStore } from 'store/menu';
import LogDetail from './LogDetail.vue';
@ -62,6 +62,10 @@ const props = defineProps({
type: String,
default: '',
},
goal:{
type:String,
default: ''
}
});
const menuStory = useMenuStore();
const exceed = ref();
@ -81,8 +85,8 @@ const columns = [
},
{
title: '告警源',
dataIndex: 'sourceId',
key: 'sourceId',
dataIndex: 'sourceName',
key: 'sourceName',
},
{
title: '告警原因',
@ -97,7 +101,7 @@ const columns = [
}
];
const queryData = async () => {
const res = await queryLogList(props.configId, {
const params = {
pageIndex: 0,
pageSize: 51,
terms: [
@ -114,7 +118,8 @@ const queryData = async () => {
order: 'desc',
},
],
});
}
const res = props.goal ? await queryPreconditioningLogList(props.configId,params) : await queryLogList(props.configId, params);
if (res.success) {
if (res.result.data?.length > 50) {
exceed.value = true;

View File

@ -7,7 +7,7 @@ import { storeToRefs } from 'pinia';
import { useSceneStore } from '@/store/scene'
import { Form } from 'jetlinks-ui-components'
import { queryProductList } from '@/api/device/product'
import {query as deviceQuery, detail as deviceDetailQuery} from '@/api/device/instance'
import {query as deviceQuery, detail as deviceDetailQuery, queryNoPagingPost} from '@/api/device/instance'
import { getTreeData_api } from '@/api/system/department'
import {queryDetailListNoPaging} from "@/api/device/firmware";
@ -36,24 +36,29 @@ const check = async (): Promise<boolean> => {
//
if (deviceTrigger.selector === 'fixed') { //
const deviceResp = await queryDetailListNoPaging({
terms: [{ column: 'id', termType: 'in', value: selectorValues?.toString() }],
context: {
"includeTags": false,
"includeBind": false,
"includeRelations": false,
"includeFirmwareInfos": false
}
})
// const deviceResp = await queryDetailListNoPaging({
// terms: [{ column: 'id', termType: 'in', value: selectorValues?.toString() }],
// context: {
// "includeTags": false,
// "includeBind": false,
// "includeRelations": false,
// "includeFirmwareInfos": false
// }
// })
const deviceResp = await queryNoPagingPost(
{
terms: [{ terms: [{ column: 'id', termType: 'in', value: selectorValues?.toString() }]}],
})
if (deviceResp.success && deviceResp.result.length !== (selectorValues!.length)) {
data.value.trigger!.device!.selectorValues = undefined
return false
}
if (selectorValues!.length === 1) {
// const deviceDetailResp = await deviceDetailQuery(selectorValues![0])
// const deviceDetail = deviceDetailResp?.result
const deviceDetail = deviceResp.result[0]
const deviceDetailResp = await deviceDetailQuery(selectorValues![0])
const deviceDetail = deviceDetailResp?.result
// const deviceDetail = deviceResp.result[0]
metadata = JSON.parse(deviceDetail?.metadata || '{}') //
}
} else if (deviceTrigger.selector === 'org') { //

View File

@ -14,7 +14,7 @@ import { EventEmitter, EventSubscribeKeys, getParams } from '@/views/rule-engine
import { getOption } from '@/views/rule-engine/Scene/Save/components/DropdownButton/util'
import { getBuildInData, getNotifyVariablesUser } from './util'
import { defineExpose } from 'vue'
import {queryDetailListNoPaging} from "@/api/device/firmware";
import {detail as getDeviceDetail, queryNoPagingPost} from "@/api/device/instance";
const sceneStore = useSceneStore();
@ -62,22 +62,17 @@ const checkDeviceDelete = async () => {
let hasDevice = false
if (item!.selectorValues) {
const deviceList = item!.selectorValues?.map(item => item.value) || []
const deviceResp = await queryDetailListNoPaging(
const deviceResp = await queryNoPagingPost(
{
terms: [{ terms: [{ column: 'id', termType: 'in', value: deviceList.toString() }]}],
context: {
"includeTags": false,
"includeBind": false,
"includeRelations": false,
"includeFirmwareInfos": false
}
})
hasDevice = deviceResp.success && deviceResp.result.length === (item!.selectorValues?.length || 0)
if (item!.selectorValues!.length === 1 && hasDevice) {
const deviceDetail = deviceResp.result?.[0]
// const deviceDetailResp = await getDeviceDetail(deviceList[0])
const deviceDetailResp = await getDeviceDetail(deviceList[0])
// const deviceDetail = deviceResp.result?.[0]
const deviceDetail = deviceDetailResp.result
metadata = JSON.parse(deviceDetail?.metadata || productDetail?.metadata || '{}') //
}
}

View File

@ -167,6 +167,18 @@ const queryOrgList = async (id: string) => {
}
};
const queryTagList = async (id:string) =>{
if (! props.notifierId) return '';
const resp = await TemplateApi.getTags(
props.notifierId,
);
if (resp.status === 200) {
return resp.result?.find((item: any) => item.id === id)?.name;
} else {
return '';
}
}
const getOptions = async (dt: any) => {
const obj = {};
//
@ -177,6 +189,9 @@ const getOptions = async (dt: any) => {
if (dt?.template?.toUser) {
obj['sendTo'] = await queryUserList(dt?.template?.toUser);
}
if (dt?.template?.toTag) {
obj['tagName'] = await queryTagList(dt?.template?.toTag);
}
}
if (props.notifyType === 'dingTalk') {
if (dt?.template?.departmentIdList) {
@ -190,10 +205,11 @@ const getOptions = async (dt: any) => {
};
const handleClick = async (dt: any) => {
console.log(dt,'dt')
if (_selectedRowKeys.value.includes(dt.id)) {
_selectedRowKeys.value = [];
emit('update:value', undefined);
emit('change', { templateName: undefined, orgName: undefined, sendTo: undefined });
emit('change', { templateName: undefined, orgName: undefined, sendTo: undefined ,tagName: undefined });
emit('update:detail', undefined);
} else {
const obj = await getOptions(dt)

View File

@ -23,7 +23,7 @@ import notice from '@/api/notice/config';
const iconMap = new Map();
iconMap.set('dingTalk', getImage('/notice/dingtalk.png'));
iconMap.set('weixin', getImage('/notice/wechat.png'));
iconMap.set('weixin', getImage('/notice/weixin-corp.png'));
iconMap.set('email', getImage('/notice/email.png'));
iconMap.set('voice', getImage('/notice/voice.png'));
iconMap.set('sms', getImage('/notice/sms.png'));

View File

@ -10,7 +10,12 @@
:label="item?.name"
v-for="(item, index) in variableDefinitions"
:key="item.id"
:required="!['file', 'user', 'org', 'tag'].includes(getType(item)) ? true : false"
:required="
!['file', 'user', 'org', 'tag'].includes(getType(item)) ||
item.id === 'calledNumber'
? true
: false
"
:rules="[
{
validator: (_rule, value) => checkValue(_rule, value, item),
@ -48,7 +53,10 @@
v-else
:item="item"
v-model:value="modelRef[item.id]"
@change="(val, _options) => onChange(val, 'build-in', index, _options)"
@change="
(val, _options) =>
onChange(val, 'build-in', index, _options)
"
/>
</j-form-item>
</j-form>
@ -82,9 +90,9 @@ const props = defineProps({
default: () => ({}),
},
options: {
type: Object,
default: () => ({})
}
type: Object,
default: () => ({}),
},
});
const emit = defineEmits(['update:value', 'change']);
@ -92,15 +100,23 @@ const emit = defineEmits(['update:value', 'change']);
const formRef = ref();
const modelRef = reactive({});
const otherColumns = ref<(string | undefined)[]>(props.options?.otherColumns || [])
const otherColumns = ref<(string | undefined)[]>(
props.options?.otherColumns || [],
);
watchEffect(() => {
Object.assign(modelRef, props?.value);
});
watchEffect(() => {
if(props?.template?.template?.sendTo && Array.isArray(props?.template?.template?.sendTo) && props?.template?.template?.sendTo?.length) {
emit('change', { sendTo: props?.template?.template?.sendTo?.join(' ') });
if (
props?.template?.template?.sendTo &&
Array.isArray(props?.template?.template?.sendTo) &&
props?.template?.template?.sendTo?.length
) {
emit('change', {
sendTo: props?.template?.template?.sendTo?.join(' '),
});
}
});
@ -109,10 +125,15 @@ const getType = (item: any) => {
};
const checkValue = (_rule: any, value: any, item: any) => {
if(!value){
if (!value) {
return Promise.resolve();
}
const type = item.expands?.businessType || item?.type;
if (
['user', 'org', 'tag', 'userIdList', 'departmentIdList'].includes(type)
) {
return Promise.resolve();
}
if (type === 'file') {
return Promise.resolve();
} else if (type === 'link') {
@ -142,10 +163,10 @@ const checkValue = (_rule: any, value: any, item: any) => {
}
}
} else if (value?.source === 'fixed' && !value?.value) {
let tip = '请输入' + item.name
if (props.notify.notifyType === 'email') {
tip = '请输入收件人'
}
let tip = '请输入' + item.name;
if (props.notify.notifyType === 'email') {
tip = '请输入收件人';
}
return Promise.reject(new Error(tip));
} else if (
value?.source === 'relation' &&
@ -181,7 +202,8 @@ const checkValue = (_rule: any, value: any, item: any) => {
if (
props.notify.notifyType &&
['sms', 'voice'].includes(props?.notify?.notifyType) &&
value?.source !== 'relation' && value?.value
value?.source !== 'relation' &&
value?.value
) {
const reg = /^[1][3-9]\d{9}$/;
if (!reg.test(value?.value)) {
@ -196,9 +218,9 @@ const checkValue = (_rule: any, value: any, item: any) => {
const onChange = (val: any, type: any, index: number, options?: string) => {
if (type === 'build-in') {
otherColumns.value[index] = options
otherColumns.value[index] = options;
} else {
otherColumns.value[index] = undefined
otherColumns.value[index] = undefined;
}
if (type === 'org') {
@ -208,24 +230,58 @@ const onChange = (val: any, type: any, index: number, options?: string) => {
} else if (type === 'user') {
emit('change', { sendTo: val, otherColumns: [] });
} else {
emit('change', { otherColumns: otherColumns.value });
emit('change', { otherColumns: otherColumns.value });
}
};
const onSave = () =>
new Promise((resolve, reject) => {
const pass = props.variableDefinitions.filter(item => ['user', 'org', 'tag'].includes(getType(item))).some(item => {
return modelRef[item.id]
})
if(!pass && props.notify.notifyType === 'weixin') {
onlyMessage('收信人,收信人部门,收信人标签至少填写一个', 'warning')
return reject(false)
const filterData = props.variableDefinitions.filter((item) =>
['user', 'org', 'tag', 'userIdList', 'departmentIdList'].includes(
getType(item),
),
);
const pass = filterData.length
? filterData.some((item) => {
if (
item.id === 'toUser' &&
modelRef[item.id]?.source === 'relation'
) {
return (
modelRef[item.id].relation?.objectId ||
modelRef[item.id].relation?.related
);
} else if (item.id === 'userIdList') {
return (
modelRef[item.id]?.value ||
modelRef[item.id]?.relation?.objectId
);
} else {
return modelRef[item.id]?.value;
}
})
: true;
if (!pass && props.notify.notifyType === 'weixin') {
onlyMessage(
'收信人,收信人部门,收信人标签至少填写一个',
'warning',
);
return reject(false);
}
formRef.value?.validate().then((_data: any) => {
resolve(_data);
}).catch(() => {
reject(false)
})
if (!pass && props.notify.notifyType === 'dingTalk') {
onlyMessage('收信人,收信人部门至少填写一个', 'warning');
return reject(false);
}
formRef.value
?.validate()
.then((_data: any) => {
resolve(_data);
})
.catch(() => {
reject(false);
});
});
defineExpose({ onSave });

View File

@ -5,6 +5,7 @@
placeholder="请选择组织"
:tree-data="departmentTree"
@change="onChange"
allowClear
:fieldNames="{
label: 'name',
value: 'id',

View File

@ -4,6 +4,7 @@
v-model:value="keys"
placeholder="请选择标签"
:options="tagsList"
allowClear
@change="onChange"
/>
</template>

View File

@ -44,6 +44,7 @@
:dropdown-style="{ maxHeight: '400px', overflow: 'auto' }"
:value="relationData"
showSearch
allowClear
treeNodeFilterProp="title"
>
<template #title="{ key, username, title }">
@ -72,6 +73,7 @@
:dropdown-style="{ maxHeight: '400px', overflow: 'auto' }"
:value="relationData"
showSearch
allowClear
treeNodeFilterProp="title"
>
<template #title="{ key, username, title }">
@ -97,6 +99,7 @@
placeholder="请选择收信人"
:value="value?.value"
showSearch
allowClear
@change="
(val, option) =>
onChange(
@ -219,6 +222,7 @@ const treeData = ref<any[]>([
const mySource = ref<string>('relation');
const treeDataMap = new Map()
const getRelationUsers = async (notifyType: string, notifierId: string) => {
let resp = undefined;
if (notifyType === 'dingTalk') {
@ -294,6 +298,7 @@ const sourceChange = (v: any) => {
emit('update:value', {
source: v,
});
emit('change',undefined)
};
const getObj = (
@ -375,7 +380,6 @@ const onChange = (
const _isRelation = item?.isRelation
_values = getObj(_source, _value, _isRelation);
}
emit('update:value', _values);
emit('change', _names.filter((item) => !!item).join(','));
};

View File

@ -100,10 +100,10 @@
<script setup lang="ts">
import { queryAlarmPage } from '@/api/rule-engine/scene';
import AlarmModal from "./AlarmModal.vue";
import {unBindAlarm} from "@/api/rule-engine/configuration";
import {queryBindScene, unBindAlarm, unbindScene} from "@/api/rule-engine/configuration";
import {onlyMessage} from "@/utils/comm";
import { EventEmitter } from '@/views/rule-engine/Scene/Save/util'
import {useAlarmLevel} from "@/hook";
import {useAlarmLevel, useRequest} from "@/hook";
const props = defineProps({
id: {
@ -131,6 +131,18 @@ const visible = ref(false)
const tableRef = ref()
const { levelMap } = useAlarmLevel();
const { data: activeKeys } = useRequest<any, Record<string, any>>(queryBindScene, {
defaultParams: { terms: [{ column: 'ruleId', value: props.id}]},
onSuccess(res) {
const _result = res.result.data || []
return _result.reduce((prev: Record<string, any>, next: { branchIndex: string, ruleId: string }) => {
prev[next.ruleId] = next.branchIndex
return prev
}, {})
},
defaultValue: []
})
const map = {
product: '产品',
device: '设备',
@ -193,7 +205,8 @@ const showAlarm = () => {
}
const unBind = async (record: any) => {
const res = await unBindAlarm(props.id, record.id, [props.actionId])
const branchId = activeKeys.value![props.id]
const res = branchId === -1 ? await unbindScene(record.id, [props.id]) : await unBindAlarm(props.id, record.id, [props.actionId || props.branchId])
if (res.success) {
tableRef.value.reload()
onlyMessage('操作成功!')

View File

@ -1,15 +1,6 @@
<template>
<div class='actions-terms'>
<TitleComponent data='执行动作' style='font-size: 14px;' >
<template #extra>
<j-switch
v-model:checked='open'
@change='change'
checkedChildren='开'
unCheckedChildren='关'
style='margin-left: 4px;'
/>
</template>
</TitleComponent>
<!-- <template v-if='open'>-->
<div>
@ -25,6 +16,16 @@
{{ b.branchName || `条件${i+1}` }}
</TermsTabPane>
</template>
<div class="filterConditionSwitch">
<span>执行</span>
<j-switch
v-model:checked='b.openFilter'
@change='(e)=>change(e,b,i)'
checkedChildren='开'
unCheckedChildren='关'
style='margin-left: 4px;'
/>
</div>
<template v-for='(item, index) in data.branches'>
<template v-if="index >= b.start && index < (b.start + b.len)">
<Branches
@ -94,7 +95,6 @@ import {queryBindScene, unBindAlarmMultiple} from "@/api/rule-engine/configurati
const sceneStore = useSceneStore()
const { data } = storeToRefs(sceneStore)
const open = ref<boolean>(false)
const columnOptions = ref<any>([])
const group = ref<Array<{ id: string, len: number}>>([])
const activeKey = ref('')
@ -103,15 +103,42 @@ const conditionName = ref<any>()
provide(ContextKey, columnOptions)
const change = (e: boolean) => {
group.value = []
activeKey.value = ''
if (!e) {
data.value.branches!.length = 1
data.value.branches![0].when = []
} else {
data.value.branches!.push(null as any)
data.value.branches![0].when = [
const change = (e: boolean,groupItem:any,index:number) => {
// group.value = []
// activeKey.value = ''
// if (!e) {
// data.value.branches!.length = 1
// data.value.branches![0].when = []
// } else {
// data.value.branches!.push(null as any)
// data.value.branches![0].when = [
// {
// terms: [
// {
// column: undefined,
// value: {
// source: 'fixed',
// value: undefined
// },
// termType: undefined,
// key: `params_${randomString()}`,
// type: 'and',
// },
// ],
// type: 'and',
// key: `terms_${randomString()}`,
// },
// ]
// }
const start = groupItem.start
const len = groupItem.len
if(!e){
data.value.branches?.splice(start + 1 , len - 1)
data.value.branches![start].when = []
data.value.options!.when.splice(start,len - 1)
}else{
data.value.branches!.splice(start + 1,0,null)
data.value.branches![start].when = [
{
terms: [
{
@ -141,9 +168,10 @@ const queryColumn = (dataModel: FormModelType) => {
}
const addBranches = (len: number) => {
const key = randomNumber()
const branchesItem = {
when: [],
key: randomNumber(),
key: key,
shakeLimit: {
enabled: false,
time: 1,
@ -151,12 +179,13 @@ const addBranches = (len: number) => {
alarmFirst: false,
},
then: [],
branchId: randomNumber()
branchId: key
}
// const lastIndex = data.value.branches!.length - 1 || 0
data.value.branches?.splice(len - 1, 1, branchesItem)
data.value.options!.when.splice(len - 1, 1, {
terms: []
terms: [],
key
})
}
@ -378,12 +407,6 @@ watchEffect(() => {
watchEffect(() => {
const branches = data.value.branches
if (data.value.branches?.filter(item => item).length) {
open.value = !!data.value.branches[0].when.length
} else {
open.value = true
}
let _group = []
let _branchesIndex = 0
if (branches) {
@ -407,8 +430,10 @@ watchEffect(() => {
branchId: item.branchId,
// branchName: item.branchName || whenItem?.branchName || ` ${_branchesIndex + 1}`,
branchName: item.branchName || whenItem?.branchName || `条件`,
groupIndex: _branchesIndex
groupIndex: _branchesIndex,
openFilter: !!item.when.length
}
} else {
_group[lastIndex].len += 1
}
@ -444,4 +469,13 @@ defineExpose({
}
}
}
.filterConditionSwitch{
display: flex;
gap: 16px;
align-items: center;
margin-bottom: 16px;
font-weight: 800;
font-size: 14px;
line-height: 32px;
}
</style>

View File

@ -15,6 +15,7 @@
:hex="tagInfo.color"
:rgba="tagInfo.color"
:themeColor="themeColor"
:show-opacity="false"
@change="changeColor"
/>
</a-form-item>
@ -84,8 +85,8 @@ const themeColor = [
'#FF85C0'
]
const submit = () => {
loading.value = true
form.value.validate().then(async () => {
loading.value = true
let id;
if (props.editType === 'add') {
id = randomString();
@ -96,7 +97,9 @@ const submit = () => {
id,
name: tagInfo.name,
};
const res = await saveTag(submitData);
const res = await saveTag(submitData).finally(()=>{
loading.value = false
});
if (res.success) {
colorData.value[id] = tagInfo.color;
const saveRes = await saveTagsColor(colorData.value).catch(()=>{

View File

@ -83,6 +83,11 @@ const addTag = () => {
editType.value = 'add';
};
const buildInTag = ['weekend', 'holiday', 'workday'];
const defaultColor = new Map()
defaultColor.set('weekend', 'rgb(149, 222, 100)')
defaultColor.set('holiday', 'rgb(161, 180, 204)')
defaultColor.set('workday', 'rgba(105,177,255,1')
const createDrag = () => {
new Draggable(tags.value, {
itemSelector: '.tagName',
@ -111,6 +116,7 @@ const queryTagsData = async () => {
}
if (buildInTag.includes(i.id)) {
disabled = true;
color = defaultColor.get(i.id);
}
return {
...i,

View File

@ -202,8 +202,9 @@ const queryData = (first?: Boolean, searchName?: any) => {
if (first && res.result.length) {
selectDic(res.result[0]);
}else if(selectedKeys.value){
console.log(selectedKeys.value)
selectDic(res.result.find(i=>{
return i.id = selectedKeys.value[0]
return i.id === selectedKeys.value[0]
}))
}
}
@ -228,10 +229,10 @@ const showEdit = (data: any) => {
const reload = () => {
queryData();
};
const saveSuccess = () => {
const saveSuccess = (id?: string) => {
saveShow.value = false;
selectedKeys.value = [id] ;
reload();
};
/**
*
@ -270,7 +271,7 @@ const updateDic = (data: any) => {
* 切换选中字典
*/
const selectDic = (selectKeys: any) => {
selectedKeys.value = [selectKeys.id];
selectedKeys.value = [selectKeys.id]
emit('selectData', selectKeys);
};
/**

View File

@ -109,7 +109,7 @@ const submitData = () =>{
const res = await addDictionary(form)
if(res.status === 200){
onlyMessage('保存成功!')
emit('success')
emit('success',form.id)
}else{
onlyMessage('操作失败!','error')
}

View File

@ -215,11 +215,13 @@ const handleOk = async () => {
_dataSorts.push(USER_CENTER_MENU_DATA);
const res = await updateMenus(_dataSorts).catch(() => {});
if (res?.status === 200) {
loading.value = false;
visible.value = false;
onlyMessage('操作成功', 'success');
location.reload();
setTimeout(() => {
location.reload();
}, 100);
}
loading.value = false;
visible.value = false;
};
const handleCancel = () => {
visible.value = false;

View File

@ -22,7 +22,7 @@
<h5>接口描述</h5>
<div>{{ props.selectApi.description }}</div>
</div>
<div class="api-card" v-if="requestCard.codeText">
<div class="api-card" v-if="requestCard.codeText !== undefined">
<h5>请求示例</h5>
<JsonViewer :value="requestCard.codeText" copyable />
</div>
@ -154,7 +154,9 @@ const requestCard = reactive<cardType>({
// schemaJava
if (!_ref) {
const type = schema.type || '';
console.log(type,'type')
requestCard.codeText = dealNoRef(type, schema);
console.log(requestCard.codeText)
} else {
const schemaName = _ref?.split('/').pop();
const type = schema.type || '';

View File

@ -66,10 +66,10 @@
max: 64,
message: '最多输入64个字符',
},
{
validator: vailName,
trigger: 'blur',
},
// {
// validator: vailName,
// trigger: 'blur',
// },
]"
>
<j-input
@ -86,10 +86,10 @@
required: true,
message: '请输入区划代码',
},
{
validator: vailCode,
trigger: 'blur',
},
// {
// validator: vailCode,
// trigger: 'blur',
// },
]"
>
<j-input-number
@ -156,7 +156,7 @@ import type {PropType} from 'vue';
import {reactive, ref, watch} from 'vue';
import BuildIn from './BuildIn.vue';
import {updateRegion, validateName, validateCode} from '@/api/system/region';
import {omit} from "lodash-es";
import {cloneDeep, omit} from "lodash-es";
import {onlyMessage} from "@/utils/comm";
import RadioButton from '@/components/CardSelect/RadioButton.vue'
import GeoJsonModal from './GeoJsonModal.vue'
@ -293,8 +293,8 @@ const handleSave = () => {
newData.parentId = newData.parentId || ''
if (newData.properties.sync) {
const _syncChildren = syncChildren(newData.code, props.areaTree)
const arr = cloneDeep(props.areaTree)
const _syncChildren = syncChildren(newData.code, arr)
const different = _syncChildren.filter(item => {
if (newData.children && newData.children.some(oldItem => oldItem.code === item.code)) {
return false
@ -310,7 +310,10 @@ const handleSave = () => {
newData.children = [
...(newData.children || []),
...different
]
].map(item=>{
const {id,...extra} =item
return extra
})
}
loading.value = true;

View File

@ -95,8 +95,8 @@ function openEdit() {
}
function layerSetData(geoJson: Record<string, any>, edit = true) {
regionState.type = geoJson.features[0].properties.type
mapRef.value?.showGeoJson(geoJson.features[0].geometry.coordinates)
regionState.type = geoJson?.features?.[0]?.properties?.type
mapRef.value?.showGeoJson(geoJson?.features?.[0]?.geometry?.coordinates)
if (edit) {
mapRef.value?.openEdit()
}

View File

@ -46,8 +46,9 @@
}}</Ellipsis>
</div>
<div
@click="(e) => e.stopPropagation()"
v-if="item.id !== 'default_group'"
v-if="item.id !== 'default_group' && admin"
>
<PermissionButton
type="text"
@ -94,6 +95,7 @@ import {
import { randomString } from '@/utils/utils';
import { useUserInfo } from '@/store/userInfo';
import { storeToRefs } from 'pinia';
const emit = defineEmits(['selectData']);
const userInfoStore = useUserInfo();
const { userInfos } = storeToRefs(userInfoStore);