fix: 合并冲突
This commit is contained in:
commit
de0159ab2e
|
@ -83,22 +83,22 @@ export const batchDeleteDevice = (data: string[]) => server.put(`/device-instanc
|
|||
* @param type 文件类型
|
||||
* @returns
|
||||
*/
|
||||
export const deviceTemplateDownload = (productId: string, type: string) => `${BASE_API_PATH}/device-instance/${productId}/template.${type}`
|
||||
export const deviceTemplateDownload = (productId: string, type: string) => `${BASE_API_PATH}/device-instance/${productId}/template.${type}`
|
||||
|
||||
/**
|
||||
* 设备导入
|
||||
* @param productId 产品id
|
||||
* @param type 文件类型
|
||||
* @returns
|
||||
*/
|
||||
export const deviceImport = (productId: string, fileUrl: string, autoDeploy: boolean) => `${BASE_API_PATH}/device-instance/${productId}/import?fileUrl=${fileUrl}&autoDeploy=${autoDeploy}&:X_Access_Token=${LocalStore.get(TOKEN_KEY)}`
|
||||
|
||||
/**
|
||||
* 设备导出
|
||||
* @param productId 产品id
|
||||
* @param type 文件类型
|
||||
* @returns
|
||||
*/
|
||||
/**
|
||||
* 设备导入
|
||||
* @param productId 产品id
|
||||
* @param type 文件类型
|
||||
* @returns
|
||||
*/
|
||||
export const deviceImport = (productId: string, fileUrl: string, autoDeploy: boolean) => `${BASE_API_PATH}/device-instance/${productId}/import?fileUrl=${fileUrl}&autoDeploy=${autoDeploy}&:X_Access_Token=${LocalStore.get(TOKEN_KEY)}`
|
||||
|
||||
/**
|
||||
* 设备导出
|
||||
* @param productId 产品id
|
||||
* @param type 文件类型
|
||||
* @returns
|
||||
*/
|
||||
export const deviceExport = (productId: string, type: string) => `${BASE_API_PATH}/device-instance${!!productId ? '/' + productId : ''}/export.${type}`
|
||||
|
||||
/**
|
||||
|
@ -143,7 +143,7 @@ export const _disconnect = (id: string) => server.post(`/device-instance/${id}/d
|
|||
*/
|
||||
export const queryUserListNoPaging = () => server.post(`/user/_query/no-paging`, {
|
||||
paging: false,
|
||||
sorts: [{name: 'name', order: "asc"}]
|
||||
sorts: [{ name: 'name', order: "asc" }]
|
||||
})
|
||||
|
||||
/**
|
||||
|
@ -347,4 +347,59 @@ export const settingProperties = (deviceId: string, data: any) => server.put(`/d
|
|||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
export const execute = (id: string, action: string, data: any) => server.post(`/device/invoked/${id}/function/${action}`, data)
|
||||
export const execute = (id: string, action: string, data: any) => server.post(`/device/invoked/${id}/function/${action}`, data)
|
||||
|
||||
/**
|
||||
* 查询通道列表不分页
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
export const queryChannelNoPaging = (data: any) => server.post(`data-collect/channel/_query/no-paging`, data)
|
||||
|
||||
/**
|
||||
* 查询采集器列表不分页
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
export const queryCollectorNoPaging = (data: any) => server.post(`/data-collect/collector/_query/no-paging`, data)
|
||||
|
||||
/**
|
||||
* 查询点位列表不分页
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
export const queryPointNoPaging = (data: any) => server.post(`/data-collect/point/_query/no-paging`, data)
|
||||
|
||||
/**
|
||||
* 查询映射列表
|
||||
* @param thingType
|
||||
* @param thingId
|
||||
* @param params
|
||||
* @returns
|
||||
*/
|
||||
export const queryMapping = (thingType: string, thingId: any, params?: any) => server.get(`/things/collector/${thingType}/${thingId}/_query`, params)
|
||||
|
||||
/**
|
||||
* 删除映射
|
||||
* @param thingType
|
||||
* @param thingId
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
export const removeMapping = (thingType: string, thingId: any, data?: any) => server.post(`/things/collector/${thingType}/${thingId}/_delete`, data)
|
||||
|
||||
/**
|
||||
* 映射树
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
export const treeMapping = (data?: any) => server.post(`/data-collect/channel/_all/tree`, data)
|
||||
|
||||
/**
|
||||
* 保存映射
|
||||
* @param thingId
|
||||
* @param provider
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
export const saveMapping = (thingId: any, provider: string, data?: any) => server.patch(`/things/collector/device/${thingId}/${provider}`, data)
|
|
@ -49,6 +49,7 @@ const iconKeys = [
|
|||
'PartitionOutlined',
|
||||
'ShareAltOutlined',
|
||||
'playCircleOutlined',
|
||||
'RightOutlined'
|
||||
'FileTextOutlined',
|
||||
'UploadOutlined'
|
||||
]
|
||||
|
|
|
@ -110,12 +110,19 @@ import { PropType } from 'vue'
|
|||
import type { SearchItemData, SearchProps, Terms } from './types'
|
||||
import { cloneDeep, get, isArray, isFunction } from 'lodash-es'
|
||||
import { filterTreeSelectNode, filterSelectNode } from '@/utils/comm'
|
||||
import { useUrlSearchParams } from '@vueuse/core'
|
||||
|
||||
type ItemType = SearchProps['type']
|
||||
|
||||
interface Emit {
|
||||
(e: 'change', data: SearchItemData): void
|
||||
}
|
||||
type UrlParam = {
|
||||
q: string | null
|
||||
target: string | null
|
||||
}
|
||||
|
||||
const urlParams = useUrlSearchParams<UrlParam>('hash')
|
||||
|
||||
const props = defineProps({
|
||||
columns: {
|
||||
|
@ -134,6 +141,10 @@ const props = defineProps({
|
|||
termsItem: {
|
||||
type: Object as PropType<Terms>,
|
||||
default: {}
|
||||
},
|
||||
reset: {
|
||||
type: Number,
|
||||
default: 1
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -278,28 +289,35 @@ const valueChange = () => {
|
|||
})
|
||||
}
|
||||
|
||||
const handleQuery = (_params: UrlParam) => {
|
||||
if (_params.q) {
|
||||
const path = props.index < 4 ? [0, 'terms', props.index - 1] : [1, 'terms', props.index - 4]
|
||||
const itemData: SearchItemData = get(props.termsItem.terms, path)
|
||||
if (itemData) {
|
||||
termsModel.type = itemData.type
|
||||
termsModel.column = itemData.column
|
||||
termsModel.termType = itemData.termType
|
||||
termsModel.value = itemData.value
|
||||
const item = columnOptionMap.get(itemData.column)
|
||||
getComponent(item.type) // 处理Item的组件类型
|
||||
|
||||
// 处理options 以及 request
|
||||
if ('options' in item) {
|
||||
handleItemOptions(item.options)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleItem()
|
||||
|
||||
watch( props.termsItem, (newValue) => {
|
||||
nextTick(() => {
|
||||
handleQuery(urlParams)
|
||||
})
|
||||
|
||||
const path = props.index < 4 ? [0, 'terms', props.index - 1] : [1, 'terms', props.index - 4]
|
||||
const itemData: SearchItemData = get(newValue.terms, path)
|
||||
if (itemData) {
|
||||
termsModel.type = itemData.type
|
||||
termsModel.column = itemData.column
|
||||
termsModel.termType = itemData.termType
|
||||
termsModel.value = itemData.value
|
||||
const item = columnOptionMap.get(itemData.column)
|
||||
getComponent(item.type) // 处理Item的组件类型
|
||||
|
||||
// 处理options 以及 request
|
||||
if ('options' in item) {
|
||||
handleItemOptions(item.options)
|
||||
}
|
||||
} else {
|
||||
handleItem()
|
||||
}
|
||||
}, { immediate: true, deep: true })
|
||||
watch(() => props.reset, () => {
|
||||
handleItem()
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
<div v-if='props.type === "advanced"' :class='["JSearch-content senior", expand ? "senior-expand" : "", screenSize ? "big" : "small"]'>
|
||||
<div :class='["JSearch-items", expand ? "items-expand" : "", layout]'>
|
||||
<div class='left'>
|
||||
<SearchItem :expand='expand' :index='1' :columns='searchItems' @change='(v) => itemValueChange(v, 1)' :termsItem='terms'/>
|
||||
<SearchItem v-if='expand' :expand='expand' :index='2' :columns='searchItems' @change='(v) => itemValueChange(v, 2)' :termsItem='terms'/>
|
||||
<SearchItem v-if='expand' :expand='expand' :index='3' :columns='searchItems' @change='(v) => itemValueChange(v, 3)' :termsItem='terms'/>
|
||||
<SearchItem :expand='expand' :index='1' :columns='searchItems' @change='(v) => itemValueChange(v, 1)' :termsItem='terms' :reset='resetNumber'/>
|
||||
<SearchItem v-if='expand' :expand='expand' :index='2' :columns='searchItems' @change='(v) => itemValueChange(v, 2)' :termsItem='terms' :reset='resetNumber'/>
|
||||
<SearchItem v-if='expand' :expand='expand' :index='3' :columns='searchItems' @change='(v) => itemValueChange(v, 3)' :termsItem='terms' :reset='resetNumber'/>
|
||||
</div>
|
||||
<div class='center' v-if='expand'>
|
||||
<a-select
|
||||
|
@ -16,9 +16,9 @@
|
|||
/>
|
||||
</div>
|
||||
<div class='right' v-if='expand'>
|
||||
<SearchItem :expand='expand' :index='4' :columns='searchItems' @change='(v) => itemValueChange(v, 4)' :termsItem='terms'/>
|
||||
<SearchItem :expand='expand' :index='5' :columns='searchItems' @change='(v) => itemValueChange(v, 5)' :termsItem='terms'/>
|
||||
<SearchItem :expand='expand' :index='6' :columns='searchItems' @change='(v) => itemValueChange(v, 6)' :termsItem='terms'/>
|
||||
<SearchItem :expand='expand' :index='4' :columns='searchItems' @change='(v) => itemValueChange(v, 4)' :termsItem='terms' :reset='resetNumber'/>
|
||||
<SearchItem :expand='expand' :index='5' :columns='searchItems' @change='(v) => itemValueChange(v, 5)' :termsItem='terms' :reset='resetNumber'/>
|
||||
<SearchItem :expand='expand' :index='6' :columns='searchItems' @change='(v) => itemValueChange(v, 6)' :termsItem='terms' :reset='resetNumber'/>
|
||||
</div>
|
||||
</div>
|
||||
<div :class='["JSearch-footer", expand ? "expand" : ""]'>
|
||||
|
@ -40,7 +40,7 @@
|
|||
<div v-else class='JSearch-content simple big'>
|
||||
<div class='JSearch-items'>
|
||||
<div class='left'>
|
||||
<SearchItem :expand='false' :index='1' :columns='searchItems' @change='(v) => itemValueChange(v, 1)' :termsItem='terms'/>
|
||||
<SearchItem :expand='false' :index='1' :columns='searchItems' @change='(v) => itemValueChange(v, 1)' :termsItem='terms' :reset='resetNumber'/>
|
||||
</div>
|
||||
</div>
|
||||
<div class='JSearch-footer'>
|
||||
|
@ -114,8 +114,10 @@ const historyList = ref([])
|
|||
const layout = ref('horizontal')
|
||||
// 当前组件宽度 true 大于1000
|
||||
const screenSize = ref(true)
|
||||
const resetNumber = ref(1)
|
||||
|
||||
const searchItems = ref<SearchProps[]>([])
|
||||
|
||||
// 当前查询条件
|
||||
const terms = reactive<Terms>({ terms: [] })
|
||||
|
||||
|
@ -134,7 +136,8 @@ const searchParams = reactive({
|
|||
const handleItems = () => {
|
||||
searchItems.value = []
|
||||
columnOptionMap.clear()
|
||||
props.columns!.forEach((item, index) => {
|
||||
const cloneColumns = cloneDeep(props.columns)
|
||||
cloneColumns!.forEach((item, index) => {
|
||||
if (item.search && Object.keys(item.search).length) {
|
||||
columnOptionMap.set(item.dataIndex, item.search)
|
||||
searchItems.value.push({
|
||||
|
@ -231,6 +234,8 @@ const reset = () => {
|
|||
urlParams.q = null
|
||||
urlParams.target = null
|
||||
}
|
||||
resetNumber.value += 1
|
||||
emit('search', terms)
|
||||
}
|
||||
|
||||
watch(width, (value) => {
|
||||
|
|
|
@ -14,56 +14,45 @@ const router = createRouter({
|
|||
}
|
||||
})
|
||||
|
||||
const filterPath = [
|
||||
'/form',
|
||||
'/search'
|
||||
]
|
||||
|
||||
router.beforeEach((to, from, next) => {
|
||||
// TODO 切换路由取消请求
|
||||
const isFilterPath = filterPath.includes(to.path)
|
||||
if (isFilterPath) {
|
||||
next()
|
||||
} else {
|
||||
const token = getToken()
|
||||
if (token) {
|
||||
if (to.path === LoginPath) {
|
||||
next({ path: '/' })
|
||||
} else {
|
||||
const userInfo = useUserInfo()
|
||||
const system = useSystem()
|
||||
const menu = useMenuStore()
|
||||
|
||||
if (!menu.siderMenus.length) {
|
||||
userInfo.getUserInfo().then(() => {
|
||||
system.getSystemVersion().then((menuData: any[]) => {
|
||||
menuData.forEach(r => {
|
||||
router.addRoute('base', r)
|
||||
})
|
||||
// router.addRoute('base',{
|
||||
// path: '/:pathMatch(.*)',
|
||||
// name: 'error',
|
||||
// component: () => NotFindPage
|
||||
// })
|
||||
|
||||
next({ ...to, replace: true })
|
||||
})
|
||||
}).catch(() => {
|
||||
cleanToken()
|
||||
next({ path: LoginPath })
|
||||
})
|
||||
} else {
|
||||
next()
|
||||
}
|
||||
}
|
||||
|
||||
const token = getToken()
|
||||
if (token) {
|
||||
if (to.path === LoginPath) {
|
||||
next({ path: '/' })
|
||||
} else {
|
||||
if (to.path === LoginPath) {
|
||||
next()
|
||||
const userInfo = useUserInfo()
|
||||
const system = useSystem()
|
||||
const menu = useMenuStore()
|
||||
if (!menu.siderMenus.length) {
|
||||
userInfo.getUserInfo().then(() => {
|
||||
system.getSystemVersion().then((menuData: any[]) => {
|
||||
menuData.forEach(r => {
|
||||
router.addRoute('base', r)
|
||||
})
|
||||
router.addRoute('base',{
|
||||
path: '/:pathMatch(.*)',
|
||||
name: 'error',
|
||||
component: () => NotFindPage
|
||||
})
|
||||
|
||||
next({ ...to, replace: true })
|
||||
})
|
||||
}).catch(() => {
|
||||
cleanToken()
|
||||
next({ path: LoginPath })
|
||||
})
|
||||
} else {
|
||||
next({ path: LoginPath })
|
||||
next()
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
if (to.path === LoginPath) {
|
||||
next()
|
||||
} else {
|
||||
next({ path: LoginPath })
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
@ -36,54 +36,60 @@ type MenuStateType = {
|
|||
siderMenus: MenuItem[]
|
||||
}
|
||||
|
||||
// export const useMenusStore = defineStore('menu', () => {
|
||||
// const state = reactive<MenuStateType>({
|
||||
// menus: {},
|
||||
// siderMenus: []
|
||||
// })
|
||||
//
|
||||
// const hasPermission = (code: string | string[]) => {
|
||||
// if (!code || !(code as string[]).length) {
|
||||
// console.warn(`function hasPermission: 没有传入${code}`)
|
||||
// return false
|
||||
// }
|
||||
//
|
||||
// }
|
||||
//
|
||||
// const queryMenuTree = (): Promise<any[]> => {
|
||||
//
|
||||
// }
|
||||
//
|
||||
// return {
|
||||
// state,
|
||||
// hasPermission,
|
||||
// queryMenuTree
|
||||
// }
|
||||
// })
|
||||
|
||||
export const useMenuStore = defineStore({
|
||||
id: 'menu',
|
||||
state: () => ({
|
||||
state: (): MenuStateType => ({
|
||||
menus: {},
|
||||
siderMenus: []
|
||||
}),
|
||||
getters: {
|
||||
hasPermission(state) {
|
||||
return (menuCode: string | string[]) => {
|
||||
if (!menuCode) {
|
||||
return (code: string | string[]) => {
|
||||
if (!code) {
|
||||
return true
|
||||
}
|
||||
if (!!Object.keys(state.menus).length) {
|
||||
if (typeof menuCode === 'string') {
|
||||
return !!this.menus[menuCode].buttons
|
||||
let codes: string[] = []
|
||||
|
||||
if (typeof code === 'string') {
|
||||
codes.push(code)
|
||||
} else {
|
||||
codes = code
|
||||
}
|
||||
return menuCode.some(code => !!this.menus[code])
|
||||
|
||||
return codes.some(_c => {
|
||||
const menu_code = _c.split(':')
|
||||
if (menu_code.length > 1) {
|
||||
return !!this.menus[menu_code[0]]?.buttons?.includes(menu_code[1])
|
||||
}
|
||||
return false
|
||||
})
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
hasMenu(code: string) {
|
||||
return this.menus[code]?.path
|
||||
},
|
||||
/**
|
||||
* 路由跳转
|
||||
* @param name 菜单code
|
||||
* @param params 路由参数
|
||||
* @param query 路由参数
|
||||
*/
|
||||
jumpPage(name: string, params?: Record<string, any>, query?: Record<string, any>) {
|
||||
const path = this.hasMenu(name)
|
||||
if (path) {
|
||||
router.push({
|
||||
name, params, query
|
||||
})
|
||||
} else {
|
||||
console.warn(`没有找到对应的页面: ${name}`)
|
||||
}
|
||||
},
|
||||
queryMenuTree(isCommunity = false): Promise<any[]> {
|
||||
return new Promise(async (res) => {
|
||||
//过滤非集成的菜单
|
||||
|
@ -94,11 +100,11 @@ export const useMenuStore = defineStore({
|
|||
const handleMenuItem = (menu: any) => {
|
||||
if (isArray(menu)) {
|
||||
menu.forEach(menuItem => {
|
||||
this.menus[menuItem.code] = {
|
||||
this.menus[menuItem.name] = {
|
||||
path: menuItem.path,
|
||||
buttons: menuItem.buttons
|
||||
buttons: menuItem.meta.buttons
|
||||
}
|
||||
if (menuItem.children && menuItem.length) {
|
||||
if (menuItem.children && menuItem.children.length) {
|
||||
handleMenuItem(menuItem.children)
|
||||
}
|
||||
})
|
||||
|
@ -114,7 +120,7 @@ export const useMenuStore = defineStore({
|
|||
}
|
||||
})
|
||||
this.siderMenus = silderMenus
|
||||
console.log('silderMenus', silderMenus)
|
||||
console.log('menusData', menusData)
|
||||
res(menusData)
|
||||
}
|
||||
})
|
||||
|
|
|
@ -95,30 +95,4 @@ export const modifySearchColumnValue = (e: any, column: object) => {
|
|||
});
|
||||
});
|
||||
return e;
|
||||
};
|
||||
|
||||
// /**
|
||||
// * 根据code返回对应的菜单path
|
||||
// * @param code 菜单code
|
||||
// */
|
||||
// export const getMenu = (code: string): string | undefined => {
|
||||
// const menuStore = useMenuStore()
|
||||
// return menuStore.menus[code]?.path
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 根据菜单code跳转对应路由
|
||||
// * @param code 菜单code
|
||||
// * @param params 路由params参数
|
||||
// * @param query 路由query参数
|
||||
// */
|
||||
// export const jumpPath = (code: string, params?: any, query?: any) => {
|
||||
// const path = getMenu(code)
|
||||
// if (path) {
|
||||
// router.push({
|
||||
// path, params, query
|
||||
// })
|
||||
// } else {
|
||||
// console.warn(`menus中没有${code}`)
|
||||
// }
|
||||
// }
|
||||
};
|
|
@ -178,7 +178,7 @@ const findDetailRouteItem = (code: string, url: string): Partial<MenuItem> | nul
|
|||
const detailComponent = resolveComponent(`${code}/Detail`)
|
||||
if (detailComponent) {
|
||||
return {
|
||||
url: `${url}/Detail/:id`,
|
||||
url: `${url}/detail/:id`,
|
||||
code: `${code}/Detail`,
|
||||
component: detailComponent,
|
||||
name: '详情信息',
|
||||
|
@ -227,12 +227,13 @@ export function filterAsnycRouter(asyncRouterMap: any, parentCode = '', level =
|
|||
_route.children = _menusData
|
||||
silder.children = _silderMenus
|
||||
const showChildren = _route.children.some((r: any) => !r.meta.hideInMenu)
|
||||
if (showChildren) {
|
||||
|
||||
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;
|
||||
// _route.component = myComponent ? myComponent : BlankLayoutPage;
|
||||
if (!!myComponent) {
|
||||
_route.component = myComponent;
|
||||
_route.children.map((r: any) => menusData.push(r))
|
||||
|
|
|
@ -151,12 +151,15 @@ import {
|
|||
import type { ActionsType } from '@/components/Table/index.vue';
|
||||
import { getImage } from '@/utils/comm';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { useMenuStore } from 'store/menu'
|
||||
|
||||
const router = useRouter();
|
||||
const instanceRef = ref<Record<string, any>>({});
|
||||
const params = ref<Record<string, any>>({});
|
||||
const current = ref<Record<string, any>>({});
|
||||
|
||||
const menuStory = useMenuStore()
|
||||
|
||||
const statusMap = new Map();
|
||||
statusMap.set('enabled', 'success');
|
||||
statusMap.set('disabled', 'error');
|
||||
|
@ -209,19 +212,14 @@ const columns = [
|
|||
* 新增
|
||||
*/
|
||||
const handleAdd = () => {
|
||||
router.push('/iot/northbound/AliCloud/detail/:id');
|
||||
menuStory.jumpPage('Northbound/AliCloud/Detail', { id: ':id'})
|
||||
};
|
||||
|
||||
/**
|
||||
* 查看
|
||||
*/
|
||||
const handleView = (id: string) => {
|
||||
router.push({
|
||||
path: '/iot/northbound/AliCloud/detail/' + id,
|
||||
query: {
|
||||
type: 'view'
|
||||
}
|
||||
});
|
||||
menuStory.jumpPage('Northbound/AliCloud/Detail', { id }, { type: 'view'})
|
||||
};
|
||||
|
||||
const getActions = (
|
||||
|
@ -249,12 +247,7 @@ const getActions = (
|
|||
},
|
||||
icon: 'EditOutlined',
|
||||
onClick: () => {
|
||||
router.push({
|
||||
path: '/iot/northbound/AliCloud/detail/' + data.id,
|
||||
query: {
|
||||
type: 'edit'
|
||||
}
|
||||
});
|
||||
menuStory.jumpPage('Northbound/AliCloud/Detail', { id: data.id }, { type: 'edit'})
|
||||
},
|
||||
},
|
||||
{
|
|
@ -147,11 +147,13 @@ import { query, _undeploy, _deploy, _delete, queryProductList, queryTypes } from
|
|||
import type { ActionsType } from '@/components/Table/index.vue';
|
||||
import { getImage } from '@/utils/comm';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { useMenuStore } from 'store/menu'
|
||||
|
||||
const router = useRouter();
|
||||
const instanceRef = ref<Record<string, any>>({});
|
||||
const params = ref<Record<string, any>>({});
|
||||
const current = ref<Record<string, any>>({});
|
||||
const menuStory = useMenuStore()
|
||||
|
||||
const statusMap = new Map();
|
||||
statusMap.set('enabled', 'success');
|
||||
|
@ -236,20 +238,14 @@ const columns = [
|
|||
* 新增
|
||||
*/
|
||||
const handleAdd = () => {
|
||||
router.push('/iot/northbound/DuerOS/detail/:id');
|
||||
menuStory.jumpPage('Northbound/DuerOS/Detail', { id: ':id'})
|
||||
};
|
||||
|
||||
/**
|
||||
* 查看
|
||||
*/
|
||||
const handleView = (id: string) => {
|
||||
// router.push('/iot/northbound/DuerOS/detail/' + id);
|
||||
router.push({
|
||||
path: '/iot/northbound/DuerOS/detail/' + id,
|
||||
query: {
|
||||
type: 'view',
|
||||
},
|
||||
});
|
||||
menuStory.jumpPage('Northbound/DuerOS/Detail', { id }, { type: 'view' })
|
||||
};
|
||||
|
||||
const getActions = (
|
||||
|
@ -277,12 +273,7 @@ const getActions = (
|
|||
},
|
||||
icon: 'EditOutlined',
|
||||
onClick: () => {
|
||||
router.push({
|
||||
path: '/iot/northbound/DuerOS/detail/' + data.id,
|
||||
query: {
|
||||
type: 'edit',
|
||||
},
|
||||
});
|
||||
menuStory.jumpPage('Northbound/DuerOS/Detail', { id: data.id }, { type: 'edit' })
|
||||
},
|
||||
},
|
||||
{
|
|
@ -1,20 +1,47 @@
|
|||
<template>
|
||||
<div class="dialog-item" :key="data.key" :class="{'dialog-active' : !data?.upstream}">
|
||||
<div
|
||||
class="dialog-item"
|
||||
:key="data.key"
|
||||
:class="{ 'dialog-active': !data?.upstream }"
|
||||
>
|
||||
<div class="dialog-card">
|
||||
<div class="dialog-list" v-for="item in data.list" :key="item.key">
|
||||
<div class="dialog-icon">
|
||||
<AIcon :type="visible.includes(item.key) ? 'DownOutlined' : 'RightOutlined'" />
|
||||
<div class="dialog-icon" @click="getDetail(item)">
|
||||
<AIcon
|
||||
v-if="visible.includes(item.key)"
|
||||
type="DownOutlined"
|
||||
/>
|
||||
<AIcon v-else type="RightOutlined" />
|
||||
</div>
|
||||
<div class="dialog-box">
|
||||
<div class="dialog-header">
|
||||
<div class="dialog-title">
|
||||
<a-badge :color="statusColor.get(item.error ? 'error' : 'success')" style="margin-right: 5px" />
|
||||
{{operationMap.get(item.operation) || item?.operation}}
|
||||
<a-badge
|
||||
:color="
|
||||
statusColor.get(
|
||||
item.error ? 'error' : 'success',
|
||||
)
|
||||
"
|
||||
style="margin-right: 5px"
|
||||
/>
|
||||
{{
|
||||
operationMap.get(item.operation) ||
|
||||
item?.operation
|
||||
}}
|
||||
</div>
|
||||
<div class="dialog-time">
|
||||
{{
|
||||
moment(item.endTime).format(
|
||||
'YYYY-MM-DD HH:mm:ss',
|
||||
)
|
||||
}}
|
||||
</div>
|
||||
<div class="dialog-item">{{moment(item.endTime).format('YYYY-MM-DD HH:mm:ss')}}</div>
|
||||
</div>
|
||||
<div class="dialog-editor" v-if="visible.includes(item.key)">
|
||||
<a-textarea :bordered="false" :value="item?.detail" />
|
||||
<div
|
||||
class="dialog-editor"
|
||||
v-if="visible.includes(item.key)"
|
||||
>
|
||||
<a-textarea autoSize :bordered="false" :value="item?.detail" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -24,7 +51,7 @@
|
|||
|
||||
<script lang="ts" setup>
|
||||
const operationMap = new Map();
|
||||
import moment from 'moment'
|
||||
import moment from 'moment';
|
||||
operationMap.set('connection', '连接');
|
||||
operationMap.set('auth', '权限验证');
|
||||
operationMap.set('decode', '解码');
|
||||
|
@ -41,102 +68,113 @@ statusColor.set('success', '#24B276');
|
|||
const props = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
default: () => {},
|
||||
},
|
||||
});
|
||||
const visible = ref<string[]>([]);
|
||||
const getDetail = (item: any) => {
|
||||
const index = visible.value.indexOf(item.key);
|
||||
if (index === -1) {
|
||||
visible.value.push(item.key);
|
||||
} else {
|
||||
visible.value.splice(index, 1);
|
||||
}
|
||||
})
|
||||
const visible = ref<string[]>([])
|
||||
};
|
||||
|
||||
watchEffect(() => {
|
||||
console.log(props.data)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import 'ant-design-vue/es/style/themes/default.less';
|
||||
|
||||
:root {
|
||||
--dialog-primary-color: @primary-color;
|
||||
--dialog-primary-color: @primary-color;
|
||||
}
|
||||
|
||||
.dialog-item {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
width: 100%;
|
||||
padding-bottom: 12px;
|
||||
|
||||
.dialog-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 60%;
|
||||
padding: 24px;
|
||||
background-color: #fff;
|
||||
justify-content: flex-start;
|
||||
width: 100%;
|
||||
padding-bottom: 12px;
|
||||
|
||||
.dialog-list {
|
||||
display: flex;
|
||||
|
||||
.dialog-icon {
|
||||
margin-right: 10px;
|
||||
color: rgba(0, 0, 0, 0.75);
|
||||
font-weight: 500;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.dialog-box {
|
||||
.dialog-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
width: 60%;
|
||||
padding: 24px;
|
||||
background-color: #fff;
|
||||
|
||||
.dialog-header {
|
||||
.dialog-title {
|
||||
color: rgba(0, 0, 0, 0.75);
|
||||
font-weight: 700;
|
||||
font-size: 14px;
|
||||
}
|
||||
.dialog-list {
|
||||
display: flex;
|
||||
|
||||
.dialog-time {
|
||||
color: rgba(0, 0, 0, 0.65);
|
||||
font-size: 12px;
|
||||
}
|
||||
.dialog-icon {
|
||||
margin-right: 10px;
|
||||
color: rgba(0, 0, 0, 0.75);
|
||||
font-weight: 500;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.dialog-box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
|
||||
.dialog-header {
|
||||
.dialog-title {
|
||||
color: rgba(0, 0, 0, 0.75);
|
||||
font-weight: 700;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.dialog-time {
|
||||
color: rgba(0, 0, 0, 0.65);
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.dialog-editor {
|
||||
width: 100%;
|
||||
margin-top: 10px;
|
||||
color: rgba(0, 0, 0, 0.75);
|
||||
|
||||
textarea::-webkit-scrollbar {
|
||||
width: 5px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dialog-editor {
|
||||
width: 100%;
|
||||
margin-top: 10px;
|
||||
color: rgba(0, 0, 0, 0.75);
|
||||
|
||||
textarea::-webkit-scrollbar {
|
||||
width: 5px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dialog-active {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
.dialog-card {
|
||||
background-color: @primary-color;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
.dialog-card {
|
||||
background-color: @primary-color;
|
||||
|
||||
.dialog-list {
|
||||
.dialog-icon {
|
||||
color: #fff;
|
||||
}
|
||||
.dialog-list {
|
||||
.dialog-icon {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.dialog-box {
|
||||
.dialog-header {
|
||||
.dialog-title,
|
||||
.dialog-time {
|
||||
color: #fff;
|
||||
}
|
||||
.dialog-box {
|
||||
.dialog-header {
|
||||
.dialog-title,
|
||||
.dialog-time {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.dialog-editor {
|
||||
textarea {
|
||||
color: #fff !important;
|
||||
background-color: @primary-color !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dialog-editor {
|
||||
textarea {
|
||||
color: #fff !important;
|
||||
background-color: @primary-color !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -67,7 +67,7 @@
|
|||
message: '请输入值',
|
||||
}"
|
||||
>
|
||||
<a-input v-model:value="propertyValue" />
|
||||
<a-input v-model:value="modelRef.propertyValue" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="6" v-if="modelRef.type === 'INVOKE_FUNCTION'">
|
||||
|
|
|
@ -1,11 +1,17 @@
|
|||
<template>
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="16">
|
||||
<a-row :gutter="24" style="margin-bottom: 20px;">
|
||||
<a-row :gutter="24" style="margin-bottom: 20px">
|
||||
<a-col :span="12" v-for="item in messageArr" :key="item">
|
||||
<div :style="messageStyleMap.get(item.status)" class="message-status">
|
||||
<a-badge :status="messageStatusMap.get(item.status)" style="margin-right: 5px;" />
|
||||
<span>{{item.text}}</span>
|
||||
<div
|
||||
:style="messageStyleMap.get(item.status)"
|
||||
class="message-status"
|
||||
>
|
||||
<a-badge
|
||||
:status="messageStatusMap.get(item.status)"
|
||||
style="margin-right: 5px"
|
||||
/>
|
||||
<span>{{ item.text }}</span>
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
@ -26,7 +32,11 @@
|
|||
<TitleComponent data="日志" />
|
||||
<div :style="{ marginTop: '10px' }">
|
||||
<template v-if="logList.length">
|
||||
<Log v-for="item in logList" :data="item" :key="item.key" />
|
||||
<Log
|
||||
v-for="item in logList"
|
||||
:data="item"
|
||||
:key="item.key"
|
||||
/>
|
||||
</template>
|
||||
<a-empty v-else />
|
||||
</div>
|
||||
|
@ -36,58 +46,146 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { MessageType } from './util'
|
||||
import { messageStatusMap, messageStyleMap } from './util'
|
||||
import Dialog from './Dialog/index.vue'
|
||||
import Function from './Function/index.vue'
|
||||
import Log from './Log/index.vue'
|
||||
import type { MessageType } from './util';
|
||||
import { messageStatusMap, messageStyleMap } from './util';
|
||||
import Dialog from './Dialog/index.vue';
|
||||
import Function from './Function/index.vue';
|
||||
import Log from './Log/index.vue';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { useInstanceStore } from '@/store/instance';
|
||||
import { getWebSocket } from '@/utils/websocket';
|
||||
import { randomString } from '@/utils/utils';
|
||||
import _ from 'lodash';
|
||||
|
||||
const message = reactive<MessageType>({
|
||||
up: {
|
||||
text: '上行消息诊断中',
|
||||
status: 'loading',
|
||||
text: '上行消息诊断中',
|
||||
status: 'loading',
|
||||
},
|
||||
down: {
|
||||
text: '下行消息诊断中',
|
||||
status: 'loading',
|
||||
text: '下行消息诊断中',
|
||||
status: 'loading',
|
||||
},
|
||||
})
|
||||
});
|
||||
|
||||
const dialogList = ref<Record<string, any>>([])
|
||||
const logList = ref<Record<string, any>>([])
|
||||
const instanceStore = useInstanceStore();
|
||||
|
||||
const allDialogList = ref<Record<string, any>[]>([]);
|
||||
const dialogList = ref<Record<string, any>[]>([]);
|
||||
const logList = ref<Record<string, any>[]>([]);
|
||||
|
||||
const diagnoseRef = ref();
|
||||
|
||||
const messageArr = computed(() => {
|
||||
const arr = Object.keys(message) || []
|
||||
return arr.map(i => { return {...message[i], key: i}})
|
||||
})
|
||||
const arr = Object.keys(message) || [];
|
||||
return arr.map((i) => {
|
||||
return { ...message[i], key: i };
|
||||
});
|
||||
});
|
||||
|
||||
const subscribeLog = () => {
|
||||
const id = `device-debug-${instanceStore.current?.id}`;
|
||||
const topic = `/debug/device/${instanceStore.current?.id}/trace`;
|
||||
diagnoseRef.value = getWebSocket(id, topic, {})
|
||||
?.pipe(map((res: any) => res.payload))
|
||||
.subscribe((payload) => {
|
||||
if (payload.type === 'log') {
|
||||
logList.value.push({
|
||||
key: randomString(),
|
||||
...payload,
|
||||
});
|
||||
} else {
|
||||
const data = { key: randomString(), ...payload };
|
||||
allDialogList.value.push(data);
|
||||
const flag = allDialogList.value
|
||||
.filter(
|
||||
(i: any) =>
|
||||
i.traceId === data.traceId &&
|
||||
(data.downstream === i.downstream ||
|
||||
data.upstream === i.upstream),
|
||||
)
|
||||
.every((item: any) => {
|
||||
return !item.error;
|
||||
});
|
||||
if (!data.upstream) {
|
||||
message.down = {
|
||||
text: !flag ? '下行消息通信异常' : '下行消息通信正常',
|
||||
status: !flag ? 'error' : 'success',
|
||||
};
|
||||
} else {
|
||||
message.up = {
|
||||
text: !flag ? '上行消息通信异常' : '上行消息通信正常',
|
||||
status: !flag ? 'error' : 'success',
|
||||
};
|
||||
}
|
||||
const list: any[] = _.cloneDeep(dialogList.value);
|
||||
const t = list.find(
|
||||
(item) =>
|
||||
item.traceId === data.traceId &&
|
||||
data.downstream === item.downstream &&
|
||||
data.upstream === item.upstream,
|
||||
);
|
||||
if (t) {
|
||||
const arr = list.map((item) => {
|
||||
if (item.traceId === data.traceId) {
|
||||
item.list.push(data);
|
||||
}
|
||||
return item;
|
||||
});
|
||||
dialogList.value = _.cloneDeep(arr);
|
||||
} else {
|
||||
list.push({
|
||||
key: randomString(),
|
||||
traceId: data.traceId,
|
||||
downstream: data.downstream,
|
||||
upstream: data.upstream,
|
||||
list: [data],
|
||||
});
|
||||
dialogList.value = _.cloneDeep(list);
|
||||
}
|
||||
}
|
||||
const chatBox = document.getElementById('dialog');
|
||||
if (chatBox) {
|
||||
chatBox.scrollTop = chatBox.scrollHeight;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
}
|
||||
const topState: any = inject('topState') || '';
|
||||
|
||||
watchEffect(() => {
|
||||
if (topState && topState?.value === 'success') {
|
||||
subscribeLog();
|
||||
}
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (diagnoseRef.value) {
|
||||
diagnoseRef.value.unsubscribe();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.message-status {
|
||||
padding: 8px 24px;
|
||||
padding: 8px 24px;
|
||||
}
|
||||
.content {
|
||||
width: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.dialog {
|
||||
width: 100%;
|
||||
min-height: 300px;
|
||||
max-height: 500px;
|
||||
padding: 24px;
|
||||
overflow: hidden;
|
||||
overflow-y: auto;
|
||||
background-color: #f2f5f7;
|
||||
width: 100%;
|
||||
min-height: 300px;
|
||||
max-height: 500px;
|
||||
padding: 24px;
|
||||
overflow: hidden;
|
||||
overflow-y: auto;
|
||||
background-color: #f2f5f7;
|
||||
}
|
||||
.right-log {
|
||||
padding-left: 20px;
|
||||
border-left: 1px solid rgba(0, 0, 0, .09);
|
||||
border-left: 1px solid rgba(0, 0, 0, 0.09);
|
||||
overflow: hidden;
|
||||
max-height: 600px;
|
||||
overflow-y: auto;
|
||||
|
|
|
@ -34,8 +34,8 @@
|
|||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Message v-if="activeKey === 'message'" />
|
||||
<Status v-else :providerType="providerType" @countChange="countChange" @percentChange="percentChange" @stateChange="stateChange" />
|
||||
<Message v-show="activeKey === 'message'" />
|
||||
<Status v-show="activeKey !== 'message'" :providerType="providerType" @countChange="countChange" @percentChange="percentChange" @stateChange="stateChange" />
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
|
@ -70,6 +70,7 @@ const percent = ref<number>(0)
|
|||
const activeKey = ref<'status' | 'message'>('status')
|
||||
const providerType = ref()
|
||||
|
||||
provide('topState', topState)
|
||||
|
||||
const onTabChange = (key: 'status' | 'message') => {
|
||||
if(topState.value === 'success'){
|
||||
|
|
|
@ -0,0 +1,289 @@
|
|||
<template>
|
||||
<a-spin :spinning="loading">
|
||||
<a-card>
|
||||
<template #extra>
|
||||
<a-space>
|
||||
<a-button @click="visible = true">批量映射</a-button>
|
||||
<a-button type="primary" @click="onSave">保存</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
<a-form ref="formRef" :model="modelRef">
|
||||
<a-table :dataSource="modelRef.dataSource" :columns="columns">
|
||||
<template #headerCell="{ column }">
|
||||
<template v-if="column.key === 'collectorId'">
|
||||
采集器
|
||||
<a-tooltip title="边缘网关代理的真实物理设备">
|
||||
<AIcon type="QuestionCircleOutlined" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record, index }">
|
||||
<template v-if="column.dataIndex === 'channelId'">
|
||||
<a-form-item
|
||||
:name="['dataSource', index, 'channelId']"
|
||||
>
|
||||
<a-select
|
||||
style="width: 100%"
|
||||
v-model:value="record[column.dataIndex]"
|
||||
placeholder="请选择"
|
||||
allowClear
|
||||
:filter-option="filterOption"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="item in channelList"
|
||||
:key="item.value"
|
||||
:value="item.value"
|
||||
:label="item.label"
|
||||
>{{ item.label }}</a-select-option
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'collectorId'">
|
||||
<a-form-item
|
||||
:name="['dataSource', index, 'collectorId']"
|
||||
:rules="[
|
||||
{
|
||||
required: !!record.channelId,
|
||||
message: '请选择采集器',
|
||||
},
|
||||
]"
|
||||
>
|
||||
<MSelect
|
||||
v-model="record[column.dataIndex]"
|
||||
:id="record.channelId"
|
||||
type="COLLECTOR"
|
||||
/>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'pointId'">
|
||||
<a-form-item
|
||||
:name="['dataSource', index, 'pointId']"
|
||||
:rules="[
|
||||
{
|
||||
required: !!record.channelId,
|
||||
message: '请选择点位',
|
||||
},
|
||||
]"
|
||||
>
|
||||
<MSelect
|
||||
v-model="record[column.dataIndex]"
|
||||
:id="record.collectorId"
|
||||
type="POINT"
|
||||
/>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'id'">
|
||||
<a-badge
|
||||
v-if="record[column.dataIndex]"
|
||||
status="success"
|
||||
text="已绑定"
|
||||
/>
|
||||
<a-badge v-else status="error" text="未绑定" />
|
||||
</template>
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-tooltip title="解绑">
|
||||
<a-popconfirm
|
||||
title="确认解绑"
|
||||
@confirm="unbind(record.id)"
|
||||
>
|
||||
<a-button type="link" :disabled="!record.id"
|
||||
><AIcon type="icon-jiebang"
|
||||
/></a-button>
|
||||
</a-popconfirm>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-form>
|
||||
</a-card>
|
||||
<!-- <PatchMapping
|
||||
:deviceId="instanceStore.current.id"
|
||||
v-if="visible"
|
||||
@close="visible = false"
|
||||
@save="onPatchBind"
|
||||
:type="provider"
|
||||
:metaData="modelRef.dataSource"
|
||||
/> -->
|
||||
</a-spin>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useInstanceStore } from '@/store/instance';
|
||||
import {
|
||||
queryMapping,
|
||||
saveMapping,
|
||||
removeMapping,
|
||||
queryChannelNoPaging,
|
||||
} from '@/api/device/instance';
|
||||
import MSelect from '../components/MSelect.vue';
|
||||
// import PatchMapping from '../components/PatchMapping.vue';
|
||||
import { message } from 'ant-design-vue/es';
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: '名称',
|
||||
dataIndex: 'metadataName',
|
||||
key: 'metadataName',
|
||||
width: '20%',
|
||||
},
|
||||
{
|
||||
title: '通道',
|
||||
dataIndex: 'channelId',
|
||||
key: 'channelId',
|
||||
width: '20%',
|
||||
},
|
||||
{
|
||||
title: '采集器',
|
||||
dataIndex: 'collectorId',
|
||||
key: 'collectorId',
|
||||
width: '20%',
|
||||
},
|
||||
{
|
||||
title: '点位',
|
||||
key: 'pointId',
|
||||
dataIndex: 'pointId',
|
||||
width: '20%',
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
key: 'id',
|
||||
dataIndex: 'id',
|
||||
width: '10%',
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: '10%',
|
||||
},
|
||||
];
|
||||
|
||||
const filterOption = (input: string, option: any) => {
|
||||
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
|
||||
};
|
||||
|
||||
const props = defineProps({
|
||||
provider: {
|
||||
type: String,
|
||||
default: 'MODBUS_TCP',
|
||||
},
|
||||
});
|
||||
|
||||
const instanceStore = useInstanceStore();
|
||||
const metadata = JSON.parse(instanceStore.current?.metadata || '{}');
|
||||
const loading = ref<boolean>(false);
|
||||
const channelList = ref([]);
|
||||
|
||||
const modelRef = reactive({
|
||||
dataSource: [],
|
||||
});
|
||||
|
||||
const formRef = ref();
|
||||
const visible = ref<boolean>(false);
|
||||
|
||||
const getChannel = async () => {
|
||||
const resp: any = await queryChannelNoPaging({
|
||||
paging: false,
|
||||
terms: [
|
||||
{
|
||||
terms: [
|
||||
{
|
||||
column: 'provider',
|
||||
value: props.provider,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
if (resp.status === 200) {
|
||||
channelList.value = resp.result?.map((item: any) => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
provider: item.provider,
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
const handleSearch = async () => {
|
||||
loading.value = true;
|
||||
getChannel();
|
||||
const _metadata = metadata.properties.map((item: any) => ({
|
||||
metadataId: item.id,
|
||||
metadataName: `${item.name}(${item.id})`,
|
||||
metadataType: 'property',
|
||||
name: item.name,
|
||||
}));
|
||||
if (_metadata && _metadata.length) {
|
||||
const resp: any = await queryMapping(
|
||||
'device',
|
||||
instanceStore.current.id,
|
||||
);
|
||||
if (resp.status === 200) {
|
||||
const array = resp.result.reduce((x: any, y: any) => {
|
||||
const metadataId = _metadata.find(
|
||||
(item: any) => item.metadataId === y.metadataId,
|
||||
);
|
||||
if (metadataId) {
|
||||
Object.assign(metadataId, y);
|
||||
} else {
|
||||
x.push(y);
|
||||
}
|
||||
return x;
|
||||
}, _metadata);
|
||||
modelRef.dataSource = array;
|
||||
}
|
||||
}
|
||||
loading.value = false;
|
||||
};
|
||||
|
||||
const unbind = async (id: string) => {
|
||||
if (id) {
|
||||
const resp = await removeMapping('device', instanceStore.current.id, [
|
||||
id,
|
||||
]);
|
||||
if (resp.status === 200) {
|
||||
message.success('操作成功!');
|
||||
handleSearch();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const onPatchBind = () => {
|
||||
visible.value = false;
|
||||
handleSearch();
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
handleSearch();
|
||||
});
|
||||
|
||||
const onSave = () => {
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(async () => {
|
||||
const arr = toRaw(modelRef).dataSource.filter(
|
||||
(i: any) => i.channelId,
|
||||
);
|
||||
if (arr && arr.length !== 0) {
|
||||
const resp = await saveMapping(
|
||||
instanceStore.current.id,
|
||||
props.provider,
|
||||
arr,
|
||||
);
|
||||
if (resp.status === 200) {
|
||||
message.success('操作成功!');
|
||||
handleSearch();
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch((err: any) => {
|
||||
console.log('error', err);
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
:deep(.ant-form-item) {
|
||||
margin: 0 !important;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,7 @@
|
|||
<template>
|
||||
<EditTable provider="MODBUS_TCP" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import EditTable from '../components/EditTable/index.vue'
|
||||
</script>
|
|
@ -0,0 +1,7 @@
|
|||
<template>
|
||||
<EditTable provider="OPC_UA" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import EditTable from '../components/EditTable/index.vue'
|
||||
</script>
|
|
@ -24,7 +24,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="value">
|
||||
<ValueRender :data="data" :value="_props.data" />
|
||||
<ValueRender :data="data" :value="_props.data" type="card" />
|
||||
</div>
|
||||
<div class="bottom">
|
||||
<div style="color: rgba(0, 0, 0, .65); font-size: 12px">更新时间</div>
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
<template>
|
||||
<a-modal
|
||||
:maskClosable="false"
|
||||
width="600px"
|
||||
:visible="true"
|
||||
title="详情"
|
||||
okText="确定"
|
||||
cancelText="取消"
|
||||
@ok="handleCancel"
|
||||
@cancel="handleCancel"
|
||||
>
|
||||
<template v-if="['.jpg', '.png'].includes(type)">
|
||||
<a-image :src="value?.formatValue" />
|
||||
</template>
|
||||
<template v-else-if="['.flv', '.m3u8', '.mp4'].includes(type)">
|
||||
<!-- TODO 视频组件缺失 -->
|
||||
</template>
|
||||
<template v-else>
|
||||
<!-- <json-viewer
|
||||
:value="{
|
||||
'id': '123'
|
||||
}"
|
||||
copyable
|
||||
boxed
|
||||
sort
|
||||
></json-viewer> -->
|
||||
</template>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
// import JsonViewer from 'vue3-json-viewer';
|
||||
|
||||
const _data = defineProps({
|
||||
type: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
value: {
|
||||
type: [Object, String],
|
||||
default: () => {},
|
||||
},
|
||||
});
|
||||
const _emit = defineEmits(['close']);
|
||||
const handleCancel = () => {
|
||||
_emit('close');
|
||||
};
|
||||
|
||||
// watchEffect(() => {
|
||||
// console.log(_data.value?.formatValue)
|
||||
// })
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
</style>
|
|
@ -1,11 +1,58 @@
|
|||
<template>
|
||||
<div class="value">
|
||||
{{value?.value || '--'}}
|
||||
<div v-if="value?.formatValue !== 0 && !value?.formatValue" :class="valueClass">--</div>
|
||||
<div v-else-if="data?.valueType?.type === 'file'">
|
||||
<template v-if="data?.valueType?.fileType === 'base64'">
|
||||
<div :class="valueClass" v-if="!!getType(value?.formatValue)">
|
||||
<img :src="imgMap.get(_type)" @error="onError" />
|
||||
</div>
|
||||
<div v-else :class="valueClass">
|
||||
<img :src="imgMap.get('other')" />
|
||||
</div>
|
||||
</template>
|
||||
<div v-else-if="data?.valueType?.fileType === 'Binary(二进制)'" :class="valueClass">
|
||||
<img :src="imgMap.get('other')" />
|
||||
</div>
|
||||
<template v-else>
|
||||
<template v-if="imgList.some((item) => value?.formatValue.includes(item))">
|
||||
<div :class="valueClass" @click="getDetail('img')">
|
||||
<img :src="value?.formatValue" @error="imgError" />
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="videoList.some((item) => value?.formatValue.includes(item))">
|
||||
<div :class="valueClass" @click="getDetail('video')">
|
||||
<img :src="imgMap.get('video')" />
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="fileList.some((item) => value?.formatValue.includes(item))">
|
||||
<div :class="valueClass">
|
||||
<img :src="imgMap.get(fileList.find((item) => value?.formatValue.includes(item)).slice(1))" />
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div :class="valueClass">
|
||||
<img :src="imgMap.get('other')" />
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
<div v-else-if="data?.valueType?.type === 'object'" @click="getDetail('obj')" :class="valueClass">
|
||||
<img :src="imgMap.get('obj')" />
|
||||
</div>
|
||||
<div v-else-if="data?.valueType?.type === 'geoPoint' || data?.valueType?.type === 'array'" :class="valueClass">
|
||||
{{JSON.stringify(value?.formatValue)}}
|
||||
</div>
|
||||
<div v-else :class="valueClass">
|
||||
{{String(value?.formatValue)}}
|
||||
</div>
|
||||
<ValueDetail v-if="visible" :type="_types" :value="value" @close="visible = false" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { getImage } from "@/utils/comm";
|
||||
import { message } from "ant-design-vue";
|
||||
import ValueDetail from './ValueDetail.vue'
|
||||
|
||||
const _data = defineProps({
|
||||
data: {
|
||||
|
@ -22,6 +69,10 @@ const _data = defineProps({
|
|||
}
|
||||
});
|
||||
|
||||
const valueClass = computed(() => {
|
||||
return _data.type === 'card' ? 'cardValue' : 'otherValue'
|
||||
})
|
||||
|
||||
const imgMap = new Map<any, any>();
|
||||
imgMap.set('txt', getImage('/running/txt.png'));
|
||||
imgMap.set('doc', getImage('/running/doc.png'));
|
||||
|
@ -41,6 +92,64 @@ const imgList = ['.jpg', '.png', '.swf', '.tiff'];
|
|||
const videoList = ['.m3u8', '.flv', '.mp4', '.rmvb', '.mvb'];
|
||||
const fileList = ['.txt', '.doc', '.xls', '.pdf', '.ppt', '.docx', '.xlsx', '.pptx'];
|
||||
|
||||
const isHttps = document.location.protocol === 'https:';
|
||||
|
||||
const _types = ref<string>('')
|
||||
const visible = ref<boolean>(false)
|
||||
const temp = ref<boolean>(false)
|
||||
|
||||
const getType = (url: string) => {
|
||||
let t: string = '';
|
||||
[...imgList, ...videoList, ...fileList].map((item) => {
|
||||
const str = item.slice(1, item.length);
|
||||
if (url && String(url).indexOf(str) !== -1) {
|
||||
if (imgList.includes(item)) {
|
||||
t = 'img';
|
||||
} else if (videoList.includes(item)) {
|
||||
t = 'video';
|
||||
} else {
|
||||
t = str;
|
||||
}
|
||||
}
|
||||
});
|
||||
return t;
|
||||
};
|
||||
|
||||
const onError = (e: any) => {
|
||||
e.target.src = imgMap.get('other')
|
||||
}
|
||||
|
||||
const imgError = (e: any) => {
|
||||
e.target.src = imgMap.get('error')
|
||||
temp.value = true
|
||||
}
|
||||
|
||||
const getDetail = (_type: string) => {
|
||||
const value = _data.value
|
||||
let flag: string = ''
|
||||
if(_type === 'img'){
|
||||
if (isHttps && value?.formatValue.indexOf('http:') !== -1) {
|
||||
message.error('域名为https时,不支持访问http地址');
|
||||
} else if (temp.value) {
|
||||
message.error('该图片无法访问');
|
||||
} else {
|
||||
flag = ['.jpg', '.png'].find((item) => value?.formatValue.includes(item)) || '--';
|
||||
}
|
||||
} else if(_type === 'video'){
|
||||
if (isHttps && value?.formatValue.indexOf('http:') !== -1) {
|
||||
message.error('域名为https时,不支持访问http地址');
|
||||
} else if (['.rmvb', '.mvb'].some((item) => value?.formatValue.includes(item))) {
|
||||
message.error('当前仅支持播放.mp4,.flv,.m3u8格式的视频');
|
||||
} else {
|
||||
flag = ['.m3u8', '.flv', '.mp4'].find((item) => value?.formatValue.includes(item)) || '--';
|
||||
}
|
||||
}else if(_type === 'obj'){
|
||||
flag = 'obj'
|
||||
}
|
||||
_types.value = flag
|
||||
visible.value = true
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
</template>
|
||||
<template #value="slotProps">
|
||||
<ValueRender
|
||||
type="table"
|
||||
:data="slotProps"
|
||||
:value="propertyValue[slotProps?.id]"
|
||||
/>
|
||||
|
@ -332,6 +333,10 @@ watch(
|
|||
const onSearch = () => {
|
||||
query(0, 8, value.value);
|
||||
};
|
||||
|
||||
onUnmounted(() => {
|
||||
subRef.value && subRef.value?.unsubscribe()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
|
|
|
@ -109,6 +109,7 @@ const tabChange = (key: string) => {
|
|||
<style lang="less" scoped>
|
||||
.property-box {
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
.property-box-left {
|
||||
width: 200px;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,192 @@
|
|||
<template>
|
||||
<a-modal
|
||||
width="900px"
|
||||
title="批量映射"
|
||||
visible
|
||||
@ok="handleClick"
|
||||
@cancel="handleClose"
|
||||
>
|
||||
<div class="map-tree">
|
||||
<div class="map-tree-top">
|
||||
采集器的点位名称与属性名称一致时将自动映射绑定;有多个采集器点位名称与属性名称一致时以第1个采集器的点位数据进行绑定
|
||||
</div>
|
||||
<a-spin :spinning="loading">
|
||||
<div class="map-tree-content">
|
||||
<a-card class="map-tree-content-card" title="源数据">
|
||||
<a-tree
|
||||
checkable
|
||||
:height="300"
|
||||
:tree-data="dataSource"
|
||||
:checkedKeys="checkedKeys"
|
||||
@check="onCheck"
|
||||
/>
|
||||
</a-card>
|
||||
<div style="width: 100px">
|
||||
<a-button
|
||||
:disabled="rightList.length >= leftList.length"
|
||||
@click="onRight"
|
||||
>加入右侧</a-button
|
||||
>
|
||||
</div>
|
||||
<a-card class="map-tree-content-card" title="采集器">
|
||||
<a-list
|
||||
size="small"
|
||||
:data-source="rightList"
|
||||
class="map-tree-content-card-list"
|
||||
>
|
||||
<template #renderItem="{ item }">
|
||||
<a-list-item>
|
||||
{{ item.title }}
|
||||
<template #actions>
|
||||
<a-popconfirm
|
||||
title="确定删除?"
|
||||
@confirm="_delete(item.key)"
|
||||
>
|
||||
<AIcon type="DeleteOutlined" />
|
||||
</a-popconfirm>
|
||||
</template>
|
||||
</a-list-item>
|
||||
</template>
|
||||
</a-list>
|
||||
</a-card>
|
||||
</div>
|
||||
</a-spin>
|
||||
</div>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { treeMapping, saveMapping } from '@/api/device/instance';
|
||||
import { message } from 'ant-design-vue/es';
|
||||
const _props = defineProps({
|
||||
type: {
|
||||
type: String,
|
||||
default: 'MODBUS_TCP',
|
||||
},
|
||||
metaData: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
deviceId: {
|
||||
type: String,
|
||||
default: '',
|
||||
}
|
||||
});
|
||||
const _emits = defineEmits(['close', 'save']);
|
||||
|
||||
const checkedKeys = ref<string[]>([]);
|
||||
|
||||
const leftList = ref<any[]>([]);
|
||||
const rightList = ref<any[]>([]);
|
||||
|
||||
const dataSource = ref<any[]>([]);
|
||||
const loading = ref<boolean>(false);
|
||||
|
||||
const handleData = (data: any[], type: string) => {
|
||||
data.forEach((item) => {
|
||||
item.key = item.id;
|
||||
item.title = item.name;
|
||||
item.checkable = type === 'collectors';
|
||||
if (
|
||||
item.collectors &&
|
||||
Array.isArray(item.collectors) &&
|
||||
item.collectors.length
|
||||
) {
|
||||
item.children = handleData(item.collectors, 'collectors');
|
||||
}
|
||||
if (item.points && Array.isArray(item.points) && item.points.length) {
|
||||
item.children = handleData(item.points, 'points');
|
||||
}
|
||||
});
|
||||
return data as any[];
|
||||
};
|
||||
|
||||
const handleSearch = async () => {
|
||||
loading.value = true;
|
||||
const resp = await treeMapping({
|
||||
terms: [
|
||||
{
|
||||
column: 'provider',
|
||||
value: _props.type,
|
||||
},
|
||||
],
|
||||
});
|
||||
loading.value = false;
|
||||
if (resp.status === 200) {
|
||||
dataSource.value = handleData(resp.result as any[], 'channel');
|
||||
}
|
||||
};
|
||||
|
||||
const onCheck = (keys: string[], e: any) => {
|
||||
checkedKeys.value = [...keys];
|
||||
leftList.value = e?.checkedNodes || [];
|
||||
};
|
||||
|
||||
const onRight = () => {
|
||||
rightList.value = leftList.value;
|
||||
};
|
||||
|
||||
const _delete = (_key: string) => {
|
||||
const _index = rightList.value.findIndex((i) => i.key === _key);
|
||||
rightList.value.splice(_index, 1);
|
||||
checkedKeys.value = rightList.value.map((i) => i.key);
|
||||
leftList.value = rightList.value;
|
||||
};
|
||||
|
||||
const handleClick = async () => {
|
||||
if (!rightList.value.length) {
|
||||
message.warning('请选择采集器');
|
||||
} else {
|
||||
const params: any[] = [];
|
||||
rightList.value.map((item: any) => {
|
||||
const array = (item.children || []).map((element: any) => ({
|
||||
channelId: item.parentId,
|
||||
collectorId: element.collectorId,
|
||||
pointId: element.id,
|
||||
metadataType: 'property',
|
||||
metadataId: (_props.metaData as any[]).find((i: any) => i.name === element.name)
|
||||
?.metadataId,
|
||||
provider: _props.type
|
||||
}));
|
||||
params.push(...array);
|
||||
});
|
||||
const filterParms = params.filter((item) => !!item.metadataId);
|
||||
if (filterParms && filterParms.length !== 0) {
|
||||
const res = await saveMapping(_props.deviceId, _props.type, filterParms);
|
||||
if (res.status === 200) {
|
||||
message.success('操作成功');
|
||||
_emits('save');
|
||||
}
|
||||
} else {
|
||||
message.error('暂无对应属性的映射');
|
||||
}
|
||||
}
|
||||
};
|
||||
const handleClose = () => {
|
||||
_emits('close');
|
||||
};
|
||||
|
||||
watchEffect(() => {
|
||||
if (_props.type) {
|
||||
handleSearch();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.map-tree-content {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
.map-tree-content-card {
|
||||
width: 350px;
|
||||
height: 400px;
|
||||
|
||||
.map-tree-content-card-list {
|
||||
overflow-y: auto;
|
||||
height: 300px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,289 @@
|
|||
<template>
|
||||
<a-spin :spinning="loading">
|
||||
<a-card>
|
||||
<template #extra>
|
||||
<a-space>
|
||||
<a-button @click="visible = true">批量映射</a-button>
|
||||
<a-button type="primary" @click="onSave">保存</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
<a-form ref="formRef" :model="modelRef">
|
||||
<a-table :dataSource="modelRef.dataSource" :columns="columns">
|
||||
<template #headerCell="{ column }">
|
||||
<template v-if="column.key === 'collectorId'">
|
||||
采集器
|
||||
<a-tooltip title="数据采集中配置的真实物理设备">
|
||||
<AIcon type="QuestionCircleOutlined" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record, index }">
|
||||
<template v-if="column.dataIndex === 'channelId'">
|
||||
<a-form-item
|
||||
:name="['dataSource', index, 'channelId']"
|
||||
>
|
||||
<a-select
|
||||
style="width: 100%"
|
||||
v-model:value="record[column.dataIndex]"
|
||||
placeholder="请选择"
|
||||
allowClear
|
||||
:filter-option="filterOption"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="item in channelList"
|
||||
:key="item.value"
|
||||
:value="item.value"
|
||||
:label="item.label"
|
||||
>{{ item.label }}</a-select-option
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'collectorId'">
|
||||
<a-form-item
|
||||
:name="['dataSource', index, 'collectorId']"
|
||||
:rules="[
|
||||
{
|
||||
required: !!record.channelId,
|
||||
message: '请选择采集器',
|
||||
},
|
||||
]"
|
||||
>
|
||||
<MSelect
|
||||
v-model="record[column.dataIndex]"
|
||||
:id="record.channelId"
|
||||
type="COLLECTOR"
|
||||
/>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'pointId'">
|
||||
<a-form-item
|
||||
:name="['dataSource', index, 'pointId']"
|
||||
:rules="[
|
||||
{
|
||||
required: !!record.channelId,
|
||||
message: '请选择点位',
|
||||
},
|
||||
]"
|
||||
>
|
||||
<MSelect
|
||||
v-model="record[column.dataIndex]"
|
||||
:id="record.collectorId"
|
||||
type="POINT"
|
||||
/>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'id'">
|
||||
<a-badge
|
||||
v-if="record[column.dataIndex]"
|
||||
status="success"
|
||||
text="已绑定"
|
||||
/>
|
||||
<a-badge v-else status="error" text="未绑定" />
|
||||
</template>
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-tooltip title="解绑">
|
||||
<a-popconfirm
|
||||
title="确认解绑"
|
||||
@confirm="unbind(record.id)"
|
||||
>
|
||||
<a-button type="link" :disabled="!record.id"
|
||||
><AIcon type="icon-jiebang"
|
||||
/></a-button>
|
||||
</a-popconfirm>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-form>
|
||||
</a-card>
|
||||
<PatchMapping
|
||||
:deviceId="instanceStore.current.id"
|
||||
v-if="visible"
|
||||
@close="visible = false"
|
||||
@save="onPatchBind"
|
||||
:type="provider"
|
||||
:metaData="modelRef.dataSource"
|
||||
/>
|
||||
</a-spin>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useInstanceStore } from '@/store/instance';
|
||||
import {
|
||||
queryMapping,
|
||||
saveMapping,
|
||||
removeMapping,
|
||||
queryChannelNoPaging,
|
||||
} from '@/api/device/instance';
|
||||
import MSelect from '../MSelect.vue';
|
||||
import PatchMapping from './PatchMapping.vue';
|
||||
import { message } from 'ant-design-vue/es';
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: '名称',
|
||||
dataIndex: 'metadataName',
|
||||
key: 'metadataName',
|
||||
width: '20%',
|
||||
},
|
||||
{
|
||||
title: '通道',
|
||||
dataIndex: 'channelId',
|
||||
key: 'channelId',
|
||||
width: '20%',
|
||||
},
|
||||
{
|
||||
title: '采集器',
|
||||
dataIndex: 'collectorId',
|
||||
key: 'collectorId',
|
||||
width: '20%',
|
||||
},
|
||||
{
|
||||
title: '点位',
|
||||
key: 'pointId',
|
||||
dataIndex: 'pointId',
|
||||
width: '20%',
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
key: 'id',
|
||||
dataIndex: 'id',
|
||||
width: '10%',
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: '10%',
|
||||
},
|
||||
];
|
||||
|
||||
const filterOption = (input: string, option: any) => {
|
||||
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
|
||||
};
|
||||
|
||||
const props = defineProps({
|
||||
provider: {
|
||||
type: String,
|
||||
default: 'MODBUS_TCP',
|
||||
},
|
||||
});
|
||||
|
||||
const instanceStore = useInstanceStore();
|
||||
const metadata = JSON.parse(instanceStore.current?.metadata || '{}');
|
||||
const loading = ref<boolean>(false);
|
||||
const channelList = ref([]);
|
||||
|
||||
const modelRef = reactive({
|
||||
dataSource: [],
|
||||
});
|
||||
|
||||
const formRef = ref();
|
||||
const visible = ref<boolean>(false);
|
||||
|
||||
const getChannel = async () => {
|
||||
const resp: any = await queryChannelNoPaging({
|
||||
paging: false,
|
||||
terms: [
|
||||
{
|
||||
terms: [
|
||||
{
|
||||
column: 'provider',
|
||||
value: props.provider,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
if (resp.status === 200) {
|
||||
channelList.value = resp.result?.map((item: any) => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
provider: item.provider,
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
const handleSearch = async () => {
|
||||
loading.value = true;
|
||||
getChannel();
|
||||
const _metadata = metadata.properties.map((item: any) => ({
|
||||
metadataId: item.id,
|
||||
metadataName: `${item.name}(${item.id})`,
|
||||
metadataType: 'property',
|
||||
name: item.name,
|
||||
}));
|
||||
if (_metadata && _metadata.length) {
|
||||
const resp: any = await queryMapping(
|
||||
'device',
|
||||
instanceStore.current.id,
|
||||
);
|
||||
if (resp.status === 200) {
|
||||
const array = resp.result.reduce((x: any, y: any) => {
|
||||
const metadataId = _metadata.find(
|
||||
(item: any) => item.metadataId === y.metadataId,
|
||||
);
|
||||
if (metadataId) {
|
||||
Object.assign(metadataId, y);
|
||||
} else {
|
||||
x.push(y);
|
||||
}
|
||||
return x;
|
||||
}, _metadata);
|
||||
modelRef.dataSource = array;
|
||||
}
|
||||
}
|
||||
loading.value = false;
|
||||
};
|
||||
|
||||
const unbind = async (id: string) => {
|
||||
if (id) {
|
||||
const resp = await removeMapping('device', instanceStore.current.id, [
|
||||
id,
|
||||
]);
|
||||
if (resp.status === 200) {
|
||||
message.success('操作成功!');
|
||||
handleSearch();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const onPatchBind = () => {
|
||||
visible.value = false;
|
||||
handleSearch();
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
handleSearch();
|
||||
});
|
||||
|
||||
const onSave = () => {
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(async () => {
|
||||
const arr = toRaw(modelRef).dataSource.filter(
|
||||
(i: any) => i.channelId,
|
||||
);
|
||||
if (arr && arr.length !== 0) {
|
||||
const resp = await saveMapping(
|
||||
instanceStore.current.id,
|
||||
props.provider,
|
||||
arr,
|
||||
);
|
||||
if (resp.status === 200) {
|
||||
message.success('操作成功!');
|
||||
handleSearch();
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch((err: any) => {
|
||||
console.log('error', err);
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
:deep(.ant-form-item) {
|
||||
margin: 0 !important;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,113 @@
|
|||
<template>
|
||||
<a-select allowClear v-model:value="_value" @change="onChange" placeholder="请选择" style="width: 100%">
|
||||
<a-select-option
|
||||
v-for="item in list"
|
||||
:key="item.id"
|
||||
:value="item.id"
|
||||
:label="item.name"
|
||||
:filter-option="filterOption"
|
||||
>{{ item.name }}</a-select-option
|
||||
>
|
||||
</a-select>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
queryCollectorNoPaging,
|
||||
queryPointNoPaging,
|
||||
} from '@/api/device/instance';
|
||||
|
||||
const _props = defineProps({
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: undefined,
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: 'POINT',
|
||||
},
|
||||
id: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
|
||||
const filterOption = (input: string, option: any) => {
|
||||
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
|
||||
};
|
||||
|
||||
type Emits = {
|
||||
(e: 'update:modelValue', data: string | undefined): void;
|
||||
};
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const list = ref<any[]>([]);
|
||||
const _value = ref<string | undefined>(undefined);
|
||||
|
||||
watchEffect(() => {
|
||||
_value.value = _props.modelValue;
|
||||
});
|
||||
|
||||
const onChange = (_val: string) => {
|
||||
emit('update:modelValue', _val);
|
||||
};
|
||||
|
||||
const getCollector = async (_val: string) => {
|
||||
if (!_val) {
|
||||
return [];
|
||||
} else {
|
||||
const resp = await queryCollectorNoPaging({
|
||||
terms: [
|
||||
{
|
||||
terms: [
|
||||
{
|
||||
column: 'channelId',
|
||||
value: _val,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
if (resp.status === 200) {
|
||||
list.value = resp.result as any[];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const getPoint = async (_val: string) => {
|
||||
if (!_val) {
|
||||
return [];
|
||||
} else {
|
||||
const resp = await queryPointNoPaging({
|
||||
terms: [
|
||||
{
|
||||
terms: [
|
||||
{
|
||||
column: 'collectorId',
|
||||
value: _val,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
if (resp.status === 200) {
|
||||
list.value = resp.result as any[];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
watchEffect(() => {
|
||||
if (_props.id) {
|
||||
if (_props.type === 'POINT') {
|
||||
getPoint(_props.id);
|
||||
} else {
|
||||
getCollector(_props.id);
|
||||
}
|
||||
} else {
|
||||
list.value = [];
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
</style>
|
|
@ -1,146 +1,261 @@
|
|||
<template>
|
||||
<page-container :tabList="list" @back="onBack" :tabActiveKey="instanceStore.active" @tabChange="onTabChange">
|
||||
<page-container
|
||||
:tabList="list"
|
||||
@back="onBack"
|
||||
:tabActiveKey="instanceStore.active"
|
||||
@tabChange="onTabChange"
|
||||
>
|
||||
<template #title>
|
||||
<div>
|
||||
<div style="display: flex; align-items: center;">
|
||||
<div>{{instanceStore.current.name}}</div>
|
||||
<div style="display: flex; align-items: center">
|
||||
<div>{{ instanceStore.current.name }}</div>
|
||||
<a-divider type="vertical" />
|
||||
<a-space>
|
||||
<a-badge :text="instanceStore.current.state?.text" :status="statusMap.get(instanceStore.current.state?.value)" />
|
||||
<a-popconfirm title="确认启用设备" @confirm="handleAction" v-if="instanceStore.current.state?.value === 'notActive'">
|
||||
<a-badge
|
||||
:text="instanceStore.current.state?.text"
|
||||
:status="
|
||||
statusMap.get(
|
||||
instanceStore.current.state?.value,
|
||||
)
|
||||
"
|
||||
/>
|
||||
<a-popconfirm
|
||||
title="确认启用设备"
|
||||
@confirm="handleAction"
|
||||
v-if="
|
||||
instanceStore.current.state?.value ===
|
||||
'notActive'
|
||||
"
|
||||
>
|
||||
<a-button type="link">启用设备</a-button>
|
||||
</a-popconfirm>
|
||||
<a-popconfirm title="确认断开连接" @confirm="handleDisconnect" v-if="instanceStore.current.state?.value === 'online'">
|
||||
<a-popconfirm
|
||||
title="确认断开连接"
|
||||
@confirm="handleDisconnect"
|
||||
v-if="
|
||||
instanceStore.current.state?.value === 'online'
|
||||
"
|
||||
>
|
||||
<a-button type="link">断开连接</a-button>
|
||||
</a-popconfirm>
|
||||
<a-tooltip v-if="instanceStore.current?.accessProvider === 'child-device' &&
|
||||
instanceStore.current?.state?.value === 'offline'" :title="instanceStore.current?.features?.find((item) => item.id === 'selfManageState')
|
||||
? '该设备的在线状态与父设备(网关设备)保持一致'
|
||||
: '该设备在线状态由设备自身运行状态决定,不继承父设备(网关设备)的在线状态'">
|
||||
<AIcon type="QuestionCircleOutlined" style="font-size: 14px" />
|
||||
<a-tooltip
|
||||
v-if="
|
||||
instanceStore.current?.accessProvider ===
|
||||
'child-device' &&
|
||||
instanceStore.current?.state?.value ===
|
||||
'offline'
|
||||
"
|
||||
:title="
|
||||
instanceStore.current?.features?.find(
|
||||
(item) => item.id === 'selfManageState',
|
||||
)
|
||||
? '该设备的在线状态与父设备(网关设备)保持一致'
|
||||
: '该设备在线状态由设备自身运行状态决定,不继承父设备(网关设备)的在线状态'
|
||||
"
|
||||
>
|
||||
<AIcon
|
||||
type="QuestionCircleOutlined"
|
||||
style="font-size: 14px"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</div>
|
||||
<div style="padding-top: 10px">
|
||||
<a-descriptions size="small" :column="4">
|
||||
<a-descriptions-item label="ID">{{ instanceStore.current.id }}</a-descriptions-item>
|
||||
<a-descriptions-item label="ID">{{
|
||||
instanceStore.current.id
|
||||
}}</a-descriptions-item>
|
||||
<a-descriptions-item label="所属产品">
|
||||
<a-button style="margin-top: -5px; padding: 0" type="link" @click="jumpProduct">{{ instanceStore.current.productName }}</a-button>
|
||||
<a-button
|
||||
style="margin-top: -5px; padding: 0"
|
||||
type="link"
|
||||
@click="jumpProduct"
|
||||
>{{
|
||||
instanceStore.current.productName
|
||||
}}</a-button
|
||||
>
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #extra>
|
||||
<img @click="handleRefresh" :src="getImage('/device/button.png')" style="margin-right: 20px; cursor: pointer;" />
|
||||
<img
|
||||
@click="handleRefresh"
|
||||
:src="getImage('/device/button.png')"
|
||||
style="margin-right: 20px; cursor: pointer"
|
||||
/>
|
||||
</template>
|
||||
<component :is="tabs[instanceStore.tabActiveKey]" v-bind="{ type: 'device' }" @onJump="onTabChange" />
|
||||
<component
|
||||
:is="tabs[instanceStore.tabActiveKey]"
|
||||
v-bind="{ type: 'device' }"
|
||||
@onJump="onTabChange"
|
||||
/>
|
||||
</page-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useInstanceStore } from '@/store/instance';
|
||||
import Info from './Info/index.vue';
|
||||
import Running from './Running/index.vue'
|
||||
import Running from './Running/index.vue';
|
||||
import Metadata from '../../components/Metadata/index.vue';
|
||||
import ChildDevice from './ChildDevice/index.vue';
|
||||
import Diagnose from './Diagnose/index.vue'
|
||||
import Function from './Function/index.vue'
|
||||
import { _deploy, _disconnect } from '@/api/device/instance'
|
||||
import Diagnose from './Diagnose/index.vue';
|
||||
import Function from './Function/index.vue';
|
||||
import Modbus from './Modbus/index.vue';
|
||||
import OPCUA from './OPCUA/index.vue';
|
||||
import EdgeMap from './EdgeMap/index.vue';
|
||||
import { _deploy, _disconnect } from '@/api/device/instance';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { getImage } from '@/utils/comm';
|
||||
import { getWebSocket } from '@/utils/websocket';
|
||||
|
||||
const route = useRoute();
|
||||
const instanceStore = useInstanceStore()
|
||||
const instanceStore = useInstanceStore();
|
||||
|
||||
const statusMap = new Map();
|
||||
statusMap.set('online', 'success');
|
||||
statusMap.set('offline', 'error');
|
||||
statusMap.set('notActive', 'warning');
|
||||
|
||||
const list = [
|
||||
const statusRef = ref();
|
||||
|
||||
const list = ref([
|
||||
{
|
||||
key: 'Info',
|
||||
tab: '实例信息'
|
||||
tab: '实例信息',
|
||||
},
|
||||
{
|
||||
key: 'Running',
|
||||
tab: '运行状态'
|
||||
tab: '运行状态',
|
||||
},
|
||||
{
|
||||
key: 'Metadata',
|
||||
tab: '物模型'
|
||||
tab: '物模型',
|
||||
},
|
||||
{
|
||||
key: 'Function',
|
||||
tab: '设备功能'
|
||||
tab: '设备功能',
|
||||
},
|
||||
{
|
||||
key: 'ChildDevice',
|
||||
tab: '子设备'
|
||||
tab: '子设备',
|
||||
},
|
||||
{
|
||||
key: 'Diagnose',
|
||||
tab: '设备诊断'
|
||||
},
|
||||
]
|
||||
]);
|
||||
|
||||
const tabs = {
|
||||
Info,
|
||||
Metadata,
|
||||
Running,
|
||||
ChildDevice,
|
||||
Diagnose,
|
||||
Function
|
||||
}
|
||||
Info,
|
||||
Metadata,
|
||||
Running,
|
||||
ChildDevice,
|
||||
Diagnose,
|
||||
Function,
|
||||
Modbus,
|
||||
OPCUA,
|
||||
EdgeMap,
|
||||
};
|
||||
|
||||
const getStatus = (id: string) => {
|
||||
statusRef.value = getWebSocket(
|
||||
`instance-editor-info-status-${id}`,
|
||||
`/dashboard/device/status/change/realTime`,
|
||||
{
|
||||
deviceId: id,
|
||||
},
|
||||
).subscribe(() => {
|
||||
instanceStore.refresh(id);
|
||||
});
|
||||
};
|
||||
|
||||
watch(
|
||||
() => route.params.id,
|
||||
(newId) => {
|
||||
if(newId){
|
||||
instanceStore.tabActiveKey = 'Info'
|
||||
instanceStore.refresh(newId as string)
|
||||
if (newId) {
|
||||
instanceStore.tabActiveKey = 'Info';
|
||||
instanceStore.refresh(newId as string);
|
||||
|
||||
getStatus(String(newId));
|
||||
}
|
||||
},
|
||||
{immediate: true, deep: true}
|
||||
{ immediate: true, deep: true },
|
||||
);
|
||||
|
||||
const onBack = () => {
|
||||
|
||||
}
|
||||
const onBack = () => {};
|
||||
|
||||
const onTabChange = (e: string) => {
|
||||
instanceStore.tabActiveKey = e
|
||||
}
|
||||
instanceStore.tabActiveKey = e;
|
||||
};
|
||||
|
||||
const handleAction = async () => {
|
||||
if(instanceStore.current.id){
|
||||
const resp = await _deploy(instanceStore.current.id)
|
||||
if(resp.status === 200){
|
||||
message.success('操作成功!')
|
||||
instanceStore.refresh(instanceStore.current.id)
|
||||
if (instanceStore.current.id) {
|
||||
const resp = await _deploy(instanceStore.current.id);
|
||||
if (resp.status === 200) {
|
||||
message.success('操作成功!');
|
||||
instanceStore.refresh(instanceStore.current.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleDisconnect = async () => {
|
||||
if(instanceStore.current.id){
|
||||
const resp = await _disconnect(instanceStore.current.id)
|
||||
if(resp.status === 200){
|
||||
message.success('操作成功!')
|
||||
instanceStore.refresh(instanceStore.current.id)
|
||||
if (instanceStore.current.id) {
|
||||
const resp = await _disconnect(instanceStore.current.id);
|
||||
if (resp.status === 200) {
|
||||
message.success('操作成功!');
|
||||
instanceStore.refresh(instanceStore.current.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleRefresh = async () => {
|
||||
if(instanceStore.current.id){
|
||||
await instanceStore.refresh(instanceStore.current.id)
|
||||
message.success('操作成功')
|
||||
if (instanceStore.current.id) {
|
||||
await instanceStore.refresh(instanceStore.current.id);
|
||||
message.success('操作成功');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const jumpProduct = () => {
|
||||
message.warn('暂未开发')
|
||||
}
|
||||
message.warn('暂未开发');
|
||||
};
|
||||
|
||||
watchEffect(() => {
|
||||
const keys = list.value.map((i) => i.key);
|
||||
if (instanceStore.current.protocol && !(['modbus-tcp', 'opc-ua'].includes(instanceStore.current.protocol)) && !keys.includes('Diagnose')) {
|
||||
list.value.push({
|
||||
key: 'Diagnose',
|
||||
tab: '设备诊断',
|
||||
});
|
||||
}
|
||||
if (
|
||||
instanceStore.current.protocol === 'modbus-tcp' &&
|
||||
!keys.includes('Modbus')
|
||||
) {
|
||||
list.value.push({
|
||||
key: 'Modbus',
|
||||
tab: 'Modbus TCP',
|
||||
});
|
||||
}
|
||||
if (
|
||||
instanceStore.current.protocol === 'opc-ua' &&
|
||||
!keys.includes('OPCUA')
|
||||
) {
|
||||
list.value.push({
|
||||
key: 'OPCUA',
|
||||
tab: 'OPC UA',
|
||||
});
|
||||
}
|
||||
if (
|
||||
instanceStore.current.accessProvider === 'edge-child-device' &&
|
||||
instanceStore.current.parentId &&
|
||||
!keys.includes('EdgeMap')
|
||||
) {
|
||||
list.value.push({
|
||||
key: 'EdgeMap',
|
||||
tab: '边缘端映射',
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
statusRef.value && statusRef.value.unsubscribe();
|
||||
});
|
||||
</script>
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="page-container">
|
||||
<page-container>
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="6">
|
||||
<TopCard
|
||||
|
@ -42,7 +42,7 @@
|
|||
/>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</page-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
@ -200,10 +200,7 @@ const getPlayCount = async (params: any) => {
|
|||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.page-container {
|
||||
padding: 24px;
|
||||
.dash-board-bottom {
|
||||
margin-top: 24px;
|
||||
}
|
||||
.dash-board-bottom {
|
||||
margin-top: 24px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<!-- 通知模板详情 -->
|
||||
<template>
|
||||
<div class="page-container">
|
||||
<page-container>
|
||||
<a-card>
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="12">
|
||||
|
@ -241,7 +241,7 @@
|
|||
:channel="formData.channel"
|
||||
@close="getProductList"
|
||||
/>
|
||||
</div>
|
||||
</page-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
@ -353,7 +353,8 @@ const saveProductVis = ref(false);
|
|||
const getDetail = async () => {
|
||||
const res = await DeviceApi.detail(route.query.id as string);
|
||||
// console.log('res: ', res);
|
||||
formData.value = res.result;
|
||||
// formData.value = res.result;
|
||||
Object.assign(formData.value, res.result);
|
||||
formData.value.channel = res.result.provider;
|
||||
};
|
||||
|
||||
|
@ -366,8 +367,7 @@ onMounted(() => {
|
|||
*/
|
||||
const btnLoading = ref<boolean>(false);
|
||||
const handleSubmit = () => {
|
||||
const form = useForm(formData.value, formRules.value);
|
||||
form.validate()
|
||||
validate()
|
||||
.then(async () => {
|
||||
btnLoading.value = true;
|
||||
let res;
|
||||
|
@ -392,8 +392,4 @@ const handleSubmit = () => {
|
|||
|
||||
<style lang="less" scoped>
|
||||
@import './index.less';
|
||||
.page-container {
|
||||
background: #f0f2f5;
|
||||
padding: 24px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="page-container">
|
||||
<page-container>
|
||||
<Search
|
||||
:columns="columns"
|
||||
target="notice-config"
|
||||
|
@ -126,7 +126,7 @@
|
|||
</a-space>
|
||||
</template>
|
||||
</JTable>
|
||||
</div>
|
||||
</page-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
@ -137,7 +137,9 @@ import { getImage } from '@/utils/comm';
|
|||
import { PROVIDER_OPTIONS } from '@/views/media/Device/const';
|
||||
import { providerType } from './const';
|
||||
|
||||
const router = useRouter();
|
||||
import { useMenuStore } from 'store/menu';
|
||||
|
||||
const menuStory = useMenuStore();
|
||||
|
||||
const listRef = ref<Record<string, any>>({});
|
||||
const params = ref<Record<string, any>>({});
|
||||
|
@ -240,7 +242,9 @@ const handleSearch = (e: any) => {
|
|||
* 新增
|
||||
*/
|
||||
const handleAdd = () => {
|
||||
router.push(`/media/device/Save`);
|
||||
menuStory.jumpPage('media/Device/Save', {
|
||||
id: ':id',
|
||||
});
|
||||
};
|
||||
|
||||
const getActions = (
|
||||
|
@ -257,7 +261,9 @@ const getActions = (
|
|||
},
|
||||
icon: 'EditOutlined',
|
||||
onClick: () => {
|
||||
router.push(`/media/device/Save?id=${data.id}`);
|
||||
menuStory.jumpPage('media/Device/Save', {
|
||||
id: data.id,
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -268,9 +274,13 @@ const getActions = (
|
|||
},
|
||||
icon: 'PartitionOutlined',
|
||||
onClick: () => {
|
||||
router.push(
|
||||
`/media/device/Channel?id=${data.id}&type=${data.provider}`,
|
||||
);
|
||||
// router.push(
|
||||
// `/media/device/Channel?id=${data.id}&type=${data.provider}`,
|
||||
// );
|
||||
menuStory.jumpPage('media/Device/Channel', {
|
||||
id: data.id,
|
||||
type: data.provider,
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -320,9 +330,3 @@ const getActions = (
|
|||
return actions;
|
||||
};
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.page-container {
|
||||
background: #f0f2f5;
|
||||
padding: 24px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -27,6 +27,9 @@
|
|||
<script setup lang="ts">
|
||||
import homeApi from '@/api/media/home';
|
||||
import { getImage } from '@/utils/comm';
|
||||
import { useMenuStore } from 'store/menu';
|
||||
|
||||
const menuStory = useMenuStore();
|
||||
|
||||
const channelCount = ref(0);
|
||||
const deviceCount = ref(0);
|
||||
|
@ -44,9 +47,10 @@ const getData = () => {
|
|||
});
|
||||
};
|
||||
|
||||
const router = useRouter();
|
||||
const jumpPage = () => {
|
||||
router.push('/media/dashboard');
|
||||
menuStory.jumpPage('media/DashBoard', {
|
||||
id: ':id',
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="page-container">
|
||||
<page-container>
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="14">
|
||||
<BootCard
|
||||
|
@ -21,7 +21,7 @@
|
|||
/>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</page-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
@ -96,9 +96,3 @@ const deviceStepDetails: recommendList[] = [
|
|||
},
|
||||
];
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.page-container {
|
||||
padding: 24px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<!-- 通知配置详情 -->
|
||||
<template>
|
||||
<div class="page-container">
|
||||
<page-container>
|
||||
<a-card>
|
||||
<a-row>
|
||||
<a-col :span="10">
|
||||
|
@ -273,7 +273,7 @@
|
|||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
</div>
|
||||
</page-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
@ -427,8 +427,8 @@ const clearValid = () => {
|
|||
const getDetail = async () => {
|
||||
if (route.params.id === ':id') return;
|
||||
const res = await configApi.detail(route.params.id as string);
|
||||
// console.log('res: ', res);
|
||||
formData.value = res.result;
|
||||
// formData.value = res.result;
|
||||
Object.assign(formData.value, res.result);
|
||||
// console.log('formData.value: ', formData.value);
|
||||
};
|
||||
getDetail();
|
||||
|
@ -438,8 +438,7 @@ getDetail();
|
|||
*/
|
||||
const btnLoading = ref<boolean>(false);
|
||||
const handleSubmit = () => {
|
||||
const form = useForm(formData.value, formRules.value);
|
||||
form.validate()
|
||||
validate()
|
||||
.then(async () => {
|
||||
// console.log('formData.value: ', formData.value);
|
||||
btnLoading.value = true;
|
||||
|
@ -463,10 +462,3 @@ const handleSubmit = () => {
|
|||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.page-container {
|
||||
background: #f0f2f5;
|
||||
padding: 24px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="page-container">
|
||||
<page-container>
|
||||
<Search
|
||||
:columns="columns"
|
||||
target="notice-config"
|
||||
|
@ -166,7 +166,7 @@
|
|||
<Debug v-model:visible="debugVis" :data="currentConfig" />
|
||||
<Log v-model:visible="logVis" :data="currentConfig" />
|
||||
<SyncUser v-model:visible="syncVis" :data="currentConfig" />
|
||||
</div>
|
||||
</page-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
@ -180,14 +180,15 @@ import SyncUser from './SyncUser/index.vue';
|
|||
import Debug from './Debug/index.vue';
|
||||
import Log from './Log/index.vue';
|
||||
import { downloadObject } from '@/utils/utils';
|
||||
import { useMenuStore } from 'store/menu';
|
||||
|
||||
const menuStory = useMenuStore();
|
||||
|
||||
let providerList: any = [];
|
||||
Object.keys(MSG_TYPE).forEach((key) => {
|
||||
providerList = [...providerList, ...MSG_TYPE[key]];
|
||||
});
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const configRef = ref<Record<string, any>>({});
|
||||
const params = ref<Record<string, any>>({});
|
||||
|
||||
|
@ -270,7 +271,7 @@ const getMethodTxt = (type: string) => {
|
|||
* 新增
|
||||
*/
|
||||
const handleAdd = () => {
|
||||
router.push(`/iot/notice/Config/detail/:id`);
|
||||
menuStory.jumpPage('notice/Config/Detail', { id: ':id' });
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -330,7 +331,9 @@ const getActions = (
|
|||
onClick: () => {
|
||||
// visible.value = true;
|
||||
// current.value = data;
|
||||
router.push(`/iot/notice/Config/detail/${data.id}`);
|
||||
menuStory.jumpPage('notice/Config/Detail', {
|
||||
id: data.id,
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -426,9 +429,3 @@ const getActions = (
|
|||
}
|
||||
};
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.page-container {
|
||||
background: #f0f2f5;
|
||||
padding: 24px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
placeholder="请选择收信部门"
|
||||
style="width: 100%"
|
||||
:allowClear="true"
|
||||
v-model:value="_value"
|
||||
/>
|
||||
</template>
|
||||
|
||||
|
@ -17,10 +18,16 @@ type Emits = {
|
|||
const emit = defineEmits<Emits>();
|
||||
|
||||
const props = defineProps({
|
||||
toParty: { type: String, default: '' },
|
||||
type: { type: String, default: '' },
|
||||
configId: { type: String, default: '' },
|
||||
});
|
||||
|
||||
const _value = computed({
|
||||
get: () => props.toParty,
|
||||
set: (val: string) => emit('update:toParty', val),
|
||||
});
|
||||
|
||||
const options = ref([]);
|
||||
const queryData = async () => {
|
||||
const { result } = await templateApi.getDept(props.type, props.configId);
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
placeholder="请选择标签推送,多个标签用,号分隔"
|
||||
style="width: 100%"
|
||||
:allowClear="true"
|
||||
v-model:value="_value"
|
||||
/>
|
||||
</template>
|
||||
|
||||
|
@ -17,10 +18,16 @@ type Emits = {
|
|||
const emit = defineEmits<Emits>();
|
||||
|
||||
const props = defineProps({
|
||||
toTag: { type: String, default: '' },
|
||||
type: { type: String, default: '' },
|
||||
configId: { type: String, default: '' },
|
||||
});
|
||||
|
||||
const _value = computed({
|
||||
get: () => props.toTag,
|
||||
set: (val: string) => emit('update:toTag', val),
|
||||
});
|
||||
|
||||
const options = ref([]);
|
||||
const queryData = async () => {
|
||||
const { result } = await templateApi.getTags(props.configId);
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
placeholder="请选择收信人"
|
||||
style="width: 100%"
|
||||
:allowClear="true"
|
||||
v-model:value="_value"
|
||||
/>
|
||||
</template>
|
||||
|
||||
|
@ -17,10 +18,16 @@ type Emits = {
|
|||
const emit = defineEmits<Emits>();
|
||||
|
||||
const props = defineProps({
|
||||
toUser: { type: String, default: '' },
|
||||
type: { type: String, default: '' },
|
||||
configId: { type: String, default: '' },
|
||||
});
|
||||
|
||||
const _value = computed({
|
||||
get: () => props.toUser,
|
||||
set: (val: string) => emit('update:toUser', val),
|
||||
});
|
||||
|
||||
const options = ref([]);
|
||||
const queryData = async () => {
|
||||
const { result } = await templateApi.getUser(props.type, props.configId);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<!-- 通知模板详情 -->
|
||||
<template>
|
||||
<div class="page-container">
|
||||
<page-container>
|
||||
<a-card>
|
||||
<a-row>
|
||||
<a-col :span="10">
|
||||
|
@ -13,6 +13,7 @@
|
|||
v-model:value="formData.type"
|
||||
placeholder="请选择通知方式"
|
||||
:disabled="!!formData.id"
|
||||
@change="resetPublicFiles"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="(item, index) in NOTICE_METHOD"
|
||||
|
@ -40,14 +41,26 @@
|
|||
<RadioCard
|
||||
:options="msgType"
|
||||
v-model="formData.provider"
|
||||
@change="getConfigList"
|
||||
@change="handleProviderChange"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="绑定配置"
|
||||
v-bind="validateInfos.configId"
|
||||
v-if="formData.type !== 'email'"
|
||||
>
|
||||
<template #label>
|
||||
<span>
|
||||
绑定配置
|
||||
<a-tooltip
|
||||
title="使用固定的通知配置来发送此通知模版"
|
||||
>
|
||||
<AIcon
|
||||
type="QuestionCircleOutlined"
|
||||
style="margin-left: 2px"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<a-select
|
||||
v-model:value="formData.configId"
|
||||
placeholder="请选择绑定配置"
|
||||
|
@ -68,9 +81,19 @@
|
|||
v-if="formData.provider === 'dingTalkMessage'"
|
||||
>
|
||||
<a-form-item
|
||||
label="AgentId"
|
||||
v-bind="validateInfos['template.agentId']"
|
||||
>
|
||||
<template #label>
|
||||
<span>
|
||||
AgentID
|
||||
<a-tooltip title="应用唯一标识">
|
||||
<AIcon
|
||||
type="QuestionCircleOutlined"
|
||||
style="margin-left: 2px"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<a-input
|
||||
v-model:value="
|
||||
formData.template.agentId
|
||||
|
@ -78,6 +101,45 @@
|
|||
placeholder="请输入AppSecret"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-row :gutter="10">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="收信部门">
|
||||
<ToOrg
|
||||
v-model:toParty="
|
||||
formData.template.toParty
|
||||
"
|
||||
:type="formData.type"
|
||||
:config-id="formData.configId"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item>
|
||||
<template #label>
|
||||
<span>
|
||||
收信人
|
||||
<a-tooltip
|
||||
title="如果不填写该字段,将在使用此模板发送通知时进行指定"
|
||||
>
|
||||
<AIcon
|
||||
type="QuestionCircleOutlined"
|
||||
style="
|
||||
margin-left: 2px;
|
||||
"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<ToUser
|
||||
v-model:toUser="
|
||||
formData.template.toUser
|
||||
"
|
||||
:type="formData.type"
|
||||
:config-id="formData.configId"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
<template
|
||||
v-if="
|
||||
|
@ -166,10 +228,13 @@
|
|||
}"
|
||||
:showUploadList="false"
|
||||
@change="
|
||||
(e) => handleChange(e)
|
||||
(e) =>
|
||||
handleLinkChange(e)
|
||||
"
|
||||
>
|
||||
<AIcon type="UploadOutlined" />
|
||||
<AIcon
|
||||
type="UploadOutlined"
|
||||
/>
|
||||
</a-upload>
|
||||
</template>
|
||||
</a-input>
|
||||
|
@ -189,9 +254,19 @@
|
|||
<!-- 微信 -->
|
||||
<template v-if="formData.type === 'weixin'">
|
||||
<a-form-item
|
||||
label="AgentId"
|
||||
v-bind="validateInfos['template.agentId']"
|
||||
>
|
||||
<template #label>
|
||||
<span>
|
||||
AgentId
|
||||
<a-tooltip title="应用唯一标识">
|
||||
<AIcon
|
||||
type="QuestionCircleOutlined"
|
||||
style="margin-left: 2px"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<a-input
|
||||
v-model:value="formData.template.agentId"
|
||||
placeholder="请输入agentId"
|
||||
|
@ -199,9 +274,22 @@
|
|||
</a-form-item>
|
||||
<a-row :gutter="10">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="收信人">
|
||||
<a-form-item>
|
||||
<template #label>
|
||||
<span>
|
||||
收信人
|
||||
<a-tooltip
|
||||
title="如果不填写该字段,将在使用此模版发送通知时进行指定。"
|
||||
>
|
||||
<AIcon
|
||||
type="QuestionCircleOutlined"
|
||||
style="margin-left: 2px"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<ToUser
|
||||
v-model:to-user="
|
||||
v-model:toUser="
|
||||
formData.template.toUser
|
||||
"
|
||||
:type="formData.type"
|
||||
|
@ -212,7 +300,7 @@
|
|||
<a-col :span="12">
|
||||
<a-form-item label="收信部门">
|
||||
<ToOrg
|
||||
v-model:to-user="
|
||||
v-model:toParty="
|
||||
formData.template.toParty
|
||||
"
|
||||
:type="formData.type"
|
||||
|
@ -221,9 +309,22 @@
|
|||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-form-item label="标签推送">
|
||||
<a-form-item>
|
||||
<template #label>
|
||||
<span>
|
||||
标签推送
|
||||
<a-tooltip
|
||||
title="本企业微信的标签ID列表,最多支持100个,如果不填写该字段,将在使用此模版发送通知时进行指定"
|
||||
>
|
||||
<AIcon
|
||||
type="QuestionCircleOutlined"
|
||||
style="margin-left: 2px"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<ToTag
|
||||
v-model:to-user="formData.template.toTag"
|
||||
v-model:toTag="formData.template.toTag"
|
||||
:type="formData.type"
|
||||
:config-id="formData.configId"
|
||||
/>
|
||||
|
@ -232,15 +333,38 @@
|
|||
<!-- 邮件 -->
|
||||
<template v-if="formData.type === 'email'">
|
||||
<a-form-item
|
||||
label="标题"
|
||||
v-bind="validateInfos['template.subject']"
|
||||
>
|
||||
<template #label>
|
||||
<span>
|
||||
标题
|
||||
<a-tooltip title="邮件标题">
|
||||
<AIcon
|
||||
type="QuestionCircleOutlined"
|
||||
style="margin-left: 2px"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<a-input
|
||||
v-model:value="formData.template.subject"
|
||||
placeholder="请输入标题"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="收件人">
|
||||
<a-form-item>
|
||||
<template #label>
|
||||
<span>
|
||||
收件人
|
||||
<a-tooltip
|
||||
title="多个收件人用换行分隔 最大支持1000个号码"
|
||||
>
|
||||
<AIcon
|
||||
type="QuestionCircleOutlined"
|
||||
style="margin-left: 2px"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<a-select
|
||||
mode="tags"
|
||||
:options="[]"
|
||||
|
@ -248,7 +372,20 @@
|
|||
placeholder="请选择收件人"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="附件信息">
|
||||
<a-form-item>
|
||||
<template #label>
|
||||
<span>
|
||||
附件信息
|
||||
<a-tooltip
|
||||
title="附件只输入文件名称将在发送邮件时进行文件上传"
|
||||
>
|
||||
<AIcon
|
||||
type="QuestionCircleOutlined"
|
||||
style="margin-left: 2px"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<Attachments
|
||||
v-model:attachments="
|
||||
formData.template.attachments
|
||||
|
@ -259,9 +396,21 @@
|
|||
<!-- 语音 -->
|
||||
<template v-if="formData.type === 'voice'">
|
||||
<a-form-item
|
||||
label="类型"
|
||||
v-bind="validateInfos['template.templateType']"
|
||||
>
|
||||
<template #label>
|
||||
<span>
|
||||
类型
|
||||
<a-tooltip
|
||||
title="语音验证码类型可配置变量,并且只支持数字和英文字母"
|
||||
>
|
||||
<AIcon
|
||||
type="QuestionCircleOutlined"
|
||||
style="margin-left: 2px"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<a-select
|
||||
v-model:value="
|
||||
formData.template.templateType
|
||||
|
@ -280,13 +429,25 @@
|
|||
<a-row :gutter="10">
|
||||
<a-col :span="12">
|
||||
<a-form-item
|
||||
label="模板ID"
|
||||
v-bind="
|
||||
validateInfos[
|
||||
'template.templateCode'
|
||||
]
|
||||
"
|
||||
>
|
||||
<template #label>
|
||||
<span>
|
||||
模板ID
|
||||
<a-tooltip
|
||||
title="阿里云内部分配的唯一ID标识"
|
||||
>
|
||||
<AIcon
|
||||
type="QuestionCircleOutlined"
|
||||
style="margin-left: 2px"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<a-input
|
||||
v-model:value="
|
||||
formData.template.templateCode
|
||||
|
@ -296,7 +457,20 @@
|
|||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="被叫号码">
|
||||
<a-form-item>
|
||||
<template #label>
|
||||
<span>
|
||||
被叫号码
|
||||
<a-tooltip
|
||||
title="仅支持中国大陆号码"
|
||||
>
|
||||
<AIcon
|
||||
type="QuestionCircleOutlined"
|
||||
style="margin-left: 2px"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<a-input
|
||||
v-model:value="
|
||||
formData.template.calledNumber
|
||||
|
@ -306,7 +480,20 @@
|
|||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-form-item label="被叫显号">
|
||||
<a-form-item>
|
||||
<template #label>
|
||||
<span>
|
||||
被叫显号
|
||||
<a-tooltip
|
||||
title="必须是已购买的号码,用于呼叫号码显示"
|
||||
>
|
||||
<AIcon
|
||||
type="QuestionCircleOutlined"
|
||||
style="margin-left: 2px"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<a-input
|
||||
v-model:value="
|
||||
formData.template.calledShowNumbers
|
||||
|
@ -314,16 +501,39 @@
|
|||
placeholder="请输入被叫显号"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="播放次数">
|
||||
<a-form-item>
|
||||
<template #label>
|
||||
<span>
|
||||
播放次数
|
||||
<a-tooltip title="语音文件的播放次数">
|
||||
<AIcon
|
||||
type="QuestionCircleOutlined"
|
||||
style="margin-left: 2px"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<a-input
|
||||
v-model:value="formData.template.playTimes"
|
||||
placeholder="请输入播放次数"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="模版内容"
|
||||
v-if="formData.template.templateType === 'tts'"
|
||||
>
|
||||
<template #label>
|
||||
<span>
|
||||
模版内容
|
||||
<a-tooltip
|
||||
title="语音验证码内容输入框,用于渲染验语音证码变量。"
|
||||
>
|
||||
<AIcon
|
||||
type="QuestionCircleOutlined"
|
||||
style="margin-left: 2px"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<a-textarea
|
||||
v-model:value="formData.template.message"
|
||||
show-count
|
||||
|
@ -337,14 +547,27 @@
|
|||
<a-row :gutter="10">
|
||||
<a-col :span="12">
|
||||
<a-form-item
|
||||
label="模板"
|
||||
v-bind="validateInfos['template.code']"
|
||||
>
|
||||
<template #label>
|
||||
<span>
|
||||
模板
|
||||
<a-tooltip
|
||||
title="阿里云短信平台自定义的模版名称"
|
||||
>
|
||||
<AIcon
|
||||
type="QuestionCircleOutlined"
|
||||
style="margin-left: 2px"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<a-select
|
||||
v-model:value="
|
||||
formData.template.code
|
||||
"
|
||||
placeholder="请选择模板"
|
||||
@change="handleTemplateChange"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="(
|
||||
|
@ -359,7 +582,20 @@
|
|||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="收信人">
|
||||
<a-form-item>
|
||||
<template #label>
|
||||
<span>
|
||||
收信人
|
||||
<a-tooltip
|
||||
title="仅支持中国大陆号码"
|
||||
>
|
||||
<AIcon
|
||||
type="QuestionCircleOutlined"
|
||||
style="margin-left: 2px"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<a-input
|
||||
v-model:value="
|
||||
formData.template.phoneNumber
|
||||
|
@ -370,9 +606,21 @@
|
|||
</a-col>
|
||||
</a-row>
|
||||
<a-form-item
|
||||
label="签名"
|
||||
v-bind="validateInfos['template.signName']"
|
||||
>
|
||||
<template #label>
|
||||
<span>
|
||||
签名
|
||||
<a-tooltip
|
||||
title="用于短信内容签名信息显示"
|
||||
>
|
||||
<AIcon
|
||||
type="QuestionCircleOutlined"
|
||||
style="margin-left: 2px"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<a-select
|
||||
v-model:value="formData.template.signName"
|
||||
placeholder="请选择签名"
|
||||
|
@ -417,17 +665,28 @@
|
|||
</a-form-item>
|
||||
</template>
|
||||
<a-form-item
|
||||
label="模版内容"
|
||||
v-if="
|
||||
formData.type !== 'sms' &&
|
||||
formData.type !== 'webhook' &&
|
||||
formData.type !== 'voice'
|
||||
"
|
||||
v-bind="validateInfos['template.message']"
|
||||
>
|
||||
<template #label>
|
||||
<span>
|
||||
模版内容
|
||||
<a-tooltip title="发送的内容,支持录入变量">
|
||||
<AIcon
|
||||
type="QuestionCircleOutlined"
|
||||
style="margin-left: 2px"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<a-textarea
|
||||
v-model:value="formData.template.message"
|
||||
:maxlength="200"
|
||||
:rows="5"
|
||||
:disabled="formData.type === 'sms'"
|
||||
placeholder="变量格式:${name};
|
||||
示例:尊敬的${name},${time}有设备触发告警,请注意处理"
|
||||
/>
|
||||
|
@ -471,7 +730,7 @@
|
|||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
</div>
|
||||
</page-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
@ -534,6 +793,27 @@ const formData = ref<TemplateFormData>({
|
|||
configId: '',
|
||||
});
|
||||
|
||||
/**
|
||||
* 重置公用字段值
|
||||
*/
|
||||
const resetPublicFiles = () => {
|
||||
formData.value.template.message = '';
|
||||
formData.value.configId = undefined;
|
||||
|
||||
if (
|
||||
formData.value.type === 'dingTalk' ||
|
||||
formData.value.type === 'weixin'
|
||||
) {
|
||||
formData.value.template.toTag = undefined;
|
||||
formData.value.template.toUser = undefined;
|
||||
}
|
||||
if (formData.value.type === 'weixin')
|
||||
formData.value.template.toParty = undefined;
|
||||
if (formData.value.type === 'email')
|
||||
formData.value.template.toParty = undefined;
|
||||
// formData.value.description = '';
|
||||
};
|
||||
|
||||
// 根据通知方式展示对应的字段
|
||||
watch(
|
||||
() => formData.value.type,
|
||||
|
@ -547,8 +827,14 @@ watch(
|
|||
formData.value.template =
|
||||
TEMPLATE_FIELD_MAP[val][formData.value.provider];
|
||||
|
||||
getConfigList();
|
||||
if (val !== 'email') getConfigList();
|
||||
clearValid();
|
||||
// console.log('formData.value: ', formData.value);
|
||||
|
||||
if (val === 'sms') {
|
||||
getTemplateList();
|
||||
getSignsList();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
|
@ -587,6 +873,7 @@ const formRules = ref({
|
|||
'template.signName': [{ required: true, message: '请输入签名' }],
|
||||
// webhook
|
||||
description: [{ max: 200, message: '最多可输入200个字符' }],
|
||||
'template.message': [{ required: true, message: '请输入' }],
|
||||
});
|
||||
|
||||
const { resetFields, validate, validateInfos, clearValidate } = useForm(
|
||||
|
@ -632,12 +919,14 @@ const clearValid = () => {
|
|||
* 获取详情
|
||||
*/
|
||||
const getDetail = async () => {
|
||||
const res = await templateApi.detail(route.params.id as string);
|
||||
// console.log('res: ', res);
|
||||
formData.value = res.result;
|
||||
// console.log('formData.value: ', formData.value);
|
||||
if (route.params.id !== ':id') {
|
||||
const res = await templateApi.detail(route.params.id as string);
|
||||
// formData.value = res.result;
|
||||
Object.assign(formData.value, res.result);
|
||||
// console.log('formData.value: ', formData.value);
|
||||
}
|
||||
};
|
||||
// getDetail();
|
||||
getDetail();
|
||||
|
||||
/**
|
||||
* 获取绑定配置
|
||||
|
@ -651,12 +940,19 @@ const getConfigList = async () => {
|
|||
const { result } = await templateApi.getConfig({ terms });
|
||||
configList.value = result;
|
||||
};
|
||||
getConfigList();
|
||||
|
||||
/**
|
||||
* 通知类型改变
|
||||
*/
|
||||
const handleProviderChange = () => {
|
||||
getConfigList();
|
||||
resetPublicFiles();
|
||||
};
|
||||
|
||||
/**
|
||||
* link消息类型 图片链接
|
||||
*/
|
||||
const handleChange = (info: UploadChangeParam) => {
|
||||
const handleLinkChange = (info: UploadChangeParam) => {
|
||||
if (info.file.status === 'done') {
|
||||
formData.value.template.link.picUrl = info.file.response?.result;
|
||||
}
|
||||
|
@ -675,22 +971,29 @@ const handleConfigChange = () => {
|
|||
*/
|
||||
const templateList = ref();
|
||||
const getTemplateList = async () => {
|
||||
const { result } = await templateApi.getAliTemplate(
|
||||
formData.value.configId,
|
||||
);
|
||||
const id = formData.value.configId || undefined;
|
||||
const { result } = await templateApi.getAliTemplate(id);
|
||||
templateList.value = result;
|
||||
};
|
||||
getTemplateList();
|
||||
|
||||
/**
|
||||
* 短信模板改变
|
||||
*/
|
||||
const handleTemplateChange = () => {
|
||||
formData.value.template.message = templateList.value.find(
|
||||
(f: any) => formData.value.template.code === f.templateCode,
|
||||
)?.templateContent;
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取签名
|
||||
*/
|
||||
const signsList = ref();
|
||||
const getSignsList = async () => {
|
||||
const { result } = await templateApi.getSigns(formData.value.configId);
|
||||
const id = formData.value.configId || undefined;
|
||||
const { result } = await templateApi.getSigns(id);
|
||||
signsList.value = result;
|
||||
};
|
||||
getSignsList();
|
||||
|
||||
/**
|
||||
* 表单提交
|
||||
|
@ -698,11 +1001,12 @@ getSignsList();
|
|||
const btnLoading = ref<boolean>(false);
|
||||
const handleSubmit = () => {
|
||||
if (formData.value.type === 'email') delete formData.value.configId;
|
||||
if (formData.value.template.messageType === 'markdown') delete formData.value.template.link
|
||||
if (formData.value.template.messageType === 'link') delete formData.value.template.markdown
|
||||
if (formData.value.template.messageType === 'markdown')
|
||||
delete formData.value.template.link;
|
||||
if (formData.value.template.messageType === 'link')
|
||||
delete formData.value.template.markdown;
|
||||
// console.log('formData.value: ', formData.value);
|
||||
const form = useForm(formData.value, formRules.value);
|
||||
form.validate()
|
||||
validate()
|
||||
.then(async () => {
|
||||
formData.value.template.ttsCode =
|
||||
formData.value.template.templateCode;
|
||||
|
@ -737,10 +1041,3 @@ const handleSubmit = () => {
|
|||
// );
|
||||
// test
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.page-container {
|
||||
background: #f0f2f5;
|
||||
padding: 24px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="page-container">
|
||||
<page-container>
|
||||
<Search
|
||||
:columns="columns"
|
||||
target="notice-config"
|
||||
|
@ -152,7 +152,7 @@
|
|||
|
||||
<Debug v-model:visible="debugVis" :data="currentConfig" />
|
||||
<Log v-model:visible="logVis" :data="currentConfig" />
|
||||
</div>
|
||||
</page-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
@ -167,14 +167,15 @@ import { NOTICE_METHOD, MSG_TYPE } from '@/views/notice/const';
|
|||
import Debug from './Debug/index.vue';
|
||||
import Log from './Log/index.vue';
|
||||
import { downloadObject } from '@/utils/utils';
|
||||
import { useMenuStore } from 'store/menu';
|
||||
|
||||
const menuStory = useMenuStore();
|
||||
|
||||
let providerList: any = [];
|
||||
Object.keys(MSG_TYPE).forEach((key) => {
|
||||
providerList = [...providerList, ...MSG_TYPE[key]];
|
||||
});
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const configRef = ref<Record<string, any>>({});
|
||||
const params = ref<Record<string, any>>({});
|
||||
|
||||
|
@ -257,7 +258,9 @@ const getMethodTxt = (type: string) => {
|
|||
* 新增
|
||||
*/
|
||||
const handleAdd = () => {
|
||||
router.push(`/iot/notice/Template/detail/:id`);
|
||||
menuStory.jumpPage('notice/Template/Detail', {
|
||||
id: ':id',
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -324,7 +327,9 @@ const getActions = (
|
|||
onClick: () => {
|
||||
// visible.value = true;
|
||||
// current.value = data;
|
||||
router.push(`/iot/notice/Template/detail/${data.id}`);
|
||||
menuStory.jumpPage('notice/Template/Detail', {
|
||||
id: data.id,
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -383,9 +388,3 @@ const getActions = (
|
|||
return actions;
|
||||
};
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.page-container {
|
||||
background: #f0f2f5;
|
||||
padding: 24px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -184,11 +184,11 @@ export const TEMPLATE_FIELD_MAP = {
|
|||
},
|
||||
voice: {
|
||||
aliyun: {
|
||||
templateType: '',
|
||||
templateType: 'tts',
|
||||
templateCode: '',
|
||||
ttsCode: '',
|
||||
message: '',
|
||||
playTimes: undefined,
|
||||
playTimes: 1,
|
||||
calledShowNumbers: '',
|
||||
calledNumber: '',
|
||||
}
|
||||
|
|
|
@ -1,15 +1,57 @@
|
|||
<template>
|
||||
<page-container>
|
||||
<search
|
||||
|
||||
:columns='columns'
|
||||
/>
|
||||
<j-table
|
||||
:columns='columns'
|
||||
/>
|
||||
</page-container>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
<script setup lang='ts'>
|
||||
|
||||
import type { SceneItem } from './typings'
|
||||
|
||||
const columns = [
|
||||
{
|
||||
dataIndex: 'name',
|
||||
fixed: 'left',
|
||||
ellipsis: true,
|
||||
width: 300,
|
||||
title: '名称',
|
||||
search: {
|
||||
type: 'string'
|
||||
}
|
||||
},
|
||||
{
|
||||
dataIndex: 'triggerType',
|
||||
title: '触发方式',
|
||||
search: {
|
||||
type: 'select',
|
||||
options: [
|
||||
{ label: '手动触发', value: 'manual'},
|
||||
{ label: '定时触发', value: 'timer'},
|
||||
{ label: '设备触发', value: 'device'}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
dataIndex: 'description',
|
||||
title: '说明',
|
||||
},
|
||||
{
|
||||
dataIndex: 'state',
|
||||
title: '状态',
|
||||
search: {
|
||||
type: 'select',
|
||||
options: [
|
||||
{ label: '正常', value: 'started'},
|
||||
{ label: '禁用', value: 'disable'},
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
|
|
@ -0,0 +1,307 @@
|
|||
type State = {
|
||||
value: string;
|
||||
text: string;
|
||||
};
|
||||
|
||||
type Action = {
|
||||
executor: string;
|
||||
configuration: Record<string, unknown>;
|
||||
};
|
||||
|
||||
type Trigger = {
|
||||
type: string;
|
||||
device: Record<string, any>;
|
||||
};
|
||||
|
||||
export enum ParallelEnum {
|
||||
'parallel' = 'parallel',
|
||||
'serial' = 'serial',
|
||||
}
|
||||
|
||||
export type ParallelType = keyof typeof ParallelEnum;
|
||||
|
||||
export enum Source {
|
||||
'manual' = 'manual',
|
||||
'metric' = 'metric',
|
||||
'fixed' = 'fixed',
|
||||
}
|
||||
|
||||
export enum ActionDeviceSelector {
|
||||
'all' = 'all',
|
||||
'fixed' = 'fixed',
|
||||
'tag' = 'tag',
|
||||
'relation' = 'relation',
|
||||
}
|
||||
|
||||
export enum ActionDeviceSource {
|
||||
'fixed' = 'fixed',
|
||||
'upper' = 'upper',
|
||||
'relation' = 'relation',
|
||||
}
|
||||
|
||||
export enum OperatorType {
|
||||
'online' = 'online',
|
||||
'offline' = 'offline',
|
||||
'reportEvent' = 'reportEvent',
|
||||
'reportProperty' = 'reportProperty',
|
||||
'readProperty' = 'readProperty',
|
||||
'writeProperty' = 'writeProperty',
|
||||
'invokeFunction' = 'invokeFunction',
|
||||
}
|
||||
|
||||
export enum TimerTrigger {
|
||||
'week' = 'week',
|
||||
'month' = 'month',
|
||||
'cron' = 'cron',
|
||||
}
|
||||
|
||||
export enum TimeUnit {
|
||||
'seconds' = 'seconds',
|
||||
'minutes' = 'minutes',
|
||||
'hours' = 'hours',
|
||||
}
|
||||
|
||||
export enum Executor {
|
||||
'notify' = 'notify',
|
||||
'delay' = 'delay',
|
||||
'device' = 'device',
|
||||
'alarm' = 'alarm',
|
||||
}
|
||||
|
||||
export enum DeviceMessageType {
|
||||
'INVOKE_FUNCTION' = 'INVOKE_FUNCTION',
|
||||
'READ_PROPERTY' = 'READ_PROPERTY',
|
||||
'WRITE_PROPERTY' = 'WRITE_PROPERTY',
|
||||
}
|
||||
|
||||
export enum ActionAlarmMode {
|
||||
'trigger' = 'trigger',
|
||||
'relieve' = 'relieve',
|
||||
}
|
||||
|
||||
export interface OperationTimerPeriod {
|
||||
from: string;
|
||||
to: string;
|
||||
every: string[];
|
||||
unit: keyof typeof TimeUnit;
|
||||
}
|
||||
|
||||
export interface OperationTimer {
|
||||
trigger: keyof typeof TimerTrigger;
|
||||
mod: string;
|
||||
cron?: string;
|
||||
when?: string[];
|
||||
period?: OperationTimerPeriod;
|
||||
once?: Record<string, any>;
|
||||
}
|
||||
|
||||
export interface TriggerDeviceOptions {
|
||||
operator: keyof typeof OperatorType;
|
||||
/** 触发类型为readProperty,writeProperty,invokeFunction时不能为空 */
|
||||
timer?: OperationTimer;
|
||||
/** 触发类型为reportEvent时不能为空 */
|
||||
eventId?: string;
|
||||
/** 触发类型为readProperty时不能为空 */
|
||||
readProperties?: string[];
|
||||
/** 触发类型为writeProperty时不能为空 */
|
||||
writeProperties?: Record<string, any>;
|
||||
/** 触发类型为invokeFunction时不能为空 */
|
||||
functionId?: string;
|
||||
/** 触发类型为invokeFunction时不能为空 */
|
||||
functionParameters?: Record<string, any>[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 设备触发配置
|
||||
*/
|
||||
export interface TriggerDevice {
|
||||
productId: string;
|
||||
selector: string;
|
||||
selectorValues?: Record<string, any>[];
|
||||
operation?: TriggerDeviceOptions;
|
||||
}
|
||||
|
||||
export interface ShakeLimitType {
|
||||
enabled: boolean;
|
||||
groupType?: string; // 执行动作没有该参数
|
||||
time: number;
|
||||
threshold: number;
|
||||
alarmFirst: boolean;
|
||||
}
|
||||
|
||||
export interface BranchesType {
|
||||
enabled: boolean;
|
||||
groupType?: string; // 执行动作没有该参数
|
||||
time: number;
|
||||
threshold: number;
|
||||
alarmFirst: boolean;
|
||||
}
|
||||
|
||||
export interface SceneItem {
|
||||
parallel: boolean;
|
||||
state: State;
|
||||
actions: Action[];
|
||||
trigger: Trigger;
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
branches: BranchesType[];
|
||||
options: any;
|
||||
triggerType: string;
|
||||
}
|
||||
|
||||
export type TriggerType = {
|
||||
type: string;
|
||||
/**
|
||||
* 防抖配置
|
||||
*/
|
||||
shakeLimit?: ShakeLimitType;
|
||||
/**
|
||||
* 拓展信息
|
||||
*/
|
||||
options?: Record<string, any>;
|
||||
/**
|
||||
* 设备触发配置
|
||||
*/
|
||||
device?: TriggerDevice;
|
||||
/**
|
||||
* 定时触发配置
|
||||
*/
|
||||
timer?: OperationTimer;
|
||||
};
|
||||
|
||||
export interface TermsVale {
|
||||
source: keyof typeof Source;
|
||||
/** 手动输入值,source为 manual 时不能为空 */
|
||||
value?: Record<string, any> | any[];
|
||||
/** 指标值,source为 metric 时不能为空 */
|
||||
metric?: Record<string, any>;
|
||||
}
|
||||
|
||||
export type TermsType = {
|
||||
column?: string;
|
||||
value?: TermsVale;
|
||||
type?: string;
|
||||
termType?: string;
|
||||
options?: any[];
|
||||
terms?: TermsType[];
|
||||
key?: string;
|
||||
};
|
||||
|
||||
export type PlatformRelation = {
|
||||
objectType: string;
|
||||
objectId: string;
|
||||
};
|
||||
|
||||
export type Relationship = {
|
||||
objectType: string;
|
||||
objectSource: {
|
||||
source: string;
|
||||
upperKey: string;
|
||||
};
|
||||
related: {
|
||||
objectType: string;
|
||||
relation: string;
|
||||
};
|
||||
};
|
||||
|
||||
export interface NotifyVariablesType {
|
||||
source: string;
|
||||
value?: Record<string, any>;
|
||||
upperKey?: string;
|
||||
relation?: PlatformRelation | Relationship;
|
||||
}
|
||||
|
||||
export interface NotifyProps {
|
||||
notifyType: string;
|
||||
notifierId: string;
|
||||
templateId: string;
|
||||
variables: Record<string, NotifyVariablesType>;
|
||||
options?: Record<string, any>;
|
||||
}
|
||||
|
||||
export type SelectorValuesType =
|
||||
| { value: string; name: string }
|
||||
| { value: { column: string; value: any }[]; name: string }
|
||||
| { value: { objectType: string; relation: any }[] };
|
||||
|
||||
export type ActionDeviceMessageType = {
|
||||
deviceId: string;
|
||||
messageType: keyof typeof DeviceMessageType;
|
||||
/** 功能调用时使用 */
|
||||
functionId?: string;
|
||||
/** 功能调用时使用 */
|
||||
inputs?: Record<string, any>[];
|
||||
/** 读取属性时使用, 读取属性时为String数组,设置属性时为 Object */
|
||||
properties?: string[] | Record<string, any>;
|
||||
};
|
||||
|
||||
export interface ActionsDeviceProps {
|
||||
selector: keyof typeof ActionDeviceSelector;
|
||||
source: keyof typeof ActionDeviceSource;
|
||||
productId?: string;
|
||||
message?: ActionDeviceMessageType;
|
||||
selectorValues?: SelectorValuesType[];
|
||||
/** 来源为upper时不能为空 */
|
||||
upperKey?: string;
|
||||
/** 来源为relation时不能为空 */
|
||||
relation?: any;
|
||||
}
|
||||
|
||||
export interface BranchesThen {
|
||||
parallel: boolean;
|
||||
actions: ActionsType[];
|
||||
key?: string;
|
||||
}
|
||||
|
||||
export interface ActionBranchesProps {
|
||||
when: TermsType[];
|
||||
shakeLimit: ShakeLimitType;
|
||||
then: BranchesThen[];
|
||||
key?: string;
|
||||
}
|
||||
|
||||
export interface ActionsType {
|
||||
executor: keyof typeof Executor;
|
||||
/** 执行器类型为notify时不能为空 */
|
||||
notify?: NotifyProps;
|
||||
/** 执行器类型为delay时不能为空 */
|
||||
delay?: {
|
||||
time?: number;
|
||||
unit?: keyof typeof TimeUnit;
|
||||
};
|
||||
device?: ActionsDeviceProps;
|
||||
alarm?: {
|
||||
mode: keyof typeof ActionAlarmMode;
|
||||
};
|
||||
terms?: TermsType[];
|
||||
/** map中的key,用于删除 */
|
||||
key?: string;
|
||||
options?: Record<string, any>;
|
||||
}
|
||||
|
||||
export interface FormModelType {
|
||||
id?: string;
|
||||
name?: string;
|
||||
/**
|
||||
* 触发方式
|
||||
*/
|
||||
trigger?: TriggerType;
|
||||
/**
|
||||
* 触发条件,结构与通用查询条件相同。条件数据来自接口:根据触发器解析出支持的条件列
|
||||
*/
|
||||
terms?: TermsType[];
|
||||
/**
|
||||
* 执行动作
|
||||
*/
|
||||
actions?: ActionsType[];
|
||||
/**
|
||||
* 动作分支
|
||||
*/
|
||||
branches?: ActionBranchesProps[];
|
||||
/**
|
||||
* 拓展信息,用于前端存储一些渲染数据
|
||||
*/
|
||||
options?: Record<string, any>;
|
||||
description?: string;
|
||||
}
|
Loading…
Reference in New Issue