fix: 优化monaco-editor编辑;修改我的订阅;修复运维管理仪表盘在暂无数据情况下切换导致无数据展示问题

* fix: 优化菜单跳转

* fix: 优化菜单跳转

* fix: bug#16693

* feat: task#2572

* fix: 优化物模型映射保存请求异常

* fix: 修复运维管理仪表盘在暂无数据情况下切换导致无数据展示问题

* fix: 修改订阅管理文字提示

* fix: totalFlow

* fix: 修改我的订阅

* fix: 优化monaco-editor编辑
This commit is contained in:
XieYongHong 2023-07-26 10:09:06 +08:00 committed by GitHub
parent 143144a64e
commit 49cf7c5594
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 777 additions and 466 deletions

View File

@ -25,7 +25,7 @@
"event-source-polyfill": "^1.0.31", "event-source-polyfill": "^1.0.31",
"global": "^4.4.0", "global": "^4.4.0",
"jetlinks-store": "^0.0.3", "jetlinks-store": "^0.0.3",
"jetlinks-ui-components": "^1.0.26", "jetlinks-ui-components": "^1.0.27",
"js-cookie": "^3.0.1", "js-cookie": "^3.0.1",
"jsencrypt": "^3.3.2", "jsencrypt": "^3.3.2",
"less": "^4.1.3", "less": "^4.1.3",

View File

@ -1,10 +1,5 @@
<template> <template>
<ConfigProvider :locale='zhCN'> <ConfigProvider :locale='zhCN'>
<!-- <router-view v-slot="{ Component }">-->
<!-- <keep-alive>-->
<!-- <component :is="Component" />-->
<!-- </keep-alive>-->
<!-- </router-view>-->
<router-view /> <router-view />
</ConfigProvider> </ConfigProvider>
</template> </template>

View File

@ -3,6 +3,9 @@ import server from '@/utils/request';
export const queryCollector = (data: any) => export const queryCollector = (data: any) =>
server.post(`/data-collect/collector/_query/no-paging?paging=false`, data); server.post(`/data-collect/collector/_query/no-paging?paging=false`, data);
export const queryCollectorTree = (data: any) =>
server.post(`/data-collect/collector/_detail/no-paging?paging=false`, data);
export const queryChannelNoPaging = () => export const queryChannelNoPaging = () =>
server.post(`/data-collect/channel/_query/no-paging`, { server.post(`/data-collect/channel/_query/no-paging`, {
paging: false, paging: false,

View File

@ -1,12 +1,12 @@
<template> <template>
<j-pro-layout <j-pro-layout
v-bind="layoutConf" v-bind="layoutConf"
v-model:openKeys="state.openKeys" v-model:collapsed="basicLayout.collapsed"
v-model:collapsed="state.collapsed" v-model:openKeys="basicLayout.openKeys"
v-model:selectedKeys="state.selectedKeys" :selectedKeys="basicLayout.selectedKeys"
:headerHeight='layout.headerHeight' :headerHeight='layout.headerHeight'
:pure="state.pure" :breadcrumb="{ routes: breadcrumbs }"
:breadcrumb="{ routes: breadcrumb }" :pure="basicLayout.pure"
@backClick='routerBack' @backClick='routerBack'
> >
<template #breadcrumbRender="slotProps"> <template #breadcrumbRender="slotProps">
@ -26,7 +26,7 @@
</div> </div>
</template> </template>
<router-view v-slot="{ Component }"> <router-view v-slot="{ Component }">
<component :is="Component" /> <component :is="components || Component" />
</router-view> </router-view>
</j-pro-layout> </j-pro-layout>
</template> </template>
@ -54,14 +54,14 @@ const route = useRoute();
const menu = useMenuStore(); const menu = useMenuStore();
const system = useSystem(); const system = useSystem();
const {configInfo,layout} = storeToRefs(system); const {configInfo,layout, basicLayout} = storeToRefs(system);
const layoutConf = reactive({ const layoutConf = reactive({
theme: DefaultSetting.layout.theme, theme: DefaultSetting.layout.theme,
siderWidth: layout.value.siderWidth, siderWidth: layout.value.siderWidth,
logo: DefaultSetting.layout.logo, logo: DefaultSetting.layout.logo,
title: DefaultSetting.layout.title, title: DefaultSetting.layout.title,
menuData: [...clearMenuItem(menu.siderMenus), AccountMenu], menuData: [...menu.siderMenus, AccountMenu],
// menuData: menu.siderMenus, // menuData: menu.siderMenus,
splitMenus: true, splitMenus: true,
}); });
@ -72,87 +72,51 @@ watchEffect(() => {
layoutConf.logo = configInfo.value.front?.logo || DefaultSetting.layout.logo; layoutConf.logo = configInfo.value.front?.logo || DefaultSetting.layout.logo;
}) })
const state = reactive<StateType>({ const components = computed(() => {
pure: false, const componentName = route.matched[route.matched.length - 1]?.components?.default?.name
collapsed: false, // default value if (componentName !== 'BasicLayoutPage') {
openKeys: [], return route.matched[route.matched.length - 1]?.components?.default
selectedKeys: [], }
}); return undefined
})
/**
* 面包屑
*/
const breadcrumbs = computed(() =>
{
const paths = router.currentRoute.value.matched
return paths.map((item, index) => {
return {
index,
isLast: index === (paths.length -1),
path: item.path,
breadcrumbName: (item.meta as any).title || '',
}
})
}
);
const routerBack = () => { const routerBack = () => {
router.go(-1) router.go(-1)
} }
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 jump = (item: any) => { const jump = (item: any) => {
let path = history.state.back router.push(item.path)
if (path) {
// query,
if (path.includes('?')) {
const _path = path.split('?')[0]
path = _path === item.path ? path : item.path
} else if (path !== item.path) {
path = item.path
}
} else {
path = item.path
}
// jumpPage(slotProps.route.path)
router.push(path)
} }
const breadcrumb = computed(() =>
{
const paths = router.currentRoute.value.name as string
const metas = findRouteMeta(paths)
return metas.map((item, index) => {
return {
index,
isLast: index === (metas.length - 1),
path: item.path,
breadcrumbName: item.title || '',
};
})
}
);
watchEffect(() => { watchEffect(() => {
if (router.currentRoute) { if (router.currentRoute) {
const paths = router.currentRoute.value.name as string const paths = router.currentRoute.value.matched
if (paths) { basicLayout.value.selectedKeys = paths.map(item => item.path)
const _metas = findRouteMeta(paths) basicLayout.value.openKeys = paths.map(item => item.path)
state.selectedKeys = _metas.map(item => item.path) console.log(paths) //
state.openKeys = _metas.filter((r) => r !== router.currentRoute.value.path).map(item => item.path)
} }
} })
});
watchEffect(() => {
if (
route.query &&
'layout' in route.query &&
route.query.layout === 'false'
) {
state.pure = true;
} else {
state.pure = false;
}
});
const toDoc = () => window.open('http://doc.v2.jetlinks.cn/'); const toDoc = () => window.open('http://doc.v2.jetlinks.cn/');
</script> </script>

View File

@ -1,11 +1,21 @@
<template> <template>
<router-view /> <router-view v-slot="Component">
<component :is="components || Component" />
</router-view>
</template> </template>
<script> <script name="BlankLayoutPage" setup>
export default {
name: 'BlankLayoutPage' const route = useRoute()
}
const components = computed(() => {
const componentName = route.matched[route.matched.length - 1]?.components?.default?.name
if (componentName !== 'BlankLayoutPage') {
return route.matched[route.matched.length - 1]?.components?.default
}
return undefined
})
</script> </script>
<style scoped> <style scoped>

View File

@ -1,6 +1,11 @@
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { queryOwnThree } from '@/api/system/menu' import { queryOwnThree } from '@/api/system/menu'
import {filterAsyncRouter, filterCommunityMenus, findCodeRoute, MenuItem} from '@/utils/menu' import {
handleMenus,
MenuItem,
handleSiderMenu,
getAsyncRoutesMap, handleMenusMap
} from '@/utils/menu'
import { cloneDeep, isArray } from 'lodash-es' import { cloneDeep, isArray } from 'lodash-es'
import { usePermissionStore } from './permission' import { usePermissionStore } from './permission'
import router from '@/router' import router from '@/router'
@ -32,14 +37,7 @@ const defaultOwnParams = [
] ]
type MenuStateType = { type MenuStateType = {
menus: { menus: any
[key: string]: {
buttons?: string[]
title: string
parentName: string
path: string
}
}
siderMenus: MenuItem[] siderMenus: MenuItem[]
params: Record<string, any> params: Record<string, any>
} }
@ -96,6 +94,10 @@ export const useMenuStore = defineStore({
name, params, query, state: { params } name, params, query, state: { params }
}) })
}, },
handleMenusMapById(item: { code: string, path: string}) {
const { name, path } = item
this.menus[name] = {path}
},
queryMenuTree(isCommunity = false): Promise<any[]> { queryMenuTree(isCommunity = false): Promise<any[]> {
return new Promise(async (res) => { return new Promise(async (res) => {
//过滤非集成的菜单 //过滤非集成的菜单
@ -106,18 +108,12 @@ export const useMenuStore = defineStore({
// if (!isNoCommunity) { // if (!isNoCommunity) {
// resultData = filterCommunityMenus(resultData) // resultData = filterCommunityMenus(resultData)
// } // }
permission.permissions = {} const components = getAsyncRoutesMap()
const { menusData, silderMenus } = filterAsyncRouter(resultData) const menusData = handleMenus(cloneDeep(resultData), components)
permission.handlePermission(resultData)
const silderMenus = handleSiderMenu(cloneDeep(resultData))
this.menus = findCodeRoute([...resultData]) // AccountMenu // const { menusData, silderMenus } = filterAsyncRouter(resultData)
Object.keys(this.menus).forEach((item) => { handleMenusMap(cloneDeep(menusData), this.handleMenusMapById)
const _item = this.menus[item]
if (_item.buttons?.length) {
permission.permissions[item] = _item.buttons
}
})
menusData.push({ menusData.push({
path: '/', path: '/',
redirect: menusData[0]?.path, redirect: menusData[0]?.path,
@ -125,6 +121,7 @@ export const useMenuStore = defineStore({
hideInMenu: true hideInMenu: true
} }
}) })
// console.log(menusData)
// menusData.push(AccountMenu) // menusData.push(AccountMenu)
this.siderMenus = silderMenus.filter((item: { name: string }) => ![USER_CENTER_MENU_CODE, NotificationRecordCode, NotificationSubscriptionCode].includes(item.name)) this.siderMenus = silderMenus.filter((item: { name: string }) => ![USER_CENTER_MENU_CODE, NotificationRecordCode, NotificationSubscriptionCode].includes(item.name))
res(menusData) res(menusData)

View File

@ -1,4 +1,5 @@
import { defineStore } from "pinia"; import { defineStore } from "pinia";
import {handleAuthMenu} from "@/utils/menu";
export const usePermissionStore = defineStore({ export const usePermissionStore = defineStore({
id: 'permission', id: 'permission',
@ -39,5 +40,17 @@ export const usePermissionStore = defineStore({
return false return false
} }
} }
},
actions: {
setPermission(code: string, buttons: string[]) {
this.permissions[code] = buttons
},
cleanPermission() {
this.permissions = {}
},
handlePermission(data: any) {
this.cleanPermission()
handleAuthMenu(data, this.setPermission)
}
} }
}) })

View File

@ -13,6 +13,12 @@ type SystemStateType = {
siderWidth: string | number | undefined; // 左侧菜单栏宽度 siderWidth: string | number | undefined; // 左侧菜单栏宽度
headerHeight: string | number | undefined; // 头部高度 headerHeight: string | number | undefined; // 头部高度
collapsedWidth: string | number | undefined; collapsedWidth: string | number | undefined;
},
basicLayout: {
selectedKeys: string[],
openKeys: string[],
collapsed: boolean
pure: boolean
} }
} }
@ -25,6 +31,12 @@ export const useSystem = defineStore('system', {
siderWidth: 208, // 左侧菜单栏宽度 siderWidth: 208, // 左侧菜单栏宽度
headerHeight: 60, // 头部高度 headerHeight: 60, // 头部高度
collapsedWidth: 48, collapsedWidth: 48,
},
basicLayout: {
selectedKeys: [],
openKeys: [],
collapsed: false,
pure: false
} }
}), }),
actions: { actions: {

View File

@ -1,6 +1,6 @@
import { cloneDeep } from 'lodash-es'
import { BlankLayoutPage, BasicLayoutPage } from 'components/Layout' import { BlankLayoutPage, BasicLayoutPage } from 'components/Layout'
import { isNoCommunity } from '@/utils/utils' import { isNoCommunity } from '@/utils/utils'
import Iframe from '../views/iframe/index.vue'
const pagesComponent = import.meta.glob('../views/**/*.vue'); const pagesComponent = import.meta.glob('../views/**/*.vue');
@ -155,185 +155,363 @@ const extraRouteObj = {
} }
}; };
//
// const resolveComponent = (name: any) => {
// const importPage = pagesComponent[`../views/${name}/index.vue`];
// console.log(importPage)
// if (!importPage) {
// return undefined
// } else {
// const res = () => importPage()
// return res
// }
// //@ts-ignore
// }
//
// const findChildrenRoute = (code: string, url: string, routes: any[] = []): MenuItem[] => {
// if (extraRouteObj[code]) {
// const extraRoutes = extraRouteObj[code].children.map((route: MenuItem) => {
// return {
// url: `${url}/${route.code}`,
// code: `${code}/${route.code}`,
// name: route.name,
// isShow: false
// }
// })
// return [...routes, ...extraRoutes]
// }
// return routes
// }
//
// const findDetailRouteItem = (code: string, url: string): Partial<MenuItem> | null => {
// const detailComponent = resolveComponent(`${code}/Detail`)
// if (detailComponent) {
// return {
// url: `${url}/detail/:id`,
// code: `${code}/Detail`,
// component: detailComponent,
// name: '详情信息',
// isShow: false
// }
// }
// return null
// }
//
// const findDetailRoutes = (routes: any[]): any[] => {
// const newRoutes: any[] = []
// routes.forEach((route: any) => {
// newRoutes.push(route)
// const detail = findDetailRouteItem(route.code, route.url)
// if (detail) {
// newRoutes.push(detail)
// }
// })
// return newRoutes
// }
//
// const filterMenus = ['device/DashBoard']
// export const filterCommunityMenus = (menuData: any[]) => {
// return menuData.filter(item => {
// if (item.children) {
// item.children = filterCommunityMenus(item.children)
// }
// return !filterMenus.includes(item.code)
// })
// }
//
// export const findCodeRoute = (asyncRouterMap: any[]) => {
// const routeMeta = {}
//
// function getDetail(code: string, url: string) {
// const detail = findDetailRouteItem(code, url)
// if (!detail) return
//
// routeMeta[(detail as MenuItem).code] = {
// path: detail.url,
// title: detail.name,
// parentName: code,
// buttons: detail.buttons?.map((b: any) => b.id) || []
// }
// }
//
// 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 otherRoutes = extraRouteObj[route.code]
//
// if (otherRoutes) {
// otherRoutes.children.map((item: any) => {
// const _code = `${route.code}/${item.code}`
// const url = `${route.url}/${item.code}`
// routeMeta[_code] = {
// path: `${route.url}/${item.code}`,
// title: item.name,
// parentName: route.code,
// buttons: item.buttons?.map((b: any) => b.id) || []
// }
// getDetail(_code, url)
// })
// }
// const _code = route.appId ? `/${route.appId}${route.url}` : route.code
// if (!route.appId) {
// getDetail(_code, route.url)
// } else {
// routeMeta[_code] = {
// path: `/${route.appId}${route.url}`,
// title: route.name,
// parentName: code,
// buttons: []
// }
// }
// if (route.children) {
// findChildren(route.children, _code)
// }
// })
// }
//
// findChildren(asyncRouterMap)
//
// return routeMeta
// }
//
// export function filterAsyncRouter(asyncRouterMap: any, parentCode = '', level = 1): { menusData: any, silderMenus: any } {
// const _asyncRouterMap = cloneDeep(asyncRouterMap)
// const menusData: any[] = []
// const silderMenus: any[] = []
// _asyncRouterMap.forEach((route: any) => {
// const hasAppId = route.appId
// const _route: any = {
// path: hasAppId ? `/${route.appId}${route.url}` : `${route.url}`,
// name: hasAppId ? `/${route.appId}${route.url}` : route.code,
// url: hasAppId ? `/${route.appId}${route.url}` : route.url,
// meta: {
// icon: route.icon,
// title: route.name,
// hideInMenu: route.isShow === false,
// buttons: route.buttons?.map((b: any) => b.id) || [],
// isApp: hasAppId,
// },
// }
//
// const silder = { ..._route }
// // 查看是否有隐藏子路由
// route.children = findChildrenRoute(route.code, route.url, route.children)
// route.children = findDetailRoutes(route.children)
// if (route.children && route.children.length) {
// // TODO 查看是否具有详情页
// const { menusData: _menusData, silderMenus: _silderMenus } = filterAsyncRouter(route.children, `${parentCode}/${route.code}`, level + 1)
// _route.children = _menusData
// silder.children = _silderMenus
// const showChildren = _route.children.some((r: any) => !r.meta.hideInMenu)
//
// if (showChildren && _route.children.length) {
// _route.component = level === 1 ? BasicLayoutPage : BlankLayoutPage
// _route.redirect = _route.children[0].url
// } else {
// const myComponent = resolveComponent(route.code)
// // _route.component = myComponent ? myComponent : BlankLayoutPage;
// if (!!myComponent) {
// _route.component = myComponent;
// _route.children.map((r: any) => menusData.push(r))
// delete _route.children
// } else {
// _route.component = BlankLayoutPage
// }
// }
// } else {
// if (hasAppId) {
// _route.component = route.component || resolveComponent('iframe')
// } else {
// _route.component = route.component || resolveComponent(route.code) || BlankLayoutPage
// }
// }
// menusData.push(_route)
// silderMenus.push(silder)
// })
// return {
// menusData,
// silderMenus,
// }
// }
const resolveComponent = (name: any) => { import { shallowRef } from 'vue'
const importPage = pagesComponent[`../views/${name}/index.vue`];
if (!importPage) { type Buttons = Array<{ id: string }>
const hasAppID = (item: { appId?: string, url?: string }): { isApp: boolean, appUrl: string } => {
return {
isApp: !!item.appId,
appUrl: `/${item.appId}${item.url}`
}
}
const handleButtons = (buttons?: Buttons) => {
return buttons?.map((b) => b.id) || []
}
const handleMeta = (item: MenuItem, isApp: boolean) => {
return {
icon: item.icon,
title: item.name,
hideInMenu: item.isShow === false,
buttons: handleButtons(item.buttons),
isApp
}
}
const findComponents = (code: string, level: number, isApp: boolean, components: any) => {
const myComponents = components[code]
if (level === 1) { // BasicLayoutPage
return myComponents ? () => myComponents() : BasicLayoutPage
} else if (level === 2) { // BlankLayoutPage or components
return myComponents ? () => myComponents() : BlankLayoutPage
} else if (isApp){ // iframe
return () => Iframe
} else if(myComponents) { // components
return () => myComponents()
}
// return components['demo'] // 开发测试用
return undefined return undefined
} else {
const res = () => importPage()
return res
}
//@ts-ignore
} }
const findChildrenRoute = (code: string, url: string, routes: any[] = []): MenuItem[] => { const hasExtraChildren = (item: MenuItem, extraMenus: any ) => {
if (extraRouteObj[code]) { const extraItem = extraMenus[item.code]
const extraRoutes = extraRouteObj[code].children.map((route: MenuItem) => { if (extraItem) {
return { return extraItem.children.map(e => ({
url: `${url}/${route.code}`, ...e,
code: `${code}/${route.code}`, url: `${item.url}${e.url}`,
name: route.name,
isShow: false isShow: false
}))
} }
})
return [...routes, ...extraRoutes] return undefined
}
return routes
} }
const findDetailRouteItem = (code: string, url: string): Partial<MenuItem> | null => { export const getAsyncRoutesMap = () => {
const detailComponent = resolveComponent(`${code}/Detail`) const modules = {}
Object.keys(pagesComponent).forEach(item => {
const code = item.replace('../views/', '').replace('/index.vue', '')
modules[code] = pagesComponent[item]
})
return modules
}
const findDetailRouteItem = (item: any, components: any) => {
const { code, url } = item
const detailComponent = components[`${item.code}/Detail`]
if (detailComponent) { if (detailComponent) {
return { return [{
url: `${url}/detail/:id`, url: `${url}/detail/:id`,
code: `${code}/Detail`, code: `${code}/Detail`,
component: detailComponent, component: detailComponent,
name: '详情信息', name: '详情信息',
isShow: false isShow: false
}]
} }
} return []
return null
} }
const findDetailRoutes = (routes: any[]): any[] => { export const handleMenus = (menuData: any[], components: any, level: number = 1) => {
const newRoutes: any[] = [] if (menuData && menuData.length) {
routes.forEach((route: any) => { return menuData.map(item => {
newRoutes.push(route) const { isApp, appUrl } = hasAppID(item) // 是否为第三方程序
const detail = findDetailRouteItem(route.code, route.url) const meta = handleMeta(item, isApp)
if (detail) { const route: any = {
newRoutes.push(detail) path: isApp ? appUrl : `${item.url}`,
} name: isApp ? appUrl : item.code,
}) url: isApp ? appUrl : item.url,
return newRoutes meta: meta,
} children: item.children
const filterMenus = ['device/DashBoard']
export const filterCommunityMenus = (menuData: any[]) => {
return menuData.filter(item => {
if (item.children) {
item.children = filterCommunityMenus(item.children)
}
return !filterMenus.includes(item.code)
})
}
export const findCodeRoute = (asyncRouterMap: any[]) => {
const routeMeta = {}
function getDetail(code: string, url: string) {
const detail = findDetailRouteItem(code, url)
if (!detail) return
routeMeta[(detail as MenuItem).code] = {
path: detail.url,
title: detail.name,
parentName: code,
buttons: detail.buttons?.map((b: any) => b.id) || []
}
} }
function findChildren(data: any[], code: string = '') { route.component = findComponents(item.code, level, isApp, components)
data.forEach(route => { const extraRoute = hasExtraChildren(item, extraRouteObj)
routeMeta[route.code] = { const detail_components = findDetailRouteItem(item, components)
path: route.url || route.path,
title: route.meta?.title || route.name,
parentName: code,
buttons: route.buttons?.map((b: any) => b.id) || []
}
const otherRoutes = extraRouteObj[route.code]
if (otherRoutes) {
otherRoutes.children.map((item: any) => { if (extraRoute && !isApp) { // 包含额外的子路由
const _code = `${route.code}/${item.code}` route.children = route.children ? [...route.children, ...extraRoute] : extraRoute
const url = `${route.url}/${item.code}`
routeMeta[_code] = {
path: `${route.url}/${item.code}`,
title: item.name,
parentName: route.code,
buttons: item.buttons?.map((b: any) => b.id) || []
}
getDetail(_code, url)
})
}
const _code = route.appId ? `/${route.appId}${route.url}` : route.code
if (!route.appId) {
getDetail(_code, route.url)
} else {
routeMeta[_code] = {
path: `/${route.appId}${route.url}`,
title: route.name,
parentName: code,
buttons: []
}
}
if (route.children) {
findChildren(route.children, _code)
}
})
} }
findChildren(asyncRouterMap) if (detail_components.length) {
route.children = route.children ? route.children.concat(detail_components) : detail_components
return routeMeta
}
export function filterAsyncRouter(asyncRouterMap: any, parentCode = '', level = 1): { menusData: any, silderMenus: any } {
const _asyncRouterMap = cloneDeep(asyncRouterMap)
const menusData: any[] = []
const silderMenus: any[] = []
_asyncRouterMap.forEach((route: any) => {
const hasAppId = route.appId
const _route: any = {
path: hasAppId ? `/${route.appId}${route.url}` : `${route.url}`,
name: hasAppId ? `/${route.appId}${route.url}` : route.code,
url: hasAppId ? `/${route.appId}${route.url}` : route.url,
meta: {
icon: route.icon,
title: route.name,
hideInMenu: route.isShow === false,
buttons: route.buttons?.map((b: any) => b.id) || [],
isApp: hasAppId,
},
} }
const silder = { ..._route }
// 查看是否有隐藏子路由
route.children = findChildrenRoute(route.code, route.url, route.children)
route.children = findDetailRoutes(route.children)
if (route.children && route.children.length) { if (route.children && route.children.length) {
// TODO 查看是否具有详情页 route.children = handleMenus(route.children, components, level + 1)
const { menusData: _menusData, silderMenus: _silderMenus } = filterAsyncRouter(route.children, `${parentCode}/${route.code}`, level + 1) }
_route.children = _menusData
silder.children = _silderMenus
const showChildren = _route.children.some((r: any) => !r.meta.hideInMenu)
if (showChildren && _route.children.length) { const showChildren = route.children?.filter(r => !r.meta?.hideInMenu) || []
_route.component = level === 1 ? BasicLayoutPage : BlankLayoutPage
_route.redirect = _route.children[0].url if (route.children && route.children.length && showChildren.length) {
} else { route.redirect = showChildren[0].path
const myComponent = resolveComponent(route.code) }
// _route.component = myComponent ? myComponent : BlankLayoutPage;
if (!!myComponent) { return route
_route.component = myComponent; })
_route.children.map((r: any) => menusData.push(r)) }
delete _route.children
} else { return []
_route.component = BlankLayoutPage }
export const handleMenusMap = (menuData: any[], cb: (data: any) => void) => {
if (menuData && menuData.length) {
menuData.forEach(item => {
cb(item)
if (item.children) {
handleMenusMap(item.children, cb)
}
})
}
}
const hideInMenu = (code: string) => {
return ['account-center', 'message-subscribe'].includes(code)
}
export const handleSiderMenu = (menuData: any[]) => {
if (menuData && menuData.length) {
return menuData.map(item => {
const { isApp, appUrl } = hasAppID(item) // 是否为第三方程序
const meta = handleMeta(item, isApp)
const route: any = {
path: isApp ? appUrl : `${item.url}`,
name: isApp ? appUrl : item.code,
url: isApp ? appUrl : item.url,
meta: meta,
children: item.children
}
if (route.children && route.children.length) {
route.children = handleSiderMenu(route.children)
}
route.meta.hideInMenu = hideInMenu(item.code)
return route
})
}
return []
}
export const handleAuthMenu = (menuData: any[], cb: (code: any, buttons: any[]) => void) => {
if (menuData && menuData.length) {
return menuData.forEach(item => {
const { code, buttons, children} = item
if (buttons) {
cb(code, buttons.map(a => a.id))
}
if (children) {
handleAuthMenu(children, cb)
} }
}
} else {
if (hasAppId) {
_route.component = route.component || resolveComponent('iframe')
} else {
_route.component = route.component || resolveComponent(route.code) || BlankLayoutPage
}
}
menusData.push(_route)
silderMenus.push(silder)
}) })
return {
menusData,
silderMenus,
} }
} }

View File

@ -54,13 +54,13 @@
/> />
</j-form-item> </j-form-item>
<j-form-item <j-form-item
:name="['circuitBreaker', 'type']" :name="['configuration', 'inheritBreakerSpec', 'type']"
:rules="LeftTreeRules.type" :rules="LeftTreeRules.type"
label="故障处理" label="点位熔断处理"
> >
<j-card-select <j-card-select
:showImage="false" :showImage="false"
v-model:value="formData.circuitBreaker.type" v-model:value="formData.configuration.inheritBreakerSpec.type"
:options="[ :options="[
{ label: '降频', value: 'LowerFrequency' }, { label: '降频', value: 'LowerFrequency' },
{ label: '断开', value: 'Break' }, { label: '断开', value: 'Break' },
@ -70,7 +70,7 @@
/> />
</j-form-item> </j-form-item>
<p style="color: #616161"> <p style="color: #616161">
{{ getTypeTooltip(formData.circuitBreaker.type) }} {{ getTypeTooltip(formData.configuration.inheritBreakerSpec.type) }}
</p> </p>
<j-form-item <j-form-item
label="双字高低位切换" label="双字高低位切换"
@ -158,6 +158,7 @@
import { save, update } from '@/api/data-collect/collector'; import { save, update } from '@/api/data-collect/collector';
import { LeftTreeRules } from '../../data'; import { LeftTreeRules } from '../../data';
import type { FormInstance } from 'ant-design-vue'; import type { FormInstance } from 'ant-design-vue';
import {cloneDeep} from "lodash-es";
const loading = ref(false); const loading = ref(false);
const visibleEndian = ref(false); const visibleEndian = ref(false);
@ -214,9 +215,13 @@ const formData = ref({
endian: 'BIG', endian: 'BIG',
endianIn: 'BIG', endianIn: 'BIG',
requsetTimeout: 2000, requsetTimeout: 2000,
inheritBreakerSpec: {
type: 'LowerFrequency',
}
}, },
circuitBreaker: { circuitBreaker: {
type: 'LowerFrequency', // type: 'LowerFrequency',
type: 'Ignore'
}, },
description: '', description: '',
}); });
@ -243,19 +248,21 @@ const handleOk = async () => {
} }
}; };
const getTypeTooltip = (value: string) => const getTypeTooltip = (value: string) => {
value === 'LowerFrequency' switch (value) {
? '连续20次采集异常后降低采集频率至设定频率的1/10故障处理后采集频率将恢复至设定频率。' case 'LowerFrequency': return '连续20次采集异常后降低采集频率至设定频率的1/10故障处理后采集频率将恢复至设定频率。';
: value === 'Break' // case 'Break': return '10'
? '连续10分钟异常停止采集数据进入断开状态设备重新启用后恢复采集状态。' case 'Break': return '连续20次采集异常后降低采集频率至设定频率的1/1010分钟内未排除故障将停止采集。'
: '忽略异常保持原采集频率超时时间为5s。'; default: return '忽略异常,保持设定采集频率。';
}
}
const handleCancel = () => { const handleCancel = () => {
emit('change', false); emit('change', false);
}; };
const changeCardSelectType = (value: Array<string>) => { const changeCardSelectType = (value: Array<string>) => {
formData.value.circuitBreaker.type = value[0]; formData.value.configuration.inheritBreakerSpec.type = value[0];
}; };
const changeCardSelectEndian = (value: Array<string>) => { const changeCardSelectEndian = (value: Array<string>) => {
formData.value.configuration.endian = value[0]; formData.value.configuration.endian = value[0];
@ -281,7 +288,19 @@ watch(
watch( watch(
() => props.data, () => props.data,
(value) => { (value) => {
if (value.id) formData.value = value; if (value.id) {
let copyValue = cloneDeep(value)
if (!copyValue?.configuration?.inheritBreakerSpec) {
copyValue.configuration = {
...copyValue.configuration,
inheritBreakerSpec: {
type: value.circuitBreaker.type
}
}
copyValue.circuitBreaker.type = 'Ignore'
}
formData.value = copyValue
};
}, },
{ immediate: true, deep: true }, { immediate: true, deep: true },
); );

View File

@ -39,8 +39,16 @@
<j-tag <j-tag
class="tree-left-tag" class="tree-left-tag"
v-if="data.id !== '*'" v-if="data.id !== '*'"
:color="colorMap.get(data?.runningState?.value)" :color="
>{{ data?.runningState?.text }}</j-tag data?.uniformState?.value === 'normal' || data?.state?.value === 'disabled' ?
colorMap.get(data?.runningState?.value) :
colorMap.get(data?.uniformState?.value)
"
>{{
data?.uniformState?.value === 'normal' || data?.state?.value === 'disabled' ?
data?.runningState?.text :
data?.uniformState?.text
}}</j-tag
> >
<j-tag <j-tag
class="tree-left-tag2" class="tree-left-tag2"
@ -117,7 +125,7 @@
<script setup lang="ts" name="TreePage"> <script setup lang="ts" name="TreePage">
import { import {
queryCollector, queryCollectorTree,
queryChannelNoPaging, queryChannelNoPaging,
update, update,
remove, remove,
@ -245,7 +253,7 @@ const handleSearch = async (value: any) => {
!!value && (params.value = value); !!value && (params.value = value);
} }
spinning.value = true; spinning.value = true;
const res: any = await queryCollector(params.value); const res: any = await queryCollectorTree(params.value);
if (res.status === 200) { if (res.status === 200) {
if (clickSearch) { if (clickSearch) {
defualtDataSource.value = res.result; defualtDataSource.value = res.result;

View File

@ -1,7 +1,9 @@
export const colorMap = new Map(); export const colorMap = new Map();
colorMap.set('running', 'success'); colorMap.set('running', 'success');
colorMap.set('partialError', 'warning'); colorMap.set('partialError', 'warning');
colorMap.set('partError', 'warning'); // 部分错误
colorMap.set('failed', 'error'); colorMap.set('failed', 'error');
colorMap.set('allError', 'error'); // 全部错误
colorMap.set('stopped', 'default'); colorMap.set('stopped', 'default');
colorMap.set('processing', '#cccccc'); colorMap.set('processing', '#cccccc');
colorMap.set('enabled', 'processing'); colorMap.set('enabled', 'processing');

View File

@ -6,6 +6,8 @@
width="770px" width="770px"
@cancel="emits('close')" @cancel="emits('close')"
:confirmLoading="loading" :confirmLoading="loading"
:zIndex="1100"
:maskClosable='false'
> >
<j-form :model="form" layout="vertical" ref="formRef"> <j-form :model="form" layout="vertical" ref="formRef">
<j-row :gutter="24"> <j-row :gutter="24">
@ -66,7 +68,7 @@
:rules="[ :rules="[
{ {
pattern: /^1[3456789]\d{9}$/, pattern: /^1[3456789]\d{9}$/,
message: '请输入正确手机号', message: '请输入正确手机号',
}, },
]" ]"
> >

View File

@ -1,5 +1,5 @@
<template> <template>
<j-modal :width="'900px'" visible @cancel="emit('close')" :zIndex="1100"> <j-modal :maskClosable='false' :width="'900px'" visible @cancel="emit('close')" :zIndex="1100">
<template v-if="getType === 'notifier-weixin'"> <template v-if="getType === 'notifier-weixin'">
<j-spin :spinning="loading"> <j-spin :spinning="loading">
<div class="code" style="height: 450px"> <div class="code" style="height: 450px">

View File

@ -28,11 +28,14 @@
<template v-else> <template v-else>
<Detail <Detail
@unsubscribe="onUnSubscribe" @unsubscribe="onUnSubscribe"
@save="onSave" v-if="
v-if="current?.channelProvider !== 'inside-mail' && popoverVisible" current?.channelProvider !== 'inside-mail' &&
popoverVisible
"
:current="current" :current="current"
:data="props.data" :data="data"
@close="popoverVisible = false" @bindChange="onChange('bind')"
@infoChange="onChange('info')"
/> />
<PermissionButton <PermissionButton
v-else v-else
@ -51,12 +54,28 @@
{{ current?.name }} {{ current?.name }}
</j-ellipsis> </j-ellipsis>
</div> </div>
<EditInfo
v-if="editInfoVisible"
:data="user.userInfos"
@close="editInfoVisible = false"
@save="onSave"
/>
<Bind
@close="visible = false"
v-if="visible"
:data="data"
:current="current"
@save="onBindSave"
/>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { getImage } from '@/utils/comm'; import { getImage } from '@/utils/comm';
import Detail from './Detail.vue'; import Detail from './Detail.vue';
import { useUserInfo } from '@/store/userInfo';
import EditInfo from '../../EditInfo/index.vue';
import Bind from './Bind.vue';
const iconMap = new Map(); const iconMap = new Map();
iconMap.set('notifier-dingTalk', getImage('/notice-rule/dingtalk.png')); iconMap.set('notifier-dingTalk', getImage('/notice-rule/dingtalk.png'));
@ -81,19 +100,44 @@ const props = defineProps({
}, },
notifyChannels: { notifyChannels: {
type: Array, type: Array,
default: () => [] default: () => [],
} },
}); });
const user = useUserInfo();
const popoverVisible = ref<boolean>(false); const popoverVisible = ref<boolean>(false);
const editInfoVisible = ref<boolean>(false);
const visible = ref<boolean>(false);
const onChange = (type: 'bind' | 'info') => {
if(type === 'bind'){
editInfoVisible.value = true
} else {
visible.value = true
}
}
const onSave = () => {
editInfoVisible.value = false;
user.getUserInfo();
emit('save', props.current);
popoverVisible.value = false;
};
const onBindSave = () => {
visible.value = false
emit('save', props.current);
popoverVisible.value = false;
}
const onUnSubscribe = (dt: any) => { const onUnSubscribe = (dt: any) => {
emit('unsubscribe', dt); emit('unsubscribe', dt);
popoverVisible.value = false popoverVisible.value = false;
}; };
const onCheckChange = (dt: any) => { const onCheckChange = (dt: any) => {
emit('save', dt) emit('save', dt);
popoverVisible.value = false popoverVisible.value = false;
}; };
</script> </script>

View File

@ -21,29 +21,14 @@
> >
</div> </div>
</div> </div>
<EditInfo
v-if="editInfoVisible"
:data="user.userInfos"
@close="editInfoVisible = false"
@save="onSave"
/>
<Bind
@close="visible = false"
v-if="visible"
:data="props.data"
:current="current"
@save="onBindSave"
/>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { getIsBindThird } from '@/api/account/notificationSubscription'; import { getIsBindThird } from '@/api/account/notificationSubscription';
import { useUserInfo } from '@/store/userInfo'; import { useUserInfo } from '@/store/userInfo';
import EditInfo from '../../EditInfo/index.vue';
import Bind from './Bind.vue';
const user = useUserInfo(); const user = useUserInfo();
const emit = defineEmits(['save', 'unsubscribe', 'close']); const emit = defineEmits(['infoChange', 'unsubscribe', 'bindChange']);
const info = ref<any>(null); const info = ref<any>(null);
const props = defineProps({ const props = defineProps({
data: { data: {
@ -58,39 +43,23 @@ const props = defineProps({
}, },
}); });
const editInfoVisible = ref<boolean>(false);
const visible = ref<boolean>(false);
const getType = computed(() => { const getType = computed(() => {
return props.current?.channelProvider; return props.current?.channelProvider;
}); });
const onBind = () => { const onBind = () => {
if ( if (
['notifier-voice', 'notifier-sms', 'notifier-email'].includes( !['notifier-voice', 'notifier-sms', 'notifier-email'].includes(
props.current?.channelProvider, props.current?.channelProvider,
) )
) { ) {
editInfoVisible.value = true; emit('infoChange')
} else { } else {
visible.value = true emit('bindChange')
} }
}; };
const onSave = () => {
editInfoVisible.value = false;
user.getUserInfo();
emit('save', props.current);
emit('close')
};
const onBindSave = () => {
visible.value = false
emit('save', props.current);
emit('close')
}
const handleSearch = async () => { const handleSearch = async () => {
if ( if (
!['notifier-voice', 'notifier-sms', 'notifier-email'].includes( !['notifier-voice', 'notifier-sms', 'notifier-email'].includes(
@ -114,16 +83,6 @@ onMounted(() => {
handleSearch() handleSearch()
}) })
// watch(
// () => props.current,
// () => {
// handleSearch();
// },
// {
// immediate: true,
// deep: true,
// },
// );
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>

View File

@ -1,5 +1,5 @@
<template> <template>
<j-modal :width="'384px'" visible @cancel="emit('close')" :footer="null"> <j-modal :maskClosable='false' :width="'384px'" visible @cancel="emit('close')" :footer="null">
<template v-if="getType === 'notifier-dingTalk'"> <template v-if="getType === 'notifier-dingTalk'">
<div class="tip">请先绑定钉钉账号</div> <div class="tip">请先绑定钉钉账号</div>
</template> </template>

View File

@ -358,7 +358,7 @@ onMounted(() => {
}); });
onUnmounted(() => { onUnmounted(() => {
if (_delTag.value) { if (_delTag.value && dataSourceCache.value) {
// //
const arr = dataSourceCache.value.filter((i: any) => i?.original).map((item: any) => { const arr = dataSourceCache.value.filter((i: any) => i?.original).map((item: any) => {
return { return {

View File

@ -277,7 +277,7 @@ const initPage = async (newId: any) => {
} }
onBeforeRouteUpdate((to: any) => { onBeforeRouteUpdate((to: any) => {
if (to.params?.id!==instanceStore.current.id) { if (to.params?.id!==instanceStore.current.id && to.name === 'device/Instance/Detail') {
initPage(to.params?.id) initPage(to.params?.id)
} }
}) })

View File

@ -354,7 +354,7 @@ onMounted(() => {
}); });
onUnmounted(() => { onUnmounted(() => {
if (_delTag.value) { if (_delTag.value && dataSourceCache.value.length) {
// //
const arr = dataSourceCache.value.filter((i: any) => i?.plugin).map((item: any) => { const arr = dataSourceCache.value.filter((i: any) => i?.plugin).map((item: any) => {
return { return {

View File

@ -164,7 +164,7 @@ const deviceList = ref([
label: '网关子设备', label: '网关子设备',
value: 'childrenDevice', value: 'childrenDevice',
iconUrl: getImage('/device-type-2.png'), iconUrl: getImage('/device-type-2.png'),
tooltip: '作为网关的子设备,网关代理连接到物联网平台', tooltip: '作为网关的子设备,网关代理连接到物联网平台',
}, },
{ {
label: '网关设备', label: '网关设备',

View File

@ -20,8 +20,12 @@
<j-scrollbar v-if='deviceList.length'> <j-scrollbar v-if='deviceList.length'>
<j-spin :spinning='deviceSpinning'> <j-spin :spinning='deviceSpinning'>
<div class='device-list-items'> <div class='device-list-items'>
<template v-for='item in deviceList'>
<template v-if='disabledKeys.includes(item.id)'>
<j-tooltip
title='该设备已绑定平台设备'
>
<div <div
v-for='item in deviceList'
:class='{ :class='{
"device-list-item": true, "device-list-item": true,
"active": checkKeys.includes(item.id), "active": checkKeys.includes(item.id),
@ -29,21 +33,45 @@
}' }'
@click='() => deviceClick(item.id, item)' @click='() => deviceClick(item.id, item)'
> >
<template v-if='disabledKeys.includes(item.id)'> <div class='item-title'>
<j-tooltip <span class="title-name no-tooltip">
title='该设备已绑定平台设备' {{item.name}}
> </span>
<span class='item-title'>{{ item.id }}</span> <span class="title-id">
({{ item.id }})
</span>
</div>
</div>
</j-tooltip> </j-tooltip>
</template> </template>
<span v-else class='item-title'> <div
{{ item.id }} v-else
:class='{
"device-list-item": true,
"active": checkKeys.includes(item.id),
"disabled": disabledKeys.includes(item.id)
}'
@click='() => deviceClick(item.id, item)'
>
<div class='item-title'>
<span class="title-name">
<j-ellipsis>
{{item.name}}
</j-ellipsis>
</span> </span>
<span class="title-id">
<j-ellipsis>
({{ item.id }})
</j-ellipsis>
</span>
</div>
<a-icon <a-icon
v-if='checkKeys.includes(item.id)' v-if='checkKeys.includes(item.id)'
type='CheckOutlined' type='CheckOutlined'
/> />
</div> </div>
</template>
</div> </div>
</j-spin> </j-spin>
</j-scrollbar> </j-scrollbar>
@ -278,7 +306,31 @@ onMounted(() => {
justify-content: space-between; justify-content: space-between;
> .item-title { > .item-title {
display: flex;
min-width: 0;
flex-grow: 1;
gap: 8px;
.title-name {
max-width: 70%;
}
.no-tooltip {
overflow: hidden;
vertical-align: bottom;
cursor: pointer;
display: -webkit-box;
-webkit-box-orient: vertical;
word-break: break-all;
max-height: 380px;
-webkit-line-clamp: 1;
text-overflow: ellipsis;
}
.title-id {
flex: 1 1 auto; flex: 1 1 auto;
color: #a3a3a3;
}
} }
&:hover { &:hover {
@ -288,6 +340,10 @@ onMounted(() => {
&.active { &.active {
background-color: rgba(153, 153, 153, 0.06); background-color: rgba(153, 153, 153, 0.06);
color: @primary-color; color: @primary-color;
.title-id {
color: @primary-color;
opacity: .8;
}
} }
&.disabled { &.disabled {

View File

@ -192,7 +192,7 @@ import type {
ProductItem, ProductItem,
} from '@/views/device/Product/typings'; } from '@/views/device/Product/typings';
import type { PropType } from 'vue'; import type { PropType } from 'vue';
import { onBeforeRouteLeave } from 'vue-router' import {useRouter, onBeforeRouteUpdate} from 'vue-router'
import { useMetadata, useOperateLimits } from './hooks'; import { useMetadata, useOperateLimits } from './hooks';
import {TypeStringMap, useColumns} from './columns'; import {TypeStringMap, useColumns} from './columns';
import { levelMap, sourceMap, expandsType, limitsMap } from './utils'; import { levelMap, sourceMap, expandsType, limitsMap } from './utils';
@ -211,6 +211,8 @@ import { Modal } from 'jetlinks-ui-components'
import {EventEmitter} from "@/utils/utils"; import {EventEmitter} from "@/utils/utils";
import {watch} from "vue"; import {watch} from "vue";
import {cloneDeep} from "lodash"; import {cloneDeep} from "lodash";
import {useSystem} from "store/system";
import {storeToRefs} from "pinia";
const props = defineProps({ const props = defineProps({
target: { target: {
@ -229,6 +231,10 @@ const props = defineProps({
const _target = inject<'device' | 'product'>('_metadataType', props.target); const _target = inject<'device' | 'product'>('_metadataType', props.target);
const system = useSystem();
const {basicLayout} = storeToRefs(system);
const router = useRouter()
const { data: metadata, noEdit, productNoEdit } = useMetadata(_target, props.type); const { data: metadata, noEdit, productNoEdit } = useMetadata(_target, props.type);
const { hasOperate } = useOperateLimits(_target); const { hasOperate } = useOperateLimits(_target);
@ -337,28 +343,11 @@ const handleAddClick = async (_data?: any, index?: number) => {
const newObject = _data || getDataByType() const newObject = _data || getDataByType()
// const data = [...dataSource.value];
//
// if (index !== undefined) {
// //
// const _data = await tableRef.value.getData()
// console.log(_data)
// if (_data) {
// data.splice(index + 1, 0, newObject);
// }
// } else {
// data.push(newObject);
// }
// dataSource.value = data
const _addData = await tableRef.value.addItem(newObject, index) const _addData = await tableRef.value.addItem(newObject, index)
if (_addData.length === 1) { if (_addData.length === 1) {
showLastDelete.value = true showLastDelete.value = true
} }
showSave.value = true showSave.value = true
// const _index = index !== undefined ? index + 1 : 0
// tableRef.value?.addItemAll?.(_index)
}; };
const copyItem = (record: any, index: number) => { const copyItem = (record: any, index: number) => {
@ -452,16 +441,24 @@ const tabsChange = inject('tabsChange')
const parentTabsChange = (next?: Function) => { const parentTabsChange = (next?: Function) => {
if (editStatus.value) { if (editStatus.value) {
Modal.confirm({ const modal = Modal.confirm({
content: '页面改动数据未保存', content: '页面改动数据未保存',
okText: '保存', okText: '保存',
cancelText: '不保存', cancelText: '不保存',
zIndex: 1400, zIndex: 1400,
closable: true,
onOk: () => { onOk: () => {
handleSaveClick(next as Function) handleSaveClick(next as Function)
}, },
onCancel: () => { onCancel: (e: any) => {
if (!e.triggerCancel) { //
modal.destroy();
(next as Function)?.() (next as Function)?.()
} else {//
const paths = router.currentRoute.value.matched
// basicLayout.value.selectedKeys = paths.map(item => item.path)
basicLayout.value.openKeys = paths.map(item => item.path)
}
} }
}) })
} else { } else {
@ -479,7 +476,7 @@ watch(() => metadata.value, () => {
dataSource.value = metadata.value dataSource.value = metadata.value
}, { immediate: true }) }, { immediate: true })
onBeforeRouteLeave((to, from, next) => { onBeforeRouteUpdate((to, from, next) => {
parentTabsChange(next as Function) parentTabsChange(next as Function)
}) })

View File

@ -302,22 +302,22 @@ export const useColumns = (type?: MetadataType, target?: 'device' | 'product', n
title: '输入参数', title: '输入参数',
dataIndex: 'inputs', dataIndex: 'inputs',
width: 120, width: 120,
form: { // form: {
required: true, // required: true,
rules: [{ // rules: [{
callback(rule:any,value: any, dataSource: any[]) { // callback(rule:any,value: any, dataSource: any[]) {
const field = rule.field.split('.') // const field = rule.field.split('.')
const fieldIndex = Number(field[1]) // const fieldIndex = Number(field[1])
//
const values = dataSource.find((item, index) => index === fieldIndex) // const values = dataSource.find((item, index) => index === fieldIndex)
//
return validatorConfig({ // return validatorConfig({
type: 'object', // type: 'object',
properties: values.inputs // properties: values.inputs
}, true) // }, true)
} // }
}] // }]
}, // },
control(newValue, oldValue) { control(newValue, oldValue) {
if (newValue && !oldValue) { if (newValue && !oldValue) {
return true return true

View File

@ -193,11 +193,11 @@
<div class="progress-text"> <div class="progress-text">
<div> <div>
{{ {{
( slotProps.totalFlow ? (
(slotProps.usedFlow / (slotProps.usedFlow /
slotProps.totalFlow) * slotProps.totalFlow) *
100 100
).toFixed(2) ).toFixed(2) : '0.00'
}} }}
% %
</div> </div>
@ -209,9 +209,9 @@
:strokeColor="'#ADC6FF'" :strokeColor="'#ADC6FF'"
:showInfo="false" :showInfo="false"
:percent=" :percent="
(slotProps.usedFlow / slotProps.totalFlow ? (slotProps.usedFlow /
slotProps.totalFlow) * slotProps.totalFlow) *
100 100 : 0
" "
/> />
</div> </div>

View File

@ -139,6 +139,7 @@ const getCPUEcharts = async (val: any) => {
const _cpuOptions = {}; const _cpuOptions = {};
const _cpuXAxis = new Set(); const _cpuXAxis = new Set();
if (res.result?.length) { if (res.result?.length) {
isEmpty.value = false;
// //
// const filterArray = props.isNoCommunity ? res.result.filter((item : any) => item.data?.clusterNodeId === props.serviceId) : res.result // const filterArray = props.isNoCommunity ? res.result.filter((item : any) => item.data?.clusterNodeId === props.serviceId) : res.result
const filterArray = res.result const filterArray = res.result

View File

@ -103,6 +103,7 @@ const getJVMEcharts = async (val: any) => {
const _jvmOptions = {}; const _jvmOptions = {};
const _jvmXAxis = new Set(); const _jvmXAxis = new Set();
if (res.result?.length) { if (res.result?.length) {
isEmpty.value = false;
// const filterArray = props.isNoCommunity ? res.result.filter((item : any) => item.data?.clusterNodeId === props.serviceId) : res.result // const filterArray = props.isNoCommunity ? res.result.filter((item : any) => item.data?.clusterNodeId === props.serviceId) : res.result
const filterArray = res.result const filterArray = res.result
filterArray.forEach((item: any) => { filterArray.forEach((item: any) => {

View File

@ -114,6 +114,7 @@ const getNetworkEcharts = async (val: any) => {
const _networkOptions = {}; const _networkOptions = {};
const _networkXAxis = new Set(); const _networkXAxis = new Set();
if (resp.result.length) { if (resp.result.length) {
isEmpty.value = false;
const filterArray = resp.result const filterArray = resp.result
// const filterArray = resp.result.filter((item : any) => item.data?.clusterNodeId === props.serviceId) // const filterArray = resp.result.filter((item : any) => item.data?.clusterNodeId === props.serviceId)
filterArray.forEach((item: any) => { filterArray.forEach((item: any) => {

View File

@ -44,7 +44,7 @@ watch(() => props.options, () => {
echartsRender() echartsRender()
}) })
} }
}, { deep: true }) }, { immediate: true, deep: true })
</script> </script>

View File

@ -6,6 +6,10 @@
@cancel="emit('close')" @cancel="emit('close')"
@ok="onSave" @ok="onSave"
> >
<div class="alert">
<AIcon type="InfoCircleOutlined" />
通过角色控制{{ name }}的所有的通知方式可被哪些用户订阅
</div>
<Role v-model="_selectedRowKeys" :gridColumn="2" /> <Role v-model="_selectedRowKeys" :gridColumn="2" />
</j-modal> </j-modal>
</template> </template>
@ -20,6 +24,10 @@ const props = defineProps({
type: Array as PropType<string[]>, type: Array as PropType<string[]>,
default: () => [], default: () => [],
}, },
name: {
type: String,
default: ''
}
}); });
const _selectedRowKeys = ref<string[]>([]); const _selectedRowKeys = ref<string[]>([]);
@ -32,3 +40,14 @@ const onSave = () => {
emit('save', _selectedRowKeys.value); emit('save', _selectedRowKeys.value);
}; };
</script> </script>
<style lang="less" scoped>
.alert {
height: 40px;
padding: 0 20px 0 10px;
margin-bottom: 10px;
color: rgba(0, 0, 0, 0.55);
line-height: 40px;
background-color: #f6f6f6;
}
</style>

View File

@ -22,7 +22,7 @@
<template #title> <template #title>
<span v-if="!update">暂无权限请联系管理员</span> <span v-if="!update">暂无权限请联系管理员</span>
<div v-else> <div v-else>
用于配制外层权限<br />未配置外层权限将执行通知方式中配置的权限<br />配置外层权限后将覆盖所有通知方式中配置的权限 通过角色控制{{ data.name }}的所有的通知方式可被哪些用户订阅
</div> </div>
</template> </template>
<j-button <j-button
@ -129,6 +129,7 @@
@save="onSave" @save="onSave"
:loading="loading" :loading="loading"
:provider="provider" :provider="provider"
:name="data.name"
/> />
<Detail <Detail
:data="current" :data="current"
@ -140,6 +141,7 @@
:data="data?.grant?.role?.idList" :data="data?.grant?.role?.idList"
@close="authVisible = false" @close="authVisible = false"
@save="onAuthSave" @save="onAuthSave"
:name="data.name"
/> />
</template> </template>
@ -345,6 +347,9 @@ const onSwitchChange = (e: boolean) => {
let _checked: boolean = e ? _value?.open : _value?.close; let _checked: boolean = e ? _value?.open : _value?.close;
if (_checked) { if (_checked) {
onAction(e); onAction(e);
} else {
if (e) {
onAction(e);
} else { } else {
Modal.confirm({ Modal.confirm({
title: e title: e
@ -390,6 +395,7 @@ const onSwitchChange = (e: boolean) => {
onCancel() {}, onCancel() {},
}); });
} }
}
}; };
const onSave = (_data: any) => { const onSave = (_data: any) => {
@ -489,7 +495,7 @@ const onSave = (_data: any) => {
.box-item-add { .box-item-add {
cursor: pointer; cursor: pointer;
background-color: #F7F8FA; background-color: #f7f8fa;
width: 54px; width: 54px;
height: 54px; height: 54px;
display: flex; display: flex;

View File

@ -70,7 +70,7 @@
</j-alert> </j-alert>
</div> </div>
</template> </template>
<j-scrollbar height="400px"> <j-scrollbar :height="gridColumn <= 2 ? '320px' : '250px'">
<j-pro-table <j-pro-table
ref="tableRef" ref="tableRef"
:columns="columns" :columns="columns"

View File

@ -20,7 +20,7 @@
<AIcon type="InfoCircleOutlined" /> <AIcon type="InfoCircleOutlined" />
钉钉群机器人类型的配置在当前页面将被过滤 钉钉群机器人类型的配置在当前页面将被过滤
</div> </div>
<div style="height: 400px; overflow-y: auto"> <div style="max-height: 400px; overflow-y: auto">
<JProTable <JProTable
:columns="columns" :columns="columns"
:request="query" :request="query"

View File

@ -19,7 +19,7 @@
<AIcon type="InfoCircleOutlined" /> <AIcon type="InfoCircleOutlined" />
已规定固定收信人的模板在当前页面将被过滤 已规定固定收信人的模板在当前页面将被过滤
</div> </div>
<div style="height: 400px; overflow-y: auto"> <div style="max-height: 400px; overflow-y: auto">
<JProTable <JProTable
:columns="columns" :columns="columns"
:request="(e) => handleData(e)" :request="(e) => handleData(e)"

View File

@ -69,10 +69,12 @@ onMounted(() => {
iconUrl: iconMap.get(item.id), iconUrl: iconMap.get(item.id),
}; };
}); });
emit('update:value', options.value?.[0]?.value);
emit('update:name', options.value?.[0]?.label);
emit('change', {label: options.value?.[0]?.label, value: options.value?.[0]?.value});
} }
loading.value = false; loading.value = false;
}); });
notifyType.value = props.value;
}); });
</script> </script>

View File

@ -11,11 +11,12 @@
<j-step v-for="(item, index) in stepList" :key="item"> <j-step v-for="(item, index) in stepList" :key="item">
<template #title> <template #title>
{{ item {{ item
}}<j-tooltip }}<j-tooltip v-if="index === 4">
v-if="index === 4"
>
<template #title> <template #title>
<span>内层权限配置<br />外层权限已配置的情况下将取外层权限与当前页面分配权限的交集向对应角色发送通知<br />外层权限未配置的情况下将按此处配置的权限发送通知</span> <span>
通过角色控制{{ name }}下的{{ showName }}通知可被哪些用户订阅<br />
注意当前配置会被外层{{ name }}中的权限控制覆盖
</span>
</template> </template>
<AIcon type="QuestionCircleOutlined" <AIcon type="QuestionCircleOutlined"
/></j-tooltip> /></j-tooltip>
@ -32,6 +33,7 @@
<template v-if="current === 0"> <template v-if="current === 0">
<NotifyWay <NotifyWay
:value="formModel.channelProvider" :value="formModel.channelProvider"
v-model:name="showName"
@change="onWayChange" @change="onWayChange"
/> />
</template> </template>
@ -161,6 +163,10 @@ const props = defineProps({
type: String, type: String,
default: '', default: '',
}, },
name: {
type: String,
default: '',
},
}); });
const stepList = [ const stepList = [
@ -190,14 +196,15 @@ const formModel = reactive<{
}); });
const variableRef = ref(); const variableRef = ref();
const formRef = ref(); const formRef = ref();
const showName = ref<string>('钉钉')
const _getType = computed(() => { const _getType = computed(() => {
if(['notifier-dingTalk'].includes(props.data?.channelProvider)) { if (['notifier-dingTalk'].includes(props.data?.channelProvider)) {
return ['user', 'tag'] return ['user', 'tag'];
} else { } else {
return ['user', 'org', 'tag'] return ['user', 'org', 'tag'];
} }
}) });
const _variableDefinitions = computed(() => { const _variableDefinitions = computed(() => {
return variable.value.filter((item: any) => { return variable.value.filter((item: any) => {

View File

@ -3738,7 +3738,7 @@ jetlinks-store@^0.0.3:
resolved "https://registry.npmjs.org/jetlinks-store/-/jetlinks-store-0.0.3.tgz" resolved "https://registry.npmjs.org/jetlinks-store/-/jetlinks-store-0.0.3.tgz"
integrity sha512-AZf/soh1hmmwjBZ00fr1emuMEydeReaI6IBTGByQYhTmK1Zd5pQAxC7WLek2snRAn/HHDgJfVz2hjditKThl6Q== integrity sha512-AZf/soh1hmmwjBZ00fr1emuMEydeReaI6IBTGByQYhTmK1Zd5pQAxC7WLek2snRAn/HHDgJfVz2hjditKThl6Q==
jetlinks-ui-components@^1.0.23, jetlinks-ui-components@^1.0.26: jetlinks-ui-components@^1.0.23:
version "1.0.26" version "1.0.26"
resolved "http://registry.jetlinks.cn/jetlinks-ui-components/-/jetlinks-ui-components-1.0.26.tgz#26896c578396b09d49649ac87c3943491af3a9ae" resolved "http://registry.jetlinks.cn/jetlinks-ui-components/-/jetlinks-ui-components-1.0.26.tgz#26896c578396b09d49649ac87c3943491af3a9ae"
integrity sha512-HkLk52C6pDKe/Ca9O4w34h1/PrC7GdBUheiicPOX2V/Lc49N+WzI9wmrCd82XBm8MocPM4gAOJxNaTxY20EO9w== integrity sha512-HkLk52C6pDKe/Ca9O4w34h1/PrC7GdBUheiicPOX2V/Lc49N+WzI9wmrCd82XBm8MocPM4gAOJxNaTxY20EO9w==
@ -3753,6 +3753,21 @@ jetlinks-ui-components@^1.0.23, jetlinks-ui-components@^1.0.26:
sortablejs "^1.15.0" sortablejs "^1.15.0"
vuedraggable "^4.1.0" vuedraggable "^4.1.0"
jetlinks-ui-components@^1.0.27:
version "1.0.27"
resolved "http://registry.jetlinks.cn/jetlinks-ui-components/-/jetlinks-ui-components-1.0.27.tgz#c2e62ebf26d9465aed5489fdde37297738d57f2b"
integrity sha512-IVWCPfC5awTIIMQ5t6V5KTHLjfENj0owb6HsHxqRLrHebz7FaDng6oNfebvn17P+gHGVwwMemTCF7b8PpWFiwg==
dependencies:
"@vueuse/core" "^9.12.0"
"@vueuse/router" "^9.13.0"
ant-design-vue "^3.2.15"
colorpicker-v3 "^2.10.2"
jetlinks-ui-components "^1.0.23"
lodash-es "^4.17.21"
monaco-editor "^0.40.0"
sortablejs "^1.15.0"
vuedraggable "^4.1.0"
js-cookie@^3.0.1: js-cookie@^3.0.1:
version "3.0.1" version "3.0.1"
resolved "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.1.tgz" resolved "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.1.tgz"