feat(项目管理): 环境管理环境页面接口对接

This commit is contained in:
RubyLiu 2024-01-18 20:16:48 +08:00 committed by 刘瑞斌
parent 222a6901f4
commit a6c8adf1e4
15 changed files with 602 additions and 131 deletions

View File

@ -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 });
}

View File

@ -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';

View File

@ -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>

View File

@ -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>

View File

@ -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[]>([]);
// Itemkey
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[]>([]);
// Itemkey
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>

View File

@ -9,4 +9,5 @@ export default {
'ms.assertion.noValidation': '不校验',
'ms.assertion.matchCondition': '匹配条件',
'ms.assertion.matchValue': '匹配值',
'ms.assertion.variableName': '变量名',
};

View File

@ -0,0 +1,5 @@
export interface MsAssertionItem {
id: string;
label: string;
value: string;
}

View File

@ -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;
}

View File

@ -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,
};
},
{

View File

@ -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

View File

@ -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>

View File

@ -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',

View File

@ -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>

View File

@ -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;
}
}
}

View File

@ -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': '数据库',