fix: 合并冲突
This commit is contained in:
commit
86c15261e8
2
.npmrc
2
.npmrc
|
@ -1,2 +1,2 @@
|
|||
always-auth=true
|
||||
registry=http://47.108.170.157:9013/
|
||||
registry=http://registry.jetlinks.cn/
|
|
@ -23,14 +23,14 @@
|
|||
"event-source-polyfill": "^1.0.31",
|
||||
"global": "^4.4.0",
|
||||
"jetlinks-store": "^0.0.3",
|
||||
"jetlinks-ui-components": "^1.0.0",
|
||||
"js-cookie": "^3.0.1",
|
||||
"less": "^4.1.3",
|
||||
"less-loader": "^11.1.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"marked": "^4.2.12",
|
||||
"mavon-editor": "^2.10.4",
|
||||
"moment": "^2.29.4",
|
||||
"monaco-editor": "^0.24.0",
|
||||
"monaco-editor": "^0.36.0",
|
||||
"nrm": "^1.2.5",
|
||||
"pinia": "^2.0.28",
|
||||
"unplugin-auto-import": "^0.12.1",
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
import server from '@/utils/request'
|
||||
|
||||
export const getSsoBinds_api = (): any =>server.get(`/application/sso/me/bindings`)
|
|
@ -485,11 +485,60 @@ export const getPropertiesInfo = (deviceId: string, data: Record<string, unknown
|
|||
export const getPropertiesList = (deviceId: string, property: string, data: Record<string, unknown>) => server.post(`/device-instance/${deviceId}/property/${property}/_query`, data)
|
||||
|
||||
/**
|
||||
* 获取指定协议
|
||||
* @param id
|
||||
* @param transport
|
||||
* @returns
|
||||
*/
|
||||
export const getProtocal = (id: string, transport: string) => server.get(`/protocol/${id}/transport/${transport}`)
|
||||
|
||||
/**
|
||||
* 获取产品解析规则
|
||||
* @param productId
|
||||
* @returns
|
||||
*/
|
||||
export const productCode = (productId: string) => server.get(`/device/transparent-codec/${productId}`)
|
||||
/**
|
||||
* 保存产品解析规则
|
||||
* @param productId
|
||||
* @returns
|
||||
*/
|
||||
export const saveProductCode = (productId: string,data: Record<string, unknown>) => server.post(`/device/transparent-codec/${productId}`,data)
|
||||
/**
|
||||
* 获取设备解析规则
|
||||
* @param productId
|
||||
* @param deviceId
|
||||
* @returns
|
||||
*/
|
||||
export const deviceCode = (productId: string,deviceId:string) => server.get(`device/transparent-codec/${productId}/${deviceId}`)
|
||||
/**
|
||||
* 保存设备解析规则
|
||||
* @param productId
|
||||
* 查询设备日志
|
||||
* @param deviceId
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
export const saveDeviceCode = (productId: string,deviceId:string,data: Record<string, unknown>) => server.post(`/device/transparent-codec/${productId}/${deviceId}`,data)
|
||||
/**
|
||||
* 编码测试
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
export const testCode = (data: Record<string, unknown>) => server.post(`/device/transparent-codec/decode-test`,data)
|
||||
/**
|
||||
* 删除设备解析规则
|
||||
* @param productId
|
||||
* @param deviceId
|
||||
* @returns
|
||||
*/
|
||||
export const delDeviceCode = (productId: string, deviceId: string) => server.remove(`/device/transparent-codec/${productId}/${deviceId}`)
|
||||
/**
|
||||
* 删除产品解析规则
|
||||
* @param productId
|
||||
* @returns
|
||||
*/
|
||||
export const delProductCode = (productId: string) => server.remove(`/device/transparent-codec/${productId}`)
|
||||
export const queryLog = (deviceId: string, data: Record<string, unknown>) => server.post(`/device-instance/${deviceId}/logs`, data)
|
||||
|
||||
/**
|
||||
|
|
|
@ -4,8 +4,6 @@ import type { CascadeItem } from '@/views/media/Cascade/typings'
|
|||
export default {
|
||||
// 列表
|
||||
list: (data: any) => server.post<any>(`/media/gb28181-cascade/_query`, data),
|
||||
// 列表字段通道数量, 来自下面接口的total
|
||||
queryCount: (id: string) => server.post<any>(`/media/gb28181-cascade/${id}/bindings/_query`),
|
||||
// 详情
|
||||
detail: (id: string): any => server.get(`/media/gb28181-cascade/${id}`),
|
||||
// 新增
|
||||
|
@ -26,4 +24,17 @@ export default {
|
|||
// SIP本地地址
|
||||
all: () => server.get<any>(`/network/resources/alive/_all`),
|
||||
|
||||
// 查询已绑定的通道, list列表字段通道数量, 来自下面接口的total
|
||||
queryBindChannel: (id: string, data: any) => server.post<any>(`/media/gb28181-cascade/${id}/bindings/_query`, data),
|
||||
// 绑定通道
|
||||
bindChannel: (id: string, data: string[]) => server.post<any>(`/media/gb28181-cascade/${id}/_bind`, data),
|
||||
// 解绑
|
||||
unbindChannel: (id: string, data: string[]) => server.post<any>(`/media/gb28181-cascade/${id}/_unbind`, data),
|
||||
// 验证国标ID是否存在
|
||||
validateField: (id: string, data: string[]): any => server.post(`/media/gb28181-cascade/${id}/gbChannelId/_validate`, data),
|
||||
// 更改国标ID
|
||||
updateGbChannelId: (id: string, data: any): any => server.post(`/media/gb28181-cascade/binding/${id}`, data),
|
||||
// 查询通道分页列表
|
||||
queryChannelList: (data: any): any => server.post(`media/channel/_query`, data),
|
||||
|
||||
}
|
|
@ -19,3 +19,8 @@ export const getOrgList = (parmas?:any) => server.get('/organization/_query/no-p
|
|||
* 搜索
|
||||
*/
|
||||
export const query = (data:any) => server.post('/alarm/record/_query/',data);
|
||||
|
||||
/**
|
||||
* 告警处理
|
||||
*/
|
||||
export const handleLog = (data:any) => server.post('/alarm/record/_handle',data)
|
|
@ -8,3 +8,14 @@ export const save = (data: any) => server.post(`/scene`, data)
|
|||
export const detail = (id: string) => server.get(`/scene/${id}`)
|
||||
|
||||
export const query = (data: any) => server.post('/scene/_query/',data);
|
||||
|
||||
export const _delete = (id: string) => server.remove(`/scene/${id}/`);
|
||||
|
||||
export const _action = (id: string, type: '_disable' | '_enable') => server.put(`/scene/${id}/${type}`);
|
||||
|
||||
/**
|
||||
* 手动触发
|
||||
* @param id
|
||||
* @returns
|
||||
*/
|
||||
export const _execute = (id: string) => server.post(`/scene/${id}/_execute`);
|
|
@ -12,7 +12,7 @@ export const delApply_api = (id: string) => server.remove(`/application/${id}`)
|
|||
|
||||
// 获取组织列表
|
||||
export const getDepartmentList_api = () => server.get(`/organization/_all/tree`);
|
||||
// 获取组织列表
|
||||
// 获取应用详情
|
||||
export const getAppInfo_api = (id: string) => server.get(`/application/${id}`);
|
||||
// 新增应用
|
||||
export const addApp_api = (data: object) => server.post(`/application`, data);
|
||||
|
|
|
@ -58,9 +58,12 @@ const iconKeys = [
|
|||
'PauseOutlined',
|
||||
'ControlOutlined',
|
||||
'RedoOutlined',
|
||||
'ExpandOutlined',
|
||||
'VideoCameraOutlined',
|
||||
'HistoryOutlined',
|
||||
'CalendarOutlined',
|
||||
'ToolOutlined',
|
||||
'FileOutlined',
|
||||
'LikeOutlined',
|
||||
]
|
||||
|
||||
const Icon = (props: {type: string}) => {
|
||||
|
|
|
@ -75,7 +75,7 @@ watchEffect(() => {
|
|||
});
|
||||
|
||||
const insert = (val) => {
|
||||
if (!instance) return
|
||||
if (!instance) return;
|
||||
const position = instance.getPosition();
|
||||
instance.executeEdits(instance.getValue(), [
|
||||
{
|
||||
|
@ -88,17 +88,19 @@ const insert = (val) => {
|
|||
text: val,
|
||||
},
|
||||
]);
|
||||
}
|
||||
};
|
||||
|
||||
watch(() => props.modelValue,
|
||||
(val) => {
|
||||
instance.setValue(val)
|
||||
})
|
||||
// watch(
|
||||
// () => props.modelValue,
|
||||
// (val) => {
|
||||
// instance.setValue(val);
|
||||
// },
|
||||
// );
|
||||
|
||||
defineExpose({
|
||||
editorFormat,
|
||||
insert,
|
||||
})
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
|
|
@ -4,14 +4,14 @@
|
|||
<a-popconfirm v-bind="popConfirm" :disabled="!isPermission || props.disabled">
|
||||
<a-tooltip v-if="tooltip" v-bind="tooltip">
|
||||
<slot v-if="noButton"></slot>
|
||||
<a-button v-else v-bind="_buttonProps" :disabled="_isPermission" :style="props.style">
|
||||
<a-button v-else v-bind="props" :disabled="_isPermission" :style="props.style">
|
||||
<slot></slot>
|
||||
<template #icon>
|
||||
<slot name="icon"></slot>
|
||||
</template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-button v-else v-bind="_buttonProps" :disabled="_isPermission" >
|
||||
<a-button v-else v-bind="props" :disabled="_isPermission" >
|
||||
<slot></slot>
|
||||
<template #icon>
|
||||
<slot name="icon"></slot>
|
||||
|
@ -22,7 +22,7 @@
|
|||
<template v-else-if="tooltip">
|
||||
<a-tooltip v-bind="tooltip">
|
||||
<slot v-if="noButton"></slot>
|
||||
<a-button v-else v-bind="_buttonProps" :disabled="_isPermission" :style="props.style">
|
||||
<a-button v-else v-bind="props" :disabled="_isPermission" :style="props.style">
|
||||
<slot></slot>
|
||||
<template #icon>
|
||||
<slot name="icon"></slot>
|
||||
|
@ -32,7 +32,7 @@
|
|||
</template>
|
||||
<template v-else>
|
||||
<slot v-if="noButton"></slot>
|
||||
<a-button v-else v-bind="_buttonProps" :disabled="_isPermission" :style="props.style">
|
||||
<a-button v-else v-bind="props" :disabled="_isPermission" :style="props.style">
|
||||
<slot></slot>
|
||||
<template #icon>
|
||||
<slot name="icon"></slot>
|
||||
|
@ -42,7 +42,7 @@
|
|||
</template>
|
||||
<a-tooltip v-else title="没有权限">
|
||||
<slot v-if="noButton"></slot>
|
||||
<a-button v-else v-bind="_buttonProps" :disabled="_isPermission" :style="props.style">
|
||||
<a-button v-else v-bind="props" :disabled="_isPermission" :style="props.style">
|
||||
<slot></slot>
|
||||
<template #icon>
|
||||
<slot name="icon"></slot>
|
||||
|
@ -91,7 +91,7 @@ const props = defineProps({
|
|||
...buttonProps()
|
||||
})
|
||||
|
||||
const { tooltip, popConfirm, hasPermission, noButton, ..._buttonProps } = props;
|
||||
// const { tooltip, popConfirm, hasPermission, noButton, ..._buttonProps } = props;
|
||||
|
||||
const permissionStore = usePermissionStore()
|
||||
|
||||
|
@ -103,8 +103,8 @@ const isPermission = computed(() => {
|
|||
})
|
||||
const _isPermission = computed(() =>
|
||||
'hasPermission' in props && isPermission.value
|
||||
? 'disabled' in _buttonProps
|
||||
? _buttonProps.disabled as boolean
|
||||
? 'disabled' in props
|
||||
? props.disabled as boolean
|
||||
: false
|
||||
: true
|
||||
)
|
||||
|
|
|
@ -158,7 +158,6 @@ const JTable = defineComponent<JTableProps>({
|
|||
const pageSize = ref<number>(6)
|
||||
const total = ref<number>(0)
|
||||
const loading = ref<boolean>(true)
|
||||
const loading1 = ref<boolean>(true)
|
||||
|
||||
const _columns = computed(() => props.columns.filter(i => !(i?.hideInTable)))
|
||||
|
||||
|
@ -240,6 +239,7 @@ const JTable = defineComponent<JTableProps>({
|
|||
)
|
||||
|
||||
onMounted(() => {
|
||||
windowChange() // 初始化
|
||||
window.onresize = () => {
|
||||
windowChange()
|
||||
}
|
||||
|
|
|
@ -4,14 +4,14 @@ import store from './store'
|
|||
import components from './components'
|
||||
import router from './router'
|
||||
import './style.less'
|
||||
import 'ant-design-vue/es/notification/style/css';
|
||||
// import jConmonents from 'jetlinks-ui-components'
|
||||
// import 'jetlinks-ui-components/lib/style'
|
||||
import jComponents from 'jetlinks-ui-components'
|
||||
import 'jetlinks-ui-components/es/style.js'
|
||||
import 'jetlinks-ui-components/es/style/variable.less'
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
app.use(store)
|
||||
.use(router)
|
||||
.use(components)
|
||||
// .use(jConmonents)
|
||||
.use(jComponents)
|
||||
.mount('#app')
|
||||
|
|
|
@ -31,6 +31,10 @@ export default [
|
|||
path: '/system/Api',
|
||||
component: () => import('@/views/system/Platforms/index.vue')
|
||||
},
|
||||
{
|
||||
path: '/account/center',
|
||||
component: () => import('@/views/account/Center/index.vue')
|
||||
},
|
||||
|
||||
// end: 测试用, 可删除
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
@import 'ant-design-vue/es/style/themes/default.less';
|
||||
@import 'jetlinks-ui-components/es/style/variable.less';
|
||||
|
||||
.ellipsisFn(@num: 1, @width: 100%) {
|
||||
display: -webkit-box;
|
||||
|
|
|
@ -0,0 +1,266 @@
|
|||
<template>
|
||||
<page-container>
|
||||
<div class="center-container">
|
||||
<div class="card">
|
||||
<div class="content" style="margin-top: 0">
|
||||
<div
|
||||
class="content-item flex-item"
|
||||
style="width: 350px; justify-content: center"
|
||||
>
|
||||
<img
|
||||
:src="userInfo.avatar"
|
||||
style="width: 140px; border-radius: 70px"
|
||||
alt=""
|
||||
/>
|
||||
<div
|
||||
style="
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
margin-top: 20px;
|
||||
"
|
||||
>
|
||||
<a-upload
|
||||
v-model:file-list="upload.fileList"
|
||||
accept=".jpg,.png,.jfif,.pjp,.pjpeg,.jpeg"
|
||||
:maxCount="1"
|
||||
:show-upload-list="false"
|
||||
:headers="{
|
||||
[TOKEN_KEY]: LocalStore.get(TOKEN_KEY),
|
||||
}"
|
||||
:action="`${BASE_API_PATH}/file/static`"
|
||||
@change="upload.changeBackUpload"
|
||||
>
|
||||
<a-button>
|
||||
<AIcon type="UploadOutlined" />
|
||||
更换头像
|
||||
</a-button>
|
||||
</a-upload>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="content-item flex-item"
|
||||
style="flex: 1; padding: 15px 0"
|
||||
>
|
||||
<div class="info-card">
|
||||
<p>用户名</p>
|
||||
<p>{{ userInfo.username }}</p>
|
||||
</div>
|
||||
<div class="info-card">
|
||||
<p>账号ID</p>
|
||||
<p>{{ userInfo.id }}</p>
|
||||
</div>
|
||||
<div class="info-card">
|
||||
<p>注册时间</p>
|
||||
<p>{{ userInfo.createTime }}</p>
|
||||
</div>
|
||||
<div class="info-card">
|
||||
<p>电话</p>
|
||||
<p>{{ userInfo.telephone }}</p>
|
||||
</div>
|
||||
<div class="info-card">
|
||||
<p>姓名</p>
|
||||
<p>{{ userInfo.name }}</p>
|
||||
</div>
|
||||
<div class="info-card">
|
||||
<p>角色</p>
|
||||
<p>{{ userInfo.roleList.join(',') || '-' }}</p>
|
||||
</div>
|
||||
<div class="info-card">
|
||||
<p>组织</p>
|
||||
<p>{{ userInfo.orgList.join(',') || '-' }}</p>
|
||||
</div>
|
||||
<div class="info-card">
|
||||
<p>邮箱</p>
|
||||
<p>{{ userInfo.email || '-' }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<AIcon
|
||||
type="EditOutlined"
|
||||
class="edit"
|
||||
style="right: 40px"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>修改密码</h3>
|
||||
<div class="content">
|
||||
<div class="content" style="align-items: flex-end">
|
||||
<lock-outlined
|
||||
style="color: #1d39c4; font-size: 70px"
|
||||
/>
|
||||
<!-- <AIcon type="LockOutlined" /> -->
|
||||
<span
|
||||
style="margin-left: 5px; color: rgba(0, 0, 0, 0.55)"
|
||||
>安全性高的密码可以使帐号更安全。建议您定期更换密码,设置一个包含字母,符号或数字中至少两项且长度超过8位的密码</span
|
||||
>
|
||||
</div>
|
||||
<AIcon type="EditOutlined" class="edit" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>绑定三方账号</h3>
|
||||
<div class="content">
|
||||
<div class="account-card" v-for="item in bindList">
|
||||
<img
|
||||
:src="getImage(bindIcon[item.provider])"
|
||||
style="height: 50px"
|
||||
alt=""
|
||||
/>
|
||||
<div class="text">
|
||||
<div v-if="item.bound">
|
||||
<div>绑定名:{{ item.others.name }}</div>
|
||||
<div>
|
||||
绑定时间:{{
|
||||
moment(item.bindTime).format(
|
||||
'YYYY-MM-DD HH:mm:ss',
|
||||
)
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>{{ item.name }}未绑定</div>
|
||||
</div>
|
||||
|
||||
<a-button v-if="item.bound">解除绑定</a-button>
|
||||
<a-button v-else type="primary">立即绑定</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>首页视图</h3>
|
||||
</div>
|
||||
</div>
|
||||
</page-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { LockOutlined } from '@ant-design/icons-vue';
|
||||
import { BASE_API_PATH, TOKEN_KEY } from '@/utils/variable';
|
||||
import { LocalStore, getImage } from '@/utils/comm';
|
||||
import { useUserInfo } from '@/store/userInfo';
|
||||
import { message, UploadChangeParam, UploadFile } from 'ant-design-vue';
|
||||
import { getSsoBinds_api } from '@/api/account/center';
|
||||
import moment from 'moment';
|
||||
|
||||
const userInfo = useUserInfo().$state.userInfos as any as userInfoType;
|
||||
const bindList = ref<any[]>([]);
|
||||
const bindIcon = {
|
||||
'dingtalk-ent-app': '/notice/dingtalk.png',
|
||||
'wechat-webapp': '/notice/wechat.png',
|
||||
'internal-standalone': '/apply/provider1.png',
|
||||
'third-party': '/apply/provider5.png',
|
||||
};
|
||||
const upload = reactive({
|
||||
fileList: [] as any[],
|
||||
uploadLoading: false,
|
||||
changeBackUpload: (info: UploadChangeParam<UploadFile<any>>) => {
|
||||
if (info.file.status === 'uploading') {
|
||||
upload.uploadLoading = true;
|
||||
} else if (info.file.status === 'done') {
|
||||
info.file.url = info.file.response?.result;
|
||||
upload.uploadLoading = false;
|
||||
userInfo.avatar = info.file.response?.result;
|
||||
} else if (info.file.status === 'error') {
|
||||
console.log(info.file);
|
||||
upload.uploadLoading = false;
|
||||
message.error('logo上传失败,请稍后再试');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
init();
|
||||
function init() {
|
||||
getSsoBinds_api().then((resp: any) => {
|
||||
if (resp.status === 200) bindList.value = resp.result;
|
||||
});
|
||||
}
|
||||
|
||||
type userInfoType = {
|
||||
avatar: string;
|
||||
createTime: number;
|
||||
email: string;
|
||||
id: string;
|
||||
name: string;
|
||||
orgList: string[];
|
||||
roleList: string[];
|
||||
status: number;
|
||||
telephone: string;
|
||||
tenantDisabled: boolean;
|
||||
type: { name: string; id: string };
|
||||
username: string;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.center-container {
|
||||
background-color: #f0f2f5;
|
||||
min-height: 100vh;
|
||||
.card {
|
||||
margin: 24px;
|
||||
padding: 24px;
|
||||
background-color: #fff;
|
||||
position: relative;
|
||||
|
||||
h3 {
|
||||
font-size: 22px;
|
||||
|
||||
&::before {
|
||||
display: inline-block;
|
||||
width: 3px;
|
||||
height: 0.7em;
|
||||
content: '';
|
||||
background-color: #2f54eb;
|
||||
margin: 0 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
margin-top: 24px;
|
||||
.content-item {
|
||||
margin-right: 24px;
|
||||
.info-card {
|
||||
width: 25%;
|
||||
|
||||
:first-child {
|
||||
font-weight: bold;
|
||||
}
|
||||
:last-child {
|
||||
color: #666363d9;
|
||||
}
|
||||
}
|
||||
&.flex-item {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
|
||||
.edit {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
top: 30px;
|
||||
right: 24px;
|
||||
color: #1d39c4;
|
||||
}
|
||||
|
||||
.account-card {
|
||||
margin-right: 24px;
|
||||
width: 415px;
|
||||
background-image: url(/images/notice/dingtalk-background.png);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 24px;
|
||||
|
||||
.text {
|
||||
display: -webkit-box;
|
||||
font-size: 22px;
|
||||
width: 150px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
word-break: break-all;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -87,11 +87,11 @@ watchEffect(() => {
|
|||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import 'ant-design-vue/es/style/themes/default.less';
|
||||
// @import 'ant-design-vue/es/style/themes/default.less';
|
||||
|
||||
:root {
|
||||
--dialog-primary-color: @primary-color;
|
||||
}
|
||||
// :root {
|
||||
// --dialog-primary-color: @primary-color;
|
||||
// }
|
||||
|
||||
.dialog-item {
|
||||
display: flex;
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
<template>
|
||||
<a-card>
|
||||
<a-empty
|
||||
v-if="!metadata || (metadata && !metadata.functions)"
|
||||
style="margin-top: 100px"
|
||||
v-if="!metadata || (metadata && !metadata.functions.length)"
|
||||
style="margin-top: 50px"
|
||||
>
|
||||
<template #description>
|
||||
暂无数据,请配置
|
||||
<a @click="emits('onJump', 'Metadata')">物模型</a>
|
||||
请配置对应产品的
|
||||
<!-- <a @click="emits('onJump', 'Metadata')">物模型属性功能</a> -->
|
||||
<a @click="onJump">物模型属性功能</a>
|
||||
</template>
|
||||
</a-empty>
|
||||
<template v-else>
|
||||
|
@ -23,9 +24,12 @@
|
|||
import { useInstanceStore } from '@/store/instance';
|
||||
import Simple from './components/Simple.vue';
|
||||
import Advance from './components/Advance.vue';
|
||||
import { useMenuStore } from 'store/menu';
|
||||
|
||||
const menuStory = useMenuStore();
|
||||
|
||||
const instanceStore = useInstanceStore();
|
||||
const emits = defineEmits(['onJump']);
|
||||
// const emits = defineEmits(['onJump']);
|
||||
|
||||
const metadata = computed(() => JSON.parse(instanceStore.detail.metadata));
|
||||
|
||||
|
@ -34,6 +38,14 @@ const tabs = {
|
|||
Simple,
|
||||
Advance,
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
const onJump = () => {
|
||||
menuStory.jumpPage(
|
||||
'device/Product/Detail',
|
||||
{
|
||||
id: instanceStore.current.productId,
|
||||
},
|
||||
{ key: 'metadata' },
|
||||
);
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -0,0 +1,306 @@
|
|||
|
||||
<template>
|
||||
<a-card>
|
||||
<div>
|
||||
<div class="top">
|
||||
<div class="top-left">
|
||||
<div>
|
||||
<AIcon type="ExclamationCircleOutlined" />
|
||||
<template v-if="topTitle === 'rest'">
|
||||
当前数据解析内容已脱离产品影响,
|
||||
<PermissionButton type="link" hasPermission="device/Instance:update" @click="rest()">
|
||||
重置
|
||||
</PermissionButton>
|
||||
后将继承产品数据解析内容
|
||||
</template>
|
||||
<template v-else>
|
||||
当前数据解析内容继承自产品,
|
||||
<PermissionButton type="link" hasPermission="device/Instance:update" @click="readOnly = false"
|
||||
:style="color">
|
||||
修改
|
||||
</PermissionButton>
|
||||
后将脱离产品影响。
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
脚本语言:
|
||||
<a-select :defaultValue="'JavaScript'" style="width: 200;margin-left: 5px;">
|
||||
<a-select-option value="JavaScript">JavaScript(ECMAScript 5)</a-select-option>
|
||||
</a-select>
|
||||
<AIcon type="ExpandOutlined" style="margin-left: 20px;" @click="toggle" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="edit" ref="el">
|
||||
<div v-show="readOnly" class="edit-only" @click="() => {
|
||||
message.warning({
|
||||
key: 1,
|
||||
content: () => '请点击上方修改字样,用以编辑脚本',
|
||||
style: {
|
||||
marginTop: '260px'
|
||||
}
|
||||
|
||||
})
|
||||
}"></div>
|
||||
<MonacoEditor language="javascript" style="height: 100%;" theme="vs" v-model:modelValue="editorValue" />
|
||||
</div>
|
||||
<div class="bottom">
|
||||
<div style="width: 49.5%;">
|
||||
<div class="bottom-title">
|
||||
<div class="bottom-title-text">模拟输入</div>
|
||||
<div class="bottom-title-topic">
|
||||
<template v-if="instanceStore.current.transport === 'MQTT'">
|
||||
<div style="margin-right: 5px;">Topic:</div>
|
||||
<a-auto-complete placeholder="请输入Topic" style="width: 300px" :options="topicList"
|
||||
:allowClear="true" :filterOption="(inputValue: any, option: any) =>
|
||||
option!.value.indexOf(inputValue) !== -1" v-model:value="topic" />
|
||||
</template>
|
||||
<template v-else>
|
||||
<div style="margin-right: 5px;">URL:</div>
|
||||
<a-input placeholder="请输入URL" v-model:value="url" style="width: 300px"></a-input>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<a-textarea :rows="5" placeholder="// 二进制数据以0x开头的十六进制输入,字符串数据输入原始字符串" style="margin-top: 10px;"
|
||||
v-model:value="simulation" />
|
||||
</div>
|
||||
<div style="width: 49.5%;">
|
||||
<div class="bottom-title">
|
||||
<div class="bottom-title-text">运行结果</div>
|
||||
</div>
|
||||
<a-textarea :autoSize="{ minRows: 5 }" :style="resStyle" v-model:value="result" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin-top: 10px;margin-left: 10px;">
|
||||
<PermissionButton type="primary" hasPermission="device/Instance:update" :loading="loading"
|
||||
:disabled="isDisabled" @click="debug()" :tooltip="{
|
||||
title: '需输入脚本和模拟数据后再点击',
|
||||
}">
|
||||
调试
|
||||
</PermissionButton>
|
||||
<PermissionButton hasPermission="device/Instance:update" :loading="loading" :disabled="!isTest" @click="save()"
|
||||
:style="{ marginLeft: '10px' }" :tooltip="{
|
||||
title: isTest ? '' : '请先调试',
|
||||
}">
|
||||
保存
|
||||
</PermissionButton>
|
||||
</div>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script setup lang='ts' name="Parsing">
|
||||
import AIcon from '@/components/AIcon'
|
||||
import PermissionButton from '@/components/PermissionButton/index.vue'
|
||||
import MonacoEditor from '@/components/MonacoEditor/index.vue';
|
||||
import { useFullscreen } from '@vueuse/core'
|
||||
import { useInstanceStore } from '@/store/instance';
|
||||
import {
|
||||
deviceCode,
|
||||
getProtocal,
|
||||
testCode,
|
||||
saveDeviceCode,
|
||||
delDeviceCode
|
||||
} from '@/api/device/instance'
|
||||
import { message } from 'ant-design-vue';
|
||||
import { isBoolean } from 'lodash';
|
||||
|
||||
const defaultValue =
|
||||
'//解码函数\r\nfunction decode(context) {\r\n //原始报文\r\n var buffer = context.payload();\r\n // 转为json\r\n // var json = context.json();\r\n //mqtt 时通过此方法获取topic\r\n // var topic = context.topic();\r\n\r\n // 提取变量\r\n // var topicVars = context.pathVars("/{deviceId}/**",topic)\r\n //温度属性\r\n var temperature = buffer.getShort(3) * 10;\r\n //湿度属性\r\n var humidity = buffer.getShort(6) * 10;\r\n return {\r\n "temperature": temperature,\r\n "humidity": humidity\r\n };\r\n}\r\n';
|
||||
|
||||
const el = ref<HTMLElement | null>(null)
|
||||
const { toggle } = useFullscreen(el)
|
||||
const instanceStore = useInstanceStore();
|
||||
|
||||
|
||||
const topTitle = ref<string>('')
|
||||
const readOnly = ref<boolean>(true)
|
||||
const url = ref<string>('')
|
||||
const topic = ref<string>('')
|
||||
const topicList = ref([])
|
||||
const simulation = ref<string>('')
|
||||
const resultValue = ref<any>({})
|
||||
const loading = ref<boolean>(false)
|
||||
const isTest = ref<boolean>(false)
|
||||
const editorValue = ref<string>('')
|
||||
|
||||
const color = computed(() => ({
|
||||
color: readOnly.value ? '#415ed1' : '#a6a6a6'
|
||||
}))
|
||||
const resStyle = computed(() => (isBoolean(resultValue.value.success) ? {
|
||||
'margin-top': '10px',
|
||||
'border-color': resultValue.value.success ? 'green' : 'red'
|
||||
} : {
|
||||
'margin-top': '10px',
|
||||
}))
|
||||
|
||||
const isDisabled = computed(() => simulation.value === '')
|
||||
|
||||
const result = computed(() => resultValue.value.success ? JSON.stringify(resultValue.value.outputs?.[0]) : resultValue.value.reason)
|
||||
|
||||
//重置
|
||||
const rest = async () => {
|
||||
const res = await delDeviceCode(instanceStore.current.productId, instanceStore.current.id)
|
||||
if (res.status === 200) {
|
||||
getDeviceCode();
|
||||
message.success('操作成功')
|
||||
}
|
||||
// service.delDeviceCode(productId, deviceId).then((res) => {
|
||||
// if (res.status === 200) {
|
||||
// getDeviceCode(productId, deviceId);
|
||||
// onlyMessage('操作成功');
|
||||
// }
|
||||
// });
|
||||
};
|
||||
//获取topic
|
||||
const getTopic = async () => {
|
||||
const res: any = await getProtocal(instanceStore.current.protocol, instanceStore.current.transport)
|
||||
if (res.status === 200) {
|
||||
const item = res.result.routes?.map((items: any) => ({
|
||||
value: items.topic,
|
||||
}));
|
||||
// setTopicList(item);
|
||||
topicList.value = item
|
||||
}
|
||||
};
|
||||
//获取设备解析规则
|
||||
const getDeviceCode = async () => {
|
||||
const res: any = await deviceCode(instanceStore.current.productId, instanceStore.current.id)
|
||||
if (res.status === 200) {
|
||||
const item = res.result?.configuration?.script ? res.result?.configuration?.script : defaultValue
|
||||
if (res.result?.deviceId) {
|
||||
readOnly.value = false
|
||||
topTitle.value = 'rest'
|
||||
editorValue.value = item
|
||||
} else {
|
||||
readOnly.value = true
|
||||
topTitle.value = 'edit'
|
||||
editorValue.value = item
|
||||
}
|
||||
}
|
||||
}
|
||||
//调试
|
||||
const test = async (dataTest: any) => {
|
||||
loading.value = true
|
||||
const res = await testCode(dataTest)
|
||||
if (res.status === 200) {
|
||||
loading.value = false
|
||||
resultValue.value = res?.result
|
||||
} else {
|
||||
loading.value = false
|
||||
}
|
||||
};
|
||||
|
||||
//保存设备解析规则
|
||||
const save = async () => {
|
||||
const item = {
|
||||
provider: 'jsr223',
|
||||
configuration: {
|
||||
script: editorValue.value,
|
||||
lang: 'javascript',
|
||||
},
|
||||
}
|
||||
const res = await saveDeviceCode(instanceStore.current.productId, instanceStore.current.id, item)
|
||||
if (res.status === 200) {
|
||||
message.success('保存成功');
|
||||
getDeviceCode();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const debug = () => {
|
||||
if (instanceStore.current.transport === 'MQTT') {
|
||||
if (topic.value !== '') {
|
||||
test({
|
||||
headers: {
|
||||
topic: topic.value,
|
||||
},
|
||||
configuration: {
|
||||
script: editorValue.value,
|
||||
lang: 'javascript',
|
||||
},
|
||||
provider: 'jsr223',
|
||||
payload: simulation.value,
|
||||
})
|
||||
isTest.value = true
|
||||
} else {
|
||||
message.error('请输入topic');
|
||||
}
|
||||
} else {
|
||||
if (url.value !== '') {
|
||||
test({
|
||||
headers: {
|
||||
url: url.value,
|
||||
},
|
||||
provider: 'jsr223',
|
||||
configuration: {
|
||||
script: editorValue.value,
|
||||
lang: 'javascript',
|
||||
},
|
||||
payload: simulation.value,
|
||||
});
|
||||
isTest.value = true
|
||||
} else {
|
||||
message.error('请输入url');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
getDeviceCode()
|
||||
getTopic()
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang='less'>
|
||||
.top {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 10px;
|
||||
|
||||
.top-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.edit {
|
||||
height: 550px;
|
||||
border: 1px solid #dcdcdc;
|
||||
|
||||
.edit-only {
|
||||
height: 550px;
|
||||
width: 97%;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
background-color: #eeeeee70;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
.bottom {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 10px;
|
||||
background-color: '#f7f7f7';
|
||||
|
||||
.bottom-title {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
.bottom-title-text {
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.bottom-title-topic {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -116,6 +116,7 @@ import Function from './Function/index.vue';
|
|||
import Modbus from './Modbus/index.vue';
|
||||
import OPCUA from './OPCUA/index.vue';
|
||||
import EdgeMap from './EdgeMap/index.vue';
|
||||
import Parsing from './Parsing/index.vue'
|
||||
import Log from './Log/index.vue'
|
||||
import { _deploy, _disconnect } from '@/api/device/instance';
|
||||
import { message } from 'ant-design-vue';
|
||||
|
@ -172,6 +173,7 @@ const tabs = {
|
|||
Modbus,
|
||||
OPCUA,
|
||||
EdgeMap,
|
||||
Parsing,
|
||||
Log
|
||||
};
|
||||
|
||||
|
@ -281,6 +283,15 @@ watchEffect(() => {
|
|||
tab: '边缘端映射',
|
||||
});
|
||||
}
|
||||
if (
|
||||
instanceStore.current.features?.find((item: any) => item.id === 'transparentCodec') &&
|
||||
!keys.includes('Parsing')
|
||||
) {
|
||||
list.value.push({
|
||||
key: 'Parsing',
|
||||
tab: '数据解析',
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
|
|
|
@ -1,4 +1,246 @@
|
|||
<!-- 数据解析 -->
|
||||
<template></template>
|
||||
<script></script>
|
||||
<style></style>
|
||||
|
||||
<template>
|
||||
<a-card>
|
||||
<div>
|
||||
<div class="top">
|
||||
<div>
|
||||
脚本语言:
|
||||
<a-select :defaultValue="'JavaScript'" style="width: 200;margin-left: 5px;">
|
||||
<a-select-option value="JavaScript">JavaScript(ECMAScript 5)</a-select-option>
|
||||
</a-select>
|
||||
<AIcon type="ExpandOutlined" style="margin-left: 20px;" @click="toggle" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="edit" ref="el">
|
||||
<MonacoEditor language="javascript" style="height: 100%;" theme="vs" v-model:modelValue="editorValue" />
|
||||
</div>
|
||||
<div class="bottom">
|
||||
<div style="width: 49.5%;">
|
||||
<div class="bottom-title">
|
||||
<div class="bottom-title-text">模拟输入</div>
|
||||
<div class="bottom-title-topic">
|
||||
<template v-if="productStore.current.transportProtocol === 'MQTT'">
|
||||
<div style="margin-right: 5px;">Topic:</div>
|
||||
<a-auto-complete placeholder="请输入Topic" style="width: 300px" :options="topicList"
|
||||
:allowClear="true" :filterOption="(inputValue: any, option: any) =>
|
||||
option!.value.indexOf(inputValue) !== -1" v-model:value="topic" />
|
||||
</template>
|
||||
<template v-else>
|
||||
<div style="margin-right: 5px;">URL:</div>
|
||||
<a-input placeholder="请输入URL" v-model:value="url" style="width: 300px"></a-input>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<a-textarea :rows="5" placeholder="// 二进制数据以0x开头的十六进制输入,字符串数据输入原始字符串" style="margin-top: 10px;"
|
||||
v-model:value="simulation" />
|
||||
</div>
|
||||
<div style="width: 49.5%;">
|
||||
<div class="bottom-title">
|
||||
<div class="bottom-title-text">运行结果</div>
|
||||
</div>
|
||||
<a-textarea :autoSize="{ minRows: 5 }" :style="resStyle" v-model:value="result" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin-top: 10px;margin-left: 10px;">
|
||||
<PermissionButton type="primary" hasPermission="device/Instance:update" :loading="loading"
|
||||
:disabled="isDisabled" @click="debug()" :tooltip="{
|
||||
title: '需输入脚本和模拟数据后再点击',
|
||||
}">
|
||||
调试
|
||||
</PermissionButton>
|
||||
<PermissionButton hasPermission="device/Instance:update" :loading="loading" :disabled="!isTest" @click="save()"
|
||||
:style="{ marginLeft: '10px' }" :tooltip="{
|
||||
title: isTest ? '' : '请先调试',
|
||||
}">
|
||||
保存
|
||||
</PermissionButton>
|
||||
</div>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script setup lang='ts' name="Parsing">
|
||||
import AIcon from '@/components/AIcon'
|
||||
import PermissionButton from '@/components/PermissionButton/index.vue'
|
||||
import MonacoEditor from '@/components/MonacoEditor/index.vue';
|
||||
import { useFullscreen } from '@vueuse/core'
|
||||
import { useProductStore } from '@/store/product';
|
||||
import {
|
||||
productCode,
|
||||
getProtocal,
|
||||
testCode,
|
||||
saveProductCode,
|
||||
} from '@/api/device/instance'
|
||||
import { message } from 'ant-design-vue';
|
||||
import { isBoolean } from 'lodash';
|
||||
|
||||
const defaultValue =
|
||||
'//解码函数\r\nfunction decode(context) {\r\n //原始报文\r\n var buffer = context.payload();\r\n // 转为json\r\n // var json = context.json();\r\n //mqtt 时通过此方法获取topic\r\n // var topic = context.topic();\r\n\r\n // 提取变量\r\n // var topicVars = context.pathVars("/{deviceId}/**",topic)\r\n //温度属性\r\n var temperature = buffer.getShort(3) * 10;\r\n //湿度属性\r\n var humidity = buffer.getShort(6) * 10;\r\n return {\r\n "temperature": temperature,\r\n "humidity": humidity\r\n };\r\n}\r\n';
|
||||
|
||||
const el = ref<HTMLElement | null>(null)
|
||||
const { toggle } = useFullscreen(el)
|
||||
const productStore = useProductStore()
|
||||
|
||||
|
||||
const url = ref<string>('')
|
||||
const topic = ref<string>('')
|
||||
const topicList = ref([])
|
||||
const simulation = ref<string>('')
|
||||
const resultValue = ref<any>({})
|
||||
const loading = ref<boolean>(false)
|
||||
const isTest = ref<boolean>(false)
|
||||
const editorValue = ref<string>('')
|
||||
|
||||
const resStyle = computed(() => (isBoolean(resultValue.value.success) ? {
|
||||
'margin-top': '10px',
|
||||
'border-color': resultValue.value.success ? 'green' : 'red'
|
||||
} : {
|
||||
'margin-top': '10px',
|
||||
}))
|
||||
|
||||
const isDisabled = computed(() => simulation.value === '')
|
||||
|
||||
const result = computed(() => resultValue.value.success ? JSON.stringify(resultValue.value.outputs?.[0]) : resultValue.value.reason)
|
||||
|
||||
|
||||
//获取topic
|
||||
const getTopic = async () => {
|
||||
const res: any = await getProtocal(productStore.current.messageProtocol, productStore.current.transportProtocol)
|
||||
if (res.status === 200) {
|
||||
const item = res.result.routes?.map((items: any) => ({
|
||||
value: items.topic,
|
||||
}));
|
||||
topicList.value = item
|
||||
}
|
||||
};
|
||||
//获取产品解析规则
|
||||
const getProductCode = async () => {
|
||||
const res: any = await productCode(productStore.current.id)
|
||||
if (res.status === 200) {
|
||||
if(res.result){
|
||||
editorValue.value = res.result?.configuration?.script
|
||||
}else{
|
||||
editorValue.value = defaultValue
|
||||
}
|
||||
}
|
||||
}
|
||||
//调试
|
||||
const test = async (dataTest: any) => {
|
||||
loading.value = true
|
||||
const res = await testCode(dataTest)
|
||||
if (res.status === 200) {
|
||||
loading.value = false
|
||||
resultValue.value = res?.result
|
||||
} else {
|
||||
loading.value = false
|
||||
}
|
||||
};
|
||||
|
||||
//保存产品解析规则
|
||||
const save = async () => {
|
||||
const item = {
|
||||
provider: 'jsr223',
|
||||
configuration: {
|
||||
script: editorValue.value,
|
||||
lang: 'javascript',
|
||||
},
|
||||
}
|
||||
const res = await saveProductCode(productStore.current.id, item)
|
||||
if (res.status === 200) {
|
||||
message.success('保存成功');
|
||||
getProductCode();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const debug = () => {
|
||||
if (productStore.current.transportProtocol === 'MQTT') {
|
||||
if (topic.value !== '') {
|
||||
test({
|
||||
headers: {
|
||||
topic: topic.value,
|
||||
},
|
||||
configuration: {
|
||||
script: editorValue.value,
|
||||
lang: 'javascript',
|
||||
},
|
||||
provider: 'jsr223',
|
||||
payload: simulation.value,
|
||||
})
|
||||
isTest.value = true
|
||||
} else {
|
||||
message.error('请输入topic');
|
||||
}
|
||||
} else {
|
||||
if (url.value !== '') {
|
||||
test({
|
||||
headers: {
|
||||
url: url.value,
|
||||
},
|
||||
provider: 'jsr223',
|
||||
configuration: {
|
||||
script: editorValue.value,
|
||||
lang: 'javascript',
|
||||
},
|
||||
payload: simulation.value,
|
||||
});
|
||||
isTest.value = true
|
||||
} else {
|
||||
message.error('请输入url');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
getProductCode()
|
||||
getTopic()
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang='less'>
|
||||
.top {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.edit {
|
||||
height: 550px;
|
||||
border: 1px solid #dcdcdc;
|
||||
|
||||
.edit-only {
|
||||
height: 550px;
|
||||
width: 97%;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
background-color: #eeeeee70;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
.bottom {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 10px;
|
||||
background-color: '#f7f7f7';
|
||||
|
||||
.bottom-title {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
.bottom-title-text {
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.bottom-title-topic {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -123,6 +123,7 @@ const tabs = {
|
|||
Info,
|
||||
Metadata,
|
||||
Device,
|
||||
DataAnalysis
|
||||
};
|
||||
|
||||
watch(
|
||||
|
@ -188,7 +189,7 @@ const handleUndeploy = async () => {
|
|||
*/
|
||||
const getProtocol = async () => {
|
||||
if (productStore.current?.messageProtocol) {
|
||||
const res = await getProtocolDetail(
|
||||
const res:any = await getProtocolDetail(
|
||||
productStore.current?.messageProtocol,
|
||||
);
|
||||
if (res.status === 200) {
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
:class="{ selected: selectValue === 'device' }"
|
||||
@click="selectValue = 'device'"
|
||||
>
|
||||
<img src="/images/home/device.png" alt="" />
|
||||
<img :src="getImage('/home/device.png')" alt="" />
|
||||
</a-col>
|
||||
<a-col
|
||||
:span="8"
|
||||
|
@ -18,7 +18,7 @@
|
|||
:class="{ selected: selectValue === 'ops' }"
|
||||
@click="selectValue = 'ops'"
|
||||
>
|
||||
<img src="/images/home/ops.png" alt="" />
|
||||
<img :src="getImage('/home/ops.png')" alt="" />
|
||||
</a-col>
|
||||
<a-col
|
||||
:span="8"
|
||||
|
@ -26,7 +26,7 @@
|
|||
:class="{ selected: selectValue === 'comprehensive' }"
|
||||
@click="selectValue = 'comprehensive'"
|
||||
>
|
||||
<img src="/images/home/comprehensive.png" alt="" />
|
||||
<img :src="getImage('/home/comprehensive.png')" alt="" />
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-button type="primary" class="btn" @click="confirm"
|
||||
|
@ -38,6 +38,7 @@
|
|||
|
||||
<script lang="ts" setup>
|
||||
import { setView_api } from '@/api/home';
|
||||
import { getImage } from '@/utils/comm';
|
||||
|
||||
const emits = defineEmits(['refresh']);
|
||||
const selectValue = ref('device');
|
||||
|
|
|
@ -6,8 +6,26 @@
|
|||
<DevOpsHome v-else-if="currentView === 'ops'" />
|
||||
<ComprehensiveHome v-else-if="currentView === 'comprehensive'" />
|
||||
|
||||
<Api :mode="'home'" hasHome showTitle>
|
||||
<template #top> </template>
|
||||
<Api
|
||||
v-else-if="currentView === 'api'"
|
||||
:mode="'home'"
|
||||
hasHome
|
||||
showTitle
|
||||
:code="clientId"
|
||||
>
|
||||
<template #top>
|
||||
<div class="card">
|
||||
<h3 style="margin: 0 0 24px 0">基本信息</h3>
|
||||
<p>
|
||||
<span style="font-weight: bold">clientId: </span>
|
||||
<span>{{ clientId }}</span>
|
||||
</p>
|
||||
<p>
|
||||
<span style="font-weight: bold">secureKey:</span>
|
||||
<span>{{ secureKey }}</span>
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
</Api>
|
||||
</div>
|
||||
</page-container>
|
||||
|
@ -19,14 +37,16 @@ import DeviceHome from './components/DeviceHome/index.vue';
|
|||
import DevOpsHome from './components/DevOpsHome/index.vue';
|
||||
import ComprehensiveHome from './components/ComprehensiveHome/index.vue';
|
||||
import Api from '@/views/system/Platforms/Api/index.vue';
|
||||
import { useUserInfo } from '@/store/userInfo';
|
||||
|
||||
import { isNoCommunity } from '@/utils/utils';
|
||||
import { getMe_api, getView_api } from '@/api/home';
|
||||
|
||||
const router = useRouter();
|
||||
import { getAppInfo_api } from '@/api/system/apply';
|
||||
|
||||
const currentView = ref<string>('');
|
||||
const loading = ref<boolean>(true);
|
||||
const clientId = useUserInfo().$state.userInfos.id;
|
||||
const secureKey = ref<string>('');
|
||||
|
||||
// 获取选择的视图
|
||||
const setCurrentView = () => {
|
||||
|
@ -49,7 +69,12 @@ if (isNoCommunity) {
|
|||
item.type === 'api-client' || item.type.id === 'api-client',
|
||||
);
|
||||
|
||||
isApiUser ? router.push('/system/api') : setCurrentView();
|
||||
if (isApiUser) {
|
||||
currentView.value = 'api';
|
||||
getAppInfo_api(clientId).then((resp: any) => {
|
||||
secureKey.value = resp.result.apiServer.secureKey;
|
||||
});
|
||||
} else setCurrentView();
|
||||
}
|
||||
});
|
||||
} else setCurrentView();
|
||||
|
@ -57,7 +82,15 @@ if (isNoCommunity) {
|
|||
|
||||
<style lang="less" scoped>
|
||||
.iot-home-container {
|
||||
background: #f0f2f5;
|
||||
overflow: hidden;
|
||||
.card {
|
||||
background-color: #fff;
|
||||
padding: 24px;
|
||||
margin-bottom: 24px;
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -0,0 +1,211 @@
|
|||
<!-- 国标级联-绑定通道 -->
|
||||
<template>
|
||||
<a-modal
|
||||
v-model:visible="_vis"
|
||||
title="绑定通道"
|
||||
cancelText="取消"
|
||||
okText="确定"
|
||||
width="80%"
|
||||
@ok="handleSave"
|
||||
@cancel="_vis = false"
|
||||
:confirmLoading="loading"
|
||||
>
|
||||
<Search
|
||||
type="simple"
|
||||
:columns="columns"
|
||||
target="media"
|
||||
@search="handleSearch"
|
||||
/>
|
||||
|
||||
<JTable
|
||||
ref="listRef"
|
||||
model="table"
|
||||
:columns="columns"
|
||||
:request="CascadeApi.queryChannelList"
|
||||
:defaultParams="{
|
||||
sorts: [{ name: 'name', order: 'desc' }],
|
||||
terms: [
|
||||
{
|
||||
column: 'id',
|
||||
termType: 'cascade_channel$not',
|
||||
type: 'and',
|
||||
value: route.query.id,
|
||||
},
|
||||
{
|
||||
column: 'catalogType',
|
||||
termType: 'eq',
|
||||
type: 'and',
|
||||
value: 'device',
|
||||
},
|
||||
],
|
||||
}"
|
||||
:params="params"
|
||||
:rowSelection="{
|
||||
selectedRowKeys: _selectedRowKeys,
|
||||
onSelect: onSelectChange,
|
||||
onSelectAll: onSelectAllChange,
|
||||
}"
|
||||
@cancelSelect="_selectedRowKeys = []"
|
||||
>
|
||||
<template #headerTitle>
|
||||
<h3>通道列表</h3>
|
||||
</template>
|
||||
<template #status="slotProps">
|
||||
<a-space>
|
||||
<a-badge
|
||||
:status="
|
||||
slotProps.status.value === 'online'
|
||||
? 'success'
|
||||
: 'error'
|
||||
"
|
||||
:text="slotProps.status.text"
|
||||
></a-badge>
|
||||
</a-space>
|
||||
</template>
|
||||
</JTable>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import CascadeApi from '@/api/media/cascade';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { PropType } from 'vue';
|
||||
|
||||
const route = useRoute();
|
||||
|
||||
type Emits = {
|
||||
(e: 'update:visible', data: boolean): void;
|
||||
(e: 'submit'): void;
|
||||
};
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const props = defineProps({
|
||||
visible: { type: Boolean, default: false },
|
||||
data: {
|
||||
type: Object as PropType<Partial<Record<string, any>>>,
|
||||
default: () => ({}),
|
||||
},
|
||||
});
|
||||
|
||||
const _vis = computed({
|
||||
get: () => props.visible,
|
||||
set: (val) => emit('update:visible', val),
|
||||
});
|
||||
|
||||
watch(
|
||||
() => _vis.value,
|
||||
(val) => {
|
||||
if (val) handleSearch({ terms: [] });
|
||||
},
|
||||
);
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: '设备名称',
|
||||
dataIndex: 'deviceName',
|
||||
key: 'deviceName',
|
||||
search: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '通道名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
search: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '安装地址',
|
||||
dataIndex: 'address',
|
||||
key: 'address',
|
||||
search: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '厂商',
|
||||
dataIndex: 'manufacturer',
|
||||
key: 'manufacturer',
|
||||
search: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
scopedSlots: true,
|
||||
search: {
|
||||
type: 'select',
|
||||
options: [
|
||||
{ label: '已连接', value: 'online' },
|
||||
{ label: '未连接', value: 'offline' },
|
||||
],
|
||||
handleValue: (v: any) => {
|
||||
return v;
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const params = ref<Record<string, any>>({});
|
||||
|
||||
/**
|
||||
* 搜索
|
||||
* @param params
|
||||
*/
|
||||
const handleSearch = (e: any) => {
|
||||
params.value = e;
|
||||
console.log('params.value: ', params.value);
|
||||
};
|
||||
|
||||
const listRef = ref();
|
||||
const _selectedRowKeys = ref<string[]>([]);
|
||||
|
||||
const onSelectChange = (
|
||||
record: any[],
|
||||
selected: boolean,
|
||||
selectedRows: any[],
|
||||
) => {
|
||||
_selectedRowKeys.value = selected
|
||||
? [...getSetRowKey(selectedRows)]
|
||||
: _selectedRowKeys.value.filter((item: any) => item !== record?.id);
|
||||
};
|
||||
const onSelectAllChange = (
|
||||
selected: boolean,
|
||||
selectedRows: any[],
|
||||
changeRows: any[],
|
||||
) => {
|
||||
const unRowsKeys = getSelectedRowsKey(changeRows);
|
||||
_selectedRowKeys.value = selected
|
||||
? [...getSetRowKey(selectedRows)]
|
||||
: _selectedRowKeys.value
|
||||
.concat(unRowsKeys)
|
||||
.filter((item) => !unRowsKeys.includes(item));
|
||||
};
|
||||
const getSelectedRowsKey = (selectedRows: any[]) =>
|
||||
selectedRows.map((item) => item?.id).filter((i) => !!i);
|
||||
|
||||
const getSetRowKey = (selectedRows: any[]) =>
|
||||
new Set([..._selectedRowKeys.value, ...getSelectedRowsKey(selectedRows)]);
|
||||
|
||||
const loading = ref(false);
|
||||
const handleSave = async () => {
|
||||
if (!_selectedRowKeys.value.length) message.error('请勾选数据');
|
||||
loading.value = true;
|
||||
const resp = await CascadeApi.bindChannel(
|
||||
route.query.id as string,
|
||||
_selectedRowKeys.value,
|
||||
);
|
||||
loading.value = false;
|
||||
if (resp.success) {
|
||||
message.success('操作成功!');
|
||||
_vis.value = false;
|
||||
emit('submit');
|
||||
} else {
|
||||
message.error('操作失败!');
|
||||
}
|
||||
};
|
||||
</script>
|
|
@ -0,0 +1,267 @@
|
|||
<!-- 国标级联-通道列表 -->
|
||||
<template>
|
||||
<page-container>
|
||||
<Search
|
||||
type="simple"
|
||||
:columns="columns"
|
||||
target="media"
|
||||
@search="handleSearch"
|
||||
/>
|
||||
|
||||
<JTable
|
||||
ref="listRef"
|
||||
model="table"
|
||||
:columns="columns"
|
||||
:request="(e:any) => CascadeApi.queryBindChannel(route?.query.id as string, e)"
|
||||
:defaultParams="{
|
||||
sorts: [{ name: 'name', order: 'desc' }],
|
||||
}"
|
||||
:params="params"
|
||||
:rowSelection="{
|
||||
selectedRowKeys: _selectedRowKeys,
|
||||
onSelect: onSelectChange,
|
||||
onSelectAll: onSelectAllChange,
|
||||
}"
|
||||
@cancelSelect="_selectedRowKeys = []"
|
||||
>
|
||||
<template #headerTitle>
|
||||
<h3>通道列表</h3>
|
||||
</template>
|
||||
<template #rightExtraRender>
|
||||
<a-space>
|
||||
<a-button type="primary" @click="bindVis = true">
|
||||
绑定通道
|
||||
</a-button>
|
||||
<a-popconfirm
|
||||
title="确认解绑?"
|
||||
@confirm="handleMultipleUnbind"
|
||||
>
|
||||
<a-button> 批量解绑 </a-button>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
</template>
|
||||
<template #status="slotProps">
|
||||
<a-space>
|
||||
<a-badge
|
||||
:status="
|
||||
slotProps.status.value === 'online'
|
||||
? 'success'
|
||||
: 'error'
|
||||
"
|
||||
:text="slotProps.status.text"
|
||||
></a-badge>
|
||||
</a-space>
|
||||
</template>
|
||||
<template #action="slotProps">
|
||||
<a-space :size="16">
|
||||
<a-tooltip
|
||||
v-for="i in getActions(slotProps, 'table')"
|
||||
:key="i.key"
|
||||
v-bind="i.tooltip"
|
||||
>
|
||||
<a-popconfirm
|
||||
v-if="i.popConfirm"
|
||||
v-bind="i.popConfirm"
|
||||
:disabled="i.disabled"
|
||||
>
|
||||
<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>
|
||||
</JTable>
|
||||
|
||||
<BindChannel v-model:visible="bindVis" @submit="listRef.reload()" />
|
||||
</page-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import CascadeApi from '@/api/media/cascade';
|
||||
import type { ActionsType } from '@/components/Table/index.vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import BindChannel from './BindChannel/index.vue';
|
||||
|
||||
const route = useRoute();
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: '设备名称',
|
||||
dataIndex: 'deviceName',
|
||||
key: 'deviceName',
|
||||
search: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '通道名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
search: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '国标ID',
|
||||
dataIndex: 'gbChannelId',
|
||||
key: 'gbChannelId',
|
||||
scopedSlots: true,
|
||||
search: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '安装地址',
|
||||
dataIndex: 'address',
|
||||
key: 'address',
|
||||
search: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '厂商',
|
||||
dataIndex: 'manufacturer',
|
||||
key: 'manufacturer',
|
||||
search: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
scopedSlots: true,
|
||||
search: {
|
||||
type: 'select',
|
||||
options: [
|
||||
{ label: '已连接', value: 'online' },
|
||||
{ label: '未连接', value: 'offline' },
|
||||
],
|
||||
handleValue: (v: any) => {
|
||||
return v;
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
scopedSlots: true,
|
||||
},
|
||||
];
|
||||
|
||||
const params = ref<Record<string, any>>({});
|
||||
|
||||
/**
|
||||
* 搜索
|
||||
* @param params
|
||||
*/
|
||||
const handleSearch = (e: any) => {
|
||||
params.value = e;
|
||||
console.log('params.value: ', params.value);
|
||||
};
|
||||
|
||||
const listRef = ref();
|
||||
const _selectedRowKeys = ref<string[]>([]);
|
||||
const bindVis = ref(false);
|
||||
|
||||
const onSelectChange = (
|
||||
record: any[],
|
||||
selected: boolean,
|
||||
selectedRows: any[],
|
||||
) => {
|
||||
_selectedRowKeys.value = selected
|
||||
? [...getSetRowKey(selectedRows)]
|
||||
: _selectedRowKeys.value.filter((item: any) => item !== record?.id);
|
||||
};
|
||||
const onSelectAllChange = (
|
||||
selected: boolean,
|
||||
selectedRows: any[],
|
||||
changeRows: any[],
|
||||
) => {
|
||||
const unRowsKeys = getSelectedRowsKey(changeRows);
|
||||
_selectedRowKeys.value = selected
|
||||
? [...getSetRowKey(selectedRows)]
|
||||
: _selectedRowKeys.value
|
||||
.concat(unRowsKeys)
|
||||
.filter((item) => !unRowsKeys.includes(item));
|
||||
};
|
||||
const getSelectedRowsKey = (selectedRows: any[]) =>
|
||||
selectedRows.map((item) => item?.id).filter((i) => !!i);
|
||||
|
||||
const getSetRowKey = (selectedRows: any[]) =>
|
||||
new Set([..._selectedRowKeys.value, ...getSelectedRowsKey(selectedRows)]);
|
||||
|
||||
/**
|
||||
* 表格操作按钮
|
||||
* @param data 表格数据项
|
||||
* @param type 表格展示类型
|
||||
*/
|
||||
const getActions = (
|
||||
data: Partial<Record<string, any>>,
|
||||
type: 'card' | 'table',
|
||||
): ActionsType[] => {
|
||||
if (!data) return [];
|
||||
const actions = [
|
||||
{
|
||||
key: 'delete',
|
||||
text: '解绑',
|
||||
tooltip: {
|
||||
title: '解绑',
|
||||
},
|
||||
icon: 'DisconnectOutlined',
|
||||
popConfirm: {
|
||||
title: '确认解绑?',
|
||||
onConfirm: async () => {
|
||||
const resp = await CascadeApi.unbindChannel(
|
||||
route.query.id as string,
|
||||
[data.channelId],
|
||||
);
|
||||
if (resp.success) {
|
||||
message.success('操作成功!');
|
||||
listRef.value?.reload();
|
||||
} else {
|
||||
message.error('操作失败!');
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
return actions;
|
||||
};
|
||||
|
||||
/**
|
||||
* 批量解绑
|
||||
*/
|
||||
const handleMultipleUnbind = async () => {
|
||||
const channelIds = listRef.value?._dataSource
|
||||
.filter((f: any) => _selectedRowKeys.value.includes(f.id))
|
||||
.map((m: any) => m.channelId);
|
||||
const resp = await CascadeApi.unbindChannel(
|
||||
route.query.id as string,
|
||||
channelIds,
|
||||
);
|
||||
if (resp.success) {
|
||||
message.success('操作成功!');
|
||||
listRef.value?.reload();
|
||||
} else {
|
||||
message.error('操作失败!');
|
||||
}
|
||||
};
|
||||
</script>
|
|
@ -157,8 +157,7 @@
|
|||
message: '请输入上级SIP 地址',
|
||||
},
|
||||
{
|
||||
max: 64,
|
||||
message: '最多可输入64个字符',
|
||||
validator: checkSIP,
|
||||
},
|
||||
]"
|
||||
>
|
||||
|
@ -213,7 +212,10 @@
|
|||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
message: '请输入SIP本地地址',
|
||||
message: '请选择SIP本地地址',
|
||||
},
|
||||
{
|
||||
validator: checkLocalSIP,
|
||||
},
|
||||
]"
|
||||
>
|
||||
|
@ -242,11 +244,8 @@
|
|||
<a-select
|
||||
v-model:value="formData.port"
|
||||
placeholder="请选择端口"
|
||||
>
|
||||
<a-select-option value="1">
|
||||
1
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
:options="allListPorts"
|
||||
/>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form-item>
|
||||
|
@ -261,8 +260,7 @@
|
|||
message: '请输入SIP远程地址',
|
||||
},
|
||||
{
|
||||
max: 64,
|
||||
message: '最多可输入64个字符',
|
||||
validator: checkPublicSIP,
|
||||
},
|
||||
]"
|
||||
>
|
||||
|
@ -303,6 +301,7 @@
|
|||
<a-radio-group
|
||||
button-style="solid"
|
||||
v-model:value="formData.transport"
|
||||
@change="setPorts"
|
||||
>
|
||||
<a-radio-button value="UDP">
|
||||
UDP
|
||||
|
@ -558,14 +557,11 @@
|
|||
|
||||
<script setup lang="ts">
|
||||
import { getImage } from '@/utils/comm';
|
||||
import { Form } from 'ant-design-vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
import CascadeApi from '@/api/media/cascade';
|
||||
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const useForm = Form.useForm;
|
||||
|
||||
// 表单数据
|
||||
const formData = ref({
|
||||
|
@ -614,59 +610,102 @@ getClustersList();
|
|||
/**
|
||||
* SIP本地地址
|
||||
*/
|
||||
const allList = ref([]);
|
||||
const allList = ref<any[]>([]);
|
||||
const getAllList = async () => {
|
||||
const { result } = await CascadeApi.all();
|
||||
allList.value = result.map((m: any) => ({
|
||||
label: m.host,
|
||||
value: m.host,
|
||||
}));
|
||||
setPorts();
|
||||
};
|
||||
getAllList();
|
||||
|
||||
/**
|
||||
* 传输协议改变, 获取对应的端口
|
||||
*/
|
||||
const allListPorts = ref([]);
|
||||
const setPorts = () => {
|
||||
allListPorts.value = allList.value.find(
|
||||
(f: any) => f.host === formData.value.host,
|
||||
)?.ports[formData.value.transport || ''];
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取详情
|
||||
*/
|
||||
const getDetail = async () => {
|
||||
if (!route.query.id) return;
|
||||
const res = await CascadeApi.detail(route.query.id as string);
|
||||
// console.log('res: ', res);
|
||||
// formData.value = res.result;
|
||||
// Object.assign(formData.value, res.result);
|
||||
|
||||
const { id, name, proxyStream, sipConfigs } = res.result;
|
||||
formData.value = {
|
||||
id,
|
||||
cascadeName: name,
|
||||
proxyStream,
|
||||
clusterNodeId: sipConfigs[0]?.clusterNodeId,
|
||||
name: sipConfigs[0]?.name,
|
||||
sipId: sipConfigs[0]?.sipId,
|
||||
domain: sipConfigs[0]?.domain,
|
||||
remoteAddress: sipConfigs[0]?.remoteAddress,
|
||||
remotePort: sipConfigs[0]?.remotePort,
|
||||
localSipId: sipConfigs[0]?.localSipId,
|
||||
host: sipConfigs[0]?.host,
|
||||
port: sipConfigs[0]?.port,
|
||||
publicHost: sipConfigs[0]?.publicHost,
|
||||
publicPort: sipConfigs[0]?.publicPort,
|
||||
transport: sipConfigs[0]?.transport,
|
||||
user: sipConfigs[0]?.user,
|
||||
password: sipConfigs[0]?.password,
|
||||
manufacturer: sipConfigs[0]?.manufacturer,
|
||||
model: sipConfigs[0]?.model,
|
||||
firmware: sipConfigs[0]?.firmware,
|
||||
keepaliveInterval: sipConfigs[0]?.keepaliveInterval,
|
||||
registerInterval: sipConfigs[0]?.registerInterval,
|
||||
};
|
||||
|
||||
console.log('formData.value: ', formData.value);
|
||||
const { id, name, proxyStream, sipConfigs, ...others } = res.result;
|
||||
Object.keys(formData.value).forEach((key: string) => {
|
||||
if (key === 'id') formData.value[key] = id;
|
||||
else if (key === 'cascadeName') formData.value[key] = name;
|
||||
else if (key === 'proxyStream') formData.value[key] = proxyStream;
|
||||
else formData.value[key] = sipConfigs[0][key];
|
||||
});
|
||||
// console.log('formData.value: ', formData.value);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getDetail();
|
||||
});
|
||||
|
||||
const regDomain =
|
||||
/[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+\.?/;
|
||||
/**
|
||||
* 上级SIP地址 字段验证
|
||||
* @param _
|
||||
* @param value 此处绑定的是 remoteAddress
|
||||
*/
|
||||
const checkSIP = (_: any, value: string) => {
|
||||
return checkHost(value, formData.value.remotePort);
|
||||
};
|
||||
/**
|
||||
* SIP远程地址 字段验证
|
||||
* @param _
|
||||
* @param value 此处绑定的是 publicHost
|
||||
*/
|
||||
const checkPublicSIP = (_: any, value: string) => {
|
||||
return checkHost(value, formData.value.publicPort);
|
||||
};
|
||||
|
||||
/**
|
||||
* 字段验证
|
||||
* @param host ip
|
||||
* @param port 端口
|
||||
*/
|
||||
const checkHost = (host: string, port: string | number | undefined) => {
|
||||
if (!host) {
|
||||
return Promise.resolve();
|
||||
} else if (!host) {
|
||||
return Promise.reject(new Error('请输入IP 地址'));
|
||||
} else if (host && !regDomain.test(host)) {
|
||||
return Promise.reject(new Error('请输入正确的IP地址'));
|
||||
} else if (!port) {
|
||||
return Promise.reject(new Error('请输入端口'));
|
||||
} else if ((host && Number(host) < 1) || Number(host) > 65535) {
|
||||
return Promise.reject(new Error('端口请输入1~65535之间的正整数'));
|
||||
}
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
/**
|
||||
* SIP本地地址 字段验证
|
||||
* @param _
|
||||
* @param value
|
||||
*/
|
||||
const checkLocalSIP = (_: any, value: string) => {
|
||||
if (!value) {
|
||||
return Promise.resolve();
|
||||
} else if (!value) {
|
||||
return Promise.reject(new Error('请选择IP地址'));
|
||||
} else if (!formData.value.port) {
|
||||
return Promise.reject(new Error('请选择端口'));
|
||||
}
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
/**
|
||||
* 表单提交
|
||||
*/
|
||||
|
|
|
@ -197,12 +197,10 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import DeviceApi from '@/api/media/device';
|
||||
import CascadeApi from '@/api/media/cascade';
|
||||
import type { ActionsType } from '@/components/Table/index.vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { getImage } from '@/utils/comm';
|
||||
import { PROVIDER_OPTIONS } from '@/views/media/Device/const';
|
||||
|
||||
import { useMenuStore } from 'store/menu';
|
||||
|
||||
|
@ -295,7 +293,7 @@ const handleSearch = (e: any) => {
|
|||
const lastValueFrom = async (params: any) => {
|
||||
const res = await CascadeApi.list(params);
|
||||
res.result.data.forEach(async (item: any) => {
|
||||
const resp = await queryBindChannel(item.id);
|
||||
const resp = await queryChannelCount(item.id);
|
||||
item.count = resp.result.total;
|
||||
});
|
||||
return res;
|
||||
|
@ -305,8 +303,8 @@ const lastValueFrom = async (params: any) => {
|
|||
* 查询通道数量
|
||||
* @param id
|
||||
*/
|
||||
const queryBindChannel = async (id: string) => {
|
||||
return await CascadeApi.queryCount(id);
|
||||
const queryChannelCount = async (id: string) => {
|
||||
return await CascadeApi.queryBindChannel(id, {});
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -55,8 +55,8 @@
|
|||
:status="item.state?.value"
|
||||
:statusText="item.state?.text"
|
||||
:statusNames="{
|
||||
online: 'enabled',
|
||||
offline: 'disabled',
|
||||
enabled: 'processing',
|
||||
disabled: 'error',
|
||||
}"
|
||||
>
|
||||
<template #img>
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
<JTable
|
||||
:columns="columns"
|
||||
:request="queryList"
|
||||
:gridColumn="3"
|
||||
ref="tableRef"
|
||||
:defaultParams="{
|
||||
sorts: [{ name: 'createTime', order: 'desc' }],
|
||||
|
@ -42,7 +43,7 @@
|
|||
</slot>
|
||||
</template>
|
||||
<template #content>
|
||||
<Ellipsis>
|
||||
<Ellipsis style="width: calc(100% - 100px)">
|
||||
<span style="font-weight: 600; font-size: 16px">
|
||||
{{ slotProps.name }}
|
||||
</span>
|
||||
|
@ -70,35 +71,14 @@
|
|||
</a-row>
|
||||
</template>
|
||||
<template #actions="item">
|
||||
<a-tooltip
|
||||
v-bind="item.tooltip"
|
||||
:title="item.disabled && item.tooltip.title"
|
||||
<PermissionButton
|
||||
v-if="
|
||||
item.key != 'trigger' ||
|
||||
slotProps.sceneTriggerType == 'manual'
|
||||
"
|
||||
>
|
||||
<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"
|
||||
:popConfirm="item.popConfirm"
|
||||
:tooltip="{ ...item.tootip }"
|
||||
@click="item.onClick"
|
||||
>
|
||||
<AIcon
|
||||
|
@ -109,9 +89,7 @@
|
|||
<AIcon :type="item.icon" />
|
||||
<span>{{ item?.text }}</span>
|
||||
</template>
|
||||
</a-button>
|
||||
</template>
|
||||
</a-tooltip>
|
||||
</PermissionButton>
|
||||
</template>
|
||||
</CardBox>
|
||||
</template>
|
||||
|
@ -151,45 +129,29 @@
|
|||
</template>
|
||||
<template #action="slotProps">
|
||||
<a-space :size="16">
|
||||
<a-tooltip
|
||||
v-for="i in getActions(slotProps)"
|
||||
<template
|
||||
v-for="i in getActions(slotProps, 'table')"
|
||||
:key="i.key"
|
||||
v-bind="i.tooltip"
|
||||
>
|
||||
<span
|
||||
<PermissionButton
|
||||
v-if="
|
||||
i.key != 'trigger' ||
|
||||
slotProps.sceneTriggerType == 'manual'
|
||||
"
|
||||
>
|
||||
<a-popconfirm
|
||||
v-if="i.popConfirm"
|
||||
v-bind="i.popConfirm"
|
||||
okText="确定"
|
||||
cancelText="取消"
|
||||
>
|
||||
<a-button
|
||||
:disabled="i.disabled"
|
||||
style="padding: 0"
|
||||
:popConfirm="i.popConfirm"
|
||||
:tooltip="{
|
||||
...i.tooltip,
|
||||
}"
|
||||
@click="i.onClick"
|
||||
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)"
|
||||
style="padding: 0px"
|
||||
>
|
||||
<a-button
|
||||
:disabled="i.disabled"
|
||||
style="padding: 0"
|
||||
type="link"
|
||||
<template #icon
|
||||
><AIcon :type="i.icon"
|
||||
/></a-button>
|
||||
</a-button>
|
||||
</span>
|
||||
</a-tooltip>
|
||||
/></template>
|
||||
</PermissionButton>
|
||||
</template>
|
||||
</a-space>
|
||||
</template>
|
||||
</JTable>
|
||||
|
@ -214,7 +176,6 @@ import { message } from 'ant-design-vue';
|
|||
import { getImage } from '@/utils/comm';
|
||||
import { useMenuStore } from '@/store/menu';
|
||||
import encodeQuery from '@/utils/encodeQuery';
|
||||
import { useStorage } from '@vueuse/core';
|
||||
const params = ref<Record<string, any>>({});
|
||||
let isAdd = ref<number>(0);
|
||||
let title = ref<string>('');
|
||||
|
@ -291,7 +252,10 @@ const columns = [
|
|||
}),
|
||||
);
|
||||
if (res.status === 200) {
|
||||
return res.result.map((item:any) => ({label:item.name, value:item.id}))
|
||||
return res.result.map((item: any) => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
}));
|
||||
}
|
||||
return [];
|
||||
},
|
||||
|
@ -322,7 +286,7 @@ const columns = [
|
|||
key: 'description',
|
||||
search: {
|
||||
type: 'string',
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
|
@ -396,7 +360,11 @@ const getActions = (
|
|||
|
||||
icon: 'EditOutlined',
|
||||
onClick: () => {
|
||||
menuStory.jumpPage('rule-engine/Alarm/Configuration/Save',{},{id:data.id});
|
||||
menuStory.jumpPage(
|
||||
'rule-engine/Alarm/Configuration/Save',
|
||||
{},
|
||||
{ id: data.id },
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -456,8 +424,6 @@ const getActions = (
|
|||
icon: 'DeleteOutlined',
|
||||
},
|
||||
];
|
||||
if (type === 'card')
|
||||
return actions.filter((i: ActionsType) => i.key !== 'view');
|
||||
return actions;
|
||||
};
|
||||
const add = () => {
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
<template>
|
||||
<page-container>
|
||||
<Search :columns="columns" target="alarm-log-detail"></Search>
|
||||
<JTable :columns="columns" model="TABLE" :request="queryList"></JTable>
|
||||
</page-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
const columns = [{
|
||||
title:'告警时间',
|
||||
dataIndex:'alarmTime',
|
||||
key:'alarmTime',
|
||||
search:{
|
||||
type:'date'
|
||||
}
|
||||
},{
|
||||
title:'告警名称',
|
||||
dataIndex:'alarmConfigName',
|
||||
key:'alarmConfigName',
|
||||
},{
|
||||
title:'说明',
|
||||
dataIndex:'description',
|
||||
key:'description'
|
||||
},{
|
||||
title:'操作',
|
||||
dataIndex:'action',
|
||||
key:'action'
|
||||
}]
|
||||
|
||||
/**
|
||||
* 获取详情列表
|
||||
*/
|
||||
const queryList = () =>{
|
||||
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
</style>
|
|
@ -0,0 +1,79 @@
|
|||
<template>
|
||||
<a-modal
|
||||
title="告警处理"
|
||||
okText="确定"
|
||||
cancelText="取消"
|
||||
visible
|
||||
@cancel="handleCancel"
|
||||
@ok="handleSave"
|
||||
destroyOnClose
|
||||
:confirmLoading="loading"
|
||||
>
|
||||
<a-form :rules="rules" layout="vertical" ref="formRef" :model="form">
|
||||
<a-form-item label="处理结果" name="describe">
|
||||
<a-textarea
|
||||
:rows="8"
|
||||
:maxlength="200"
|
||||
showCount
|
||||
placeholder="请输入处理结果"
|
||||
v-model:value="form.describe"
|
||||
></a-textarea>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { handleLog } from '@/api/rule-engine/log';
|
||||
import { onlyMessage } from '@/utils/comm';
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
},
|
||||
});
|
||||
const loading = ref<boolean>(false);
|
||||
const formRef = ref();
|
||||
const rules = {
|
||||
describe: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入处理结果',
|
||||
},
|
||||
],
|
||||
};
|
||||
const form = reactive({
|
||||
describe: '',
|
||||
});
|
||||
let visible = ref(true);
|
||||
const emit = defineEmits(['closeSolve'])
|
||||
const handleCancel = () => {
|
||||
emit('closeSolve');
|
||||
};
|
||||
const handleSave = () => {
|
||||
loading.value = true;
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(async () => {
|
||||
const res = await handleLog({
|
||||
describe: form.describe,
|
||||
type: 'user',
|
||||
state: 'normal',
|
||||
alarmRecordId: props.data?.current?.id || '',
|
||||
alarmConfigId: props.data?.current?.alarmConfigId || '',
|
||||
alarmTime: props?.data?.current?.alarmTime || '',
|
||||
});
|
||||
if (res.status === 200) {
|
||||
onlyMessage('操作成功!');
|
||||
} else {
|
||||
onlyMessage('操作失败!', 'error');
|
||||
}
|
||||
loading.value = false;
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error);
|
||||
loading.value = false;
|
||||
});
|
||||
};
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
</style>
|
|
@ -24,11 +24,18 @@
|
|||
v-if="props.type === 'org'"
|
||||
@search="search"
|
||||
></Search>
|
||||
<JTable :columns="columns" :request="handleSearch" :params="params">
|
||||
<JTable
|
||||
:columns="columns"
|
||||
:request="handleSearch"
|
||||
:params="params"
|
||||
:gridColumn="2"
|
||||
model="CARD"
|
||||
>
|
||||
<template #card="slotProps">
|
||||
<CardBox
|
||||
:value="slotProps"
|
||||
v-bind="slotProps"
|
||||
:actions="getActions(slotProps, 'card')"
|
||||
:statusText="
|
||||
data.defaultLevel.find(
|
||||
(i) => i.level === slotProps.level,
|
||||
|
@ -39,7 +46,7 @@
|
|||
<img :src="imgMap.get(slotProps.targetType)" alt="" />
|
||||
</template>
|
||||
<template #content>
|
||||
<Ellipsis>
|
||||
<Ellipsis style="width: calc(100% - 100px)">
|
||||
<span style="font-weight: 500">
|
||||
{{ slotProps.alarmName }}
|
||||
</span>
|
||||
|
@ -90,10 +97,25 @@
|
|||
</span>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
</template>
|
||||
<template #actions="item">
|
||||
<PermissionButton
|
||||
:disabled="item.key === 'solve' && slotProps.state.value ==='normal'"
|
||||
:popConfirm="item.popConfirm"
|
||||
:tooltip="{
|
||||
...item.tooltip,
|
||||
}"
|
||||
@click="item.onClick"
|
||||
>
|
||||
<AIcon :type="item.icon" />
|
||||
<span>{{ item?.text }}</span>
|
||||
</PermissionButton>
|
||||
</template>
|
||||
</CardBox>
|
||||
</template>
|
||||
</JTable>
|
||||
<SolveLog :data="data" v-if="data.solveVisible" @closeSolve="closeSolve"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -111,6 +133,11 @@ import { useAlarmStore } from '@/store/alarm';
|
|||
import { storeToRefs } from 'pinia';
|
||||
import { Store } from 'jetlinks-store';
|
||||
import moment from 'moment';
|
||||
import type { ActionsType } from '@/components/Table';
|
||||
import SolveLog from '../SolveLog/index.vue'
|
||||
import { useMenuStore } from '@/store/menu';
|
||||
const menuStory = useMenuStore();
|
||||
|
||||
const alarmStore = useAlarmStore();
|
||||
const { data } = storeToRefs(alarmStore);
|
||||
const getDefaulitLevel = () => {
|
||||
|
@ -156,11 +183,11 @@ const columns = [
|
|||
},
|
||||
},
|
||||
{
|
||||
title: '最近告警事件',
|
||||
title: '最近告警时间',
|
||||
dataIndex: 'alarmTime',
|
||||
key: 'alarmTime',
|
||||
search: {
|
||||
type: 'dateTime',
|
||||
type: 'date',
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -254,12 +281,7 @@ let param = reactive({
|
|||
pageSize: 10,
|
||||
terms: [],
|
||||
});
|
||||
// let dataSource = reactive({
|
||||
// data: [],
|
||||
// pageSize: 10,
|
||||
// pageIndex: 0,
|
||||
// total: 0,
|
||||
// });
|
||||
|
||||
const handleSearch = async (params: any) => {
|
||||
const resp = await query(params);
|
||||
if (resp.status === 200) {
|
||||
|
@ -284,33 +306,97 @@ const handleSearch = async (params: any) => {
|
|||
};
|
||||
watchEffect(() => {
|
||||
if (props.type !== 'all' && !props.id) {
|
||||
params.value.terms.push({
|
||||
params.value.terms = [
|
||||
{
|
||||
termType: 'eq',
|
||||
column: 'targetType',
|
||||
value: props.type,
|
||||
type: 'and',
|
||||
});
|
||||
},
|
||||
];
|
||||
}
|
||||
if (props.id) {
|
||||
params.value.terms.push({
|
||||
params.value.terms = [
|
||||
{
|
||||
termType: 'eq',
|
||||
column: 'alarmConfigId',
|
||||
value: props.id,
|
||||
type: 'and',
|
||||
});
|
||||
},
|
||||
];
|
||||
}
|
||||
if(props.type === 'all'){
|
||||
params.value.terms = [];
|
||||
}
|
||||
});
|
||||
|
||||
const search = (data: any) => {
|
||||
const dt = {
|
||||
pageSize: 10,
|
||||
terms: [...data?.terms],
|
||||
params.value.terms = [...data?.terms];
|
||||
if (props.type !== 'all' && !props.id) {
|
||||
params.value.terms.push(
|
||||
{
|
||||
termType: 'eq',
|
||||
column: 'targetType',
|
||||
value: props.type,
|
||||
type: 'and',
|
||||
},
|
||||
);
|
||||
}
|
||||
if (props.id) {
|
||||
params.value.terms.push (
|
||||
{
|
||||
termType: 'eq',
|
||||
column: 'alarmConfigId',
|
||||
value: props.id,
|
||||
type: 'and',
|
||||
},
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const getActions = (
|
||||
currentData: Partial<Record<string, any>>,
|
||||
type: 'card',
|
||||
): ActionsType[] => {
|
||||
if (!currentData) return [];
|
||||
const actions = [
|
||||
{
|
||||
key: 'solve',
|
||||
text: '告警处理',
|
||||
tooltip: {
|
||||
title: '告警处理',
|
||||
},
|
||||
icon: 'ToolOutlined',
|
||||
onClick: () =>{
|
||||
data.value.current = currentData;
|
||||
data.value.solveVisible = true;
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'log',
|
||||
text: '告警日志',
|
||||
tooltip: {
|
||||
title: '告警日志',
|
||||
},
|
||||
icon: 'FileOutlined',
|
||||
onClick: () =>{
|
||||
menuStory.jumpPage(`rule-engine/Alarm/Log/Detail`,{id:currentData.id});
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'detail',
|
||||
text: '处理记录',
|
||||
tooltip: {
|
||||
title: '处理记录',
|
||||
},
|
||||
icon: 'FileTextOutlined',
|
||||
},
|
||||
];
|
||||
return actions;
|
||||
};
|
||||
const log = () => {
|
||||
console.log(data.value.defaultLevel);
|
||||
};
|
||||
log();
|
||||
const closeSolve = () =>{
|
||||
data.value.solveVisible = false
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
</style>
|
||||
|
|
|
@ -40,7 +40,7 @@
|
|||
</slot>
|
||||
</template>
|
||||
<template #content>
|
||||
<Ellipsis>
|
||||
<Ellipsis style="width: calc(100% - 100px)">
|
||||
<span style="font-weight: 600; font-size: 16px">
|
||||
{{ slotProps.name }}
|
||||
</span>
|
||||
|
@ -56,31 +56,12 @@
|
|||
</a-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
|
||||
<PermissionButton
|
||||
:disabled="item.disabled"
|
||||
:popConfirm="item.popConfirm"
|
||||
:tooltip="{
|
||||
...item.tooltip,
|
||||
}"
|
||||
@click="item.onClick"
|
||||
>
|
||||
<AIcon
|
||||
|
@ -91,9 +72,7 @@
|
|||
<AIcon :type="item.icon" />
|
||||
<span>{{ item?.text }}</span>
|
||||
</template>
|
||||
</a-button>
|
||||
</template>
|
||||
</a-tooltip>
|
||||
</PermissionButton>
|
||||
</template>
|
||||
</CardBox>
|
||||
</template>
|
||||
|
@ -113,38 +92,26 @@
|
|||
</template>
|
||||
<template #action="slotProps">
|
||||
<a-space :size="16">
|
||||
<a-tooltip
|
||||
v-for="i in getActions(slotProps)"
|
||||
<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"
|
||||
okText="确定"
|
||||
cancelText="取消"
|
||||
>
|
||||
<a-button
|
||||
<PermissionButton
|
||||
:disabled="i.disabled"
|
||||
style="padding: 0"
|
||||
:popConfirm="i.popConfirm"
|
||||
:tooltip="{
|
||||
...i.tooltip,
|
||||
}"
|
||||
@click="i.onClick"
|
||||
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)"
|
||||
style="padding: 0px"
|
||||
:hasPermission="'device/Instance:' + i.key"
|
||||
>
|
||||
<a-button
|
||||
:disabled="i.disabled"
|
||||
style="padding: 0"
|
||||
type="link"
|
||||
<template #icon
|
||||
><AIcon :type="i.icon"
|
||||
/></a-button>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
/></template>
|
||||
</PermissionButton>
|
||||
</template>
|
||||
</a-space>
|
||||
</template>
|
||||
</JTable>
|
||||
|
|
|
@ -28,7 +28,7 @@ type Emit = {
|
|||
const options = [
|
||||
{ value: 'device', label: '设备触发', tip: '适用于设备数据或行为满足触发条件时,执行指定的动作', image: getImage('/device-trigger.png') },
|
||||
{ value: 'manual', label: '手动触发', tip: '适用于第三方平台向物联网平台下发指令控制设备', image: getImage('/manual-trigger.png') },
|
||||
{ value: 'timing', label: '定时触发', tip: '适用于定期执行固定任务', image: getImage('/timing-trigger.png') },
|
||||
{ value: 'timer', label: '定时触发', tip: '适用于定期执行固定任务', image: getImage('/timing-trigger.png') },
|
||||
]
|
||||
|
||||
const props = defineProps({
|
||||
|
@ -63,7 +63,7 @@ const handleClick = (type: string) => {
|
|||
</script>
|
||||
|
||||
<style scoped lang='less'>
|
||||
@import 'ant-design-vue/es/style/themes/default.less';
|
||||
// @import 'ant-design-vue/es/style/themes/default.less';
|
||||
|
||||
.scene-trigger-way-warp {display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
|
|
@ -66,6 +66,10 @@ const props = defineProps({
|
|||
}
|
||||
})
|
||||
|
||||
watchEffect(() => {
|
||||
Object.assign(formModel, props.data)
|
||||
})
|
||||
|
||||
const emit = defineEmits<Emit>()
|
||||
|
||||
const title = computed(() => {
|
||||
|
|
|
@ -0,0 +1,370 @@
|
|||
<template>
|
||||
<div class="card">
|
||||
<div
|
||||
class="card-warp"
|
||||
:class="{ active: active ? 'active' : '' }"
|
||||
@click="handleClick"
|
||||
>
|
||||
<div class="card-type">
|
||||
<div class="card-type-text"><slot name="type"></slot></div>
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<div style="display: flex">
|
||||
<!-- 图片 -->
|
||||
<div class="card-item-avatar">
|
||||
<slot name="img"> </slot>
|
||||
</div>
|
||||
<!-- 内容 -->
|
||||
<div class="card-item-body">
|
||||
<slot name="title"></slot>
|
||||
<span class="subTitle">
|
||||
<slot name="subTitle"></slot>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 勾选 -->
|
||||
<div v-if="active" class="checked-icon">
|
||||
<div>
|
||||
<AIcon type="CheckOutlined" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 状态 -->
|
||||
<div
|
||||
v-if="showStatus"
|
||||
class="card-state"
|
||||
:class="statusNames ? statusNames[status] : ''"
|
||||
>
|
||||
<div class="card-state-content">
|
||||
<BadgeStatus
|
||||
:status="status"
|
||||
:text="statusText"
|
||||
:statusNames="statusNames"
|
||||
></BadgeStatus>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 按钮 -->
|
||||
<slot name="bottom-tool">
|
||||
<div
|
||||
v-if="showTool && actions && actions.length"
|
||||
class="card-tools"
|
||||
>
|
||||
<div
|
||||
v-for="item in actions"
|
||||
:key="item.key"
|
||||
class="card-button"
|
||||
:class="{
|
||||
delete: item.key === 'delete',
|
||||
}"
|
||||
>
|
||||
<slot name="actions" v-bind="item"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import BadgeStatus from '@/components/BadgeStatus/index.vue';
|
||||
import { StatusColorEnum } from '@/utils/consts.ts';
|
||||
import type { ActionsType } from '@/components/Table/index.vue';
|
||||
import { PropType } from 'vue';
|
||||
|
||||
type EmitProps = {
|
||||
(e: 'click', data: Record<string, any>): void;
|
||||
};
|
||||
|
||||
type TableActionsType = Partial<ActionsType>;
|
||||
|
||||
const emit = defineEmits<EmitProps>();
|
||||
|
||||
const props = defineProps({
|
||||
value: {
|
||||
type: Object as PropType<Record<string, any>>,
|
||||
default: () => {},
|
||||
},
|
||||
showStatus: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
showTool: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
statusText: {
|
||||
type: String,
|
||||
default: '正常',
|
||||
},
|
||||
status: {
|
||||
type: [String, Number],
|
||||
default: 'default',
|
||||
},
|
||||
statusNames: {
|
||||
type: Object,
|
||||
},
|
||||
actions: {
|
||||
type: Array as PropType<TableActionsType[]>,
|
||||
default: () => [],
|
||||
},
|
||||
active: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
const handleClick = () => {
|
||||
emit('click', props.value);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.card {
|
||||
width: 100%;
|
||||
background-color: #fff;
|
||||
.checked-icon {
|
||||
position: absolute;
|
||||
right: -22px;
|
||||
bottom: -22px;
|
||||
z-index: 2;
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
color: #fff;
|
||||
background-color: red;
|
||||
background-color: #2f54eb;
|
||||
transform: rotate(-45deg);
|
||||
|
||||
> div {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
transform: rotate(45deg);
|
||||
|
||||
> span {
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
left: 6px;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.card-warp {
|
||||
position: relative;
|
||||
border: 1px solid #e6e6e6;
|
||||
overflow: hidden;
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
box-shadow: 0 0 24px rgba(#000, 0.1);
|
||||
|
||||
.card-mask {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
|
||||
&.active {
|
||||
position: relative;
|
||||
border: 1px solid #2f54eb;
|
||||
}
|
||||
|
||||
.card-type {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -14px;
|
||||
height: 32px;
|
||||
padding: 0 30px;
|
||||
color: rgba(0, 0, 0, 0.65);
|
||||
line-height: 32px;
|
||||
background-color: rgba(0, 0, 0, 0.06);
|
||||
transform: skewX(-45deg);
|
||||
.card-type-text {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transform: skewX(45deg);
|
||||
}
|
||||
}
|
||||
|
||||
.card-content {
|
||||
position: relative;
|
||||
padding: 43px 12px 19px 30px;
|
||||
overflow: hidden;
|
||||
|
||||
.card-item-avatar {
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
.card-item-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
width: 0;
|
||||
|
||||
.subTitle {
|
||||
color: rgba(0, 0, 0, 0.65);
|
||||
font-size: 14px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.card-state {
|
||||
position: absolute;
|
||||
top: 40px;
|
||||
right: -12px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: 100px;
|
||||
padding: 2px 0;
|
||||
background-color: rgba(#5995f5, 0.15);
|
||||
transform: skewX(45deg);
|
||||
|
||||
&.success {
|
||||
background-color: @success-color-deprecated-bg;
|
||||
}
|
||||
|
||||
&.warning {
|
||||
background-color: rgba(#ff9000, 0.1);
|
||||
}
|
||||
|
||||
&.error {
|
||||
background-color: rgba(#e50012, 0.1);
|
||||
}
|
||||
|
||||
.card-state-content {
|
||||
transform: skewX(-45deg);
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.card-item-content-title) {
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: @primary-color;
|
||||
width: calc(100% - 100px);
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
:deep(.card-item-heard-name) {
|
||||
font-weight: 700;
|
||||
font-size: 16px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
:deep(.card-item-content-text) {
|
||||
color: rgba(0, 0, 0, 0.75);
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.item-active {
|
||||
position: relative;
|
||||
color: #2f54eb;
|
||||
|
||||
.checked-icon {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.card-warp {
|
||||
border: 1px solid #2f54eb;
|
||||
}
|
||||
}
|
||||
|
||||
.card-tools {
|
||||
display: flex;
|
||||
margin-top: 8px;
|
||||
|
||||
.card-button {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
|
||||
& > :deep(span, button) {
|
||||
width: 100%;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
:deep(button) {
|
||||
width: 100%;
|
||||
border-radius: 0;
|
||||
background: #f6f6f6;
|
||||
border: 1px solid #e6e6e6;
|
||||
color: #2f54eb;
|
||||
|
||||
&:hover {
|
||||
background-color: @primary-color-hover;
|
||||
border-color: @primary-color-hover;
|
||||
|
||||
span {
|
||||
color: #fff !important;
|
||||
}
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: @primary-color-active;
|
||||
border-color: @primary-color-active;
|
||||
|
||||
span {
|
||||
color: #fff !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
&.delete {
|
||||
flex-basis: 60px;
|
||||
flex-grow: 0;
|
||||
|
||||
:deep(button) {
|
||||
background: @error-color-deprecated-bg;
|
||||
border: 1px solid @error-color-outline;
|
||||
|
||||
span {
|
||||
color: @error-color !important;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: @error-color-hover;
|
||||
|
||||
span {
|
||||
color: #fff !important;
|
||||
}
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: @error-color-active;
|
||||
|
||||
span {
|
||||
color: #fff !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(button[disabled]) {
|
||||
background: @disabled-bg;
|
||||
border-color: @disabled-color;
|
||||
|
||||
span {
|
||||
color: @disabled-color !important;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: @disabled-active-bg;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: @disabled-active-bg;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,28 +1,167 @@
|
|||
<template>
|
||||
<page-container>
|
||||
<search
|
||||
:columns='columns'
|
||||
/>
|
||||
<j-table
|
||||
:columns='columns'
|
||||
<Search :columns="columns" target="scene" @search="handleSearch" />
|
||||
<JTable
|
||||
ref="sceneRef"
|
||||
:columns="columns"
|
||||
:request="query"
|
||||
:defaultParams="{ sorts: [{ name: 'createTime', order: 'desc' }] }"
|
||||
:params="params"
|
||||
>
|
||||
<template #headerTitle>
|
||||
<a-space>
|
||||
<a-button type="primary" @click="visible = true">新增</a-button>
|
||||
<PermissionButton
|
||||
type="primary"
|
||||
@click="handleAdd"
|
||||
hasPermission="device/Instance:add"
|
||||
>
|
||||
<template #icon><AIcon type="PlusOutlined" /></template>
|
||||
新增
|
||||
</PermissionButton>
|
||||
</a-space>
|
||||
</template>
|
||||
</j-table>
|
||||
<SaveModal v-if='visible' @close='visible = false'/>
|
||||
<template #card="slotProps">
|
||||
<SceneCard
|
||||
:value="slotProps"
|
||||
@click="handleClick"
|
||||
:actions="getActions(slotProps, 'card')"
|
||||
:status="slotProps.state?.value"
|
||||
:statusText="slotProps.state?.text"
|
||||
:statusNames="{
|
||||
started: 'success',
|
||||
disable: 'error',
|
||||
}"
|
||||
>
|
||||
<template #type>
|
||||
<span
|
||||
><img
|
||||
:height="16"
|
||||
:src="typeMap.get(slotProps.triggerType)?.icon"
|
||||
style="margin-right: 5px"
|
||||
/>{{
|
||||
typeMap.get(slotProps.triggerType)?.text
|
||||
}}</span
|
||||
>
|
||||
</template>
|
||||
<template #img>
|
||||
<img :src="typeMap.get(slotProps.triggerType)?.img" />
|
||||
</template>
|
||||
<template #title>
|
||||
<Ellipsis style="width: calc(100% - 100px)">
|
||||
<span
|
||||
style="font-size: 16px; font-weight: 600"
|
||||
@click.stop="handleView(slotProps.id)"
|
||||
>
|
||||
{{ slotProps.name }}
|
||||
</span>
|
||||
</Ellipsis>
|
||||
</template>
|
||||
<template #subTitle>
|
||||
<Ellipsis :lineClamp="2">
|
||||
说明:{{
|
||||
slotProps?.description ||
|
||||
typeMap.get(slotProps.triggerType)?.tip
|
||||
}}
|
||||
</Ellipsis>
|
||||
</template>
|
||||
<template #actions="item">
|
||||
<PermissionButton
|
||||
:disabled="item.disabled"
|
||||
:popConfirm="item.popConfirm"
|
||||
:tooltip="{
|
||||
...item.tooltip,
|
||||
}"
|
||||
@click="item.onClick"
|
||||
:hasPermission="'rule-engine/Scene:' + item.key"
|
||||
>
|
||||
<AIcon
|
||||
type="DeleteOutlined"
|
||||
v-if="item.key === 'delete'"
|
||||
/>
|
||||
<template v-else>
|
||||
<AIcon :type="item.icon" />
|
||||
<span>{{ item?.text }}</span>
|
||||
</template>
|
||||
</PermissionButton>
|
||||
</template>
|
||||
</SceneCard>
|
||||
</template>
|
||||
<template #triggerType="slotProps">
|
||||
{{ typeMap.get(slotProps.triggerType)?.text }}
|
||||
</template>
|
||||
<template #state="slotProps">
|
||||
<a-badge
|
||||
:text="slotProps.state?.text"
|
||||
:status="statusMap.get(slotProps.state?.value)"
|
||||
/>
|
||||
</template>
|
||||
<template #action="slotProps">
|
||||
<a-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: 0px"
|
||||
:hasPermission="'rule-engine/Scene:' + i.key"
|
||||
>
|
||||
<template #icon><AIcon :type="i.icon" /></template>
|
||||
</PermissionButton>
|
||||
</template>
|
||||
</a-space>
|
||||
</template>
|
||||
</JTable>
|
||||
<SaveModal v-if="visible" @close="visible = false" :data="current" />
|
||||
</page-container>
|
||||
</template>
|
||||
|
||||
<script setup lang='ts'>
|
||||
import SaveModal from './Save/save.vue'
|
||||
import type { SceneItem } from './typings'
|
||||
import { useMenuStore } from 'store/menu'
|
||||
import SaveModal from './Save/save.vue';
|
||||
import type { SceneItem } from './typings';
|
||||
import { useMenuStore } from 'store/menu';
|
||||
import { query, _delete, _action } from '@/api/rule-engine/scene';
|
||||
import { message } from 'ant-design-vue';
|
||||
import type { ActionsType } from '@/components/Table';
|
||||
import { getImage } from '@/utils/comm';
|
||||
import SceneCard from './SceneCard.vue';
|
||||
|
||||
const menuStory = useMenuStore()
|
||||
const visible = ref<boolean>(false)
|
||||
const menuStory = useMenuStore();
|
||||
const visible = ref<boolean>(false);
|
||||
const current = ref<Record<string, any>>({});
|
||||
|
||||
const statusMap = new Map();
|
||||
statusMap.set('started', 'success');
|
||||
statusMap.set('disable', 'error');
|
||||
|
||||
const params = ref<Record<string, any>>({});
|
||||
const sceneRef = ref<Record<string, any>>({});
|
||||
|
||||
const typeMap = new Map();
|
||||
typeMap.set('manual', {
|
||||
text: '手动触发',
|
||||
img: getImage('/scene/scene-hand.png'),
|
||||
icon: getImage('/scene/trigger-type-icon/manual.png'),
|
||||
tip: '适用于第三方平台向物联网平台下发指令控制设备',
|
||||
});
|
||||
typeMap.set('timer', {
|
||||
text: '定时触发',
|
||||
img: getImage('/scene/scene-timer.png'),
|
||||
icon: getImage('/scene/trigger-type-icon/timing.png'),
|
||||
tip: '适用于定期执行固定任务',
|
||||
});
|
||||
typeMap.set('device', {
|
||||
text: '设备触发',
|
||||
img: getImage('/scene/scene-device.png'),
|
||||
icon: getImage('/scene/trigger-type-icon/device.png'),
|
||||
tip: '适用于设备数据或行为满足触发条件时,执行指定的动作',
|
||||
});
|
||||
|
||||
const columns = [
|
||||
{
|
||||
|
@ -32,37 +171,170 @@ const columns = [
|
|||
width: 300,
|
||||
title: '名称',
|
||||
search: {
|
||||
type: 'string'
|
||||
}
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
{
|
||||
dataIndex: 'triggerType',
|
||||
title: '触发方式',
|
||||
scopedSlots: true,
|
||||
search: {
|
||||
type: 'select',
|
||||
options: [
|
||||
{ label: '手动触发', value: 'manual'},
|
||||
{ label: '定时触发', value: 'timer'},
|
||||
{ label: '设备触发', value: 'device'}
|
||||
]
|
||||
}
|
||||
options: Array.from(typeMap).map((item) => ({
|
||||
label: item[1],
|
||||
value: item[0],
|
||||
})),
|
||||
},
|
||||
{
|
||||
dataIndex: 'description',
|
||||
title: '说明',
|
||||
},
|
||||
{
|
||||
dataIndex: 'state',
|
||||
title: '状态',
|
||||
scopedSlots: true,
|
||||
search: {
|
||||
type: 'select',
|
||||
options: [
|
||||
{ label: '正常', value: 'started' },
|
||||
{ label: '禁用', value: 'disable' },
|
||||
]
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
dataIndex: 'description',
|
||||
title: '说明',
|
||||
search: {
|
||||
type: 'string',
|
||||
},
|
||||
scopedSlots: true,
|
||||
},
|
||||
{
|
||||
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: ActionsType[] = [
|
||||
{
|
||||
key: 'update',
|
||||
text: '编辑',
|
||||
tooltip: {
|
||||
title: '编辑',
|
||||
},
|
||||
icon: 'EditOutlined',
|
||||
onClick: () => {
|
||||
visible.value = true;
|
||||
current.value = data;
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'action',
|
||||
text: data.state?.value !== 'disable' ? '禁用' : '启用',
|
||||
tooltip: {
|
||||
title: !(!!data.triggerType && (data.branches || [])?.length)
|
||||
? '未配置规则的不能启用'
|
||||
: data.state?.value !== 'disable'
|
||||
? '禁用'
|
||||
: '启用',
|
||||
},
|
||||
disabled: !(!!data?.triggerType && (data?.branches || [])?.length),
|
||||
icon:
|
||||
data.state.value !== 'disable'
|
||||
? 'StopOutlined'
|
||||
: 'CheckCircleOutlined',
|
||||
popConfirm: {
|
||||
title: `确认${
|
||||
data.state.value !== 'disable' ? '禁用' : '启用'
|
||||
}?`,
|
||||
onConfirm: async () => {
|
||||
let response = undefined;
|
||||
if (data.state.value !== 'disable') {
|
||||
response = await _action(data.id, '_disable');
|
||||
} else {
|
||||
response = await _action(data.id, '_enable');
|
||||
}
|
||||
if (response && response.status === 200) {
|
||||
message.success('操作成功!');
|
||||
sceneRef.value?.reload();
|
||||
} else {
|
||||
message.error('操作失败!');
|
||||
}
|
||||
]
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'delete',
|
||||
text: '删除',
|
||||
disabled: data.state?.value !== 'disable',
|
||||
tooltip: {
|
||||
title:
|
||||
data.state.value !== 'disable'
|
||||
? '请先禁用该场景,再删除'
|
||||
: '删除',
|
||||
},
|
||||
popConfirm: {
|
||||
title: '确认删除?',
|
||||
onConfirm: async () => {
|
||||
const resp = await _delete(data.id);
|
||||
if (resp.status === 200) {
|
||||
message.success('操作成功!');
|
||||
sceneRef.value?.reload();
|
||||
} else {
|
||||
message.error('操作失败!');
|
||||
}
|
||||
},
|
||||
},
|
||||
icon: 'DeleteOutlined',
|
||||
},
|
||||
];
|
||||
if (data.triggerType === 'manual') {
|
||||
const _item: ActionsType = {
|
||||
key: 'trigger',
|
||||
text: '手动触发',
|
||||
disabled: data.state?.value === 'disable',
|
||||
tooltip: {
|
||||
title:
|
||||
data.state.value !== 'disable'
|
||||
? '手动触发'
|
||||
: '未启用,不能手动触发',
|
||||
},
|
||||
icon: 'LikeOutlined',
|
||||
onClick: () => {
|
||||
// handleView(data.id, data.triggerType);
|
||||
},
|
||||
};
|
||||
actions.splice(1, 0, _item);
|
||||
}
|
||||
if (type === 'table') {
|
||||
actions.splice(0, 0, {
|
||||
key: 'view',
|
||||
text: '查看',
|
||||
tooltip: {
|
||||
title: '查看',
|
||||
},
|
||||
icon: 'EyeOutlined',
|
||||
onClick: () => {
|
||||
handleView(data.id, data.triggerType);
|
||||
},
|
||||
});
|
||||
}
|
||||
return actions;
|
||||
};
|
||||
|
||||
const handleSearch = (_params: any) => {
|
||||
params.value = _params;
|
||||
};
|
||||
|
||||
const handleAdd = () => {
|
||||
visible.value = true;
|
||||
current.value = {};
|
||||
};
|
||||
|
||||
/**
|
||||
* 编辑
|
||||
|
@ -70,8 +342,12 @@ const columns = [
|
|||
* @param triggerType 触发类型
|
||||
*/
|
||||
const handleEdit = (id: string, triggerType: string) => {
|
||||
menuStory.jumpPage('Scene/Save', { }, { triggerType: triggerType, id, type: 'edit' })
|
||||
}
|
||||
menuStory.jumpPage(
|
||||
'rule-engine/Scene/Save',
|
||||
{},
|
||||
{ triggerType: triggerType, id, type: 'edit' },
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* 查看
|
||||
|
@ -79,10 +355,13 @@ const handleEdit = (id: string, triggerType: string) => {
|
|||
* @param triggerType 触发类型
|
||||
*/
|
||||
const handleView = (id: string, triggerType: string) => {
|
||||
menuStory.jumpPage('Scene/Save', { }, { triggerType: triggerType, id, type: 'view' })
|
||||
menuStory.jumpPage(
|
||||
'rule-engine/Scene/Save',
|
||||
{},
|
||||
{ triggerType: triggerType, id, type: 'view' },
|
||||
);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -1,12 +1,13 @@
|
|||
<template>
|
||||
<page-container>
|
||||
<Api :mode="'appManger'" hasHome>
|
||||
</Api>
|
||||
<Api :mode="'appManger'" hasHome :code="code" />
|
||||
</page-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="apiPage">
|
||||
import Api from '@/views/system/Platforms/Api/index.vue';
|
||||
const route = useRoute()
|
||||
const code = route.query.code as string
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
|
@ -1,14 +1,45 @@
|
|||
<template>
|
||||
<page-container>
|
||||
<Api :mode="'appManger'" hasHome showTitle :code="code">
|
||||
<Api :mode="'home'" hasHome showTitle :code="clientId">
|
||||
<template #top>
|
||||
<div class="card">
|
||||
<h3 style="margin: 0 0 24px 0">基本信息</h3>
|
||||
<p>
|
||||
<span style="font-weight: bold">clientId: </span>
|
||||
<span>{{ clientId }}</span>
|
||||
</p>
|
||||
<p>
|
||||
<span style="font-weight: bold">secureKey:</span>
|
||||
<span>{{ secureKey }}</span>
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
</Api>
|
||||
</page-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="apiPage">
|
||||
import { getAppInfo_api } from '@/api/system/apply';
|
||||
import Api from '@/views/system/Platforms/Api/index.vue';
|
||||
const route = useRoute()
|
||||
const code = route.query.code as string
|
||||
const route = useRoute();
|
||||
const clientId = route.query.code as string;
|
||||
|
||||
const secureKey = ref<string>('');
|
||||
|
||||
getAppInfo_api(clientId).then((resp: any) => {
|
||||
secureKey.value = resp.result.apiServer.secureKey;
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
<style lang="less" scoped>
|
||||
.card {
|
||||
background-color: #fff;
|
||||
padding: 24px;
|
||||
margin-bottom: 24px;
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1906,7 +1906,7 @@ export default [
|
|||
],
|
||||
},
|
||||
{
|
||||
id: 'tigger',
|
||||
id: 'trigger',
|
||||
name: '手动触发',
|
||||
permissions: [
|
||||
{
|
||||
|
@ -2323,7 +2323,7 @@ export default [
|
|||
],
|
||||
},
|
||||
{
|
||||
id: 'tigger',
|
||||
id: 'trigger',
|
||||
name: '手动触发',
|
||||
permissions: [
|
||||
{
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<JTable
|
||||
:columns="columns"
|
||||
:dataSource="props.tableData"
|
||||
:rowSelection="rowSelection"
|
||||
:rowSelection="props.mode !== 'home' ? rowSelection : undefined"
|
||||
noPagination
|
||||
model="TABLE"
|
||||
>
|
||||
|
@ -16,7 +16,9 @@
|
|||
</template>
|
||||
</JTable>
|
||||
|
||||
<a-button type="primary" @click="save">保存</a-button>
|
||||
<a-button type="primary" @click="save" v-if="props.mode !== 'home'"
|
||||
>保存</a-button
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -132,11 +132,9 @@ const filterPath = (path: object, filterArr: string[]) => {
|
|||
delete value[prop];
|
||||
}
|
||||
}
|
||||
if(Object.keys(value).length === 0) delete path[key]
|
||||
if (Object.keys(value).length === 0) delete path[key];
|
||||
}
|
||||
}
|
||||
console.log(path, filterArr);
|
||||
|
||||
return path;
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<div class="top">
|
||||
<slot name="top" />
|
||||
</div>
|
||||
<a-row :gutter="24" style="background-color: #fff; padding: 20px">
|
||||
<a-row :gutter="24" style="background-color: #fff; padding: 20px;margin: 0;">
|
||||
<a-col
|
||||
:span="24"
|
||||
v-if="props.showTitle"
|
||||
|
@ -16,6 +16,7 @@
|
|||
:mode="props.mode"
|
||||
:has-home="props.hasHome"
|
||||
:filter-array="treeFilter"
|
||||
:code="props.code"
|
||||
/>
|
||||
</a-col>
|
||||
<a-col :span="19">
|
||||
|
@ -71,7 +72,6 @@ import ChooseApi from './components/ChooseApi.vue';
|
|||
import ApiDoes from './components/ApiDoes.vue';
|
||||
import ApiTest from './components/ApiTest.vue';
|
||||
|
||||
const route = useRoute();
|
||||
const props = defineProps<{
|
||||
mode: modeType;
|
||||
showTitle?: boolean;
|
||||
|
@ -117,15 +117,17 @@ const initSelectedApi: apiDetailsType = {
|
|||
};
|
||||
const selectedApi = ref<apiDetailsType>(initSelectedApi);
|
||||
|
||||
const canSelectKeys = ref<string[]>([]); // 左侧可展示的项
|
||||
const selectedKeys = ref<string[]>([]); // 右侧默认勾选的项
|
||||
let selectSourceKeys = ref<string[]>([]);
|
||||
init();
|
||||
|
||||
function init() {
|
||||
const code = route.query.code;
|
||||
// 右侧默认选中初始化
|
||||
if (props.mode === 'appManger') {
|
||||
} else if (props.mode === 'home') {
|
||||
getApiGranted_api(props.code as string).then((resp) => {
|
||||
selectedKeys.value = resp.result as string[];
|
||||
selectSourceKeys.value = [...(resp.result as string[])];
|
||||
})
|
||||
} else if (props.mode === 'api') {
|
||||
apiOperations_api().then((resp) => {
|
||||
selectedKeys.value = resp.result as string[];
|
||||
|
|
|
@ -115,7 +115,7 @@
|
|||
>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button
|
||||
<j-button
|
||||
:loading="loading"
|
||||
type="primary"
|
||||
html-type="submit"
|
||||
|
@ -123,7 +123,7 @@
|
|||
block
|
||||
>
|
||||
登录
|
||||
</a-button>
|
||||
</j-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<div class="other">
|
||||
|
@ -133,14 +133,14 @@
|
|||
</div>
|
||||
</a-divider>
|
||||
<div class="other-button">
|
||||
<a-button
|
||||
<j-button
|
||||
v-for="(item, index) in bindings"
|
||||
:key="index"
|
||||
type="link"
|
||||
@Click="handleClickOther(item)"
|
||||
>
|
||||
<img
|
||||
style="width: 32px, height: 33px"
|
||||
style="width: 32px; height: 33px"
|
||||
:alt="item.name"
|
||||
:src="
|
||||
iconMap.get(
|
||||
|
@ -148,7 +148,7 @@
|
|||
) || defaultImg
|
||||
"
|
||||
/>
|
||||
</a-button>
|
||||
</j-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -443,6 +443,7 @@ screenRotation(screenWidth.value, screenHeight.value);
|
|||
position: relative;
|
||||
bottom: 10px;
|
||||
text-align: center;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -463,6 +464,10 @@ screenRotation(screenWidth.value, screenHeight.value);
|
|||
// vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
.login-form-button {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -98,8 +98,8 @@ export default defineConfig(({ mode}) => {
|
|||
preprocessorOptions: {
|
||||
less: {
|
||||
modifyVars: {
|
||||
'root-entry-name': 'variable',
|
||||
hack: `true; @import (reference) "${path.resolve('src/style/variable.less')}";`,
|
||||
...Config.theme,
|
||||
},
|
||||
javascriptEnabled: true,
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue