fix: merge

This commit is contained in:
100011797 2023-03-14 13:44:11 +08:00
commit d488752e20
21 changed files with 418 additions and 293 deletions

View File

@ -8,10 +8,13 @@
:breadcrumb="{ routes: breadcrumb }" :breadcrumb="{ routes: breadcrumb }"
> >
<template #breadcrumbRender="slotProps"> <template #breadcrumbRender="slotProps">
<a v-if="slotProps.route.index !== 0">{{ <a
slotProps.route.breadcrumbName v-if="slotProps.route.index !== 0 && !slotProps.route.isLast"
}}</a> @click='() => jumpPage(slotProps.route.path)'
<span v-else>{{ slotProps.route.breadcrumbName }}</span> >
{{ slotProps.route.breadcrumbName }}
</a>
<span v-else >{{ slotProps.route.breadcrumbName }}</span>
</template> </template>
<template #rightContentRender> <template #rightContentRender>
<div class="right-content"> <div class="right-content">
@ -32,6 +35,7 @@ import Notice from './components/Notice.vue';
import DefaultSetting from '../../../config/config'; import DefaultSetting from '../../../config/config';
import { useMenuStore } from '@/store/menu'; import { useMenuStore } from '@/store/menu';
import { clearMenuItem } from 'jetlinks-ui-components/es/ProLayout/util'; import { clearMenuItem } from 'jetlinks-ui-components/es/ProLayout/util';
import { AccountMenu } from '@/router/menu'
type StateType = { type StateType = {
collapsed: boolean; collapsed: boolean;
@ -50,7 +54,8 @@ const layoutConf = reactive({
siderWidth: DefaultSetting.layout.siderWidth, siderWidth: DefaultSetting.layout.siderWidth,
logo: DefaultSetting.layout.logo, logo: DefaultSetting.layout.logo,
title: DefaultSetting.layout.title, title: DefaultSetting.layout.title,
menuData: clearMenuItem(menu.siderMenus), menuData: [...clearMenuItem(menu.siderMenus), AccountMenu],
// menuData: menu.siderMenus,
splitMenus: true, splitMenus: true,
}); });
@ -61,26 +66,49 @@ const state = reactive<StateType>({
selectedKeys: [], selectedKeys: [],
}); });
const findRouteMeta = (code: string) => {
let meta = []
let menuItem: any = menu.menus[code]
while (menuItem) {
meta.unshift(menuItem)
if (menuItem.parentName) {
menuItem = menu.menus[menuItem.parentName]
} else {
menuItem = false
}
}
return meta
}
const jumpPage = (path: string) => {
console.log(path)
router.push(path)
}
const breadcrumb = computed(() => const breadcrumb = computed(() =>
router.currentRoute.value.matched.concat().map((item, index) => { {
const paths = router.currentRoute.value.name as string
const metas = findRouteMeta(paths)
return metas.map((item, index) => {
return { return {
index, index,
isLast: index === (metas.length - 1),
path: item.path, path: item.path,
breadcrumbName: item.meta.title || '', breadcrumbName: item.title || '',
}; };
}), })
}
); );
watchEffect(() => { watchEffect(() => {
if (router.currentRoute) { if (router.currentRoute) {
const matched = router.currentRoute.value.matched.concat(); const paths = router.currentRoute.value.name as string
state.selectedKeys = matched.map((r) => r.path); if (paths) {
state.openKeys = matched const _metas = findRouteMeta(paths)
.filter((r) => r.path !== router.currentRoute.value.path) state.selectedKeys = _metas.map(item => item.path)
.map((r) => r.path); state.openKeys = _metas.filter((r) => r !== router.currentRoute.value.path).map(item => item.path)
console.log(state.selectedKeys); }
} }
// TODO pure
}); });
watchEffect(() => { watchEffect(() => {

View File

@ -5,6 +5,7 @@ export const AccountMenu = {
component: () => import('@/components/Layout/BasicLayoutPage.vue'), component: () => import('@/components/Layout/BasicLayoutPage.vue'),
redirect: '/account/center', redirect: '/account/center',
name: 'account', name: 'account',
code: 'account',
meta: { meta: {
title: '个人中心', title: '个人中心',
icon: '', icon: '',
@ -14,6 +15,7 @@ export const AccountMenu = {
{ {
path: '/account/center', path: '/account/center',
name: 'account/center', name: 'account/center',
code: 'account/center',
meta: { meta: {
title: '基本设置', title: '基本设置',
icon: '', icon: '',
@ -24,6 +26,7 @@ export const AccountMenu = {
{ {
path: '/account/NotificationSubscription', path: '/account/NotificationSubscription',
name: 'account/NotificationSubscription', name: 'account/NotificationSubscription',
code: 'account/NotificationSubscription',
meta: { meta: {
title: '通知订阅', title: '通知订阅',
icon: '', icon: '',
@ -34,6 +37,7 @@ export const AccountMenu = {
{ {
path: '/account/NotificationRecord', path: '/account/NotificationRecord',
name: 'account/NotificationRecord', name: 'account/NotificationRecord',
code: 'account/NotificationRecord',
meta: { meta: {
title: '通知记录', title: '通知记录',
icon: '', icon: '',

View File

@ -1,6 +1,6 @@
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { queryOwnThree } from '@/api/system/menu' import { queryOwnThree } from '@/api/system/menu'
import { filterAsnycRouter, MenuItem } from '@/utils/menu' import { filterAsyncRouter, findCodeRoute, MenuItem } from '@/utils/menu'
import { isArray } from 'lodash-es' import { isArray } from 'lodash-es'
import { usePermissionStore } from './permission' import { usePermissionStore } from './permission'
import router from '@/router' import router from '@/router'
@ -33,6 +33,8 @@ type MenuStateType = {
menus: { menus: {
[key: string]: { [key: string]: {
buttons?: string[] buttons?: string[]
title: string
parentName: string
path: string path: string
} }
} }
@ -98,24 +100,15 @@ export const useMenuStore = defineStore({
if (resp.success) { if (resp.success) {
const permission = usePermissionStore() const permission = usePermissionStore()
permission.permissions = {} permission.permissions = {}
const { menusData, silderMenus } = filterAsnycRouter(resp.result) const { menusData, silderMenus } = filterAsyncRouter(resp.result)
this.menus = {} this.menus = findCodeRoute([...resp.result, AccountMenu])
const handleMenuItem = (menu: any) => { Object.keys(this.menus).forEach((item) => {
if (isArray(menu)) { const _item = this.menus[item]
menu.forEach(menuItem => { if (_item.buttons?.length) {
this.menus[menuItem.name] = { permission.permissions[item] = _item.buttons
path: menuItem.path,
buttons: menuItem.meta.buttons
}
permission.permissions[menuItem.name] = menuItem.meta.buttons
if (menuItem.children && menuItem.children.length) {
handleMenuItem(menuItem.children)
} }
}) })
}
}
handleMenuItem(menusData)
menusData.push({ menusData.push({
path: '/', path: '/',
redirect: menusData[0]?.path, redirect: menusData[0]?.path,
@ -124,10 +117,7 @@ export const useMenuStore = defineStore({
} }
}) })
menusData.push(AccountMenu) menusData.push(AccountMenu)
silderMenus.push(AccountMenu)
this.siderMenus = silderMenus this.siderMenus = silderMenus
console.log('menusData', menusData)
console.log('silderMenus', silderMenus)
res(menusData) res(menusData)
} }
}) })

View File

@ -3,11 +3,12 @@ import { defineStore } from "pinia";
export const usePermissionStore = defineStore({ export const usePermissionStore = defineStore({
id: 'permission', id: 'permission',
state: () => ({ state: () => ({
permissions: {} as {[key: string]: string}, permissions: {} as {[key: string]: string[]},
}), }),
getters: { getters: {
check(state) { check(state) {
return (permissionCode: string) => { return (permissionCode: string) => {
if (!permissionCode) { if (!permissionCode) {
return true return true
} }
@ -16,6 +17,7 @@ export const usePermissionStore = defineStore({
} }
const code = permissionCode.split(":")[0] const code = permissionCode.split(":")[0]
const value = permissionCode.split(":")[1] const value = permissionCode.split(":")[1]
const _buttonArray = state.permissions[code] const _buttonArray = state.permissions[code]
if (!_buttonArray) { if (!_buttonArray) {
return false return false

View File

@ -151,7 +151,6 @@ const extraRouteObj = {
const resolveComponent = (name: any) => { const resolveComponent = (name: any) => {
const importPage = pagesComponent[`../views/${name}/index.vue`]; const importPage = pagesComponent[`../views/${name}/index.vue`];
if (!importPage) { if (!importPage) {
console.warn(`Unknown page ${name}. Is it located under Pages with a .vue extension?`)
return undefined return undefined
} else { } else {
const res = () => importPage() const res = () => importPage()
@ -201,7 +200,52 @@ const findDetailRoutes = (routes: any[]): any[] => {
return newRoutes return newRoutes
} }
export function filterAsnycRouter(asyncRouterMap: any, parentCode = '', level = 1): { menusData: any, silderMenus: any } { export const findCodeRoute = (asyncRouterMap: any[]) => {
const routeMeta = {}
function findChildren (data: any[], code: string = '') {
data.forEach(route => {
routeMeta[route.code] = {
path: route.url || route.path,
title: route.meta?.title || route.name,
parentName: code,
buttons: route.buttons?.map((b: any) => b.id) || []
}
const detail = findDetailRouteItem(route.code, route.url)
if (detail) {
routeMeta[(detail as MenuItem).code] = {
path: detail.url,
title: detail.name,
parentName: route.code,
buttons: detail.buttons?.map((b: any) => b.id) || []
}
}
const otherRoutes = extraRouteObj[route.code]
if (otherRoutes) {
otherRoutes.children.map((item: any) => {
const _code = `${route.code}/${item.code}`
routeMeta[_code] = {
path: `${route.url}/${item.code}`,
title: item.name,
parentName: route.code,
buttons: item.buttons?.map((b: any) => b.id) || []
}
})
}
if (route.children) {
findChildren(route.children, route.code)
}
})
}
findChildren(asyncRouterMap)
return routeMeta
}
export function filterAsyncRouter(asyncRouterMap: any, parentCode = '', level = 1): { menusData: any, silderMenus: any} {
const _asyncRouterMap = cloneDeep(asyncRouterMap) const _asyncRouterMap = cloneDeep(asyncRouterMap)
const menusData: any[] = [] const menusData: any[] = []
const silderMenus: any[] = [] const silderMenus: any[] = []
@ -224,7 +268,7 @@ export function filterAsnycRouter(asyncRouterMap: any, parentCode = '', level =
route.children = findDetailRoutes(route.children) route.children = findDetailRoutes(route.children)
if (route.children && route.children.length) { if (route.children && route.children.length) {
// TODO 查看是否具有详情页 // TODO 查看是否具有详情页
const { menusData: _menusData, silderMenus: _silderMenus } = filterAsnycRouter(route.children, `${parentCode}/${route.code}`, level + 1) const { menusData: _menusData, silderMenus: _silderMenus } = filterAsyncRouter(route.children, `${parentCode}/${route.code}`, level + 1)
_route.children = _menusData _route.children = _menusData
silder.children = _silderMenus silder.children = _silderMenus
const showChildren = _route.children.some((r: any) => !r.meta.hideInMenu) const showChildren = _route.children.some((r: any) => !r.meta.hideInMenu)
@ -251,6 +295,6 @@ export function filterAsnycRouter(asyncRouterMap: any, parentCode = '', level =
}) })
return { return {
menusData, menusData,
silderMenus silderMenus,
} }
} }

View File

@ -95,6 +95,7 @@ import { queryFlow, list } from '@/api/iot-card/home';
import * as echarts from 'echarts'; import * as echarts from 'echarts';
import { useMenuStore } from '@/store/menu'; import { useMenuStore } from '@/store/menu';
import { usePermissionStore } from '@/store/permission'; import { usePermissionStore } from '@/store/permission';
import { message } from 'jetlinks-ui-components'
const { proxy } = <any>getCurrentInstance(); const { proxy } = <any>getCurrentInstance();
@ -178,11 +179,10 @@ const pieChartData = ref<any[]>([
]); ]);
const jumpPage = (data: GuideItemProps) => { const jumpPage = (data: GuideItemProps) => {
// if (data.url && data.auth) { if (!data.auth){
// router.push({ path: `${data.url}`, ...data.param }); message.warning('暂无权限,请联系管理员');
// } else { return
// message.warning(''); }
// }
if (data.key === 'EQUIPMENT') { if (data.key === 'EQUIPMENT') {
menuStory.jumpPage(data.url, { id: 'add' }); menuStory.jumpPage(data.url, { id: 'add' });
} else { } else {

View File

@ -9,32 +9,50 @@
:confirmLoading="btnLoading" :confirmLoading="btnLoading"
width="660px" width="660px"
> >
<j-form layout="vertical"> <j-form ref="formRef" :model="formData" layout="vertical">
<j-form-item label="产品名称" v-bind="validateInfos.name"> <j-form-item
label="产品名称"
name="name"
:rules="{ required: true, message: '请输入产品名称' }"
>
<j-input <j-input
v-model:value="formData.name" v-model:value="formData.name"
placeholder="请输入名称" placeholder="请输入名称"
/> />
</j-form-item> </j-form-item>
<template v-if="channel === 'gb28181-2016' && formData.accessId"> <template v-for="(item, index) in extendFormItem" :key="index">
<j-form-item <j-form-item
label="接入密码" :name="item.name"
v-bind="validateInfos['configuration.access_pwd']" :label="item.label"
:rules="{
required: item.required,
message: item.message,
trigger: 'change',
}"
> >
<j-input-password
v-model:value="formData.configuration.access_pwd"
placeholder="请输入接入密码"
/>
</j-form-item>
<j-form-item label="流传输模式">
<j-select <j-select
v-model:value="formData.configuration.stream_mode" v-if="item.type === 'enum'"
placeholder="请选择流传输模式" v-model:value="formData[item.name[0]][item.name[1]]"
:options="streamMode" :options="item.options"
:placeholder="item.message"
/>
<j-input-password
v-else-if="item.type === 'password'"
v-model:value="formData[item.name[0]][item.name[1]]"
:placeholder="item.message"
/>
<j-input
v-else
v-model:value="formData[item.name[0]][item.name[1]]"
:placeholder="item.message"
/> />
</j-form-item> </j-form-item>
</template> </template>
<j-form-item label="接入网关" v-bind="validateInfos.accessId"> <j-form-item
label="接入网关"
name="accessId"
:rules="{ required: true, message: '请选择接入网关' }"
>
<div class="gateway-box"> <div class="gateway-box">
<div v-if="!gatewayList.length"> <div v-if="!gatewayList.length">
暂无数据请先 暂无数据请先
@ -119,16 +137,12 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { Form, message } from 'ant-design-vue'; import { message } from 'ant-design-vue';
import { PropType } from 'vue';
import { streamMode } from '@/views/media/Device/const';
import DeviceApi from '@/api/media/device'; import DeviceApi from '@/api/media/device';
import { getImage } from '@/utils/comm'; import { getImage } from '@/utils/comm';
import { gatewayType } from '@/views/media/Device/typings'; import { gatewayType } from '@/views/media/Device/typings';
import { providerType } from '../const'; import { providerType } from '../const';
const useForm = Form.useForm;
type Emits = { type Emits = {
(e: 'update:visible', data: boolean): void; (e: 'update:visible', data: boolean): void;
(e: 'update:productId', data: string): void; (e: 'update:productId', data: string): void;
@ -168,6 +182,7 @@ const getGatewayList = async () => {
* @param e * @param e
*/ */
const _selectedRowKeys = ref<string[]>([]); const _selectedRowKeys = ref<string[]>([]);
const extendFormItem = ref<any[]>();
const handleClick = async (e: any) => { const handleClick = async (e: any) => {
_selectedRowKeys.value = [e.id]; _selectedRowKeys.value = [e.id];
formData.value.accessId = e.id; formData.value.accessId = e.id;
@ -181,7 +196,22 @@ const handleClick = async (e: any) => {
e.protocol, e.protocol,
e.transport, e.transport,
); );
console.log('result: ', result);
extendFormItem.value = result.properties.map((item: any) => ({
name: ['configuration', item.property],
label: item.name,
type: item.type?.type,
value: item.type.expands?.defaultValue,
options: item.type.elements?.map((e: any) => ({
label: e.text,
value: e.value,
})),
required: !!item.type.expands?.required,
message:
item.type?.type === 'enum'
? `请选择${item.name}`
: `请输入${item.name}`,
}));
}; };
watch( watch(
@ -189,10 +219,6 @@ watch(
(val) => { (val) => {
if (val) { if (val) {
getGatewayList(); getGatewayList();
formRules.value['configuration.access_pwd'][0].required =
props.channel === 'gb28181-2016';
validate();
} else { } else {
emit('close'); emit('close');
} }
@ -200,6 +226,7 @@ watch(
); );
// //
const formRef = ref();
const formData = ref({ const formData = ref({
accessId: '', accessId: '',
accessName: '', accessName: '',
@ -215,25 +242,13 @@ const formData = ref({
transportProtocol: '', transportProtocol: '',
}); });
//
const formRules = ref({
name: [{ required: true, message: '请输入产品名称' }],
'configuration.access_pwd': [{ required: true, message: '请输入接入密码' }],
accessId: [{ required: true, message: '请选择接入网关' }],
});
const { resetFields, validate, validateInfos, clearValidate } = useForm(
formData.value,
formRules.value,
);
/** /**
* 提交 * 提交
*/ */
const btnLoading = ref(false); const btnLoading = ref(false);
const handleOk = () => { const handleOk = () => {
// console.log('formData.value: ', formData.value); formRef.value
validate() ?.validate()
.then(async () => { .then(async () => {
btnLoading.value = true; btnLoading.value = true;
const res = await DeviceApi.saveProduct(formData.value); const res = await DeviceApi.saveProduct(formData.value);
@ -249,14 +264,14 @@ const handleOk = () => {
} }
btnLoading.value = false; btnLoading.value = false;
}) })
.catch((err) => { .catch((err: any) => {
console.log('err: ', err); console.log('err: ', err);
}); });
}; };
const handleCancel = () => { const handleCancel = () => {
_vis.value = false; _vis.value = false;
resetFields(); formRef.value.resetFields();
}; };
</script> </script>

View File

@ -50,10 +50,19 @@
<template v-else> <template v-else>
<j-form-item <j-form-item
:name="['templateDetailTable', index, 'value']" :name="['templateDetailTable', index, 'value']"
:rules="{ :rules="[
{
required: record.required, required: record.required,
message: '该字段为必填字段', message: '该字段为必填字段',
}" },
...record.otherRules,
]"
>
<template
v-if="
data.type === 'dingTalk' ||
data.type === 'weixin'
"
> >
<ToUser <ToUser
v-if="record.type === 'user'" v-if="record.type === 'user'"
@ -78,6 +87,13 @@
v-model:modelValue="record.value" v-model:modelValue="record.value"
:itemType="record.type" :itemType="record.type"
/> />
</template>
<template v-else>
<ValueItem
v-model:modelValue="record.value"
:itemType="record.type"
/>
</template>
</j-form-item> </j-form-item>
</template> </template>
</template> </template>
@ -100,6 +116,8 @@ import { message } from 'ant-design-vue';
import ToUser from '../Detail/components/ToUser.vue'; import ToUser from '../Detail/components/ToUser.vue';
import ToOrg from '../Detail/components/ToOrg.vue'; import ToOrg from '../Detail/components/ToOrg.vue';
import ToTag from '../Detail/components/ToTag.vue'; import ToTag from '../Detail/components/ToTag.vue';
import type { Rule } from 'ant-design-vue/es/form';
import { phoneRegEx } from '@/utils/validate';
type Emits = { type Emits = {
(e: 'update:visible', data: boolean): void; (e: 'update:visible', data: boolean): void;
@ -156,6 +174,26 @@ const getTemplateDetail = async () => {
...m, ...m,
type: m.expands ? m.expands.businessType : m.type, type: m.expands ? m.expands.businessType : m.type,
value: undefined, value: undefined,
//
otherRules:
m.id === 'calledNumber'
? [
{
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();
},
},
]
: '',
}), }),
); );
}; };

View File

@ -85,7 +85,7 @@
> >
<template #label> <template #label>
<span> <span>
AgentID AgentId
<j-tooltip title="应用唯一标识"> <j-tooltip title="应用唯一标识">
<AIcon <AIcon
type="QuestionCircleOutlined" type="QuestionCircleOutlined"
@ -98,7 +98,7 @@
v-model:value=" v-model:value="
formData.template.agentId formData.template.agentId
" "
placeholder="请输入AppSecret" placeholder="请输入AgentId"
/> />
</j-form-item> </j-form-item>
<j-row :gutter="10"> <j-row :gutter="10">
@ -271,7 +271,7 @@
</template> </template>
<j-input <j-input
v-model:value="formData.template.agentId" v-model:value="formData.template.agentId"
placeholder="请输入agentId" placeholder="请输入AgentId"
/> />
</j-form-item> </j-form-item>
<j-row :gutter="10"> <j-row :gutter="10">
@ -664,7 +664,6 @@
<j-radio :value="false">自定义</j-radio> <j-radio :value="false">自定义</j-radio>
</j-radio-group> </j-radio-group>
<j-textarea <j-textarea
v-model:value="formData.template.body"
placeholder="请求体中的数据来自于发送通知时指定的所有变量" placeholder="请求体中的数据来自于发送通知时指定的所有变量"
v-if="formData.template.contextAsBody" v-if="formData.template.contextAsBody"
disabled disabled
@ -902,7 +901,10 @@ const formRules = ref({
provider: [{ required: true, message: '请选择类型' }], provider: [{ required: true, message: '请选择类型' }],
configId: [{ required: true, message: '请选择绑定配置' }], configId: [{ required: true, message: '请选择绑定配置' }],
// //
'template.agentId': [{ required: true, message: '请输入agentId' }], 'template.agentId': [
{ required: true, message: '请输入AgentId' },
{ max: 64, message: '最多可输入64个字符', trigger: 'change' },
],
'template.messageType': [{ required: true, message: '请选择消息类型' }], 'template.messageType': [{ required: true, message: '请选择消息类型' }],
'template.markdown.title': [ 'template.markdown.title': [
{ required: true, message: '请输入标题', trigger: 'change' }, { required: true, message: '请输入标题', trigger: 'change' },
@ -914,7 +916,7 @@ const formRules = ref({
], ],
// 'template.url': [{ required: true, message: 'WebHook' }], // 'template.url': [{ required: true, message: 'WebHook' }],
// //
// 'template.agentId': [{ required: true, message: 'agentId' }], // 'template.agentId': [{ required: true, message: 'AgentId' }],
// //
'template.subject': [ 'template.subject': [
{ required: true, message: '请输入标题' }, { required: true, message: '请输入标题' },

View File

@ -1,27 +1,27 @@
<template> <template>
<div> <div>
<a-form layout="vertical" :rules="rule" :model="form" ref="formRef"> <j-form layout="vertical" :rules="rule" :model="form" ref="formRef">
<a-row :gutter="24"> <j-row :gutter="24">
<a-col :span="12"> <j-col :span="12">
<a-form-item label="名称" name="name"> <j-form-item label="名称" name="name">
<a-input <j-input
placeholder="请输入名称" placeholder="请输入名称"
v-model:value="form.name" v-model:value="form.name"
></a-input> </a-form-item ></j-input> </j-form-item
></a-col> ></j-col>
<a-col :span="12"> <j-col :span="12">
<a-form-item label="类型" name="targetType"> <j-form-item label="类型" name="targetType">
<a-select <j-select
:options="options" :options="options"
v-model:value="form.targetType" v-model:value="form.targetType"
:disabled="selectDisable" :disabled="selectDisable"
></a-select> ></j-select>
</a-form-item> </j-form-item>
</a-col> </j-col>
</a-row> </j-row>
<a-form-item label="级别" name="level"> <j-form-item label="级别" name="level">
<a-radio-group v-model:value="form.level"> <j-radio-group v-model:value="form.level">
<a-radio-button <j-radio-button
v-for="(item, index) in levelOption" v-for="(item, index) in levelOption"
:key="index" :key="index"
:value="item.value" :value="item.value"
@ -40,14 +40,14 @@
alt="" alt=""
/>{{ item.label }} />{{ item.label }}
</div> </div>
</a-radio-button> </j-radio-button>
</a-radio-group> </j-radio-group>
</a-form-item> </j-form-item>
<a-form-item label="说明" name="description"> <j-form-item label="说明" name="description">
<a-textarea v-model:value="form.description"></a-textarea> <j-textarea v-model:value="form.description"></j-textarea>
</a-form-item> </j-form-item>
<PermissionButton type="primary" @click="handleSave" :hasPermission="['rule-engine/Alarm/Configuration:add','rule-engine/Alarm/Configuration:update']">保存</PermissionButton> <PermissionButton type="primary" @click="handleSave" :hasPermission="['rule-engine/Alarm/Configuration:add','rule-engine/Alarm/Configuration:update']">保存</PermissionButton>
</a-form> </j-form>
</div> </div>
</template> </template>
@ -178,7 +178,7 @@ const handleSave = async () => {
const res = await save(form); const res = await save(form);
loading.value = false; loading.value = false;
if (res.status === 200) { if (res.status === 200) {
message.success('操作成功'); message.success('操作成功,请配置关联的场景联动');
menuStory.jumpPage( menuStory.jumpPage(
'rule-engine/Alarm/Configuration/Save', 'rule-engine/Alarm/Configuration/Save',
{}, {},

View File

@ -1,5 +1,5 @@
<template> <template>
<a-modal <j-modal
visible visible
title="新增" title="新增"
okText="确定" okText="确定"
@ -8,7 +8,7 @@
@cancel="closeModal" @cancel="closeModal"
@ok="saveCorrelation" @ok="saveCorrelation"
> >
<Search :columns="columns" @search="handleSearch"></Search> <pro-search :columns="columns" @search="handleSearch"/>
<div style="height: 500px; overflow-y: auto"> <div style="height: 500px; overflow-y: auto">
<JProTable <JProTable
model="CARD" model="CARD"
@ -78,7 +78,7 @@
</template> </template>
</JProTable> </JProTable>
</div> </div>
</a-modal> </j-modal>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
@ -213,7 +213,7 @@ const saveCorrelation = async () => {
const list = _selectedRowKeys.value.map((item: any) => { const list = _selectedRowKeys.value.map((item: any) => {
return { return {
alarmId: props.id, alarmId: props.id,
releId: item, ruleId: item,
}; };
}); });
const res = await bindScene([...list]); const res = await bindScene([...list]);

View File

@ -10,12 +10,12 @@
ref="actionRef" ref="actionRef"
> >
<template #headerTitle> <template #headerTitle>
<a-space> <j-space>
<PermissionButton type="primary" @click="showModal" hasPermission="rule-engine/Alarm/Configuration:add"> <PermissionButton type="primary" @click="showModal" hasPermission="rule-engine/Alarm/Configuration:add">
<template #icon><AIcon type="PlusOutlined" /></template> <template #icon><AIcon type="PlusOutlined" /></template>
新增 新增
</PermissionButton> </PermissionButton>
</a-space> </j-space>
</template> </template>
<template #card="slotProps"> <template #card="slotProps">
<SceneCard <SceneCard

View File

@ -3,13 +3,13 @@
<j-card> <j-card>
<j-tabs :activeKey="activeKey" @change="changeTabs"> <j-tabs :activeKey="activeKey" @change="changeTabs">
<j-tab-pane key="1" tab="基础配置"> <j-tab-pane key="1" tab="基础配置">
<Base/> <Base />
</j-tab-pane> </j-tab-pane>
<j-tab-pane key="2" tab="关联场景联动"> <j-tab-pane key="2" tab="关联场景联动">
<Scene></Scene> <Scene></Scene>
</j-tab-pane> </j-tab-pane>
<j-tab-pane key="3" tab="告警记录"> <j-tab-pane key="3" tab="告警记录">
<Log/> <Log v-if="activeKey === '3'" />
</j-tab-pane> </j-tab-pane>
</j-tabs> </j-tabs>
</j-card> </j-card>
@ -23,13 +23,13 @@ import Log from './Log/indev.vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { message } from 'ant-design-vue'; import { message } from 'ant-design-vue';
const route = useRoute(); const route = useRoute();
const changeTabs = (e:any) =>{ const changeTabs = (e: any) => {
if(route.query?.id){ if (route.query?.id) {
activeKey.value = e; activeKey.value = e;
}else{ } else {
message.error('请先保存基础配置'); message.error('请先保存基础配置');
} }
} };
const activeKey = ref('1'); const activeKey = ref('1');
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>

View File

@ -56,8 +56,8 @@
{{ slotProps.name }} {{ slotProps.name }}
</span> </span>
</Ellipsis> </Ellipsis>
<a-row> <j-row>
<a-col :span="12"> <j-col :span="12">
<div class="content-des-title"> <div class="content-des-title">
关联场景联动 关联场景联动
</div> </div>
@ -66,8 +66,8 @@
{{ (slotProps?.scene || []).map((item: any) => item?.name).join(',') || '' }} {{ (slotProps?.scene || []).map((item: any) => item?.name).join(',') || '' }}
</div></Ellipsis </div></Ellipsis
> >
</a-col> </j-col>
<a-col :span="12"> <j-col :span="12">
<div class="content-des-title"> <div class="content-des-title">
告警级别 告警级别
</div> </div>
@ -75,8 +75,8 @@
{{ (Store.get('default-level') || []).find((item: any) => item?.level === slotProps.level)?.title || {{ (Store.get('default-level') || []).find((item: any) => item?.level === slotProps.level)?.title ||
slotProps.level }} slotProps.level }}
</div> </div>
</a-col> </j-col>
</a-row> </j-row>
</template> </template>
<template #actions="item"> <template #actions="item">
<PermissionButton <PermissionButton
@ -109,7 +109,7 @@
<span>{{ map[slotProps.targetType] }}</span> <span>{{ map[slotProps.targetType] }}</span>
</template> </template>
<template #level="slotProps"> <template #level="slotProps">
<a-tooltip <j-tooltip
placement="topLeft" placement="topLeft"
:title="(Store.get('default-level') || []).find((item: any) => item?.level === slotProps.level)?.title || :title="(Store.get('default-level') || []).find((item: any) => item?.level === slotProps.level)?.title ||
slotProps.level" slotProps.level"
@ -118,7 +118,7 @@
{{ (Store.get('default-level') || []).find((item: any) => item?.level === slotProps.level)?.title || {{ (Store.get('default-level') || []).find((item: any) => item?.level === slotProps.level)?.title ||
slotProps.level }} slotProps.level }}
</div> </div>
</a-tooltip> </j-tooltip>
</template> </template>
<template #sceneId="slotProps"> <template #sceneId="slotProps">
<span <span
@ -126,7 +126,7 @@
> >
</template> </template>
<template #state="slotProps"> <template #state="slotProps">
<a-badge <j-badge
:text=" :text="
slotProps.state?.value === 'enabled' slotProps.state?.value === 'enabled'
? '正常' ? '正常'
@ -140,7 +140,7 @@
/> />
</template> </template>
<template #action="slotProps"> <template #action="slotProps">
<a-space :size="16"> <j-space :size="16">
<template <template
v-for="i in getActions(slotProps, 'table')" v-for="i in getActions(slotProps, 'table')"
:key="i.key" :key="i.key"
@ -168,7 +168,7 @@
/></template> /></template>
</PermissionButton> </PermissionButton>
</template> </template>
</a-space> </j-space>
</template> </template>
</JProTable> </JProTable>
</div> </div>

View File

@ -1,10 +1,10 @@
<template> <template>
<page-container> <page-container>
<Search <pro-search
:columns="columns" :columns="columns"
target="alarm-log-detail" target="alarm-log-detail"
@search="handleSearch" @search="handleSearch"
></Search> />
<JProTable <JProTable
:columns="columns" :columns="columns"
model="TABLE" model="TABLE"
@ -19,7 +19,7 @@
moment(slotProps.alarmTime).format('YYYY-MM-DD HH:mm:ss') moment(slotProps.alarmTime).format('YYYY-MM-DD HH:mm:ss')
}}</template> }}</template>
<template #action="slotProps"> <template #action="slotProps">
<a-space <j-space
><template ><template
v-for="i in getActions(slotProps, 'table')" v-for="i in getActions(slotProps, 'table')"
:key="i.key" :key="i.key"
@ -37,7 +37,7 @@
<template #icon><AIcon :type="i.icon"/></template> <template #icon><AIcon :type="i.icon"/></template>
</PermissionButton> </PermissionButton>
</template> </template>
</a-space> </j-space>
</template> </template>
</JProTable> </JProTable>
<Info v-if="visiable" :data="current" @close="close"/> <Info v-if="visiable" :data="current" @close="close"/>

View File

@ -1,16 +1,16 @@
<template> <template>
<a-modal visible title="详情" okText="确定" cancelText="取消" :width="1000" @ok="closeModal" @cancel="closeModal"> <j-modal visible title="详情" okText="确定" cancelText="取消" :width="1000" @ok="closeModal" @cancel="closeModal">
<a-descriptions bordered :column="2"> <j-descriptions bordered :column="2">
<a-descriptions-item v-if="props.data.targetType==='device'" label="告警设备" :span="1">{{props.data?.targetName || ''}}</a-descriptions-item> <j-descriptions-item v-if="props.data.targetType==='device'" label="告警设备" :span="1">{{props.data?.targetName || ''}}</j-descriptions-item>
<a-descriptions-item v-if="props.data.targetType==='device'" label="设备ID" :span="1">{{props.data?.targetId || ''}}</a-descriptions-item> <j-descriptions-item v-if="props.data.targetType==='device'" label="设备ID" :span="1">{{props.data?.targetId || ''}}</j-descriptions-item>
<a-descriptions-item label="告警名称" :span="1">{{ <j-descriptions-item label="告警名称" :span="1">{{
props.data?.alarmConfigName props.data?.alarmConfigName
}}</a-descriptions-item> }}</j-descriptions-item>
<a-descriptions-item label="告警时间" :span="1">{{ <j-descriptions-item label="告警时间" :span="1">{{
moment(data?.alarmTime).format('YYYY-MM-DD HH:mm:ss') moment(data?.alarmTime).format('YYYY-MM-DD HH:mm:ss')
}}</a-descriptions-item> }}</j-descriptions-item>
<a-descriptions-item label="告警级别" :span="1"> <j-descriptions-item label="告警级别" :span="1">
<a-tooltip <j-tooltip
placement="topLeft" placement="topLeft"
:title="(Store.get('default-level') || []).find((item: any) => item?.level === data?.level) :title="(Store.get('default-level') || []).find((item: any) => item?.level === data?.level)
?.title || props.data?.level" ?.title || props.data?.level"
@ -21,10 +21,10 @@
?.title || props.data?.level}} ?.title || props.data?.level}}
</span> </span>
</Ellipsis> </Ellipsis>
</a-tooltip> </j-tooltip>
</a-descriptions-item> </j-descriptions-item>
<a-descriptions-item label="告警说明" :span="1" <j-descriptions-item label="告警说明" :span="1"
><a-tooltip ><j-tooltip
placement="topLeft" placement="topLeft"
:title="data?.description || ''" :title="data?.description || ''"
> >
@ -33,14 +33,14 @@
{{ data?.description || '' }} {{ data?.description || '' }}
</span> </Ellipsis </span> </Ellipsis
> >
</a-tooltip></a-descriptions-item </j-tooltip></j-descriptions-item
> >
<a-descriptions-item <j-descriptions-item
label="告警流水" label="告警流水"
:span="2" :span="2"
><div style="max-height: 500px; overflow-y: auto;"><JsonViewer :value="JSON.parse(data?.alarmInfo || '{}')" :expand-depth="5"></JsonViewer></div></a-descriptions-item> ><div style="max-height: 500px; overflow-y: auto;"><JsonViewer :value="JSON.parse(data?.alarmInfo || '{}')" :expand-depth="5"></JsonViewer></div></j-descriptions-item>
</a-descriptions> </j-descriptions>
</a-modal> </j-modal>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>

View File

@ -1,5 +1,5 @@
<template> <template>
<a-modal <j-modal
title="告警处理" title="告警处理"
okText="确定" okText="确定"
cancelText="取消" cancelText="取消"
@ -9,18 +9,18 @@
destroyOnClose destroyOnClose
:confirmLoading="loading" :confirmLoading="loading"
> >
<a-form :rules="rules" layout="vertical" ref="formRef" :model="form"> <j-form :rules="rules" layout="vertical" ref="formRef" :model="form">
<a-form-item label="处理结果" name="describe"> <j-form-item label="处理结果" name="describe">
<a-textarea <j-textarea
:rows="8" :rows="8"
:maxlength="200" :maxlength="200"
showCount showCount
placeholder="请输入处理结果" placeholder="请输入处理结果"
v-model:value="form.describe" v-model:value="form.describe"
></a-textarea> ></j-textarea>
</a-form-item> </j-form-item>
</a-form> </j-form>
</a-modal> </j-modal>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
@ -64,6 +64,7 @@ const handleSave = () => {
}); });
if (res.status === 200) { if (res.status === 200) {
onlyMessage('操作成功!'); onlyMessage('操作成功!');
emit('closeSolve');
} else { } else {
onlyMessage('操作失败!', 'error'); onlyMessage('操作失败!', 'error');
} }

View File

@ -1,5 +1,5 @@
<template> <template>
<a-modal <j-modal
visible visible
title="处理记录" title="处理记录"
:width="1200" :width="1200"
@ -8,11 +8,11 @@
@ok="clsoeModal" @ok="clsoeModal"
@cancel="clsoeModal" @cancel="clsoeModal"
> >
<Search <pro-search
:columns="columns" :columns="columns"
target="bind-channel" target="bind-channel"
@search="handleSearch" @search="handleSearch"
></Search> />
<JProTable <JProTable
model="TABLE" model="TABLE"
:columns="columns" :columns="columns"
@ -48,7 +48,7 @@
</span> </span>
</template> </template>
</JProTable> </JProTable>
</a-modal> </j-modal>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>

View File

@ -1,29 +1,29 @@
<template> <template>
<div class="alarm-log-card"> <div class="alarm-log-card">
<Search <pro-search
:columns="columns" :columns="columns"
target="alarm-log" target="alarm-log"
v-if="['all', 'detail'].includes(props.type)" v-if="['all', 'detail'].includes(props.type)"
@search="search" @search="search"
></Search> />
<Search <pro-search
:columns="produtCol" :columns="produtCol"
target="alarm-log" target="alarm-log"
v-if="['product', 'other'].includes(props.type)" v-if="['product', 'other'].includes(props.type)"
@search="search" @search="search"
></Search> />
<Search <pro-search
:columns="deviceCol" :columns="deviceCol"
target="alarm-log" target="alarm-log"
v-if="props.type === 'device'" v-if="props.type === 'device'"
@search="search" @search="search"
></Search> />
<Search <pro-search
:columns="orgCol" :columns="orgCol"
target="alarm-log" target="alarm-log"
v-if="props.type === 'org'" v-if="props.type === 'org'"
@search="search" @search="search"
></Search> />
<JProTable <JProTable
:columns="columns" :columns="columns"
:request="handleSearch" :request="handleSearch"
@ -31,6 +31,7 @@
:gridColumns="[1, 1, 2]" :gridColumns="[1, 1, 2]"
:gridColumn="2" :gridColumn="2"
model="CARD" model="CARD"
ref="tableRef"
> >
<template #card="slotProps"> <template #card="slotProps">
<CardBox <CardBox
@ -52,8 +53,8 @@
{{ slotProps.alarmName }} {{ slotProps.alarmName }}
</span> </span>
</Ellipsis> </Ellipsis>
<a-row :gutter="24"> <j-row :gutter="24">
<a-col :span="8"> <j-col :span="8">
<div class="content-des-title"> <div class="content-des-title">
{{ titleMap.get(slotProps.targetType) }} {{ titleMap.get(slotProps.targetType) }}
</div> </div>
@ -62,8 +63,8 @@
{{ slotProps?.targetName }} {{ slotProps?.targetName }}
</div></Ellipsis </div></Ellipsis
> >
</a-col> </j-col>
<a-col :span="8"> <j-col :span="8">
<div class="content-des-title"> <div class="content-des-title">
最近告警时间 最近告警时间
</div> </div>
@ -76,17 +77,17 @@
}} }}
</div></Ellipsis </div></Ellipsis
> >
</a-col> </j-col>
<a-col :span="8"> <j-col :span="8">
<div class="content-des-title">状态</div> <div class="content-des-title">状态</div>
<a-badge <j-badge
:status=" :status="
slotProps.state.value === 'warning' slotProps.state.value === 'warning'
? 'error' ? 'error'
: 'default' : 'default'
" "
> >
</a-badge </j-badge
><span ><span
:style=" :style="
slotProps.state.value === 'warning' slotProps.state.value === 'warning'
@ -96,8 +97,8 @@
> >
{{ slotProps.state.text }} {{ slotProps.state.text }}
</span> </span>
</a-col> </j-col>
</a-row> </j-row>
</template> </template>
<template #actions="item"> <template #actions="item">
<PermissionButton <PermissionButton
@ -105,7 +106,6 @@
item.key === 'solve' && item.key === 'solve' &&
slotProps.state.value === 'normal' slotProps.state.value === 'normal'
" "
:popConfirm="item.popConfirm"
:tooltip="{ :tooltip="{
...item.tooltip, ...item.tooltip,
}" }"
@ -152,11 +152,11 @@ import { Store } from 'jetlinks-store';
import moment from 'moment'; import moment from 'moment';
import type { ActionsType } from '@/components/Table'; import type { ActionsType } from '@/components/Table';
import SolveComponent from '../SolveComponent/index.vue'; import SolveComponent from '../SolveComponent/index.vue';
import SolveLog from '../SolveLog/index.vue' import SolveLog from '../SolveLog/index.vue';
import { useMenuStore } from '@/store/menu'; import { useMenuStore } from '@/store/menu';
import { usePermissionStore } from '@/store/permission'; import { usePermissionStore } from '@/store/permission';
const menuStory = useMenuStore(); const menuStory = useMenuStore();
const tableRef = ref();
const alarmStore = useAlarmStore(); const alarmStore = useAlarmStore();
const { data } = storeToRefs(alarmStore); const { data } = storeToRefs(alarmStore);
const getDefaulitLevel = () => { const getDefaulitLevel = () => {
@ -339,7 +339,7 @@ watchEffect(() => {
}, },
]; ];
} }
if(props.type === 'all'){ if (props.type === 'all') {
params.value.terms = []; params.value.terms = [];
} }
}); });
@ -347,24 +347,20 @@ watchEffect(() => {
const search = (data: any) => { const search = (data: any) => {
params.value.terms = [...data?.terms]; params.value.terms = [...data?.terms];
if (props.type !== 'all' && !props.id) { if (props.type !== 'all' && !props.id) {
params.value.terms.push( params.value.terms.push({
{
termType: 'eq', termType: 'eq',
column: 'targetType', column: 'targetType',
value: props.type, value: props.type,
type: 'and', type: 'and',
}, });
);
} }
if (props.id) { if (props.id) {
params.value.terms.push ( params.value.terms.push({
{
termType: 'eq', termType: 'eq',
column: 'alarmConfigId', column: 'alarmConfigId',
value: props.id, value: props.id,
type: 'and', type: 'and',
}, });
);
} }
}; };
@ -381,17 +377,19 @@ const getActions = (
title: '告警处理', title: '告警处理',
}, },
icon: 'ToolOutlined', icon: 'ToolOutlined',
onClick: () =>{ onClick: () => {
data.value.current = currentData; data.value.current = currentData;
data.value.solveVisible = true; data.value.solveVisible = true;
}, },
popConfirm:{ // popConfirm: {
title: !usePermissionStore().hasPermission('rule-engine/Alarm/Log:action') // title: !usePermissionStore().hasPermission(
? '暂无权限,请联系管理员' // 'rule-engine/Alarm/Log:action',
: data.state?.value === 'normal' // )
? '无告警' // ? ''
: '' // : data.state?.value === 'normal'
} // ? ''
// : '',
// },
}, },
{ {
key: 'log', key: 'log',
@ -400,9 +398,11 @@ const getActions = (
title: '告警日志', title: '告警日志',
}, },
icon: 'FileOutlined', icon: 'FileOutlined',
onClick: () =>{ onClick: () => {
menuStory.jumpPage(`rule-engine/Alarm/Log/Detail`,{id:currentData.id}); menuStory.jumpPage(`rule-engine/Alarm/Log/Detail`, {
} id: currentData.id,
});
},
}, },
{ {
key: 'detail', key: 'detail',
@ -411,10 +411,10 @@ const getActions = (
title: '处理记录', title: '处理记录',
}, },
icon: 'FileTextOutlined', icon: 'FileTextOutlined',
onClick:() =>{ onClick: () => {
data.value.current = currentData; data.value.current = currentData;
data.value.logVisible = true; data.value.logVisible = true;
} },
}, },
]; ];
return actions; return actions;
@ -422,15 +422,16 @@ const getActions = (
/** /**
* 关闭告警日志 * 关闭告警日志
*/ */
const closeSolve = () =>{ const closeSolve = () => {
data.value.solveVisible = false; data.value.solveVisible = false;
} tableRef.value.reload(params.value);
};
/** /**
* 关闭处理记录 * 关闭处理记录
*/ */
const closeLog = () =>{ const closeLog = () => {
data.value.logVisible = false; data.value.logVisible = false;
} };
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
</style> </style>

View File

@ -1,5 +1,5 @@
<template> <template>
<a-modal <j-modal
:maskClosable="false" :maskClosable="false"
width="650px" width="650px"
destroyOnClose destroyOnClose
@ -12,30 +12,30 @@
:confirmLoading="loading" :confirmLoading="loading"
> >
<div style="margin-top: 10px"> <div style="margin-top: 10px">
<a-form <j-form
:layout="'vertical'" :layout="'vertical'"
ref="formRef" ref="formRef"
:rules="rules" :rules="rules"
:model="modelRef" :model="modelRef"
> >
<a-form-item label="名称" name="name"> <j-form-item label="名称" name="name">
<a-input <j-input
v-model:value="modelRef.name" v-model:value="modelRef.name"
placeholder="请输入名称" placeholder="请输入名称"
/> />
</a-form-item> </j-form-item>
<a-form-item label="说明" name="describe"> <j-form-item label="说明" name="describe">
<a-textarea <j-textarea
v-model:value="modelRef.description" v-model:value="modelRef.description"
placeholder="请输入说明" placeholder="请输入说明"
showCount showCount
:maxlength="200" :maxlength="200"
:rows="4" :rows="4"
/> />
</a-form-item> </j-form-item>
</a-form> </j-form>
</div> </div>
</a-modal> </j-modal>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>

View File

@ -1,11 +1,11 @@
<template> <template>
<page-container> <page-container>
<div> <div>
<Search <pro-search
:columns="query.columns" :columns="query.columns"
target="device-instance" target="device-instance"
@search="handleSearch" @search="handleSearch"
></Search> />
<JProTable <JProTable
:columns="columns" :columns="columns"
:request="queryList" :request="queryList"
@ -53,15 +53,15 @@
{{ slotProps.name }} {{ slotProps.name }}
</span> </span>
</Ellipsis> </Ellipsis>
<a-row> <j-row>
<a-col :span="12"> <j-col :span="12">
<Ellipsis> <Ellipsis>
<div> <div>
{{ slotProps.description }} {{ slotProps.description }}
</div> </div>
</Ellipsis> </Ellipsis>
</a-col> </j-col>
</a-row> </j-row>
</template> </template>
<template #actions="item"> <template #actions="item">
<PermissionButton <PermissionButton
@ -88,7 +88,7 @@
</CardBox> </CardBox>
</template> </template>
<template #state="slotProps"> <template #state="slotProps">
<a-badge <j-badge
:text=" :text="
slotProps.state?.value === 'started' slotProps.state?.value === 'started'
? '正常' ? '正常'
@ -102,7 +102,7 @@
/> />
</template> </template>
<template #action="slotProps"> <template #action="slotProps">
<a-space :size="16"> <j-space :size="16">
<template <template
v-for="i in getActions(slotProps, 'table')" v-for="i in getActions(slotProps, 'table')"
:key="i.key" :key="i.key"
@ -123,7 +123,7 @@
/></template> /></template>
</PermissionButton> </PermissionButton>
</template> </template>
</a-space> </j-space>
</template> </template>
</JProTable> </JProTable>
<!-- 新增编辑 --> <!-- 新增编辑 -->