feat(系统设置): 服务集成本地联调和插件&服务集成联动调试

This commit is contained in:
xinxin.wu 2023-08-18 10:42:23 +08:00 committed by 刘瑞斌
parent 6bd1f650b0
commit cab07f4836
11 changed files with 369 additions and 264 deletions

View File

@ -30,8 +30,8 @@ export function getValidate(id: string) {
return MSR.get({ url: GetValidateServiceUrl, params: id }); return MSR.get({ url: GetValidateServiceUrl, params: id });
} }
// 内部校验测试连接 注:不同的平台对应的不同的字段 // 内部校验测试连接 注:不同的平台对应的不同的字段
export function postValidate(data: any) { export function postValidate(data: any, pluginId: string) {
return MSR.post({ url: PostValidateServiceUrl, data }); return MSR.post({ url: PostValidateServiceUrl, data, params: pluginId });
} }
// 前端配置脚本 // 前端配置脚本
export function configScript(pluginId: string) { export function configScript(pluginId: string) {

View File

@ -3,6 +3,6 @@ export const AddServiceUrl = '/service/integration/add';
export const UpdateServiceUrl = '/service/integration/update'; export const UpdateServiceUrl = '/service/integration/update';
export const ResetServiceUrl = '/service/integration/delete'; export const ResetServiceUrl = '/service/integration/delete';
export const GetValidateServiceUrl = '/service/integration/validate'; export const GetValidateServiceUrl = '/service/integration/validate';
export const PostValidateServiceUrl = '/service/integration/validate'; export const PostValidateServiceUrl = '/service/integration/validate/';
export const ConfigServiceScriptUrl = '/service/integration/script/'; export const ConfigServiceScriptUrl = '/service/integration/script/';
export const getLogoUrl = '/plugin/image'; export const getLogoUrl = '/plugin/image';

View File

@ -1,4 +1,4 @@
export interface ServiceItem { export type ServiceItem = Partial<{
id?: string; id?: string;
pluginId: string; // 插件id pluginId: string; // 插件id
title: string; title: string;
@ -8,16 +8,31 @@ export interface ServiceItem {
logo: string; logo: string;
organizationId: string; // 组织id organizationId: string; // 组织id
configuration?: null; // 配置项 configuration?: null; // 配置项
} }>;
export type ServiceList = ServiceItem[]; export type ServiceList = ServiceItem[];
// 创建和更新服务 // 创建和更新服务
export interface AddOrUpdateServiceModel { export type AddOrUpdateServiceModel = Partial<{
id?: string; id?: string;
pluginId: string; pluginId: string;
enable: boolean; enable: boolean | undefined;
organizationId: string; organizationId: string;
configuration: any; configuration?: any;
}>;
export interface SkipTitle {
name: string;
src: string;
active: boolean; // 是否激活
}
export interface StepListType {
id: string;
icon: string;
title: string;
skipTitle: SkipTitle[];
step: string;
description: string;
} }

View File

@ -6,14 +6,14 @@
:ok-text="t('organization.member.Confirm')" :ok-text="t('organization.member.Confirm')"
:cancel-text="t('organization.member.Cancel')" :cancel-text="t('organization.member.Cancel')"
> >
<template #title> 标题 </template> <template #title> {{ title }} </template>
<div> <div>
<MsFormCreate v-model:api="fApi" :rule="formRules" :option="options" /> <MsFormCreate v-model:api="fApi" :rule="formRules" :option="options" />
</div> </div>
<template #footer> <template #footer>
<div class="flex justify-between"> <div class="flex justify-between">
<div class="flex flex-row items-center justify-center"> <div class="flex flex-row items-center justify-center">
<a-switch size="small" /> <a-switch v-model="isEnable" :disabled="isDisabled" size="small" />
<a-tooltip> <a-tooltip>
<template #content> <template #content>
<div class="text-sm">{{ t('organization.service.statusEnableTip') }}</div> <div class="text-sm">{{ t('organization.service.statusEnableTip') }}</div>
@ -24,8 +24,12 @@
</div> </div>
<a-space> <a-space>
<a-button type="secondary" @click="handleCancel">{{ t('organization.service.Cancel') }}</a-button> <a-button type="secondary" @click="handleCancel">{{ t('organization.service.Cancel') }}</a-button>
<a-button type="outline">{{ t('organization.service.testLink') }}</a-button> <a-button type="outline" :loading="testLoading" @click="testLink">{{
<a-button type="primary" @click="saveHandler">{{ t('organization.service.Confirm') }}</a-button> t('organization.service.testLink')
}}</a-button>
<a-button type="primary" :loading="loading" @click="saveHandler">{{
t('organization.service.Confirm')
}}</a-button>
</a-space> </a-space>
</div> </div>
</template> </template>
@ -36,50 +40,28 @@
import { ref, watchEffect, watch } from 'vue'; import { ref, watchEffect, watch } from 'vue';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import MsFormCreate from '@/components/pure/ms-form-create/formCreate.vue'; import MsFormCreate from '@/components/pure/ms-form-create/formCreate.vue';
import { configScript, addOrUpdate, postValidate } from '@/api/modules/setting/serviceIntegration';
import { Message } from '@arco-design/web-vue';
import type { ServiceItem, AddOrUpdateServiceModel } from '@/models/setting/serviceIntegration';
import useLoading from '@/hooks/useLoading';
import { useUserStore } from '@/store';
const { t } = useI18n(); const { t } = useI18n();
const userStore = useUserStore();
const lastOrganizationId = userStore.$state?.lastOrganizationId as string;
const emits = defineEmits<{ const emits = defineEmits<{
(event: 'update:visible', visible: boolean): void; (event: 'update:visible', visible: boolean): void;
(event: 'success'): void;
}>(); }>();
const props = defineProps<{ const props = defineProps<{
visible: boolean; visible: boolean;
rule: any;
}>(); }>();
const detailVisible = ref<boolean>(false); const detailVisible = ref<boolean>(false);
const { loading, setLoading } = useLoading(false);
const fApi = ref<any>({}); const fApi = ref<any>({});
const formRules = ref([]);
watchEffect(() => {
detailVisible.value = props.visible;
formRules.value = props.rule;
});
watch(
() => detailVisible.value,
(val) => {
emits('update:visible', val);
}
);
const handleCancel = () => {
detailVisible.value = false;
};
const submit = () => {
fApi.value?.submit((formData: FormData) => {
console.log(formData, 'formData');
});
};
const saveHandler = () => {
fApi.value?.validate((valid: any, fail: any) => {
if (valid) {
submit();
} else {
console.log(fail);
}
});
};
const options = ref({ const options = ref({
resetBtn: false, resetBtn: false,
submitBtn: false, submitBtn: false,
@ -93,8 +75,118 @@
}, },
wrap: { wrap: {
'asterisk-position': 'end', 'asterisk-position': 'end',
'validate-trigger': ['change'],
}, },
}); });
const formRules = ref<any>([]);
const title = ref<string>('');
const formItem = ref<any>({});
watchEffect(() => {
detailVisible.value = props.visible;
});
watch(
() => detailVisible.value,
(val) => {
emits('update:visible', val);
}
);
const testLoading = ref<boolean>(false);
//
const isDisabled = ref<boolean>(false);
//
const isConfigOrigin = ref<boolean | undefined>(false);
//
const isEnable = ref<boolean | undefined>(false);
// id
const pluginId = ref<string>('');
const type = ref<string>('');
const handleCancel = () => {
fApi.value.clearValidateState();
detailVisible.value = false;
};
const submit = () => {
fApi.value?.submit(async (formData: FormData) => {
setLoading(true);
try {
const params: AddOrUpdateServiceModel = {
id: type.value === 'edit' ? formItem.value.id : undefined,
pluginId: pluginId.value,
enable: isEnable.value,
organizationId: lastOrganizationId,
configuration: { ...formData },
};
const message =
type.value === 'edit' ? t('organization.service.updateSuccess') : t('organization.service.configSuccess');
await addOrUpdate(params, type.value);
Message.success(message);
handleCancel();
emits('success');
} catch (error) {
console.log(error);
} finally {
setLoading(false);
}
});
};
const saveHandler = () => {
fApi.value?.validate((valid: any, fail: any) => {
if (valid) {
submit();
} else {
console.log(fail);
}
});
};
//
const getPluginScript = async (cuurentPluginId: string) => {
try {
const result = await configScript(cuurentPluginId);
formRules.value = [...result];
if (type.value === 'edit') {
fApi.value.nextTick(() => {
fApi.value.setValue({ ...formItem.value.configuration });
fApi.value.refresh();
});
}
} catch (error) {
console.log(error);
}
};
//
const testLink = async () => {
testLoading.value = true;
try {
const formValue = {
...fApi.value.formData(),
};
await postValidate(formValue, pluginId.value);
if (!isConfigOrigin.value) isDisabled.value = false;
Message.success(t('organization.service.successMessage'));
} catch (error) {
console.log(error);
} finally {
testLoading.value = false;
}
};
// &
const addOrEdit = (serviceItem: ServiceItem) => {
type.value = serviceItem.config ? 'edit' : 'create';
isConfigOrigin.value = serviceItem.config;
isEnable.value = serviceItem.enable;
isDisabled.value = !serviceItem.config;
pluginId.value = serviceItem.pluginId as string;
formItem.value = { ...serviceItem };
getPluginScript(pluginId.value);
};
defineExpose({
addOrEdit,
title,
});
</script> </script>
<style scoped></style> <style scoped></style>

View File

@ -1,14 +1,15 @@
<template> <template>
<MsCard simple class="mb-[16px]" auto-height> <MsCard simple class="mb-[16px]" auto-height :loading="loading">
<div class="outer-wrapper"> <div class="outer-wrapper">
<div class="mb-[16px] flex justify-between"> <div class="mb-[16px] flex justify-between">
<div class="font-medium text-[var(--color-text-000)]">{{ t('organization.service.serviceIntegration') }}</div> <div class="font-medium text-[var(--color-text-000)]">{{ t('organization.service.integrationList') }}</div>
<div> <div>
<a-input-search <a-input-search
v-model="keyword" v-model="keyword"
:placeholder="t('organization.member.searchMember')" :placeholder="t('organization.service.searchService')"
class="w-[230px]"
:max-length="250" :max-length="250"
@search="searchHandler"
@press-enter="searchHandler"
/> />
</div> </div>
</div> </div>
@ -16,17 +17,19 @@
<a-scrollbar <a-scrollbar
:style="{ :style="{
overflow: 'auto', overflow: 'auto',
height: `calc(100vh - ${collapseHeight} - 220px)`, height: `calc(100vh - ${collapseHeight} - 230px)`,
}" }"
> >
<div class="list"> <div class="list">
<div v-for="item of data" :key="item.id" class="item"> <div v-for="item of filterList" :key="item.id" class="item">
<div class="flex"> <div class="flex">
<span class="icon float-left mr-2 h-[40px] w-[40px]">{{ item.name }}</span> <span class="icon float-left mr-2 h-[40px] w-[40px] rounded">
<img class="rounded" :src="`http://172.16.200.18:8081${item.logo}`" alt="log" />
</span>
<div class="flex flex-col justify-start"> <div class="flex flex-col justify-start">
<p> <p>
<span class="mr-4 font-semibold">TAPD</span> <span class="mr-4 font-semibold">{{ item.title }}</span>
<span v-if="!item.isConfig" class="ms-enable">{{ t('organization.service.unconfigured') }}</span> <span v-if="!item.config" class="ms-enable">{{ t('organization.service.unconfigured') }}</span>
<span <span
v-else v-else
class="ms-enable active" class="ms-enable active"
@ -37,30 +40,37 @@
>{{ t('organization.service.configured') }}</span >{{ t('organization.service.configured') }}</span
> >
</p> </p>
<p class="mt-2 text-sm text-[var(--color-text-4)]">一站式敏捷研发协作云平台</p> <p class="mt-2 text-sm text-[var(--color-text-4)]">{{ item.description }}</p>
</div> </div>
</div> </div>
<div class="flex justify-between"> <div class="flex justify-between">
<a-space> <a-space>
<a-tooltip v-if="!item.isConfig" :content="t('organization.service.unconfiguredTip')" position="tl"> <a-tooltip v-if="!item.config" :content="t('organization.service.unconfiguredTip')" position="tl">
<span> <span>
<a-button <a-button
v-if="!item.config"
type="outline" type="outline"
class="arco-btn-outline--secondary" class="arco-btn-outline--secondary"
size="mini" size="mini"
:disabled="!item.isConfig" :disabled="!item.config"
@click="getValidateHandler(item)"
>{{ t('organization.service.testLink') }}</a-button >{{ t('organization.service.testLink') }}</a-button
></span ></span
> >
</a-tooltip> </a-tooltip>
<a-button v-else type="outline" class="arco-btn-outline--secondary" size="mini">{{ <a-button
t('organization.service.testLink') v-else
}}</a-button> type="outline"
class="arco-btn-outline--secondary"
size="mini"
@click="getValidateHandler(item)"
>{{ t('organization.service.testLink') }}</a-button
>
<a-button type="outline" class="arco-btn-outline--secondary" size="mini" @click="editHandler(item)">{{ <a-button type="outline" class="arco-btn-outline--secondary" size="mini" @click="editHandler(item)">{{
t('organization.service.edit') t('organization.service.edit')
}}</a-button> }}</a-button>
<a-button <a-button
v-if="item.isConfig" v-if="item.config"
type="outline" type="outline"
class="arco-btn-outline--secondary" class="arco-btn-outline--secondary"
size="mini" size="mini"
@ -69,10 +79,16 @@
> >
</a-space> </a-space>
<span> <span>
<a-tooltip v-if="!item.isConfig" :content="t('organization.service.unconfiguredTip')" position="br"> <a-tooltip v-if="!item.config" :content="t('organization.service.unconfiguredTip')" position="br">
<span><a-switch size="small" :disabled="true" /></span> <span
><a-switch
v-model="item.enable"
size="small"
:disabled="true"
@change="(v) => changeStatus(v, item.id)"
/></span>
</a-tooltip> </a-tooltip>
<a-switch v-else size="small" /> <a-switch v-else v-model="item.enable" size="small" @change="(v) => changeStatus(v, item.id)" />
</span> </span>
</div> </div>
</div> </div>
@ -81,163 +97,118 @@
</div> </div>
</div> </div>
</MsCard> </MsCard>
<ConfigModal v-model:visible="serviceVisible" :rule="createRules" /> <ConfigModal ref="ConfigRef" v-model:visible="serviceVisible" @success="loadList()" />
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted, reactive } from 'vue'; import { ref, onBeforeMount } from 'vue';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import MsCard from '@/components/pure/ms-card/index.vue'; import MsCard from '@/components/pure/ms-card/index.vue';
import { getServiceList, getValidate, resetService, addOrUpdate } from '@/api/modules/setting/serviceIntegration';
import ConfigModal from './conifgModal.vue'; import ConfigModal from './conifgModal.vue';
import { useUserStore } from '@/store';
import useModal from '@/hooks/useModal';
import Message from '@arco-design/web-vue/es/message';
import { characterLimit } from '@/utils';
import type { ServiceList, ServiceItem } from '@/models/setting/serviceIntegration';
const { t } = useI18n(); const { t } = useI18n();
const { openModal } = useModal();
const props = defineProps<{ const userStore = useUserStore();
const lastOrganizationId = userStore.$state?.lastOrganizationId as string;
defineProps<{
collapseHeight: string; collapseHeight: string;
}>(); }>();
const keyword = ref(''); const keyword = ref('');
const data = ref([ const filterList = ref<ServiceList>([]);
{ const data = ref<ServiceList>([]);
id: '1-1-1-1', const loading = ref<boolean>(false);
name: 'xxx',
enable: true, //
isConfig: false, const loadList = async () => {
}, loading.value = true;
{ try {
id: '2-2-2-2', const result = await getServiceList(lastOrganizationId);
name: 'xxx', data.value = result;
enable: false, filterList.value = result;
isConfig: true, } catch (error) {
}, console.log(error);
{ } finally {
id: '3-3-3-3', loading.value = false;
name: 'xxx', }
enable: false, };
},
{ const searchHandler = () => {
id: 'xxxx', filterList.value = data.value.filter((item) => item.title?.includes(keyword.value));
name: 'xxx', };
enable: false,
},
{
id: 'xxxx',
name: 'xxx',
enable: false,
},
{
id: '3-3-3-3',
name: 'xxx',
enable: false,
},
{
id: '3-3-3-3',
name: 'xxx',
enable: false,
},
{
id: 'xxxx',
name: 'xxx',
enable: false,
},
{
id: 'xxxx',
name: 'xxx',
enable: false,
},
{
id: '3-3-3-3',
name: 'xxx',
enable: false,
},
{
id: '3-3-3-3',
name: 'xxx',
enable: false,
},
]);
const serviceVisible = ref<boolean>(false); const serviceVisible = ref<boolean>(false);
let createRules = reactive([]); const ConfigRef = ref();
const editHandler = (item: any) => { //
const editHandler = (serviceItem: ServiceItem) => {
serviceVisible.value = true; serviceVisible.value = true;
ConfigRef.value.addOrEdit(serviceItem);
ConfigRef.value.title = serviceItem.title;
};
//
const resetHandler = async (serviceItem: ServiceItem) => {
openModal({
type: 'error',
title: t('organization.service.resetServiceTip', { name: characterLimit(serviceItem.title) }),
content: t('organization.service.resetServiceContentTip'),
okText: t('organization.service.confirmReset'),
cancelText: t('organization.service.Cancel'),
okButtonProps: {
status: 'normal',
},
onBeforeOk: async () => {
try {
await resetService(serviceItem.id as string);
Message.success(t('organization.service.resetConfigTip'));
loadList();
} catch (error) {
console.log(error);
}
},
hideCancel: false,
});
}; };
const resetHandler = (item: any) => {};
onMounted(() => { //
setTimeout(() => { const getValidateHandler = async (serviceItem: ServiceItem) => {
const result = JSON.stringify([ loading.value = true;
{ try {
type: 'input', await getValidate(serviceItem.id as string);
field: 'address', Message.success(t('organization.service.testLinkStatusTip'));
title: 'JIRA地址', loadList();
value: 'JIRA地址', } catch (error) {
validate: [{ type: 'string', required: true, message: 'JIRA地址不能为空' }], console.log(error);
}, } finally {
{ loading.value = false;
type: 'radio', }
title: '认证方式', };
field: 'method',
value: '1', //
options: [ const changeStatus = async (value: string | number | boolean, id: string | undefined) => {
{ loading.value = true;
value: '1', const message = value ? 'organization.service.enableSuccess' : 'organization.service.closeSuccess';
label: 'Auth', try {
}, await addOrUpdate({ enable: value as boolean, id }, 'edit');
{ Message.success(t(message));
value: '2', loadList();
label: 'Token', } catch (error) {
}, console.log(error);
], } finally {
props: { loading.value = false;
type: 'button', }
}, };
wrap: {
tooltip: 'info提示', onBeforeMount(() => {
}, loadList();
control: [
{
value: '1',
rule: [
{
type: 'input',
field: 'info',
title: 'JIRA-账号',
value: '账号',
validate: [{ type: 'string', required: true, message: 'JIRA账号不能为空' }],
wrap: {},
},
{
type: 'PassWord',
value: '',
field: 'password',
title: 'JIRA密码',
validate: [{ type: 'string', required: true, message: 'JIRA密码不能为空' }],
props: {
placeholder: '请输入密码',
},
},
],
},
{
value: '2',
rule: [
{
type: 'input',
field: 'token',
title: 'Token',
value: 'token 账号',
validate: [{ type: 'string', required: true, message: 'JIRA账号不能为空' }],
wrap: {},
},
],
},
],
},
]);
createRules = JSON.parse(result);
}, 1000);
}); });
</script> </script>
@ -245,7 +216,6 @@
.ms-card-wrap { .ms-card-wrap {
overflow: hidden; overflow: hidden;
padding: 8px; padding: 8px;
min-width: 1150px;
height: calc(100% - 58px) !important; height: calc(100% - 58px) !important;
min-height: 300px; min-height: 300px;
border-radius: var(--border-radius-small); border-radius: var(--border-radius-small);
@ -262,7 +232,6 @@
height: 144px; height: 144px;
border-radius: 4px; border-radius: 4px;
background: white; background: white;
flex-basis: calc(25% - 16px);
@apply flex flex-col justify-between; @apply flex flex-col justify-between;
.icon { .icon {
border: 1px solid var(--color-text-n9); border: 1px solid var(--color-text-n9);
@ -273,27 +242,20 @@
@apply px-2 py-1 text-xs; @apply px-2 py-1 text-xs;
} }
} }
@media screen and (max-width: 992px) { }
.item { @media screen and (min-width: 800px) {
flex-basis: calc(50% - 16px); .item {
} flex-basis: calc(50% - 16px);
} }
@media screen and (min-width: 1000px) and (max-width: 1440px) { }
.item { @media screen and (min-width: 1160px) {
flex-basis: calc(33.3% - 16px); .item {
} flex-basis: calc(33.3% - 16px);
} }
}
@media screen and (max-width: 1600px) and (min-width: 1800px) { @media screen and (min-width: 1800px) {
.item { .item {
flex-basis: calc(25% - 16px); flex-basis: calc(25% - 16px);
}
}
@media screen and (min-width: 1800px) {
.item {
flex-basis: calc(25% - 16px);
}
} }
} }
} }

View File

@ -4,17 +4,26 @@
<a-collapse :bordered="false" expand-icon-position="right" @change="changeHandler"> <a-collapse :bordered="false" expand-icon-position="right" @change="changeHandler">
<a-collapse-item key="1" class="font-medium" :header="t('organization.service.headerTip')"> <a-collapse-item key="1" class="font-medium" :header="t('organization.service.headerTip')">
<template #expand-icon="{ active }"> <template #expand-icon="{ active }">
<span v-if="active" class="text-[rgb(var(--primary-6))]">{{ t('organization.service.packUp') }}</span> <span v-if="active" class="float-right -mr-4 text-[rgb(var(--primary-6))]">{{
<span v-else class="text-[rgb(var(--primary-6))]">{{ t('organization.service.expand') }}</span> t('organization.service.packUp')
}}</span>
<span v-else class="float-right -mr-4 text-[rgb(var(--primary-6))]">{{
t('organization.service.expand')
}}</span>
</template> </template>
<div class="flex w-[100%] flex-row justify-between text-sm font-normal"> <div class="flex w-[100%] flex-row justify-between text-sm font-normal">
<div v-for="(item, index) in cardContent" :key="item.id" class="item" :class="`ms-item-${index}`"> <div
<span> v-for="(item, index) in cardContent"
:key="item.id"
class="item mt-4 p-[16px]"
:class="`ms-item-${index}`"
>
<span class="mr-3">
<svg-icon width="64px" height="46px" :name="item.icon" /> <svg-icon width="64px" height="46px" :name="item.icon" />
</span> </span>
<div class="flex h-[100%] flex-1 flex-col justify-between p-4"> <div class="flex h-[100%] flex-1 flex-col justify-between">
<div class="flex justify-between"> <div class="flex items-center justify-between">
<span class="leading-6">{{ t(item.title) }}</span> <span class="font-normal">{{ t(item.title) }}</span>
<span> <span>
<a-button <a-button
v-for="links of item.skipTitle" v-for="links of item.skipTitle"
@ -24,13 +33,14 @@
type="text" type="text"
:href="links.src" :href="links.src"
target="_blank" target="_blank"
@click="jumpHandler(links)"
> >
{{ t(links.name) }} {{ t(links.name) }}
</a-button> </a-button>
</span> </span>
</div> </div>
<div class="text-xs text-[var(--color-text-4)]"> <div class="text-xs text-[var(--color-text-4)]">
{{ t(item.description) }} <div class="one-line-text w-[400px]"> {{ t(item.description) }}</div>
</div> </div>
</div> </div>
</div> </div>
@ -47,9 +57,12 @@
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import MsCard from '@/components/pure/ms-card/index.vue'; import MsCard from '@/components/pure/ms-card/index.vue';
import ServiceList from './components/serviceList.vue'; import ServiceList from './components/serviceList.vue';
import { useRouter } from 'vue-router';
import type { StepListType, SkipTitle } from '@/models/setting/serviceIntegration';
const { t } = useI18n(); const { t } = useI18n();
const cardContent = ref([ const router = useRouter();
const cardContent = ref<StepListType[]>([
{ {
id: '1001', id: '1001',
icon: 'configplugin', icon: 'configplugin',
@ -57,11 +70,13 @@
skipTitle: [ skipTitle: [
{ {
name: 'organization.service.developmentDoc', name: 'organization.service.developmentDoc',
src: '', src: 'https://github.com/metersphere/metersphere-platform-plugin/wiki/%E6%8F%92%E4%BB%B6%E5%BC%80%E5%8F%91%E6%8C%87%E5%8D%97',
active: false,
}, },
{ {
name: 'organization.service.downPlugin', name: 'organization.service.downPlugin',
src: 'https://github.com/metersphere/metersphere-platform-plugin', src: 'https://github.com/metersphere/metersphere-platform-plugin',
active: false,
}, },
], ],
step: '@/assets/images/ms_plugindownload.jpg', step: '@/assets/images/ms_plugindownload.jpg',
@ -75,6 +90,7 @@
{ {
name: 'organization.service.jumpPlugin', name: 'organization.service.jumpPlugin',
src: '', src: '',
active: true,
}, },
], ],
step: '@/assets/images/ms_configplugin.jpg', step: '@/assets/images/ms_configplugin.jpg',
@ -88,6 +104,13 @@
isCollapse.value = activeKey.length > 0; isCollapse.value = activeKey.length > 0;
collapseHeight.value = activeKey.length > 0 ? '158px' : '72px'; collapseHeight.value = activeKey.length > 0 ? '158px' : '72px';
}; };
const jumpHandler = (links: SkipTitle) => {
if (links.active)
router.push({
name: 'settingSystemPluginManger',
});
};
</script> </script>
<style scoped lang="less"> <style scoped lang="less">
@ -99,9 +122,10 @@
} }
.item { .item {
width: calc(50% - 10px); width: calc(50% - 10px);
height: 78px;
border: 1px solid #ffffff; border: 1px solid #ffffff;
box-shadow: 0 0 7px rgb(120 56 135 / 10%); box-shadow: 0 0 7px rgb(120 56 135 / 10%);
@apply flex h-20 items-center rounded-md; @apply flex items-center rounded-md;
} }
.ms-item-0 { .ms-item-0 {
background: url('@/assets/images/ms_plugindownload.jpg') no-repeat center / cover; background: url('@/assets/images/ms_plugindownload.jpg') no-repeat center / cover;

View File

@ -8,15 +8,15 @@ export default {
'organization.service.integrationList': 'Integration of List', 'organization.service.integrationList': 'Integration of List',
'organization.service.packUp': 'Pack Up', 'organization.service.packUp': 'Pack Up',
'organization.service.expand': 'Expand', 'organization.service.expand': 'Expand',
'organization.service.downloadPluginOrDev': 'Download plug-ins or develop plug-ins', 'organization.service.downloadPluginOrDev': 'Download & develop',
'organization.service.configPlugin': 'Configuring the plugin', 'organization.service.configPlugin': 'Configuring the plugin',
'organization.service.downPlugin': 'Download the plugin', 'organization.service.downPlugin': 'Download',
'organization.service.developmentDoc': 'Plug-in development documentation', 'organization.service.developmentDoc': 'development documentation',
'organization.service.description': 'organization.service.description':
'Download third-party project management platform plug-ins that need to be integrated; you can also develop your own relevant project management platform plug-ins', 'Download third-party project management platform plug-ins that need to be integrated; you can also develop your own relevant project management platform plug-ins',
'organization.service.configDescription': 'organization.service.configDescription':
'Downloaded or developed plug-ins need to be uploaded to plug-in management, upload, you can configure the current page', 'Downloaded or developed plug-ins need to be uploaded to plug-in management, upload, you can configure the current page',
'organization.service.jumpPlugin': 'JUMP to plug-in management', 'organization.service.jumpPlugin': 'Jump to plugin',
'organization.service.testLink': 'Test link', 'organization.service.testLink': 'Test link',
'organization.service.testLinkStatusTip': 'Test connection successful!', 'organization.service.testLinkStatusTip': 'Test connection successful!',
'organization.service.unconfigured': 'Unconfigured', 'organization.service.unconfigured': 'Unconfigured',
@ -35,21 +35,19 @@ export default {
'organization.service.tableColunmUsergroup': 'UserGroup', 'organization.service.tableColunmUsergroup': 'UserGroup',
'organization.service.tableColunmStatus': 'Status', 'organization.service.tableColunmStatus': 'Status',
'organization.service.tableColunmActions': 'Actions', 'organization.service.tableColunmActions': 'Actions',
'organization.service.service': 'Member',
'organization.service.selectMemberScope': 'Select the service you want to add. Multiple selection is supported',
'organization.service.selectProjectScope': 'Select the project you want to add. Multiple selection is supported',
'organization.service.selectMemberEmptyTip': 'The service can not be empty',
'organization.service.selectProjectEmptyTip': 'The project can not be empty',
'organization.service.selectUserEmptyTip': 'The user group can not be empty',
'organization.service.Confirm': 'Confirm', 'organization.service.Confirm': 'Confirm',
'organization.service.Cancel': 'Cancel', 'organization.service.Cancel': 'Cancel',
'organization.service.deleteMemberTip': 'Are you sure to remove the user `{name}` ?', 'system.user.deleteUserTip': 'Are you sure to delete the user {name} ?',
'system.user.deleteUserTip': 'Are you sure to delete the user `{name}` ?',
'organization.service.deleteMemberConfirm': 'Delete', 'organization.service.deleteMemberConfirm': 'Delete',
'organization.service.deleteMemberCancel': 'Cancel',
'organization.service.deleteMemberSuccess': 'Delete successful',
'organization.service.batchModalSuccess': 'Successfully added',
'organization.service.batchUpdateSuccess': 'Successfully updated',
'organization.service.project': 'Project', 'organization.service.project': 'Project',
'organization.service.selectUserScope': 'Please select a user group for the above members', 'organization.service.confirmReset': 'Reset',
'organization.service.resetServiceTip': 'Confirm reset {name} this service integration?',
'organization.service.resetServiceContentTip':
'After the Reset, the integration information will be cleared, the project can not integrate with the platform and the platform default template is not available, be careful!',
'organization.service.searchService': 'Search by plug-in name',
'organization.service.successMessage': 'The test connection was successful',
'organization.service.enableSuccess': 'Enable successfully',
'organization.service.closeSuccess': 'Disable successfully',
'organization.service.configSuccess': 'Configuration successfully',
'organization.service.updateSuccess': 'Update successfully',
}; };

View File

@ -31,20 +31,18 @@ export default {
'organization.service.tableColunmUsergroup': '用户组', 'organization.service.tableColunmUsergroup': '用户组',
'organization.service.tableColunmStatus': '状态', 'organization.service.tableColunmStatus': '状态',
'organization.service.tableColunmActions': '操作', 'organization.service.tableColunmActions': '操作',
'organization.service.service': '成员',
'organization.service.selectMemberScope': '请选择需要添加的成员支持多选',
'organization.service.selectProjectScope': '请选择需要添加的项目支持多选',
'organization.service.selectMemberEmptyTip': '成员不能为空',
'organization.service.selectProjectEmptyTip': '项目不能为空',
'organization.service.selectUserEmptyTip': '用户组不能为空',
'organization.service.Confirm': '确定', 'organization.service.Confirm': '确定',
'organization.service.Cancel': '取消', 'organization.service.Cancel': '取消',
'organization.service.deleteMemberTip': '确认移除 `{name}` 这个成员吗?',
'organization.service.deleteMemberConfirm': '确认删除', 'organization.service.deleteMemberConfirm': '确认删除',
'organization.service.deleteMemberCancel': '取消',
'organization.service.deleteMemberSuccess': '删除成功',
'organization.service.batchModalSuccess': '添加成功',
'organization.service.batchUpdateSuccess': '更新成功',
'organization.service.project': '项目', 'organization.service.project': '项目',
'organization.service.selectUserScope': '请为以上成员选择用户组', 'organization.service.confirmReset': '确认重置',
'organization.service.resetServiceTip': '确认重置 {name} 这个服务集成吗?',
'organization.service.resetServiceContentTip':
'重置后,集成信息将被清空,项目无法与该平台集成且该平台默认模版不可用,谨慎操作!',
'organization.service.searchService': '通过插件名称搜索',
'organization.service.successMessage': '测试连接成功',
'organization.service.enableSuccess': '启用成功',
'organization.service.closeSuccess': '禁用成功',
'organization.service.configSuccess': '配置成功',
'organization.service.updateSuccess': '更新成功',
}; };

View File

@ -32,12 +32,22 @@
@expand="handleExpand" @expand="handleExpand"
> >
<template #columns> <template #columns>
<a-table-column :width="300" fixed="left" :title="t('system.plugin.tableColumnsName')" :ellipsis="true"> <a-table-column
:width="300"
fixed="left"
:title="t('system.plugin.tableColumnsName')"
:ellipsis="true"
:tooltip="true"
>
<template #cell="{ record }"> <template #cell="{ record }">
{{ record.name }} <span class="text-[--color-text-4]">({{ (record.pluginForms || []).length }})</span> {{ record.name }} <span class="text-[--color-text-4]">({{ (record.pluginForms || []).length }})</span>
</template> </template>
</a-table-column> </a-table-column>
<a-table-column :title="t('system.plugin.tableColumnsDescription')" data-index="description" /> <a-table-column
:title="t('system.plugin.tableColumnsDescription')"
data-index="description"
:ellipsis="true"
/>
<a-table-column :title="t('system.plugin.tableColumnsStatus')"> <a-table-column :title="t('system.plugin.tableColumnsStatus')">
<template #cell="{ record }"> <template #cell="{ record }">
<div v-if="record.enable" class="flex items-center"> <div v-if="record.enable" class="flex items-center">

View File

@ -63,6 +63,8 @@
<MsUpload <MsUpload
v-model:file-list="fileList" v-model:file-list="fileList"
accept="jar" accept="jar"
:max-size="50"
size-unit="MB"
main-text="system.user.importModalDragtext" main-text="system.user.importModalDragtext"
:sub-text="t('system.plugin.supportFormat')" :sub-text="t('system.plugin.supportFormat')"
:show-file-list="false" :show-file-list="false"

View File

@ -22,7 +22,9 @@
<div> <div>
<a-space> <a-space>
<a-button type="primary" @click="continueAdd">{{ t('system.plugin.continueUpload') }}</a-button> <a-button type="primary" @click="continueAdd">{{ t('system.plugin.continueUpload') }}</a-button>
<a-button type="outline">{{ t('system.plugin.ServiceIntegration') }}</a-button> <a-button type="outline" @click="router.push({ name: 'settingOrganizationService' })">{{
t('system.plugin.ServiceIntegration')
}}</a-button>
<a-button type="secondary">{{ t('system.plugin.backPluginList') }}</a-button> <a-button type="secondary">{{ t('system.plugin.backPluginList') }}</a-button>
</a-space> </a-space>
</div> </div>
@ -38,8 +40,10 @@
import { useDialog } from '@/hooks/useDialog'; import { useDialog } from '@/hooks/useDialog';
import useVisit from '@/hooks/useVisit'; import useVisit from '@/hooks/useVisit';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import { useRouter } from 'vue-router';
const { t } = useI18n(); const { t } = useI18n();
const router = useRouter();
const visitedKey = 'doNotShowAgain'; const visitedKey = 'doNotShowAgain';
const { addVisited } = useVisit(visitedKey); const { addVisited } = useVisit(visitedKey);
const props = defineProps<{ const props = defineProps<{