Merge branch 'dev' into dev-hub
This commit is contained in:
commit
9736d6e954
|
@ -1 +1,2 @@
|
|||
ENV=develop
|
||||
VITE_APP_BASE_API=/api
|
|
@ -1 +1,2 @@
|
|||
ENV=production
|
||||
VITE_APP_BASE_API=/api
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,49 @@
|
|||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
|
||||
const rootPath = path.resolve(__dirname, '../')
|
||||
|
||||
function optimizeAntdComponents(moduleName: string): string[] {
|
||||
const moduleESPath = `${moduleName}/es`
|
||||
const nodeModulePath = `./node_modules/${moduleESPath}`
|
||||
const includes: string[] = [moduleESPath]
|
||||
|
||||
const folders = fs.readdirSync(
|
||||
path.resolve(rootPath, nodeModulePath)
|
||||
)
|
||||
|
||||
folders.map(name => {
|
||||
const folderName = path.resolve(
|
||||
rootPath,
|
||||
nodeModulePath,
|
||||
name
|
||||
)
|
||||
let stat = fs.lstatSync(folderName)
|
||||
if (stat.isDirectory()) {
|
||||
let styleFolder = path.resolve(folderName, 'style')
|
||||
if (fs.existsSync((styleFolder))) {
|
||||
let _stat = fs.lstatSync(styleFolder)
|
||||
if (_stat.isDirectory()) {
|
||||
includes.push(`${moduleESPath}/${name}/style`)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return includes
|
||||
}
|
||||
|
||||
export function optimizeDeps() {
|
||||
return {
|
||||
name: "optimizeDeps",
|
||||
configResolved: async (config) => {
|
||||
const components = [
|
||||
...optimizeAntdComponents('ant-design-vue'),
|
||||
...optimizeAntdComponents('jetlinks-ui-components')
|
||||
]
|
||||
let concat = config.optimizeDeps.include.concat(components)
|
||||
config.optimizeDeps.include = Array.from(new Set(concat))
|
||||
console.log(config.optimizeDeps.include)
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 285 KiB |
|
@ -1,8 +1,13 @@
|
|||
<template>
|
||||
<ConfigProvider :locale='zhCN'>
|
||||
<router-view />
|
||||
</ConfigProvider>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ConfigProvider } from 'jetlinks-ui-components'
|
||||
import zhCN from 'jetlinks-ui-components/es/locale/zh_CN';
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
|
|
@ -33,6 +33,13 @@ export const detail = (id: string) => server.get<DeviceInstance>(`/device-instan
|
|||
*/
|
||||
export const query = (data?: Record<string, any>) => server.post('/device-instance/_query', data)
|
||||
|
||||
/**
|
||||
* 不分页查询设备
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
export const queryNoPagingPost = (data?: Record<string, any>) => server.post('/device-instance/_query/no-paging?paging=false', data)
|
||||
|
||||
/**
|
||||
* 删除设备
|
||||
* @param id 设备ID
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
import server from '@/utils/request'
|
||||
|
||||
export const restPassword = (id: string) => server.post(`/edge/operations/${id}/auth-user-password-reset/invoke`)
|
||||
|
||||
export const _control = (deviceId: string) => server.get(`/edge/remote/${deviceId}/url`)
|
||||
|
||||
export const _stopControl = (deviceId: string) => server.get(`/edge/remote/${deviceId}/stop`, {})
|
||||
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
import server from '@/utils/request'
|
||||
|
||||
export const query = (data: Record<string, any>) => server.post(`/entity/template/_query`, data)
|
||||
|
||||
export const modify = (id: string, data: Record<string, any>) => server.put(`/entity/template/${id}`, data)
|
||||
|
||||
export const _delete = (id: string) => server.remove(`/entity/template/${id}`)
|
||||
|
||||
export const _start = (data: Record<string, any>) => server.post(`/entity/template/start/_batch`, data)
|
||||
|
||||
export const _stop = (data: Record<string, any>) => server.post(`/entity/template/stop/_batch`, data)
|
||||
|
||||
export const queryDeviceList = (data: Record<string, any>) => server.post(`/device-instance/detail/_query`, data)
|
||||
|
|
@ -24,3 +24,5 @@ export const _execute = (id: string) => server.post(`/scene/${id}/_execute`);
|
|||
export const queryBuiltInParams = (data: any, params?: any) => server.post(`/scene/parse-variables`, data, params);
|
||||
|
||||
export const getParseTerm = (data: Record<string, any>) => server.post(`/scene/parse-term-column`, data)
|
||||
|
||||
export const queryAlarmList = (data: Record<string, any>) => server.post(`/alarm/config/_query/`, data)
|
|
@ -24,3 +24,12 @@ export const addOperations_api = (data:object) => server.patch(`/application/ope
|
|||
* 删除可授权的接口ID
|
||||
*/
|
||||
export const delOperations_api = (data:object) => server.remove(`/application/operations/_batch`,{},{data});
|
||||
|
||||
/**
|
||||
* 赋权-选中/取消选中api
|
||||
* @param id
|
||||
* @param type
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
export const updateOperations_api = (code:string,type:'_add'| '_delete', data: object) => server.post(`/application/${code}/grant/${type}`, data);
|
||||
|
|
|
@ -22,3 +22,7 @@ export const rdbTree_api = (id: string) => server.get(`/datasource/rdb/${id}/tab
|
|||
export const rdbTables_api = (id: string,key:string) => server.get(`/datasource/rdb/${id}/table/${key}`);
|
||||
// 保存表格
|
||||
export const saveTable_api = (id: string,data:object) => server.patch(`/datasource/rdb/${id}/table`,data);
|
||||
/**
|
||||
* 删除已保存的行
|
||||
*/
|
||||
export const delSaveRow_api = (datasourceId: string, table: string, data: any) => server.post(`/datasource/rdb/${datasourceId}/table/${table}/drop-column`,data);
|
||||
|
|
|
@ -4,7 +4,11 @@ import server from '@/utils/request';
|
|||
export const getMenuTree_api = (data: object) => server.post(`/menu/_all/tree`, data);
|
||||
|
||||
export const queryOwnThree = (data: any) => server.post<any>('/menu/user-own/tree', data)
|
||||
|
||||
/**
|
||||
* 校验编码唯一性
|
||||
* @param data
|
||||
*/
|
||||
export const validCode_api = (data:object) => server.get(`/menu/code/_validate`,data);
|
||||
|
||||
|
||||
// 获取资产类型
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
|
||||
const color = {
|
||||
'processing': '64, 169, 255',
|
||||
'error': '247, 79, 70',
|
||||
'success': '74, 234, 220',
|
||||
'warning': '250, 178, 71',
|
||||
'default': '63, 73, 96'
|
||||
}
|
||||
export const getHexColor = (code: string, pe: number = 0.3) => {
|
||||
const _color = color[code] || color.default
|
||||
if (code === 'default') {
|
||||
pe = 0.1
|
||||
}
|
||||
return `rgba(${_color}, ${pe})`
|
||||
}
|
||||
|
||||
export default color
|
|
@ -1,12 +1,13 @@
|
|||
<template>
|
||||
<j-badge
|
||||
:status="statusNames ? statusNames[status] : 'default'"
|
||||
:color="_color"
|
||||
:text="text"
|
||||
></j-badge>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
// import { StatusColorEnum } from '@/utils/consts.ts';
|
||||
import { getHexColor } from './color'
|
||||
const props = defineProps({
|
||||
text: {
|
||||
type: String,
|
||||
|
@ -26,6 +27,18 @@ const props = defineProps({
|
|||
* 0: 'error'
|
||||
* }
|
||||
*/
|
||||
statusNames: { type: Object },
|
||||
statusNames: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
'success': 'success',
|
||||
'warning': 'warning',
|
||||
'error': 'error',
|
||||
'default': 'default',
|
||||
})
|
||||
},
|
||||
});
|
||||
|
||||
const _color = computed(() => {
|
||||
return getHexColor(props.statusNames[props.status], 1)
|
||||
})
|
||||
</script>
|
||||
|
|
|
@ -29,7 +29,9 @@
|
|||
<div
|
||||
v-if="showStatus"
|
||||
class="card-state"
|
||||
:class="statusNames ? statusNames[status] : ''"
|
||||
:style='{
|
||||
backgroundColor: getHexColor(statusNames[status])
|
||||
}'
|
||||
>
|
||||
<div class="card-state-content">
|
||||
<BadgeStatus
|
||||
|
@ -68,9 +70,10 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
<script setup lang="ts" name='CardBox'>
|
||||
import BadgeStatus from '@/components/BadgeStatus/index.vue';
|
||||
import type { ActionsType } from '@/components/Table/index.vue';
|
||||
import { getHexColor } from '../BadgeStatus/color'
|
||||
import type { ActionsType } from '@/components/Table';
|
||||
import { PropType } from 'vue';
|
||||
|
||||
type EmitProps = {
|
||||
|
|
|
@ -45,7 +45,6 @@ const handleCancel = () => {
|
|||
emit('change', 'simple')
|
||||
}
|
||||
const handleOk = () => {
|
||||
console.log(_value.value)
|
||||
emit('update:value', _value.value)
|
||||
emit('change', 'simple')
|
||||
}
|
||||
|
|
|
@ -185,7 +185,6 @@ const getProperty = () => {
|
|||
label: item.name,
|
||||
value: item.id,
|
||||
}));
|
||||
console.log(options.value)
|
||||
}
|
||||
getProperty()
|
||||
</script>
|
||||
|
|
|
@ -87,7 +87,6 @@ const productStore = useProductStore()
|
|||
|
||||
const getData = async (id?: string) => {
|
||||
const metadata = productStore.current.metadata || '{}';
|
||||
console.log('metadata', metadata)
|
||||
const _properties = JSON.parse(metadata).properties || [] as PropertyMetadata[]
|
||||
const properties = {
|
||||
id: 'property',
|
||||
|
|
|
@ -8,9 +8,12 @@
|
|||
:breadcrumb="{ routes: breadcrumb }"
|
||||
>
|
||||
<template #breadcrumbRender="slotProps">
|
||||
<a v-if="slotProps.route.index !== 0">{{
|
||||
slotProps.route.breadcrumbName
|
||||
}}</a>
|
||||
<a
|
||||
v-if="slotProps.route.index !== 0 && !slotProps.route.isLast"
|
||||
@click='() => jumpPage(slotProps.route.path)'
|
||||
>
|
||||
{{ slotProps.route.breadcrumbName }}
|
||||
</a>
|
||||
<span v-else >{{ slotProps.route.breadcrumbName }}</span>
|
||||
</template>
|
||||
<template #rightContentRender>
|
||||
|
@ -32,6 +35,7 @@ import Notice from './components/Notice.vue';
|
|||
import DefaultSetting from '../../../config/config';
|
||||
import { useMenuStore } from '@/store/menu';
|
||||
import { clearMenuItem } from 'jetlinks-ui-components/es/ProLayout/util';
|
||||
import { AccountMenu } from '@/router/menu'
|
||||
|
||||
type StateType = {
|
||||
collapsed: boolean;
|
||||
|
@ -50,7 +54,8 @@ const layoutConf = reactive({
|
|||
siderWidth: DefaultSetting.layout.siderWidth,
|
||||
logo: DefaultSetting.layout.logo,
|
||||
title: DefaultSetting.layout.title,
|
||||
menuData: clearMenuItem(menu.siderMenus),
|
||||
menuData: [...clearMenuItem(menu.siderMenus), AccountMenu],
|
||||
// menuData: menu.siderMenus,
|
||||
splitMenus: true,
|
||||
});
|
||||
|
||||
|
@ -61,26 +66,49 @@ const state = reactive<StateType>({
|
|||
selectedKeys: [],
|
||||
});
|
||||
|
||||
const findRouteMeta = (code: string) => {
|
||||
let meta = []
|
||||
let menuItem: any = menu.menus[code]
|
||||
while (menuItem) {
|
||||
meta.unshift(menuItem)
|
||||
if (menuItem.parentName) {
|
||||
menuItem = menu.menus[menuItem.parentName]
|
||||
} else {
|
||||
menuItem = false
|
||||
}
|
||||
}
|
||||
return meta
|
||||
}
|
||||
|
||||
const jumpPage = (path: string) => {
|
||||
console.log(path)
|
||||
router.push(path)
|
||||
}
|
||||
|
||||
const breadcrumb = computed(() =>
|
||||
router.currentRoute.value.matched.concat().map((item, index) => {
|
||||
{
|
||||
const paths = router.currentRoute.value.name as string
|
||||
const metas = findRouteMeta(paths)
|
||||
return metas.map((item, index) => {
|
||||
return {
|
||||
index,
|
||||
isLast: index === (metas.length - 1),
|
||||
path: item.path,
|
||||
breadcrumbName: item.meta.title || '',
|
||||
breadcrumbName: item.title || '',
|
||||
};
|
||||
}),
|
||||
})
|
||||
}
|
||||
);
|
||||
|
||||
watchEffect(() => {
|
||||
if (router.currentRoute) {
|
||||
const matched = router.currentRoute.value.matched.concat();
|
||||
state.selectedKeys = matched.map((r) => r.path);
|
||||
state.openKeys = matched
|
||||
.filter((r) => r.path !== router.currentRoute.value.path)
|
||||
.map((r) => r.path);
|
||||
console.log(state.selectedKeys);
|
||||
const paths = router.currentRoute.value.name as string
|
||||
if (paths) {
|
||||
const _metas = findRouteMeta(paths)
|
||||
state.selectedKeys = _metas.map(item => item.path)
|
||||
state.openKeys = _metas.filter((r) => r !== router.currentRoute.value.path).map(item => item.path)
|
||||
}
|
||||
}
|
||||
// TODO 获取当前路由中参数,用于控制pure
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
|
|
|
@ -18,11 +18,11 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
<script setup lang="tsx">
|
||||
import { getList_api } from '@/api/account/notificationRecord';
|
||||
import NoticeInfo from './NoticeInfo.vue';
|
||||
import { getWebSocket } from '@/utils/websocket';
|
||||
import { notification } from 'ant-design-vue';
|
||||
import { notification, Button } from 'ant-design-vue';
|
||||
import { changeStatus_api } from '@/api/account/notificationRecord';
|
||||
import { useUserInfo } from '@/store/userInfo';
|
||||
|
||||
|
@ -38,12 +38,40 @@ const subscribeNotice = () => {
|
|||
.subscribe((resp: any) => {
|
||||
total.value += 1;
|
||||
notification.open({
|
||||
message: 'Notification Title',
|
||||
description:
|
||||
'This is the content of the notification. This is the content of the notification. This is the content of the notification.',
|
||||
message: resp?.payload?.topicName,
|
||||
description: () => (
|
||||
<div
|
||||
class="ellipsis"
|
||||
style={{ cursor: 'pointer' }}
|
||||
onClick={() => {
|
||||
changeStatus_api('_read', [resp.id]);
|
||||
}}
|
||||
>
|
||||
{resp?.payload?.message}
|
||||
</div>
|
||||
),
|
||||
onClick: () => {
|
||||
changeStatus_api('_read', [resp.id]);
|
||||
},
|
||||
key: resp.payload.id,
|
||||
btn: (
|
||||
<Button
|
||||
type="primary"
|
||||
size="small"
|
||||
onClick={() => {
|
||||
changeStatus_api('_read', [resp.id]).then(
|
||||
(resp: any) => {
|
||||
if (resp.status === 200) {
|
||||
notification.close(resp.payload.id);
|
||||
getList();
|
||||
}
|
||||
},
|
||||
);
|
||||
}}
|
||||
>
|
||||
标记已读
|
||||
</Button>
|
||||
),
|
||||
});
|
||||
});
|
||||
};
|
||||
|
|
|
@ -96,7 +96,6 @@ const handleDelete = (index: number) => {
|
|||
_value.value.splice(index, 1)
|
||||
}
|
||||
const handleClose = () => {
|
||||
console.log(editIndex.value)
|
||||
editIndex.value = -1
|
||||
}
|
||||
const handleAdd = () => {
|
||||
|
|
|
@ -7,28 +7,36 @@
|
|||
@mousedown="emit('onMouseDown', 'UP')"
|
||||
@mouseup="emit('onMouseUp', 'UP')"
|
||||
>
|
||||
<AIcon class="direction-icon" type="CaretUpOutlined" />
|
||||
<div class="direction-icon">
|
||||
<AIcon type="CaretUpOutlined" />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="direction-item right"
|
||||
@mousedown="emit('onMouseDown', 'RIGHT')"
|
||||
@mouseup="emit('onMouseUp', 'RIGHT')"
|
||||
>
|
||||
<AIcon class="direction-icon" type="CaretRightOutlined" />
|
||||
<div class="direction-icon">
|
||||
<AIcon type="CaretRightOutlined" />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="direction-item left"
|
||||
@mousedown="emit('onMouseDown', 'LEFT')"
|
||||
@mouseup="emit('onMouseUp', 'LEFT')"
|
||||
>
|
||||
<AIcon class="direction-icon" type="CaretLeftOutlined" />
|
||||
<div class="direction-icon">
|
||||
<AIcon type="CaretLeftOutlined" />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="direction-item down"
|
||||
@mousedown="emit('onMouseDown', 'DOWN')"
|
||||
@mouseup="emit('onMouseUp', 'DOWN')"
|
||||
>
|
||||
<AIcon class="direction-icon" type="CaretDownOutlined" />
|
||||
<div class="direction-icon">
|
||||
<AIcon type="CaretDownOutlined" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="direction-audio">
|
||||
<!-- <AIcon type="AudioOutlined" /> -->
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
:request='saveSearchHistory'
|
||||
:historyRequest='getSearchHistory'
|
||||
:columns='columns'
|
||||
:class='props.class'
|
||||
@search='searchSubmit'
|
||||
/>
|
||||
</template>
|
||||
|
|
|
@ -54,7 +54,7 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
<script lang="ts" setup name='JProUpload'>
|
||||
import { message, UploadChangeParam, UploadProps } from 'ant-design-vue';
|
||||
import { FILE_UPLOAD } from '@/api/comm';
|
||||
import { TOKEN_KEY } from '@/utils/variable';
|
|
@ -1,6 +1,6 @@
|
|||
<!-- 参数类型输入组件 -->
|
||||
<template>
|
||||
<div class="wrapper">
|
||||
<div class="value-item-warp">
|
||||
<j-select
|
||||
v-if="typeMap.get(itemType) === 'select'"
|
||||
v-model:value="myValue"
|
||||
|
@ -55,15 +55,15 @@
|
|||
allowClear
|
||||
>
|
||||
<template #addonAfter>
|
||||
<j-upload
|
||||
<a-upload
|
||||
name="file"
|
||||
:action="FILE_UPLOAD"
|
||||
:headers="headers"
|
||||
:showUploadList="false"
|
||||
@change="handleFileChange"
|
||||
>
|
||||
<AIcon type="CloudUploadOutlined" />
|
||||
</j-upload>
|
||||
<AIcon type="UploadOutlined" />
|
||||
</a-upload>
|
||||
</template>
|
||||
</j-input>
|
||||
<j-input
|
||||
|
@ -92,7 +92,7 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
<script setup lang="ts" name='ValueItem'>
|
||||
import { PropType } from 'vue';
|
||||
import { UploadChangeParam, UploadFile } from 'ant-design-vue';
|
||||
import { DefaultOptionType } from 'ant-design-vue/lib/select';
|
||||
|
@ -102,6 +102,7 @@ import { BASE_API_PATH, TOKEN_KEY } from '@/utils/variable';
|
|||
import { LocalStore } from '@/utils/comm';
|
||||
import { ItemData, ITypes } from './types';
|
||||
import { FILE_UPLOAD } from '@/api/comm';
|
||||
import { Upload } from 'jetlinks-ui-components'
|
||||
|
||||
type Emits = {
|
||||
(e: 'update:modelValue', data: string | number | boolean): void;
|
||||
|
|
|
@ -8,7 +8,7 @@ import CardBox from './CardBox/index.vue';
|
|||
import Search from './Search'
|
||||
import NormalUpload from './NormalUpload/index.vue'
|
||||
import FileFormat from './FileFormat/index.vue'
|
||||
import JProUpload from './JUpload/index.vue'
|
||||
import JProUpload from './Upload/index.vue'
|
||||
import { BasicLayoutPage, BlankLayoutPage } from './Layout'
|
||||
import { PageContainer, AIcon } from 'jetlinks-ui-components'
|
||||
import Ellipsis from './Ellipsis/index.vue'
|
||||
|
|
|
@ -4,13 +4,14 @@ import store from './store'
|
|||
import components from './components'
|
||||
import router from './router'
|
||||
import './style.less'
|
||||
// import jComponents from 'jetlinks-ui-components'
|
||||
// import 'jetlinks-ui-components/es/style.js'
|
||||
|
||||
import dayjs from 'dayjs';
|
||||
import 'dayjs/locale/zh-cn';
|
||||
dayjs.locale('zh-cn');
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
app.use(store)
|
||||
.use(router)
|
||||
.use(components)
|
||||
// .use(jComponents)
|
||||
.mount('#app')
|
||||
|
|
|
@ -5,6 +5,7 @@ export const AccountMenu = {
|
|||
component: () => import('@/components/Layout/BasicLayoutPage.vue'),
|
||||
redirect: '/account/center',
|
||||
name: 'account',
|
||||
code: 'account',
|
||||
meta: {
|
||||
title: '个人中心',
|
||||
icon: '',
|
||||
|
@ -14,6 +15,7 @@ export const AccountMenu = {
|
|||
{
|
||||
path: '/account/center',
|
||||
name: 'account/center',
|
||||
code: 'account/center',
|
||||
meta: {
|
||||
title: '基本设置',
|
||||
icon: '',
|
||||
|
@ -24,6 +26,7 @@ export const AccountMenu = {
|
|||
{
|
||||
path: '/account/NotificationSubscription',
|
||||
name: 'account/NotificationSubscription',
|
||||
code: 'account/NotificationSubscription',
|
||||
meta: {
|
||||
title: '通知订阅',
|
||||
icon: '',
|
||||
|
@ -34,6 +37,7 @@ export const AccountMenu = {
|
|||
{
|
||||
path: '/account/NotificationRecord',
|
||||
name: 'account/NotificationRecord',
|
||||
code: 'account/NotificationRecord',
|
||||
meta: {
|
||||
title: '通知记录',
|
||||
icon: '',
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { defineStore } from 'pinia'
|
||||
import { queryOwnThree } from '@/api/system/menu'
|
||||
import { filterAsnycRouter, MenuItem } from '@/utils/menu'
|
||||
import { filterAsyncRouter, findCodeRoute, MenuItem } from '@/utils/menu'
|
||||
import { isArray } from 'lodash-es'
|
||||
import { usePermissionStore } from './permission'
|
||||
import router from '@/router'
|
||||
|
@ -33,6 +33,8 @@ type MenuStateType = {
|
|||
menus: {
|
||||
[key: string]: {
|
||||
buttons?: string[]
|
||||
title: string
|
||||
parentName: string
|
||||
path: string
|
||||
}
|
||||
}
|
||||
|
@ -98,24 +100,15 @@ export const useMenuStore = defineStore({
|
|||
if (resp.success) {
|
||||
const permission = usePermissionStore()
|
||||
permission.permissions = {}
|
||||
const { menusData, silderMenus } = filterAsnycRouter(resp.result)
|
||||
this.menus = {}
|
||||
const handleMenuItem = (menu: any) => {
|
||||
if (isArray(menu)) {
|
||||
menu.forEach(menuItem => {
|
||||
this.menus[menuItem.name] = {
|
||||
path: menuItem.path,
|
||||
buttons: menuItem.meta.buttons
|
||||
}
|
||||
permission.permissions[menuItem.name] = menuItem.meta.buttons
|
||||
if (menuItem.children && menuItem.children.length) {
|
||||
handleMenuItem(menuItem.children)
|
||||
const { menusData, silderMenus } = filterAsyncRouter(resp.result)
|
||||
this.menus = findCodeRoute([...resp.result, AccountMenu])
|
||||
Object.keys(this.menus).forEach((item) => {
|
||||
const _item = this.menus[item]
|
||||
if (_item.buttons?.length) {
|
||||
permission.permissions[item] = _item.buttons
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
handleMenuItem(menusData)
|
||||
menusData.push({
|
||||
path: '/',
|
||||
redirect: menusData[0]?.path,
|
||||
|
@ -124,10 +117,7 @@ export const useMenuStore = defineStore({
|
|||
}
|
||||
})
|
||||
menusData.push(AccountMenu)
|
||||
silderMenus.push(AccountMenu)
|
||||
this.siderMenus = silderMenus
|
||||
console.log('menusData', menusData)
|
||||
console.log('silderMenus', silderMenus)
|
||||
res(menusData)
|
||||
}
|
||||
})
|
||||
|
|
|
@ -3,11 +3,12 @@ import { defineStore } from "pinia";
|
|||
export const usePermissionStore = defineStore({
|
||||
id: 'permission',
|
||||
state: () => ({
|
||||
permissions: {} as {[key: string]: string},
|
||||
permissions: {} as {[key: string]: string[]},
|
||||
}),
|
||||
getters: {
|
||||
check(state) {
|
||||
return (permissionCode: string) => {
|
||||
|
||||
if (!permissionCode) {
|
||||
return true
|
||||
}
|
||||
|
@ -16,6 +17,7 @@ export const usePermissionStore = defineStore({
|
|||
}
|
||||
const code = permissionCode.split(":")[0]
|
||||
const value = permissionCode.split(":")[1]
|
||||
|
||||
const _buttonArray = state.permissions[code]
|
||||
if (!_buttonArray) {
|
||||
return false
|
||||
|
|
|
@ -145,13 +145,15 @@ const extraRouteObj = {
|
|||
{ code: 'Save', name: '详情' },
|
||||
],
|
||||
},
|
||||
'edge/Device': {
|
||||
children: [{ code: 'Remote', name: '远程控制' }],
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
const resolveComponent = (name: any) => {
|
||||
const importPage = pagesComponent[`../views/${name}/index.vue`];
|
||||
if (!importPage) {
|
||||
console.warn(`Unknown page ${name}. Is it located under Pages with a .vue extension?`)
|
||||
return undefined
|
||||
} else {
|
||||
const res = () => importPage()
|
||||
|
@ -201,7 +203,52 @@ const findDetailRoutes = (routes: any[]): any[] => {
|
|||
return newRoutes
|
||||
}
|
||||
|
||||
export function filterAsnycRouter(asyncRouterMap: any, parentCode = '', level = 1): { menusData: any, silderMenus: any } {
|
||||
export const findCodeRoute = (asyncRouterMap: any[]) => {
|
||||
const routeMeta = {}
|
||||
|
||||
function findChildren (data: any[], code: string = '') {
|
||||
data.forEach(route => {
|
||||
routeMeta[route.code] = {
|
||||
path: route.url || route.path,
|
||||
title: route.meta?.title || route.name,
|
||||
parentName: code,
|
||||
buttons: route.buttons?.map((b: any) => b.id) || []
|
||||
}
|
||||
const detail = findDetailRouteItem(route.code, route.url)
|
||||
if (detail) {
|
||||
routeMeta[(detail as MenuItem).code] = {
|
||||
path: detail.url,
|
||||
title: detail.name,
|
||||
parentName: route.code,
|
||||
buttons: detail.buttons?.map((b: any) => b.id) || []
|
||||
}
|
||||
}
|
||||
|
||||
const otherRoutes = extraRouteObj[route.code]
|
||||
if (otherRoutes) {
|
||||
otherRoutes.children.map((item: any) => {
|
||||
const _code = `${route.code}/${item.code}`
|
||||
routeMeta[_code] = {
|
||||
path: `${route.url}/${item.code}`,
|
||||
title: item.name,
|
||||
parentName: route.code,
|
||||
buttons: item.buttons?.map((b: any) => b.id) || []
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (route.children) {
|
||||
findChildren(route.children, route.code)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
findChildren(asyncRouterMap)
|
||||
|
||||
return routeMeta
|
||||
}
|
||||
|
||||
export function filterAsyncRouter(asyncRouterMap: any, parentCode = '', level = 1): { menusData: any, silderMenus: any} {
|
||||
const _asyncRouterMap = cloneDeep(asyncRouterMap)
|
||||
const menusData: any[] = []
|
||||
const silderMenus: any[] = []
|
||||
|
@ -224,7 +271,7 @@ export function filterAsnycRouter(asyncRouterMap: any, parentCode = '', level =
|
|||
route.children = findDetailRoutes(route.children)
|
||||
if (route.children && route.children.length) {
|
||||
// TODO 查看是否具有详情页
|
||||
const { menusData: _menusData, silderMenus: _silderMenus } = filterAsnycRouter(route.children, `${parentCode}/${route.code}`, level + 1)
|
||||
const { menusData: _menusData, silderMenus: _silderMenus } = filterAsyncRouter(route.children, `${parentCode}/${route.code}`, level + 1)
|
||||
_route.children = _menusData
|
||||
silder.children = _silderMenus
|
||||
const showChildren = _route.children.some((r: any) => !r.meta.hideInMenu)
|
||||
|
@ -251,6 +298,6 @@ export function filterAsnycRouter(asyncRouterMap: any, parentCode = '', level =
|
|||
})
|
||||
return {
|
||||
menusData,
|
||||
silderMenus
|
||||
silderMenus,
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<page-container>
|
||||
<j-advanced-search
|
||||
<pro-search
|
||||
:columns="columns"
|
||||
target="northbound-aliyun"
|
||||
@search="handleSearch"
|
||||
|
@ -50,13 +50,17 @@
|
|||
<div class="card-item-content-text">
|
||||
网桥产品
|
||||
</div>
|
||||
<Ellipsis>
|
||||
<div>{{ slotProps?.bridgeProductName }}</div>
|
||||
</Ellipsis>
|
||||
</j-col>
|
||||
<j-col :span="12">
|
||||
<div class="card-item-content-text">
|
||||
<label>说明</label>
|
||||
</div>
|
||||
<Ellipsis>
|
||||
<div>{{ slotProps?.description }}</div>
|
||||
</Ellipsis>
|
||||
</j-col>
|
||||
</j-row>
|
||||
</template>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<page-container>
|
||||
<j-advanced-search
|
||||
<pro-search
|
||||
:columns="columns"
|
||||
target="northbound-dueros"
|
||||
@search="handleSearch"
|
||||
|
@ -48,13 +48,17 @@
|
|||
<j-row>
|
||||
<j-col :span="12">
|
||||
<div class="card-item-content-text">产品</div>
|
||||
<Ellipsis>
|
||||
<div>{{ slotProps?.productName }}</div>
|
||||
</Ellipsis>
|
||||
</j-col>
|
||||
<j-col :span="12">
|
||||
<div class="card-item-content-text">
|
||||
设备类型
|
||||
</div>
|
||||
<Ellipsis>
|
||||
<div>{{ slotProps?.applianceType?.text }}</div>
|
||||
</Ellipsis>
|
||||
</j-col>
|
||||
</j-row>
|
||||
</template>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<!-- 已登录-绑定三方账号 -->
|
||||
<template v-if="!token">
|
||||
<div class="info">
|
||||
<a-card style="width: 280px">
|
||||
<j-card style="width: 280px">
|
||||
<template #title>
|
||||
<div class="info-head">
|
||||
<img :src="getImage('/bind/Rectangle.png')" />
|
||||
|
@ -18,9 +18,9 @@
|
|||
<p>账号:admin</p>
|
||||
<p>用户名:超级管理员</p>
|
||||
</div>
|
||||
</a-card>
|
||||
</j-card>
|
||||
<img :src="getImage('/bind/Vector.png')" />
|
||||
<a-card style="width: 280px">
|
||||
<j-card style="width: 280px">
|
||||
<template #title>
|
||||
<div class="info-head">
|
||||
<img :src="getImage('/bind/Rectangle.png')" />
|
||||
|
@ -37,11 +37,11 @@
|
|||
<p>用户名:-</p>
|
||||
<p>名称:{{ accountInfo?.name || '-' }}</p>
|
||||
</div>
|
||||
</a-card>
|
||||
</j-card>
|
||||
</div>
|
||||
<div class="btn">
|
||||
<a-button type="primary" @click="handleBind"
|
||||
>立即绑定</a-button
|
||||
<j-button type="primary" @click="handleBind"
|
||||
>立即绑定</j-button
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -60,30 +60,30 @@
|
|||
你已通过微信授权,完善以下登录信息即可以完成绑定
|
||||
</div>
|
||||
<div class="login-form">
|
||||
<a-form layout="vertical">
|
||||
<a-form-item
|
||||
<j-form layout="vertical">
|
||||
<j-form-item
|
||||
label="账户"
|
||||
v-bind="validateInfos.username"
|
||||
>
|
||||
<a-input
|
||||
<j-input
|
||||
v-model:value="formData.username"
|
||||
placeholder="请输入账户"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
</j-form-item>
|
||||
<j-form-item
|
||||
label="密码"
|
||||
v-bind="validateInfos.password"
|
||||
>
|
||||
<a-input-password
|
||||
<j-input-password
|
||||
v-model:value="formData.password"
|
||||
placeholder="请输入密码"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
</j-form-item>
|
||||
<j-form-item
|
||||
label="验证码"
|
||||
v-bind="validateInfos.verifyCode"
|
||||
>
|
||||
<a-input
|
||||
<j-input
|
||||
v-model:value="formData.verifyCode"
|
||||
placeholder="请输入验证码"
|
||||
>
|
||||
|
@ -94,18 +94,18 @@
|
|||
style="cursor: pointer"
|
||||
/>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button
|
||||
</j-input>
|
||||
</j-form-item>
|
||||
<j-form-item>
|
||||
<j-button
|
||||
type="primary"
|
||||
@click="handleLoginBind"
|
||||
style="width: 100%"
|
||||
>
|
||||
登录并绑定账户
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</j-button>
|
||||
</j-form-item>
|
||||
</j-form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
<template>
|
||||
<page-container>
|
||||
<div class="notification-record-container">
|
||||
<j-advanced-search
|
||||
<pro-search
|
||||
:columns="columns"
|
||||
target="category"
|
||||
@search="(params:any)=>queryParams = {...params}"
|
||||
/>
|
||||
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
<template>
|
||||
<page-container>
|
||||
<div class="notification-subscription-container">
|
||||
<j-advanced-search
|
||||
<pro-search
|
||||
:columns="columns"
|
||||
target="category"
|
||||
@search="(params:any)=>queryParams = {...params}"
|
||||
/>
|
||||
<j-pro-table
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<!-- 新增编辑弹窗 -->
|
||||
<template>
|
||||
<a-modal
|
||||
<j-modal
|
||||
:title="props.title"
|
||||
:maskClosable="false"
|
||||
destroy-on-close
|
||||
|
@ -11,38 +11,38 @@
|
|||
cancelText="取消"
|
||||
v-bind="layout"
|
||||
>
|
||||
<a-form
|
||||
<j-form
|
||||
layout="vertical"
|
||||
ref="formRef"
|
||||
:rules="rules"
|
||||
:model="formModel"
|
||||
>
|
||||
<a-form-item label="名称" name="name">
|
||||
<a-input
|
||||
<j-form-item label="名称" name="name">
|
||||
<j-input
|
||||
v-model:value="formModel.name"
|
||||
:maxlength="64"
|
||||
placeholder="请输入名称"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="排序" name="sortIndex">
|
||||
<a-input-number
|
||||
</j-form-item>
|
||||
<j-form-item label="排序" name="sortIndex">
|
||||
<j-input-number
|
||||
style="width: 100%"
|
||||
id="inputNumber"
|
||||
v-model:value="formModel.sortIndex"
|
||||
:min="1"
|
||||
placeholder="请输入排序"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="说明">
|
||||
<a-textarea
|
||||
</j-form-item>
|
||||
<j-form-item label="说明">
|
||||
<j-textarea
|
||||
v-model:value="formModel.description"
|
||||
show-count
|
||||
:maxlength="200"
|
||||
placeholder="请输入说明"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</j-form-item>
|
||||
</j-form>
|
||||
</j-modal>
|
||||
</template>
|
||||
<script setup lang="ts" name="modifyModal">
|
||||
import { PropType } from 'vue';
|
||||
|
@ -111,20 +111,20 @@ const submitData = async () => {
|
|||
if (props.isChild === 1) {
|
||||
addParams.value = {
|
||||
...formModel.value,
|
||||
sortIndex:
|
||||
childArr.value[childArr.value.length - 1].sortIndex + 1,
|
||||
// sortIndex:
|
||||
// childArr.value[childArr.value.length - 1].sortIndex + 1,
|
||||
parentId: addObj.value.id,
|
||||
};
|
||||
} else if (props.isChild === 2) {
|
||||
addParams.value = {
|
||||
parentId: addObj.value.id,
|
||||
...formModel.value,
|
||||
sortIndex: 1,
|
||||
// sortIndex: 1,
|
||||
};
|
||||
} else if (props.isChild === 3) {
|
||||
addParams.value = {
|
||||
...formModel.value,
|
||||
sortIndex: arr.value[arr.value.length - 1].sortIndex + 1,
|
||||
// sortIndex: arr.value[arr.value.length - 1].sortIndex + 1,
|
||||
};
|
||||
}
|
||||
const res = await saveTree(addParams.value);
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
<!--产品分类 -->
|
||||
<template>
|
||||
<a-card class="product-category">
|
||||
<Search
|
||||
<page-container>
|
||||
<pro-search
|
||||
:columns="query.columns"
|
||||
target="category"
|
||||
@search="handleSearch"
|
||||
/>
|
||||
<JTable
|
||||
<JProTable
|
||||
ref="tableRef"
|
||||
:columns="table.columns"
|
||||
:dataSource="dataSource"
|
||||
|
@ -25,46 +25,38 @@
|
|||
:loading="tableLoading"
|
||||
>
|
||||
<template #headerTitle>
|
||||
<a-button type="primary" @click="add"
|
||||
><plus-outlined />新增</a-button
|
||||
<PermissionButton
|
||||
type="primary"
|
||||
@click="add"
|
||||
hasPermission="device/Category:add"
|
||||
>
|
||||
<template #icon><AIcon type="PlusOutlined" /></template>
|
||||
新增
|
||||
</PermissionButton>
|
||||
</template>
|
||||
<template #action="slotProps">
|
||||
<a-space :size="16">
|
||||
<a-tooltip
|
||||
<j-space :size="16">
|
||||
<template
|
||||
v-for="i in getActions(slotProps, 'table')"
|
||||
:key="i.key"
|
||||
v-bind="i.tooltip"
|
||||
>
|
||||
<a-popconfirm
|
||||
v-if="i.popConfirm"
|
||||
v-bind="i.popConfirm"
|
||||
<PermissionButton
|
||||
:disabled="i.disabled"
|
||||
:popConfirm="i.popConfirm"
|
||||
:hasPermission="'device/Category:' + i.key"
|
||||
:tooltip="{
|
||||
...i.tooltip,
|
||||
}"
|
||||
@click="i.onClick"
|
||||
type="link"
|
||||
style="padding: 0px"
|
||||
>
|
||||
<a-button
|
||||
:disabled="i.disabled"
|
||||
style="padding: 0"
|
||||
type="link"
|
||||
><AIcon :type="i.icon"
|
||||
/></a-button>
|
||||
</a-popconfirm>
|
||||
<a-button
|
||||
style="padding: 0"
|
||||
type="link"
|
||||
v-else
|
||||
@click="i.onClick && i.onClick(slotProps)"
|
||||
>
|
||||
<a-button
|
||||
:disabled="i.disabled"
|
||||
style="padding: 0"
|
||||
type="link"
|
||||
><AIcon :type="i.icon"
|
||||
/></a-button>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
<template #icon><AIcon :type="i.icon" /></template>
|
||||
</PermissionButton>
|
||||
</template>
|
||||
</JTable>
|
||||
</j-space>
|
||||
</template>
|
||||
</JProTable>
|
||||
<!-- 新增和编辑弹窗 -->
|
||||
<ModifyModal
|
||||
ref="modifyRef"
|
||||
|
@ -74,7 +66,7 @@
|
|||
:isChild="isChild"
|
||||
@refresh="refresh"
|
||||
/>
|
||||
</a-card>
|
||||
</page-container>
|
||||
</template>
|
||||
<script lang="ts" name="Category" setup>
|
||||
import { queryTree, deleteTree } from '@/api/device/category';
|
||||
|
@ -146,6 +138,7 @@ const getTableData = async () => {
|
|||
if (res.status === 200) {
|
||||
dataSource.value = res.result;
|
||||
}
|
||||
tableLoading.value = false;
|
||||
};
|
||||
getTableData();
|
||||
/**
|
||||
|
@ -168,7 +161,7 @@ const getActions = (
|
|||
if (!data) return [];
|
||||
const actions = [
|
||||
{
|
||||
key: 'edit',
|
||||
key: 'update',
|
||||
text: '编辑',
|
||||
tooltip: {
|
||||
title: '编辑',
|
||||
|
@ -238,8 +231,8 @@ const table = reactive({
|
|||
},
|
||||
{
|
||||
title: '说明',
|
||||
dataIndex: 'describe',
|
||||
key: 'describe',
|
||||
dataIndex: 'description',
|
||||
key: 'description',
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
|
|
|
@ -1,21 +1,21 @@
|
|||
<template>
|
||||
<div>
|
||||
<a-radio-group
|
||||
<j-radio-group
|
||||
v-if="quickBtn"
|
||||
default-value="today"
|
||||
button-style="solid"
|
||||
v-model:value="radioValue"
|
||||
@change="(e) => handleBtnChange(e.target.value)"
|
||||
>
|
||||
<a-radio-button
|
||||
<j-radio-button
|
||||
v-for="item in quickBtnList"
|
||||
:key="item.value"
|
||||
:value="item.value"
|
||||
>
|
||||
{{ item.label }}
|
||||
</a-radio-button>
|
||||
</a-radio-group>
|
||||
<a-range-picker
|
||||
</j-radio-button>
|
||||
</j-radio-group>
|
||||
<j-range-picker
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
valueFormat="YYYY-MM-DD HH:mm:ss"
|
||||
style="margin-left: 12px"
|
||||
|
@ -23,7 +23,7 @@
|
|||
v-model:value="rangeVal"
|
||||
:allowClear="false"
|
||||
>
|
||||
</a-range-picker>
|
||||
</j-range-picker>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -4,12 +4,12 @@
|
|||
<div class="content-left">
|
||||
<div class="content-left-title">
|
||||
<span>{{ title }}</span>
|
||||
<a-tooltip placement="top" v-if="tooltip">
|
||||
<j-tooltip placement="top" v-if="tooltip">
|
||||
<template #title>
|
||||
<span>{{ tooltip }}</span>
|
||||
</template>
|
||||
<AIcon type="QuestionCircleOutlined" />
|
||||
</a-tooltip>
|
||||
</j-tooltip>
|
||||
</div>
|
||||
<div class="content-left-value">{{ value }}</div>
|
||||
</div>
|
||||
|
@ -23,7 +23,7 @@
|
|||
<div class="top-card-footer">
|
||||
<template v-for="(item, index) in footer" :key="index">
|
||||
<span v-if="!item.status">{{ item.title }}</span>
|
||||
<a-badge v-else :text="item.title" :status="item.status" />
|
||||
<j-badge v-else :text="item.title" :status="item.status" />
|
||||
<div class="footer-item-value">{{ item.value }}</div>
|
||||
</template>
|
||||
</div>
|
||||
|
|
|
@ -1,24 +1,24 @@
|
|||
<template>
|
||||
<page-container>
|
||||
<div class="DashBoardBox">
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="6">
|
||||
<j-row :gutter="24">
|
||||
<j-col :span="6">
|
||||
<TopCard
|
||||
title="产品数量"
|
||||
:img="getImage('/device/device-product.png')"
|
||||
:footer="productFooter"
|
||||
:value="productTotal"
|
||||
></TopCard>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
</j-col>
|
||||
<j-col :span="6">
|
||||
<TopCard
|
||||
title="设备数量"
|
||||
:img="getImage('/device/device-number.png')"
|
||||
:footer="deviceFooter"
|
||||
:value="deviceTotal"
|
||||
></TopCard
|
||||
></a-col>
|
||||
<a-col :span="6"
|
||||
></j-col>
|
||||
<j-col :span="6"
|
||||
><TopCard
|
||||
title="当前在线"
|
||||
:footer="onlineFooter"
|
||||
|
@ -29,18 +29,18 @@
|
|||
:chartYData="barChartYData"
|
||||
></BarChart> -->
|
||||
<Charts :options="onlineOptions"></Charts> </TopCard
|
||||
></a-col>
|
||||
<a-col :span="6"
|
||||
></j-col>
|
||||
<j-col :span="6"
|
||||
><TopCard
|
||||
title="今日设备信息量"
|
||||
:footer="messageFooter"
|
||||
:value="dayMessage"
|
||||
>
|
||||
<Charts :options="TodayDevOptions"></Charts> </TopCard
|
||||
></a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="24">
|
||||
></j-col>
|
||||
</j-row>
|
||||
<j-row :gutter="24">
|
||||
<j-col :span="24">
|
||||
<div class="message-card">
|
||||
<Guide title="设备消息">
|
||||
<template #extra>
|
||||
|
@ -56,18 +56,18 @@
|
|||
<Charts :options="devMegOptions"></Charts>
|
||||
</div>
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :span="24">
|
||||
<a-col :span="24">
|
||||
</j-col>
|
||||
</j-row>
|
||||
<j-row :span="24">
|
||||
<j-col :span="24">
|
||||
<div class="device-position">
|
||||
<Guide title="设备分布"></Guide>
|
||||
<div class="device-map">
|
||||
<Amap></Amap>
|
||||
</div>
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</j-col>
|
||||
</j-row>
|
||||
</div>
|
||||
</page-container>
|
||||
</template>
|
||||
|
@ -336,7 +336,7 @@ const setDevMesChartOption = (
|
|||
grid: {
|
||||
top: '2%',
|
||||
bottom: '5%',
|
||||
left: maxY > 100000 ? '90px' : '50px',
|
||||
left: maxY > 100000 ? '90px' : '60px',
|
||||
right: '50px',
|
||||
},
|
||||
series: [
|
||||
|
|
|
@ -178,6 +178,8 @@ const handleClick = async () => {
|
|||
_emits('save');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
message.error('暂无对应属性的映射');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<a-spin :spinning="loading" v-if="_metadata">
|
||||
<a-spin :spinning="loading" v-if="_metadata.length">
|
||||
<a-card :bordered="false">
|
||||
<template #title>
|
||||
<TitleComponent data="点位映射"></TitleComponent>
|
||||
|
@ -7,7 +7,7 @@
|
|||
<template #extra>
|
||||
<a-space>
|
||||
<a-button @click="showModal">批量映射</a-button>
|
||||
<a-button type="primary" @click="onSave">保存</a-button>
|
||||
<a-button type="primary" @click="onSave">保存并应用</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
<a-form ref="formRef" :model="modelRef">
|
||||
|
@ -203,31 +203,33 @@ const getChannel = async () => {
|
|||
|
||||
const handleSearch = async () => {
|
||||
loading.value = true;
|
||||
modelRef.dataSource = _metadata;
|
||||
getChannel();
|
||||
if (_metadata && _metadata.length) {
|
||||
const resp: any = await getEdgeMap(instanceStore.current?.orgId || '', {
|
||||
deviceId: instanceStore.current.id,
|
||||
query: {},
|
||||
}).catch(() => {
|
||||
modelRef.dataSource = _metadata;
|
||||
loading.value = false;
|
||||
});
|
||||
if (resp.status === 200) {
|
||||
const array = resp.result?.[0].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;
|
||||
}
|
||||
}
|
||||
modelRef.dataSource = _metadata.value;
|
||||
console.log(modelRef.dataSource);
|
||||
// if (_metadata.value && _metadata.value.length) {
|
||||
// console.log(1234);
|
||||
// const resp: any = await getEdgeMap(instanceStore.current?.orgId || '', {
|
||||
// deviceId: instanceStore.current.id,
|
||||
// query: {},
|
||||
// }).catch(() => {
|
||||
// modelRef.dataSource = _metadata;
|
||||
// loading.value = false;
|
||||
// });
|
||||
// if (resp.status === 200) {
|
||||
// const array = resp.result?.[0].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;
|
||||
};
|
||||
|
||||
|
@ -251,17 +253,13 @@ const onPatchBind = () => {
|
|||
visible.value = false;
|
||||
_emit('close');
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
handleSearch();
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
if (instanceStore.current?.metadata) {
|
||||
_metadata.value = instanceStore.current?.metadata;
|
||||
} else {
|
||||
_metadata.value = {};
|
||||
}
|
||||
handleSearch();
|
||||
});
|
||||
const onSave = async () => {
|
||||
form.value = await validate();
|
||||
|
|
|
@ -70,38 +70,13 @@ const getProductList = async () => {
|
|||
});
|
||||
if (res.status === 200) {
|
||||
productList.value = res.result;
|
||||
}
|
||||
};
|
||||
getProductList();
|
||||
const selectChange = (e: any) => {
|
||||
if (e) {
|
||||
visible.value = true;
|
||||
}
|
||||
const item = productList.value.filter((i: any) => i.id === e)[0];
|
||||
const array = JSON.parse(item.metadata || [])?.properties?.map(
|
||||
(i: any) => ({
|
||||
metadataType: 'property',
|
||||
metadataName: `${i.name}(${i.id})`,
|
||||
metadataId: i.id,
|
||||
name: i.name,
|
||||
}),
|
||||
);
|
||||
current.value.metadata = array;
|
||||
};
|
||||
watchEffect(() => {
|
||||
if (props.childData?.id) {
|
||||
current.value.parentId = props.childData.id;
|
||||
form.name = props.childData?.name;
|
||||
form.productId = props.childData?.productId;
|
||||
if (props.childData.deriveMetadata) {
|
||||
const metadata = JSON.parse(
|
||||
props.childData?.deriveMetadata || {},
|
||||
)?.properties?.map((item: any) => ({
|
||||
metadataId: item.id,
|
||||
metadataName: `${item.name}(${item.id})`,
|
||||
metadataType: 'property',
|
||||
name: item.name,
|
||||
}));
|
||||
selectChange(form.productId);
|
||||
if (current.value.metadata) {
|
||||
const metadata = current.value.metadata;
|
||||
if (metadata && metadata.length !== 0) {
|
||||
getEdgeMap(current.value.id, {
|
||||
deviceId: props.childData.id,
|
||||
|
@ -145,7 +120,28 @@ watchEffect(() => {
|
|||
}
|
||||
}
|
||||
visible.value = true;
|
||||
} else {
|
||||
current.value.parentId = '';
|
||||
}
|
||||
}
|
||||
};
|
||||
const selectChange = (e: any) => {
|
||||
if (e) {
|
||||
visible.value = true;
|
||||
}
|
||||
const item = productList.value.filter((i: any) => i.id === e)[0];
|
||||
const array = JSON.parse(item.metadata || [])?.properties?.map(
|
||||
(i: any) => ({
|
||||
metadataType: 'property',
|
||||
metadataName: `${i.name}(${i.id})`,
|
||||
metadataId: i.id,
|
||||
name: i.name,
|
||||
}),
|
||||
);
|
||||
current.value.metadata = array;
|
||||
};
|
||||
onMounted(() => {
|
||||
getProductList();
|
||||
});
|
||||
|
||||
const validate = async () => {
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<SaveChild
|
||||
v-if="childVisible"
|
||||
@close-child-save="closeChildSave"
|
||||
:childData="current"
|
||||
:childData="_current"
|
||||
/>
|
||||
<div v-else>
|
||||
<Search
|
||||
|
@ -43,7 +43,7 @@
|
|||
"
|
||||
hasPermission="device/Instance:update"
|
||||
@click="
|
||||
current = {};
|
||||
_current = {};
|
||||
childVisible = true;
|
||||
"
|
||||
>新增并绑定</PermissionButton
|
||||
|
@ -139,7 +139,7 @@ const childDeviceRef = ref<Record<string, any>>({});
|
|||
const params = ref<Record<string, any>>({});
|
||||
const _selectedRowKeys = ref<string[]>([]);
|
||||
const visible = ref<boolean>(false);
|
||||
const current = ref({});
|
||||
const _current = ref({});
|
||||
|
||||
const columns = [
|
||||
{
|
||||
|
@ -252,7 +252,7 @@ const getActions = (data: Partial<Record<string, any>>): ActionsType[] => {
|
|||
},
|
||||
icon: 'EditOutlined',
|
||||
onClick: () => {
|
||||
current.value = data;
|
||||
_current.value = data;
|
||||
childVisible.value = true;
|
||||
},
|
||||
},
|
||||
|
|
|
@ -202,6 +202,7 @@ const handleSearch = async () => {
|
|||
metadataType: 'property',
|
||||
name: item.name,
|
||||
}));
|
||||
console.log(metadata);
|
||||
if (_metadata && _metadata.length) {
|
||||
const resp: any = await getEdgeMap(instanceStore.current?.parentId || '', {
|
||||
deviceId: instanceStore.current.id,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<j-card>
|
||||
<j-advanced-search
|
||||
<pro-search
|
||||
:columns="columns"
|
||||
target="device-instance-log"
|
||||
@search="handleSearch"
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<j-advanced-search class="search" type="simple" :columns="columns" target="device-instance-running-events" @search="handleSearch" />
|
||||
<pro-search class="search" type="simple" :columns="columns" target="device-instance-running-events" @search="handleSearch" />
|
||||
<JProTable
|
||||
ref="eventsRef"
|
||||
:columns="columns"
|
||||
|
|
|
@ -191,15 +191,17 @@ watch(
|
|||
() => route.params.id,
|
||||
(newId) => {
|
||||
if (newId) {
|
||||
instanceStore.tabActiveKey = 'Info';
|
||||
instanceStore.refresh(newId as string);
|
||||
|
||||
instanceStore.refresh(String(newId));
|
||||
getStatus(String(newId));
|
||||
}
|
||||
},
|
||||
{ immediate: true, deep: true },
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
instanceStore.tabActiveKey = history.state?.params?.tab || 'Info'
|
||||
})
|
||||
|
||||
const onBack = () => {
|
||||
menuStory.jumpPage('device/Instance');
|
||||
};
|
||||
|
@ -282,7 +284,7 @@ watchEffect(() => {
|
|||
tab: 'OPC UA',
|
||||
});
|
||||
}
|
||||
if (instanceStore.current.deviceType?.value === 'gateway') {
|
||||
if (instanceStore.current.deviceType?.value === 'gateway' && !keys.includes('ChildDevice')) {
|
||||
// 产品类型为网关的情况下才显示此模块
|
||||
list.value.push({
|
||||
key: 'ChildDevice',
|
||||
|
|
|
@ -1,12 +1,29 @@
|
|||
<template>
|
||||
<j-modal :maskClosable="false" width="800px" :visible="true" title="导入" @ok="handleSave" @cancel="handleCancel">
|
||||
<j-modal
|
||||
:maskClosable="false"
|
||||
width="800px"
|
||||
:visible="true"
|
||||
title="导入"
|
||||
@ok="handleSave"
|
||||
@cancel="handleCancel"
|
||||
>
|
||||
<div style="margin-top: 10px">
|
||||
<j-form :layout="'vertical'">
|
||||
<j-row>
|
||||
<j-col span="24">
|
||||
<j-form-item label="产品" required>
|
||||
<j-select showSearch v-model:value="modelRef.product" placeholder="请选择产品">
|
||||
<j-select-option :value="item.id" v-for="item in productList" :key="item.id" :label="item.name">{{ item.name }}</j-select-option>
|
||||
<j-select
|
||||
showSearch
|
||||
v-model:value="modelRef.product"
|
||||
placeholder="请选择产品"
|
||||
>
|
||||
<j-select-option
|
||||
:value="item.id"
|
||||
v-for="item in productList"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
>{{ item.name }}</j-select-option
|
||||
>
|
||||
</j-select>
|
||||
</j-form-item>
|
||||
</j-col>
|
||||
|
@ -17,7 +34,11 @@
|
|||
</j-col>
|
||||
<j-col span="12">
|
||||
<j-form-item label="文件上传" v-if="modelRef.product">
|
||||
<NormalUpload :product="modelRef.product" v-model="modelRef.upload" :file="modelRef.file" />
|
||||
<NormalUpload
|
||||
:product="modelRef.product"
|
||||
v-model="modelRef.upload"
|
||||
:file="modelRef.file"
|
||||
/>
|
||||
</j-form-item>
|
||||
</j-col>
|
||||
</j-row>
|
||||
|
@ -27,16 +48,17 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { queryNoPagingPost } from '@/api/device/product'
|
||||
import { queryNoPagingPost } from '@/api/device/product';
|
||||
|
||||
const emit = defineEmits(['close', 'save'])
|
||||
const emit = defineEmits(['close', 'save']);
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
default: undefined
|
||||
}
|
||||
})
|
||||
const productList = ref<Record<string, any>[]>([])
|
||||
default: undefined,
|
||||
},
|
||||
type: String,
|
||||
});
|
||||
const productList = ref<Record<string, any>[]>([]);
|
||||
|
||||
const modelRef = reactive({
|
||||
product: undefined,
|
||||
|
@ -44,26 +66,40 @@ const modelRef = reactive({
|
|||
file: {
|
||||
fileType: 'xlsx',
|
||||
autoDeploy: false,
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.data,
|
||||
() => {
|
||||
queryNoPagingPost({paging: false}).then(resp => {
|
||||
if(resp.status === 200){
|
||||
productList.value = resp.result as Record<string, any>[]
|
||||
}
|
||||
})
|
||||
queryNoPagingPost({
|
||||
paging: false,
|
||||
terms: [
|
||||
{
|
||||
column: 'state',
|
||||
value: '1',
|
||||
type: 'and'
|
||||
},
|
||||
{immediate: true, deep: true}
|
||||
)
|
||||
{
|
||||
column: 'accessProvider',
|
||||
value: props?.type
|
||||
}
|
||||
],
|
||||
sorts: [{ name: 'createTime', order: 'desc' }]
|
||||
}).then((resp) => {
|
||||
if (resp.status === 200) {
|
||||
productList.value = resp.result as Record<string, any>[];
|
||||
}
|
||||
});
|
||||
},
|
||||
{ immediate: true, deep: true },
|
||||
);
|
||||
|
||||
const handleCancel = () => {
|
||||
emit('close')
|
||||
}
|
||||
emit('close');
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
emit('save')
|
||||
}
|
||||
emit('save');
|
||||
};
|
||||
</script>
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<page-container>
|
||||
<j-advanced-search
|
||||
<pro-search
|
||||
:columns="columns"
|
||||
target="device-instance"
|
||||
@search="handleSearch"
|
||||
|
@ -315,7 +315,7 @@ const columns = [
|
|||
key: 'id',
|
||||
search: {
|
||||
type: 'string',
|
||||
defaultTermType: 'eq'
|
||||
defaultTermType: 'eq',
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -324,7 +324,7 @@ const columns = [
|
|||
key: 'name',
|
||||
search: {
|
||||
type: 'string',
|
||||
first: true
|
||||
first: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -524,6 +524,12 @@ const paramsFormat = (
|
|||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
if(history.state?.params?.type === 'add'){
|
||||
handleAdd()
|
||||
}
|
||||
})
|
||||
|
||||
const handleParams = (config: Record<string, any>) => {
|
||||
const _terms: Record<string, any> = {};
|
||||
paramsFormat(config, _terms);
|
||||
|
|
|
@ -6,18 +6,18 @@
|
|||
@click="handleClick"
|
||||
>
|
||||
<div class="card-content">
|
||||
<a-row :gutter="20">
|
||||
<a-col :span="10">
|
||||
<j-row :gutter="20">
|
||||
<j-col :span="10">
|
||||
<!-- 图片 -->
|
||||
<div class="card-item-avatar">
|
||||
<slot name="img"> </slot>
|
||||
</div>
|
||||
</a-col>
|
||||
<a-col :span="14">
|
||||
</j-col>
|
||||
<j-col :span="14">
|
||||
<!-- 内容 -->
|
||||
<slot name="content"></slot>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</j-col>
|
||||
</j-row>
|
||||
|
||||
<!-- 勾选 -->
|
||||
<div v-if="active" class="checked-icon">
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<!-- 配置信息 -->
|
||||
<template>
|
||||
<a-card style="min-height: 100%">
|
||||
<a-descriptions bordered>
|
||||
<j-card style="min-height: 100%">
|
||||
<j-descriptions bordered>
|
||||
<template #title>
|
||||
<div style="display: flex">
|
||||
<h3>配置信息</h3>
|
||||
|
@ -11,35 +11,35 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<a-descriptions-item label="ID">{{
|
||||
<j-descriptions-item label="ID">{{
|
||||
productStore.current.id
|
||||
}}</a-descriptions-item>
|
||||
<a-descriptions-item label="产品分类">{{
|
||||
}}</j-descriptions-item>
|
||||
<j-descriptions-item label="产品分类">{{
|
||||
productStore.current.classifiedName
|
||||
}}</a-descriptions-item>
|
||||
<a-descriptions-item label="设备类型">{{
|
||||
}}</j-descriptions-item>
|
||||
<j-descriptions-item label="设备类型">{{
|
||||
productStore.current.deviceType?.text
|
||||
}}</a-descriptions-item>
|
||||
}}</j-descriptions-item>
|
||||
|
||||
<a-descriptions-item label="接入方式">
|
||||
<a-button type="link" @click="changeTables">{{
|
||||
productStore.current.transportProtocol
|
||||
? productStore.current.transportProtocol
|
||||
<j-descriptions-item label="接入方式">
|
||||
<j-button type="link" @click="changeTables">{{
|
||||
productStore.current.accessName
|
||||
? productStore.current.accessName
|
||||
: '配置接入方式'
|
||||
}}</a-button>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="创建时间">{{
|
||||
}}</j-button>
|
||||
</j-descriptions-item>
|
||||
<j-descriptions-item label="创建时间">{{
|
||||
moment(productStore.current.createTime).format('YYYY-MM-DD HH:mm:ss')
|
||||
}}</a-descriptions-item>
|
||||
<a-descriptions-item label="更新时间">{{
|
||||
}}</j-descriptions-item>
|
||||
<j-descriptions-item label="更新时间">{{
|
||||
moment(productStore.current.modifyTime).format('YYYY-MM-DD HH:mm:ss')
|
||||
}}</a-descriptions-item>
|
||||
}}</j-descriptions-item>
|
||||
|
||||
<a-descriptions-item label="说明" :span="3">
|
||||
<j-descriptions-item label="说明" :span="3">
|
||||
{{ productStore.current.describe }}
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-card>
|
||||
</j-descriptions-item>
|
||||
</j-descriptions>
|
||||
</j-card>
|
||||
<!-- 编辑 -->
|
||||
<Save ref="saveRef" :isAdd="isAdd" :title="title" />
|
||||
</template>
|
||||
|
|
|
@ -1,24 +1,30 @@
|
|||
<!-- 设备接入 -->
|
||||
<template>
|
||||
<a-card style="min-height: 100%">
|
||||
<j-card style="min-height: 100%">
|
||||
<div v-if="productStore.current.accessId === undefined || null">
|
||||
<a-empty :image="simpleImage">
|
||||
<j-empty :image="simpleImage">
|
||||
<template #description>
|
||||
<span v-if="permissionStore.hasPermission('device/Product:update')">
|
||||
请先<a-button type="link" @click="showModal"
|
||||
>选择</a-button
|
||||
<span
|
||||
v-if="
|
||||
permissionStore.hasPermission(
|
||||
'device/Product:update',
|
||||
)
|
||||
"
|
||||
>
|
||||
请先<j-button type="link" @click="showModal"
|
||||
>选择</j-button
|
||||
>设备接入网关,用以提供设备接入能力
|
||||
</span>
|
||||
<span v-else>暂无权限,请联系管理员</span>
|
||||
</template>
|
||||
</a-empty>
|
||||
</j-empty>
|
||||
</div>
|
||||
<div v-else>
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="12">
|
||||
<j-row :gutter="24">
|
||||
<j-col :span="12">
|
||||
<Title data="接入方式">
|
||||
<template #extra>
|
||||
<a-tooltip
|
||||
<j-tooltip
|
||||
:title="
|
||||
productStore.current?.count &&
|
||||
productStore.current?.count > 0
|
||||
|
@ -26,7 +32,7 @@
|
|||
: ''
|
||||
"
|
||||
>
|
||||
<a-button
|
||||
<j-button
|
||||
style="margin: 0 0 0 20px"
|
||||
size="small"
|
||||
:disabled="
|
||||
|
@ -35,9 +41,9 @@
|
|||
"
|
||||
type="primary"
|
||||
@click="showDevice"
|
||||
>更换</a-button
|
||||
>更换</j-button
|
||||
>
|
||||
</a-tooltip>
|
||||
</j-tooltip>
|
||||
</template>
|
||||
</Title>
|
||||
<div>
|
||||
|
@ -71,13 +77,13 @@
|
|||
v-for="item in access?.channelInfo?.addresses"
|
||||
:key="item.address"
|
||||
>
|
||||
<a-badge
|
||||
<j-badge
|
||||
:color="
|
||||
item.health === -1 ? 'red' : 'green'
|
||||
"
|
||||
:text="item.address"
|
||||
>
|
||||
</a-badge>
|
||||
</j-badge>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>{{ '暂无连接信息' }}</div>
|
||||
|
@ -88,28 +94,29 @@
|
|||
class="config"
|
||||
>
|
||||
<template #extra>
|
||||
<a-tooltip
|
||||
<j-tooltip
|
||||
title="此配置来自于产品接入方式所选择的协议"
|
||||
>
|
||||
<AIcon
|
||||
type="QuestionCircleOutlined"
|
||||
style="margin-left: 2px"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</j-tooltip>
|
||||
</template>
|
||||
</Title>
|
||||
<a-form
|
||||
<j-form
|
||||
ref="formRef"
|
||||
:model="formData.data"
|
||||
layout="vertical"
|
||||
>
|
||||
<div v-for="item in metadata.properties" :key="item">
|
||||
<a-form-item
|
||||
<j-form-item
|
||||
:name="item.property"
|
||||
v-for="item in metadata.properties"
|
||||
:key="item"
|
||||
:label="item.name"
|
||||
:rules="[
|
||||
{
|
||||
required:
|
||||
!!item?.type?.expands?.required,
|
||||
required: !!item?.type?.expands?.required,
|
||||
message: `${
|
||||
item.type.type === 'enum'
|
||||
? '请选择'
|
||||
|
@ -118,65 +125,69 @@
|
|||
},
|
||||
]"
|
||||
>
|
||||
<a-input
|
||||
<j-input
|
||||
placeholder="请输入"
|
||||
v-if="item.type.type === 'string'"
|
||||
v-model:value="formData.data[item.name]"
|
||||
></a-input>
|
||||
<a-input-password
|
||||
v-model:value="formData.data[item.property]"
|
||||
></j-input>
|
||||
<j-input-password
|
||||
placeholder="请输入"
|
||||
v-if="item.type.type === 'password'"
|
||||
v-model:value="formData.data[item.name]"
|
||||
></a-input-password>
|
||||
<a-select
|
||||
v-model:value="formData.data[item.property]"
|
||||
></j-input-password>
|
||||
<j-select
|
||||
placeholder="请选择"
|
||||
v-if="item.type.type === 'enum'"
|
||||
v-model:value="formData.data[item.name]"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="el in item?.type?.type ===
|
||||
'enum' && item?.type?.elements
|
||||
<j-select-option
|
||||
v-for="el in item?.type?.type === 'enum' &&
|
||||
item?.type?.elements
|
||||
? item?.type?.elements
|
||||
: []"
|
||||
:key="el"
|
||||
:value="el.value"
|
||||
>
|
||||
{{ el.text }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</div>
|
||||
</a-form>
|
||||
</j-select-option>
|
||||
</j-select>
|
||||
</j-form-item>
|
||||
</j-form>
|
||||
<Title data="存储策略">
|
||||
<template #extra>
|
||||
<a-tooltip
|
||||
<j-tooltip
|
||||
title="若修改存储策略,需要手动做数据迁移,平台只能搜索最新存储策略中的数据"
|
||||
>
|
||||
<AIcon
|
||||
type="QuestionCircleOutlined"
|
||||
style="margin-left: 2px"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</j-tooltip>
|
||||
</template>
|
||||
</Title>
|
||||
<a-form layout="vertical">
|
||||
<a-form-item>
|
||||
<a-select
|
||||
<j-form layout="vertical">
|
||||
<j-form-item>
|
||||
<j-select
|
||||
ref="select"
|
||||
v-model:value="form.storePolicy"
|
||||
>
|
||||
<a-select-option
|
||||
<j-select-option
|
||||
v-for="(item, index) in storageList"
|
||||
:key="index"
|
||||
:value="item.id"
|
||||
>{{ item.name }}</a-select-option
|
||||
>{{ item.name }}</j-select-option
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<PermissionButton type="primary" @click="submitDevice" hasPermission="device/Instance:update">保存</PermissionButton>
|
||||
</a-col>
|
||||
<a-col
|
||||
</j-select>
|
||||
</j-form-item>
|
||||
</j-form>
|
||||
<PermissionButton
|
||||
type="primary"
|
||||
@click="submitDevice"
|
||||
hasPermission="device/Instance:update"
|
||||
>保存</PermissionButton
|
||||
>
|
||||
</j-col>
|
||||
<j-col
|
||||
:span="12"
|
||||
v-if="config?.routes && config?.routes?.length > 0"
|
||||
>
|
||||
|
@ -191,7 +202,7 @@
|
|||
: 'URL信息'
|
||||
}}
|
||||
</div>
|
||||
<a-table
|
||||
<j-table
|
||||
:columns="
|
||||
config.id === 'MQTT'
|
||||
? columnsMQTT
|
||||
|
@ -203,14 +214,14 @@
|
|||
>
|
||||
<template #bodyCell="{ text, column, record }">
|
||||
<template v-if="column.key === 'topic'">
|
||||
<a-tooltip
|
||||
<j-tooltip
|
||||
placement="topLeft"
|
||||
:title="text"
|
||||
>
|
||||
<div class="ellipsis-style">
|
||||
{{ text }}
|
||||
</div>
|
||||
</a-tooltip>
|
||||
</j-tooltip>
|
||||
</template>
|
||||
<template v-if="column.key === 'stream'">
|
||||
<div>{{ getStream(record) }}</div>
|
||||
|
@ -218,45 +229,45 @@
|
|||
<template
|
||||
v-if="column.key === 'description'"
|
||||
>
|
||||
<a-tooltip
|
||||
<j-tooltip
|
||||
placement="topLeft"
|
||||
:title="text"
|
||||
>
|
||||
<div class="ellipsis-style">
|
||||
{{ text }}
|
||||
</div>
|
||||
</a-tooltip>
|
||||
</j-tooltip>
|
||||
</template>
|
||||
<template v-if="column.key === 'address'">
|
||||
<a-tooltip
|
||||
<j-tooltip
|
||||
placement="topLeft"
|
||||
:title="text"
|
||||
>
|
||||
<div class="ellipsis-style">
|
||||
{{ text }}
|
||||
</div>
|
||||
</a-tooltip>
|
||||
</j-tooltip>
|
||||
</template>
|
||||
<template v-if="column.key === 'example'">
|
||||
<a-tooltip
|
||||
<j-tooltip
|
||||
placement="topLeft"
|
||||
:title="text"
|
||||
>
|
||||
<div class="ellipsis-style">
|
||||
{{ text }}
|
||||
</div>
|
||||
</a-tooltip>
|
||||
</j-tooltip>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</j-table>
|
||||
</div>
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</j-col>
|
||||
</j-row>
|
||||
</div>
|
||||
</a-card>
|
||||
</j-card>
|
||||
<!-- 选择设备 -->
|
||||
<a-modal
|
||||
<j-modal
|
||||
title="设备接入配置"
|
||||
:visible="visible"
|
||||
width="1200px"
|
||||
|
@ -265,16 +276,16 @@
|
|||
@ok="submitData"
|
||||
@cancel="cancel"
|
||||
>
|
||||
<Search
|
||||
<pro-search
|
||||
:columns="query.columns"
|
||||
target="deviceModal"
|
||||
@search="search"
|
||||
/>
|
||||
<JProTable
|
||||
:columns="columns"
|
||||
:columns="query.columns"
|
||||
:request="queryList"
|
||||
ref="tableRef"
|
||||
modal="card"
|
||||
model="CARD"
|
||||
:defaultParams="{
|
||||
...temp,
|
||||
sorts: [
|
||||
|
@ -288,8 +299,8 @@
|
|||
:gridColumns="[2]"
|
||||
>
|
||||
<template #headerTitle>
|
||||
<a-button type="primary" @click="add"
|
||||
><plus-outlined />新增</a-button
|
||||
<j-button type="primary" @click="add"
|
||||
><plus-outlined />新增</j-button
|
||||
>
|
||||
</template>
|
||||
<template #deviceType="slotProps">
|
||||
|
@ -314,22 +325,35 @@
|
|||
</slot>
|
||||
</template>
|
||||
<template #content>
|
||||
<Ellipsis style="width: calc(100% - 100px)">
|
||||
<h3 style="font-weight: 600">
|
||||
{{ slotProps.name }}
|
||||
</h3>
|
||||
<a-row>
|
||||
<a-col :span="12">
|
||||
</Ellipsis>
|
||||
<j-row>
|
||||
<j-col :span="12" v-if="slotProps.channelInfo">
|
||||
<div class="card-item-content-text">
|
||||
设备类型
|
||||
{{ slotProps.channelInfo?.name }}
|
||||
</div>
|
||||
<div>直连设备</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<div>
|
||||
{{
|
||||
slotProps.channelInfo?.addresses
|
||||
? slotProps.channelInfo
|
||||
?.addresses[0].address
|
||||
: ''
|
||||
}}
|
||||
</div>
|
||||
</j-col>
|
||||
<j-col :span="12">
|
||||
<div class="card-item-content-text">协议</div>
|
||||
<div>{{ slotProps.protocolDetail?.name }}</div>
|
||||
</j-col>
|
||||
</j-row>
|
||||
</template>
|
||||
</CardBox>
|
||||
</template>
|
||||
<template #state="slotProps">
|
||||
<a-badge
|
||||
<j-badge
|
||||
:text="slotProps.state === 1 ? '正常' : '禁用'"
|
||||
:status="statusMap.get(slotProps.state)"
|
||||
/>
|
||||
|
@ -338,13 +362,13 @@
|
|||
<a>{{ slotProps.id }}</a>
|
||||
</template>
|
||||
</JProTable>
|
||||
</a-modal>
|
||||
</j-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useProductStore } from '@/store/product';
|
||||
import { ConfigMetadata } from '@/views/device/Product/typings';
|
||||
import { Empty, message } from 'ant-design-vue';
|
||||
import { Empty, FormItem, message } from 'ant-design-vue';
|
||||
import { getImage } from '@/utils/comm';
|
||||
import Title from '../Title/index.vue';
|
||||
import { usePermissionStore } from '@/store/permission';
|
||||
|
@ -370,6 +394,9 @@ import Driver from 'driver.js';
|
|||
import 'driver.js/dist/driver.min.css';
|
||||
import { marked } from 'marked';
|
||||
import type { FormInstance, TableColumnType } from 'ant-design-vue';
|
||||
import { useMenuStore } from '@/store/menu';
|
||||
const formRef = ref();
|
||||
const menuStore = useMenuStore();
|
||||
const permissionStore = usePermissionStore();
|
||||
const render = new marked.Renderer();
|
||||
marked.setOptions({
|
||||
|
@ -396,7 +423,6 @@ const current = ref({
|
|||
name: productStore.current?.protocolName,
|
||||
},
|
||||
});
|
||||
|
||||
//存储数据
|
||||
const form = reactive<Record<string, any>>({
|
||||
storePolicy: 'default-row' || productStore.current?.storePolicy || '',
|
||||
|
@ -442,15 +468,14 @@ const query = reactive({
|
|||
},
|
||||
{
|
||||
title: '网关类型',
|
||||
key: 'accessProvider',
|
||||
dataIndex: 'accessProvider',
|
||||
key: 'provider',
|
||||
dataIndex: 'provider',
|
||||
search: {
|
||||
type: 'select',
|
||||
options: async () => {
|
||||
return new Promise((res) => {
|
||||
getProviders().then((resp: any) => {
|
||||
listData.value = [];
|
||||
// const list = () => {
|
||||
if (isNoCommunity) {
|
||||
listData.value = (resp?.result || []).map(
|
||||
(item: any) => ({
|
||||
|
@ -489,19 +514,19 @@ const query = reactive({
|
|||
options: [
|
||||
{
|
||||
label: '正常',
|
||||
value: 1,
|
||||
value: 'enabled',
|
||||
},
|
||||
{
|
||||
label: '禁用',
|
||||
value: 0,
|
||||
value: 'disabled',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '说明',
|
||||
key: 'describe',
|
||||
dataIndex: 'describe',
|
||||
key: 'description',
|
||||
dataIndex: 'description',
|
||||
search: {
|
||||
type: 'string',
|
||||
},
|
||||
|
@ -564,9 +589,9 @@ const search = (e: any) => {
|
|||
//引导页数据
|
||||
const steps = [
|
||||
{
|
||||
element: '.device-detail-metadata',
|
||||
element: '#rc-tabs-0-tab-Metadata',
|
||||
popover: {
|
||||
className: 'driver',
|
||||
id: 'driver',
|
||||
title: `<div id='title'>配置物模型</div><div id='guide'>1/3</div>`,
|
||||
description: `配置产品物模型,实现设备在云端的功能描述。`,
|
||||
position: 'bottom',
|
||||
|
@ -820,7 +845,6 @@ const getStream = (record: any) => {
|
|||
* 查询接入方式
|
||||
*/
|
||||
const queryAccessDetail = async (id: string) => {
|
||||
console.log(id, 'id');
|
||||
const res = await queryList({
|
||||
terms: [
|
||||
{
|
||||
|
@ -896,7 +920,6 @@ const getProviderList = async () => {
|
|||
* 提交设备数据
|
||||
*/
|
||||
const submitData = async () => {
|
||||
console.log(current.value, 'vvv');
|
||||
if (current.value) {
|
||||
const obj: any = {
|
||||
...productStore.current,
|
||||
|
@ -938,7 +961,6 @@ const submitData = async () => {
|
|||
? await updateDevice(obj)
|
||||
: await saveDevice(obj);
|
||||
if (resp.status === 200) {
|
||||
console.log(productStore.current?.id, 'productStore.current?.id');
|
||||
detail(productStore.current?.id || '').then((res) => {
|
||||
if (res.status === 200) {
|
||||
productStore.current = { ...res.result };
|
||||
|
@ -1015,10 +1037,12 @@ const getData = async () => {
|
|||
* 保存设备接入
|
||||
*/
|
||||
const submitDevice = async () => {
|
||||
const res = await formRef.value.validate();
|
||||
const values = { storePolicy: form.storePolicy, ...formData.data };
|
||||
const result: any = {};
|
||||
flatObj(values, result);
|
||||
const { storePolicy, ...extra } = result;
|
||||
console.log({ ...extra });
|
||||
const id = productStore.current?.id;
|
||||
const resp = await modify(id || '', {
|
||||
id: id,
|
||||
|
@ -1047,6 +1071,13 @@ const flatObj = (obj: any, result: any) => {
|
|||
});
|
||||
};
|
||||
const getDetailInfo = () => {};
|
||||
|
||||
const add = () => {
|
||||
const url = menuStore.hasMenu('link/AccessConfig/Detail');
|
||||
if (url) {
|
||||
window.open(`${origin}/#${url}`);
|
||||
}
|
||||
};
|
||||
/**
|
||||
* 初始化
|
||||
*/
|
||||
|
|
|
@ -10,60 +10,79 @@
|
|||
<div style="display: flex; align-items: center">
|
||||
<div>{{ productStore.current.name }}</div>
|
||||
<div style="margin: -5px 0 0 20px">
|
||||
<a-popconfirm
|
||||
<j-popconfirm
|
||||
title="确认禁用"
|
||||
@confirm="handleUndeploy"
|
||||
v-if="productStore.current.state === 1"
|
||||
okText="确定"
|
||||
cancelText="取消"
|
||||
>
|
||||
<a-switch
|
||||
<j-switch
|
||||
:checked="productStore.current.state === 1"
|
||||
checked-children="正常"
|
||||
un-checked-children="禁用"
|
||||
/>
|
||||
</a-popconfirm>
|
||||
<a-popconfirm
|
||||
</j-popconfirm>
|
||||
<j-popconfirm
|
||||
title="确认启用"
|
||||
@confirm="handleDeploy"
|
||||
v-if="productStore.current.state === 0"
|
||||
okText="确定"
|
||||
cancelText="取消"
|
||||
>
|
||||
<a-switch
|
||||
<j-switch
|
||||
:unCheckedValue="
|
||||
productStore.current.state === 0
|
||||
"
|
||||
checked-children="正常"
|
||||
un-checked-children="禁用"
|
||||
/>
|
||||
</a-popconfirm>
|
||||
</j-popconfirm>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="padding-top: 10px">
|
||||
<a-descriptions size="small" :column="4">
|
||||
<a-descriptions-item label="设备数量">{{
|
||||
<j-descriptions size="small" :column="4">
|
||||
<j-descriptions-item
|
||||
label="设备数量"
|
||||
style="cursor: pointer"
|
||||
><span @click="jumpDevice">{{
|
||||
productStore.current?.count
|
||||
? productStore.current?.count
|
||||
: 0
|
||||
}}</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
}}</span></j-descriptions-item
|
||||
>
|
||||
</j-descriptions>
|
||||
</div>
|
||||
</template>
|
||||
<template #extra>
|
||||
<a-popconfirm
|
||||
<!-- <j-popconfirm
|
||||
title="确认应用配置"
|
||||
@confirm="handleCofig"
|
||||
okText="确定"
|
||||
cancelText="取消"
|
||||
>
|
||||
<a-button
|
||||
<j-button
|
||||
:disabled="productStore.current.state === 0"
|
||||
type="primary"
|
||||
>应用配置</a-button
|
||||
>应用配置</j-button
|
||||
>
|
||||
</j-popconfirm> -->
|
||||
<PermissionButton
|
||||
type="primary"
|
||||
:popConfirm="{
|
||||
title: `确定应用配置?`,
|
||||
onConfirm: handleConfig,
|
||||
}"
|
||||
:disabled="productStore.current?.state === 0"
|
||||
:tooltip="
|
||||
productStore.current?.state === 0
|
||||
? { title: '请先启用产品' }
|
||||
: undefined
|
||||
"
|
||||
hasPermission="device/Product:update"
|
||||
>应用配置</PermissionButton
|
||||
>
|
||||
</a-popconfirm>
|
||||
</template>
|
||||
<component
|
||||
:is="tabs[productStore.tabActiveKey]"
|
||||
|
@ -88,6 +107,8 @@ import {
|
|||
import { message } from 'ant-design-vue';
|
||||
import { getImage } from '@/utils/comm';
|
||||
import encodeQuery from '@/utils/encodeQuery';
|
||||
import { useMenuStore } from '@/store/menu';
|
||||
const menuStory = useMenuStore();
|
||||
|
||||
const route = useRoute();
|
||||
const checked = ref<boolean>(true);
|
||||
|
@ -123,7 +144,7 @@ const tabs = {
|
|||
Info,
|
||||
Metadata,
|
||||
Device,
|
||||
DataAnalysis
|
||||
DataAnalysis,
|
||||
};
|
||||
|
||||
watch(
|
||||
|
@ -205,9 +226,27 @@ const getProtocol = async () => {
|
|||
}
|
||||
}
|
||||
};
|
||||
/**
|
||||
* 详情页跳转到设备页
|
||||
*/
|
||||
const jumpDevice = () => {
|
||||
console.log(productStore.current?.id);
|
||||
const searchParams = {
|
||||
column: 'productId',
|
||||
termType: 'eq',
|
||||
value: productStore.current?.id,
|
||||
};
|
||||
menuStory.jumpPage('device/Instance',{},{
|
||||
target: 'device-instance',
|
||||
q: JSON.stringify({ terms: [{ terms: [{searchParams}] }] }),
|
||||
});
|
||||
};
|
||||
onMounted(() => {
|
||||
getProtocol();
|
||||
})
|
||||
if(history.state?.params?.tab){
|
||||
productStore.tabActiveKey = history.state?.params?.tab
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<style scoped lang="less">
|
||||
.ant-switch-loading,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<!-- 产品保存成功后的提示框 -->
|
||||
<template>
|
||||
<a-modal
|
||||
<j-modal
|
||||
:maskClosable="false"
|
||||
destroy-on-close
|
||||
v-model:visible="visible"
|
||||
|
@ -12,7 +12,7 @@
|
|||
<span>产品创建成功</span>
|
||||
</template>
|
||||
<template #footer>
|
||||
<a-button @click="cancel">关闭</a-button>
|
||||
<j-button @click="cancel">关闭</j-button>
|
||||
</template>
|
||||
<div class="product-tips">
|
||||
<div style="display: flex">
|
||||
|
@ -43,7 +43,7 @@
|
|||
进入设备列表页面,点击批量导入设备,批量添加同一产品下的设备
|
||||
</div>
|
||||
</div>
|
||||
</a-modal>
|
||||
</j-modal>
|
||||
</template>
|
||||
<script lang="ts" setup name="DialogTips">
|
||||
import { getImage } from '@/utils/comm.ts';
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<!-- 新增、编辑产品 -->
|
||||
<template>
|
||||
<a-modal
|
||||
<j-modal
|
||||
:title="props.title"
|
||||
:maskClosable="false"
|
||||
destroy-on-close
|
||||
|
@ -14,18 +14,18 @@
|
|||
:confirmLoading="loading"
|
||||
>
|
||||
<div style="margin-top: 10px">
|
||||
<a-form
|
||||
<j-form
|
||||
:layout="'vertical'"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
ref="formRef"
|
||||
>
|
||||
<a-row type="flex">
|
||||
<a-col flex="180px">
|
||||
<a-form-item name="photoUrl">
|
||||
<j-row type="flex">
|
||||
<j-col flex="180px">
|
||||
<j-form-item name="photoUrl">
|
||||
<JUpload v-model="form.photoUrl" />
|
||||
</a-form-item>
|
||||
<!-- <a-form-item>
|
||||
</j-form-item>
|
||||
<!-- <j-form-item>
|
||||
<div class="upload-image-warp-logo">
|
||||
<div class="upload-image-border-logo">
|
||||
<a-upload
|
||||
|
@ -89,37 +89,37 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-form-item> -->
|
||||
</a-col>
|
||||
<a-col flex="auto">
|
||||
<a-form-item name="id">
|
||||
</j-form-item> -->
|
||||
</j-col>
|
||||
<j-col flex="auto">
|
||||
<j-form-item name="id">
|
||||
<template #label>
|
||||
<span>ID</span>
|
||||
<a-tooltip
|
||||
<j-tooltip
|
||||
title="若不填写,系统将自动生成唯一ID"
|
||||
>
|
||||
<span>ID</span>
|
||||
<AIcon
|
||||
type="QuestionCircleOutlined"
|
||||
style="margin-left: 2px"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</j-tooltip>
|
||||
</template>
|
||||
<a-input
|
||||
<j-input
|
||||
v-model:value="form.id"
|
||||
placeholder="请输入ID"
|
||||
:disabled="disabled"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="名称" name="name">
|
||||
<a-input
|
||||
</j-form-item>
|
||||
<j-form-item label="名称" name="name">
|
||||
<j-input
|
||||
v-model:value="form.name"
|
||||
placeholder="请输入名称"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-form-item label="产品分类" name="classifiedId">
|
||||
<a-tree-select
|
||||
</j-form-item>
|
||||
</j-col>
|
||||
</j-row>
|
||||
<j-form-item label="产品分类" name="classifiedId">
|
||||
<j-tree-select
|
||||
showSearch
|
||||
v-model:value="form.classifiedId"
|
||||
placeholder="请选择产品分类"
|
||||
|
@ -131,40 +131,40 @@
|
|||
"
|
||||
>
|
||||
<template> </template>
|
||||
</a-tree-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="设备类型" name="deviceType">
|
||||
<a-radio-group
|
||||
</j-tree-select>
|
||||
</j-form-item>
|
||||
<j-form-item label="设备类型" name="deviceType">
|
||||
<j-radio-group
|
||||
v-model:value="form.deviceType"
|
||||
style="width: 100%"
|
||||
@change="changeValue"
|
||||
>
|
||||
<a-row :span="24" :gutter="10">
|
||||
<a-col
|
||||
<j-row :span="24" :gutter="10">
|
||||
<j-col
|
||||
:span="8"
|
||||
v-for="item in deviceList"
|
||||
:key="item.value"
|
||||
>
|
||||
<div class="button-style">
|
||||
<a-radio-button
|
||||
<j-radio-button
|
||||
:value="item.value"
|
||||
style="height: 100%; width: 100%"
|
||||
:disabled="disabled"
|
||||
>
|
||||
<div class="card-content">
|
||||
<a-row :gutter="20">
|
||||
<a-col :span="10">
|
||||
<j-row :gutter="20">
|
||||
<j-col :span="10">
|
||||
<!-- 图片 -->
|
||||
<div class="img-style">
|
||||
<img :src="item.logo" />
|
||||
</div>
|
||||
</a-col>
|
||||
<a-col :span="14">
|
||||
</j-col>
|
||||
<j-col :span="14">
|
||||
<span class="card-style">
|
||||
{{ item.label }}
|
||||
</span>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</j-col>
|
||||
</j-row>
|
||||
|
||||
<!-- 勾选 -->
|
||||
<div
|
||||
|
@ -179,21 +179,21 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-radio-button>
|
||||
</j-radio-button>
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item label="说明" name="describe">
|
||||
<a-textarea
|
||||
</j-col>
|
||||
</j-row>
|
||||
</j-radio-group>
|
||||
</j-form-item>
|
||||
<j-form-item label="说明" name="describe">
|
||||
<j-textarea
|
||||
v-model:value="form.describe"
|
||||
placeholder="请输入说明"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</j-form-item>
|
||||
</j-form>
|
||||
</div>
|
||||
</a-modal>
|
||||
</j-modal>
|
||||
<DialogTips ref="dialogRef" />
|
||||
</template>
|
||||
|
||||
|
@ -275,6 +275,7 @@ const form = reactive({
|
|||
*/
|
||||
const validateInput = async (_rule: Rule, value: string) => {
|
||||
if (value) {
|
||||
console.log(value.split('').length);
|
||||
if (!isInput(value)) {
|
||||
return Promise.reject('请输入英文或者数字或者-或者_');
|
||||
} else {
|
||||
|
@ -302,8 +303,14 @@ const validateDeviceType = async (_rule: Rule, value: string) => {
|
|||
}
|
||||
};
|
||||
const rules = reactive({
|
||||
id: [{ validator: validateInput, trigger: 'blur' }],
|
||||
name: [{ required: true, message: '请输入名称', trigger: 'blur' }],
|
||||
id: [
|
||||
{ validator: validateInput, trigger: 'blur' },
|
||||
{ max: 64, message: '最多可输入64位字符', trigger: 'change' },
|
||||
],
|
||||
name: [
|
||||
{ required: true, message: '请输入名称', trigger: 'blur' },
|
||||
{ max: 64, message: '最多可输入64位字符', trigger: 'change' },
|
||||
],
|
||||
deviceType: [
|
||||
{
|
||||
required: true,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<page-container>
|
||||
<Search
|
||||
<pro-search
|
||||
:columns="query.columns"
|
||||
target="product-manage"
|
||||
@search="handleSearch"
|
||||
|
@ -24,7 +24,7 @@
|
|||
<template #icon><AIcon type="PlusOutlined" /></template>
|
||||
新增
|
||||
</PermissionButton>
|
||||
<a-upload
|
||||
<j-upload
|
||||
name="file"
|
||||
accept=".json"
|
||||
:showUploadList="false"
|
||||
|
@ -33,7 +33,7 @@
|
|||
<PermissionButton hasPermission="device/Product:import"
|
||||
>导入</PermissionButton
|
||||
>
|
||||
</a-upload>
|
||||
</j-upload>
|
||||
</j-space>
|
||||
</template>
|
||||
<template #deviceType="slotProps">
|
||||
|
@ -64,7 +64,7 @@
|
|||
</slot>
|
||||
</template>
|
||||
<template #content>
|
||||
<Ellipsis
|
||||
<Ellipsis style="width: calc(100% - 100px)"
|
||||
><span
|
||||
@click.stop="handleView(slotProps.id)"
|
||||
style="font-weight: 600; font-size: 16px"
|
||||
|
@ -72,64 +72,30 @@
|
|||
{{ slotProps.name }}
|
||||
</span></Ellipsis
|
||||
>
|
||||
<a-row>
|
||||
<a-col :span="12">
|
||||
<j-row>
|
||||
<j-col :span="12">
|
||||
<div class="card-item-content-text">
|
||||
设备类型
|
||||
</div>
|
||||
<div>{{ slotProps?.deviceType?.text }}</div>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
</j-col>
|
||||
<j-col :span="12">
|
||||
<div class="card-item-content-text">
|
||||
接入方式
|
||||
</div>
|
||||
<Ellipsis
|
||||
><div>
|
||||
{{ slotProps?.accessName }}
|
||||
{{
|
||||
slotProps?.accessName
|
||||
? slotProps?.accessName
|
||||
: '未接入'
|
||||
}}
|
||||
</div></Ellipsis
|
||||
>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</j-col>
|
||||
</j-row>
|
||||
</template>
|
||||
<template #actions="item">
|
||||
<!-- <a-tooltip
|
||||
v-bind="item.tooltip"
|
||||
:title="item.disabled && item.tooltip.title"
|
||||
>
|
||||
<a-popconfirm
|
||||
v-if="item.popConfirm"
|
||||
v-bind="item.popConfirm"
|
||||
:disabled="item.disabled"
|
||||
okText="确定"
|
||||
cancelText="取消"
|
||||
>
|
||||
<a-button :disabled="item.disabled">
|
||||
<AIcon
|
||||
type="DeleteOutlined"
|
||||
v-if="item.key === 'delete'"
|
||||
/>
|
||||
<template v-else>
|
||||
<AIcon :type="item.icon" />
|
||||
<span>{{ item?.text }}</span>
|
||||
</template>
|
||||
</a-button>
|
||||
</a-popconfirm>
|
||||
<template v-else>
|
||||
<a-button
|
||||
:disabled="item.disabled"
|
||||
@click="item.onClick"
|
||||
>
|
||||
<AIcon
|
||||
type="DeleteOutlined"
|
||||
v-if="item.key === 'delete'"
|
||||
/>
|
||||
<template v-else>
|
||||
<AIcon :type="item.icon" />
|
||||
<span>{{ item?.text }}</span>
|
||||
</template>
|
||||
</a-button>
|
||||
</template>
|
||||
</a-tooltip> -->
|
||||
<PermissionButton
|
||||
:disabled="item.disabled"
|
||||
:popConfirm="item.popConfirm"
|
||||
|
@ -152,7 +118,7 @@
|
|||
</CardBox>
|
||||
</template>
|
||||
<template #state="slotProps">
|
||||
<a-badge
|
||||
<j-badge
|
||||
:text="slotProps.state === 1 ? '正常' : '禁用'"
|
||||
:status="statusMap.get(slotProps.state)"
|
||||
/>
|
||||
|
@ -161,39 +127,7 @@
|
|||
<a>{{ slotProps.id }}</a>
|
||||
</template>
|
||||
<template #action="slotProps">
|
||||
<a-space :size="16">
|
||||
<!-- <a-tooltip
|
||||
v-for="i in getActions(slotProps)"
|
||||
:key="i.key"
|
||||
v-bind="i.tooltip"
|
||||
>
|
||||
<a-popconfirm
|
||||
v-if="i.popConfirm"
|
||||
v-bind="i.popConfirm"
|
||||
okText="确定"
|
||||
cancelText="取消"
|
||||
>
|
||||
<a-button
|
||||
:disabled="i.disabled"
|
||||
style="padding: 0"
|
||||
type="link"
|
||||
><AIcon :type="i.icon"
|
||||
/></a-button>
|
||||
</a-popconfirm>
|
||||
<a-button
|
||||
style="padding: 0"
|
||||
type="link"
|
||||
v-else
|
||||
@click="i.onClick && i.onClick(slotProps)"
|
||||
>
|
||||
<a-button
|
||||
:disabled="i.disabled"
|
||||
style="padding: 0"
|
||||
type="link"
|
||||
><AIcon :type="i.icon"
|
||||
/></a-button>
|
||||
</a-button>
|
||||
</a-tooltip> -->
|
||||
<j-space :size="16">
|
||||
<template
|
||||
v-for="i in getActions(slotProps, 'table')"
|
||||
:key="i.key"
|
||||
|
@ -212,7 +146,7 @@
|
|||
<template #icon><AIcon :type="i.icon" /></template>
|
||||
</PermissionButton>
|
||||
</template>
|
||||
</a-space>
|
||||
</j-space>
|
||||
</template>
|
||||
</JProTable>
|
||||
<!-- 新增、编辑 -->
|
||||
|
@ -249,11 +183,11 @@ import { omit } from 'lodash-es';
|
|||
import { typeOptions } from '@/components/Search/util';
|
||||
import Save from './Save/index.vue';
|
||||
import { useMenuStore } from 'store/menu';
|
||||
import { useRoute } from 'vue-router';
|
||||
/**
|
||||
* 表格数据
|
||||
*/
|
||||
const menuStory = useMenuStore();
|
||||
const router = useRouter();
|
||||
const isAdd = ref<number>(0);
|
||||
const title = ref<string>('');
|
||||
const params = ref<Record<string, any>>({});
|
||||
|
@ -673,6 +607,12 @@ const saveRef = ref();
|
|||
const handleSearch = (e: any) => {
|
||||
params.value = e;
|
||||
};
|
||||
const route = useRoute();
|
||||
onMounted(() => {
|
||||
if(history.state?.params?.save){
|
||||
add();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
|
|
@ -76,8 +76,6 @@ const save = reactive({
|
|||
const type = metadataStore.model.type
|
||||
const _detail: ProductItem | DeviceInstance = props.type === 'device' ? instanceStore.detail : productStore.current
|
||||
const _metadata = JSON.parse(_detail?.metadata || '{}')
|
||||
console.log(_metadata)
|
||||
console.log(type)
|
||||
const list = (_metadata[type] as any[]) || []
|
||||
if (formValue.id) {
|
||||
if (metadataStore.model.action === 'add' && list.some(item => item.id === formValue.id)) {
|
||||
|
|
|
@ -39,7 +39,6 @@ export const validateIdName = async (_rule: Rule, val: Record<any, any>) => {
|
|||
}
|
||||
|
||||
export const validateValueType = async (_rule: Rule, val: Record<any, any>, title = '数据类型') => {
|
||||
console.log(val)
|
||||
if (!val) return Promise.reject(new Error('请输入元素配置'));
|
||||
if (!val?.type) {
|
||||
return Promise.reject(new Error(`请选择${title}`))
|
||||
|
|
|
@ -164,7 +164,6 @@ const beforeUpload: UploadProps['beforeUpload'] = file => {
|
|||
}
|
||||
const fileChange = (info: UploadChangeParam) => {
|
||||
if (info.file.status === 'done') {
|
||||
console.log(info)
|
||||
const { response } = info.file
|
||||
if (response.status === 200) {
|
||||
formModel.upload = response.result
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
<template>
|
||||
<page-container>
|
||||
<div class="box">
|
||||
<iframe :src="url" class="box-iframe"></iframe>
|
||||
</div>
|
||||
</page-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { _control, _stopControl } from '@/api/edge/device';
|
||||
|
||||
const url = ref<string>('');
|
||||
const deviceId = ref<string>('');
|
||||
|
||||
watch(
|
||||
() => history.state?.params?.id,
|
||||
(newId) => {
|
||||
if (newId) {
|
||||
deviceId.value = newId as string;
|
||||
_control(newId).then((resp: any) => {
|
||||
if (resp.status === 200) {
|
||||
const item = `http://${resp.result?.url}/#/login?token=${resp.result.token}`;
|
||||
url.value = item;
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
onUnmounted(() => {
|
||||
if (deviceId.value) {
|
||||
_stopControl(unref(deviceId));
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.box {
|
||||
width: 100%;
|
||||
height: 85vh;
|
||||
background-color: #fff;
|
||||
}
|
||||
.box-iframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,265 @@
|
|||
<template>
|
||||
<j-modal
|
||||
:maskClosable="false"
|
||||
width="650px"
|
||||
:visible="true"
|
||||
:title="!!data?.id ? '编辑' : '新增'"
|
||||
@ok="handleSave"
|
||||
@cancel="handleCancel"
|
||||
:confirmLoading="loading"
|
||||
>
|
||||
<div style="margin-top: 10px">
|
||||
<j-form :layout="'vertical'" ref="formRef" :model="modelRef">
|
||||
<j-row type="flex">
|
||||
<j-col flex="180px">
|
||||
<j-form-item name="photoUrl">
|
||||
<JProUpload v-model="modelRef.photoUrl" />
|
||||
</j-form-item>
|
||||
</j-col>
|
||||
<j-col flex="auto">
|
||||
<j-form-item
|
||||
name="id"
|
||||
:rules="[
|
||||
{
|
||||
pattern: /^[a-zA-Z0-9_\-]+$/,
|
||||
message: '请输入英文或者数字或者-或者_',
|
||||
},
|
||||
{
|
||||
max: 64,
|
||||
message: '最多输入64个字符',
|
||||
},
|
||||
{
|
||||
validator: vailId,
|
||||
trigger: 'blur',
|
||||
},
|
||||
]"
|
||||
>
|
||||
<template #label>
|
||||
<span>
|
||||
ID
|
||||
<j-tooltip
|
||||
title="若不填写,系统将自动生成唯一ID"
|
||||
>
|
||||
<AIcon
|
||||
type="QuestionCircleOutlined"
|
||||
style="margin-left: 2px"
|
||||
/>
|
||||
</j-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<j-input
|
||||
v-model:value="modelRef.id"
|
||||
placeholder="请输入ID"
|
||||
:disabled="!!data?.id"
|
||||
/>
|
||||
</j-form-item>
|
||||
<j-form-item
|
||||
label="名称"
|
||||
name="name"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
message: '请输入名称',
|
||||
},
|
||||
{
|
||||
max: 64,
|
||||
message: '最多输入64个字符',
|
||||
},
|
||||
]"
|
||||
>
|
||||
<j-input
|
||||
v-model:value="modelRef.name"
|
||||
placeholder="请输入名称"
|
||||
/>
|
||||
</j-form-item>
|
||||
</j-col>
|
||||
</j-row>
|
||||
<j-row>
|
||||
<j-col :span="22">
|
||||
<j-form-item
|
||||
name="productId"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
message: '请选择所属产品',
|
||||
},
|
||||
]"
|
||||
>
|
||||
<template #label>
|
||||
<span
|
||||
>所属产品
|
||||
<j-tooltip title="只能选择“正常”状态的产品">
|
||||
<AIcon
|
||||
type="QuestionCircleOutlined"
|
||||
style="margin-left: 2px"
|
||||
/>
|
||||
</j-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<j-select
|
||||
showSearch
|
||||
v-model:value="modelRef.productId"
|
||||
:disabled="!!data?.id"
|
||||
placeholder="请选择所属产品"
|
||||
>
|
||||
<j-select-option
|
||||
:value="item.id"
|
||||
v-for="item in productList"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
>{{ item.name }}</j-select-option
|
||||
>
|
||||
</j-select>
|
||||
</j-form-item>
|
||||
</j-col>
|
||||
<j-col :span="2" style="margin-top: 30px">
|
||||
<PermissionButton
|
||||
type="link"
|
||||
:disabled="data.id"
|
||||
@click="visible = true"
|
||||
hasPermission="device/Product:add"
|
||||
>
|
||||
<AIcon type="PlusOutlined" />
|
||||
</PermissionButton>
|
||||
</j-col>
|
||||
</j-row>
|
||||
<j-form-item
|
||||
label="说明"
|
||||
name="describe"
|
||||
:rules="[
|
||||
{
|
||||
max: 200,
|
||||
message: '最多输入200个字符',
|
||||
},
|
||||
]"
|
||||
>
|
||||
<j-textarea
|
||||
v-model:value="modelRef.describe"
|
||||
placeholder="请输入说明"
|
||||
showCount
|
||||
:maxlength="200"
|
||||
/>
|
||||
</j-form-item>
|
||||
</j-form>
|
||||
</div>
|
||||
</j-modal>
|
||||
<SaveProduct
|
||||
v-model:visible="visible"
|
||||
v-model:productId="modelRef.productId"
|
||||
:channel="'official-edge-gateway'"
|
||||
@close="onClose"
|
||||
:deviceType="'gateway'"
|
||||
@save="onSave"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { queryNoPagingPost } from '@/api/device/product';
|
||||
import { isExists, update } from '@/api/device/instance';
|
||||
import { getImage } from '@/utils/comm';
|
||||
import { message } from 'jetlinks-ui-components';
|
||||
import SaveProduct from '@/views/media/Device/Save/SaveProduct.vue';
|
||||
|
||||
const emit = defineEmits(['close', 'save']);
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
default: undefined,
|
||||
},
|
||||
});
|
||||
const productList = ref<Record<string, any>[]>([]);
|
||||
const loading = ref<boolean>(false);
|
||||
const visible = ref<boolean>(false);
|
||||
|
||||
const formRef = ref();
|
||||
|
||||
const modelRef = reactive({
|
||||
productId: undefined,
|
||||
id: undefined,
|
||||
name: '',
|
||||
describe: '',
|
||||
photoUrl: getImage('/device/instance/device-card.png'),
|
||||
});
|
||||
|
||||
const vailId = async (_: Record<string, any>, value: string) => {
|
||||
if (!props?.data?.id && value) {
|
||||
const resp = await isExists(value);
|
||||
if (resp.status === 200 && resp.result) {
|
||||
return Promise.reject('ID重复');
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
}
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
}
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.data,
|
||||
(newValue) => {
|
||||
queryNoPagingPost({
|
||||
paging: false,
|
||||
sorts: [{ name: 'createTime', order: 'desc' }],
|
||||
terms: [
|
||||
{
|
||||
terms: [
|
||||
{
|
||||
termType: 'eq',
|
||||
column: 'state',
|
||||
value: 1,
|
||||
type: 'and',
|
||||
},
|
||||
{
|
||||
termType: 'eq',
|
||||
column: 'accessProvider',
|
||||
value: 'official-edge-gateway',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}).then((resp) => {
|
||||
if (resp.status === 200) {
|
||||
productList.value = resp.result as Record<string, any>[];
|
||||
}
|
||||
});
|
||||
Object.assign(modelRef, newValue);
|
||||
},
|
||||
{ immediate: true, deep: true },
|
||||
);
|
||||
|
||||
const handleCancel = () => {
|
||||
emit('close');
|
||||
formRef.value.resetFields();
|
||||
};
|
||||
|
||||
const onClose = () => {
|
||||
visible.value = false;
|
||||
};
|
||||
|
||||
const onSave = (_data: any) => {
|
||||
productList.value.push(_data)
|
||||
}
|
||||
|
||||
const handleSave = () => {
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(async (_data: any) => {
|
||||
loading.value = true;
|
||||
const obj = { ..._data };
|
||||
if (!obj.id) {
|
||||
delete obj.id;
|
||||
}
|
||||
const resp = await update(obj).finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
if (resp.status === 200) {
|
||||
message.success('操作成功!');
|
||||
emit('save');
|
||||
formRef.value.resetFields();
|
||||
}
|
||||
})
|
||||
.catch((err: any) => {
|
||||
console.log('error', err);
|
||||
});
|
||||
};
|
||||
</script>
|
|
@ -0,0 +1,438 @@
|
|||
<template>
|
||||
<page-container>
|
||||
<pro-search
|
||||
:columns="columns"
|
||||
target="edge-device"
|
||||
@search="handleSearch"
|
||||
/>
|
||||
<JProTable
|
||||
ref="edgeDeviceRef"
|
||||
:columns="columns"
|
||||
:request="query"
|
||||
:defaultParams="defaultParams"
|
||||
:params="params"
|
||||
:gridColumn="3"
|
||||
>
|
||||
<template #headerTitle>
|
||||
<j-space>
|
||||
<PermissionButton
|
||||
type="primary"
|
||||
@click="handleAdd"
|
||||
hasPermission="edge/Device:add"
|
||||
>
|
||||
<template #icon><AIcon type="PlusOutlined" /></template>
|
||||
新增
|
||||
</PermissionButton>
|
||||
<PermissionButton
|
||||
@click="importVisible = true"
|
||||
hasPermission="edge/Device:import"
|
||||
>
|
||||
<template #icon
|
||||
><AIcon type="ImportOutlined"
|
||||
/></template>
|
||||
导入
|
||||
</PermissionButton>
|
||||
</j-space>
|
||||
</template>
|
||||
<template #card="slotProps">
|
||||
<CardBox
|
||||
:value="slotProps"
|
||||
:actions="getActions(slotProps, 'card')"
|
||||
:status="slotProps.state?.value"
|
||||
:statusText="slotProps.state?.text"
|
||||
:statusNames="{
|
||||
online: 'success',
|
||||
offline: 'error',
|
||||
notActive: 'warning',
|
||||
}"
|
||||
>
|
||||
<template #img>
|
||||
<img
|
||||
:src="getImage('/device/instance/device-card.png')"
|
||||
/>
|
||||
</template>
|
||||
<template #content>
|
||||
<Ellipsis style="width: calc(100% - 100px)">
|
||||
<span
|
||||
style="font-size: 16px; font-weight: 600"
|
||||
@click.stop="handleView(slotProps.id)"
|
||||
>
|
||||
{{ slotProps.name }}
|
||||
</span>
|
||||
</Ellipsis>
|
||||
<j-row style="margin-top: 20px">
|
||||
<j-col :span="12">
|
||||
<div class="card-item-content-text">
|
||||
设备类型
|
||||
</div>
|
||||
<div>{{ slotProps.deviceType?.text }}</div>
|
||||
</j-col>
|
||||
<j-col :span="12">
|
||||
<div class="card-item-content-text">
|
||||
产品名称
|
||||
</div>
|
||||
<Ellipsis style="width: 100%">
|
||||
{{ slotProps.productName }}
|
||||
</Ellipsis>
|
||||
</j-col>
|
||||
</j-row>
|
||||
</template>
|
||||
<template #actions="item">
|
||||
<PermissionButton
|
||||
:disabled="item.disabled"
|
||||
:popConfirm="item.popConfirm"
|
||||
:tooltip="{
|
||||
...item.tooltip,
|
||||
}"
|
||||
@click="item.onClick"
|
||||
:hasPermission="'edge/Device:' + item.key"
|
||||
>
|
||||
<AIcon
|
||||
type="DeleteOutlined"
|
||||
v-if="item.key === 'delete'"
|
||||
/>
|
||||
<template v-else>
|
||||
<AIcon :type="item.icon" />
|
||||
<span>{{ item?.text }}</span>
|
||||
</template>
|
||||
</PermissionButton>
|
||||
</template>
|
||||
</CardBox>
|
||||
</template>
|
||||
<template #state="slotProps">
|
||||
<j-badge
|
||||
:text="slotProps.state?.text"
|
||||
:status="statusMap.get(slotProps.state?.value)"
|
||||
/>
|
||||
</template>
|
||||
<template #createTime="slotProps">
|
||||
<span>{{
|
||||
dayjs(slotProps.createTime).format('YYYY-MM-DD HH:mm:ss')
|
||||
}}</span>
|
||||
</template>
|
||||
<template #action="slotProps">
|
||||
<j-space>
|
||||
<template
|
||||
v-for="i in getActions(slotProps, 'table')"
|
||||
:key="i.key"
|
||||
>
|
||||
<PermissionButton
|
||||
:disabled="i.disabled"
|
||||
:popConfirm="i.popConfirm"
|
||||
:tooltip="{
|
||||
...i.tooltip,
|
||||
}"
|
||||
@click="i.onClick"
|
||||
type="link"
|
||||
style="padding: 0 5px"
|
||||
:hasPermission="'edge/Device:' + i.key"
|
||||
>
|
||||
<template #icon><AIcon :type="i.icon" /></template>
|
||||
</PermissionButton>
|
||||
</template>
|
||||
</j-space>
|
||||
</template>
|
||||
</JProTable>
|
||||
<Save
|
||||
v-if="visible"
|
||||
:data="current"
|
||||
@close="visible = false"
|
||||
@save="saveBtn"
|
||||
/>
|
||||
<Import @save="onRefresh" @close="importVisible = false" v-if="importVisible" type="official-edge-gateway" />
|
||||
</page-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { queryNoPagingPost } from '@/api/device/product';
|
||||
import { queryTree } from '@/api/device/category';
|
||||
import { message } from 'jetlinks-ui-components';
|
||||
import { ActionsType } from '@/views/device/Instance/typings';
|
||||
import { useMenuStore } from '@/store/menu';
|
||||
import { getImage } from '@/utils/comm';
|
||||
import dayjs from 'dayjs';
|
||||
import { query, _delete, _deploy, _undeploy } from '@/api/device/instance';
|
||||
import { restPassword } from '@/api/edge/device';
|
||||
import Save from './Save/index.vue';
|
||||
import Import from '@/views/device/Instance/Import/index.vue';
|
||||
|
||||
const menuStory = useMenuStore();
|
||||
|
||||
const defaultParams = {
|
||||
sorts: [{ name: 'createTime', order: 'desc' }],
|
||||
terms: [
|
||||
{
|
||||
terms: [
|
||||
{
|
||||
column: 'productId$product-info',
|
||||
value: 'accessProvider is official-edge-gateway',
|
||||
},
|
||||
],
|
||||
type: 'and',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const statusMap = new Map();
|
||||
statusMap.set('online', 'success');
|
||||
statusMap.set('offline', 'error');
|
||||
statusMap.set('notActive', 'warning');
|
||||
|
||||
const params = ref<Record<string, any>>({});
|
||||
const edgeDeviceRef = ref<Record<string, any>>({});
|
||||
const importVisible = ref<boolean>(false);
|
||||
const visible = ref<boolean>(false);
|
||||
const current = ref<Record<string, any>>({});
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: 'ID',
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
search: {
|
||||
type: 'string',
|
||||
defaultTermType: 'eq',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '设备名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
search: {
|
||||
type: 'string',
|
||||
first: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '产品名称',
|
||||
dataIndex: 'productName',
|
||||
key: 'productName',
|
||||
search: {
|
||||
type: 'select',
|
||||
options: () =>
|
||||
new Promise((resolve) => {
|
||||
queryNoPagingPost({ paging: false }).then((resp: any) => {
|
||||
resolve(
|
||||
resp.result.map((item: any) => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
})),
|
||||
);
|
||||
});
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '注册时间',
|
||||
dataIndex: 'registryTime',
|
||||
key: 'registryTime',
|
||||
scopedSlots: true,
|
||||
search: {
|
||||
type: 'date',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'state',
|
||||
key: 'state',
|
||||
scopedSlots: true,
|
||||
search: {
|
||||
type: 'select',
|
||||
options: [
|
||||
{ label: '禁用', value: 'notActive' },
|
||||
{ label: '离线', value: 'offline' },
|
||||
{ label: '在线', value: 'online' },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'classifiedId',
|
||||
dataIndex: 'classifiedId',
|
||||
title: '产品分类',
|
||||
hideInTable: true,
|
||||
search: {
|
||||
type: 'treeSelect',
|
||||
options: () =>
|
||||
new Promise((resolve) => {
|
||||
queryTree({ paging: false }).then((resp: any) => {
|
||||
resolve(resp.result);
|
||||
});
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
dataIndex: 'deviceType',
|
||||
title: '设备类型',
|
||||
valueType: 'select',
|
||||
hideInTable: true,
|
||||
search: {
|
||||
type: 'select',
|
||||
options: [
|
||||
{ label: '直连设备', value: 'device' },
|
||||
{ label: '网关子设备', value: 'childrenDevice' },
|
||||
{ label: '网关设备', value: 'gateway' },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '说明',
|
||||
dataIndex: 'describe',
|
||||
key: 'describe',
|
||||
search: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
fixed: 'right',
|
||||
width: 250,
|
||||
scopedSlots: true,
|
||||
},
|
||||
];
|
||||
|
||||
const getActions = (
|
||||
data: Partial<Record<string, any>>,
|
||||
type: 'card' | 'table',
|
||||
): ActionsType[] => {
|
||||
if (!data) return [];
|
||||
const actions = [
|
||||
{
|
||||
key: 'view',
|
||||
text: '查看',
|
||||
tooltip: {
|
||||
title: '查看',
|
||||
},
|
||||
icon: 'EyeOutlined',
|
||||
onClick: () => {
|
||||
handleView(data.id);
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'update',
|
||||
text: '编辑',
|
||||
tooltip: {
|
||||
title: '编辑',
|
||||
},
|
||||
icon: 'EditOutlined',
|
||||
onClick: () => {
|
||||
visible.value = true;
|
||||
current.value = data;
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'setting',
|
||||
text: '远程控制',
|
||||
tooltip: {
|
||||
title: '远程控制',
|
||||
},
|
||||
icon: 'ControlOutlined',
|
||||
onClick: () => {
|
||||
menuStory.jumpPage('edge/Device/Remote', { id: data.id });
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'password',
|
||||
text: '重置密码',
|
||||
tooltip: {
|
||||
title: '重置密码',
|
||||
},
|
||||
icon: 'RedoOutlined',
|
||||
popConfirm: {
|
||||
title: '确认重置密码为P@ssw0rd?',
|
||||
onConfirm: async () => {
|
||||
restPassword(data.id).then((resp: any) => {
|
||||
if (resp.status === 200) {
|
||||
message.success('操作成功!');
|
||||
edgeDeviceRef.value?.reload();
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'action',
|
||||
text: data.state?.value !== 'notActive' ? '禁用' : '启用',
|
||||
tooltip: {
|
||||
title: data.state?.value !== 'notActive' ? '禁用' : '启用',
|
||||
},
|
||||
icon:
|
||||
data.state.value !== 'notActive'
|
||||
? 'StopOutlined'
|
||||
: 'CheckCircleOutlined',
|
||||
popConfirm: {
|
||||
title: `确认${
|
||||
data.state.value !== 'notActive' ? '禁用' : '启用'
|
||||
}?`,
|
||||
onConfirm: async () => {
|
||||
let response = undefined;
|
||||
if (data.state.value !== 'notActive') {
|
||||
response = await _undeploy(data.id);
|
||||
} else {
|
||||
response = await _deploy(data.id);
|
||||
}
|
||||
if (response && response.status === 200) {
|
||||
message.success('操作成功!');
|
||||
edgeDeviceRef.value?.reload();
|
||||
} else {
|
||||
message.error('操作失败!');
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'delete',
|
||||
text: '删除',
|
||||
disabled: data.state?.value !== 'notActive',
|
||||
tooltip: {
|
||||
title:
|
||||
data.state.value !== 'notActive'
|
||||
? '已启用的设备不能删除'
|
||||
: '删除',
|
||||
},
|
||||
popConfirm: {
|
||||
title: '确认删除?',
|
||||
onConfirm: async () => {
|
||||
const resp = await _delete(data.id);
|
||||
if (resp.status === 200) {
|
||||
message.success('操作成功!');
|
||||
edgeDeviceRef.value?.reload();
|
||||
} else {
|
||||
message.error('操作失败!');
|
||||
}
|
||||
},
|
||||
},
|
||||
icon: 'DeleteOutlined',
|
||||
},
|
||||
];
|
||||
if (type === 'card')
|
||||
return actions.filter((i: ActionsType) => i.key !== 'view');
|
||||
return actions;
|
||||
};
|
||||
|
||||
const handleSearch = (_params: any) => {
|
||||
params.value = _params;
|
||||
};
|
||||
|
||||
const handleView = (id: string) => {
|
||||
menuStory.jumpPage('device/Instance/Detail', { id });
|
||||
};
|
||||
|
||||
const handleAdd = () => {
|
||||
visible.value = true;
|
||||
current.value = {};
|
||||
};
|
||||
|
||||
const saveBtn = () => {
|
||||
visible.value = false;
|
||||
edgeDeviceRef.value?.reload();
|
||||
};
|
||||
|
||||
const onRefresh = () => {
|
||||
importVisible.value = false
|
||||
edgeDeviceRef.value?.reload();
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
</style>
|
|
@ -0,0 +1,132 @@
|
|||
<template>
|
||||
<j-modal
|
||||
visible
|
||||
title="下发结果"
|
||||
:width="900"
|
||||
@ok="emit('close')"
|
||||
@cancel="emit('close')"
|
||||
>
|
||||
<j-row>
|
||||
<j-col :span="8">
|
||||
<div>成功:{{ count }}</div>
|
||||
<div>
|
||||
失败:{{ countErr }}
|
||||
<j-button @click="_download(errMessage || '', '下发失败原因')" v-if="errMessage.length" type="link"
|
||||
>下载</j-button
|
||||
>
|
||||
</div>
|
||||
</j-col>
|
||||
<j-col :span="8">下发设备数量:{{ list.length || 0 }}</j-col>
|
||||
<j-col :span="8">已下发数量:{{ countErr + count }}</j-col>
|
||||
</j-row>
|
||||
<div v-if="!flag">
|
||||
<j-textarea :rows="20" :value="JSON.stringify(errMessage)" />
|
||||
</div>
|
||||
</j-modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { LocalStore } from '@/utils/comm';
|
||||
import { BASE_API_PATH, TOKEN_KEY } from '@/utils/variable';
|
||||
import dayjs from 'dayjs';
|
||||
import { EventSourcePolyfill } from 'event-source-polyfill';
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
list: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
});
|
||||
const emit = defineEmits(['close']);
|
||||
|
||||
const count = ref<number>(0);
|
||||
const countErr = ref<number>(0);
|
||||
const flag = ref<boolean>(true);
|
||||
const errMessage = ref<any[]>([]);
|
||||
|
||||
const getData = () => {
|
||||
let dt = 0;
|
||||
let et = 0;
|
||||
const errMessages: any[] = [];
|
||||
const _terms = {
|
||||
deviceId: (props.list || []).map((item: any) => item?.id),
|
||||
params: JSON.stringify({
|
||||
name: props.data.name,
|
||||
targetId: props.data.targetId,
|
||||
targetType: props.data.targetType,
|
||||
category: props.data.category,
|
||||
metadata: props.data?.metadata,
|
||||
}),
|
||||
};
|
||||
const url = new URLSearchParams();
|
||||
Object.keys(_terms).forEach((key) => {
|
||||
if (Array.isArray(_terms[key]) && _terms[key].length) {
|
||||
_terms[key].map((item: string) => {
|
||||
url.append(key, item);
|
||||
});
|
||||
} else {
|
||||
url.append(key, _terms[key]);
|
||||
}
|
||||
});
|
||||
const source = new EventSourcePolyfill(
|
||||
`${BASE_API_PATH}/edge/operations/entity-template-save/invoke/_batch?:X_Access_Token=${LocalStore.get(
|
||||
TOKEN_KEY,
|
||||
)}&${url}`,
|
||||
);
|
||||
source.onmessage = (e: any) => {
|
||||
const res = JSON.parse(e.data);
|
||||
if (res.successful) {
|
||||
dt += 1;
|
||||
count.value = dt;
|
||||
} else {
|
||||
et += 1;
|
||||
countErr.value = et;
|
||||
flag.value = false;
|
||||
if (errMessages.length <= 5) {
|
||||
errMessages.push({ ...res });
|
||||
errMessage.value = [...errMessages];
|
||||
}
|
||||
}
|
||||
};
|
||||
source.onerror = () => {
|
||||
source.close();
|
||||
};
|
||||
source.onopen = () => {};
|
||||
};
|
||||
|
||||
const _download = (record: Record<string, any>, fileName: string, format?: string) => {
|
||||
// 创建隐藏的可下载链接
|
||||
const ghostLink = document.createElement('a');
|
||||
ghostLink.download = `${fileName ? '' : record?.name}${fileName}_${dayjs(new Date()).format(
|
||||
format || 'YYYY_MM_DD',
|
||||
)}.txt`;
|
||||
ghostLink.style.display = 'none';
|
||||
//字符串内容转成Blob地址
|
||||
const blob = new Blob([JSON.stringify(record)]);
|
||||
ghostLink.href = URL.createObjectURL(blob);
|
||||
//触发点击
|
||||
document.body.appendChild(ghostLink);
|
||||
ghostLink.click();
|
||||
//移除
|
||||
document.body.removeChild(ghostLink);
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.data.id,
|
||||
(newId) => {
|
||||
if(newId){
|
||||
getData()
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
},
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
</style>
|
|
@ -0,0 +1,181 @@
|
|||
<template>
|
||||
<j-modal
|
||||
visible
|
||||
title="下发设备"
|
||||
:width="1000"
|
||||
@ok="onSave"
|
||||
@cancel="onCancel"
|
||||
>
|
||||
<div class="alert">
|
||||
<AIcon
|
||||
type="InfoCircleOutlined"
|
||||
style="margin-right: 10px"
|
||||
/>离线设备无法进行设备模板下发
|
||||
</div>
|
||||
<pro-search
|
||||
:columns="columns"
|
||||
target="edge-resource-issue"
|
||||
@search="handleSearch"
|
||||
type="simple"
|
||||
class="search"
|
||||
/>
|
||||
<JProTable
|
||||
ref="edgeResourceIssueRef"
|
||||
:columns="columns"
|
||||
:request="queryDeviceList"
|
||||
:defaultParams="defaultParams"
|
||||
:params="params"
|
||||
model="TABLE"
|
||||
:bodyStyle="{ padding: 0 }"
|
||||
:rowSelection="{
|
||||
selectedRowKeys: _selectedRowKeys,
|
||||
onChange: onSelectChange,
|
||||
}"
|
||||
>
|
||||
<template #state="slotProps">
|
||||
<j-badge
|
||||
:text="slotProps.state?.text"
|
||||
:status="statusMap.get(slotProps.state?.value)"
|
||||
/>
|
||||
</template>
|
||||
<template #sourceId="slotProps">
|
||||
{{ slotProps.sourceName }}
|
||||
</template>
|
||||
<template #registerTime="slotProps">
|
||||
<span>{{
|
||||
dayjs(slotProps.registerTime).format('YYYY-MM-DD HH:mm:ss')
|
||||
}}</span>
|
||||
</template>
|
||||
</JProTable>
|
||||
<Result v-if="visible" :data="props.data" :list="_data" @close="onCancel" />
|
||||
</j-modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onlyMessage } from '@/utils/comm';
|
||||
import { queryDeviceList } from '@/api/edge/resource';
|
||||
import dayjs from 'dayjs';
|
||||
import Result from './Result.vue';
|
||||
|
||||
const defaultParams = {
|
||||
sorts: [{ name: 'createTime', order: 'desc' }],
|
||||
terms: [
|
||||
{
|
||||
terms: [
|
||||
{
|
||||
termType: 'eq',
|
||||
column: 'productId$product-info',
|
||||
value: 'accessProvider is official-edge-gateway',
|
||||
},
|
||||
],
|
||||
type: 'and',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
});
|
||||
const emit = defineEmits(['close']);
|
||||
|
||||
const params = ref({});
|
||||
const edgeResourceIssueRef = ref();
|
||||
const _selectedRowKeys = ref<string[]>([]);
|
||||
const _data = ref<any[]>([]);
|
||||
const visible = ref<boolean>(false);
|
||||
|
||||
const statusMap = new Map();
|
||||
statusMap.set('online', 'success');
|
||||
statusMap.set('offline', 'error');
|
||||
statusMap.set('notActive', 'warning');
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: 'ID',
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
ellipsis: true,
|
||||
width: 200,
|
||||
fixed: 'left',
|
||||
search: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '产品名称',
|
||||
dataIndex: 'productName',
|
||||
key: 'productName',
|
||||
ellipsis: true,
|
||||
search: {
|
||||
type: 'select',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '设备名称',
|
||||
ellipsis: true,
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
},
|
||||
{
|
||||
title: '注册时间',
|
||||
dataIndex: 'registerTime',
|
||||
key: 'registerTime',
|
||||
width: 200,
|
||||
scopedSlots: true,
|
||||
search: {
|
||||
type: 'date',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'state',
|
||||
key: 'state',
|
||||
scopedSlots: true,
|
||||
search: {
|
||||
type: 'select',
|
||||
options: [
|
||||
{ label: '禁用', value: 'notActive' },
|
||||
{ label: '离线', value: 'offline' },
|
||||
{ label: '在线', value: 'online' },
|
||||
],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const onSelectChange = (keys: string[], _options: any[]) => {
|
||||
_selectedRowKeys.value = [...keys];
|
||||
_data.value = _options;
|
||||
};
|
||||
|
||||
const handleSearch = (v: any) => {
|
||||
params.value = v;
|
||||
};
|
||||
|
||||
const onSave = () => {
|
||||
if(_data.value.length){
|
||||
visible.value = true
|
||||
} else {
|
||||
onlyMessage('请选择设备', 'error')
|
||||
}
|
||||
};
|
||||
|
||||
const onCancel = () => {
|
||||
emit('close');
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.search {
|
||||
padding: 0 0 0 24px;
|
||||
}
|
||||
.alert {
|
||||
height: 40px;
|
||||
padding-left: 10px;
|
||||
color: rgba(0, 0, 0, 0.55);
|
||||
line-height: 40px;
|
||||
background-color: #f6f6f6;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,45 @@
|
|||
<template>
|
||||
<j-modal visible title="编辑" :width="700" @ok="onSave" @cancel="onCancel">
|
||||
<MonacoEditor
|
||||
style="width: 100%; height: 370px"
|
||||
theme="vs"
|
||||
v-model="monacoValue"
|
||||
language="json"
|
||||
/>
|
||||
</j-modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import MonacoEditor from '@/components/MonacoEditor/index.vue';
|
||||
import { modify } from '@/api/edge/resource';
|
||||
import { onlyMessage } from '@/utils/comm';
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
});
|
||||
const emit = defineEmits(['close', 'save']);
|
||||
|
||||
const monacoValue = ref<string>('{}');
|
||||
|
||||
watchEffect(() => {
|
||||
monacoValue.value = props.data?.metadata || '{}';
|
||||
});
|
||||
|
||||
const onSave = async () => {
|
||||
const resp = await modify(props.data.id, { metadata: unref(monacoValue) });
|
||||
if (resp.status === 200) {
|
||||
emit('save');
|
||||
onlyMessage('操作成功', 'success');
|
||||
}
|
||||
};
|
||||
|
||||
const onCancel = () => {
|
||||
emit('close');
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
</style>
|
|
@ -0,0 +1,383 @@
|
|||
<template>
|
||||
<page-container>
|
||||
<pro-search
|
||||
:columns="columns"
|
||||
target="edge-resource"
|
||||
@search="handleSearch"
|
||||
/>
|
||||
<JProTable
|
||||
ref="edgeResourceRef"
|
||||
:columns="columns"
|
||||
:request="query"
|
||||
:defaultParams="defaultParams"
|
||||
:params="params"
|
||||
>
|
||||
<template #card="slotProps">
|
||||
<CardBox
|
||||
:value="slotProps"
|
||||
:actions="getActions(slotProps, 'card')"
|
||||
:status="slotProps.state?.value"
|
||||
:statusText="slotProps.state?.text"
|
||||
:statusNames="{
|
||||
enabled: 'success',
|
||||
disabled: 'error',
|
||||
}"
|
||||
>
|
||||
<template #img>
|
||||
<img
|
||||
:src="getImage('/device/instance/device-card.png')"
|
||||
/>
|
||||
</template>
|
||||
<template #content>
|
||||
<Ellipsis style="width: calc(100% - 100px)">
|
||||
<span
|
||||
style="font-size: 16px; font-weight: 600"
|
||||
@click.stop="handleView(slotProps.id)"
|
||||
>
|
||||
{{ slotProps.name }}
|
||||
</span>
|
||||
</Ellipsis>
|
||||
<j-row style="margin-top: 20px">
|
||||
<j-col :span="12">
|
||||
<div class="card-item-content-text">
|
||||
通讯协议
|
||||
</div>
|
||||
<Ellipsis>{{
|
||||
options.find(
|
||||
(i) => i.value === slotProps.category,
|
||||
)?.label || slotProps.category
|
||||
}}</Ellipsis>
|
||||
</j-col>
|
||||
<j-col :span="12">
|
||||
<div class="card-item-content-text">
|
||||
所属边缘网关
|
||||
</div>
|
||||
<Ellipsis style="width: 100%">
|
||||
{{ slotProps.sourceName }}
|
||||
</Ellipsis>
|
||||
</j-col>
|
||||
</j-row>
|
||||
</template>
|
||||
<template #actions="item">
|
||||
<PermissionButton
|
||||
:disabled="item.disabled"
|
||||
:popConfirm="item.popConfirm"
|
||||
:tooltip="{
|
||||
...item.tooltip,
|
||||
}"
|
||||
@click="item.onClick"
|
||||
:hasPermission="'edge/Resource:' + item.key"
|
||||
>
|
||||
<AIcon
|
||||
type="DeleteOutlined"
|
||||
v-if="item.key === 'delete'"
|
||||
/>
|
||||
<template v-else>
|
||||
<AIcon :type="item.icon" />
|
||||
<span>{{ item?.text }}</span>
|
||||
</template>
|
||||
</PermissionButton>
|
||||
</template>
|
||||
</CardBox>
|
||||
</template>
|
||||
<template #state="slotProps">
|
||||
<j-badge
|
||||
:text="slotProps.state?.text"
|
||||
:status="statusMap.get(slotProps.state?.value)"
|
||||
/>
|
||||
</template>
|
||||
<template #sourceId="slotProps">
|
||||
{{ slotProps.sourceName }}
|
||||
</template>
|
||||
<template #category="slotProps">
|
||||
{{
|
||||
options.find((i) => i.value === slotProps.category)
|
||||
?.label || slotProps.category
|
||||
}}
|
||||
</template>
|
||||
<template #createTime="slotProps">
|
||||
<span>{{
|
||||
dayjs(slotProps.createTime).format('YYYY-MM-DD HH:mm:ss')
|
||||
}}</span>
|
||||
</template>
|
||||
<template #action="slotProps">
|
||||
<j-space>
|
||||
<template
|
||||
v-for="i in getActions(slotProps, 'table')"
|
||||
:key="i.key"
|
||||
>
|
||||
<PermissionButton
|
||||
:disabled="i.disabled"
|
||||
:popConfirm="i.popConfirm"
|
||||
:tooltip="{
|
||||
...i.tooltip,
|
||||
}"
|
||||
@click="i.onClick"
|
||||
type="link"
|
||||
style="padding: 0 5px"
|
||||
:hasPermission="'edge/Resource:' + i.key"
|
||||
>
|
||||
<template #icon><AIcon :type="i.icon" /></template>
|
||||
</PermissionButton>
|
||||
</template>
|
||||
</j-space>
|
||||
</template>
|
||||
</JProTable>
|
||||
<Save
|
||||
v-if="visible"
|
||||
:data="current"
|
||||
@close="visible = false"
|
||||
@save="saveBtn"
|
||||
/>
|
||||
<Issue
|
||||
v-if="settingVisible"
|
||||
:data="current"
|
||||
@close="settingVisible = false"
|
||||
/>
|
||||
</page-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { queryNoPagingPost } from '@/api/device/instance';
|
||||
import { message } from 'jetlinks-ui-components';
|
||||
import { ActionsType } from '@/views/device/Instance/typings';
|
||||
import { useMenuStore } from '@/store/menu';
|
||||
import { getImage } from '@/utils/comm';
|
||||
import dayjs from 'dayjs';
|
||||
import { query, _delete, _start, _stop } from '@/api/edge/resource';
|
||||
import Save from './Save/index.vue';
|
||||
import Issue from './Issue/index.vue';
|
||||
|
||||
const menuStory = useMenuStore();
|
||||
|
||||
const defaultParams = { sorts: [{ name: 'createTime', order: 'desc' }] };
|
||||
|
||||
const statusMap = new Map();
|
||||
statusMap.set('enabled', 'success');
|
||||
statusMap.set('disabled', 'error');
|
||||
|
||||
const options = [
|
||||
{ label: 'UA接入', value: 'OPC_UA' },
|
||||
{ label: 'Modbus TCP接入', value: 'MODBUS_TCP' },
|
||||
{ label: 'S7-200接入', value: 'snap7' },
|
||||
{ label: 'BACnet接入', value: 'BACNetIp' },
|
||||
{ label: 'MODBUS_RTU接入', value: 'MODBUS_RTU' },
|
||||
];
|
||||
|
||||
const params = ref<Record<string, any>>({});
|
||||
const edgeResourceRef = ref<Record<string, any>>({});
|
||||
const settingVisible = ref<boolean>(false);
|
||||
const visible = ref<boolean>(false);
|
||||
const current = ref<Record<string, any>>({});
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: 'ID',
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
},
|
||||
{
|
||||
title: '名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
ellipsis: true,
|
||||
search: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
{
|
||||
dataIndex: 'category',
|
||||
title: '通信协议',
|
||||
valueType: 'select',
|
||||
scopedSlots: true,
|
||||
key: 'category',
|
||||
search: {
|
||||
type: 'select',
|
||||
options: options,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '所属边缘网关',
|
||||
dataIndex: 'sourceId',
|
||||
key: 'sourceId',
|
||||
scopedSlots: true,
|
||||
search: {
|
||||
type: 'select',
|
||||
options: () =>
|
||||
new Promise((resolve) => {
|
||||
queryNoPagingPost({
|
||||
paging: false,
|
||||
terms: [
|
||||
{
|
||||
terms: [
|
||||
{
|
||||
column: 'productId$product-info',
|
||||
value: 'accessProvider is official-edge-gateway',
|
||||
},
|
||||
],
|
||||
type: 'and',
|
||||
},
|
||||
],
|
||||
sorts: [
|
||||
{
|
||||
name: 'createTime',
|
||||
order: 'desc',
|
||||
},
|
||||
],
|
||||
}).then((resp: any) => {
|
||||
resolve(
|
||||
resp.result.map((item: any) => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
})),
|
||||
);
|
||||
});
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime',
|
||||
key: 'createTime',
|
||||
scopedSlots: true,
|
||||
search: {
|
||||
type: 'date',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'state',
|
||||
key: 'state',
|
||||
scopedSlots: true,
|
||||
search: {
|
||||
type: 'select',
|
||||
options: [
|
||||
{ label: '禁用', value: 'disabled' },
|
||||
{ label: '正常', value: 'enabled' },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
fixed: 'right',
|
||||
width: 250,
|
||||
scopedSlots: true,
|
||||
},
|
||||
];
|
||||
|
||||
const getActions = (
|
||||
data: Partial<Record<string, any>>,
|
||||
type: 'card' | 'table',
|
||||
): ActionsType[] => {
|
||||
if (!data) return [];
|
||||
const actions = [
|
||||
{
|
||||
key: 'update',
|
||||
text: '编辑',
|
||||
tooltip: {
|
||||
title: '编辑',
|
||||
},
|
||||
icon: 'EditOutlined',
|
||||
onClick: () => {
|
||||
visible.value = true;
|
||||
current.value = data;
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'setting',
|
||||
text: '下发',
|
||||
disabled: data.state?.value === 'disabled',
|
||||
tooltip: {
|
||||
title:
|
||||
data.state.value === 'disabled'
|
||||
? '请先启用,再下发'
|
||||
: '下发',
|
||||
},
|
||||
icon: 'DownSquareOutlined',
|
||||
onClick: () => {
|
||||
settingVisible.value = true;
|
||||
current.value = data;
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'action',
|
||||
text: data.state?.value !== 'disabled' ? '禁用' : '启用',
|
||||
tooltip: {
|
||||
title: data.state?.value !== 'disabled' ? '禁用' : '启用',
|
||||
},
|
||||
icon:
|
||||
data.state.value !== 'disabled'
|
||||
? 'StopOutlined'
|
||||
: 'CheckCircleOutlined',
|
||||
popConfirm: {
|
||||
title: `确认${
|
||||
data.state.value !== 'disabled' ? '禁用' : '启用'
|
||||
}?`,
|
||||
onConfirm: async () => {
|
||||
let response = undefined;
|
||||
if (data.state.value !== 'disabled') {
|
||||
response = await _stop([data.id]);
|
||||
} else {
|
||||
response = await _start([data.id]);
|
||||
}
|
||||
if (response && response.status === 200) {
|
||||
message.success('操作成功!');
|
||||
edgeResourceRef.value?.reload();
|
||||
} else {
|
||||
message.error('操作失败!');
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'delete',
|
||||
text: '删除',
|
||||
disabled: data.state?.value !== 'disabled',
|
||||
tooltip: {
|
||||
title:
|
||||
data.state.value !== 'disabled'
|
||||
? '请先禁用,再删除。'
|
||||
: '删除',
|
||||
},
|
||||
popConfirm: {
|
||||
title: '确认删除?',
|
||||
onConfirm: async () => {
|
||||
const resp = await _delete(data.id);
|
||||
if (resp.status === 200) {
|
||||
message.success('操作成功!');
|
||||
edgeResourceRef.value?.reload();
|
||||
} else {
|
||||
message.error('操作失败!');
|
||||
}
|
||||
},
|
||||
},
|
||||
icon: 'DeleteOutlined',
|
||||
},
|
||||
];
|
||||
if (type === 'card')
|
||||
return actions.filter((i: ActionsType) => i.key !== 'view');
|
||||
return actions;
|
||||
};
|
||||
|
||||
const handleSearch = (_params: any) => {
|
||||
params.value = _params;
|
||||
};
|
||||
|
||||
const handleView = (id: string) => {
|
||||
menuStory.jumpPage('device/Instance/Detail', { id });
|
||||
};
|
||||
|
||||
const saveBtn = () => {
|
||||
visible.value = false;
|
||||
edgeResourceRef.value?.reload();
|
||||
};
|
||||
|
||||
const onRefresh = () => {
|
||||
settingVisible.value = false;
|
||||
edgeResourceRef.value?.reload();
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
</style>
|
|
@ -1,8 +1,6 @@
|
|||
<template>
|
||||
<a-card class="boot-card-container" :bordered="false">
|
||||
<template #title>
|
||||
<div class="boot-card-container">
|
||||
<h5 class="title">{{ cardTitle }}</h5>
|
||||
</template>
|
||||
<div class="box">
|
||||
<div
|
||||
class="box-item"
|
||||
|
@ -23,7 +21,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
@ -46,9 +44,8 @@ const jumpPage = (item: bootConfig) => {
|
|||
|
||||
<style lang="less" scoped>
|
||||
.boot-card-container {
|
||||
:deep(.ant-card-body) {
|
||||
padding-top: 0;
|
||||
}
|
||||
background-color: #fff;
|
||||
padding: 24px 12px;
|
||||
.title {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
|
|
|
@ -13,8 +13,8 @@
|
|||
<j-row :gutter="24">
|
||||
<j-col :span="12"><DeviceCountCard /></j-col>
|
||||
<j-col :span="12"><BasicCountCard /></j-col>
|
||||
<j-col :span="24" style="margin-top: 24px">
|
||||
<PlatformPicCard image="/images/home/content1.png" />
|
||||
<j-col :span="24" style="margin-top: 24px;">
|
||||
<PlatformPicCard image="/images/home/content1.svg" />
|
||||
</j-col>
|
||||
</j-row>
|
||||
</j-col>
|
||||
|
@ -41,7 +41,7 @@
|
|||
<DeviceChooseDialog
|
||||
v-if="deviceDialogVisible"
|
||||
v-model:visible="deviceDialogVisible"
|
||||
@confirm="(id:string)=>jumpPage('device/Instance/Detail', { id })"
|
||||
@confirm="(id:string)=>jumpPage('device/Instance/Detail', { id, tab: 'Diagnose' })"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -157,7 +157,7 @@ const deviceStepDetails: recommendList[] = [
|
|||
linkUrl: 'device/Instance',
|
||||
auth: devicePermission('import'),
|
||||
params: {
|
||||
import: true,
|
||||
type: 'import'
|
||||
},
|
||||
},
|
||||
];
|
||||
|
@ -175,7 +175,7 @@ const opsBootConfig: bootConfig[] = [
|
|||
label: '日志排查',
|
||||
link: 'Log',
|
||||
params: {
|
||||
key: 'system',
|
||||
tab: 'system',
|
||||
},
|
||||
image: '/images/home/guide-home5.png',
|
||||
},
|
||||
|
@ -220,7 +220,7 @@ const opsStepDetails: recommendList[] = [
|
|||
iconUrl: '/images/home/bottom-5.png',
|
||||
linkUrl: 'Log',
|
||||
params: {
|
||||
key: 'system',
|
||||
tab: 'system',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
|
|
@ -39,7 +39,7 @@ const opsBootConfig: bootConfig[] = [
|
|||
label: '日志排查',
|
||||
link: 'Log',
|
||||
params: {
|
||||
key: 'system',
|
||||
tab: 'system',
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -83,7 +83,7 @@ const opsStepDetails: recommendList[] = [
|
|||
iconUrl: '/images/home/bottom-5.png',
|
||||
linkUrl: 'Log',
|
||||
params: {
|
||||
key: 'system',
|
||||
tab: 'system',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
<DeviceChooseDialog
|
||||
v-if="deviceDialogVisible"
|
||||
v-model:visible="deviceDialogVisible"
|
||||
@confirm="(id:string)=>jumpPage('device/Instance/Detail', { id })"
|
||||
@confirm="(id:string)=>jumpPage('device/Instance/Detail', { id, tab: 'Diagnose' })"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -25,10 +25,11 @@ const props = defineProps({
|
|||
overflow: hidden;
|
||||
background-color: #fff;
|
||||
border-bottom: 1px solid #2f54eb;
|
||||
|
||||
height: 458px;
|
||||
.bj {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.title {
|
||||
|
|
|
@ -59,7 +59,7 @@
|
|||
<j-form-item label="系统logo">
|
||||
<div class="upload-image-warp-logo">
|
||||
<div class="upload-image-border-logo">
|
||||
<a-upload
|
||||
<j-upload
|
||||
name="file"
|
||||
:action="FILE_UPLOAD"
|
||||
:headers="headers"
|
||||
|
@ -109,7 +109,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-upload>
|
||||
</j-upload>
|
||||
<div v-if="logoLoading">
|
||||
<div class="upload-loading-mask">
|
||||
<LoadingOutlined
|
||||
|
@ -138,7 +138,7 @@
|
|||
</template>
|
||||
<div class="upload-image-warp-logo">
|
||||
<div class="upload-image-border-logo">
|
||||
<a-upload
|
||||
<j-upload
|
||||
name="file"
|
||||
:action="FILE_UPLOAD"
|
||||
:headers="headers"
|
||||
|
@ -183,7 +183,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-upload>
|
||||
</j-upload>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -197,7 +197,7 @@
|
|||
<j-form-item label="登录背景图">
|
||||
<div class="upload-image-warp-back">
|
||||
<div class="upload-image-border-back">
|
||||
<a-upload
|
||||
<j-upload
|
||||
name="file"
|
||||
:action="FILE_UPLOAD"
|
||||
:headers="headers"
|
||||
|
@ -242,7 +242,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-upload>
|
||||
</j-upload>
|
||||
</div>
|
||||
</div>
|
||||
<div class="upload-tips">支持4M以内的图片:支持jpg、png</div>
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<j-modal :maskClosable="false" width="1100px" :visible="true" title="选择设备" okText="确定" cancelText="取消" @ok="handleOk"
|
||||
@cancel="handleCancel" :confirmLoading="btnLoading">
|
||||
<div style="margin-top: 10px">
|
||||
<Search :columns="columns" target="iot-card-bind-device" @search="handleSearch" type="simple" />
|
||||
<pro-search :columns="columns" target="iot-card-bind-device" @search="handleSearch" type="simple" />
|
||||
<j-pro-table ref="bindDeviceRef" :columns="columns" :request="queryUnbounded" model="TABLE" :defaultParams="{
|
||||
sorts: [{ name: 'createTime', order: 'desc' }],
|
||||
}" :rowSelection="{
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<!-- 物联卡管理 -->
|
||||
<template>
|
||||
<page-container>
|
||||
<Search :columns="columns" target="iot-card-management-search" @search="handleSearch" />
|
||||
<pro-search :columns="columns" target="iot-card-management-search" @search="handleSearch" />
|
||||
<j-pro-table ref="cardManageRef" :columns="columns" :request="query"
|
||||
:defaultParams="{ sorts: [{ name: 'createTime', order: 'desc' }] }" :rowSelection="{
|
||||
selectedRowKeys: _selectedRowKeys,
|
||||
|
|
|
@ -95,6 +95,7 @@ import { queryFlow, list } from '@/api/iot-card/home';
|
|||
import * as echarts from 'echarts';
|
||||
import { useMenuStore } from '@/store/menu';
|
||||
import { usePermissionStore } from '@/store/permission';
|
||||
import { message } from 'jetlinks-ui-components'
|
||||
|
||||
const { proxy } = <any>getCurrentInstance();
|
||||
|
||||
|
@ -178,11 +179,10 @@ const pieChartData = ref<any[]>([
|
|||
]);
|
||||
|
||||
const jumpPage = (data: GuideItemProps) => {
|
||||
// if (data.url && data.auth) {
|
||||
// router.push({ path: `${data.url}`, ...data.param });
|
||||
// } else {
|
||||
// message.warning('暂无权限,请联系管理员');
|
||||
// }
|
||||
if (!data.auth){
|
||||
message.warning('暂无权限,请联系管理员');
|
||||
return
|
||||
}
|
||||
if (data.key === 'EQUIPMENT') {
|
||||
menuStory.jumpPage(data.url, { id: 'add' });
|
||||
} else {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<!-- 平台对接 -->
|
||||
<template>
|
||||
<page-container>
|
||||
<Search :columns="columns" target="platform-search" @search="handleSearch" />
|
||||
<pro-search :columns="columns" target="platform-search" @search="handleSearch" />
|
||||
<j-pro-table ref="platformRef" :columns="columns" :request="queryList"
|
||||
:defaultParams="{ sorts: [{ name: 'createTime', order: 'desc' }] }" :params="params" :gridColumn="3">
|
||||
<template #headerTitle>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<!-- 充值管理 -->
|
||||
<template>
|
||||
<page-container>
|
||||
<Search :columns="columns" target="recharge-search" @search="handleSearch" />
|
||||
<pro-search :columns="columns" target="recharge-search" @search="handleSearch" />
|
||||
<j-pro-table ref="rechargeRef" :columns="columns" :request="queryRechargeList" model="TABLE"
|
||||
:defaultParams="{ sorts: [{ name: 'createTime', order: 'desc' }] }" :params="params">
|
||||
<template #headerTitle>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<!-- 操作记录 -->
|
||||
<template>
|
||||
<page-container>
|
||||
<Search
|
||||
<pro-search
|
||||
:columns="columns"
|
||||
target="record-search"
|
||||
@search="handleSearch"
|
||||
|
|
|
@ -9,32 +9,52 @@
|
|||
:confirmLoading="btnLoading"
|
||||
width="660px"
|
||||
>
|
||||
<j-form layout="vertical">
|
||||
<j-form-item label="产品名称" v-bind="validateInfos.name">
|
||||
<j-form ref="formRef" :model="formData" layout="vertical">
|
||||
<j-form-item
|
||||
label="产品名称"
|
||||
name="name"
|
||||
:rules="{ required: true, message: '请输入产品名称' }"
|
||||
>
|
||||
<j-input
|
||||
v-model:value="formData.name"
|
||||
placeholder="请输入名称"
|
||||
/>
|
||||
</j-form-item>
|
||||
<template v-if="channel === 'gb28181-2016' && formData.accessId">
|
||||
<template v-if="deviceType !== 'gateway'">
|
||||
<template v-for="(item, index) in extendFormItem" :key="index">
|
||||
<j-form-item
|
||||
label="接入密码"
|
||||
v-bind="validateInfos['configuration.access_pwd']"
|
||||
:name="item.name"
|
||||
:label="item.label"
|
||||
:rules="{
|
||||
required: item.required,
|
||||
message: item.message,
|
||||
trigger: 'change',
|
||||
}"
|
||||
>
|
||||
<j-input-password
|
||||
v-model:value="formData.configuration.access_pwd"
|
||||
placeholder="请输入接入密码"
|
||||
/>
|
||||
</j-form-item>
|
||||
<j-form-item label="流传输模式">
|
||||
<j-select
|
||||
v-model:value="formData.configuration.stream_mode"
|
||||
placeholder="请选择流传输模式"
|
||||
:options="streamMode"
|
||||
v-if="item.type === 'enum'"
|
||||
v-model:value="formData[item.name[0]][item.name[1]]"
|
||||
:options="item.options"
|
||||
:placeholder="item.message"
|
||||
/>
|
||||
<j-input-password
|
||||
v-else-if="item.type === 'password'"
|
||||
v-model:value="formData[item.name[0]][item.name[1]]"
|
||||
:placeholder="item.message"
|
||||
/>
|
||||
<j-input
|
||||
v-else
|
||||
v-model:value="formData[item.name[0]][item.name[1]]"
|
||||
:placeholder="item.message"
|
||||
/>
|
||||
</j-form-item>
|
||||
</template>
|
||||
<j-form-item label="接入网关" v-bind="validateInfos.accessId">
|
||||
</template>
|
||||
<j-form-item
|
||||
label="接入网关"
|
||||
name="accessId"
|
||||
:rules="{ required: true, message: '请选择接入网关' }"
|
||||
>
|
||||
<div class="gateway-box">
|
||||
<div v-if="!gatewayList.length">
|
||||
暂无数据,请先
|
||||
|
@ -119,20 +139,17 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Form, message } from 'ant-design-vue';
|
||||
import { PropType } from 'vue';
|
||||
import { streamMode } from '@/views/media/Device/const';
|
||||
import { message } from 'ant-design-vue';
|
||||
import DeviceApi from '@/api/media/device';
|
||||
import { getImage } from '@/utils/comm';
|
||||
import { gatewayType } from '@/views/media/Device/typings';
|
||||
import { providerType } from '../const';
|
||||
|
||||
const useForm = Form.useForm;
|
||||
|
||||
type Emits = {
|
||||
(e: 'update:visible', data: boolean): void;
|
||||
(e: 'update:productId', data: string): void;
|
||||
(e: 'close'): void;
|
||||
(e: 'save', data: Record<string, any>): void;
|
||||
};
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
|
@ -140,6 +157,7 @@ const props = defineProps({
|
|||
visible: { type: Boolean, default: false },
|
||||
productId: { type: String, default: '' },
|
||||
channel: { type: String, default: '' },
|
||||
deviceType: { type: String, default: 'device' },
|
||||
});
|
||||
|
||||
const _vis = computed({
|
||||
|
@ -166,20 +184,36 @@ const getGatewayList = async () => {
|
|||
* @param e
|
||||
*/
|
||||
const _selectedRowKeys = ref<string[]>([]);
|
||||
const extendFormItem = ref<any[]>();
|
||||
const handleClick = async (e: any) => {
|
||||
_selectedRowKeys.value = [e.id];
|
||||
formData.value.accessId = e.id;
|
||||
formData.value.accessName = e.name;
|
||||
formData.value.accessProvider = e.provider;
|
||||
formData.value.messageProtocol = e.provider;
|
||||
formData.value.messageProtocol = e.protocolDetail.id;
|
||||
formData.value.protocolName = e.protocolDetail.name;
|
||||
formData.value.transportProtocol = e.transport;
|
||||
|
||||
const { result } = await DeviceApi.getConfiguration(
|
||||
props.channel,
|
||||
e.protocol,
|
||||
e.transport,
|
||||
);
|
||||
console.log('result: ', result);
|
||||
|
||||
extendFormItem.value = result.properties.map((item: any) => ({
|
||||
name: ['configuration', item.property],
|
||||
label: item.name,
|
||||
type: item.type?.type,
|
||||
value: item.type.expands?.defaultValue,
|
||||
options: item.type.elements?.map((e: any) => ({
|
||||
label: e.text,
|
||||
value: e.value,
|
||||
})),
|
||||
required: !!item.type.expands?.required,
|
||||
message:
|
||||
item.type?.type === 'enum'
|
||||
? `请选择${item.name}`
|
||||
: `请输入${item.name}`,
|
||||
}));
|
||||
};
|
||||
|
||||
watch(
|
||||
|
@ -187,10 +221,6 @@ watch(
|
|||
(val) => {
|
||||
if (val) {
|
||||
getGatewayList();
|
||||
|
||||
formRules.value['configuration.access_pwd'][0].required =
|
||||
props.channel === 'gb28181-2016';
|
||||
validate();
|
||||
} else {
|
||||
emit('close');
|
||||
}
|
||||
|
@ -198,6 +228,7 @@ watch(
|
|||
);
|
||||
|
||||
// 表单数据
|
||||
const formRef = ref();
|
||||
const formData = ref({
|
||||
accessId: '',
|
||||
accessName: '',
|
||||
|
@ -206,32 +237,20 @@ const formData = ref({
|
|||
access_pwd: '',
|
||||
stream_mode: 'UDP',
|
||||
},
|
||||
deviceType: 'device',
|
||||
deviceType: props.deviceType,
|
||||
messageProtocol: '',
|
||||
name: '',
|
||||
protocolName: '',
|
||||
transportProtocol: '',
|
||||
});
|
||||
|
||||
// 验证规则
|
||||
const formRules = ref({
|
||||
name: [{ required: true, message: '请输入产品名称' }],
|
||||
'configuration.access_pwd': [{ required: true, message: '请输入接入密码' }],
|
||||
accessId: [{ required: true, message: '请选择接入网关' }],
|
||||
});
|
||||
|
||||
const { resetFields, validate, validateInfos, clearValidate } = useForm(
|
||||
formData.value,
|
||||
formRules.value,
|
||||
);
|
||||
|
||||
/**
|
||||
* 提交
|
||||
*/
|
||||
const btnLoading = ref(false);
|
||||
const handleOk = () => {
|
||||
// console.log('formData.value: ', formData.value);
|
||||
validate()
|
||||
formRef.value
|
||||
?.validate()
|
||||
.then(async () => {
|
||||
btnLoading.value = true;
|
||||
const res = await DeviceApi.saveProduct(formData.value);
|
||||
|
@ -241,20 +260,21 @@ const handleOk = () => {
|
|||
res.result.id,
|
||||
);
|
||||
if (deployResp.success) {
|
||||
emit('save', {...res.result})
|
||||
message.success('操作成功');
|
||||
handleCancel();
|
||||
}
|
||||
}
|
||||
btnLoading.value = false;
|
||||
})
|
||||
.catch((err) => {
|
||||
.catch((err: any) => {
|
||||
console.log('err: ', err);
|
||||
});
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
_vis.value = false;
|
||||
resetFields();
|
||||
formRef.value.resetFields();
|
||||
};
|
||||
</script>
|
||||
|
||||
|
|
|
@ -132,6 +132,8 @@ const getTemplateList = async () => {
|
|||
};
|
||||
const { result } = await ConfigApi.getTemplate(params, props.data.id);
|
||||
templateList.value = result;
|
||||
formData.value.templateId = result[0]?.id as string;
|
||||
getTemplateDetail()
|
||||
};
|
||||
|
||||
watch(
|
||||
|
|
|
@ -125,16 +125,56 @@
|
|||
v-bind="validateInfos['configuration.host']"
|
||||
>
|
||||
<j-space>
|
||||
<j-input
|
||||
<j-auto-complete
|
||||
v-model:value="
|
||||
formData.configuration.host
|
||||
"
|
||||
placeholder="请输入服务器地址"
|
||||
style="width: 180px"
|
||||
:options="[
|
||||
{
|
||||
label: 'smtp.163.com',
|
||||
value: 'smtp.163.com',
|
||||
},
|
||||
{
|
||||
label: 'pop.163.com',
|
||||
value: 'pop.163.com',
|
||||
},
|
||||
{
|
||||
label: 'smtp.exmail.qq.com',
|
||||
value: 'smtp.exmail.qq.com',
|
||||
},
|
||||
{
|
||||
label: 'pop.exmail.qq.com',
|
||||
value: 'pop.exmail.qq.com',
|
||||
},
|
||||
{
|
||||
label: 'smtp.qq.com',
|
||||
value: 'smtp.qq.com',
|
||||
},
|
||||
{
|
||||
label: 'pop.qq.com',
|
||||
value: 'pop.qq.com',
|
||||
},
|
||||
{
|
||||
label: 'smtpdm.aliyun.com',
|
||||
value: 'smtpdm.aliyun.com',
|
||||
},
|
||||
{
|
||||
label: 'smtp.126.com',
|
||||
value: 'smtp.126.com',
|
||||
},
|
||||
{
|
||||
label: 'pop.126.com',
|
||||
value: 'pop.126.com',
|
||||
},
|
||||
]"
|
||||
/>
|
||||
<j-input-number
|
||||
v-model:value="
|
||||
formData.configuration.port
|
||||
"
|
||||
:precision="0"
|
||||
:min="1"
|
||||
:max="65535"
|
||||
/>
|
||||
|
@ -352,50 +392,53 @@ const formRules = ref({
|
|||
provider: [{ required: true, message: '请选择类型' }],
|
||||
// 钉钉
|
||||
'configuration.appKey': [
|
||||
{ required: true, message: '请输入AppKey' },
|
||||
{ max: 64, message: '最多可输入64个字符' },
|
||||
{ required: true, message: '请输入AppKey', trigger: 'blur' },
|
||||
{ max: 64, message: '最多可输入64个字符', trigger: 'change' },
|
||||
],
|
||||
'configuration.appSecret': [
|
||||
{ required: true, message: '请输入AppSecret' },
|
||||
{ max: 64, message: '最多可输入64个字符' },
|
||||
{ required: true, message: '请输入AppSecret', trigger: 'blur' },
|
||||
{ max: 64, message: '最多可输入64个字符', trigger: 'change' },
|
||||
],
|
||||
// 'configuration.url': [{ required: true, message: '请输入WebHook' }],
|
||||
// 微信
|
||||
'configuration.corpId': [
|
||||
{ required: true, message: '请输入corpId' },
|
||||
{ required: true, message: '请输入corpId', trigger: 'blur' },
|
||||
{ max: 64, message: '最多可输入64个字符' },
|
||||
],
|
||||
'configuration.corpSecret': [
|
||||
{ required: true, message: '请输入corpSecret' },
|
||||
{ required: true, message: '请输入corpSecret', trigger: 'blur' },
|
||||
{ max: 64, message: '最多可输入64个字符' },
|
||||
],
|
||||
// 阿里云语音/短信
|
||||
'configuration.regionId': [
|
||||
{ required: true, message: '请输入RegionId' },
|
||||
{ required: true, message: '请输入RegionId', trigger: 'blur' },
|
||||
{ max: 64, message: '最多可输入64个字符' },
|
||||
],
|
||||
'configuration.accessKeyId': [
|
||||
{ required: true, message: '请输入AccessKeyId' },
|
||||
{ required: true, message: '请输入AccessKeyId', trigger: 'blur' },
|
||||
{ max: 64, message: '最多可输入64个字符' },
|
||||
],
|
||||
'configuration.secret': [
|
||||
{ required: true, message: '请输入Secret' },
|
||||
{ required: true, message: '请输入Secret', trigger: 'blur' },
|
||||
{ max: 64, message: '最多可输入64个字符' },
|
||||
],
|
||||
// 邮件
|
||||
'configuration.host': [{ required: true, message: '请输入服务器地址' }],
|
||||
'configuration.sender': [{ required: true, message: '请输入发件人' }],
|
||||
'configuration.host': [{ required: true, message: '请输入服务器地址', trigger: 'blur' }],
|
||||
'configuration.sender': [
|
||||
{ required: true, message: '请输入发件人', trigger: 'blur' },
|
||||
{ max: 64, message: '最多可输入64个字符' },
|
||||
],
|
||||
'configuration.username': [
|
||||
{ required: true, message: '请输入用户名' },
|
||||
{ required: true, message: '请输入用户名', trigger: 'blur' },
|
||||
{ max: 64, message: '最多可输入64个字符' },
|
||||
],
|
||||
'configuration.password': [
|
||||
{ required: true, message: '请输入密码' },
|
||||
{ required: true, message: '请输入密码', trigger: 'blur' },
|
||||
{ max: 64, message: '最多可输入64个字符' },
|
||||
],
|
||||
// webhook
|
||||
'configuration.url': [
|
||||
{ required: true, message: '请输入Webhook' },
|
||||
{ required: true, message: '请输入Webhook', trigger: 'blur' },
|
||||
// {
|
||||
// pattern:
|
||||
// /^(((ht|f)tps?):\/\/)?([^!@#$%^&*?.\s-]([^!@#$%^&*?.\s]{0,63}[^!@#$%^&*?.\s])?\.)+[j-z]{2,6}\/?/,
|
||||
|
@ -415,8 +458,6 @@ const getDetail = async () => {
|
|||
const res = await configApi.detail(route.params.id as string);
|
||||
// formData.value = res.result;
|
||||
Object.assign(formData.value, res.result);
|
||||
// console.log('res.result: ', res.result);
|
||||
// console.log('formData.value: ', formData.value);
|
||||
};
|
||||
getDetail();
|
||||
|
||||
|
@ -494,7 +535,6 @@ const btnLoading = ref<boolean>(false);
|
|||
const handleSubmit = () => {
|
||||
validate()
|
||||
.then(async () => {
|
||||
// console.log('formData.value: ', formData.value);
|
||||
btnLoading.value = true;
|
||||
let res;
|
||||
if (!formData.value.id) {
|
||||
|
@ -502,7 +542,6 @@ const handleSubmit = () => {
|
|||
} else {
|
||||
res = await configApi.update(formData.value);
|
||||
}
|
||||
// console.log('res: ', res);
|
||||
if (res?.success) {
|
||||
message.success('保存成功');
|
||||
router.back();
|
||||
|
|
|
@ -34,31 +34,37 @@
|
|||
<j-empty v-if="!deptTreeData.length" />
|
||||
</j-col>
|
||||
<j-col :span="20">
|
||||
<JProTable
|
||||
<j-button type="primary" @click="handleAutoBind">
|
||||
自动绑定
|
||||
</j-button>
|
||||
<JTable
|
||||
ref="tableRef"
|
||||
:columns="columns"
|
||||
:dataSource="dataSource"
|
||||
:loading="tableLoading"
|
||||
model="table"
|
||||
noPagination
|
||||
:pagination="{
|
||||
total: dataSource.length,
|
||||
current: current,
|
||||
pageSize: pageSize,
|
||||
pageSizeOptions: ['12', '24', '48', '96'],
|
||||
showSizeChanger: true,
|
||||
showTotal: (total: number, range: number) => `第 ${range[0]} - ${range[1]} 条/总共 ${total} 条`,
|
||||
}"
|
||||
@change="handleTableChange"
|
||||
>
|
||||
<template #headerTitle>
|
||||
<j-button type="primary" @click="handleAutoBind">
|
||||
自动绑定
|
||||
</j-button>
|
||||
</template>
|
||||
<template #status="slotProps">
|
||||
<template #bodyCell="{ column, record, index }">
|
||||
<template v-if="column.dataIndex === 'status'">
|
||||
<j-space>
|
||||
<j-badge
|
||||
:status="slotProps.status.value"
|
||||
:text="slotProps.status.text"
|
||||
:status="record.status.value"
|
||||
:text="record.status.text"
|
||||
></j-badge>
|
||||
</j-space>
|
||||
</template>
|
||||
<template #action="slotProps">
|
||||
<template v-if="column.dataIndex === 'action'">
|
||||
<j-space :size="16">
|
||||
<j-tooltip
|
||||
v-for="i in getActions(slotProps, 'table')"
|
||||
v-for="i in getActions(record, 'table')"
|
||||
:key="i.key"
|
||||
v-bind="i.tooltip"
|
||||
>
|
||||
|
@ -79,7 +85,7 @@
|
|||
type="link"
|
||||
v-else
|
||||
@click="
|
||||
i.onClick && i.onClick(slotProps)
|
||||
i.onClick && i.onClick(record)
|
||||
"
|
||||
>
|
||||
<j-button
|
||||
|
@ -92,7 +98,8 @@
|
|||
</j-tooltip>
|
||||
</j-space>
|
||||
</template>
|
||||
</JProTable>
|
||||
</template>
|
||||
</JTable>
|
||||
</j-col>
|
||||
</j-row>
|
||||
</j-modal>
|
||||
|
@ -184,23 +191,10 @@ const getDepartment = async () => {
|
|||
);
|
||||
}
|
||||
|
||||
// deptTreeData.value = arrayToTree(_result, _result[0]?.parentId);
|
||||
deptTreeData.value = _result;
|
||||
deptId.value = _result[0]?.id;
|
||||
};
|
||||
|
||||
/**
|
||||
* 扁平数据转树形结构
|
||||
*/
|
||||
// const arrayToTree = (arr: any, pid: string | number) => {
|
||||
// return arr
|
||||
// .filter((item: any) => item.parentId === pid)
|
||||
// .map((item: any) => ({
|
||||
// ...item,
|
||||
// children: arrayToTree(arr, item.id),
|
||||
// }));
|
||||
// };
|
||||
|
||||
/**
|
||||
* 部门点击
|
||||
*/
|
||||
|
@ -230,6 +224,7 @@ const columns = [
|
|||
},
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'action',
|
||||
key: 'action',
|
||||
scopedSlots: true,
|
||||
},
|
||||
|
@ -285,7 +280,7 @@ const handleAutoBind = () => {
|
|||
thirdPartyUserId: i.thirdPartyUserId,
|
||||
};
|
||||
});
|
||||
// console.log('arr: ', arr);
|
||||
|
||||
configApi.dingTalkBindUser(arr, props.data.id).then(() => {
|
||||
message.success('操作成功');
|
||||
getTableData();
|
||||
|
@ -346,8 +341,8 @@ const dataSource = ref<any>([]);
|
|||
const tableLoading = ref(false);
|
||||
const getTableData = () => {
|
||||
tableLoading.value = true;
|
||||
Promise.all<any>([getDeptUsers(), getBindUsers(), getAllUsers()]).then(
|
||||
(res) => {
|
||||
Promise.all<any>([getDeptUsers(), getBindUsers(), getAllUsers()])
|
||||
.then((res) => {
|
||||
dataSource.value = [];
|
||||
const [deptUsers, bindUsers, unBindUsers] = res;
|
||||
(deptUsers || []).forEach((deptUser: any) => {
|
||||
|
@ -379,9 +374,20 @@ const getTableData = () => {
|
|||
});
|
||||
});
|
||||
// console.log('dataSource.value: ', dataSource.value);
|
||||
},
|
||||
);
|
||||
})
|
||||
.finally(() => {
|
||||
tableLoading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 前端分页
|
||||
*/
|
||||
const current = ref(1);
|
||||
const pageSize = ref(12);
|
||||
const handleTableChange = (pagination: any) => {
|
||||
current.value = pagination.current;
|
||||
pageSize.value = pagination.pageSize;
|
||||
};
|
||||
|
||||
watch(
|
||||
|
@ -475,5 +481,8 @@ const handleCancel = () => {
|
|||
.model-body {
|
||||
height: 600px;
|
||||
overflow-y: auto;
|
||||
&:deep(.ant-pagination-item) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
</PermissionButton>
|
||||
<a-upload
|
||||
name="file"
|
||||
accept="json"
|
||||
accept=".json"
|
||||
:showUploadList="false"
|
||||
:before-upload="beforeUpload"
|
||||
>
|
||||
|
@ -147,6 +147,19 @@
|
|||
</template>
|
||||
</CardBox>
|
||||
</template>
|
||||
<template #type="slotProps">
|
||||
<span> {{ getMethodTxt(slotProps.type) }}</span>
|
||||
</template>
|
||||
<template #provider="slotProps">
|
||||
<span>
|
||||
{{ getProviderTxt(slotProps.type, slotProps.provider) }}
|
||||
</span>
|
||||
</template>
|
||||
<!-- <template #description="slotProps">
|
||||
<Ellipsis>
|
||||
{{ slotProps.description }}
|
||||
</Ellipsis>
|
||||
</template> -->
|
||||
<template #action="slotProps">
|
||||
<j-space :size="16">
|
||||
<template
|
||||
|
@ -205,6 +218,7 @@ const columns = [
|
|||
title: '配置名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
width: 100,
|
||||
search: {
|
||||
type: 'string',
|
||||
},
|
||||
|
@ -214,6 +228,7 @@ const columns = [
|
|||
dataIndex: 'type',
|
||||
key: 'type',
|
||||
scopedSlots: true,
|
||||
width: 100,
|
||||
search: {
|
||||
type: 'select',
|
||||
options: NOTICE_METHOD,
|
||||
|
@ -227,6 +242,7 @@ const columns = [
|
|||
dataIndex: 'provider',
|
||||
key: 'provider',
|
||||
scopedSlots: true,
|
||||
width: 200,
|
||||
search: {
|
||||
type: 'select',
|
||||
options: providerList,
|
||||
|
@ -239,6 +255,8 @@ const columns = [
|
|||
title: '说明',
|
||||
dataIndex: 'description',
|
||||
key: 'description',
|
||||
scopedSlots: true,
|
||||
ellipsis: true,
|
||||
search: {
|
||||
type: 'string',
|
||||
},
|
||||
|
@ -272,6 +290,14 @@ const getLogo = (type: string, provider: string) => {
|
|||
const getMethodTxt = (type: string) => {
|
||||
return NOTICE_METHOD.find((f) => f.value === type)?.label;
|
||||
};
|
||||
/**
|
||||
* 根据类型展示对应文案
|
||||
* @param type
|
||||
* @param provider
|
||||
*/
|
||||
const getProviderTxt = (type: string, provider: string) => {
|
||||
return MSG_TYPE[type].find((f: any) => f.value === provider)?.label;
|
||||
};
|
||||
|
||||
/**
|
||||
* 新增
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
<j-form-item
|
||||
label="通知配置"
|
||||
name="configId"
|
||||
:rules="{ required: true, message: '该字段为必填字段' }"
|
||||
:rules="{ required: true, message: '请选择通知配置' }"
|
||||
>
|
||||
<j-select
|
||||
v-model:value="formData.configId"
|
||||
|
@ -50,10 +50,19 @@
|
|||
<template v-else>
|
||||
<j-form-item
|
||||
:name="['templateDetailTable', index, 'value']"
|
||||
:rules="{
|
||||
:rules="[
|
||||
{
|
||||
required: record.required,
|
||||
message: '该字段为必填字段',
|
||||
}"
|
||||
},
|
||||
...record.otherRules,
|
||||
]"
|
||||
>
|
||||
<template
|
||||
v-if="
|
||||
data.type === 'dingTalk' ||
|
||||
data.type === 'weixin'
|
||||
"
|
||||
>
|
||||
<ToUser
|
||||
v-if="record.type === 'user'"
|
||||
|
@ -78,6 +87,13 @@
|
|||
v-model:modelValue="record.value"
|
||||
:itemType="record.type"
|
||||
/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<ValueItem
|
||||
v-model:modelValue="record.value"
|
||||
:itemType="record.type"
|
||||
/>
|
||||
</template>
|
||||
</j-form-item>
|
||||
</template>
|
||||
</template>
|
||||
|
@ -100,6 +116,8 @@ import { message } from 'ant-design-vue';
|
|||
import ToUser from '../Detail/components/ToUser.vue';
|
||||
import ToOrg from '../Detail/components/ToOrg.vue';
|
||||
import ToTag from '../Detail/components/ToTag.vue';
|
||||
import type { Rule } from 'ant-design-vue/es/form';
|
||||
import { phoneRegEx } from '@/utils/validate';
|
||||
|
||||
type Emits = {
|
||||
(e: 'update:visible', data: boolean): void;
|
||||
|
@ -154,8 +172,28 @@ const getTemplateDetail = async () => {
|
|||
formData.value.templateDetailTable = result.variableDefinitions.map(
|
||||
(m: any) => ({
|
||||
...m,
|
||||
type: m.expands ? m.expands.businessType : m.type,
|
||||
type: m.expands?.businessType ? m.expands.businessType : m.type,
|
||||
value: undefined,
|
||||
// 电话字段校验
|
||||
otherRules:
|
||||
m.id === 'calledNumber' || m.id === 'phoneNumber'
|
||||
? [
|
||||
{
|
||||
max: 64,
|
||||
message: '最多可输入64个字符',
|
||||
trigger: 'change',
|
||||
},
|
||||
{
|
||||
trigger: 'change',
|
||||
validator(_rule: Rule, value: string) {
|
||||
if (!value) return Promise.resolve();
|
||||
if (!phoneRegEx(value))
|
||||
return Promise.reject('请输入有效号码');
|
||||
return Promise.resolve();
|
||||
},
|
||||
},
|
||||
]
|
||||
: '',
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
v-for="(item, index) in fileList"
|
||||
:key="index"
|
||||
>
|
||||
<j-input v-model:value="item.name">
|
||||
<j-input v-model:value="item.name" @change="emitEvents">
|
||||
<template #addonAfter>
|
||||
<a-upload
|
||||
name="file"
|
||||
|
|
|
@ -55,7 +55,7 @@
|
|||
yyyy-MM-dd
|
||||
</j-select-option>
|
||||
<j-select-option value="yyyy-MM-dd HH:mm:ss">
|
||||
yyyy-MM-dd HH:mm:ss
|
||||
<Ellipsis>yyyy-MM-dd HH:mm:ss</Ellipsis>
|
||||
</j-select-option>
|
||||
</j-select>
|
||||
<j-input
|
||||
|
@ -143,7 +143,7 @@ const handleTypeChange = (record: IVariable) => {
|
|||
record.format = 'timestamp';
|
||||
break;
|
||||
case 'double':
|
||||
record.format = '%.0f';
|
||||
record.format = '%.xf';
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -85,7 +85,7 @@
|
|||
>
|
||||
<template #label>
|
||||
<span>
|
||||
AgentID
|
||||
AgentId
|
||||
<j-tooltip title="应用唯一标识">
|
||||
<AIcon
|
||||
type="QuestionCircleOutlined"
|
||||
|
@ -98,7 +98,7 @@
|
|||
v-model:value="
|
||||
formData.template.agentId
|
||||
"
|
||||
placeholder="请输入AppSecret"
|
||||
placeholder="请输入AgentId"
|
||||
/>
|
||||
</j-form-item>
|
||||
<j-row :gutter="10">
|
||||
|
@ -271,7 +271,7 @@
|
|||
</template>
|
||||
<j-input
|
||||
v-model:value="formData.template.agentId"
|
||||
placeholder="请输入agentId"
|
||||
placeholder="请输入AgentId"
|
||||
/>
|
||||
</j-form-item>
|
||||
<j-row :gutter="10">
|
||||
|
@ -353,7 +353,9 @@
|
|||
placeholder="请输入标题"
|
||||
/>
|
||||
</j-form-item>
|
||||
<j-form-item>
|
||||
<j-form-item
|
||||
v-bind="validateInfos['template.sendTo']"
|
||||
>
|
||||
<template #label>
|
||||
<span>
|
||||
收件人
|
||||
|
@ -369,9 +371,8 @@
|
|||
</template>
|
||||
<j-select
|
||||
mode="tags"
|
||||
:options="[]"
|
||||
v-model:value="formData.template.sendTo"
|
||||
placeholder="请选择收件人"
|
||||
placeholder="请输入收件人邮箱,多个收件人用换行分隔"
|
||||
/>
|
||||
</j-form-item>
|
||||
<j-form-item>
|
||||
|
@ -459,7 +460,13 @@
|
|||
</j-form-item>
|
||||
</j-col>
|
||||
<j-col :span="12">
|
||||
<j-form-item>
|
||||
<j-form-item
|
||||
v-bind="
|
||||
validateInfos[
|
||||
'template.calledNumber'
|
||||
]
|
||||
"
|
||||
>
|
||||
<template #label>
|
||||
<span>
|
||||
被叫号码
|
||||
|
@ -507,7 +514,9 @@
|
|||
placeholder="请输入被叫显号"
|
||||
/>
|
||||
</j-form-item>
|
||||
<j-form-item>
|
||||
<j-form-item
|
||||
v-bind="validateInfos['template.playTimes']"
|
||||
>
|
||||
<template #label>
|
||||
<span>
|
||||
播放次数
|
||||
|
@ -519,9 +528,10 @@
|
|||
</j-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<j-input
|
||||
<j-input-number
|
||||
v-model:value="formData.template.playTimes"
|
||||
placeholder="请输入播放次数"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</j-form-item>
|
||||
<j-form-item
|
||||
|
@ -654,7 +664,6 @@
|
|||
<j-radio :value="false">自定义</j-radio>
|
||||
</j-radio-group>
|
||||
<j-textarea
|
||||
v-model:value="formData.template.body"
|
||||
placeholder="请求体中的数据来自于发送通知时指定的所有变量"
|
||||
v-if="formData.template.contextAsBody"
|
||||
disabled
|
||||
|
@ -694,7 +703,6 @@
|
|||
</template>
|
||||
<j-textarea
|
||||
v-model:value="formData.template.message"
|
||||
:maxlength="200"
|
||||
:rows="5"
|
||||
:disabled="formData.type === 'sms'"
|
||||
placeholder="变量格式:${name};
|
||||
|
@ -887,31 +895,64 @@ watch(
|
|||
const formRules = ref({
|
||||
type: [{ required: true, message: '请选择通知方式' }],
|
||||
name: [
|
||||
{ required: true, message: '请输入名称' },
|
||||
{ required: true, message: '请输入名称', trigger: 'blur' },
|
||||
{ max: 64, message: '最多可输入64个字符' },
|
||||
],
|
||||
provider: [{ required: true, message: '请选择类型' }],
|
||||
configId: [{ required: true, message: '请选择绑定配置' }],
|
||||
configId: [{ required: true, message: '请选择绑定配置', trigger: 'blur' }],
|
||||
// 钉钉
|
||||
'template.agentId': [{ required: true, message: '请输入agentId' }],
|
||||
'template.messageType': [{ required: true, message: '请选择消息类型' }],
|
||||
'template.agentId': [
|
||||
{ required: true, message: '请输入AgentId', trigger: 'blur' },
|
||||
{ max: 64, message: '最多可输入64个字符', trigger: 'change' },
|
||||
],
|
||||
'template.messageType': [
|
||||
{ required: true, message: '请选择消息类型', trigger: 'blur' },
|
||||
],
|
||||
'template.markdown.title': [
|
||||
{ required: true, message: '请输入标题' },
|
||||
{ max: 64, message: '最多可输入64个字符' },
|
||||
{ required: true, message: '请输入标题', trigger: 'blur' },
|
||||
{ max: 64, message: '最多可输入64个字符', trigger: 'change' },
|
||||
],
|
||||
'template.link.title': [
|
||||
{ required: true, message: '请输入标题' },
|
||||
{ max: 64, message: '最多可输入64个字符' },
|
||||
{ required: true, message: '请输入标题', trigger: 'blur' },
|
||||
{ max: 64, message: '最多可输入64个字符', trigger: 'change' },
|
||||
],
|
||||
// 'template.url': [{ required: true, message: '请输入WebHook' }],
|
||||
// 微信
|
||||
// 'template.agentId': [{ required: true, message: '请输入agentId' }],
|
||||
// 'template.agentId': [{ required: true, message: '请输入AgentId' }],
|
||||
// 邮件
|
||||
'template.subject': [{ required: true, message: '请输入标题' }],
|
||||
'template.subject': [
|
||||
{ required: true, message: '请输入标题', trigger: 'blur' },
|
||||
{ max: 64, message: '最多可输入64个字符', trigger: 'change' },
|
||||
],
|
||||
'template.sendTo': [
|
||||
{
|
||||
trigger: 'change',
|
||||
validator(_rule: Rule, value: string[]) {
|
||||
const regEmail =
|
||||
/^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/;
|
||||
let error;
|
||||
if (value) {
|
||||
value.some((item: string) => {
|
||||
if (!regEmail.test(item)) {
|
||||
error = item;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
if (error)
|
||||
return Promise.reject(error ? `${error}邮件格式错误` : '');
|
||||
else return Promise.resolve();
|
||||
},
|
||||
},
|
||||
],
|
||||
// 阿里云语音
|
||||
'template.templateType': [{ required: true, message: '请选择类型' }],
|
||||
'template.templateCode': [{ required: true, message: '请输入模板ID' }],
|
||||
'template.calledShowNumbers': [
|
||||
'template.templateCode': [
|
||||
{ required: true, message: '请输入模板ID', trigger: 'blur' },
|
||||
],
|
||||
'template.calledNumber': [
|
||||
{ max: 64, message: '最多可输入64个字符', trigger: 'change' },
|
||||
{
|
||||
trigger: 'change',
|
||||
validator(_rule: Rule, value: string) {
|
||||
|
@ -921,17 +962,43 @@ const formRules = ref({
|
|||
},
|
||||
},
|
||||
],
|
||||
'template.calledShowNumbers': [
|
||||
{ max: 64, message: '最多可输入64个字符', trigger: 'change' },
|
||||
{
|
||||
trigger: 'change',
|
||||
validator(_rule: Rule, value: string) {
|
||||
if (!value) return Promise.resolve();
|
||||
if (!phoneRegEx(value)) return Promise.reject('请输入有效号码');
|
||||
return Promise.resolve();
|
||||
},
|
||||
},
|
||||
],
|
||||
'template.playTimes': [
|
||||
{
|
||||
trigger: 'change',
|
||||
validator(_rule: Rule, value: number) {
|
||||
if (value < 1 || value > 3)
|
||||
return Promise.reject('仅支持1~3次');
|
||||
else return Promise.resolve();
|
||||
},
|
||||
},
|
||||
],
|
||||
// 短信
|
||||
'template.code': [{ required: true, message: '请选择模板' }],
|
||||
'template.signName': [{ required: true, message: '请输入签名' }],
|
||||
'template.code': [
|
||||
{ required: true, message: '请选择模板', trigger: 'blur' },
|
||||
],
|
||||
'template.signName': [
|
||||
{ required: true, message: '请输入签名', trigger: 'blur' },
|
||||
],
|
||||
// webhook
|
||||
description: [{ max: 200, message: '最多可输入200个字符' }],
|
||||
'template.message': [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入',
|
||||
message: '请输入模板内容',
|
||||
trigger: 'blur',
|
||||
},
|
||||
{ max: 500, message: '最多可输入500个字符' },
|
||||
{ max: 500, message: '最多可输入500个字符', trigger: 'change' },
|
||||
],
|
||||
'template.ttsmessage': [{ max: 500, message: '最多可输入500个字符' }],
|
||||
});
|
||||
|
@ -978,6 +1045,15 @@ watch(
|
|||
},
|
||||
{ deep: true },
|
||||
);
|
||||
// 模板内容变量提取
|
||||
watch(
|
||||
() => formData.value.template.ttsmessage,
|
||||
(val) => {
|
||||
if (!val) return;
|
||||
variableReg();
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
// webhook请求体变量提取
|
||||
watch(
|
||||
() => formData.value.template.body,
|
||||
|
@ -1004,7 +1080,9 @@ const spliceStr = () => {
|
|||
variableFieldsStr += formData.value.template.subject as string;
|
||||
if (formData.value.provider === 'http')
|
||||
variableFieldsStr += formData.value.template.body as string;
|
||||
// console.log('variableFieldsStr: ', variableFieldsStr);
|
||||
if (formData.value.provider === 'aliyun')
|
||||
variableFieldsStr += formData.value.template.ttsmessage as string;
|
||||
|
||||
return variableFieldsStr || '';
|
||||
};
|
||||
|
||||
|
@ -1061,7 +1139,6 @@ const handleMessageTypeChange = () => {
|
|||
};
|
||||
}
|
||||
formData.value.variableDefinitions = [];
|
||||
// formData.value.template.message = '';
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -1072,7 +1149,6 @@ const getDetail = async () => {
|
|||
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();
|
||||
|
@ -1107,8 +1183,7 @@ const handleTypeChange = () => {
|
|||
const handleProviderChange = () => {
|
||||
formData.value.template =
|
||||
TEMPLATE_FIELD_MAP[formData.value.type][formData.value.provider];
|
||||
// console.log('formData.value: ', formData.value);
|
||||
// console.log('formData.value.template: ', formData.value.template);
|
||||
|
||||
getConfigList();
|
||||
resetPublicFiles();
|
||||
};
|
||||
|
@ -1176,7 +1251,6 @@ const handleSubmit = () => {
|
|||
delete formData.value.template.link;
|
||||
if (formData.value.template.messageType === 'link')
|
||||
delete formData.value.template.markdown;
|
||||
// console.log('formData.value: ', formData.value);
|
||||
// 提交必填验证无法通过, 实际已有值, 问题未知, 暂时解决方法: 延迟验证
|
||||
setTimeout(() => {
|
||||
validate()
|
||||
|
@ -1192,13 +1266,11 @@ const handleSubmit = () => {
|
|||
}
|
||||
|
||||
btnLoading.value = true;
|
||||
let res;
|
||||
if (!formData.value.id) {
|
||||
res = await templateApi.save(formData.value);
|
||||
} else {
|
||||
res = await templateApi.update(formData.value);
|
||||
}
|
||||
// console.log('res: ', res);
|
||||
|
||||
const res = formData.value.id
|
||||
? await templateApi.update(formData.value)
|
||||
: await templateApi.save(formData.value);
|
||||
|
||||
if (res?.success) {
|
||||
message.success('保存成功');
|
||||
router.back();
|
||||
|
@ -1212,14 +1284,4 @@ const handleSubmit = () => {
|
|||
});
|
||||
}, 200);
|
||||
};
|
||||
|
||||
// test
|
||||
// watch(
|
||||
// () => formData.value,
|
||||
// (val) => {
|
||||
// console.log('formData.value: ', val);
|
||||
// },
|
||||
// { deep: true },
|
||||
// );
|
||||
// test
|
||||
</script>
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
</PermissionButton>
|
||||
<a-upload
|
||||
name="file"
|
||||
accept="json"
|
||||
accept=".json"
|
||||
:showUploadList="false"
|
||||
:before-upload="beforeUpload"
|
||||
>
|
||||
|
@ -110,14 +110,19 @@
|
|||
</template>
|
||||
</CardBox>
|
||||
</template>
|
||||
<template #bodyCell="{ column, text, record }">
|
||||
<span v-if="column.dataIndex === 'type'">
|
||||
{{ getMethodTxt(record.type) }}
|
||||
</span>
|
||||
<span v-if="column.dataIndex === 'provider'">
|
||||
{{ getProviderTxt(record.type, record.provider) }}
|
||||
<template #type="slotProps">
|
||||
<span> {{ getMethodTxt(slotProps.type) }}</span>
|
||||
</template>
|
||||
<template #provider="slotProps">
|
||||
<span>
|
||||
{{ getProviderTxt(slotProps.type, slotProps.provider) }}
|
||||
</span>
|
||||
</template>
|
||||
<!-- <template #description="slotProps">
|
||||
<Ellipsis>
|
||||
{{ slotProps.description }}
|
||||
</Ellipsis>
|
||||
</template> -->
|
||||
<template #action="slotProps">
|
||||
<j-space :size="16">
|
||||
<template
|
||||
|
@ -150,12 +155,8 @@
|
|||
<script setup lang="ts">
|
||||
import TemplateApi from '@/api/notice/template';
|
||||
import type { ActionsType } from '@/components/Table/index.vue';
|
||||
// import { getImage, LocalStore } from '@/utils/comm';
|
||||
import { message } from 'ant-design-vue';
|
||||
// import { BASE_API_PATH, TOKEN_KEY } from '@/utils/variable';
|
||||
|
||||
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';
|
||||
|
@ -210,6 +211,8 @@ const columns = [
|
|||
title: '说明',
|
||||
dataIndex: 'description',
|
||||
key: 'description',
|
||||
scopedSlots: true,
|
||||
ellipsis: true,
|
||||
search: {
|
||||
type: 'string',
|
||||
},
|
||||
|
|
|
@ -82,7 +82,7 @@ export const MSG_TYPE = {
|
|||
],
|
||||
email: [
|
||||
{
|
||||
label: 'email',
|
||||
label: '邮件',
|
||||
value: 'embedded',
|
||||
logo: getImage('/notice/email.png'),
|
||||
},
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<a-modal
|
||||
<j-modal
|
||||
:maskClosable="false"
|
||||
width="45vw"
|
||||
title="编辑"
|
||||
|
@ -9,8 +9,8 @@
|
|||
cancelText="取消"
|
||||
okText="确定"
|
||||
>
|
||||
<a-form layout="vertical" :model="inputData">
|
||||
<a-form-item
|
||||
<j-form layout="vertical" :model="inputData" ref="formRef">
|
||||
<j-form-item
|
||||
label="kafka地址"
|
||||
name="address"
|
||||
:rules="[
|
||||
|
@ -20,12 +20,12 @@
|
|||
},
|
||||
]"
|
||||
>
|
||||
<a-input
|
||||
<j-input
|
||||
v-model:value="inputData.address"
|
||||
placeholder="请输入kafka地址"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
></j-input>
|
||||
</j-form-item>
|
||||
<j-form-item
|
||||
label="topic"
|
||||
name="topic"
|
||||
:rules="[
|
||||
|
@ -35,17 +35,17 @@
|
|||
},
|
||||
]"
|
||||
>
|
||||
<a-input v-model:value="inputData.topic"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="状态">
|
||||
<a-switch
|
||||
<j-input v-model:value="inputData.topic"></j-input>
|
||||
</j-form-item>
|
||||
<j-form-item label="状态">
|
||||
<j-switch
|
||||
checked-children="启用"
|
||||
un-checked-children="启用"
|
||||
v-model:checked="inputData.status"
|
||||
></a-switch>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
></j-switch>
|
||||
</j-form-item>
|
||||
</j-form>
|
||||
</j-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
@ -53,6 +53,7 @@ import { Form } from 'ant-design-vue';
|
|||
import { saveOutputData } from '@/api/rule-engine/config';
|
||||
import { message } from 'ant-design-vue/es';
|
||||
const useForm = Form.useForm;
|
||||
const formRef = ref();
|
||||
const Myprops = defineProps({
|
||||
data: {
|
||||
default: '',
|
||||
|
@ -74,6 +75,7 @@ const close = () => {
|
|||
emit('closeModel');
|
||||
};
|
||||
const save = () => {
|
||||
formRef.value.validateFields().then(() => {
|
||||
saveOutputData({
|
||||
config: {
|
||||
sourceType: 'kafka',
|
||||
|
@ -92,6 +94,7 @@ const save = () => {
|
|||
emit('saveSuc');
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
const emit = defineEmits(['closeModel', 'saveSuc']);
|
||||
</script>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<a-modal
|
||||
<j-modal
|
||||
:maskClosable="false"
|
||||
width="45vw"
|
||||
title="编辑"
|
||||
|
@ -9,15 +9,15 @@
|
|||
cancelText="取消"
|
||||
okText="确定"
|
||||
>
|
||||
<a-form layout="vertical" :model="outputData">
|
||||
<a-form-item label="状态">
|
||||
<a-switch
|
||||
<j-form layout="vertical" :model="outputData" ref="formRef">
|
||||
<j-form-item label="状态">
|
||||
<j-switch
|
||||
checked-children="启用"
|
||||
un-checked-children="启用"
|
||||
v-model:checked="outputData.status"
|
||||
></a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
></j-switch>
|
||||
</j-form-item>
|
||||
<j-form-item
|
||||
v-if="outputData.status"
|
||||
label="kafka地址"
|
||||
name="address"
|
||||
|
@ -32,12 +32,12 @@
|
|||
},
|
||||
]"
|
||||
>
|
||||
<a-input
|
||||
<j-input
|
||||
v-model:value="outputData.address"
|
||||
placeholder="请输入kafka地址"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
></j-input>
|
||||
</j-form-item>
|
||||
<j-form-item
|
||||
v-if="outputData.status"
|
||||
label="topic"
|
||||
name="topic"
|
||||
|
@ -52,16 +52,17 @@
|
|||
},
|
||||
]"
|
||||
>
|
||||
<a-input v-model:value="outputData.topic"></a-input>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
<j-input v-model:value="outputData.topic"></j-input>
|
||||
</j-form-item>
|
||||
</j-form>
|
||||
</j-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { Form } from 'ant-design-vue';
|
||||
import { saveOutputData } from '@/api/rule-engine/config';
|
||||
import { message } from 'ant-design-vue/es';
|
||||
const formRef = ref();
|
||||
const useForm = Form.useForm;
|
||||
const Myprops = defineProps({
|
||||
data: {
|
||||
|
@ -84,6 +85,7 @@ const close = () => {
|
|||
emit('closeModel');
|
||||
};
|
||||
const save = () => {
|
||||
formRef.value.validateFields().then(() => {
|
||||
saveOutputData({
|
||||
config: {
|
||||
sourceType: 'kafka',
|
||||
|
@ -102,6 +104,7 @@ const save = () => {
|
|||
emit('saveSuc');
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
const emit = defineEmits(['closeModel', 'saveSuc']);
|
||||
</script>
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
<template>
|
||||
<div>
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="14">
|
||||
<j-row :gutter="24">
|
||||
<j-col :span="14">
|
||||
<div class="alarmFlow-left">
|
||||
<a-card
|
||||
<j-card
|
||||
:head-style="{ borderBottom: 'none', height: '30px' }"
|
||||
:bordered="false"
|
||||
>
|
||||
<template #title>
|
||||
<div class="alarmTitle">
|
||||
<span>告警数据输出</span>
|
||||
<a-tooltip
|
||||
<j-tooltip
|
||||
title="将告警数据输出到其他第三方系统"
|
||||
>
|
||||
<AIcon
|
||||
|
@ -20,7 +20,7 @@
|
|||
line-height: 35px;
|
||||
"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</j-tooltip>
|
||||
|
||||
<PermissionButton
|
||||
type="link"
|
||||
|
@ -33,16 +33,16 @@
|
|||
</PermissionButton>
|
||||
</div>
|
||||
</template>
|
||||
<a-descriptions
|
||||
<j-descriptions
|
||||
bordered
|
||||
:labelStyle="{ width: 112 + 'px' }"
|
||||
:contentStyle="{ minWidth: 100 + 'px' }"
|
||||
:column="2"
|
||||
>
|
||||
<a-descriptions-item
|
||||
<j-descriptions-item
|
||||
label="kafka地址"
|
||||
:content-style="{ minWidth: '200px' }"
|
||||
><a-badge
|
||||
><j-badge
|
||||
:status="
|
||||
output?.running ? 'success' : 'error'
|
||||
"
|
||||
|
@ -50,31 +50,31 @@
|
|||
output?.data?.config?.config?.address ||
|
||||
''
|
||||
"
|
||||
></a-badge
|
||||
></a-descriptions-item>
|
||||
<a-descriptions-item label="topic">{{
|
||||
></j-badge
|
||||
></j-descriptions-item>
|
||||
<j-descriptions-item label="topic">{{
|
||||
output?.data?.config?.config?.topic || ''
|
||||
}}</a-descriptions-item>
|
||||
<a-descriptions-item label="状态" :span="2"
|
||||
><a-badge
|
||||
}}</j-descriptions-item>
|
||||
<j-descriptions-item label="状态" :span="2"
|
||||
><j-badge
|
||||
:status="
|
||||
output?.data?.state?.value === 'enabled'
|
||||
? 'success'
|
||||
: 'error'
|
||||
"
|
||||
:text="output?.data?.state?.text || ''"
|
||||
></a-badge
|
||||
></a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-card>
|
||||
<a-card
|
||||
></j-badge
|
||||
></j-descriptions-item>
|
||||
</j-descriptions>
|
||||
</j-card>
|
||||
<j-card
|
||||
:head-style="{ borderBottom: 'none', height: '30px' }"
|
||||
:bordered="false"
|
||||
>
|
||||
<template #title>
|
||||
<div class="alarmTitle">
|
||||
<span>告警处理结果输入</span>
|
||||
<a-tooltip title="接收第三方系统处理的告警结果">
|
||||
<j-tooltip title="接收第三方系统处理的告警结果">
|
||||
<AIcon
|
||||
type="QuestionCircleOutlined"
|
||||
style="
|
||||
|
@ -82,7 +82,7 @@
|
|||
line-height: 35px;
|
||||
"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</j-tooltip>
|
||||
<PermissionButton
|
||||
type="link"
|
||||
@click="showInput"
|
||||
|
@ -93,14 +93,14 @@
|
|||
></PermissionButton>
|
||||
</div>
|
||||
</template>
|
||||
<a-descriptions
|
||||
<j-descriptions
|
||||
bordered
|
||||
:labelStyle="{ width: 112 + 'px' }"
|
||||
:contentStyle="{ minWidth: 150 + 'px' }"
|
||||
:column="2"
|
||||
>
|
||||
<a-descriptions-item label="kafka地址"
|
||||
><a-badge
|
||||
<j-descriptions-item label="kafka地址"
|
||||
><j-badge
|
||||
:status="
|
||||
input?.running ? 'success' : 'error'
|
||||
"
|
||||
|
@ -108,34 +108,34 @@
|
|||
input?.data?.config?.config?.address ||
|
||||
''
|
||||
"
|
||||
></a-badge
|
||||
></a-descriptions-item>
|
||||
<a-descriptions-item label="topic">{{
|
||||
></j-badge
|
||||
></j-descriptions-item>
|
||||
<j-descriptions-item label="topic">{{
|
||||
input?.data?.config?.config?.topic || ''
|
||||
}}</a-descriptions-item>
|
||||
<a-descriptions-item label="状态" :span="2"
|
||||
><a-badge
|
||||
}}</j-descriptions-item>
|
||||
<j-descriptions-item label="状态" :span="2"
|
||||
><j-badge
|
||||
:status="
|
||||
input?.data?.state?.value === 'enabled'
|
||||
? 'success'
|
||||
: 'error'
|
||||
"
|
||||
:text="input?.data?.state?.text || ''"
|
||||
></a-badge
|
||||
></a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-card>
|
||||
></j-badge
|
||||
></j-descriptions-item>
|
||||
</j-descriptions>
|
||||
</j-card>
|
||||
</div>
|
||||
</a-col>
|
||||
<a-col :span="10">
|
||||
</j-col>
|
||||
<j-col :span="10">
|
||||
<div class="alarmFlow-right">
|
||||
<div class="doc">
|
||||
<h1>功能图示</h1>
|
||||
<div class="image">
|
||||
<a-image
|
||||
<j-image
|
||||
width="100%"
|
||||
:src="getImage('/alarm/io.png')"
|
||||
></a-image>
|
||||
></j-image>
|
||||
</div>
|
||||
<h1>功能说明</h1>
|
||||
<div>
|
||||
|
@ -143,11 +143,11 @@
|
|||
</div>
|
||||
<h2>输出参数</h2>
|
||||
<div>
|
||||
<a-table
|
||||
<j-table
|
||||
:dataSource="outputData"
|
||||
:pagination="false"
|
||||
:columns="outputColumns"
|
||||
></a-table>
|
||||
></j-table>
|
||||
</div>
|
||||
<h2>示例</h2>
|
||||
<div v-html="markdownOutputText" class="code"></div>
|
||||
|
@ -156,18 +156,18 @@
|
|||
</div>
|
||||
<h2>订阅参数</h2>
|
||||
<div>
|
||||
<a-table
|
||||
<j-table
|
||||
:dataSource="subData"
|
||||
:pagination="false"
|
||||
:columns="subColumns"
|
||||
></a-table>
|
||||
></j-table>
|
||||
</div>
|
||||
<h2>示例</h2>
|
||||
<div class="code" v-html="markdownSubText"></div>
|
||||
</div>
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</j-col>
|
||||
</j-row>
|
||||
<InputSave
|
||||
:data="input"
|
||||
v-if="inputVisible"
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
<template>
|
||||
<page-container :tabList="list" @tabChange="onTabChange" :tabActiveKey="tab">
|
||||
<div v-if="tab=='config'">
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="14">
|
||||
<j-row :gutter="24">
|
||||
<j-col :span="14">
|
||||
<div class="alarm-level">
|
||||
<a-card
|
||||
<j-card
|
||||
:headStyle="{ borderBottom: 'none', padding: 0 }"
|
||||
:bodyStyle="{ padding: 0 }"
|
||||
:bordered="false"
|
||||
|
@ -27,24 +27,24 @@
|
|||
<span>{{ `级别${i + 1}` }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<a-input
|
||||
<j-input
|
||||
type="text"
|
||||
v-model:value="item.title"
|
||||
:maxlength="64"
|
||||
></a-input>
|
||||
></j-input>
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
<!-- <a-button
|
||||
</j-card>
|
||||
<!-- <j-button
|
||||
type="primary"
|
||||
size="middle"
|
||||
@click="handleSaveLevel"
|
||||
>保存</a-button
|
||||
>保存</j-button
|
||||
> -->
|
||||
<PermissionButton type="primary" size="middle" @click="handleSaveLevel" hasPermission="rule-engine/Alarm/Config:update">保存</PermissionButton>
|
||||
</div>
|
||||
</a-col>
|
||||
<a-col :span="10">
|
||||
</j-col>
|
||||
<j-col :span="10">
|
||||
<div class="description">
|
||||
<h1>功能说明</h1>
|
||||
<div>
|
||||
|
@ -53,8 +53,8 @@
|
|||
<div>2、告警级别将会在告警配置中被引用。</div>
|
||||
<div>3、最多可配置5个级别。</div>
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</j-col>
|
||||
</j-row>
|
||||
</div>
|
||||
<Io v-else></Io>
|
||||
</page-container>
|
||||
|
|
|
@ -1,27 +1,27 @@
|
|||
<template>
|
||||
<div>
|
||||
<a-form layout="vertical" :rules="rule" :model="form" ref="formRef">
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="名称" name="name">
|
||||
<a-input
|
||||
<j-form layout="vertical" :rules="rule" :model="form" ref="formRef">
|
||||
<j-row :gutter="24">
|
||||
<j-col :span="12">
|
||||
<j-form-item label="名称" name="name">
|
||||
<j-input
|
||||
placeholder="请输入名称"
|
||||
v-model:value="form.name"
|
||||
></a-input> </a-form-item
|
||||
></a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="类型" name="targetType">
|
||||
<a-select
|
||||
></j-input> </j-form-item
|
||||
></j-col>
|
||||
<j-col :span="12">
|
||||
<j-form-item label="类型" name="targetType">
|
||||
<j-select
|
||||
:options="options"
|
||||
v-model:value="form.targetType"
|
||||
:disabled="selectDisable"
|
||||
></a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-form-item label="级别" name="level">
|
||||
<a-radio-group v-model:value="form.level">
|
||||
<a-radio-button
|
||||
></j-select>
|
||||
</j-form-item>
|
||||
</j-col>
|
||||
</j-row>
|
||||
<j-form-item label="级别" name="level">
|
||||
<j-radio-group v-model:value="form.level">
|
||||
<j-radio-button
|
||||
v-for="(item, index) in levelOption"
|
||||
:key="index"
|
||||
:value="item.value"
|
||||
|
@ -40,14 +40,14 @@
|
|||
alt=""
|
||||
/>{{ item.label }}
|
||||
</div>
|
||||
</a-radio-button>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item label="说明" name="description">
|
||||
<a-textarea v-model:value="form.description"></a-textarea>
|
||||
</a-form-item>
|
||||
</j-radio-button>
|
||||
</j-radio-group>
|
||||
</j-form-item>
|
||||
<j-form-item label="说明" name="description">
|
||||
<j-textarea v-model:value="form.description"></j-textarea>
|
||||
</j-form-item>
|
||||
<PermissionButton type="primary" @click="handleSave" :hasPermission="['rule-engine/Alarm/Configuration:add','rule-engine/Alarm/Configuration:update']">保存</PermissionButton>
|
||||
</a-form>
|
||||
</j-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -178,7 +178,7 @@ const handleSave = async () => {
|
|||
const res = await save(form);
|
||||
loading.value = false;
|
||||
if (res.status === 200) {
|
||||
message.success('操作成功');
|
||||
message.success('操作成功,请配置关联的场景联动');
|
||||
menuStory.jumpPage(
|
||||
'rule-engine/Alarm/Configuration/Save',
|
||||
{},
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<a-modal
|
||||
<j-modal
|
||||
visible
|
||||
title="新增"
|
||||
okText="确定"
|
||||
|
@ -8,7 +8,7 @@
|
|||
@cancel="closeModal"
|
||||
@ok="saveCorrelation"
|
||||
>
|
||||
<Search :columns="columns" @search="handleSearch"></Search>
|
||||
<pro-search :columns="columns" @search="handleSearch"/>
|
||||
<div style="height: 500px; overflow-y: auto">
|
||||
<JProTable
|
||||
model="CARD"
|
||||
|
@ -78,7 +78,7 @@
|
|||
</template>
|
||||
</JProTable>
|
||||
</div>
|
||||
</a-modal>
|
||||
</j-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
@ -213,7 +213,7 @@ const saveCorrelation = async () => {
|
|||
const list = _selectedRowKeys.value.map((item: any) => {
|
||||
return {
|
||||
alarmId: props.id,
|
||||
releId: item,
|
||||
ruleId: item,
|
||||
};
|
||||
});
|
||||
const res = await bindScene([...list]);
|
||||
|
|
|
@ -10,12 +10,12 @@
|
|||
ref="actionRef"
|
||||
>
|
||||
<template #headerTitle>
|
||||
<a-space>
|
||||
<j-space>
|
||||
<PermissionButton type="primary" @click="showModal" hasPermission="rule-engine/Alarm/Configuration:add">
|
||||
<template #icon><AIcon type="PlusOutlined" /></template>
|
||||
新增
|
||||
</PermissionButton>
|
||||
</a-space>
|
||||
</j-space>
|
||||
</template>
|
||||
<template #card="slotProps">
|
||||
<SceneCard
|
||||
|
|
|
@ -1,25 +1,35 @@
|
|||
<template>
|
||||
<page-container>
|
||||
<a-card>
|
||||
<a-tabs v-model:activeKey="activeKey">
|
||||
<a-tab-pane key="1" tab="基础配置">
|
||||
<j-card>
|
||||
<j-tabs :activeKey="activeKey" @change="changeTabs">
|
||||
<j-tab-pane key="1" tab="基础配置">
|
||||
<Base />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="2" tab="关联场景联动">
|
||||
</j-tab-pane>
|
||||
<j-tab-pane key="2" tab="关联场景联动">
|
||||
<Scene></Scene>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="3" tab="告警记录">
|
||||
<Log/>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</a-card>
|
||||
</j-tab-pane>
|
||||
<j-tab-pane key="3" tab="告警记录">
|
||||
<Log v-if="activeKey === '3'" />
|
||||
</j-tab-pane>
|
||||
</j-tabs>
|
||||
</j-card>
|
||||
</page-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import Base from './Base/index.vue';
|
||||
import Scene from './Scene/index.vue'
|
||||
import Log from './Log/indev.vue'
|
||||
import Scene from './Scene/index.vue';
|
||||
import Log from './Log/indev.vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { message } from 'ant-design-vue';
|
||||
const route = useRoute();
|
||||
const changeTabs = (e: any) => {
|
||||
if (route.query?.id) {
|
||||
activeKey.value = e;
|
||||
} else {
|
||||
message.error('请先保存基础配置');
|
||||
}
|
||||
};
|
||||
const activeKey = ref('1');
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue