Merge remote-tracking branch 'origin/dev' into dev
This commit is contained in:
commit
00d57cc44a
|
@ -114,6 +114,14 @@ export const isExists = (id: string) => server.get(`/device-instance/${id}/exist
|
|||
*/
|
||||
export const update = (data: Partial<DeviceInstance>) => data.id ? server.patch(`/device-instance`, data) : server.post(`/device-instance`, data)
|
||||
|
||||
/**
|
||||
* 修改设备信息
|
||||
* @param id 设备id
|
||||
* @param data 设备信息
|
||||
* @returns
|
||||
*/
|
||||
export const modify = (id: string, data: Partial<DeviceInstance>) => server.put(`/device-instance/${id}`, data)
|
||||
|
||||
/**
|
||||
* 获取配置信息
|
||||
* @param id 设备id
|
||||
|
|
|
@ -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);
|
|
@ -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}`);
|
|
@ -0,0 +1,10 @@
|
|||
import server from '@/utils/request'
|
||||
import type { Agg, AggPlaying } from '@/views/media/DashBoard/typings'
|
||||
|
||||
export default {
|
||||
// 录像数量
|
||||
agg: () => server.get<Agg>(`/media/record/file/agg`),
|
||||
// 播放中数量
|
||||
aggPlaying: () => server.get<AggPlaying>(`/media/channel/playing/agg`),
|
||||
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
import server from '@/utils/request'
|
||||
|
||||
export default {
|
||||
// 设备数量
|
||||
deviceCount: () => server.get<number>(`/media/device/_count`),
|
||||
// 通道数量
|
||||
channelCount: () => server.post<number>(`/media/channel/_count`),
|
||||
|
||||
}
|
|
@ -14,4 +14,6 @@ export const getMenuInfo_api = (id:string) => server.get(`/menu/${id}`);
|
|||
// 编辑菜单信息
|
||||
export const saveMenuInfo_api = (data: object) => server.patch(`/menu`, data);
|
||||
// 新增菜单信息
|
||||
export const addMenuInfo_api = (data: object) => server.post(`/menu`, data);
|
||||
export const addMenuInfo_api = (data: object) => server.post(`/menu`, data);
|
||||
// 删除菜单信息
|
||||
export const delMenuInfo_api = (id: string) => server.remove(`/menu/${id}`);
|
|
@ -45,6 +45,7 @@ const iconKeys = [
|
|||
'InfoCircleOutlined',
|
||||
'SearchOutlined',
|
||||
'EllipsisOutlined',
|
||||
'ClockCircleOutlined'
|
||||
]
|
||||
|
||||
const Icon = (props: {type: string}) => {
|
||||
|
|
|
@ -2,22 +2,21 @@
|
|||
<div class="card">
|
||||
<div
|
||||
class="card-warp"
|
||||
:class="{ active: active ? 'active' : ''}"
|
||||
:class="{ active: active ? 'active' : '' }"
|
||||
@click="handleClick"
|
||||
>
|
||||
<div class="card-content">
|
||||
<a-row>
|
||||
<a-col :span="6">
|
||||
<!-- 图片 -->
|
||||
<div class="card-item-avatar">
|
||||
<slot name="img"> </slot>
|
||||
</div>
|
||||
</a-col>
|
||||
<a-col :span="18">
|
||||
<!-- 内容 -->
|
||||
<div style="display: flex">
|
||||
<!-- 图片 -->
|
||||
<div class="card-item-avatar">
|
||||
<slot name="img"> </slot>
|
||||
</div>
|
||||
|
||||
<!-- 内容 -->
|
||||
<div class="card-item-body">
|
||||
<slot name="content"></slot>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 勾选 -->
|
||||
<div v-if="active" class="checked-icon">
|
||||
|
@ -201,6 +200,13 @@ const handleClick = () => {
|
|||
margin-right: 16px;
|
||||
}
|
||||
|
||||
.card-item-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.card-state {
|
||||
position: absolute;
|
||||
top: 30px;
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
.jtable-body {
|
||||
width: 100%;
|
||||
padding: 0 24px 24px;
|
||||
padding: 16px 24px 24px;
|
||||
background-color: white;
|
||||
.jtable-body-header {
|
||||
padding: 16px 0;
|
||||
padding-bottom: 16px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
|
|
@ -40,11 +40,11 @@ export interface ActionsType {
|
|||
children?: ActionsType[];
|
||||
}
|
||||
|
||||
export interface JColumnProps extends ColumnProps{
|
||||
export interface JColumnProps extends ColumnProps {
|
||||
scopedSlots?: boolean; // 是否为插槽 true: 是 false: 否
|
||||
}
|
||||
|
||||
export interface JTableProps extends TableProps{
|
||||
export interface JTableProps extends TableProps {
|
||||
request?: (params?: Record<string, any>) => Promise<Partial<RequestData>>;
|
||||
cardBodyClass?: string;
|
||||
columns: JColumnProps[];
|
||||
|
@ -53,8 +53,8 @@ export interface JTableProps extends TableProps{
|
|||
// actions?: ActionsType[];
|
||||
noPagination?: boolean;
|
||||
rowSelection?: TableProps['rowSelection'];
|
||||
cardProps?: Record<string, any>;
|
||||
dataSource?: Record<string, any>[];
|
||||
cardProps?: Record<string, any>;
|
||||
dataSource?: Record<string, any>[];
|
||||
gridColumn?: number;
|
||||
/**
|
||||
* 用于不同分辨率
|
||||
|
@ -62,10 +62,11 @@ export interface JTableProps extends TableProps{
|
|||
* gridColumns[1] 1440 ~ 1600 分辨率;
|
||||
* gridColumns[2] > 1600 分辨率;
|
||||
*/
|
||||
gridColumns?: number[];
|
||||
alertRender?: boolean;
|
||||
type?: keyof typeof TypeEnum;
|
||||
defaultParams?: Record<string, any>;
|
||||
gridColumns?: number[];
|
||||
alertRender?: boolean;
|
||||
type?: keyof typeof TypeEnum;
|
||||
defaultParams?: Record<string, any>;
|
||||
bodyStyle?: Record<string, any>;
|
||||
}
|
||||
|
||||
const JTable = defineComponent<JTableProps>({
|
||||
|
@ -88,13 +89,17 @@ const JTable = defineComponent<JTableProps>({
|
|||
type: String,
|
||||
default: ''
|
||||
},
|
||||
bodyStyle: {
|
||||
type: Object,
|
||||
default: {}
|
||||
},
|
||||
columns: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
params: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
default: () => { }
|
||||
},
|
||||
model: {
|
||||
type: [String, undefined],
|
||||
|
@ -142,7 +147,7 @@ const JTable = defineComponent<JTableProps>({
|
|||
}
|
||||
}
|
||||
} as any,
|
||||
setup(props: JTableProps ,{ slots, emit, expose }){
|
||||
setup(props: JTableProps, { slots, emit, expose }) {
|
||||
const simpleImage = Empty.PRESENTED_IMAGE_SIMPLE
|
||||
const _model = ref<keyof typeof ModelEnum>(props.model ? props.model : ModelEnum.CARD); // 模式切换
|
||||
const column = ref<number>(props.gridColumn || 4);
|
||||
|
@ -174,9 +179,9 @@ const JTable = defineComponent<JTableProps>({
|
|||
*/
|
||||
const handleSearch = async (_params?: Record<string, any>) => {
|
||||
loading.value = true
|
||||
if(props.request) {
|
||||
if (props.request) {
|
||||
const resp = await props.request({
|
||||
pageIndex: 0,
|
||||
pageIndex: 0,
|
||||
pageSize: 12,
|
||||
...props.defaultParams,
|
||||
..._params,
|
||||
|
@ -185,14 +190,14 @@ const JTable = defineComponent<JTableProps>({
|
|||
...(_params?.terms || [])
|
||||
]
|
||||
})
|
||||
if(resp.status === 200){
|
||||
if(props.type === 'PAGE'){
|
||||
if (resp.status === 200) {
|
||||
if (props.type === 'PAGE') {
|
||||
// 判断如果是最后一页且最后一页为空,就跳转到前一页
|
||||
if(resp.result.total && resp.result.pageSize && resp.result.pageIndex && resp.result?.data?.length === 0) {
|
||||
if (resp.result.total && resp.result.pageSize && resp.result.pageIndex && resp.result?.data?.length === 0) {
|
||||
handleSearch({
|
||||
..._params,
|
||||
pageSize: pageSize.value,
|
||||
pageIndex: pageIndex.value > 0 ? pageIndex.value - 1 : 0,
|
||||
pageIndex: pageIndex.value > 0 ? pageIndex.value - 1 : 0,
|
||||
})
|
||||
} else {
|
||||
_dataSource.value = resp.result?.data || []
|
||||
|
@ -204,31 +209,30 @@ const JTable = defineComponent<JTableProps>({
|
|||
_dataSource.value = resp?.result || []
|
||||
}
|
||||
} else {
|
||||
_dataSource.value = []
|
||||
_dataSource.value = []
|
||||
}
|
||||
} else {
|
||||
console.log(props?.dataSource)
|
||||
_dataSource.value = props?.dataSource || []
|
||||
}
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.params,
|
||||
() => props.params,
|
||||
(newValue) => {
|
||||
handleSearch(newValue)
|
||||
},
|
||||
{deep: true, immediate: true}
|
||||
},
|
||||
{ deep: true, immediate: true }
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.dataSource,
|
||||
(newValue) => {
|
||||
if(props.dataSource){
|
||||
() => props.dataSource,
|
||||
() => {
|
||||
if (props.dataSource && !props.request) {
|
||||
handleSearch(props.params)
|
||||
}
|
||||
},
|
||||
{deep: true, immediate: true}
|
||||
},
|
||||
{ deep: true, immediate: true }
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
|
@ -241,6 +245,10 @@ const JTable = defineComponent<JTableProps>({
|
|||
window.onresize = null
|
||||
})
|
||||
|
||||
watchEffect(() => {
|
||||
// console.log(props.bodyStyle)
|
||||
})
|
||||
|
||||
/**
|
||||
* 刷新数据
|
||||
* @param _params
|
||||
|
@ -256,10 +264,10 @@ const JTable = defineComponent<JTableProps>({
|
|||
/**
|
||||
* 导出方法
|
||||
*/
|
||||
expose({ reload })
|
||||
|
||||
expose({ reload })
|
||||
|
||||
return () => <Spin spinning={loading.value}>
|
||||
<div class={styles["jtable-body"]}>
|
||||
<div class={styles["jtable-body"]} style={{ ...props.bodyStyle }}>
|
||||
<div class={styles["jtable-body-header"]}>
|
||||
<div class={styles["jtable-body-header-left"]}>
|
||||
{/* 顶部左边插槽 */}
|
||||
|
@ -278,7 +286,7 @@ const JTable = defineComponent<JTableProps>({
|
|||
<div class={[styles["jtable-setting-item"], ModelEnum.TABLE === _model.value ? styles['active'] : '']} onClick={() => {
|
||||
_model.value = ModelEnum.TABLE
|
||||
}}>
|
||||
<UnorderedListOutlined />
|
||||
<UnorderedListOutlined />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
@ -288,66 +296,66 @@ const JTable = defineComponent<JTableProps>({
|
|||
<div class={styles['jtable-content']}>
|
||||
{
|
||||
props.alertRender && props?.rowSelection && props?.rowSelection?.selectedRowKeys && props.rowSelection.selectedRowKeys?.length ?
|
||||
<div class={styles['jtable-alert']}>
|
||||
<Alert
|
||||
message={'已选择' + props?.rowSelection?.selectedRowKeys?.length + '项'}
|
||||
type="info"
|
||||
onClose={() => {
|
||||
emit('cancelSelect')
|
||||
}}
|
||||
closeText={<a-button type="link">取消选择</a-button>}
|
||||
/>
|
||||
</div> : null
|
||||
<div class={styles['jtable-alert']}>
|
||||
<Alert
|
||||
message={'已选择' + props?.rowSelection?.selectedRowKeys?.length + '项'}
|
||||
type="info"
|
||||
onClose={() => {
|
||||
emit('cancelSelect')
|
||||
}}
|
||||
closeText={<a-button type="link">取消选择</a-button>}
|
||||
/>
|
||||
</div> : null
|
||||
}
|
||||
{
|
||||
_model.value === ModelEnum.CARD ?
|
||||
<div class={styles['jtable-card']}>
|
||||
{
|
||||
_dataSource.value.length ?
|
||||
<div
|
||||
class={styles['jtable-card-items']}
|
||||
style={{gridTemplateColumns: `repeat(${column.value}, 1fr)`}}
|
||||
>
|
||||
{
|
||||
_dataSource.value.map(item => slots.card ?
|
||||
<div class={[styles['jtable-card-item'], props.cardBodyClass]}>
|
||||
{slots.card(item)}
|
||||
</div> : null
|
||||
)
|
||||
}
|
||||
</div> :
|
||||
<div><Empty image={Empty.PRESENTED_IMAGE_SIMPLE} /></div>
|
||||
}
|
||||
</div> :
|
||||
<div>
|
||||
<Table
|
||||
dataSource={_dataSource.value}
|
||||
columns={_columns.value}
|
||||
pagination={false}
|
||||
rowKey="id"
|
||||
rowSelection={props.rowSelection}
|
||||
scroll={{x: 1366}}
|
||||
v-slots={{
|
||||
bodyCell: (dt: Record<string, any>) => {
|
||||
const {column, record} = dt;
|
||||
if((column?.key || column?.dataIndex) && column?.scopedSlots && (slots?.[column?.dataIndex] || slots?.[column?.key])) {
|
||||
const _key = column?.key || column?.dataIndex
|
||||
return slots?.[_key]!(record)
|
||||
} else {
|
||||
return record?.[column?.dataIndex] || ''
|
||||
<div class={styles['jtable-card']}>
|
||||
{
|
||||
_dataSource.value.length ?
|
||||
<div
|
||||
class={styles['jtable-card-items']}
|
||||
style={{ gridTemplateColumns: `repeat(${column.value}, 1fr)` }}
|
||||
>
|
||||
{
|
||||
_dataSource.value.map(item => slots.card ?
|
||||
<div class={[styles['jtable-card-item'], props.cardBodyClass]}>
|
||||
{slots.card(item)}
|
||||
</div> : null
|
||||
)
|
||||
}
|
||||
</div> :
|
||||
<div><Empty image={Empty.PRESENTED_IMAGE_SIMPLE} /></div>
|
||||
}
|
||||
</div> :
|
||||
<div>
|
||||
<Table
|
||||
dataSource={_dataSource.value}
|
||||
columns={_columns.value}
|
||||
pagination={false}
|
||||
rowKey="id"
|
||||
rowSelection={props.rowSelection}
|
||||
scroll={{ x: 1366 }}
|
||||
v-slots={{
|
||||
bodyCell: (dt: Record<string, any>) => {
|
||||
const { column, record } = dt;
|
||||
if ((column?.key || column?.dataIndex) && column?.scopedSlots && (slots?.[column?.dataIndex] || slots?.[column?.key])) {
|
||||
const _key = column?.key || column?.dataIndex
|
||||
return slots?.[_key]!(record)
|
||||
} else {
|
||||
return record?.[column?.dataIndex] || ''
|
||||
}
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
{/* 分页 */}
|
||||
{
|
||||
(!!_dataSource.value.length) && !props.noPagination && props.type === 'PAGE' &&
|
||||
<div class={styles['jtable-pagination']}>
|
||||
<Pagination
|
||||
size="small"
|
||||
<Pagination
|
||||
size="small"
|
||||
total={total.value}
|
||||
showQuickJumper={false}
|
||||
showSizeChanger={true}
|
||||
|
|
|
@ -76,3 +76,23 @@ export function getSlotVNode<T>(slots: Slots, props: Record<string, unknown>, pr
|
|||
}
|
||||
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;
|
||||
};
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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;
|
||||
};
|
|
@ -0,0 +1,105 @@
|
|||
<template>
|
||||
<a-drawer placement="right" :closable="false" :visible="true">
|
||||
<template #title>
|
||||
<div
|
||||
style="
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
"
|
||||
>
|
||||
<span
|
||||
><AIcon
|
||||
type="CloseOutlined"
|
||||
style="margin-right: 5px"
|
||||
@click="onClose"
|
||||
/>编辑配置</span
|
||||
>
|
||||
<a-button type="primary" @click="saveBtn">保存</a-button>
|
||||
</div>
|
||||
</template>
|
||||
<a-form layout="vertical" ref="formRef" :model="modelRef">
|
||||
<template v-for="(item, index) in props.config" :key="index">
|
||||
<a-form-item
|
||||
:name="item.property"
|
||||
v-for="i in item.properties"
|
||||
:key="i.property"
|
||||
>
|
||||
<template #label>
|
||||
<span style="margin-right: 5px">{{ i.name }}</span>
|
||||
<a-tooltip v-if="i.description" :title="i.description"
|
||||
><AIcon type="QuestionCircleOutlined"
|
||||
/></a-tooltip>
|
||||
</template>
|
||||
<ValueItem
|
||||
v-model:modelValue="modelRef[i.property]"
|
||||
:itemType="i.type.type"
|
||||
:options="
|
||||
i.type.type === 'enum'
|
||||
? (i.type?.elements || []).map((item) => {
|
||||
return {
|
||||
label: item?.text,
|
||||
value: item?.value,
|
||||
};
|
||||
})
|
||||
: undefined
|
||||
"
|
||||
/>
|
||||
</a-form-item>
|
||||
</template>
|
||||
</a-form>
|
||||
</a-drawer>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { modify } from '@/api/device/instance';
|
||||
import { useInstanceStore } from '@/store/instance';
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
const emit = defineEmits(['close', 'save']);
|
||||
|
||||
const formRef = ref();
|
||||
const modelRef = reactive({});
|
||||
|
||||
const instanceStore = useInstanceStore();
|
||||
|
||||
const props = defineProps({
|
||||
config: {
|
||||
type: Array,
|
||||
default: []
|
||||
}
|
||||
})
|
||||
|
||||
watchEffect(() => {
|
||||
const obj = instanceStore.current?.configuration
|
||||
if(obj && Object.keys(obj).length) {
|
||||
(props?.config || []).map((item: any) => {
|
||||
if(Array.isArray(item.properties) && item?.properties.length){
|
||||
item.properties.map((i: any) => {
|
||||
modelRef[i.property] = obj[i.property]
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
const onClose = () => {
|
||||
emit('close');
|
||||
formRef.value.resetFields();
|
||||
};
|
||||
|
||||
const saveBtn = () => {
|
||||
formRef.value.validate().then(async () => {
|
||||
const values = toRaw(modelRef);
|
||||
const resp = await modify(instanceStore.current?.id || '', {
|
||||
id: instanceStore.current?.id,
|
||||
configuration: { ...values }
|
||||
})
|
||||
if(resp.status === 200){
|
||||
message.success('操作成功!')
|
||||
emit('save');
|
||||
}
|
||||
});
|
||||
};
|
||||
</script>
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div style="margin-top: 20px" v-if="config.length">
|
||||
<div style="display: flex;">
|
||||
<div style="display: flex; margin-bottom: 20px; align-items: center;">
|
||||
<div style="font-size: 16px; font-weight: 700">配置</div>
|
||||
<a-space>
|
||||
<a-button type="link" @click="visible = true"><AIcon type="EditOutlined" />编辑</a-button>
|
||||
|
@ -13,11 +13,11 @@
|
|||
</a-space>
|
||||
</div>
|
||||
<a-descriptions bordered size="small" v-for="i in config" :key="i.name">
|
||||
<template #title><h4>{{i.name}}</h4></template>
|
||||
<template #title><h4 style="font-size: 15px">{{i.name}}</h4></template>
|
||||
<a-descriptions-item v-for="item in i.properties" :key="item.property">
|
||||
<template #label>
|
||||
<span style="margin-right: 5px">{{item.name}}</span>
|
||||
<a-tooltip v-if="item.description" :title="item.description"><AIcon type="QuestionCircleOutlined" /></a-tooltip>
|
||||
<span>{{item.name}}</span>
|
||||
</template>
|
||||
<span v-if="item.type.type === 'password' && instanceStore.current?.configuration?.[item.property]?.length > 0">******</span>
|
||||
<span v-else>
|
||||
|
@ -26,6 +26,7 @@
|
|||
</span>
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
<Save v-if="visible" @save="saveBtn" @close="visible = false" :config="config" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -34,6 +35,7 @@ import { useInstanceStore } from "@/store/instance"
|
|||
import { ConfigMetadata } from "@/views/device/Product/typings"
|
||||
import { getConfigMetadata, _deploy, configurationReset } from '@/api/device/instance'
|
||||
import { message } from "ant-design-vue"
|
||||
import Save from './Save.vue'
|
||||
|
||||
const instanceStore = useInstanceStore()
|
||||
const visible = ref<boolean>(false)
|
||||
|
@ -78,4 +80,11 @@ const resetBtn = async () => {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
const saveBtn = () => {
|
||||
visible.value = false
|
||||
if(instanceStore.current.id){
|
||||
instanceStore.refresh(instanceStore.current.id)
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,68 @@
|
|||
<template>
|
||||
<Search :columns="columns" target="device-instance-running-events" />
|
||||
<JTable
|
||||
ref="eventsRef"
|
||||
:columns="columns"
|
||||
:dataSource="dataSource"
|
||||
model="TABLE"
|
||||
:bodyStyle="{padding: '0 24px'}"
|
||||
>
|
||||
<template #timestamp="slotProps">
|
||||
{{ moment(slotProps.timestamp).format('YYYY-MM-DD HH:mm:ss') }}
|
||||
</template>
|
||||
<template #action="slotProps">
|
||||
<a-button type="link" @click="detail(slotProps)">
|
||||
<AIcon type="SearchOutlined" />
|
||||
</a-button>
|
||||
</template>
|
||||
</JTable>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import moment from 'moment'
|
||||
const events = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
}
|
||||
})
|
||||
|
||||
const columns = ref<Record<string, any>>([
|
||||
{
|
||||
title: '时间',
|
||||
dataIndex: 'timestamp',
|
||||
key: 'timestamp',
|
||||
scopedSlots: true,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'action',
|
||||
key: 'action',
|
||||
scopedSlots: true,
|
||||
}
|
||||
])
|
||||
|
||||
const dataSource = ref<Record<string, any>[]>([])
|
||||
|
||||
watchEffect(() => {
|
||||
if(events.data?.valueType?.type === 'object'){
|
||||
(events.data.valueType?.properties || []).map((i: any) => {
|
||||
columns.value.splice(0, 0, {
|
||||
key: i.id,
|
||||
title: i.name,
|
||||
dataIndex: `${i.id}_format`,
|
||||
// renderText: (text) => (typeof text === 'object' ? JSON.stringify(text) : text),
|
||||
})
|
||||
})
|
||||
} else {
|
||||
columns.value.splice(0, 0, {
|
||||
title: '数据',
|
||||
dataIndex: 'value',
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const detail = () => {
|
||||
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,114 @@
|
|||
<template>
|
||||
<a-card :hoverable="true" class="card-box">
|
||||
<a-spin :spinning="loading">
|
||||
<div class="card-container">
|
||||
<div class="header">
|
||||
<div class="title">{{ _props.data.name }}</div>
|
||||
<div class="extra">
|
||||
<a-space>
|
||||
<a-tooltip title="设置属性至设备" v-if="data.expands?.type?.includes('write')">
|
||||
<AIcon
|
||||
type="EditOutlined"
|
||||
style="font-size: 12px"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip title="指标" v-if="(data.expands?.metrics || []).length > 0 &&
|
||||
['int', 'long', 'float', 'double', 'string', 'boolean', 'date'].includes(
|
||||
data.valueType?.type || '',
|
||||
)">
|
||||
<AIcon
|
||||
type="ClockCircleOutlined"
|
||||
style="font-size: 12px"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip title="获取最新属性值" v-if="data.expands?.type?.includes('read')">
|
||||
<AIcon
|
||||
type="SyncOutlined"
|
||||
style="font-size: 12px"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip title="详情">
|
||||
<AIcon
|
||||
type="BarsOutlined"
|
||||
style="font-size: 12px"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</div>
|
||||
</div>
|
||||
<div class="value">
|
||||
<ValueRender :data="data" />
|
||||
</div>
|
||||
<div class="bottom">
|
||||
<div style="color: rgba(0, 0, 0, .65); font-size: 12px">更新时间</div>
|
||||
<div class="time-value">{{data?.time || '--'}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-spin>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import ValueRender from './ValueRender.vue'
|
||||
const _props = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
});
|
||||
const loading = ref<boolean>(true);
|
||||
|
||||
watchEffect(() => {
|
||||
if (_props.data.name) {
|
||||
loading.value = false;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.card-box {
|
||||
background-color: rgba(0, 0, 0, 0.02);
|
||||
width: 100%;
|
||||
|
||||
.card-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
height: 154px;
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
.title {
|
||||
width: 60%;
|
||||
margin-right: 10px;
|
||||
overflow: hidden;
|
||||
color: rgba(0, 0, 0, 0.65);
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
.value {
|
||||
height: 60px;
|
||||
line-height: 60px;
|
||||
color: #323130;
|
||||
font-weight: 700;
|
||||
font-size: 24px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.bottom {
|
||||
.time-value {
|
||||
margin-top: 5px;
|
||||
font-size: 16px;
|
||||
min-height: 25px;
|
||||
color: #000;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,14 @@
|
|||
<template>
|
||||
<div>
|
||||
{{data.value || '--'}}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
const _data = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
});
|
||||
</script>
|
|
@ -0,0 +1,166 @@
|
|||
<template>
|
||||
<JTable
|
||||
ref="metadataRef"
|
||||
:columns="columns"
|
||||
:dataSource="dataSource"
|
||||
:bodyStyle="{padding: 0}"
|
||||
>
|
||||
<template #headerTitle>
|
||||
<a-input-search
|
||||
placeholder="请输入名称"
|
||||
style="width: 300px; margin-bottom: 10px"
|
||||
@search="onSearch"
|
||||
/>
|
||||
</template>
|
||||
<template #card="slotProps">
|
||||
<PropertyCard :data="slotProps" />
|
||||
</template>
|
||||
<template #value="slotProps">
|
||||
<ValueRender :data="slotProps" />
|
||||
</template>
|
||||
<template #time="slotProps">
|
||||
{{slotProps.time || '--'}}
|
||||
</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"
|
||||
: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>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { PropertyData } from "../../../typings"
|
||||
import PropertyCard from './PropertyCard.vue'
|
||||
import ValueRender from './ValueRender.vue'
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: '名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
},
|
||||
{
|
||||
title: '值',
|
||||
dataIndex: 'value',
|
||||
key: 'value',
|
||||
scopedSlots: true
|
||||
},
|
||||
{
|
||||
title: '更新时间',
|
||||
dataIndex: 'time',
|
||||
key: 'time',
|
||||
scopedSlots: true,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'action',
|
||||
key: 'action',
|
||||
scopedSlots: true,
|
||||
},
|
||||
]
|
||||
|
||||
const _data = defineProps({
|
||||
data: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
})
|
||||
|
||||
const dataSource = ref<PropertyData[]>([])
|
||||
|
||||
const getActions = (data: Partial<Record<string, any>>) => {
|
||||
const arr = []
|
||||
if(data.expands?.type?.includes('write')){
|
||||
arr.push({
|
||||
key: 'edit',
|
||||
tooltip: {
|
||||
title: '设置属性至设备',
|
||||
},
|
||||
icon: 'EditOutlined',
|
||||
onClick: () => {
|
||||
|
||||
},
|
||||
})
|
||||
}
|
||||
if((data.expands?.metrics || []).length && ['int', 'long', 'float', 'double', 'string', 'boolean', 'date'].includes(
|
||||
data.valueType?.type || '',
|
||||
)){
|
||||
arr.push({
|
||||
key: 'metrics',
|
||||
tooltip: {
|
||||
title: '指标',
|
||||
},
|
||||
icon: 'ClockCircleOutlined',
|
||||
onClick: () => {
|
||||
|
||||
},
|
||||
})
|
||||
}
|
||||
if(data.expands?.type?.includes('read')){
|
||||
arr.push({
|
||||
key: 'read',
|
||||
tooltip: {
|
||||
title: '获取最新属性值',
|
||||
},
|
||||
icon: 'SyncOutlined',
|
||||
onClick: () => {
|
||||
|
||||
},
|
||||
})
|
||||
}
|
||||
arr.push({
|
||||
key: 'detail',
|
||||
text: '详情',
|
||||
tooltip: {
|
||||
title: '详情',
|
||||
},
|
||||
icon: 'BarsOutlined',
|
||||
onClick: () => {
|
||||
|
||||
},
|
||||
})
|
||||
return arr
|
||||
}
|
||||
|
||||
watchEffect(() => {
|
||||
dataSource.value = _data.data as PropertyData[]
|
||||
})
|
||||
|
||||
const onSearch = () => {};
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
|
||||
</style>
|
|
@ -1,46 +1,120 @@
|
|||
<template>
|
||||
<a-card>
|
||||
<a-row type="flex">
|
||||
<a-col flex="200px">
|
||||
<div>
|
||||
<a-input-search
|
||||
v-model:value="value"
|
||||
placeholder="请输入事件名称"
|
||||
style="width: 200px; margin-bottom: 10px"
|
||||
@search="onSearch"
|
||||
<div class="property-box">
|
||||
<div class="property-box-left">
|
||||
<a-input-search
|
||||
v-model:value="value"
|
||||
placeholder="请输入事件名称"
|
||||
style="width: 200px; margin-bottom: 10px"
|
||||
@search="onSearch"
|
||||
:allowClear="true"
|
||||
/>
|
||||
<a-tabs
|
||||
tab-position="left"
|
||||
style="height: 600px"
|
||||
v-model:activeKey="activeKey"
|
||||
:tabBarStyle="{ width: '200px' }"
|
||||
@change="tabChange"
|
||||
>
|
||||
<a-tab-pane
|
||||
v-for="i in tabList"
|
||||
:key="i.key"
|
||||
:tab="i.tab"
|
||||
/>
|
||||
<a-tabs
|
||||
tab-position="left"
|
||||
:style="{ height: '600px' }"
|
||||
v-model:activeKey="activeKey"
|
||||
tabBarStyle="width: 200px"
|
||||
>
|
||||
<a-tab-pane v-for="i in tabList" :key="i.key" :tab="i.tab" />
|
||||
</a-tabs>
|
||||
</div>
|
||||
</a-col>
|
||||
<a-col flex="auto">
|
||||
<!-- <component :is="tabs[activeKey]" /> -->
|
||||
123
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-tabs>
|
||||
</div>
|
||||
<div class="property-box-right">
|
||||
<Event v-if="type === 'event'" :data="data" />
|
||||
<Property v-else :data="properties" />
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
const activeKey = ref<string>('property')
|
||||
const tabList = ref<{key: string, tab: string}[]>([
|
||||
{
|
||||
key: 'property',
|
||||
tab: '属性'
|
||||
},
|
||||
{
|
||||
key: 'event1',
|
||||
tab: '事件1'
|
||||
import { useInstanceStore } from '@/store/instance';
|
||||
import _ from 'lodash';
|
||||
import Event from './Event/index.vue';
|
||||
import Property from './Property/index.vue';
|
||||
|
||||
const activeKey = ref<string>('property');
|
||||
const tabList = ref<{ key: string; tab: string; type: 'property' | 'event' }[]>(
|
||||
[
|
||||
{
|
||||
key: 'property',
|
||||
tab: '属性',
|
||||
type: 'property',
|
||||
},
|
||||
],
|
||||
);
|
||||
const type = ref<string>('property');
|
||||
const data = ref<Record<string, any>>({})
|
||||
const value = ref<string>('');
|
||||
const instanceStore = useInstanceStore()
|
||||
const metadata = JSON.parse(instanceStore.current?.metadata || '{}')
|
||||
const properties = metadata.properties
|
||||
const events = metadata.events
|
||||
|
||||
watch(() => events, (newVal) => {
|
||||
if(events && newVal.length){
|
||||
newVal.map((item: any) => {
|
||||
tabList.value.push({
|
||||
...item,
|
||||
key: item.id,
|
||||
tab: item.name,
|
||||
type: 'event',
|
||||
})
|
||||
})
|
||||
}
|
||||
])
|
||||
}, {
|
||||
deep: true,
|
||||
immediate: true
|
||||
})
|
||||
|
||||
const onSearch = () => {
|
||||
const arr = [
|
||||
{
|
||||
key: 'property',
|
||||
tab: '属性',
|
||||
type: 'property',
|
||||
},
|
||||
...events.map((item: any) => {
|
||||
return {
|
||||
...item,
|
||||
key: item.id,
|
||||
tab: item.name,
|
||||
type: 'event',
|
||||
}
|
||||
})
|
||||
]
|
||||
if(value.value){
|
||||
const li = arr.filter((i: any) => {
|
||||
return i?.tab.indexOf(value.value) !== -1;
|
||||
})
|
||||
tabList.value = _.cloneDeep(li)
|
||||
} else {
|
||||
tabList.value = _.cloneDeep(arr)
|
||||
}
|
||||
};
|
||||
const tabChange = (key: string) => {
|
||||
const dt = tabList.value.find((i) => i.key === key);
|
||||
if (dt) {
|
||||
data.value = dt
|
||||
type.value = dt.type;
|
||||
}
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.property-box {
|
||||
display: flex;
|
||||
.property-box-left {
|
||||
width: 200px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
.property-box-right {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</style>
|
|
@ -117,11 +117,14 @@ const objToParams = (source: object): string => {
|
|||
}
|
||||
|
||||
.box-list {
|
||||
grid-template-columns: repeat(5, 1fr);
|
||||
display: grid;
|
||||
grid-column-gap: 66px;
|
||||
// grid-template-columns: repeat(5, 1fr);
|
||||
// display: grid;
|
||||
// grid-column-gap: 66px;
|
||||
display: flex;
|
||||
gap: 66px;
|
||||
|
||||
.list-item {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
.box-top {
|
||||
position: relative;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<!-- 物联卡查看 -->
|
||||
<template>
|
||||
<div class="page-container">
|
||||
<page-container>
|
||||
<!-- 新增、编辑 -->
|
||||
<Save
|
||||
v-if="visible"
|
||||
|
@ -117,7 +117,7 @@
|
|||
</a-row>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</page-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
@ -138,7 +138,7 @@ const detail = ref<any>({});
|
|||
const flowData = ref<any[]>([]);
|
||||
|
||||
const getDetail = () => {
|
||||
queryDetail(route.query.id).then((resp: any) => {
|
||||
queryDetail(route.params.id).then((resp: any) => {
|
||||
if (resp.status === 200) {
|
||||
detail.value = resp.result;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<!-- 物联卡管理 -->
|
||||
<template>
|
||||
<div class="page-container">
|
||||
<page-container class="container">
|
||||
<Search
|
||||
:columns="columns"
|
||||
target="iot-card-management-search"
|
||||
|
@ -323,7 +323,7 @@
|
|||
:data="current"
|
||||
@change="saveChange"
|
||||
/>
|
||||
</div>
|
||||
</page-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
@ -539,10 +539,7 @@ const getActions = (
|
|||
icon: 'EyeOutlined',
|
||||
onClick: () => {
|
||||
router.push({
|
||||
path: '/iot-card/CardManagement/Detail',
|
||||
query: {
|
||||
id: data.id,
|
||||
},
|
||||
path: `/iot-card/CardManagement/detail/${data.id}`,
|
||||
});
|
||||
},
|
||||
},
|
||||
|
@ -795,7 +792,7 @@ const handelRemove = async () => {
|
|||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.page-container {
|
||||
.container {
|
||||
.search {
|
||||
width: calc(100% - 330px);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<!-- 物联卡-仪表盘 -->
|
||||
<template>
|
||||
<div class="page-container">
|
||||
<page-container class="container">
|
||||
<a-card>
|
||||
<a-row :gutter="20" :style="{ marginBottom: '20px' }">
|
||||
<a-col :span="24"><Guide title="数据统计" /></a-col>
|
||||
|
@ -104,7 +104,7 @@
|
|||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
</div>
|
||||
</page-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
@ -229,7 +229,7 @@ const dTime = [
|
|||
getTopRang(dTime[0], dTime[1]);
|
||||
</script>
|
||||
<style scoped lang="less">
|
||||
.page-container {
|
||||
.container {
|
||||
.data-statistics-item {
|
||||
height: 140px;
|
||||
background: #fcfcfc;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<!-- 物联卡-首页 -->
|
||||
<template>
|
||||
<div class="page-container">
|
||||
<page-container>
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="14">
|
||||
<div class="home-guide">
|
||||
|
@ -79,7 +79,7 @@
|
|||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</page-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
<!-- 充值管理 -->
|
||||
<template>
|
||||
<div class="page-container">
|
||||
<page-container>
|
||||
<Search
|
||||
:columns="columns"
|
||||
target="iot-card-management-search"
|
||||
target="recharge-search"
|
||||
@search="handleSearch"
|
||||
/>
|
||||
<JTable
|
||||
|
@ -71,7 +71,7 @@
|
|||
<!-- 充值 -->
|
||||
<Save v-if="visible" @change="saveChange" />
|
||||
<Detail v-if="detailVisible" :data="current" @close="close" />
|
||||
</div>
|
||||
</page-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
|
|
@ -1,42 +1,44 @@
|
|||
<template>
|
||||
<a-spin :spinning="loading">
|
||||
<a-card :bordered="false">
|
||||
<div v-if="type && modeType === 'add'">
|
||||
<Provider
|
||||
@onClick="goProviders"
|
||||
:dataSource="dataSource"
|
||||
></Provider>
|
||||
</div>
|
||||
<div v-else>
|
||||
<div v-if="!id"><a @click="goBack">返回</a></div>
|
||||
<AccessNetwork
|
||||
v-if="showType === 'network'"
|
||||
:provider="provider"
|
||||
:data="data"
|
||||
/>
|
||||
<Media
|
||||
v-if="showType === 'media'"
|
||||
:provider="provider"
|
||||
:data="data"
|
||||
/>
|
||||
<Channel
|
||||
v-if="showType === 'channel'"
|
||||
:provider="provider"
|
||||
:data="data"
|
||||
/>
|
||||
<Edge
|
||||
v-if="showType === 'edge'"
|
||||
:provider="provider"
|
||||
:data="data"
|
||||
/>
|
||||
<Cloud
|
||||
v-if="showType === 'cloud'"
|
||||
:provider="provider"
|
||||
:data="data"
|
||||
/>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-spin>
|
||||
<page-container>
|
||||
<a-spin :spinning="loading">
|
||||
<a-card :bordered="false">
|
||||
<div v-if="type && id === ':id'">
|
||||
<Provider
|
||||
@onClick="goProviders"
|
||||
:dataSource="dataSource"
|
||||
></Provider>
|
||||
</div>
|
||||
<div v-else>
|
||||
<div v-if="!id"><a @click="goBack">返回</a></div>
|
||||
<AccessNetwork
|
||||
v-if="showType === 'network'"
|
||||
:provider="provider"
|
||||
:data="data"
|
||||
/>
|
||||
<Media
|
||||
v-if="showType === 'media'"
|
||||
:provider="provider"
|
||||
:data="data"
|
||||
/>
|
||||
<Channel
|
||||
v-if="showType === 'channel'"
|
||||
:provider="provider"
|
||||
:data="data"
|
||||
/>
|
||||
<Edge
|
||||
v-if="showType === 'edge'"
|
||||
:provider="provider"
|
||||
:data="data"
|
||||
/>
|
||||
<Cloud
|
||||
v-if="showType === 'cloud'"
|
||||
:provider="provider"
|
||||
:data="data"
|
||||
/>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-spin>
|
||||
</page-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup name="AccessConfigDetail">
|
||||
|
@ -51,7 +53,7 @@ import Cloud from '../components/Cloud/index.vue';
|
|||
|
||||
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 dataSource = ref([]);
|
||||
|
@ -138,7 +140,7 @@ const queryProviders = async () => {
|
|||
};
|
||||
|
||||
const getProvidersData = async () => {
|
||||
if (id && modeType !== 'add') {
|
||||
if (id !== ':id') {
|
||||
getProviders().then((response) => {
|
||||
if (response.status === 200) {
|
||||
const list = getTypeList(response.result);
|
||||
|
|
|
@ -40,7 +40,7 @@
|
|||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button
|
||||
v-if="modeType !== 'view'"
|
||||
v-if="view === 'false'"
|
||||
type="primary"
|
||||
html-type="submit"
|
||||
>保存</a-button
|
||||
|
@ -96,7 +96,7 @@ interface FormState {
|
|||
description: string;
|
||||
}
|
||||
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 props = defineProps({
|
||||
|
@ -126,9 +126,7 @@ const onFinish = async (values: any) => {
|
|||
channel: providerId === 'modbus-tcp' ? 'modbus' : 'opc-ua',
|
||||
};
|
||||
const resp =
|
||||
!!id && modeType !== 'add'
|
||||
? await update({ ...params, id })
|
||||
: await save(params);
|
||||
id === ':id' ? await save(params) : await update({ ...params, id });
|
||||
if (resp.status === 200) {
|
||||
message.success('操作成功!');
|
||||
// if (params.get('save')) {
|
||||
|
@ -145,7 +143,7 @@ const onFinish = async (values: any) => {
|
|||
};
|
||||
|
||||
onMounted(() => {
|
||||
if (modeType !== 'add') {
|
||||
if (id === ':id') {
|
||||
formState.value = {
|
||||
name: props.data.name,
|
||||
description: props.data?.description || '',
|
||||
|
|
|
@ -283,7 +283,7 @@
|
|||
下一步
|
||||
</a-button>
|
||||
<a-button
|
||||
v-if="current === 2 && modeType !== 'view'"
|
||||
v-if="current === 2 && view === 'false'"
|
||||
type="primary"
|
||||
style="margin-right: 8px"
|
||||
@click="saveData"
|
||||
|
@ -383,7 +383,7 @@ interface Form {
|
|||
description: string;
|
||||
}
|
||||
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 props = defineProps({
|
||||
|
@ -450,13 +450,13 @@ const saveData = async () => {
|
|||
transport: 'HTTP_SERVER',
|
||||
};
|
||||
const resp =
|
||||
!!id && modeType !== 'add'
|
||||
? await update({
|
||||
id === ':id'
|
||||
? await save(params)
|
||||
: await update({
|
||||
...props.data,
|
||||
...params,
|
||||
})
|
||||
: await save(params);
|
||||
|
||||
id,
|
||||
});
|
||||
if (resp.status === 200) {
|
||||
message.success('操作成功!');
|
||||
// 回到列表页面
|
||||
|
@ -489,7 +489,7 @@ const queryProcotolList = async (id: string, params = {}) => {
|
|||
|
||||
const addProcotol = () => {
|
||||
// const url = this.$store.state.permission.routes['Link/Protocol']
|
||||
const url = '/demo';
|
||||
const url = '/iot/link/protocol';
|
||||
const tab = window.open(
|
||||
`${window.location.origin + window.location.pathname}#${url}?save=true`,
|
||||
);
|
||||
|
@ -518,7 +518,7 @@ const prev = () => {
|
|||
current.value = current.value - 1;
|
||||
};
|
||||
onMounted(() => {
|
||||
if (modeType !== 'add') {
|
||||
if (id !== ':id') {
|
||||
formState.value = props.data.configuration;
|
||||
procotolCurrent.value = props.data.protocol;
|
||||
formData.value = {
|
||||
|
|
|
@ -377,7 +377,7 @@
|
|||
</a-button>
|
||||
<a-button
|
||||
style="margin-right: 8px"
|
||||
v-if="current === 2 && modeType !== 'view'"
|
||||
v-if="current === 2 && view === 'false'"
|
||||
type="primary"
|
||||
@click="saveData"
|
||||
>
|
||||
|
@ -479,7 +479,7 @@ interface Form {
|
|||
description: string;
|
||||
}
|
||||
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 props = defineProps({
|
||||
|
@ -547,12 +547,13 @@ const saveData = async () => {
|
|||
transport: 'HTTP_SERVER',
|
||||
};
|
||||
const resp =
|
||||
!!id && modeType !== 'add'
|
||||
? await update({
|
||||
id === ':id'
|
||||
? await save(params)
|
||||
: await update({
|
||||
...props.data,
|
||||
...params,
|
||||
})
|
||||
: await save(params);
|
||||
id,
|
||||
});
|
||||
|
||||
if (resp.status === 200) {
|
||||
message.success('操作成功!');
|
||||
|
@ -585,7 +586,7 @@ const queryProcotolList = async (id: string, params = {}) => {
|
|||
|
||||
const addProcotol = () => {
|
||||
// const url = this.$store.state.permission.routes['Link/Protocol']
|
||||
const url = '/demo';
|
||||
const url = '/iot/link/protocol';
|
||||
const tab = window.open(
|
||||
`${window.location.origin + window.location.pathname}#${url}?save=true`,
|
||||
);
|
||||
|
@ -615,7 +616,7 @@ const prev = () => {
|
|||
};
|
||||
|
||||
onMounted(() => {
|
||||
if (modeType !== 'add') {
|
||||
if (id !== ':id') {
|
||||
formState.value = props.data.configuration;
|
||||
procotolCurrent.value = props.data.protocol;
|
||||
formData.value = {
|
||||
|
|
|
@ -140,7 +140,7 @@
|
|||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button
|
||||
v-if="current !== 1 && modeType !== 'view'"
|
||||
v-if="current !== 1 && view === 'false'"
|
||||
type="primary"
|
||||
html-type="submit"
|
||||
>保存</a-button
|
||||
|
@ -179,7 +179,7 @@
|
|||
下一步
|
||||
</a-button>
|
||||
<a-button
|
||||
v-if="current === 1 && modeType !== 'view'"
|
||||
v-if="current === 1 && view === 'false'"
|
||||
type="primary"
|
||||
style="margin-right: 8px"
|
||||
@click="saveData"
|
||||
|
@ -297,7 +297,7 @@ interface FormState {
|
|||
description: string;
|
||||
}
|
||||
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 props = defineProps({
|
||||
|
@ -338,9 +338,7 @@ const onFinish = async (values: any) => {
|
|||
};
|
||||
if (networkCurrent.value) params.channelId = networkCurrent.value;
|
||||
const resp =
|
||||
!!id && modeType !== 'add'
|
||||
? await update({ ...params, id })
|
||||
: await save(params);
|
||||
id === ':id' ? await save(params) : await update({ ...params, id });
|
||||
if (resp.status === 200) {
|
||||
message.success('操作成功!');
|
||||
// if (params.get('save')) {
|
||||
|
@ -392,7 +390,7 @@ const saveData = async () => {
|
|||
|
||||
const addNetwork = () => {
|
||||
// const url = this.$store.state.permission.routes['Link/Type/Detail']
|
||||
const url = '/demo';
|
||||
const url = '/iot/link/type/detail/:id';
|
||||
const tab = window.open(
|
||||
`${window.location.origin + window.location.pathname}#${url}?type=${
|
||||
NetworkTypeMapping.get(props.provider?.id) || ''
|
||||
|
@ -421,7 +419,7 @@ onMounted(() => {
|
|||
if (props.provider.id === 'official-edge-gateway') {
|
||||
queryNetworkList(props.provider.id, '');
|
||||
}
|
||||
if (modeType !== 'add') {
|
||||
if (id !== ':id') {
|
||||
formState.value = {
|
||||
name: props.data.name,
|
||||
description: props.data?.description || '',
|
||||
|
|
|
@ -503,7 +503,7 @@
|
|||
下一步
|
||||
</a-button>
|
||||
<a-button
|
||||
v-if="current === 1 && modeType !== 'view'"
|
||||
v-if="current === 1 && view === 'false'"
|
||||
type="primary"
|
||||
style="margin-right: 8px"
|
||||
@click="saveData"
|
||||
|
@ -559,7 +559,7 @@ const props = defineProps({
|
|||
});
|
||||
|
||||
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 activeKey: any = ref([]);
|
||||
|
@ -663,9 +663,7 @@ const saveData = () => {
|
|||
};
|
||||
|
||||
const resp =
|
||||
!!id && modeType !== 'add'
|
||||
? await update({ ...params, id })
|
||||
: await save(params);
|
||||
id === ':id' ? await save(params) : await update({ ...params, id });
|
||||
if (resp.status === 200) {
|
||||
message.success('操作成功!');
|
||||
// if (params.get('save')) {
|
||||
|
@ -741,7 +739,7 @@ onMounted(() => {
|
|||
}
|
||||
});
|
||||
|
||||
if (modeType !== 'add') {
|
||||
if (id !== ':id') {
|
||||
formState.value = props.data.configuration;
|
||||
formData.value = {
|
||||
name: props.data.name,
|
||||
|
|
|
@ -40,7 +40,7 @@
|
|||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button
|
||||
v-if="modeType !== 'view'"
|
||||
v-if="view === 'false'"
|
||||
type="primary"
|
||||
html-type="submit"
|
||||
>保存</a-button
|
||||
|
@ -91,7 +91,7 @@ interface FormState {
|
|||
description: string;
|
||||
}
|
||||
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 props = defineProps({
|
||||
|
@ -119,9 +119,7 @@ const onFinish = async (values: any) => {
|
|||
channel: 'fixed-media',
|
||||
};
|
||||
const resp =
|
||||
!!id && modeType !== 'add'
|
||||
? await update({ ...params, id })
|
||||
: await save(params);
|
||||
id === ':id' ? await save(params) : await update({ ...params, id });
|
||||
if (resp.status === 200) {
|
||||
message.success('操作成功!');
|
||||
// if (params.get('save')) {
|
||||
|
@ -138,7 +136,7 @@ const onFinish = async (values: any) => {
|
|||
};
|
||||
|
||||
onMounted(() => {
|
||||
if (modeType !== 'add') {
|
||||
if (id !== ':id') {
|
||||
formState.value = {
|
||||
name: props.data.name,
|
||||
description: props.data?.description || '',
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div style="margin-top: 10px">
|
||||
<div>
|
||||
<a-steps :current="stepCurrent">
|
||||
<a-step v-for="item in steps" :key="item" :title="item" />
|
||||
</a-steps>
|
||||
|
@ -304,7 +304,7 @@
|
|||
下一步
|
||||
</a-button>
|
||||
<a-button
|
||||
v-if="current === 2 && modeType !== 'view'"
|
||||
v-if="current === 2 && view === 'false'"
|
||||
type="primary"
|
||||
style="margin-right: 8px"
|
||||
@click="saveData"
|
||||
|
@ -767,7 +767,7 @@ const props = defineProps({
|
|||
const clientHeight = document.body.clientHeight;
|
||||
const type = props.provider.channel;
|
||||
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 formRef = ref<FormInstance>();
|
||||
|
@ -839,7 +839,7 @@ const queryProcotolList = async (id: string, params = {}) => {
|
|||
|
||||
const addNetwork = () => {
|
||||
// const url = this.$store.state.permission.routes['Link/Type/Detail']
|
||||
const url = '/demo';
|
||||
const url = '/iot/link/type/detail/:id';
|
||||
const tab = window.open(
|
||||
`${window.location.origin + window.location.pathname}#${url}?type=${
|
||||
NetworkTypeMapping.get(props.provider?.id) || ''
|
||||
|
@ -854,7 +854,7 @@ const addNetwork = () => {
|
|||
};
|
||||
const addProcotol = () => {
|
||||
// const url = this.$store.state.permission.routes['Link/Protocol']
|
||||
const url = '/demo';
|
||||
const url = '/iot/link/protocol';
|
||||
const tab = window.open(
|
||||
`${window.location.origin + window.location.pathname}#${url}?save=true`,
|
||||
);
|
||||
|
@ -903,27 +903,25 @@ const procotolSearch = (value: string) => {
|
|||
const saveData = () => {
|
||||
validate()
|
||||
.then(async (values) => {
|
||||
let resp = undefined;
|
||||
let params = {
|
||||
const params = {
|
||||
...props.data,
|
||||
...values,
|
||||
protocol: procotolCurrent.value,
|
||||
channel: 'network', // 网络组件
|
||||
channelId: networkCurrent.value,
|
||||
};
|
||||
if (!!id && modeType !== 'add') {
|
||||
resp = await update(params);
|
||||
} else {
|
||||
params = {
|
||||
...params,
|
||||
provider: props.provider.id,
|
||||
transport:
|
||||
props.provider?.id === 'child-device'
|
||||
? 'Gateway'
|
||||
: ProtocolMapping.get(props.provider.id),
|
||||
};
|
||||
resp = await save(params);
|
||||
}
|
||||
const resp =
|
||||
id === ':id'
|
||||
? await save(params)
|
||||
: await update({
|
||||
...params,
|
||||
id,
|
||||
provider: props.provider.id,
|
||||
transport:
|
||||
props.provider?.id === 'child-device'
|
||||
? 'Gateway'
|
||||
: ProtocolMapping.get(props.provider.id),
|
||||
});
|
||||
if (resp.status === 200) {
|
||||
message.success('操作成功!');
|
||||
// 回到列表页面
|
||||
|
@ -1104,7 +1102,7 @@ onMounted(() => {
|
|||
});
|
||||
|
||||
onMounted(() => {
|
||||
if (modeType !== 'add') {
|
||||
if (id !== ':id') {
|
||||
procotolCurrent.value = props.data.protocol;
|
||||
formData.value = {
|
||||
name: props.data.name,
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
<template>
|
||||
<div class="page-container">
|
||||
<a-card style="margin-bottom: 20px">
|
||||
<page-container>
|
||||
<div>
|
||||
<Search :columns="columns" target="search" @search="handleSearch" />
|
||||
</a-card>
|
||||
<a-card>
|
||||
|
||||
<JTable
|
||||
ref="tableRef"
|
||||
model="CARD"
|
||||
|
@ -187,8 +186,8 @@
|
|||
/>
|
||||
</template>
|
||||
</JTable>
|
||||
</a-card>
|
||||
</div>
|
||||
</div>
|
||||
</page-container>
|
||||
</template>
|
||||
<script lang="ts" setup name="AccessConfigPage">
|
||||
import type { ActionsType } from '@/components/Table/index.vue';
|
||||
|
@ -345,13 +344,25 @@ const getProvidersList = async () => {
|
|||
getProvidersList();
|
||||
|
||||
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) => {
|
||||
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) => {
|
||||
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>
|
||||
<style lang="less" scoped>
|
||||
.page-container {
|
||||
background: #f0f2f5;
|
||||
padding: 24px;
|
||||
}
|
||||
.tableCardDisabled {
|
||||
width: 100%;
|
||||
background: url('/images/access-config-diaabled.png') no-repeat;
|
||||
|
|
|
@ -1,96 +1,104 @@
|
|||
<template>
|
||||
<a-card>
|
||||
<a-row :gutter="[24, 24]" style="padding: 24px">
|
||||
<a-col :span="12">
|
||||
<a-form
|
||||
class="form"
|
||||
layout="vertical"
|
||||
:model="formData"
|
||||
name="basic"
|
||||
:label-col="{ span: 8 }"
|
||||
: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']"
|
||||
<page-container>
|
||||
<a-card>
|
||||
<a-row :gutter="[24, 24]" style="padding: 24px">
|
||||
<a-col :span="12">
|
||||
<a-form
|
||||
class="form"
|
||||
layout="vertical"
|
||||
:model="formData"
|
||||
name="basic"
|
||||
:label-col="{ span: 8 }"
|
||||
:wrapper-col="{ span: 16 }"
|
||||
autocomplete="off"
|
||||
>
|
||||
<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="modeType !== 'view'"
|
||||
class="form-submit"
|
||||
html-type="submit"
|
||||
type="primary"
|
||||
@click.prevent="onSubmit"
|
||||
:loading="loading"
|
||||
>保存</a-button
|
||||
<a-form-item
|
||||
label="证书标准"
|
||||
v-bind="validateInfos.type"
|
||||
>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<div class="doc">
|
||||
<h1>1. 概述</h1>
|
||||
<div>
|
||||
证书由受信任的数字证书颁发机构CA,在验证服务器身份后颁发,具有服务器身份验证和数据传输加密功能,保障设备与平台间的数据传输安全。配置后可被网络组件引用。
|
||||
<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
|
||||
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>
|
||||
<h1>2. 配置说明</h1>
|
||||
<h2>1、证书文件</h2>
|
||||
<div>
|
||||
您可以使用文本编辑工具打开PEM或者CRT格式的证书文件,复制其中的内容并粘贴到该文本框,或者单击该文本框下的上传,并选择存储在本地计算机的证书文件,将文件内容上传到文本框。
|
||||
</div>
|
||||
<h2>2、证书私钥</h2>
|
||||
<div>
|
||||
填写证书私钥内容的PEM编码。
|
||||
您可以使用文本编辑工具打开KEY格式的证书私钥文件,复制其中的内容并粘贴到该文本框,或者单击该文本框下的上传并选择存储在本地计算机的证书私钥文件,将文件内容上传到文本框。
|
||||
</div>
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
</page-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup name="CertificateDetail">
|
||||
|
@ -103,7 +111,7 @@ import { FormDataType, TypeObjType } from '../type';
|
|||
|
||||
const router = useRouter();
|
||||
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 useForm = Form.useForm;
|
||||
|
@ -145,10 +153,12 @@ const onSubmit = () => {
|
|||
const params = toRaw(formData.value);
|
||||
loading.value = true;
|
||||
const response =
|
||||
modeType === 'edit' ? await update(params) : await save(params);
|
||||
id === ':id'
|
||||
? await save(params)
|
||||
: await update({ ...params, id });
|
||||
if (response.status === 200) {
|
||||
message.success('操作成功');
|
||||
router.push('/link/certificate');
|
||||
router.push('/iot/link/certificate');
|
||||
}
|
||||
loading.value = false;
|
||||
})
|
||||
|
@ -168,7 +178,7 @@ const handleChange = (info: UploadChangeParam) => {
|
|||
};
|
||||
|
||||
const detail = async (id: string) => {
|
||||
if (modeType !== 'add') {
|
||||
if (id !== ':id') {
|
||||
loading.value = true;
|
||||
const res = await queryDetail(id);
|
||||
if (res.success) {
|
||||
|
|
|
@ -1,13 +1,7 @@
|
|||
<template>
|
||||
<div class="page-container">
|
||||
<a-card style="margin-bottom: 20px">
|
||||
<Search
|
||||
:columns="columns"
|
||||
target="search"
|
||||
@search="handleSearch"
|
||||
/>
|
||||
</a-card>
|
||||
<a-card>
|
||||
<page-container>
|
||||
<div>
|
||||
<Search :columns="columns" target="search" @search="handleSearch" />
|
||||
<JTable
|
||||
ref="tableRef"
|
||||
model="TABLE"
|
||||
|
@ -61,8 +55,8 @@
|
|||
</a-space>
|
||||
</template>
|
||||
</JTable>
|
||||
</a-card>
|
||||
</div>
|
||||
</div>
|
||||
</page-container>
|
||||
</template>
|
||||
<script lang="ts" setup name="CertificatePage">
|
||||
import type { ActionsType } from '@/components/Table/index.vue';
|
||||
|
@ -158,15 +152,24 @@ const getActions = (data: Partial<Record<string, any>>): ActionsType[] => {
|
|||
};
|
||||
|
||||
const handlAdd = () => {
|
||||
router.push('/link/certificate/detail/add/new');
|
||||
router.push({
|
||||
path: `/iot/link/certificate/detail/:id`,
|
||||
query: { view: false },
|
||||
});
|
||||
};
|
||||
|
||||
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) => {
|
||||
router.push(`/link/certificate/detail/edit/${id}`);
|
||||
router.push({
|
||||
path: `/iot/link/certificate/detail/${id}`,
|
||||
query: { view: false },
|
||||
});
|
||||
};
|
||||
|
||||
const handlDelete = async (id: string) => {
|
||||
|
@ -182,15 +185,8 @@ const handlDelete = async (id: string) => {
|
|||
* @param params
|
||||
*/
|
||||
const handleSearch = (e: any) => {
|
||||
console.log(1211, e);
|
||||
|
||||
params.value = e;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.page-container {
|
||||
background: #f0f2f5;
|
||||
padding: 24px;
|
||||
}
|
||||
</style>
|
||||
<style lang="less" scoped></style>
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
<template>
|
||||
<div>访问日志</div>
|
||||
</template>
|
||||
<script lang="ts" setup name="SystemLog">
|
||||
|
||||
</script>
|
|
@ -1,6 +0,0 @@
|
|||
<template>
|
||||
<div>系统日志</div>
|
||||
</template>
|
||||
<script lang="ts" setup name="AccessLog">
|
||||
|
||||
</script>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -0,0 +1,7 @@
|
|||
import type { BaseItem } from '@/utils/typings';
|
||||
|
||||
type ProtocolItem = {
|
||||
state: number;
|
||||
type: string;
|
||||
configuration: Record<string, any>;
|
||||
} & BaseItem;
|
|
@ -0,0 +1,7 @@
|
|||
<template>
|
||||
<div class="page-container"></div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<style lang="less" scoped></style>
|
|
@ -0,0 +1,94 @@
|
|||
<template>
|
||||
<div class="top-card">
|
||||
<div class="top-card-content">
|
||||
<div class="content-left">
|
||||
<div class="content-left-title">
|
||||
<span>{{ title }}</span>
|
||||
<a-tooltip placement="top" v-if="tooltip">
|
||||
<template #title>
|
||||
<span>{{ tooltip }}</span>
|
||||
</template>
|
||||
<AIcon type="QuestionCircleOutlined" />
|
||||
</a-tooltip>
|
||||
</div>
|
||||
<div class="content-left-value">{{ value }}</div>
|
||||
</div>
|
||||
<div class="content-right">
|
||||
<img :src="img" alt="" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="top-card-footer">
|
||||
<template v-for="(item, index) in footer" :key="index">
|
||||
<a-badge :text="item.title" :status="item.status" />
|
||||
<div class="footer-item-value">{{ item.value }}</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { PropType } from 'vue';
|
||||
import type { Footer } from '@/views/media/DashBoard/typings';
|
||||
|
||||
const props = defineProps({
|
||||
title: { type: String, default: '' },
|
||||
tooltip: { type: String, default: '' },
|
||||
img: { type: String, default: '' },
|
||||
footer: { type: Array as PropType<Footer[]>, default: '' },
|
||||
value: { type: Number, default: 0 },
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.top-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
// height: 200px;
|
||||
padding: 24px;
|
||||
background-color: #fff;
|
||||
border: 1px solid #e0e4e8;
|
||||
border-radius: 2px;
|
||||
.top-card-content {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-grow: 1;
|
||||
.content-left {
|
||||
height: 100%;
|
||||
width: 50%;
|
||||
&-title {
|
||||
color: rgba(0, 0, 0, 0.64);
|
||||
}
|
||||
&-value {
|
||||
padding: 12px 0;
|
||||
color: #323130;
|
||||
font-weight: 700;
|
||||
font-size: 36px;
|
||||
}
|
||||
}
|
||||
.content-right {
|
||||
width: 0;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
align-items: flex-end;
|
||||
justify-content: flex-end;
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
.top-card-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
.footer-item-value {
|
||||
color: #323130;
|
||||
font-weight: 700;
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,38 @@
|
|||
.media-dash-board {
|
||||
.top-card-items {
|
||||
margin-bottom: 12px;
|
||||
|
||||
.top-card-item {
|
||||
width: 25%;
|
||||
padding: 6px 24px;
|
||||
border: 1px solid #e3e3e3;
|
||||
|
||||
.top-card-top {
|
||||
display: flex;
|
||||
padding: 12px 0;
|
||||
|
||||
.top-card-top-left {
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
.top-card-top-right {
|
||||
.top-card-total {
|
||||
font-weight: bold;
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.top-card-bottom {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 12px 0;
|
||||
border-top: 1px solid #e3e3e3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.media-dash-board-body {
|
||||
border: 1px solid #f0f0f0;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
<template>
|
||||
<div class="page-container">
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="6">
|
||||
<TopCard
|
||||
title="设备数量"
|
||||
:img="getImage('/media/dashboard-1.png')"
|
||||
:footer="deviceFooter"
|
||||
:value="deviceTotal"
|
||||
/>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<TopCard
|
||||
title="通道数量"
|
||||
:img="getImage('/media/dashboard-2.png')"
|
||||
:footer="channelFooter"
|
||||
:value="channelTotal"
|
||||
/>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<TopCard
|
||||
title="录像数量"
|
||||
:img="getImage('/media/dashboard-3.png')"
|
||||
:footer="aggFooter"
|
||||
:value="aggTotal"
|
||||
/>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<TopCard
|
||||
title="播放中数量"
|
||||
tooltip="当前正在播放的通道数量之和"
|
||||
:img="getImage('/media/dashboard-4.png')"
|
||||
:footer="aggPlayingFooter"
|
||||
:value="aggPlayingTotal"
|
||||
/>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import TopCard from '@/views/media/DashBoard/components/TopCard.vue'
|
||||
import { getImage } from '@/utils/comm';
|
||||
import homeApi from '@/api/media/home';
|
||||
import dashboardApi from '@/api/media/dashboard';
|
||||
import type { Footer } from '@/views/media/DashBoard/typings';
|
||||
|
||||
// 设备
|
||||
const deviceFooter = ref<Footer[]>([]);
|
||||
const deviceTotal = ref(0);
|
||||
const getDeviceData = () => {
|
||||
homeApi.deviceCount().then((res) => {
|
||||
deviceTotal.value = res.result;
|
||||
});
|
||||
};
|
||||
getDeviceData();
|
||||
|
||||
// 通道
|
||||
const channelFooter = ref<Footer[]>([]);
|
||||
const channelTotal = ref(0);
|
||||
const getChannelData = () => {
|
||||
homeApi.channelCount().then((res) => {
|
||||
channelTotal.value = res.result;
|
||||
});
|
||||
};
|
||||
getChannelData();
|
||||
|
||||
// 录像
|
||||
const aggFooter = ref<Footer[]>([]);
|
||||
const aggTotal = ref(0);
|
||||
const getAggData = () => {
|
||||
dashboardApi.agg().then((res) => {
|
||||
aggTotal.value = res.result.total;
|
||||
});
|
||||
};
|
||||
getAggData();
|
||||
|
||||
// 播放中
|
||||
const aggPlayingFooter = ref<Footer[]>([]);
|
||||
const aggPlayingTotal = ref(0);
|
||||
const getAggPlayingData = () => {
|
||||
dashboardApi.aggPlaying().then((res) => {
|
||||
aggTotal.value = res.result.playingTotal;
|
||||
});
|
||||
};
|
||||
getAggPlayingData();
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.page-container {
|
||||
padding: 24px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,16 @@
|
|||
export type Agg = {
|
||||
duration: number
|
||||
total: number
|
||||
}
|
||||
|
||||
export type AggPlaying = {
|
||||
playerTotal: number
|
||||
playingTotal: number
|
||||
}
|
||||
|
||||
export type Footer = {
|
||||
title: string;
|
||||
value: number;
|
||||
status?: "default" | "error" | "success" | "warning" | "processing"
|
||||
}
|
||||
|
|
@ -0,0 +1,118 @@
|
|||
<template>
|
||||
<a-card class="device-count-container">
|
||||
<template #title>
|
||||
<h5 class="title">基础统计</h5>
|
||||
</template>
|
||||
<template #extra>
|
||||
<span style="color: #1d39c4; cursor: pointer" @click="jumpPage"
|
||||
>详情</span
|
||||
>
|
||||
</template>
|
||||
|
||||
<div class="box-list">
|
||||
<div class="box-item">
|
||||
<div class="label">设备数量</div>
|
||||
<div class="value">{{ deviceCount }}</div>
|
||||
<img :src="getImage('/home/top-1.png')" alt="" />
|
||||
</div>
|
||||
<div class="box-item">
|
||||
<div class="label">通道数量</div>
|
||||
<div class="value">{{ channelCount }}</div>
|
||||
<img :src="getImage('/home/top-2.png')" alt="" />
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import homeApi from '@/api/media/home';
|
||||
import { getImage } from '@/utils/comm';
|
||||
|
||||
const channelCount = ref(0);
|
||||
const deviceCount = ref(0);
|
||||
|
||||
onMounted(() => {
|
||||
getData();
|
||||
});
|
||||
|
||||
const getData = () => {
|
||||
homeApi.deviceCount().then((resp) => {
|
||||
deviceCount.value = resp.result;
|
||||
});
|
||||
homeApi.channelCount().then((resp) => {
|
||||
channelCount.value = resp.result;
|
||||
});
|
||||
};
|
||||
|
||||
const router = useRouter();
|
||||
const jumpPage = () => {
|
||||
router.push('/media/dashboard');
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.device-count-container {
|
||||
:deep(.ant-card-body) {
|
||||
padding-top: 0;
|
||||
}
|
||||
.title {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 12px;
|
||||
padding-left: 18px;
|
||||
font-weight: 700;
|
||||
font-size: 18px;
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 0;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background-color: #1d39c4;
|
||||
border: 1px solid #b4c0da;
|
||||
transform: translateY(-50%);
|
||||
content: ' ';
|
||||
}
|
||||
}
|
||||
|
||||
.box-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
grid-gap: 24px;
|
||||
gap: 24px;
|
||||
|
||||
.box-item {
|
||||
position: relative;
|
||||
padding: 16px;
|
||||
background: linear-gradient(
|
||||
135.62deg,
|
||||
#f6f7fd 22.27%,
|
||||
hsla(0, 0%, 100%, 0.86) 91.82%
|
||||
);
|
||||
border-radius: 2px;
|
||||
box-shadow: 0 4px 18px #efefef;
|
||||
|
||||
.label {
|
||||
color: #4f4f4f;
|
||||
}
|
||||
.value {
|
||||
margin: 20px 0;
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
font-weight: 700;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
img {
|
||||
position: absolute;
|
||||
right: 10%;
|
||||
bottom: 0;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,104 @@
|
|||
<template>
|
||||
<div class="page-container">
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="14">
|
||||
<BootCard
|
||||
:cardData="deviceBootConfig"
|
||||
cardTitle="视频中心引导"
|
||||
/>
|
||||
</a-col>
|
||||
<a-col :span="10">
|
||||
<BasicCountCard />
|
||||
</a-col>
|
||||
<a-col :span="24" style="margin: 20px 0">
|
||||
<PlatformPicCard />
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<StepCard
|
||||
cardTitle="设备接入推荐步骤"
|
||||
tooltip="不同的设备因为通信协议的不同,存在接入步骤的差异"
|
||||
:dataList="deviceStepDetails"
|
||||
/>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import BootCard from '@/views/home/components/BootCard.vue';
|
||||
import PlatformPicCard from '@/views/home/components/PlatformPicCard.vue';
|
||||
import StepCard from '@/views/home/components/StepCard.vue';
|
||||
import BasicCountCard from '@/views/media/Home/components/BasicCountCard.vue';
|
||||
|
||||
import { usePermissionStore } from '@/store/permission';
|
||||
import type { bootConfig, recommendList } from '@/views/home/index';
|
||||
|
||||
// 权限控制
|
||||
const hasPermission = usePermissionStore().hasPermission;
|
||||
|
||||
const deviceBootConfig: bootConfig[] = [
|
||||
{
|
||||
english: 'STEP1',
|
||||
label: '添加视频设备',
|
||||
link: '/media/device/Save',
|
||||
auth: hasPermission('/media/device/Save'),
|
||||
params: {
|
||||
save: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
english: 'STEP2',
|
||||
label: '分屏展示',
|
||||
link: '/media/SplitScreen',
|
||||
auth: hasPermission('/media/SplitScreen'),
|
||||
params: {
|
||||
save: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
english: 'STEP3',
|
||||
label: '国标级联',
|
||||
link: '/media/Cascade',
|
||||
auth: hasPermission('/media/Cascade'),
|
||||
params: {
|
||||
save: true,
|
||||
},
|
||||
},
|
||||
];
|
||||
const deviceStepDetails: recommendList[] = [
|
||||
{
|
||||
title: '添加视频设备',
|
||||
details: '根据视频设备的传输协议,在已创建的产品下添加对应的设备。',
|
||||
iconUrl: '/images/home/bottom-6.png',
|
||||
linkUrl: '/media/device/Save',
|
||||
auth: hasPermission('/media/device/Save'),
|
||||
params: {
|
||||
save: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '查看通道',
|
||||
details: '查看设备下的通道数据,可以进行直播、录制等操作。',
|
||||
iconUrl: '/images/home/bottom-7.png',
|
||||
linkUrl: '/media/device/Channel',
|
||||
auth: hasPermission('/media/device/Save'),
|
||||
dialogTag: 'accessMethod',
|
||||
},
|
||||
{
|
||||
title: '分屏展示',
|
||||
details: '对多个通道的视频流数据进行分屏展示。',
|
||||
iconUrl: '/images/home/bottom-8.png',
|
||||
linkUrl: '/media/SplitScreen',
|
||||
auth: hasPermission('/media/SplitScreen'),
|
||||
params: {
|
||||
save: true,
|
||||
},
|
||||
},
|
||||
];
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.page-container {
|
||||
padding: 24px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,24 @@
|
|||
type BaseItem = {
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
type State = {
|
||||
value: string;
|
||||
text: string;
|
||||
};
|
||||
|
||||
export type DeviceItem = {
|
||||
photoUrl?: string;
|
||||
channelNumber: number;
|
||||
createTime: number;
|
||||
firmware: string;
|
||||
gatewayId: string;
|
||||
host: string;
|
||||
manufacturer: string;
|
||||
model: string;
|
||||
port: number;
|
||||
provider: string;
|
||||
state: State;
|
||||
streamMode: string;
|
||||
transport: string;
|
||||
} & BaseItem;
|
|
@ -47,6 +47,7 @@
|
|||
<template #img>
|
||||
<slot name="img">
|
||||
<img
|
||||
style="width: 80px; height: 80px"
|
||||
:src="
|
||||
getLogo(slotProps.type, slotProps.provider)
|
||||
"
|
||||
|
|
|
@ -181,11 +181,18 @@
|
|||
</a-form-item>
|
||||
</a-form-item>
|
||||
<a-form-item label="权限">
|
||||
<PermissChoose :first-width="3" max-height="350px" v-model:value="form.data.permissions" />
|
||||
<PermissChoose
|
||||
:first-width="3"
|
||||
max-height="350px"
|
||||
v-model:value="form.data.permissions"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
<a-button type="primary" @click="clickSave" v-loading="saveLoading"
|
||||
<a-button
|
||||
type="primary"
|
||||
@click="form.clickSave"
|
||||
v-loading="form.saveLoading"
|
||||
>保存</a-button
|
||||
>
|
||||
</a-card>
|
||||
|
@ -215,14 +222,17 @@ import {
|
|||
addMenuInfo_api,
|
||||
} from '@/api/system/menu';
|
||||
|
||||
// 路由
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const routeParams = {
|
||||
id: route.params.id === ':id' ? undefined : (route.params.id as string),
|
||||
...route.query,
|
||||
url: route.query.basePath,
|
||||
parentId: route.query.pid,
|
||||
};
|
||||
|
||||
// 表单
|
||||
const basicFormRef = ref<FormInstance>();
|
||||
const permissFormRef = ref<FormInstance>();
|
||||
const form = reactive({
|
||||
|
@ -238,15 +248,18 @@ const form = reactive({
|
|||
indirectMenus: [],
|
||||
...routeParams,
|
||||
} as formType,
|
||||
|
||||
treeData: [], // 关联菜单
|
||||
assetsType: [] as assetType[], // 资产类型
|
||||
saveLoading: false,
|
||||
|
||||
init: () => {
|
||||
// 获取菜单详情
|
||||
routeParams.id &&
|
||||
getMenuInfo_api(routeParams.id).then((resp) => {
|
||||
form.data = resp.result as formType
|
||||
getMenuInfo_api(routeParams.id).then((resp: any) => {
|
||||
form.data = {
|
||||
...(resp.result as formType),
|
||||
accessSupport: resp.result.accessSupport.value,
|
||||
};
|
||||
});
|
||||
// 获取关联菜单
|
||||
getMenuTree_api({ paging: false }).then((resp: any) => {
|
||||
|
@ -260,9 +273,52 @@ const form = reactive({
|
|||
}));
|
||||
});
|
||||
},
|
||||
clickSave: () => {
|
||||
if (!basicFormRef || !permissFormRef) return;
|
||||
Promise.all([
|
||||
basicFormRef.value?.validate(),
|
||||
permissFormRef.value?.validate(),
|
||||
])
|
||||
.then(() => {
|
||||
const api = routeParams.id ? saveMenuInfo_api : addMenuInfo_api;
|
||||
form.saveLoading = true;
|
||||
const accessSupportValue = form.data.accessSupport;
|
||||
const params = {
|
||||
...form.data,
|
||||
accessSupport: {
|
||||
value: accessSupportValue,
|
||||
label:
|
||||
accessSupportValue === 'unsupported'
|
||||
? '不支持'
|
||||
: accessSupportValue === 'support'
|
||||
? '支持'
|
||||
: '间接控制',
|
||||
},
|
||||
};
|
||||
api(params)
|
||||
.then((resp: any) => {
|
||||
if (resp.status === 200) {
|
||||
message.success('操作成功!');
|
||||
// 新增后刷新页面,编辑则不需要
|
||||
if (!routeParams.id) {
|
||||
router.push(
|
||||
`/system/Menu/detail/${resp.result.id}`,
|
||||
);
|
||||
routeParams.id = resp.result.id;
|
||||
form.init();
|
||||
}
|
||||
} else {
|
||||
message.error('操作失败!');
|
||||
}
|
||||
})
|
||||
.finally(() => (form.saveLoading = false));
|
||||
})
|
||||
.catch((err) => {});
|
||||
},
|
||||
});
|
||||
form.init();
|
||||
|
||||
// 弹窗
|
||||
const ChooseIconRef = ref<any>(null);
|
||||
const dialog = {
|
||||
openDialog: () => {
|
||||
|
@ -272,36 +328,6 @@ const dialog = {
|
|||
form.data.icon = typeStr || form.data.icon;
|
||||
},
|
||||
};
|
||||
const saveLoading = ref<boolean>(false);
|
||||
const clickSave = () => {
|
||||
if (!basicFormRef || !permissFormRef) return;
|
||||
Promise.all([
|
||||
basicFormRef.value?.validate(),
|
||||
permissFormRef.value?.validate(),
|
||||
])
|
||||
.then(() => {
|
||||
const api = routeParams.id ? saveMenuInfo_api : addMenuInfo_api;
|
||||
saveLoading.value = true;
|
||||
api(form.data)
|
||||
.then((resp: any) => {
|
||||
if (resp.status === 200) {
|
||||
message.success('操作成功!');
|
||||
// 新增后刷新页面,编辑则不需要
|
||||
if (!routeParams.id) {
|
||||
router.push(
|
||||
`/system/Menu/detail/${resp.result.id}`,
|
||||
);
|
||||
routeParams.id = resp.result.id;
|
||||
form.init();
|
||||
}
|
||||
} else {
|
||||
message.error('操作失败!');
|
||||
}
|
||||
})
|
||||
.finally(() => (saveLoading.value = false));
|
||||
})
|
||||
.catch((err) => {});
|
||||
};
|
||||
|
||||
type formType = {
|
||||
name: string;
|
||||
|
@ -314,6 +340,7 @@ type formType = {
|
|||
accessSupport: string;
|
||||
assetType: string | undefined;
|
||||
indirectMenus: any[];
|
||||
parentId?: string;
|
||||
};
|
||||
|
||||
type assetType = {
|
||||
|
|
|
@ -4,13 +4,14 @@
|
|||
ref="tableRef"
|
||||
:columns="table.columns"
|
||||
model="TABLE"
|
||||
:dataSource="table.data"
|
||||
:dataSource="table.tableData"
|
||||
noPagination
|
||||
>
|
||||
<template #headerTitle>
|
||||
<a-button
|
||||
type="primary"
|
||||
style="margin-right: 10px"
|
||||
@click="() => dialog.openDialog()"
|
||||
@click="() => dialog.openDialog('新增')"
|
||||
><plus-outlined />新增</a-button
|
||||
>
|
||||
</template>
|
||||
|
@ -21,7 +22,7 @@
|
|||
<a-button
|
||||
style="padding: 0"
|
||||
type="link"
|
||||
@click="() => dialog.openDialog(slotProps)"
|
||||
@click="() => dialog.openDialog('编辑', slotProps)"
|
||||
>
|
||||
<edit-outlined />
|
||||
</a-button>
|
||||
|
@ -31,9 +32,9 @@
|
|||
<a-button
|
||||
style="padding: 0"
|
||||
type="link"
|
||||
@click="() => dialog.openDialog(slotProps)"
|
||||
@click="() => dialog.openDialog('查看', slotProps)"
|
||||
>
|
||||
<edit-outlined />
|
||||
<search-outlined />
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
|
||||
|
@ -41,7 +42,7 @@
|
|||
title="确认删除"
|
||||
ok-text="确定"
|
||||
cancel-text="取消"
|
||||
:disabled="slotProps.status"
|
||||
@confirm="table.clickDel(slotProps)"
|
||||
>
|
||||
<a-tooltip>
|
||||
<template #title>删除</template>
|
||||
|
@ -55,7 +56,11 @@
|
|||
</JTable>
|
||||
|
||||
<div class="dialog">
|
||||
<ButtonAddDialog ref="dialogRef" @confirm="dialog.confirm" />
|
||||
<ButtonAddDialog
|
||||
ref="dialogRef"
|
||||
@confirm="dialog.confirm"
|
||||
:menu-info="menuInfo"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -63,12 +68,14 @@
|
|||
<script setup lang="ts">
|
||||
import {
|
||||
EditOutlined,
|
||||
SearchOutlined,
|
||||
DeleteOutlined,
|
||||
PlusOutlined,
|
||||
} from '@ant-design/icons-vue';
|
||||
import ButtonAddDialog from '../components/ButtonAddDialog.vue';
|
||||
|
||||
import { getMenuInfo_api } from '@/api/system/menu';
|
||||
import { getMenuInfo_api, saveMenuInfo_api } from '@/api/system/menu';
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
// 路由
|
||||
const route = useRoute();
|
||||
|
@ -81,11 +88,15 @@ const routeParams = {
|
|||
const dialogRef = ref<any>(null);
|
||||
const dialog = {
|
||||
// 打开弹窗
|
||||
openDialog: (row?: object) => {
|
||||
dialogRef.value && dialogRef.value.openDialog(row);
|
||||
openDialog: (mode: string, row?: object) => {
|
||||
dialogRef.value && dialogRef.value.openDialog(mode, { ...row });
|
||||
},
|
||||
confirm: () => {
|
||||
table.getList();
|
||||
},
|
||||
confirm: () => {},
|
||||
};
|
||||
// 菜单的基本信息-其中包括了表格的数据
|
||||
const menuInfo = ref<any>({});
|
||||
// 表格相关
|
||||
const table = reactive({
|
||||
columns: [
|
||||
|
@ -114,13 +125,27 @@ const table = reactive({
|
|||
width: 240,
|
||||
},
|
||||
],
|
||||
data: [] as tableDataItem[],
|
||||
tableData: [] as tableDataItem[],
|
||||
getList: () => {
|
||||
routeParams.id &&
|
||||
getMenuInfo_api(routeParams.id).then((resp: any) => {
|
||||
table.data = resp.result.buttons as tableDataItem[];
|
||||
menuInfo.value = resp.result;
|
||||
table.tableData = resp.result.buttons as tableDataItem[];
|
||||
});
|
||||
},
|
||||
clickDel: (row: tableDataItem) => {
|
||||
const buttons = menuInfo.value.buttons.filter(
|
||||
(item: tableDataItem) => item.id !== row.id,
|
||||
);
|
||||
const params = {
|
||||
...menuInfo.value,
|
||||
buttons,
|
||||
};
|
||||
saveMenuInfo_api(params).then(() => {
|
||||
message.success('操作成功');
|
||||
table.getList();
|
||||
});
|
||||
},
|
||||
});
|
||||
table.getList();
|
||||
type tableDataItem = {
|
||||
|
|
|
@ -17,6 +17,11 @@ const activeKey = ref('basic');
|
|||
|
||||
<style lang="less" scoped>
|
||||
.menu-detail-container {
|
||||
:deep(.ant-tabs-nav) {
|
||||
background-color: #fff;
|
||||
padding-left: 24px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.ant-tabs-tabpane {
|
||||
background-color: #f0f2f5;
|
||||
padding: 24px;
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
<template>
|
||||
<div class="setting-container">
|
||||
<a-card>
|
||||
<h5>
|
||||
<info-circle-outlined />
|
||||
<span>基于系统源代码中的菜单数据,配置系统菜单。</span>
|
||||
</h5>
|
||||
</a-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { InfoCircleOutlined } from '@ant-design/icons-vue';
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
|
@ -1,11 +1,15 @@
|
|||
<template>
|
||||
<a-modal
|
||||
v-model:visible="dialog.visible"
|
||||
title="新增"
|
||||
:title="form.mode"
|
||||
width="660px"
|
||||
@ok="dialog.handleOk"
|
||||
:maskClosable="false"
|
||||
cancelText="取消"
|
||||
okText="确定"
|
||||
:confirmLoading="dialog.loading"
|
||||
>
|
||||
<a-form :model="form.data" class="basic-form">
|
||||
<a-form :model="form.data" class="basic-form" ref="formRef">
|
||||
<a-form-item
|
||||
label="编码"
|
||||
name="id"
|
||||
|
@ -14,7 +18,10 @@
|
|||
{ max: 64, message: '最多可输入64个字符' },
|
||||
]"
|
||||
>
|
||||
<a-input v-model:value="form.data.id" />
|
||||
<a-input
|
||||
v-model:value="form.data.id"
|
||||
:disabled="form.mode !== '新增'"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="名称"
|
||||
|
@ -24,20 +31,43 @@
|
|||
{ max: 64, message: '最多可输入64个字符' },
|
||||
]"
|
||||
>
|
||||
<a-input v-model:value="form.data.name" />
|
||||
</a-form-item>
|
||||
<a-form-item label="权限">
|
||||
<PermissChoose
|
||||
:first-width="8"
|
||||
max-height="350px"
|
||||
v-model:value="form.data.permissions"
|
||||
<a-input
|
||||
v-model:value="form.data.name"
|
||||
:disabled="form.mode === '查看'"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="权限"
|
||||
name="permissions"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
message: '请选择权限',
|
||||
validator: form.checkPermission,
|
||||
},
|
||||
]"
|
||||
>
|
||||
<!-- <a-form-item-rest>
|
||||
<PermissChoose
|
||||
:first-width="8"
|
||||
max-height="350px"
|
||||
v-model:value="form.data.permissions"
|
||||
:disabled="form.mode === '查看'"
|
||||
/>
|
||||
</a-form-item-rest> -->
|
||||
<PermissChoose
|
||||
:first-width="8"
|
||||
max-height="350px"
|
||||
v-model:value="form.data.permissions"
|
||||
:disabled="form.mode === '查看'"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="说明" name="describe">
|
||||
<a-textarea
|
||||
v-model:value="form.data.describe"
|
||||
:rows="4"
|
||||
placeholder="请输入说明"
|
||||
:disabled="form.mode === '查看'"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
@ -45,18 +75,56 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { FormInstance, message } from 'ant-design-vue';
|
||||
import { Rule } from 'ant-design-vue/es/form';
|
||||
import PermissChoose from '../components/PermissChoose.vue';
|
||||
import { saveMenuInfo_api } from '@/api/system/menu';
|
||||
|
||||
const props = defineProps<{
|
||||
menuInfo: {
|
||||
buttons: formType[];
|
||||
id: string;
|
||||
};
|
||||
}>();
|
||||
const emits = defineEmits(['confirm']);
|
||||
const dialog = reactive({
|
||||
visible: false,
|
||||
loading: false,
|
||||
handleOk: () => {
|
||||
dialog.changeVisible();
|
||||
props.menuInfo.id && formRef.value &&
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
const buttons = toRaw(props.menuInfo.buttons);
|
||||
const button = buttons.find(
|
||||
(item) => item.id === form.data.id,
|
||||
);
|
||||
if (button) {
|
||||
Object.entries(form.data).forEach(([key, value]) => {
|
||||
button[key] = value;
|
||||
});
|
||||
} else buttons.push({ ...form.data });
|
||||
const params = {
|
||||
...props.menuInfo,
|
||||
buttons,
|
||||
};
|
||||
dialog.loading = true;
|
||||
saveMenuInfo_api(params)
|
||||
.then((resp) => {
|
||||
dialog.changeVisible();
|
||||
message.success('操作成功')
|
||||
emits('confirm');
|
||||
})
|
||||
.finally(() => (dialog.loading = false));
|
||||
})
|
||||
.catch(() => {});
|
||||
},
|
||||
changeVisible: (formValue?: formType, show?: boolean) => {
|
||||
dialog.visible = show === undefined ? !dialog.visible : show;
|
||||
form.data = formValue || { ...initForm };
|
||||
console.log(1111111111, form.data);
|
||||
changeVisible: (mode?: string, formValue?: formType) => {
|
||||
dialog.visible = !dialog.visible;
|
||||
form.data = { ...initForm, ...formValue };
|
||||
form.mode = mode || '';
|
||||
|
||||
formRef.value && formRef.value.clearValidate();
|
||||
},
|
||||
});
|
||||
const initForm = {
|
||||
|
@ -65,8 +133,14 @@ const initForm = {
|
|||
permissions: [],
|
||||
describe: '',
|
||||
} as formType;
|
||||
const formRef = ref<FormInstance>();
|
||||
const form = reactive({
|
||||
data: { ...initForm },
|
||||
mode: '',
|
||||
checkPermission: async (_rule: Rule, value: string[]) => {
|
||||
if (!value || value.length < 1) return Promise.reject('请选择权限');
|
||||
return Promise.resolve();
|
||||
},
|
||||
});
|
||||
|
||||
// 将打开弹窗的操作暴露给父组件
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
allowClear
|
||||
placeholder="请输入权限名称"
|
||||
@input="search.search"
|
||||
:disabled="props.disabled"
|
||||
/>
|
||||
|
||||
<div class="permission-table">
|
||||
|
@ -24,6 +25,7 @@
|
|||
v-model:checked="rowItem.checkAll"
|
||||
:indeterminate="rowItem.indeterminate"
|
||||
@change="() => permission.selectAllOpions(rowItem)"
|
||||
:disabled="props.disabled"
|
||||
>
|
||||
{{ rowItem.name }}
|
||||
</a-checkbox>
|
||||
|
@ -33,6 +35,7 @@
|
|||
v-model:value="rowItem.checkedList"
|
||||
:options="rowItem.options"
|
||||
@change="((checkValue:string[])=>permission.selectOption(rowItem, checkValue))"
|
||||
:disabled="props.disabled"
|
||||
/>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
@ -43,11 +46,13 @@
|
|||
|
||||
<script setup lang="ts">
|
||||
import { exportPermission_api } from '@/api/system/permission';
|
||||
|
||||
import { Form } from 'ant-design-vue';
|
||||
Form.useInjectFormItemContext()
|
||||
const props = defineProps<{
|
||||
value: any[];
|
||||
firstWidth: number;
|
||||
maxHeight: string;
|
||||
disabled?: boolean;
|
||||
}>();
|
||||
const emits = defineEmits(['update:value']);
|
||||
const searchValue = ref<string>('');
|
||||
|
@ -127,18 +132,14 @@ const permission = reactive({
|
|||
emits('update:value', newProp);
|
||||
},
|
||||
makeList: (checkedValue: any[], sourceList: any[]): permissionType[] => {
|
||||
console.log(checkedValue);
|
||||
|
||||
const result = sourceList.map((item) => {
|
||||
const checked = checkedValue.find(
|
||||
const checked = checkedValue?.find(
|
||||
(checkedItem) => checkedItem.permission === item.id,
|
||||
);
|
||||
const options = item.actions.map((actionItem: any) => ({
|
||||
label: actionItem.name,
|
||||
value: actionItem.action,
|
||||
}));
|
||||
console.log(item, checked);
|
||||
|
||||
return {
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
|
@ -146,7 +147,7 @@ const permission = reactive({
|
|||
checkAll:
|
||||
(checked &&
|
||||
item.actions &&
|
||||
checked?.actions.length === item.actions.length) ||
|
||||
checked.actions.length === item.actions.length) ||
|
||||
false,
|
||||
indeterminate:
|
||||
(checked &&
|
||||
|
@ -179,6 +180,9 @@ type paramsType = {
|
|||
|
||||
<style lang="less" scoped>
|
||||
.permission-choose-container {
|
||||
.ant-input-affix-wrapper {
|
||||
border-color: #d9d9d9 !important;
|
||||
}
|
||||
.permission-table {
|
||||
margin-top: 12px;
|
||||
font-size: 14px;
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
style="margin-right: 10px"
|
||||
><plus-outlined />新增</a-button
|
||||
>
|
||||
<a-button>菜单实例</a-button>
|
||||
<a-button @click="router.push('/system/Menu/Setting')">菜单实例</a-button>
|
||||
</template>
|
||||
<template #createTime="slotProps">
|
||||
{{ moment(slotProps.createTime).format('YYYY-MM-DD HH:mm:ss') }}
|
||||
|
@ -30,7 +30,7 @@
|
|||
type="link"
|
||||
@click="table.toDetails(slotProps)"
|
||||
>
|
||||
<search-outlined />
|
||||
<search-outlined />
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
|
@ -38,9 +38,9 @@
|
|||
<a-button
|
||||
style="padding: 0"
|
||||
type="link"
|
||||
@click="table.toDetails(slotProps)"
|
||||
@click="table.addChildren(slotProps)"
|
||||
>
|
||||
<plus-circle-outlined />
|
||||
<plus-circle-outlined />
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
|
||||
|
@ -49,7 +49,6 @@
|
|||
ok-text="确定"
|
||||
cancel-text="取消"
|
||||
@confirm="table.clickDel(slotProps)"
|
||||
:disabled="slotProps.status"
|
||||
>
|
||||
<a-tooltip>
|
||||
<template #title>删除</template>
|
||||
|
@ -65,13 +64,14 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { getMenuTree_api } from '@/api/system/menu';
|
||||
import { getMenuTree_api, delMenuInfo_api } from '@/api/system/menu';
|
||||
import {
|
||||
SearchOutlined,
|
||||
DeleteOutlined,
|
||||
PlusOutlined,
|
||||
PlusCircleOutlined
|
||||
PlusCircleOutlined,
|
||||
} from '@ant-design/icons-vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import moment from 'moment';
|
||||
|
||||
const router = useRouter();
|
||||
|
@ -216,7 +216,7 @@ const table = reactive({
|
|||
const resp: any = await getMenuTree_api(params);
|
||||
const lastItem = resp.result[resp.result.length - 1];
|
||||
table.total = lastItem ? lastItem.sortIndex + 1 : 1;
|
||||
|
||||
|
||||
return {
|
||||
code: resp.message,
|
||||
result: {
|
||||
|
@ -228,22 +228,32 @@ const table = reactive({
|
|||
status: resp.status,
|
||||
};
|
||||
},
|
||||
addChildren: (row: any) => {
|
||||
console.log(row);
|
||||
router.push(
|
||||
`/system/Menu/detail/:id?pid=${row.id}&basePath=${
|
||||
row.url || ''
|
||||
}&sortIndex=${row.children.length + 1}`,
|
||||
);
|
||||
},
|
||||
// 跳转至详情页
|
||||
toDetails: (row: any) => {
|
||||
router.push(
|
||||
`/system/Menu/detail/${row.id || ':id'}?pid=${
|
||||
row.pid || ''
|
||||
}&basePath=${row.basePath || ''}&sortIndex=${table.total}`,
|
||||
}&basePath=${row.url|| ''}&sortIndex=${table.total}`,
|
||||
);
|
||||
},
|
||||
// 删除
|
||||
clickDel: (row: any) => {
|
||||
// delPermission_api(row.id).then((resp: any) => {
|
||||
// if (resp.status === 200) {
|
||||
// tableRef.value?.reload();
|
||||
// message.success('操作成功!');
|
||||
// }
|
||||
// });
|
||||
console.log(row.id);
|
||||
|
||||
delMenuInfo_api(row.id).then((resp: any) => {
|
||||
if (resp.status === 200) {
|
||||
tableRef.value?.reload();
|
||||
message.success('操作成功!');
|
||||
}
|
||||
});
|
||||
},
|
||||
// 刷新列表
|
||||
refresh: () => {
|
||||
|
@ -252,4 +262,8 @@ const table = reactive({
|
|||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
<style lang="less" scoped>
|
||||
.menu-container {
|
||||
padding: 24px;
|
||||
}
|
||||
</style>
|
||||
|
|
Loading…
Reference in New Issue