Merge branch 'dev' of github.com:jetlinks/jetlinks-ui-vue into dev
This commit is contained in:
commit
abdfee115c
|
@ -3,7 +3,7 @@
|
|||
<template #title>
|
||||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<div style="width: 150px;">配置元素</div>
|
||||
<close-outlined @click="visible = false" />
|
||||
<AIcon type="CloseOutlined" @click="visible = false"/>
|
||||
</div>
|
||||
</template>
|
||||
<template #content>
|
||||
|
@ -19,14 +19,13 @@
|
|||
</div>
|
||||
</template>
|
||||
<a-button type="dashed" block @click="visible = true">
|
||||
配置元素<edit-outlined class="item-icon" />
|
||||
配置元素<AIcon type="EditOutlined" 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>;
|
||||
|
|
|
@ -16,13 +16,12 @@
|
|||
</div>
|
||||
</template>
|
||||
<a-button type="dashed" block>
|
||||
存储配置<edit-outlined class="item-icon" />
|
||||
存储配置<AIcon type="EditOutlined" class="item-icon"/>
|
||||
</a-button>
|
||||
</a-popover>
|
||||
</template>
|
||||
<script setup lang="ts" name="ConfigParam">
|
||||
import { PropType } from 'vue';
|
||||
import { EditOutlined } from '@ant-design/icons-vue';
|
||||
|
||||
type ValueType = Record<any, any>;
|
||||
const props = defineProps({
|
||||
|
|
|
@ -2,14 +2,14 @@
|
|||
<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" />
|
||||
<AIcon type="MenuOutlined" 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" />
|
||||
<AIcon type="CloseOutlined" @click="handleClose" />
|
||||
</div>
|
||||
</template>
|
||||
<template #content>
|
||||
|
@ -28,23 +28,22 @@
|
|||
</template>
|
||||
<div class="item-edit" @click="handleEdit(index)">
|
||||
{{ item.text || '枚举项配置' }}
|
||||
<edit-outlined class="item-icon" />
|
||||
<AIcon type="EditOutlined" class="item-icon" />
|
||||
</div>
|
||||
</a-popover>
|
||||
</div>
|
||||
<div class="item-right">
|
||||
<delete-outlined @click="handleDelete(index)"/>
|
||||
<AIcon type="DeleteOutlined" @click="handleDelete(index)"/>
|
||||
</div>
|
||||
</div>
|
||||
<a-button type="dashed" block @click="handleAdd">
|
||||
<template #icon><plus-outlined class="item-icon" /></template>
|
||||
<template #icon><AIcon type="PlusOutlined" 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,
|
||||
|
|
|
@ -2,14 +2,14 @@
|
|||
<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" />
|
||||
<AIcon type="MenuOutlined" 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" />
|
||||
<AIcon type="CloseOutlined" @click="handleClose" />
|
||||
</div>
|
||||
</template>
|
||||
<template #content>
|
||||
|
@ -38,23 +38,22 @@
|
|||
</template>
|
||||
<div class="item-edit" @click="handleEdit(index)">
|
||||
{{ item.name || '配置参数' }}
|
||||
<edit-outlined class="item-icon" />
|
||||
<AIcon type="EditOutlined" class="item-icon" />
|
||||
</div>
|
||||
</a-popover>
|
||||
</div>
|
||||
<div class="item-right">
|
||||
<delete-outlined @click="handleDelete(index)" />
|
||||
<AIcon type="DeleteOutlined" @click="handleDelete(index)" />
|
||||
</div>
|
||||
</div>
|
||||
<a-button type="dashed" block @click="handleAdd">
|
||||
<template #icon><plus-outlined class="item-icon" /></template>
|
||||
<template #icon><AIcon type="PlusOutlined" 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>;
|
||||
|
@ -66,20 +65,23 @@ const emit = defineEmits<Emits>()
|
|||
const props = defineProps({
|
||||
value: {
|
||||
type: Object as PropType<JsonType[]>,
|
||||
default: () => ([])
|
||||
}
|
||||
})
|
||||
|
||||
const _value = ref<JsonType[]>([])
|
||||
watchEffect(() => {
|
||||
_value.value = props.value
|
||||
_value.value = props.value || [{
|
||||
valueType: {
|
||||
expands: {}
|
||||
},
|
||||
}]
|
||||
})
|
||||
|
||||
watch(_value,
|
||||
() => {
|
||||
emit('update:value', _value.value)
|
||||
},
|
||||
{ deep: true })
|
||||
{ deep: true, immediate: true })
|
||||
|
||||
const editIndex = ref<number>(-1)
|
||||
const handleEdit = (index: number) => {
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<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" />
|
||||
<AIcon type="MenuOutlined" class="item-drag item-icon" />
|
||||
{{ `#${index + 1}.` }}
|
||||
</div>
|
||||
<div class="item-middle item-editable">
|
||||
|
@ -10,7 +10,7 @@
|
|||
<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" />
|
||||
<AIcon type="CloseOutlined" @click="handleClose" />
|
||||
</div>
|
||||
</template>
|
||||
<template #content>
|
||||
|
@ -43,23 +43,22 @@
|
|||
</template>
|
||||
<div class="item-edit" @click="handleEdit(index)">
|
||||
{{ item.name || '配置参数' }}
|
||||
<edit-outlined class="item-icon" />
|
||||
<AIcon type="EditOutlined" class="item-icon" />
|
||||
</div>
|
||||
</a-popover>
|
||||
</div>
|
||||
<div class="item-right">
|
||||
<delete-outlined @click="handleDelete(index)" />
|
||||
<AIcon type="DeleteOutlined" @click="handleDelete(index)" />
|
||||
</div>
|
||||
</div>
|
||||
<a-button type="dashed" block @click="handleAdd">
|
||||
<template #icon><plus-outlined class="item-icon" /></template>
|
||||
<template #icon><AIcon type="PlusOutlined" class="item-icon" /></template>
|
||||
添加指标
|
||||
</a-button>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts" name="MetricsParam">
|
||||
import { PropType } from 'vue'
|
||||
import { MenuOutlined, EditOutlined, DeleteOutlined, PlusOutlined, CloseOutlined } from '@ant-design/icons-vue';
|
||||
import JIndicators from '@/components/JIndicators/index.vue';
|
||||
|
||||
interface Emits {
|
||||
|
|
|
@ -34,7 +34,6 @@ type ValueType = Record<any, any>;
|
|||
const props = defineProps({
|
||||
value: {
|
||||
type: Object as PropType<ValueType>,
|
||||
default: () => ({})
|
||||
},
|
||||
type: {
|
||||
type: String
|
||||
|
@ -62,13 +61,23 @@ interface Emits {
|
|||
}
|
||||
const emit = defineEmits<Emits>()
|
||||
|
||||
const _value = computed({
|
||||
get: () => props.value,
|
||||
set: val => {
|
||||
emit('update:value', val)
|
||||
}
|
||||
// const _value = computed({
|
||||
// get: () => props.value || {},
|
||||
// set: val => {
|
||||
// emit('update:value', val)
|
||||
// }
|
||||
// })
|
||||
const _value = ref<ValueType>({})
|
||||
watchEffect(() => {
|
||||
_value.value = props.value || {}
|
||||
})
|
||||
|
||||
watch(_value,
|
||||
() => {
|
||||
emit('update:value', _value.value)
|
||||
},
|
||||
{ deep: true, immediate: true })
|
||||
|
||||
const options = [
|
||||
{
|
||||
label: '读',
|
||||
|
@ -87,7 +96,7 @@ const options = [
|
|||
const metadataStore = useMetadataStore()
|
||||
|
||||
onMounted(() => {
|
||||
if (props.type === 'product' || !props.value.source) {
|
||||
if (props.type === 'product' || !props.value?.source) {
|
||||
emit('update:value', { ...props.value, source: 'device' })
|
||||
}
|
||||
})
|
||||
|
|
|
@ -15,9 +15,8 @@
|
|||
]">
|
||||
<a-input v-model:value="value.name" size="small"></a-input>
|
||||
</a-form-item>
|
||||
{{ modelType }}
|
||||
<template v-if="modelType === 'properties'">
|
||||
<value-type-form :name="['valueType']" v-model:value="value.valueType" key="property"
|
||||
<value-type-form :name="['valueType']" v-model:value="value.valueType" key="property" title="数据类型"
|
||||
@change-type="changeValueType"></value-type-form>
|
||||
|
||||
<expands-form :name="['expands']" v-model:value="value.expands" :type="type" :id="value.id" :config="config"
|
||||
|
@ -37,12 +36,15 @@
|
|||
]">
|
||||
<JsonParam v-model:value="value.inputs" :name="['inputs']"></JsonParam>
|
||||
</a-form-item>
|
||||
<a-form-item label="输出参数" name="output">
|
||||
<JsonParam v-model:value="value.output" :name="['output']"></JsonParam>
|
||||
|
||||
<value-type-form :name="['output']" v-model:value="value.valueType" key="function"
|
||||
@change-type="changeValueType"></value-type-form>
|
||||
<value-type-form :name="['output']" v-model:value="value.output" key="function" title="输出参数"></value-type-form>
|
||||
</template>
|
||||
<template v-if="modelType === 'events'">
|
||||
<a-form-item label="级别" :name="['expands', 'level']" :rules="[
|
||||
{ required: true, message: '请选择级别' },
|
||||
]">
|
||||
<a-select v-model:value="value.expands.level" :options="EventLevel" size="small"></a-select>
|
||||
</a-form-item>
|
||||
<value-type-form :name="['valueType']" v-model:value="value.valueType" key="function" title="输出参数"></value-type-form>
|
||||
</template>
|
||||
<a-form-item label="说明" name="description" :rules="[
|
||||
{ max: 200, message: '最多可输入200个字符' },
|
||||
|
@ -57,6 +59,7 @@ import ValueTypeForm from './ValueTypeForm.vue'
|
|||
import { useProductStore } from '@/store/product';
|
||||
import { getMetadataConfig } from '@/api/device/product'
|
||||
import JsonParam from '@/components/Metadata/JsonParam/index.vue'
|
||||
import { EventLevel } from '@/views/device/data';
|
||||
|
||||
const props = defineProps({
|
||||
type: {
|
||||
|
@ -73,12 +76,19 @@ const props = defineProps({
|
|||
default: ''
|
||||
}
|
||||
})
|
||||
if (props.modelType === 'events') {
|
||||
if (!props.value.expands) {
|
||||
props.value.expands = {}
|
||||
}
|
||||
}
|
||||
|
||||
const productStore = useProductStore()
|
||||
|
||||
const config = ref<Record<any, any>[]>([])
|
||||
const asyncOtherConfig = async () => {
|
||||
if (props.type !== 'product') return
|
||||
const { valueType: { type }, id } = props.value
|
||||
const { valueType, id } = props.value
|
||||
const { type } = valueType || {}
|
||||
const productId = productStore.current?.id
|
||||
if (!productId || !id || !type) return
|
||||
const resp = await getMetadataConfig({
|
||||
|
@ -93,8 +103,11 @@ const asyncOtherConfig = async () => {
|
|||
config.value = resp.result
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
asyncOtherConfig()
|
||||
if (props.modelType === 'properties') {
|
||||
asyncOtherConfig()
|
||||
}
|
||||
})
|
||||
|
||||
const changeValueType = (val: string) => {
|
||||
|
|
|
@ -1,28 +1,25 @@
|
|||
<template>
|
||||
<a-form-item label="数据类型" :name="name.concat(['type'])" :rules="[
|
||||
{ required: true, message: '请选择数据类型' },
|
||||
<a-form-item :label="title" :name="name.concat(['type'])" :rules="[
|
||||
metadataStore.model.type !== 'functions' ? { required: true, message: '请选择数据类型' } : {},
|
||||
]">
|
||||
<a-select v-model:value="value.type" :options="_dataTypeList" size="small" @change="changeType"></a-select>
|
||||
<a-select v-model:value="_value.type" :options="metadataStore.model.type === 'events' ? eventDataTypeList : _dataTypeList" size="small" @change="changeType"></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 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 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 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="[
|
||||
<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>
|
||||
<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)">
|
||||
<a-form-item :name="name.concat(['expands', 'maxLength'])" v-if="['string', 'password'].includes(_value.type)">
|
||||
<template #label>
|
||||
<a-space>
|
||||
最大长度
|
||||
|
@ -31,19 +28,20 @@
|
|||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
<a-input-number v-model:value="value.expands.maxLength" size="small" :max="2147483647" :min="1" :precision="0"
|
||||
<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 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 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 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">
|
||||
|
@ -57,14 +55,13 @@ 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'
|
||||
import { useMetadataStore } from '@/store/metadata';
|
||||
|
||||
type ValueType = Record<any, any>;
|
||||
const props = defineProps({
|
||||
value: {
|
||||
type: Object as PropType<ValueType>,
|
||||
default: () => ({
|
||||
extends: {}
|
||||
})
|
||||
// default: () => ({})
|
||||
},
|
||||
isSub: {
|
||||
type: Boolean,
|
||||
|
@ -74,6 +71,10 @@ const props = defineProps({
|
|||
type: Array as PropType<string[]>,
|
||||
default: () => ([]),
|
||||
required: true
|
||||
},
|
||||
title: {
|
||||
String,
|
||||
default: '数据类型'
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -83,11 +84,34 @@ interface Emits {
|
|||
}
|
||||
const emit = defineEmits<Emits>()
|
||||
|
||||
// emit('update:value', { extends: {}, ...props.value })
|
||||
|
||||
const _value = computed({
|
||||
get: () => props.value,
|
||||
set: val => {
|
||||
emit('update:value', val)
|
||||
const metadataStore = useMetadataStore()
|
||||
// const _value = computed({
|
||||
// get: () => props.value,
|
||||
// set: val => {
|
||||
// emit('update:value', val)
|
||||
// }
|
||||
// })
|
||||
const _value = ref<ValueType>({})
|
||||
watchEffect(() => {
|
||||
_value.value = props.value || {
|
||||
expands: {}
|
||||
}
|
||||
})
|
||||
|
||||
watch(_value,
|
||||
() => {
|
||||
emit('update:value', _value.value)
|
||||
},
|
||||
{ deep: true, immediate: true })
|
||||
|
||||
onMounted(() => {
|
||||
if (metadataStore.model.type === 'events') {
|
||||
_value.value = {
|
||||
type: 'object',
|
||||
expands: {}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -108,6 +132,12 @@ const unit = {
|
|||
unit.getUnit()
|
||||
|
||||
const _dataTypeList = computed(() => props.isSub ? DataTypeList.filter(item => item.value !== 'array' && item.value !== 'object') : DataTypeList)
|
||||
const eventDataTypeList = [
|
||||
{
|
||||
value: 'object',
|
||||
label: 'object(结构体)',
|
||||
},
|
||||
]
|
||||
|
||||
const changeType = (val: SelectValue) => {
|
||||
emit('changeType', val as string)
|
||||
|
|
|
@ -59,12 +59,7 @@ const title = computed(() => metadataStore.model.action === 'add' ? '新增' : '
|
|||
const propertyForm = ref()
|
||||
|
||||
const form = reactive({
|
||||
model: {
|
||||
valueType: {
|
||||
expands: {}
|
||||
},
|
||||
expands: {}
|
||||
} as any,
|
||||
model: {} as any,
|
||||
})
|
||||
if (metadataStore.model.action === 'edit') {
|
||||
form.model = metadataStore.model.item
|
||||
|
@ -144,6 +139,44 @@ const save = reactive({
|
|||
})
|
||||
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
<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>
|
|
@ -9,7 +9,7 @@
|
|||
title: operateLimits('add', type) ? '当前的存储方式不支持新增' : '新增',
|
||||
}">
|
||||
<template #icon>
|
||||
<PlusOutlined />
|
||||
<AIcon type="PlusOutlined" />
|
||||
</template>
|
||||
新增
|
||||
</PermissionButton>
|
||||
|
@ -38,7 +38,7 @@
|
|||
:udisabled="operateLimits('updata', type)" @click="handleEditClick(slotProps)" :tooltip="{
|
||||
title: operateLimits('updata', type) ? '当前的存储方式不支持编辑' : '编辑',
|
||||
}">
|
||||
<EditOutlined />
|
||||
<AIcon type="EditOutlined" />
|
||||
</PermissionButton>
|
||||
<PermissionButton :uhas-permission="`${permission}:delete`" type="link" key="delete" style="padding: 0"
|
||||
:pop-confirm="{
|
||||
|
@ -48,7 +48,7 @@
|
|||
}" :tooltip="{
|
||||
title: '删除',
|
||||
}">
|
||||
<DeleteOutlined />
|
||||
<Aicon type="DeleteOutlined" />
|
||||
</PermissionButton>
|
||||
</a-space>
|
||||
</template>
|
||||
|
@ -62,7 +62,6 @@ import { useInstanceStore } from '@/store/instance'
|
|||
import { useProductStore } from '@/store/product'
|
||||
import { useMetadataStore } from '@/store/metadata'
|
||||
import PermissionButton from '@/components/PermissionButton/index.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'
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
@ok="handleImport" :confirm-loading="loading">
|
||||
<div class="import-content">
|
||||
<p class="import-tip">
|
||||
<exclamation-circle-outlined style="margin-right: 5px" />
|
||||
<AIcon type="ExclamationCircleOutlined" style="margin-right: 5px" />
|
||||
导入的物模型会覆盖原来的属性、功能、事件、标签,请谨慎操作。
|
||||
</p>
|
||||
</div>
|
||||
|
@ -37,8 +37,7 @@
|
|||
<a-upload v-model:file-list="fileList" :before-upload="beforeUpload" accept=".json"
|
||||
:show-upload-list="false" :action="FILE_UPLOAD" @change="fileChange"
|
||||
:headers="{ 'X-Access-Token': getToken()}">
|
||||
<upload-outlined class="upload-button"/>
|
||||
<!-- <button id="uploadFile" style="display: none;"></button> -->
|
||||
<AIcon type="UploadOutlined" class="upload-button" />
|
||||
</a-upload>
|
||||
</template>
|
||||
</a-input>
|
||||
|
@ -62,9 +61,8 @@ import { Store } from 'jetlinks-store';
|
|||
import { SystemConst } from '@/utils/consts';
|
||||
import { useInstanceStore } from '@/store/instance'
|
||||
import { useProductStore } from '@/store/product';
|
||||
import { UploadOutlined, ExclamationCircleOutlined } from '@ant-design/icons-vue';
|
||||
import { FILE_UPLOAD } from '@/api/comm';
|
||||
import { LocalStore, getToken } from '@/utils/comm';
|
||||
import { getToken } from '@/utils/comm';
|
||||
import MonacoEditor from '@/components/MonacoEditor/index.vue'
|
||||
|
||||
const route = useRoute()
|
||||
|
@ -258,13 +256,15 @@ const handleImport = async () => {
|
|||
if (resp.status === 200) {
|
||||
if (props?.type === 'device') {
|
||||
const metadata: DeviceMetadata = JSON.parse(paramsDevice || '{}')
|
||||
// TODO导入
|
||||
// MetadataAction.insert(metadata);
|
||||
instanceStore.setCurrent(metadata)
|
||||
// instanceStore.setCurrent(metadata)
|
||||
message.success('导入成功')
|
||||
} else {
|
||||
const metadata: ProductItem = JSON.parse(params?.metadata || '{}')
|
||||
// TODO导入
|
||||
// MetadataAction.insert(metadata);
|
||||
productStore.setCurrent(metadata)
|
||||
// productStore.setCurrent(metadata)
|
||||
message.success('导入成功')
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
? '该设备已脱离产品物模型,修改产品物模型对该设备无影响'
|
||||
: '设备会默认继承产品的物模型,修改设备物模型后将脱离产品物模型'">
|
||||
<div class="ellipsis">
|
||||
<info-circle-outlined style="margin-right: 3px" />
|
||||
<AIcon type="InfoCircleOutlined" style="margin-right: 3px" />
|
||||
{{
|
||||
instanceStore.detail?.independentMetadata && type === 'device'
|
||||
? '该设备已脱离产品物模型,修改产品物模型对该设备无影响'
|
||||
|
@ -47,7 +47,6 @@
|
|||
</a-card>
|
||||
</template>
|
||||
<script setup lang="ts" name="Metadata">
|
||||
import { InfoCircleOutlined } from '@ant-design/icons-vue';
|
||||
import PermissionButton from '@/components/PermissionButton/index.vue'
|
||||
import { deleteMetadata } from '@/api/device/instance.js'
|
||||
import { message } from 'ant-design-vue'
|
||||
|
|
|
@ -1,10 +1,16 @@
|
|||
<template>
|
||||
<div class="iot-home-container" v-loading="loading">
|
||||
<InitHome v-if="currentView === 'init'" @refresh="setCurrentView" />
|
||||
<DeviceHome v-else-if="currentView === 'device'" />
|
||||
<DevOpsHome v-else-if="currentView === 'ops'" />
|
||||
<ComprehensiveHome v-else-if="currentView === 'comprehensive'" />
|
||||
</div>
|
||||
<page-container>
|
||||
<div class="iot-home-container" v-loading="loading">
|
||||
<InitHome v-if="currentView === 'init'" @refresh="setCurrentView" />
|
||||
<DeviceHome v-else-if="currentView === 'device'" />
|
||||
<DevOpsHome v-else-if="currentView === 'ops'" />
|
||||
<ComprehensiveHome v-else-if="currentView === 'comprehensive'" />
|
||||
|
||||
<Api :mode="'home'" hasHome showTitle>
|
||||
<template #top> </template>
|
||||
</Api>
|
||||
</div>
|
||||
</page-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
@ -12,6 +18,7 @@ import InitHome from './components/InitHome/index.vue';
|
|||
import DeviceHome from './components/DeviceHome/index.vue';
|
||||
import DevOpsHome from './components/DevOpsHome/index.vue';
|
||||
import ComprehensiveHome from './components/ComprehensiveHome/index.vue';
|
||||
import Api from '@/views/system/Platforms/Api/index.vue';
|
||||
|
||||
import { isNoCommunity } from '@/utils/utils';
|
||||
import { getMe_api, getView_api } from '@/api/home';
|
||||
|
|
|
@ -1,380 +0,0 @@
|
|||
<template>
|
||||
<div class="api-does-container">
|
||||
<div class="top">
|
||||
<h5>{{ selectApi.summary }}</h5>
|
||||
<div class="input">
|
||||
<InputCard :value="selectApi.method" />
|
||||
<a-input :value="selectApi?.url" disabled />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
<span class="label">请求数据类型</span>
|
||||
<span>{{
|
||||
getContent(selectApi.requestBody) ||
|
||||
'application/x-www-form-urlencoded'
|
||||
}}</span>
|
||||
<span class="label">响应数据类型</span>
|
||||
<span>{{ `["/"]` }}</span>
|
||||
</p>
|
||||
|
||||
<div class="api-card">
|
||||
<h5>请求参数</h5>
|
||||
<div class="content">
|
||||
<JTable
|
||||
:columns="requestCard.columns"
|
||||
:dataSource="requestCard.tableData"
|
||||
noPagination
|
||||
model="TABLE"
|
||||
>
|
||||
<template #required="slotProps">
|
||||
<span>{{ Boolean(slotProps.required) + '' }}</span>
|
||||
</template>
|
||||
<template #type="slotProps">
|
||||
<span>{{ slotProps.schema.type }}</span>
|
||||
</template>
|
||||
</JTable>
|
||||
</div>
|
||||
</div>
|
||||
<div class="api-card">
|
||||
<h5>响应状态</h5>
|
||||
<div class="content">
|
||||
<JTable
|
||||
:columns="responseStatusCard.columns"
|
||||
:dataSource="responseStatusCard.tableData"
|
||||
noPagination
|
||||
model="TABLE"
|
||||
>
|
||||
</JTable>
|
||||
|
||||
<a-tabs v-model:activeKey="responseStatusCard.activeKey">
|
||||
<a-tab-pane
|
||||
:key="key"
|
||||
:tab="key"
|
||||
v-for="key in tabs"
|
||||
></a-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="api-card">
|
||||
<h5>响应参数</h5>
|
||||
<div class="content">
|
||||
<JTable
|
||||
:columns="respParamsCard.columns"
|
||||
:dataSource="respParamsCard.tableData"
|
||||
noPagination
|
||||
model="TABLE"
|
||||
>
|
||||
</JTable>
|
||||
</div>
|
||||
|
||||
<MonacoEditor
|
||||
v-model:modelValue="codeText"
|
||||
style="height: 300px; width: 100%"
|
||||
theme="vs"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import MonacoEditor from '@/components/MonacoEditor/index.vue';
|
||||
import type { apiDetailsType } from '../typing';
|
||||
import InputCard from './InputCard.vue';
|
||||
import { PropType } from 'vue';
|
||||
|
||||
const emit = defineEmits(['update:paramsTable'])
|
||||
const props = defineProps({
|
||||
selectApi: {
|
||||
type: Object as PropType<apiDetailsType>,
|
||||
required: true,
|
||||
},
|
||||
schemas: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
const { selectApi } = toRefs(props);
|
||||
|
||||
type tableCardType = {
|
||||
columns: object[];
|
||||
tableData: object[];
|
||||
codeText?: any;
|
||||
activeKey?: any;
|
||||
getData?: any;
|
||||
};
|
||||
const requestCard = reactive<tableCardType>({
|
||||
columns: [
|
||||
{
|
||||
title: '参数名',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
},
|
||||
{
|
||||
title: '参数说明',
|
||||
dataIndex: 'description',
|
||||
key: 'description',
|
||||
},
|
||||
{
|
||||
title: '请求类型',
|
||||
dataIndex: 'in',
|
||||
key: 'in',
|
||||
},
|
||||
{
|
||||
title: '是否必须',
|
||||
dataIndex: 'required',
|
||||
key: 'required',
|
||||
scopedSlots: true,
|
||||
},
|
||||
{
|
||||
title: '参数类型',
|
||||
dataIndex: 'type',
|
||||
key: 'type',
|
||||
scopedSlots: true,
|
||||
},
|
||||
],
|
||||
tableData: [],
|
||||
getData: () => {
|
||||
requestCard.tableData = props.selectApi.parameters;
|
||||
},
|
||||
});
|
||||
const responseStatusCard = reactive<tableCardType>({
|
||||
activeKey: '',
|
||||
columns: [
|
||||
{
|
||||
title: '状态码',
|
||||
dataIndex: 'code',
|
||||
key: 'code',
|
||||
},
|
||||
{
|
||||
title: '说明',
|
||||
dataIndex: 'desc',
|
||||
key: 'desc',
|
||||
},
|
||||
{
|
||||
title: 'schema',
|
||||
dataIndex: 'schema',
|
||||
key: 'schema',
|
||||
},
|
||||
],
|
||||
tableData: [],
|
||||
getData: () => {
|
||||
if (!Object.keys(props.selectApi.responses).length)
|
||||
return (responseStatusCard.tableData = []);
|
||||
|
||||
const tableData = <any>[];
|
||||
Object.entries(props.selectApi.responses || {}).forEach((item: any) => {
|
||||
const desc = item[1].description;
|
||||
const schema = item[1].content['*/*'].schema.$ref?.split('/') || '';
|
||||
|
||||
tableData.push({
|
||||
code: item[0],
|
||||
desc,
|
||||
schema: schema && schema.pop(),
|
||||
});
|
||||
});
|
||||
responseStatusCard.activeKey = tableData[0]?.code;
|
||||
responseStatusCard.tableData = tableData;
|
||||
},
|
||||
});
|
||||
const tabs = computed(() =>
|
||||
responseStatusCard.tableData
|
||||
.map((item: any) => item.code + '')
|
||||
.filter((code: string) => code !== '400'),
|
||||
);
|
||||
const respParamsCard = reactive<tableCardType>({
|
||||
columns: [
|
||||
{
|
||||
title: '参数名称',
|
||||
dataIndex: 'paramsName',
|
||||
},
|
||||
{
|
||||
title: '参数说明',
|
||||
dataIndex: 'desc',
|
||||
},
|
||||
{
|
||||
title: '类型',
|
||||
dataIndex: 'paramsType',
|
||||
},
|
||||
],
|
||||
tableData: [],
|
||||
codeText: '',
|
||||
getData: (code: string) => {
|
||||
type schemaObjType = {
|
||||
paramsName: string;
|
||||
paramsType: string;
|
||||
desc?: string;
|
||||
children?: schemaObjType[];
|
||||
};
|
||||
|
||||
const schemaName = responseStatusCard.tableData.find(
|
||||
(item: any) => item.code === code,
|
||||
)?.schema;
|
||||
const schemas = toRaw(props.schemas);
|
||||
const basicType = ['string', 'integer', 'boolean'];
|
||||
|
||||
const tableData = findData(schemaName);
|
||||
const codeText = getCodeText(tableData, 3);
|
||||
|
||||
emit('update:paramsTable', tableData)
|
||||
respParamsCard.tableData = tableData;
|
||||
respParamsCard.codeText = JSON.stringify(codeText);
|
||||
|
||||
function findData(schemaName: string) {
|
||||
if (!schemaName || !schemas[schemaName]) {
|
||||
return [];
|
||||
}
|
||||
const result: schemaObjType[] = [];
|
||||
const schema = schemas[schemaName];
|
||||
Object.entries(schema.properties).forEach((item: [string, any]) => {
|
||||
const paramsType =
|
||||
item[1].type ||
|
||||
(item[1].$ref && item[1].$ref.split('/').pop()) ||
|
||||
(item[1].items && item[1].items.$ref.split('/').pop()) ||
|
||||
'';
|
||||
const schemaObj: schemaObjType = {
|
||||
paramsName: item[0],
|
||||
paramsType,
|
||||
desc: item[1].description || '',
|
||||
};
|
||||
if (!basicType.includes(paramsType))
|
||||
schemaObj.children = findData(paramsType);
|
||||
result.push(schemaObj);
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
function getCodeText(arr: schemaObjType[], level: number): object {
|
||||
const result = {};
|
||||
|
||||
arr.forEach((item) => {
|
||||
switch (item.paramsType) {
|
||||
case 'string':
|
||||
result[item.paramsName] = '';
|
||||
break;
|
||||
case 'integer':
|
||||
result[item.paramsName] = 0;
|
||||
break;
|
||||
case 'boolean':
|
||||
result[item.paramsName] = true;
|
||||
break;
|
||||
case 'array':
|
||||
result[item.paramsName] = [];
|
||||
break;
|
||||
case 'object':
|
||||
result[item.paramsName] = {};
|
||||
break;
|
||||
default: {
|
||||
const properties = schemas[item.paramsType]
|
||||
.properties as object;
|
||||
const newArr = Object.entries(properties).map(
|
||||
(item: [string, any]) => ({
|
||||
paramsName: item[0],
|
||||
paramsType: level
|
||||
? (item[1].$ref &&
|
||||
item[1].$ref.split('/').pop()) ||
|
||||
(item[1].items &&
|
||||
item[1].items.$ref
|
||||
.split('/')
|
||||
.pop()) ||
|
||||
item[1].type ||
|
||||
''
|
||||
: item[1].type,
|
||||
}),
|
||||
);
|
||||
result[item.paramsName] = getCodeText(
|
||||
newArr,
|
||||
level - 1,
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const { codeText } = toRefs(requestCard);
|
||||
|
||||
const getContent = (data: any) => {
|
||||
if (data && data.content) {
|
||||
return Object.keys(data.content || {})[0];
|
||||
}
|
||||
return '';
|
||||
};
|
||||
onMounted(() => {
|
||||
requestCard.getData();
|
||||
responseStatusCard.getData();
|
||||
});
|
||||
watch(
|
||||
() => props.selectApi,
|
||||
() => {
|
||||
requestCard.getData();
|
||||
responseStatusCard.getData();
|
||||
},
|
||||
);
|
||||
|
||||
watch([() => responseStatusCard.activeKey, () => props.selectApi], (n) => {
|
||||
n[0] && respParamsCard.getData(n[0]);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.api-does-container {
|
||||
.top {
|
||||
width: 100%;
|
||||
|
||||
h5 {
|
||||
font-weight: bold;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.input {
|
||||
display: flex;
|
||||
margin: 24px 0;
|
||||
}
|
||||
}
|
||||
p {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 14px;
|
||||
|
||||
.label {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
.api-card {
|
||||
margin-top: 24px;
|
||||
h5 {
|
||||
position: relative;
|
||||
padding-left: 10px;
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 4px;
|
||||
height: 100%;
|
||||
background-color: #1d39c4;
|
||||
border-radius: 0 3px 3px 0;
|
||||
content: ' ';
|
||||
}
|
||||
}
|
||||
.content {
|
||||
padding-left: 10px;
|
||||
|
||||
:deep(.jtable-body) {
|
||||
padding: 0;
|
||||
|
||||
.jtable-body-header {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,134 +0,0 @@
|
|||
<template>
|
||||
<div class="api-test-container">
|
||||
<div class="top">
|
||||
<h5>{{ props.selectApi.summary }}</h5>
|
||||
<div class="input">
|
||||
<InputCard :value="props.selectApi.method" />
|
||||
<a-input :value="props.selectApi?.url" disabled />
|
||||
<span class="send">发送</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="api-card">
|
||||
<h5>请求参数</h5>
|
||||
<div class="content">
|
||||
<!-- <VueJsoneditor
|
||||
height="400"
|
||||
mode="tree"
|
||||
v-model:text="requestBody.paramsText"
|
||||
/> -->
|
||||
<MonacoEditor
|
||||
v-model:modelValue="requestBody.paramsText"
|
||||
style="height: 300px; width: 100%"
|
||||
theme="vs"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="api-card">
|
||||
<h5>响应参数</h5>
|
||||
<div class="content">
|
||||
<VueJsoneditor
|
||||
height="400"
|
||||
mode="tree"
|
||||
v-model:text="responsesContent"
|
||||
/>
|
||||
<!-- <MonacoEditor
|
||||
v-model:modelValue="responsesContent"
|
||||
style="height: 300px; width: 100%"
|
||||
theme="vs"
|
||||
/> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import VueJsoneditor from 'vue3-ts-jsoneditor';
|
||||
import MonacoEditor from '@/components/MonacoEditor/index.vue';
|
||||
import type { apiDetailsType } from '../typing';
|
||||
import InputCard from './InputCard.vue';
|
||||
|
||||
const props = defineProps<{
|
||||
selectApi: apiDetailsType;
|
||||
paramsTable: any[];
|
||||
}>();
|
||||
|
||||
const requestBody = reactive({
|
||||
paramsTable: [] as requestObj[],
|
||||
paramsText: '',
|
||||
});
|
||||
|
||||
const responsesContent = ref('{"a":123}');
|
||||
|
||||
watch(
|
||||
() => props.paramsTable,
|
||||
(n) => {
|
||||
const table = n?.map((item: any) => ({
|
||||
paramsName: item.paramsName,
|
||||
value: '',
|
||||
}));
|
||||
requestBody.paramsTable = table;
|
||||
},
|
||||
);
|
||||
|
||||
type requestObj = {
|
||||
paramsName: string;
|
||||
value: string;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.api-test-container {
|
||||
.top {
|
||||
width: 100%;
|
||||
|
||||
h5 {
|
||||
font-weight: bold;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.input {
|
||||
display: flex;
|
||||
|
||||
.send {
|
||||
width: 65px;
|
||||
padding: 4px 15px;
|
||||
font-size: 14px;
|
||||
color: #fff;
|
||||
background-color: #1890ff;
|
||||
}
|
||||
}
|
||||
}
|
||||
.api-card {
|
||||
margin-top: 24px;
|
||||
h5 {
|
||||
position: relative;
|
||||
padding-left: 10px;
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 4px;
|
||||
height: 100%;
|
||||
background-color: #1d39c4;
|
||||
border-radius: 0 3px 3px 0;
|
||||
content: ' ';
|
||||
}
|
||||
}
|
||||
.content {
|
||||
padding-left: 10px;
|
||||
|
||||
:deep(.jtable-body) {
|
||||
padding: 0;
|
||||
|
||||
.jtable-body-header {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,66 +0,0 @@
|
|||
<template>
|
||||
<div class="choose-api-container">
|
||||
<JTable
|
||||
:columns="columns"
|
||||
:dataSource="props.tableData"
|
||||
:rowSelection="rowSelection"
|
||||
noPagination
|
||||
model="TABLE"
|
||||
>
|
||||
<template #url="slotProps">
|
||||
<span
|
||||
style="color: #1d39c4; cursor: pointer"
|
||||
@click="jump(slotProps)"
|
||||
>{{ slotProps.url}}</span
|
||||
>
|
||||
</template>
|
||||
</JTable>
|
||||
|
||||
<a-button type="primary">保存</a-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { TableProps } from 'ant-design-vue';
|
||||
|
||||
const emits = defineEmits(['update:clickApi'])
|
||||
const props = defineProps({
|
||||
tableData: Array,
|
||||
clickApi: Object
|
||||
});
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: 'API',
|
||||
dataIndex: 'url',
|
||||
key: 'url',
|
||||
scopedSlots: true,
|
||||
},
|
||||
{
|
||||
title: '说明',
|
||||
dataIndex: 'summary',
|
||||
key: 'summary',
|
||||
},
|
||||
];
|
||||
const rowSelection: TableProps['rowSelection'] = {
|
||||
onChange: (selectedRowKeys, selectedRows) => {
|
||||
console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows);
|
||||
},
|
||||
};
|
||||
|
||||
const jump = (row:any) => {
|
||||
emits('update:clickApi',row)
|
||||
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.choose-api-container {
|
||||
height: 100%;
|
||||
|
||||
:deep(.jtable-body-header) {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
|
@ -1,35 +0,0 @@
|
|||
<template>
|
||||
<span class="input-card-container" :class="props.value">
|
||||
{{ props.value?.toLocaleUpperCase() }}
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const props = defineProps({
|
||||
value: String,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.input-card-container {
|
||||
padding: 4px 15px;
|
||||
font-size: 14px;
|
||||
color: #fff;
|
||||
|
||||
&.get {
|
||||
background-color: #1890ff;
|
||||
}
|
||||
&.put {
|
||||
background-color: #fa8c16;
|
||||
}
|
||||
&.post {
|
||||
background-color: #52c41a;
|
||||
}
|
||||
&.delete {
|
||||
background-color: #f5222d;
|
||||
}
|
||||
&.patch {
|
||||
background-color: #a0d911;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,97 +0,0 @@
|
|||
<template>
|
||||
<a-tree
|
||||
:tree-data="treeData"
|
||||
@select="clickSelectItem"
|
||||
showLine
|
||||
class="left-tree-container"
|
||||
>
|
||||
<template #title="{ name }">
|
||||
{{ name }}
|
||||
</template>
|
||||
</a-tree>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { TreeProps } from 'ant-design-vue';
|
||||
|
||||
import { getTreeOne_api, getTreeTwo_api } from '@/api/system/apiPage';
|
||||
import { treeNodeTpye } from '../typing';
|
||||
|
||||
const emits = defineEmits(['select']);
|
||||
|
||||
const treeData = ref<TreeProps['treeData']>([]);
|
||||
|
||||
const getTreeData = () => {
|
||||
let tree: treeNodeTpye[] = [];
|
||||
getTreeOne_api().then((resp: any) => {
|
||||
tree = resp.urls.map((item: any) => ({
|
||||
...item,
|
||||
key: item.url,
|
||||
}));
|
||||
const allPromise = tree.map((item) => getTreeTwo_api(item.name));
|
||||
Promise.all(allPromise).then((values) => {
|
||||
values.forEach((item: any, i) => {
|
||||
tree[i].children = combData(item?.paths);
|
||||
tree[i].schemas = item.components.schemas
|
||||
});
|
||||
treeData.value = tree;
|
||||
});
|
||||
});
|
||||
};
|
||||
const clickSelectItem: TreeProps['onSelect'] = (key, node: any) => {
|
||||
if(!node.node.parent) return
|
||||
emits('select', node.node.dataRef, node.node?.parent.node.schemas);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getTreeData();
|
||||
});
|
||||
|
||||
const combData = (dataSource: object) => {
|
||||
const apiList: treeNodeTpye[] = [];
|
||||
const keys = Object.keys(dataSource);
|
||||
|
||||
keys.forEach((key) => {
|
||||
const method = Object.keys(dataSource[key] || {})[0];
|
||||
const name = dataSource[key][method].tags[0];
|
||||
let apiObj: treeNodeTpye | undefined = apiList.find(
|
||||
(item) => item.name === name,
|
||||
);
|
||||
if (apiObj) {
|
||||
apiObj.apiList?.push({
|
||||
url: key,
|
||||
method: dataSource[key],
|
||||
});
|
||||
} else {
|
||||
apiObj = {
|
||||
name,
|
||||
key: name,
|
||||
apiList: [
|
||||
{
|
||||
url: key,
|
||||
method: dataSource[key],
|
||||
},
|
||||
],
|
||||
};
|
||||
apiList.push(apiObj);
|
||||
}
|
||||
});
|
||||
|
||||
return apiList;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.left-tree-container {
|
||||
border-right: 1px solid #e9e9e9;
|
||||
height: calc(100vh - 150px);
|
||||
overflow-y: auto;
|
||||
.ant-tree-list {
|
||||
.ant-tree-list-holder-inner {
|
||||
.ant-tree-switcher-noop {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,95 +1,12 @@
|
|||
<template>
|
||||
<a-card class="api-page-container">
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="5">
|
||||
<LeftTree @select="treeSelect" />
|
||||
</a-col>
|
||||
<a-col :span="19">
|
||||
<ChooseApi
|
||||
v-show="!selectedApi.url"
|
||||
v-model:click-api="selectedApi"
|
||||
:table-data="tableData"
|
||||
/>
|
||||
|
||||
<div
|
||||
class="api-details"
|
||||
v-if="selectedApi.url && tableData.length > 0"
|
||||
>
|
||||
<a-button
|
||||
@click="selectedApi = initSelectedApi"
|
||||
style="margin-bottom: 24px"
|
||||
>返回</a-button
|
||||
>
|
||||
<a-tabs v-model:activeKey="activeKey" type="card">
|
||||
<a-tab-pane key="does" tab="文档">
|
||||
<ApiDoes
|
||||
:select-api="selectedApi"
|
||||
:schemas="schemas"
|
||||
v-model:params-table="paramsTable"
|
||||
/>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="test" tab="调试">
|
||||
<ApiTest :select-api="selectedApi" :params-table="paramsTable" />
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
<page-container>
|
||||
<Api :mode="'appManger'" hasHome>
|
||||
</Api>
|
||||
</page-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="apiPage">
|
||||
import type { treeNodeTpye, apiObjType, apiDetailsType } from './typing';
|
||||
import LeftTree from './components/LeftTree.vue';
|
||||
import ChooseApi from './components/ChooseApi.vue';
|
||||
import ApiDoes from './components/ApiDoes.vue';
|
||||
import ApiTest from './components/ApiTest.vue';
|
||||
|
||||
const tableData = ref([]);
|
||||
const treeSelect = (node: treeNodeTpye, nodeSchemas: object = {}) => {
|
||||
schemas.value = nodeSchemas;
|
||||
if (!node.apiList) return;
|
||||
const apiList: apiObjType[] = node.apiList as apiObjType[];
|
||||
const table: any = [];
|
||||
// 将对象形式的数据转换为表格需要的形式
|
||||
apiList?.forEach((apiItem) => {
|
||||
const { method, url } = apiItem;
|
||||
for (const key in method) {
|
||||
if (Object.prototype.hasOwnProperty.call(method, key)) {
|
||||
table.push({
|
||||
...method[key],
|
||||
url,
|
||||
method: key,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
tableData.value = table;
|
||||
};
|
||||
|
||||
const activeKey = ref<'does' | 'test'>('does');
|
||||
const schemas = ref({});
|
||||
const paramsTable = ref([])
|
||||
const initSelectedApi: apiDetailsType = {
|
||||
url: '',
|
||||
method: '',
|
||||
summary: '',
|
||||
parameters: [],
|
||||
responses: {},
|
||||
requestBody: {},
|
||||
};
|
||||
const selectedApi = ref<apiDetailsType>(initSelectedApi);
|
||||
|
||||
watch(tableData, () => {
|
||||
activeKey.value = 'does';
|
||||
selectedApi.value = initSelectedApi;
|
||||
});
|
||||
import Api from '@/views/system/Platforms/Api/index.vue';
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.api-page-container {
|
||||
padding: 24px;
|
||||
height: 100%;
|
||||
background-color: transparent;
|
||||
}
|
||||
</style>
|
||||
<style scoped></style>
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
export type treeNodeTpye = {
|
||||
name: string;
|
||||
key: string;
|
||||
schemas?:object;
|
||||
link?: string;
|
||||
apiList?: object[];
|
||||
children?: treeNodeTpye[];
|
||||
|
||||
};
|
||||
export type methodType = {
|
||||
[key: string]: object
|
||||
}
|
||||
export type apiObjType = {
|
||||
url: string,
|
||||
method: methodType
|
||||
}
|
||||
|
||||
export type apiDetailsType = {
|
||||
url: string;
|
||||
method: string;
|
||||
summary: string;
|
||||
parameters: any[];
|
||||
requestBody?: any;
|
||||
responses:object;
|
||||
}
|
|
@ -1022,9 +1022,9 @@
|
|||
.logoUrl
|
||||
"
|
||||
alt="avatar"
|
||||
width="100%"
|
||||
style="width: 150px;"
|
||||
/>
|
||||
<div v-else>
|
||||
<div v-else style="width: 150px;">
|
||||
<AIcon
|
||||
:type="
|
||||
form.uploadLoading
|
||||
|
@ -1777,6 +1777,16 @@ function clearNullProp(obj: object) {
|
|||
:deep(.ant-form-item-control) {
|
||||
.ant-form-item-control-input-content {
|
||||
display: flex;
|
||||
.ant-upload-select-picture-card {
|
||||
width: auto;
|
||||
height: auto;
|
||||
max-width: 150px;
|
||||
max-height: 150px;
|
||||
|
||||
>.ant-upload {
|
||||
height: 150px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
<template>
|
||||
<page-container>
|
||||
<Api :mode="'appManger'" hasHome showTitle :code="code">
|
||||
</Api>
|
||||
</page-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="apiPage">
|
||||
import Api from '@/views/system/Platforms/Api/index.vue';
|
||||
const route = useRoute()
|
||||
const code = route.query.code as string
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<page-container class="apply-container">
|
||||
<page-container>
|
||||
<div class="apply-container">
|
||||
<Search :columns="columns" @search="search" />
|
||||
|
||||
|
@ -374,7 +374,13 @@ const table = {
|
|||
title: '赋权',
|
||||
},
|
||||
icon: 'icon-fuquan',
|
||||
onClick: () => {},
|
||||
onClick: () => {
|
||||
menuStory.jumpPage(
|
||||
'system/Apply/Api',
|
||||
{},
|
||||
{ code: data.id },
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
permission: true,
|
||||
|
@ -384,7 +390,13 @@ const table = {
|
|||
title: '查看API',
|
||||
},
|
||||
icon: 'icon-chakanAPI',
|
||||
onClick: () => {},
|
||||
onClick: () => {
|
||||
menuStory.jumpPage(
|
||||
'system/Apply/View',
|
||||
{},
|
||||
{ code: data.id },
|
||||
);
|
||||
},
|
||||
},
|
||||
);
|
||||
// 其他不为空
|
||||
|
|
|
@ -0,0 +1,229 @@
|
|||
<template>
|
||||
<div class="home">
|
||||
<h1>第三方接入说明</h1>
|
||||
<div style="color: #666666">
|
||||
第三方平台接口请求基于数据签名调用方式,使用签名来校验客户端请求的完整性以及合法性,您可以参看如下文档,来构造
|
||||
HTTP 接口以调用对应的第三方平台接口 。
|
||||
</div>
|
||||
<h2>签名示例说明</h2>
|
||||
<div class="h2-text">1. 签名方式,支持MD5和Sha256两种方式.</div>
|
||||
<div class="h2-text">
|
||||
2. 发起请求的签名信息都需要放到请求头中,而不是请求体.
|
||||
</div>
|
||||
<div
|
||||
style="
|
||||
display: flex;
|
||||
border: 1px solid #e6e6e6;
|
||||
padding: 15;
|
||||
justify-content: space-between;
|
||||
"
|
||||
>
|
||||
<div>
|
||||
<h3>签名规则</h3>
|
||||
<p>
|
||||
注意:签名时间戳与服务器时间不能相差五分钟以上,否则服务器将拒绝本次请求
|
||||
</p>
|
||||
<div class="div-border">
|
||||
<div class="h3-text">
|
||||
将参数key按ascii排序得到: pageIndex=0&pageSize=20
|
||||
</div>
|
||||
<div class="h3-text">
|
||||
使用拼接时间戳以及密钥得到:
|
||||
pageIndex=0&pageSize=201574993804802testSecure
|
||||
</div>
|
||||
<div class="h3-text">
|
||||
使用md5(pageIndex=0&pageSize=201574993804802testSecure)得到837fe7fa29e7a5e4852d447578269523
|
||||
</div>
|
||||
</div>
|
||||
<h3>请求头示例</h3>
|
||||
<div class="div-border">
|
||||
<div class="h3-text">
|
||||
GET /api/device?pageIndex=0&pageSize=20
|
||||
</div>
|
||||
<div class="h3-text">X-Client-Id: testId</div>
|
||||
<div class="h3-text">X-Timestamp: 1574993804802</div>
|
||||
<div class="h3-text">
|
||||
X-Sign: 837fe7fa29e7a5e4852d447578269523
|
||||
</div>
|
||||
</div>
|
||||
<h3>响应结果示例</h3>
|
||||
<div class="div-border">
|
||||
<div class="h3-text">xxx</div>
|
||||
<div class="h3-text">HTTP/1.1 200 OK</div>
|
||||
<div class="h3-text">X-Timestamp: 1574994269075</div>
|
||||
<div class="h3-text">
|
||||
X-Sign: c23faa3c46784ada64423a8bba433f25
|
||||
</div>
|
||||
<div class="h3-text">status:200,result:[ ]</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="width: 50%">
|
||||
<h3>示例数据</h3>
|
||||
<div>
|
||||
<JTable
|
||||
:dataSource="data"
|
||||
model="TABLE"
|
||||
noPagination
|
||||
:columns="[
|
||||
{
|
||||
title: '示例数据类型',
|
||||
dataIndex: 'type',
|
||||
},
|
||||
{
|
||||
title: '示例数据',
|
||||
dataIndex: 'data',
|
||||
},
|
||||
]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
:style="{
|
||||
display: 'flex',
|
||||
border: '1px solid #e6e6e6',
|
||||
padding: 15,
|
||||
justifyContent: 'space-between',
|
||||
marginTop: 20,
|
||||
}"
|
||||
>
|
||||
<div>
|
||||
<h3>服务器验签流程</h3>
|
||||
<div>
|
||||
<img :src="getImage('/apiHome.png')" style="width: 80%" />
|
||||
</div>
|
||||
</div>
|
||||
<div style="width: 505px">
|
||||
<h3>验签说明</h3>
|
||||
<div>
|
||||
<p>使用和签名相同的算法(不需要对响应结果排序)</p>
|
||||
<div>
|
||||
<MonacoEditor
|
||||
style="width: 100%; height: 620px"
|
||||
theme="vs-dark"
|
||||
language="java"
|
||||
v-model="javaStr1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h2>java SDK接入说明</h2>
|
||||
<div class="div-border">
|
||||
<div class="h3-text">
|
||||
JetLinks平台java SDK基于java 8版本开发。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3>添加 SDK 依赖</h3>
|
||||
<div class="h3-text">将以下Maven依赖加入到pom.xml文件中</div>
|
||||
<div>
|
||||
<MonacoEditor
|
||||
style="width: 100%; height: 100px"
|
||||
theme="vs-dark"
|
||||
v-model="javaStr2"
|
||||
language="java"
|
||||
/>
|
||||
</div>
|
||||
<h3>SDK 客户端的初始化和请求方式</h3>
|
||||
<div>
|
||||
<MonacoEditor
|
||||
style="width: 100%; height: 370px"
|
||||
theme="vs-dark"
|
||||
v-model="javaStr"
|
||||
language="java"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { getImage } from '@/utils/comm';
|
||||
import MonacoEditor from '@/components/MonacoEditor/index.vue';
|
||||
|
||||
const data = [
|
||||
{
|
||||
key: '1',
|
||||
type: 'clientId',
|
||||
data: 'testId',
|
||||
},
|
||||
{
|
||||
key: '2',
|
||||
type: 'secureKey',
|
||||
data: 'testSecure',
|
||||
},
|
||||
{
|
||||
key: '3',
|
||||
type: '请求URI',
|
||||
data: '/api/v1/device/dev0001/log/_query',
|
||||
},
|
||||
{
|
||||
key: '4',
|
||||
type: '请求方式',
|
||||
data: 'GET',
|
||||
},
|
||||
{
|
||||
key: '5',
|
||||
type: '请求参数',
|
||||
data: 'pageSize=20&pageIndex=0',
|
||||
},
|
||||
{
|
||||
key: '6',
|
||||
type: '签名方式',
|
||||
data: 'MD5',
|
||||
},
|
||||
{
|
||||
key: '7',
|
||||
type: '签名示例时间戳',
|
||||
data: '1574993804802 ',
|
||||
},
|
||||
];
|
||||
const javaStr1 = `String secureKey = ...; //密钥\r\nString responseBody = ...;//服务端响应结果\r\nString timestampHeader = ...;//响应头: X-Timestamp\r\nString signHeader = ...; //响应头: X-Sign\r\n\r\nString sign = DigestUtils.md5Hex(responseBody+timestampHeader+secureKey);\r\nif(sign.equalsIgnoreCase(signHeader)){\r\n //验签通过\r\n}`;
|
||||
const javaStr2 =
|
||||
'<dependency>\r\n <groupId>org.jetlinks.sdk</groupId>\r\n <artifactId>api-sdk</artifactId>\r\n <version>1.0.0</version>\r\n</dependency>';
|
||||
const javaStr =
|
||||
'\r\n //服务器的baseUrl\r\n String baseUrl = "http://localhost:9000/jetlinks";\r\n //客户端Id\r\n String clientId = "aSoq98aAxzP";\r\n //访问秘钥\r\n String secureKey = "DaYsxpiWSfdTAPJyKW8rP2WAGyWErnsR";\r\n\r\n ClientConfig clientConfig = new ClientConfig(baseUrl, clientId, secureKey);\r\n\r\n ApiClient client = new WebApiClient(clientConfig);\r\n\r\nApiResponse < PagerResult < DeviceInfo >> response = client\r\n .request(QueryDeviceRequest\r\n .of(query -> query\r\n .where("productId", "demo-device")\r\n .doPaging(0, 100)));';
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.home {
|
||||
padding: 20px;
|
||||
|
||||
h1 {
|
||||
font-weight: 600;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-weight: 600;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.h2-text {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin-top: 10px;
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.h3-text {
|
||||
max-width: 530px;
|
||||
margin-top: 3px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
p {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.div-border {
|
||||
padding: 10px;
|
||||
border-left: 10px solid #eee;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -2,6 +2,7 @@
|
|||
<a-tree
|
||||
:tree-data="treeData"
|
||||
@select="clickSelectItem"
|
||||
v-model:selected-keys="selectedKeys"
|
||||
showLine
|
||||
class="left-tree-container"
|
||||
>
|
||||
|
@ -14,16 +15,23 @@
|
|||
<script setup lang="ts">
|
||||
import { TreeProps } from 'ant-design-vue';
|
||||
|
||||
import { getTreeOne_api, getTreeTwo_api } from '@/api/system/apiPage';
|
||||
import {
|
||||
apiOperations_api,
|
||||
getApiGranted_api,
|
||||
getTreeOne_api,
|
||||
getTreeTwo_api,
|
||||
} from '@/api/system/apiPage';
|
||||
import type { modeType, treeNodeTpye } from '../typing';
|
||||
|
||||
const emits = defineEmits(['select']);
|
||||
const props = defineProps<{
|
||||
mode:modeType
|
||||
}>()
|
||||
mode: modeType;
|
||||
hasHome?: boolean;
|
||||
code?: string;
|
||||
}>();
|
||||
|
||||
const treeData = ref<TreeProps['treeData']>([]);
|
||||
|
||||
const selectedKeys = ref<string[]>([]);
|
||||
const getTreeData = () => {
|
||||
let tree: treeNodeTpye[] = [];
|
||||
getTreeOne_api().then((resp: any) => {
|
||||
|
@ -32,17 +40,42 @@ const getTreeData = () => {
|
|||
key: item.url,
|
||||
}));
|
||||
const allPromise = tree.map((item) => getTreeTwo_api(item.name));
|
||||
// 若类型不为api,根据不同类型添加得到不同的过滤数组
|
||||
if (props.mode === 'appManger') allPromise.push(apiOperations_api());
|
||||
else if (props.mode === 'home')
|
||||
allPromise.push(getApiGranted_api(props.code as string));
|
||||
Promise.all(allPromise).then((values) => {
|
||||
values.forEach((item: any, i) => {
|
||||
tree[i].children = combData(item?.paths);
|
||||
tree[i].schemas = item.components.schemas
|
||||
if (props.mode === 'api') {
|
||||
tree[i].schemas = item.components.schemas;
|
||||
tree[i].children = combData(item.paths);
|
||||
} else if (i < values.length - 2) {
|
||||
const paths = filterPath(
|
||||
item.paths,
|
||||
values[values.length - 1].result as string[],
|
||||
);
|
||||
tree[i].children = combData(paths);
|
||||
tree[i].schemas = item.components.schemas;
|
||||
}
|
||||
});
|
||||
if (props.hasHome) {
|
||||
tree.unshift({
|
||||
key: 'home',
|
||||
name: '首页',
|
||||
schemas: {},
|
||||
children: [],
|
||||
});
|
||||
selectedKeys.value = ['home'];
|
||||
}
|
||||
|
||||
treeData.value = tree;
|
||||
});
|
||||
});
|
||||
};
|
||||
const clickSelectItem: TreeProps['onSelect'] = (key, node: any) => {
|
||||
if(!node.node.parent) return
|
||||
const clickSelectItem: TreeProps['onSelect'] = (key: any[], node: any) => {
|
||||
if (key[0] === 'home') return emits('select', node.node.dataRef, {});
|
||||
|
||||
if (!node.node.parent && key[0] !== 'home') return;
|
||||
emits('select', node.node.dataRef, node.node?.parent.node.schemas);
|
||||
};
|
||||
|
||||
|
@ -76,12 +109,36 @@ const combData = (dataSource: object) => {
|
|||
},
|
||||
],
|
||||
};
|
||||
|
||||
apiList.push(apiObj);
|
||||
}
|
||||
});
|
||||
|
||||
return apiList;
|
||||
};
|
||||
/**
|
||||
* 过滤能展示的接口 模式mode为api时不需要过滤
|
||||
* @param path 源数据
|
||||
* @param filterArr 过滤数组
|
||||
*/
|
||||
const filterPath = (path: object, filterArr: string[]) => {
|
||||
for (const key in path) {
|
||||
if (Object.prototype.hasOwnProperty.call(path, key)) {
|
||||
const value = path[key];
|
||||
for (const prop in value) {
|
||||
if (Object.prototype.hasOwnProperty.call(value, prop)) {
|
||||
const item = value[prop];
|
||||
if (!filterArr.includes(item.operationId))
|
||||
delete value[prop];
|
||||
}
|
||||
}
|
||||
if(Object.keys(value).length === 0) delete path[key]
|
||||
}
|
||||
}
|
||||
console.log(path, filterArr);
|
||||
|
||||
return path;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
|
|
|
@ -1,49 +1,64 @@
|
|||
<template>
|
||||
<a-card class="api-page-container">
|
||||
<p>
|
||||
<AIcon type="ExclamationCircleOutlined" style="margin-right: 12px;" />配置系统支持API赋权的范围
|
||||
</p>
|
||||
<a-row :gutter="24">
|
||||
<div class="api-page-container">
|
||||
<div class="top">
|
||||
<slot name="top" />
|
||||
</div>
|
||||
<a-row :gutter="24" style="background-color: #fff; padding: 20px">
|
||||
<a-col
|
||||
:span="24"
|
||||
v-if="props.showTitle"
|
||||
style="font-size: 16px; margin-bottom: 48px"
|
||||
>API文档</a-col
|
||||
>
|
||||
<a-col :span="5">
|
||||
<LeftTree @select="treeSelect" :mode="props.mode" />
|
||||
<LeftTree
|
||||
@select="treeSelect"
|
||||
:mode="props.mode"
|
||||
:has-home="props.hasHome"
|
||||
:filter-array="treeFilter"
|
||||
/>
|
||||
</a-col>
|
||||
<a-col :span="19">
|
||||
<ChooseApi
|
||||
v-show="!selectedApi.url"
|
||||
v-model:click-api="selectedApi"
|
||||
:table-data="tableData"
|
||||
v-model:selectedRowKeys="selectedKeys"
|
||||
:source-keys="selectSourceKeys" :mode="props.mode"
|
||||
/>
|
||||
<HomePage v-show="showHome" />
|
||||
<div class="url-page" v-show="!showHome">
|
||||
<ChooseApi
|
||||
v-show="!selectedApi.url"
|
||||
v-model:click-api="selectedApi"
|
||||
:table-data="tableData"
|
||||
v-model:selectedRowKeys="selectedKeys"
|
||||
:source-keys="selectSourceKeys"
|
||||
:mode="props.mode"
|
||||
/>
|
||||
|
||||
<div
|
||||
class="api-details"
|
||||
v-if="selectedApi.url && tableData.length > 0"
|
||||
>
|
||||
<a-button
|
||||
@click="selectedApi = initSelectedApi"
|
||||
style="margin-bottom: 24px"
|
||||
>返回</a-button
|
||||
<div
|
||||
class="api-details"
|
||||
v-if="selectedApi.url && tableData.length > 0"
|
||||
>
|
||||
<a-tabs v-model:activeKey="activeKey" type="card">
|
||||
<a-tab-pane key="does" tab="文档">
|
||||
<ApiDoes
|
||||
:select-api="selectedApi"
|
||||
:schemas="schemas"
|
||||
/>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="test" tab="调试">
|
||||
<ApiTest :select-api="selectedApi" />
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
<a-button
|
||||
@click="selectedApi = initSelectedApi"
|
||||
style="margin-bottom: 24px"
|
||||
>返回</a-button
|
||||
>
|
||||
<a-tabs v-model:activeKey="activeKey" type="card">
|
||||
<a-tab-pane key="does" tab="文档">
|
||||
<ApiDoes
|
||||
:select-api="selectedApi"
|
||||
:schemas="schemas"
|
||||
/>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="test" tab="调试">
|
||||
<ApiTest :select-api="selectedApi" />
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="apiPage">
|
||||
|
||||
import HomePage from './components/HomePage.vue';
|
||||
import { getApiGranted_api, apiOperations_api } from '@/api/system/apiPage';
|
||||
import type {
|
||||
treeNodeTpye,
|
||||
|
@ -59,12 +74,18 @@ import ApiTest from './components/ApiTest.vue';
|
|||
const route = useRoute();
|
||||
const props = defineProps<{
|
||||
mode: modeType;
|
||||
showTitle?: boolean;
|
||||
hasHome?: boolean;
|
||||
code?: string
|
||||
}>();
|
||||
|
||||
const showHome = ref<boolean>(Boolean(props.hasHome));
|
||||
const tableData = ref([]);
|
||||
const treeFilter = ref([]);
|
||||
const treeSelect = (node: treeNodeTpye, nodeSchemas: object = {}) => {
|
||||
if (node.key === 'home') return (showHome.value = true);
|
||||
schemas.value = nodeSchemas;
|
||||
if (!node.apiList) return;
|
||||
showHome.value = false;
|
||||
const apiList: apiObjType[] = node.apiList as apiObjType[];
|
||||
const table: any = [];
|
||||
// 将对象形式的数据转换为表格需要的形式
|
||||
|
@ -98,7 +119,7 @@ const selectedApi = ref<apiDetailsType>(initSelectedApi);
|
|||
|
||||
const canSelectKeys = ref<string[]>([]); // 左侧可展示的项
|
||||
const selectedKeys = ref<string[]>([]); // 右侧默认勾选的项
|
||||
let selectSourceKeys = ref<string[]>([])
|
||||
let selectSourceKeys = ref<string[]>([]);
|
||||
init();
|
||||
|
||||
function init() {
|
||||
|
@ -106,10 +127,10 @@ function init() {
|
|||
if (props.mode === 'appManger') {
|
||||
} else if (props.mode === 'home') {
|
||||
} else if (props.mode === 'api') {
|
||||
apiOperations_api().then(resp=>{
|
||||
selectedKeys.value = resp.result as string[]
|
||||
selectSourceKeys.value = [...resp.result as string[]]
|
||||
})
|
||||
apiOperations_api().then((resp) => {
|
||||
selectedKeys.value = resp.result as string[];
|
||||
selectSourceKeys.value = [...(resp.result as string[])];
|
||||
});
|
||||
}
|
||||
watch(tableData, () => {
|
||||
activeKey.value = 'does';
|
||||
|
|
|
@ -1,6 +1,15 @@
|
|||
<template>
|
||||
<page-container>
|
||||
<Api mode="api" />
|
||||
<Api mode="api">
|
||||
<template #top>
|
||||
<p>
|
||||
<AIcon
|
||||
type="ExclamationCircleOutlined"
|
||||
style="margin-right: 12px; font-size: 14px"
|
||||
/>配置系统支持API赋权的范围
|
||||
</p>
|
||||
</template>
|
||||
</Api>
|
||||
</page-container>
|
||||
</template>
|
||||
|
||||
|
|
Loading…
Reference in New Issue