feat: 系统_插件管理_页面调整_功能联调_完善

This commit is contained in:
xinxin.wu 2023-07-21 18:44:18 +08:00 committed by fit2-zhao
parent 64274427a9
commit 806556f711
20 changed files with 742 additions and 852 deletions

1
frontend/.gitignore vendored
View File

@ -5,6 +5,7 @@ dist-ssr
.history
coverage
.node/
.DS_Store
package-lock.json
pnpm-lock.yaml
yarn.lock

View File

@ -1,9 +1,25 @@
import MSR from '@/api/http/index';
import { GetPluginListUrl } from '@/api/requrls/setting/plugin';
import type { PluginList } from '@/models/setting/plugin';
import type { TableQueryParams } from '@/models/common';
import {
GetPluginListUrl,
UploadPluginUrl,
UpdatePluginUrl,
DeletePluginUrl,
GetScriptUrl,
} from '@/api/requrls/setting/plugin';
import type { PluginList, PluginItem, AddReqData, UpdatePluginModel, UploadFile } from '@/models/setting/plugin';
// eslint-disable-next-line import/prefer-default-export
export function getPluginList(data: TableQueryParams) {
return MSR.post<PluginList>({ url: GetPluginListUrl, data });
export function getPluginList() {
return MSR.get<PluginList>({ url: GetPluginListUrl });
}
export function addPlugin(data: UploadFile) {
return MSR.uploadFile<AddReqData>({ url: UploadPluginUrl }, data);
}
export function updatePlugin(data: UpdatePluginModel) {
return MSR.post<PluginItem>({ url: UpdatePluginUrl, data });
}
export function deletePluginReq(id: string) {
return MSR.get<PluginItem>({ url: DeletePluginUrl, params: id });
}
export function getScriptDetail(pluginId: string, scriptId: string) {
return MSR.get({ url: GetScriptUrl, params: `/${pluginId}/${scriptId}` });
}

View File

@ -1,2 +1,5 @@
export const GetPluginListUrl = '/plugin/page';
export const UplodaPluginUrl = '/member/add';
export const GetPluginListUrl = '/plugin/list';
export const UploadPluginUrl = '/plugin/add';
export const UpdatePluginUrl = '/plugin/update';
export const DeletePluginUrl = '/plugin/delete';
export const GetScriptUrl = '/plugin/script/get';

View File

@ -1,74 +0,0 @@
<svg width="64" height="46" viewBox="0 0 64 46" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="64" height="46" rx="4" fill="url(#paint0_linear_1946_243856)"/>
<g opacity="0.3" filter="url(#filter0_f_1946_243856)">
<rect x="20" y="39" width="24" height="2" rx="1" fill="#811FA3"/>
</g>
<g filter="url(#filter1_d_1946_243856)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M19.3335 6.33344C18.2289 6.33344 17.3335 7.22887 17.3335 8.33344V33.6668C17.3335 34.7713 18.2289 35.6668 19.3335 35.6668H44.6668C45.7714 35.6668 46.6668 34.7713 46.6668 33.6668L46.6668 15.6668L37.3335 6.33344H19.3335Z" fill="#811FA3"/>
<g filter="url(#filter2_i_1946_243856)">
<path d="M17.333 23C17.333 21.8954 18.2284 21 19.333 21H44.6663C45.7709 21 46.6663 21.8954 46.6663 23V33.6667C46.6663 34.7712 45.7709 35.6667 44.6663 35.6667H19.333C18.2284 35.6667 17.333 34.7712 17.333 33.6667V23Z" fill="url(#paint1_linear_1946_243856)"/>
</g>
<g filter="url(#filter3_d_1946_243856)">
<rect x="27" y="30" width="10" height="2.66667" rx="1.33333" fill="white"/>
</g>
<g filter="url(#filter4_d_1946_243856)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M20.3665 23.3665C20.8551 22.8778 21.6473 22.8778 22.1359 23.3665L25.8847 27.1153C26.3733 27.6039 26.3733 28.3961 25.8847 28.8847L22.1359 32.6335C21.6473 33.1222 20.8551 33.1222 20.3665 32.6335C19.8778 32.1449 19.8778 31.3527 20.3665 30.8641L21.8164 29.4142C22.5974 28.6332 22.5974 27.3668 21.8164 26.5858L20.3665 25.1359C19.8778 24.6473 19.8778 23.8551 20.3665 23.3665Z" fill="white"/>
</g>
<path fill-rule="evenodd" clip-rule="evenodd" d="M36.6976 10.7622C37.1007 10.3591 37.1007 9.70546 36.6976 9.30234C36.2945 8.89922 35.6409 8.89922 35.2378 9.30234L33.9387 10.6014C33.0853 10.1082 32.1713 10.0297 31.3856 10.1487C30.3931 10.2991 29.5065 10.7755 29.0442 11.2378L29.0442 11.2379L27.8829 12.3991C27.4798 12.8023 27.4798 13.4558 27.8829 13.859L32.141 18.117C32.3346 18.3106 32.5971 18.4194 32.8709 18.4194C33.1447 18.4194 33.4072 18.3106 33.6008 18.117L34.7621 16.9557L34.7621 16.9557C35.2244 16.4934 35.7008 15.6068 35.8512 14.6143C35.9702 13.8286 35.8917 12.9147 35.3986 12.0612L36.6976 10.7622ZM28.1815 16.5686C28.5846 16.1655 28.5846 15.5119 28.1815 15.1088C27.7783 14.7057 27.1248 14.7057 26.7216 15.1088L25.9031 15.9273L25.8588 15.8829C25.6652 15.6894 25.4026 15.5806 25.1289 15.5806C24.8551 15.5806 24.5925 15.6894 24.399 15.8829L23.2376 17.0443C22.7754 17.5066 22.299 18.3931 22.1486 19.3857C22.0725 19.8878 22.0771 20.4423 22.2191 21H30.4111C30.4494 20.6946 30.3513 20.3755 30.1169 20.141L30.0726 20.0968L30.8911 19.2783C31.2943 18.8752 31.2943 18.2216 30.8911 17.8185C30.488 17.4153 29.8344 17.4153 29.4313 17.8185L28.6128 18.637L27.363 17.3871L28.1815 16.5686Z" fill="white"/>
</g>
<defs>
<filter id="filter0_f_1946_243856" x="16" y="35" width="32" height="10" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="2" result="effect1_foregroundBlur_1946_243856"/>
</filter>
<filter id="filter1_d_1946_243856" x="16.333" y="6.33344" width="31.334" height="31.3333" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="1"/>
<feGaussianBlur stdDeviation="0.5"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.505882 0 0 0 0 0.121569 0 0 0 0 0.639216 0 0 0 0.15 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_1946_243856"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_1946_243856" result="shape"/>
</filter>
<filter id="filter2_i_1946_243856" x="17.333" y="21" width="29.3335" height="14.6667" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset/>
<feGaussianBlur stdDeviation="0.5"/>
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
<feColorMatrix type="matrix" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0"/>
<feBlend mode="normal" in2="shape" result="effect1_innerShadow_1946_243856"/>
</filter>
<filter id="filter3_d_1946_243856" x="26" y="30" width="12" height="4.66669" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="1"/>
<feGaussianBlur stdDeviation="0.5"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.505882 0 0 0 0 0.121569 0 0 0 0 0.639216 0 0 0 0.24 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_1946_243856"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_1946_243856" result="shape"/>
</filter>
<filter id="filter4_d_1946_243856" x="19" y="23" width="8.25098" height="12" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="1"/>
<feGaussianBlur stdDeviation="0.5"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.505882 0 0 0 0 0.121569 0 0 0 0 0.639216 0 0 0 0.24 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_1946_243856"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_1946_243856" result="shape"/>
</filter>
<linearGradient id="paint0_linear_1946_243856" x1="62.4941" y1="1.50001" x2="-31.3204" y2="32.2514" gradientUnits="userSpaceOnUse">
<stop stop-color="#F2E9F6"/>
<stop offset="1" stop-color="#F9F4FA" stop-opacity="0.12"/>
</linearGradient>
<linearGradient id="paint1_linear_1946_243856" x1="32.6663" y1="35.6667" x2="32.6663" y2="21" gradientUnits="userSpaceOnUse">
<stop stop-color="white"/>
<stop offset="1" stop-color="white" stop-opacity="0.8"/>
</linearGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 6.3 KiB

View File

@ -1,49 +0,0 @@
<svg width="64" height="46" viewBox="0 0 64 46" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="64" height="46" rx="4" fill="url(#paint0_linear_1946_243871)"/>
<g opacity="0.3" filter="url(#filter0_f_1946_243871)">
<rect x="20" y="39" width="24" height="2" rx="1" fill="#811FA3"/>
</g>
<path fill-rule="evenodd" clip-rule="evenodd" d="M20.6667 7C19.1939 7 18 8.16406 18 9.6V32.4C18 33.836 19.1939 35 20.6667 35H41.3333C42.8061 35 44 33.836 44 32.4V13.5C44 12.064 42.8061 10.9 41.3333 10.9H32.6245L29.691 7.46776C29.4376 7.17137 29.0624 7 28.6667 7H20.6667Z" fill="#811FA3"/>
<path d="M22.6341 15.5368C22.8486 14.6358 23.6536 14 24.5797 14H46.4679C47.7598 14 48.7127 15.2065 48.4135 16.4632L44.3659 33.4632C44.1514 34.3642 43.3464 35 42.4203 35H20.5321C19.2402 35 18.2873 33.7935 18.5865 32.5368L22.6341 15.5368Z" fill="#811FA3"/>
<g filter="url(#filter1_i_1946_243871)">
<path d="M22.6341 15.5368C22.8486 14.6358 23.6536 14 24.5797 14H46.4679C47.7598 14 48.7127 15.2065 48.4135 16.4632L44.3659 33.4632C44.1514 34.3642 43.3464 35 42.4203 35H20.5321C19.2402 35 18.2873 33.7935 18.5865 32.5368L22.6341 15.5368Z" fill="url(#paint1_linear_1946_243871)"/>
</g>
<g filter="url(#filter2_d_1946_243871)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M29.8206 26.6147C29.8206 27.2358 29.7735 27.975 30.2878 28.3234C30.3402 28.3589 30.3934 28.3935 30.4472 28.4272C31.1044 28.8382 31.9182 28.2969 32.6418 28.0186C32.8156 27.9518 33.0044 27.9151 33.2018 27.9151C33.3992 27.9151 33.588 27.9518 33.7619 28.0186C34.4854 28.2969 35.2992 28.8382 35.9565 28.4271C36.0103 28.3935 36.0634 28.3589 36.1158 28.3234C36.6301 27.975 36.583 27.2358 36.583 26.6147V26.6147C36.583 25.7528 37.2817 25.0541 38.1435 25.0541V25.0541C38.2315 25.0541 38.3117 24.9963 38.3268 24.9096C38.3773 24.6186 38.4036 24.3193 38.4036 24.0138C38.4036 23.4777 38.3225 22.9606 38.1719 22.474C38.1681 22.4615 38.1565 22.4531 38.1435 22.4532V22.4532C37.2817 22.4532 36.583 21.7545 36.583 20.8927V20.8927C36.583 20.469 36.5354 19.998 36.1888 19.7545C36.0601 19.664 35.927 19.5793 35.7901 19.5006C35.1335 19.1232 34.3431 19.599 33.6123 19.7978C33.4815 19.8333 33.3439 19.8523 33.2018 19.8523C33.0597 19.8523 32.9221 19.8333 32.7913 19.7978C32.0605 19.599 31.2701 19.1232 30.6135 19.5006C30.4766 19.5793 30.3436 19.664 30.2148 19.7545C29.8682 19.998 29.8206 20.469 29.8206 20.8927V20.8927C29.8206 21.7545 29.122 22.4532 28.2601 22.4532V22.4532C28.2471 22.4531 28.2355 22.4615 28.2317 22.474C28.0811 22.9606 28 23.4777 28 24.0138C28 24.3193 28.0263 24.6186 28.0769 24.9096C28.0919 24.9963 28.1721 25.0541 28.2601 25.0541V25.0541C29.122 25.0541 29.8206 25.7528 29.8206 26.6147V26.6147ZM35.0226 24.0137C35.0226 25.0192 34.2074 25.8344 33.2019 25.8344C32.1964 25.8344 31.3813 25.0192 31.3813 24.0137C31.3813 23.0082 32.1964 22.1931 33.2019 22.1931C34.2074 22.1931 35.0226 23.0082 35.0226 24.0137Z" fill="white"/>
</g>
<defs>
<filter id="filter0_f_1946_243871" x="16" y="35" width="32" height="10" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="2" result="effect1_foregroundBlur_1946_243871"/>
</filter>
<filter id="filter1_i_1946_243871" x="18.5312" y="14" width="29.9375" height="21" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset/>
<feGaussianBlur stdDeviation="0.5"/>
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
<feColorMatrix type="matrix" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0"/>
<feBlend mode="normal" in2="shape" result="effect1_innerShadow_1946_243871"/>
</filter>
<filter id="filter2_d_1946_243871" x="27" y="19.3593" width="12.4038" height="11.2167" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="1"/>
<feGaussianBlur stdDeviation="0.5"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.505882 0 0 0 0 0.121569 0 0 0 0 0.639216 0 0 0 0.24 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_1946_243871"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_1946_243871" result="shape"/>
</filter>
<linearGradient id="paint0_linear_1946_243871" x1="62.4941" y1="1.50001" x2="-31.3204" y2="32.2514" gradientUnits="userSpaceOnUse">
<stop stop-color="#F2E9F6"/>
<stop offset="1" stop-color="#F9F4FA" stop-opacity="0.12"/>
</linearGradient>
<linearGradient id="paint1_linear_1946_243871" x1="34.2045" y1="35" x2="34.2045" y2="14" gradientUnits="userSpaceOnUse">
<stop stop-color="white"/>
<stop offset="1" stop-color="white" stop-opacity="0.8"/>
</linearGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 5.0 KiB

View File

@ -7,6 +7,5 @@ import './system/user';
import './system/project';
import './system/resourcePool';
import './system/member';
import './system/pluginManger';
MOCK.onAny().passThrough();

View File

@ -1,179 +0,0 @@
import { mock } from '@/utils/setup-mock';
import { RequestEnum } from '@/enums/httpEnum';
const getPluginList = () => {
return [
{
id: '1-1',
name: '插件一',
describe: '插件一',
enable: true,
createTime: 'number',
updateTime: 'number',
jarPackage: 'string',
version: 'string',
applicationScene: 'string',
createUser: 'string',
updateUser: 'string',
organizationList: [
{
id: 'string',
num: 0,
name: '组织 1',
description: 'blabla',
createTime: 0,
updateTime: 0,
createUser: 'string',
updateUser: 'string',
deleted: true,
deleteUser: 'string',
deleteTime: 0,
enable: true,
},
{
id: 'string',
num: 0,
name: '组织 2',
description: 'blabla',
createTime: 0,
updateTime: 0,
createUser: 'string',
updateUser: 'string',
deleted: true,
deleteUser: 'string',
deleteTime: 0,
enable: true,
},
{
id: 'string',
num: 0,
name: '组织 3',
description: 'blabla',
createTime: 0,
updateTime: 0,
createUser: 'string',
updateUser: 'string',
deleted: true,
deleteUser: 'string',
deleteTime: 0,
enable: true,
},
{
id: 'string',
num: 0,
name: '组织 4',
description: 'blabla',
createTime: 0,
updateTime: 0,
createUser: 'string',
updateUser: 'string',
deleted: true,
deleteUser: 'string',
deleteTime: 0,
enable: true,
},
],
steps: [
{
id: '1-1-1',
name: '步骤一',
},
{
id: '1-1-2',
name: '步骤二',
},
{
id: '1-1-3',
name: '步骤三',
},
],
},
{
id: '2-1',
name: '插件一',
describe: '插件一',
enable: true,
createTime: 'number',
updateTime: 'number',
jarPackage: 'string',
version: 'string',
applicationScene: 'string',
createUser: 'string',
updateUser: 'string',
organizationList: [
{
id: 'string',
num: 0,
name: '组织 1',
description: 'blabla',
createTime: 0,
updateTime: 0,
createUser: 'string',
updateUser: 'string',
deleted: true,
deleteUser: 'string',
deleteTime: 0,
enable: true,
},
{
id: 'string',
num: 0,
name: '组织 2',
description: 'blabla',
createTime: 0,
updateTime: 0,
createUser: 'string',
updateUser: 'string',
deleted: true,
deleteUser: 'string',
deleteTime: 0,
enable: true,
},
{
id: 'string',
num: 0,
name: '组织 3',
description: 'blabla',
createTime: 0,
updateTime: 0,
createUser: 'string',
updateUser: 'string',
deleted: true,
deleteUser: 'string',
deleteTime: 0,
enable: true,
},
{
id: 'string',
num: 0,
name: '组织 4',
description: 'blabla',
createTime: 0,
updateTime: 0,
createUser: 'string',
updateUser: 'string',
deleted: true,
deleteUser: 'string',
deleteTime: 0,
enable: true,
},
],
steps: [
{
id: '2-1-1',
name: '步骤2-一',
},
{
id: '2-1-2',
name: '步骤2-二',
},
{
id: '2-1-3',
name: '步骤3-三',
},
],
},
];
};
mock(RequestEnum.POST, '/plugin/page', getPluginList(), 200, true);

View File

@ -14,41 +14,82 @@ export interface organizationItem {
}
export type organizationList = organizationItem[];
export interface PluginItem {
// 上传文件
export type UploadFile = {
fileList?: FormData[]; // 上传文件
[key: string]: any; // 表单收集字段字符串
};
export interface PluginForms {
id: string;
name: string;
describe: string;
enable: boolean;
}
export interface OrganizationsItem {
id: string;
num: number;
name: string;
}
export type OrganizationsItemList = OrganizationsItem[];
// 插件管理列表项
export type PluginItem = Partial<{
id: string;
name: string;
pluginId: string;
fileName: string; // 文件名称
createTime: number;
updateTime: number;
jarPackage: string;
version: string;
applicationScene: string;
createUser: string;
updateUser: string;
organizationList: organizationList;
steps?: string[];
}
export type PluginList = PluginItem[];
export interface StepItem {
name: string;
title: string;
status: boolean;
}
export type StepList = StepItem[];
export interface SceneItem {
name: string;
enable: boolean;
global: boolean;
xpack: boolean; // 授权类型
description: string;
isSelected: boolean;
svg: string;
scenario: string; // 应用场景
organizations: OrganizationsItemList; // 组织列表
pluginForms: PluginForms[]; // 插件步骤
expand?: any;
}>;
// 插件管理列表
export type PluginList = PluginItem[];
// 更新插件参数定义
export interface UpdatePluginModel {
id?: string;
name?: string;
global?: boolean | string; // 是否选择全部组织
description?: string;
enable?: boolean;
organizationIds?: Array<string | number>; // 指定组织
[key: string]: any;
}
export type SceneList = SceneItem[];
export type PluginForm = {
pluginName: string;
organize: string | number;
describe: string;
organizeGroup: Array<string | number>;
// 添加返回的接口
export interface AddReqData {
id?: string;
name: string;
pluginId: string; // 插件id
fileName: string;
createTime: number;
updateTime: number;
createUser: string;
enable: boolean;
global: boolean;
xpack: boolean; // 授权类型
description: string;
scenario: string;
}
export type Options = {
title: string;
visible: boolean;
onClose?: () => void;
};
// 抽屉配置
export interface DrawerConfig {
title: string;
pluginId: string; // 插件id
}
export interface DrawerReqParams {
pluginId: string; // 插件id
scriptId: string; // 脚本id
}
export interface PluginState {
doNotShowAgain: boolean;
}

View File

@ -1,67 +0,0 @@
<template>
<div class="mt-6 flex flex-col">
<div
v-for="(item, index) in sceneList"
:key="index"
class="ms-ls-row my-2"
:class="{ 'ms-ls-row--active': item.isSelected }"
@click="selectHandler(item)"
>
<div class="ms-icon-list ml-4 mr-5">
<svg-icon :width="'64px'" :height="'46px'" :name="item.svg" />
</div>
<div class="flex flex-col justify-center">
<div class="mb-1 font-medium">{{ t(item.name) }}</div>
<div class="text-sm">{{ t(item.description) }}</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import type { SceneItem, SceneList } from '@/models/setting/plugin';
import { useI18n } from '@/hooks/useI18n';
const { t } = useI18n();
const props = defineProps<{
setCurrent: (step: number) => void;
}>();
const sceneList = ref<SceneList>([
{
name: 'system.plugin.interfaceTest',
description: 'system.plugin.interfaceTestDescribe',
isSelected: true,
svg: 'apitest',
},
{
name: 'system.plugin.projectManger',
description: 'system.plugin.projectMangerDescribe',
isSelected: false,
svg: 'promanger',
},
]);
const selectHandler = (currentItem: SceneItem) => {
sceneList.value.forEach((item: SceneItem) => {
item.isSelected = false;
});
currentItem.isSelected = true;
props.setCurrent(2);
};
</script>
<style scoped lang="less">
.ms-ls-row {
border-width: 1px;
@apply flex flex-row items-center rounded border-solid py-2;
&--active {
/* stylelint-disable-next-line color-function-notation */
background-color: rgb(var(--primary-1));
}
&:hover {
background-color: rgb(var(--primary-1));
}
}
</style>
@/models/setting/plugin

View File

@ -7,19 +7,57 @@
<a-button class="mr-3" type="primary" @click="uploadPlugin">{{ t('system.plugin.uploadPlugin') }}</a-button>
</a-col>
<a-col :span="5" :offset="9">
<a-select v-model="scene" allow-clear>
<a-select v-model="searchKeys.scene" allow-clear>
<a-option v-for="item of sceneList" :key-="item.value" :value="item.value">{{ item.label }}</a-option>
</a-select>
</a-col>
<a-col :span="5">
<a-input-search :placeholder="t('system.plugin.searchPlugin')" @search="searchPlugin"></a-input-search>
<a-input-search
v-model="searchKeys.name"
:placeholder="t('system.plugin.searchPlugin')"
@search="searchHanlder"
></a-input-search>
</a-col>
</a-row>
</div>
<ms-base-table v-bind="propsRes" v-on="propsEvent">
<template #organization="{ record }">
<a-table
:data="data"
:pagination="false"
:scroll="{ y: 'auto', x: 2400 }"
:expandable="expandable"
:loading="loading"
row-key="id"
:expanded-row-keys="expandedRowKeys"
@expand="handleExpand"
>
<template #columns>
<a-table-column fixed="left" :title="t('system.plugin.tableColunmName')">
<template #cell="{ record }">
{{ record.name }} <span class="text-[--color-text-4]">({{ record.pluginForms.length }})</span>
</template>
</a-table-column>
<a-table-column :title="t('system.plugin.tableColunmDescription')" data-index="description" />
<a-table-column :title="t('system.plugin.tableColunmStatus')">
<template #cell="{ record }">
<div v-if="record.enable" class="flex items-center">
<icon-check-circle-fill class="mr-[2px] text-[rgb(var(--success-6))]" />
{{ t('system.user.tableEnable') }}
</div>
<div v-else class="flex items-center text-[var(--color-text-4)]">
<icon-stop class="mr-[2px]" />
{{ t('system.user.tableDisable') }}
</div>
</template>
</a-table-column>
<a-table-column :title="t('system.plugin.tableColunmApplicationScene')" data-index="scenario">
<template #cell="{ record }">{{
record.scenario === 'API' ? t('system.plugin.secneApi') : t('system.plugin.secneProManger')
}}</template>
</a-table-column>
<a-table-column :width="300" :title="t('system.user.tableColunmOrg')">
<template #cell="{ record }">
<a-tag
v-for="org of record.organizationList.slice(0, 2)"
v-for="org of record.organizations"
:key="org.id"
class="mr-[4px] border-[rgb(var(--primary-5))] bg-transparent !text-[rgb(var(--primary-5))]"
bordered
@ -27,99 +65,99 @@
{{ org.name }}
</a-tag>
<a-tag
v-show="record.organizationList.length > 2"
v-show="record.organizations.length > 2"
class="mr-[4px] border-[rgb(var(--primary-5))] bg-transparent !text-[rgb(var(--primary-5))]"
bordered
>
+{{ record.organizationList.length - 2 }}
+{{ record.organizations.length - 2 }}
</a-tag>
</template>
<template #action="{ record }">
</a-table-column>
<a-table-column :title="t('system.plugin.tableColunmDescription')" data-index="fileName" />
<a-table-column :width="350" :title="t('system.plugin.tableColunmVersion')" data-index="pluginId" />
<a-table-column :title="t('system.plugin.tableColunmAuthorization')">
<template #cell="{ record }">
<span>{{ record.xpack ? t('system.plugin.uploadOpenSource') : t('system.plugin.uploadCompSource') }}</span>
</template>
</a-table-column>
<a-table-column :title="t('system.plugin.tableColunmCreatedBy')" data-index="createUser" />
<a-table-column :title="t('system.plugin.tableColunmUpdateTime')">
<template #cell="{ record }">
<span>{{ getTime(record.updateTime) }}</span>
</template>
</a-table-column>
<a-table-column :width="200" fixed="right" align="center" :bordered="false">
<template #title>
{{ t('system.plugin.tableColunmActions') }}
</template>
<template #cell="{ record }">
<MsButton @click="update(record)">{{ t('system.plugin.edit') }}</MsButton>
<MsButton @click="changeScene(record)">{{ t('system.plugin.ChangeScene') }}</MsButton>
<MsButton v-if="record.enable" @click="disableHandler(record)">{{
t('system.plugin.tableDisable')
}}</MsButton>
<MsButton v-else>{{ t('system.plugin.tableEnable') }}</MsButton>
<MsTableMoreAction :list="tableActions" @select="handleSelect($event, record)"></MsTableMoreAction>
</template>
</ms-base-table>
<div class="mt-4 text-sm text-slate-500"
><span class="mx-2">{{ 101 }}</span
>项数据</div
</a-table-column>
</template>
<!-- <template #expand-icon="{ expanded }">
<span v-if="!expanded"><icon-plus /></span>
<span v-else><icon-minus /></span>
</template> -->
<template #expand-row="{ record }">
<div v-for="(item, index) in record.pluginForms" :key="item.id" class="ms-self"
><span class="circle"> {{ index + 1 }} </span
><span class="cursor-pointer text-[rgb(var(--primary-6))]" @click="detailScript(record, item)">{{
item.name
}}</span></div
>
<UploadModel :visible="uploadVisible" @cancel="uploadVisible = false" @upload="uploadPlugin" @success="okHandler" />
<UpdatePluginModal ref="updateModalRef" :visible="updateVisible" @cancel="updateVisible = false" />
</template>
</a-table>
<div class="mt-4 text-sm text-slate-500"
>{{ t('system.plugin.totalNum') }}<span class="mx-2">{{ totalNum }}</span
>{{ t('system.plugin.dataList') }}</div
>
<UploadModel :visible="uploadVisible" @cancel="uploadVisible = false" @success="okHandler" @brash="loadData()" />
<UpdatePluginModal ref="updateModalRef" v-model:visible="updateVisible" @success="loadData()" />
<scriptDetailDrawer v-model:visible="showDrawer" :value="detailYaml" :config="config" />
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, reactive } from 'vue';
import { useI18n } from '@/hooks/useI18n';
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
import useTable from '@/components/pure/ms-table/useTable';
import MsTableMoreAction from '@/components/pure/ms-table-more-action/index.vue';
import type { MsTableColumn } from '@/components/pure/ms-table/type';
import type { ActionsItem } from '@/components/pure/ms-table-more-action/types';
import { getPluginList } from '@/api/modules/setting/pluginManger';
import { getPluginList, deletePluginReq, updatePlugin, getScriptDetail } from '@/api/modules/setting/pluginManger';
import MsButton from '@/components/pure/ms-button/index.vue';
import UploadModel from './uploadModel.vue';
import UpdatePluginModal from './updatePluginModal.vue';
import uploadSuccessModal from './uploadSuccessModal.vue';
import sceneChangeModal from './sceneChangeModal.vue';
import scriptDetailDrawer from './scriptDetailDrawer.vue';
import { useCommandComponent } from '@/hooks/useCommandComponent';
import useModal from '@/hooks/useModal';
import { Message } from '@arco-design/web-vue';
import useVisit from '@/hooks/useVisit';
import type { PluginForms, PluginList, PluginItem, Options, DrawerConfig } from '@/models/setting/plugin';
import dayjs from 'dayjs';
const { t } = useI18n();
export type Options = {
title: string;
visible: boolean;
onClose?: () => void;
};
const columns: MsTableColumn = [
{
title: 'system.plugin.tableColunmName',
dataIndex: 'name',
width: 200,
fixed: 'left',
},
{
title: 'system.plugin.tableColunmDescription',
dataIndex: 'describe',
},
{
title: 'system.plugin.tableColunmApplicationScene',
dataIndex: 'applicationScene',
},
{
title: 'system.user.tableColunmOrg',
slotName: 'organization',
dataIndex: 'organizationList',
},
{
title: 'system.plugin.tableColunmJarPackage',
dataIndex: 'jarPackage',
},
{
title: 'system.plugin.tableColunmVersion',
dataIndex: 'version',
},
{
title: 'system.plugin.tableColunmAuthorization',
dataIndex: 'authorizationType',
},
{
title: 'system.plugin.tableColunmCreatedBy',
dataIndex: 'createdBy',
},
{
title: 'system.plugin.tableColunmExpirationDate',
dataIndex: 'expirationDate',
},
{
title: 'system.plugin.tableColunmActions',
slotName: 'action',
fixed: 'right',
width: 150,
},
];
const visitedKey = 'doNotShowAgain';
const { getIsVisited } = useVisit(visitedKey);
const data = ref<PluginList>([]);
const loading = ref<boolean>(false);
const expandable = reactive({
title: '',
width: 54,
});
const expandedRowKeys = reactive([]);
const config = ref<DrawerConfig>({
title: '',
pluginId: '',
});
const tableActions: ActionsItem[] = [
{
label: 'system.plugin.delete',
@ -127,34 +165,109 @@
danger: true,
},
];
const { propsRes, propsEvent, loadList, setKeyword } = useTable(getPluginList, {
columns,
'scroll': { y: 'auto', x: 1800 },
'selectable': false,
'showSelectAll': false,
'pagination': false,
'virtual-list-props': { height: 380 },
const searchKeys = reactive({
scene: '',
name: '',
});
const totalNum = ref<number>(0);
const showDrawer = ref<boolean>(false);
const detailYaml = ref('');
const { openModal } = useModal();
const keyword = ref('');
const scene = ref('1');
const sceneList = ref([
{
label: '全部应用场景',
value: '1',
label: '全部',
value: '',
},
{
label: '接口测试',
value: 'API',
},
{
label: '项目管理',
value: 'PLATFORM',
},
]);
const uploadVisible = ref<boolean>(false);
const updateVisible = ref<boolean>(false);
const updateModalRef = ref();
onMounted(async () => {
setKeyword(keyword.value);
await loadList();
});
async function searchPlugin() {
setKeyword(keyword.value);
await loadList();
const getTime = (time: string): string => {
return dayjs(time).format('YYYY-MM-DD HH:mm:ss');
};
const loadData = async () => {
loading.value = true;
try {
const result = await getPluginList();
data.value = result;
totalNum.value = result.length;
} catch (error) {
console.log(error);
data.value = [];
} finally {
loading.value = false;
}
};
const handleExpand = (rowKey: string | number) => {
Object.assign(expandedRowKeys, [rowKey]);
};
const searchHanlder = () => {};
onMounted(() => {
data.value = [
{
id: 'string1',
name: '插件一',
pluginId: 'string',
fileName: 'string',
createTime: 0,
updateTime: 3084234,
createUser: '创建人',
enable: true,
global: true,
xpack: true,
description: 'string',
scenario: 'string',
pluginForms: [
{
id: '111',
name: '步骤一',
},
{
id: '222',
name: '步骤二',
},
],
organizations: [
{
id: 'string',
num: 0,
name: 'string',
},
],
},
{
id: 'string2',
name: '插件二',
pluginId: 'string',
fileName: 'string',
createTime: 0,
updateTime: 3084234,
createUser: '创建人',
enable: true,
global: true,
xpack: true,
description: 'string',
scenario: 'string',
pluginForms: [],
organizations: [
{
id: 'string',
num: 0,
name: 'string',
},
],
},
];
// loadData();
});
function deletePlugin(record: any) {
openModal({
type: 'warning',
@ -167,7 +280,9 @@
},
onBeforeOk: async () => {
try {
deletePluginReq(record.id);
Message.success(t('system.plugin.deletePluginSuccess'));
loadData();
return true;
} catch (error) {
console.log(error);
@ -177,10 +292,6 @@
hideCancel: false,
});
}
/**
* 处理表格更多按钮事件
* @param item
*/
function handleSelect(item: ActionsItem, record: any) {
switch (item.eventTag) {
case 'delete':
@ -193,39 +304,85 @@
function uploadPlugin() {
uploadVisible.value = true;
}
function update(record: any) {
function update(record: PluginItem) {
updateVisible.value = true;
updateModalRef.value.title = record.name;
updateModalRef.value.open(record);
}
const myUploadSuccessDialog = useCommandComponent(uploadSuccessModal);
const mySceneChangeDialog = useCommandComponent(sceneChangeModal);
const uploadSuccessOptions = reactive({
title: '上传插件',
title: 'system.plugin.uploadPlugin',
visible: false,
onOpen: () => uploadPlugin(),
onClose: () => {
myUploadSuccessDialog.close();
},
});
const sceneChangeOptions = reactive({
title: '场景变更-(插件名称)',
visible: false,
onClose: () => {
myUploadSuccessDialog.close();
},
});
const dialogOpen = (options: Options) => {
options.visible = true;
myUploadSuccessDialog(uploadSuccessOptions);
};
const okHandler = () => {
const isOpen = getIsVisited();
if (!isOpen) {
dialogOpen(uploadSuccessOptions);
}
};
const changeScene = (record: any) => {
sceneChangeOptions.visible = true;
mySceneChangeDialog(sceneChangeOptions);
const disableHandler = (record: PluginItem) => {
openModal({
type: 'info',
title: t('system.plugin.disablePluginTip', { name: record.name }),
content: t('system.plugin.disablePluginContent'),
okText: t('system.plugin.disablePluginConfirm'),
cancelText: t('system.plugin.pluginCancel'),
okButtonProps: {
status: 'normal',
},
onBeforeOk: async () => {
try {
await updatePlugin({ enable: !record.enable });
Message.success(t('system.plugin.disablePluginSuccess'));
loadData();
return true;
} catch (error) {
console.log(error);
return false;
}
},
hideCancel: false,
});
};
const detailScript = async (record: PluginItem, item: PluginForms) => {
showDrawer.value = true;
config.value = {
pluginId: record.pluginId as string,
title: item.name,
};
try {
const result = await getScriptDetail(record.pluginId as string, item.id);
detailYaml.value = result || '';
} catch (error) {
console.log(error);
}
};
</script>
<style scoped></style>
@/api/modules/setting/pluginManger
<style scoped lang="less">
.circle {
color: var(--color-text-3);
background: var(--color-fill-3);
@apply ml-6 mr-10 inline-block h-4 w-4 text-center text-xs leading-4;
}
:deep(.arco-table-tr-expand .arco-table-td) {
background: none;
}
:deep(.arco-table-tr-expand .arco-table-cell) {
padding: 0 !important;
}
.ms-self {
height: 40px;
line-height: 40px;
border-bottom: 1px solid var(--color-text-n8);
@apply flex items-center align-middle leading-6;
}
</style>

View File

@ -1,95 +0,0 @@
<template>
<a-modal v-model:visible="dialogVisible" class="ms-modal-form ms-modal-medium" title-align="start">
<template #title> {{ title }} </template>
<a-alert type="warning" :closable="true">
<div>{{ t('system.plugin.changeSceneTips') }}</div>
</a-alert>
<div class="mt-6 flex flex-col">
<div
v-for="(item, index) in sceneList"
:key="index"
class="ms-ls-row my-2"
:class="{ 'ms-ls-row--active': item.isSelected }"
@click="selectHandler(item)"
>
<div class="ms-icon-list ml-4 mr-5">
<svg-icon :width="'64px'" :height="'46px'" :name="item.svg" />
</div>
<div class="flex flex-col justify-center">
<div class="mb-1 font-medium">
<span>{{ t(item.name) }}</span>
<a-tag
v-show="item.isSelected"
size="small"
class="ml-[4px] border-[rgb(var(--primary-4))] bg-transparent px-1 text-xs !text-[rgb(var(--primary-4))]"
>{{ t('system.plugin.currentScene') }}</a-tag
>
</div>
<div class="text-sm">{{ t(item.description) }}</div>
</div>
</div>
</div>
<template #footer>
<a-button type="secondary" @click="emits('close')">{{ t('system.plugin.pluginCancel') }}</a-button>
<a-button type="primary" @click="handelOk">
{{ t('system.plugin.pluginConfirm') }}
</a-button>
</template>
</a-modal>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { useDialog } from '@/hooks/useDialog';
import type { SceneItem, SceneList } from '@/models/setting/plugin';
import { useI18n } from '@/hooks/useI18n';
const { t } = useI18n();
const props = defineProps<{
visible: boolean;
title?: string;
}>();
const emits = defineEmits<{
(event: 'update:visible', visible: boolean): void;
(event: 'close'): void;
}>();
const sceneList = ref<SceneList>([
{
name: 'system.plugin.interfaceTest',
description: 'system.plugin.interfaceTestDescribe',
isSelected: true,
svg: 'apitest',
},
{
name: 'system.plugin.projectManger',
description: 'system.plugin.projectMangerDescribe',
isSelected: false,
svg: 'promanger',
},
]);
const { dialogVisible } = useDialog(props, emits);
const selectHandler = (currentItem: SceneItem) => {
sceneList.value.forEach((item: SceneItem) => {
item.isSelected = false;
});
currentItem.isSelected = true;
};
const handelOk = () => {
emits('close');
};
</script>
<style scoped lang="less">
.ms-ls-row {
border-width: 1px;
@apply flex flex-row items-center rounded border-solid py-2;
&--active {
background-color: rgb(var(--primary-1));
}
&:hover {
background-color: rgb(var(--primary-1));
}
}
</style>
@/models/setting/plugin

View File

@ -0,0 +1,52 @@
<template>
<MsDrawer
v-model:visible="showScriptDrawer"
width="680px"
:mask="false"
:title="t('system.plugin.showScriptTitle', { name: props.config.title })"
>
<MsCodeEditor
v-model:model-value="jobDefinition"
title="YAML"
width="100%"
height="calc(100vh - 205px)"
theme="MS-text"
/>
</MsDrawer>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue';
import { useI18n } from '@/hooks/useI18n';
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
import MsCodeEditor from '@/components/pure/ms-code-editor/index.vue';
import type { DrawerConfig } from '@/models/setting/plugin';
const props = defineProps<{
visible: boolean;
value: string;
config: DrawerConfig;
}>();
const emit = defineEmits(['update:value', 'update:visible']);
const { t } = useI18n();
const showScriptDrawer = ref(props.visible);
const jobDefinition = ref(props.value);
watch(
() => props.visible,
(val) => {
showScriptDrawer.value = val;
}
);
watch(
() => showScriptDrawer.value,
(val) => {
emit('update:visible', val);
}
);
</script>
<style lang="less" scoped></style>

View File

@ -1,37 +0,0 @@
<template>
<a-steps :current="currentStep" @change="changeCurrent">
<a-step v-for="item of stepList" :key="item.title">{{ t(item.title) }}</a-step>
</a-steps>
</template>
<script setup lang="ts">
import { ref, watchEffect } from 'vue';
import { useI18n } from '@/hooks/useI18n';
import type { StepList } from '@/models/setting/plugin';
const { t } = useI18n();
const props = defineProps<{
current: number;
stepList: StepList;
setCurrent: (step: number) => void;
}>();
const currentStep = ref(1);
const changeCurrent = (step: number) => {
props.setCurrent(step);
};
watchEffect(() => {
currentStep.value = props.current;
});
</script>
<style scoped lang="less">
:deep(.arco-steps-icon) {
width: 20px;
height: 20px;
line-height: 19px;
}
:deep(.arco-modal-body) {
padding: 10px;
}
</style>
@/models/setting/plugin

View File

@ -1,38 +1,41 @@
<template>
<a-modal v-model:visible="updateVisible" width="680px" title-align="start" class="ms-modal-form ms-modal-medium">
<template #title> {{ t('system.plugin.updateTitle', { name: title }) }}</template>
<template #title> {{ t('system.plugin.updateTitle', { name: form.name }) }}</template>
<div class="form">
<a-form :model="form" layout="vertical">
<!-- <a-col :span="24"> -->
<a-form-item field="pluginName" :label="t('system.plugin.name')" asterisk-position="end">
<a-input v-model="form.pluginName" :placeholder="t('system.plugin.defaultJarNameTip')" allow-clear />
<a-form ref="UpdateFormRef" :model="form" layout="vertical">
<a-form-item field="name" :label="t('system.plugin.name')" asterisk-position="end">
<a-input v-model="form.name" :placeholder="t('system.plugin.defaultJarNameTip')" allow-clear />
</a-form-item>
<!-- </a-col> -->
<a-form-item field="organize" :label="t('system.plugin.appOrganize')" asterisk-position="end">
<a-radio-group v-model="form.organize">
<a-radio value="1">全部组织</a-radio>
<a-radio value="2">指定组织</a-radio>
<a-form-item field="global" :label="t('system.plugin.appOrganize')" asterisk-position="end">
<a-radio-group v-model="form.global">
<a-radio :value="true">{{ t('system.plugin.allOrganize') }}</a-radio>
<a-radio :value="false">{{ t('system.plugin.theOrganize') }}</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item
v-if="form.organize === '2'"
field="organize"
v-if="!form.global"
field="organizationIds"
:label="t('system.plugin.selectOrganization')"
asterisk-position="end"
:rules="[{ required: true, message: t('system.plugin.selectOriginize') }]"
>
<a-select v-model="form.organizeGroup" multiple :placeholder="t('system.plugin.selectOriginize')" allow-clear>
<a-option v-for="item of originizeList" :key="item.value">{{ item.label }}</a-option>
<a-select
v-model="form.organizationIds"
multiple
:placeholder="t('system.plugin.selectOriginize')"
allow-clear
>
<a-option v-for="item of originizeList" :key="item.value" :value="item.value">{{ item.label }}</a-option>
</a-select>
</a-form-item>
<a-form-item field="describe" :label="t('system.plugin.description')" asterisk-position="end">
<a-textarea v-model="form.describe" :placeholder="t('system.plugin.pluginDescription')" allow-clear />
<a-form-item field="description" :label="t('system.plugin.description')" asterisk-position="end">
<a-textarea v-model="form.description" :placeholder="t('system.plugin.pluginDescription')" allow-clear />
</a-form-item>
</a-form>
</div>
<template #footer>
<a-button type="secondary" @click="emit('cancel')">{{ t('system.plugin.pluginCancel') }}</a-button>
<a-button type="primary" @click="handleOk">
<a-button type="secondary" @click="handleCancel">{{ t('system.plugin.pluginCancel') }}</a-button>
<a-button type="primary" :loading="confirmLoading" @click="handleOk">
{{ t('system.plugin.pluginConfirm') }}
</a-button>
</template>
@ -40,18 +43,22 @@
</template>
<script setup lang="ts">
import { ref, watchEffect, reactive, onMounted } from 'vue';
import { ref, watchEffect, watch, nextTick, reactive } from 'vue';
import { FormInstance, Message, ValidatedError } from '@arco-design/web-vue';
import { useI18n } from '@/hooks/useI18n';
import type { PluginForm } from '@/models/setting/plugin';
import type { UpdatePluginModel, PluginItem } from '@/models/setting/plugin';
import { updatePlugin } from '@/api/modules/setting/pluginManger';
const { t } = useI18n();
const props = defineProps<{
visible: boolean;
}>();
const emit = defineEmits<{
(e: 'cancel'): void;
const emits = defineEmits<{
(e: 'success'): void;
(e: 'update:visible', val: boolean): void;
}>();
const title = ref<string>();
const confirmLoading = ref<boolean>(false);
const UpdateFormRef = ref<FormInstance | null>(null);
const originizeList = ref([
{
label: '组织一',
@ -62,34 +69,62 @@
value: '2',
},
]);
const form = reactive<PluginForm>({
pluginName: '',
organize: '',
describe: '',
organizeGroup: [],
const form = ref<UpdatePluginModel>({
name: '',
global: '',
organizationIds: [],
description: '',
});
const updateVisible = ref<boolean>(false);
watchEffect(() => {
updateVisible.value = props.visible;
});
watch(
() => updateVisible.value,
(val) => {
emits('update:visible', val);
}
);
const handleCancel = () => {
emit('cancel');
UpdateFormRef.value?.resetFields();
updateVisible.value = false;
};
const open = (record: PluginItem) => {
form.value = { ...record };
};
const handleOk = () => {
UpdateFormRef.value?.validate(async (errors: undefined | Record<string, ValidatedError>) => {
if (!errors) {
confirmLoading.value = true;
try {
const { id, name, organizationIds, global, description } = form.value;
const params = {
id,
name,
organizationIds,
global,
description,
};
await updatePlugin(params);
Message.success(t('system.plugin.updateSuccessTip'));
handleCancel();
};
const initData = () => {
form.pluginName = title.value as string;
};
onMounted(() => {
initData();
emits('success');
} catch (error) {
console.log(error);
} finally {
confirmLoading.value = false;
}
} else {
return false;
}
});
};
defineExpose({
title,
open,
UpdateFormRef,
});
</script>
<style scoped></style>
@/models/setting/plugin

View File

@ -1,90 +1,223 @@
<template>
<a-modal
v-model:visible="pluginVisible"
class="ms-modal-form ms-modal-small"
title-align="start"
width="480px"
@ok="handleOk"
@cancel="handleCancel"
>
<a-modal v-model:visible="pluginVisible" class="ms-modal-form ms-modal-small" title-align="start">
<template #title> {{ t('system.plugin.uploadPlugin') }} </template>
<div>
<StepProgress :step-list="stepList" :current="currentStep" small :set-current="setCurrent" changeable />
<SceneList v-show="currentStep === 1" :set-current="setCurrent" />
<uploadPlugin v-show="currentStep === 2" />
<div class="form grid grid-cols-1">
<a-row class="grid-demo">
<a-form ref="pluginFormRef" :model="form" size="small" :style="{ width: '600px' }" layout="vertical">
<div class="relative">
<a-form-item field="pluginName" :label="t('system.plugin.name')" asterisk-position="end">
<a-input
v-model="form.name"
size="small"
:placeholder="t('system.plugin.defaultJarNameTip')"
allow-clear
/>
<span class="absolute right-0 top-1 flex items-center">
<span class="float-left">{{ t('system.plugin.getPlugin') }}</span>
<a-tooltip :content="t('system.plugin.infoTip')" position="bottom">
<a class="float-left mx-2" href="javascript:;">
<svg-icon width="16px" height="16px" :name="'infotip'"
/></a>
</a-tooltip>
</span>
</a-form-item>
</div>
<a-form-item
field="global"
:label="t('system.plugin.appOrganize')"
asterisk-position="end"
:rules="[{ required: true, message: 'must select one' }]"
>
<a-radio-group v-model="form.global" size="small">
<a-radio :value="true">{{ t('system.plugin.allOrganize') }}</a-radio>
<a-radio :value="false">{{ t('system.plugin.theOrganize') }}</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item
v-if="!form.global"
field="organizationIds"
:label="t('system.plugin.selectOrganization')"
asterisk-position="end"
:rules="[{ required: true, message: t('system.plugin.selectOriginize') }]"
>
<a-select
v-model="form.organizationIds"
multiple
:placeholder="t('system.plugin.selectOriginize')"
allow-clear
>
<a-option v-for="item of originizeList" :key="item.value" :value="item.value">{{ item.label }}</a-option>
</a-select>
</a-form-item>
<a-form-item field="describe" :label="t('system.plugin.description')" asterisk-position="end">
<a-textarea
v-model="form.description"
size="small"
:placeholder="t('system.plugin.pluginDescription')"
allow-clear
/>
</a-form-item>
</a-form>
</a-row>
<MsUpload
accept="jar"
:on-before-upload="beforeUpload"
main-text="system.user.importModalDragtext"
:sub-text="t('system.plugin.supportFormat')"
:show-file-list="true"
:file-list="fileList"
:on-before-remove="removeHandler"
></MsUpload>
</div>
<template #footer>
<div v-show="currentStep === 2" class="float-right">
<div class="flex justify-between">
<div class="flex flex-row items-center justify-center">
<a-switch v-model="form.enable" size="small" />
<a-tooltip>
<template #content>
<div class="text-sm">{{ t('system.plugin.statusEnableTip') }}</div>
<div class="text-sm">{{ t('system.plugin.statusDisableTip') }}</div>
</template>
<a class="mx-2" href="javascript:;"> <svg-icon width="16px" height="16px" :name="'infotip'" /></a>
</a-tooltip>
</div>
<div>
<a-space>
<a-button type="secondary" @click="handleCancel">{{ t('system.plugin.pluginCancel') }}</a-button>
<a-button type="secondary" @click="preStep">{{ t('system.plugin.pluginPreStep') }}</a-button>
<a-button type="secondary" @click="saveAndAddPlugin">{{ t('system.plugin.saveAndAdd') }}</a-button>
<a-button type="primary" @click="saveConfirm('confirm')">{{ t('system.plugin.pluginConfirm') }}</a-button>
<a-button type="secondary" :loading="saveLoading" @click="saveAndAddPlugin">{{
t('system.plugin.saveAndAdd')
}}</a-button>
<a-button type="primary" :loading="confirmLoading" @click="saveConfirm">{{
t('system.plugin.pluginConfirm')
}}</a-button>
</a-space>
</div>
</div>
</template>
</a-modal>
</template>
<script setup lang="ts">
import { ref, watchEffect } from 'vue';
import MsUpload from '@/components/pure/ms-upload/index.vue';
import type { FormInstance, ValidatedError } from '@arco-design/web-vue';
import { addPlugin } from '@/api/modules/setting/pluginManger';
import { Message } from '@arco-design/web-vue';
import { formatFileSize } from '@/utils';
import { useI18n } from '@/hooks/useI18n';
import type { StepList } from '@/models/setting/plugin';
import StepProgress from './stepProgress.vue';
import SceneList from './SceneList.vue';
import uploadPlugin from './uploadPlugin.vue';
const { t } = useI18n();
const currentStep = ref<number>(1);
const stepList = ref<StepList>([
{
name: '选择应用场景',
title: 'system.plugin.SelectApplicationScene',
status: true,
},
{
name: '上传插件',
title: 'system.plugin.uploadPlugin',
status: true,
},
]);
const pluginVisible = ref(false);
const emits = defineEmits<{
(e: 'cancel'): void;
(e: 'upload'): void;
(e: 'success'): void;
(e: 'brash'): void;
}>();
const props = defineProps<{
visible: boolean;
}>();
const pluginVisible = ref(false);
const fileName = ref<string>('');
const fileList = ref<File[]>([]);
const saveLoading = ref<boolean>(false);
const confirmLoading = ref<boolean>(false);
const pluginFormRef = ref<FormInstance | null>(null);
const initForm = {
name: '',
description: '',
organizationIds: [],
enable: true,
global: true,
};
const form = ref({ ...initForm });
const originizeList = ref([
{
label: '组织一',
value: '1',
},
{
label: '组织二',
value: '2',
},
]);
watchEffect(() => {
pluginVisible.value = props.visible;
});
const handleCancel = () => {
emits('cancel');
};
const handleOk = () => {
handleCancel();
};
const setCurrent = (step: number) => {
currentStep.value = step;
};
const preStep = () => {
currentStep.value = currentStep.value === 2 ? 1 : 2;
};
const saveConfirm = (flag: string) => {
if (flag === 'confirm') {
handleCancel();
emits('success');
const beforeUpload = (file: File) => {
const size = formatFileSize(file.size);
if (size.includes('MB') && Number(size.replace('MB', '')) > 50) {
Message.warning(t('system.plugin.sizeExceedTip'));
} else {
fileName.value = file.name;
fileList.value = [...fileList.value, file];
fileList.value = fileList.value.slice(-1);
}
};
const removeHandler = () => {
fileList.value = [];
};
const handleCancel = () => {
pluginFormRef.value?.resetFields();
emits('cancel');
};
const resetForm = () => {
form.value = { ...initForm };
fileList.value = [];
};
const confirmHandler = async (flag: string) => {
try {
if (fileList.value.length < 1) {
Message.warning(t('system.plugin.uploadFileTip'));
return;
}
const params: any = {
request: {
...form.value,
name: form.value.name || fileName.value,
},
fileList: fileList.value,
};
await addPlugin(params);
Message.success(t('system.plugin.uploadSuccessTip'));
if (flag === 'Confirm') {
emits('success');
handleCancel();
}
resetForm();
emits('brash');
} catch (error) {
console.log(error);
return false;
} finally {
confirmLoading.value = false;
saveLoading.value = false;
}
};
const saveConfirm = () => {
pluginFormRef.value?.validate((errors: undefined | Record<string, ValidatedError>) => {
if (!errors) {
confirmLoading.value = true;
confirmHandler('Confirm');
} else {
return false;
}
});
};
const saveAndAddPlugin = () => {
saveConfirm('saveAndAdd');
preStep();
pluginFormRef.value?.validate((errors: undefined | Record<string, ValidatedError>) => {
if (!errors) {
saveLoading.value = true;
confirmHandler('Continue');
} else {
return false;
}
});
};
</script>
<style scoped lang="less"></style>
@/models/setting/plugin
<style scoped lang="less">
:deep(.arco-upload) {
width: 100%;
}
</style>

View File

@ -1,84 +0,0 @@
<template>
<div class="form grid grid-cols-1">
<a-row class="grid-demo">
<a-form :model="form" size="small" :style="{ width: '600px' }" layout="vertical">
<div class="relative">
<a-form-item field="pluginName" :label="t('system.plugin.name')" asterisk-position="end">
<a-input
v-model="form.pluginName"
size="small"
:placeholder="t('system.plugin.defaultJarNameTip')"
allow-clear
/>
<span class="absolute right-0 top-1 flex items-center">
<span class="float-left">{{ t('system.plugin.getPlugin') }}</span>
<a-tooltip :content="t('system.plugin.infoTip')">
<a class="float-left mx-2" href="javascript:;">
<svg-icon :width="'16px'" :height="'16px'" :name="'infotip'"
/></a>
</a-tooltip>
</span>
</a-form-item>
</div>
<a-form-item field="organize" :label="t('system.plugin.appOrganize')" asterisk-position="end">
<a-radio-group v-model="form.organize" size="small">
<a-radio value="1">全部组织</a-radio>
<a-radio value="2">指定组织</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item
v-if="form.organize === '2'"
field="organize"
:label="t('system.plugin.selectOrganization')"
asterisk-position="end"
:rules="[{ required: true, message: t('system.plugin.selectOriginize') }]"
>
<a-select v-model="form.organizeGroup" multiple :placeholder="t('system.plugin.selectOriginize')" allow-clear>
<a-option v-for="item of originizeList" :key="item.value">{{ item.label }}</a-option>
</a-select>
</a-form-item>
<a-form-item field="describe" :label="t('system.plugin.description')" asterisk-position="end">
<a-textarea
v-model="form.describe"
size="small"
:placeholder="t('system.plugin.pluginDescription')"
allow-clear
/>
</a-form-item>
</a-form>
</a-row>
<MsUpload
action="/"
accept="excel"
main-text="system.user.importModalDragtext"
:sub-text="t('system.plugin.supportFormat')"
:show-file-list="false"
></MsUpload>
</div>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue';
import MsUpload from '@/components/pure/ms-upload/index.vue';
import { useI18n } from '@/hooks/useI18n';
const { t } = useI18n();
const form = reactive({
pluginName: '',
organize: '1',
describe: '',
organizeGroup: [],
});
const originizeList = ref([
{
label: '组织一',
value: '1',
},
{
label: '组织二',
value: '2',
},
]);
</script>
<style scoped></style>

View File

@ -6,7 +6,7 @@
:footer="false"
@open="BeforeOpen"
>
<template #title> {{ title }} </template>
<template #title> {{ t(title as string) }} </template>
<div class="flex w-full flex-col items-center justify-center">
<div class="mb-5"><svg-icon :width="'60px'" :height="'60px'" :name="'success'" /></div>
<div class="font-semibold">{{ t('system.plugin.uploadSuccess') }}</div>
@ -21,7 +21,7 @@
</div>
<div>
<a-space>
<a-button type="primary">{{ 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="secondary">{{ t('system.plugin.backPluginList') }}</a-button>
</a-space>
@ -34,14 +34,18 @@
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { ref, watch } from 'vue';
import { useDialog } from '@/hooks/useDialog';
import useVisit from '@/hooks/useVisit';
import { useI18n } from '@/hooks/useI18n';
const { t } = useI18n();
const visitedKey = 'doNotShowAgain';
const { addVisited } = useVisit(visitedKey);
const props = defineProps<{
visible: boolean;
title?: string;
onOpen: (visible: boolean) => void;
}>();
const emits = defineEmits<{
(event: 'update:visible', visible: boolean): void;
@ -62,6 +66,18 @@
}
}, 1000);
};
const isDoNotShowAgainChecked = () => {
if (isTip.value) {
addVisited();
}
};
watch(isTip, () => {
isDoNotShowAgainChecked();
});
const continueAdd = () => {
emits('close');
props.onOpen(true);
};
</script>
<style scoped>

View File

@ -1,7 +1,7 @@
/* stylelint-disable order/properties-order */
<template>
<div>
<a-alert type="info" :closable="true">
<a-alert :closable="true">
<div>
{{ t('system.plugin.alertDescribe') }}
<a class="mx-1" href="javascript:;">{{ t('system.plugin.viewTable') }}</a

View File

@ -17,7 +17,9 @@ export default {
'system.plugin.tableColunmCreatedBy': '创建人',
'system.plugin.tableColunmExpirationDate': '到期时间',
'system.plugin.tableColunmActions': '操作',
'system.plugin.tableColunmStatus': '状态',
'system.plugin.ChangeScene': '场景变更',
'system.plugin.ChangeSceneName': '场景变更({name})',
'system.plugin.SelectApplicationScene': '选择应用场景',
'system.plugin.interfaceTest': '接口测试',
'system.plugin.projectManger': '项目管理',
@ -33,9 +35,11 @@ export default {
'system.resourcePool.disablePoolCancel': '取消',
'system.resourcePool.disablePoolSuccess': '禁用成功',
'system.plugin.deletePluginTip': '确认删除 `{name}` 这个插件吗?',
'system.plugin.disablePluginTip': '确认禁用 `{name}` 这个插件吗?',
'system.resourcePool.deletePoolContentUsed': '该资源池已被使用,删除后相关测试会立即停止,请谨慎操作!',
'system.resourcePool.deletePoolContentUnuse': '该资源池未被使用,是否确认删除?',
'system.plugin.deletePluginConfirm': '确认删除',
'system.plugin.disablePluginConfirm': '确认禁用',
'system.plugin.pluginConfirm': '确认',
'system.plugin.pluginCancel': '取消',
'system.plugin.pluginPreStep': '上一步',
@ -56,11 +60,29 @@ export default {
'system.plugin.changeSceneTips': '插件内容与应用场景不一致时插件功能将无法启用,请谨慎操作!',
'system.plugin.currentScene': '当前场景',
'system.plugin.deletePluginSuccess': '删除成功',
'system.plugin.disablePluginSuccess': '禁用成功',
'system.plugin.disablePluginContent': '项目无法与该平台集成且该平台默认模版不可用,谨慎操作!',
'system.plugin.alertDescribe':
'MeterSphereV2.10LTS版本支持DevOps、API导入、请求、项目管理、协议类型的插件具体支持插件请',
'system.plugin.viewTable': '查看表格',
'system.plugin.downAddress': '更多开源插件,请在此下载',
'system.plugin.goDownload': '去下载',
'system.plugin.totalNum': '共',
'system.plugin.dataList': '项数据',
'system.plugin.allOrganize': '全部组织',
'system.plugin.theOrganize': '指定组织',
'system.plugin.statusEnableTip': '开启:项目可以与该平台集成并生成该平台的默认模版',
'system.plugin.statusDisableTip': '关闭:项目无法与该平台集成且该平台默认模版不可用',
'system.plugin.uploadSuccessTip': '上传成功',
'system.plugin.updateSuccessTip': '更新成功',
'system.plugin.uploadDefeatTip': '插件解析失败,请重新上传!',
'system.plugin.uploadFileTip': '请上传文件再提交!',
'system.plugin.uploadOpenSource': '开源版',
'system.plugin.uploadCompSource': '企业版',
'system.plugin.secneApi': '接口测试',
'system.plugin.secneProManger': '项目管理',
'system.plugin.sizeExceedTip': '文件大小超出限制!',
'system.plugin.showScriptTitle': '查看脚本({name})',
'system.resourcePool.detailDesc': '描述',
'system.resourcePool.detailUrl': '当前站点 URL',
'system.resourcePool.detailRange': '可用范围',

View File

@ -32,7 +32,7 @@ export interface UploadFileParams {
// File parameter interface field name
name?: string;
// file name
file: File | Blob;
file?: File | Blob;
// file name
filename?: string;
[key: string]: any;