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 * @param id ID
* @returns * @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)
/** /**
* *
@ -666,20 +666,20 @@ export const queryProductThreshold = (productId: string, propertyId: string,hidd
/** /**
* - * -
* @param productId * @param productId
* @param propertyId * @param propertyId
* @returns * @returns
*/ */
export const deleteProductThreshold = (productId:string,propertyId:string,data:any) => server.remove(`/message/preprocessor/product/${productId}/property/${propertyId}`,data) export const deleteProductThreshold = (productId:string,propertyId:string,data:any) => server.remove(`/message/preprocessor/product/${productId}/property/${propertyId}`,data)
/** /**
* - * -
* @param productId * @param productId
* @param propertyId * @param propertyId
* @returns * @returns
*/ */
export const deleteDeviceThreshold = (productId:string,deviceId:string,propertyId:string,data:any) => server.remove(`/message/preprocessor/device/${productId}/${deviceId}/property/${propertyId}`,data) export const deleteDeviceThreshold = (productId:string,deviceId:string,propertyId:string,data:any) => server.remove(`/message/preprocessor/device/${productId}/${deviceId}/property/${propertyId}`,data)
export const getTemplate = (id: string, format: string) => `${BASE_API_PATH}/device/instance/${id}/property-metadata/template.${format}` 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 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 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 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 showSearch
:options="options" :options="options"
v-model:value="record.id" v-model:value="record.id"
:getPopupContainer="(node) => tableWrapperRef || node" :getPopupContainer="getPopupContainer"
size="small" size="small"
style="width: 100%;" style="width: 100%;"
:virtual="true" :virtual="true"
@ -105,7 +105,7 @@
size="small" size="small"
style="width: 100%;" style="width: 100%;"
:virtual="true" :virtual="true"
:getPopupContainer="(node) => tableWrapperRef || node" :getPopupContainer="getPopupContainer"
:dropdownStyle="{ :dropdownStyle="{
zIndex: 1072 zIndex: 1072
}" }"
@ -202,6 +202,7 @@ import { getWebSocket } from '@/utils/websocket';
import {useTableWrapper} from "@/components/Metadata/Table/context"; import {useTableWrapper} from "@/components/Metadata/Table/context";
import { onlyMessage } from '@/utils/comm'; import { onlyMessage } from '@/utils/comm';
import {message} from "ant-design-vue"; import {message} from "ant-design-vue";
import { useTableFullScreen} from "@/components/Metadata/Table/context";
const props = defineProps({ const props = defineProps({
virtualRule: Object as PropType<Record<any, any>>, virtualRule: Object as PropType<Record<any, any>>,
@ -222,7 +223,7 @@ type propertyType = {
const property = ref<propertyType[]>([]); const property = ref<propertyType[]>([]);
const tag = ref<Array<any>>([]); const tag = ref<Array<any>>([]);
const tableWrapperRef = useTableWrapper() const tableWrapperRef = useTableWrapper()
const fullScreen = useTableFullScreen()
const columns = [ const columns = [
{ {
title: '属性名称', title: '属性名称',
@ -287,6 +288,14 @@ const ruleEditorStore = useRuleEditorStore();
const time = ref<number>(0); const time = ref<number>(0);
const timer = ref<any>(null); const timer = ref<any>(null);
//
const getPopupContainer = (node: any) => {
if (fullScreen.value) {
return tableWrapperRef.value || node
}
return document.body
}
const runScript = () => { const runScript = () => {
const propertiesList = medataSource?.value || [] const propertiesList = medataSource?.value || []
const _properties = property.value.map((item: any) => { 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) 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) || [] const error = res?.filter(item => item.field === filedName.value) || []
if (error.length === 0) { if (error.length === 0) {
hideErrorTip() hideErrorTip()

View File

@ -1,8 +1,8 @@
<template> <template>
<a-form-item :name="name" :rules="rules" :validate-first="true"> <a-form-item :name="name" :rules="rules" :validate-first="true">
<template #label> <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> </template>
<Content ref="tableRef" v-model:value="dataSource" @change="change" /> <Content ref="tableRef" v-model:value="dataSource" @change="change" />
</a-form-item> </a-form-item>

View File

@ -1,232 +1,347 @@
<template> <template>
<PopoverModal <PopoverModal
v-model:visible="visible" v-model:visible="visible"
:placement="placement" :placement="placement"
@ok="onOk" @ok="onOk"
@cancel="onCancel" @cancel="onCancel"
> >
<template #content> <template #content>
<div style="width: 750px"> <div style="width: 750px">
<EditTable <EditTable
ref="tableRef" ref="tableRef"
:columns="myColumns" :columns="myColumns"
:dataSource="dataSource" :dataSource="dataSource"
:pagination="false" :pagination="false"
:height="200" :height="200"
> >
<!-- <template v-for="(_, key) in slots" :key="key" #[key]="slotData">--> <!-- <template v-for="(_, key) in slots" :key="key" #[key]="slotData">-->
<!-- <slot :name="key" v-bind="slotData"/>--> <!-- <slot :name="key" v-bind="slotData"/>-->
<!-- </template>--> <!-- </template>-->
<template #id="{ record, index }"> <template #id="{ record, index }">
<EditTableFormItem :name="[index, 'id']"> <EditTableFormItem :name="[index, 'id']">
<a-input v-model:value="record.id" placeholder="请输入标识"/> <a-input
</EditTableFormItem> v-model:value="record.id"
</template> placeholder="请输入标识"
<template #name="{ record, index }"> />
<EditTableFormItem :name="[index, 'name']"> </EditTableFormItem>
<a-input v-model:value="record.name" placeholder="请输入名称"/> </template>
</EditTableFormItem> <template #name="{ record, index }">
</template> <EditTableFormItem :name="[index, 'name']">
<template #expands="{ record }"> <a-input
<BooleanSelect v-model:value="record.expands.required"/> v-model:value="record.name"
</template> placeholder="请输入名称"
<template #valueType="{ record, index }"> />
<EditTableFormItem :name="[index, 'valueType']"> </EditTableFormItem>
<div style="display: flex; gap: 12px; align-items: center"> </template>
<TypeSelect v-model:value="record.valueType.type" style="flex: 1 1 0;min-width: 0" /> <template #expands="{ record }">
<DoubleParams v-if="['float', 'double'].includes(record.valueType.type)" v-model:value="record.valueType" placement="topRight"/> <BooleanSelect
<StringParams v-else-if="record.valueType.type === 'string'" v-model:value="record.valueType" placement="topRight"/> v-model:value="record.expands.required"
<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"/> </template>
<EnumParams v-else-if="record.valueType.type === 'enum'" v-model:value="record.valueType.elements" placement="topRight"/> <template #valueType="{ record, index }">
<BooleanParams <EditTableFormItem :name="[index, 'valueType']">
v-else-if="record.valueType.type === 'boolean'" <div
v-model:falseText="record.valueType.falseText" style="
v-model:falseValue="record.valueType.falseValue" display: flex;
v-model:trueText="record.valueType.trueText" gap: 12px;
v-model:trueValue="record.valueType.trueValue" align-items: center;
placement="topRight" "
/> >
<ArrayParams v-else-if="record.valueType.type === 'array'" v-model:value="record.valueType.elementType" placement="topRight"/> <TypeSelect
</div> v-model:value="record.valueType.type"
</EditTableFormItem> style="flex: 1 1 0; min-width: 0"
</template> />
<template #action="{ index }"> <DoubleParams
<a-button danger type="link" style="padding: 0 5px" @click="() => deleteItem(index)"> v-if="
<template #icon> ['float', 'double'].includes(
<AIcon type="DeleteOutlined" /> record.valueType.type,
</template> )
</a-button> "
</template> v-model:value="record.valueType"
</EditTable> placement="topRight"
<a-button style="width: 100%;margin-top: 4px" @click="addItem"> />
<template #icon><AIcon type="PlusOutlined" /></template> <StringParams
新增 v-else-if="
</a-button> record.valueType.type === 'string'
</div> "
</template> v-model:value="record.valueType"
<slot> placement="topRight"
<a-button type="link" :disabled="disabled" style="padding: 0"> />
<template #icon> <DateParams
<AIcon type="EditOutlined" :class="{'table-form-required-aicon': !value.length}"/> 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> </template>
</a-button> <slot>
</slot> <a-button type="link" :disabled="disabled" style="padding: 0">
</PopoverModal> <template #icon>
<AIcon
type="EditOutlined"
:class="{ 'table-form-required-aicon': !value.length }"
/>
</template>
</a-button>
</slot>
</PopoverModal>
</template> </template>
<script setup name="MetadataObject"> <script setup name="MetadataObject">
import { PopoverModal } from '../index' import { PopoverModal } from '../index';
import BooleanSelect from "../BooleanSelect/index.vue"; import BooleanSelect from '../BooleanSelect/index.vue';
import { EditTable, TypeSelect, EditTableFormItem, StringParams, DateParams, FileParams, EnumParams, BooleanParams, ObjectParams, ArrayParams, DoubleParams } from '@/components/Metadata/Table' import {
import {Form} from "ant-design-vue"; EditTable,
TypeSelect,
EditTableFormItem,
StringParams,
DateParams,
FileParams,
EnumParams,
BooleanParams,
ObjectParams,
ArrayParams,
DoubleParams,
} from '@/components/Metadata/Table';
import { Form } from 'ant-design-vue';
const props = defineProps({ const props = defineProps({
value: { value: {
type: Array, type: Array,
default: () => [], default: () => [],
}, },
placement: { placement: {
type: String, type: String,
default: 'top', default: 'top',
}, },
type: { type: {
type: String, type: String,
default: 'properties' default: 'properties',
}, },
disabled: { disabled: {
type: Boolean, type: Boolean,
default:false 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 emit = defineEmits(['update:value', 'confirm', 'cancel']);
const formItemContext = Form.useInjectFormItemContext(); const formItemContext = Form.useInjectFormItemContext();
const slots = useSlots() const slots = useSlots();
const tableRef = ref() const tableRef = ref();
const dataSource = ref([]) const dataSource = ref([]);
const visible = ref(false) const visible = ref(false);
const defaultColumns = [ const defaultColumns = [
{ {
title: '参数标识', title: '参数标识',
dataIndex: 'id', dataIndex: 'id',
form: { form: {
rules: [ rules: [
{ {
asyncValidator(_, value, ...setting) { asyncValidator(_, value, ...setting) {
if (value) { if (value) {
const option = setting[2] const option = setting[2];
const isSome = dataSource.value.some((item) => { const isSome = dataSource.value.some((item) => {
return item.__dataIndex !== option.index && item.id === value return (
}) item.__dataIndex !== option.index &&
item.id === value
);
});
if (isSome) { if (isSome) {
return Promise.reject('该标识已存在') return Promise.reject('该标识已存在');
} }
return Promise.resolve() return Promise.resolve();
} }
return Promise.reject('请输入标识') 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(() => { const myColumns = computed(() => {
return [
return [ ...defaultColumns.filter((item) => !!item),
...defaultColumns.filter(item => !!item), {
{ dataIndex: 'action',
dataIndex: 'action', title: '操作',
title: '操作', width: 60,
width: 60, },
} ];
] });
})
const onOk = async () => { const onOk = async () => {
const data = await tableRef.value.validate() const data = await tableRef.value.validate();
if (data) { if (data) {
visible.value = false visible.value = false;
emit('update:value', data) emit('update:value', data);
emit('confirm', data) emit('confirm', data);
formItemContext.onFieldChange() formItemContext.onFieldChange();
} }
} };
const onCancel = () => { const onCancel = () => {
emit('cancel'); emit('cancel');
}; };
const deleteItem = (index) => { const deleteItem = (index) => {
dataSource.value.splice(index, 1) dataSource.value.splice(index, 1);
} };
const addItem = () => { const addItem = () => {
dataSource.value.push({ dataSource.value.push({
id: undefined, id: undefined,
name: undefined, name: undefined,
expands: { expands: {
required: false required: false,
},
valueType: {
expands: {},
},
});
};
watch(
() => [JSON.stringify(props.value), visible.value],
(val) => {
if (visible.value) {
dataSource.value = JSON.parse(val[0] || '[]');
}
}, },
valueType: { { immediate: true },
expands: {} );
}
})
}
watch(() => [JSON.stringify(props.value), visible.value], (val) => {
if (visible.value) {
dataSource.value = JSON.parse(val[0] || '[]')
}
}, { immediate: true })
</script> </script>
<style scoped> <style scoped></style>
</style>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -260,7 +260,7 @@ export const useColumns = (dataSource: Ref<MetadataItem[]>, type?: MetadataType,
{ {
asyncValidator: async (rule: any, value: any) => { asyncValidator: async (rule: any, value: any) => {
const source = value.source const source = value?.source
if (source) { if (source) {
if (source === 'device' && !value.type?.length) { if (source === 'device' && !value.type?.length) {
return Promise.reject('请选择读写类型'); 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 {getTemplate as getProductTemplate} from '@/api/device/product'
import {downloadFileByUrl} from "@/utils/utils"; import {downloadFileByUrl} from "@/utils/utils";
import {useGroupActive, useTableWrapper} from "@/components/Metadata/Table/context"; import {useGroupActive, useTableWrapper} from "@/components/Metadata/Table/context";
import { useProductStore } from '@/store/product';
const props = defineProps({ const props = defineProps({
target: { target: {
@ -82,7 +83,7 @@ const props = defineProps({
}) })
const emit = defineEmits(['ok']) const emit = defineEmits(['ok'])
const { current } = useProductStore()
const visible = ref(false); const visible = ref(false);
const successCount = ref(0); const successCount = ref(0);
const errorCount = ref(0); const errorCount = ref(0);
@ -117,7 +118,6 @@ const onCancel = () => {
} }
const submitData = async (metadataStr) => { const submitData = async (metadataStr) => {
console.log(metadataStr)
if (metadataStr) { if (metadataStr) {
const _metadataObject = JSON.parse(metadataStr || "{}") const _metadataObject = JSON.parse(metadataStr || "{}")
const properties = _metadataObject.properties const properties = _metadataObject.properties
@ -176,7 +176,7 @@ const beforeUpload = (file) => {
formData.append('file', file) formData.append('file', file)
step.value = 2 step.value = 2
uploadAnalyzeMetadata(formData).then(res => { uploadAnalyzeMetadata(current.id , formData).then(res => {
if (res.success) { if (res.success) {
submitData(res.result) submitData(res.result)
} }
@ -189,7 +189,6 @@ const beforeUpload = (file) => {
const uploadChange = async (info) => { const uploadChange = async (info) => {
console.log(info)
if (info.file.status === 'uploading') { if (info.file.status === 'uploading') {
step.value = 2 step.value = 2
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -76,4 +76,4 @@ watch(() => JSON.stringify(props.value), () => {
} }
} }
} }
</style> </style>

View File

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

View File

@ -50,7 +50,7 @@
<div>开始录像</div> <div>开始录像</div>
<template #overlay> <template #overlay>
<j-menu @click="recordStart"> <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 style="padding-right: 12px"
>本地存储</span >本地存储</span
> >
@ -129,7 +129,7 @@
</MediaTool> </MediaTool>
</div> </div>
<Preset <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" :data="data"
@refresh="onRefresh" @refresh="onRefresh"
/> />

View File

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

View File

@ -128,140 +128,404 @@
/> />
</j-form-item> </j-form-item>
</j-col> </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 <template
style="width: 100%" v-if="formData.provider === 'embedded-zlmedia'"
:min="1" >
:max="65535" <j-col :span="24">
:precision="0" <j-form-item
placeholder="请输入端口" :name="['configuration', 'rtpIp']"
v-model:value=" :rules="[
formData.configuration.rtpPort {
" required: true,
/> message: '请输入IP',
</j-form-item> },
</j-col> {
<j-col :span="4" v-if="checked"> validator: validateAddress,
<j-form-item message:
class="form-item" '请输入正确的IP地址或者域名',
: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 #label>
</j-checkbox> IP
</j-form-item> <j-tooltip
</j-col> 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-col :span="24">
<j-form-item> <j-form-item>
@ -309,10 +573,7 @@ const Validator = {
const validateAddress = (_rule: any, value: string): Promise<any> => const validateAddress = (_rule: any, value: string): Promise<any> =>
new Promise(async (resolve, reject) => { new Promise(async (resolve, reject) => {
if ( if (testIpv4_6(value) || Validator.regDomain.test(value)) {
testIpv4_6(value) ||
Validator.regDomain.test(value)
) {
return resolve(''); return resolve('');
} else { } else {
return value ? reject('请输入正确的IP地址或者域名') : resolve(''); return value ? reject('请输入正确的IP地址或者域名') : resolve('');
@ -328,6 +589,8 @@ const formData = ref<FormDataType>({
apiPort: '', apiPort: '',
rtpIp: '', rtpIp: '',
rtpPort: '', rtpPort: '',
rtspPort: '',
rtmpPort: '',
dynamicRtpPort: false, dynamicRtpPort: false,
// dynamicRtpPortRange: [], // dynamicRtpPortRange: [],
dynamicRtpPortRange0: '', dynamicRtpPortRange0: '',
@ -413,5 +676,12 @@ watch(
height: 30px; height: 30px;
padding-bottom: 8px; padding-bottom: 8px;
} }
.item{
padding-top: 6px;
border: 1px solid #d1d1d1;
background-color: #e0e0e007;
border-radius: 4px;
margin-bottom: 10px;
}
} }
</style> </style>

View File

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

View File

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

View File

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

View File

@ -142,62 +142,6 @@ const _vis = computed({
* 获取通知模板 * 获取通知模板
*/ */
const configList = ref<BindConfig[]>([]); 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 = [ const columns = [
{ {
@ -236,7 +180,74 @@ const formData = ref<{
*/ */
const formRef = ref(); const formRef = ref();
const btnLoading = ref(false); 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 = () => { 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 formRef.value
.validate() .validate()
.then(async () => { .then(async () => {
@ -267,6 +278,16 @@ const handleCancel = () => {
formRef.value.resetFields(); formRef.value.resetFields();
formData.value.templateDetailTable = []; formData.value.templateDetailTable = [];
}; };
watch(
() => _vis.value,
(val) => {
if (val) {
getConfigList();
getTemplateDetail();
}
},
);
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -38,7 +38,7 @@
<script setup name="SceneDrawer"> <script setup name="SceneDrawer">
import {useRequest} from "@/hook"; 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 {handleGroupAndFilter, typeMap} from "./Save/utils";
import BranchesTabs from './Save/BranchesTabs.vue' import BranchesTabs from './Save/BranchesTabs.vue'
import { unBindAlarm, bindScene } from "@/api/rule-engine/configuration"; import { unBindAlarm, bindScene } from "@/api/rule-engine/configuration";
@ -97,11 +97,13 @@ const handleBind = (id, selected) => {
loading.value = false loading.value = false
}) })
} else { } 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) activeKeys.value = activeKeys.value.filter(key => key !== id)
}).finally(() => { }).finally(() => {
loading.value = false loading.value = false
}) })
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -7,7 +7,7 @@ import { storeToRefs } from 'pinia';
import { useSceneStore } from '@/store/scene' import { useSceneStore } from '@/store/scene'
import { Form } from 'jetlinks-ui-components' import { Form } from 'jetlinks-ui-components'
import { queryProductList } from '@/api/device/product' 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 { getTreeData_api } from '@/api/system/department'
import {queryDetailListNoPaging} from "@/api/device/firmware"; import {queryDetailListNoPaging} from "@/api/device/firmware";
@ -36,24 +36,29 @@ const check = async (): Promise<boolean> => {
// //
if (deviceTrigger.selector === 'fixed') { // if (deviceTrigger.selector === 'fixed') { //
const deviceResp = await queryDetailListNoPaging({ // const deviceResp = await queryDetailListNoPaging({
terms: [{ column: 'id', termType: 'in', value: selectorValues?.toString() }], // terms: [{ column: 'id', termType: 'in', value: selectorValues?.toString() }],
context: { // context: {
"includeTags": false, // "includeTags": false,
"includeBind": false, // "includeBind": false,
"includeRelations": false, // "includeRelations": false,
"includeFirmwareInfos": false // "includeFirmwareInfos": false
} // }
}) // })
const deviceResp = await queryNoPagingPost(
{
terms: [{ terms: [{ column: 'id', termType: 'in', value: selectorValues?.toString() }]}],
})
if (deviceResp.success && deviceResp.result.length !== (selectorValues!.length)) { if (deviceResp.success && deviceResp.result.length !== (selectorValues!.length)) {
data.value.trigger!.device!.selectorValues = undefined data.value.trigger!.device!.selectorValues = undefined
return false return false
} }
if (selectorValues!.length === 1) { if (selectorValues!.length === 1) {
// const deviceDetailResp = await deviceDetailQuery(selectorValues![0]) const deviceDetailResp = await deviceDetailQuery(selectorValues![0])
// const deviceDetail = deviceDetailResp?.result const deviceDetail = deviceDetailResp?.result
const deviceDetail = deviceResp.result[0] // const deviceDetail = deviceResp.result[0]
metadata = JSON.parse(deviceDetail?.metadata || '{}') // metadata = JSON.parse(deviceDetail?.metadata || '{}') //
} }
} else if (deviceTrigger.selector === 'org') { // } 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 { getOption } from '@/views/rule-engine/Scene/Save/components/DropdownButton/util'
import { getBuildInData, getNotifyVariablesUser } from './util' import { getBuildInData, getNotifyVariablesUser } from './util'
import { defineExpose } from 'vue' import { defineExpose } from 'vue'
import {queryDetailListNoPaging} from "@/api/device/firmware"; import {detail as getDeviceDetail, queryNoPagingPost} from "@/api/device/instance";
const sceneStore = useSceneStore(); const sceneStore = useSceneStore();
@ -62,22 +62,17 @@ const checkDeviceDelete = async () => {
let hasDevice = false let hasDevice = false
if (item!.selectorValues) { if (item!.selectorValues) {
const deviceList = item!.selectorValues?.map(item => item.value) || [] 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() }]}], 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) hasDevice = deviceResp.success && deviceResp.result.length === (item!.selectorValues?.length || 0)
if (item!.selectorValues!.length === 1 && hasDevice) { 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 || '{}') // 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 getOptions = async (dt: any) => {
const obj = {}; const obj = {};
// //
@ -177,6 +189,9 @@ const getOptions = async (dt: any) => {
if (dt?.template?.toUser) { if (dt?.template?.toUser) {
obj['sendTo'] = await queryUserList(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 (props.notifyType === 'dingTalk') {
if (dt?.template?.departmentIdList) { if (dt?.template?.departmentIdList) {
@ -190,10 +205,11 @@ const getOptions = async (dt: any) => {
}; };
const handleClick = async (dt: any) => { const handleClick = async (dt: any) => {
console.log(dt,'dt')
if (_selectedRowKeys.value.includes(dt.id)) { if (_selectedRowKeys.value.includes(dt.id)) {
_selectedRowKeys.value = []; _selectedRowKeys.value = [];
emit('update:value', undefined); 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); emit('update:detail', undefined);
} else { } else {
const obj = await getOptions(dt) const obj = await getOptions(dt)

View File

@ -23,7 +23,7 @@ import notice from '@/api/notice/config';
const iconMap = new Map(); const iconMap = new Map();
iconMap.set('dingTalk', getImage('/notice/dingtalk.png')); 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('email', getImage('/notice/email.png'));
iconMap.set('voice', getImage('/notice/voice.png')); iconMap.set('voice', getImage('/notice/voice.png'));
iconMap.set('sms', getImage('/notice/sms.png')); iconMap.set('sms', getImage('/notice/sms.png'));

View File

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

View File

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

View File

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

View File

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

View File

@ -100,10 +100,10 @@
<script setup lang="ts"> <script setup lang="ts">
import { queryAlarmPage } from '@/api/rule-engine/scene'; import { queryAlarmPage } from '@/api/rule-engine/scene';
import AlarmModal from "./AlarmModal.vue"; 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 {onlyMessage} from "@/utils/comm";
import { EventEmitter } from '@/views/rule-engine/Scene/Save/util' import { EventEmitter } from '@/views/rule-engine/Scene/Save/util'
import {useAlarmLevel} from "@/hook"; import {useAlarmLevel, useRequest} from "@/hook";
const props = defineProps({ const props = defineProps({
id: { id: {
@ -131,6 +131,18 @@ const visible = ref(false)
const tableRef = ref() const tableRef = ref()
const { levelMap } = useAlarmLevel(); 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 = { const map = {
product: '产品', product: '产品',
device: '设备', device: '设备',
@ -193,7 +205,8 @@ const showAlarm = () => {
} }
const unBind = async (record: any) => { 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) { if (res.success) {
tableRef.value.reload() tableRef.value.reload()
onlyMessage('操作成功!') onlyMessage('操作成功!')

View File

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

View File

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

View File

@ -83,6 +83,11 @@ const addTag = () => {
editType.value = 'add'; editType.value = 'add';
}; };
const buildInTag = ['weekend', 'holiday', 'workday']; 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 = () => { const createDrag = () => {
new Draggable(tags.value, { new Draggable(tags.value, {
itemSelector: '.tagName', itemSelector: '.tagName',
@ -111,6 +116,7 @@ const queryTagsData = async () => {
} }
if (buildInTag.includes(i.id)) { if (buildInTag.includes(i.id)) {
disabled = true; disabled = true;
color = defaultColor.get(i.id);
} }
return { return {
...i, ...i,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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