feat(接口测试): 接口文档分享联调(不包含导出)
This commit is contained in:
parent
98e38ffefc
commit
1f23110358
|
@ -7,6 +7,7 @@ import {
|
|||
AddDefinitionUrl,
|
||||
AddMockUrl,
|
||||
AddModuleUrl,
|
||||
AddShareUrl,
|
||||
BatchCleanOutApiUrl,
|
||||
BatchDeleteCaseUrl,
|
||||
BatchDeleteDefinitionUrl,
|
||||
|
@ -24,6 +25,7 @@ import {
|
|||
CasePageUrl,
|
||||
caseTableBatchSyncUrl,
|
||||
CheckDefinitionScheduleUrl,
|
||||
checkSharePsdUrl,
|
||||
clearThisChangeUrl,
|
||||
ConvertJsonSchemaToJsonUrl,
|
||||
CopyMockUrl,
|
||||
|
@ -39,6 +41,7 @@ import {
|
|||
DeleteModuleUrl,
|
||||
DeleteRecycleApiUrl,
|
||||
DeleteRecycleCaseUrl,
|
||||
DeleteShareUrl,
|
||||
diffDataUrl,
|
||||
ExecuteCaseUrl,
|
||||
ExportDefinitionUrl,
|
||||
|
@ -59,6 +62,7 @@ import {
|
|||
GetModuleTreeUrl,
|
||||
GetPoolId,
|
||||
GetPoolOptionUrl,
|
||||
GetSharePageUrl,
|
||||
getSyncedCaseDetailUrl,
|
||||
GetTrashModuleCountUrl,
|
||||
GetTrashModuleTreeUrl,
|
||||
|
@ -74,6 +78,9 @@ import {
|
|||
RecycleCasePageUrl,
|
||||
RunCaseUrl,
|
||||
SaveOperationHistoryUrl,
|
||||
shareDetailUrl,
|
||||
shareModuleCountUrl,
|
||||
shareModuleTreeUrl,
|
||||
SortCaseUrl,
|
||||
SortDefinitionUrl,
|
||||
StopApiExportUrl,
|
||||
|
@ -94,6 +101,7 @@ import {
|
|||
UpdateMockStatusUrl,
|
||||
UpdateMockUrl,
|
||||
UpdateModuleUrl,
|
||||
UpdateShareUrl,
|
||||
UploadTempFileCaseUrl,
|
||||
UploadTempFileUrl,
|
||||
UploadTempMockFileUrl,
|
||||
|
@ -127,6 +135,7 @@ import {
|
|||
ApiDefinitionUpdateParams,
|
||||
BatchRecoverApiParams,
|
||||
CheckScheduleParams,
|
||||
CheckSharePsdType,
|
||||
CreateImportApiDefinitionScheduleParams,
|
||||
DefinitionHistoryItem,
|
||||
DefinitionHistoryPageParams,
|
||||
|
@ -136,19 +145,22 @@ import {
|
|||
ImportApiDefinitionParams,
|
||||
mockParams,
|
||||
RecoverDefinitionParams,
|
||||
ShareDetail,
|
||||
ShareDetailType,
|
||||
shareItem,
|
||||
UpdateScheduleParams,
|
||||
} from '@/models/apiTest/management';
|
||||
import type { BatchEditMockParams, MockDetail, MockParams, UpdateMockParams } from '@/models/apiTest/mock';
|
||||
import {
|
||||
import type {
|
||||
AddModuleParams,
|
||||
type BatchApiParams,
|
||||
BatchApiParams,
|
||||
CommonList,
|
||||
DragSortParams,
|
||||
ModuleTreeNode,
|
||||
MoveModules,
|
||||
TableQueryParams,
|
||||
TransferFileParams,
|
||||
} from '@/models/common';
|
||||
import { TableQueryParams } from '@/models/common';
|
||||
import { ResourcePoolItem } from '@/models/setting/resourcePool';
|
||||
|
||||
// 更新模块
|
||||
|
@ -635,3 +647,40 @@ export function logCaseReportBatchExport(data: BatchApiParams) {
|
|||
export function getCaseBatchExportParams(data: BatchApiParams) {
|
||||
return MSR.post({ url: `${GetCaseBatchExportParamsUrl}`, data });
|
||||
}
|
||||
|
||||
// 接口定义-接口文档
|
||||
// 接口测试-接口管理-新增分享
|
||||
export function addShare(data: ShareDetail) {
|
||||
return MSR.post({ url: `${AddShareUrl}`, data });
|
||||
}
|
||||
// 接口测试-接口管理-更新分享
|
||||
export function updateShare(data: ShareDetail) {
|
||||
return MSR.post({ url: `${UpdateShareUrl}`, data });
|
||||
}
|
||||
// 接口测试-接口管理-删除分享
|
||||
export function deleteShare(id: string) {
|
||||
return MSR.get({ url: DeleteShareUrl, params: id });
|
||||
}
|
||||
// 接口测试-接口管理-分享列表
|
||||
export function getSharePage(data: TableQueryParams) {
|
||||
return MSR.post<CommonList<shareItem>>({ url: `${GetSharePageUrl}`, data });
|
||||
}
|
||||
// 接口测试-接口管理-分享详情
|
||||
export function shareDetail(id: string) {
|
||||
return MSR.get<ShareDetailType>({ url: shareDetailUrl, params: id });
|
||||
}
|
||||
|
||||
// 接口测试-接口管理-校验分享密码
|
||||
export function checkSharePsd(data: CheckSharePsdType) {
|
||||
return MSR.post<CommonList<shareItem>>({ url: `${checkSharePsdUrl}`, data });
|
||||
}
|
||||
|
||||
// 接口测试-接口管理-分享模块树
|
||||
export function getShareModuleTree(data: ApiDefinitionGetModuleParams) {
|
||||
return MSR.post<ModuleTreeNode[]>({ url: shareModuleTreeUrl, data });
|
||||
}
|
||||
|
||||
// 接口测试-接口管理-分享模块数量
|
||||
export function getShareModuleCount(data: ApiDefinitionGetModuleParams) {
|
||||
return MSR.post({ url: shareModuleCountUrl, data });
|
||||
}
|
||||
|
|
|
@ -112,3 +112,13 @@ export const AddCaseUrl = '/api/case/add'; // 添加用例
|
|||
|
||||
export const GetPoolOptionUrl = '/api/test/pool-option'; // 获取接口资源池
|
||||
export const GetPoolId = '/api/test/get-pool/'; // 获取项目应用设置的资源池id
|
||||
|
||||
// 接口定义文档
|
||||
export const AddShareUrl = '/api/doc/share/add'; // 接口测试-接口管理-新增分享
|
||||
export const UpdateShareUrl = '/api/doc/share/update'; // 接口测试-接口管理-更新分享
|
||||
export const DeleteShareUrl = '/api/doc/share/delete'; // 接口测试-接口管理-删除分享
|
||||
export const GetSharePageUrl = '/api/doc/share/page'; // 接口测试-接口管理-分享列表
|
||||
export const checkSharePsdUrl = '/api/doc/share/check'; // 接口测试-接口管理-校验分享密码
|
||||
export const shareDetailUrl = '/api/doc/share/detail'; // 接口测试-接口管理-查看链接
|
||||
export const shareModuleTreeUrl = '/api/doc/share/module/tree'; // 接口测试-接口管理-模块树
|
||||
export const shareModuleCountUrl = '/api/doc/share/module/count'; // 接口测试-接口管理-模块数量
|
||||
|
|
|
@ -174,6 +174,7 @@ export default {
|
|||
'common.refresh': 'Refresh',
|
||||
'common.searchByIdName': 'Search by ID/name',
|
||||
'common.searchByIDNameTag': 'Search by ID/name/tag',
|
||||
'common.searchByName': 'Search by name',
|
||||
'common.archive': 'archive',
|
||||
'common.running': 'Running',
|
||||
'common.unExecute': 'Pending',
|
||||
|
|
|
@ -174,6 +174,7 @@ export default {
|
|||
'common.refresh': '刷新',
|
||||
'common.searchByIdName': '通过 ID/名称搜索',
|
||||
'common.searchByIDNameTag': '通过 ID/名称/标签搜索',
|
||||
'common.searchByName': '通过名称搜索',
|
||||
'common.archive': '归档',
|
||||
'common.running': '执行中',
|
||||
'common.unExecute': '未执行',
|
||||
|
|
|
@ -105,6 +105,7 @@ export interface ApiDefinitionGetModuleParams {
|
|||
projectId: string;
|
||||
versionId?: string;
|
||||
refId?: string;
|
||||
shareId?: string;
|
||||
}
|
||||
|
||||
// 环境-选中的模块
|
||||
|
@ -438,3 +439,38 @@ export interface diffSyncParams {
|
|||
deleteRedundantParam: boolean; // 是否删除多余参数
|
||||
apiCaseRequest: RequestParam; // 用例详情请求request
|
||||
}
|
||||
|
||||
export type ApiRangeType = 'ALL' | 'MODULE' | 'PATH' | 'TAG';
|
||||
|
||||
// 接口定义-接口文档-分享
|
||||
export interface ShareDetail {
|
||||
id?: string;
|
||||
name: string;
|
||||
apiRange: ApiRangeType; // 接口范围;全部接口(ALL)、模块(MODULE)、路径(PATH)、标签(TAG)
|
||||
rangeMatchSymbol: string; // 范围匹配符
|
||||
rangeMatchVal: string; // 范围匹配值;eg: 选中路径范围时, 该值作为路径匹配
|
||||
isPrivate: boolean; // 是否公开
|
||||
password: string; // 访问密码
|
||||
allowExport: boolean; // 允许导出
|
||||
projectId: string;
|
||||
invalidTime?: string; // 失效时间值
|
||||
invalidUnit?: string; // 失效时间单位;小时(HOUR)、天(DAY)、月(MONTH)、年(YEAR)
|
||||
}
|
||||
// 分享列表
|
||||
export interface shareItem extends ShareDetail {
|
||||
createTime: number;
|
||||
createUser: string;
|
||||
invalid: boolean;
|
||||
apiShareNum: number;
|
||||
deadline: number;
|
||||
}
|
||||
export interface CheckSharePsdType {
|
||||
docShareId: string;
|
||||
password: string;
|
||||
}
|
||||
// 分享详情
|
||||
export interface ShareDetailType {
|
||||
invalid: boolean;
|
||||
allowExport: boolean;
|
||||
isPrivate: boolean;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
import { defineStore } from 'pinia';
|
||||
|
||||
// 验证分享的文档是否校验过
|
||||
const useDocShareCheckStore = defineStore('shareCheckStore', {
|
||||
state: (): { verifiedDocs: string[] } => ({
|
||||
verifiedDocs: [],
|
||||
}),
|
||||
actions: {
|
||||
// 检查该 docShareId 和 userId 组合是否已经验证过
|
||||
isDocVerified(docShareId: string, userId: string) {
|
||||
const key: string = `verified_${docShareId}_${userId}`;
|
||||
return this.verifiedDocs.includes(key) || localStorage.getItem(key) === 'true';
|
||||
},
|
||||
// 将 docShareId 和 userId 组合标记为已验证
|
||||
markDocAsVerified(docShareId: string, userId: string) {
|
||||
const key: string = `verified_${docShareId}_${userId}`;
|
||||
if (!this.verifiedDocs.includes(key)) {
|
||||
this.verifiedDocs.push(key);
|
||||
localStorage.setItem(key, 'true');
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export default useDocShareCheckStore;
|
|
@ -0,0 +1,245 @@
|
|||
<template>
|
||||
<div class="h-[calc(100%-32px)]">
|
||||
<ApiPreview
|
||||
:detail="activeApiDetail"
|
||||
:protocols="props.selectedProtocols"
|
||||
@update-follow="activeApiDetail.follow = !activeApiDetail.follow"
|
||||
/>
|
||||
</div>
|
||||
<div class="doc-toggle-footer">
|
||||
<div v-if="props?.previousNode" class="doc-toggle" @click="toggleApiDetail('prev')">
|
||||
<MsIcon
|
||||
type="icon-icon_pull-left_outlined"
|
||||
:class="` text-[var(--color-text-4)] ${props.previousNode ? 'hover:text-[rgb(var(--primary-5))]' : ''}`"
|
||||
:size="16"
|
||||
/>
|
||||
<apiMethodName
|
||||
:method="
|
||||
props?.previousNode?.attachInfo.protocol === 'HTTP'
|
||||
? props.previousNode?.attachInfo.method ?? ''
|
||||
: props.previousNode?.attachInfo.protocol ?? ''
|
||||
"
|
||||
class="mr-[4px]"
|
||||
/>
|
||||
|
||||
<a-tooltip :content="`${props.previousNode?.name}`" position="tl" :disabled="!props.previousNode?.name">
|
||||
<div class="doc-toggle-name one-line-text">
|
||||
{{ props.previousNode?.name }}
|
||||
</div>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
<div v-else></div>
|
||||
<div v-if="props?.nextNode" class="doc-toggle justify-end" @click="toggleApiDetail('next')">
|
||||
<apiMethodName
|
||||
:method="
|
||||
props?.nextNode?.attachInfo.protocol === 'HTTP'
|
||||
? props.nextNode?.attachInfo.method ?? ''
|
||||
: props.nextNode?.attachInfo.protocol ?? ''
|
||||
"
|
||||
class="mr-[4px]"
|
||||
/>
|
||||
<a-tooltip :content="`${props?.nextNode?.name}`" position="tr">
|
||||
<div class="doc-toggle-name one-line-text">
|
||||
{{ props?.nextNode?.name }}
|
||||
</div>
|
||||
</a-tooltip>
|
||||
|
||||
<MsIcon
|
||||
type="icon-icon_pull-right_outlined"
|
||||
:class="` text-[var(--color-text-4)] ${props?.nextNode ? 'hover:text-[rgb(var(--primary-5))]' : ''}`"
|
||||
:size="16"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
|
||||
import { TabItem } from '@/components/pure/ms-editable-tab/types';
|
||||
import apiMethodName from '@/views/api-test/components/apiMethodName.vue';
|
||||
import ApiPreview from '@/views/api-test/management/components/management/api/preview/index.vue';
|
||||
|
||||
import { getDefinitionDetail } from '@/api/modules/api-test/management';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
|
||||
import { ProtocolItem } from '@/models/apiTest/common';
|
||||
import { ModuleTreeNode } from '@/models/common';
|
||||
import {
|
||||
ProtocolKeyEnum,
|
||||
RequestAuthType,
|
||||
RequestComposition,
|
||||
RequestDefinitionStatus,
|
||||
RequestMethods,
|
||||
ResponseComposition,
|
||||
} from '@/enums/apiEnum';
|
||||
|
||||
import { defaultBodyParams, defaultResponse, defaultResponseItem } from '@/views/api-test/components/config';
|
||||
import type { RequestParam } from '@/views/api-test/components/requestComposition/index.vue';
|
||||
import { parseRequestBodyFiles } from '@/views/api-test/components/utils';
|
||||
|
||||
const appStore = useAppStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
const props = defineProps<{
|
||||
apiInfo?: ModuleTreeNode | null;
|
||||
previousNode?: ModuleTreeNode | null;
|
||||
nextNode?: ModuleTreeNode | null;
|
||||
selectedProtocols: ProtocolItem[];
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'toggleDetail', type: string): void;
|
||||
}>();
|
||||
|
||||
const localProtocol = localStorage.getItem(ProtocolKeyEnum.API_NEW_PROTOCOL);
|
||||
const initDefaultId = `definition-${Date.now()}`;
|
||||
const defaultDefinitionParams: RequestParam = {
|
||||
type: 'api',
|
||||
definitionActiveKey: 'definition',
|
||||
id: initDefaultId,
|
||||
moduleId: '',
|
||||
protocol: localProtocol || 'HTTP',
|
||||
tags: [],
|
||||
status: RequestDefinitionStatus.PROCESSING,
|
||||
description: '',
|
||||
url: '',
|
||||
activeTab: RequestComposition.HEADER,
|
||||
label: t('apiTestDebug.newApi'),
|
||||
closable: true,
|
||||
method: RequestMethods.GET,
|
||||
unSaved: false,
|
||||
headers: [],
|
||||
body: cloneDeep(defaultBodyParams),
|
||||
query: [],
|
||||
rest: [],
|
||||
polymorphicName: '',
|
||||
name: '',
|
||||
path: '',
|
||||
projectId: '',
|
||||
uploadFileIds: [],
|
||||
linkFileIds: [],
|
||||
authConfig: {
|
||||
authType: RequestAuthType.NONE,
|
||||
basicAuth: {
|
||||
userName: '',
|
||||
password: '',
|
||||
},
|
||||
digestAuth: {
|
||||
userName: '',
|
||||
password: '',
|
||||
},
|
||||
},
|
||||
children: [
|
||||
{
|
||||
polymorphicName: 'MsCommonElement', // 协议多态名称,写死MsCommonElement
|
||||
assertionConfig: {
|
||||
enableGlobal: true,
|
||||
assertions: [],
|
||||
},
|
||||
postProcessorConfig: {
|
||||
enableGlobal: true,
|
||||
processors: [],
|
||||
},
|
||||
preProcessorConfig: {
|
||||
enableGlobal: true,
|
||||
processors: [],
|
||||
},
|
||||
},
|
||||
],
|
||||
otherConfig: {
|
||||
connectTimeout: 60000,
|
||||
responseTimeout: 60000,
|
||||
certificateAlias: '',
|
||||
followRedirects: true,
|
||||
autoRedirects: false,
|
||||
},
|
||||
responseActiveTab: ResponseComposition.BODY,
|
||||
response: cloneDeep(defaultResponse),
|
||||
responseDefinition: [cloneDeep(defaultResponseItem)],
|
||||
isNew: true,
|
||||
mode: 'definition',
|
||||
executeLoading: false,
|
||||
preDependency: [], // 前置依赖
|
||||
postDependency: [], // 后置依赖
|
||||
errorMessageInfo: {},
|
||||
};
|
||||
|
||||
const loading = ref(false);
|
||||
|
||||
const activeApiDetail = ref<RequestParam>(cloneDeep(defaultDefinitionParams));
|
||||
|
||||
async function initDetail() {
|
||||
if (props.apiInfo && props.apiInfo.id) {
|
||||
try {
|
||||
appStore.showLoading();
|
||||
loading.value = true;
|
||||
const res = await getDefinitionDetail(props.apiInfo.id);
|
||||
appStore.hideLoading();
|
||||
let parseRequestBodyResult;
|
||||
if (res.protocol === 'HTTP') {
|
||||
parseRequestBodyResult = parseRequestBodyFiles(res.request.body, res.response); // 解析请求体中的文件,将详情中的文件 id 集合收集,更新时以判断文件是否删除以及是否新上传的文件
|
||||
}
|
||||
const { request } = res;
|
||||
const defaultProps: Partial<TabItem> = {
|
||||
label: res.name,
|
||||
...res,
|
||||
...request,
|
||||
name: res.name || '-',
|
||||
num: res.num || '-',
|
||||
response: cloneDeep(defaultResponse),
|
||||
responseDefinition: res.response.map((e) => ({ ...e, responseActiveTab: ResponseComposition.BODY })),
|
||||
url: res.path,
|
||||
definitionActiveKey: 'preview',
|
||||
...parseRequestBodyResult,
|
||||
};
|
||||
activeApiDetail.value = {
|
||||
...cloneDeep(defaultDefinitionParams),
|
||||
...defaultProps,
|
||||
};
|
||||
nextTick(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
loading.value = false;
|
||||
appStore.hideLoading();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 切换上一条&下一条
|
||||
function toggleApiDetail(type: string) {
|
||||
emit('toggleDetail', type);
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.apiInfo,
|
||||
(val) => {
|
||||
if (val) {
|
||||
initDetail();
|
||||
}
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.doc-toggle-footer {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
z-index: 99;
|
||||
padding: 16px;
|
||||
height: 22px;
|
||||
|
||||
@apply flex w-full items-center justify-between bg-white;
|
||||
.doc-toggle {
|
||||
@apply flex flex-1 cursor-pointer items-center gap-2;
|
||||
.doc-toggle-name {
|
||||
max-width: 300px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -284,8 +284,13 @@
|
|||
</div>
|
||||
</template>
|
||||
</a-modal>
|
||||
<CreateShareModal v-model:visible="showShareModal" :edit-id="editId" @close="cancelHandler" />
|
||||
<ShareListDrawer v-model:visible="showShareListDrawer" @edit-or-create="editHandler" />
|
||||
<CreateShareModal
|
||||
v-model:visible="showShareModal"
|
||||
:record="editRecord"
|
||||
@close="cancelHandler"
|
||||
@load-list="loadShareList"
|
||||
/>
|
||||
<ShareListDrawer ref="shareListRef" v-model:visible="showShareListDrawer" @edit-or-create="editHandler" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
@ -332,6 +337,7 @@
|
|||
import { hasAnyPermission } from '@/utils/permission';
|
||||
|
||||
import { ProtocolItem } from '@/models/apiTest/common';
|
||||
import type { ShareDetail } from '@/models/apiTest/management';
|
||||
import { ApiDefinitionDetail, ApiDefinitionGetModuleParams } from '@/models/apiTest/management';
|
||||
import { DragSortParams, ModuleTreeNode } from '@/models/common';
|
||||
import { FilterType, ViewTypeEnum } from '@/enums/advancedFilterEnum';
|
||||
|
@ -1288,14 +1294,25 @@
|
|||
showShareListDrawer.value = true;
|
||||
}
|
||||
|
||||
const editId = ref<string>();
|
||||
function editHandler(id?: string) {
|
||||
editId.value = id;
|
||||
const editRecord = ref<ShareDetail>();
|
||||
// 编辑分享
|
||||
function editHandler(record?: ShareDetail) {
|
||||
editRecord.value = record;
|
||||
showShareModal.value = true;
|
||||
}
|
||||
const shareListRef = ref<InstanceType<typeof ShareListDrawer>>();
|
||||
|
||||
function cancelHandler() {
|
||||
editId.value = '';
|
||||
showShareModal.value = false;
|
||||
editRecord.value = undefined;
|
||||
}
|
||||
// 创建分享后打开分享列表
|
||||
function loadShareList() {
|
||||
if (!showShareListDrawer.value) {
|
||||
showShareListDrawer.value = true;
|
||||
} else {
|
||||
shareListRef.value?.searchList();
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<a-modal
|
||||
v-model:visible="innerVisible"
|
||||
:title="props.editId ? t('apiTestManagement.updateCreateShare') : t('apiTestManagement.newCreateShare')"
|
||||
:title="props?.record?.id ? t('apiTestManagement.updateCreateShare') : t('apiTestManagement.newCreateShare')"
|
||||
title-align="start"
|
||||
class="ms-modal-form"
|
||||
:cancel-button-props="{ disabled: confirmLoading }"
|
||||
|
@ -20,54 +20,49 @@
|
|||
</a-form-item>
|
||||
<a-form-item field="interfaceRange" :label="t('apiTestManagement.interfaceRange')" asterisk-position="end">
|
||||
<div class="flex w-full items-center gap-[8px]">
|
||||
<a-select v-model="form.type" class="w-[120px]">
|
||||
<a-select v-model="form.apiRange" class="w-[120px]">
|
||||
<a-option v-for="item in shareTypeOptions" :key="item.value" :value="item.value">
|
||||
{{ item.label }}
|
||||
</a-option>
|
||||
</a-select>
|
||||
|
||||
<a-tree-select
|
||||
v-if="form.type === 'module'"
|
||||
v-model:modelValue="form.moduleId"
|
||||
<MsTreeSelect
|
||||
v-if="form.apiRange === 'MODULE'"
|
||||
v-model:model-value="moduleIds"
|
||||
:data="moduleTree"
|
||||
allow-clear
|
||||
:multiple="true"
|
||||
:tree-checkable="true"
|
||||
:placeholder="t('common.pleaseSelect')"
|
||||
:field-names="{ title: 'name', key: 'id', children: 'children' }"
|
||||
:tree-props="{
|
||||
virtualListProps: {
|
||||
height: 200,
|
||||
threshold: 200,
|
||||
},
|
||||
}"
|
||||
:filter-tree-node="filterTreeNode"
|
||||
allow-search
|
||||
>
|
||||
<template #tree-slot-title="node">
|
||||
<a-tooltip :content="`${node.name}`" position="tl">
|
||||
<div class="one-line-text w-[300px]">{{ node.name }}</div>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-tree-select>
|
||||
<a-select v-if="form.type === 'tag'" v-model="form.operator" class="w-[120px]">
|
||||
/>
|
||||
<a-input
|
||||
v-if="form.apiRange === 'PATH'"
|
||||
v-model="form.rangeMatchVal"
|
||||
class="w-full"
|
||||
:max-length="255"
|
||||
:placeholder="t('project.environmental.http.pathPlaceholder')"
|
||||
/>
|
||||
<a-select v-if="form.apiRange === 'TAG'" v-model="form.rangeMatchSymbol" class="w-[120px]">
|
||||
<a-option v-for="item in tagOperators" :key="item.value" :value="item.value">
|
||||
{{ t(item.label) }}
|
||||
</a-option>
|
||||
</a-select>
|
||||
<MsTagsInput
|
||||
v-if="form.type === 'tag'"
|
||||
v-model:model-value="form.tags"
|
||||
v-if="form.apiRange === 'TAG'"
|
||||
v-model:model-value="tags"
|
||||
class="flex-1"
|
||||
placeholder="apiTestManagement.enterTheInputTag"
|
||||
allow-clear
|
||||
unique-value
|
||||
empty-priority-highest
|
||||
retain-input-value
|
||||
/>
|
||||
</div>
|
||||
</a-form-item>
|
||||
<a-form-item field="effectiveTime" :label="t('apiTestManagement.effectiveTime')" asterisk-position="end">
|
||||
<MsTimeSelectorVue v-model="form.time" @change="handleTimeChange" />
|
||||
<MsTimeSelectorVue v-model="invalidTimeValue" @change="handleTimeChange" />
|
||||
</a-form-item>
|
||||
<div class="mb-[16px] flex items-center">
|
||||
<a-switch v-model:model-value="form.passwordAccess" class="mr-[8px]" size="small" />
|
||||
<a-switch v-model:model-value="form.isPrivate" class="mr-[8px]" size="small" />
|
||||
{{ t('apiTestManagement.passwordAccess') }}
|
||||
</div>
|
||||
<a-form-item
|
||||
|
@ -77,7 +72,7 @@
|
|||
hide-asterisk
|
||||
hide-label
|
||||
:validate-trigger="['blur']"
|
||||
:rules="form.passwordAccess ? [{ validator: validatePassword }] : []"
|
||||
:rules="form.isPrivate ? [{ validator: validatePassword }] : []"
|
||||
>
|
||||
<a-input
|
||||
v-model:model-value="form.password"
|
||||
|
@ -105,72 +100,82 @@
|
|||
import { FormInstance, Message, SelectOptionData } from '@arco-design/web-vue';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
|
||||
import { CONTAINS, EQUAL } from '@/components/pure/ms-advance-filter/index';
|
||||
import { CONTAINS } from '@/components/pure/ms-advance-filter/index';
|
||||
import MsTagsInput from '@/components/pure/ms-tags-input/index.vue';
|
||||
import MsTimeSelectorVue from '@/components/pure/ms-time-selector/MsTimeSelector.vue';
|
||||
import MsTreeSelect from '@/components/pure/ms-tree-select/index.vue';
|
||||
|
||||
import { addShare, getEnvModules, updateShare } from '@/api/modules/api-test/management';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import { filterTreeNode, TreeNode } from '@/utils';
|
||||
import { useAppStore } from '@/store';
|
||||
import { TreeNode } from '@/utils';
|
||||
|
||||
import type { ShareDetail } from '@/models/apiTest/management';
|
||||
import type { ModuleTreeNode } from '@/models/common';
|
||||
import { OperatorEnum } from '@/enums/advancedFilterEnum';
|
||||
|
||||
const { t } = useI18n();
|
||||
const appStore = useAppStore();
|
||||
|
||||
const props = defineProps<{
|
||||
editId?: string;
|
||||
record?: ShareDetail;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'close'): void;
|
||||
(e: 'loadList'): void;
|
||||
}>();
|
||||
|
||||
const innerVisible = defineModel<boolean>('visible', {
|
||||
required: true,
|
||||
});
|
||||
const initForm = {
|
||||
id: '',
|
||||
|
||||
const initForm: ShareDetail = {
|
||||
name: '',
|
||||
type: 'allApi',
|
||||
moduleId: '',
|
||||
operator: OperatorEnum.CONTAINS,
|
||||
tags: [],
|
||||
time: '',
|
||||
passwordAccess: false,
|
||||
apiRange: 'ALL',
|
||||
rangeMatchSymbol: OperatorEnum.CONTAINS,
|
||||
rangeMatchVal: '',
|
||||
invalidTime: '',
|
||||
invalidUnit: '',
|
||||
isPrivate: false,
|
||||
password: '',
|
||||
allowExport: false,
|
||||
projectId: '',
|
||||
};
|
||||
|
||||
const form = ref({ ...initForm });
|
||||
const moduleTree = ref<TreeNode<ModuleTreeNode>[]>([]);
|
||||
const tags = ref<string[]>([]);
|
||||
const moduleIds = ref<string[]>([]);
|
||||
const invalidTimeValue = ref('');
|
||||
|
||||
const form = ref<ShareDetail>({ ...initForm });
|
||||
|
||||
const shareTypeOptions = ref<SelectOptionData>([
|
||||
{
|
||||
label: t('apiTestManagement.allApi'),
|
||||
value: 'allApi',
|
||||
value: 'ALL',
|
||||
},
|
||||
{
|
||||
label: t('apiTestManagement.module'),
|
||||
value: 'module',
|
||||
value: 'MODULE',
|
||||
},
|
||||
{
|
||||
label: t('apiTestManagement.path'),
|
||||
value: 'path',
|
||||
value: 'PATH',
|
||||
},
|
||||
{
|
||||
label: t('common.tag'),
|
||||
value: 'tag',
|
||||
value: 'TAG',
|
||||
},
|
||||
]);
|
||||
|
||||
const tagOperators = ref([CONTAINS, EQUAL]);
|
||||
const tagOperators = ref([CONTAINS]);
|
||||
|
||||
function handleTimeChange(value: string) {
|
||||
form.value.time = value;
|
||||
invalidTimeValue.value = value;
|
||||
}
|
||||
|
||||
const okText = computed(() => {
|
||||
return props.editId ? t('common.update') : t('common.newCreate');
|
||||
return props?.record?.id ? t('common.update') : t('common.newCreate');
|
||||
});
|
||||
|
||||
const validatePassword = (value: string | undefined, callback: (error?: string) => void) => {
|
||||
|
@ -187,12 +192,53 @@
|
|||
|
||||
const formRef = ref<FormInstance>();
|
||||
function handleCancel() {
|
||||
innerVisible.value = false;
|
||||
formRef.value?.resetFields();
|
||||
form.value = cloneDeep(initForm);
|
||||
tags.value = [];
|
||||
moduleIds.value = [];
|
||||
emit('close');
|
||||
}
|
||||
|
||||
const timeValueUnit = computed(() => {
|
||||
let time: string | undefined;
|
||||
let unit: string | undefined;
|
||||
|
||||
if (invalidTimeValue.value) {
|
||||
// 匹配时间部分和单位部分,时间部分为数字,单位部分为H, D, M, Y
|
||||
const match = invalidTimeValue.value.match(/^(\d+)([HDMY])$/);
|
||||
|
||||
if (match) {
|
||||
const [_, timeValue, symbol] = match;
|
||||
|
||||
time = timeValue; // 时间部分
|
||||
const unitSymbol = symbol; // 单位部分 (H, D, M, Y)
|
||||
|
||||
// 根据符号转换为全称
|
||||
switch (unitSymbol) {
|
||||
case 'H':
|
||||
unit = 'HOUR';
|
||||
break;
|
||||
case 'D':
|
||||
unit = 'DAY';
|
||||
break;
|
||||
case 'M':
|
||||
unit = 'MONTH';
|
||||
break;
|
||||
case 'Y':
|
||||
unit = 'YEAR';
|
||||
break;
|
||||
default:
|
||||
unit = undefined; // 如果有其他单位,默认不处理
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
time,
|
||||
unit,
|
||||
};
|
||||
});
|
||||
|
||||
const confirmLoading = ref<boolean>(false);
|
||||
|
||||
function handleConfirm() {
|
||||
|
@ -200,8 +246,29 @@
|
|||
if (!errors) {
|
||||
confirmLoading.value = true;
|
||||
try {
|
||||
// 等待联调
|
||||
const params: ShareDetail = {
|
||||
...form.value,
|
||||
invalidTime: timeValueUnit.value.time,
|
||||
invalidUnit: timeValueUnit.value.unit,
|
||||
projectId: appStore.currentProjectId,
|
||||
};
|
||||
if (form.value.apiRange === 'TAG') {
|
||||
params.rangeMatchVal = tags.value.join(',');
|
||||
}
|
||||
if (form.value.apiRange === 'MODULE') {
|
||||
params.rangeMatchVal = moduleIds.value.join(',');
|
||||
}
|
||||
if (props?.record?.id) {
|
||||
await updateShare(params);
|
||||
} else {
|
||||
await addShare(params);
|
||||
}
|
||||
|
||||
emit('loadList');
|
||||
handleCancel();
|
||||
Message.success(props?.record?.id ? t('common.updateSuccess') : t('common.createSuccess'));
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
} finally {
|
||||
confirmLoading.value = false;
|
||||
|
@ -209,6 +276,60 @@
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
const moduleTree = ref<TreeNode<ModuleTreeNode>[]>([]);
|
||||
async function initModuleTree() {
|
||||
try {
|
||||
const res = await getEnvModules({
|
||||
projectId: appStore.currentProjectId,
|
||||
});
|
||||
moduleTree.value = res.moduleTree;
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
function getOriginalUnit() {
|
||||
switch (props.record?.invalidUnit) {
|
||||
case 'HOUR':
|
||||
return 'H';
|
||||
case 'DAY':
|
||||
return 'D';
|
||||
case 'MONTH':
|
||||
return 'M';
|
||||
case 'YEAR':
|
||||
return 'Y';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
function initDetail() {
|
||||
if (props.record?.id) {
|
||||
form.value = {
|
||||
...props.record,
|
||||
};
|
||||
const { rangeMatchVal, invalidTime } = form.value;
|
||||
if (form.value.apiRange === 'TAG') {
|
||||
tags.value = rangeMatchVal.split(',');
|
||||
}
|
||||
if (form.value.apiRange === 'MODULE') {
|
||||
moduleIds.value = rangeMatchVal.split(',');
|
||||
}
|
||||
invalidTimeValue.value = `${invalidTime}${getOriginalUnit()}`;
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => innerVisible.value,
|
||||
(val) => {
|
||||
if (val) {
|
||||
initModuleTree();
|
||||
initDetail();
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<apiMethodName :method="previewDetail.method as RequestMethods" tag-size="small" is-tag />
|
||||
</template>
|
||||
<template #titleAppend>
|
||||
<a-tooltip :content="t('report.detail.api.copyLink')">
|
||||
<a-tooltip v-if="!docShareId" :content="t('report.detail.api.copyLink')">
|
||||
<MsIcon
|
||||
type="icon-icon_copy_outlined"
|
||||
class="cursor-pointer text-[var(--color-text-4)]"
|
||||
|
@ -15,7 +15,7 @@
|
|||
@click="share"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip :content="t(previewDetail.follow ? 'common.forked' : 'common.notForked')">
|
||||
<a-tooltip v-if="!docShareId" :content="t(previewDetail.follow ? 'common.forked' : 'common.notForked')">
|
||||
<MsIcon
|
||||
v-permission="['PROJECT_API_DEFINITION:READ+UPDATE']"
|
||||
:loading="followLoading"
|
||||
|
@ -26,10 +26,22 @@
|
|||
@click="toggleFollowReview"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<!-- 分享导出 TODO 联调 -->
|
||||
<a-tooltip v-if="docShareId && shareDetailInfo?.allowExport" :content="t('common.export')">
|
||||
<MsIcon
|
||||
type="icon-icon_top-align_outlined"
|
||||
class="cursor-pointer text-[var(--color-text-4)]"
|
||||
:size="16"
|
||||
@click="exportShare"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</MsDetailCard>
|
||||
</div>
|
||||
<a-tabs v-model:active-key="activeKey" animation lazy-load>
|
||||
<div v-if="docShareId" class="px-[16px]">
|
||||
<detailTab :detail="previewDetail" :protocols="props.protocols" />
|
||||
</div>
|
||||
<a-tabs v-else v-model:active-key="activeKey" animation lazy-load>
|
||||
<a-tab-pane key="detail" :title="t('apiTestManagement.detail')" class="px-[18px] py-[16px]">
|
||||
<detailTab :detail="previewDetail" :protocols="props.protocols" />
|
||||
</a-tab-pane>
|
||||
|
@ -64,6 +76,7 @@
|
|||
import { toggleFollowDefinition } from '@/api/modules/api-test/management';
|
||||
|
||||
import { ProtocolItem } from '@/models/apiTest/common';
|
||||
import { ShareDetailType } from '@/models/apiTest/management';
|
||||
import { RequestMethods } from '@/enums/apiEnum';
|
||||
|
||||
import { getValidRequestTableParams } from '@/views/api-test/components/utils';
|
||||
|
@ -78,6 +91,8 @@
|
|||
const { t } = useI18n();
|
||||
|
||||
const previewDetail = ref<RequestParam>(cloneDeep(props.detail));
|
||||
const docShareId: string | undefined = inject('docShareId');
|
||||
const shareDetailInfo = inject<Ref<ShareDetailType>>('shareDetailInfo');
|
||||
|
||||
watch(
|
||||
() => props.detail.id,
|
||||
|
@ -174,6 +189,8 @@
|
|||
}
|
||||
|
||||
const activeKey = ref('detail');
|
||||
// 导出分享 TODO 等待联调
|
||||
function exportShare() {}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
|
|
@ -30,21 +30,21 @@
|
|||
</div>
|
||||
<div
|
||||
v-for="item in shareList"
|
||||
:key="item.value"
|
||||
:class="[`share-option-item ${item.value === currentShare ? 'share-option-item-active' : ''} w-full`]"
|
||||
:key="item.id"
|
||||
:class="[`share-option-item ${item.id === currentShare ? 'share-option-item-active' : ''} w-full`]"
|
||||
@click="changeShare(item)"
|
||||
>
|
||||
<div class="flex w-full items-center justify-between">
|
||||
<a-tooltip :content="item.label">
|
||||
<a-tooltip :content="item.name">
|
||||
<div class="one-line-text max-w-[100px]">
|
||||
{{ item.label }}
|
||||
{{ item.name }}
|
||||
</div>
|
||||
</a-tooltip>
|
||||
<MsIcon
|
||||
type="icon-icon_copy_outlined"
|
||||
class="cursor-pointer text-[var(--color-text-4)] hover:text-[rgb(var(--primary-5))]"
|
||||
:size="16"
|
||||
@click="copyShareLink(item.value)"
|
||||
@click="copyShareLink(item.id as string)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -55,9 +55,18 @@
|
|||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { SelectOptionData } from '@arco-design/web-vue';
|
||||
import { useClipboard } from '@vueuse/core';
|
||||
import { Message, SelectOptionData } from '@arco-design/web-vue';
|
||||
|
||||
import { getSharePage } from '@/api/modules/api-test/management';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import { useAppStore } from '@/store';
|
||||
|
||||
import type { shareItem } from '@/models/apiTest/management';
|
||||
|
||||
const appStore = useAppStore();
|
||||
|
||||
const { copy, isSupported } = useClipboard({ legacy: true });
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
|
@ -66,11 +75,6 @@
|
|||
(e: 'showShareList'): void;
|
||||
}>();
|
||||
|
||||
const isSelectedShare = ref(false);
|
||||
function visibleChange(val: boolean) {
|
||||
isSelectedShare.value = val;
|
||||
}
|
||||
|
||||
const internalShare = ref([
|
||||
{
|
||||
label: t('apiTestManagement.shareList'),
|
||||
|
@ -82,16 +86,7 @@
|
|||
},
|
||||
]);
|
||||
|
||||
const shareList = ref([
|
||||
{
|
||||
label: '001',
|
||||
value: '001',
|
||||
},
|
||||
{
|
||||
label: '002',
|
||||
value: '002',
|
||||
},
|
||||
]);
|
||||
const shareList = ref<shareItem[]>();
|
||||
|
||||
const currentShare = ref<string>('');
|
||||
// 创建分享
|
||||
|
@ -104,6 +99,14 @@
|
|||
emit('showShareList');
|
||||
}
|
||||
|
||||
const isSelectedShare = ref(false);
|
||||
function visibleChange(val: boolean) {
|
||||
isSelectedShare.value = val;
|
||||
if (!val) {
|
||||
currentShare.value = '';
|
||||
}
|
||||
}
|
||||
|
||||
function changeShare(item: SelectOptionData) {
|
||||
currentShare.value = item.value as string;
|
||||
switch (item.value) {
|
||||
|
@ -118,8 +121,44 @@
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 快捷复制分享
|
||||
function copyShareLink(value: string) {}
|
||||
function copyShareLink(value: string) {
|
||||
if (isSupported) {
|
||||
// 判断路由中是不是有dId参数,有的话只是修改当前的值就可以了
|
||||
const url = window.location.href;
|
||||
const dIdParam = `&docShareId=${value}`;
|
||||
copy(`${url}${dIdParam}`);
|
||||
Message.success(t('apiTestManagement.shareUrlCopied'));
|
||||
} else {
|
||||
Message.error(t('common.copyNotSupport'));
|
||||
}
|
||||
}
|
||||
|
||||
async function initShareList() {
|
||||
try {
|
||||
const res = await getSharePage({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
sort: {},
|
||||
combineSearch: {
|
||||
searchMode: 'AND',
|
||||
conditions: [],
|
||||
},
|
||||
projectId: appStore.currentProjectId,
|
||||
filter: {},
|
||||
});
|
||||
|
||||
shareList.value = res.list;
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
initShareList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
|
@ -149,7 +188,7 @@
|
|||
}
|
||||
}
|
||||
.share-option-item {
|
||||
padding: 3px 8px;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
@apply flex w-full items-center justify-between;
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
</a-button>
|
||||
<a-input-search
|
||||
v-model:model-value="keyword"
|
||||
:placeholder="t('apiScenario.params.searchPlaceholder')"
|
||||
:placeholder="t('common.searchByName')"
|
||||
allow-clear
|
||||
class="mx-[8px] w-[240px]"
|
||||
@search="searchList"
|
||||
|
@ -23,19 +23,21 @@
|
|||
/>
|
||||
</div>
|
||||
<MsBaseTable v-bind="propsRes" no-disable :row-class="getRowClass" v-on="propsEvent">
|
||||
<template #accessRestriction="{ record }">
|
||||
{{ record.accessRestriction ? t('apiTestManagement.passwordView') : t('apiTestManagement.publicityView') }}
|
||||
<template #isPrivate="{ record }">
|
||||
{{ record.isPrivate ? t('apiTestManagement.passwordView') : t('apiTestManagement.publicityView') }}
|
||||
</template>
|
||||
<template #operation="{ record }">
|
||||
<MsButton class="!mx-0" @click="viewLink(record)">
|
||||
{{ t('apiTestManagement.viewLink') }}
|
||||
</MsButton>
|
||||
<a-tooltip :disabled="!!record.apiShareNum" :content="t('apiTestManagement.apiShareNumberTip')">
|
||||
<MsButton class="!mx-0" :disabled="!record.apiShareNum" @click="viewLink(record)">
|
||||
{{ t('apiTestManagement.viewLink') }}
|
||||
</MsButton>
|
||||
</a-tooltip>
|
||||
<a-divider direction="vertical" :margin="8" />
|
||||
<MsButton class="!mx-0" @click="editShare(record.id)">
|
||||
<MsButton class="!mx-0" @click="editShare(record)">
|
||||
{{ t('common.edit') }}
|
||||
</MsButton>
|
||||
<a-divider direction="vertical" :margin="8" />
|
||||
<MsButton class="!mx-0" @click="deleteShare(record)">
|
||||
<MsButton class="!mx-0" @click="deleteHandler(record)">
|
||||
{{ t('common.delete') }}
|
||||
</MsButton>
|
||||
</template>
|
||||
|
@ -47,6 +49,7 @@
|
|||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
|
||||
|
@ -54,12 +57,14 @@
|
|||
import type { MsTableColumn } from '@/components/pure/ms-table/type';
|
||||
import useTable from '@/components/pure/ms-table/useTable';
|
||||
|
||||
import { deleteShare, getSharePage } from '@/api/modules/api-test/management';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useModal from '@/hooks/useModal';
|
||||
import useOpenNewPage from '@/hooks/useOpenNewPage';
|
||||
import { useTableStore } from '@/store';
|
||||
import { useAppStore, useTableStore } from '@/store';
|
||||
import { characterLimit } from '@/utils';
|
||||
|
||||
import type { ShareDetail, shareItem } from '@/models/apiTest/management';
|
||||
import { ApiTestRouteEnum } from '@/enums/routeEnum';
|
||||
import { TableKeyEnum } from '@/enums/tableEnum';
|
||||
|
||||
|
@ -70,9 +75,10 @@
|
|||
const { t } = useI18n();
|
||||
|
||||
const tableStore = useTableStore();
|
||||
const appStore = useAppStore();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'editOrCreate', id?: string): void;
|
||||
(e: 'editOrCreate', record?: ShareDetail): void;
|
||||
}>();
|
||||
|
||||
const innerVisible = defineModel<boolean>('visible', {
|
||||
|
@ -90,8 +96,14 @@
|
|||
},
|
||||
{
|
||||
title: 'apiTestManagement.accessRestriction',
|
||||
slotName: 'accessRestriction',
|
||||
dataIndex: 'accessRestriction',
|
||||
slotName: 'isPrivate',
|
||||
dataIndex: 'isPrivate',
|
||||
showDrag: true,
|
||||
},
|
||||
{
|
||||
title: 'apiTestManagement.apiShareNum',
|
||||
slotName: 'apiShareNum',
|
||||
dataIndex: 'apiShareNum',
|
||||
showDrag: true,
|
||||
},
|
||||
{
|
||||
|
@ -103,7 +115,7 @@
|
|||
sortDirections: ['ascend', 'descend'],
|
||||
sorter: true,
|
||||
},
|
||||
width: 150,
|
||||
width: 200,
|
||||
showDrag: true,
|
||||
},
|
||||
{
|
||||
|
@ -129,30 +141,45 @@
|
|||
},
|
||||
];
|
||||
|
||||
const { propsRes, propsEvent, loadList, setKeyword } = useTable(undefined, {
|
||||
tableKey: TableKeyEnum.SYSTEM_RESOURCE_POOL_CAPACITY,
|
||||
scroll: { y: 'auto' },
|
||||
selectable: false,
|
||||
showSetting: true,
|
||||
heightUsed: 310,
|
||||
showSelectAll: false,
|
||||
});
|
||||
const { propsRes, propsEvent, loadList, setLoadListParams } = useTable(
|
||||
getSharePage,
|
||||
{
|
||||
tableKey: TableKeyEnum.SYSTEM_RESOURCE_POOL_CAPACITY,
|
||||
scroll: { x: '100%' },
|
||||
selectable: false,
|
||||
showSetting: true,
|
||||
heightUsed: 310,
|
||||
showSelectAll: false,
|
||||
},
|
||||
(item) => ({
|
||||
...item,
|
||||
deadline: item.deadline ? dayjs(item.deadline).format('YYYY-MM-DD HH:mm:ss') : '-',
|
||||
})
|
||||
);
|
||||
|
||||
// 查看链接
|
||||
function viewLink(record: any) {
|
||||
function viewLink(record: shareItem) {
|
||||
openNewPage(ApiTestRouteEnum.API_TEST_MANAGEMENT, {
|
||||
dId: record.id,
|
||||
pId: record.projectId,
|
||||
docShareId: record.id,
|
||||
});
|
||||
}
|
||||
|
||||
// 编辑
|
||||
function editShare(id: string) {
|
||||
emit('editOrCreate', id);
|
||||
function editShare(record: ShareDetail) {
|
||||
emit('editOrCreate', record);
|
||||
}
|
||||
|
||||
const keyword = ref<string>('');
|
||||
function searchList() {
|
||||
setLoadListParams({
|
||||
keyword: keyword.value,
|
||||
projectId: appStore.currentProjectId,
|
||||
});
|
||||
loadList();
|
||||
}
|
||||
|
||||
// 删除
|
||||
function deleteShare(record: any) {
|
||||
function deleteHandler(record: shareItem) {
|
||||
openModal({
|
||||
type: 'error',
|
||||
title: t('common.deleteConfirmTitle', { name: characterLimit(record.name) }),
|
||||
|
@ -164,8 +191,13 @@
|
|||
maskClosable: false,
|
||||
onBeforeOk: async () => {
|
||||
try {
|
||||
Message.success(t('caseManagement.featureCase.deleteSuccess'));
|
||||
if (record.id) {
|
||||
await deleteShare(record.id);
|
||||
Message.success(t('caseManagement.featureCase.deleteSuccess'));
|
||||
searchList();
|
||||
}
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
}
|
||||
},
|
||||
|
@ -173,16 +205,21 @@
|
|||
});
|
||||
}
|
||||
|
||||
const keyword = ref<string>('');
|
||||
|
||||
function searchList() {}
|
||||
|
||||
function getRowClass(record: any) {
|
||||
return record.expired ? 'grey-row-class' : '';
|
||||
function getRowClass(record: shareItem) {
|
||||
return record.invalid ? 'grey-row-class' : '';
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
searchList();
|
||||
watch(
|
||||
() => innerVisible.value,
|
||||
(val) => {
|
||||
if (val) {
|
||||
searchList();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
defineExpose({
|
||||
searchList,
|
||||
});
|
||||
|
||||
await tableStore.initColumn(TableKeyEnum.SYSTEM_RESOURCE_POOL_CAPACITY, columns, 'drawer');
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div>
|
||||
<template v-if="!props.isModal">
|
||||
<div v-if="!props.readOnly && !props.trash" class="mb-[8px] flex items-center gap-[8px]">
|
||||
<div v-if="!props.readOnly && !props.trash && !props.docShareId" class="mb-[8px] flex items-center gap-[8px]">
|
||||
<a-button
|
||||
v-permission="['PROJECT_API_DEFINITION:READ+ADD']"
|
||||
type="primary"
|
||||
|
@ -34,21 +34,26 @@
|
|||
:folder-name="t('apiTestManagement.allApi')"
|
||||
:all-count="allFileCount"
|
||||
:active-folder="selectedKeys[0] as string"
|
||||
:show-expand-api="!props.readOnly && !props.trash"
|
||||
:show-expand-api="!props.readOnly && !props.trash && !props.docShareId"
|
||||
@set-active-folder="setActiveFolder"
|
||||
@change-api-expand="changeApiExpand"
|
||||
@selected-protocols-change="selectedProtocolsChange"
|
||||
>
|
||||
<template #expandRight>
|
||||
<popConfirm
|
||||
v-if="hasAnyPermission(['PROJECT_API_DEFINITION:READ+ADD']) && !props.readOnly && !props.trash"
|
||||
v-if="
|
||||
hasAnyPermission(['PROJECT_API_DEFINITION:READ+ADD']) &&
|
||||
!props.readOnly &&
|
||||
!props.trash &&
|
||||
!props.docShareId
|
||||
"
|
||||
mode="add"
|
||||
:all-names="rootModulesName"
|
||||
parent-id="NONE"
|
||||
:add-module-api="addModule"
|
||||
@add-finish="handleAddFinish"
|
||||
>
|
||||
<MsButton type="icon" class="!mr-0 p-[2px]">
|
||||
<MsButton v-if="!props.docShareId" type="icon" class="!mr-0 p-[2px]">
|
||||
<MsIcon
|
||||
type="icon-icon_create_planarity"
|
||||
size="18"
|
||||
|
@ -56,6 +61,11 @@
|
|||
/>
|
||||
</MsButton>
|
||||
</popConfirm>
|
||||
<a-tooltip v-if="props.docShareId && shareDetailInfo?.allowExport" :content="t('common.export')">
|
||||
<MsButton type="icon" status="secondary" class="!mr-[4px] p-[4px]" @click="changeApiExpand">
|
||||
<MsIcon type="icon-icon_top-align_outlined" :size="16" @click="exportShare" />
|
||||
</MsButton>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</TreeFolderAll>
|
||||
</template>
|
||||
|
@ -162,6 +172,8 @@
|
|||
getModuleCount,
|
||||
getModuleTree,
|
||||
getModuleTreeOnlyModules,
|
||||
getShareModuleCount,
|
||||
getShareModuleTree,
|
||||
getTrashModuleCount,
|
||||
getTrashModuleTree,
|
||||
moveModule,
|
||||
|
@ -173,11 +185,11 @@
|
|||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useModal from '@/hooks/useModal';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
import { characterLimit, mapTree } from '@/utils';
|
||||
import { characterLimit, filterTree, mapTree, TreeNode } from '@/utils';
|
||||
import { getLocalStorage } from '@/utils/local-storage';
|
||||
import { hasAnyPermission } from '@/utils/permission';
|
||||
|
||||
import { ApiDefinitionGetModuleParams } from '@/models/apiTest/management';
|
||||
import { ApiDefinitionGetModuleParams, ShareDetailType } from '@/models/apiTest/management';
|
||||
import { ModuleTreeNode } from '@/models/common';
|
||||
import { ProtocolKeyEnum } from '@/enums/apiEnum';
|
||||
|
||||
|
@ -189,6 +201,7 @@
|
|||
activeNodeId?: string | number; // 当前选中节点 id
|
||||
isModal?: boolean; // 是否弹窗模式,只读且只可见模块树
|
||||
trash?: boolean; // 是否是回收站
|
||||
docShareId?: string; // 是否分享文档
|
||||
}>(),
|
||||
{
|
||||
activeModule: 'all',
|
||||
|
@ -207,6 +220,8 @@
|
|||
'updateApiNode',
|
||||
'deleteNode',
|
||||
'execute',
|
||||
'openCurrentNode',
|
||||
'exportShare',
|
||||
]);
|
||||
|
||||
const appStore = useAppStore();
|
||||
|
@ -253,14 +268,14 @@
|
|||
const virtualListProps = computed(() => {
|
||||
if (props.readOnly || props.isModal) {
|
||||
return {
|
||||
height: 'calc(60vh - 190px)',
|
||||
height: props.docShareId ? 'calc(60vh - 150px)' : 'calc(60vh - 190px)',
|
||||
threshold: 200,
|
||||
fixedSize: true,
|
||||
buffer: 15, // 缓冲区默认 10 的时候,虚拟滚动的底部 padding 计算有问题
|
||||
};
|
||||
}
|
||||
return {
|
||||
height: 'calc(100vh - 273px)',
|
||||
height: props.docShareId ? 'calc(100vh - 233px)' : 'calc(100vh - 273px)',
|
||||
threshold: 200,
|
||||
fixedSize: true,
|
||||
buffer: 15, // 缓冲区默认 10 的时候,虚拟滚动的底部 padding 计算有问题
|
||||
|
@ -268,7 +283,7 @@
|
|||
});
|
||||
|
||||
const moduleKeyword = ref(''); // 只用于前端过滤树节点,不传入后台查询!!!
|
||||
const folderTree = ref<ModuleTreeNode[]>([]);
|
||||
const folderTree = ref<TreeNode<ModuleTreeNode>[]>([]);
|
||||
const focusNodeKey = ref<string | number>('');
|
||||
const selectedKeys = ref<Array<string | number>>([props.activeModule]);
|
||||
const loading = ref(false);
|
||||
|
@ -340,6 +355,29 @@
|
|||
protocols: selectedProtocols.value,
|
||||
moduleIds: [],
|
||||
});
|
||||
// 分享详情
|
||||
const shareDetailInfo = inject<Ref<ShareDetailType>>('shareDetailInfo');
|
||||
|
||||
const apiNodes = ref<TreeNode<ModuleTreeNode>[]>([]);
|
||||
const currentNode = ref<TreeNode<ModuleTreeNode> | null>(null);
|
||||
// 设置当前节点
|
||||
const setCurrentNode = (id: string, isSelectedNode = false) => {
|
||||
currentNode.value = apiNodes.value.find((node) => node.id === id) || null;
|
||||
selectedKeys.value = [id];
|
||||
emit('openCurrentNode', currentNode.value, apiNodes.value, isSelectedNode);
|
||||
};
|
||||
|
||||
const getTreeNodeList = (nodes: TreeNode<ModuleTreeNode>[]) => {
|
||||
nodes.forEach((node: TreeNode<ModuleTreeNode>) => {
|
||||
if (node.type === 'API') {
|
||||
apiNodes.value.push(node);
|
||||
}
|
||||
if (node.children) {
|
||||
getTreeNodeList(node.children);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
async function initModuleCount(params: ApiDefinitionGetModuleParams) {
|
||||
try {
|
||||
lastModuleCountParam.value = params;
|
||||
|
@ -378,8 +416,45 @@
|
|||
emit('folderNodeSelect', _selectedKeys, offspringIds);
|
||||
} else if (node.type === 'API') {
|
||||
emit('clickApiNode', node);
|
||||
if (props.docShareId) {
|
||||
setCurrentNode(node.id, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 分享模块count
|
||||
async function initShareModuleCount(params: ApiDefinitionGetModuleParams) {
|
||||
try {
|
||||
modulesCount.value = await getShareModuleCount({
|
||||
...params,
|
||||
shareId: props.docShareId,
|
||||
});
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
// 分享模块树
|
||||
async function initShareModuleTree() {
|
||||
await initShareModuleCount(lastModuleCountParam.value);
|
||||
let res;
|
||||
res = await getShareModuleTree({
|
||||
keyword: '',
|
||||
protocols: selectedProtocols.value,
|
||||
projectId: appStore.currentProjectId,
|
||||
moduleIds: [],
|
||||
});
|
||||
res = mapTree<ModuleTreeNode>(res, (node) => ({
|
||||
...node,
|
||||
count: modulesCount.value[node.id] || 0,
|
||||
draggable: node.id !== 'root' && !(props.readOnly || props.isModal),
|
||||
disabled: props.readOnly || props.isModal ? node.id === selectedKeys.value[0] : false,
|
||||
hideMoreAction: node.id === 'root' || !!props.docShareId,
|
||||
}));
|
||||
|
||||
// 过滤count为0 且类型为 MODULE 的节点
|
||||
res = filterTree(res, (node) => !(node.count === 0 && node.type === 'MODULE'));
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化模块树
|
||||
|
@ -389,7 +464,14 @@
|
|||
try {
|
||||
loading.value = true;
|
||||
let res;
|
||||
if (props.trash) {
|
||||
if (props.docShareId) {
|
||||
res = await initShareModuleTree();
|
||||
folderTree.value = res;
|
||||
getTreeNodeList(folderTree.value);
|
||||
if (apiNodes.value.length) {
|
||||
setCurrentNode(apiNodes.value[0].id);
|
||||
}
|
||||
} else if (props.trash) {
|
||||
res = await getTrashModuleTree({
|
||||
// 回收站下的模块
|
||||
keyword: '',
|
||||
|
@ -429,7 +511,7 @@
|
|||
disabled: e.id === selectedKeys.value[0],
|
||||
};
|
||||
});
|
||||
} else {
|
||||
} else if (!props.docShareId) {
|
||||
folderTree.value = mapTree<ModuleTreeNode>(res, (e, fullPath) => {
|
||||
// 拼接当前节点的完整路径
|
||||
nodePathObj[e.id] = {
|
||||
|
@ -452,7 +534,9 @@
|
|||
console.log(error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
initModuleCount(lastModuleCountParam.value);
|
||||
if (!props.docShareId) {
|
||||
initModuleCount(lastModuleCountParam.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -462,6 +546,36 @@
|
|||
initModules();
|
||||
}
|
||||
|
||||
// 获取上一条ID
|
||||
const getPreviousApiId = () => {
|
||||
if (!currentNode.value) return null;
|
||||
const index = apiNodes.value.indexOf(currentNode.value);
|
||||
return index > 0 ? apiNodes.value[index - 1].id : null;
|
||||
};
|
||||
|
||||
// 获取下一条ID
|
||||
const getNextApiId = () => {
|
||||
if (!currentNode.value) return null;
|
||||
const index = apiNodes.value.indexOf(currentNode.value);
|
||||
return index < apiNodes.value.length - 1 ? apiNodes.value[index + 1].id : null;
|
||||
};
|
||||
|
||||
// 上一条
|
||||
const previousApi = () => {
|
||||
const previousId = getPreviousApiId();
|
||||
if (previousId) {
|
||||
setCurrentNode(previousId);
|
||||
}
|
||||
};
|
||||
|
||||
// 下一条
|
||||
const nextApi = () => {
|
||||
const nextId = getNextApiId();
|
||||
if (nextId) {
|
||||
setCurrentNode(nextId);
|
||||
}
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.isExpandAll,
|
||||
(val) => {
|
||||
|
@ -630,6 +744,11 @@
|
|||
}
|
||||
}
|
||||
|
||||
// 导出分享
|
||||
function exportShare() {
|
||||
emit('exportShare');
|
||||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
initProtocolList();
|
||||
});
|
||||
|
@ -642,6 +761,9 @@
|
|||
refresh,
|
||||
initModuleCount,
|
||||
setActiveFolder,
|
||||
setCurrentNode,
|
||||
previousApi,
|
||||
nextApi,
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
<moduleTree
|
||||
ref="moduleTreeRef"
|
||||
:active-node-id="activeNodeId"
|
||||
:doc-share-id="docShareId"
|
||||
@init="handleModuleInit"
|
||||
@new-api="newApi"
|
||||
@import="importDrawerVisible = true"
|
||||
|
@ -16,9 +17,10 @@
|
|||
@update-api-node="handleUpdateApiNode"
|
||||
@delete-node="handleDeleteApiFromModuleTree"
|
||||
@execute="handleExecute"
|
||||
@open-current-node="openCurrentNode"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<div v-if="!docShareId" class="flex-1">
|
||||
<a-divider class="!my-0 !mb-0" />
|
||||
<div class="case h-[40px] !px-[24px]" @click="setActiveFolder('recycle')">
|
||||
<div class="flex items-center" :class="getActiveClass('recycle')">
|
||||
|
@ -33,6 +35,7 @@
|
|||
<template #second>
|
||||
<div class="relative flex h-full flex-col">
|
||||
<div
|
||||
v-if="!docShareId"
|
||||
id="managementContainer"
|
||||
:class="['absolute z-[102] h-full w-full', importDrawerVisible ? '' : 'invisible']"
|
||||
style="transition: all 0.3s"
|
||||
|
@ -46,6 +49,7 @@
|
|||
/>
|
||||
</div>
|
||||
<management
|
||||
v-if="!docShareId"
|
||||
ref="managementRef"
|
||||
:module-tree="folderTree"
|
||||
:active-module="activeModule"
|
||||
|
@ -54,9 +58,55 @@
|
|||
@import="importDrawerVisible = true"
|
||||
@handle-adv-search="handleAdvSearch"
|
||||
/>
|
||||
|
||||
<ApiSharePreview
|
||||
v-if="docShareId"
|
||||
:selected-protocols="protocols"
|
||||
:api-info="currentNode"
|
||||
:previous-node="previousNode"
|
||||
:next-node="nextNode"
|
||||
@toggle-detail="toggleDetail"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</MsSplitBox>
|
||||
<!-- 分享密码校验 -->
|
||||
<a-modal
|
||||
v-model:visible="checkPsdModal"
|
||||
:mask-closable="false"
|
||||
:closable="false"
|
||||
:mask="true"
|
||||
title-align="start"
|
||||
class="ms-modal-upload ms-modal-medium ms-modal-share"
|
||||
:width="280"
|
||||
unmount-on-close
|
||||
@close="closeShareHandler"
|
||||
>
|
||||
<div class="no-resource-svg"></div>
|
||||
<a-form ref="formRef" :rules="rules" :model="checkForm" layout="vertical">
|
||||
<a-form-item
|
||||
class="password-form mb-0"
|
||||
field="password"
|
||||
:label="t('apiTestManagement.effectiveTime')"
|
||||
hide-asterisk
|
||||
hide-label
|
||||
:validate-trigger="['blur']"
|
||||
>
|
||||
<a-input-password
|
||||
v-model="checkForm.password"
|
||||
:max-length="6"
|
||||
:placeholder="t('apiTestManagement.sharePasswordPlaceholder')"
|
||||
allow-clear
|
||||
autocomplete="new-password"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<template #footer>
|
||||
<a-button type="primary" :loading="checkLoading" :disabled="!checkForm.password" @click="handleCheckPsd">
|
||||
{{ t('common.confirm') }}
|
||||
</a-button>
|
||||
</template>
|
||||
</a-modal>
|
||||
</MsCard>
|
||||
</template>
|
||||
|
||||
|
@ -66,6 +116,7 @@
|
|||
*/
|
||||
import { provide } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { FormInstance, Message } from '@arco-design/web-vue';
|
||||
|
||||
import MsCard from '@/components/pure/ms-card/index.vue';
|
||||
import MsSplitBox from '@/components/pure/ms-split-box/index.vue';
|
||||
|
@ -73,12 +124,17 @@
|
|||
import importApi from './components/import.vue';
|
||||
import management from './components/management/index.vue';
|
||||
import moduleTree from './components/moduleTree.vue';
|
||||
import ApiSharePreview from '@/views/api-test/management/components/management/api/apiSharePreview.vue';
|
||||
|
||||
import { getTrashModuleCount } from '@/api/modules/api-test/management';
|
||||
import { getProtocolList } from '@/api/modules/api-test/common';
|
||||
import { checkSharePsd, getTrashModuleCount, shareDetail } from '@/api/modules/api-test/management';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import { NOT_FOUND_RESOURCE } from '@/router/constants';
|
||||
import { useUserStore } from '@/store';
|
||||
import useDocShareCheckStore from '@/store/modules/api/docShareCheck';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
|
||||
import { ApiDefinitionGetModuleParams } from '@/models/apiTest/management';
|
||||
import { ApiDefinitionGetModuleParams, ShareDetailType } from '@/models/apiTest/management';
|
||||
import { ModuleTreeNode } from '@/models/common';
|
||||
import { ApiTestRouteEnum } from '@/enums/routeEnum';
|
||||
|
||||
|
@ -86,6 +142,8 @@
|
|||
const route = useRoute();
|
||||
const { t } = useI18n();
|
||||
const router = useRouter();
|
||||
const docCheckStore = useDocShareCheckStore();
|
||||
const userStore = useUserStore();
|
||||
|
||||
const activeModule = ref<string>('all');
|
||||
const folderTree = ref<ModuleTreeNode[]>([]);
|
||||
|
@ -96,7 +154,6 @@
|
|||
const activeNodeId = ref<string | number>('all');
|
||||
const moduleTreeRef = ref<InstanceType<typeof moduleTree>>();
|
||||
const managementRef = ref<InstanceType<typeof management>>();
|
||||
|
||||
function newApi() {
|
||||
importDrawerVisible.value = false;
|
||||
managementRef.value?.newTab();
|
||||
|
@ -111,7 +168,6 @@
|
|||
function handleApiNodeClick(node: ModuleTreeNode) {
|
||||
managementRef.value?.newTab(node);
|
||||
}
|
||||
|
||||
function setActiveApi(params: RequestParam) {
|
||||
if (params.id === 'all') {
|
||||
// 切换到全部 tab 时需设置为上次激活的 api 节点的模块
|
||||
|
@ -121,19 +177,36 @@
|
|||
}
|
||||
}
|
||||
|
||||
const protocols = ref<any[]>([]);
|
||||
async function initProtocolList() {
|
||||
try {
|
||||
protocols.value = await getProtocolList(appStore.currentOrgId);
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
initProtocolList();
|
||||
});
|
||||
|
||||
function handleProtocolChange(val: string[]) {
|
||||
selectedProtocols.value = val;
|
||||
}
|
||||
|
||||
const docShareId = ref<string>(route.query.docShareId as string);
|
||||
const recycleModulesCount = ref(0);
|
||||
async function selectRecycleCount() {
|
||||
const res = await getTrashModuleCount({
|
||||
projectId: appStore.currentProjectId,
|
||||
keyword: '',
|
||||
moduleIds: [],
|
||||
protocols: selectedProtocols.value,
|
||||
});
|
||||
recycleModulesCount.value = res.all;
|
||||
if (!docShareId.value) {
|
||||
const res = await getTrashModuleCount({
|
||||
projectId: appStore.currentProjectId,
|
||||
keyword: '',
|
||||
moduleIds: [],
|
||||
protocols: selectedProtocols.value,
|
||||
});
|
||||
recycleModulesCount.value = res.all;
|
||||
}
|
||||
}
|
||||
|
||||
function handleModuleInit(tree: ModuleTreeNode[], _protocols: string[], pathMap: Record<string, any>) {
|
||||
|
@ -198,11 +271,129 @@
|
|||
moduleTreeRef.value?.setActiveFolder('all');
|
||||
}
|
||||
|
||||
const checkLoading = ref<boolean>(false);
|
||||
const checkPsdModal = ref<boolean>(false);
|
||||
const checkForm = ref({
|
||||
docShareId: route.query.docShareId as string,
|
||||
password: '',
|
||||
});
|
||||
|
||||
const validatePassword = (value: string | undefined, callback: (error?: string) => void) => {
|
||||
const sixDigitRegex = /^\d{6}$/;
|
||||
|
||||
if (value === undefined || value === '') {
|
||||
callback(t('apiTestManagement.enterPassword'));
|
||||
} else if (!sixDigitRegex.test(value)) {
|
||||
callback(t('apiTestManagement.enterPassword'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
const rules = {
|
||||
password: [
|
||||
{
|
||||
required: true,
|
||||
message: t('apiTestManagement.sharePasswordPlaceholder'),
|
||||
},
|
||||
{
|
||||
validator: validatePassword,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
// 上一条|下一条
|
||||
function toggleDetail(type: string) {
|
||||
if (type === 'prev') {
|
||||
moduleTreeRef.value?.previousApi();
|
||||
} else {
|
||||
moduleTreeRef.value?.nextApi();
|
||||
}
|
||||
}
|
||||
const formRef = ref<FormInstance>();
|
||||
|
||||
// 关闭分享
|
||||
function closeShareHandler() {
|
||||
checkPsdModal.value = false;
|
||||
formRef.value?.resetFields();
|
||||
checkForm.value.password = '';
|
||||
}
|
||||
|
||||
const shareDetailInfo = ref<ShareDetailType>();
|
||||
const currentNode = ref();
|
||||
|
||||
// 获取分享详情
|
||||
async function getShareDetail() {
|
||||
try {
|
||||
shareDetailInfo.value = await shareDetail(docShareId.value);
|
||||
// 资源无效
|
||||
if (shareDetailInfo.value.invalid) {
|
||||
router.push({
|
||||
name: NOT_FOUND_RESOURCE,
|
||||
query: {
|
||||
type: 'EXPIRED',
|
||||
},
|
||||
});
|
||||
}
|
||||
// 限制访问校验
|
||||
if (shareDetailInfo.value.isPrivate && !docCheckStore.isDocVerified(docShareId.value, userStore.id || '')) {
|
||||
checkPsdModal.value = true;
|
||||
}
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
const previousNode = ref<ModuleTreeNode | null>();
|
||||
const nextNode = ref<ModuleTreeNode | null>();
|
||||
|
||||
// 设置当前预览节点
|
||||
function openCurrentNode(node: ModuleTreeNode, apiNodes: ModuleTreeNode[]) {
|
||||
const index = apiNodes.indexOf(node);
|
||||
currentNode.value = node;
|
||||
previousNode.value = index > 0 ? apiNodes[index - 1] : null;
|
||||
nextNode.value = index < apiNodes.length - 1 ? apiNodes[index + 1] : null;
|
||||
}
|
||||
|
||||
// 校验密码
|
||||
function handleCheckPsd() {
|
||||
formRef.value?.validate(async (errors) => {
|
||||
if (!errors) {
|
||||
try {
|
||||
checkLoading.value = true;
|
||||
const res = await checkSharePsd(checkForm.value);
|
||||
if (res) {
|
||||
closeShareHandler();
|
||||
// 标记为已验证
|
||||
docCheckStore.markDocAsVerified(docShareId.value, userStore.id || '');
|
||||
checkPsdModal.value = false;
|
||||
} else {
|
||||
Message.error(t('apiTestManagement.apiSharePsdError'));
|
||||
}
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
} finally {
|
||||
checkLoading.value = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
if (docShareId.value) {
|
||||
getShareDetail();
|
||||
}
|
||||
});
|
||||
|
||||
/** 向子孙组件提供方法和值 */
|
||||
provide('setActiveApi', setActiveApi);
|
||||
provide('refreshModuleTree', refreshModuleTree);
|
||||
provide('refreshModuleTreeCount', refreshModuleTreeCount);
|
||||
provide('folderTreePathMap', folderTreePathMap.value);
|
||||
provide('docShareId', docShareId.value);
|
||||
provide('shareDetailInfo', shareDetailInfo.value);
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
@ -258,4 +449,21 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
.no-resource-svg {
|
||||
margin: 0 auto 24px;
|
||||
width: 160px;
|
||||
height: 98px;
|
||||
background: url('@/assets/svg/no_resource.svg');
|
||||
background-size: cover;
|
||||
}
|
||||
:deep(.ms-modal-share) {
|
||||
.arco-modal-mask {
|
||||
background: var(--color-text-1) !important;
|
||||
}
|
||||
}
|
||||
:deep(.password-form) {
|
||||
.arco-form-item-message {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -180,6 +180,10 @@ export default {
|
|||
'apiTestManagement.interfaceRange': 'Interface range',
|
||||
'apiTestManagement.effectiveTime': 'Effective time',
|
||||
'apiTestManagement.passwordAccess': 'Password access',
|
||||
'apiTestManagement.sharePasswordPlaceholder': 'Please enter the share password',
|
||||
'apiTestManagement.apiShareNum': 'Api number',
|
||||
'apiTestManagement.apiShareNumberTip': 'The number of shared interfaces is 0, please check!',
|
||||
'apiTestManagement.apiSharePsdError': 'Password error!',
|
||||
'apiTestManagement.allowExport': 'Allow export',
|
||||
'apiTestManagement.pleaseEnterName': 'Please enter name',
|
||||
'apiTestManagement.module': 'Module',
|
||||
|
|
|
@ -173,6 +173,10 @@ export default {
|
|||
'apiTestManagement.interfaceRange': '接口范围',
|
||||
'apiTestManagement.effectiveTime': '有效时间',
|
||||
'apiTestManagement.passwordAccess': '密码访问',
|
||||
'apiTestManagement.sharePasswordPlaceholder': '请输入分享密码',
|
||||
'apiTestManagement.apiShareNum': '接口数量',
|
||||
'apiTestManagement.apiShareNumberTip': '分享的接口数量为0,请检查!',
|
||||
'apiTestManagement.apiSharePsdError': '密码错误!',
|
||||
'apiTestManagement.allowExport': '允许导出',
|
||||
'apiTestManagement.pleaseEnterName': '请输入名称',
|
||||
'apiTestManagement.module': '模块',
|
||||
|
|
Loading…
Reference in New Issue