初始版本提交
This commit is contained in:
9
src/components/ApiConfig/index.ts
Normal file
9
src/components/ApiConfig/index.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { withInstall } from '/@/utils/index';
|
||||
import apiConfig from './src/ApiConfig.vue';
|
||||
import selectApiConfig from './src/components/ApiSelect.vue';
|
||||
import selectInterfaceAddress from './src/components/SelectInterfaceAddress.vue';
|
||||
import inputModel from './src/components/InputModel.vue';
|
||||
export const ApiConfig = withInstall(apiConfig);
|
||||
export const SelectApiConfig = withInstall(selectApiConfig);
|
||||
export const InputModel = withInstall(inputModel);
|
||||
export const SelectInterfaceAddress = withInstall(selectInterfaceAddress);
|
||||
567
src/components/ApiConfig/src/ApiConfig.vue
Normal file
567
src/components/ApiConfig/src/ApiConfig.vue
Normal file
@ -0,0 +1,567 @@
|
||||
<template>
|
||||
<a-modal
|
||||
:width="900"
|
||||
v-model:visible="state.apiConfigDia"
|
||||
:title="title"
|
||||
:maskClosable="false"
|
||||
:bodyStyle="state.modalBodyStyle"
|
||||
destroyOnClose
|
||||
@cancel="handleClose"
|
||||
>
|
||||
<div class="list-title">{{ t('API信息') }}</div>
|
||||
<a-row type="flex" align="middle">
|
||||
<a-col flex="90px" class="text-right"
|
||||
><em class="required-icon">*</em> {{ t('接口地址') }}:</a-col
|
||||
>
|
||||
<a-col flex="auto">
|
||||
<a-input
|
||||
v-model:value="state.apiConfigInfo.path"
|
||||
:placeholder="t('点击选择接口')"
|
||||
@click="state.apiSelectDialog = true"
|
||||
>
|
||||
<template #suffix>
|
||||
<Icon icon="ant-design:ellipsis-outlined" />
|
||||
</template>
|
||||
</a-input>
|
||||
</a-col>
|
||||
<a-col flex="90px" class="text-right">{{ t('请求方式') }}:</a-col>
|
||||
<a-col flex="auto">
|
||||
<a-input
|
||||
v-model:value="state.apiConfigInfo.method"
|
||||
:placeholder="t('选择接口后自动显示接口请求方式')"
|
||||
disabled
|
||||
/>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-tabs v-model:activeKey="state.activeKey">
|
||||
<a-tab-pane :key="item.key" :tab="item.title" v-for="item in state.apiConfigInfo.apiParams">
|
||||
<a-table
|
||||
:dataSource="item.tableInfo"
|
||||
:columns="state.apiConfigColumns"
|
||||
:pagination="false"
|
||||
:scroll="{ y: '400px' }"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'bindType'">
|
||||
<a-select
|
||||
v-model:value="record.bindType"
|
||||
style="width: 100%"
|
||||
:placeholder="t('请选择赋值类型')"
|
||||
:options="state.bindType"
|
||||
allowClear
|
||||
@change="record.value = ''"
|
||||
/>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'value'">
|
||||
<a-tree-select
|
||||
v-model:value="record.value"
|
||||
show-search
|
||||
style="width: 100%"
|
||||
:dropdown-style="{ maxHeight: '400px', overflow: 'auto' }"
|
||||
:placeholder="t('点击选择表单数据')"
|
||||
allow-clear
|
||||
tree-default-expand-all
|
||||
:tree-data="state.dataInfo"
|
||||
@select="(_, node) => handleSelect(node, record)"
|
||||
v-if="record.bindType === 'data'"
|
||||
/>
|
||||
<a-input
|
||||
v-model:value="record.value"
|
||||
:placeholder="record.bindType ? t('请填写值') : t('请先选择赋值类型后再配置值')"
|
||||
v-else
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
|
||||
<template #footer>
|
||||
<div class="footer-container">
|
||||
<a-button type="primary" @mouseenter="state.isShowExample = true">{{
|
||||
t('返回出参格式')
|
||||
}}</a-button>
|
||||
<div>
|
||||
<a-button key="back" @click="handleClose">{{ t('取消') }}</a-button>
|
||||
<a-button key="submit" type="primary" @click="handleSubmit">{{ t('确定') }}</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div class="editor-box" v-if="state.isShowExample">
|
||||
<CodeEditor
|
||||
:value="isCascader ? cascaderInfoExample : isQrcode ? qrcodeExample : infoExample"
|
||||
language="json"
|
||||
readonly
|
||||
style="font-size: 12px"
|
||||
/>
|
||||
<span class="editor-close" @click="state.isShowExample = false"> x </span>
|
||||
<span class="editor-copy" @click="copy">{{ t('复制代码') }}</span>
|
||||
</div>
|
||||
</a-modal>
|
||||
<ApiSelect
|
||||
v-if="state.apiSelectDialog"
|
||||
v-model:apiSelectDialog="state.apiSelectDialog"
|
||||
v-model:selectedApiId="state.apiConfigInfo.apiId"
|
||||
@success="handleSuccess"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, inject, onMounted } from 'vue';
|
||||
import { Icon } from '/@/components/Icon';
|
||||
import { ColumnProps } from 'ant-design-vue/lib/table/Column';
|
||||
import ApiSelect from './components/ApiConfigSelect.vue';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { CodeEditor } from '/@/components/CodeEditor';
|
||||
import useClipboard from 'vue-clipboard3';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
import { isValidJSON } from '/@/utils/event/design';
|
||||
const { t } = useI18n();
|
||||
|
||||
const props = defineProps({
|
||||
apiConfig: {
|
||||
type: Object as PropType<any>,
|
||||
},
|
||||
apiConfigDialog: { type: Boolean },
|
||||
title: { type: String },
|
||||
isCascader: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
isQrcode: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
isLeftMenu: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
isSubForm: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
formItem: {
|
||||
type: Object as PropType<any>,
|
||||
},
|
||||
});
|
||||
const emit = defineEmits(['update:apiConfigDialog', 'update:apiConfig']);
|
||||
const widgetForm = inject('widgetForm') as any;
|
||||
|
||||
const { toClipboard } = useClipboard();
|
||||
|
||||
const state = reactive({
|
||||
apiSelectDialog: false as boolean,
|
||||
apiConfigDia: props.apiConfigDialog as boolean,
|
||||
activeKey: '1' as string,
|
||||
isShowExample: false as boolean,
|
||||
modalBodyStyle: {
|
||||
padding: '15px 15px 10px 10px',
|
||||
minHeight: '400px',
|
||||
},
|
||||
apiConfigColumns: [
|
||||
{
|
||||
title: t('API入参名称'),
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: t('API入参类型'),
|
||||
dataIndex: 'dataType',
|
||||
key: 'dataType',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: t('赋值类型'),
|
||||
dataIndex: 'bindType',
|
||||
key: 'bindType',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: t('赋值配置'),
|
||||
dataIndex: 'value',
|
||||
key: 'value',
|
||||
align: 'center',
|
||||
},
|
||||
] as ColumnProps[],
|
||||
bindType: [
|
||||
{
|
||||
label: t('值'),
|
||||
value: 'value',
|
||||
},
|
||||
{
|
||||
label: t('表单数据'),
|
||||
value: 'data',
|
||||
},
|
||||
],
|
||||
dataInfo: [
|
||||
{
|
||||
title: t('表单数据'),
|
||||
value: 'formData',
|
||||
disabled: true,
|
||||
children: [],
|
||||
},
|
||||
{
|
||||
title: '隐藏组件',
|
||||
value: 'hiddenComponents',
|
||||
disabled: true,
|
||||
children: [],
|
||||
},
|
||||
{
|
||||
title: '当前信息',
|
||||
value: 'currentInfo',
|
||||
disabled: true,
|
||||
children: [
|
||||
{
|
||||
title: t('当前人员名称'),
|
||||
value: '3-name',
|
||||
},
|
||||
{
|
||||
title: t('当前人员ID'),
|
||||
value: '3-id',
|
||||
},
|
||||
{
|
||||
title: t('当前人员编码'),
|
||||
value: '3-code',
|
||||
},
|
||||
{
|
||||
title: t('当前人员手机号'),
|
||||
value: '3-mobile',
|
||||
},
|
||||
{
|
||||
title: t('当前人员所属组织架构名称'),
|
||||
value: '3-departmentName',
|
||||
},
|
||||
{
|
||||
title: t('当前人员所属组织架构ID'),
|
||||
value: '3-departmentId',
|
||||
},
|
||||
{
|
||||
title: t('当前人员岗位ID'),
|
||||
value: '3-postId',
|
||||
},
|
||||
{
|
||||
title: t('当前人员角色ID'),
|
||||
value: '3-roles.id',
|
||||
},
|
||||
],
|
||||
},
|
||||
] as any[],
|
||||
interfaceInfo: [
|
||||
{
|
||||
key: '1',
|
||||
title: 'Query Params',
|
||||
tableInfo: [],
|
||||
},
|
||||
{
|
||||
key: '2',
|
||||
title: 'Header',
|
||||
tableInfo: [],
|
||||
},
|
||||
{
|
||||
key: '3',
|
||||
title: 'Body',
|
||||
tableInfo: [],
|
||||
},
|
||||
],
|
||||
apiConfigInfo: {
|
||||
apiParams: [],
|
||||
} as any,
|
||||
});
|
||||
const infoExample = JSON.stringify({
|
||||
code: 0,
|
||||
msg: t('提示信息'),
|
||||
data: [
|
||||
{
|
||||
label: t('选项一'),
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
label: t('选项二'),
|
||||
value: 2,
|
||||
},
|
||||
{
|
||||
label: t('选项三'),
|
||||
value: 3,
|
||||
},
|
||||
],
|
||||
});
|
||||
const cascaderInfoExample = JSON.stringify({
|
||||
code: 0,
|
||||
msg: t('提示信息'),
|
||||
data: [
|
||||
{
|
||||
label: t('选项一'),
|
||||
value: 1,
|
||||
children: [
|
||||
{
|
||||
label: t('选项1-1'),
|
||||
value: '1-1',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: t('选项二'),
|
||||
value: 2,
|
||||
children: [
|
||||
{
|
||||
label: t('选项2-1'),
|
||||
value: '2-1',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: t('选项三'),
|
||||
value: 3,
|
||||
children: [
|
||||
{
|
||||
label: t('选项3-1'),
|
||||
value: '3-1',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
const qrcodeExample = JSON.stringify({
|
||||
code: 0,
|
||||
msg: t('提示信息'),
|
||||
data: t('二维码内容'),
|
||||
});
|
||||
onMounted(() => {
|
||||
if (props.isLeftMenu) {
|
||||
state.dataInfo.splice(0, 2);
|
||||
}
|
||||
if (props.isSubForm) {
|
||||
state.dataInfo.splice(2, 1);
|
||||
}
|
||||
|
||||
if (!!widgetForm?.value && !props.isLeftMenu) {
|
||||
widgetForm?.value?.hiddenComponent?.map((com) => {
|
||||
let obj = {
|
||||
bindField: com.bindField,
|
||||
bindTable: '',
|
||||
tableKey: '',
|
||||
fieldKey: com.key,
|
||||
};
|
||||
state.dataInfo[1].children.push({
|
||||
title: com.label,
|
||||
value: JSON.stringify(obj),
|
||||
});
|
||||
});
|
||||
if (widgetForm?.value?.list.length) {
|
||||
if (props.isSubForm) {
|
||||
getFormSelectedList(widgetForm.value.list);
|
||||
} else {
|
||||
getSelectedList(widgetForm.value.list);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!!props.apiConfig) {
|
||||
state.apiConfigInfo = cloneDeep(props.apiConfig);
|
||||
if (!props.apiConfig?.apiParams?.length) {
|
||||
state.apiConfigInfo.apiParams = [];
|
||||
state.apiConfigInfo.apiParams = state.interfaceInfo;
|
||||
}
|
||||
}
|
||||
// if (props.isSubForm) {
|
||||
// state.bindType = [
|
||||
// {
|
||||
// label: t('值'),
|
||||
// value: 'value',
|
||||
// },
|
||||
// ];
|
||||
// }
|
||||
});
|
||||
const filtercomps = ['divider', 'upload', 'image', 'qrcode', 'button', 'map', 'opinion', 'title'];
|
||||
const getFormSelectedList = (list, bindTable?, key?) => {
|
||||
list?.map((item) => {
|
||||
if (['tab', 'grid', 'card'].includes(item.type)) {
|
||||
for (const child of item.layout!) {
|
||||
getFormSelectedList(child.list);
|
||||
}
|
||||
} else if (item.type == 'one-for-one') {
|
||||
getFormSelectedList(item.children, item.bindTable, item.key);
|
||||
} else if (item.type == 'form') {
|
||||
let child: any[] = [];
|
||||
if (item.bindTable == props.formItem.bindTable) {
|
||||
item.children.map((o) => {
|
||||
if (!filtercomps.includes(o.type)) {
|
||||
let params: string[] = [];
|
||||
if (o.options.datasourceType == 'api' && o.options.apiConfig.apiParams) {
|
||||
o.options.apiConfig.apiParams.forEach((p) => {
|
||||
p.tableInfo &&
|
||||
p.tableInfo.forEach((o) => {
|
||||
const value = isValidJSON(o.value);
|
||||
if (value) params.push(value.fieldKey);
|
||||
});
|
||||
});
|
||||
}
|
||||
if (o.key !== props.formItem.key && !params.includes(props.formItem.key)) {
|
||||
let bindField = o.bindField;
|
||||
if (o.type == 'time-range' || o.type == 'date-range') {
|
||||
bindField = o.bindStartTime + ',' + o.bindEndTime;
|
||||
}
|
||||
let obj = {
|
||||
bindField: bindField,
|
||||
bindTable: props.formItem.bindTable,
|
||||
tableKey: item.key,
|
||||
fieldKey: o.key,
|
||||
};
|
||||
child.push({
|
||||
title: o.label,
|
||||
value: JSON.stringify(obj),
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
state.dataInfo[0].children.push({
|
||||
title: item.label,
|
||||
value: item.bindTable,
|
||||
disabled: true,
|
||||
children: child,
|
||||
});
|
||||
}
|
||||
} else if (!filtercomps.includes(item.type)) {
|
||||
let bindField = item.bindField;
|
||||
if (item.type == 'time-range' || item.type == 'date-range') {
|
||||
bindField = item.bindStartTime + ',' + item.bindEndTime;
|
||||
}
|
||||
let obj = {
|
||||
bindField: bindField,
|
||||
bindTable: bindTable,
|
||||
tableKey: key,
|
||||
fieldKey: item.key,
|
||||
};
|
||||
state.dataInfo[0].children.push({
|
||||
title: item.label,
|
||||
value: JSON.stringify(obj),
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
const getSelectedList = (list, bindTable?, key?) => {
|
||||
list?.map((item) => {
|
||||
if (['tab', 'grid', 'card'].includes(item.type)) {
|
||||
for (const child of item.layout!) {
|
||||
getSelectedList(child.list);
|
||||
}
|
||||
} else if (item.type == 'one-for-one') {
|
||||
getSelectedList(item.children, item.bindTable, item.key);
|
||||
} else if (item.type !== 'form' && !filtercomps.includes(item.type)) {
|
||||
let params: string[] = [];
|
||||
if (item.options.datasourceType == 'api' && item.options.apiConfig.apiParams) {
|
||||
item.options.apiConfig.apiParams.forEach((p) => {
|
||||
p.tableInfo &&
|
||||
p.tableInfo.forEach((o) => {
|
||||
const value = isValidJSON(o.value);
|
||||
if (value) params.push(value.fieldKey);
|
||||
});
|
||||
});
|
||||
}
|
||||
if (item.key !== props.formItem?.key && !params.includes(props.formItem?.key)) {
|
||||
let bindField = item.bindField;
|
||||
if (item.type == 'time-range' || item.type == 'date-range') {
|
||||
bindField = item.bindStartTime + ',' + item.bindEndTime;
|
||||
}
|
||||
let obj = {
|
||||
bindField: bindField,
|
||||
bindTable: bindTable,
|
||||
tableKey: key,
|
||||
fieldKey: item.key,
|
||||
};
|
||||
state.dataInfo[0].children.push({
|
||||
title: item.label,
|
||||
value: JSON.stringify(obj),
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const handleSelect = ({ value }, record) => {
|
||||
if (!value) {
|
||||
message.error(t('请先选择该组件的绑定表及绑定字段'));
|
||||
record.value = null;
|
||||
}
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
emit('update:apiConfigDialog', false);
|
||||
};
|
||||
|
||||
const handleSubmit = () => {
|
||||
emit('update:apiConfig', state.apiConfigInfo);
|
||||
emit('update:apiConfigDialog', false);
|
||||
};
|
||||
|
||||
const handleSuccess = (config) => {
|
||||
state.apiConfigInfo = config;
|
||||
state.apiConfigInfo?.apiParams.forEach((para) => {
|
||||
if (!!para.tableInfo && para.tableInfo.length) {
|
||||
para.tableInfo?.map((item) => {
|
||||
return (item.bindType = !!item.value && !item.bindType ? 'value' : '');
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const copy = async () => {
|
||||
try {
|
||||
await toClipboard(infoExample);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.list-title {
|
||||
font-size: 14px;
|
||||
line-height: 16px;
|
||||
margin-bottom: 10px;
|
||||
padding-left: 6px;
|
||||
border-left: 6px solid #5e95ff;
|
||||
}
|
||||
|
||||
.required-icon {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.footer-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
:deep(.ant-tabs-nav) {
|
||||
margin: 10px 0 5px 10px;
|
||||
}
|
||||
|
||||
.editor-box {
|
||||
width: 300px;
|
||||
position: absolute;
|
||||
height: 350px;
|
||||
left: 140px;
|
||||
bottom: 13px;
|
||||
border: 1px solid #ccc;
|
||||
box-shadow: 0 0 6px 3px #ccc;
|
||||
|
||||
.editor-close {
|
||||
position: absolute;
|
||||
top: -3px;
|
||||
right: 13px;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.editor-copy {
|
||||
position: absolute;
|
||||
bottom: 5px;
|
||||
right: 15px;
|
||||
cursor: pointer;
|
||||
z-index: 999;
|
||||
color: #5e95ff;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
269
src/components/ApiConfig/src/components/ApiConfigSelect.vue
Normal file
269
src/components/ApiConfig/src/components/ApiConfigSelect.vue
Normal file
@ -0,0 +1,269 @@
|
||||
<template>
|
||||
<a-modal
|
||||
:width="900"
|
||||
v-model:visible="state.apiSelectDia"
|
||||
:title="t('选择接口')"
|
||||
:maskClosable="false"
|
||||
:bodyStyle="state.modalBodyStyle"
|
||||
@ok="handleSubmit"
|
||||
@cancel="handleClose"
|
||||
>
|
||||
<div class="box-left">
|
||||
<div class="title">{{ t('接口分类') }}</div>
|
||||
<a-tree
|
||||
v-model:expandedKeys="state.expandedKeys"
|
||||
v-model:selectedKeys="state.selectedKeys"
|
||||
:tree-data="state.interfaceTreeData"
|
||||
:fieldNames="state.fieldNames"
|
||||
@select="selectTree"
|
||||
/>
|
||||
</div>
|
||||
<div class="box-right">
|
||||
<div class="title">{{ t('接口列表') }}</div>
|
||||
<a-row :gutter="12" style="padding: 10px 0; border-top: 1px solid #f0f0f0">
|
||||
<a-col :span="8">
|
||||
<a-input v-model:value="state.searchText" :placeholder="t('输入搜索关键字')" allowClear />
|
||||
</a-col>
|
||||
<a-col>
|
||||
<a-button type="primary" @click="handleSearch"> {{ t('搜索') }} </a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-table
|
||||
:dataSource="state.interfaceDataSource"
|
||||
:columns="state.apiConfigColumns"
|
||||
rowKey="id"
|
||||
:pagination="state.paginationProps"
|
||||
:row-selection="{
|
||||
selectedRowKeys: state.selectedRowKeys,
|
||||
onChange: onSelectChange,
|
||||
type: 'radio',
|
||||
}"
|
||||
:customRow="customRow"
|
||||
:scroll="{ y: '200px' }"
|
||||
/>
|
||||
</div>
|
||||
</a-modal>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, reactive, watch } from 'vue';
|
||||
import { ColumnProps } from 'ant-design-vue/lib/table/Column';
|
||||
import { getInterfaceTree, getInterfaceList } from '/@/api/system/interface/index';
|
||||
import { InterfaceList } from '../interface';
|
||||
import { getInterfaceInfo } from '/@/api/system/interface/index';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
const { t } = useI18n();
|
||||
const props = defineProps({
|
||||
apiSelectDialog: { type: Boolean },
|
||||
selectedApiId: { type: String },
|
||||
});
|
||||
const emit = defineEmits(['update:apiSelectDialog', 'update:selectedApiId', 'success']);
|
||||
const state = reactive({
|
||||
apiSelectDia: props.apiSelectDialog,
|
||||
interfaceTreeData: [] as any[],
|
||||
expandedKeys: [] as string[],
|
||||
selectedKeys: [] as string[],
|
||||
interfaceList: [] as InterfaceList[], //接口列表全部
|
||||
interfaceListSearch: [] as InterfaceList[], //接口列表搜索全部
|
||||
interfaceDataSource: [] as InterfaceList[], //接口列表分页
|
||||
selectedRowKeys: [] as string[],
|
||||
searchText: '' as string,
|
||||
isSearch: false,
|
||||
fieldNames: { children: 'children', title: 'name', key: 'id' },
|
||||
modalBodyStyle: {
|
||||
display: 'flex',
|
||||
padding: '15px 15px 10px 10px',
|
||||
minHeight: '400px',
|
||||
},
|
||||
apiConfigColumns: [
|
||||
{
|
||||
key: 'name',
|
||||
title: t('接口名称'),
|
||||
dataIndex: 'name',
|
||||
width: 300,
|
||||
},
|
||||
{
|
||||
key: 'path',
|
||||
title: t('接口地址'),
|
||||
dataIndex: 'path',
|
||||
},
|
||||
] as ColumnProps[],
|
||||
paginationProps: {
|
||||
current: 1,
|
||||
total: 0,
|
||||
pageSize: 10,
|
||||
showQuickJumper: true,
|
||||
showSizeChanger: true,
|
||||
onChange: (page: number) => getInterfacePage(page),
|
||||
onShowSizeChange: (current: number, pageSize: number) => {
|
||||
state.paginationProps.pageSize = pageSize;
|
||||
getInterfacePage(current);
|
||||
},
|
||||
},
|
||||
interfaceInfo: [
|
||||
{
|
||||
key: '1',
|
||||
title: 'Query Params',
|
||||
tableInfo: [],
|
||||
},
|
||||
{
|
||||
key: '2',
|
||||
title: 'Header',
|
||||
tableInfo: [],
|
||||
},
|
||||
{
|
||||
key: '3',
|
||||
title: 'Body',
|
||||
tableInfo: [],
|
||||
},
|
||||
],
|
||||
configInfo: {
|
||||
path: '',
|
||||
method: '',
|
||||
apiId: '',
|
||||
apiParams: [] as any[],
|
||||
script: '',
|
||||
},
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
getInterfaceTreeData();
|
||||
});
|
||||
watch(
|
||||
() => props.selectedApiId,
|
||||
(val) => {
|
||||
if (!!val) {
|
||||
state.selectedRowKeys.push(val);
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
deep: true,
|
||||
},
|
||||
);
|
||||
//获取左边树结构
|
||||
const getInterfaceTreeData = async () => {
|
||||
state.interfaceTreeData = await getInterfaceTree();
|
||||
//默认展示第一个接口分类的列表
|
||||
if (state.interfaceTreeData.length && state.interfaceTreeData[0].id) {
|
||||
const id = state.interfaceTreeData[0].id;
|
||||
state.selectedKeys.push(id);
|
||||
state.expandedKeys.push(id);
|
||||
selectTree(state.selectedKeys);
|
||||
}
|
||||
};
|
||||
//获取右边接口列表
|
||||
const selectTree = async (selectedKeys: string[], e?) => {
|
||||
state.isSearch = false;
|
||||
if (!selectedKeys.length) state.selectedKeys.push(e.node.dataRef.id);
|
||||
const groupId = selectedKeys[selectedKeys.length - 1];
|
||||
const data = await getInterfaceList({ groupId });
|
||||
//清空接口列表
|
||||
state.interfaceList = [];
|
||||
data.map((item: any) => {
|
||||
state.interfaceList.push({
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
path: item.path,
|
||||
});
|
||||
});
|
||||
getInterfacePage(1);
|
||||
};
|
||||
//展示右边接口列表(分页)
|
||||
const getInterfacePage = (page) => {
|
||||
const size = state.paginationProps.pageSize;
|
||||
const startIndex = (page - 1) * size;
|
||||
const endIndex = page * size;
|
||||
|
||||
const list = state.isSearch ? state.interfaceListSearch : state.interfaceList;
|
||||
|
||||
state.paginationProps.current = page;
|
||||
state.paginationProps.total = list.length;
|
||||
state.interfaceDataSource = list.slice(startIndex, endIndex);
|
||||
};
|
||||
const handleSearch = () => {
|
||||
state.isSearch = true;
|
||||
state.interfaceListSearch = state.interfaceList.filter((item: InterfaceList) => {
|
||||
const hasKeywords = Object.values(item).find((val: string, index: number) => {
|
||||
//index!==0 id不参与搜索
|
||||
return index !== 0 && val.indexOf(state.searchText) !== -1;
|
||||
});
|
||||
return hasKeywords;
|
||||
});
|
||||
|
||||
getInterfacePage(1);
|
||||
};
|
||||
|
||||
const customRow = (record: InterfaceList) => {
|
||||
return {
|
||||
onClick: () => {
|
||||
let selectedRowKeys = [...state.selectedRowKeys];
|
||||
if (selectedRowKeys.indexOf(record.id) >= 0) {
|
||||
let index = selectedRowKeys.indexOf(record.id);
|
||||
selectedRowKeys.splice(index, 1);
|
||||
} else {
|
||||
selectedRowKeys = [record.id];
|
||||
}
|
||||
state.configInfo.apiId = selectedRowKeys[0];
|
||||
state.selectedRowKeys = selectedRowKeys;
|
||||
},
|
||||
};
|
||||
};
|
||||
const onSelectChange = (rowKey: string[]) => {
|
||||
state.selectedRowKeys = rowKey;
|
||||
state.configInfo.apiId = rowKey[0];
|
||||
};
|
||||
const getInterfaceInfoById = async (id) => {
|
||||
const data = await getInterfaceInfo({ id });
|
||||
state.interfaceInfo[0].tableInfo = data.parameters;
|
||||
state.interfaceInfo[1].tableInfo = data.headers;
|
||||
state.interfaceInfo[2].tableInfo = data.requestBodyDefinition?.children;
|
||||
state.configInfo.path = data.path;
|
||||
state.configInfo.method = data.method;
|
||||
state.configInfo.script = data.script;
|
||||
state.configInfo.apiParams = state.interfaceInfo;
|
||||
emit('success', state.configInfo);
|
||||
};
|
||||
const handleSubmit = async () => {
|
||||
if (!!state.selectedRowKeys[0]) {
|
||||
await getInterfaceInfoById(state.selectedRowKeys[0]);
|
||||
emit('update:selectedApiId', state.selectedRowKeys[0]);
|
||||
}
|
||||
emit('update:apiSelectDialog', false);
|
||||
};
|
||||
const handleClose = () => {
|
||||
emit('update:apiSelectDialog', false);
|
||||
};
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.box-left {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.box-right {
|
||||
flex: 2;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 16px;
|
||||
line-height: 18px;
|
||||
margin-bottom: 15px;
|
||||
padding-left: 6px;
|
||||
border-left: 6px solid #5e95ff;
|
||||
}
|
||||
|
||||
:deep(.ant-tree) {
|
||||
padding: 10px 15px 0 0;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
:deep(.ant-tree-treenode),
|
||||
:deep(.ant-tree-node-content-wrapper) {
|
||||
width: 100%;
|
||||
height: 35px;
|
||||
line-height: 35px;
|
||||
}
|
||||
|
||||
:deep(.ant-table-body) {
|
||||
height: 200px;
|
||||
}
|
||||
</style>
|
||||
229
src/components/ApiConfig/src/components/ApiSelect.vue
Normal file
229
src/components/ApiConfig/src/components/ApiSelect.vue
Normal file
@ -0,0 +1,229 @@
|
||||
<template>
|
||||
<div @click.stop="open">
|
||||
<slot></slot>
|
||||
<ModalPanel
|
||||
:visible="visible"
|
||||
:width="1100"
|
||||
:title="t('API配置')"
|
||||
@submit="submit"
|
||||
@close="close"
|
||||
>
|
||||
<NodeHead :nodeName="t('API信息')" class="title" />
|
||||
<SelectInterfaceAddress :config="data.config" @set-api-config="setApiConfig" />
|
||||
<InputParams
|
||||
class="padding"
|
||||
v-if="visible"
|
||||
:apiParams="data.apiParams"
|
||||
:paramTree="data.paramTree"
|
||||
/>
|
||||
<a-button
|
||||
type="primary"
|
||||
class="btn-item"
|
||||
@click.stop="data.isShowExample = false"
|
||||
@mouseenter="data.isShowExample = true"
|
||||
>{{ t('返回出参示例') }}</a-button
|
||||
>
|
||||
<div class="editor-box" v-if="data.isShowExample">
|
||||
<CodeEditor
|
||||
v-if="data.isShowExample"
|
||||
:value="exampleStr"
|
||||
language="json"
|
||||
readonly
|
||||
style="font-size: 12px"
|
||||
/>
|
||||
<span class="editor-close" @click.stop="data.isShowExample = false"> x </span>
|
||||
<span class="editor-copy" @click.stop="copy">{{ t('复制代码') }}</span>
|
||||
</div>
|
||||
</ModalPanel>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref } from 'vue';
|
||||
import { NodeHead, ModalPanel } from '/@/components/ModalPanel/index';
|
||||
import SelectInterfaceAddress from './SelectInterfaceAddress.vue';
|
||||
import { CodeEditor } from '/@/components/CodeEditor';
|
||||
import InputParams from './InputParams.vue';
|
||||
import { TreeProps } from 'ant-design-vue';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { InterfaceListInfo } from '/@/api/system/interface/model';
|
||||
import useClipboard from 'vue-clipboard3';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
import { ApiConfig, ApiParams } from '../interface';
|
||||
const { t } = useI18n();
|
||||
const emit = defineEmits(['update:modelValue']);
|
||||
let props = withDefaults(
|
||||
defineProps<{
|
||||
paramTree?: TreeProps['treeData'];
|
||||
modelValue: ApiConfig;
|
||||
exampleStr?: string;
|
||||
}>(),
|
||||
{
|
||||
modelValue: () => {
|
||||
return {
|
||||
id: '',
|
||||
name: '',
|
||||
method: '',
|
||||
script: '',
|
||||
requestParamsConfigs: [], //Query Params
|
||||
requestHeaderConfigs: [], //Header
|
||||
requestBodyConfigs: [], //Body
|
||||
};
|
||||
},
|
||||
exampleStr: `{
|
||||
code: 0,
|
||||
msg: 'success',
|
||||
data: 'value',
|
||||
}`,
|
||||
},
|
||||
);
|
||||
const { toClipboard } = useClipboard();
|
||||
let data: {
|
||||
isShowExample: boolean;
|
||||
config: ApiConfig;
|
||||
paramTree: TreeProps['treeData'];
|
||||
apiParams: Array<ApiParams>;
|
||||
} = reactive({
|
||||
isShowExample: false,
|
||||
config: props.modelValue,
|
||||
paramTree: [],
|
||||
apiParams: [
|
||||
{
|
||||
key: '1',
|
||||
title: 'Query Params',
|
||||
tableInfo: [],
|
||||
},
|
||||
{
|
||||
key: '2',
|
||||
title: 'Header',
|
||||
tableInfo: [],
|
||||
},
|
||||
{
|
||||
key: '3',
|
||||
title: 'Body',
|
||||
tableInfo: [],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const visible = ref<boolean>(false);
|
||||
function open() {
|
||||
data.config = cloneDeep(props.modelValue);
|
||||
if (props.paramTree) {
|
||||
data.paramTree = props.paramTree;
|
||||
}
|
||||
if (data.config.requestParamsConfigs)
|
||||
data.apiParams[0].tableInfo = data.config.requestParamsConfigs;
|
||||
if (data.config.requestHeaderConfigs)
|
||||
data.apiParams[1].tableInfo = data.config.requestHeaderConfigs;
|
||||
if (data.config.requestBodyConfigs)
|
||||
data.apiParams[2].tableInfo = data.config.requestBodyConfigs;
|
||||
visible.value = true;
|
||||
}
|
||||
function close() {
|
||||
visible.value = false;
|
||||
}
|
||||
async function copy() {
|
||||
try {
|
||||
await toClipboard(props.exampleStr);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
function submit() {
|
||||
emit('update:modelValue', cloneDeep(data.config));
|
||||
close();
|
||||
}
|
||||
function setApiConfig(config: InterfaceListInfo) {
|
||||
data.config.id = config.id;
|
||||
data.config.name = config.name;
|
||||
data.config.method = config.method;
|
||||
data.config.path = config.path;
|
||||
data.config.script = config.script;
|
||||
data.config.requestParamsConfigs = [];
|
||||
if (config.parameters) {
|
||||
config.parameters.forEach((element) => {
|
||||
data.config.requestParamsConfigs.push({
|
||||
name: element.name, //API入参名称
|
||||
dataType: element.dataType, //API入参类型
|
||||
assignmentType: 'value', //赋值类型
|
||||
value: element.value, //值
|
||||
config: '', //赋值配置
|
||||
});
|
||||
});
|
||||
}
|
||||
data.apiParams[0].tableInfo = data.config.requestParamsConfigs;
|
||||
data.config.requestHeaderConfigs = [];
|
||||
if (config.headers) {
|
||||
config.headers.forEach((element) => {
|
||||
data.config.requestHeaderConfigs.push({
|
||||
name: element.name, //API入参名称
|
||||
dataType: element.dataType, //API入参类型
|
||||
assignmentType: 'value', //赋值类型
|
||||
value: element.value, //值
|
||||
config: '', //赋值配置
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
data.apiParams[1].tableInfo = data.config.requestHeaderConfigs;
|
||||
data.config.requestBodyConfigs = [];
|
||||
if (config.requestBodyDefinition && config.requestBodyDefinition.children) {
|
||||
config.requestBodyDefinition.children.forEach((element) => {
|
||||
data.config.requestBodyConfigs.push({
|
||||
name: element.name, //API入参名称
|
||||
dataType: element.dataType, //API入参类型
|
||||
assignmentType: 'value', //赋值类型
|
||||
value: element.value, //值
|
||||
config: '', //赋值配置
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
data.apiParams[2].tableInfo = data.config.requestBodyConfigs;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.title {
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.padding {
|
||||
padding: 10px 20px;
|
||||
}
|
||||
|
||||
.btn-item {
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
}
|
||||
|
||||
.editor-box {
|
||||
width: 300px;
|
||||
position: absolute;
|
||||
height: 350px;
|
||||
left: 140px;
|
||||
bottom: 13px;
|
||||
border: 1px solid #ccc;
|
||||
box-shadow: 0 0 6px 3px #ccc;
|
||||
|
||||
.editor-close {
|
||||
position: absolute;
|
||||
top: -3px;
|
||||
right: 13px;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.editor-copy {
|
||||
position: absolute;
|
||||
bottom: 5px;
|
||||
right: 15px;
|
||||
cursor: pointer;
|
||||
z-index: 999;
|
||||
color: #5e95ff;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
45
src/components/ApiConfig/src/components/InputModel.vue
Normal file
45
src/components/ApiConfig/src/components/InputModel.vue
Normal file
@ -0,0 +1,45 @@
|
||||
<template>
|
||||
<div :class="hasSlot ? 'rule-box' : ''">
|
||||
<slot></slot>
|
||||
<template v-if="hasSlot">
|
||||
<span v-if="!value" class="rule-color">{{ placeholder }}</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
<Input class="rule-box" :value="value" :placeholder="placeholder" readonly>
|
||||
<template #suffix>
|
||||
<Icon icon="ant-design:ellipsis-outlined" class="rule-color" />
|
||||
</template>
|
||||
</Input>
|
||||
</template>
|
||||
<Icon icon="ant-design:ellipsis-outlined" class="rule-color" v-if="hasSlot" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, useSlots } from 'vue';
|
||||
import Icon from '/@/components/Icon/index';
|
||||
import { Input } from 'ant-design-vue';
|
||||
|
||||
defineProps({
|
||||
value: [String, Number],
|
||||
placeholder: String,
|
||||
});
|
||||
const hasSlot = computed(() => {
|
||||
return !!useSlots().default;
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.rule-box {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: 30px;
|
||||
border: 1px solid #d9d9d9;
|
||||
padding: 0 4px;
|
||||
|
||||
.rule-color {
|
||||
color: #d0cfd0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
144
src/components/ApiConfig/src/components/InputParams.vue
Normal file
144
src/components/ApiConfig/src/components/InputParams.vue
Normal file
@ -0,0 +1,144 @@
|
||||
<template>
|
||||
<a-tabs>
|
||||
<a-tab-pane :key="item.key" :tab="item.title" v-for="item in apiParams">
|
||||
<a-table
|
||||
:dataSource="item.tableInfo"
|
||||
:columns="apiConfigColumns"
|
||||
:pagination="false"
|
||||
:scroll="{ y: '400px' }"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'assignmentType'">
|
||||
<a-select
|
||||
v-model:value="record.assignmentType"
|
||||
style="width: 100%"
|
||||
:placeholder="t('请选择赋值类型')"
|
||||
allowClear
|
||||
>
|
||||
<a-select-option
|
||||
:value="bind.value"
|
||||
v-for="bind in props.paramTree.length > 0 ? processBindType : bindType"
|
||||
:key="bind.value"
|
||||
>
|
||||
{{ bind.label }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'value'">
|
||||
<a-tree-select
|
||||
v-if="record.assignmentType === 'data'"
|
||||
v-model:value="record.config"
|
||||
show-search
|
||||
style="width: 100%"
|
||||
:dropdown-style="{ maxHeight: '400px', overflow: 'auto' }"
|
||||
placeholder="Please select"
|
||||
allow-clear
|
||||
tree-default-expand-all
|
||||
:tree-data="paramTree"
|
||||
:field-names="{
|
||||
children: 'children',
|
||||
label: 'title',
|
||||
value: 'key',
|
||||
}"
|
||||
/>
|
||||
<!-- 表单数据 -->
|
||||
<a-tree-select
|
||||
v-else-if="record.assignmentType === 'formData'"
|
||||
v-model:value="record.config"
|
||||
show-search
|
||||
style="width: 100%"
|
||||
:dropdown-style="{ maxHeight: '400px', overflow: 'auto' }"
|
||||
:placeholder="t('请选择表单数据')"
|
||||
allow-clear
|
||||
tree-default-expand-all
|
||||
:tree-data="formDataTree"
|
||||
:field-names="{
|
||||
children: 'children',
|
||||
label: 'title',
|
||||
value: 'key',
|
||||
}"
|
||||
/>
|
||||
<a-input
|
||||
v-else
|
||||
v-model:value="record.value"
|
||||
:placeholder="record.type ? t('请填写值') : t('请先选择赋值类型后再配置值')"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { TreeProps } from 'ant-design-vue';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
import { ApiParams } from '../interface';
|
||||
import { getVariablesTree } from '/@/views/workflow/design/bpmn/config/info';
|
||||
import { ref } from 'vue';
|
||||
const { t } = useI18n();
|
||||
let props = withDefaults(
|
||||
defineProps<{
|
||||
paramTree: TreeProps['treeData'];
|
||||
apiParams: Array<ApiParams>;
|
||||
}>(),
|
||||
{
|
||||
paramTree: () => {
|
||||
return [];
|
||||
},
|
||||
apiParams: () => {
|
||||
return [];
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
let bindType = [
|
||||
{
|
||||
label: t('值'),
|
||||
value: 'value',
|
||||
},
|
||||
];
|
||||
let processBindType = [
|
||||
{
|
||||
label: t('值'),
|
||||
value: 'value',
|
||||
},
|
||||
{
|
||||
label: t('流程数据'),
|
||||
value: 'data',
|
||||
},
|
||||
{
|
||||
label: t('表单数据'),
|
||||
value: 'formData',
|
||||
},
|
||||
];
|
||||
let apiConfigColumns = [
|
||||
{
|
||||
title: t('API入参名称'),
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: t('API入参类型'),
|
||||
dataIndex: 'dataType',
|
||||
key: 'dataType',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: t('赋值类型'),
|
||||
dataIndex: 'assignmentType',
|
||||
key: 'assignmentType',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: t('赋值配置'),
|
||||
dataIndex: 'value',
|
||||
key: 'value',
|
||||
align: 'center',
|
||||
},
|
||||
];
|
||||
const formDataTree = ref(getVariablesTree({ needHideComponents: true }));
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
196
src/components/ApiConfig/src/components/InterfaceAddressList.vue
Normal file
196
src/components/ApiConfig/src/components/InterfaceAddressList.vue
Normal file
@ -0,0 +1,196 @@
|
||||
<template>
|
||||
<div class="box">
|
||||
<div class="box-left">
|
||||
<div class="title">{{ t('接口分类') }}</div>
|
||||
<a-tree
|
||||
v-model:expandedKeys="data.expandedKeys"
|
||||
v-model:selectedKeys="data.selectedKeys"
|
||||
:tree-data="data.interfaceTreeData"
|
||||
:fieldNames="data.fieldNames"
|
||||
@select="selectTree"
|
||||
/>
|
||||
</div>
|
||||
<div class="box-right">
|
||||
<div class="title">{{ t('接口列表') }}</div>
|
||||
<a-row :gutter="12" style="padding: 10px 0; border-top: 1px solid #f0f0f0">
|
||||
<a-col :span="8">
|
||||
<a-input v-model:value="data.searchText" :placeholder="t('输入搜索关键字')" />
|
||||
</a-col>
|
||||
<a-col>
|
||||
<a-button type="primary" @click="handleSearch"> {{ t('搜索') }} </a-button>
|
||||
<a-button type="primary" @click="handleReset" class="ml-2"> {{ t('重置') }} </a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-table
|
||||
:dataSource="data.interfaceDataSource"
|
||||
:columns="data.apiConfigColumns"
|
||||
rowKey="id"
|
||||
:pagination="data.paginationProps"
|
||||
:row-selection="rowSelection"
|
||||
:customRow="customRow"
|
||||
:scroll="{ y: '200px' }"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, reactive } from 'vue';
|
||||
import { ColumnProps } from 'ant-design-vue/lib/table/Column';
|
||||
import { getInterfaceTree, getInterfaceList } from '/@/api/system/interface/index';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
import { InterfaceList } from '../interface';
|
||||
const { t } = useI18n();
|
||||
const emit = defineEmits(['update:id']);
|
||||
const props = defineProps(['id']);
|
||||
const data = reactive({
|
||||
interfaceTreeData: [] as any[],
|
||||
expandedKeys: [] as string[],
|
||||
selectedKeys: [] as string[],
|
||||
interfaceList: [] as InterfaceList[], //接口列表全部
|
||||
interfaceListSearch: [] as InterfaceList[], //接口列表搜索全部
|
||||
interfaceDataSource: [] as InterfaceList[], //接口列表分页
|
||||
selectedRowKeys: [] as string[],
|
||||
searchText: '' as string,
|
||||
isSearch: false,
|
||||
fieldNames: { children: 'children', title: 'name', key: 'id' },
|
||||
modalBodyStyle: {
|
||||
display: 'flex',
|
||||
padding: '15px 15px 10px 10px',
|
||||
minHeight: '400px',
|
||||
},
|
||||
apiConfigColumns: [
|
||||
{
|
||||
key: 'name',
|
||||
title: t('接口名称'),
|
||||
dataIndex: 'name',
|
||||
width: 300,
|
||||
},
|
||||
{
|
||||
key: 'path',
|
||||
title: t('接口地址'),
|
||||
dataIndex: 'path',
|
||||
},
|
||||
] as ColumnProps[],
|
||||
paginationProps: {
|
||||
current: 1,
|
||||
total: 0,
|
||||
pageSize: 10,
|
||||
showQuickJumper: true,
|
||||
showSizeChanger: true,
|
||||
onChange: (page: number) => getInterfacePage(page),
|
||||
onShowSizeChange: (current: number, pageSize: number) => {
|
||||
data.paginationProps.pageSize = pageSize;
|
||||
getInterfacePage(current);
|
||||
},
|
||||
},
|
||||
});
|
||||
onMounted(() => {
|
||||
data.selectedRowKeys = [props.id];
|
||||
getInterfaceTreeData();
|
||||
});
|
||||
const rowSelection = computed(() => {
|
||||
return {
|
||||
checkStrictly: true,
|
||||
type: 'radio',
|
||||
selectedRowKeys: data.selectedRowKeys,
|
||||
onChange: (selectedRowKeys: string[]) => {
|
||||
data.selectedRowKeys = selectedRowKeys;
|
||||
emit('update:id', selectedRowKeys[0]);
|
||||
},
|
||||
};
|
||||
});
|
||||
const customRow = (record: InterfaceList) => {
|
||||
return {
|
||||
onClick: () => {
|
||||
let selectedRowKeys = [...data.selectedRowKeys];
|
||||
if (selectedRowKeys.indexOf(record.id) >= 0) {
|
||||
let index = selectedRowKeys.indexOf(record.id);
|
||||
selectedRowKeys.splice(index, 1);
|
||||
} else {
|
||||
selectedRowKeys = [record.id];
|
||||
}
|
||||
emit('update:id', selectedRowKeys[0]);
|
||||
data.selectedRowKeys = selectedRowKeys;
|
||||
},
|
||||
};
|
||||
};
|
||||
const getInterfacePage = (page) => {
|
||||
const size = data.paginationProps.pageSize;
|
||||
const startIndex = (page - 1) * size;
|
||||
const endIndex = page * size;
|
||||
|
||||
const list = data.isSearch ? data.interfaceListSearch : data.interfaceList;
|
||||
|
||||
data.paginationProps.current = page;
|
||||
data.paginationProps.total = list.length;
|
||||
data.interfaceDataSource = list.slice(startIndex, endIndex);
|
||||
};
|
||||
const handleSearch = () => {
|
||||
data.isSearch = true;
|
||||
data.interfaceListSearch = data.interfaceList.filter((item: InterfaceList) => {
|
||||
if (item.name.includes(data.searchText)) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
getInterfacePage(1);
|
||||
};
|
||||
const handleReset = () => {
|
||||
data.searchText = '';
|
||||
data.interfaceListSearch = data.interfaceList;
|
||||
getInterfacePage(1);
|
||||
};
|
||||
//获取左边树结构
|
||||
const getInterfaceTreeData = async () => {
|
||||
data.interfaceTreeData = await getInterfaceTree();
|
||||
//默认展示第一个接口分类的列表
|
||||
if (data.interfaceTreeData.length && data.interfaceTreeData[0].id) {
|
||||
const id = data.interfaceTreeData[0].id;
|
||||
data.selectedKeys.push(id);
|
||||
data.expandedKeys.push(id);
|
||||
selectTree(data.selectedKeys);
|
||||
}
|
||||
};
|
||||
//获取右边接口列表
|
||||
const selectTree = async (selectedKeys: string[]) => {
|
||||
if (selectedKeys.length > 0) {
|
||||
data.isSearch = false;
|
||||
const groupId = selectedKeys[selectedKeys.length - 1];
|
||||
const list = await getInterfaceList({ groupId });
|
||||
//清空接口列表
|
||||
data.interfaceList = list;
|
||||
getInterfacePage(1);
|
||||
} else {
|
||||
handleReset();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.box {
|
||||
display: flex;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.box-left {
|
||||
flex-basis: 20%;
|
||||
}
|
||||
|
||||
.box-right {
|
||||
flex-basis: 80%;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 16px;
|
||||
line-height: 18px;
|
||||
margin-bottom: 15px;
|
||||
padding-left: 6px;
|
||||
border-left: 6px solid #5e95ff;
|
||||
}
|
||||
|
||||
:deep(.ant-table-body) {
|
||||
height: 200px;
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,183 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="form-box">
|
||||
<div class="item">
|
||||
<div class="label"><em class="text-red-600">*</em>{{ t('接口名称') }}:</div>
|
||||
<a-input
|
||||
v-model:value="data.config.name"
|
||||
:placeholder="t('点击选择接口')"
|
||||
@click="open"
|
||||
style="width: 100%"
|
||||
>
|
||||
<template #suffix>
|
||||
<Icon icon="ant-design:ellipsis-outlined" />
|
||||
</template>
|
||||
</a-input>
|
||||
</div>
|
||||
<div class="item">
|
||||
<div class="label"><em class="text-red-600">*</em>{{ t('请求方法') }}:</div>
|
||||
<a-input
|
||||
v-model:value="data.config.method"
|
||||
disabled
|
||||
:placeholder="t('请求方法')"
|
||||
style="width: 100%"
|
||||
>
|
||||
<template #suffix>
|
||||
<Icon icon="ant-design:ellipsis-outlined" />
|
||||
</template>
|
||||
</a-input>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a-modal
|
||||
:width="1200"
|
||||
v-model:visible="visible"
|
||||
:title="t('选择接口')"
|
||||
:maskClosable="false"
|
||||
@ok="handleSubmit"
|
||||
@cancel="handleClose"
|
||||
>
|
||||
<InterfaceAddressList v-if="visible" v-model:id="data.config.id" />
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, reactive, ref } from 'vue';
|
||||
import { Icon } from '/@/components/Icon';
|
||||
import InterfaceAddressList from './InterfaceAddressList.vue';
|
||||
import { getInterfaceInfo } from '/@/api/system/interface/index';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
import { ApiConfig } from '../interface';
|
||||
import { message } from 'ant-design-vue';
|
||||
const { t } = useI18n();
|
||||
const emit = defineEmits(['setApiConfig']);
|
||||
const props = withDefaults(defineProps<{ config: ApiConfig }>(), {
|
||||
config: () => {
|
||||
return {
|
||||
id: '',
|
||||
name: '',
|
||||
method: '',
|
||||
requestParamsConfigs: [], //Query Params
|
||||
requestHeaderConfigs: [], //Header
|
||||
requestBodyConfigs: [], //Body
|
||||
};
|
||||
},
|
||||
});
|
||||
let data: { config: ApiConfig } = reactive({
|
||||
config: {
|
||||
id: '',
|
||||
name: '',
|
||||
method: '',
|
||||
requestParamsConfigs: [], //Query Params
|
||||
requestHeaderConfigs: [], //Header
|
||||
requestBodyConfigs: [], //Body
|
||||
},
|
||||
});
|
||||
const visible = ref<boolean>(false);
|
||||
onMounted(() => {
|
||||
if (props.config.id) {
|
||||
data.config.id = props.config.id;
|
||||
// getApiData();
|
||||
}
|
||||
if (props.config.name) data.config.name = props.config.name;
|
||||
if (props.config.method) data.config.method = props.config.method;
|
||||
});
|
||||
function open() {
|
||||
visible.value = true;
|
||||
}
|
||||
async function handleSubmit() {
|
||||
if (data.config.id) {
|
||||
getApiData();
|
||||
visible.value = false;
|
||||
} else {
|
||||
message.warning('请选择一个接口');
|
||||
}
|
||||
}
|
||||
async function getApiData() {
|
||||
let interfaceInfo = await getInterfaceInfo({ id: data.config.id });
|
||||
if (interfaceInfo.id) data.config.id = interfaceInfo.id;
|
||||
if (interfaceInfo.name) data.config.name = interfaceInfo.name;
|
||||
if (interfaceInfo.method) data.config.method = interfaceInfo.method;
|
||||
emit('setApiConfig', interfaceInfo);
|
||||
}
|
||||
function handleClose() {
|
||||
data.config.id = props.config.id; //还原
|
||||
visible.value = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.title {
|
||||
display: flex;
|
||||
height: 40px;
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.form-box {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.item {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.label {
|
||||
width: 120px;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.select-box {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border: 1px solid #d9d9d9;
|
||||
padding: 0 4px;
|
||||
height: 30px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.rule-text {
|
||||
color: #d0cfd0;
|
||||
}
|
||||
|
||||
.list {
|
||||
.row {
|
||||
height: 40px;
|
||||
line-height: 30px;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
|
||||
span {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.common {
|
||||
flex-basis: 25%;
|
||||
margin-right: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.head {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
.item {
|
||||
border-bottom: 1px solid #f9f9f9;
|
||||
}
|
||||
|
||||
.delete-icon {
|
||||
color: @clear-color;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
28
src/components/ApiConfig/src/interface.ts
Normal file
28
src/components/ApiConfig/src/interface.ts
Normal file
@ -0,0 +1,28 @@
|
||||
export interface InterfaceList {
|
||||
id: string;
|
||||
name: string;
|
||||
path: string;
|
||||
}
|
||||
export interface ApiConfig {
|
||||
id: string; //接口Id
|
||||
name: string; //接口path
|
||||
method: string; //接口请求方式
|
||||
path?: string; //接口地址
|
||||
script?: string; //接口地址
|
||||
requestParamsConfigs: Array<InputParamItem>; //Query Params 输入参数
|
||||
requestHeaderConfigs: Array<InputParamItem>; //Header 输入参数
|
||||
requestBodyConfigs: Array<InputParamItem>; //Body 输入参数
|
||||
}
|
||||
// 参数配置
|
||||
export interface InputParamItem {
|
||||
name: string; //API入参名称
|
||||
dataType: string; //API入参类型
|
||||
assignmentType: string; //赋值类型
|
||||
value: string; //值
|
||||
config: string; //赋值配置
|
||||
}
|
||||
export interface ApiParams {
|
||||
key: string;
|
||||
title: string;
|
||||
tableInfo: Array<InputParamItem>;
|
||||
}
|
||||
Reference in New Issue
Block a user