Merge branch 'dev' of github.com:jetlinks/jetlinks-ui-vue into dev
This commit is contained in:
commit
c36d4b95e0
|
@ -2,6 +2,7 @@ import { LocalStore } from '@/utils/comm'
|
|||
import server from '@/utils/request'
|
||||
import { BASE_API_PATH, TOKEN_KEY } from '@/utils/variable'
|
||||
import { DeviceInstance } from '@/views/device/Instance/typings'
|
||||
import { UnitType } from '@/views/device/Product/typings';
|
||||
|
||||
/**
|
||||
* 删除设备物模型
|
||||
|
@ -242,3 +243,74 @@ export const unbindBatchDevice = (deviceId: string, data: Record<string, any>) =
|
|||
* @returns
|
||||
*/
|
||||
export const bindDevice = (deviceId: string, data: Record<string, any>) => server.post(`/device/gateway/${deviceId}/bind`, data)
|
||||
|
||||
/**
|
||||
* 设备接入网关状态
|
||||
* @param id 设备接入网关id
|
||||
* @returns
|
||||
*/
|
||||
export const queryGatewayState = (id: string) => server.get(`/gateway/device/${id}/detail`)
|
||||
|
||||
/**
|
||||
* 网络组件状态
|
||||
* @param id 网络组件id
|
||||
* @returns
|
||||
*/
|
||||
export const queryNetworkState = (id: string) => server.get(`/network/config/${id}`)
|
||||
|
||||
/**
|
||||
* 产品状态
|
||||
* @param id 产品id
|
||||
* @returns
|
||||
*/
|
||||
export const queryProductState = (id: string) => server.get(`/device/product/${id}`)
|
||||
|
||||
/**
|
||||
* 产品配置
|
||||
* @param id 产品id
|
||||
* @returns
|
||||
*/
|
||||
export const queryProductConfig = (id: string) => server.get(`/device/product/${id}/config-metadata`)
|
||||
|
||||
/**
|
||||
* 设备配置
|
||||
* @param id 设备id
|
||||
* @returns
|
||||
*/
|
||||
export const queryDeviceConfig = (id: string) => server.get(`/device-instance/${id}/config-metadata`)
|
||||
|
||||
/**
|
||||
* 查询协议
|
||||
* @param type
|
||||
* @param transport
|
||||
* @returns
|
||||
*/
|
||||
export const queryProtocolDetail = (type: string, transport: string) => server.get(`/protocol/${type}/transport/${transport}`)
|
||||
|
||||
/**
|
||||
* 网络组件启用
|
||||
* @param id 网络组件ID
|
||||
* @returns
|
||||
*/
|
||||
export const startNetwork = (id: string) => server.post(`/network/config/${id}/_start`)
|
||||
|
||||
/**
|
||||
* 启用网关
|
||||
* @param id 网关id
|
||||
* @returns
|
||||
*/
|
||||
export const startGateway = (id: string) => server.post(`/gateway/device/${id}/_startup`)
|
||||
|
||||
/**
|
||||
* 网关详情
|
||||
* @param id 网关id
|
||||
* @returns
|
||||
*/
|
||||
export const getGatewayDetail = (id: string) => server.get(`/gateway/device/${id}`)
|
||||
|
||||
|
||||
/*
|
||||
* 获取单位列表
|
||||
* @returns 单位列表
|
||||
*/
|
||||
export const getUnit = () => server.get<UnitType[]>(`/protocol/units`)
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
import server from '@/utils/request';
|
||||
|
||||
|
||||
// 获取数据源列表
|
||||
export const getDataSourceList_api = (data: object) => server.post(`/datasource/config/_query/`, data);
|
||||
|
||||
// 获取数据库类型字典
|
||||
export const getDataTypeDict_api = () => server.get(`/datasource/config/types`);
|
||||
|
||||
// 修改数据源状态
|
||||
export const changeStatus_api = (id:string, status:'_disable'|'_enable') => server.put(`/datasource/config/${id}/${status}`);
|
|
@ -0,0 +1,40 @@
|
|||
<template>
|
||||
<a-select v-model:value="_value" mode="tags" :options="options" :size="size" @change="change"></a-select>
|
||||
</template>
|
||||
<script setup lang="ts" name="InputSelect">
|
||||
import { SizeType } from 'ant-design-vue/es/config-provider';
|
||||
import { DefaultOptionType, SelectValue } from 'ant-design-vue/es/select';
|
||||
import { PropType } from 'vue';
|
||||
type valueType = string | number
|
||||
type Emits = {
|
||||
(e: 'update:value', data: valueType | undefined): void;
|
||||
(e: 'change'): void;
|
||||
};
|
||||
const emit = defineEmits<Emits>();
|
||||
const props = defineProps({
|
||||
value: [String, Number],
|
||||
options: {
|
||||
type: Array as PropType<DefaultOptionType[]> | undefined,
|
||||
},
|
||||
size: String as PropType<SizeType>
|
||||
})
|
||||
const _value = ref<valueType[]>();
|
||||
watch(
|
||||
() => props.value,
|
||||
(val) => {
|
||||
_value.value = val ? [val] : undefined
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
const change = (value: SelectValue) => {
|
||||
const _val = (value as valueType[])
|
||||
if (_val.length > 1) {
|
||||
emit('update:value', _val.slice(_val.length - 1)?.[0])
|
||||
} else {
|
||||
emit('update:value', value?.[0])
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped lang="less">
|
||||
</style>
|
|
@ -0,0 +1,67 @@
|
|||
<template>
|
||||
<a-popover :visible="visible" placement="left">
|
||||
<template #title>
|
||||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<div style="width: 150px;">配置元素</div>
|
||||
<close-outlined @click="visible = false" />
|
||||
</div>
|
||||
</template>
|
||||
<template #content>
|
||||
<div style="max-width: 400px;">
|
||||
<a-form layout="vertical" :model="_value">
|
||||
<value-type-form v-model:value="_value" :name="[]" isSub key="sub"></value-type-form>
|
||||
<a-form-item label="说明" name="description" :rules="[
|
||||
{ max: 200, message: '最多可输入200个字符' },
|
||||
]">
|
||||
<a-textarea v-model:value="_value.description" size="small"></a-textarea>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</template>
|
||||
<a-button type="dashed" block @click="visible = true">
|
||||
配置元素<edit-outlined class="item-icon" />
|
||||
</a-button>
|
||||
</a-popover>
|
||||
|
||||
</template>
|
||||
<script setup lang="ts" name="ArrayParam">
|
||||
import ValueTypeForm from '@/views/device/components/Metadata/Base/Edit/ValueTypeForm.vue';
|
||||
import { EditOutlined, CloseOutlined } from '@ant-design/icons-vue';
|
||||
import { PropType } from 'vue';
|
||||
|
||||
type ValueType = Record<any, any>;
|
||||
|
||||
const props = defineProps({
|
||||
value: {
|
||||
type: Object as PropType<ValueType>,
|
||||
default: () => ({ extends: {} })
|
||||
},
|
||||
name: {
|
||||
type: Array as PropType<string[]>,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
interface Emits {
|
||||
(e: 'update:value', data: ValueType): void;
|
||||
}
|
||||
const emit = defineEmits<Emits>()
|
||||
|
||||
const _value = computed({
|
||||
get: () => props.value,
|
||||
set: val => {
|
||||
emit('update:value', val)
|
||||
}
|
||||
})
|
||||
|
||||
const visible = ref(false)
|
||||
|
||||
onMounted(() => {
|
||||
emit('update:value', { extends: {}, ...props.value })
|
||||
})
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.item-icon {
|
||||
color: rgb(136, 136, 136);
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,81 @@
|
|||
<template>
|
||||
<div class="boolean-param">
|
||||
<a-row :gutter="4">
|
||||
<a-col :span="12">
|
||||
<a-form-item label=" " :name="name.concat(['trueText'])" :rules="[
|
||||
{ required: true, message: '请输入trueText' },
|
||||
]">
|
||||
<a-input v-model:value="value.trueText" placeholder="trueText" size="small" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="-" :name="name.concat(['trueValue'])" :rules="[
|
||||
{ required: true, message: '请输入trueValue' },
|
||||
]">
|
||||
<a-input v-model:value="value.trueValue" placeholder="trueValue" size="small"/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label=" " :name="name.concat(['falseText'])" :rules="[
|
||||
{ required: true, message: '请输入falseText' },
|
||||
]">
|
||||
<a-input v-model:value="value.falseText" placeholder="falseText" size="small" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="-" :name="name.concat(['falseValue'])" :rules="[
|
||||
{ required: true, message: '请输入falseValue' },
|
||||
]">
|
||||
<a-input v-model:value="value.falseValue" placeholder="falseValue" size="small" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts" name="BooleanParam">
|
||||
import { PropType } from 'vue';
|
||||
type ModelValueType = Record<string, string>
|
||||
|
||||
interface Emits {
|
||||
(e: 'update:value', data: ModelValueType): void;
|
||||
}
|
||||
const emit = defineEmits<Emits>()
|
||||
const props = defineProps({
|
||||
value: {
|
||||
type: Object as PropType<ModelValueType>,
|
||||
default: () => ({
|
||||
})
|
||||
},
|
||||
name: {
|
||||
type: Array as PropType<string[]>,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
emit('update:value',
|
||||
{
|
||||
trueText: '是',
|
||||
trueValue: 'true',
|
||||
falseText: '否',
|
||||
falseValue: 'false',
|
||||
...props.value
|
||||
})
|
||||
})
|
||||
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.boolean-param {
|
||||
:deep(.ant-form-item) {
|
||||
flex-direction: row;
|
||||
|
||||
.ant-form-item-label {
|
||||
>label {
|
||||
margin: 0 10px 0 0;
|
||||
height: 28px;
|
||||
line-height: 28px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,160 @@
|
|||
<template>
|
||||
<div class="enum-param">
|
||||
<div class="list-item" v-for="(item, index) in _value" :key="index">
|
||||
<div class="item-left">
|
||||
<menu-outlined class="item-drag item-icon" />
|
||||
</div>
|
||||
<div class="item-middle item-editable">
|
||||
<a-popover :visible="editIndex === index" placement="top">
|
||||
<template #title>
|
||||
<div class="edit-title" style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<div style="width: 150px;">枚举项配置</div>
|
||||
<close-outlined @click="handleClose" />
|
||||
</div>
|
||||
</template>
|
||||
<template #content>
|
||||
<a-form :model="_value[index]" layout="vertical">
|
||||
<a-form-item label="Value" name="value" :rules="[
|
||||
{ required: true, message: '请输入Value' },
|
||||
]">
|
||||
<a-input v-model:value="_value[index].value" size="small"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="Text" name="text" :rules="[
|
||||
{ required: true, message: '请输入Text' },
|
||||
]">
|
||||
<a-input v-model:value="_value[index].text" size="small"></a-input>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</template>
|
||||
<div class="item-edit" @click="handleEdit(index)">
|
||||
{{ item.text || '枚举项配置' }}
|
||||
<edit-outlined class="item-icon" />
|
||||
</div>
|
||||
</a-popover>
|
||||
</div>
|
||||
<div class="item-right">
|
||||
<delete-outlined @click="handleDelete(index)"/>
|
||||
</div>
|
||||
</div>
|
||||
<a-button type="dashed" block @click="handleAdd">
|
||||
<template #icon><plus-outlined class="item-icon" /></template>
|
||||
新增枚举型
|
||||
</a-button>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts" name="BooleanParam">
|
||||
import { PropType } from 'vue'
|
||||
import { MenuOutlined, EditOutlined, DeleteOutlined, PlusOutlined, CloseOutlined } from '@ant-design/icons-vue';
|
||||
|
||||
type EnumType = {
|
||||
text?: string,
|
||||
value?: string,
|
||||
}
|
||||
|
||||
interface Emits {
|
||||
(e: 'update:value', data: EnumType[]): void;
|
||||
}
|
||||
const emit = defineEmits<Emits>()
|
||||
const props = defineProps({
|
||||
value: {
|
||||
type: Object as PropType<EnumType[]>,
|
||||
default: () => ([])
|
||||
}
|
||||
})
|
||||
|
||||
const _value = ref<EnumType[]>([])
|
||||
watchEffect(() => {
|
||||
_value.value = props.value
|
||||
})
|
||||
|
||||
watch(_value,
|
||||
() => {
|
||||
emit('update:value', _value.value)
|
||||
},
|
||||
{ deep: true })
|
||||
|
||||
const editIndex = ref<number>(-1)
|
||||
const handleEdit = (index: number) => {
|
||||
editIndex.value = index
|
||||
}
|
||||
const handleDelete = (index: number) => {
|
||||
editIndex.value = -1
|
||||
_value.value.splice(index, 1)
|
||||
}
|
||||
const handleClose = () => {
|
||||
editIndex.value = -1
|
||||
}
|
||||
const handleAdd = () => {
|
||||
_value.value.push({})
|
||||
emit('update:value', _value.value)
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.enum-param {
|
||||
.list-item {
|
||||
border: 1px solid #f0f0f0;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
padding: 3px 6px;
|
||||
margin-bottom: 10px;
|
||||
background-color: #fff;
|
||||
line-height: 26px;
|
||||
font-size: 14px;
|
||||
|
||||
// .item-left {
|
||||
// .item-drag {
|
||||
// cursor: move;
|
||||
// }
|
||||
// }
|
||||
.item-edit {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.item-icon {
|
||||
color: rgb(136, 136, 136);
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ant-form-item-label) {
|
||||
line-height: 1;
|
||||
|
||||
>label {
|
||||
font-size: 12px;
|
||||
|
||||
&.ant-form-item-required:not(.ant-form-item-required-mark-optional)::before {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ant-form-item-explain) {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
:deep(.ant-form-item-with-help) {
|
||||
.ant-form-item-explain {
|
||||
min-height: 20px;
|
||||
line-height: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ant-form-item) {
|
||||
margin-bottom: 20px;
|
||||
|
||||
&.ant-form-item-with-help {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
input {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ant-input),
|
||||
:deep(.ant-select) {
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,172 @@
|
|||
<template>
|
||||
<div class="json-param">
|
||||
<div class="list-item" v-for="(item, index) in _value" :key="`object_${index}`">
|
||||
<div class="item-left">
|
||||
<menu-outlined class="item-drag item-icon" />
|
||||
</div>
|
||||
<div class="item-middle item-editable">
|
||||
<a-popover :visible="editIndex === index" placement="left">
|
||||
<template #title>
|
||||
<div class="edit-title" style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<div style="width: 150px;">配置参数</div>
|
||||
<close-outlined @click="handleClose" />
|
||||
</div>
|
||||
</template>
|
||||
<template #content>
|
||||
<div style="max-width: 400px;">
|
||||
<a-form :model="_value[index]" layout="vertical">
|
||||
<a-form-item label="标识" name="id" :rules="[
|
||||
{ required: true, message: '请输入标识' },
|
||||
{ max: 64, message: '最多可输入64个字符' },
|
||||
{
|
||||
pattern: /^[a-zA-Z0-9_]+$/,
|
||||
message: '请输入英文或者数字或者-或者_',
|
||||
},
|
||||
]">
|
||||
<a-input v-model:value="_value[index].id" size="small"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="名称" name="name" :rules="[
|
||||
{ required: true, message: '请输入名称' },
|
||||
{ max: 64, message: '最多可输入64个字符' },
|
||||
]">
|
||||
<a-input v-model:value="_value[index].name" size="small"></a-input>
|
||||
</a-form-item>
|
||||
<value-type-form v-model:value="_value[index].valueType" :name="['valueType']" isSub
|
||||
key="json_sub"></value-type-form>
|
||||
</a-form>
|
||||
</div>
|
||||
</template>
|
||||
<div class="item-edit" @click="handleEdit(index)">
|
||||
{{ item.name || '配置参数' }}
|
||||
<edit-outlined class="item-icon" />
|
||||
</div>
|
||||
</a-popover>
|
||||
</div>
|
||||
<div class="item-right">
|
||||
<delete-outlined @click="handleDelete(index)" />
|
||||
</div>
|
||||
</div>
|
||||
<a-button type="dashed" block @click="handleAdd">
|
||||
<template #icon><plus-outlined class="item-icon" /></template>
|
||||
添加参数
|
||||
</a-button>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts" name="JsonParam">
|
||||
import { PropType } from 'vue'
|
||||
import { MenuOutlined, EditOutlined, DeleteOutlined, PlusOutlined, CloseOutlined } from '@ant-design/icons-vue';
|
||||
import ValueTypeForm from '@/views/device/components/Metadata/Base/Edit/ValueTypeForm.vue';
|
||||
|
||||
type JsonType = Record<any, any>;
|
||||
|
||||
interface Emits {
|
||||
(e: 'update:value', data: JsonType[]): void;
|
||||
}
|
||||
const emit = defineEmits<Emits>()
|
||||
const props = defineProps({
|
||||
value: {
|
||||
type: Object as PropType<JsonType[]>,
|
||||
default: () => ([])
|
||||
}
|
||||
})
|
||||
|
||||
const _value = ref<JsonType[]>([])
|
||||
watchEffect(() => {
|
||||
_value.value = props.value
|
||||
})
|
||||
|
||||
watch(_value,
|
||||
() => {
|
||||
emit('update:value', _value.value)
|
||||
},
|
||||
{ deep: true })
|
||||
|
||||
const editIndex = ref<number>(-1)
|
||||
const handleEdit = (index: number) => {
|
||||
editIndex.value = index
|
||||
}
|
||||
const handleDelete = (index: number) => {
|
||||
editIndex.value = -1
|
||||
_value.value.slice(index, 1)
|
||||
}
|
||||
const handleClose = () => {
|
||||
editIndex.value = -1
|
||||
}
|
||||
const handleAdd = () => {
|
||||
_value.value.push({
|
||||
valueType: {
|
||||
expands: {}
|
||||
},
|
||||
})
|
||||
emit('update:value', _value.value)
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.json-param {
|
||||
.list-item {
|
||||
border: 1px solid #f0f0f0;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
padding: 3px 6px;
|
||||
margin-bottom: 10px;
|
||||
background-color: #fff;
|
||||
line-height: 26px;
|
||||
font-size: 14px;
|
||||
|
||||
// .item-left {
|
||||
// .item-drag {
|
||||
// cursor: move;
|
||||
// }
|
||||
// }
|
||||
.item-edit {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.item-icon {
|
||||
color: rgb(136, 136, 136);
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ant-form-item-label) {
|
||||
line-height: 1;
|
||||
|
||||
>label {
|
||||
font-size: 12px;
|
||||
|
||||
&.ant-form-item-required:not(.ant-form-item-required-mark-optional)::before {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ant-form-item-explain) {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
:deep(.ant-form-item-with-help) {
|
||||
.ant-form-item-explain {
|
||||
min-height: 20px;
|
||||
line-height: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ant-form-item) {
|
||||
margin-bottom: 20px;
|
||||
|
||||
&.ant-form-item-with-help {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
input {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ant-input),
|
||||
:deep(.ant-select) {
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
|
@ -1,10 +1,11 @@
|
|||
import { UnorderedListOutlined, AppstoreOutlined } from '@ant-design/icons-vue'
|
||||
import styles from './index.module.less'
|
||||
import { Pagination, Table, Empty, Spin, Alert } from 'ant-design-vue'
|
||||
import type { TableProps, ColumnProps } from 'ant-design-vue/es/table'
|
||||
import type { TableProps } from 'ant-design-vue/es/table'
|
||||
import type { TooltipProps } from 'ant-design-vue/es/tooltip'
|
||||
import type { PopconfirmProps } from 'ant-design-vue/es/popconfirm'
|
||||
import { CSSProperties, PropType } from 'vue';
|
||||
import type { JColumnsProps } from './types'
|
||||
|
||||
enum ModelEnum {
|
||||
TABLE = 'TABLE',
|
||||
|
@ -40,14 +41,10 @@ export interface ActionsType {
|
|||
children?: ActionsType[];
|
||||
}
|
||||
|
||||
export interface JColumnProps extends ColumnProps {
|
||||
scopedSlots?: boolean; // 是否为插槽 true: 是 false: 否
|
||||
}
|
||||
|
||||
export interface JTableProps extends TableProps {
|
||||
request?: (params?: Record<string, any>) => Promise<Partial<RequestData>>;
|
||||
cardBodyClass?: string;
|
||||
columns: JColumnProps[];
|
||||
columns: JColumnsProps[];
|
||||
params?: Record<string, any>;
|
||||
model?: keyof typeof ModelEnum | undefined; // 显示table还是card
|
||||
// actions?: ActionsType[];
|
||||
|
@ -156,9 +153,10 @@ const JTable = defineComponent<JTableProps>({
|
|||
const pageIndex = ref<number>(0)
|
||||
const pageSize = ref<number>(6)
|
||||
const total = ref<number>(0)
|
||||
const _columns = ref<JColumnProps[]>(props?.columns || [])
|
||||
const loading = ref<boolean>(true)
|
||||
|
||||
const _columns = computed(() => props.columns.filter(i => !(i?.hideInTable)))
|
||||
|
||||
/**
|
||||
* 监听宽度,计算显示卡片个数
|
||||
*/
|
||||
|
|
|
@ -3,5 +3,6 @@ import { ColumnType } from 'ant-design-vue/es/table'
|
|||
|
||||
export interface JColumnsProps extends ColumnType{
|
||||
scopedSlots?: boolean;
|
||||
search: SearchProps
|
||||
search: SearchProps;
|
||||
hideInTable?: boolean;
|
||||
}
|
|
@ -0,0 +1,142 @@
|
|||
<template>
|
||||
<div class="dialog-item" :key="data.key" :class="{'dialog-active' : !data?.upstream}">
|
||||
<div class="dialog-card">
|
||||
<div class="dialog-list" v-for="item in data.list" :key="item.key">
|
||||
<div class="dialog-icon">
|
||||
<AIcon :type="visible.includes(item.key) ? 'DownOutlined' : 'RightOutlined'" />
|
||||
</div>
|
||||
<div class="dialog-box">
|
||||
<div class="dialog-header">
|
||||
<div class="dialog-title">
|
||||
<a-badge :color="statusColor.get(item.error ? 'error' : 'success')" style="margin-right: 5px" />
|
||||
{{operationMap.get(item.operation) || item?.operation}}
|
||||
</div>
|
||||
<div class="dialog-item">{{moment(item.endTime).format('YYYY-MM-DD HH:mm:ss')}}</div>
|
||||
</div>
|
||||
<div class="dialog-editor" v-if="visible.includes(item.key)">
|
||||
<a-textarea :bordered="false" :value="item?.detail" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
const operationMap = new Map();
|
||||
import moment from 'moment'
|
||||
operationMap.set('connection', '连接');
|
||||
operationMap.set('auth', '权限验证');
|
||||
operationMap.set('decode', '解码');
|
||||
operationMap.set('encode', '编码');
|
||||
operationMap.set('request', '请求');
|
||||
operationMap.set('response', '响应');
|
||||
operationMap.set('downstream', '下行消息');
|
||||
operationMap.set('upstream', '上行消息');
|
||||
|
||||
const statusColor = new Map();
|
||||
statusColor.set('error', '#E50012');
|
||||
statusColor.set('success', '#24B276');
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
}
|
||||
})
|
||||
const visible = ref<string[]>([])
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import 'ant-design-vue/es/style/themes/default.less';
|
||||
|
||||
:root {
|
||||
--dialog-primary-color: @primary-color;
|
||||
}
|
||||
|
||||
.dialog-item {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
width: 100%;
|
||||
padding-bottom: 12px;
|
||||
|
||||
.dialog-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 60%;
|
||||
padding: 24px;
|
||||
background-color: #fff;
|
||||
|
||||
.dialog-list {
|
||||
display: flex;
|
||||
|
||||
.dialog-icon {
|
||||
margin-right: 10px;
|
||||
color: rgba(0, 0, 0, 0.75);
|
||||
font-weight: 500;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.dialog-box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
|
||||
.dialog-header {
|
||||
.dialog-title {
|
||||
color: rgba(0, 0, 0, 0.75);
|
||||
font-weight: 700;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.dialog-time {
|
||||
color: rgba(0, 0, 0, 0.65);
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.dialog-editor {
|
||||
width: 100%;
|
||||
margin-top: 10px;
|
||||
color: rgba(0, 0, 0, 0.75);
|
||||
|
||||
textarea::-webkit-scrollbar {
|
||||
width: 5px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dialog-active {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
.dialog-card {
|
||||
background-color: @primary-color;
|
||||
|
||||
.dialog-list {
|
||||
.dialog-icon {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.dialog-box {
|
||||
.dialog-header {
|
||||
.dialog-title,
|
||||
.dialog-time {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.dialog-editor {
|
||||
textarea {
|
||||
color: #fff !important;
|
||||
background-color: @primary-color !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,93 @@
|
|||
<template>
|
||||
<a-table
|
||||
rowKey="id"
|
||||
:columns="columns"
|
||||
:data-source="dataSource"
|
||||
bordered
|
||||
:pagination="false"
|
||||
>
|
||||
<template #bodyCell="{ column, text, record }">
|
||||
<div style="width: 280px">
|
||||
<template v-if="['valueType', 'name'].includes(column.dataIndex)">
|
||||
<span>{{ text }}</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
<ValueItem
|
||||
v-model:modelValue="record.value"
|
||||
:itemType="record.type"
|
||||
:options="
|
||||
record.type === 'enum'
|
||||
? (record?.dataType?.elements || []).map(
|
||||
(item) => {
|
||||
return {
|
||||
label: item.text,
|
||||
value: item.value,
|
||||
};
|
||||
},
|
||||
)
|
||||
: record.type === 'boolean'
|
||||
? [
|
||||
{ label: '是', value: true },
|
||||
{ label: '否', value: false },
|
||||
]
|
||||
: undefined
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</a-table>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { PropType } from "vue-demi";
|
||||
|
||||
|
||||
type Emits = {
|
||||
(e: 'update:modelValue', data: Record<string, any>[]): void;
|
||||
};
|
||||
const _emit = defineEmits<Emits>();
|
||||
|
||||
const _props = defineProps({
|
||||
modelValue: {
|
||||
type: Array as PropType<Record<string, any>[]>,
|
||||
default: '',
|
||||
}
|
||||
});
|
||||
const columns = [
|
||||
{
|
||||
title: '参数名称',
|
||||
dataIndex: 'name',
|
||||
with: '33%',
|
||||
},
|
||||
{
|
||||
title: '类型',
|
||||
dataIndex: 'valueType',
|
||||
with: '33%',
|
||||
},
|
||||
{
|
||||
title: '值',
|
||||
dataIndex: 'value',
|
||||
with: '34%',
|
||||
},
|
||||
];
|
||||
|
||||
// const dataSource = ref<Record<any, any>[]>(_props.modelValue || []);
|
||||
|
||||
const dataSource = computed({
|
||||
get: () => {
|
||||
return _props.modelValue || {
|
||||
messageType: undefined,
|
||||
message: {
|
||||
properties: undefined,
|
||||
functionId: undefined,
|
||||
inputs: []
|
||||
}
|
||||
}
|
||||
},
|
||||
set: (val: any) => {
|
||||
_emit('update:modelValue', val);
|
||||
}
|
||||
})
|
||||
|
||||
</script>
|
|
@ -0,0 +1,127 @@
|
|||
<template>
|
||||
<div class="function">
|
||||
<a-form
|
||||
:layout="'vertical'"
|
||||
ref="formRef"
|
||||
:model="modelRef"
|
||||
>
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="6">
|
||||
<a-form-item name="messageType" :rules="{
|
||||
required: true,
|
||||
message: '请选择',
|
||||
}">
|
||||
<a-select placeholder="请选择" v-model:value="modelRef.messageType" show-search :filter-option="filterOption">
|
||||
<a-select-option value="READ_PROPERTY">读取属性</a-select-option>
|
||||
<a-select-option value="WRITE_PROPERTY">修改属性</a-select-option>
|
||||
<a-select-option value="INVOKE_FUNCTION">调用功能</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="6" v-if="['READ_PROPERTY','WRITE_PROPERTY'].includes(modelRef.messageType)">
|
||||
<a-form-item :name="['message', 'properties']" :rules="{
|
||||
required: true,
|
||||
message: '请选择属性',
|
||||
}">
|
||||
<a-select placeholder="请选择属性" v-model:value="modelRef.message.properties" show-search :filter-option="filterOption">
|
||||
<a-select-option v-for="i in (metadata?.properties) || []" :key="i.id" :value="i.id" :label="i.name">{{i.name}}</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="6" v-if="modelRef.messageType === 'WRITE_PROPERTY'">
|
||||
<a-form-item :name="['message', 'value']" :rules="{
|
||||
required: true,
|
||||
message: '请输入值',
|
||||
}">
|
||||
<a-input />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="6" v-if="modelRef.messageType === 'INVOKE_FUNCTION'">
|
||||
<a-form-item :name="['message', 'functionId']" :rules="{
|
||||
required: true,
|
||||
message: '请选择功能',
|
||||
}">
|
||||
<a-select placeholder="请选择功能" v-model:value="modelRef.message.functionId" show-search :filter-option="filterOption" @change="funcChange">
|
||||
<a-select-option v-for="i in (metadata?.functions) || []" :key="i.id" :value="i.id" :label="i.name">{{i.name}}</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="4">
|
||||
<a-button type="primary" @click="saveBtn">发送</a-button>
|
||||
</a-col>
|
||||
<a-col :span="24" v-if="modelRef.messageType === 'INVOKE_FUNCTION' && modelRef.message.functionId">
|
||||
<a-form-item :name="['message', 'inputs']" label="参数列表" :rules="{
|
||||
required: true,
|
||||
message: '请输入参数列表',
|
||||
}">
|
||||
<EditTable v-model="modelRef.message.inputs"/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useInstanceStore } from '@/store/instance';
|
||||
import EditTable from './EditTable.vue'
|
||||
|
||||
const instanceStore = useInstanceStore()
|
||||
|
||||
const formRef = ref();
|
||||
|
||||
const filterOption = (input: string, option: any) => {
|
||||
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
|
||||
};
|
||||
|
||||
type Emits = {
|
||||
(e: 'update:modelValue', data: any): void;
|
||||
};
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const modelRef = reactive({
|
||||
messageType: undefined,
|
||||
message: {
|
||||
properties: undefined,
|
||||
functionId: undefined,
|
||||
inputs: []
|
||||
}
|
||||
})
|
||||
|
||||
const metadata = computed(() => {
|
||||
return JSON.parse(instanceStore.current?.metadata || '{}')
|
||||
})
|
||||
|
||||
const funcChange = (val: string) => {
|
||||
if(val){
|
||||
const arr = metadata.value?.functions.find((item: any) => item.id === val)?.inputs || []
|
||||
const list = arr.map((item: any) => {
|
||||
return {
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
value: undefined,
|
||||
valueType: item?.valueType?.type,
|
||||
}
|
||||
})
|
||||
modelRef.message.inputs = list
|
||||
}
|
||||
}
|
||||
|
||||
const saveBtn = () => {
|
||||
formRef.value.validate()
|
||||
.then(() => {
|
||||
console.log(toRaw(modelRef))
|
||||
})
|
||||
}
|
||||
|
||||
defineExpose({ saveBtn })
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.function {
|
||||
padding: 15px;
|
||||
background-color: #e7eaec;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,3 @@
|
|||
<template>
|
||||
log
|
||||
</template>
|
|
@ -0,0 +1,92 @@
|
|||
<template>
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="16">
|
||||
<a-row :gutter="24" style="margin-bottom: 20px;">
|
||||
<a-col :span="12" v-for="item in messageArr" :key="item">
|
||||
<div :style="messageStyleMap.get(item.status)" class="message-status">
|
||||
<a-badge :status="messageStatusMap.get(item.status)" style="margin-right: 5px;" />
|
||||
<span>{{item.text}}</span>
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<div>
|
||||
<TitleComponent data="调试" />
|
||||
<div class="content">
|
||||
<div class="dialog" id="dialog">
|
||||
<template v-for="item in dialogList" :key="item.key">
|
||||
<Dialog :data="item" />
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div><Function /></div>
|
||||
</div>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<div class="right-log">
|
||||
<TitleComponent data="日志" />
|
||||
<div :style="{ marginTop: 10 }">
|
||||
<template v-if="logList.length">
|
||||
<Log v-for="item in logList" :data="item" :key="item.key" />
|
||||
</template>
|
||||
<a-empty v-else />
|
||||
</div>
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { MessageType } from './util'
|
||||
import { messageStatusMap, messageStyleMap } from './util'
|
||||
import Dialog from './Dialog/index.vue'
|
||||
import Function from './Function/index.vue'
|
||||
import Log from './Log/index.vue'
|
||||
|
||||
const message = reactive<MessageType>({
|
||||
up: {
|
||||
text: '上行消息诊断中',
|
||||
status: 'loading',
|
||||
},
|
||||
down: {
|
||||
text: '下行消息诊断中',
|
||||
status: 'loading',
|
||||
},
|
||||
})
|
||||
|
||||
const dialogList = ref<Record<string, any>>([])
|
||||
const logList = ref<Record<string, any>>([])
|
||||
|
||||
const messageArr = computed(() => {
|
||||
const arr = Object.keys(message) || []
|
||||
return arr.map(i => { return {...message[i], key: i}})
|
||||
})
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.message-status {
|
||||
padding: 8px 24px;
|
||||
}
|
||||
.content {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.dialog {
|
||||
width: 100%;
|
||||
min-height: 300px;
|
||||
max-height: 500px;
|
||||
padding: 24px;
|
||||
overflow: hidden;
|
||||
overflow-y: auto;
|
||||
background-color: #f2f5f7;
|
||||
}
|
||||
.right-log {
|
||||
padding-left: 20px;
|
||||
border-left: 1px solid rgba(0, 0, 0, .09);
|
||||
overflow: hidden;
|
||||
max-height: 600px;
|
||||
overflow-y: auto;
|
||||
min-height: 400px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,29 @@
|
|||
export type MessageType = {
|
||||
up: {
|
||||
text: string;
|
||||
status: 'loading' | 'success' | 'error';
|
||||
};
|
||||
down: {
|
||||
text: string;
|
||||
status: 'loading' | 'success' | 'error';
|
||||
};
|
||||
}
|
||||
|
||||
export const messageStyleMap = new Map();
|
||||
messageStyleMap.set('loading', {
|
||||
background: 'linear-gradient(0deg, rgba(30, 165, 241, 0.03), rgba(30, 165, 241, 0.03)), #FFFFFF',
|
||||
boxShadow: '-2px 0px 0px #1EA5F1',
|
||||
});
|
||||
messageStyleMap.set('error', {
|
||||
background: 'linear-gradient(0deg, rgba(255, 77, 79, 0.03), rgba(255, 77, 79, 0.03)), #FFFFFF',
|
||||
boxShadow: '-2px 0px 0px #FF4D4F',
|
||||
});
|
||||
messageStyleMap.set('success', {
|
||||
background: 'linear-gradient(0deg, rgba(50, 212, 164, 0.03), rgba(50, 212, 164, 0.03)), #FFFFFF',
|
||||
boxShadow: '-2px 0px 0px #32D4A4',
|
||||
});
|
||||
|
||||
export const messageStatusMap = new Map();
|
||||
messageStatusMap.set('loading', 'processing');
|
||||
messageStatusMap.set('error', 'error');
|
||||
messageStatusMap.set('success', 'success');
|
|
@ -0,0 +1,101 @@
|
|||
import { Badge, Descriptions, Modal, Tooltip } from "ant-design-vue"
|
||||
import TitleComponent from '@/components/TitleComponent/index.vue'
|
||||
import styles from './index.module.less'
|
||||
import AIcon from "@/components/AIcon";
|
||||
import _ from "lodash";
|
||||
|
||||
const DiagnosticAdvice = defineComponent({
|
||||
props: {
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => { }
|
||||
}
|
||||
},
|
||||
emits: ['close'],
|
||||
setup(props, { emit }) {
|
||||
const { data } = props
|
||||
return () => <Modal visible title="设备诊断" width={1000} onOk={() => {
|
||||
emit('close')
|
||||
}}
|
||||
onCancel={() => {
|
||||
emit('close')
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<TitleComponent data="诊断建议" />
|
||||
<div class={styles.advice}>
|
||||
<div class={styles.alert}>
|
||||
<span style={{ marginRight: 10 }}><AIcon type="InfoCircleOutlined" /></span>
|
||||
所有诊断均无异常但设备仍未上线,请检查以下内容
|
||||
</div>
|
||||
<div style={{ marginLeft: 10 }}>
|
||||
{
|
||||
(data?.list || []).map((item: any, index: number) => (
|
||||
<div class={styles.infoItem} key={index} style={{ margin: '10px 0' }}>
|
||||
{item}
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ marginTop: 15 }}>
|
||||
<TitleComponent data="连接信息" />
|
||||
<Descriptions column={2}>
|
||||
<Descriptions.Item span={1} label="设备ID">
|
||||
{data?.info?.id || ''}
|
||||
</Descriptions.Item>
|
||||
{data?.info?.address?.length > 0 && (
|
||||
<Descriptions.Item span={1} label="连接地址">
|
||||
<Tooltip
|
||||
placement="topLeft"
|
||||
title={
|
||||
<div class="serverItem">
|
||||
{(data?.info?.address || []).map((i: any) => (
|
||||
<div key={i.address}>
|
||||
<Badge color={i.health === -1 ? 'red' : 'green'} />
|
||||
{i.address}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div class="serverItem">
|
||||
{(data?.info?.address || []).slice(0, 1).map((i: any) => (
|
||||
<div key={i.address}>
|
||||
<Badge color={i.health === -1 ? 'red' : 'green'} />
|
||||
{i.address}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Tooltip>
|
||||
</Descriptions.Item>
|
||||
)}
|
||||
|
||||
{(_.flatten(_.map(data?.info?.config, 'properties')) || []).map((item: any, index: number) => (
|
||||
<Descriptions.Item
|
||||
key={index}
|
||||
span={1}
|
||||
label={
|
||||
item?.description ? (
|
||||
<div>
|
||||
<span style={{ marginRight: '10px' }}>{item.name}</span>
|
||||
<Tooltip title={item.description}>
|
||||
<AIcon type="QuestionCircleOutlined" />
|
||||
</Tooltip>
|
||||
</div>
|
||||
) : (
|
||||
item.name
|
||||
)
|
||||
}
|
||||
>
|
||||
{data?.info?.configValue[item?.property] || ''}
|
||||
</Descriptions.Item>
|
||||
))}
|
||||
</Descriptions>
|
||||
</div>
|
||||
</Modal>
|
||||
}
|
||||
})
|
||||
|
||||
export default DiagnosticAdvice
|
|
@ -0,0 +1,217 @@
|
|||
import AIcon from "@/components/AIcon";
|
||||
import { Button, Descriptions, Modal } from "ant-design-vue"
|
||||
import styles from './index.module.less'
|
||||
|
||||
const ManualInspection = defineComponent({
|
||||
props: {
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => { }
|
||||
}
|
||||
},
|
||||
emits: ['close', 'save'],
|
||||
setup(props, { emit }) {
|
||||
|
||||
const { data } = props
|
||||
|
||||
const dataRender = () => {
|
||||
if (data.type === 'device' || data.type === 'product') {
|
||||
return (
|
||||
<>
|
||||
<div style={{ flex: 1 }}>
|
||||
<div class={styles.alert}>
|
||||
<span style={{ marginRight: 10 }}><AIcon type="InfoCircleOutlined" /></span>
|
||||
请检查配置项是否填写正确,若您确定该项无需诊断可
|
||||
<Button type="link" style="padding: 0"
|
||||
onClick={() => {
|
||||
emit('save', data)
|
||||
}}
|
||||
>
|
||||
忽略
|
||||
</Button>
|
||||
</div>
|
||||
<div style={{ marginTop: 10 }}>
|
||||
<Descriptions title={data?.data?.name} layout="vertical" bordered>
|
||||
{(data?.data?.properties || []).map((item: any) => (
|
||||
<Descriptions.Item
|
||||
key={item.property}
|
||||
label={`${item.name}${item?.description ? `(${item.description})` : ''}`}
|
||||
>
|
||||
{data?.configuration[item.property] || ''}
|
||||
</Descriptions.Item>
|
||||
))}
|
||||
</Descriptions>
|
||||
</div>
|
||||
</div>
|
||||
{data?.data?.description ? (
|
||||
<div
|
||||
style={{ width: '50%', border: '1px solid #f0f0f0', padding: 10, borderLeft: 'none' }}
|
||||
>
|
||||
<h4>诊断项说明</h4>
|
||||
<p>{data?.data?.description}</p>
|
||||
</div>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</>
|
||||
);
|
||||
} else if (data.type === 'cloud') {
|
||||
return (
|
||||
<>
|
||||
<div style={{ flex: 1 }}>
|
||||
<div class={styles.alert}>
|
||||
<span style={{ marginRight: 10 }}><AIcon type="InfoCircleOutlined" /></span>
|
||||
请检查配置项是否填写正确,若您确定该项无需诊断可
|
||||
<Button type="link" style="padding: 0"
|
||||
onClick={() => {
|
||||
emit('save', data)
|
||||
}}
|
||||
>
|
||||
忽略
|
||||
</Button>
|
||||
</div>
|
||||
<div style={{ marginTop: 10 }}>
|
||||
<Descriptions title={data?.data?.name} layout="vertical" bordered>
|
||||
{data.configuration?.provider === 'OneNet' ? (
|
||||
<>
|
||||
<Descriptions.Item label={'接口地址'}>
|
||||
{data?.configuration?.configuration?.apiAddress || ''}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label={'apiKey'}>
|
||||
{data?.configuration?.configuration?.apiKey || ''}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label={'通知Token'}>
|
||||
{data?.configuration?.configuration?.validateToken || ''}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label={'aesKey'}>
|
||||
{data?.configuration?.configuration?.aesKey || ''}
|
||||
</Descriptions.Item>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Descriptions.Item label={'接口地址'}>
|
||||
{data?.configuration?.configuration?.apiAddress || ''}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label={'appKey'}>
|
||||
{data?.configuration?.configuration?.appKey || ''}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label={'appSecret'}>
|
||||
{data?.configuration?.configuration?.appSecret || ''}
|
||||
</Descriptions.Item>
|
||||
</>
|
||||
)}
|
||||
</Descriptions>
|
||||
</div>
|
||||
</div>
|
||||
{data?.configuration?.configuration?.description ? (
|
||||
<div
|
||||
style={{ width: '50%', border: '1px solid #f0f0f0', padding: 10, borderLeft: 'none' }}
|
||||
>
|
||||
<h4>诊断项说明</h4>
|
||||
<p>{data?.configuration?.configuration?.description}</p>
|
||||
</div>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</>
|
||||
);
|
||||
} else if (data.type === 'media') {
|
||||
return (
|
||||
<>
|
||||
<div style={{ flex: 1 }}>
|
||||
<div class={styles.alert}>
|
||||
<span style={{ marginRight: 10 }}><AIcon type="InfoCircleOutlined" /></span>
|
||||
请检查配置项是否填写正确,若您确定该项无需诊断可
|
||||
<Button type="link" style="padding: 0"
|
||||
onClick={() => {
|
||||
emit('save', data)
|
||||
}}
|
||||
>
|
||||
忽略
|
||||
</Button>
|
||||
</div>
|
||||
<div style={{ marginTop: 10 }}>
|
||||
<Descriptions title={data?.data?.name} layout="vertical" bordered>
|
||||
{data?.configuration?.configuration?.shareCluster ? (
|
||||
<>
|
||||
<Descriptions.Item label={'SIP 域'}>
|
||||
{data?.configuration?.configuration?.domain || ''}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label={'SIP ID'}>
|
||||
{data?.configuration?.configuration?.sipId || ''}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label={'集群'}>
|
||||
{data?.configuration?.configuration?.shareCluster ? '共享配置' : '独立配置'}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label={'SIP 地址'}>
|
||||
{`${data?.configuration?.configuration?.hostPort?.host}:${data?.configuration?.configuration?.hostPort?.port}`}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label={'公网 Host'}>
|
||||
{`${data?.configuration?.configuration?.hostPort?.publicHost}:${data?.configuration?.configuration?.hostPort?.publicPort}`}
|
||||
</Descriptions.Item>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Descriptions.Item label={'SIP 域'}>
|
||||
{data?.configuration?.configuration?.domain || ''}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label={'SIP ID'}>
|
||||
{data?.configuration?.configuration?.sipId || ''}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label={'集群'}>
|
||||
{data?.configuration?.configuration?.shareCluster ? '共享配置' : '独立配置'}
|
||||
</Descriptions.Item>
|
||||
{data?.configuration?.configuration?.cluster.map((i: any, it: number) => (
|
||||
<div key={it}>
|
||||
<div>节点{it + 1}</div>
|
||||
<Descriptions.Item label={'节点名称'}>
|
||||
{i?.clusterNodeId || ''}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label={'SIP 地址'}>
|
||||
{`${i.host}:${i?.port}`}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label={'公网 Host'}>
|
||||
{`${i?.publicHost}:${i?.publicPort}`}
|
||||
</Descriptions.Item>
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</Descriptions>
|
||||
</div>
|
||||
</div>
|
||||
{data?.configuration?.configuration.description ? (
|
||||
<div
|
||||
style={{ width: '50%', border: '1px solid #f0f0f0', padding: 10, borderLeft: 'none' }}
|
||||
>
|
||||
<h4>诊断项说明</h4>
|
||||
<p>{data?.configuration?.description}</p>
|
||||
</div>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
return () => <Modal
|
||||
title="人工检查"
|
||||
visible
|
||||
width={1000}
|
||||
cancelText="去修改"
|
||||
okText="确认无误"
|
||||
onOk={() => {
|
||||
emit('save', data)
|
||||
}}
|
||||
onCancel={() => {
|
||||
// TODO 跳转设备和产品
|
||||
}}>
|
||||
<div style={{ display: 'flex' }}>{dataRender()}</div>
|
||||
</Modal>
|
||||
}
|
||||
})
|
||||
|
||||
export default ManualInspection
|
|
@ -0,0 +1,90 @@
|
|||
.statusBox {
|
||||
width: 100%;
|
||||
|
||||
.statusHeader {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.statusContent {
|
||||
width: 100%;
|
||||
margin: 20px 0;
|
||||
border: 1px solid #ececec;
|
||||
border-bottom: none;
|
||||
|
||||
.statusItem {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 20px;
|
||||
border-bottom: 1px solid #ececec;
|
||||
|
||||
.statusLeft {
|
||||
display: flex;
|
||||
|
||||
.statusImg {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
margin: 15px 20px 0 0;
|
||||
}
|
||||
|
||||
.statusContext {
|
||||
.statusTitle {
|
||||
color: rgba(0, 0, 0, 0.8);
|
||||
font-weight: 700;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.statusDesc {
|
||||
color: rgba(0, 0, 0, 0.65);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.info {
|
||||
margin-top: 10px;
|
||||
color: #646464;
|
||||
font-size: 14px;
|
||||
|
||||
.infoItem {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.statusRight {
|
||||
margin-top: 10px;
|
||||
font-weight: 700;
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.loading {
|
||||
animation: loading 2s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes loading {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
25% {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
50% {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
75% {
|
||||
transform: rotate(270deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.alert {
|
||||
height: 40px;
|
||||
padding-left: 10px;
|
||||
color: rgba(0, 0, 0, 0.55);
|
||||
line-height: 40px;
|
||||
background-color: #f6f6f6;
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,262 @@
|
|||
import { getImage } from '@/utils/comm';
|
||||
import { VNode } from 'vue';
|
||||
|
||||
export type ListProps = {
|
||||
key: string;
|
||||
name: string;
|
||||
desc?: string;
|
||||
status: 'loading' | 'error' | 'success' | 'warning';
|
||||
text?: string;
|
||||
info?: VNode | null;
|
||||
};
|
||||
|
||||
export const TextColorMap = new Map();
|
||||
TextColorMap.set('loading', 'black');
|
||||
TextColorMap.set('error', 'red');
|
||||
TextColorMap.set('success', 'green');
|
||||
TextColorMap.set('warning', '#FAB247');
|
||||
|
||||
export const StatusMap = new Map();
|
||||
StatusMap.set('error', getImage('/diagnose/status/error.png'));
|
||||
StatusMap.set('success', getImage('/diagnose/status/success.png'));
|
||||
StatusMap.set('warning', getImage('/diagnose/status/warning.png'));
|
||||
StatusMap.set('loading', getImage('/diagnose/status/loading.png'));
|
||||
|
||||
export const networkInitList: ListProps[] = [
|
||||
// {
|
||||
// key: 'access',
|
||||
// name: '设备接入配置',
|
||||
// desc: '诊断该设备所属产品是否已配置“设备接入”方式,未配置将导致设备连接失败。',
|
||||
// status: 'loading',
|
||||
// text: '正在诊断中...',
|
||||
// info: null,
|
||||
// },
|
||||
{
|
||||
key: 'network',
|
||||
name: '网络组件',
|
||||
desc: '诊断网络组件配置是否正确,配置错误将导致设备连接失败',
|
||||
status: 'loading',
|
||||
text: '正在诊断中...',
|
||||
info: null,
|
||||
},
|
||||
{
|
||||
key: 'gateway',
|
||||
name: '设备接入网关',
|
||||
desc: '诊断设备接入网关状态是否正常,禁用状态将导致连接失败',
|
||||
status: 'loading',
|
||||
text: '正在诊断中...',
|
||||
info: null,
|
||||
},
|
||||
{
|
||||
key: 'product',
|
||||
name: '产品状态',
|
||||
desc: '诊断产品状态是否正常,禁用状态将导致设备连接失败',
|
||||
status: 'loading',
|
||||
text: '正在诊断中...',
|
||||
info: null,
|
||||
},
|
||||
{
|
||||
key: 'device',
|
||||
name: '设备状态',
|
||||
desc: '诊断设备状态是否正常,禁用状态将导致设备连接失败',
|
||||
status: 'loading',
|
||||
text: '正在诊断中...',
|
||||
info: null,
|
||||
},
|
||||
];
|
||||
|
||||
export const childInitList: ListProps[] = [
|
||||
// {
|
||||
// key: 'access',
|
||||
// name: '设备接入配置',
|
||||
// desc: '诊断该设备所属产品是否已配置“设备接入”方式,未配置将导致设备连接失败。',
|
||||
// status: 'loading',
|
||||
// text: '正在诊断中...',
|
||||
// info: null,
|
||||
// },
|
||||
// {
|
||||
// key: 'network',
|
||||
// name: '网络组件',
|
||||
// desc: '诊断网络组件配置是否正确,配置错误将导致设备连接失败',
|
||||
// status: 'loading',
|
||||
// text: '正在诊断中...',
|
||||
// info: null,
|
||||
// },
|
||||
{
|
||||
key: 'gateway',
|
||||
name: '设备接入网关',
|
||||
desc: '诊断设备接入网关状态是否正常,网关配置是否正确',
|
||||
status: 'loading',
|
||||
text: '正在诊断中...',
|
||||
info: null,
|
||||
},
|
||||
{
|
||||
key: 'parent-device',
|
||||
name: '网关父设备',
|
||||
desc: '诊断网关父设备状态是否正常,禁用或离线将导致连接失败',
|
||||
status: 'loading',
|
||||
text: '正在诊断中...',
|
||||
info: null,
|
||||
},
|
||||
{
|
||||
key: 'product',
|
||||
name: '产品状态',
|
||||
desc: '诊断产品状态是否正常,禁用状态将导致设备连接失败',
|
||||
status: 'loading',
|
||||
text: '正在诊断中...',
|
||||
info: null,
|
||||
},
|
||||
{
|
||||
key: 'device',
|
||||
name: '设备状态',
|
||||
desc: '诊断设备状态是否正常,禁用状态将导致设备连接失败',
|
||||
status: 'loading',
|
||||
text: '正在诊断中...',
|
||||
info: null,
|
||||
},
|
||||
];
|
||||
|
||||
export const cloudInitList: ListProps[] = [
|
||||
// {
|
||||
// key: 'access',
|
||||
// name: '设备接入配置',
|
||||
// desc: '诊断该设备所属产品是否已配置“设备接入”方式,未配置将导致设备连接失败。',
|
||||
// status: 'loading',
|
||||
// text: '正在诊断中...',
|
||||
// info: null,
|
||||
// },
|
||||
{
|
||||
key: 'gateway',
|
||||
name: '设备接入网关',
|
||||
desc: '诊断设备接入网关状态是否正常,网关配置是否正确',
|
||||
status: 'loading',
|
||||
text: '正在诊断中...',
|
||||
info: null,
|
||||
},
|
||||
{
|
||||
key: 'product',
|
||||
name: '产品状态',
|
||||
desc: '诊断产品状态是否正常,禁用状态将导致设备连接失败',
|
||||
status: 'loading',
|
||||
text: '正在诊断中...',
|
||||
info: null,
|
||||
},
|
||||
{
|
||||
key: 'device',
|
||||
name: '设备状态',
|
||||
desc: '诊断设备状态是否正常,禁用状态将导致设备连接失败',
|
||||
status: 'loading',
|
||||
text: '正在诊断中...',
|
||||
info: null,
|
||||
},
|
||||
];
|
||||
|
||||
export const channelInitList: ListProps[] = [
|
||||
// {
|
||||
// key: 'access',
|
||||
// name: '设备接入配置',
|
||||
// desc: '诊断该设备所属产品是否已配置“设备接入”方式,未配置将导致设备连接失败。',
|
||||
// status: 'loading',
|
||||
// text: '正在诊断中...',
|
||||
// info: null,
|
||||
// },
|
||||
{
|
||||
key: 'gateway',
|
||||
name: '设备接入网关',
|
||||
desc: '诊断设备接入网关状态是否正常,禁用状态将导致连接失败',
|
||||
status: 'loading',
|
||||
text: '正在诊断中...',
|
||||
info: null,
|
||||
},
|
||||
{
|
||||
key: 'product',
|
||||
name: '产品状态',
|
||||
desc: '诊断产品状态是否正常,禁用状态将导致设备连接失败',
|
||||
status: 'loading',
|
||||
text: '正在诊断中...',
|
||||
info: null,
|
||||
},
|
||||
{
|
||||
key: 'device',
|
||||
name: '设备状态',
|
||||
desc: '诊断设备状态是否正常,禁用状态将导致设备连接失败',
|
||||
status: 'loading',
|
||||
text: '正在诊断中...',
|
||||
info: null,
|
||||
},
|
||||
];
|
||||
|
||||
export const mediaInitList: ListProps[] = [
|
||||
// {
|
||||
// key: 'access',
|
||||
// name: '设备接入配置',
|
||||
// desc: '诊断该设备所属产品是否已配置“设备接入”方式,未配置将导致设备连接失败。',
|
||||
// status: 'loading',
|
||||
// text: '正在诊断中...',
|
||||
// info: null,
|
||||
// },
|
||||
{
|
||||
key: 'gateway',
|
||||
name: '设备接入网关',
|
||||
desc: '诊断设备接入网关状态是否正常,禁用状态将导致连接失败',
|
||||
status: 'loading',
|
||||
text: '正在诊断中...',
|
||||
info: null,
|
||||
},
|
||||
{
|
||||
key: 'product',
|
||||
name: '产品状态',
|
||||
desc: '诊断产品状态是否正常,禁用状态将导致设备连接失败',
|
||||
status: 'loading',
|
||||
text: '正在诊断中...',
|
||||
info: null,
|
||||
},
|
||||
{
|
||||
key: 'device',
|
||||
name: '设备状态',
|
||||
desc: '诊断设备状态是否正常,禁用状态将导致设备连接失败',
|
||||
status: 'loading',
|
||||
text: '正在诊断中...',
|
||||
info: null,
|
||||
},
|
||||
];
|
||||
|
||||
export const modifyArrayList = (oldList: ListProps[], item: ListProps, index?: number) => {
|
||||
let newList: ListProps[] = [];
|
||||
if (index !== 0 && !index) {
|
||||
// 添加
|
||||
for (let i = 0; i < oldList.length; i++) {
|
||||
const dt = oldList[i];
|
||||
if (item.key === dt.key) {
|
||||
newList.push(item);
|
||||
} else {
|
||||
newList.push(dt);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 修改
|
||||
oldList.splice(index, 0, item);
|
||||
newList = [...oldList];
|
||||
}
|
||||
return newList;
|
||||
};
|
||||
|
||||
export const isExit = (arr1: any[], arr2: any[]) => {
|
||||
return arr1.find((item) => arr2.includes(item));
|
||||
};
|
||||
|
||||
export const gatewayList = [
|
||||
'websocket-server',
|
||||
'http-server-gateway',
|
||||
'udp-device-gateway',
|
||||
'coap-server-gateway',
|
||||
'mqtt-client-gateway',
|
||||
'tcp-server-gateway',
|
||||
];
|
||||
|
||||
export const urlMap = new Map();
|
||||
urlMap.set('mqtt-client-gateway', 'topic');
|
||||
urlMap.set('http-server-gateway', 'url');
|
||||
urlMap.set('websocket-server', 'url');
|
||||
urlMap.set('coap-server-gateway', 'url');
|
||||
|
|
@ -0,0 +1,201 @@
|
|||
<template>
|
||||
<a-card>
|
||||
<div class="diagnose">
|
||||
<div class="diagnose-header" :style="{background: headerColorMap.get(topState)}">
|
||||
<div class="diagnose-top">
|
||||
<div class="diagnose-img">
|
||||
<div v-if="topState === 'loading'" style="width: 100%; height: 100%; position: relative">
|
||||
<img :src="headerImgMap.get(topState)" style="height: 100%; position: absolute; z-index: 2" />
|
||||
<img :src="getImage('/diagnose/loading-1.png')" style="height: 100%" />
|
||||
</div>
|
||||
<img v-else :src="headerImgMap.get(topState)" style="height: 100%" />
|
||||
</div>
|
||||
<div class="diagnose-text">
|
||||
<div class="diagnose-title">{{headerTitleMap.get(topState)}}</div>
|
||||
<div class="diagnose-desc">
|
||||
<template v-if="topState !== 'loading'">{{headerDescMap.get(topState)}}</template>
|
||||
<template v-else>已诊断{{count}}个</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="diagnose-progress">
|
||||
<a-progress
|
||||
:percent="percent"
|
||||
:showInfo="false"
|
||||
size="small"
|
||||
:strokeColor="progressMap.get(topState)"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</div>
|
||||
<div class="diagnose-radio">
|
||||
<div class="diagnose-radio-item" :class="item.key === 'message' && topState !== 'success' ? 'disabled' : ''" v-for="item in tabList" :key="item.key" :style="activeKey === item.key ? {...activeStyle} : {}" @click="onTabChange(item.key)">
|
||||
{{item.text}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Message v-if="activeKey === 'message'" />
|
||||
<Status v-else :providerType="providerType" @countChange="countChange" @percentChange="percentChange" @stateChange="stateChange" />
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { headerImgMap, headerColorMap, headerTitleMap, headerDescMap, progressMap } from './util'
|
||||
import { getImage } from '@/utils/comm';
|
||||
import Status from './Status/index'
|
||||
import Message from './Message/index.vue'
|
||||
import { useInstanceStore } from '@/store/instance';
|
||||
|
||||
type TypeProps = 'network' | 'child-device' | 'media' | 'cloud' | 'channel'
|
||||
|
||||
const instanceStore = useInstanceStore()
|
||||
|
||||
const tabList = [
|
||||
{ key: 'status', text: '连接状态' },
|
||||
{ key: 'message', text: '消息通信' },
|
||||
];
|
||||
|
||||
const activeStyle = {
|
||||
background: '#FFFFFF',
|
||||
border: '1px solid rgba(0, 0, 0, 0.09)',
|
||||
borderRadius: '2px 2px 0px 0px',
|
||||
color: '#000000BF',
|
||||
};
|
||||
|
||||
const topState = ref<'loading' | 'success' | 'error'>('loading')
|
||||
const count = ref<number>(0)
|
||||
const percent = ref<number>(0)
|
||||
const activeKey = ref<'status' | 'message'>('status')
|
||||
const providerType = ref()
|
||||
|
||||
|
||||
const onTabChange = (key: 'status' | 'message') => {
|
||||
if(topState.value === 'success'){
|
||||
activeKey.value = key
|
||||
}
|
||||
}
|
||||
|
||||
const percentChange = (num: number) => {
|
||||
if(num === 0){
|
||||
percent.value = 0
|
||||
} else if( percent.value < 100 && !num) {
|
||||
percent.value += 20
|
||||
} else {
|
||||
percent.value = num
|
||||
}
|
||||
}
|
||||
|
||||
const stateChange = (_type: 'loading' | 'success' | 'error') => {
|
||||
topState.value = _type
|
||||
}
|
||||
|
||||
const countChange = (num: number) => {
|
||||
count.value = num
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
const provider = instanceStore.current?.accessProvider;
|
||||
if (provider === 'fixed-media' || provider === 'gb28181-2016') {
|
||||
providerType.value = 'media'
|
||||
} else if (provider === 'OneNet' || provider === 'Ctwing') {
|
||||
providerType.value = 'cloud'
|
||||
} else if (provider === 'modbus-tcp' || provider === 'opc-ua') {
|
||||
providerType.value = 'channel'
|
||||
} else if (provider === 'child-device') {
|
||||
providerType.value = 'child-device'
|
||||
} else {
|
||||
providerType.value = 'network'
|
||||
}
|
||||
topState.value = 'loading';
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.diagnose {
|
||||
.diagnose-header {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 150px;
|
||||
margin-bottom: 20px;
|
||||
padding: 15px 25px;
|
||||
|
||||
.diagnose-top {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
|
||||
.diagnose-img {
|
||||
width: 65px;
|
||||
height: 65px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.diagnose-text {
|
||||
.diagnose-title {
|
||||
color: #000c;
|
||||
font-weight: 700;
|
||||
font-size: 25px;
|
||||
}
|
||||
|
||||
.diagnose-desc {
|
||||
color: rgba(0, 0, 0, 0.65);
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.diagnose-progress {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.diagnose-radio {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
|
||||
.diagnose-radio-item {
|
||||
width: 150px;
|
||||
height: 35px;
|
||||
margin-right: 8px;
|
||||
color: #00000073;
|
||||
line-height: 35px;
|
||||
text-align: center;
|
||||
background: #f2f2f2;
|
||||
border-radius: 2px 2px 0 0;
|
||||
cursor: pointer;
|
||||
&.disabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.diagnose-loading {
|
||||
animation: diagnose-loading 2s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes diagnose-loading {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
25% {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
75% {
|
||||
transform: rotate(270deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
|
@ -0,0 +1,30 @@
|
|||
import { getImage } from '@/utils/comm';
|
||||
|
||||
export const headerImgMap = new Map();
|
||||
headerImgMap.set('loading', getImage('/diagnose/loading-2.png'));
|
||||
headerImgMap.set('error', getImage('/diagnose/error.png'));
|
||||
headerImgMap.set('success', getImage('/diagnose/success.png'));
|
||||
|
||||
export const headerColorMap = new Map();
|
||||
headerColorMap.set('loading', 'linear-gradient(89.95deg, #E6F5FF 0.03%, #E9EAFF 99.95%)');
|
||||
headerColorMap.set(
|
||||
'error',
|
||||
'linear-gradient(89.95deg, rgba(231, 173, 86, 0.1) 0.03%, rgba(247, 111, 93, 0.1) 99.95%)',
|
||||
);
|
||||
headerColorMap.set('success', 'linear-gradient(89.95deg, #E8F8F7 0.03%, #EBEFFA 99.95%)');
|
||||
|
||||
|
||||
export const headerTitleMap = new Map();
|
||||
headerTitleMap.set('loading', '正在诊断中');
|
||||
headerTitleMap.set('error', '发现连接问题');
|
||||
headerTitleMap.set('success', '连接状态正常');
|
||||
|
||||
export const headerDescMap = new Map();
|
||||
headerDescMap.set('loading', '已诊断XX个');
|
||||
headerDescMap.set('error', '请处理连接异常');
|
||||
headerDescMap.set('success', '现在可调试消息通信');
|
||||
|
||||
export const progressMap = new Map();
|
||||
progressMap.set('loading', '#597EF7');
|
||||
progressMap.set('error', '#FAB247');
|
||||
progressMap.set('success', '#32D4A4');
|
|
@ -44,6 +44,7 @@ import Info from './Info/index.vue';
|
|||
import Running from './Running/index.vue'
|
||||
import Metadata from '../../components/Metadata/index.vue';
|
||||
import ChildDevice from './ChildDevice/index.vue';
|
||||
import Diagnose from './Diagnose/index.vue'
|
||||
import { _deploy, _disconnect } from '@/api/device/instance'
|
||||
import { message } from 'ant-design-vue';
|
||||
import { getImage } from '@/utils/comm';
|
||||
|
@ -52,7 +53,7 @@ const route = useRoute();
|
|||
const instanceStore = useInstanceStore()
|
||||
|
||||
const statusMap = new Map();
|
||||
statusMap.set('online', 'processing');
|
||||
statusMap.set('online', 'success');
|
||||
statusMap.set('offline', 'error');
|
||||
statusMap.set('notActive', 'warning');
|
||||
|
||||
|
@ -72,7 +73,11 @@ const list = [
|
|||
{
|
||||
key: 'ChildDevice',
|
||||
tab: '子设备'
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'Diagnose',
|
||||
tab: '设备诊断'
|
||||
},
|
||||
]
|
||||
|
||||
const tabs = {
|
||||
|
@ -80,6 +85,7 @@ const tabs = {
|
|||
Metadata,
|
||||
Running,
|
||||
ChildDevice,
|
||||
Diagnose
|
||||
}
|
||||
|
||||
watch(
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
<template>
|
||||
<page-container>
|
||||
<Search :columns="columns" target="device-instance" />
|
||||
<Search
|
||||
:columns="columns"
|
||||
target="device-instance"
|
||||
@search="handleSearch"
|
||||
/>
|
||||
<JTable
|
||||
ref="instanceRef"
|
||||
:columns="columns"
|
||||
|
@ -267,6 +271,13 @@ import Export from './Export/index.vue';
|
|||
import Process from './Process/index.vue';
|
||||
import Save from './Save/index.vue';
|
||||
import { BASE_API_PATH, TOKEN_KEY } from '@/utils/variable';
|
||||
import {
|
||||
getProviders,
|
||||
queryGatewayList,
|
||||
queryNoPagingPost,
|
||||
queryOrgThree,
|
||||
} from '@/api/device/product';
|
||||
import { queryTree } from '@/api/device/category';
|
||||
|
||||
const router = useRouter();
|
||||
const instanceRef = ref<Record<string, any>>({});
|
||||
|
@ -290,33 +301,172 @@ const columns = [
|
|||
title: 'ID',
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
search: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '设备名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
search: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '产品名称',
|
||||
dataIndex: 'productName',
|
||||
key: 'productName',
|
||||
search: {
|
||||
type: 'select',
|
||||
options: () =>
|
||||
new Promise((resolve) => {
|
||||
queryNoPagingPost({ paging: false }).then((resp: any) => {
|
||||
resolve(
|
||||
resp.result.map((item: any) => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
})),
|
||||
);
|
||||
});
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime',
|
||||
key: 'createTime',
|
||||
scopedSlots: true,
|
||||
search: {
|
||||
type: 'date',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'state',
|
||||
key: 'state',
|
||||
scopedSlots: true,
|
||||
search: {
|
||||
type: 'select',
|
||||
options: [
|
||||
{ label: '禁用', value: 'notActive' },
|
||||
{ label: '离线', value: 'offline' },
|
||||
{ label: '在线', value: 'online' },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'classifiedId',
|
||||
dataIndex: 'classifiedId',
|
||||
title: '产品分类',
|
||||
hideInTable: true,
|
||||
search: {
|
||||
type: 'treeSelect',
|
||||
options: () =>
|
||||
new Promise((resolve) => {
|
||||
queryTree({ paging: false }).then((resp: any) => {
|
||||
resolve(resp.result);
|
||||
});
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'accessProvider',
|
||||
title: '网关类型',
|
||||
dataIndex: 'accessProvider',
|
||||
valueType: 'select',
|
||||
hideInTable: true,
|
||||
search: {
|
||||
type: 'select',
|
||||
options: () =>
|
||||
new Promise((resolve) => {
|
||||
getProviders().then((resp: any) => {
|
||||
resolve(
|
||||
resp.result.map((item: any) => ({
|
||||
label: item.name,
|
||||
value: `accessProvider is ${item.id}`,
|
||||
})),
|
||||
);
|
||||
});
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'productId$product-info',
|
||||
dataIndex: 'productId$product-info',
|
||||
title: '接入方式',
|
||||
hideInTable: true,
|
||||
search: {
|
||||
type: 'select',
|
||||
options: () =>
|
||||
new Promise((resolve) => {
|
||||
queryGatewayList({}).then((resp: any) => {
|
||||
resolve(
|
||||
resp.result.map((item: any) => ({
|
||||
label: item.name,
|
||||
value: `accessId is ${item.id}`,
|
||||
})),
|
||||
);
|
||||
});
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
dataIndex: 'deviceType',
|
||||
title: '设备类型',
|
||||
valueType: 'select',
|
||||
hideInTable: true,
|
||||
search: {
|
||||
type: 'select',
|
||||
options: [
|
||||
{ label: '直连设备', value: 'device' },
|
||||
{ label: '网关子设备', value: 'childrenDevice' },
|
||||
{ label: '网关设备', value: 'gateway' },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
dataIndex: 'id$dim-assets',
|
||||
title: '所属组织',
|
||||
hideInTable: true,
|
||||
search: {
|
||||
type: 'treeSelect',
|
||||
options: () =>
|
||||
new Promise((resolve) => {
|
||||
queryOrgThree({}).then((resp: any) => {
|
||||
const formatValue = (list: any[]) => {
|
||||
const _list: any[] = [];
|
||||
list.forEach((item) => {
|
||||
if (item.children) {
|
||||
item.children = formatValue(item.children);
|
||||
}
|
||||
_list.push({
|
||||
...item,
|
||||
id: JSON.stringify({
|
||||
assetType: 'device',
|
||||
targets: [
|
||||
{
|
||||
type: 'org',
|
||||
id: item.id,
|
||||
},
|
||||
],
|
||||
}),
|
||||
});
|
||||
});
|
||||
return _list;
|
||||
};
|
||||
resolve(formatValue(resp.result));
|
||||
});
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '说明',
|
||||
dataIndex: 'describe',
|
||||
key: 'describe',
|
||||
search: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
|
@ -543,4 +693,9 @@ const saveBtn = () => {
|
|||
visible.value = false;
|
||||
instanceRef.value?.reload();
|
||||
};
|
||||
|
||||
const handleSearch = (_params: any) => {
|
||||
console.log(_params);
|
||||
params.value = _params;
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { MetadataItem } from "../Product/typings";
|
||||
|
||||
export type DeviceInstance = {
|
||||
id: string;
|
||||
name: string;
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
<template>
|
||||
<a-form ref="addFormRef" :model="form.model" layout="vertical">
|
||||
<a-form-item label="标识" name="id" :rules="[
|
||||
{ required: true, message: '请输入标识' },
|
||||
{ max: 64, message: '最多可输入64个字符' },
|
||||
{
|
||||
pattern: /^[a-zA-Z0-9_]+$/,
|
||||
message: '请输入英文或者数字或者-或者_',
|
||||
},
|
||||
]">
|
||||
<a-input v-model:value="form.model.id" size="small"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="名称" name="name" :rules="[
|
||||
{ required: true, message: '请输入名称' },
|
||||
{ max: 64, message: '最多可输入64个字符' },
|
||||
]">
|
||||
<a-input v-model:value="form.model.name" size="small"></a-input>
|
||||
</a-form-item>
|
||||
<ValueTypeForm :name="['valueType']" v-model:value="form.model.valueType" key="property"></ValueTypeForm>
|
||||
<a-form-item label="读写类型" :name="['expands', 'type']" :rules="[
|
||||
{ required: true, message: '请选择读写类型' },
|
||||
]">
|
||||
<a-select v-model:value="form.model.expands.type" :options="form.expandsType" mode="multiple" size="small"></a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="说明" name="description" :rules="[
|
||||
{ max: 200, message: '最多可输入200个字符' },
|
||||
]">
|
||||
<a-textarea v-model:value="form.model.description" size="small"></a-textarea>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</template>
|
||||
<script setup lang="ts" name="PropertyForm">
|
||||
import ValueTypeForm from './ValueTypeForm.vue'
|
||||
|
||||
const form = reactive({
|
||||
model: {
|
||||
valueType: {
|
||||
expands: {}
|
||||
},
|
||||
expands: {}
|
||||
} as any,
|
||||
expandsType: [
|
||||
{
|
||||
label: '读',
|
||||
value: 'read',
|
||||
},
|
||||
{
|
||||
label: '写',
|
||||
value: 'write',
|
||||
},
|
||||
{
|
||||
label: '上报',
|
||||
value: 'report',
|
||||
},
|
||||
]
|
||||
})
|
||||
|
||||
</script>
|
||||
<style scoped lang="less">
|
||||
:deep(.ant-form-item-label) {
|
||||
line-height: 1;
|
||||
|
||||
>label {
|
||||
font-size: 12px;
|
||||
|
||||
&.ant-form-item-required:not(.ant-form-item-required-mark-optional)::before {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ant-form-item-explain) {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
:deep(.ant-form-item-with-help) {
|
||||
.ant-form-item-explain {
|
||||
min-height: 20px;
|
||||
line-height: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ant-form-item) {
|
||||
margin-bottom: 20px;
|
||||
|
||||
&.ant-form-item-with-help {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
input {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ant-input),
|
||||
:deep(.ant-select) {
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,151 @@
|
|||
<template>
|
||||
<a-form-item label="数据类型" :name="name.concat(['type'])" :rules="[
|
||||
{ required: true, message: '请选择数据类型' },
|
||||
]">
|
||||
<a-select v-model:value="value.type" :options="_dataTypeList" size="small"></a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="单位" :name="name.concat(['unit'])" v-if="['int', 'float', 'long', 'double'].includes(value.type)">
|
||||
<InputSelect v-model:value="value.unit" :options="unit.unitOptions" size="small"></InputSelect>
|
||||
</a-form-item>
|
||||
<a-form-item label="精度" :name="name.concat(['scale'])" v-if="['float', 'double'].includes(value.type)">
|
||||
<a-input-number v-model:value="value.scale" size="small" :min="0" :max="2147483647" :precision="0"
|
||||
:default-value="2" style="width: 100%"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label="布尔值" name="booleanConfig" v-if="['boolean'].includes(value.type)">
|
||||
<BooleanParam
|
||||
:name="name"
|
||||
v-model:value="_value"
|
||||
></BooleanParam>
|
||||
</a-form-item>
|
||||
<a-form-item label="枚举项" :name="name.concat(['elements'])" v-if="['enum'].includes(value.type)" :rules="[
|
||||
{ required: true, message: '请配置枚举项' }
|
||||
]">
|
||||
<EnumParam v-model:value="value.elements"></EnumParam>
|
||||
</a-form-item>
|
||||
<a-form-item :name="name.concat(['expands', 'maxLength'])" v-if="['string', 'password'].includes(value.type)">
|
||||
<template #label>
|
||||
<a-space>
|
||||
最大长度
|
||||
<a-tooltip title="字节">
|
||||
<question-circle-outlined style="color: rgb(136, 136, 136); font-size: 12px;" />
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
<a-input-number v-model:value="value.expands.maxLength" size="small" :max="2147483647" :min="1" :precision="0"
|
||||
style="width: 100%;"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label="元素配置" :name="name.concat(['elementType'])" v-if="['array'].includes(value.type)">
|
||||
<ArrayParam v-model:value="value.elementType" :name="name.concat(['elementType'])"></ArrayParam>
|
||||
</a-form-item>
|
||||
<a-form-item label="JSON对象" :name="name.concat(['properties'])" v-if="['object'].includes(value.type)">
|
||||
<JsonParam v-model:value="value.jsonConfig" :name="name.concat(['properties'])"></JsonParam>
|
||||
</a-form-item>
|
||||
<a-form-item label="文件类型" :name="name.concat(['fileType'])" v-if="['file'].includes(value.type)" initialValue="url" :rules="[
|
||||
{ required: true, message: '请选择文件类型' },
|
||||
]">
|
||||
<a-select v-model:value="value.fileType" :options="FileTypeList" size="small"></a-select>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<script lang="ts" setup mame="BaseForm">
|
||||
import { DataTypeList, FileTypeList } from '@/views/device/data';
|
||||
import { DefaultOptionType } from 'ant-design-vue/es/select';
|
||||
import { PropType } from 'vue'
|
||||
import { getUnit } from '@/api/device/instance';
|
||||
import { Store } from 'jetlinks-store';
|
||||
import InputSelect from '@/components/InputSelect/index.vue';
|
||||
import BooleanParam from '@/components/Metadata/BooleanParam/index.vue'
|
||||
import EnumParam from '@/components/Metadata/EnumParam/index.vue'
|
||||
import ArrayParam from '@/components/Metadata/ArrayParam/index.vue'
|
||||
import JsonParam from '@/components/Metadata/JsonParam/index.vue'
|
||||
|
||||
type ValueType = Record<any, any>;
|
||||
const props = defineProps({
|
||||
value: {
|
||||
type: Object as PropType<ValueType>,
|
||||
default: () => ({
|
||||
extends: {}
|
||||
})
|
||||
},
|
||||
isSub: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
name: {
|
||||
type: Array as PropType<string[]>,
|
||||
default: () => ([]),
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
interface Emits {
|
||||
(e: 'update:value', data: ValueType): void;
|
||||
}
|
||||
const emit = defineEmits<Emits>()
|
||||
|
||||
|
||||
const _value = computed({
|
||||
get: () => props.value,
|
||||
set: val => {
|
||||
emit('update:value', val)
|
||||
}
|
||||
})
|
||||
|
||||
const unit = {
|
||||
unitOptions: [] as DefaultOptionType[],
|
||||
getUnit: () => {
|
||||
getUnit().then(resp => {
|
||||
const _data = resp.result.map(item => ({
|
||||
label: item.description,
|
||||
value: item.id,
|
||||
}));
|
||||
// 缓存单位数据
|
||||
Store.set('units', _data);
|
||||
unit.unitOptions = _data;
|
||||
})
|
||||
},
|
||||
}
|
||||
unit.getUnit()
|
||||
|
||||
const _dataTypeList = computed(() => props.isSub ? DataTypeList.filter(item => item.value !== 'array' && item.value !== 'object') : DataTypeList)
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
:deep(.ant-form-item-label) {
|
||||
line-height: 1;
|
||||
|
||||
>label {
|
||||
font-size: 12px;
|
||||
|
||||
&.ant-form-item-required:not(.ant-form-item-required-mark-optional)::before {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ant-form-item-explain) {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
:deep(.ant-form-item-with-help) {
|
||||
.ant-form-item-explain {
|
||||
min-height: 20px;
|
||||
line-height: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ant-form-item) {
|
||||
margin-bottom: 20px;
|
||||
|
||||
&.ant-form-item-with-help {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
input {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ant-input),
|
||||
:deep(.ant-select) {
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
|
@ -1,10 +1,10 @@
|
|||
<template>
|
||||
<a-drawer :mask-closable="false" width="25vw" visible :title="`新增${typeMapping[metadataStore.model.type]}`"
|
||||
<a-drawer :mask-closable="false" width="25vw" visible :title="`${title}-${typeMapping[metadataStore.model.type]}`"
|
||||
@close="close" destroy-on-close :z-index="1000" placement="right">
|
||||
<template #extra>
|
||||
<a-button :loading="save.loading" type="primary" @click="save.saveMetadata">保存</a-button>
|
||||
</template>
|
||||
<a-form ref="addFormRef" :model="form.model"></a-form>
|
||||
<PropertyForm v-if="metadataStore.model.type === 'properties'"></PropertyForm>
|
||||
</a-drawer>
|
||||
</template>
|
||||
<script lang="ts" setup name="Edit">
|
||||
|
@ -19,6 +19,7 @@ import { Store } from 'jetlinks-store';
|
|||
import { SystemConst } from '@/utils/consts';
|
||||
import { detail } from '@/api/device/instance';
|
||||
import { DeviceInstance } from '@/views/device/Instance/typings';
|
||||
import PropertyForm from './PropertyForm.vue';
|
||||
|
||||
interface Props {
|
||||
type: 'product' | 'device';
|
||||
|
@ -41,6 +42,8 @@ const close = () => {
|
|||
metadataStore.set('item', {})
|
||||
}
|
||||
|
||||
const title = computed(() => metadataStore.model.action === 'add' ? '新增' : '修改')
|
||||
|
||||
const addFormRef = ref<FormInstance>()
|
||||
/**
|
||||
* 保存按钮
|
||||
|
@ -113,7 +116,7 @@ const save = reactive({
|
|||
})
|
||||
|
||||
const form = reactive({
|
||||
model: {}
|
||||
model: {} as Record<string, any>
|
||||
})
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
|
|
|
@ -2,8 +2,10 @@
|
|||
<JTable :loading="loading" :data-source="data" size="small" :columns="columns" row-key="id" model="TABLE">
|
||||
<template #headerTitle>
|
||||
<a-input-search v-model:value="searchValue" placeholder="请输入名称" @search="handleSearch"></a-input-search>
|
||||
</template>
|
||||
<template #rightExtraRender>
|
||||
<PermissionButton type="primary" :uhas-permission="`${permission}:update`" key="add" @click="handleAddClick"
|
||||
:udisabled="operateLimits('add', type)" :tooltip="{
|
||||
:disabled="operateLimits('add', type)" :tooltip="{
|
||||
title: operateLimits('add', type) ? '当前的存储方式不支持新增' : '新增',
|
||||
}">
|
||||
<template #icon>
|
||||
|
@ -31,20 +33,20 @@
|
|||
</a-tag>
|
||||
</template>
|
||||
<template #action="slotProps">
|
||||
<PermissionButton :has-permission="`${permission}:update`" type="link" key="edit" style="padding: 0"
|
||||
:disabled="operateLimits('updata', type)" @click="handleEditClick(slotProps)" :tooltip="{
|
||||
<PermissionButton :uhas-permission="`${permission}:update`" type="link" key="edit" style="padding: 0"
|
||||
:udisabled="operateLimits('updata', type)" @click="handleEditClick(slotProps)" :tooltip="{
|
||||
title: operateLimits('updata', type) ? '当前的存储方式不支持编辑' : '编辑',
|
||||
}">
|
||||
<EditOutlined />
|
||||
</PermissionButton>,
|
||||
<PermissionButton :has-permission="`${permission}:delete`" type="link" key="delete" style="padding: 0"
|
||||
</PermissionButton>
|
||||
<PermissionButton :uhas-permission="`${permission}:delete`" type="link" key="delete" style="padding: 0"
|
||||
:pop-confirm="{
|
||||
title: '确认删除?', onConfirm: async () => {
|
||||
await removeItem(slotProps);
|
||||
},
|
||||
}" :tooltip="{
|
||||
title: '删除',
|
||||
}">
|
||||
title: '删除',
|
||||
}">
|
||||
<DeleteOutlined />
|
||||
</PermissionButton>
|
||||
</template>
|
||||
|
@ -58,12 +60,13 @@ import { useInstanceStore } from '@/store/instance'
|
|||
import { useProductStore } from '@/store/product'
|
||||
import { useMetadataStore } from '@/store/metadata'
|
||||
import PermissionButton from '@/components/PermissionButton/index.vue'
|
||||
import { PlusOutlined } from '@ant-design/icons-vue'
|
||||
import { DeleteOutlined, EditOutlined, PlusOutlined } from '@ant-design/icons-vue'
|
||||
import { message } from 'ant-design-vue/es'
|
||||
import { SystemConst } from '@/utils/consts'
|
||||
import { Store } from 'jetlinks-store'
|
||||
import { asyncUpdateMetadata, removeMetadata } from '../metadata'
|
||||
import { detail } from '@/api/device/instance'
|
||||
import Edit from './Edit/index.vue'
|
||||
// import { detail } from '@/api/device/instance'
|
||||
// import { detail as productDetail } from '@/api/device/product'
|
||||
interface Props {
|
||||
|
|
|
@ -0,0 +1,135 @@
|
|||
import { isNoCommunity } from '@/utils/utils';
|
||||
|
||||
export const DataTypeList: { label: string; value: string }[] = [
|
||||
{
|
||||
value: 'int',
|
||||
label: 'int(整数型)',
|
||||
},
|
||||
{
|
||||
value: 'long',
|
||||
label: 'long(长整数型)',
|
||||
},
|
||||
{
|
||||
value: 'float',
|
||||
label: 'float(单精度浮点型)',
|
||||
},
|
||||
{
|
||||
value: 'double',
|
||||
label: 'double(双精度浮点数)',
|
||||
},
|
||||
{
|
||||
value: 'string',
|
||||
label: 'text(字符串)',
|
||||
},
|
||||
{
|
||||
value: 'boolean',
|
||||
label: 'boolean(布尔型)',
|
||||
},
|
||||
{
|
||||
value: 'date',
|
||||
label: 'date(时间型)',
|
||||
},
|
||||
{
|
||||
value: 'enum',
|
||||
label: 'enum(枚举)',
|
||||
},
|
||||
{
|
||||
value: 'array',
|
||||
label: 'array(数组)',
|
||||
},
|
||||
{
|
||||
value: 'object',
|
||||
label: 'object(结构体)',
|
||||
},
|
||||
{
|
||||
value: 'file',
|
||||
label: 'file(文件)',
|
||||
},
|
||||
{
|
||||
value: 'password',
|
||||
label: 'password(密码)',
|
||||
},
|
||||
{
|
||||
value: 'geoPoint',
|
||||
label: 'geoPoint(地理位置)',
|
||||
},
|
||||
];
|
||||
|
||||
export const PropertySource: { label: string; value: string }[] = isNoCommunity
|
||||
? [
|
||||
{
|
||||
value: 'device',
|
||||
label: '设备',
|
||||
},
|
||||
{
|
||||
value: 'manual',
|
||||
label: '手动',
|
||||
},
|
||||
{
|
||||
value: 'rule',
|
||||
label: '规则',
|
||||
},
|
||||
]
|
||||
: [
|
||||
{
|
||||
value: 'device',
|
||||
label: '设备',
|
||||
},
|
||||
{
|
||||
value: 'manual',
|
||||
label: '手动',
|
||||
},
|
||||
];
|
||||
|
||||
export const FileTypeList: { label: string; value: string }[] = [
|
||||
{
|
||||
label: 'URL(链接)',
|
||||
value: 'url',
|
||||
},
|
||||
{
|
||||
label: 'Base64(Base64编码)',
|
||||
value: 'base64',
|
||||
},
|
||||
{
|
||||
label: 'binary',
|
||||
value: 'Binary(二进制)',
|
||||
},
|
||||
];
|
||||
|
||||
export const EventLevel: { label: string; value: string }[] = [
|
||||
{
|
||||
label: '普通',
|
||||
value: 'ordinary',
|
||||
},
|
||||
{
|
||||
label: '警告',
|
||||
value: 'warn',
|
||||
},
|
||||
{
|
||||
value: 'urgent',
|
||||
label: '紧急',
|
||||
},
|
||||
];
|
||||
|
||||
export const DateTypeList = [
|
||||
// {
|
||||
// label: 'String类型的UTC时间戳 (毫秒)',
|
||||
// value: 'string',
|
||||
// },
|
||||
{
|
||||
label: 'yyyy-MM-dd',
|
||||
value: 'yyyy-MM-dd',
|
||||
},
|
||||
{
|
||||
label: 'yyyy-MM-DD HH:mm:ss',
|
||||
value: 'yyyy-MM-DD HH:mm:ss',
|
||||
},
|
||||
// {
|
||||
// label: 'yyyy-MM-dd HH:mm:ss EE',
|
||||
// value: 'yyyy-MM-dd HH:mm:ss EE',
|
||||
// },
|
||||
// {
|
||||
// label: 'yyyy-MM-dd HH:mm:ss zzz',
|
||||
// value: 'yyyy-MM-dd HH:mm:ss zzz',
|
||||
// },
|
||||
];
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -1,7 +1,7 @@
|
|||
const configuration = {
|
||||
export const Configuration = {
|
||||
parserType: undefined,
|
||||
port: undefined,
|
||||
host: '0.0.0.0',
|
||||
host: undefined,
|
||||
publicPort: '',
|
||||
publicHost: '',
|
||||
remoteHost: '',
|
||||
|
@ -11,7 +11,7 @@ const configuration = {
|
|||
password: '',
|
||||
topicPrefix: '',
|
||||
maxMessageSize: '',
|
||||
certId: '',
|
||||
certId: undefined,
|
||||
privateKeyAlias: '',
|
||||
clientId: '',
|
||||
parserConfiguration: {
|
||||
|
@ -29,24 +29,21 @@ export const FormStates = {
|
|||
name: '',
|
||||
type: 'UDP',
|
||||
shareCluster: true,
|
||||
// configuration,
|
||||
description: '',
|
||||
};
|
||||
|
||||
export const FormStates2 = {
|
||||
serverId: undefined,
|
||||
configuration,
|
||||
configuration: Configuration,
|
||||
};
|
||||
|
||||
|
||||
|
||||
// export const DefaultCluster = {
|
||||
|
||||
// }
|
||||
export const DefaultFormStates = {
|
||||
...FormStates,
|
||||
cluster: [{ ...FormStates2, id: 1 }],
|
||||
};
|
||||
export const TCPList = [
|
||||
'TCP_SERVER',
|
||||
'WEB_SOCKET_SERVER',
|
||||
'HTTP_SERVER',
|
||||
'MQTT_SERVER',
|
||||
];
|
||||
export const UDPList = ['UDP', 'COAP_SERVER'];
|
||||
|
||||
const VisibleMost = [
|
||||
'COAP_SERVER',
|
||||
|
@ -63,6 +60,7 @@ export const VisibleData = {
|
|||
host: VisibleMost,
|
||||
publicPort: VisibleMost,
|
||||
publicHost: VisibleMost,
|
||||
serverId: ['MQTT_CLIENT'],
|
||||
remoteHost: ['MQTT_CLIENT'],
|
||||
remotePort: ['MQTT_CLIENT'],
|
||||
secure: ['TCP_SERVER', 'UDP', 'COAP_SERVER'],
|
||||
|
@ -114,3 +112,193 @@ export const Validator = {
|
|||
),
|
||||
regOnlyNumber: new RegExp(/^\d+$/),
|
||||
};
|
||||
|
||||
export const Rules = {
|
||||
name: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入名称',
|
||||
},
|
||||
{
|
||||
max: 64,
|
||||
message: '最大可输入64个字符',
|
||||
},
|
||||
],
|
||||
type: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择类型',
|
||||
},
|
||||
],
|
||||
shareCluster: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择集群',
|
||||
},
|
||||
],
|
||||
host: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择本地地址',
|
||||
},
|
||||
],
|
||||
port: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择本地端口',
|
||||
},
|
||||
],
|
||||
publicHost: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入公网地址',
|
||||
},
|
||||
{
|
||||
pattern: Validator.regIp || Validator.regDomain,
|
||||
message: '请输入IP或者域名',
|
||||
},
|
||||
],
|
||||
publicPort: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入公网端口',
|
||||
},
|
||||
{
|
||||
pattern: Validator.regOnlyNumber,
|
||||
message: '请输入1-65535之间的正整数',
|
||||
},
|
||||
],
|
||||
remoteHost: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入远程地址',
|
||||
},
|
||||
{
|
||||
pattern: Validator.regIp || Validator.regDomain,
|
||||
message: '请输入IP或者域名',
|
||||
},
|
||||
],
|
||||
remotePort: [
|
||||
{
|
||||
required: true,
|
||||
message: '输入远程端口',
|
||||
},
|
||||
{
|
||||
pattern: Validator.regOnlyNumber,
|
||||
message: '请输入1-65535之间的正整数',
|
||||
},
|
||||
],
|
||||
clientId: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入ClientId',
|
||||
},
|
||||
{
|
||||
max: 64,
|
||||
message: '最大可输入64个字符',
|
||||
},
|
||||
],
|
||||
username: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入用户名',
|
||||
},
|
||||
{
|
||||
max: 64,
|
||||
message: '最大可输入64个字符',
|
||||
},
|
||||
],
|
||||
password: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入密码',
|
||||
},
|
||||
{
|
||||
max: 64,
|
||||
message: '最大可输入64个字符',
|
||||
},
|
||||
],
|
||||
topicPrefix: [
|
||||
{
|
||||
max: 64,
|
||||
message: '最大可输入64个字符',
|
||||
},
|
||||
],
|
||||
maxMessageSize: [
|
||||
{
|
||||
max: 64,
|
||||
message: '最大可输入64个字符',
|
||||
},
|
||||
],
|
||||
secure: [
|
||||
{
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
certId: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择证书',
|
||||
},
|
||||
],
|
||||
privateKeyAlias: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入私钥别名',
|
||||
},
|
||||
{
|
||||
max: 64,
|
||||
message: '最大可输入64个字符',
|
||||
},
|
||||
],
|
||||
parserType: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择粘拆包规则',
|
||||
},
|
||||
],
|
||||
delimited: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入分隔符',
|
||||
},
|
||||
{
|
||||
max: 64,
|
||||
message: '最大可输入64个字符',
|
||||
},
|
||||
],
|
||||
lang: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择脚本语言',
|
||||
},
|
||||
{
|
||||
max: 64,
|
||||
message: '最大可输入64个字符',
|
||||
},
|
||||
],
|
||||
script: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入脚本',
|
||||
},
|
||||
],
|
||||
size: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入长度值',
|
||||
},
|
||||
],
|
||||
length: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择长度',
|
||||
},
|
||||
],
|
||||
offset: [
|
||||
{
|
||||
pattern: Validator.regOnlyNumber,
|
||||
message: '请输入0-65535之间的正整数',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
|
@ -1,31 +1,38 @@
|
|||
export interface Form2 {
|
||||
id: number;
|
||||
serverId: string | undefined;
|
||||
configuration: {
|
||||
parserType: undefined;
|
||||
port: undefined;
|
||||
host: string;
|
||||
publicPort: string;
|
||||
publicHost: string;
|
||||
remoteHost: string;
|
||||
remotePort: string;
|
||||
secure: boolean;
|
||||
username: string;
|
||||
password: string;
|
||||
topicPrefix: string;
|
||||
maxMessageSize: string;
|
||||
certId: string;
|
||||
privateKeyAlias: string;
|
||||
clientId: string;
|
||||
parserConfiguration: {
|
||||
delimited: string;
|
||||
lang: string;
|
||||
script: string;
|
||||
size: string;
|
||||
length: string;
|
||||
offset: string;
|
||||
little: string | boolean;
|
||||
};
|
||||
export interface ConfigurationType {
|
||||
parserType: string | undefined;
|
||||
port: string | undefined;
|
||||
host: string | undefined;;
|
||||
publicPort: string;
|
||||
publicHost: string;
|
||||
remoteHost: string;
|
||||
remotePort: string;
|
||||
secure: boolean;
|
||||
username: string;
|
||||
password: string;
|
||||
topicPrefix: string;
|
||||
maxMessageSize: string;
|
||||
certId: string | undefined;
|
||||
privateKeyAlias: string;
|
||||
clientId: string;
|
||||
parserConfiguration: {
|
||||
delimited: string;
|
||||
lang: string;
|
||||
script: string;
|
||||
size: string;
|
||||
length: string;
|
||||
offset: string;
|
||||
little: string | boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export interface FormDataType {
|
||||
name: string;
|
||||
type: string;
|
||||
shareCluster: boolean;
|
||||
description: string;
|
||||
}
|
||||
export interface FormData2Type {
|
||||
id?: number | string;
|
||||
serverId?: string | undefined;
|
||||
configuration: ConfigurationType;
|
||||
}
|
||||
|
|
|
@ -11,143 +11,312 @@
|
|||
>
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="24">
|
||||
<a-form-item label="名称" name="name" :rules=" [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入名称',
|
||||
},
|
||||
{
|
||||
max: 64,
|
||||
message: '最多输入64个字符',
|
||||
},
|
||||
]">
|
||||
<a-input placeholder="请输入名称" v-model:value="modelRef.name" />
|
||||
<a-form-item
|
||||
label="名称"
|
||||
name="name"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
message: '请输入名称',
|
||||
},
|
||||
{
|
||||
max: 64,
|
||||
message: '最多输入64个字符',
|
||||
},
|
||||
]"
|
||||
>
|
||||
<a-input
|
||||
placeholder="请输入名称"
|
||||
v-model:value="modelRef.name"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<a-form-item :name="['accessConfig', 'regionId']" :rules="[{
|
||||
required: true,
|
||||
message: '请选择服务地址',
|
||||
}]">
|
||||
<a-form-item
|
||||
:name="['accessConfig', 'regionId']"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
message: '请选择服务地址',
|
||||
},
|
||||
]"
|
||||
>
|
||||
<template #label>
|
||||
<span>
|
||||
服务地址
|
||||
<a-tooltip title="阿里云内部给每台机器设置的唯一编号">
|
||||
<a-tooltip
|
||||
title="阿里云内部给每台机器设置的唯一编号"
|
||||
>
|
||||
<AIcon
|
||||
type="QuestionCircleOutlined"
|
||||
style="margin-left: 2px;" />
|
||||
style="margin-left: 2px"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<a-select placeholder="请选择服务地址" v-model:value="modelRef.accessConfig.regionId" show-search :filter-option="filterOption" @blur="productChange">
|
||||
<a-select-option v-for="item in regionsList" :key="item.id" :value="item.id" :label="item.name">{{item.name}}</a-select-option>
|
||||
<a-select
|
||||
placeholder="请选择服务地址"
|
||||
v-model:value="
|
||||
modelRef.accessConfig.regionId
|
||||
"
|
||||
show-search
|
||||
:filter-option="filterOption"
|
||||
@blur="productChange"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="item in regionsList"
|
||||
:key="item.id"
|
||||
:value="item.id"
|
||||
:label="item.name"
|
||||
>{{ item.name }}</a-select-option
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<a-form-item :name="['accessConfig', 'instanceId']">
|
||||
<a-form-item
|
||||
:name="['accessConfig', 'instanceId']"
|
||||
>
|
||||
<template #label>
|
||||
<span>
|
||||
实例ID
|
||||
<a-tooltip title="阿里云物联网平台中的实例ID,没有则不填">
|
||||
<a-tooltip
|
||||
title="阿里云物联网平台中的实例ID,没有则不填"
|
||||
>
|
||||
<AIcon
|
||||
type="QuestionCircleOutlined"
|
||||
style="margin-left: 2px;" />
|
||||
style="margin-left: 2px"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<a-input placeholder="请输入实例ID" v-model:value="modelRef.accessConfig.instanceId" @blur="productChange" />
|
||||
<a-input
|
||||
placeholder="请输入实例ID"
|
||||
v-model:value="
|
||||
modelRef.accessConfig.instanceId
|
||||
"
|
||||
@blur="productChange"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<a-form-item :name="['accessConfig', 'accessKeyId']" :rules="[{
|
||||
required: true,
|
||||
message: '请输入accessKey',
|
||||
},
|
||||
{
|
||||
max: 64,
|
||||
message: '最多输入64个字符',
|
||||
},
|
||||
]">
|
||||
<a-form-item
|
||||
:name="['accessConfig', 'accessKeyId']"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
message: '请输入accessKey',
|
||||
},
|
||||
{
|
||||
max: 64,
|
||||
message: '最多输入64个字符',
|
||||
},
|
||||
]"
|
||||
>
|
||||
<template #label>
|
||||
<span>
|
||||
accessKey
|
||||
<a-tooltip title="用于程序通知方式调用云服务API的用户标识">
|
||||
<a-tooltip
|
||||
title="用于程序通知方式调用云服务API的用户标识"
|
||||
>
|
||||
<AIcon
|
||||
type="QuestionCircleOutlined"
|
||||
style="margin-left: 2px;" />
|
||||
style="margin-left: 2px"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<a-input placeholder="请输入accessKey" v-model:value="modelRef.accessConfig.accessKeyId" @blur="productChange" />
|
||||
<a-input
|
||||
placeholder="请输入accessKey"
|
||||
v-model:value="
|
||||
modelRef.accessConfig.accessKeyId
|
||||
"
|
||||
@blur="productChange"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<a-form-item :name="['accessConfig', 'accessSecret']" :rules="[{
|
||||
required: true,
|
||||
message: '请输入accessSecret',
|
||||
},
|
||||
{
|
||||
max: 64,
|
||||
message: '最多输入64个字符',
|
||||
},
|
||||
]">
|
||||
<a-form-item
|
||||
:name="['accessConfig', 'accessSecret']"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
message: '请输入accessSecret',
|
||||
},
|
||||
{
|
||||
max: 64,
|
||||
message: '最多输入64个字符',
|
||||
},
|
||||
]"
|
||||
>
|
||||
<template #label>
|
||||
<span>
|
||||
accessSecret
|
||||
<a-tooltip title="用于程序通知方式调用云服务费API的秘钥标识">
|
||||
<a-tooltip
|
||||
title="用于程序通知方式调用云服务费API的秘钥标识"
|
||||
>
|
||||
<AIcon
|
||||
type="QuestionCircleOutlined"
|
||||
style="margin-left: 2px;" />
|
||||
style="margin-left: 2px"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<a-input placeholder="请输入accessSecret" v-model:value="modelRef.accessConfig.accessSecret" @blur="productChange" />
|
||||
<a-input
|
||||
placeholder="请输入accessSecret"
|
||||
v-model:value="
|
||||
modelRef.accessConfig.accessSecret
|
||||
"
|
||||
@blur="productChange"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<a-form-item name="bridgeProductKey" :rules="{
|
||||
<a-form-item
|
||||
name="bridgeProductKey"
|
||||
:rules="{
|
||||
required: true,
|
||||
message: '请选择网桥产品',
|
||||
}">
|
||||
}"
|
||||
>
|
||||
<template #label>
|
||||
<span>
|
||||
网桥产品
|
||||
<a-tooltip title="物联网平台对应的阿里云产品">
|
||||
<a-tooltip
|
||||
title="物联网平台对应的阿里云产品"
|
||||
>
|
||||
<AIcon
|
||||
type="QuestionCircleOutlined"
|
||||
style="margin-left: 2px;" />
|
||||
style="margin-left: 2px"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<a-select placeholder="请选择网桥产品" v-model:value="modelRef.bridgeProductKey" show-search :filter-option="filterOption">
|
||||
<a-select-option v-for="item in aliyunProductList" :key="item.productKey" :value="item.productKey" :label="item.productName">{{item.productName}}</a-select-option>
|
||||
<a-select
|
||||
placeholder="请选择网桥产品"
|
||||
v-model:value="
|
||||
modelRef.bridgeProductKey
|
||||
"
|
||||
show-search
|
||||
:filter-option="filterOption"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="item in aliyunProductList"
|
||||
:key="item.productKey"
|
||||
:value="item.productKey"
|
||||
:label="item.productName"
|
||||
>{{
|
||||
item.productName
|
||||
}}</a-select-option
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<p>产品映射</p>
|
||||
<a-collapse v-if="modelRef.mappings.length" :activeKey="modelRef.mappings.map((_, _index) => _index)">
|
||||
<a-collapse-panel v-for="(item, index) in modelRef.mappings" :key="index" :header="item.productKey ? aliyunProductList.find(i => i.productKey === item.productKey)?.productName : `产品映射${index + 1}`">
|
||||
<template #extra><AIcon type="DeleteOutlined" @click="delItem(index)" /></template>
|
||||
<a-collapse
|
||||
v-if="modelRef.mappings.length"
|
||||
:activeKey="activeKey"
|
||||
@change="onCollChange"
|
||||
>
|
||||
<a-collapse-panel
|
||||
v-for="(
|
||||
item, index
|
||||
) in modelRef.mappings"
|
||||
:key="index"
|
||||
:header="
|
||||
item.productKey
|
||||
? aliyunProductList.find(
|
||||
(i) =>
|
||||
i.productKey ===
|
||||
item.productKey,
|
||||
)?.productName
|
||||
: `产品映射${index + 1}`
|
||||
"
|
||||
>
|
||||
<template #extra
|
||||
><AIcon
|
||||
type="DeleteOutlined"
|
||||
@click="delItem(index)"
|
||||
/></template>
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="阿里云产品" :name="['mappings', index, 'productKey']" :rules="{
|
||||
required: true,
|
||||
message: '请选择阿里云产品',
|
||||
}">
|
||||
<a-select placeholder="请选择阿里云产品" v-model:value="item.productKey" show-search :filter-option="filterOption">
|
||||
<a-select-option v-for="i in getAliyunProductList(item.productKey)" :key="i.productKey" :value="i.productKey" :label="i.productName">{{i.productName}}</a-select-option>
|
||||
<a-form-item
|
||||
label="阿里云产品"
|
||||
:name="[
|
||||
'mappings',
|
||||
index,
|
||||
'productKey',
|
||||
]"
|
||||
:rules="{
|
||||
required: true,
|
||||
message:
|
||||
'请选择阿里云产品',
|
||||
}"
|
||||
>
|
||||
<a-select
|
||||
placeholder="请选择阿里云产品"
|
||||
v-model:value="
|
||||
item.productKey
|
||||
"
|
||||
show-search
|
||||
:filter-option="
|
||||
filterOption
|
||||
"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="i in getAliyunProductList(
|
||||
item.productKey,
|
||||
)"
|
||||
:key="i.productKey"
|
||||
:value="
|
||||
i.productKey
|
||||
"
|
||||
:label="
|
||||
i.productName
|
||||
"
|
||||
>{{
|
||||
i.productName
|
||||
}}</a-select-option
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="平台产品" :name="['mappings', index, 'productId']" :rules="{
|
||||
required: true,
|
||||
message: '请选择平台产品',
|
||||
}">
|
||||
<a-select placeholder="请选择平台产品" v-model:value="item.productId" show-search :filter-option="filterOption">
|
||||
<a-select-option v-for="i in getPlatProduct(item.productId)" :key="i.id" :value="item.id" :label="i.name">{{i.name}}</a-select-option>
|
||||
<a-form-item
|
||||
label="平台产品"
|
||||
:name="[
|
||||
'mappings',
|
||||
index,
|
||||
'productId',
|
||||
]"
|
||||
:rules="{
|
||||
required: true,
|
||||
message:
|
||||
'请选择平台产品',
|
||||
}"
|
||||
>
|
||||
<a-select
|
||||
placeholder="请选择平台产品"
|
||||
v-model:value="
|
||||
item.productId
|
||||
"
|
||||
show-search
|
||||
:filter-option="
|
||||
filterOption
|
||||
"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="i in getPlatProduct(
|
||||
item.productId,
|
||||
)"
|
||||
:key="i.id"
|
||||
:value="item.id"
|
||||
:label="i.name"
|
||||
>{{
|
||||
i.name
|
||||
}}</a-select-option
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
|
@ -156,17 +325,26 @@
|
|||
</a-collapse>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<a-button type="dashed" style="width: 100%; margin-top: 10px" @click="addItem">
|
||||
<a-button
|
||||
type="dashed"
|
||||
style="width: 100%; margin-top: 10px"
|
||||
@click="addItem"
|
||||
>
|
||||
<AIcon
|
||||
type="PlusOutlined"
|
||||
style="margin-left: 2px;" />添加
|
||||
style="margin-left: 2px"
|
||||
/>添加
|
||||
</a-button>
|
||||
</a-col>
|
||||
<a-col :span="24" style="margin-top: 20px">
|
||||
<a-form-item label="说明" name="description" :rules="{
|
||||
max: 200,
|
||||
message: '最多输入200个字符',
|
||||
}">
|
||||
<a-form-item
|
||||
label="说明"
|
||||
name="description"
|
||||
:rules="{
|
||||
max: 200,
|
||||
message: '最多输入200个字符',
|
||||
}"
|
||||
>
|
||||
<a-textarea
|
||||
v-model:value="modelRef.description"
|
||||
placeholder="请输入说明"
|
||||
|
@ -178,7 +356,12 @@
|
|||
</a-row>
|
||||
</a-form>
|
||||
<div v-if="type === 'edit'">
|
||||
<a-button :loading="loading" type="primary" @click="saveBtn">保存</a-button>
|
||||
<a-button
|
||||
:loading="loading"
|
||||
type="primary"
|
||||
@click="saveBtn"
|
||||
>保存</a-button
|
||||
>
|
||||
</div>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
|
@ -190,8 +373,14 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import Doc from './doc.vue'
|
||||
import {savePatch, detail, getRegionsList, getAliyunProductsList, queryProductList } from '@/api/northbound/alicloud'
|
||||
import Doc from './doc.vue';
|
||||
import {
|
||||
savePatch,
|
||||
detail,
|
||||
getRegionsList,
|
||||
getAliyunProductsList,
|
||||
queryProductList,
|
||||
} from '@/api/northbound/alicloud';
|
||||
import _ from 'lodash';
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
|
@ -207,127 +396,153 @@ const modelRef = reactive({
|
|||
regionId: undefined,
|
||||
instanceId: undefined,
|
||||
accessKeyId: undefined,
|
||||
accessSecret: undefined
|
||||
accessSecret: undefined,
|
||||
},
|
||||
bridgeProductKey: undefined,
|
||||
bridgeProductName: undefined,
|
||||
mappings: [{
|
||||
productKey: undefined,
|
||||
productId: undefined,
|
||||
}],
|
||||
description: undefined
|
||||
mappings: [
|
||||
{
|
||||
productKey: undefined,
|
||||
productId: undefined,
|
||||
},
|
||||
],
|
||||
description: undefined,
|
||||
});
|
||||
|
||||
const addItem = () => {
|
||||
activeKey.value.push(String(modelRef.mappings.length));
|
||||
modelRef.mappings.push({
|
||||
productKey: undefined,
|
||||
productId: undefined,
|
||||
})
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const delItem = (index: number) => {
|
||||
modelRef.mappings.splice(index, 1)
|
||||
}
|
||||
modelRef.mappings.splice(index, 1);
|
||||
};
|
||||
|
||||
const productList = ref<Record<string, any>[]>([])
|
||||
const regionsList = ref<Record<string, any>[]>([])
|
||||
const aliyunProductList = ref<Record<string, any>[]>([])
|
||||
const loading = ref<boolean>(false)
|
||||
const type = ref<'edit' | 'view'>('view')
|
||||
const productList = ref<Record<string, any>[]>([]);
|
||||
const regionsList = ref<Record<string, any>[]>([]);
|
||||
const aliyunProductList = ref<Record<string, any>[]>([]);
|
||||
const loading = ref<boolean>(false);
|
||||
const type = ref<'edit' | 'view'>('edit');
|
||||
const activeKey = ref<string[]>(['0']);
|
||||
|
||||
const filterOption = (input: string, option: any) => {
|
||||
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
|
||||
};
|
||||
|
||||
const queryRegionsList = async () => {
|
||||
const resp = await getRegionsList()
|
||||
if(resp.status === 200){
|
||||
regionsList.value = resp.result as Record<string, any>[]
|
||||
const resp = await getRegionsList();
|
||||
if (resp.status === 200) {
|
||||
regionsList.value = resp.result as Record<string, any>[];
|
||||
}
|
||||
}
|
||||
};
|
||||
const getProduct = async () => {
|
||||
const resp = await queryProductList({
|
||||
paging: false,
|
||||
sorts: [{ name: 'createTime', order: 'desc' }],
|
||||
})
|
||||
if(resp.status === 200){
|
||||
productList.value = (resp?.result as Record<string, any>[])
|
||||
paging: false,
|
||||
sorts: [{ name: 'createTime', order: 'desc' }],
|
||||
});
|
||||
if (resp.status === 200) {
|
||||
productList.value = resp?.result as Record<string, any>[];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const getAliyunProduct = async (data: any) => {
|
||||
if(data.regionId && data.accessKeyId && data.accessSecret){
|
||||
const resp: any = await getAliyunProductsList(data)
|
||||
if(resp.status === 200){
|
||||
aliyunProductList.value = (resp?.result?.data as Record<string, any>[])
|
||||
if (data.regionId && data.accessKeyId && data.accessSecret) {
|
||||
const resp: any = await getAliyunProductsList(data);
|
||||
if (resp.status === 200) {
|
||||
aliyunProductList.value = resp?.result?.data as Record<
|
||||
string,
|
||||
any
|
||||
>[];
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const productChange = () => {
|
||||
const data = modelRef.accessConfig
|
||||
getAliyunProduct(data)
|
||||
}
|
||||
const data = modelRef.accessConfig;
|
||||
getAliyunProduct(data);
|
||||
};
|
||||
|
||||
const getPlatProduct = (val: string) => {
|
||||
const arr = modelRef.mappings.map(item => item?.productId) || []
|
||||
const checked = _.cloneDeep(arr)
|
||||
const _index = checked.findIndex(i => i === val)
|
||||
checked.splice(_index, 1)
|
||||
const list = productList.value.filter((i: any) => !checked.includes(i?.id as any))
|
||||
return list || []
|
||||
}
|
||||
const arr = modelRef.mappings.map((item) => item?.productId) || [];
|
||||
const checked = _.cloneDeep(arr);
|
||||
const _index = checked.findIndex((i) => i === val);
|
||||
checked.splice(_index, 1);
|
||||
const list = productList.value.filter(
|
||||
(i: any) => !checked.includes(i?.id as any),
|
||||
);
|
||||
return list || [];
|
||||
};
|
||||
|
||||
const getAliyunProductList = (val: string) => {
|
||||
const items = modelRef.mappings.map((item) => item?.productKey) || []
|
||||
const checked = _.cloneDeep(items)
|
||||
const _index = checked.findIndex(i => i === val)
|
||||
checked.splice(_index, 1)
|
||||
const list = aliyunProductList.value?.filter((i: any) => !checked.includes(i?.productKey as any))
|
||||
return list || []
|
||||
}
|
||||
|
||||
const saveBtn = async () => {
|
||||
const data = await formRef.value.validate()
|
||||
const product = (aliyunProductList.value || []).find(
|
||||
(item: any) => item?.bridgeProductKey === data?.bridgeProductKey,
|
||||
const items = modelRef.mappings.map((item) => item?.productKey) || [];
|
||||
const checked = _.cloneDeep(items);
|
||||
const _index = checked.findIndex((i) => i === val);
|
||||
checked.splice(_index, 1);
|
||||
const list = aliyunProductList.value?.filter(
|
||||
(i: any) => !checked.includes(i?.productKey as any),
|
||||
);
|
||||
data.bridgeProductName = product?.productName || '';
|
||||
loading.value = true;
|
||||
const resp = await savePatch(toRaw(modelRef));
|
||||
loading.value = false;
|
||||
if (resp.status === 200) {
|
||||
message.success('操作成功!');
|
||||
formRef.value.resetFields();
|
||||
router.push('/iot/northbound/AliCloud/');
|
||||
}
|
||||
}
|
||||
return list || [];
|
||||
};
|
||||
|
||||
const onCollChange = (_key: string[]) => {
|
||||
activeKey.value = _key;
|
||||
};
|
||||
|
||||
const saveBtn = () => {
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(async (data: any) => {
|
||||
const product = (aliyunProductList.value || []).find(
|
||||
(item: any) =>
|
||||
item?.bridgeProductKey === data?.bridgeProductKey,
|
||||
);
|
||||
data.bridgeProductName = product?.productName || '';
|
||||
loading.value = true;
|
||||
const resp = await savePatch(toRaw(modelRef));
|
||||
loading.value = false;
|
||||
if (resp.status === 200) {
|
||||
message.success('操作成功!');
|
||||
formRef.value.resetFields();
|
||||
router.push('/iot/northbound/AliCloud');
|
||||
}
|
||||
})
|
||||
.catch((err: any) => {
|
||||
const _arr = err.errorFields.map((i: any) => i.name);
|
||||
_arr.map((item: string | any[]) => {
|
||||
if (item.length === 3 && !activeKey.value.includes(item[1])) {
|
||||
activeKey.value.push(item[1]);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
watch(
|
||||
() => route.params?.id,
|
||||
async (newId) => {
|
||||
if(newId){
|
||||
queryRegionsList()
|
||||
getProduct()
|
||||
if (newId) {
|
||||
queryRegionsList();
|
||||
getProduct();
|
||||
if (newId === ':id' || !newId) return;
|
||||
const resp = await detail(newId as string)
|
||||
const resp = await detail(newId as string);
|
||||
const _data: any = resp.result;
|
||||
if (_data) {
|
||||
getAliyunProduct(_data?.accessConfig)
|
||||
getAliyunProduct(_data?.accessConfig);
|
||||
}
|
||||
Object.assign(modelRef, _data)
|
||||
Object.assign(modelRef, _data);
|
||||
}
|
||||
},
|
||||
{immediate: true, deep: true}
|
||||
{ immediate: true, deep: true },
|
||||
);
|
||||
|
||||
|
||||
watch(
|
||||
() => route.query.type,
|
||||
(newVal) => {
|
||||
if(newVal){
|
||||
type.value = newVal as 'edit' | 'view'
|
||||
if (newVal) {
|
||||
type.value = newVal as 'edit' | 'view';
|
||||
}
|
||||
},
|
||||
{immediate: true, deep: true}
|
||||
{ immediate: true, deep: true },
|
||||
);
|
||||
</script>
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<page-container>
|
||||
<Search :columns="columns" target="northbound-dueros" :params="params" />
|
||||
<Search :columns="columns" target="northbound-dueros" @search="handleSearch" />
|
||||
<JTable
|
||||
ref="instanceRef"
|
||||
:columns="columns"
|
||||
|
@ -166,11 +166,17 @@ const columns = [
|
|||
title: '名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
search: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '网桥产品',
|
||||
dataIndex: 'bridgeProductName',
|
||||
key: 'bridgeProductName',
|
||||
search: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '说明',
|
||||
|
@ -182,6 +188,13 @@ const columns = [
|
|||
dataIndex: 'state',
|
||||
key: 'state',
|
||||
scopedSlots: true,
|
||||
search: {
|
||||
type: 'select',
|
||||
options: [
|
||||
{ label: '正常', value: 'enabled' },
|
||||
{ label: '禁用', value: 'disabled' }
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
|
@ -303,4 +316,8 @@ const getActions = (
|
|||
return actions.filter((i: ActionsType) => i.key !== 'view');
|
||||
return actions;
|
||||
};
|
||||
|
||||
const handleSearch = (_params: any) => {
|
||||
params.value = _params
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -127,10 +127,15 @@ const funcChange = (val: string) => {
|
|||
const saveBtn = () => new Promise((resolve) => {
|
||||
formRef.value.validate()
|
||||
.then(() => {
|
||||
resolve(toRaw(modelRef))
|
||||
const _arr = toRaw(modelRef).value?.message?.inputs || []
|
||||
if(_arr.length && !_arr.every((_a: any) => _a.value)){
|
||||
resolve(false)
|
||||
} else {
|
||||
resolve(toRaw(modelRef))
|
||||
}
|
||||
})
|
||||
.catch((err: any) => {
|
||||
resolve(false)
|
||||
resolve(err)
|
||||
});
|
||||
})
|
||||
|
||||
|
|
|
@ -11,98 +11,247 @@
|
|||
>
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="24">
|
||||
<a-form-item label="名称" name="name" :rules=" [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入名称',
|
||||
},
|
||||
{
|
||||
max: 64,
|
||||
message: '最多输入64个字符',
|
||||
},
|
||||
]">
|
||||
<a-input placeholder="请输入名称" v-model:value="modelRef.name" />
|
||||
<a-form-item
|
||||
label="名称"
|
||||
name="name"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
message: '请输入名称',
|
||||
},
|
||||
{
|
||||
max: 64,
|
||||
message: '最多输入64个字符',
|
||||
},
|
||||
]"
|
||||
>
|
||||
<a-input
|
||||
placeholder="请输入名称"
|
||||
v-model:value="modelRef.name"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="产品" name="id" :rules="[{
|
||||
required: true,
|
||||
message: '请选择产品',
|
||||
}]">
|
||||
<a-select :disabled="modelRef.id !== ':id'" placeholder="请选择产品" v-model:value="modelRef.id" show-search :filter-option="filterOption" @change="productChange">
|
||||
<a-select-option v-for="item in productList" :key="item.id" :value="item.id" :label="item.name">{{item.name}}</a-select-option>
|
||||
<a-form-item
|
||||
label="产品"
|
||||
name="id"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
message: '请选择产品',
|
||||
},
|
||||
]"
|
||||
>
|
||||
<a-select
|
||||
:disabled="type !== 'edit' && modelRef.id && modelRef.id !== ':id'"
|
||||
placeholder="请选择产品"
|
||||
v-model:value="modelRef.id"
|
||||
show-search
|
||||
:filter-option="filterOption"
|
||||
@change="productChange"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="item in productList"
|
||||
:key="item.id"
|
||||
:value="item.id"
|
||||
:label="item.name"
|
||||
>{{ item.name }}</a-select-option
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item name="applianceType" :rules="{
|
||||
<a-form-item
|
||||
name="applianceType"
|
||||
:rules="{
|
||||
required: true,
|
||||
message: '请选择设备类型',
|
||||
}">
|
||||
}"
|
||||
>
|
||||
<template #label>
|
||||
<span>
|
||||
设备类型
|
||||
<a-tooltip title="DuerOS平台拟定的规范">
|
||||
<a-tooltip
|
||||
title="DuerOS平台拟定的规范"
|
||||
>
|
||||
<AIcon
|
||||
type="QuestionCircleOutlined"
|
||||
style="margin-left: 2px;" />
|
||||
style="margin-left: 2px"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<a-select placeholder="请选择设备类型" v-model:value="modelRef.applianceType" show-search :filter-option="filterOption" @change="typeChange">
|
||||
<a-select-option v-for="item in typeList" :key="item.id" :value="item.id" :label="item.name">{{item.name}}</a-select-option>
|
||||
<a-select
|
||||
placeholder="请选择设备类型"
|
||||
v-model:value="modelRef.applianceType"
|
||||
show-search
|
||||
:filter-option="filterOption"
|
||||
@change="typeChange"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="item in typeList"
|
||||
:key="item.id"
|
||||
:value="item.id"
|
||||
:label="item.name"
|
||||
>{{ item.name }}</a-select-option
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item name="productName" v-show="false" label="产品名称">
|
||||
<a-input v-model:value="modelRef.productName" />
|
||||
<a-form-item
|
||||
name="productName"
|
||||
v-show="false"
|
||||
label="产品名称"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="modelRef.productName"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<p>动作映射</p>
|
||||
<a-collapse v-if="modelRef.actionMappings.length" :activeKey="modelRef.actionMappings.map((_, _index) => _index)">
|
||||
<a-collapse-panel v-for="(item, index) in modelRef.actionMappings" :key="index" :header="item.action ? getTypesActions(item.action).find(i => i.id === item.action)?.name : `动作映射${index + 1}`">
|
||||
<template #extra><AIcon type="DeleteOutlined" @click="delItem(index)" /></template>
|
||||
<a-collapse
|
||||
v-if="modelRef.actionMappings.length"
|
||||
:activeKey="actionActiveKey"
|
||||
@change="onActionCollChange"
|
||||
>
|
||||
<a-collapse-panel
|
||||
v-for="(
|
||||
item, index
|
||||
) in modelRef.actionMappings"
|
||||
:key="index"
|
||||
:header="
|
||||
item.action
|
||||
? getTypesActions(
|
||||
item.action,
|
||||
).find(
|
||||
(i) =>
|
||||
i.id === item.action,
|
||||
)?.name
|
||||
: `动作映射${index + 1}`
|
||||
"
|
||||
>
|
||||
<template #extra
|
||||
><AIcon
|
||||
type="DeleteOutlined"
|
||||
@click="delItem(index)"
|
||||
/></template>
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="12">
|
||||
<a-form-item :name="['actionMappings', index, 'action']" :rules="{
|
||||
required: true,
|
||||
message: '请选择动作',
|
||||
}">
|
||||
<a-form-item
|
||||
:name="[
|
||||
'actionMappings',
|
||||
index,
|
||||
'action',
|
||||
]"
|
||||
:rules="{
|
||||
required: true,
|
||||
message: '请选择动作',
|
||||
}"
|
||||
>
|
||||
<template #label>
|
||||
<span>
|
||||
动作
|
||||
<a-tooltip title="DuerOS平台拟定的设备类型具有的相关动作">
|
||||
<AIcon type="QuestionCircleOutlined" />
|
||||
<a-tooltip
|
||||
title="DuerOS平台拟定的设备类型具有的相关动作"
|
||||
>
|
||||
<AIcon
|
||||
type="QuestionCircleOutlined"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<a-select placeholder="请选择动作" v-model:value="item.action" show-search :filter-option="filterOption">
|
||||
<a-select-option v-for="i in getTypesActions(item.action)" :key="i.id" :value="i.id" :label="i.name">{{i.name}}</a-select-option>
|
||||
<a-select
|
||||
placeholder="请选择动作"
|
||||
v-model:value="
|
||||
item.action
|
||||
"
|
||||
show-search
|
||||
:filter-option="
|
||||
filterOption
|
||||
"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="i in getTypesActions(
|
||||
item.action,
|
||||
)"
|
||||
:key="i.id"
|
||||
:value="i.id"
|
||||
:label="i.name"
|
||||
>{{
|
||||
i.name
|
||||
}}</a-select-option
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item :name="['actionMappings', index, 'actionType']" :rules="{
|
||||
required: true,
|
||||
message: '请选择操作',
|
||||
}">
|
||||
<a-form-item
|
||||
:name="[
|
||||
'actionMappings',
|
||||
index,
|
||||
'actionType',
|
||||
]"
|
||||
:rules="{
|
||||
required: true,
|
||||
message: '请选择操作',
|
||||
}"
|
||||
>
|
||||
<template #label>
|
||||
<span>
|
||||
操作
|
||||
<a-tooltip title="映射物联网平台中所选产品具备的动作">
|
||||
<AIcon type="QuestionCircleOutlined" />
|
||||
<a-tooltip
|
||||
title="映射物联网平台中所选产品具备的动作"
|
||||
>
|
||||
<AIcon
|
||||
type="QuestionCircleOutlined"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<a-select placeholder="请选择操作" v-model:value="item.actionType" show-search :filter-option="filterOption">
|
||||
<a-select-option value="command">下发指令</a-select-option>
|
||||
<a-select-option value="latestData">获取历史数据</a-select-option>
|
||||
<a-select
|
||||
placeholder="请选择操作"
|
||||
v-model:value="
|
||||
item.actionType
|
||||
"
|
||||
show-search
|
||||
:filter-option="
|
||||
filterOption
|
||||
"
|
||||
>
|
||||
<a-select-option
|
||||
value="command"
|
||||
>下发指令</a-select-option
|
||||
>
|
||||
<a-select-option
|
||||
value="latestData"
|
||||
>获取历史数据</a-select-option
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="24" v-if="item.actionType">
|
||||
<a-form-item :name="['actionMappings', index, 'command']">
|
||||
<Command ref="command" :metadata="findProductMetadata" v-model:modelValue="item.command" :actionType="item.actionType" />
|
||||
<a-col
|
||||
:span="24"
|
||||
v-if="item.actionType"
|
||||
>
|
||||
<a-form-item
|
||||
:name="[
|
||||
'actionMappings',
|
||||
index,
|
||||
'command',
|
||||
]"
|
||||
>
|
||||
<Command
|
||||
ref="command"
|
||||
:metadata="
|
||||
findProductMetadata
|
||||
"
|
||||
v-model:modelValue="
|
||||
item.command
|
||||
"
|
||||
:actionType="
|
||||
item.actionType
|
||||
"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
@ -110,35 +259,118 @@
|
|||
</a-collapse>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<a-button type="dashed" style="width: 100%; margin-top: 10px" @click="addItem">
|
||||
<a-button
|
||||
type="dashed"
|
||||
style="width: 100%; margin-top: 10px"
|
||||
@click="addItem"
|
||||
>
|
||||
<AIcon
|
||||
type="PlusOutlined"
|
||||
style="margin-left: 2px;" />新增动作
|
||||
style="margin-left: 2px"
|
||||
/>新增动作
|
||||
</a-button>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<p style="margin-top: 20px">属性映射</p>
|
||||
<a-collapse v-if="modelRef.propertyMappings.length" :activeKey="modelRef.propertyMappings.map((_, _index) => _index)">
|
||||
<a-collapse-panel v-for="(item, index) in modelRef.propertyMappings" :key="index" :header="item.source ? getDuerOSProperties(item.source).find(i => i.id === item.source)?.name : `属性映射${index + 1}`">
|
||||
<template #extra><AIcon type="DeleteOutlined" @click="delPropertyItem(index)" /></template>
|
||||
<a-collapse
|
||||
v-if="modelRef.propertyMappings.length"
|
||||
:activeKey="propertyActiveKey"
|
||||
@change="onPropertyCollChange"
|
||||
>
|
||||
<a-collapse-panel
|
||||
v-for="(
|
||||
item, index
|
||||
) in modelRef.propertyMappings"
|
||||
:key="index"
|
||||
:header="
|
||||
item.source
|
||||
? getDuerOSProperties(
|
||||
item.source,
|
||||
).find(
|
||||
(i) =>
|
||||
i.id === item.source,
|
||||
)?.name
|
||||
: `属性映射${index + 1}`
|
||||
"
|
||||
>
|
||||
<template #extra
|
||||
><AIcon
|
||||
type="DeleteOutlined"
|
||||
@click="delPropertyItem(index)"
|
||||
/></template>
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="DuerOS属性" :name="['propertyMappings', index, 'source']" :rules="{
|
||||
required: true,
|
||||
message: '请选择DuerOS属性',
|
||||
}">
|
||||
<a-select placeholder="请选择DuerOS属性" v-model:value="item.source" show-search :filter-option="filterOption">
|
||||
<a-select-option v-for="i in getDuerOSProperties(item.source)" :key="i.id" :value="i.id">{{i.name}}</a-select-option>
|
||||
<a-form-item
|
||||
label="DuerOS属性"
|
||||
:name="[
|
||||
'propertyMappings',
|
||||
index,
|
||||
'source',
|
||||
]"
|
||||
:rules="{
|
||||
required: true,
|
||||
message:
|
||||
'请选择DuerOS属性',
|
||||
}"
|
||||
>
|
||||
<a-select
|
||||
placeholder="请选择DuerOS属性"
|
||||
v-model:value="
|
||||
item.source
|
||||
"
|
||||
show-search
|
||||
:filter-option="
|
||||
filterOption
|
||||
"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="i in getDuerOSProperties(
|
||||
item.source,
|
||||
)"
|
||||
:key="i.id"
|
||||
:value="i.id"
|
||||
>{{
|
||||
i.name
|
||||
}}</a-select-option
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="平台属性" :name="['propertyMappings', index, 'target']" :rules="{
|
||||
required: true,
|
||||
message: '请选择平台属性',
|
||||
}">
|
||||
<a-select placeholder="请选择平台属性" v-model:value="item.target" mode="tags" show-search :filter-option="filterOption">
|
||||
<a-select-option v-for="i in getProductProperties(item.target)" :key="i.id" :value="item.id">{{i.name}}</a-select-option>
|
||||
<a-form-item
|
||||
label="平台属性"
|
||||
:name="[
|
||||
'propertyMappings',
|
||||
index,
|
||||
'target',
|
||||
]"
|
||||
:rules="{
|
||||
required: true,
|
||||
message:
|
||||
'请选择平台属性',
|
||||
}"
|
||||
>
|
||||
<a-select
|
||||
placeholder="请选择平台属性"
|
||||
v-model:value="
|
||||
item.target
|
||||
"
|
||||
mode="tags"
|
||||
show-search
|
||||
:filter-option="
|
||||
filterOption
|
||||
"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="i in getProductProperties(
|
||||
item.target,
|
||||
)"
|
||||
:key="i.id"
|
||||
:value="item.id"
|
||||
>{{
|
||||
i.name
|
||||
}}</a-select-option
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
|
@ -147,17 +379,26 @@
|
|||
</a-collapse>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<a-button type="dashed" style="width: 100%; margin-top: 10px" @click="addPropertyItem">
|
||||
<a-button
|
||||
type="dashed"
|
||||
style="width: 100%; margin-top: 10px"
|
||||
@click="addPropertyItem"
|
||||
>
|
||||
<AIcon
|
||||
type="PlusOutlined"
|
||||
style="margin-left: 2px;" />新增属性
|
||||
style="margin-left: 2px"
|
||||
/>新增属性
|
||||
</a-button>
|
||||
</a-col>
|
||||
<a-col :span="24" style="margin-top: 20px">
|
||||
<a-form-item label="说明" name="description" :rules="{
|
||||
max: 200,
|
||||
message: '最多输入200个字符',
|
||||
}">
|
||||
<a-form-item
|
||||
label="说明"
|
||||
name="description"
|
||||
:rules="{
|
||||
max: 200,
|
||||
message: '最多输入200个字符',
|
||||
}"
|
||||
>
|
||||
<a-textarea
|
||||
v-model:value="modelRef.description"
|
||||
placeholder="请输入说明"
|
||||
|
@ -169,7 +410,12 @@
|
|||
</a-row>
|
||||
</a-form>
|
||||
<div v-if="type === 'edit'">
|
||||
<a-button :loading="loading" type="primary" @click="saveBtn">保存</a-button>
|
||||
<a-button
|
||||
:loading="loading"
|
||||
type="primary"
|
||||
@click="saveBtn"
|
||||
>保存</a-button
|
||||
>
|
||||
</div>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
|
@ -181,9 +427,14 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import Doc from './doc.vue'
|
||||
import Command from './command/index.vue'
|
||||
import { queryProductList, queryTypes, savePatch, detail } from '@/api/northbound/dueros'
|
||||
import Doc from './doc.vue';
|
||||
import Command from './command/index.vue';
|
||||
import {
|
||||
queryProductList,
|
||||
queryTypes,
|
||||
savePatch,
|
||||
detail,
|
||||
} from '@/api/northbound/dueros';
|
||||
import _ from 'lodash';
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
|
@ -197,26 +448,51 @@ const modelRef = reactive({
|
|||
name: undefined,
|
||||
applianceType: undefined,
|
||||
productName: undefined,
|
||||
actionMappings: [{
|
||||
actionType: undefined,
|
||||
action: undefined,
|
||||
command: {
|
||||
messageType: undefined,
|
||||
message: {
|
||||
properties: undefined,
|
||||
functionId: undefined,
|
||||
inputs: []
|
||||
}
|
||||
}
|
||||
}],
|
||||
propertyMappings: [{
|
||||
source: undefined,
|
||||
target: []
|
||||
}],
|
||||
description: undefined
|
||||
actionMappings: [
|
||||
{
|
||||
actionType: undefined,
|
||||
action: undefined,
|
||||
command: {
|
||||
messageType: undefined,
|
||||
message: {
|
||||
properties: undefined,
|
||||
functionId: undefined,
|
||||
inputs: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
propertyMappings: [
|
||||
{
|
||||
source: undefined,
|
||||
target: [],
|
||||
},
|
||||
],
|
||||
description: undefined,
|
||||
});
|
||||
|
||||
const productList = ref<Record<string, any>[]>([]);
|
||||
const typeList = ref<Record<string, any>[]>([]);
|
||||
const command = ref([]);
|
||||
const loading = ref<boolean>(false);
|
||||
const type = ref<'edit' | 'view'>('edit');
|
||||
const actionActiveKey = ref<string[]>(['0']);
|
||||
const propertyActiveKey = ref<string[]>(['0']);
|
||||
|
||||
const onPropertyCollChange = (_key: string[]) => {
|
||||
propertyActiveKey.value = _key;
|
||||
};
|
||||
|
||||
const onActionCollChange = (_key: string[]) => {
|
||||
actionActiveKey.value = _key;
|
||||
};
|
||||
|
||||
const filterOption = (input: string, option: any) => {
|
||||
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
|
||||
};
|
||||
|
||||
const addItem = () => {
|
||||
actionActiveKey.value.push(String(modelRef.actionMappings.length));
|
||||
modelRef.actionMappings.push({
|
||||
actionType: undefined,
|
||||
action: undefined,
|
||||
|
@ -225,161 +501,182 @@ const addItem = () => {
|
|||
message: {
|
||||
properties: undefined,
|
||||
functionId: undefined,
|
||||
inputs: []
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const productList = ref<Record<string, any>[]>([])
|
||||
const typeList = ref<Record<string, any>[]>([])
|
||||
const command = ref([])
|
||||
const loading = ref<boolean>(false)
|
||||
const type = ref<'edit' | 'view'>('view')
|
||||
|
||||
const filterOption = (input: string, option: any) => {
|
||||
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
|
||||
inputs: [],
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const delItem = (index: number) => {
|
||||
modelRef.actionMappings.splice(index, 1)
|
||||
}
|
||||
modelRef.actionMappings.splice(index, 1);
|
||||
};
|
||||
|
||||
const addPropertyItem = () => {
|
||||
propertyActiveKey.value.push(String(modelRef.propertyMappings.length));
|
||||
modelRef.propertyMappings.push({
|
||||
source: undefined,
|
||||
target: []
|
||||
})
|
||||
}
|
||||
target: [],
|
||||
});
|
||||
};
|
||||
|
||||
const delPropertyItem = (index: number) => {
|
||||
modelRef.propertyMappings.splice(index, 1)
|
||||
}
|
||||
modelRef.propertyMappings.splice(index, 1);
|
||||
};
|
||||
|
||||
const productChange = (value: string) => {
|
||||
modelRef.propertyMappings = modelRef.propertyMappings.map(item => {
|
||||
return {source: item.source, target: []}
|
||||
})
|
||||
const item = productList.value.find(item => item.id === value)
|
||||
if(item){
|
||||
modelRef.productName = item.name
|
||||
modelRef.propertyMappings = modelRef.propertyMappings.map((item) => {
|
||||
return { source: item.source, target: [] };
|
||||
});
|
||||
const item = productList.value.find((item) => item.id === value);
|
||||
if (item) {
|
||||
modelRef.productName = item.name;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const typeChange = () => {
|
||||
modelRef.propertyMappings = modelRef.propertyMappings.map(item => {
|
||||
return {source: undefined, target: item.target}
|
||||
})
|
||||
modelRef.actionMappings = modelRef.actionMappings.map(item => {
|
||||
return {...item, action: undefined}
|
||||
})
|
||||
}
|
||||
modelRef.propertyMappings = modelRef.propertyMappings.map((item) => {
|
||||
return { source: undefined, target: item.target };
|
||||
});
|
||||
modelRef.actionMappings = modelRef.actionMappings.map((item) => {
|
||||
return { ...item, action: undefined };
|
||||
});
|
||||
};
|
||||
|
||||
const findApplianceType = computed(() => {
|
||||
if(!modelRef.applianceType) return
|
||||
return typeList.value.find(item => item.id === modelRef.applianceType)
|
||||
})
|
||||
if (!modelRef.applianceType) return;
|
||||
return typeList.value.find((item) => item.id === modelRef.applianceType);
|
||||
});
|
||||
|
||||
const findProductMetadata = computed(() => {
|
||||
if(!modelRef.id) return
|
||||
const _product = productList.value?.find((item: any) => item.id === modelRef.id)
|
||||
return _product?.metadata && JSON.parse(_product.metadata || '{}')
|
||||
})
|
||||
if (!modelRef.id) return;
|
||||
const _product = productList.value?.find(
|
||||
(item: any) => item.id === modelRef.id,
|
||||
);
|
||||
return _product?.metadata && JSON.parse(_product.metadata || '{}');
|
||||
});
|
||||
|
||||
// 查询产品列表
|
||||
const getProduct = async (id?: string) => {
|
||||
const resp = await queryProductList(id)
|
||||
if(resp.status === 200){
|
||||
productList.value = (resp?.result as Record<string, any>[])
|
||||
const resp = await queryProductList(id);
|
||||
if (resp.status === 200) {
|
||||
productList.value = resp?.result as Record<string, any>[];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const getTypes = async () => {
|
||||
const resp = await queryTypes()
|
||||
if(resp.status === 200){
|
||||
typeList.value = (resp?.result as Record<string, any>[])
|
||||
const resp = await queryTypes();
|
||||
if (resp.status === 200) {
|
||||
typeList.value = resp?.result as Record<string, any>[];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const getDuerOSProperties = (val: string) => {
|
||||
const arr = modelRef.propertyMappings.map(item => item?.source) || []
|
||||
const checked = _.cloneDeep(arr)
|
||||
const _index = checked.findIndex(i => i === val)
|
||||
const arr = modelRef.propertyMappings.map((item) => item?.source) || [];
|
||||
const checked = _.cloneDeep(arr);
|
||||
const _index = checked.findIndex((i) => i === val);
|
||||
// 去掉重复的
|
||||
checked.splice(_index, 1)
|
||||
checked.splice(_index, 1);
|
||||
const targetList = findApplianceType.value?.properties;
|
||||
const list = targetList?.filter((i: {id: string}) => !checked.includes(i?.id as any))
|
||||
return list || []
|
||||
}
|
||||
const list = targetList?.filter(
|
||||
(i: { id: string }) => !checked.includes(i?.id as any),
|
||||
);
|
||||
return list || [];
|
||||
};
|
||||
|
||||
const getProductProperties = (val: string[]) => {
|
||||
const items = modelRef.propertyMappings.map((item: {target: string[]}) => item?.target.map(j => j)) || []
|
||||
const checked = _.flatMap(items)
|
||||
const _checked: any[] = []
|
||||
checked.map(_item => {
|
||||
if(!val.includes(_item)){
|
||||
_checked.push(_item)
|
||||
const items =
|
||||
modelRef.propertyMappings.map((item: { target: string[] }) =>
|
||||
item?.target.map((j) => j),
|
||||
) || [];
|
||||
const checked = _.flatMap(items);
|
||||
const _checked: any[] = [];
|
||||
checked.map((_item) => {
|
||||
if (!val.includes(_item)) {
|
||||
_checked.push(_item);
|
||||
}
|
||||
})
|
||||
const sourceList = findProductMetadata.value?.properties
|
||||
const list = sourceList?.filter((i: { id: string }) => !_checked.includes(i.id))
|
||||
return list || []
|
||||
}
|
||||
});
|
||||
const sourceList = findProductMetadata.value?.properties;
|
||||
const list = sourceList?.filter(
|
||||
(i: { id: string }) => !_checked.includes(i.id),
|
||||
);
|
||||
return list || [];
|
||||
};
|
||||
|
||||
const getTypesActions = (val: string) => {
|
||||
const items = modelRef.actionMappings.map((item) => item?.action) || []
|
||||
const checked = _.cloneDeep(items)
|
||||
const _index = checked.findIndex(i => i === val)
|
||||
checked.splice(_index, 1)
|
||||
const actionsList = findApplianceType.value?.actions || []
|
||||
const list = actionsList?.filter((i: { id: string, name: string }) => !checked.includes(i?.id as any))
|
||||
return list || []
|
||||
}
|
||||
const items = modelRef.actionMappings.map((item) => item?.action) || [];
|
||||
const checked = _.cloneDeep(items);
|
||||
const _index = checked.findIndex((i) => i === val);
|
||||
checked.splice(_index, 1);
|
||||
const actionsList = findApplianceType.value?.actions || [];
|
||||
const list = actionsList?.filter(
|
||||
(i: { id: string; name: string }) => !checked.includes(i?.id as any),
|
||||
);
|
||||
return list || [];
|
||||
};
|
||||
const saveBtn = async () => {
|
||||
const tasks = []
|
||||
for(let i = 0; i < command.value.length; i++){
|
||||
const tasks: any[] = [];
|
||||
for (let i = 0; i < command.value.length; i++) {
|
||||
const res = await (command.value[i] as any)?.saveBtn()
|
||||
tasks.push(res)
|
||||
if(!res) break
|
||||
}
|
||||
const data = await formRef.value.validate()
|
||||
if(tasks.every(item => item) && data){
|
||||
loading.value = true;
|
||||
const resp = await savePatch(toRaw(modelRef));
|
||||
loading.value = false;
|
||||
if (resp.status === 200) {
|
||||
message.success('操作成功!');
|
||||
formRef.value.resetFields();
|
||||
router.push('/iot/northbound/DuerOS/');
|
||||
if(!res || (res?.errorFields && res.errorFields.length)) {
|
||||
actionActiveKey.value.push(String(i));
|
||||
tasks.push(false);
|
||||
} else {
|
||||
tasks.push(res);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(async (data: any) => {
|
||||
if (tasks.every((item) => item) && data) {
|
||||
loading.value = true;
|
||||
const resp = await savePatch(toRaw(modelRef));
|
||||
loading.value = false;
|
||||
if (resp.status === 200) {
|
||||
message.success('操作成功!');
|
||||
formRef.value.resetFields();
|
||||
router.push('/iot/northbound/DuerOS/');
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch((err: any) => {
|
||||
const _arr = err.errorFields.map((item: any) => item.name);
|
||||
_arr.map((item: string | any[]) => {
|
||||
if (item.length >= 3) {
|
||||
if(item[0] === 'propertyMappings' && !propertyActiveKey.value.includes(item[1])){
|
||||
propertyActiveKey.value.push(item[1]);
|
||||
}
|
||||
if(item[0] === 'actionMappings' && !actionActiveKey.value.includes(item[1])){
|
||||
actionActiveKey.value.push(item[1]);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
watch(
|
||||
() => route.params?.id,
|
||||
async (newId) => {
|
||||
if(newId){
|
||||
getProduct(newId as string)
|
||||
getTypes()
|
||||
if (newId) {
|
||||
getProduct(newId as string);
|
||||
getTypes();
|
||||
if (newId === ':id') return;
|
||||
const resp = await detail(newId as string)
|
||||
const resp = await detail(newId as string);
|
||||
const _data: any = resp.result;
|
||||
if (_data) {
|
||||
_data.applianceType = _data?.applianceType?.value;
|
||||
_data.applianceType = _data?.applianceType?.value;
|
||||
}
|
||||
Object.assign(modelRef, _data)
|
||||
Object.assign(modelRef, _data);
|
||||
}
|
||||
},
|
||||
{immediate: true, deep: true}
|
||||
{ immediate: true, deep: true },
|
||||
);
|
||||
|
||||
watch(
|
||||
() => route.query.type,
|
||||
(newVal) => {
|
||||
if(newVal){
|
||||
type.value = newVal as 'edit' | 'view'
|
||||
if (newVal) {
|
||||
type.value = newVal as 'edit' | 'view';
|
||||
}
|
||||
},
|
||||
{immediate: true, deep: true}
|
||||
{ immediate: true, deep: true },
|
||||
);
|
||||
</script>
|
|
@ -1,6 +1,10 @@
|
|||
<template>
|
||||
<page-container>
|
||||
<Search :columns="columns" target="northbound-dueros" :params="params" />
|
||||
<Search
|
||||
:columns="columns"
|
||||
target="northbound-dueros"
|
||||
@search="handleSearch"
|
||||
/>
|
||||
<JTable
|
||||
ref="instanceRef"
|
||||
:columns="columns"
|
||||
|
@ -22,16 +26,12 @@
|
|||
:statusText="slotProps.state?.text"
|
||||
:statusNames="{
|
||||
enabled: 'success',
|
||||
disabled: 'error'
|
||||
disabled: 'error',
|
||||
}"
|
||||
>
|
||||
<template #img>
|
||||
<slot name="img">
|
||||
<img
|
||||
:src="
|
||||
getImage('/cloud/dueros.png')
|
||||
"
|
||||
/>
|
||||
<img :src="getImage('/cloud/dueros.png')" />
|
||||
</slot>
|
||||
</template>
|
||||
<template #content>
|
||||
|
@ -43,9 +43,7 @@
|
|||
</h3>
|
||||
<a-row>
|
||||
<a-col :span="12">
|
||||
<div class="card-item-content-text">
|
||||
产品
|
||||
</div>
|
||||
<div class="card-item-content-text">产品</div>
|
||||
<div>{{ slotProps?.productName }}</div>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
|
@ -103,7 +101,7 @@
|
|||
/>
|
||||
</template>
|
||||
<template #applianceType="slotProps">
|
||||
{{slotProps.applianceType.text}}
|
||||
{{ slotProps.applianceType.text }}
|
||||
</template>
|
||||
<template #action="slotProps">
|
||||
<a-space :size="16">
|
||||
|
@ -145,12 +143,7 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
query,
|
||||
_undeploy,
|
||||
_deploy,
|
||||
_delete
|
||||
} from '@/api/northbound/dueros';
|
||||
import { query, _undeploy, _deploy, _delete, queryProductList, queryTypes } from '@/api/northbound/dueros';
|
||||
import type { ActionsType } from '@/components/Table/index.vue';
|
||||
import { getImage } from '@/utils/comm';
|
||||
import { message } from 'ant-design-vue';
|
||||
|
@ -169,17 +162,48 @@ const columns = [
|
|||
title: '名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
search: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '产品名称',
|
||||
dataIndex: 'productName',
|
||||
key: 'productName',
|
||||
search: {
|
||||
type: 'select',
|
||||
options: () =>
|
||||
new Promise((resolve) => {
|
||||
queryProductList().then((resp: any) => {
|
||||
resolve(
|
||||
resp.result.map((item: any) => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
})),
|
||||
);
|
||||
});
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '设备类型',
|
||||
dataIndex: 'applianceType',
|
||||
key: 'applianceType',
|
||||
scopedSlots: true,
|
||||
search: {
|
||||
type: 'select',
|
||||
options: () =>
|
||||
new Promise((resolve) => {
|
||||
queryTypes().then((resp: any) => {
|
||||
resolve(
|
||||
resp.result.map((item: any) => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
})),
|
||||
);
|
||||
});
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '说明',
|
||||
|
@ -191,6 +215,13 @@ const columns = [
|
|||
dataIndex: 'state',
|
||||
key: 'state',
|
||||
scopedSlots: true,
|
||||
search: {
|
||||
type: 'select',
|
||||
options: [
|
||||
{ label: '正常', value: 'enabled' },
|
||||
{ label: '禁用', value: 'disabled' },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
|
@ -216,8 +247,8 @@ const handleView = (id: string) => {
|
|||
router.push({
|
||||
path: '/iot/northbound/DuerOS/detail/' + id,
|
||||
query: {
|
||||
type: 'view'
|
||||
}
|
||||
type: 'view',
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -249,8 +280,8 @@ const getActions = (
|
|||
router.push({
|
||||
path: '/iot/northbound/DuerOS/detail/' + data.id,
|
||||
query: {
|
||||
type: 'edit'
|
||||
}
|
||||
type: 'edit',
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
|
@ -313,4 +344,8 @@ const getActions = (
|
|||
return actions.filter((i: ActionsType) => i.key !== 'view');
|
||||
return actions;
|
||||
};
|
||||
|
||||
const handleSearch = (_params: any) => {
|
||||
params.value = _params;
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -0,0 +1,224 @@
|
|||
<template>
|
||||
<a-modal
|
||||
class="edit-dialog-container"
|
||||
:title="dialog.title"
|
||||
width="1050px"
|
||||
@ok="dialog.handleOk"
|
||||
:confirmLoading="dialog.loading.value"
|
||||
cancelText="取消"
|
||||
okText="确定"
|
||||
v-model:visible="dialog.visible.value"
|
||||
>
|
||||
<a-form ref="formRef" :model="form.data" layout="vertical">
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="12">
|
||||
<a-form-item
|
||||
name="name"
|
||||
label="名称"
|
||||
:rules="[
|
||||
{ required: true, message: '请输入名称' },
|
||||
{ max: 64, message: '最多可输入64个字符' },
|
||||
]"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="form.data.name"
|
||||
placeholder="请输入名称"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item
|
||||
name="typeId"
|
||||
label="类型"
|
||||
:rules="[{ required: true, message: '请选择类型' }]"
|
||||
>
|
||||
<a-select
|
||||
v-model:value="form.data.typeId"
|
||||
style="width: 120px"
|
||||
:options="form.typeOptions"
|
||||
placeholder="请选择类型"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="24" v-if="form.data.typeId === 'rdb'">
|
||||
<a-col :span="24">
|
||||
<a-form-item
|
||||
:name="['shareConfig', 'url']"
|
||||
label="URL"
|
||||
:rules="[{ required: true, message: '请输入URL' }]"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="form.data.shareConfig.url"
|
||||
placeholder="请输入r2bdc或者jdbc连接地址,示例:r2dbc:mysql://127.0.0.1:3306/test"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="24" v-if="form.data.typeId === 'rabbitmq'">
|
||||
<a-col :span="24">
|
||||
<a-form-item
|
||||
:name="['shareConfig', 'adminUrl']"
|
||||
label="管理地址"
|
||||
:rules="[{ required: true, message: '请输入管理地址' }]"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="form.data.shareConfig.adminUrl"
|
||||
placeholder="请输入管理地址,示例:http://localhost:15672"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="24" v-if="form.data.typeId === 'rabbitmq'">
|
||||
<a-col :span="24">
|
||||
<a-form-item
|
||||
:name="['shareConfig', 'addresses']"
|
||||
label="链接地址"
|
||||
:rules="[{ required: true, message: '请输入链接地址' }]"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="form.data.shareConfig.addresses"
|
||||
placeholder="请输入链接地址,示例:localhost:5672"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="12">
|
||||
<a-form-item
|
||||
:name="['shareConfig', 'username']"
|
||||
label="用户名"
|
||||
:rules="[
|
||||
{ required: true, message: '请输入用户名' },
|
||||
{
|
||||
max: 64,
|
||||
message: '最多可输入64个字符',
|
||||
},
|
||||
]"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="form.data.shareConfig.username"
|
||||
placeholder="请输入用户名"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item
|
||||
:name="['shareConfig', 'password']"
|
||||
label="密码"
|
||||
:rules="[
|
||||
{ required: true, message: '请输入密码' },
|
||||
{
|
||||
max: 64,
|
||||
message: '最多可输入64个字符',
|
||||
},
|
||||
]"
|
||||
>
|
||||
<a-input-password
|
||||
v-model:value="form.data.shareConfig.password"
|
||||
placeholder="请输入密码"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="24" v-if="form.data.typeId === 'rabbitmq'">
|
||||
<a-col :span="24">
|
||||
<a-form-item
|
||||
:name="['shareConfig', 'virtualHost']"
|
||||
label="虚拟域"
|
||||
:rules="[
|
||||
{ required: true, message: '请输入虚拟域' },
|
||||
{
|
||||
max: 64,
|
||||
message: '最多可输入64个字符',
|
||||
},
|
||||
]"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="form.data.shareConfig.virtualHost"
|
||||
placeholder="请输入虚拟域"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="24" v-if="form.data.typeId === 'rdb'">
|
||||
<a-col :span="24">
|
||||
<a-form-item
|
||||
:name="['shareConfig', 'schema']"
|
||||
label="schema"
|
||||
:rules="[
|
||||
{ required: true, message: '请输入schema' },
|
||||
{
|
||||
max: 64,
|
||||
message: '最多可输入64个字符',
|
||||
},
|
||||
]"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="form.data.shareConfig.schema"
|
||||
placeholder="请输入schema"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="24">
|
||||
<a-form-item name="description" label="说明">
|
||||
<a-textarea
|
||||
v-model:value="form.data.description"
|
||||
placeholder="请输入说明"
|
||||
:rows="3"
|
||||
showCount
|
||||
:maxlength="200"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { getDataTypeDict_api } from '@/api/system/dataSource';
|
||||
import type { dictItemType, optionItemType, sourceItemType } from '../typing';
|
||||
|
||||
// 弹窗相关
|
||||
const dialog = {
|
||||
title: '',
|
||||
loading: ref<boolean>(false),
|
||||
visible: ref<boolean>(false),
|
||||
handleOk: () => {},
|
||||
// 打开弹窗
|
||||
changeVisible: (row: sourceItemType) => {
|
||||
if (row.id) dialog.title = '编辑数据源';
|
||||
else dialog.title = '新增数据源';
|
||||
form.data = { ...row };
|
||||
dialog.visible.value = true;
|
||||
},
|
||||
};
|
||||
// 将打开弹窗的操作暴露给父组件
|
||||
defineExpose({
|
||||
openDialog: dialog.changeVisible,
|
||||
});
|
||||
|
||||
const form = reactive({
|
||||
data: {
|
||||
shareConfig: {},
|
||||
} as sourceItemType,
|
||||
|
||||
typeOptions: [] as optionItemType[],
|
||||
|
||||
getTypeOption: () => {
|
||||
getDataTypeDict_api().then((resp: any) => {
|
||||
const result = resp.result as dictItemType[];
|
||||
form.typeOptions = result.map((item) => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
}));
|
||||
});
|
||||
},
|
||||
});
|
||||
form.getTypeOption();
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
|
@ -0,0 +1,304 @@
|
|||
<template>
|
||||
<div class="data-source-container">
|
||||
<Search :columns="query.columns" @search="query.search" />
|
||||
|
||||
<JTable
|
||||
ref="tableRef"
|
||||
:columns="table.columns"
|
||||
:request="getDataSourceList_api"
|
||||
model="TABLE"
|
||||
:params="query.params.value"
|
||||
:defaultParams="{ sorts: [{ name: 'createTime', order: 'desc' }] }"
|
||||
>
|
||||
<template #headerTitle>
|
||||
<PermissionButton
|
||||
type="primary"
|
||||
:uhasPermission="`${permission}:add`"
|
||||
@click="table.openDialog({})"
|
||||
>
|
||||
<AIcon type="PlusOutlined" />新增
|
||||
</PermissionButton>
|
||||
</template>
|
||||
<template #state="slotProps">
|
||||
<BadgeStatus
|
||||
:status="slotProps.state?.value"
|
||||
:text="slotProps.state?.text"
|
||||
:statusNames="{
|
||||
enabled: 'success',
|
||||
disabled: 'error',
|
||||
}"
|
||||
>
|
||||
</BadgeStatus>
|
||||
</template>
|
||||
<template #typeId="slotProps">
|
||||
{{
|
||||
(table.typeOptions.value.length &&
|
||||
table.getTypeLabel(slotProps.typeId)) ||
|
||||
''
|
||||
}}
|
||||
</template>
|
||||
<template #action="slotProps">
|
||||
<a-space :size="16">
|
||||
<PermissionButton
|
||||
:uhasPermission="`${permission}:update`"
|
||||
type="link"
|
||||
:tooltip="{
|
||||
title: '编辑',
|
||||
}"
|
||||
@click="table.openDialog(slotProps)"
|
||||
>
|
||||
<AIcon type="EditOutlined" />
|
||||
</PermissionButton>
|
||||
<PermissionButton
|
||||
:uhasPermission="`${permission}:manage`"
|
||||
type="link"
|
||||
:tooltip="{
|
||||
title:
|
||||
slotProps?.typeId === 'rabbitmq'
|
||||
? '暂不支持管理功能'
|
||||
: table.getRowStatus(slotProps)
|
||||
? '管理'
|
||||
: '请先启用数据源',
|
||||
}"
|
||||
@click="
|
||||
() =>
|
||||
router.push(
|
||||
`/system/DataSource/Management?id=${slotProps.id}`,
|
||||
)
|
||||
"
|
||||
>
|
||||
<AIcon type="icon-ziyuankuguanli" />
|
||||
</PermissionButton>
|
||||
<PermissionButton
|
||||
:uhasPermission="`${permission}:action`"
|
||||
type="link"
|
||||
:popConfirm="{
|
||||
title: `确定要${
|
||||
table.getRowStatus(slotProps) ? '禁用' : '启用'
|
||||
}吗?`,
|
||||
onConfirm: () => table.clickChangeStatus(slotProps),
|
||||
}"
|
||||
:tooltip="{
|
||||
title: table.getRowStatus(slotProps)
|
||||
? '禁用'
|
||||
: '启用',
|
||||
}"
|
||||
>
|
||||
<AIcon
|
||||
:type="
|
||||
table.getRowStatus(slotProps)
|
||||
? 'StopOutlined'
|
||||
: 'PlayCircleOutlined'
|
||||
"
|
||||
/>
|
||||
<!-- <AIcon type="PlayCircleOutlined" /> -->
|
||||
</PermissionButton>
|
||||
|
||||
<PermissionButton
|
||||
:uhasPermission="`${permission}:delete`"
|
||||
type="link"
|
||||
:tooltip="{
|
||||
title: table.getRowStatus(slotProps)
|
||||
? '请先禁用,再删除'
|
||||
: '删除',
|
||||
}"
|
||||
:popConfirm="{
|
||||
title: `确认删除`,
|
||||
onConfirm: () => table.clickDel(slotProps),
|
||||
}"
|
||||
:disabled="table.getRowStatus(slotProps)"
|
||||
>
|
||||
<AIcon type="DeleteOutlined" />
|
||||
</PermissionButton>
|
||||
</a-space>
|
||||
</template>
|
||||
</JTable>
|
||||
|
||||
<div class="dialogs">
|
||||
<EditDialog ref="editDialogRef" @confirm="table.refresh" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="DataSource">
|
||||
import PermissionButton from '@/components/PermissionButton/index.vue';
|
||||
import BadgeStatus from '@/components/BadgeStatus/index.vue';
|
||||
import EditDialog from './components/EditDialog.vue';
|
||||
|
||||
import type { dictItemType, optionItemType, sourceItemType } from './typing';
|
||||
|
||||
import {
|
||||
getDataSourceList_api,
|
||||
getDataTypeDict_api,
|
||||
changeStatus_api,
|
||||
} from '@/api/system/dataSource';
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
const permission = 'system/Relationship';
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const query = {
|
||||
columns: [
|
||||
{
|
||||
title: '名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
search: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '类型',
|
||||
dataIndex: 'typeId',
|
||||
key: 'typeId',
|
||||
search: {
|
||||
type: 'select',
|
||||
options: () =>
|
||||
new Promise((resolve) => {
|
||||
if (table.typeOptions.value.length > 0)
|
||||
return resolve(table.typeOptions.value);
|
||||
getDataTypeDict_api().then((resp: any) => {
|
||||
const result = resp.result as dictItemType[];
|
||||
resolve(
|
||||
result.map((item) => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
})),
|
||||
);
|
||||
});
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '说明',
|
||||
dataIndex: 'description',
|
||||
key: 'description',
|
||||
search: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'state',
|
||||
key: 'state',
|
||||
ellipsis: true,
|
||||
fixed: 'left',
|
||||
search: {
|
||||
type: 'select',
|
||||
options: [
|
||||
{
|
||||
label: '正常',
|
||||
value: 'enabled',
|
||||
},
|
||||
{
|
||||
label: '已禁用',
|
||||
value: 'disabled',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
params: ref({}),
|
||||
search: (params: object) => {
|
||||
query.params.value = params;
|
||||
},
|
||||
};
|
||||
|
||||
const editDialogRef = ref(); // 新增弹窗实例
|
||||
const tableRef = ref<Record<string, any>>({}); // 表格实例
|
||||
const table = {
|
||||
columns: [
|
||||
{
|
||||
title: '名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
width: '250px',
|
||||
},
|
||||
{
|
||||
title: '类型',
|
||||
dataIndex: 'typeId',
|
||||
key: 'typeId',
|
||||
scopedSlots: true,
|
||||
},
|
||||
|
||||
{
|
||||
title: '说明',
|
||||
dataIndex: 'description',
|
||||
key: 'description',
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'state',
|
||||
key: 'state',
|
||||
scopedSlots: true,
|
||||
width: '120px',
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'action',
|
||||
key: 'action',
|
||||
scopedSlots: true,
|
||||
width: '200px',
|
||||
},
|
||||
],
|
||||
|
||||
typeOptions: ref<optionItemType[]>([]),
|
||||
|
||||
getTypeOption: () => {
|
||||
getDataTypeDict_api().then((resp: any) => {
|
||||
const result = resp.result as dictItemType[];
|
||||
table.typeOptions.value = result.map((item) => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
}));
|
||||
});
|
||||
},
|
||||
getTypeLabel: (val: string): string => {
|
||||
const options = table.typeOptions.value;
|
||||
if (options.length < 1 || val === '') return '';
|
||||
return options.find((item) => item.value === val)?.label || '';
|
||||
},
|
||||
|
||||
getRowStatus: (row: sourceItemType) => {
|
||||
return row.state?.value === 'enabled';
|
||||
},
|
||||
// 打开编辑弹窗
|
||||
openDialog: (row: sourceItemType | {}) => {
|
||||
editDialogRef.value.openDialog({shareConfig:{},...row});
|
||||
},
|
||||
// 删除
|
||||
clickDel: (row: sourceItemType) => {
|
||||
// delRelation_api(row.id).then((resp: any) => {
|
||||
// if (resp.status === 200) {
|
||||
// tableRef.value?.reload();
|
||||
// message.success('操作成功!');
|
||||
// }
|
||||
// });
|
||||
},
|
||||
clickChangeStatus: (row: sourceItemType) => {
|
||||
const status = row.state.value === 'enabled' ? '_disable' : '_enable';
|
||||
|
||||
changeStatus_api(row.id as string, status).then(() => {
|
||||
message.success('操作成功');
|
||||
table.refresh();
|
||||
});
|
||||
},
|
||||
// 刷新列表
|
||||
refresh: () => {
|
||||
tableRef.value.reload();
|
||||
},
|
||||
};
|
||||
table.getTypeOption();
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.data-source-container {
|
||||
padding: 24px;
|
||||
:deep(.ant-table-cell) {
|
||||
.ant-btn-link {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,24 @@
|
|||
export type dictItemType = {
|
||||
id: string,
|
||||
name: string
|
||||
}
|
||||
export type optionItemType = {
|
||||
label: string,
|
||||
value: string
|
||||
}
|
||||
export type sourceItemType = {
|
||||
id?: string,
|
||||
name: string,
|
||||
state: { text: string, value: "enabled" | 'disabled' },
|
||||
typeId: string,
|
||||
shareConfig:{
|
||||
url:string,
|
||||
adminUrl:string,
|
||||
addresses:string,
|
||||
username:string,
|
||||
password:string,
|
||||
virtualHost:string,
|
||||
schema:string
|
||||
}
|
||||
description: string
|
||||
}
|
|
@ -20,7 +20,7 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { dictType, optionsType } from '../typing.d.ts';
|
||||
import type { dictType, optionsType } from '../typing';
|
||||
import { updatePermission_api } from '@/api/system/department';
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
|
|
|
@ -167,7 +167,7 @@ import {
|
|||
} from '@/api/system/department';
|
||||
import { intersection } from 'lodash-es';
|
||||
|
||||
import { dictType } from '../typing.d.ts';
|
||||
import type { dictType } from '../typing';
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
const permission = 'system/Department';
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
type dictType = {
|
||||
export type dictType = {
|
||||
id: string;
|
||||
name: string;
|
||||
}[];
|
||||
|
||||
type optionsType = {
|
||||
export type optionsType = {
|
||||
label: string,
|
||||
value: string;
|
||||
disabled?:boolean
|
||||
|
|
|
@ -185,16 +185,18 @@
|
|||
:first-width="3"
|
||||
max-height="350px"
|
||||
v-model:value="form.data.permissions"
|
||||
:key="form.data.id || ''"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
<a-button
|
||||
<PermissionButton
|
||||
type="primary"
|
||||
:uhasPermission="`${permission}:update`"
|
||||
@click="form.clickSave"
|
||||
v-loading="form.saveLoading"
|
||||
>保存</a-button
|
||||
>
|
||||
保存
|
||||
</PermissionButton>
|
||||
</a-card>
|
||||
|
||||
<!-- 弹窗 -->
|
||||
|
@ -205,6 +207,7 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import PermissionButton from '@/components/PermissionButton/index.vue';
|
||||
import {
|
||||
PlusOutlined,
|
||||
QuestionCircleFilled,
|
||||
|
@ -222,6 +225,7 @@ import {
|
|||
addMenuInfo_api,
|
||||
} from '@/api/system/menu';
|
||||
|
||||
const permission = 'system/Menu';
|
||||
// 路由
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
@ -330,6 +334,7 @@ const dialog = {
|
|||
};
|
||||
|
||||
type formType = {
|
||||
id?: string;
|
||||
name: string;
|
||||
code: string;
|
||||
url: string;
|
||||
|
|
|
@ -8,49 +8,43 @@
|
|||
noPagination
|
||||
>
|
||||
<template #headerTitle>
|
||||
<a-button
|
||||
<PermissionButton
|
||||
type="primary"
|
||||
style="margin-right: 10px"
|
||||
@click="() => dialog.openDialog('新增')"
|
||||
><plus-outlined />新增</a-button
|
||||
:uhasPermission="`${permission}:update`"
|
||||
@click="dialog.openDialog('新增')"
|
||||
>
|
||||
<AIcon type="PlusOutlined" />新增
|
||||
</PermissionButton>
|
||||
</template>
|
||||
<template #action="slotProps">
|
||||
<a-space :size="16">
|
||||
<a-tooltip>
|
||||
<template #title>编辑</template>
|
||||
<a-button
|
||||
style="padding: 0"
|
||||
type="link"
|
||||
@click="() => dialog.openDialog('编辑', slotProps)"
|
||||
>
|
||||
<edit-outlined />
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>查看</template>
|
||||
<a-button
|
||||
style="padding: 0"
|
||||
type="link"
|
||||
@click="() => dialog.openDialog('查看', slotProps)"
|
||||
>
|
||||
<search-outlined />
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
|
||||
<a-popconfirm
|
||||
title="确认删除"
|
||||
ok-text="确定"
|
||||
cancel-text="取消"
|
||||
@confirm="table.clickDel(slotProps)"
|
||||
<PermissionButton
|
||||
type="link"
|
||||
:uhasPermission="`${permission}:update`"
|
||||
:tooltip="{ title: '编辑' }"
|
||||
@click="dialog.openDialog('编辑', slotProps)"
|
||||
>
|
||||
<a-tooltip>
|
||||
<template #title>删除</template>
|
||||
<a-button style="padding: 0" type="link">
|
||||
<delete-outlined />
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-popconfirm>
|
||||
<AIcon type="EditOutlined" />
|
||||
</PermissionButton>
|
||||
<PermissionButton
|
||||
type="link"
|
||||
:uhasPermission="true"
|
||||
:tooltip="{ title: '查看' }"
|
||||
@click="dialog.openDialog('查看', slotProps)"
|
||||
>
|
||||
<AIcon type="SearchOutlined" />
|
||||
</PermissionButton>
|
||||
<PermissionButton
|
||||
type="link"
|
||||
:uhasPermission="`${permission}:update`"
|
||||
:tooltip="{ title: '删除' }"
|
||||
:popConfirm="{
|
||||
title: `确认删除`,
|
||||
onConfirm: () => table.clickDel(slotProps),
|
||||
}"
|
||||
>
|
||||
<AIcon type="DeleteOutlined" />
|
||||
</PermissionButton>
|
||||
</a-space>
|
||||
</template>
|
||||
</JTable>
|
||||
|
@ -66,17 +60,14 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
EditOutlined,
|
||||
SearchOutlined,
|
||||
DeleteOutlined,
|
||||
PlusOutlined,
|
||||
} from '@ant-design/icons-vue';
|
||||
import PermissionButton from '@/components/PermissionButton/index.vue';
|
||||
|
||||
import ButtonAddDialog from '../components/ButtonAddDialog.vue';
|
||||
|
||||
import { getMenuInfo_api, saveMenuInfo_api } from '@/api/system/menu';
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
const permission = 'system/Menu';
|
||||
// 路由
|
||||
const route = useRoute();
|
||||
const routeParams = {
|
||||
|
|
|
@ -56,11 +56,12 @@
|
|||
/>
|
||||
</a-form-item-rest> -->
|
||||
<PermissChoose
|
||||
:first-width="8"
|
||||
max-height="350px"
|
||||
v-model:value="form.data.permissions"
|
||||
:disabled="form.mode === '查看'"
|
||||
/>
|
||||
:first-width="8"
|
||||
max-height="350px"
|
||||
v-model:value="form.data.permissions"
|
||||
:disabled="form.mode === '查看'"
|
||||
:key="form.data.id || ''"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="说明" name="describe">
|
||||
<a-textarea
|
||||
|
@ -91,7 +92,8 @@ const dialog = reactive({
|
|||
visible: false,
|
||||
loading: false,
|
||||
handleOk: () => {
|
||||
props.menuInfo.id && formRef.value &&
|
||||
props.menuInfo.id &&
|
||||
formRef.value &&
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
|
@ -112,7 +114,7 @@ const dialog = reactive({
|
|||
saveMenuInfo_api(params)
|
||||
.then((resp) => {
|
||||
dialog.changeVisible();
|
||||
message.success('操作成功')
|
||||
message.success('操作成功');
|
||||
emits('confirm');
|
||||
})
|
||||
.finally(() => (dialog.loading = false));
|
||||
|
|
|
@ -47,8 +47,10 @@
|
|||
<script setup lang="ts">
|
||||
import { exportPermission_api } from '@/api/system/permission';
|
||||
import { Form } from 'ant-design-vue';
|
||||
Form.useInjectFormItemContext()
|
||||
Form.useInjectFormItemContext();
|
||||
|
||||
const props = defineProps<{
|
||||
key: string;
|
||||
value: any[];
|
||||
firstWidth: number;
|
||||
maxHeight: string;
|
||||
|
@ -58,7 +60,6 @@ const emits = defineEmits(['update:value']);
|
|||
const searchValue = ref<string>('');
|
||||
|
||||
const search = reactive({
|
||||
value: '',
|
||||
searchTimer: null as null | number,
|
||||
search: () => {
|
||||
if (search.searchTimer) {
|
||||
|
@ -72,17 +73,34 @@ const search = reactive({
|
|||
});
|
||||
const permission = reactive({
|
||||
list: [] as permissionType[],
|
||||
sourceList: [] as any[],
|
||||
|
||||
init: () => {
|
||||
permission.getList();
|
||||
watch(
|
||||
() => props.key,
|
||||
() => {
|
||||
nextTick(() => {
|
||||
permission.list = permission.makeList(
|
||||
props.value,
|
||||
permission.sourceList,
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
},
|
||||
// 获取权限列表
|
||||
getList: () => {
|
||||
const params: paramsType = {
|
||||
paging: false,
|
||||
};
|
||||
if (search.value) {
|
||||
if (searchValue.value) {
|
||||
params.terms = [
|
||||
{ column: 'name$like', value: `%${search.value}%` },
|
||||
{ column: 'name$like', value: `%${searchValue.value}%` },
|
||||
];
|
||||
}
|
||||
exportPermission_api(params).then((resp) => {
|
||||
permission.sourceList = [...(resp.result as any[])];
|
||||
permission.list = permission.makeList(
|
||||
props.value,
|
||||
resp.result as any[],
|
||||
|
@ -91,7 +109,7 @@ const permission = reactive({
|
|||
},
|
||||
// 全选/取消全选
|
||||
selectAllOpions: (row: permissionType) => {
|
||||
row.indeterminate = false
|
||||
row.indeterminate = false;
|
||||
const newValue = props.value.filter(
|
||||
(item) => item.permission !== row.id,
|
||||
);
|
||||
|
@ -162,8 +180,8 @@ const permission = reactive({
|
|||
return result;
|
||||
},
|
||||
});
|
||||
permission.init();
|
||||
|
||||
permission.getList();
|
||||
|
||||
type permissionType = {
|
||||
id: string;
|
||||
|
|
|
@ -10,13 +10,24 @@
|
|||
:params="query.params"
|
||||
>
|
||||
<template #headerTitle>
|
||||
<a-button
|
||||
<PermissionButton
|
||||
type="primary"
|
||||
:uhasPermission="`${permission}:add`"
|
||||
@click="table.toDetails({})"
|
||||
style="margin-right: 10px"
|
||||
><plus-outlined />新增</a-button
|
||||
>
|
||||
<a-button @click="router.push('/system/Menu/Setting')">菜单实例</a-button>
|
||||
<AIcon type="PlusOutlined" />新增
|
||||
</PermissionButton>
|
||||
<a-button
|
||||
style="margin-left: 12px"
|
||||
@click="router.push('/system/Menu/Setting')"
|
||||
>菜单配置</a-button
|
||||
>
|
||||
<!-- <PermissionButton
|
||||
:uhasPermission="true"
|
||||
@click="router.push('/system/Menu/Setting')"
|
||||
>
|
||||
菜单配置
|
||||
</PermissionButton> -->
|
||||
</template>
|
||||
<template #createTime="slotProps">
|
||||
{{ moment(slotProps.createTime).format('YYYY-MM-DD HH:mm:ss') }}
|
||||
|
@ -33,30 +44,26 @@
|
|||
<search-outlined />
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>新增子菜单</template>
|
||||
<a-button
|
||||
style="padding: 0"
|
||||
type="link"
|
||||
@click="table.addChildren(slotProps)"
|
||||
>
|
||||
<plus-circle-outlined />
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
|
||||
<a-popconfirm
|
||||
title="是否删除该菜单"
|
||||
ok-text="确定"
|
||||
cancel-text="取消"
|
||||
@confirm="table.clickDel(slotProps)"
|
||||
<PermissionButton
|
||||
type="link"
|
||||
:uhasPermission="`${permission}:add`"
|
||||
:tooltip="{ title: '新增子菜单' }"
|
||||
@click="table.addChildren(slotProps)"
|
||||
>
|
||||
<a-tooltip>
|
||||
<template #title>删除</template>
|
||||
<a-button style="padding: 0" type="link">
|
||||
<delete-outlined />
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-popconfirm>
|
||||
<AIcon type="PlusCircleOutlined" />
|
||||
</PermissionButton>
|
||||
<PermissionButton
|
||||
type="link"
|
||||
:uhasPermission="`${permission}:delete`"
|
||||
:tooltip="{ title: '删除' }"
|
||||
:popConfirm="{
|
||||
title: `是否删除该菜单`,
|
||||
onConfirm: () => table.clickDel(slotProps),
|
||||
}"
|
||||
>
|
||||
<AIcon type="DeleteOutlined" />
|
||||
</PermissionButton>
|
||||
</a-space>
|
||||
</template>
|
||||
</JTable>
|
||||
|
@ -64,16 +71,15 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import PermissionButton from '@/components/PermissionButton/index.vue';
|
||||
|
||||
import { getMenuTree_api, delMenuInfo_api } from '@/api/system/menu';
|
||||
import {
|
||||
SearchOutlined,
|
||||
DeleteOutlined,
|
||||
PlusOutlined,
|
||||
PlusCircleOutlined,
|
||||
} from '@ant-design/icons-vue';
|
||||
import { SearchOutlined } from '@ant-design/icons-vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import moment from 'moment';
|
||||
|
||||
const permission = 'system/Menu';
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
// 筛选
|
||||
|
@ -240,13 +246,13 @@ const table = reactive({
|
|||
router.push(
|
||||
`/system/Menu/detail/${row.id || ':id'}?pid=${
|
||||
row.pid || ''
|
||||
}&basePath=${row.url|| ''}&sortIndex=${table.total}`,
|
||||
}&basePath=${row.url || ''}&sortIndex=${table.total}`,
|
||||
);
|
||||
},
|
||||
// 删除
|
||||
clickDel: (row: any) => {
|
||||
console.log(row.id);
|
||||
|
||||
|
||||
delMenuInfo_api(row.id).then((resp: any) => {
|
||||
if (resp.status === 200) {
|
||||
tableRef.value?.reload();
|
||||
|
@ -264,5 +270,11 @@ const table = reactive({
|
|||
<style lang="less" scoped>
|
||||
.menu-container {
|
||||
padding: 24px;
|
||||
|
||||
:deep(.ant-table-cell) {
|
||||
.ant-btn-link {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -11,12 +11,13 @@
|
|||
:defaultParams="{ sorts: [{ name: 'id', order: 'asc' }] }"
|
||||
>
|
||||
<template #headerTitle>
|
||||
<a-button
|
||||
<PermissionButton
|
||||
type="primary"
|
||||
:uhasPermission="`${permission}:add`"
|
||||
@click="table.openDialog(undefined)"
|
||||
style="margin-right: 10px"
|
||||
><plus-outlined />新增</a-button
|
||||
>
|
||||
<AIcon type="PlusOutlined" />新增
|
||||
</PermissionButton>
|
||||
<a-dropdown trigger="hover">
|
||||
<a-button>批量操作</a-button>
|
||||
<template #overlay>
|
||||
|
@ -28,19 +29,27 @@
|
|||
accept=".json"
|
||||
:showUploadList="false"
|
||||
:before-upload="table.clickImport"
|
||||
:disabled="
|
||||
!hasPermission(`${permission}:import`)
|
||||
"
|
||||
>
|
||||
<a-button>导入</a-button>
|
||||
<PermissionButton
|
||||
:hasPermission="`${permission}:import`"
|
||||
>
|
||||
导入
|
||||
</PermissionButton>
|
||||
</a-upload>
|
||||
</a-menu-item>
|
||||
<a-menu-item>
|
||||
<a-popconfirm
|
||||
title="确认导出?"
|
||||
ok-text="确定"
|
||||
cancel-text="取消"
|
||||
@confirm="table.clickExport"
|
||||
<PermissionButton
|
||||
:uhasPermission="`${permission}:export`"
|
||||
:popConfirm="{
|
||||
title: `确认导出?`,
|
||||
onConfirm: () => table.clickExport(),
|
||||
}"
|
||||
>
|
||||
<a-button>导出</a-button>
|
||||
</a-popconfirm>
|
||||
导出
|
||||
</PermissionButton>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
|
@ -51,63 +60,53 @@
|
|||
</template>
|
||||
<template #action="slotProps">
|
||||
<a-space :size="16">
|
||||
<a-tooltip>
|
||||
<template #title>编辑</template>
|
||||
<a-button
|
||||
style="padding: 0"
|
||||
type="link"
|
||||
@click="table.openDialog(slotProps)"
|
||||
>
|
||||
<edit-outlined />
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
|
||||
<a-popconfirm
|
||||
:title="`确定要${
|
||||
slotProps.status ? '禁用' : '启用'
|
||||
}吗?`"
|
||||
ok-text="确定"
|
||||
cancel-text="取消"
|
||||
@confirm="table.changeStatus(slotProps)"
|
||||
<PermissionButton
|
||||
:uhasPermission="`${permission}:update`"
|
||||
type="link"
|
||||
:tooltip="{
|
||||
title: '编辑',
|
||||
}"
|
||||
@click="table.openDialog(slotProps)"
|
||||
>
|
||||
<a-tooltip>
|
||||
<template #title>{{
|
||||
slotProps.status ? '禁用' : '启用'
|
||||
}}</template>
|
||||
<a-button style="padding: 0" type="link">
|
||||
<stop-outlined v-if="slotProps.status" />
|
||||
<play-circle-outlined v-else />
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-popconfirm>
|
||||
<AIcon type="EditOutlined" />
|
||||
</PermissionButton>
|
||||
|
||||
<a-popconfirm
|
||||
title="确认删除"
|
||||
ok-text="确定"
|
||||
cancel-text="取消"
|
||||
@confirm="table.clickDel(slotProps)"
|
||||
<PermissionButton
|
||||
:uhasPermission="`${permission}:action`"
|
||||
type="link"
|
||||
:popConfirm="{
|
||||
title: `确定要${
|
||||
slotProps.status ? '禁用' : '启用'
|
||||
}吗?`,
|
||||
onConfirm: () => table.changeStatus(slotProps),
|
||||
}"
|
||||
:tooltip="{ title: slotProps.status ? '禁用' : '启用' }"
|
||||
>
|
||||
<AIcon
|
||||
:type="
|
||||
slotProps.status
|
||||
? 'StopOutlined'
|
||||
: 'PlayCircleOutlined '
|
||||
"
|
||||
/>
|
||||
</PermissionButton>
|
||||
|
||||
<PermissionButton
|
||||
:uhasPermission="`${permission}:delete`"
|
||||
type="link"
|
||||
:tooltip="{
|
||||
title: slotProps.status
|
||||
? '请先禁用,再删除'
|
||||
: '删除',
|
||||
}"
|
||||
:popConfirm="{
|
||||
title: `确认删除`,
|
||||
onConfirm: () => table.clickDel(slotProps),
|
||||
}"
|
||||
:disabled="slotProps.status"
|
||||
>
|
||||
<a-tooltip>
|
||||
<template #title>{{
|
||||
systemPermission('delete')
|
||||
? slotProps.status
|
||||
? '请先禁用,再删除'
|
||||
: '删除'
|
||||
: '暂无权限,请联系管理员'
|
||||
}}</template>
|
||||
<a-button
|
||||
style="padding: 0"
|
||||
type="link"
|
||||
:disabled="
|
||||
!systemPermission('delete') ||
|
||||
slotProps.status
|
||||
"
|
||||
>
|
||||
<delete-outlined />
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-popconfirm>
|
||||
<AIcon type="DeleteOutlined" />
|
||||
</PermissionButton>
|
||||
</a-space>
|
||||
</template>
|
||||
</JTable>
|
||||
|
@ -119,16 +118,10 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import PermissionButton from '@/components/PermissionButton/index.vue';
|
||||
import EditDialog from './components/EditDialog.vue';
|
||||
import StatusLabel from './components/StatusLabel.vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import {
|
||||
EditOutlined,
|
||||
DeleteOutlined,
|
||||
PlusOutlined,
|
||||
StopOutlined,
|
||||
PlayCircleOutlined,
|
||||
} from '@ant-design/icons-vue';
|
||||
import {
|
||||
getPermission_api,
|
||||
editPermission_api,
|
||||
|
@ -138,13 +131,11 @@ import {
|
|||
import { downloadObject } from '@/utils/utils';
|
||||
import { usePermissionStore } from '@/store/permission';
|
||||
|
||||
const permission = 'system/Permission';
|
||||
const hasPermission = usePermissionStore().hasPermission;
|
||||
|
||||
const editDialogRef = ref(); // 新增弹窗实例
|
||||
const tableRef = ref<Record<string, any>>({}); // 表格实例
|
||||
|
||||
// 按钮权限控制
|
||||
const hasPermission = usePermissionStore().hasPermission;
|
||||
const systemPermission = (code: string) =>
|
||||
hasPermission('system/Permission:${code}');
|
||||
// 筛选
|
||||
const query = reactive({
|
||||
columns: [
|
||||
|
@ -223,12 +214,7 @@ const table = reactive({
|
|||
tableData: [],
|
||||
// 打开编辑弹窗
|
||||
openDialog: (row: object | undefined = {}) => {
|
||||
let permissionCode = '';
|
||||
if (Object.keys(row).length < 1) permissionCode = 'add';
|
||||
else permissionCode = 'update';
|
||||
if (systemPermission(permissionCode))
|
||||
editDialogRef.value.openDialog(true, row);
|
||||
else message.warn('暂无权限,请联系管理员');
|
||||
editDialogRef.value.openDialog(true, row);
|
||||
},
|
||||
// 导入数据
|
||||
clickImport: (file: File) => {
|
||||
|
@ -268,8 +254,6 @@ const table = reactive({
|
|||
},
|
||||
// 修改状态
|
||||
changeStatus: (row: any) => {
|
||||
if (!systemPermission('action'))
|
||||
return message.warn('暂无权限,请联系管理员');
|
||||
const params = {
|
||||
...row,
|
||||
status: row.status ? 0 : 1,
|
||||
|
@ -295,4 +279,18 @@ const table = reactive({
|
|||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
<style lang="less" scoped>
|
||||
.permission-container {
|
||||
padding: 24px;
|
||||
|
||||
.ant-dropdown-trigger {
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
:deep(.ant-table-cell) {
|
||||
.ant-btn-link {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -7,42 +7,43 @@
|
|||
:columns="table.columns"
|
||||
:request="getRelationshipList_api"
|
||||
model="TABLE"
|
||||
:params="query.params"
|
||||
:params="query.params.value"
|
||||
:defaultParams="{ sorts: [{ name: 'createTime', order: 'desc' }] }"
|
||||
>
|
||||
<template #headerTitle>
|
||||
<a-button
|
||||
<PermissionButton
|
||||
type="primary"
|
||||
:uhasPermission="`${permission}:add`"
|
||||
@click="table.openDialog(undefined)"
|
||||
style="margin-right: 10px"
|
||||
><plus-outlined />新增</a-button
|
||||
>
|
||||
<AIcon type="PlusOutlined" />新增
|
||||
</PermissionButton>
|
||||
</template>
|
||||
<template #action="slotProps">
|
||||
<a-space :size="16">
|
||||
<a-tooltip>
|
||||
<template #title>编辑</template>
|
||||
<a-button
|
||||
style="padding: 0"
|
||||
type="link"
|
||||
@click="table.openDialog(slotProps)"
|
||||
>
|
||||
<edit-outlined />
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-popconfirm
|
||||
title="确认删除"
|
||||
ok-text="确定"
|
||||
cancel-text="取消"
|
||||
@confirm="table.clickDel(slotProps)"
|
||||
<PermissionButton
|
||||
:uhasPermission="`${permission}:update`"
|
||||
type="link"
|
||||
:tooltip="{
|
||||
title: '编辑',
|
||||
}"
|
||||
@click="table.openDialog(slotProps)"
|
||||
>
|
||||
<a-tooltip>
|
||||
<template #title>删除</template>
|
||||
<a-button style="padding: 0" type="link">
|
||||
<delete-outlined />
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-popconfirm>
|
||||
<AIcon type="EditOutlined" />
|
||||
</PermissionButton>
|
||||
|
||||
<PermissionButton
|
||||
:uhasPermission="`${permission}:delete`"
|
||||
type="link"
|
||||
:tooltip="{ title: '删除' }"
|
||||
:popConfirm="{
|
||||
title: `确认删除`,
|
||||
onConfirm: () => table.clickDel(slotProps),
|
||||
}"
|
||||
:disabled="slotProps.status"
|
||||
>
|
||||
<AIcon type="DeleteOutlined" />
|
||||
</PermissionButton>
|
||||
</a-space>
|
||||
</template>
|
||||
</JTable>
|
||||
|
@ -52,14 +53,16 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts" name="Relationship">
|
||||
import { getRelationshipList_api, delRelation_api } from '@/api/system/relationship';
|
||||
import PermissionButton from '@/components/PermissionButton/index.vue';
|
||||
import {
|
||||
getRelationshipList_api,
|
||||
delRelation_api,
|
||||
} from '@/api/system/relationship';
|
||||
import { message } from 'ant-design-vue';
|
||||
import EditDialog from './components/EditDialog.vue';
|
||||
import {
|
||||
EditOutlined,
|
||||
DeleteOutlined,
|
||||
PlusOutlined,
|
||||
} from '@ant-design/icons-vue';
|
||||
|
||||
const permission = 'system/Relationship';
|
||||
|
||||
const query = {
|
||||
columns: [
|
||||
{
|
||||
|
@ -119,9 +122,9 @@ const query = {
|
|||
},
|
||||
},
|
||||
],
|
||||
params: {},
|
||||
params: ref({}),
|
||||
search: (params: object) => {
|
||||
query.params = params;
|
||||
query.params.value = params;
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -158,7 +161,7 @@ const table = {
|
|||
],
|
||||
// 打开编辑弹窗
|
||||
openDialog: (row: object | undefined = {}) => {
|
||||
editDialogRef.value.openDialog(true, row)
|
||||
editDialogRef.value.openDialog(true, row);
|
||||
},
|
||||
// 删除
|
||||
clickDel: (row: any) => {
|
||||
|
@ -178,6 +181,11 @@ const table = {
|
|||
|
||||
<style lang="less" scoped>
|
||||
.relationship-container {
|
||||
padding:24px ;
|
||||
padding: 24px;
|
||||
:deep(.ant-table-cell) {
|
||||
.ant-btn-link {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
Loading…
Reference in New Issue