Merge branch 'dev-hub' into dev

This commit is contained in:
jackhoo_98 2023-02-01 18:14:44 +08:00
commit c384fd6f6c
24 changed files with 1206 additions and 261 deletions

7
src/api/link/log.ts Normal file
View File

@ -0,0 +1,7 @@
import server from '@/utils/request';
export const queryAccess = (data: object) =>
server.post(`/logger/access/_query`, data);
export const querySystem = (data: object) =>
server.post(`/logger/system/_query`, data);

10
src/api/link/protocol.ts Normal file
View File

@ -0,0 +1,10 @@
import server from '@/utils/request';
export const detail = (id: string) => server.get(`/gateway/device/${id}`);
export const save = (data: Object) => server.post(`/gateway/device`, data);
export const list = (data: Object) =>
server.post(`/protocol/_query`, data);
export const remove = (id: string) => server.remove(`/gateway/device/${id}`);

View File

@ -76,3 +76,23 @@ export function getSlotVNode<T>(slots: Slots, props: Record<string, unknown>, pr
} }
return (props[prop] || slots[prop]?.()) as T; return (props[prop] || slots[prop]?.()) as T;
} }
/**
* Select参数column的值
* @param e // 查询参数 e
* @param column {Object} {需要修改的值: 修改后的值}
* {
username: 'context.username',
}
*/
export const modifySearchColumnValue = (e: any, column: object) => {
e.terms.forEach((item: any) => {
item.terms.forEach((t: any) => {
if (column[t.column]) {
t.column = column[t.column];
}
});
});
return e;
};

View File

@ -0,0 +1,255 @@
<template>
<div>
<Search :columns="columns" target="search" @search="handleSearch" />
<JTable
ref="tableRef"
model="TABLE"
:columns="columns"
:request="queryAccess"
:defaultParams="{
sorts: [{ name: 'responseTime', order: 'desc' }],
}"
:params="params"
>
<template #requestTime="slotProps">
{{
moment(slotProps.requestTime).format('YYYY-MM-DD HH:mm:ss')
}}
</template>
<template #responseTime="slotProps">
<a-tag color="purple">
{{ slotProps.responseTime - slotProps.requestTime }} ms
</a-tag>
</template>
<template #username="slotProps">
<a-tag color="geekblue">
{{ slotProps.context.userName }}
</a-tag>
</template>
<template #action="slotProps">
<a-space :size="16">
<a-tooltip
v-for="i in getActions(slotProps)"
:key="i.key"
v-bind="i.tooltip"
>
<a-popconfirm v-if="i.popConfirm" v-bind="i.popConfirm">
<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>
</div>
<a-modal
:width="1100"
v-model:visible="visible"
title="详情"
@ok="handleOk"
>
<a-descriptions :data="descriptionsData" title="" bordered :column="2">
<a-descriptions-item label="URL">
{{ descriptionsData?.url }}
</a-descriptions-item>
<a-descriptions-item label="请求方法">
{{ descriptionsData?.httpMethod }}
</a-descriptions-item>
<a-descriptions-item label="动作">
{{ descriptionsData?.action }}
</a-descriptions-item>
<a-descriptions-item label="类名">
{{ descriptionsData?.target }}
</a-descriptions-item>
<a-descriptions-item label="方法名">
{{ descriptionsData?.method }}
</a-descriptions-item>
<a-descriptions-item label="IP">
{{ descriptionsData?.ip }}
</a-descriptions-item>
<a-descriptions-item label="请求时间">
{{
moment(descriptionsData?.requestTime).format(
'YYYY-MM-DD HH:mm:ss',
)
}}
</a-descriptions-item>
<a-descriptions-item label="请求耗时">
{{
descriptionsData?.responseTime -
descriptionsData?.requestTime +
'ms'
}}
</a-descriptions-item>
<a-descriptions-item label="请求头" :span="2">
{{ descriptionsData?.httpHeaders }}
</a-descriptions-item>
<a-descriptions-item label="请求参数" :span="2">
{{ descriptionsData?.parameters }}
</a-descriptions-item>
<a-descriptions-item label="异常信息" :span="2">
<a-textarea
v-model:value="descriptionsData.exception"
placeholder="暂无数据"
:auto-size="{ minRows: 3, maxRows: 20 }"
/>
</a-descriptions-item>
</a-descriptions>
</a-modal>
</template>
<script lang="ts" setup name="AccessLog">
import type { ActionsType } from '@/components/Table/index.vue';
import type { AccessLogItem } from '../typings';
import { queryAccess } from '@/api/link/log';
import moment from 'moment';
import { modifySearchColumnValue } from '@/utils/comm';
const tableRef = ref<Record<string, any>>({});
const params = ref<Record<string, any>>({});
const columns = [
{
title: 'IP',
dataIndex: 'ip',
key: 'ip',
search: {
type: 'string',
},
scopedSlots: true,
width: 150,
fixed: 'left',
},
{
title: '请求路径',
dataIndex: 'url',
key: 'url',
search: {
type: 'string',
},
// width: 200,
},
{
title: '请求方法',
dataIndex: 'httpMethod',
key: 'httpMethod',
search: {
type: 'select',
options: [
{
label: 'POST',
value: 'POST',
},
{
label: 'GET',
value: 'GET',
},
{
label: 'PATCH',
value: 'PATCH',
},
{
label: 'DELETE',
value: 'DELETE',
},
{
label: 'PUT',
value: 'PUT',
},
],
},
scopedSlots: true,
width: 100,
},
{
title: '请求时间',
dataIndex: 'requestTime',
key: 'requestTime',
scopedSlots: true,
search: {
type: 'date',
},
width: 200,
},
{
title: '请求耗时',
dataIndex: 'responseTime',
key: 'responseTime',
scopedSlots: true,
width: 100,
},
{
title: '请求用户',
dataIndex: 'username',
key: 'username',
search: {
type: 'string',
},
width: 150,
scopedSlots: true,
},
{
title: '操作',
key: 'action',
fixed: 'right',
width: 150,
scopedSlots: true,
},
];
const descriptionsData = ref<AccessLogItem>();
const visible = ref<boolean>(false);
const handleOk = (e: MouseEvent) => {
visible.value = false;
};
const getActions = (data: Partial<Record<string, any>>): ActionsType[] => {
if (!data) {
return [];
}
return [
{
key: 'eye',
text: '查看',
tooltip: {
title: '查看',
},
icon: 'EyeOutlined',
onClick: (data: AccessLogItem) => {
descriptionsData.value = data;
visible.value = true;
},
},
];
};
const column = {
username: 'context.username',
};
/**
* 搜索
* @param params
*/
const handleSearch = (e: any) => {
params.value = modifySearchColumnValue(e, column);
};
</script>

View File

@ -0,0 +1,246 @@
<template>
<div>
<Search :columns="columns" target="search" @search="handleSearch" />
<JTable
ref="tableRef"
model="TABLE"
:columns="columns"
:request="querySystem"
:defaultParams="{
sorts: [{ name: 'createTime', order: 'desc' }],
}"
:params="params"
>
<template #level="slotProps">
<a-tag
:color="
slotProps.level === 'WARN'
? 'orange'
: slotProps.level === 'ERROR'
? 'red'
: slotProps.level === 'DEBUG'
? 'blue'
: 'green'
"
>
{{ slotProps.level }}
</a-tag>
</template>
<template #createTime="slotProps">
{{ moment(slotProps.createTime).format('YYYY-MM-DD HH:mm:ss') }}
</template>
<template #server="slotProps">
{{ slotProps.context.server }}
</template>
<template #action="slotProps">
<a-space :size="16">
<a-tooltip
v-for="i in getActions(slotProps)"
:key="i.key"
v-bind="i.tooltip"
>
<a-popconfirm v-if="i.popConfirm" v-bind="i.popConfirm">
<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>
</div>
<a-modal
:width="1100"
v-model:visible="visible"
title="详情"
@ok="handleOk"
>
<div>
<span class="mr-10">[{{ descriptionsData?.threadName }}]</span>
<span class="mr-10">{{
moment(descriptionsData?.createTime).format(
'YYYY-MM-DD HH:mm:ss',
)
}}</span>
<span>{{ descriptionsData?.className }}</span>
</div>
<div class="mb-10">
<a-tag
:color="
descriptionsData?.level === 'WARN'
? 'orange'
: descriptionsData?.level === 'ERROR'
? 'red'
: descriptionsData?.level === 'DEBUG'
? 'blue'
: 'green'
"
>
{{ descriptionsData?.level }}
</a-tag>
<span>{{ descriptionsData?.message }}</span>
</div>
<a-textarea
v-model:value="descriptionsData.exceptionStack"
placeholder="暂无数据"
:auto-size="{ minRows: 24, maxRows: 28 }"
/>
</a-modal>
</template>
<script lang="ts" setup name="SystemLog">
import type { ActionsType } from '@/components/Table/index.vue';
import type { SystemLogItem } from '../typings';
import { querySystem } from '@/api/link/log';
import moment from 'moment';
import { modifySearchColumnValue } from '@/utils/comm';
const tableRef = ref<Record<string, any>>({});
const params = ref<Record<string, any>>({});
const columns = [
{
title: '名称',
dataIndex: 'name',
key: 'name',
search: {
type: 'string',
},
scopedSlots: true,
width: 400,
fixed: 'left',
ellipsis: true,
},
{
title: '日志级别',
dataIndex: 'level',
key: 'level',
search: {
type: 'select',
options: [
{
label: 'ERROR',
value: 'ERROR',
},
{
label: 'INFO',
value: 'INFO',
},
{
label: 'DEBUG',
value: 'DEBUG',
},
{
label: 'WARN',
value: 'WARN',
},
],
},
scopedSlots: true,
width: 100,
},
{
title: '日志内容',
dataIndex: 'message',
key: 'message',
search: {
type: 'string',
},
scopedSlots: true,
ellipsis: true,
},
{
title: '服务名',
dataIndex: 'server',
key: 'server',
scopedSlots: true,
search: {
type: 'string',
},
width: 200,
ellipsis: true,
},
{
title: '创建时间',
dataIndex: 'createTime',
key: 'createTime',
search: {
type: 'date',
},
scopedSlots: true,
width: 200,
},
{
title: '操作',
key: 'action',
fixed: 'right',
width: 150,
scopedSlots: true,
},
];
const descriptionsData = ref<SystemLogItem>();
const visible = ref<boolean>(false);
const handleOk = (e: MouseEvent) => {
visible.value = false;
};
const getActions = (data: Partial<Record<string, any>>): ActionsType[] => {
if (!data) {
return [];
}
return [
{
key: 'eye',
text: '查看',
tooltip: {
title: '查看',
},
icon: 'EyeOutlined',
onClick: (data: SystemLogItem) => {
descriptionsData.value = data;
visible.value = true;
},
},
];
};
const column = {
server: 'context.server',
};
/**
* 搜索
* @param params
*/
const handleSearch = (e: any) => {
params.value = modifySearchColumnValue(e, column);
};
</script>
<style scoped lang="less">
.mr-10 {
margin-right: 10px;
}
.mb-10 {
margin-bottom: 10px;
}
</style>

28
src/views/Log/index.vue Normal file
View File

@ -0,0 +1,28 @@
<template>
<page-container :tabList="list" @tabChange="onTabChange">
<AccessLog v-if="activeKey === '1'" />
<SystemLog v-else />
</page-container>
</template>
<script lang="ts" setup name="LogPage">
import { defineComponent, ref } from 'vue';
import AccessLog from './Access/index.vue';
import SystemLog from './System/index.vue';
const activeKey = ref('1');
const list = [
{
key: '1',
tab: '访问日志',
},
{
key: '2',
tab: '系统日志',
},
];
const onTabChange = (e: string) => {
activeKey.value = e;
};
</script>

31
src/views/Log/typings.d.ts vendored Normal file
View File

@ -0,0 +1,31 @@
export type AccessLogItem = {
id: string;
context: any;
describe: string;
exception: string;
httpHeaders: any;
httpMethod: string;
ip: string;
method: string;
parameters: any;
requestTime: number;
responseTime: number;
target: string;
url: string;
action: string;
};
export type SystemLogItem = {
id: string;
className: string;
context: any;
createTime: number;
exceptionStack: string;
level: string;
lineNumber: number;
message: string;
methodName: string;
name: string;
threadId: string;
threadName: string;
};

View File

@ -1,42 +1,44 @@
<template> <template>
<a-spin :spinning="loading"> <page-container>
<a-card :bordered="false"> <a-spin :spinning="loading">
<div v-if="type && modeType === 'add'"> <a-card :bordered="false">
<Provider <div v-if="type && id === ':id'">
@onClick="goProviders" <Provider
:dataSource="dataSource" @onClick="goProviders"
></Provider> :dataSource="dataSource"
</div> ></Provider>
<div v-else> </div>
<div v-if="!id"><a @click="goBack">返回</a></div> <div v-else>
<AccessNetwork <div v-if="!id"><a @click="goBack">返回</a></div>
v-if="showType === 'network'" <AccessNetwork
:provider="provider" v-if="showType === 'network'"
:data="data" :provider="provider"
/> :data="data"
<Media />
v-if="showType === 'media'" <Media
:provider="provider" v-if="showType === 'media'"
:data="data" :provider="provider"
/> :data="data"
<Channel />
v-if="showType === 'channel'" <Channel
:provider="provider" v-if="showType === 'channel'"
:data="data" :provider="provider"
/> :data="data"
<Edge />
v-if="showType === 'edge'" <Edge
:provider="provider" v-if="showType === 'edge'"
:data="data" :provider="provider"
/> :data="data"
<Cloud />
v-if="showType === 'cloud'" <Cloud
:provider="provider" v-if="showType === 'cloud'"
:data="data" :provider="provider"
/> :data="data"
</div> />
</a-card> </div>
</a-spin> </a-card>
</a-spin>
</page-container>
</template> </template>
<script lang="ts" setup name="AccessConfigDetail"> <script lang="ts" setup name="AccessConfigDetail">
@ -51,7 +53,7 @@ import Cloud from '../components/Cloud/index.vue';
const route = useRoute(); const route = useRoute();
const modeType = route.params.type as string; const view = route.query.view as string;
const id = route.params.id as string; const id = route.params.id as string;
const dataSource = ref([]); const dataSource = ref([]);
@ -138,7 +140,7 @@ const queryProviders = async () => {
}; };
const getProvidersData = async () => { const getProvidersData = async () => {
if (id && modeType !== 'add') { if (id !== ':id') {
getProviders().then((response) => { getProviders().then((response) => {
if (response.status === 200) { if (response.status === 200) {
const list = getTypeList(response.result); const list = getTypeList(response.result);

View File

@ -40,7 +40,7 @@
</a-form-item> </a-form-item>
<a-form-item> <a-form-item>
<a-button <a-button
v-if="modeType !== 'view'" v-if="view === 'false'"
type="primary" type="primary"
html-type="submit" html-type="submit"
>保存</a-button >保存</a-button
@ -96,7 +96,7 @@ interface FormState {
description: string; description: string;
} }
const route = useRoute(); const route = useRoute();
const modeType = route.params.type as string; const view = route.query.view as string;
const id = route.params.id as string; const id = route.params.id as string;
const props = defineProps({ const props = defineProps({
@ -126,9 +126,7 @@ const onFinish = async (values: any) => {
channel: providerId === 'modbus-tcp' ? 'modbus' : 'opc-ua', channel: providerId === 'modbus-tcp' ? 'modbus' : 'opc-ua',
}; };
const resp = const resp =
!!id && modeType !== 'add' id === ':id' ? await save(params) : await update({ ...params, id });
? await update({ ...params, id })
: await save(params);
if (resp.status === 200) { if (resp.status === 200) {
message.success('操作成功!'); message.success('操作成功!');
// if (params.get('save')) { // if (params.get('save')) {
@ -145,7 +143,7 @@ const onFinish = async (values: any) => {
}; };
onMounted(() => { onMounted(() => {
if (modeType !== 'add') { if (id === ':id') {
formState.value = { formState.value = {
name: props.data.name, name: props.data.name,
description: props.data?.description || '', description: props.data?.description || '',

View File

@ -283,7 +283,7 @@
下一步 下一步
</a-button> </a-button>
<a-button <a-button
v-if="current === 2 && modeType !== 'view'" v-if="current === 2 && view === 'false'"
type="primary" type="primary"
style="margin-right: 8px" style="margin-right: 8px"
@click="saveData" @click="saveData"
@ -383,7 +383,7 @@ interface Form {
description: string; description: string;
} }
const route = useRoute(); const route = useRoute();
const modeType = route.params.type as string; const view = route.query.view as string;
const id = route.params.id as string; const id = route.params.id as string;
const props = defineProps({ const props = defineProps({
@ -450,13 +450,13 @@ const saveData = async () => {
transport: 'HTTP_SERVER', transport: 'HTTP_SERVER',
}; };
const resp = const resp =
!!id && modeType !== 'add' id === ':id'
? await update({ ? await save(params)
: await update({
...props.data, ...props.data,
...params, ...params,
}) id,
: await save(params); });
if (resp.status === 200) { if (resp.status === 200) {
message.success('操作成功!'); message.success('操作成功!');
// //
@ -489,7 +489,7 @@ const queryProcotolList = async (id: string, params = {}) => {
const addProcotol = () => { const addProcotol = () => {
// const url = this.$store.state.permission.routes['Link/Protocol'] // const url = this.$store.state.permission.routes['Link/Protocol']
const url = '/demo'; const url = '/iot/link/protocol';
const tab = window.open( const tab = window.open(
`${window.location.origin + window.location.pathname}#${url}?save=true`, `${window.location.origin + window.location.pathname}#${url}?save=true`,
); );
@ -518,7 +518,7 @@ const prev = () => {
current.value = current.value - 1; current.value = current.value - 1;
}; };
onMounted(() => { onMounted(() => {
if (modeType !== 'add') { if (id !== ':id') {
formState.value = props.data.configuration; formState.value = props.data.configuration;
procotolCurrent.value = props.data.protocol; procotolCurrent.value = props.data.protocol;
formData.value = { formData.value = {

View File

@ -377,7 +377,7 @@
</a-button> </a-button>
<a-button <a-button
style="margin-right: 8px" style="margin-right: 8px"
v-if="current === 2 && modeType !== 'view'" v-if="current === 2 && view === 'false'"
type="primary" type="primary"
@click="saveData" @click="saveData"
> >
@ -479,7 +479,7 @@ interface Form {
description: string; description: string;
} }
const route = useRoute(); const route = useRoute();
const modeType = route.params.type as string; const view = route.query.view as string;
const id = route.params.id as string; const id = route.params.id as string;
const props = defineProps({ const props = defineProps({
@ -547,12 +547,13 @@ const saveData = async () => {
transport: 'HTTP_SERVER', transport: 'HTTP_SERVER',
}; };
const resp = const resp =
!!id && modeType !== 'add' id === ':id'
? await update({ ? await save(params)
: await update({
...props.data, ...props.data,
...params, ...params,
}) id,
: await save(params); });
if (resp.status === 200) { if (resp.status === 200) {
message.success('操作成功!'); message.success('操作成功!');
@ -585,7 +586,7 @@ const queryProcotolList = async (id: string, params = {}) => {
const addProcotol = () => { const addProcotol = () => {
// const url = this.$store.state.permission.routes['Link/Protocol'] // const url = this.$store.state.permission.routes['Link/Protocol']
const url = '/demo'; const url = '/iot/link/protocol';
const tab = window.open( const tab = window.open(
`${window.location.origin + window.location.pathname}#${url}?save=true`, `${window.location.origin + window.location.pathname}#${url}?save=true`,
); );
@ -615,7 +616,7 @@ const prev = () => {
}; };
onMounted(() => { onMounted(() => {
if (modeType !== 'add') { if (id !== ':id') {
formState.value = props.data.configuration; formState.value = props.data.configuration;
procotolCurrent.value = props.data.protocol; procotolCurrent.value = props.data.protocol;
formData.value = { formData.value = {

View File

@ -140,7 +140,7 @@
</a-form-item> </a-form-item>
<a-form-item> <a-form-item>
<a-button <a-button
v-if="current !== 1 && modeType !== 'view'" v-if="current !== 1 && view === 'false'"
type="primary" type="primary"
html-type="submit" html-type="submit"
>保存</a-button >保存</a-button
@ -179,7 +179,7 @@
下一步 下一步
</a-button> </a-button>
<a-button <a-button
v-if="current === 1 && modeType !== 'view'" v-if="current === 1 && view === 'false'"
type="primary" type="primary"
style="margin-right: 8px" style="margin-right: 8px"
@click="saveData" @click="saveData"
@ -297,7 +297,7 @@ interface FormState {
description: string; description: string;
} }
const route = useRoute(); const route = useRoute();
const modeType = route.params.type as string; const view = route.query.view as string;
const id = route.params.id as string; const id = route.params.id as string;
const props = defineProps({ const props = defineProps({
@ -338,9 +338,7 @@ const onFinish = async (values: any) => {
}; };
if (networkCurrent.value) params.channelId = networkCurrent.value; if (networkCurrent.value) params.channelId = networkCurrent.value;
const resp = const resp =
!!id && modeType !== 'add' id === ':id' ? await save(params) : await update({ ...params, id });
? await update({ ...params, id })
: await save(params);
if (resp.status === 200) { if (resp.status === 200) {
message.success('操作成功!'); message.success('操作成功!');
// if (params.get('save')) { // if (params.get('save')) {
@ -392,7 +390,7 @@ const saveData = async () => {
const addNetwork = () => { const addNetwork = () => {
// const url = this.$store.state.permission.routes['Link/Type/Detail'] // const url = this.$store.state.permission.routes['Link/Type/Detail']
const url = '/demo'; const url = '/iot/link/type/detail/:id';
const tab = window.open( const tab = window.open(
`${window.location.origin + window.location.pathname}#${url}?type=${ `${window.location.origin + window.location.pathname}#${url}?type=${
NetworkTypeMapping.get(props.provider?.id) || '' NetworkTypeMapping.get(props.provider?.id) || ''
@ -421,7 +419,7 @@ onMounted(() => {
if (props.provider.id === 'official-edge-gateway') { if (props.provider.id === 'official-edge-gateway') {
queryNetworkList(props.provider.id, ''); queryNetworkList(props.provider.id, '');
} }
if (modeType !== 'add') { if (id !== ':id') {
formState.value = { formState.value = {
name: props.data.name, name: props.data.name,
description: props.data?.description || '', description: props.data?.description || '',

View File

@ -503,7 +503,7 @@
下一步 下一步
</a-button> </a-button>
<a-button <a-button
v-if="current === 1 && modeType !== 'view'" v-if="current === 1 && view === 'false'"
type="primary" type="primary"
style="margin-right: 8px" style="margin-right: 8px"
@click="saveData" @click="saveData"
@ -559,7 +559,7 @@ const props = defineProps({
}); });
const route = useRoute(); const route = useRoute();
const modeType = route.params.type as string; const view = route.query.view as string;
const id = route.params.id as string; const id = route.params.id as string;
const activeKey: any = ref([]); const activeKey: any = ref([]);
@ -663,9 +663,7 @@ const saveData = () => {
}; };
const resp = const resp =
!!id && modeType !== 'add' id === ':id' ? await save(params) : await update({ ...params, id });
? await update({ ...params, id })
: await save(params);
if (resp.status === 200) { if (resp.status === 200) {
message.success('操作成功!'); message.success('操作成功!');
// if (params.get('save')) { // if (params.get('save')) {
@ -741,7 +739,7 @@ onMounted(() => {
} }
}); });
if (modeType !== 'add') { if (id !== ':id') {
formState.value = props.data.configuration; formState.value = props.data.configuration;
formData.value = { formData.value = {
name: props.data.name, name: props.data.name,

View File

@ -40,7 +40,7 @@
</a-form-item> </a-form-item>
<a-form-item> <a-form-item>
<a-button <a-button
v-if="modeType !== 'view'" v-if="view === 'false'"
type="primary" type="primary"
html-type="submit" html-type="submit"
>保存</a-button >保存</a-button
@ -91,7 +91,7 @@ interface FormState {
description: string; description: string;
} }
const route = useRoute(); const route = useRoute();
const modeType = route.params.type as string; const view = route.query.view as string;
const id = route.params.id as string; const id = route.params.id as string;
const props = defineProps({ const props = defineProps({
@ -119,9 +119,7 @@ const onFinish = async (values: any) => {
channel: 'fixed-media', channel: 'fixed-media',
}; };
const resp = const resp =
!!id && modeType !== 'add' id === ':id' ? await save(params) : await update({ ...params, id });
? await update({ ...params, id })
: await save(params);
if (resp.status === 200) { if (resp.status === 200) {
message.success('操作成功!'); message.success('操作成功!');
// if (params.get('save')) { // if (params.get('save')) {
@ -138,7 +136,7 @@ const onFinish = async (values: any) => {
}; };
onMounted(() => { onMounted(() => {
if (modeType !== 'add') { if (id !== ':id') {
formState.value = { formState.value = {
name: props.data.name, name: props.data.name,
description: props.data?.description || '', description: props.data?.description || '',

View File

@ -1,5 +1,5 @@
<template> <template>
<div style="margin-top: 10px"> <div>
<a-steps :current="stepCurrent"> <a-steps :current="stepCurrent">
<a-step v-for="item in steps" :key="item" :title="item" /> <a-step v-for="item in steps" :key="item" :title="item" />
</a-steps> </a-steps>
@ -304,7 +304,7 @@
下一步 下一步
</a-button> </a-button>
<a-button <a-button
v-if="current === 2 && modeType !== 'view'" v-if="current === 2 && view === 'false'"
type="primary" type="primary"
style="margin-right: 8px" style="margin-right: 8px"
@click="saveData" @click="saveData"
@ -767,7 +767,7 @@ const props = defineProps({
const clientHeight = document.body.clientHeight; const clientHeight = document.body.clientHeight;
const type = props.provider.channel; const type = props.provider.channel;
const route = useRoute(); const route = useRoute();
const modeType = route.params.type as string; const view = route.query.view as string;
const id = route.params.id as string; const id = route.params.id as string;
const formRef = ref<FormInstance>(); const formRef = ref<FormInstance>();
@ -839,7 +839,7 @@ const queryProcotolList = async (id: string, params = {}) => {
const addNetwork = () => { const addNetwork = () => {
// const url = this.$store.state.permission.routes['Link/Type/Detail'] // const url = this.$store.state.permission.routes['Link/Type/Detail']
const url = '/demo'; const url = '/iot/link/type/detail/:id';
const tab = window.open( const tab = window.open(
`${window.location.origin + window.location.pathname}#${url}?type=${ `${window.location.origin + window.location.pathname}#${url}?type=${
NetworkTypeMapping.get(props.provider?.id) || '' NetworkTypeMapping.get(props.provider?.id) || ''
@ -854,7 +854,7 @@ const addNetwork = () => {
}; };
const addProcotol = () => { const addProcotol = () => {
// const url = this.$store.state.permission.routes['Link/Protocol'] // const url = this.$store.state.permission.routes['Link/Protocol']
const url = '/demo'; const url = '/iot/link/protocol';
const tab = window.open( const tab = window.open(
`${window.location.origin + window.location.pathname}#${url}?save=true`, `${window.location.origin + window.location.pathname}#${url}?save=true`,
); );
@ -903,27 +903,25 @@ const procotolSearch = (value: string) => {
const saveData = () => { const saveData = () => {
validate() validate()
.then(async (values) => { .then(async (values) => {
let resp = undefined; const params = {
let params = {
...props.data, ...props.data,
...values, ...values,
protocol: procotolCurrent.value, protocol: procotolCurrent.value,
channel: 'network', // channel: 'network', //
channelId: networkCurrent.value, channelId: networkCurrent.value,
}; };
if (!!id && modeType !== 'add') { const resp =
resp = await update(params); id === ':id'
} else { ? await save(params)
params = { : await update({
...params, ...params,
provider: props.provider.id, id,
transport: provider: props.provider.id,
props.provider?.id === 'child-device' transport:
? 'Gateway' props.provider?.id === 'child-device'
: ProtocolMapping.get(props.provider.id), ? 'Gateway'
}; : ProtocolMapping.get(props.provider.id),
resp = await save(params); });
}
if (resp.status === 200) { if (resp.status === 200) {
message.success('操作成功!'); message.success('操作成功!');
// //
@ -1104,7 +1102,7 @@ onMounted(() => {
}); });
onMounted(() => { onMounted(() => {
if (modeType !== 'add') { if (id !== ':id') {
procotolCurrent.value = props.data.protocol; procotolCurrent.value = props.data.protocol;
formData.value = { formData.value = {
name: props.data.name, name: props.data.name,

View File

@ -1,9 +1,8 @@
<template> <template>
<div class="page-container"> <page-container>
<a-card style="margin-bottom: 20px"> <div>
<Search :columns="columns" target="search" @search="handleSearch" /> <Search :columns="columns" target="search" @search="handleSearch" />
</a-card>
<a-card>
<JTable <JTable
ref="tableRef" ref="tableRef"
model="CARD" model="CARD"
@ -187,8 +186,8 @@
/> />
</template> </template>
</JTable> </JTable>
</a-card> </div>
</div> </page-container>
</template> </template>
<script lang="ts" setup name="AccessConfigPage"> <script lang="ts" setup name="AccessConfigPage">
import type { ActionsType } from '@/components/Table/index.vue'; import type { ActionsType } from '@/components/Table/index.vue';
@ -345,13 +344,25 @@ const getProvidersList = async () => {
getProvidersList(); getProvidersList();
const handlAdd = () => { const handlAdd = () => {
router.push('/link/accessConfig/detail/add/new'); // router.push('/link/accessConfig/detail/add/new');
router.push({
path: `/iot/link/accessConfig/detail/:id`,
query: { view: false },
});
}; };
const handlEdit = (id: string) => { const handlEdit = (id: string) => {
router.push(`/link/accessConfig/detail/edit/${id}`); // router.push(`/link/accessConfig/detail/edit/${id}`);
router.push({
path: `/iot/link/accessConfig/detail/${id}`,
query: { view: false },
});
}; };
const handlEye = (id: string) => { const handlEye = (id: string) => {
router.push(`/link/accessConfig/detail/view/${id}`); // router.push(`/link/accessConfig/detail/view/${id}`);
router.push({
path: `/iot/link/accessConfig/detail/${id}`,
query: { view: true },
});
}; };
/** /**
@ -375,10 +386,6 @@ const handleSearch = (e: any) => {
// } // }
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
.page-container {
background: #f0f2f5;
padding: 24px;
}
.tableCardDisabled { .tableCardDisabled {
width: 100%; width: 100%;
background: url('/images/access-config-diaabled.png') no-repeat; background: url('/images/access-config-diaabled.png') no-repeat;

View File

@ -1,96 +1,104 @@
<template> <template>
<a-card> <page-container>
<a-row :gutter="[24, 24]" style="padding: 24px"> <a-card>
<a-col :span="12"> <a-row :gutter="[24, 24]" style="padding: 24px">
<a-form <a-col :span="12">
class="form" <a-form
layout="vertical" class="form"
:model="formData" layout="vertical"
name="basic" :model="formData"
:label-col="{ span: 8 }" name="basic"
:wrapper-col="{ span: 16 }" :label-col="{ span: 8 }"
autocomplete="off" :wrapper-col="{ span: 16 }"
> autocomplete="off"
<a-form-item label="证书标准" v-bind="validateInfos.type">
<a-radio-group v-model:value="formData.type">
<a-radio-button
class="form-radio-button"
value="common"
>
<img :src="getImage('/certificate.png')" />
</a-radio-button>
</a-radio-group>
</a-form-item>
<a-form-item label="证书名称" v-bind="validateInfos.name">
<a-input
placeholder="请输入证书名称"
v-model:value="formData.name"
/>
</a-form-item>
<a-form-item
label="证书文件"
v-bind="validateInfos['configs.cert']"
> >
<CertificateFile <a-form-item
name="cert" label="证书标准"
v-model:modelValue="formData.configs.cert" v-bind="validateInfos.type"
placeholder='证书格式以"-----BEGIN CERTIFICATE-----"开头,以"-----END CERTIFICATE-----"结尾"'
/>
</a-form-item>
<a-form-item
label="证书私钥"
v-bind="validateInfos['configs.key']"
>
<CertificateFile
name="key"
v-model:modelValue="formData.configs.key"
placeholder='证书私钥格式以"-----BEGIN (RSA|EC) PRIVATE KEY-----"开头,以"-----END(RSA|EC) PRIVATE KEY-----"结尾。'
/>
</a-form-item>
<a-form-item label="说明" name="description">
<a-textarea
placeholder="请输入说明"
v-model:value="formData.description"
:maxlength="200"
:rows="3"
showCount
/>
</a-form-item>
<a-form-item>
<a-button
v-if="modeType !== 'view'"
class="form-submit"
html-type="submit"
type="primary"
@click.prevent="onSubmit"
:loading="loading"
>保存</a-button
> >
</a-form-item> <a-radio-group v-model:value="formData.type">
</a-form> <a-radio-button
</a-col> class="form-radio-button"
<a-col :span="12"> value="common"
<div class="doc"> >
<h1>1. 概述</h1> <img :src="getImage('/certificate.png')" />
<div> </a-radio-button>
证书由受信任的数字证书颁发机构CA在验证服务器身份后颁发具有服务器身份验证和数据传输加密功能保障设备与平台间的数据传输安全配置后可被网络组件引用 </a-radio-group>
</a-form-item>
<a-form-item
label="证书名称"
v-bind="validateInfos.name"
>
<a-input
placeholder="请输入证书名称"
v-model:value="formData.name"
/>
</a-form-item>
<a-form-item
label="证书文件"
v-bind="validateInfos['configs.cert']"
>
<CertificateFile
name="cert"
v-model:modelValue="formData.configs.cert"
placeholder='证书格式以"-----BEGIN CERTIFICATE-----"开头,以"-----END CERTIFICATE-----"结尾"'
/>
</a-form-item>
<a-form-item
label="证书私钥"
v-bind="validateInfos['configs.key']"
>
<CertificateFile
name="key"
v-model:modelValue="formData.configs.key"
placeholder='证书私钥格式以"-----BEGIN (RSA|EC) PRIVATE KEY-----"开头,以"-----END(RSA|EC) PRIVATE KEY-----"结尾。'
/>
</a-form-item>
<a-form-item label="说明" name="description">
<a-textarea
placeholder="请输入说明"
v-model:value="formData.description"
:maxlength="200"
:rows="3"
showCount
/>
</a-form-item>
<a-form-item>
<a-button
v-if="view === 'false'"
class="form-submit"
html-type="submit"
type="primary"
@click.prevent="onSubmit"
:loading="loading"
>保存</a-button
>
</a-form-item>
</a-form>
</a-col>
<a-col :span="12">
<div class="doc">
<h1>1. 概述</h1>
<div>
证书由受信任的数字证书颁发机构CA在验证服务器身份后颁发具有服务器身份验证和数据传输加密功能保障设备与平台间的数据传输安全配置后可被网络组件引用
</div>
<h1>2. 配置说明</h1>
<h2>1证书文件</h2>
<div>
您可以使用文本编辑工具打开PEM或者CRT格式的证书文件复制其中的内容并粘贴到该文本框或者单击该文本框下的上传并选择存储在本地计算机的证书文件将文件内容上传到文本框
</div>
<h2>2证书私钥</h2>
<div>
填写证书私钥内容的PEM编码
您可以使用文本编辑工具打开KEY格式的证书私钥文件复制其中的内容并粘贴到该文本框或者单击该文本框下的上传并选择存储在本地计算机的证书私钥文件将文件内容上传到文本框
</div>
</div> </div>
<h1>2. 配置说明</h1> </a-col>
<h2>1证书文件</h2> </a-row>
<div> </a-card>
您可以使用文本编辑工具打开PEM或者CRT格式的证书文件复制其中的内容并粘贴到该文本框或者单击该文本框下的上传并选择存储在本地计算机的证书文件将文件内容上传到文本框 </page-container>
</div>
<h2>2证书私钥</h2>
<div>
填写证书私钥内容的PEM编码
您可以使用文本编辑工具打开KEY格式的证书私钥文件复制其中的内容并粘贴到该文本框或者单击该文本框下的上传并选择存储在本地计算机的证书私钥文件将文件内容上传到文本框
</div>
</div>
</a-col>
</a-row>
</a-card>
</template> </template>
<script lang="ts" setup name="CertificateDetail"> <script lang="ts" setup name="CertificateDetail">
@ -103,7 +111,7 @@ import { FormDataType, TypeObjType } from '../type';
const router = useRouter(); const router = useRouter();
const route = useRoute(); const route = useRoute();
const modeType = route.params.type as string; const view = route.query.view as string;
const id = route.params.id as string; const id = route.params.id as string;
const useForm = Form.useForm; const useForm = Form.useForm;
@ -145,10 +153,12 @@ const onSubmit = () => {
const params = toRaw(formData.value); const params = toRaw(formData.value);
loading.value = true; loading.value = true;
const response = const response =
modeType === 'edit' ? await update(params) : await save(params); id === ':id'
? await save(params)
: await update({ ...params, id });
if (response.status === 200) { if (response.status === 200) {
message.success('操作成功'); message.success('操作成功');
router.push('/link/certificate'); router.push('/iot/link/certificate');
} }
loading.value = false; loading.value = false;
}) })
@ -168,7 +178,7 @@ const handleChange = (info: UploadChangeParam) => {
}; };
const detail = async (id: string) => { const detail = async (id: string) => {
if (modeType !== 'add') { if (id !== ':id') {
loading.value = true; loading.value = true;
const res = await queryDetail(id); const res = await queryDetail(id);
if (res.success) { if (res.success) {

View File

@ -1,13 +1,7 @@
<template> <template>
<div class="page-container"> <page-container>
<a-card style="margin-bottom: 20px"> <div>
<Search <Search :columns="columns" target="search" @search="handleSearch" />
:columns="columns"
target="search"
@search="handleSearch"
/>
</a-card>
<a-card>
<JTable <JTable
ref="tableRef" ref="tableRef"
model="TABLE" model="TABLE"
@ -61,8 +55,8 @@
</a-space> </a-space>
</template> </template>
</JTable> </JTable>
</a-card> </div>
</div> </page-container>
</template> </template>
<script lang="ts" setup name="CertificatePage"> <script lang="ts" setup name="CertificatePage">
import type { ActionsType } from '@/components/Table/index.vue'; import type { ActionsType } from '@/components/Table/index.vue';
@ -158,15 +152,24 @@ const getActions = (data: Partial<Record<string, any>>): ActionsType[] => {
}; };
const handlAdd = () => { const handlAdd = () => {
router.push('/link/certificate/detail/add/new'); router.push({
path: `/iot/link/certificate/detail/:id`,
query: { view: false },
});
}; };
const handlEye = (id: string) => { const handlEye = (id: string) => {
router.push(`/link/certificate/detail/view/${id}`); router.push({
path: `/iot/link/certificate/detail/${id}`,
query: { view: true },
});
}; };
const handlEdit = (id: string) => { const handlEdit = (id: string) => {
router.push(`/link/certificate/detail/edit/${id}`); router.push({
path: `/iot/link/certificate/detail/${id}`,
query: { view: false },
});
}; };
const handlDelete = async (id: string) => { const handlDelete = async (id: string) => {
@ -182,15 +185,8 @@ const handlDelete = async (id: string) => {
* @param params * @param params
*/ */
const handleSearch = (e: any) => { const handleSearch = (e: any) => {
console.log(1211, e);
params.value = e; params.value = e;
}; };
</script> </script>
<style lang="less" scoped> <style lang="less" scoped></style>
.page-container {
background: #f0f2f5;
padding: 24px;
}
</style>

View File

@ -1,6 +0,0 @@
<template>
<div>访问日志</div>
</template>
<script lang="ts" setup name="SystemLog">
</script>

View File

@ -1,6 +0,0 @@
<template>
<div>系统日志</div>
</template>
<script lang="ts" setup name="AccessLog">
</script>

View File

@ -1,17 +0,0 @@
<template>
<a-tabs v-model:activeKey="activeKey">
<a-tab-pane key="1" tab="访问日志">
<AccessLog />
</a-tab-pane>
<a-tab-pane key="2" tab="系统日志" force-render>
<SystemLog />
</a-tab-pane>
</a-tabs>
</template>
<script lang="ts" setup name="LogPage">
import { defineComponent, ref } from 'vue';
import AccessLog from './Access/index.vue';
import SystemLog from './System/index.vue';
const activeKey = ref('1');
</script>

View File

@ -0,0 +1,37 @@
<template lang="">
<a-modal
:title="data.id ? '编辑' : '新增'"
ok-text="确认"
cancel-text="取消"
:visible="true"
width="600px"
:confirm-loading="loading"
@cancel="handleCancel"
@ok="handleOk"
>
123
</a-modal>
</template>
<script lang="ts" setup>
const loading = ref(false);
const props = defineProps({
data: {
type: Object,
default: () => {},
},
});
const emit = defineEmits(['change']);
const handleOk = () => {
console.log(2);
emit('change', true);
};
const handleCancel = () => {
console.log(1);
emit('change', false);
};
</script>
<style lang=""></style>

View File

@ -0,0 +1,327 @@
<template>
<page-container>
<div>
<Search :columns="columns" target="search" @search="handleSearch" />
<JTable
ref="tableRef"
:columns="columns"
:request="list"
:defaultParams="{
sorts: [{ name: 'createTime', order: 'desc' }],
}"
:params="params"
>
<template #headerTitle>
<a-button type="primary" @click="handlAdd"
><plus-outlined />新增</a-button
>
</template>
<template #card="slotProps">
<CardBox
:showStatus="false"
:value="slotProps"
:actions="getActions(slotProps, 'card')"
v-bind="slotProps"
>
<template #img>
<slot name="img">
<img :src="getImage('/device-access.png')" />
</slot>
</template>
<template #content>
<div class="card-item-content">
<h3 class="card-item-content-title-a">
{{ slotProps.name }}
</h3>
<a-row class="card-item-content-box">
<a-col
:span="12"
class="card-item-content-text"
>
<div class="card-item-content-text">
ID
</div>
<div class="card-item-content-text">
<a-tooltip>
<template #title>{{
slotProps.id
}}</template>
{{ slotProps.id }}
</a-tooltip>
</div>
</a-col>
<a-col :span="12">
<div class="card-item-content-text">
类型
</div>
<div class="card-item-content-text">
<a-tooltip>
<template #title>{{
slotProps.type
}}</template>
{{ slotProps.type }}
</a-tooltip>
</div>
</a-col>
</a-row>
</div>
</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"
>
<a-button :disabled="item.disabled">
<AIcon
type="DeleteOutlined"
v-if="item.key === 'delete'"
/>
<template v-else>
<AIcon :type="item.icon" />
<span>{{ item.text }}</span>
</template>
</a-button>
</a-popconfirm>
<template v-else>
<a-button
:disabled="item.disabled"
@click="item.onClick"
>
<AIcon
type="DeleteOutlined"
v-if="item.key === 'delete'"
/>
<template v-else>
<AIcon :type="item.icon" />
<span>{{ item.text }}</span>
</template>
</a-button>
</template>
</a-tooltip>
</template>
</CardBox>
</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>
</div>
<Save v-if="visible" :data="current" @change="saveChange" />
</page-container>
</template>
<script lang="ts" setup name="AccessConfigPage">
import type { ActionsType } from '@/components/Table/index.vue';
import { getImage } from '@/utils/comm';
import { list, remove } from '@/api/link/protocol';
import { message } from 'ant-design-vue';
import Save from './Save/index.vue';
const tableRef = ref<Record<string, any>>({});
const router = useRouter();
const params = ref<Record<string, any>>({});
const visible = ref(false);
const current = ref({});
const columns = [
{
title: 'ID',
dataIndex: 'id',
key: 'id',
search: {
type: 'string',
},
width: 200,
fixed: 'left',
// scopedSlots: true,
},
{
title: '名称',
dataIndex: 'name',
key: 'name',
search: {
type: 'string',
},
},
{
title: '类型',
dataIndex: 'type',
key: 'type',
search: {
type: 'select',
options: [
{
label: 'jar',
value: 'jar',
},
{
label: 'local',
value: 'local',
},
],
},
scopedSlots: true,
},
{
title: '说明',
dataIndex: 'description',
key: 'description',
search: {
type: 'string',
},
ellipsis: true,
},
{
title: '操作',
key: 'action',
fixed: 'right',
width: 200,
scopedSlots: true,
},
];
const getActions = (
data: Partial<Record<string, any>>,
type: 'card' | 'table',
): ActionsType[] => {
if (!data) return [];
const actions = [
{
key: 'edit',
text: '编辑',
tooltip: {
title: '编辑',
},
icon: 'EditOutlined',
onClick: () => {
handlEdit(data.id);
},
},
{
key: 'delete',
text: '删除',
popConfirm: {
title: '确认删除?',
onConfirm: async () => {
console.log(11, data.id);
// const res = await remove(data.id);
// if (res.success) {
// message.success('');
// tableRef.value.reload();
// } else {
// message.error('');
// }
},
},
icon: 'DeleteOutlined',
},
];
return actions;
};
const handlAdd = () => {
console.log(11);
visible.value = true;
};
const handlEdit = (id: string) => {
// router.push(`/link/accessConfig/detail/edit/${id}`);
// router.push({
// path: `/iot/link/accessConfig/detail/${id}`,
// query: { view: false },
// });
console.log(id);
visible.value = true;
};
const saveChange = (value: object) => {
visible.value = false;
current.value = {};
if (value) {
message.success('操作成功');
tableRef.value.reload();
}
};
/**
* 搜索
* @param params
*/
const handleSearch = (e: any) => {
params.value = e;
};
</script>
<style lang="less" scoped>
.tableCardDisabled {
width: 100%;
background: url('/images/access-config-diaabled.png') no-repeat;
background-size: 100% 100%;
}
.tableCardEnabled {
width: 100%;
background: url('/images/access-config-enabled.png') no-repeat;
background-size: 100% 100%;
}
.card-item-content {
min-height: 100px;
.card-item-content-title-a {
// color: #000 !important;
font-weight: 700;
font-size: 18px;
overflow: hidden; //
text-overflow: ellipsis; //
white-space: nowrap; //
}
.card-item-content-box {
min-height: 50px;
}
.card-item-content-text {
color: rgba(0, 0, 0, 0.75);
font-size: 12px;
overflow: hidden; //
text-overflow: ellipsis; //
white-space: nowrap; //
}
}
</style>

7
src/views/link/Protocol/typings.d.ts vendored Normal file
View File

@ -0,0 +1,7 @@
import type { BaseItem } from '@/utils/typings';
type ProtocolItem = {
state: number;
type: string;
configuration: Record<string, any>;
} & BaseItem;