feat(项目管理): 环境管理环境页面接口对接
This commit is contained in:
parent
222a6901f4
commit
a6c8adf1e4
|
@ -0,0 +1,69 @@
|
|||
import MSR from '@/api/http/index';
|
||||
import * as envURL from '@/api/requrls/project-management/envManagement';
|
||||
|
||||
import type {
|
||||
EnvDetailItem,
|
||||
EnvGroupListItem,
|
||||
EnvGroupProjectListItem,
|
||||
EnvListItem,
|
||||
} from '@/models/projectManagement/environmental';
|
||||
import { OptionsItem } from '@/models/setting/log';
|
||||
|
||||
export function updateEnv(data: EnvListItem) {
|
||||
return MSR.post<EnvListItem>({ url: envURL.updateEnvUrl, data });
|
||||
}
|
||||
export function listEnv(data: { projectId: string; keyword: string }) {
|
||||
return MSR.post<EnvListItem[]>({ url: envURL.listEnvUrl, data });
|
||||
}
|
||||
export function importEnv(data: { request: EnvListItem; fileList: File[] }) {
|
||||
return MSR.uploadFile({ url: envURL.importEnvUrl }, data, '', true);
|
||||
}
|
||||
export function getEntryEnv(data: EnvListItem) {
|
||||
return MSR.post<EnvListItem>({ url: envURL.getEntryEnvUrl, data });
|
||||
}
|
||||
export function exportEnv(data: EnvListItem) {
|
||||
return MSR.post<EnvListItem>({ url: envURL.exportEnvUrl, data });
|
||||
}
|
||||
export function editPosEnv(data: EnvListItem) {
|
||||
return MSR.post<EnvListItem>({ url: envURL.editPosEnvUrl, data });
|
||||
}
|
||||
|
||||
/** 测试数据库连接 */
|
||||
export function validateDatabaseEnv(data: object) {
|
||||
return MSR.post<EnvListItem>({ url: envURL.validateDatabaseEnvUrl, data });
|
||||
}
|
||||
/** 获取数据库驱动option */
|
||||
export function driverOptionFun(organizationId: string) {
|
||||
return MSR.get<OptionsItem[]>({ url: envURL.driverOptionUrl + organizationId });
|
||||
}
|
||||
|
||||
export function addEnv(data: EnvListItem) {
|
||||
return MSR.post<EnvListItem>({ url: envURL.addEnvUrl, data });
|
||||
}
|
||||
export function getDetailEnv(id: string) {
|
||||
return MSR.get<EnvDetailItem>({ url: envURL.detailEnvUrl + id });
|
||||
}
|
||||
export function deleteEnv(data: EnvListItem) {
|
||||
return MSR.post<EnvListItem>({ url: envURL.deleteEnvUrl, data });
|
||||
}
|
||||
export function groupUpdateEnv(data: EnvListItem) {
|
||||
return MSR.post<EnvListItem>({ url: envURL.groupUpdateEnvUrl, data });
|
||||
}
|
||||
export function groupListEnv(data: EnvGroupListItem) {
|
||||
return MSR.post<EnvListItem>({ url: envURL.groupListEnvUrl, data });
|
||||
}
|
||||
export function groupEditPosEnv(data: EnvGroupListItem) {
|
||||
return MSR.post<EnvListItem>({ url: envURL.groupEditPosEnvUrl, data });
|
||||
}
|
||||
export function groupAddEnv(data: EnvGroupListItem) {
|
||||
return MSR.post<EnvListItem>({ url: envURL.groupAddEnvUrl, data });
|
||||
}
|
||||
export function groupDetailEnv(data: EnvListItem) {
|
||||
return MSR.post<EnvListItem>({ url: envURL.groupDetailEnvUrl, data });
|
||||
}
|
||||
export function groupDeleteEnv(data: EnvListItem) {
|
||||
return MSR.post<EnvListItem>({ url: envURL.groupDeleteEnvUrl, data });
|
||||
}
|
||||
export function groupProjectEnv(data: EnvGroupProjectListItem) {
|
||||
return MSR.post<EnvListItem>({ url: envURL.groupProjectEnvUrl, data });
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
export const updateEnvUrl = '/project/environment/update';
|
||||
export const listEnvUrl = '/project/environment/list';
|
||||
export const importEnvUrl = '/project/environment/import';
|
||||
export const getEntryEnvUrl = '/project/environment/get/entry';
|
||||
export const exportEnvUrl = '/project/environment/export';
|
||||
export const editPosEnvUrl = '/project/environment/edit/pos';
|
||||
export const validateDatabaseEnvUrl = '/project/environment/database/validate';
|
||||
export const addEnvUrl = '/project/environment/add';
|
||||
export const detailEnvUrl = '/project/environment/get/';
|
||||
export const deleteEnvUrl = '/project/environment/delete/';
|
||||
export const driverOptionUrl = '/project/environment/database/driver-options/';
|
||||
export const groupUpdateEnvUrl = '/project/environment/group/update';
|
||||
export const groupListEnvUrl = '/project/environment/group/list';
|
||||
export const groupEditPosEnvUrl = '/project/environment/group/edit/pos';
|
||||
export const groupAddEnvUrl = '/project/environment/group/add';
|
||||
export const groupDetailEnvUrl = '/project/environment/group/get/';
|
||||
export const groupDeleteEnvUrl = '/project/environment/group/delete/';
|
||||
export const groupProjectEnvUrl = '/project/environment/group/get-project';
|
|
@ -0,0 +1,22 @@
|
|||
<template>
|
||||
<div class="flex flex-col gap-[8px]">
|
||||
<div class="flex flex-row items-center gap-[8px]">
|
||||
<span class="text-[var(--color-text-1)]">{{ t('ms.assertion.responseTime') }}</span>
|
||||
<span class="text-[var(--color-text-4)]">(ms)</span>
|
||||
</div>
|
||||
<a-input-number v-model:model-value="value" :step="100" mode="button" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const value = defineModel('modelValue', {
|
||||
default: 1000,
|
||||
type: Number,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
|
@ -0,0 +1,89 @@
|
|||
<template>
|
||||
<div>
|
||||
<paramsTable
|
||||
v-model:params="innerParams"
|
||||
:selectable="false"
|
||||
:columns="columns"
|
||||
:scroll="{ minWidth: '700px' }"
|
||||
:default-param-item="defaultParamItem"
|
||||
@change="handleParamTableChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { statusCodeOptions } from '@/components/pure/ms-advance-filter/index';
|
||||
import paramsTable, { type ParamTableColumn } from '@/views/api-test/components/paramTable.vue';
|
||||
|
||||
interface Param {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
const innerParams = defineModel<Param[]>('modelValue', { default: [] });
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'change'): void; // 数据发生变化
|
||||
}>();
|
||||
|
||||
const defaultParamItem = {
|
||||
responseHeader: '',
|
||||
matchCondition: '',
|
||||
matchValue: '',
|
||||
enable: true,
|
||||
};
|
||||
|
||||
const responseHeaderOption = [
|
||||
{ label: 'Accept', value: 'accept' },
|
||||
{ label: 'Accept-Encoding', value: 'acceptEncoding' },
|
||||
{ label: 'Accept-Language', value: 'acceptLanguage' },
|
||||
{ label: 'Cache-Control', value: 'cacheControl' },
|
||||
{ label: 'Content-Type', value: 'contentType' },
|
||||
{ label: 'Content-Length', value: 'contentLength' },
|
||||
{ label: 'User-Agent', value: 'userAgent' },
|
||||
{ label: 'Referer', value: 'referer' },
|
||||
{ label: 'Cookie', value: 'cookie' },
|
||||
{ label: 'Authorization', value: 'authorization' },
|
||||
{ label: 'If-None-Match', value: 'ifNoneMatch' },
|
||||
{ label: 'If-Modified-Since', value: 'ifModifiedSince' },
|
||||
];
|
||||
|
||||
const columns: ParamTableColumn[] = [
|
||||
{
|
||||
title: 'ms.assertion.variableName', // 变量名
|
||||
dataIndex: 'name',
|
||||
slotName: 'name',
|
||||
showInTable: true,
|
||||
showDrag: true,
|
||||
options: responseHeaderOption,
|
||||
},
|
||||
{
|
||||
title: 'ms.assertion.matchCondition', // 匹配条件
|
||||
dataIndex: 'matchCondition',
|
||||
slotName: 'matchCondition',
|
||||
showInTable: true,
|
||||
showDrag: true,
|
||||
options: statusCodeOptions,
|
||||
},
|
||||
{
|
||||
title: 'ms.assertion.matchValue', // 匹配值
|
||||
dataIndex: 'value',
|
||||
slotName: 'value',
|
||||
showInTable: true,
|
||||
showDrag: true,
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
columnTitle: 'common.operation',
|
||||
slotName: 'operation',
|
||||
width: 50,
|
||||
showInTable: true,
|
||||
showDrag: true,
|
||||
},
|
||||
];
|
||||
function handleParamTableChange(resultArr: any[], isInit?: boolean) {
|
||||
innerParams.value = [...resultArr];
|
||||
if (!isInit) {
|
||||
emit('change');
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -8,15 +8,19 @@
|
|||
</div>
|
||||
</a-button>
|
||||
<template #content>
|
||||
<a-doption v-for="item in assertOption" :key="item.value" :value="item.value">{{ item.label }}</a-doption>
|
||||
<a-doption v-for="item in assertOptionSource" :key="item.value" :value="item.value">{{ item.label }}</a-doption>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
<div v-if="showBody" class="ms-assertion-body">
|
||||
<article class="ms-assertion-body-left">
|
||||
<VueDraggable v-model="selectItems" class="ms-assertion-body-left" ghost-class="ghost" handle=".sort-handle">
|
||||
<div
|
||||
v-for="(item, index) in activeOption"
|
||||
:key="item.value"
|
||||
v-for="(item, index) in selectItems"
|
||||
:key="item.id"
|
||||
class="ms-assertion-body-left-item"
|
||||
:class="{
|
||||
'ms-assertion-body-left-item-active': activeKey === item.id,
|
||||
'ms-assertion-body-left-item-active-focus': focusKey === item.id,
|
||||
}"
|
||||
@click="handleItemClick(item)"
|
||||
>
|
||||
<div class="ms-assertion-body-left-item-row">
|
||||
|
@ -24,38 +28,92 @@
|
|||
<span class="ms-assertion-body-left-item-row-title">{{ item.label }}</span>
|
||||
</div>
|
||||
<div class="ms-assertion-body-left-item-switch">
|
||||
<div class="ms-assertion-body-left-item-switch-action">
|
||||
<MsIcon
|
||||
type="icon-icon_drag"
|
||||
class="action-btn-move sort-handle cursor-move text-[12px] text-[var(--color-text-4)]"
|
||||
/>
|
||||
<MsTableMoreAction
|
||||
:list="itemMoreActions"
|
||||
trigger="click"
|
||||
@select="handleMoreActionSelect($event, item)"
|
||||
@close="focusKey = ''"
|
||||
>
|
||||
<MsButton type="icon" size="mini" class="action-btn-more">
|
||||
<MsIcon
|
||||
type="icon-icon_more_outlined"
|
||||
size="14"
|
||||
class="text-[var(--color-text-4)]"
|
||||
@click="focusKey = item.id"
|
||||
/>
|
||||
</MsButton>
|
||||
</MsTableMoreAction>
|
||||
</div>
|
||||
|
||||
<a-switch type="line" size="small" />
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</VueDraggable>
|
||||
<section class="ms-assertion-body-right">
|
||||
<MsAssertionStatusCodeTab
|
||||
v-if="activeKey === 'statusCode'"
|
||||
<StatusCodeTab
|
||||
v-if="valueKey === 'statusCode'"
|
||||
v-model:selectValue="codeTabState.selectValue"
|
||||
v-model:statusCode="codeTabState.statusCode"
|
||||
/>
|
||||
<ResponseHeaderTab v-if="activeKey === 'responseHeader'" />
|
||||
<ResponseHeaderTab v-if="valueKey === 'responseHeader'" />
|
||||
<ResponseTimeTab v-if="valueKey === 'responseTime'" />
|
||||
<VariableTab v-if="valueKey === 'variable'" />
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { VueDraggable } from 'vue-draggable-plus';
|
||||
|
||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
|
||||
import MsTableMoreAction from '@/components/pure/ms-table-more-action/index.vue';
|
||||
import { ActionsItem } from '@/components/pure/ms-table-more-action/types';
|
||||
import ResponseHeaderTab from './comp/ResponseHeaderTab.vue';
|
||||
import MsAssertionStatusCodeTab from './comp/StatusCodeTab.vue';
|
||||
import ResponseTimeTab from './comp/ResponseTimeTab.vue';
|
||||
import StatusCodeTab from './comp/StatusCodeTab.vue';
|
||||
import VariableTab from './comp/VariableTab.vue';
|
||||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
import { MsAssertionItem } from './type';
|
||||
|
||||
defineOptions({
|
||||
name: 'MsAssertion',
|
||||
});
|
||||
|
||||
const { t } = useI18n();
|
||||
// 当前鼠标所在的key
|
||||
const focusKey = ref<string>('');
|
||||
// 选中的选项
|
||||
const selectItems = ref<MsAssertionItem[]>([]);
|
||||
// Item点击的key
|
||||
const activeKey = ref<string>('');
|
||||
// valueKey
|
||||
const valueKey = computed(() => {
|
||||
return activeKey.value && selectItems.value.find((item) => item.id === activeKey.value)?.value;
|
||||
});
|
||||
|
||||
const codeTabState = reactive({
|
||||
selectValue: '',
|
||||
statusCode: 200,
|
||||
});
|
||||
const itemMoreActions: ActionsItem[] = [
|
||||
{
|
||||
label: 'common.copy',
|
||||
eventTag: 'copy',
|
||||
},
|
||||
{
|
||||
label: 'project.fileManagement.delete',
|
||||
eventTag: 'delete',
|
||||
},
|
||||
];
|
||||
// 源选项
|
||||
const assertOptionSource = [
|
||||
{
|
||||
|
@ -76,37 +134,53 @@
|
|||
},
|
||||
{
|
||||
label: t('ms.assertion.param'),
|
||||
value: 'param',
|
||||
value: 'variable',
|
||||
},
|
||||
{
|
||||
label: t('ms.assertion.script'),
|
||||
value: 'script',
|
||||
},
|
||||
];
|
||||
// 选中的选项
|
||||
const selectIds = ref<string[]>([]);
|
||||
// Item点击的key
|
||||
const activeKey = ref<string>('');
|
||||
|
||||
// 未选中的选项
|
||||
const assertOption = computed(() => {
|
||||
return assertOptionSource.filter((item) => !selectIds.value.includes(item.value));
|
||||
});
|
||||
// 选中的选项
|
||||
const activeOption = computed(() => {
|
||||
return assertOptionSource.filter((item) => selectIds.value.includes(item.value));
|
||||
});
|
||||
// 是否显示主体
|
||||
const showBody = computed(() => {
|
||||
return selectIds.value.length > 0;
|
||||
return selectItems.value.length > 0;
|
||||
});
|
||||
// dropdown选择
|
||||
const handleSelect = (value: string | number | Record<string, any> | undefined) => {
|
||||
selectIds.value.push(value as string);
|
||||
const tmpObj = {
|
||||
label: assertOptionSource.find((item) => item.value === value)?.label || '',
|
||||
value: value as string,
|
||||
id: new Date().getTime().toString(),
|
||||
};
|
||||
if (activeKey.value) {
|
||||
const currentIndex = selectItems.value.findIndex((item) => item.id === activeKey.value);
|
||||
const tmpArr = selectItems.value;
|
||||
tmpArr.splice(currentIndex, 0, tmpObj);
|
||||
selectItems.value = tmpArr;
|
||||
} else {
|
||||
selectItems.value.push(tmpObj);
|
||||
}
|
||||
activeKey.value = tmpObj.id;
|
||||
};
|
||||
const handleMoreActionSelect = (event: ActionsItem, item: MsAssertionItem) => {
|
||||
const currentIndex = selectItems.value.findIndex((tmpItem) => tmpItem.id === item.id);
|
||||
if (event.eventTag === 'delete') {
|
||||
selectItems.value.splice(currentIndex, 1);
|
||||
activeKey.value = currentIndex > 0 ? selectItems.value[currentIndex - 1].id : '';
|
||||
} else {
|
||||
// copy 当前item
|
||||
const tmpObj = { ...selectItems.value[currentIndex], id: new Date().getTime().valueOf().toString() };
|
||||
const tmpArr = selectItems.value;
|
||||
tmpArr.splice(currentIndex, 0, tmpObj);
|
||||
selectItems.value = tmpArr;
|
||||
activeKey.value = tmpObj.id;
|
||||
}
|
||||
};
|
||||
|
||||
// item点击
|
||||
const handleItemClick = (item: { label: string; value: string }) => {
|
||||
activeKey.value = item.value;
|
||||
const handleItemClick = (item: MsAssertionItem) => {
|
||||
activeKey.value = item.id;
|
||||
};
|
||||
</script>
|
||||
|
||||
|
@ -153,12 +227,23 @@
|
|||
line-height: 16px;
|
||||
}
|
||||
&-title {
|
||||
font-size: 12px;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
color: var(--color-text-1);
|
||||
line-height: 22px;
|
||||
}
|
||||
}
|
||||
&-switch {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
&-action {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
&-right {
|
||||
|
@ -171,4 +256,35 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
.action-btn-move,
|
||||
.action-btn-more {
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
.ms-assertion-body-left-item-active {
|
||||
background-color: rgb(var(--primary-1)) !important;
|
||||
.ms-assertion-body-left-item-row {
|
||||
&-num {
|
||||
color: rgb(var(--primary-5)) !important;
|
||||
background-color: var(--color-text-fff) !important;
|
||||
}
|
||||
&-title {
|
||||
color: rgb(var(--primary-5));
|
||||
}
|
||||
}
|
||||
}
|
||||
.ms-assertion-body-left-item:hover {
|
||||
background-color: rgb(var(--primary-1));
|
||||
.action-btn-move,
|
||||
.action-btn-more {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
.ms-assertion-body-left-item-active-focus {
|
||||
background-color: rgb(var(--primary-1));
|
||||
.action-btn-move,
|
||||
.action-btn-more {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -9,4 +9,5 @@ export default {
|
|||
'ms.assertion.noValidation': '不校验',
|
||||
'ms.assertion.matchCondition': '匹配条件',
|
||||
'ms.assertion.matchValue': '匹配值',
|
||||
'ms.assertion.variableName': '变量名',
|
||||
};
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
export interface MsAssertionItem {
|
||||
id: string;
|
||||
label: string;
|
||||
value: string;
|
||||
}
|
|
@ -1,3 +1,5 @@
|
|||
import { key } from 'localforage';
|
||||
|
||||
export interface EnvListItem {
|
||||
name: string;
|
||||
id: string;
|
||||
|
@ -14,3 +16,37 @@ export interface EnvGroupListItem {
|
|||
id: string;
|
||||
projectList: EnvGroupProjectListItem[];
|
||||
}
|
||||
export interface DataSourceItem {
|
||||
id?: string;
|
||||
name: string;
|
||||
driverId?: string;
|
||||
dbUrl: string;
|
||||
username: string;
|
||||
password?: string;
|
||||
poolMax?: number;
|
||||
timeout?: number;
|
||||
enable?: boolean;
|
||||
}
|
||||
|
||||
export interface EnvConfigItem {
|
||||
[key: string]: any;
|
||||
}
|
||||
export interface EnvConfig {
|
||||
commonParams?: EnvConfigItem;
|
||||
commmonVariables?: EnvConfigItem[];
|
||||
httpConfig?: EnvConfigItem[];
|
||||
dataSource?: DataSourceItem[];
|
||||
hostConfig?: EnvConfigItem;
|
||||
authConfig?: EnvConfigItem;
|
||||
preScript?: EnvConfigItem;
|
||||
postScript?: EnvConfigItem;
|
||||
assertions?: EnvConfigItem;
|
||||
}
|
||||
export interface EnvDetailItem {
|
||||
id?: string;
|
||||
projectId: string;
|
||||
name: string;
|
||||
config: EnvConfig;
|
||||
mock?: string;
|
||||
description?: string;
|
||||
}
|
||||
|
|
|
@ -1,13 +1,17 @@
|
|||
import { defineStore } from 'pinia';
|
||||
|
||||
import { EnvGroupListItem } from '@/models/projectManagement/environmental';
|
||||
import { getDetailEnv } from '@/api/modules/project-management/envManagement';
|
||||
|
||||
import { EnvDetailItem, EnvGroupListItem } from '@/models/projectManagement/environmental';
|
||||
|
||||
export const ALL_PARAM = 'allParam';
|
||||
export const NEW_ENV_PARAM = 'newEnvParam';
|
||||
|
||||
const useProjectEnvStore = defineStore(
|
||||
'projectEnv',
|
||||
() => {
|
||||
const currentId = ref<string | number>(1);
|
||||
const currentId = ref<string>('');
|
||||
const currentEnvDetailInfo = ref<EnvDetailItem>();
|
||||
const httpNoWarning = ref(true);
|
||||
const envGroupList = ref<EnvGroupListItem[]>([]);
|
||||
|
||||
|
@ -16,22 +20,41 @@ const useProjectEnvStore = defineStore(
|
|||
const getGroupLength = computed(() => 1);
|
||||
|
||||
const getDatabaseList = computed(() => [{ id: 1, name: 'test' }]);
|
||||
function setCurrentId(id: string | number) {
|
||||
function setCurrentId(id: string) {
|
||||
currentId.value = id;
|
||||
}
|
||||
function setHttpNoWarning(noWarning: boolean) {
|
||||
httpNoWarning.value = noWarning;
|
||||
}
|
||||
function setEnvDetailInfo(item: EnvDetailItem) {
|
||||
currentEnvDetailInfo.value = item;
|
||||
}
|
||||
async function initEnvDetail() {
|
||||
const id = currentId.value;
|
||||
try {
|
||||
if (id === NEW_ENV_PARAM) {
|
||||
currentEnvDetailInfo.value = undefined;
|
||||
} else if (id !== ALL_PARAM && id) {
|
||||
currentEnvDetailInfo.value = await getDetailEnv(id);
|
||||
}
|
||||
} catch (e) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
getCurrentId,
|
||||
currentId,
|
||||
httpNoWarning,
|
||||
setCurrentId,
|
||||
setHttpNoWarning,
|
||||
setEnvDetailInfo,
|
||||
getHttpNoWarning,
|
||||
getDatabaseList,
|
||||
envGroupList,
|
||||
getGroupLength,
|
||||
initEnvDetail,
|
||||
};
|
||||
},
|
||||
{
|
||||
|
|
|
@ -183,17 +183,17 @@
|
|||
/>
|
||||
</template>
|
||||
<template #responseHeader="{ record, columnConfig }">
|
||||
<a-select v-model="record.responseHeader" @change="(val) => addTableLine(val as string)">
|
||||
<a-select v-model="record.responseHeader" class="param-input" @change="(val) => addTableLine(val as string)">
|
||||
<a-option v-for="item in columnConfig.options" :key="item.value">{{ t(item.label) }}</a-option>
|
||||
</a-select>
|
||||
</template>
|
||||
<template #matchCondition="{ record, columnConfig }">
|
||||
<a-select v-model="record.condition" @change="(val) => addTableLine(val as string)">
|
||||
<a-select v-model="record.condition" class="param-input" @change="(val) => addTableLine(val as string)">
|
||||
<a-option v-for="item in columnConfig.options" :key="item.value">{{ t(item.label) }}</a-option>
|
||||
</a-select>
|
||||
</template>
|
||||
<template #matchValue="{ record }">
|
||||
<a-input-number v-model="record.matchValue" hide-button @change="(val) => addTableLine(val)" />
|
||||
<a-input v-model="record.matchValue" class="param-input" @change="(val) => addTableLine(val)" />
|
||||
</template>
|
||||
</MsBaseTable>
|
||||
<a-modal
|
||||
|
|
|
@ -54,12 +54,17 @@
|
|||
import TcpTab from './envParams/TcpTab.vue';
|
||||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import { useAppStore } from '@/store';
|
||||
import useProjectEnvStore, { NEW_ENV_PARAM } from '@/store/modules/setting/useProjectEnvStore';
|
||||
|
||||
const activeKey = ref('tcp');
|
||||
const activeKey = ref('assert');
|
||||
const envForm = ref();
|
||||
const canSave = ref(false);
|
||||
const { t } = useI18n();
|
||||
|
||||
const store = useProjectEnvStore();
|
||||
const appStore = useAppStore();
|
||||
|
||||
const form = reactive({
|
||||
name: '',
|
||||
});
|
||||
|
@ -112,6 +117,20 @@
|
|||
}
|
||||
});
|
||||
};
|
||||
const initData = async () => {
|
||||
await store.initEnvDetail();
|
||||
};
|
||||
watchEffect(() => {
|
||||
if (store.currentId === NEW_ENV_PARAM) {
|
||||
store.setEnvDetailInfo({
|
||||
name: '',
|
||||
projectId: appStore.currentProjectId,
|
||||
config: {},
|
||||
});
|
||||
} else {
|
||||
initData();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
|
|
@ -21,7 +21,12 @@
|
|||
</div>
|
||||
</template>
|
||||
</MsBaseTable>
|
||||
<AddDatabaseModal v-model:visible="addVisible" :current-id="currentId" @close="addVisible = false" />
|
||||
<AddDatabaseModal
|
||||
v-model:visible="addVisible"
|
||||
v-model="currentDatabase"
|
||||
:current-id="currentId"
|
||||
@close="addVisible = false"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" async setup>
|
||||
|
@ -40,6 +45,7 @@
|
|||
import useProjectEnvStore from '@/store/modules/setting/useProjectEnvStore';
|
||||
|
||||
import { BugListItem } from '@/models/bug-management';
|
||||
import { DataSourceItem } from '@/models/projectManagement/environmental';
|
||||
import { TableKeyEnum } from '@/enums/tableEnum';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
@ -51,6 +57,18 @@
|
|||
const addVisible = ref(false);
|
||||
const currentId = ref('');
|
||||
|
||||
const currentDatabase = ref<DataSourceItem>({
|
||||
id: '',
|
||||
name: '',
|
||||
driverId: '',
|
||||
dbUrl: '',
|
||||
username: '',
|
||||
password: '',
|
||||
poolMax: 1,
|
||||
timeout: 1000,
|
||||
enable: true,
|
||||
});
|
||||
|
||||
const columns: MsTableColumn = [
|
||||
{
|
||||
title: 'project.environmental.database.name',
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
<template #title>
|
||||
<span v-if="isEdit">
|
||||
{{ t('project.environmental.database.updateDatabase') }}
|
||||
<span class="text-[var(--color-text-4)]">({{ props.currentProject?.name }})</span>
|
||||
<span class="text-[var(--color-text-4)]">({{ form.name }})</span>
|
||||
</span>
|
||||
<span v-else>
|
||||
{{ t('project.environmental.database.addDatabase') }}
|
||||
|
@ -26,20 +26,18 @@
|
|||
>
|
||||
<a-input v-model="form.name" allow-clear :placeholder="t('project.environmental.database.namePlaceholder')" />
|
||||
</a-form-item>
|
||||
<a-form-item field="driver" asterisk-position="end" :label="t('project.environmental.database.driver')">
|
||||
<a-select v-model="form.driver">
|
||||
<a-option value="mysql">MySQL</a-option>
|
||||
</a-select>
|
||||
<a-form-item field="driverId" asterisk-position="end" :label="t('project.environmental.database.driver')">
|
||||
<a-select v-model="form.driverId" :options="driverOption" />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
field="url"
|
||||
field="dbUrl"
|
||||
required
|
||||
:label="t('project.environmental.database.url')"
|
||||
asterisk-position="end"
|
||||
:extra="t('project.environmental.database.urlExtra')"
|
||||
:rules="[{ required: true, message: t('project.environmental.database.urlIsRequire') }]"
|
||||
>
|
||||
<a-input v-model="form.url" allow-clear :placeholder="t('common.pleaseInput')" />
|
||||
<a-input v-model="form.dbUrl" allow-clear :placeholder="t('common.pleaseInput')" />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
field="username"
|
||||
|
@ -48,10 +46,16 @@
|
|||
asterisk-position="end"
|
||||
:rules="[{ required: true, message: t('project.environmental.database.usernameIsRequire') }]"
|
||||
>
|
||||
<a-input v-model="form.username" allow-clear :placeholder="t('common.pleaseInput')" />
|
||||
<a-input
|
||||
v-model="form.username"
|
||||
:max-length="255"
|
||||
show-word-limit
|
||||
allow-clear
|
||||
:placeholder="t('common.pleaseInput')"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item field="password" :label="t('project.environmental.database.password')">
|
||||
<a-input
|
||||
<a-input-password
|
||||
v-model="form.password"
|
||||
:placeholder="t('common.pleaseInput')"
|
||||
allow-clear
|
||||
|
@ -59,12 +63,25 @@
|
|||
/>
|
||||
</a-form-item>
|
||||
<a-form-item field="poolMax" :label="t('project.environmental.database.poolMax')">
|
||||
<a-input-number v-model:model-value="form.poolMax" :min="1" :default-value="1" />
|
||||
<a-input-number
|
||||
v-model:model-value="form.poolMax"
|
||||
class="w-[152px]"
|
||||
mode="button"
|
||||
:min="1"
|
||||
:default-value="1"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item field="timeout" :label="t('project.environmental.database.timeout')">
|
||||
<a-input-number v-model:model-value="form.timeout" :default-value="1000" />
|
||||
<a-input-number
|
||||
v-model:model-value="form.timeout"
|
||||
class="w-[152px]"
|
||||
mode="button"
|
||||
:step="100"
|
||||
:min="0"
|
||||
:default-value="1000"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-button type="outline" class="w-[88px]">
|
||||
<a-button type="outline" class="w-[88px]" @click="testConnection">
|
||||
{{ t('project.environmental.database.testConnection') }}
|
||||
</a-button>
|
||||
</a-form>
|
||||
|
@ -85,28 +102,29 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, reactive, ref, watchEffect } from 'vue';
|
||||
import { computed, ref } from 'vue';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
|
||||
import { driverOptionFun, validateDatabaseEnv } from '@/api/modules/project-management/envManagement';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import { useAppStore } from '@/store';
|
||||
import useLicenseStore from '@/store/modules/setting/license';
|
||||
|
||||
import { CreateOrUpdateSystemProjectParams, SystemOrgOption } from '@/models/setting/system/orgAndProject';
|
||||
import { DataSourceItem } from '@/models/projectManagement/environmental';
|
||||
|
||||
import type { FormInstance, ValidatedError } from '@arco-design/web-vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: DataSourceItem;
|
||||
visible: boolean;
|
||||
currentProject?: CreateOrUpdateSystemProjectParams;
|
||||
}>();
|
||||
|
||||
const formRef = ref<FormInstance>();
|
||||
|
||||
const loading = ref(false);
|
||||
const isEdit = computed(() => props.currentProject && props.currentProject.id);
|
||||
const driverOption = ref<SystemOrgOption[]>([]);
|
||||
const driverOption = ref<{ label: string; value: string }[]>([]);
|
||||
const appStore = useAppStore();
|
||||
const licenseStore = useLicenseStore();
|
||||
|
||||
|
@ -114,33 +132,86 @@
|
|||
(e: 'cancel', shouldSearch: boolean): void;
|
||||
}>();
|
||||
|
||||
const form = reactive({
|
||||
id: '',
|
||||
name: '',
|
||||
driver: 'mysql',
|
||||
url: '',
|
||||
username: '',
|
||||
password: '',
|
||||
poolMax: 1,
|
||||
timeout: 1000,
|
||||
enable: true,
|
||||
const currentVisible = defineModel('visible', {
|
||||
default: false,
|
||||
type: Boolean,
|
||||
});
|
||||
|
||||
const currentVisible = ref(props.visible);
|
||||
const form = defineModel<DataSourceItem>('modelValue', {
|
||||
required: true,
|
||||
});
|
||||
|
||||
const isEdit = computed(() => form.value.id);
|
||||
|
||||
const getDriverOption = async () => {
|
||||
try {
|
||||
const res = (await driverOptionFun(appStore.currentOrgId)) || [];
|
||||
driverOption.value = res.map((item) => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
}));
|
||||
if (res.length && !isEdit.value) {
|
||||
// 创建模式下默认选中第一个
|
||||
form.value.driverId = res[0].id;
|
||||
}
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
const testConnection = async () => {
|
||||
await formRef.value?.validateField(
|
||||
['url', 'username'],
|
||||
async (errors: undefined | Record<string, ValidatedError>) => {
|
||||
if (errors) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
loading.value = true;
|
||||
await validateDatabaseEnv({
|
||||
name: form.value.name,
|
||||
driverId: form.value.driverId,
|
||||
dbUrl: form.value.dbUrl,
|
||||
username: form.value.username,
|
||||
password: form.value.password,
|
||||
poolMax: form.value.poolMax,
|
||||
timeout: form.value.timeout,
|
||||
});
|
||||
Message.success(t('project.environmental.database.testConnectionSuccess'));
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const isXpack = computed(() => {
|
||||
return licenseStore.hasLicense();
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
currentVisible.value = props.visible;
|
||||
});
|
||||
|
||||
const formReset = () => {
|
||||
form.name = '';
|
||||
if (!isEdit.value) {
|
||||
form.value = {
|
||||
id: '',
|
||||
name: '',
|
||||
driverId: '',
|
||||
dbUrl: '',
|
||||
username: '',
|
||||
password: '',
|
||||
poolMax: 1,
|
||||
timeout: 1000,
|
||||
enable: true,
|
||||
};
|
||||
}
|
||||
};
|
||||
const handleCancel = (shouldSearch: boolean) => {
|
||||
emit('cancel', shouldSearch);
|
||||
currentVisible.value = false;
|
||||
formReset();
|
||||
};
|
||||
|
||||
const handleBeforeOk = async () => {
|
||||
|
@ -150,6 +221,7 @@
|
|||
}
|
||||
try {
|
||||
loading.value = true;
|
||||
|
||||
Message.success(
|
||||
isEdit.value
|
||||
? t('project.environmental.database.updateProjectSuccess')
|
||||
|
@ -164,29 +236,12 @@
|
|||
}
|
||||
});
|
||||
};
|
||||
const initDriverOption = async () => {
|
||||
try {
|
||||
const res = [];
|
||||
driverOption.value = res;
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(error);
|
||||
}
|
||||
const initData = () => {
|
||||
getDriverOption();
|
||||
};
|
||||
watchEffect(() => {
|
||||
if (isEdit.value && props.currentProject) {
|
||||
form.name = props.currentProject.name;
|
||||
if (props.visible) {
|
||||
initData();
|
||||
}
|
||||
});
|
||||
watch(
|
||||
() => props.visible,
|
||||
(val) => {
|
||||
currentVisible.value = val;
|
||||
if (!val) {
|
||||
formReset();
|
||||
} else {
|
||||
initDriverOption();
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
|
|
@ -9,9 +9,10 @@
|
|||
</a-radio-group>
|
||||
<template v-if="showType === 'PROJECT'">
|
||||
<a-input-search
|
||||
v-model="keyword"
|
||||
:placeholder="t('project.environmental.searchHolder')"
|
||||
allow-clear
|
||||
@press-enter="enterData"
|
||||
@press-enter="searchData"
|
||||
@search="searchData"
|
||||
/>
|
||||
<!-- 全局参数-->
|
||||
|
@ -42,7 +43,7 @@
|
|||
@select="(value) => handleMoreAction(value, 'all', EnvAuthTypeEnum.ENVIRONMENT)"
|
||||
/>
|
||||
</div>
|
||||
<MsButton type="icon" class="!mr-0 p-[2px]">
|
||||
<MsButton type="icon" class="!mr-0 p-[2px]" @click="handleCreateEnv">
|
||||
<MsIcon
|
||||
type="icon-icon_create_planarity"
|
||||
size="18"
|
||||
|
@ -54,7 +55,7 @@
|
|||
<div>
|
||||
<!-- 环境list-->
|
||||
<div v-if="envList.length">
|
||||
<VueDraggable v-model="envList" ghost-class="ghost">
|
||||
<VueDraggable v-model="envList" ghost-class="ghost" handle=".drag-handle">
|
||||
<div
|
||||
v-for="element in envList"
|
||||
:key="element.id"
|
||||
|
@ -66,7 +67,7 @@
|
|||
:type="(showType as EnvAuthScopeEnum)"
|
||||
v-bind="popVisible[element.id]"
|
||||
@cancel="handleRenameCancel(element)"
|
||||
@submit="handleRenameCancel(element, element.id)"
|
||||
@submit="handleRenameCancel(element, true)"
|
||||
>
|
||||
<div class="flex max-w-[100%] grow flex-row items-center justify-between">
|
||||
<a-tooltip :content="element.name">
|
||||
|
@ -78,7 +79,7 @@
|
|||
</a-tooltip>
|
||||
<div class="node-extra">
|
||||
<div class="flex flex-row items-center gap-[8px]">
|
||||
<MsButton type="icon" class="!mr-0 p-[2px]">
|
||||
<MsButton type="icon" class="drag-handle !mr-0 p-[2px]">
|
||||
<MsIcon
|
||||
type="icon-icon_drag"
|
||||
size="16"
|
||||
|
@ -118,9 +119,9 @@
|
|||
</MsButton>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 环境list-->
|
||||
<!-- 环境组list-->
|
||||
<div v-if="envList.length">
|
||||
<VueDraggable v-model="envList" ghost-class="ghost">
|
||||
<VueDraggable v-model="envList" ghost-class="ghost" handle=".drag-handle">
|
||||
<div
|
||||
v-for="element in envList"
|
||||
:key="element.id"
|
||||
|
@ -132,7 +133,7 @@
|
|||
:type="(showType as EnvAuthScopeEnum)"
|
||||
v-bind="popVisible[element.id]"
|
||||
@cancel="handleRenameCancel(element)"
|
||||
@submit="handleRenameCancel(element, element.id)"
|
||||
@submit="handleRenameCancel(element, true)"
|
||||
>
|
||||
<div class="flex max-w-[100%] grow flex-row items-center justify-between">
|
||||
<a-tooltip :content="element.name">
|
||||
|
@ -144,7 +145,7 @@
|
|||
</a-tooltip>
|
||||
<div class="node-extra">
|
||||
<div class="flex flex-row items-center gap-[8px]">
|
||||
<MsButton type="icon" class="!mr-0 p-[2px]">
|
||||
<MsButton type="icon" class="drag-handle !mr-0 p-[2px]">
|
||||
<MsIcon
|
||||
type="icon-icon_drag"
|
||||
size="16"
|
||||
|
@ -194,8 +195,10 @@
|
|||
import EnvParamBox from './components/EnvParamBox.vue';
|
||||
import RenamePop from './components/RenamePop.vue';
|
||||
|
||||
import { listEnv } from '@/api/modules/project-management/envManagement';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useProjectEnvStore, { ALL_PARAM } from '@/store/modules/setting/useProjectEnvStore';
|
||||
import { useAppStore } from '@/store';
|
||||
import useProjectEnvStore, { ALL_PARAM, NEW_ENV_PARAM } from '@/store/modules/setting/useProjectEnvStore';
|
||||
|
||||
import { EnvListItem } from '@/models/projectManagement/environmental';
|
||||
import { PopVisible } from '@/models/setting/usergroup';
|
||||
|
@ -210,6 +213,9 @@
|
|||
|
||||
const activeKey = computed(() => store.currentId); // 当前选中的id
|
||||
|
||||
const keyword = ref<string>(''); // 搜索关键字
|
||||
const appStore = useAppStore();
|
||||
|
||||
// 气泡弹窗
|
||||
const popVisible = ref<PopVisible>({});
|
||||
|
||||
|
@ -262,26 +268,33 @@
|
|||
console.log(item, id, scopeType);
|
||||
};
|
||||
|
||||
const handleCreateEnv = () => {
|
||||
const tmpArr = envList.value;
|
||||
tmpArr.unshift({
|
||||
id: NEW_ENV_PARAM,
|
||||
name: t('project.environmental.newEnv'),
|
||||
});
|
||||
store.setCurrentId(NEW_ENV_PARAM);
|
||||
envList.value = tmpArr;
|
||||
};
|
||||
|
||||
function changeShowType(value: string | number | boolean) {
|
||||
console.log(value);
|
||||
}
|
||||
|
||||
// 用户组数据初始化
|
||||
const initData = async (id?: string, isSelect = true) => {
|
||||
const tmpArr: EnvListItem[] = [];
|
||||
for (let i = 0; i < 100; i++) {
|
||||
tmpArr.push({
|
||||
id: `${i + 1}`,
|
||||
name: `环境${i + 1}`,
|
||||
});
|
||||
const initData = async (keywordStr = '') => {
|
||||
try {
|
||||
envList.value = await listEnv({ projectId: appStore.currentProjectId, keyword: keywordStr });
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
}
|
||||
envList.value = tmpArr;
|
||||
console.log(id, isSelect);
|
||||
};
|
||||
|
||||
const handleRenameCancel = (element: EnvListItem, id?: string) => {
|
||||
if (id) {
|
||||
initData(id, true);
|
||||
const handleRenameCancel = (element: EnvListItem, shouldSearch?: boolean) => {
|
||||
if (shouldSearch) {
|
||||
initData();
|
||||
}
|
||||
popVisible.value[element.id].visible = false;
|
||||
};
|
||||
|
@ -291,22 +304,8 @@
|
|||
store.setCurrentId(id);
|
||||
};
|
||||
|
||||
function enterData(eve: Event) {
|
||||
if (!(eve.target as HTMLInputElement).value) {
|
||||
return;
|
||||
}
|
||||
const keyword = (eve.target as HTMLInputElement).value;
|
||||
const tmpArr = envList.value.filter((ele) => ele.name.includes(keyword));
|
||||
envList.value = tmpArr;
|
||||
}
|
||||
function searchData(value: string) {
|
||||
if (!value) {
|
||||
// initData('', false);
|
||||
return;
|
||||
}
|
||||
const keyword = value;
|
||||
const tmpArr = envList.value.filter((ele) => ele.name.includes(keyword));
|
||||
envList.value = tmpArr;
|
||||
function searchData() {
|
||||
initData(keyword.value);
|
||||
}
|
||||
onMounted(() => {
|
||||
initData();
|
||||
|
@ -330,14 +329,14 @@
|
|||
border-radius: var(--border-radius-base);
|
||||
cursor: pointer;
|
||||
.node-extra {
|
||||
@apply relative hidden;
|
||||
opacity: 0;
|
||||
&:hover {
|
||||
@apply block;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
.node-extra {
|
||||
@apply block;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
:active {
|
||||
|
@ -351,7 +350,7 @@
|
|||
}
|
||||
&:hover {
|
||||
.env-row-extra {
|
||||
@apply block;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ export default {
|
|||
'project.environmental.desc': '描述',
|
||||
'project.environmental.envName': '环境名称',
|
||||
'project.environmental.envParams': '环境变量',
|
||||
'project.environmental.newEnv': '未命名环境',
|
||||
'project.environmental.envNamePlaceholder': '请输入环境名称',
|
||||
'project.environmental.envNameRequired': '环境名称不能为空',
|
||||
'project.environmental.database': '数据库',
|
||||
|
|
Loading…
Reference in New Issue