feat(接口测试): 接口调试-部分 tab&用例评审部分接口联调&部分组件调整
This commit is contained in:
parent
f227f97679
commit
f6fb6d384b
|
@ -3,31 +3,52 @@ import {
|
||||||
AddReviewModuleUrl,
|
AddReviewModuleUrl,
|
||||||
AddReviewUrl,
|
AddReviewUrl,
|
||||||
AssociateReviewUrl,
|
AssociateReviewUrl,
|
||||||
|
BatchChangeReviewerUrl,
|
||||||
|
BatchDisassociateReviewCaseUrl,
|
||||||
|
BatchReviewUrl,
|
||||||
CopyReviewUrl,
|
CopyReviewUrl,
|
||||||
DeleteReviewModuleUrl,
|
DeleteReviewModuleUrl,
|
||||||
|
DeleteReviewUrl,
|
||||||
|
DisassociateReviewCaseUrl,
|
||||||
EditReviewUrl,
|
EditReviewUrl,
|
||||||
FollowReviewUrl,
|
FollowReviewUrl,
|
||||||
GetAssociatedIdsUrl,
|
GetAssociatedIdsUrl,
|
||||||
|
GetCaseReviewHistoryListUrl,
|
||||||
|
GetReviewDetailCasePageUrl,
|
||||||
|
GetReviewDetailModuleCountUrl,
|
||||||
|
GetReviewDetailModuleTreeUrl,
|
||||||
GetReviewDetailUrl,
|
GetReviewDetailUrl,
|
||||||
GetReviewListUrl,
|
GetReviewListUrl,
|
||||||
GetReviewModulesUrl,
|
GetReviewModulesUrl,
|
||||||
GetReviewUsersUrl,
|
GetReviewUsersUrl,
|
||||||
MoveReviewModuleUrl,
|
MoveReviewModuleUrl,
|
||||||
MoveReviewUrl,
|
MoveReviewUrl,
|
||||||
|
ReviewModuleCountUrl,
|
||||||
|
SaveCaseReviewResultUrl,
|
||||||
|
SortReviewDetailCaseUrl,
|
||||||
SortReviewUrl,
|
SortReviewUrl,
|
||||||
UpdateReviewModuleUrl,
|
UpdateReviewModuleUrl,
|
||||||
} from '@/api/requrls/case-management/caseReview';
|
} from '@/api/requrls/case-management/caseReview';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AssociateReviewCaseParams,
|
AssociateReviewCaseParams,
|
||||||
|
BatchCancelReviewCaseParams,
|
||||||
|
BatchChangeReviewerParams,
|
||||||
BatchMoveReviewParams,
|
BatchMoveReviewParams,
|
||||||
|
BatchReviewCaseParams,
|
||||||
|
CommitReviewResultParams,
|
||||||
|
CopyReviewParams,
|
||||||
FollowReviewParams,
|
FollowReviewParams,
|
||||||
Review,
|
Review,
|
||||||
|
ReviewCaseItem,
|
||||||
|
ReviewDetailCaseListQueryParams,
|
||||||
|
ReviewHistoryItem,
|
||||||
ReviewItem,
|
ReviewItem,
|
||||||
ReviewListQueryParams,
|
ReviewListQueryParams,
|
||||||
ReviewModule,
|
ReviewModule,
|
||||||
ReviewModuleItem,
|
ReviewModuleItem,
|
||||||
ReviewUserItem,
|
ReviewUserItem,
|
||||||
|
SortReviewCaseParams,
|
||||||
SortReviewParams,
|
SortReviewParams,
|
||||||
UpdateReviewModuleParams,
|
UpdateReviewModuleParams,
|
||||||
UpdateReviewParams,
|
UpdateReviewParams,
|
||||||
|
@ -59,6 +80,11 @@ export const deleteReviewModule = (id: string) => {
|
||||||
return MSR.get({ url: DeleteReviewModuleUrl, params: id });
|
return MSR.get({ url: DeleteReviewModuleUrl, params: id });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 评审模块树-统计用例数量
|
||||||
|
export const reviewModuleCount = (data: ReviewListQueryParams) => {
|
||||||
|
return MSR.post({ url: ReviewModuleCountUrl, data });
|
||||||
|
};
|
||||||
|
|
||||||
// 新增评审
|
// 新增评审
|
||||||
export const addReview = (data: Review) => {
|
export const addReview = (data: Review) => {
|
||||||
return MSR.post({ url: AddReviewUrl, data });
|
return MSR.post({ url: AddReviewUrl, data });
|
||||||
|
@ -70,7 +96,7 @@ export const associateReviewCase = (data: AssociateReviewCaseParams) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
// 复制评审
|
// 复制评审
|
||||||
export const copyReview = (data: Review) => {
|
export const copyReview = (data: CopyReviewParams) => {
|
||||||
return MSR.post({ url: CopyReviewUrl, data });
|
return MSR.post({ url: CopyReviewUrl, data });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -109,7 +135,62 @@ export const getReviewUsers = (projectId: string, keyword: string) => {
|
||||||
return MSR.get<ReviewUserItem[]>({ url: `${GetReviewUsersUrl}/${projectId}`, params: { keyword } });
|
return MSR.get<ReviewUserItem[]>({ url: `${GetReviewUsersUrl}/${projectId}`, params: { keyword } });
|
||||||
};
|
};
|
||||||
|
|
||||||
// 获取评审人员列表
|
// 取消关联用例
|
||||||
|
export const disassociateReviewCase = (reviewId: string, caseId: string) => {
|
||||||
|
return MSR.get<ReviewUserItem[]>({ url: `${DisassociateReviewCaseUrl}/${reviewId}/${caseId}` });
|
||||||
|
};
|
||||||
|
|
||||||
|
// 删除评审
|
||||||
|
export const deleteReview = (reviewId: string, projectId: string) => {
|
||||||
|
return MSR.get<ReviewUserItem[]>({ url: `${DeleteReviewUrl}/${projectId}/${reviewId}` });
|
||||||
|
};
|
||||||
|
|
||||||
|
// 评审详情-获取用例列表
|
||||||
|
export const getReviewDetailCasePage = (data: ReviewDetailCaseListQueryParams) => {
|
||||||
|
return MSR.post<CommonList<ReviewCaseItem>>({ url: GetReviewDetailCasePageUrl, data });
|
||||||
|
};
|
||||||
|
|
||||||
|
// 评审详情-用例拖拽排序
|
||||||
|
export const sortReviewDetailCase = (data: SortReviewCaseParams) => {
|
||||||
|
return MSR.post({ url: SortReviewDetailCaseUrl, data });
|
||||||
|
};
|
||||||
|
|
||||||
|
// 评审详情-批量评审
|
||||||
|
export const batchReview = (data: BatchReviewCaseParams) => {
|
||||||
|
return MSR.post({ url: BatchReviewUrl, data });
|
||||||
|
};
|
||||||
|
|
||||||
|
// 评审详情-批量修改评审人
|
||||||
|
export const batchChangeReviewer = (data: BatchChangeReviewerParams) => {
|
||||||
|
return MSR.post({ url: BatchChangeReviewerUrl, data });
|
||||||
|
};
|
||||||
|
|
||||||
|
// 评审详情-批量取消关联用例
|
||||||
|
export const batchDisassociateReviewCase = (data: BatchCancelReviewCaseParams) => {
|
||||||
|
return MSR.post({ url: BatchDisassociateReviewCaseUrl, data });
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取关联用例 id集合
|
||||||
export const getAssociatedIds = (reviewId: string) => {
|
export const getAssociatedIds = (reviewId: string) => {
|
||||||
return MSR.get<string[]>({ url: `${GetAssociatedIdsUrl}/${reviewId}` });
|
return MSR.get<string[]>({ url: `${GetAssociatedIdsUrl}/${reviewId}` });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 评审详情-模块下用例数量统计
|
||||||
|
export const getReviewDetailModuleCount = (data: ReviewDetailCaseListQueryParams) => {
|
||||||
|
return MSR.post({ url: GetReviewDetailModuleCountUrl, data });
|
||||||
|
};
|
||||||
|
|
||||||
|
// 评审详情-已关联用例模块树
|
||||||
|
export const getReviewDetailModuleTree = (projectId: string, reviewId: string) => {
|
||||||
|
return MSR.get({ url: `${GetReviewDetailModuleTreeUrl}/${projectId}/${reviewId}` });
|
||||||
|
};
|
||||||
|
|
||||||
|
// 评审详情-获取用例评审历史
|
||||||
|
export const getCaseReviewHistoryList = (reviewId: string, caseId: string) => {
|
||||||
|
return MSR.get<ReviewHistoryItem[]>({ url: `${GetCaseReviewHistoryListUrl}/${reviewId}/${caseId}` });
|
||||||
|
};
|
||||||
|
|
||||||
|
// 评审详情-提交用例评审结果
|
||||||
|
export const saveCaseReviewResult = (data: CommitReviewResultParams) => {
|
||||||
|
return MSR.post({ url: SaveCaseReviewResultUrl, data });
|
||||||
|
};
|
||||||
|
|
|
@ -76,6 +76,7 @@ import type {
|
||||||
CreateOrUpdateModule,
|
CreateOrUpdateModule,
|
||||||
DeleteCaseType,
|
DeleteCaseType,
|
||||||
DemandItem,
|
DemandItem,
|
||||||
|
DetailCase,
|
||||||
ModulesTreeType,
|
ModulesTreeType,
|
||||||
OperationFile,
|
OperationFile,
|
||||||
UpdateModule,
|
UpdateModule,
|
||||||
|
|
|
@ -11,15 +11,19 @@ import {
|
||||||
GetInfoUrl,
|
GetInfoUrl,
|
||||||
GetLocalConfigUrl,
|
GetLocalConfigUrl,
|
||||||
GetMenuListUrl,
|
GetMenuListUrl,
|
||||||
|
GetPlatformAccountUrl,
|
||||||
|
GetPlatformUrl,
|
||||||
GetPublicKeyUrl,
|
GetPublicKeyUrl,
|
||||||
isLoginUrl,
|
isLoginUrl,
|
||||||
LoginUrl,
|
LoginUrl,
|
||||||
LogoutUrl,
|
LogoutUrl,
|
||||||
|
SavePlatformUrl,
|
||||||
UpdateAPIKEYUrl,
|
UpdateAPIKEYUrl,
|
||||||
UpdateInfoUrl,
|
UpdateInfoUrl,
|
||||||
UpdateLocalConfigUrl,
|
UpdateLocalConfigUrl,
|
||||||
UpdatePswUrl,
|
UpdatePswUrl,
|
||||||
ValidAPIKEYUrl,
|
ValidAPIKEYUrl,
|
||||||
|
ValidatePlatformUrl,
|
||||||
ValidLocalConfigUrl,
|
ValidLocalConfigUrl,
|
||||||
} from '@/api/requrls/user';
|
} from '@/api/requrls/user';
|
||||||
|
|
||||||
|
@ -137,3 +141,23 @@ export function updateBaseInfo(data: UpdateBaseInfo) {
|
||||||
export function updatePsw(data: UpdatePswParams) {
|
export function updatePsw(data: UpdatePswParams) {
|
||||||
return MSR.post({ url: UpdatePswUrl, data });
|
return MSR.post({ url: UpdatePswUrl, data });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 个人信息-校验第三方平台账号信息
|
||||||
|
export function validatePlatform(id: string, data: Record<string, any>) {
|
||||||
|
return MSR.post({ url: `${ValidatePlatformUrl}/${id}`, data });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 个人信息-保存第三方平台账号信息
|
||||||
|
export function savePlatform(data: UpdatePswParams) {
|
||||||
|
return MSR.post({ url: SavePlatformUrl, data });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 个人信息-获取第三方平台账号信息
|
||||||
|
export function getPlatform() {
|
||||||
|
return MSR.get({ url: GetPlatformUrl });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 个人信息-获取第三方平台账号信息-插件信息
|
||||||
|
export function getPlatformAccount() {
|
||||||
|
return MSR.get({ url: GetPlatformAccountUrl });
|
||||||
|
}
|
||||||
|
|
|
@ -8,9 +8,21 @@ export const AssociateReviewUrl = '/case/review/associate'; // 关联用例
|
||||||
export const AddReviewUrl = '/case/review/add'; // 新增评审
|
export const AddReviewUrl = '/case/review/add'; // 新增评审
|
||||||
export const GetReviewUsersUrl = '/case/review/user-option'; // 获取评审人员列表
|
export const GetReviewUsersUrl = '/case/review/user-option'; // 获取评审人员列表
|
||||||
export const GetReviewDetailUrl = '/case/review/detail'; // 获取评审详情
|
export const GetReviewDetailUrl = '/case/review/detail'; // 获取评审详情
|
||||||
|
export const DisassociateReviewCaseUrl = '/case/review/disassociate'; // 取消关联用例
|
||||||
|
export const DeleteReviewUrl = '/case/review/delete'; // 删除用例评审
|
||||||
export const UpdateReviewModuleUrl = '/case/review/module/update'; // 更新评审模块
|
export const UpdateReviewModuleUrl = '/case/review/module/update'; // 更新评审模块
|
||||||
export const MoveReviewModuleUrl = '/case/review/module/move'; // 移动评审模块
|
export const MoveReviewModuleUrl = '/case/review/module/move'; // 移动评审模块
|
||||||
export const AddReviewModuleUrl = '/case/review/module/add'; // 新增评审模块
|
export const AddReviewModuleUrl = '/case/review/module/add'; // 新增评审模块
|
||||||
export const GetReviewModulesUrl = '/case/review/module/tree'; // 获取评审模块树
|
export const GetReviewModulesUrl = '/case/review/module/tree'; // 获取评审模块树
|
||||||
export const DeleteReviewModuleUrl = '/case/review/module/delete'; // 删除评审模块
|
export const DeleteReviewModuleUrl = '/case/review/module/delete'; // 删除评审模块
|
||||||
|
export const ReviewModuleCountUrl = '/case/review/module/count'; // 模块下用例数量统计
|
||||||
|
export const GetReviewDetailCasePageUrl = '/case/review/detail/page'; // 评审详情-获取已关联用例列表
|
||||||
|
export const SortReviewDetailCaseUrl = '/case/review/detail/edit/pos'; // 评审详情-已关联用例拖拽排序
|
||||||
|
export const BatchReviewUrl = '/case/review/detail/batch/review'; // 评审详情-批量评审
|
||||||
|
export const BatchChangeReviewerUrl = '/case/review/detail/batch/edit/reviewers'; // 评审详情-批量修改评审人
|
||||||
|
export const BatchDisassociateReviewCaseUrl = '/case/review/detail/batch/disassociate'; // 评审详情-批量取消关联用例
|
||||||
export const GetAssociatedIdsUrl = '/case/review/detail/get-ids'; // 获取已关联用例id集合
|
export const GetAssociatedIdsUrl = '/case/review/detail/get-ids'; // 获取已关联用例id集合
|
||||||
|
export const GetReviewDetailModuleCountUrl = '/case/review/detail/module/count'; // 评审详情-模块下用例数量统计
|
||||||
|
export const GetReviewDetailModuleTreeUrl = '/case/review/detail/tree'; // 评审详情-已关联用例模块树
|
||||||
|
export const GetCaseReviewHistoryListUrl = '/review/functional/case/get/list'; // 评审详情-获取用例评审历史
|
||||||
|
export const SaveCaseReviewResultUrl = '/review/functional/case/save'; // 评审详情-提交评审
|
||||||
|
|
|
@ -19,3 +19,7 @@ export const AddAPIKEYUrl = '/user/api/key/add'; // 个人设置-生成 APIKEY
|
||||||
export const UpdatePswUrl = '/personal/update-password'; // 个人信息-修改密码
|
export const UpdatePswUrl = '/personal/update-password'; // 个人信息-修改密码
|
||||||
export const UpdateInfoUrl = '/personal/update-info'; // 个人信息-修改信息
|
export const UpdateInfoUrl = '/personal/update-info'; // 个人信息-修改信息
|
||||||
export const GetInfoUrl = '/personal/get'; // 个人信息-获取信息
|
export const GetInfoUrl = '/personal/get'; // 个人信息-获取信息
|
||||||
|
export const ValidatePlatformUrl = '/user/platform/validate'; // 个人信息-校验服务集成信息
|
||||||
|
export const SavePlatformUrl = '/user/platform/save'; // 个人信息-保存三方平台账号信息
|
||||||
|
export const GetPlatformUrl = '/user/platform/get'; // 个人信息-获取三方平台账号信息
|
||||||
|
export const GetPlatformAccountUrl = '/user/platform/account/info'; // 个人信息-获取三方平台账号信息-插件信息
|
||||||
|
|
|
@ -407,7 +407,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.arco-radio-checked {
|
.arco-radio-checked:not(.arco-radio-disabled) {
|
||||||
.arco-radio-icon {
|
.arco-radio-icon {
|
||||||
@apply !bg-white;
|
@apply !bg-white;
|
||||||
|
|
||||||
|
@ -425,6 +425,15 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.arco-radio-checked.arco-radio-disabled {
|
||||||
|
.arco-radio-icon {
|
||||||
|
border: 1px solid var(--color-text-input-border);
|
||||||
|
background-color: var(--color-text-n8) !important;
|
||||||
|
&::after {
|
||||||
|
background-color: var(--color-text-4) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Message **/
|
/** Message **/
|
||||||
.arco-message {
|
.arco-message {
|
||||||
|
|
|
@ -15,9 +15,9 @@
|
||||||
class="ml-2 max-w-[100px]"
|
class="ml-2 max-w-[100px]"
|
||||||
:placeholder="t('caseManagement.featureCase.PleaseSelect')"
|
:placeholder="t('caseManagement.featureCase.PleaseSelect')"
|
||||||
>
|
>
|
||||||
<a-option v-for="item of props?.moduleOptions" :key="item.value" :value="item.value">{{
|
<a-option v-for="item of props?.moduleOptions" :key="item.value" :value="item.value">
|
||||||
t(item.label)
|
{{ t(item.label) }}
|
||||||
}}</a-option>
|
</a-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -142,7 +142,7 @@
|
||||||
import type { MsTreeNodeData } from '@/components/business/ms-tree/types';
|
import type { MsTreeNodeData } from '@/components/business/ms-tree/types';
|
||||||
import caseLevel from './caseLevel.vue';
|
import caseLevel from './caseLevel.vue';
|
||||||
|
|
||||||
import { getCustomFieldsTable } from '@/api/modules/case-management/featureCase';
|
import { getCaseModulesCounts, getCustomFieldsTable } from '@/api/modules/case-management/featureCase';
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
import useAppStore from '@/store/modules/app';
|
import useAppStore from '@/store/modules/app';
|
||||||
import { mapTree } from '@/utils';
|
import { mapTree } from '@/utils';
|
||||||
|
@ -164,7 +164,6 @@
|
||||||
modulesParams?: Record<string, any>; // 获取模块树请求
|
modulesParams?: Record<string, any>; // 获取模块树请求
|
||||||
getTableFunc: (params: TableQueryParams) => Promise<CommonList<CaseManagementTable>>; // 获取表请求函数
|
getTableFunc: (params: TableQueryParams) => Promise<CommonList<CaseManagementTable>>; // 获取表请求函数
|
||||||
tableParams?: TableQueryParams; // 查询表格的额外的参数
|
tableParams?: TableQueryParams; // 查询表格的额外的参数
|
||||||
modulesCount: Record<string, any>; // 模块数量统计对象
|
|
||||||
okButtonDisabled?: boolean; // 确认按钮是否禁用
|
okButtonDisabled?: boolean; // 确认按钮是否禁用
|
||||||
currentSelectCase: string | number | Record<string, any> | undefined; // 当前选中的用例类型
|
currentSelectCase: string | number | Record<string, any> | undefined; // 当前选中的用例类型
|
||||||
moduleOptions?: { label: string; value: string }[]; // 功能模块对应用例下拉
|
moduleOptions?: { label: string; value: string }[]; // 功能模块对应用例下拉
|
||||||
|
@ -178,7 +177,7 @@
|
||||||
(e: 'update:currentSelectCase', val: string | number | Record<string, any> | undefined): void;
|
(e: 'update:currentSelectCase', val: string | number | Record<string, any> | undefined): void;
|
||||||
(e: 'init', val: TableQueryParams): void; // 初始化模块数量
|
(e: 'init', val: TableQueryParams): void; // 初始化模块数量
|
||||||
(e: 'close'): void;
|
(e: 'close'): void;
|
||||||
(e: 'save', params: TableQueryParams): void; // 保存对外传递关联table 相关参数
|
(e: 'save', params: any): void; // 保存对外传递关联table 相关参数
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const virtualListProps = computed(() => {
|
const virtualListProps = computed(() => {
|
||||||
|
@ -212,6 +211,7 @@
|
||||||
|
|
||||||
const protocolType = ref('HTTP'); // 协议类型
|
const protocolType = ref('HTTP'); // 协议类型
|
||||||
const protocolOptions = ref(['HTTP']);
|
const protocolOptions = ref(['HTTP']);
|
||||||
|
const modulesCount = ref<Record<string, any>>({});
|
||||||
|
|
||||||
// 选中用例类型
|
// 选中用例类型
|
||||||
const caseType = computed({
|
const caseType = computed({
|
||||||
|
@ -248,7 +248,7 @@
|
||||||
hideMoreAction: e.id === 'root',
|
hideMoreAction: e.id === 'root',
|
||||||
draggable: false,
|
draggable: false,
|
||||||
disabled: false,
|
disabled: false,
|
||||||
count: props.modulesCount?.[e.id] || 0,
|
count: modulesCount.value[e.id] || 0,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
if (isSetDefaultKey) {
|
if (isSetDefaultKey) {
|
||||||
|
@ -498,16 +498,22 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
// 初始化模块数量
|
// 初始化模块数量
|
||||||
function initModuleCount() {
|
async function initModuleCount() {
|
||||||
emit('init', {
|
try {
|
||||||
keyword: keyword.value,
|
const params = {
|
||||||
moduleIds: [],
|
keyword: keyword.value,
|
||||||
projectId: innerProject.value,
|
moduleIds: selectedModuleKeys.value,
|
||||||
current: propsRes.value.msPagination?.current,
|
projectId: innerProject.value,
|
||||||
pageSize: propsRes.value.msPagination?.pageSize,
|
current: propsRes.value.msPagination?.current,
|
||||||
sourceId: props.caseId,
|
pageSize: propsRes.value.msPagination?.pageSize,
|
||||||
combine: combine.value,
|
combine: combine.value,
|
||||||
});
|
};
|
||||||
|
modulesCount.value = await getCaseModulesCounts(params);
|
||||||
|
emit('init', params);
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function searchCase() {
|
function searchCase() {
|
||||||
|
@ -556,6 +562,7 @@
|
||||||
innerVisible.value = val;
|
innerVisible.value = val;
|
||||||
if (val) {
|
if (val) {
|
||||||
resetSelector();
|
resetSelector();
|
||||||
|
initModules();
|
||||||
searchCase();
|
searchCase();
|
||||||
initFilter();
|
initFilter();
|
||||||
}
|
}
|
||||||
|
@ -567,7 +574,7 @@
|
||||||
(val) => {
|
(val) => {
|
||||||
emit('update:visible', val);
|
emit('update:visible', val);
|
||||||
if (val) {
|
if (val) {
|
||||||
initModules(true);
|
initModules();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -578,7 +585,7 @@
|
||||||
(val) => {
|
(val) => {
|
||||||
if (val) {
|
if (val) {
|
||||||
emit('update:currentSelectCase', val);
|
emit('update:currentSelectCase', val);
|
||||||
initModules(true);
|
initModules();
|
||||||
searchCase();
|
searchCase();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -597,8 +604,12 @@
|
||||||
() => innerProject.value,
|
() => innerProject.value,
|
||||||
(val) => {
|
(val) => {
|
||||||
emit('update:project', val);
|
emit('update:project', val);
|
||||||
initModules(true);
|
if (innerVisible.value) {
|
||||||
searchCase();
|
searchCase();
|
||||||
|
resetSelector();
|
||||||
|
initModules();
|
||||||
|
searchCase();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -613,7 +624,7 @@
|
||||||
* 初始化模块数量
|
* 初始化模块数量
|
||||||
*/
|
*/
|
||||||
watch(
|
watch(
|
||||||
() => props.modulesCount,
|
() => modulesCount.value,
|
||||||
(obj) => {
|
(obj) => {
|
||||||
folderTree.value = mapTree<ModuleTreeNode>(folderTree.value, (node) => {
|
folderTree.value = mapTree<ModuleTreeNode>(folderTree.value, (node) => {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
|
import { useVModel } from '@vueuse/core';
|
||||||
|
|
||||||
import MsAvatar from '@/components/pure/ms-avatar/index.vue';
|
import MsAvatar from '@/components/pure/ms-avatar/index.vue';
|
||||||
import MsRichText from '@/components/pure/ms-rich-text/MsRichText.vue';
|
import MsRichText from '@/components/pure/ms-rich-text/MsRichText.vue';
|
||||||
|
@ -37,17 +38,12 @@
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
|
(event: 'update:content', value: string): void;
|
||||||
(event: 'publish', value: string): void;
|
(event: 'publish', value: string): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const isActive = ref(false);
|
const isActive = ref(false);
|
||||||
const currentContent = ref('');
|
const currentContent = useVModel(props, 'content', emit);
|
||||||
|
|
||||||
watchEffect(() => {
|
|
||||||
if (props.content) {
|
|
||||||
currentContent.value = props.content;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const publish = () => {
|
const publish = () => {
|
||||||
emit('publish', currentContent.value);
|
emit('publish', currentContent.value);
|
||||||
|
|
|
@ -4,163 +4,42 @@
|
||||||
<div class="font-medium text-[var(--color-text-1)]">{{ t('ms.personal.tripartite') }}</div>
|
<div class="font-medium text-[var(--color-text-1)]">{{ t('ms.personal.tripartite') }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="platform-card-container">
|
<div class="platform-card-container">
|
||||||
<div class="platform-card">
|
<div v-for="config of dynamicForm" :key="config.key" class="platform-card">
|
||||||
<div class="mb-[16px] flex items-center">
|
<div class="mb-[16px] flex items-center">
|
||||||
<a-image src="/plugin/image/jira?imagePath=static/jira.jpg" width="24"></a-image>
|
<a-image :src="`/plugin/image/${config.key}?imagePath=static/${config.key}.jpg`" width="24"></a-image>
|
||||||
<div class="ml-[8px] mr-[4px] font-medium text-[var(--color-text-1)]">JIRA</div>
|
<div class="ml-[8px] mr-[4px] font-medium text-[var(--color-text-1)]">{{ config.key }}</div>
|
||||||
<a-tooltip :content="t('ms.personal.jiraTip')" position="right">
|
<a-tooltip v-if="config.tooltip" :content="config.tooltip" position="right">
|
||||||
<icon-exclamation-circle
|
<icon-exclamation-circle
|
||||||
class="mr-[8px] text-[var(--color-text-4)] hover:text-[rgb(var(--primary-5))]"
|
class="mr-[8px] text-[var(--color-text-4)] hover:text-[rgb(var(--primary-5))]"
|
||||||
size="16"
|
size="16"
|
||||||
/>
|
/>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<MsTag theme="light" :type="tagMap[jiraConfig.status].type" size="small" class="px-[4px]">
|
<MsTag theme="light" :type="tagMap[config.status].type" size="small" class="px-[4px]">
|
||||||
{{ tagMap[jiraConfig.status].text }}
|
{{ tagMap[config.status].text }}
|
||||||
</MsTag>
|
</MsTag>
|
||||||
</div>
|
</div>
|
||||||
<a-form ref="jiraFormRef" :model="jiraConfig">
|
<MsFormCreate
|
||||||
<a-form-item :label="t('ms.personal.authType')">
|
v-model:api="config.formModel"
|
||||||
<a-radio-group v-model:model-value="jiraConfig.authType">
|
v-model:form-item="config.formItemList"
|
||||||
<a-radio value="basic">Basic Auth</a-radio>
|
:form-rule="config.formRules"
|
||||||
<a-radio value="token">Bearer Token</a-radio>
|
:option="options"
|
||||||
</a-radio-group>
|
>
|
||||||
</a-form-item>
|
</MsFormCreate>
|
||||||
<a-form-item :label="t('ms.personal.platformAccount')">
|
<a-button type="outline" :loading="config.validateLoading" @click="validate(config)">
|
||||||
<a-input
|
{{ t('ms.personal.valid') }}
|
||||||
v-model:model-value="jiraConfig.platformAccount"
|
</a-button>
|
||||||
:placeholder="t('ms.personal.platformAccountPlaceholder', { type: 'JIRA' })"
|
|
||||||
class="w-[312px]"
|
|
||||||
allow-clear
|
|
||||||
autocomplete="new-password"
|
|
||||||
></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item :label="t('ms.personal.platformPsw')">
|
|
||||||
<a-input-password
|
|
||||||
v-model:model-value="jiraConfig.platformPsw"
|
|
||||||
:placeholder="t('ms.personal.platformPswPlaceholder', { type: 'JIRA' })"
|
|
||||||
class="mr-[8px] w-[312px]"
|
|
||||||
allow-clear
|
|
||||||
autocomplete="new-password"
|
|
||||||
></a-input-password>
|
|
||||||
<a-button type="outline" :disabled="jiraConfig.platformAccount === '' || jiraConfig.platformPsw === ''">
|
|
||||||
{{ t('ms.personal.valid') }}
|
|
||||||
</a-button>
|
|
||||||
</a-form-item>
|
|
||||||
</a-form>
|
|
||||||
</div>
|
|
||||||
<div class="platform-card">
|
|
||||||
<div class="mb-[16px] flex items-center">
|
|
||||||
<a-image src="/plugin/image/jira?imagePath=static/jira.jpg" width="24"></a-image>
|
|
||||||
<div class="ml-[8px] mr-[4px] font-medium text-[var(--color-text-1)]">
|
|
||||||
{{ t('ms.personal.zendao') }}
|
|
||||||
</div>
|
|
||||||
<a-tooltip :content="t('ms.personal.zendaoTip')" position="right">
|
|
||||||
<icon-exclamation-circle
|
|
||||||
class="mr-[8px] text-[var(--color-text-4)] hover:text-[rgb(var(--primary-5))]"
|
|
||||||
size="16"
|
|
||||||
/>
|
|
||||||
</a-tooltip>
|
|
||||||
<MsTag theme="light" :type="tagMap[zendaoConfig.status].type" size="small" class="px-[4px]">
|
|
||||||
{{ tagMap[zendaoConfig.status].text }}
|
|
||||||
</MsTag>
|
|
||||||
</div>
|
|
||||||
<a-form ref="zendaoFormRef" :model="zendaoConfig">
|
|
||||||
<a-form-item :label="t('ms.personal.platformAccount')">
|
|
||||||
<a-input
|
|
||||||
v-model:model-value="zendaoConfig.platformAccount"
|
|
||||||
:placeholder="t('ms.personal.platformAccountPlaceholder', { type: t('ms.personal.zendao') })"
|
|
||||||
class="w-[312px]"
|
|
||||||
allow-clear
|
|
||||||
autocomplete="new-password"
|
|
||||||
></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item :label="t('ms.personal.platformPsw')">
|
|
||||||
<a-input-password
|
|
||||||
v-model:model-value="zendaoConfig.platformPsw"
|
|
||||||
:placeholder="t('ms.personal.platformPswPlaceholder', { type: t('ms.personal.zendao') })"
|
|
||||||
class="mr-[8px] w-[312px]"
|
|
||||||
allow-clear
|
|
||||||
autocomplete="new-password"
|
|
||||||
></a-input-password>
|
|
||||||
<a-button type="outline" :disabled="zendaoConfig.platformAccount === '' || zendaoConfig.platformPsw === ''">
|
|
||||||
{{ t('ms.personal.valid') }}
|
|
||||||
</a-button>
|
|
||||||
</a-form-item>
|
|
||||||
</a-form>
|
|
||||||
</div>
|
|
||||||
<div class="platform-card">
|
|
||||||
<div class="mb-[16px] flex items-center">
|
|
||||||
<a-image src="/plugin/image/jira?imagePath=static/jira.jpg" width="24"></a-image>
|
|
||||||
<div class="ml-[8px] mr-[4px] font-medium text-[var(--color-text-1)]"> Azure DeVops </div>
|
|
||||||
<a-tooltip :content="t('ms.personal.azureTip')" position="right">
|
|
||||||
<icon-exclamation-circle
|
|
||||||
class="mr-[8px] text-[var(--color-text-4)] hover:text-[rgb(var(--primary-5))]"
|
|
||||||
size="16"
|
|
||||||
/>
|
|
||||||
</a-tooltip>
|
|
||||||
<MsTag theme="light" :type="tagMap[azureConfig.status].type" size="small" class="px-[4px]">
|
|
||||||
{{ tagMap[azureConfig.status].text }}
|
|
||||||
</MsTag>
|
|
||||||
</div>
|
|
||||||
<a-form ref="zendaoFormRef" :model="azureConfig">
|
|
||||||
<a-form-item>
|
|
||||||
<template #label>
|
|
||||||
<div class="flex text-right leading-none"> Personal Access Tokens </div>
|
|
||||||
</template>
|
|
||||||
<a-input
|
|
||||||
v-model:model-value="azureConfig.token"
|
|
||||||
:placeholder="t('ms.personal.azurePlaceholder')"
|
|
||||||
class="mr-[8px] w-[312px]"
|
|
||||||
allow-clear
|
|
||||||
autocomplete="new-password"
|
|
||||||
></a-input>
|
|
||||||
<a-button type="outline" :disabled="azureConfig.token === ''">
|
|
||||||
{{ t('ms.personal.valid') }}
|
|
||||||
</a-button>
|
|
||||||
</a-form-item>
|
|
||||||
</a-form>
|
|
||||||
</div>
|
|
||||||
<div class="platform-card">
|
|
||||||
<div class="mb-[16px] flex items-center">
|
|
||||||
<a-image src="/plugin/image/jira?imagePath=static/jira.jpg" width="24"></a-image>
|
|
||||||
<div class="ml-[8px] mr-[4px] font-medium text-[var(--color-text-1)]"> TAPD </div>
|
|
||||||
<a-popover position="right">
|
|
||||||
<template #content>
|
|
||||||
<div class="bg-[var(--color-text-n9)] p-[12px]">
|
|
||||||
<a-image src="/images/tapd-user.png" :width="385"></a-image>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<icon-exclamation-circle
|
|
||||||
class="mr-[8px] text-[var(--color-text-4)] hover:text-[rgb(var(--primary-5))]"
|
|
||||||
size="16"
|
|
||||||
/>
|
|
||||||
</a-popover>
|
|
||||||
<MsTag theme="light" :type="tagMap[tapdConfig.status].type" size="small" class="px-[4px]">
|
|
||||||
{{ tagMap[tapdConfig.status].text }}
|
|
||||||
</MsTag>
|
|
||||||
</div>
|
|
||||||
<a-form ref="zendaoFormRef" :model="tapdConfig">
|
|
||||||
<a-form-item :label="t('ms.personal.platformName')">
|
|
||||||
<a-input
|
|
||||||
v-model:model-value="tapdConfig.name"
|
|
||||||
:placeholder="t('ms.personal.platformNamePlaceholder')"
|
|
||||||
class="mr-[8px] w-[312px]"
|
|
||||||
allow-clear
|
|
||||||
autocomplete="new-password"
|
|
||||||
/>
|
|
||||||
<a-button type="outline" :disabled="tapdConfig.name === ''">
|
|
||||||
{{ t('ms.personal.valid') }}
|
|
||||||
</a-button>
|
|
||||||
</a-form-item>
|
|
||||||
</a-form>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { Message } from '@arco-design/web-vue';
|
||||||
|
|
||||||
|
import MsFormCreate from '@/components/pure/ms-form-create/ms-form-create.vue';
|
||||||
import MsTag, { TagType } from '@/components/pure/ms-tag/ms-tag.vue';
|
import MsTag, { TagType } from '@/components/pure/ms-tag/ms-tag.vue';
|
||||||
|
|
||||||
|
import { getPlatform, getPlatformAccount, savePlatform, validatePlatform } from '@/api/modules/user/index';
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
@ -185,33 +64,75 @@
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const jiraConfig = ref({
|
const dynamicForm = ref<any>({});
|
||||||
status: 0 as Status,
|
const options = ref({
|
||||||
authType: 'basic',
|
resetBtn: false,
|
||||||
platformAccount: '',
|
submitBtn: false,
|
||||||
platformPsw: '',
|
on: false,
|
||||||
|
form: {
|
||||||
|
layout: 'vertical',
|
||||||
|
labelAlign: 'left',
|
||||||
|
},
|
||||||
|
row: {
|
||||||
|
gutter: 0,
|
||||||
|
},
|
||||||
|
wrap: {
|
||||||
|
'asterisk-position': 'end',
|
||||||
|
'validate-trigger': ['change'],
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const zendaoConfig = ref({
|
async function initPlatformAccountInfo() {
|
||||||
status: 0 as Status,
|
try {
|
||||||
platformAccount: '',
|
const res = await getPlatformAccount();
|
||||||
platformPsw: '',
|
Object.keys(res).forEach((key) => {
|
||||||
});
|
dynamicForm.value[key] = {
|
||||||
|
key,
|
||||||
|
status: 0,
|
||||||
|
formModel: {},
|
||||||
|
formRules: res[key].formItems,
|
||||||
|
formItemList: [],
|
||||||
|
tooltip: res[key].instructionsInfo,
|
||||||
|
validateLoading: false,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const azureConfig = ref({
|
async function initPlatformInfo() {
|
||||||
status: 0 as Status,
|
try {
|
||||||
token: '',
|
const res = await getPlatform();
|
||||||
});
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const tapdConfig = ref({
|
async function validate(config: any) {
|
||||||
status: 0 as Status,
|
try {
|
||||||
name: '',
|
config.validateLoading = true;
|
||||||
|
await validatePlatform(config.key, config.formModel.form);
|
||||||
|
Message.success(t('ms.personal.validPass'));
|
||||||
|
config.status = 1;
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(error);
|
||||||
|
config.status = 2;
|
||||||
|
} finally {
|
||||||
|
config.validateLoading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onBeforeMount(() => {
|
||||||
|
initPlatformAccountInfo();
|
||||||
|
initPlatformInfo();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.platform-card-container {
|
.platform-card-container {
|
||||||
@apply flex flex-1 flex-wrap overflow-auto;
|
@apply flex flex-wrap overflow-auto;
|
||||||
.ms-scroll-bar();
|
.ms-scroll-bar();
|
||||||
|
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
|
|
|
@ -26,6 +26,7 @@ export interface MsSearchSelectProps {
|
||||||
valueKey?: string; // 选项的 value 字段名,默认为 value
|
valueKey?: string; // 选项的 value 字段名,默认为 value
|
||||||
labelKey?: string; // 选项的 label 字段名,默认为 label
|
labelKey?: string; // 选项的 label 字段名,默认为 label
|
||||||
options: SelectOptionData[];
|
options: SelectOptionData[];
|
||||||
|
objectValue?: boolean; // 是否使用选项对象作为 value
|
||||||
multiple?: boolean; // 是否多选
|
multiple?: boolean; // 是否多选
|
||||||
atLeastOne?: boolean; // 是否至少选择一个,多选模式下有效
|
atLeastOne?: boolean; // 是否至少选择一个,多选模式下有效
|
||||||
remoteFieldsMap?: RemoteFieldsMap; // 远程模式下的结果 key 映射,例如 { value: 'id' },表示远程请求时,会将返回结果的 id 赋值到 value 字段
|
remoteFieldsMap?: RemoteFieldsMap; // 远程模式下的结果 key 映射,例如 { value: 'id' },表示远程请求时,会将返回结果的 id 赋值到 value 字段
|
||||||
|
@ -217,7 +218,9 @@ export default defineComponent(
|
||||||
function handleSelectAllChange(val: boolean) {
|
function handleSelectAllChange(val: boolean) {
|
||||||
isSelectAll.value = val;
|
isSelectAll.value = val;
|
||||||
if (val) {
|
if (val) {
|
||||||
innerValue.value = [...filterOptions.value];
|
innerValue.value = props.objectValue
|
||||||
|
? [...filterOptions.value]
|
||||||
|
: filterOptions.value.map((e) => e[props.valueKey || 'value']);
|
||||||
emit('update:modelValue', innerValue.value);
|
emit('update:modelValue', innerValue.value);
|
||||||
} else {
|
} else {
|
||||||
innerValue.value = [];
|
innerValue.value = [];
|
||||||
|
@ -256,7 +259,7 @@ export default defineComponent(
|
||||||
<a-tooltip content={item.tooltipContent} mouse-enter-delay={500}>
|
<a-tooltip content={item.tooltipContent} mouse-enter-delay={500}>
|
||||||
<a-option
|
<a-option
|
||||||
key={item[props.valueKey || 'value']}
|
key={item[props.valueKey || 'value']}
|
||||||
value={item}
|
value={props.objectValue ? item : item[props.valueKey || 'value']}
|
||||||
tag-props={
|
tag-props={
|
||||||
props.multiple && props.atLeastOne
|
props.multiple && props.atLeastOne
|
||||||
? { closable: Array.isArray(innerValue.value) && innerValue.value.length > 1 }
|
? { closable: Array.isArray(innerValue.value) && innerValue.value.length > 1 }
|
||||||
|
@ -375,7 +378,7 @@ export default defineComponent(
|
||||||
class="one-line-text"
|
class="one-line-text"
|
||||||
style={singleTagMaxWidth.value > 0 ? { maxWidth: `${singleTagMaxWidth.value}px` } : {}}
|
style={singleTagMaxWidth.value > 0 ? { maxWidth: `${singleTagMaxWidth.value}px` } : {}}
|
||||||
>
|
>
|
||||||
{data.label}
|
{slots.label ? slots.label(data) : data.label}
|
||||||
</div>
|
</div>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
),
|
),
|
||||||
|
@ -411,6 +414,7 @@ export default defineComponent(
|
||||||
'fallbackOption',
|
'fallbackOption',
|
||||||
'labelKey',
|
'labelKey',
|
||||||
'atLeastOne',
|
'atLeastOne',
|
||||||
|
'objectValue',
|
||||||
],
|
],
|
||||||
emits: ['update:modelValue', 'remoteSearch', 'popupVisibleChange', 'update:loading', 'remove'],
|
emits: ['update:modelValue', 'remoteSearch', 'popupVisibleChange', 'update:loading', 'remove'],
|
||||||
}
|
}
|
||||||
|
|
|
@ -235,6 +235,7 @@
|
||||||
(e: 'dataIndexChange', value: string): void;
|
(e: 'dataIndexChange', value: string): void;
|
||||||
(e: 'update:count', value: number): void; // 用于展示 FilterIcon 的数量
|
(e: 'update:count', value: number): void; // 用于展示 FilterIcon 的数量
|
||||||
(e: 'update:rowCount', value: number): void; // 用于展示 MsBaseTable 的总行数
|
(e: 'update:rowCount', value: number): void; // 用于展示 MsBaseTable 的总行数
|
||||||
|
(e: 'reset'): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const isMultipleSelect = (dataIndex: string) => {
|
const isMultipleSelect = (dataIndex: string) => {
|
||||||
|
@ -316,6 +317,7 @@
|
||||||
const handleReset = () => {
|
const handleReset = () => {
|
||||||
formRef.value?.resetFields();
|
formRef.value?.resetFields();
|
||||||
formModel.list = [getInitItem()];
|
formModel.list = [getInitItem()];
|
||||||
|
emit('reset');
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* @description 筛选
|
* @description 筛选
|
||||||
|
|
|
@ -42,6 +42,7 @@
|
||||||
class="mt-[8px]"
|
class="mt-[8px]"
|
||||||
@on-search="handleFilter"
|
@on-search="handleFilter"
|
||||||
@data-index-change="dataIndexChange"
|
@data-index-change="dataIndexChange"
|
||||||
|
@reset="emit('reset')"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -69,6 +70,7 @@
|
||||||
(e: 'keywordSearch', value: string | undefined): void; // innerKeyword 搜索
|
(e: 'keywordSearch', value: string | undefined): void; // innerKeyword 搜索
|
||||||
(e: 'advSearch', value: FilterResult): void; // 高级搜索
|
(e: 'advSearch', value: FilterResult): void; // 高级搜索
|
||||||
(e: 'dataIndexChange', value: string): void; // 高级搜索选项变更
|
(e: 'dataIndexChange', value: string): void; // 高级搜索选项变更
|
||||||
|
(e: 'reset'): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
@ -82,7 +84,6 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleFilter = (filter: FilterResult) => {
|
const handleFilter = (filter: FilterResult) => {
|
||||||
console.log('filter', filter);
|
|
||||||
emit('advSearch', filter);
|
emit('advSearch', filter);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,9 @@
|
||||||
type="icon-icon_that_person"
|
type="icon-icon_that_person"
|
||||||
:size="props.size"
|
:size="props.size"
|
||||||
class="text-[var(--color-text-4)]"
|
class="text-[var(--color-text-4)]"
|
||||||
|
:style="{
|
||||||
|
fontSize: `${props.size}px !important`,
|
||||||
|
}"
|
||||||
/>
|
/>
|
||||||
<a-avatar
|
<a-avatar
|
||||||
v-else-if="props.avatar === 'word'"
|
v-else-if="props.avatar === 'word'"
|
||||||
|
|
|
@ -162,7 +162,7 @@
|
||||||
const handleConfirm = async () => {
|
const handleConfirm = async () => {
|
||||||
await validateForm();
|
await validateForm();
|
||||||
if (props.isDelete) {
|
if (props.isDelete) {
|
||||||
emits('confirm');
|
emits('confirm', undefined, handleCancel);
|
||||||
} else {
|
} else {
|
||||||
emits('confirm', form.value, handleCancel);
|
emits('confirm', form.value, handleCancel);
|
||||||
}
|
}
|
||||||
|
|
|
@ -85,6 +85,7 @@
|
||||||
defineProps<{
|
defineProps<{
|
||||||
raw?: string;
|
raw?: string;
|
||||||
uploadImage?: (file: File) => Promise<any>;
|
uploadImage?: (file: File) => Promise<any>;
|
||||||
|
maxHeight?: string;
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
raw: '',
|
raw: '',
|
||||||
|
@ -112,7 +113,6 @@
|
||||||
const uploadQueue: queueAsPromised<Task> = fastq.promise(asyncWorker, 1);
|
const uploadQueue: queueAsPromised<Task> = fastq.promise(asyncWorker, 1);
|
||||||
|
|
||||||
const { currentLocale } = useLocale();
|
const { currentLocale } = useLocale();
|
||||||
const locale = computed(() => currentLocale.value as 'zh-CN' | 'en-US');
|
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.raw,
|
() => props.raw,
|
||||||
|
@ -359,20 +359,30 @@
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
editor.value?.destroy();
|
editor.value?.destroy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const contentStyles = computed(() => {
|
||||||
|
return {
|
||||||
|
maxHeight: props.maxHeight || '200px',
|
||||||
|
overflow: 'auto',
|
||||||
|
};
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="rich-wrapper flex w-full">
|
<div class="rich-wrapper flex w-full">
|
||||||
<AttachmentSelectorModal v-model:visible="attachmentSelectorModal" />
|
<AttachmentSelectorModal v-model:visible="attachmentSelectorModal" />
|
||||||
<RichTextEditor v-if="editor" :editor="editor" :locale="locale" />
|
<RichTextEditor v-if="editor" :editor="editor" :content-styles="contentStyles" :locale="currentLocale" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="less">
|
<style scoped lang="less">
|
||||||
.rich-wrapper {
|
.rich-wrapper {
|
||||||
position: relative;
|
@apply relative overflow-hidden;
|
||||||
|
|
||||||
border: 1px solid var(--color-text-n8);
|
border: 1px solid var(--color-text-n8);
|
||||||
|
border-radius: var(--border-radius-small);
|
||||||
:deep(.halo-rich-text-editor .ProseMirror) {
|
:deep(.halo-rich-text-editor .ProseMirror) {
|
||||||
|
padding: 16px 24px !important;
|
||||||
p:first-child {
|
p:first-child {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
@ -383,21 +393,22 @@
|
||||||
color: var(--color-text-3) !important;
|
color: var(--color-text-3) !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 修改滚动条
|
:deep(.editor-content) {
|
||||||
:deep(.editor-header + div > div) {
|
.ms-scroll-bar();
|
||||||
&::-webkit-scrollbar {
|
}
|
||||||
width: 6px !important;
|
</style>
|
||||||
height: 4px !important;
|
|
||||||
|
<style lang="less">
|
||||||
|
.v-popper__popper {
|
||||||
|
.v-popper__inner {
|
||||||
|
.drop-shadow {
|
||||||
|
.ms-scroll-bar();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
&::-webkit-scrollbar-thumb {
|
}
|
||||||
border-radius: 8px !important;
|
.tippy-box {
|
||||||
background: var(--color-text-input-border) !important;
|
.command-items {
|
||||||
}
|
.ms-scroll-bar();
|
||||||
&::-webkit-scrollbar-thumb:hover {
|
|
||||||
background: #a1a7b0 !important;
|
|
||||||
}
|
|
||||||
&&::-webkit-scrollbar-track {
|
|
||||||
@apply bg-white !important;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -150,6 +150,9 @@
|
||||||
:deep(.arco-split-pane) {
|
:deep(.arco-split-pane) {
|
||||||
@apply relative overflow-hidden;
|
@apply relative overflow-hidden;
|
||||||
}
|
}
|
||||||
|
// :deep(.arco-split-pane-second) {
|
||||||
|
// @apply z-10;
|
||||||
|
// }
|
||||||
.animating {
|
.animating {
|
||||||
:deep(.arco-split-pane) {
|
:deep(.arco-split-pane) {
|
||||||
@apply relative overflow-hidden;
|
@apply relative overflow-hidden;
|
||||||
|
@ -207,12 +210,12 @@
|
||||||
.vertical-expand-line {
|
.vertical-expand-line {
|
||||||
@apply relative z-10 flex items-center justify-center bg-transparent;
|
@apply relative z-10 flex items-center justify-center bg-transparent;
|
||||||
&::before {
|
&::before {
|
||||||
@apply absolute w-full;
|
@apply absolute w-full bg-transparent;
|
||||||
|
|
||||||
margin-bottom: -4px;
|
margin-bottom: -4px;
|
||||||
height: 4px;
|
height: 4px;
|
||||||
box-shadow: 0 -1px 4px 0 rgb(31 35 41 / 10%), 0 -1px 4px 0 rgb(255 255 255), 0 -1px 4px 0 rgb(255 255 255),
|
box-shadow: 0 -2px 2px 0 rgb(31 35 41 / 10%), 0 -4px 4px 0 rgb(255 255 255), 0 -4px 4px 0 rgb(255 255 255),
|
||||||
0 -1px 4px 0 rgb(255 255 255);
|
0 -4px 4px 0 rgb(255 255 255);
|
||||||
content: '';
|
content: '';
|
||||||
}
|
}
|
||||||
// .expand-icon--vertical {
|
// .expand-icon--vertical {
|
||||||
|
|
|
@ -94,6 +94,8 @@ export default function useTableProps<T>(
|
||||||
const keyword = ref('');
|
const keyword = ref('');
|
||||||
// 高级筛选
|
// 高级筛选
|
||||||
const advanceFilter = reactive<FilterResult>({ accordBelow: 'AND', combine: {} });
|
const advanceFilter = reactive<FilterResult>({ accordBelow: 'AND', combine: {} });
|
||||||
|
// 表格请求参数集合
|
||||||
|
const tableQueryParams = ref<TableQueryParams>({});
|
||||||
|
|
||||||
// 是否分页
|
// 是否分页
|
||||||
if (propsRes.value.showPagination) {
|
if (propsRes.value.showPagination) {
|
||||||
|
@ -193,7 +195,7 @@ export default function useTableProps<T>(
|
||||||
try {
|
try {
|
||||||
if (loadListFunc) {
|
if (loadListFunc) {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const data = await loadListFunc({
|
tableQueryParams.value = {
|
||||||
current,
|
current,
|
||||||
pageSize: currentPageSize,
|
pageSize: currentPageSize,
|
||||||
sort: sortItem.value,
|
sort: sortItem.value,
|
||||||
|
@ -202,7 +204,8 @@ export default function useTableProps<T>(
|
||||||
combine: advanceFilter.combine,
|
combine: advanceFilter.combine,
|
||||||
searchMode: advanceFilter.accordBelow,
|
searchMode: advanceFilter.accordBelow,
|
||||||
...loadListParams.value,
|
...loadListParams.value,
|
||||||
});
|
};
|
||||||
|
const data = await loadListFunc(tableQueryParams.value);
|
||||||
const tmpArr = data.list;
|
const tmpArr = data.list;
|
||||||
propsRes.value.data = tmpArr.map((item: MsTableDataItem<T>) => {
|
propsRes.value.data = tmpArr.map((item: MsTableDataItem<T>) => {
|
||||||
if (item.updateTime) {
|
if (item.updateTime) {
|
||||||
|
@ -312,6 +315,11 @@ export default function useTableProps<T>(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 获取表格请求参数
|
||||||
|
const getTableQueryParams = () => {
|
||||||
|
return tableQueryParams.value;
|
||||||
|
};
|
||||||
|
|
||||||
// 事件触发组
|
// 事件触发组
|
||||||
const propsEvent = ref({
|
const propsEvent = ref({
|
||||||
// 排序触发
|
// 排序触发
|
||||||
|
@ -442,5 +450,6 @@ export default function useTableProps<T>(
|
||||||
resetPagination,
|
resetPagination,
|
||||||
getSelectedCount,
|
getSelectedCount,
|
||||||
resetSelector,
|
resetSelector,
|
||||||
|
getTableQueryParams,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -222,7 +222,7 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
if (props.isAllScreen) resizeObserver.value.disconnect();
|
if (props.isAllScreen) resizeObserver.value?.disconnect();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
<template v-if="showProjectSelect">
|
<template v-if="showProjectSelect">
|
||||||
<a-divider direction="vertical" class="ml-0" />
|
<a-divider direction="vertical" class="ml-0" />
|
||||||
<a-select
|
<a-select
|
||||||
class="w-auto min-w-[150px] max-w-[200px] focus-within:!bg-[var(--color-text-n8)] hover:!bg-[var(--color-text-n8)]"
|
class="w-[200px] focus-within:!bg-[var(--color-text-n8)] hover:!bg-[var(--color-text-n8)]"
|
||||||
:default-value="appStore.currentProjectId"
|
:default-value="appStore.currentProjectId"
|
||||||
:bordered="false"
|
:bordered="false"
|
||||||
allow-search
|
allow-search
|
||||||
|
|
|
@ -0,0 +1,81 @@
|
||||||
|
import { ReviewItem } from '@/models/caseManagement/caseReview';
|
||||||
|
|
||||||
|
// 评审结果
|
||||||
|
export const reviewResultMap = {
|
||||||
|
UN_REVIEWED: {
|
||||||
|
label: 'caseManagement.caseReview.unReview',
|
||||||
|
color: 'var(--color-text-input-border)',
|
||||||
|
icon: 'icon-icon_block_filled',
|
||||||
|
},
|
||||||
|
UNDER_REVIEWED: {
|
||||||
|
label: 'caseManagement.caseReview.reviewing',
|
||||||
|
color: 'rgb(var(--link-6))',
|
||||||
|
icon: 'icon-icon_testing',
|
||||||
|
},
|
||||||
|
PASS: {
|
||||||
|
label: 'caseManagement.caseReview.reviewPass',
|
||||||
|
color: 'rgb(var(--success-6))',
|
||||||
|
icon: 'icon-icon_succeed_filled',
|
||||||
|
},
|
||||||
|
UN_PASS: {
|
||||||
|
label: 'caseManagement.caseReview.fail',
|
||||||
|
color: 'rgb(var(--danger-6))',
|
||||||
|
icon: 'icon-icon_close_filled',
|
||||||
|
},
|
||||||
|
RE_REVIEWED: {
|
||||||
|
label: 'caseManagement.caseReview.reReview',
|
||||||
|
color: 'rgb(var(--warning-6))',
|
||||||
|
icon: 'icon-icon_resubmit_filled',
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
// 评审状态
|
||||||
|
export const reviewStatusMap = {
|
||||||
|
PREPARED: {
|
||||||
|
label: 'caseManagement.caseReview.unStart',
|
||||||
|
color: 'var(--color-text-n8)',
|
||||||
|
class: '!text-[var(--color-text-1)]',
|
||||||
|
},
|
||||||
|
UNDERWAY: {
|
||||||
|
label: 'caseManagement.caseReview.going',
|
||||||
|
color: 'rgb(var(--link-2))',
|
||||||
|
class: '!text-[rgb(var(--link-6))]',
|
||||||
|
},
|
||||||
|
COMPLETED: {
|
||||||
|
label: 'caseManagement.caseReview.finished',
|
||||||
|
color: 'rgb(var(--success-2))',
|
||||||
|
class: '!text-[rgb(var(--success-6))]',
|
||||||
|
},
|
||||||
|
ARCHIVED: {
|
||||||
|
label: 'caseManagement.caseReview.archived',
|
||||||
|
color: 'var(--color-text-n8)',
|
||||||
|
class: '!text-[var(--color-text-4)]',
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
// 评审详情
|
||||||
|
export const reviewDefaultDetail: ReviewItem = {
|
||||||
|
id: '',
|
||||||
|
num: 0,
|
||||||
|
moduleId: '',
|
||||||
|
projectId: '',
|
||||||
|
reviewPassRule: 'SINGLE',
|
||||||
|
name: '',
|
||||||
|
status: 'PREPARED',
|
||||||
|
caseCount: 0,
|
||||||
|
passCount: 0,
|
||||||
|
unPassCount: 0,
|
||||||
|
reviewedCount: 0,
|
||||||
|
underReviewedCount: 0,
|
||||||
|
pos: 5000,
|
||||||
|
startTime: 0,
|
||||||
|
endTime: 0,
|
||||||
|
passRate: 0,
|
||||||
|
tags: [],
|
||||||
|
description: '',
|
||||||
|
createTime: 0,
|
||||||
|
createUser: '',
|
||||||
|
updateTime: 0,
|
||||||
|
updateUser: '',
|
||||||
|
reviewers: [],
|
||||||
|
reReviewedCount: 0,
|
||||||
|
followFlag: false,
|
||||||
|
};
|
|
@ -38,7 +38,7 @@ export default function useSelect(config: UseSelectOption) {
|
||||||
let tagCount = 0;
|
let tagCount = 0;
|
||||||
const values = Object.values(config.selectVal.value);
|
const values = Object.values(config.selectVal.value);
|
||||||
for (let i = 0; i < values.length; i++) {
|
for (let i = 0; i < values.length; i++) {
|
||||||
const tagWidth = values[i][config.labelKey || 'label'].length * 12; // 计算每个标签渲染出来的宽度,文字大小在12px时宽度也是 12px
|
const tagWidth = (values[i][config.labelKey || 'label']?.length || 0) * 12; // 计算每个标签渲染出来的宽度,文字大小在12px时宽度也是 12px
|
||||||
if (lastWidth > tagWidth + 36) {
|
if (lastWidth > tagWidth + 36) {
|
||||||
tagCount += 1;
|
tagCount += 1;
|
||||||
lastWidth -= tagWidth + 36; // 36px是标签的边距、边框等宽度
|
lastWidth -= tagWidth + 36; // 36px是标签的边距、边框等宽度
|
||||||
|
|
|
@ -74,6 +74,7 @@ export default {
|
||||||
'common.expandAll': 'Expand all',
|
'common.expandAll': 'Expand all',
|
||||||
'common.copy': 'Copy',
|
'common.copy': 'Copy',
|
||||||
'common.fork': 'Fork',
|
'common.fork': 'Fork',
|
||||||
|
'common.forked': 'Forked',
|
||||||
'common.more': 'More',
|
'common.more': 'More',
|
||||||
'common.recycle': 'Recycle Bin',
|
'common.recycle': 'Recycle Bin',
|
||||||
'common.new': 'New',
|
'common.new': 'New',
|
||||||
|
@ -87,4 +88,6 @@ export default {
|
||||||
'common.json': 'Object',
|
'common.json': 'Object',
|
||||||
'common.integer': 'Integer',
|
'common.integer': 'Integer',
|
||||||
'common.file': 'File',
|
'common.file': 'File',
|
||||||
|
'common.desc': 'Description',
|
||||||
|
'common.root': 'Default Module',
|
||||||
};
|
};
|
||||||
|
|
|
@ -60,7 +60,7 @@ async function changeLocale(locale: LocaleType) {
|
||||||
|
|
||||||
export default function useLocale() {
|
export default function useLocale() {
|
||||||
const { locale } = i18n.global;
|
const { locale } = i18n.global;
|
||||||
const currentLocale = ref(locale);
|
const currentLocale = ref(locale as LocaleType);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
currentLocale,
|
currentLocale,
|
||||||
|
|
|
@ -72,10 +72,11 @@ export default {
|
||||||
'common.quickAddMember': '快速添加成员',
|
'common.quickAddMember': '快速添加成员',
|
||||||
'common.export': '导出',
|
'common.export': '导出',
|
||||||
'common.import': '导入',
|
'common.import': '导入',
|
||||||
'common.collapseAll': '展开全部',
|
'common.collapseAll': '收起全部',
|
||||||
'common.expandAll': '收起全部',
|
'common.expandAll': '展开全部',
|
||||||
'common.copy': '复制',
|
'common.copy': '复制',
|
||||||
'common.fork': '关注',
|
'common.fork': '关注',
|
||||||
|
'common.forked': '已关注',
|
||||||
'common.more': '更多',
|
'common.more': '更多',
|
||||||
'common.recycle': '回收站',
|
'common.recycle': '回收站',
|
||||||
'common.new': '新增',
|
'common.new': '新增',
|
||||||
|
@ -89,4 +90,6 @@ export default {
|
||||||
'common.json': '对象',
|
'common.json': '对象',
|
||||||
'common.integer': '整数',
|
'common.integer': '整数',
|
||||||
'common.file': '文件',
|
'common.file': '文件',
|
||||||
|
'common.desc': '描述',
|
||||||
|
'common.root': '默认模块',
|
||||||
};
|
};
|
||||||
|
|
|
@ -22,6 +22,17 @@ export interface ReviewModuleItem {
|
||||||
}
|
}
|
||||||
// 评审类型
|
// 评审类型
|
||||||
export type ReviewPassRule = 'SINGLE' | 'MULTIPLE';
|
export type ReviewPassRule = 'SINGLE' | 'MULTIPLE';
|
||||||
|
// 用例评审关联用例入参
|
||||||
|
export interface BaseAssociateCaseRequest {
|
||||||
|
excludeIds: string[];
|
||||||
|
selectIds: string[];
|
||||||
|
selectAll: boolean;
|
||||||
|
condition: Record<string, any>;
|
||||||
|
moduleIds: string[];
|
||||||
|
versionId: string;
|
||||||
|
refId: string;
|
||||||
|
projectId: string;
|
||||||
|
}
|
||||||
// 评审
|
// 评审
|
||||||
export interface Review {
|
export interface Review {
|
||||||
projectId: string;
|
projectId: string;
|
||||||
|
@ -33,18 +44,22 @@ export interface Review {
|
||||||
tags: string[];
|
tags: string[];
|
||||||
description: string;
|
description: string;
|
||||||
reviewers: string[]; // 评审人员
|
reviewers: string[]; // 评审人员
|
||||||
caseIds: string[]; // 关联用例
|
baseAssociateCaseRequest: BaseAssociateCaseRequest; // 关联用例
|
||||||
|
}
|
||||||
|
// 复制评审入参
|
||||||
|
export interface CopyReviewParams extends Omit<Review, 'baseAssociateCaseRequest'> {
|
||||||
|
copyId: string;
|
||||||
}
|
}
|
||||||
// 更新评审入参
|
// 更新评审入参
|
||||||
export interface UpdateReviewParams extends Omit<Review, 'caseIds'> {
|
export interface UpdateReviewParams extends Omit<Review, 'baseAssociateCaseRequest'> {
|
||||||
id: string;
|
id: string;
|
||||||
}
|
}
|
||||||
// 关联用例入参
|
// 关联用例入参
|
||||||
export interface AssociateReviewCaseParams {
|
export interface AssociateReviewCaseParams {
|
||||||
reviewId: string;
|
reviewId: string;
|
||||||
projectId: string;
|
projectId: string;
|
||||||
caseIds: string[];
|
|
||||||
reviewers: string[];
|
reviewers: string[];
|
||||||
|
baseAssociateCaseRequest: BaseAssociateCaseRequest;
|
||||||
}
|
}
|
||||||
// 关注/取消关注评审入参
|
// 关注/取消关注评审入参
|
||||||
export interface FollowReviewParams {
|
export interface FollowReviewParams {
|
||||||
|
@ -66,12 +81,57 @@ export interface SortReviewParams {
|
||||||
moveMode: ReviewMoveMode;
|
moveMode: ReviewMoveMode;
|
||||||
moveId: string; // 被移动的评审id
|
moveId: string; // 被移动的评审id
|
||||||
}
|
}
|
||||||
// 文件列表查询参数
|
// 评审状态, PREPARED: 待开始, UNDERWAY: 进行中, COMPLETED: 已完成, ARCHIVED: 已归档
|
||||||
|
export type ReviewStatus = 'PREPARED' | 'UNDERWAY' | 'COMPLETED' | 'ARCHIVED';
|
||||||
|
// 评审结果,UN_REVIEWED:未评审,UNDER_REVIEWED:评审中,PASS:通过,UN_PASS:未通过,RE_REVIEWED:重新提审
|
||||||
|
export type ReviewResult = 'UN_REVIEWED' | 'UNDER_REVIEWED' | 'PASS' | 'UN_PASS' | 'RE_REVIEWED';
|
||||||
|
// 评审列表查询参数
|
||||||
export interface ReviewListQueryParams extends TableQueryParams {
|
export interface ReviewListQueryParams extends TableQueryParams {
|
||||||
moduleIds: string[];
|
moduleIds: string[];
|
||||||
projectId: string;
|
projectId: string;
|
||||||
|
createByMe?: string;
|
||||||
|
reviewByMe?: string;
|
||||||
|
}
|
||||||
|
// 评审详情-用例列表查询参数
|
||||||
|
export interface ReviewDetailCaseListQueryParams extends TableQueryParams {
|
||||||
|
viewFlag: boolean; // 是否只看我的
|
||||||
|
reviewId: string;
|
||||||
|
}
|
||||||
|
// 评审详情-用例拖拽排序入参
|
||||||
|
export interface SortReviewCaseParams {
|
||||||
|
projectId: string;
|
||||||
|
targetId: string; // 目标用例id
|
||||||
|
moveMode: ReviewMoveMode;
|
||||||
|
moveId: string; // 被移动的用例id
|
||||||
|
reviewId: string; // 所属评审id
|
||||||
|
}
|
||||||
|
// 评审详情-批量评审用例
|
||||||
|
export interface BatchReviewCaseParams extends BatchApiParams {
|
||||||
|
reviewId: string; // 评审id
|
||||||
|
userId: string; // 用户id, 用来判断是否只看我的
|
||||||
|
reviewPassRule: ReviewPassRule; // 评审规则
|
||||||
|
status: ReviewResult; // 评审结果
|
||||||
|
content: string; // 评论内容
|
||||||
|
notifier: string; // 评论@的人的Id, 多个以';'隔开
|
||||||
|
}
|
||||||
|
// 评审详情-批量修改评审人
|
||||||
|
export interface BatchChangeReviewerParams extends BatchApiParams {
|
||||||
|
reviewId: string; // 评审id
|
||||||
|
userId: string; // 用户id, 用来判断是否只看我的
|
||||||
|
reviewerId: string[]; // 评审人员id
|
||||||
|
append: boolean; // 是否追加
|
||||||
|
}
|
||||||
|
// 评审详情-批量取消关联用例
|
||||||
|
export interface BatchCancelReviewCaseParams extends BatchApiParams {
|
||||||
|
reviewId: string; // 评审id
|
||||||
|
userId: string; // 用户id, 用来判断是否只看我的
|
||||||
|
}
|
||||||
|
export interface ReviewDetailReviewersItem {
|
||||||
|
avatar: string;
|
||||||
|
reviewId: string;
|
||||||
|
userId: string;
|
||||||
|
userName: string;
|
||||||
}
|
}
|
||||||
export type ReviewStatus = 'PREPARED' | 'UNDERWAY' | 'COMPLETED' | 'ARCHIVED'; // 评审状态, PREPARED: 待开始, UNDERWAY: 进行中, COMPLETED: 已完成, ARCHIVED: 已归档
|
|
||||||
// 评审列表项
|
// 评审列表项
|
||||||
export interface ReviewItem {
|
export interface ReviewItem {
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -86,13 +146,13 @@ export interface ReviewItem {
|
||||||
endTime: number;
|
endTime: number;
|
||||||
caseCount: number;
|
caseCount: number;
|
||||||
passRate: number;
|
passRate: number;
|
||||||
tags: string;
|
tags: string[];
|
||||||
description: string;
|
description: string;
|
||||||
createTime: number;
|
createTime: number;
|
||||||
createUser: string;
|
createUser: string;
|
||||||
updateTime: number;
|
updateTime: number;
|
||||||
updateUser: string;
|
updateUser: string;
|
||||||
reviewers: string[];
|
reviewers: ReviewDetailReviewersItem[];
|
||||||
passCount: number;
|
passCount: number;
|
||||||
unPassCount: number;
|
unPassCount: number;
|
||||||
reReviewedCount: number;
|
reReviewedCount: number;
|
||||||
|
@ -117,4 +177,44 @@ export interface ReviewUserItem {
|
||||||
createUser: string;
|
createUser: string;
|
||||||
updateUser: string;
|
updateUser: string;
|
||||||
deleted: boolean;
|
deleted: boolean;
|
||||||
|
avatar: string;
|
||||||
|
}
|
||||||
|
// 评审详情-用例列表项
|
||||||
|
export interface ReviewCaseItem {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
num: string;
|
||||||
|
caseId: string;
|
||||||
|
versionId: string;
|
||||||
|
versionName: string;
|
||||||
|
reviewers: string[];
|
||||||
|
reviewNames: string[];
|
||||||
|
status: string;
|
||||||
|
moduleId: string;
|
||||||
|
moduleName: string;
|
||||||
|
}
|
||||||
|
// 评审详情-提交评审入参
|
||||||
|
export interface CommitReviewResultParams {
|
||||||
|
projectId: string;
|
||||||
|
reviewId: string;
|
||||||
|
caseId: string;
|
||||||
|
reviewPassRule: ReviewPassRule;
|
||||||
|
status: ReviewResult;
|
||||||
|
content: string;
|
||||||
|
notifier: string;
|
||||||
|
}
|
||||||
|
// 评审详情-获取用例评审历史
|
||||||
|
export interface ReviewHistoryItem {
|
||||||
|
id: string;
|
||||||
|
reviewId: string;
|
||||||
|
caseId: string;
|
||||||
|
status: ReviewResult;
|
||||||
|
deleted: boolean; // 是否是取消关联或评审被删除的:0-否,1-是
|
||||||
|
notifier: string;
|
||||||
|
createUser: string;
|
||||||
|
createTime: number;
|
||||||
|
content: string;
|
||||||
|
userLogo: string;
|
||||||
|
userName: string;
|
||||||
|
contentText: string;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import { TableQueryParams } from '@/models/common';
|
import { TableQueryParams } from '@/models/common';
|
||||||
import { StatusType } from '@/enums/caseEnum';
|
import { StatusType } from '@/enums/caseEnum';
|
||||||
|
|
||||||
|
import { ReviewResult } from './caseReview';
|
||||||
|
|
||||||
export interface ModulesTreeType {
|
export interface ModulesTreeType {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
@ -148,7 +150,7 @@ export interface BatchMoveOrCopyType {
|
||||||
excludeIds: string[] | undefined;
|
excludeIds: string[] | undefined;
|
||||||
condition: Record<string, any>;
|
condition: Record<string, any>;
|
||||||
}
|
}
|
||||||
|
export type CaseEditType = 'STEP' | 'TEXT';
|
||||||
// 创建或者更新
|
// 创建或者更新
|
||||||
export interface CreateOrUpdateCase {
|
export interface CreateOrUpdateCase {
|
||||||
id?: string;
|
id?: string;
|
||||||
|
@ -156,7 +158,7 @@ export interface CreateOrUpdateCase {
|
||||||
templateId: string;
|
templateId: string;
|
||||||
name: string;
|
name: string;
|
||||||
prerequisite: string; // prerequisite
|
prerequisite: string; // prerequisite
|
||||||
caseEditType: string; // 编辑模式:步骤模式/文本模式
|
caseEditType: CaseEditType; // 编辑模式:步骤模式/文本模式
|
||||||
steps: string;
|
steps: string;
|
||||||
textDescription: string;
|
textDescription: string;
|
||||||
expectedResult: string; // 预期结果
|
expectedResult: string; // 预期结果
|
||||||
|
@ -191,8 +193,8 @@ export interface DetailCase {
|
||||||
projectId: string;
|
projectId: string;
|
||||||
templateId?: string;
|
templateId?: string;
|
||||||
name: string;
|
name: string;
|
||||||
reviewStatus?: string;
|
reviewStatus: ReviewResult;
|
||||||
tags: any;
|
tags: string[];
|
||||||
caseEditType: string;
|
caseEditType: string;
|
||||||
versionId?: string;
|
versionId?: string;
|
||||||
publicCase: boolean;
|
publicCase: boolean;
|
||||||
|
@ -206,6 +208,7 @@ export interface DetailCase {
|
||||||
customFields: CustomAttributes[];
|
customFields: CustomAttributes[];
|
||||||
attachments?: AttachFileInfo[];
|
attachments?: AttachFileInfo[];
|
||||||
followFlag?: boolean;
|
followFlag?: boolean;
|
||||||
|
functionalPriority: string;
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -88,6 +88,7 @@ const CaseManagement: AppRouteRecordRaw = {
|
||||||
isTopMenu: true,
|
isTopMenu: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
// 创建评审
|
||||||
{
|
{
|
||||||
path: 'caseManagementReviewCreate',
|
path: 'caseManagementReviewCreate',
|
||||||
name: CaseManagementRouteEnum.CASE_MANAGEMENT_REVIEW_CREATE,
|
name: CaseManagementRouteEnum.CASE_MANAGEMENT_REVIEW_CREATE,
|
||||||
|
@ -109,6 +110,7 @@ const CaseManagement: AppRouteRecordRaw = {
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
// 评审详情
|
||||||
{
|
{
|
||||||
path: 'caseManagementReviewDetail',
|
path: 'caseManagementReviewDetail',
|
||||||
name: CaseManagementRouteEnum.CASE_MANAGEMENT_REVIEW_DETAIL,
|
name: CaseManagementRouteEnum.CASE_MANAGEMENT_REVIEW_DETAIL,
|
||||||
|
@ -128,6 +130,7 @@ const CaseManagement: AppRouteRecordRaw = {
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
// 评审详情-用例详情
|
||||||
{
|
{
|
||||||
path: 'caseManagementReviewDetailCaseDetail',
|
path: 'caseManagementReviewDetailCaseDetail',
|
||||||
name: CaseManagementRouteEnum.CASE_MANAGEMENT_REVIEW_DETAIL_CASE_DETAIL,
|
name: CaseManagementRouteEnum.CASE_MANAGEMENT_REVIEW_DETAIL_CASE_DETAIL,
|
||||||
|
|
|
@ -31,7 +31,7 @@ export interface AppRouteRecordRaw {
|
||||||
component: Component | string;
|
component: Component | string;
|
||||||
children?: AppRouteRecordRaw[];
|
children?: AppRouteRecordRaw[];
|
||||||
alias?: string | string[];
|
alias?: string | string[];
|
||||||
props?: Record<string, any>;
|
props?: Record<string, any> | boolean;
|
||||||
beforeEnter?: NavigationGuard | NavigationGuard[];
|
beforeEnter?: NavigationGuard | NavigationGuard[];
|
||||||
fullPath?: string;
|
fullPath?: string;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<a-popover position="tl" :disabled="!props.desc || props.desc.trim() === ''" class="ms-params-input-popover">
|
<a-popover position="tl" :disabled="!props.desc || props.desc.trim() === ''" class="ms-params-input-popover">
|
||||||
<template #content>
|
<template #content>
|
||||||
<div class="param-popover-title">
|
<div class="param-popover-title">
|
||||||
{{ t('ms.apiTestDebug.desc') }}
|
{{ t('apiTestDebug.desc') }}
|
||||||
</div>
|
</div>
|
||||||
<div class="param-popover-value">
|
<div class="param-popover-value">
|
||||||
{{ props.desc }}
|
{{ props.desc }}
|
||||||
|
|
|
@ -2,15 +2,15 @@
|
||||||
<MsBaseTable v-bind="propsRes" id="headerTable" :hoverable="false" v-on="propsEvent">
|
<MsBaseTable v-bind="propsRes" id="headerTable" :hoverable="false" v-on="propsEvent">
|
||||||
<template #encodeTitle>
|
<template #encodeTitle>
|
||||||
<div class="flex items-center text-[var(--color-text-3)]">
|
<div class="flex items-center text-[var(--color-text-3)]">
|
||||||
{{ t('ms.apiTestDebug.encode') }}
|
{{ t('apiTestDebug.encode') }}
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<icon-question-circle
|
<icon-question-circle
|
||||||
class="ml-[4px] text-[var(--color-text-brand)] hover:text-[rgb(var(--primary-5))]"
|
class="ml-[4px] text-[var(--color-text-brand)] hover:text-[rgb(var(--primary-5))]"
|
||||||
size="16"
|
size="16"
|
||||||
/>
|
/>
|
||||||
<template #content>
|
<template #content>
|
||||||
<div>{{ t('ms.apiTestDebug.encodeTip1') }}</div>
|
<div>{{ t('apiTestDebug.encodeTip1') }}</div>
|
||||||
<div>{{ t('ms.apiTestDebug.encodeTip2') }}</div>
|
<div>{{ t('apiTestDebug.encodeTip2') }}</div>
|
||||||
</template>
|
</template>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
@ -19,7 +19,7 @@
|
||||||
<a-popover position="tl" :disabled="!record.name || record.name.trim() === ''" class="ms-params-input-popover">
|
<a-popover position="tl" :disabled="!record.name || record.name.trim() === ''" class="ms-params-input-popover">
|
||||||
<template #content>
|
<template #content>
|
||||||
<div class="param-popover-title">
|
<div class="param-popover-title">
|
||||||
{{ t('ms.apiTestDebug.paramName') }}
|
{{ t('apiTestDebug.paramName') }}
|
||||||
</div>
|
</div>
|
||||||
<div class="param-popover-value">
|
<div class="param-popover-value">
|
||||||
{{ record.name }}
|
{{ record.name }}
|
||||||
|
@ -27,14 +27,14 @@
|
||||||
</template>
|
</template>
|
||||||
<a-input
|
<a-input
|
||||||
v-model:model-value="record.name"
|
v-model:model-value="record.name"
|
||||||
:placeholder="t('ms.apiTestDebug.paramNamePlaceholder')"
|
:placeholder="t('apiTestDebug.paramNamePlaceholder')"
|
||||||
class="param-input"
|
class="param-input"
|
||||||
@input="(val) => addTableLine(val)"
|
@input="(val) => addTableLine(val)"
|
||||||
/>
|
/>
|
||||||
</a-popover>
|
</a-popover>
|
||||||
</template>
|
</template>
|
||||||
<template #type="{ record }">
|
<template #type="{ record }">
|
||||||
<a-tooltip :content="t(record.required ? 'ms.apiTestDebug.paramRequired' : 'ms.apiTestDebug.paramNotRequired')">
|
<a-tooltip :content="t(record.required ? 'apiTestDebug.paramRequired' : 'apiTestDebug.paramNotRequired')">
|
||||||
<MsButton
|
<MsButton
|
||||||
type="icon"
|
type="icon"
|
||||||
:class="[
|
:class="[
|
||||||
|
@ -65,14 +65,14 @@
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<a-input-number
|
<a-input-number
|
||||||
v-model:model-value="record.min"
|
v-model:model-value="record.min"
|
||||||
:placeholder="t('ms.apiTestDebug.paramMin')"
|
:placeholder="t('apiTestDebug.paramMin')"
|
||||||
class="param-input"
|
class="param-input"
|
||||||
@input="(val) => addTableLine(val)"
|
@input="(val) => addTableLine(val)"
|
||||||
></a-input-number>
|
></a-input-number>
|
||||||
<div class="mx-[4px]">~</div>
|
<div class="mx-[4px]">~</div>
|
||||||
<a-input-number
|
<a-input-number
|
||||||
v-model:model-value="record.max"
|
v-model:model-value="record.max"
|
||||||
:placeholder="t('ms.apiTestDebug.paramMax')"
|
:placeholder="t('apiTestDebug.paramMax')"
|
||||||
class="param-input"
|
class="param-input"
|
||||||
@input="(val) => addTableLine(val)"
|
@input="(val) => addTableLine(val)"
|
||||||
></a-input-number>
|
></a-input-number>
|
||||||
|
@ -127,7 +127,7 @@
|
||||||
<a-modal
|
<a-modal
|
||||||
v-model:visible="showQuickInputParam"
|
v-model:visible="showQuickInputParam"
|
||||||
:title="t('ms.paramsInput.value')"
|
:title="t('ms.paramsInput.value')"
|
||||||
:ok-text="t('ms.apiTestDebug.apply')"
|
:ok-text="t('apiTestDebug.apply')"
|
||||||
class="ms-modal-form"
|
class="ms-modal-form"
|
||||||
body-class="!p-0"
|
body-class="!p-0"
|
||||||
:width="680"
|
:width="680"
|
||||||
|
@ -145,7 +145,7 @@
|
||||||
<template #title>
|
<template #title>
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<div class="text-[var(--color-text-1)]">
|
<div class="text-[var(--color-text-1)]">
|
||||||
{{ t('ms.apiTestDebug.quickInputParamsTip') }}
|
{{ t('apiTestDebug.quickInputParamsTip') }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -153,7 +153,7 @@
|
||||||
</a-modal>
|
</a-modal>
|
||||||
<a-modal
|
<a-modal
|
||||||
v-model:visible="showQuickInputDesc"
|
v-model:visible="showQuickInputDesc"
|
||||||
:title="t('ms.apiTestDebug.desc')"
|
:title="t('apiTestDebug.desc')"
|
||||||
:ok-text="t('common.save')"
|
:ok-text="t('common.save')"
|
||||||
:ok-button-props="{ disabled: !quickInputDescValue || quickInputDescValue.trim() === '' }"
|
:ok-button-props="{ disabled: !quickInputDescValue || quickInputDescValue.trim() === '' }"
|
||||||
class="ms-modal-form"
|
class="ms-modal-form"
|
||||||
|
@ -166,7 +166,7 @@
|
||||||
>
|
>
|
||||||
<a-textarea
|
<a-textarea
|
||||||
v-model:model-value="quickInputDescValue"
|
v-model:model-value="quickInputDescValue"
|
||||||
:placeholder="t('ms.apiTestDebug.descPlaceholder')"
|
:placeholder="t('apiTestDebug.descPlaceholder')"
|
||||||
:max-length="255"
|
:max-length="255"
|
||||||
show-word-limit
|
show-word-limit
|
||||||
></a-textarea>
|
></a-textarea>
|
||||||
|
@ -203,7 +203,7 @@
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
params: any[];
|
params: any[];
|
||||||
columns: MsTableColumn;
|
columns: MsTableColumn;
|
||||||
format?: RequestBodyFormat;
|
format?: RequestBodyFormat | 'query' | 'rest';
|
||||||
scroll?: {
|
scroll?: {
|
||||||
x?: number | string;
|
x?: number | string;
|
||||||
y?: number | string;
|
y?: number | string;
|
||||||
|
@ -257,7 +257,11 @@
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
const typeOptions = computed(() => {
|
const typeOptions = computed(() => {
|
||||||
if (props.format === RequestBodyFormat.X_WWW_FORM_URLENCODED) {
|
if (
|
||||||
|
props.format === RequestBodyFormat.X_WWW_FORM_URLENCODED ||
|
||||||
|
props.format === 'query' ||
|
||||||
|
props.format === 'rest'
|
||||||
|
) {
|
||||||
return allType.filter((e) => e.value !== 'file' && e.value !== 'json');
|
return allType.filter((e) => e.value !== 'file' && e.value !== 'json');
|
||||||
}
|
}
|
||||||
return allType;
|
return allType;
|
||||||
|
|
|
@ -0,0 +1,75 @@
|
||||||
|
<template>
|
||||||
|
<div class="mb-[8px] font-medium">{{ t('apiTestDebug.auth') }}</div>
|
||||||
|
<div class="rounded-[var(--border-radius-small)] border border-[var(--color-text-n8)] p-[16px]">
|
||||||
|
<div class="mb-[8px]">{{ t('apiTestDebug.authType') }}</div>
|
||||||
|
<a-radio-group v-model:model-value="authForm.authType" class="mb-[16px]" @change="authTypeChange">
|
||||||
|
<a-radio value="none">No Auth</a-radio>
|
||||||
|
<a-radio value="basic">Basic Auth</a-radio>
|
||||||
|
<a-radio value="digest">Digest Auth</a-radio>
|
||||||
|
</a-radio-group>
|
||||||
|
<a-form v-if="authForm.authType !== 'none'" ref="authFormRef" :model="authForm" layout="vertical">
|
||||||
|
<a-form-item
|
||||||
|
:label="t('apiTestDebug.account')"
|
||||||
|
:rules="[{ required: true, message: t('apiTestDebug.accountRequired') }]"
|
||||||
|
>
|
||||||
|
<a-input
|
||||||
|
v-model:model-value="authForm.account"
|
||||||
|
:placeholder="t('apiTestDebug.commonPlaceholder')"
|
||||||
|
class="w-[450px]"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
:label="t('apiTestDebug.password')"
|
||||||
|
:rules="[{ required: true, message: t('apiTestDebug.passwordRequired') }]"
|
||||||
|
>
|
||||||
|
<a-input-password
|
||||||
|
v-model:model-value="authForm.password"
|
||||||
|
autocomplete="new-password"
|
||||||
|
:placeholder="t('apiTestDebug.commonPlaceholder')"
|
||||||
|
class="w-[450px]"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useVModel } from '@vueuse/core';
|
||||||
|
import { FormInstance } from '@arco-design/web-vue';
|
||||||
|
|
||||||
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
|
|
||||||
|
interface AuthForm {
|
||||||
|
authType: string;
|
||||||
|
account: string;
|
||||||
|
password: string;
|
||||||
|
}
|
||||||
|
const props = defineProps<{
|
||||||
|
params: AuthForm;
|
||||||
|
}>();
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'update:params', val: AuthForm): void;
|
||||||
|
(e: 'change', val: AuthForm): void;
|
||||||
|
}>();
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const authForm = useVModel(props, 'params', emit);
|
||||||
|
const authFormRef = ref<FormInstance>();
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => authForm.value,
|
||||||
|
() => {
|
||||||
|
emit('change', authForm.value);
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
function authTypeChange(val: string | number | boolean) {
|
||||||
|
if (val === 'none') {
|
||||||
|
authForm.value.account = '';
|
||||||
|
authForm.value.password = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped></style>
|
|
@ -1,12 +1,12 @@
|
||||||
<template>
|
<template>
|
||||||
<a-button type="outline" size="mini" @click="showBatchAddParamDrawer = true">
|
<a-button type="outline" size="mini" @click="showBatchAddParamDrawer = true">
|
||||||
{{ t('ms.apiTestDebug.batchAdd') }}
|
{{ t('apiTestDebug.batchAdd') }}
|
||||||
</a-button>
|
</a-button>
|
||||||
<MsDrawer
|
<MsDrawer
|
||||||
v-model:visible="showBatchAddParamDrawer"
|
v-model:visible="showBatchAddParamDrawer"
|
||||||
:title="t('common.batchAdd')"
|
:title="t('common.batchAdd')"
|
||||||
:width="680"
|
:width="680"
|
||||||
:ok-text="t('ms.apiTestDebug.apply')"
|
:ok-text="t('apiTestDebug.apply')"
|
||||||
disabled-width-drag
|
disabled-width-drag
|
||||||
@confirm="applyBatchParams"
|
@confirm="applyBatchParams"
|
||||||
>
|
>
|
||||||
|
@ -22,10 +22,10 @@
|
||||||
<template #title>
|
<template #title>
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<div class="text-[12px] leading-[16px] text-[var(--color-text-4)]">
|
<div class="text-[12px] leading-[16px] text-[var(--color-text-4)]">
|
||||||
{{ t('ms.apiTestDebug.batchAddParamsTip') }}
|
{{ t('apiTestDebug.batchAddParamsTip') }}
|
||||||
</div>
|
</div>
|
||||||
<div class="text-[12px] leading-[16px] text-[var(--color-text-4)]">
|
<div class="text-[12px] leading-[16px] text-[var(--color-text-4)]">
|
||||||
{{ t('ms.apiTestDebug.batchAddParamsTip2') }}
|
{{ t('apiTestDebug.batchAddParamsTip2') }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="mb-[8px] flex items-center justify-between">
|
<div class="mb-[8px] flex items-center justify-between">
|
||||||
<div class="font-medium">{{ t('ms.apiTestDebug.body') }}</div>
|
<div class="font-medium">{{ t('apiTestDebug.body') }}</div>
|
||||||
<div class="flex items-center gap-[16px]">
|
<div class="flex items-center gap-[16px]">
|
||||||
<batchAddKeyVal v-if="showParamTable" :params="currentTableParams" @apply="handleBatchParamApply" />
|
<batchAddKeyVal v-if="showParamTable" :params="currentTableParams" @apply="handleBatchParamApply" />
|
||||||
<a-radio-group v-model:model-value="format" type="button" size="small" @change="formatChange">
|
<a-radio-group v-model:model-value="format" type="button" size="small" @change="formatChange">
|
||||||
|
@ -12,7 +12,7 @@
|
||||||
v-if="format === RequestBodyFormat.NONE"
|
v-if="format === RequestBodyFormat.NONE"
|
||||||
class="flex h-[100px] items-center justify-center rounded-[var(--border-radius-small)] bg-[var(--color-text-n9)] text-[var(--color-text-4)]"
|
class="flex h-[100px] items-center justify-center rounded-[var(--border-radius-small)] bg-[var(--color-text-n9)] text-[var(--color-text-4)]"
|
||||||
>
|
>
|
||||||
{{ t('ms.apiTestDebug.noneBody') }}
|
{{ t('apiTestDebug.noneBody') }}
|
||||||
</div>
|
</div>
|
||||||
<paramTable
|
<paramTable
|
||||||
v-else-if="showParamTable"
|
v-else-if="showParamTable"
|
||||||
|
@ -23,6 +23,30 @@
|
||||||
:height-used="heightUsed"
|
:height-used="heightUsed"
|
||||||
@change="handleParamTableChange"
|
@change="handleParamTableChange"
|
||||||
/>
|
/>
|
||||||
|
<div v-else-if="format === RequestBodyFormat.BINARY">
|
||||||
|
<div class="mb-[16px] flex justify-between gap-[8px] bg-[var(--color-text-n9)] p-[12px]">
|
||||||
|
<a-input
|
||||||
|
v-model:model-value="innerParams.binaryDesc"
|
||||||
|
:placeholder="t('common.desc')"
|
||||||
|
:max-length="255"
|
||||||
|
show-word-limit
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center">
|
||||||
|
<a-switch v-model:model-value="innerParams.binarySend" class="mr-[8px]" size="small"></a-switch>
|
||||||
|
<span>{{ t('apiTestDebug.sendAsMainText') }}</span>
|
||||||
|
<a-tooltip position="right">
|
||||||
|
<template #content>
|
||||||
|
<div>{{ t('apiTestDebug.sendAsMainTextTip1') }}</div>
|
||||||
|
<div>{{ t('apiTestDebug.sendAsMainTextTip2') }}</div>
|
||||||
|
</template>
|
||||||
|
<icon-question-circle
|
||||||
|
class="ml-[4px] text-[var(--color-text-4)] hover:text-[rgb(var(--primary-5))]"
|
||||||
|
size="16"
|
||||||
|
/>
|
||||||
|
</a-tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div v-else class="flex h-[calc(100%-100px)]">
|
<div v-else class="flex h-[calc(100%-100px)]">
|
||||||
<MsCodeEditor
|
<MsCodeEditor
|
||||||
v-model:model-value="currentBodyCode"
|
v-model:model-value="currentBodyCode"
|
||||||
|
@ -35,10 +59,10 @@
|
||||||
<template #title>
|
<template #title>
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<div class="text-[12px] leading-[16px] text-[var(--color-text-4)]">
|
<div class="text-[12px] leading-[16px] text-[var(--color-text-4)]">
|
||||||
{{ t('ms.apiTestDebug.batchAddParamsTip') }}
|
{{ t('apiTestDebug.batchAddParamsTip') }}
|
||||||
</div>
|
</div>
|
||||||
<div class="text-[12px] leading-[16px] text-[var(--color-text-4)]">
|
<div class="text-[12px] leading-[16px] text-[var(--color-text-4)]">
|
||||||
{{ t('ms.apiTestDebug.batchAddParamsTip2') }}
|
{{ t('apiTestDebug.batchAddParamsTip2') }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -65,6 +89,8 @@
|
||||||
json: string;
|
json: string;
|
||||||
xml: string;
|
xml: string;
|
||||||
binary: string;
|
binary: string;
|
||||||
|
binaryDesc: string;
|
||||||
|
binarySend: boolean;
|
||||||
raw: string;
|
raw: string;
|
||||||
}
|
}
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
|
@ -83,35 +109,35 @@
|
||||||
|
|
||||||
const columns: MsTableColumn = [
|
const columns: MsTableColumn = [
|
||||||
{
|
{
|
||||||
title: 'ms.apiTestDebug.paramName',
|
title: 'apiTestDebug.paramName',
|
||||||
dataIndex: 'name',
|
dataIndex: 'name',
|
||||||
slotName: 'name',
|
slotName: 'name',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'ms.apiTestDebug.paramType',
|
title: 'apiTestDebug.paramType',
|
||||||
dataIndex: 'type',
|
dataIndex: 'type',
|
||||||
slotName: 'type',
|
slotName: 'type',
|
||||||
width: 120,
|
width: 120,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'ms.apiTestDebug.paramValue',
|
title: 'apiTestDebug.paramValue',
|
||||||
dataIndex: 'value',
|
dataIndex: 'value',
|
||||||
slotName: 'value',
|
slotName: 'value',
|
||||||
width: 240,
|
width: 240,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'ms.apiTestDebug.paramLengthRange',
|
title: 'apiTestDebug.paramLengthRange',
|
||||||
dataIndex: 'lengthRange',
|
dataIndex: 'lengthRange',
|
||||||
slotName: 'lengthRange',
|
slotName: 'lengthRange',
|
||||||
width: 200,
|
width: 200,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'ms.apiTestDebug.desc',
|
title: 'apiTestDebug.desc',
|
||||||
dataIndex: 'desc',
|
dataIndex: 'desc',
|
||||||
slotName: 'desc',
|
slotName: 'desc',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'ms.apiTestDebug.encode',
|
title: 'apiTestDebug.encode',
|
||||||
dataIndex: 'encode',
|
dataIndex: 'encode',
|
||||||
slotName: 'encode',
|
slotName: 'encode',
|
||||||
titleSlotName: 'encodeTitle',
|
titleSlotName: 'encodeTitle',
|
||||||
|
@ -150,8 +176,10 @@
|
||||||
|
|
||||||
const format = ref(RequestBodyFormat.NONE);
|
const format = ref(RequestBodyFormat.NONE);
|
||||||
const showParamTable = computed(() => {
|
const showParamTable = computed(() => {
|
||||||
|
// 仅当格式为FORM_DATA或X_WWW_FORM_URLENCODED时,显示参数表格
|
||||||
return [RequestBodyFormat.FORM_DATA, RequestBodyFormat.X_WWW_FORM_URLENCODED].includes(format.value);
|
return [RequestBodyFormat.FORM_DATA, RequestBodyFormat.X_WWW_FORM_URLENCODED].includes(format.value);
|
||||||
});
|
});
|
||||||
|
// 当前显示的参数表格数据
|
||||||
const currentTableParams = computed({
|
const currentTableParams = computed({
|
||||||
get() {
|
get() {
|
||||||
if (format.value === RequestBodyFormat.FORM_DATA) {
|
if (format.value === RequestBodyFormat.FORM_DATA) {
|
||||||
|
@ -167,6 +195,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
// 当前显示的代码
|
||||||
const currentBodyCode = computed({
|
const currentBodyCode = computed({
|
||||||
get() {
|
get() {
|
||||||
if (format.value === RequestBodyFormat.JSON) {
|
if (format.value === RequestBodyFormat.JSON) {
|
||||||
|
@ -187,6 +216,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
// 当前代码编辑器的语言
|
||||||
const currentCodeLanguage = computed(() => {
|
const currentCodeLanguage = computed(() => {
|
||||||
if (format.value === RequestBodyFormat.JSON) {
|
if (format.value === RequestBodyFormat.JSON) {
|
||||||
return 'json';
|
return 'json';
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="mb-[8px] flex items-center justify-between">
|
<div class="mb-[8px] flex items-center justify-between">
|
||||||
<div class="font-medium">{{ t('ms.apiTestDebug.header') }}</div>
|
<div class="font-medium">{{ t('apiTestDebug.header') }}</div>
|
||||||
<batchAddKeyVal :params="innerParams" @apply="handleBatchParamApply" />
|
<batchAddKeyVal :params="innerParams" @apply="handleBatchParamApply" />
|
||||||
</div>
|
</div>
|
||||||
<paramTable
|
<paramTable
|
||||||
|
@ -37,17 +37,17 @@
|
||||||
|
|
||||||
const columns: MsTableColumn = [
|
const columns: MsTableColumn = [
|
||||||
{
|
{
|
||||||
title: 'ms.apiTestDebug.paramName',
|
title: 'apiTestDebug.paramName',
|
||||||
dataIndex: 'name',
|
dataIndex: 'name',
|
||||||
slotName: 'name',
|
slotName: 'name',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'ms.apiTestDebug.paramValue',
|
title: 'apiTestDebug.paramValue',
|
||||||
dataIndex: 'value',
|
dataIndex: 'value',
|
||||||
slotName: 'value',
|
slotName: 'value',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'ms.apiTestDebug.desc',
|
title: 'apiTestDebug.desc',
|
||||||
dataIndex: 'desc',
|
dataIndex: 'desc',
|
||||||
slotName: 'desc',
|
slotName: 'desc',
|
||||||
},
|
},
|
||||||
|
|
|
@ -35,25 +35,25 @@
|
||||||
</a-select>
|
</a-select>
|
||||||
<a-input
|
<a-input
|
||||||
v-model:model-value="debugUrl"
|
v-model:model-value="debugUrl"
|
||||||
:placeholder="t('ms.apiTestDebug.urlPlaceholder')"
|
:placeholder="t('apiTestDebug.urlPlaceholder')"
|
||||||
@change="handleActiveDebugChange"
|
@change="handleActiveDebugChange"
|
||||||
/>
|
/>
|
||||||
</a-input-group>
|
</a-input-group>
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-[16px]">
|
<div class="ml-[16px]">
|
||||||
<a-dropdown-button class="exec-btn">
|
<a-dropdown-button class="exec-btn">
|
||||||
{{ t('ms.apiTestDebug.serverExec') }}
|
{{ t('apiTestDebug.serverExec') }}
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<icon-down />
|
<icon-down />
|
||||||
</template>
|
</template>
|
||||||
<template #content>
|
<template #content>
|
||||||
<a-doption>{{ t('ms.apiTestDebug.localExec') }}</a-doption>
|
<a-doption>{{ t('apiTestDebug.localExec') }}</a-doption>
|
||||||
</template>
|
</template>
|
||||||
</a-dropdown-button>
|
</a-dropdown-button>
|
||||||
<a-button type="secondary">
|
<a-button type="secondary">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
{{ t('common.save') }}
|
{{ t('common.save') }}
|
||||||
<div class="text-[var(--color-text-4)]">(<icon-command size="14" /> + S)</div>
|
<div class="text-[var(--color-text-4)]">(<icon-command size="14" />+S)</div>
|
||||||
</div>
|
</div>
|
||||||
</a-button>
|
</a-button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -88,6 +88,30 @@
|
||||||
:second-box-height="secondBoxHeight"
|
:second-box-height="secondBoxHeight"
|
||||||
@change="handleActiveDebugChange"
|
@change="handleActiveDebugChange"
|
||||||
/>
|
/>
|
||||||
|
<debugQuery
|
||||||
|
v-else-if="activeDebug.activeTab === RequestComposition.QUERY"
|
||||||
|
v-model:params="activeDebug.queryParams"
|
||||||
|
:layout="activeLayout"
|
||||||
|
:second-box-height="secondBoxHeight"
|
||||||
|
@change="handleActiveDebugChange"
|
||||||
|
/>
|
||||||
|
<debugRest
|
||||||
|
v-else-if="activeDebug.activeTab === RequestComposition.REST"
|
||||||
|
v-model:params="activeDebug.restParams"
|
||||||
|
:layout="activeLayout"
|
||||||
|
:second-box-height="secondBoxHeight"
|
||||||
|
@change="handleActiveDebugChange"
|
||||||
|
/>
|
||||||
|
<debugAuth
|
||||||
|
v-else-if="activeDebug.activeTab === RequestComposition.AUTH"
|
||||||
|
v-model:params="activeDebug.authParams"
|
||||||
|
@change="handleActiveDebugChange"
|
||||||
|
/>
|
||||||
|
<debugSetting
|
||||||
|
v-else-if="activeDebug.activeTab === RequestComposition.SETTING"
|
||||||
|
v-model:params="activeDebug.setting"
|
||||||
|
@change="handleActiveDebugChange"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #second>
|
<template #second>
|
||||||
|
@ -106,18 +130,19 @@
|
||||||
<icon-right :size="12" />
|
<icon-right :size="12" />
|
||||||
</MsButton>
|
</MsButton>
|
||||||
</template>
|
</template>
|
||||||
<div class="ml-[4px] mr-[24px] font-medium">{{ t('ms.apiTestDebug.responseContent') }}</div>
|
<div class="ml-[4px] mr-[24px] font-medium">{{ t('apiTestDebug.responseContent') }}</div>
|
||||||
<a-radio-group
|
<a-radio-group
|
||||||
v-model:model-value="activeLayout"
|
v-model:model-value="activeLayout"
|
||||||
type="button"
|
type="button"
|
||||||
size="small"
|
size="small"
|
||||||
@change="handleActiveLayoutChange"
|
@change="handleActiveLayoutChange"
|
||||||
>
|
>
|
||||||
<a-radio value="vertical">{{ t('ms.apiTestDebug.vertical') }}</a-radio>
|
<a-radio value="vertical">{{ t('apiTestDebug.vertical') }}</a-radio>
|
||||||
<a-radio value="horizontal">{{ t('ms.apiTestDebug.horizontal') }}</a-radio>
|
<a-radio value="horizontal">{{ t('apiTestDebug.horizontal') }}</a-radio>
|
||||||
</a-radio-group>
|
</a-radio-group>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="p-[16px]"></div>
|
||||||
</template>
|
</template>
|
||||||
</MsSplitBox>
|
</MsSplitBox>
|
||||||
</div>
|
</div>
|
||||||
|
@ -131,8 +156,12 @@
|
||||||
import { TabItem } from '@/components/pure/ms-editable-tab/types';
|
import { TabItem } from '@/components/pure/ms-editable-tab/types';
|
||||||
import MsSplitBox from '@/components/pure/ms-split-box/index.vue';
|
import MsSplitBox from '@/components/pure/ms-split-box/index.vue';
|
||||||
import apiMethodName from '../../../components/apiMethodName.vue';
|
import apiMethodName from '../../../components/apiMethodName.vue';
|
||||||
|
import debugAuth from './auth.vue';
|
||||||
import debugBody, { BodyParams } from './body.vue';
|
import debugBody, { BodyParams } from './body.vue';
|
||||||
import debugHeader from './header.vue';
|
import debugHeader from './header.vue';
|
||||||
|
import debugQuery from './query.vue';
|
||||||
|
import debugRest from './rest.vue';
|
||||||
|
import debugSetting from './setting.vue';
|
||||||
|
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
import { registerCatchSaveShortcut, removeCatchSaveShortcut } from '@/utils/event';
|
import { registerCatchSaveShortcut, removeCatchSaveShortcut } from '@/utils/event';
|
||||||
|
@ -150,6 +179,8 @@
|
||||||
json: '',
|
json: '',
|
||||||
xml: '',
|
xml: '',
|
||||||
binary: '',
|
binary: '',
|
||||||
|
binaryDesc: '',
|
||||||
|
binarySend: false,
|
||||||
raw: '',
|
raw: '',
|
||||||
};
|
};
|
||||||
const debugTabs = ref<TabItem[]>([
|
const debugTabs = ref<TabItem[]>([
|
||||||
|
@ -157,12 +188,25 @@
|
||||||
id: initDefaultId,
|
id: initDefaultId,
|
||||||
moduleProtocol: 'http',
|
moduleProtocol: 'http',
|
||||||
activeTab: RequestComposition.HEADER,
|
activeTab: RequestComposition.HEADER,
|
||||||
label: t('ms.apiTestDebug.newApi'),
|
label: t('apiTestDebug.newApi'),
|
||||||
closable: true,
|
closable: true,
|
||||||
method: RequestMethods.GET,
|
method: RequestMethods.GET,
|
||||||
unSave: false,
|
unSave: false,
|
||||||
headerParams: [],
|
headerParams: [],
|
||||||
bodyParams: cloneDeep(defaultBodyParams),
|
bodyParams: cloneDeep(defaultBodyParams),
|
||||||
|
queryParams: [],
|
||||||
|
restParams: [],
|
||||||
|
authParams: {
|
||||||
|
authType: 'none',
|
||||||
|
account: '',
|
||||||
|
password: '',
|
||||||
|
},
|
||||||
|
setting: {
|
||||||
|
connectTimeout: 60000,
|
||||||
|
responseTimeout: 60000,
|
||||||
|
certificateAlias: '',
|
||||||
|
redirect: 'follow',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
const debugUrl = ref('');
|
const debugUrl = ref('');
|
||||||
|
@ -182,22 +226,35 @@
|
||||||
id,
|
id,
|
||||||
moduleProtocol: 'http',
|
moduleProtocol: 'http',
|
||||||
activeTab: RequestComposition.HEADER,
|
activeTab: RequestComposition.HEADER,
|
||||||
label: t('ms.apiTestDebug.newApi'),
|
label: t('apiTestDebug.newApi'),
|
||||||
closable: true,
|
closable: true,
|
||||||
method: RequestMethods.GET,
|
method: RequestMethods.GET,
|
||||||
unSave: false,
|
unSave: false,
|
||||||
headerParams: [],
|
headerParams: [],
|
||||||
bodyParams: cloneDeep(defaultBodyParams),
|
bodyParams: cloneDeep(defaultBodyParams),
|
||||||
|
queryParams: [],
|
||||||
|
restParams: [],
|
||||||
|
authParams: {
|
||||||
|
authType: 'none',
|
||||||
|
account: '',
|
||||||
|
password: '',
|
||||||
|
},
|
||||||
|
setting: {
|
||||||
|
connectTimeout: 60000,
|
||||||
|
responseTimeout: 60000,
|
||||||
|
certificateAlias: '',
|
||||||
|
redirect: 'follow',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
activeTab.value = id;
|
activeTab.value = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeDebugTab(tab: TabItem) {
|
function closeDebugTab(tab: TabItem) {
|
||||||
const index = debugTabs.value.findIndex((item) => item.id === tab.id);
|
const index = debugTabs.value.findIndex((item) => item.id === tab.id);
|
||||||
|
debugTabs.value.splice(index, 1);
|
||||||
if (activeTab.value === tab.id) {
|
if (activeTab.value === tab.id) {
|
||||||
activeTab.value = debugTabs.value[0]?.id || '';
|
activeTab.value = debugTabs.value[0]?.id || '';
|
||||||
}
|
}
|
||||||
debugTabs.value.splice(index, 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const moreActionList = [
|
const moreActionList = [
|
||||||
|
@ -214,11 +271,11 @@
|
||||||
const contentTabList = [
|
const contentTabList = [
|
||||||
{
|
{
|
||||||
value: RequestComposition.HEADER,
|
value: RequestComposition.HEADER,
|
||||||
label: t('ms.apiTestDebug.header'),
|
label: t('apiTestDebug.header'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: RequestComposition.BODY,
|
value: RequestComposition.BODY,
|
||||||
label: t('ms.apiTestDebug.body'),
|
label: t('apiTestDebug.body'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: RequestComposition.QUERY,
|
value: RequestComposition.QUERY,
|
||||||
|
@ -230,23 +287,23 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: RequestComposition.PREFIX,
|
value: RequestComposition.PREFIX,
|
||||||
label: t('ms.apiTestDebug.prefix'),
|
label: t('apiTestDebug.prefix'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: RequestComposition.POST_CONDITION,
|
value: RequestComposition.POST_CONDITION,
|
||||||
label: t('ms.apiTestDebug.postCondition'),
|
label: t('apiTestDebug.postCondition'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: RequestComposition.ASSERTION,
|
value: RequestComposition.ASSERTION,
|
||||||
label: t('ms.apiTestDebug.assertion'),
|
label: t('apiTestDebug.assertion'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: RequestComposition.AUTH,
|
value: RequestComposition.AUTH,
|
||||||
label: t('ms.apiTestDebug.auth'),
|
label: t('apiTestDebug.auth'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: RequestComposition.SETTING,
|
value: RequestComposition.SETTING,
|
||||||
label: t('ms.apiTestDebug.setting'),
|
label: t('apiTestDebug.setting'),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -292,6 +349,7 @@
|
||||||
function handleActiveLayoutChange() {
|
function handleActiveLayoutChange() {
|
||||||
isExpanded.value = true;
|
isExpanded.value = true;
|
||||||
splitBoxSize.value = 0.6;
|
splitBoxSize.value = 0.6;
|
||||||
|
splitBoxRef.value?.expand(0.6);
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveDebug() {
|
function saveDebug() {
|
||||||
|
|
|
@ -0,0 +1,133 @@
|
||||||
|
<template>
|
||||||
|
<div class="mb-[8px] flex items-center justify-between">
|
||||||
|
<div class="flex items-center gap-[4px]">
|
||||||
|
<div class="font-medium">Query</div>
|
||||||
|
<a-tooltip :content="t('apiTestDebug.queryTip')" position="right">
|
||||||
|
<icon-question-circle
|
||||||
|
class="ml-[4px] text-[var(--color-text-brand)] hover:text-[rgb(var(--primary-5))]"
|
||||||
|
size="16"
|
||||||
|
/>
|
||||||
|
</a-tooltip>
|
||||||
|
</div>
|
||||||
|
<batchAddKeyVal :params="innerParams" @apply="handleBatchParamApply" />
|
||||||
|
</div>
|
||||||
|
<paramTable
|
||||||
|
v-model:params="innerParams"
|
||||||
|
:columns="columns"
|
||||||
|
:height-used="heightUsed"
|
||||||
|
:scroll="{ minWidth: 1160 }"
|
||||||
|
format="query"
|
||||||
|
@change="handleParamTableChange"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useVModel } from '@vueuse/core';
|
||||||
|
|
||||||
|
import type { MsTableColumn } from '@/components/pure/ms-table/type';
|
||||||
|
import paramTable from '../../../components/paramTable.vue';
|
||||||
|
import batchAddKeyVal from './batchAddKeyVal.vue';
|
||||||
|
|
||||||
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
params: any[];
|
||||||
|
layout: 'horizontal' | 'vertical';
|
||||||
|
secondBoxHeight: number;
|
||||||
|
}>();
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'update:params', value: any[]): void;
|
||||||
|
(e: 'change'): void; // 数据发生变化
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const innerParams = useVModel(props, 'params', emit);
|
||||||
|
|
||||||
|
const columns: MsTableColumn = [
|
||||||
|
{
|
||||||
|
title: 'apiTestDebug.paramName',
|
||||||
|
dataIndex: 'name',
|
||||||
|
slotName: 'name',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'apiTestDebug.paramType',
|
||||||
|
dataIndex: 'type',
|
||||||
|
slotName: 'type',
|
||||||
|
width: 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'apiTestDebug.paramValue',
|
||||||
|
dataIndex: 'value',
|
||||||
|
slotName: 'value',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'apiTestDebug.paramLengthRange',
|
||||||
|
dataIndex: 'lengthRange',
|
||||||
|
slotName: 'lengthRange',
|
||||||
|
width: 200,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'apiTestDebug.encode',
|
||||||
|
dataIndex: 'encode',
|
||||||
|
slotName: 'encode',
|
||||||
|
titleSlotName: 'encodeTitle',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'apiTestDebug.desc',
|
||||||
|
dataIndex: 'desc',
|
||||||
|
slotName: 'desc',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '',
|
||||||
|
slotName: 'operation',
|
||||||
|
fixed: 'right',
|
||||||
|
width: 50,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const heightUsed = ref<number | undefined>(undefined);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.layout,
|
||||||
|
(val) => {
|
||||||
|
heightUsed.value = val === 'horizontal' ? 422 : 422 + props.secondBoxHeight;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
immediate: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.secondBoxHeight,
|
||||||
|
(val) => {
|
||||||
|
if (props.layout === 'vertical') {
|
||||||
|
heightUsed.value = 422 + val;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
immediate: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量参数代码转换为参数表格数据
|
||||||
|
*/
|
||||||
|
function handleBatchParamApply(resultArr: any[]) {
|
||||||
|
if (resultArr.length < innerParams.value.length) {
|
||||||
|
innerParams.value.splice(0, innerParams.value.length - 1, ...resultArr);
|
||||||
|
} else {
|
||||||
|
innerParams.value = [...resultArr, innerParams.value[innerParams.value.length - 1]];
|
||||||
|
}
|
||||||
|
emit('change');
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleParamTableChange(resultArr: any[], isInit?: boolean) {
|
||||||
|
innerParams.value = [...resultArr];
|
||||||
|
if (!isInit) {
|
||||||
|
emit('change');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped></style>
|
|
@ -0,0 +1,7 @@
|
||||||
|
<template>
|
||||||
|
<div> </div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts"></script>
|
||||||
|
|
||||||
|
<style lang="less" scoped></style>
|
|
@ -0,0 +1,133 @@
|
||||||
|
<template>
|
||||||
|
<div class="mb-[8px] flex items-center justify-between">
|
||||||
|
<div class="flex items-center gap-[4px]">
|
||||||
|
<div class="font-medium">Rest</div>
|
||||||
|
<a-tooltip :content="t('apiTestDebug.restTip', { id: '{id}' })" position="right">
|
||||||
|
<icon-question-circle
|
||||||
|
class="ml-[4px] text-[var(--color-text-brand)] hover:text-[rgb(var(--primary-5))]"
|
||||||
|
size="16"
|
||||||
|
/>
|
||||||
|
</a-tooltip>
|
||||||
|
</div>
|
||||||
|
<batchAddKeyVal :params="innerParams" @apply="handleBatchParamApply" />
|
||||||
|
</div>
|
||||||
|
<paramTable
|
||||||
|
v-model:params="innerParams"
|
||||||
|
:columns="columns"
|
||||||
|
:height-used="heightUsed"
|
||||||
|
:scroll="{ minWidth: 1160 }"
|
||||||
|
format="query"
|
||||||
|
@change="handleParamTableChange"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useVModel } from '@vueuse/core';
|
||||||
|
|
||||||
|
import type { MsTableColumn } from '@/components/pure/ms-table/type';
|
||||||
|
import paramTable from '../../../components/paramTable.vue';
|
||||||
|
import batchAddKeyVal from './batchAddKeyVal.vue';
|
||||||
|
|
||||||
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
params: any[];
|
||||||
|
layout: 'horizontal' | 'vertical';
|
||||||
|
secondBoxHeight: number;
|
||||||
|
}>();
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'update:params', value: any[]): void;
|
||||||
|
(e: 'change'): void; // 数据发生变化
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const innerParams = useVModel(props, 'params', emit);
|
||||||
|
|
||||||
|
const columns: MsTableColumn = [
|
||||||
|
{
|
||||||
|
title: 'apiTestDebug.paramName',
|
||||||
|
dataIndex: 'name',
|
||||||
|
slotName: 'name',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'apiTestDebug.paramType',
|
||||||
|
dataIndex: 'type',
|
||||||
|
slotName: 'type',
|
||||||
|
width: 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'apiTestDebug.paramValue',
|
||||||
|
dataIndex: 'value',
|
||||||
|
slotName: 'value',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'apiTestDebug.paramLengthRange',
|
||||||
|
dataIndex: 'lengthRange',
|
||||||
|
slotName: 'lengthRange',
|
||||||
|
width: 200,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'apiTestDebug.encode',
|
||||||
|
dataIndex: 'encode',
|
||||||
|
slotName: 'encode',
|
||||||
|
titleSlotName: 'encodeTitle',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'apiTestDebug.desc',
|
||||||
|
dataIndex: 'desc',
|
||||||
|
slotName: 'desc',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '',
|
||||||
|
slotName: 'operation',
|
||||||
|
fixed: 'right',
|
||||||
|
width: 50,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const heightUsed = ref<number | undefined>(undefined);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.layout,
|
||||||
|
(val) => {
|
||||||
|
heightUsed.value = val === 'horizontal' ? 422 : 422 + props.secondBoxHeight;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
immediate: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.secondBoxHeight,
|
||||||
|
(val) => {
|
||||||
|
if (props.layout === 'vertical') {
|
||||||
|
heightUsed.value = 422 + val;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
immediate: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量参数代码转换为参数表格数据
|
||||||
|
*/
|
||||||
|
function handleBatchParamApply(resultArr: any[]) {
|
||||||
|
if (resultArr.length < innerParams.value.length) {
|
||||||
|
innerParams.value.splice(0, innerParams.value.length - 1, ...resultArr);
|
||||||
|
} else {
|
||||||
|
innerParams.value = [...resultArr, innerParams.value[innerParams.value.length - 1]];
|
||||||
|
}
|
||||||
|
emit('change');
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleParamTableChange(resultArr: any[], isInit?: boolean) {
|
||||||
|
innerParams.value = [...resultArr];
|
||||||
|
if (!isInit) {
|
||||||
|
emit('change');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped></style>
|
|
@ -0,0 +1,74 @@
|
||||||
|
<template>
|
||||||
|
<div class="pb-[24px]">
|
||||||
|
<div class="mb-[8px] font-medium">{{ t('apiTestDebug.auth') }}</div>
|
||||||
|
<div class="rounded-[var(--border-radius-small)] border border-[var(--color-text-n8)] p-[16px]">
|
||||||
|
<div class="mb-[8px]">{{ t('apiTestDebug.setting') }}</div>
|
||||||
|
<a-form :model="settingForm" layout="vertical">
|
||||||
|
<a-form-item>
|
||||||
|
<template #label>
|
||||||
|
<div class="flex items-center">
|
||||||
|
{{ t('apiTestDebug.connectTimeout') }}
|
||||||
|
<div class="text-[var(--color-text-brand)]">(ms)</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<a-input-number v-model:model-value="settingForm.connectTimeout" mode="button" class="w-[160px]" />
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item>
|
||||||
|
<template #label>
|
||||||
|
<div class="flex items-center">
|
||||||
|
{{ t('apiTestDebug.responseTimeout') }}
|
||||||
|
<div class="text-[var(--color-text-brand)]">(ms)</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<a-input-number v-model:model-value="settingForm.responseTimeout" mode="button" class="w-[160px]" />
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item :label="t('apiTestDebug.certificateAlias')">
|
||||||
|
<a-input
|
||||||
|
v-model:model-value="settingForm.certificateAlias"
|
||||||
|
:placeholder="t('apiTestDebug.commonPlaceholder')"
|
||||||
|
class="w-[450px]"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item :label="t('apiTestDebug.redirect')">
|
||||||
|
<a-radio-group v-model:model-value="settingForm.redirect">
|
||||||
|
<a-radio value="follow">{{ t('apiTestDebug.follow') }}</a-radio>
|
||||||
|
<a-radio value="auto">{{ t('apiTestDebug.auto') }}</a-radio>
|
||||||
|
</a-radio-group>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useVModel } from '@vueuse/core';
|
||||||
|
|
||||||
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
|
|
||||||
|
interface SettingForm {
|
||||||
|
connectTimeout: number;
|
||||||
|
responseTimeout: number;
|
||||||
|
certificateAlias: string;
|
||||||
|
redirect: 'follow' | 'auto';
|
||||||
|
}
|
||||||
|
const props = defineProps<{
|
||||||
|
params: SettingForm;
|
||||||
|
}>();
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'update:params', val: SettingForm): void;
|
||||||
|
(e: 'change', val: SettingForm): void;
|
||||||
|
}>();
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const settingForm = useVModel(props, 'params', emit);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => settingForm.value,
|
||||||
|
() => {
|
||||||
|
emit('change', settingForm.value);
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped></style>
|
|
@ -7,10 +7,10 @@
|
||||||
allow-clear
|
allow-clear
|
||||||
/>
|
/>
|
||||||
<a-dropdown @select="handleSelect">
|
<a-dropdown @select="handleSelect">
|
||||||
<a-button type="primary">{{ t('ms.apiTestDebug.newApi') }}</a-button>
|
<a-button type="primary">{{ t('apiTestDebug.newApi') }}</a-button>
|
||||||
<template #content>
|
<template #content>
|
||||||
<a-doption value="newApi">{{ t('ms.apiTestDebug.newApi') }}</a-doption>
|
<a-doption value="newApi">{{ t('apiTestDebug.newApi') }}</a-doption>
|
||||||
<a-doption value="import">{{ t('ms.apiTestDebug.importApi') }}</a-doption>
|
<a-doption value="import">{{ t('apiTestDebug.importApi') }}</a-doption>
|
||||||
</template>
|
</template>
|
||||||
</a-dropdown>
|
</a-dropdown>
|
||||||
</div>
|
</div>
|
||||||
|
@ -46,7 +46,7 @@
|
||||||
:node-more-actions="folderMoreActions"
|
:node-more-actions="folderMoreActions"
|
||||||
:default-expand-all="isExpandAll"
|
:default-expand-all="isExpandAll"
|
||||||
:expand-all="isExpandAll"
|
:expand-all="isExpandAll"
|
||||||
:empty-text="t('ms.apiTestDebug.noMatchModule')"
|
:empty-text="t('apiTestDebug.noMatchModule')"
|
||||||
:draggable="!props.isModal"
|
:draggable="!props.isModal"
|
||||||
:virtual-list-props="virtualListProps"
|
:virtual-list-props="virtualListProps"
|
||||||
:field-names="{
|
:field-names="{
|
||||||
|
|
|
@ -1,31 +1,31 @@
|
||||||
export default {
|
export default {
|
||||||
'ms.apiTestDebug.newApi': 'New request',
|
'apiTestDebug.newApi': 'New request',
|
||||||
'ms.apiTestDebug.importApi': 'Import request',
|
'apiTestDebug.importApi': 'Import request',
|
||||||
'ms.apiTestDebug.urlPlaceholder': 'Please enter the full URL including http or https',
|
'apiTestDebug.urlPlaceholder': 'Please enter the full URL including http or https',
|
||||||
'ms.apiTestDebug.serverExec': 'Server execution',
|
'apiTestDebug.serverExec': 'Server execution',
|
||||||
'ms.apiTestDebug.localExec': 'Local execution',
|
'apiTestDebug.localExec': 'Local execution',
|
||||||
'ms.apiTestDebug.noMatchModule': 'No matching module data yet',
|
'apiTestDebug.noMatchModule': 'No matching module data yet',
|
||||||
'ms.apiTestDebug.header': 'Header',
|
'apiTestDebug.header': 'Header',
|
||||||
'ms.apiTestDebug.body': 'Body',
|
'apiTestDebug.body': 'Body',
|
||||||
'ms.apiTestDebug.prefix': 'Prefix',
|
'apiTestDebug.prefix': 'Prefix',
|
||||||
'ms.apiTestDebug.postCondition': 'Post condition',
|
'apiTestDebug.postCondition': 'Post condition',
|
||||||
'ms.apiTestDebug.assertion': 'Assertion',
|
'apiTestDebug.assertion': 'Assertion',
|
||||||
'ms.apiTestDebug.auth': 'Auth',
|
'apiTestDebug.auth': 'Auth',
|
||||||
'ms.apiTestDebug.setting': 'Setting',
|
'apiTestDebug.setting': 'Setting',
|
||||||
'ms.apiTestDebug.batchAdd': 'Batch add',
|
'apiTestDebug.batchAdd': 'Batch add',
|
||||||
'ms.apiTestDebug.responseContent': 'Response content',
|
'apiTestDebug.responseContent': 'Response content',
|
||||||
'ms.apiTestDebug.vertical': 'Vertical layout',
|
'apiTestDebug.vertical': 'Vertical layout',
|
||||||
'ms.apiTestDebug.horizontal': 'Horizontal layout',
|
'apiTestDebug.horizontal': 'Horizontal layout',
|
||||||
'ms.apiTestDebug.paramName': 'Parameter name',
|
'apiTestDebug.paramName': 'Parameter name',
|
||||||
'ms.apiTestDebug.paramNamePlaceholder': 'Please enter parameter name',
|
'apiTestDebug.paramNamePlaceholder': 'Please enter parameter name',
|
||||||
'ms.apiTestDebug.paramValue': 'Parameter value',
|
'apiTestDebug.paramValue': 'Parameter value',
|
||||||
'ms.apiTestDebug.paramValuePlaceholder': 'Starting with {at}, double-click to quickly enter',
|
'apiTestDebug.paramValuePlaceholder': 'Starting with {at}, double-click to quickly enter',
|
||||||
'ms.apiTestDebug.paramValuePreview': 'Parameter preview',
|
'apiTestDebug.paramValuePreview': 'Parameter preview',
|
||||||
'ms.apiTestDebug.desc': 'Description',
|
'apiTestDebug.desc': 'Description',
|
||||||
'ms.apiTestDebug.apply': 'Apply',
|
'apiTestDebug.apply': 'Apply',
|
||||||
'ms.apiTestDebug.batchAddParamsTip': 'Writing format: parameter name: parameter value; such as nama: natural',
|
'apiTestDebug.batchAddParamsTip': 'Writing format: parameter name: parameter value; such as nama: natural',
|
||||||
'ms.apiTestDebug.batchAddParamsTip2':
|
'apiTestDebug.batchAddParamsTip2':
|
||||||
'Note: Multiple records are separated by newlines. Parameter names in batch addition are repeated. By default, the last data is the latest data.',
|
'Note: Multiple records are separated by newlines. Parameter names in batch addition are repeated. By default, the last data is the latest data.',
|
||||||
'ms.apiTestDebug.quickInputParamsTip': 'Support Mock/JMeter/Json/Text/String, etc.',
|
'apiTestDebug.quickInputParamsTip': 'Support Mock/JMeter/Json/Text/String, etc.',
|
||||||
'ms.apiTestDebug.descPlaceholder': 'Please enter content',
|
'apiTestDebug.descPlaceholder': 'Please enter content',
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,40 +1,57 @@
|
||||||
export default {
|
export default {
|
||||||
'ms.apiTestDebug.newApi': '新建请求',
|
'apiTestDebug.newApi': '新建请求',
|
||||||
'ms.apiTestDebug.importApi': '导入请求',
|
'apiTestDebug.importApi': '导入请求',
|
||||||
'ms.apiTestDebug.urlPlaceholder': '请输入包含 http 或 https 的完整URL',
|
'apiTestDebug.urlPlaceholder': '请输入包含 http 或 https 的完整URL',
|
||||||
'ms.apiTestDebug.serverExec': '服务端执行',
|
'apiTestDebug.serverExec': '服务端执行',
|
||||||
'ms.apiTestDebug.localExec': '本地执行',
|
'apiTestDebug.localExec': '本地执行',
|
||||||
'ms.apiTestDebug.noMatchModule': '暂无匹配的模块数据',
|
'apiTestDebug.noMatchModule': '暂无匹配的模块数据',
|
||||||
'ms.apiTestDebug.header': '请求头',
|
'apiTestDebug.header': '请求头',
|
||||||
'ms.apiTestDebug.body': '请求体',
|
'apiTestDebug.body': '请求体',
|
||||||
'ms.apiTestDebug.prefix': '前置',
|
'apiTestDebug.prefix': '前置',
|
||||||
'ms.apiTestDebug.postCondition': '后置',
|
'apiTestDebug.postCondition': '后置',
|
||||||
'ms.apiTestDebug.assertion': '断言',
|
'apiTestDebug.assertion': '断言',
|
||||||
'ms.apiTestDebug.auth': '认证',
|
'apiTestDebug.auth': '认证',
|
||||||
'ms.apiTestDebug.setting': '设置',
|
'apiTestDebug.setting': '设置',
|
||||||
'ms.apiTestDebug.batchAdd': '批量添加',
|
'apiTestDebug.batchAdd': '批量添加',
|
||||||
'ms.apiTestDebug.responseContent': '响应内容',
|
'apiTestDebug.responseContent': '响应内容',
|
||||||
'ms.apiTestDebug.vertical': '上下布局',
|
'apiTestDebug.vertical': '上下布局',
|
||||||
'ms.apiTestDebug.horizontal': '左右布局',
|
'apiTestDebug.horizontal': '左右布局',
|
||||||
'ms.apiTestDebug.paramName': '参数名称',
|
'apiTestDebug.paramName': '参数名称',
|
||||||
'ms.apiTestDebug.paramNamePlaceholder': '请输入参数名称',
|
'apiTestDebug.paramNamePlaceholder': '请输入参数名称',
|
||||||
'ms.apiTestDebug.paramRequired': '必填',
|
'apiTestDebug.paramRequired': '必填',
|
||||||
'ms.apiTestDebug.paramNotRequired': '非必填',
|
'apiTestDebug.paramNotRequired': '非必填',
|
||||||
'ms.apiTestDebug.paramType': '类型',
|
'apiTestDebug.paramType': '类型',
|
||||||
'ms.apiTestDebug.paramValue': '参数值',
|
'apiTestDebug.paramValue': '参数值',
|
||||||
'ms.apiTestDebug.paramValuePlaceholder': '以{at}开始,双击可快速输入',
|
'apiTestDebug.paramValuePlaceholder': '以{at}开始,双击可快速输入',
|
||||||
'ms.apiTestDebug.paramLengthRange': '长度区间',
|
'apiTestDebug.paramLengthRange': '长度区间',
|
||||||
'ms.apiTestDebug.paramMin': '最小值',
|
'apiTestDebug.paramMin': '最小值',
|
||||||
'ms.apiTestDebug.paramMax': '最大值',
|
'apiTestDebug.paramMax': '最大值',
|
||||||
'ms.apiTestDebug.paramValuePreview': '参数预览',
|
'apiTestDebug.paramValuePreview': '参数预览',
|
||||||
'ms.apiTestDebug.desc': '描述',
|
'apiTestDebug.desc': '描述',
|
||||||
'ms.apiTestDebug.encode': '编码',
|
'apiTestDebug.encode': '编码',
|
||||||
'ms.apiTestDebug.encodeTip1': '开启:使用编码',
|
'apiTestDebug.encodeTip1': '开启:使用编码',
|
||||||
'ms.apiTestDebug.encodeTip2': '关闭:不使用编码',
|
'apiTestDebug.encodeTip2': '关闭:不使用编码',
|
||||||
'ms.apiTestDebug.apply': '应用',
|
'apiTestDebug.apply': '应用',
|
||||||
'ms.apiTestDebug.batchAddParamsTip': '书写格式:参数名:参数值;如 nama:natural',
|
'apiTestDebug.batchAddParamsTip': '书写格式:参数名:参数值;如 nama:natural',
|
||||||
'ms.apiTestDebug.batchAddParamsTip2': '注: 多条记录以换行分隔,批量添加里的参数名重复,默认以最后一条数据为最新数据',
|
'apiTestDebug.batchAddParamsTip2': '注: 多条记录以换行分隔,批量添加里的参数名重复,默认以最后一条数据为最新数据',
|
||||||
'ms.apiTestDebug.quickInputParamsTip': '支持Mock/JMeter/Json/Text/String等',
|
'apiTestDebug.quickInputParamsTip': '支持Mock/JMeter/Json/Text/String等',
|
||||||
'ms.apiTestDebug.descPlaceholder': '请输入内容',
|
'apiTestDebug.descPlaceholder': '请输入内容',
|
||||||
'ms.apiTestDebug.noneBody': '请求没有 Body',
|
'apiTestDebug.noneBody': '请求没有 Body',
|
||||||
|
'apiTestDebug.sendAsMainText': '作为正文发送',
|
||||||
|
'apiTestDebug.sendAsMainTextTip1': '开启:直接读取文件内容在响应体展示,如:图片格式的文件',
|
||||||
|
'apiTestDebug.sendAsMainTextTip2': '关闭:以下载文件的形式返回',
|
||||||
|
'apiTestDebug.queryTip': '地址栏中跟在 ?后面的参数,如 updateapi?id=112',
|
||||||
|
'apiTestDebug.restTip': '地址栏中被斜杠/分隔的参数,如updateapi/{id}',
|
||||||
|
'apiTestDebug.authType': '认证方式',
|
||||||
|
'apiTestDebug.account': '账号',
|
||||||
|
'apiTestDebug.accountRequired': '账号不能为空',
|
||||||
|
'apiTestDebug.password': '密码',
|
||||||
|
'apiTestDebug.passwordRequired': '密码不能为空',
|
||||||
|
'apiTestDebug.commonPlaceholder': '请输入',
|
||||||
|
'apiTestDebug.connectTimeout': '连接超时',
|
||||||
|
'apiTestDebug.responseTimeout': '响应超时',
|
||||||
|
'apiTestDebug.certificateAlias': '证书别名',
|
||||||
|
'apiTestDebug.redirect': '重定向',
|
||||||
|
'apiTestDebug.follow': '跟随',
|
||||||
|
'apiTestDebug.auto': '自动',
|
||||||
};
|
};
|
||||||
|
|
|
@ -281,10 +281,13 @@
|
||||||
() => props.stepList,
|
() => props.stepList,
|
||||||
() => {
|
() => {
|
||||||
stepData.value = props.stepList;
|
stepData.value = props.stepList;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
immediate: true,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
onMounted(() => {
|
onBeforeMount(() => {
|
||||||
setProps({ data: stepData.value });
|
setProps({ data: stepData.value });
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -7,7 +7,11 @@
|
||||||
@save="saveHandler"
|
@save="saveHandler"
|
||||||
@save-and-continue="saveHandler(true)"
|
@save-and-continue="saveHandler(true)"
|
||||||
>
|
>
|
||||||
<CaseTemplateDetail ref="caseModuleDetailRef" v-model:form-mode-value="caseDetailInfo" />
|
<CaseTemplateDetail
|
||||||
|
ref="caseModuleDetailRef"
|
||||||
|
v-model:form-mode-value="caseDetailInfo"
|
||||||
|
:case-id="(route.query.id as string)"
|
||||||
|
/>
|
||||||
<template #footerRight>
|
<template #footerRight>
|
||||||
<div class="flex justify-end gap-[16px]">
|
<div class="flex justify-end gap-[16px]">
|
||||||
<a-button type="secondary" @click="cancelHandler">{{ t('mscard.defaultCancelText') }}</a-button>
|
<a-button type="secondary" @click="cancelHandler">{{ t('mscard.defaultCancelText') }}</a-button>
|
||||||
|
@ -38,6 +42,7 @@
|
||||||
import useFeatureCaseStore from '@/store/modules/case/featureCase';
|
import useFeatureCaseStore from '@/store/modules/case/featureCase';
|
||||||
import { scrollIntoView } from '@/utils/dom';
|
import { scrollIntoView } from '@/utils/dom';
|
||||||
|
|
||||||
|
import { CreateOrUpdateCase } from '@/models/caseManagement/featureCase';
|
||||||
import { CaseManagementRouteEnum } from '@/enums/routeEnum';
|
import { CaseManagementRouteEnum } from '@/enums/routeEnum';
|
||||||
|
|
||||||
import Message from '@arco-design/web-vue/es/message';
|
import Message from '@arco-design/web-vue/es/message';
|
||||||
|
@ -51,8 +56,8 @@
|
||||||
const visitedKey = 'doNotNextTipCreateCase';
|
const visitedKey = 'doNotNextTipCreateCase';
|
||||||
const { getIsVisited } = useVisit(visitedKey);
|
const { getIsVisited } = useVisit(visitedKey);
|
||||||
|
|
||||||
const caseDetailInfo = ref<Record<string, any>>({
|
const caseDetailInfo = ref({
|
||||||
request: {},
|
request: {} as CreateOrUpdateCase,
|
||||||
fileList: [],
|
fileList: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -76,11 +81,10 @@
|
||||||
query: { organizationId: route.query.organizationId, projectId: route.query.projectId },
|
query: { organizationId: route.query.organizationId, projectId: route.query.projectId },
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const res = await createCaseRequest(caseDetailInfo.value);
|
|
||||||
if (isReview) {
|
if (isReview) {
|
||||||
// TODO
|
caseDetailInfo.value.request.reviewId = route.query.reviewId;
|
||||||
// 创建并关联接口
|
|
||||||
}
|
}
|
||||||
|
const res = await createCaseRequest(caseDetailInfo.value);
|
||||||
createSuccessId.value = res.data.id;
|
createSuccessId.value = res.data.id;
|
||||||
Message.success(route.params.mode === 'copy' ? t('ms.description.copySuccess') : t('common.addSuccess'));
|
Message.success(route.params.mode === 'copy' ? t('ms.description.copySuccess') : t('common.addSuccess'));
|
||||||
featureCaseStore.setIsAlreadySuccess(true);
|
featureCaseStore.setIsAlreadySuccess(true);
|
||||||
|
@ -96,16 +100,24 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isReview) {
|
if (isReview) {
|
||||||
|
router.back();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
router.push({ name: CaseManagementRouteEnum.CASE_MANAGEMENT_CASE, query: { ...route.query } });
|
||||||
|
|
||||||
|
featureCaseStore.setIsAlreadySuccess(true);
|
||||||
|
isShowTip.value = !getIsVisited();
|
||||||
|
if (isShowTip.value && !route.query.id) {
|
||||||
router.push({
|
router.push({
|
||||||
name: CaseManagementRouteEnum.CASE_MANAGEMENT_REVIEW_DETAIL,
|
name: CaseManagementRouteEnum.CASE_MANAGEMENT_CASE_CREATE_SUCCESS,
|
||||||
query: {
|
query: {
|
||||||
id: route.query.reviewId,
|
id: createSuccessId.value,
|
||||||
organizationId: route.query.organizationId,
|
...route.query,
|
||||||
projectId: route.query.projectId,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
console.log(error);
|
console.log(error);
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
|
|
|
@ -295,6 +295,8 @@
|
||||||
tags: [],
|
tags: [],
|
||||||
customFields: [], // 自定义字段集合
|
customFields: [], // 自定义字段集合
|
||||||
relateFileMetaIds: [], // 关联文件ID集合
|
relateFileMetaIds: [], // 关联文件ID集合
|
||||||
|
functionalPriority: '',
|
||||||
|
reviewStatus: 'UN_REVIEWED',
|
||||||
};
|
};
|
||||||
|
|
||||||
const detailInfo = ref<DetailCase>({ ...initDetail });
|
const detailInfo = ref<DetailCase>({ ...initDetail });
|
||||||
|
|
|
@ -91,9 +91,9 @@
|
||||||
:request-fun="transferFileRequest"
|
:request-fun="transferFileRequest"
|
||||||
:params="{
|
:params="{
|
||||||
projectId: currentProjectId,
|
projectId: currentProjectId,
|
||||||
caseId:route.query.id as string,
|
caseId: props.caseId,
|
||||||
fileId:item.uid,
|
fileId: item.uid,
|
||||||
local:true
|
local: true,
|
||||||
}"
|
}"
|
||||||
@success="getCaseInfo()"
|
@success="getCaseInfo()"
|
||||||
/>
|
/>
|
||||||
|
@ -119,7 +119,7 @@
|
||||||
{{ t('ms.upload.preview') }}
|
{{ t('ms.upload.preview') }}
|
||||||
</MsButton>
|
</MsButton>
|
||||||
<MsButton
|
<MsButton
|
||||||
v-if="route.query.id"
|
v-if="props.caseId"
|
||||||
type="button"
|
type="button"
|
||||||
status="primary"
|
status="primary"
|
||||||
class="!mr-[4px]"
|
class="!mr-[4px]"
|
||||||
|
@ -128,7 +128,7 @@
|
||||||
{{ t('caseManagement.featureCase.download') }}
|
{{ t('caseManagement.featureCase.download') }}
|
||||||
</MsButton>
|
</MsButton>
|
||||||
<MsButton
|
<MsButton
|
||||||
v-if="route.query.id && item.isUpdateFlag"
|
v-if="props.caseId && item.isUpdateFlag"
|
||||||
type="button"
|
type="button"
|
||||||
status="primary"
|
status="primary"
|
||||||
@click="handleUpdateFile(item)"
|
@click="handleUpdateFile(item)"
|
||||||
|
@ -278,6 +278,7 @@
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
formModeValue: Record<string, any>; // 表单值
|
formModeValue: Record<string, any>; // 表单值
|
||||||
|
caseId: string; // 用例id
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits(['update:formModeValue', 'changeFile']);
|
const emit = defineEmits(['update:formModeValue', 'changeFile']);
|
||||||
|
@ -327,6 +328,8 @@
|
||||||
tags: [],
|
tags: [],
|
||||||
customFields: [],
|
customFields: [],
|
||||||
relateFileMetaIds: [],
|
relateFileMetaIds: [],
|
||||||
|
functionalPriority: '',
|
||||||
|
reviewStatus: 'UN_REVIEWED',
|
||||||
};
|
};
|
||||||
|
|
||||||
const form = ref<DetailCase | CreateOrUpdateCase>({ ...initForm });
|
const form = ref<DetailCase | CreateOrUpdateCase>({ ...initForm });
|
||||||
|
@ -411,7 +414,7 @@
|
||||||
fileList.value.push(...fileResultList);
|
fileList.value.push(...fileResultList);
|
||||||
}
|
}
|
||||||
|
|
||||||
const isEditOrCopy = computed(() => !!route.query.id);
|
const isEditOrCopy = computed(() => !!props.caseId);
|
||||||
const attachmentsList = ref<AttachFileInfo[]>([]);
|
const attachmentsList = ref<AttachFileInfo[]>([]);
|
||||||
|
|
||||||
// 后台传过来的local文件的item列表
|
// 后台传过来的local文件的item列表
|
||||||
|
@ -473,14 +476,14 @@
|
||||||
if (item.status !== 'init') {
|
if (item.status !== 'init') {
|
||||||
const res = await previewFile({
|
const res = await previewFile({
|
||||||
projectId: currentProjectId.value,
|
projectId: currentProjectId.value,
|
||||||
caseId: route.query.id as string,
|
caseId: props.caseId,
|
||||||
fileId: item.uid,
|
fileId: item.uid,
|
||||||
local: item.local,
|
local: item.local,
|
||||||
});
|
});
|
||||||
const blob = new Blob([res], { type: 'image/jpeg' });
|
const blob = new Blob([res], { type: 'image/jpeg' });
|
||||||
imageUrl.value = URL.createObjectURL(blob);
|
imageUrl.value = URL.createObjectURL(blob);
|
||||||
} else {
|
} else {
|
||||||
imageUrl.value = item.url as string;
|
imageUrl.value = item.url || '';
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
|
@ -532,7 +535,7 @@
|
||||||
try {
|
try {
|
||||||
isLoading.value = true;
|
isLoading.value = true;
|
||||||
await getAllCaseFields();
|
await getAllCaseFields();
|
||||||
const detailResult: DetailCase = await getCaseDetail(route.query.id as string);
|
const detailResult: DetailCase = await getCaseDetail(props.caseId);
|
||||||
const fileIds = (detailResult.attachments || []).map((item: any) => item.id);
|
const fileIds = (detailResult.attachments || []).map((item: any) => item.id);
|
||||||
if (fileIds.length) {
|
if (fileIds.length) {
|
||||||
checkUpdateFileIds.value = await checkFileIsUpdateRequest(fileIds);
|
checkUpdateFileIds.value = await checkFileIsUpdateRequest(fileIds);
|
||||||
|
@ -652,11 +655,11 @@
|
||||||
type: item.type,
|
type: item.type,
|
||||||
name: item.id,
|
name: item.id,
|
||||||
label: item.name,
|
label: item.name,
|
||||||
value: isEditOrCopy.value ? JSON.parse(rule.value) : rule.value,
|
value: rule.value,
|
||||||
options: optionsItem,
|
options: optionsItem,
|
||||||
required: item.required,
|
required: item.required,
|
||||||
props: {
|
props: {
|
||||||
modelValue: isEditOrCopy.value ? JSON.parse(rule.value) : rule.value,
|
modelValue: rule.value,
|
||||||
options: optionsItem,
|
options: optionsItem,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -707,7 +710,7 @@
|
||||||
try {
|
try {
|
||||||
const res = await downloadFileRequest({
|
const res = await downloadFileRequest({
|
||||||
projectId: currentProjectId.value,
|
projectId: currentProjectId.value,
|
||||||
caseId: route.query.id as string,
|
caseId: props.caseId,
|
||||||
fileId: item.uid,
|
fileId: item.uid,
|
||||||
local: true,
|
local: true,
|
||||||
});
|
});
|
||||||
|
|
|
@ -30,7 +30,7 @@
|
||||||
<div class="absolute left-16 top-0 font-normal">
|
<div class="absolute left-16 top-0 font-normal">
|
||||||
<a-divider direction="vertical" />
|
<a-divider direction="vertical" />
|
||||||
<a-dropdown :popup-max-height="false" @select="handleSelectType">
|
<a-dropdown :popup-max-height="false" @select="handleSelectType">
|
||||||
<span class="changeType text-[var(--color-text-3)]"
|
<span class="changeType cursor-pointer text-[var(--color-text-3)]"
|
||||||
>{{ t('system.orgTemplate.changeType') }} <icon-down
|
>{{ t('system.orgTemplate.changeType') }} <icon-down
|
||||||
/></span>
|
/></span>
|
||||||
<template #content>
|
<template #content>
|
||||||
|
@ -555,8 +555,8 @@
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.form,
|
() => props.form,
|
||||||
() => {
|
(val) => {
|
||||||
detailForm.value = { ...props.form };
|
detailForm.value = { ...val };
|
||||||
getDetails();
|
getDetails();
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,16 +1,20 @@
|
||||||
<template>
|
<template>
|
||||||
<MsCard :min-width="1100" has-breadcrumb hide-footer no-content-padding hide-divider>
|
<MsCard :loading="loading" :min-width="1100" has-breadcrumb hide-footer no-content-padding hide-divider>
|
||||||
<template #headerLeft>
|
<template #headerLeft>
|
||||||
<a-tooltip :content="reviewName">
|
<a-tooltip :content="reviewDetail.name">
|
||||||
<div class="one-line-text mr-[8px] max-w-[260px] font-medium text-[var(--color-text-000)]">
|
<div class="one-line-text mr-[8px] max-w-[260px] font-medium text-[var(--color-text-000)]">
|
||||||
{{ reviewName }}
|
{{ reviewDetail.name }}
|
||||||
</div>
|
</div>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<div
|
<div
|
||||||
class="rounded-[0_999px_999px_0] border border-solid border-[text-[rgb(var(--primary-5))]] px-[8px] py-[2px] text-[12px] leading-[16px] text-[rgb(var(--primary-5))]"
|
class="rounded-[0_999px_999px_0] border border-solid border-[text-[rgb(var(--primary-5))]] px-[8px] py-[2px] text-[12px] leading-[16px] text-[rgb(var(--primary-5))]"
|
||||||
>
|
>
|
||||||
<MsIcon type="icon-icon-contacts" size="13" />
|
<MsIcon type="icon-icon-contacts" size="13" />
|
||||||
{{ t('caseManagement.caseReview.single') }}
|
{{
|
||||||
|
reviewDetail.reviewPassRule === 'SINGLE'
|
||||||
|
? t('caseManagement.caseReview.single')
|
||||||
|
: t('caseManagement.caseReview.multi')
|
||||||
|
}}
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-[16px] flex items-center">
|
<div class="ml-[16px] flex items-center">
|
||||||
<a-switch v-model:model-value="onlyMine" size="small" class="mr-[8px]" />
|
<a-switch v-model:model-value="onlyMine" size="small" class="mr-[8px]" />
|
||||||
|
@ -22,83 +26,108 @@
|
||||||
<div class="flex h-[calc(100%-1px)] w-full">
|
<div class="flex h-[calc(100%-1px)] w-full">
|
||||||
<div class="h-full w-[356px] border-r border-[var(--color-text-n8)] pr-[16px] pt-[16px]">
|
<div class="h-full w-[356px] border-r border-[var(--color-text-n8)] pr-[16px] pt-[16px]">
|
||||||
<div class="mb-[16px] flex">
|
<div class="mb-[16px] flex">
|
||||||
<a-input
|
<a-input-search
|
||||||
v-model:model-value="keyword"
|
v-model:model-value="keyword"
|
||||||
:placeholder="t('caseManagement.caseReview.searchPlaceholder')"
|
:placeholder="t('caseManagement.caseReview.searchPlaceholder')"
|
||||||
allow-clear
|
allow-clear
|
||||||
class="mr-[8px] w-[240px]"
|
class="mr-[8px] w-[240px]"
|
||||||
|
@search="loadCaseList"
|
||||||
|
@press-enter="loadCaseList"
|
||||||
/>
|
/>
|
||||||
<a-select v-model:model-value="type" :options="typeOptions" class="w-[92px]"></a-select>
|
<a-select v-model:model-value="type" :options="typeOptions" class="w-[92px]" @change="loadCaseList">
|
||||||
|
</a-select>
|
||||||
</div>
|
</div>
|
||||||
<div class="case-list">
|
<a-spin :loading="caseListLoading" class="w-full">
|
||||||
<div
|
<div class="case-list">
|
||||||
v-for="item of caseList"
|
<div
|
||||||
:key="item.id"
|
v-for="item of caseList"
|
||||||
:class="['case-item', activeCase.id === item.id ? 'case-item--active' : '']"
|
:key="item.caseId"
|
||||||
@click="changeActiveCase(item)"
|
:class="['case-item', caseDetail.id === item.caseId ? 'case-item--active' : '']"
|
||||||
>
|
@click="changeActiveCase(item)"
|
||||||
<div class="mb-[4px] flex items-center justify-between">
|
>
|
||||||
<div>{{ item.id }}</div>
|
<div class="mb-[4px] flex items-center justify-between">
|
||||||
<div class="flex items-center gap-[4px] leading-[22px]">
|
<div>{{ item.num }}</div>
|
||||||
<MsIcon
|
<div class="flex items-center gap-[4px] leading-[22px]">
|
||||||
:type="resultMap[item.result as ResultMap].icon"
|
<MsIcon
|
||||||
:style="{color: resultMap[item.result as ResultMap].color}"
|
:type="reviewResultMap[item.status]?.icon"
|
||||||
/>
|
:style="{ color: reviewResultMap[item.status]?.color }"
|
||||||
{{ t(resultMap[item.result as ResultMap].label) }}
|
/>
|
||||||
|
{{ t(reviewResultMap[item.status]?.label) }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<a-tooltip :content="item.name">
|
||||||
|
<div class="one-line-text">{{ item.name }}</div>
|
||||||
|
</a-tooltip>
|
||||||
</div>
|
</div>
|
||||||
<a-tooltip :content="item.name">
|
<MsEmpty v-if="caseList.length === 0" />
|
||||||
<div class="one-line-text">{{ item.name }}</div>
|
|
||||||
</a-tooltip>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<MsPagination
|
||||||
<MsPagination :total="total" :page-size="pageSize" :current="pageCurrent" size="mini" simple />
|
v-model:page-size="pageNation.pageSize"
|
||||||
|
v-model:current="pageNation.current"
|
||||||
|
:total="pageNation.total"
|
||||||
|
size="mini"
|
||||||
|
simple
|
||||||
|
@change="loadCaseList"
|
||||||
|
@page-size-change="loadCaseList"
|
||||||
|
/>
|
||||||
|
</a-spin>
|
||||||
</div>
|
</div>
|
||||||
<div class="relative flex w-[calc(100%-356px)] flex-col">
|
<div class="relative flex w-[calc(100%-356px)] flex-col">
|
||||||
<div class="pl-[16px] pt-[16px]">
|
<div class="pl-[16px] pt-[16px]">
|
||||||
<div class="rounded-[var(--border-radius-small)] bg-[var(--color-text-n9)] p-[16px]">
|
<div class="rounded-[var(--border-radius-small)] bg-[var(--color-text-n9)] p-[16px]">
|
||||||
<div class="mb-[12px] flex items-center justify-between">
|
<div class="mb-[12px] flex items-center justify-between">
|
||||||
<a-tooltip :content="activeCase.module">
|
<a-tooltip :content="`【${caseDetail.num}】${caseDetail.name}`">
|
||||||
<div class="one-line-text cursor-pointer font-medium text-[rgb(var(--primary-5))]">
|
<div
|
||||||
【{{ activeCase.id }}】{{ activeCase.name }}
|
class="one-line-text cursor-pointer font-medium text-[rgb(var(--primary-5))]"
|
||||||
|
@click="goCaseDetail"
|
||||||
|
>
|
||||||
|
【{{ caseDetail.num }}】{{ caseDetail.name }}
|
||||||
</div>
|
</div>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-button type="outline" size="mini" class="arco-btn-outline--secondary">
|
<a-button
|
||||||
|
type="outline"
|
||||||
|
size="mini"
|
||||||
|
class="arco-btn-outline--secondary"
|
||||||
|
@click="editCaseVisible = true"
|
||||||
|
>
|
||||||
{{ t('common.edit') }}
|
{{ t('common.edit') }}
|
||||||
</a-button>
|
</a-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<MsIcon type="icon-icon_folder_filled1" class="mr-[4px] text-[var(--color-text-4)]" />
|
<MsIcon type="icon-icon_folder_filled1" class="mr-[4px] text-[var(--color-text-4)]" />
|
||||||
<a-tooltip :content="activeCase.module">
|
<a-tooltip :content="caseDetail.moduleName || t('common.root')">
|
||||||
<div class="one-line-text mr-[8px] max-w-[260px] font-medium text-[var(--color-text-000)]">
|
<div class="one-line-text mr-[8px] max-w-[260px] font-medium text-[var(--color-text-000)]">
|
||||||
{{ activeCase.module }}
|
{{ caseDetail.moduleName || t('common.root') }}
|
||||||
</div>
|
</div>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<div class="case-detail-label">
|
<div class="case-detail-label">
|
||||||
{{ t('caseManagement.caseReview.caseLevel') }}
|
{{ t('caseManagement.caseReview.caseLevel') }}
|
||||||
</div>
|
</div>
|
||||||
<div class="case-detail-value">
|
<div class="case-detail-value">
|
||||||
<caseLevel :case-level="activeCase.level" />
|
<caseLevel :case-level="caseDetailLevel" />
|
||||||
</div>
|
</div>
|
||||||
<div class="case-detail-label">
|
<div class="case-detail-label">
|
||||||
{{ t('caseManagement.caseReview.caseVersion') }}
|
{{ t('caseManagement.caseReview.caseVersion') }}
|
||||||
</div>
|
</div>
|
||||||
<div class="case-detail-value">
|
<div class="case-detail-value">
|
||||||
<MsIcon type="icon-icon_version" size="13" class="mr-[4px]" />
|
<MsIcon type="icon-icon_version" size="13" class="mr-[4px]" />
|
||||||
{{ activeCase.version }}
|
{{ caseDetail.versionName }}
|
||||||
</div>
|
</div>
|
||||||
<div class="case-detail-label">
|
<div class="case-detail-label">
|
||||||
{{ t('caseManagement.caseReview.reviewResult') }}
|
{{ t('caseManagement.caseReview.reviewResult') }}
|
||||||
</div>
|
</div>
|
||||||
<div class="case-detail-value">
|
<div class="case-detail-value">
|
||||||
<div class="flex items-center gap-[4px]">
|
<div
|
||||||
|
v-if="reviewResultMap[activeCaseReviewStatus as ReviewResult]"
|
||||||
|
class="flex items-center gap-[4px]"
|
||||||
|
>
|
||||||
<MsIcon
|
<MsIcon
|
||||||
:type="resultMap[activeCase.result].icon"
|
:type="reviewResultMap[activeCaseReviewStatus as ReviewResult].icon"
|
||||||
:style="{
|
:style="{
|
||||||
color: resultMap[activeCase.result].color,
|
color: reviewResultMap[activeCaseReviewStatus as ReviewResult].color,
|
||||||
}"
|
}"
|
||||||
/>
|
/>
|
||||||
{{ t(resultMap[activeCase.result].label) }}
|
{{ t(reviewResultMap[activeCaseReviewStatus as ReviewResult].label) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -128,41 +157,49 @@
|
||||||
<div v-else-if="showTab === 'detail'" class="h-full">
|
<div v-else-if="showTab === 'detail'" class="h-full">
|
||||||
<MsSplitBox :size="0.8" direction="vertical" min="0" :max="0.99">
|
<MsSplitBox :size="0.8" direction="vertical" min="0" :max="0.99">
|
||||||
<template #first>
|
<template #first>
|
||||||
<caseTabDetail :form="detailForm" :allow-edit="false" />
|
<caseTabDetail :form="caseDetail" :allow-edit="false" />
|
||||||
</template>
|
</template>
|
||||||
<template #second>
|
<template #second>
|
||||||
<div class="flex h-full flex-col overflow-hidden">
|
<div class="flex h-full flex-col overflow-hidden">
|
||||||
<div class="mb-[8px] font-medium text-[var(--color-text-1)]">
|
<div class="my-[8px] font-medium text-[var(--color-text-1)]">
|
||||||
{{ t('caseManagement.caseReview.reviewHistory') }}
|
{{ t('caseManagement.caseReview.reviewHistory') }}
|
||||||
</div>
|
</div>
|
||||||
<div class="review-history-list">
|
<div class="review-history-list">
|
||||||
<div v-for="item of reviewHistoryList" :key="item.id" class="mb-[16px]">
|
<a-spin :loading="reviewHistoryListLoading" class="h-full w-full">
|
||||||
<div class="flex items-center">
|
<div v-for="item of reviewHistoryList" :key="item.id" class="mb-[16px]">
|
||||||
<a-avatar>A</a-avatar>
|
<div class="flex items-center">
|
||||||
<div class="ml-[8px] flex items-center">
|
<MSAvatar :avatar="item.userLogo" />
|
||||||
<div class="font-medium text-[var(--color-text-1)]">{{ item.reviewer }}</div>
|
<div class="ml-[8px] flex items-center">
|
||||||
<a-divider direction="vertical" margin="8px"></a-divider>
|
<div class="font-medium text-[var(--color-text-1)]">{{ item.userName }}</div>
|
||||||
<div v-if="item.result === 1" class="flex items-center">
|
<a-divider direction="vertical" margin="8px"></a-divider>
|
||||||
<MsIcon type="icon-icon_succeed_filled" class="mr-[4px] text-[rgb(var(--success-6))]" />
|
<div v-if="item.status === 'PASS'" class="flex items-center">
|
||||||
{{ t('caseManagement.caseReview.pass') }}
|
<MsIcon type="icon-icon_succeed_filled" class="mr-[4px] text-[rgb(var(--success-6))]" />
|
||||||
</div>
|
{{ t('caseManagement.caseReview.pass') }}
|
||||||
<div v-else-if="item.result === 2" class="flex items-center">
|
</div>
|
||||||
<MsIcon type="icon-icon_close_filled" class="mr-[4px] text-[rgb(var(--danger-6))]" />
|
<div v-else-if="item.status === 'UN_PASS'" class="flex items-center">
|
||||||
{{ t('caseManagement.caseReview.fail') }}
|
<MsIcon type="icon-icon_close_filled" class="mr-[4px] text-[rgb(var(--danger-6))]" />
|
||||||
</div>
|
{{ t('caseManagement.caseReview.fail') }}
|
||||||
<div v-else-if="item.result === 3" class="flex items-center">
|
</div>
|
||||||
<MsIcon type="icon-icon_warning_filled" class="mr-[4px] text-[rgb(var(--warning-6))]" />
|
<div v-else-if="item.status === 'UNDER_REVIEWED'" class="flex items-center">
|
||||||
{{ t('caseManagement.caseReview.suggestion') }}
|
<MsIcon type="icon-icon_warning_filled" class="mr-[4px] text-[rgb(var(--warning-6))]" />
|
||||||
</div>
|
{{ t('caseManagement.caseReview.suggestion') }}
|
||||||
<div v-else-if="item.result === 4" class="flex items-center">
|
</div>
|
||||||
<MsIcon type="icon-icon_resubmit_filled" class="mr-[4px] text-[rgb(var(--warning-6))]" />
|
<div v-else-if="item.status === 'RE_REVIEWED'" class="flex items-center">
|
||||||
{{ t('caseManagement.caseReview.reReview') }}
|
<MsIcon
|
||||||
|
type="icon-icon_resubmit_filled"
|
||||||
|
class="mr-[4px] text-[rgb(var(--warning-6))]"
|
||||||
|
/>
|
||||||
|
{{ t('caseManagement.caseReview.reReview') }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="ml-[48px] text-[var(--color-text-2)]" v-html="item.contentText"></div>
|
||||||
|
<div class="ml-[48px] mt-[8px] text-[var(--color-text-4)]">
|
||||||
|
{{ dayjs(item.createTime).format('YYYY-MM-DD HH:mm:ss') }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-[48px] text-[var(--color-text-2)]">{{ item.reason }}</div>
|
<MsEmpty v-if="reviewHistoryList.length === 0" />
|
||||||
<div class="ml-[48px] mt-[8px] text-[var(--color-text-4)]">{{ item.time }}</div>
|
</a-spin>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -182,7 +219,7 @@
|
||||||
</div>
|
</div>
|
||||||
<caseTabDemand
|
<caseTabDemand
|
||||||
ref="caseDemandRef"
|
ref="caseDemandRef"
|
||||||
:fun-params="{ caseId: route.query.id as string, keyword: demandKeyword }"
|
:fun-params="{ caseId: route.query.caseId as string, keyword: demandKeyword }"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -207,19 +244,19 @@
|
||||||
<a-form ref="dialogFormRef" :model="caseResultForm" layout="vertical">
|
<a-form ref="dialogFormRef" :model="caseResultForm" layout="vertical">
|
||||||
<a-form-item field="reason" :label="t('caseManagement.caseReview.reviewResult')" class="mb-[8px]">
|
<a-form-item field="reason" :label="t('caseManagement.caseReview.reviewResult')" class="mb-[8px]">
|
||||||
<a-radio-group v-model:model-value="caseResultForm.result" @change="() => dialogFormRef?.resetFields()">
|
<a-radio-group v-model:model-value="caseResultForm.result" @change="() => dialogFormRef?.resetFields()">
|
||||||
<a-radio value="pass">
|
<a-radio value="PASS">
|
||||||
<div class="inline-flex items-center">
|
<div class="inline-flex items-center">
|
||||||
<MsIcon type="icon-icon_succeed_filled" class="mr-[4px] text-[rgb(var(--success-6))]" />
|
<MsIcon type="icon-icon_succeed_filled" class="mr-[4px] text-[rgb(var(--success-6))]" />
|
||||||
{{ t('caseManagement.caseReview.pass') }}
|
{{ t('caseManagement.caseReview.pass') }}
|
||||||
</div>
|
</div>
|
||||||
</a-radio>
|
</a-radio>
|
||||||
<a-radio value="fail">
|
<a-radio value="UN_PASS">
|
||||||
<div class="inline-flex items-center">
|
<div class="inline-flex items-center">
|
||||||
<MsIcon type="icon-icon_close_filled" class="mr-[4px] text-[rgb(var(--danger-6))]" />
|
<MsIcon type="icon-icon_close_filled" class="mr-[4px] text-[rgb(var(--danger-6))]" />
|
||||||
{{ t('caseManagement.caseReview.fail') }}
|
{{ t('caseManagement.caseReview.fail') }}
|
||||||
</div>
|
</div>
|
||||||
</a-radio>
|
</a-radio>
|
||||||
<a-radio value="suggestion">
|
<a-radio value="UNDER_REVIEWED">
|
||||||
<div class="inline-flex items-center">
|
<div class="inline-flex items-center">
|
||||||
<MsIcon type="icon-icon_warning_filled" class="mr-[4px] text-[rgb(var(--warning-6))]" />
|
<MsIcon type="icon-icon_warning_filled" class="mr-[4px] text-[rgb(var(--warning-6))]" />
|
||||||
{{ t('caseManagement.caseReview.suggestion') }}
|
{{ t('caseManagement.caseReview.suggestion') }}
|
||||||
|
@ -237,20 +274,17 @@
|
||||||
field="reason"
|
field="reason"
|
||||||
:label="t('caseManagement.caseReview.reason')"
|
:label="t('caseManagement.caseReview.reason')"
|
||||||
:rules="
|
:rules="
|
||||||
caseResultForm.result === 'fail'
|
caseResultForm.result === 'UN_PASS'
|
||||||
? [{ required: true, message: t('caseManagement.caseReview.reasonRequired') }]
|
? [{ required: true, message: t('caseManagement.caseReview.reasonRequired') }]
|
||||||
: []
|
: []
|
||||||
"
|
"
|
||||||
asterisk-position="end"
|
asterisk-position="end"
|
||||||
class="mb-0"
|
class="mb-0"
|
||||||
>
|
>
|
||||||
<a-input
|
<MsRichText v-model:modelValue="caseResultForm.reason" class="w-full" />
|
||||||
v-model:model-value="caseResultForm.reason"
|
|
||||||
:placeholder="t('caseManagement.caseReview.reasonPlaceholder')"
|
|
||||||
/>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
<a-button type="primary" class="mt-[16px]">
|
<a-button type="primary" class="mt-[16px]" :loading="submitReviewLoading" @click="submitReview">
|
||||||
{{ t('caseManagement.caseReview.submitReview') }}
|
{{ t('caseManagement.caseReview.submitReview') }}
|
||||||
</a-button>
|
</a-button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -258,73 +292,136 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</MsCard>
|
</MsCard>
|
||||||
|
<MsDrawer
|
||||||
|
v-model:visible="editCaseVisible"
|
||||||
|
:title="t('caseManagement.caseReview.updateCase')"
|
||||||
|
:width="1200"
|
||||||
|
:ok-text="t('common.update')"
|
||||||
|
:ok-loading="updateCaseLoading"
|
||||||
|
@confirm="updateCase"
|
||||||
|
>
|
||||||
|
<caseTemplateDetail v-if="editCaseVisible" v-model:form-mode-value="editCaseForm" :case-id="activeCaseId" />
|
||||||
|
</MsDrawer>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
/**
|
/**
|
||||||
* @description 功能测试-用例评审-用例详情
|
* @description 功能测试-用例评审-用例详情
|
||||||
*/
|
*/
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
import { FormInstance } from '@arco-design/web-vue';
|
import { FormInstance, Message } from '@arco-design/web-vue';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
|
import MSAvatar from '@/components/pure/ms-avatar/index.vue';
|
||||||
import MsCard from '@/components/pure/ms-card/index.vue';
|
import MsCard from '@/components/pure/ms-card/index.vue';
|
||||||
import MsDescription from '@/components/pure/ms-description/index.vue';
|
import MsDescription, { Description } from '@/components/pure/ms-description/index.vue';
|
||||||
|
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
|
||||||
|
import MsEmpty from '@/components/pure/ms-empty/index.vue';
|
||||||
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
|
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
|
||||||
import MsPagination from '@/components/pure/ms-pagination/index';
|
import MsPagination from '@/components/pure/ms-pagination/index';
|
||||||
|
import MsRichText from '@/components/pure/ms-rich-text/MsRichText.vue';
|
||||||
import MsSplitBox from '@/components/pure/ms-split-box/index.vue';
|
import MsSplitBox from '@/components/pure/ms-split-box/index.vue';
|
||||||
import caseLevel from '@/components/business/ms-case-associate/caseLevel.vue';
|
import caseLevel from '@/components/business/ms-case-associate/caseLevel.vue';
|
||||||
import type { CaseLevel } from '@/components/business/ms-case-associate/types';
|
import type { CaseLevel } from '@/components/business/ms-case-associate/types';
|
||||||
|
import caseTemplateDetail from '../caseManagementFeature/components/caseTemplateDetail.vue';
|
||||||
import caseTabDemand from '../caseManagementFeature/components/tabContent/tabDemand/associatedDemandTable.vue';
|
import caseTabDemand from '../caseManagementFeature/components/tabContent/tabDemand/associatedDemandTable.vue';
|
||||||
import caseTabDetail from '../caseManagementFeature/components/tabContent/tabDetail.vue';
|
import caseTabDetail from '../caseManagementFeature/components/tabContent/tabDetail.vue';
|
||||||
|
|
||||||
|
import {
|
||||||
|
getCaseReviewHistoryList,
|
||||||
|
getReviewDetail,
|
||||||
|
getReviewDetailCasePage,
|
||||||
|
saveCaseReviewResult,
|
||||||
|
} from '@/api/modules/case-management/caseReview';
|
||||||
|
import { getCaseDetail } from '@/api/modules/case-management/featureCase';
|
||||||
|
import { reviewDefaultDetail, reviewResultMap } from '@/config/apiTest';
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
|
import useAppStore from '@/store/modules/app';
|
||||||
|
|
||||||
|
import { ReviewCaseItem, ReviewHistoryItem, ReviewItem, ReviewResult } from '@/models/caseManagement/caseReview';
|
||||||
import type { DetailCase } from '@/models/caseManagement/featureCase';
|
import type { DetailCase } from '@/models/caseManagement/featureCase';
|
||||||
|
import { CaseManagementRouteEnum } from '@/enums/routeEnum';
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
const router = useRouter();
|
||||||
|
const appStore = useAppStore();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
const reviewName = ref('打算肯定还是觉得还是觉得还是计划的');
|
const reviewDetail = ref<ReviewItem>({ ...reviewDefaultDetail });
|
||||||
const caseDetail = ref({
|
const loading = ref(false);
|
||||||
demandCount: 999,
|
|
||||||
});
|
// 初始化评审详情
|
||||||
const onlyMine = ref(false);
|
async function initDetail() {
|
||||||
const keyword = ref('');
|
try {
|
||||||
|
loading.value = true;
|
||||||
|
const res = await getReviewDetail(route.query.id as string);
|
||||||
|
reviewDetail.value = res;
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(error);
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type ResultMap = 0 | 1 | 2 | 3;
|
|
||||||
const resultMap = {
|
|
||||||
0: {
|
|
||||||
label: t('caseManagement.caseReview.unReview'),
|
|
||||||
color: 'var(--color-text-input-border)',
|
|
||||||
icon: 'icon-icon_block_filled',
|
|
||||||
},
|
|
||||||
1: {
|
|
||||||
label: t('caseManagement.caseReview.reviewPass'),
|
|
||||||
color: 'rgb(var(--success-6))',
|
|
||||||
icon: 'icon-icon_succeed_filled',
|
|
||||||
},
|
|
||||||
2: {
|
|
||||||
label: t('caseManagement.caseReview.fail'),
|
|
||||||
color: 'rgb(var(--danger-6))',
|
|
||||||
icon: 'icon-icon_close_filled',
|
|
||||||
},
|
|
||||||
3: {
|
|
||||||
label: t('caseManagement.caseReview.reReview'),
|
|
||||||
color: 'rgb(var(--warning-6))',
|
|
||||||
icon: 'icon-icon_resubmit_filled',
|
|
||||||
},
|
|
||||||
} as const;
|
|
||||||
const type = ref('');
|
const type = ref('');
|
||||||
const typeOptions = ref([
|
const typeOptions = ref([
|
||||||
{ label: '全部', value: '' },
|
{ label: t('common.all'), value: '' },
|
||||||
{ label: resultMap[0].label, value: 'unReview' },
|
{ label: t(reviewResultMap.UN_REVIEWED.label), value: 'UN_REVIEWED' },
|
||||||
{ label: resultMap[1].label, value: 'reviewPass' },
|
{ label: t(reviewResultMap.PASS.label), value: 'PASS' },
|
||||||
{ label: resultMap[2].label, value: 'fail' },
|
{ label: t(reviewResultMap.UN_PASS.label), value: 'UN_PASS' },
|
||||||
{ label: resultMap[3].label, value: 'reReview' },
|
{ label: t(reviewResultMap.RE_REVIEWED.label), value: 'RE_REVIEWED' },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const initDetail: DetailCase = {
|
const onlyMine = ref(false);
|
||||||
|
const keyword = ref('');
|
||||||
|
const caseList = ref<ReviewCaseItem[]>([]);
|
||||||
|
const pageNation = ref({
|
||||||
|
total: 0,
|
||||||
|
pageSize: 10,
|
||||||
|
current: 1,
|
||||||
|
});
|
||||||
|
const otherListQueryParams = ref<Record<string, any>>({});
|
||||||
|
const caseListLoading = ref(false);
|
||||||
|
|
||||||
|
// 加载用例列表
|
||||||
|
async function loadCaseList() {
|
||||||
|
try {
|
||||||
|
caseListLoading.value = true;
|
||||||
|
const res = await getReviewDetailCasePage({
|
||||||
|
projectId: appStore.currentProjectId,
|
||||||
|
reviewId: route.query.id as string,
|
||||||
|
viewFlag: onlyMine.value,
|
||||||
|
keyword: keyword.value,
|
||||||
|
current: pageNation.value.current,
|
||||||
|
pageSize: pageNation.value.pageSize,
|
||||||
|
filter: type.value
|
||||||
|
? {
|
||||||
|
status: [type.value],
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
...otherListQueryParams.value,
|
||||||
|
});
|
||||||
|
caseList.value = res.list;
|
||||||
|
pageNation.value.total = res.total;
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(error);
|
||||||
|
} finally {
|
||||||
|
caseListLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => onlyMine.value,
|
||||||
|
() => loadCaseList()
|
||||||
|
);
|
||||||
|
|
||||||
|
const activeCaseId = ref(route.query.caseId as string);
|
||||||
|
const activeCaseReviewStatus = computed(() => {
|
||||||
|
return caseList.value.find((e) => e.caseId === activeCaseId.value)?.status;
|
||||||
|
});
|
||||||
|
const defaultCaseDetail: DetailCase = {
|
||||||
id: '',
|
id: '',
|
||||||
projectId: '',
|
projectId: '',
|
||||||
templateId: '',
|
templateId: '',
|
||||||
|
@ -341,60 +438,57 @@
|
||||||
tags: [],
|
tags: [],
|
||||||
customFields: [], // 自定义字段集合
|
customFields: [], // 自定义字段集合
|
||||||
relateFileMetaIds: [], // 关联文件ID集合
|
relateFileMetaIds: [], // 关联文件ID集合
|
||||||
|
reviewStatus: 'UN_REVIEWED',
|
||||||
|
functionalPriority: '',
|
||||||
};
|
};
|
||||||
const detailForm = ref<DetailCase>({ ...initDetail });
|
const caseDetail = ref<DetailCase>({ ...defaultCaseDetail });
|
||||||
|
const descriptions = ref<Description[]>([]);
|
||||||
const caseList = ref([
|
const caseDetailLevel = computed<CaseLevel>(() => {
|
||||||
{
|
if (caseDetail.value.functionalPriority) {
|
||||||
id: 'g4ggtrgrtg',
|
return (Number(JSON.parse(caseDetail.value.functionalPriority).match(/\d+/g)[0]) as CaseLevel) || 0; // 匹配出用例等级的数字
|
||||||
name: '打算肯定还是觉得还是觉得还是计划的',
|
}
|
||||||
module: '模块名称模块名称模块名称模块名称模块名称模块名称模块名称模块名称模块名称',
|
return 0;
|
||||||
level: 0 as CaseLevel,
|
|
||||||
result: 0 as ResultMap,
|
|
||||||
version: '1.0.0',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
name: '打算肯定还是觉得还是觉得还是计划的',
|
|
||||||
result: 1,
|
|
||||||
module: '模块名称模块名称模块名称模块名称模块名称模块名称模块名称模块名称模块名称',
|
|
||||||
level: 0 as CaseLevel,
|
|
||||||
version: '1.0.0',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
name: '打算肯定还是觉得还是觉得还是计划的',
|
|
||||||
result: 2,
|
|
||||||
module: '模块名称模块名称模块名称模块名称模块名称模块名称模块名称模块名称模块名称',
|
|
||||||
level: 0 as CaseLevel,
|
|
||||||
version: '1.0.0',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 4,
|
|
||||||
name: '打算肯定还是觉得还是觉得还是计划的',
|
|
||||||
result: 3,
|
|
||||||
module: '模块名称模块名称模块名称模块名称模块名称模块名称模块名称模块名称模块名称',
|
|
||||||
level: 0 as CaseLevel,
|
|
||||||
version: '1.0.0',
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
const total = ref(10);
|
|
||||||
const pageSize = ref(10);
|
|
||||||
const pageCurrent = ref(1);
|
|
||||||
const activeCase = ref({
|
|
||||||
id: 'g4ggtrgrtg',
|
|
||||||
name: '打算肯定还是觉得还是觉得还是计划的打撒打扫打扫',
|
|
||||||
module: '模块名称模块名称模块名称模块名称模块名称模块名称模块名称模块名称模块名称',
|
|
||||||
level: 0 as CaseLevel,
|
|
||||||
result: 0 as ResultMap,
|
|
||||||
version: '1.0.0',
|
|
||||||
});
|
});
|
||||||
|
|
||||||
function changeActiveCase(item: any) {
|
function changeActiveCase(item: ReviewCaseItem) {
|
||||||
if (activeCase.value.id !== item.id) {
|
if (activeCaseId.value !== item.caseId) {
|
||||||
activeCase.value = item;
|
activeCaseId.value = item.caseId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 加载用例详情
|
||||||
|
async function loadCaseDetail() {
|
||||||
|
try {
|
||||||
|
const res = await getCaseDetail(activeCaseId.value);
|
||||||
|
caseDetail.value = res;
|
||||||
|
descriptions.value = [
|
||||||
|
{
|
||||||
|
label: t('caseManagement.caseReview.belongModule'),
|
||||||
|
value: res.moduleName || t('common.root'),
|
||||||
|
},
|
||||||
|
// 解析用例模版的自定义字段
|
||||||
|
...res.customFields.map((e) => {
|
||||||
|
const val =
|
||||||
|
typeof e.defaultValue === 'string' && e.defaultValue !== '' ? JSON.parse(e.defaultValue) : e.defaultValue;
|
||||||
|
return {
|
||||||
|
label: e.fieldName,
|
||||||
|
value: Array.isArray(val) ? val.join('、') : val,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
label: t('caseManagement.caseReview.creator'),
|
||||||
|
value: res.createUser || '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('caseManagement.caseReview.createTime'),
|
||||||
|
value: dayjs().format('YYYY-MM-DD HH:mm:ss'),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const showTab = ref('detail');
|
const showTab = ref('detail');
|
||||||
const tabList = ref([
|
const tabList = ref([
|
||||||
{
|
{
|
||||||
|
@ -411,67 +505,36 @@
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const descriptions = ref([
|
const reviewHistoryListLoading = ref(false);
|
||||||
{
|
const reviewHistoryList = ref<ReviewHistoryItem[]>([]);
|
||||||
label: t('caseManagement.caseReview.belongModule'),
|
|
||||||
value: '模块模块',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: t('caseManagement.caseReview.caseStatus'),
|
|
||||||
value: '未开始',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: t('caseManagement.caseReview.responsiblePerson'),
|
|
||||||
value: '张三',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: t('caseManagement.caseReview.creator'),
|
|
||||||
value: '李四',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: t('caseManagement.caseReview.createTime'),
|
|
||||||
value: dayjs().format('YYYY-MM-DD HH:mm:ss'),
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
const reviewHistoryList = ref([
|
// 加载评审历史列表
|
||||||
{
|
async function initReviewHistoryList() {
|
||||||
id: 1,
|
try {
|
||||||
reviewer: '张三',
|
reviewHistoryListLoading.value = true;
|
||||||
avatar: '',
|
const res = await getCaseReviewHistoryList(route.query.id as string, activeCaseId.value);
|
||||||
result: 1,
|
reviewHistoryList.value = res;
|
||||||
reason: '',
|
} catch (error) {
|
||||||
time: dayjs().format('YYYY-MM-DD HH:mm:ss'),
|
// eslint-disable-next-line no-console
|
||||||
},
|
console.log(error);
|
||||||
{
|
} finally {
|
||||||
id: 2,
|
reviewHistoryListLoading.value = false;
|
||||||
reviewer: '李四',
|
}
|
||||||
avatar: '',
|
}
|
||||||
result: 2,
|
|
||||||
reason: '不通过',
|
watch(
|
||||||
time: dayjs().format('YYYY-MM-DD HH:mm:ss'),
|
() => activeCaseId.value,
|
||||||
},
|
() => {
|
||||||
{
|
loadCaseDetail();
|
||||||
id: 3,
|
if (showTab.value === 'detail') {
|
||||||
reviewer: '王五',
|
initReviewHistoryList();
|
||||||
avatar: '',
|
}
|
||||||
result: 3,
|
}
|
||||||
reason: '建议修改',
|
);
|
||||||
time: dayjs().format('YYYY-MM-DD HH:mm:ss'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 4,
|
|
||||||
reviewer: '李六',
|
|
||||||
avatar: '',
|
|
||||||
result: 4,
|
|
||||||
reason: '重新提',
|
|
||||||
time: dayjs().format('YYYY-MM-DD HH:mm:ss'),
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
const autoNext = ref(false);
|
const autoNext = ref(false);
|
||||||
const caseResultForm = ref({
|
const caseResultForm = ref({
|
||||||
result: 'pass',
|
result: 'PASS' as ReviewResult,
|
||||||
reason: '',
|
reason: '',
|
||||||
});
|
});
|
||||||
const dialogFormRef = ref<FormInstance>();
|
const dialogFormRef = ref<FormInstance>();
|
||||||
|
@ -481,6 +544,110 @@
|
||||||
function searchDemand() {
|
function searchDemand() {
|
||||||
caseDemandRef.value?.initData();
|
caseDemandRef.value?.initData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function goCaseDetail() {
|
||||||
|
router.push({
|
||||||
|
name: CaseManagementRouteEnum.CASE_MANAGEMENT,
|
||||||
|
query: {
|
||||||
|
id: activeCaseId.value,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const submitReviewLoading = ref(false);
|
||||||
|
// 提交评审
|
||||||
|
function submitReview() {
|
||||||
|
dialogFormRef.value?.validate(async (errors) => {
|
||||||
|
if (!errors) {
|
||||||
|
try {
|
||||||
|
submitReviewLoading.value = true;
|
||||||
|
const params = {
|
||||||
|
projectId: appStore.currentProjectId,
|
||||||
|
caseId: activeCaseId.value,
|
||||||
|
reviewId: route.query.id as string,
|
||||||
|
status: caseResultForm.value.result,
|
||||||
|
reviewPassRule: reviewDetail.value.reviewPassRule,
|
||||||
|
content: caseResultForm.value.reason,
|
||||||
|
notifier: '', // TODO: 通知人
|
||||||
|
};
|
||||||
|
await saveCaseReviewResult(params);
|
||||||
|
Message.success(t('caseManagement.caseReview.reviewSuccess'));
|
||||||
|
caseResultForm.value = {
|
||||||
|
result: 'PASS' as ReviewResult,
|
||||||
|
reason: '',
|
||||||
|
};
|
||||||
|
if (autoNext.value) {
|
||||||
|
const index = caseList.value.findIndex((e) => e.caseId === activeCaseId.value);
|
||||||
|
if (index < caseList.value.length - 1) {
|
||||||
|
activeCaseId.value = caseList.value[index + 1].caseId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
loadCaseList();
|
||||||
|
loadCaseDetail();
|
||||||
|
initReviewHistoryList();
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(error);
|
||||||
|
} finally {
|
||||||
|
submitReviewLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const editCaseVisible = ref(false);
|
||||||
|
const editCaseForm = ref<Record<string, any>>({});
|
||||||
|
const updateCaseLoading = ref(false);
|
||||||
|
|
||||||
|
async function updateCase() {
|
||||||
|
try {
|
||||||
|
updateCaseLoading.value = true;
|
||||||
|
// await updateCaseRequest();
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(error);
|
||||||
|
} finally {
|
||||||
|
updateCaseLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onBeforeMount(() => {
|
||||||
|
const lastPageParams = window.history.state.params ? JSON.parse(window.history.state.params) : null; // 获取上个页面带过来的表格查询参数
|
||||||
|
if (lastPageParams) {
|
||||||
|
const {
|
||||||
|
total,
|
||||||
|
pageSize,
|
||||||
|
current,
|
||||||
|
onlyMine: _onlyMine,
|
||||||
|
keyword: _keyword,
|
||||||
|
combine,
|
||||||
|
sort,
|
||||||
|
searchMode,
|
||||||
|
moduleIds,
|
||||||
|
} = lastPageParams;
|
||||||
|
pageNation.value = {
|
||||||
|
total,
|
||||||
|
pageSize,
|
||||||
|
current,
|
||||||
|
};
|
||||||
|
onlyMine.value = !!_onlyMine;
|
||||||
|
keyword.value = _keyword;
|
||||||
|
otherListQueryParams.value = {
|
||||||
|
combine,
|
||||||
|
sort,
|
||||||
|
searchMode,
|
||||||
|
moduleIds,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
keyword.value = route.query.caseId as string;
|
||||||
|
}
|
||||||
|
initDetail();
|
||||||
|
loadCaseList();
|
||||||
|
loadCaseDetail();
|
||||||
|
if (showTab.value === 'detail') {
|
||||||
|
initReviewHistoryList();
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
@ -493,8 +660,10 @@
|
||||||
background: var(--color-text-n9);
|
background: var(--color-text-n9);
|
||||||
.case-item {
|
.case-item {
|
||||||
@apply cursor-pointer;
|
@apply cursor-pointer;
|
||||||
|
&:not(:last-child) {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
margin-bottom: 8px;
|
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
border-radius: var(--border-radius-small);
|
border-radius: var(--border-radius-small);
|
||||||
background-color: white;
|
background-color: white;
|
||||||
|
|
|
@ -5,21 +5,18 @@
|
||||||
v-model:currentSelectCase="currentSelectCase"
|
v-model:currentSelectCase="currentSelectCase"
|
||||||
:ok-button-disabled="associateForm.reviewers.length === 0"
|
:ok-button-disabled="associateForm.reviewers.length === 0"
|
||||||
:get-modules-func="getCaseModuleTree"
|
:get-modules-func="getCaseModuleTree"
|
||||||
:modules-params="modulesTreeParams"
|
|
||||||
:get-table-func="getCaseList"
|
:get-table-func="getCaseList"
|
||||||
:modules-count="modulesCount"
|
|
||||||
:confirm-loading="confirmLoading"
|
:confirm-loading="confirmLoading"
|
||||||
:associated-ids="associatedIds"
|
:associated-ids="associatedIds"
|
||||||
@close="emit('close')"
|
@close="emit('close')"
|
||||||
@save="saveHandler"
|
@save="saveHandler"
|
||||||
@init="getModuleCount"
|
|
||||||
>
|
>
|
||||||
<template #footerLeft>
|
<template #footerLeft>
|
||||||
<a-form ref="associateFormRef" :model="associateForm">
|
<a-form ref="associateFormRef" :model="associateForm">
|
||||||
<a-form-item
|
<a-form-item
|
||||||
field="reviewers"
|
field="reviewers"
|
||||||
:rules="[{ required: true, message: t('caseManagement.caseReview.reviewerRequired') }]"
|
:rules="[{ required: true, message: t('caseManagement.caseReview.reviewerRequired') }]"
|
||||||
class="mb-0"
|
class="review-item mb-0"
|
||||||
>
|
>
|
||||||
<template #label>
|
<template #label>
|
||||||
<div class="inline-flex items-center">
|
<div class="inline-flex items-center">
|
||||||
|
@ -52,7 +49,7 @@
|
||||||
allow-search
|
allow-search
|
||||||
allow-clear
|
allow-clear
|
||||||
multiple
|
multiple
|
||||||
class="w-[300px]"
|
class="w-[290px]"
|
||||||
:loading="reviewerLoading"
|
:loading="reviewerLoading"
|
||||||
>
|
>
|
||||||
<template #empty>
|
<template #empty>
|
||||||
|
@ -73,19 +70,19 @@
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
|
import { useVModel } from '@vueuse/core';
|
||||||
import { FormInstance, SelectOptionData } from '@arco-design/web-vue';
|
import { FormInstance, SelectOptionData } from '@arco-design/web-vue';
|
||||||
|
|
||||||
import MsCaseAssociate from '@/components/business/ms-case-associate/index.vue';
|
import MsCaseAssociate from '@/components/business/ms-case-associate/index.vue';
|
||||||
import MsSelect from '@/components/business/ms-select';
|
import MsSelect from '@/components/business/ms-select';
|
||||||
|
|
||||||
import { getReviewUsers } from '@/api/modules/case-management/caseReview';
|
import { getReviewUsers } from '@/api/modules/case-management/caseReview';
|
||||||
import { getCaseList, getCaseModulesCounts, getCaseModuleTree } from '@/api/modules/case-management/featureCase';
|
import { getCaseList, getCaseModuleTree } from '@/api/modules/case-management/featureCase';
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
import useLocale from '@/locale/useLocale';
|
import useLocale from '@/locale/useLocale';
|
||||||
import useAppStore from '@/store/modules/app';
|
import useAppStore from '@/store/modules/app';
|
||||||
|
|
||||||
import type { CaseModuleQueryParams } from '@/models/caseManagement/featureCase';
|
import { BaseAssociateCaseRequest } from '@/models/caseManagement/caseReview';
|
||||||
import type { TableQueryParams } from '@/models/common';
|
|
||||||
import { ProjectManagementRouteEnum } from '@/enums/routeEnum';
|
import { ProjectManagementRouteEnum } from '@/enums/routeEnum';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
|
@ -95,7 +92,7 @@
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'update:visible', val: boolean): void;
|
(e: 'update:visible', val: boolean): void;
|
||||||
(e: 'update:project', val: string): void;
|
(e: 'update:project', val: string): void;
|
||||||
(e: 'success', val: string[]): void;
|
(e: 'success', val: BaseAssociateCaseRequest & { reviewers: string[] }): void;
|
||||||
(e: 'close'): void;
|
(e: 'close'): void;
|
||||||
}>();
|
}>();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
@ -103,42 +100,11 @@
|
||||||
const { currentLocale } = useLocale();
|
const { currentLocale } = useLocale();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
const innerVisible = ref(false);
|
const innerVisible = useVModel(props, 'visible', emit);
|
||||||
|
const innerProject = useVModel(props, 'project', emit);
|
||||||
watch(
|
|
||||||
() => props.visible,
|
|
||||||
(val) => {
|
|
||||||
innerVisible.value = val;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => innerVisible.value,
|
|
||||||
(val) => {
|
|
||||||
if (!val) {
|
|
||||||
emit('update:visible', false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const innerProject = ref(appStore.currentProjectId);
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.project,
|
|
||||||
(val) => {
|
|
||||||
innerProject.value = val;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => innerProject.value,
|
|
||||||
(val) => {
|
|
||||||
emit('update:project', val);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const associateForm = ref({
|
const associateForm = ref({
|
||||||
reviewers: [],
|
reviewers: [] as string[],
|
||||||
});
|
});
|
||||||
const associateFormRef = ref<FormInstance>();
|
const associateFormRef = ref<FormInstance>();
|
||||||
|
|
||||||
|
@ -167,24 +133,44 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentSelectCase = ref<string | number | Record<string, any> | undefined>('');
|
const currentSelectCase = ref<string | number | Record<string, any> | undefined>('');
|
||||||
const modulesCount = ref<Record<string, any>>({});
|
|
||||||
const modulesTreeParams = ref<TableQueryParams>({});
|
|
||||||
async function getModuleCount(params: TableQueryParams) {
|
|
||||||
try {
|
|
||||||
modulesCount.value = await getCaseModulesCounts(params);
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const associatedIds = ref<string[]>([]);
|
const associatedIds = ref<string[]>([]);
|
||||||
const confirmLoading = ref<boolean>(false);
|
const confirmLoading = ref<boolean>(false);
|
||||||
|
|
||||||
function saveHandler(params: TableQueryParams) {}
|
function saveHandler(params: BaseAssociateCaseRequest) {
|
||||||
|
associateFormRef.value?.validate(async (errors) => {
|
||||||
|
if (!errors) {
|
||||||
|
try {
|
||||||
|
confirmLoading.value = true;
|
||||||
|
associatedIds.value = [...params.selectIds];
|
||||||
|
emit('success', { ...params, reviewers: associateForm.value.reviewers });
|
||||||
|
innerVisible.value = false;
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(error);
|
||||||
|
} finally {
|
||||||
|
confirmLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
onBeforeMount(() => {
|
watch(
|
||||||
initReviewers();
|
() => props.visible,
|
||||||
});
|
(val) => {
|
||||||
|
if (val) {
|
||||||
|
// 抽屉打开才加载数据
|
||||||
|
initReviewers();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped></style>
|
<style lang="less" scoped>
|
||||||
|
:deep(.review-item) {
|
||||||
|
.arco-form-item-label-col {
|
||||||
|
flex: none;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -1,33 +1,28 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="px-[24px] py-[16px]">
|
<div class="px-[24px] py-[16px]">
|
||||||
<div class="mb-[16px] flex items-center justify-between">
|
<div class="mb-[16px] flex flex-wrap items-center justify-end">
|
||||||
<div class="flex items-center">
|
<MsAdvanceFilter
|
||||||
<a-button type="primary" class="mr-[12px]" @click="associateDrawerVisible = true">
|
v-model:keyword="keyword"
|
||||||
{{ t('ms.case.associate.title') }}
|
:filter-config-list="filterConfigList"
|
||||||
</a-button>
|
:row-count="filterRowCount"
|
||||||
<a-button type="outline" @click="createCase">{{ t('caseManagement.caseReview.createCase') }}</a-button>
|
:search-placeholder="t('caseManagement.caseReview.searchPlaceholder')"
|
||||||
</div>
|
@keyword-search="() => searchCase()"
|
||||||
<div class="flex w-[70%] items-center justify-end gap-[8px]">
|
@adv-search="searchCase"
|
||||||
<a-input-search
|
@reset="searchCase"
|
||||||
v-model="keyword"
|
>
|
||||||
:placeholder="t('caseManagement.caseReview.searchPlaceholder')"
|
<template #right>
|
||||||
allow-clear
|
<div class="flex items-center">
|
||||||
class="w-[200px]"
|
<a-radio-group v-model:model-value="showType" type="button" class="case-show-type">
|
||||||
@press-enter="searchReview"
|
<a-radio value="list" class="show-type-icon p-[2px]">
|
||||||
@search="searchReview"
|
<MsIcon type="icon-icon_view-list_outlined" />
|
||||||
/>
|
</a-radio>
|
||||||
<a-button type="outline" class="arco-btn-outline--secondary px-[8px]">
|
<a-radio value="mind" class="show-type-icon p-[2px]">
|
||||||
<MsIcon type="icon-icon-filter" class="mr-[4px] text-[var(--color-text-4)]" />
|
<MsIcon type="icon-icon_mindnote_outlined" />
|
||||||
<div class="text-[var(--color-text-4)]">{{ t('common.filter') }}</div>
|
</a-radio>
|
||||||
</a-button>
|
</a-radio-group>
|
||||||
<a-radio-group v-model:model-value="showType" type="button" class="case-show-type">
|
</div>
|
||||||
<a-radio value="list" class="show-type-icon p-[2px]"><MsIcon type="icon-icon_view-list_outlined" /></a-radio>
|
</template>
|
||||||
<a-radio value="mind" class="show-type-icon p-[2px]"><MsIcon type="icon-icon_mindnote_outlined" /></a-radio>
|
</MsAdvanceFilter>
|
||||||
</a-radio-group>
|
|
||||||
<a-button type="outline" class="arco-btn-outline--secondary p-[10px]">
|
|
||||||
<icon-refresh class="text-[var(--color-text-4)]" />
|
|
||||||
</a-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<ms-base-table
|
<ms-base-table
|
||||||
v-bind="propsRes"
|
v-bind="propsRes"
|
||||||
|
@ -51,20 +46,25 @@
|
||||||
</template>
|
</template>
|
||||||
<template #name="{ record }">
|
<template #name="{ record }">
|
||||||
<a-tooltip :content="record.name">
|
<a-tooltip :content="record.name">
|
||||||
<a-button type="text" class="px-0" @click="openDetail(record.id)">
|
<a-button type="text" class="px-0" @click="review(record)">
|
||||||
<div class="one-line-text max-w-[168px]">{{ record.name }}</div>
|
<div class="one-line-text max-w-[168px]">{{ record.name }}</div>
|
||||||
</a-button>
|
</a-button>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</template>
|
</template>
|
||||||
<template #result="{ record }">
|
<template #reviewNames="{ record }">
|
||||||
|
<a-tooltip :content="record.reviewNames.join('、')">
|
||||||
|
<div class="one-line-text">{{ record.reviewNames.join('、') }}</div>
|
||||||
|
</a-tooltip>
|
||||||
|
</template>
|
||||||
|
<template #status="{ record }">
|
||||||
<div class="flex items-center gap-[4px]">
|
<div class="flex items-center gap-[4px]">
|
||||||
<MsIcon
|
<MsIcon
|
||||||
:type="resultMap[record.result as ResultMap].icon"
|
:type="reviewResultMap[record.status as ReviewResult].icon"
|
||||||
:style="{
|
:style="{
|
||||||
color: resultMap[record.result as ResultMap].color
|
color: reviewResultMap[record.status as ReviewResult].color
|
||||||
}"
|
}"
|
||||||
/>
|
/>
|
||||||
{{ t(resultMap[record.result as ResultMap].label) }}
|
{{ t(reviewResultMap[record.status as ReviewResult].label) }}
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #action="{ record }">
|
<template #action="{ record }">
|
||||||
|
@ -76,7 +76,9 @@
|
||||||
:title="t('caseManagement.caseReview.disassociateTip')"
|
:title="t('caseManagement.caseReview.disassociateTip')"
|
||||||
:sub-title-tip="t('caseManagement.caseReview.disassociateTipContent')"
|
:sub-title-tip="t('caseManagement.caseReview.disassociateTipContent')"
|
||||||
:ok-text="t('common.confirm')"
|
:ok-text="t('common.confirm')"
|
||||||
|
:loading="disassociateLoading"
|
||||||
type="error"
|
type="error"
|
||||||
|
@confirm="(val, done) => handleDisassociateReviewCase(record, done)"
|
||||||
>
|
>
|
||||||
<MsButton type="text" class="!mr-0">
|
<MsButton type="text" class="!mr-0">
|
||||||
{{ t('caseManagement.caseReview.disassociate') }}
|
{{ t('caseManagement.caseReview.disassociate') }}
|
||||||
|
@ -98,6 +100,7 @@
|
||||||
class="p-[4px]"
|
class="p-[4px]"
|
||||||
title-align="start"
|
title-align="start"
|
||||||
body-class="p-0"
|
body-class="p-0"
|
||||||
|
:width="['review', 'reReview'].includes(dialogShowType) ? 680 : 480"
|
||||||
:mask-closable="false"
|
:mask-closable="false"
|
||||||
@close="handleDialogCancel"
|
@close="handleDialogCancel"
|
||||||
>
|
>
|
||||||
|
@ -129,13 +132,13 @@
|
||||||
class="mb-[16px]"
|
class="mb-[16px]"
|
||||||
>
|
>
|
||||||
<a-radio-group v-model:model-value="dialogForm.result" @change="() => dialogFormRef?.resetFields()">
|
<a-radio-group v-model:model-value="dialogForm.result" @change="() => dialogFormRef?.resetFields()">
|
||||||
<a-radio value="pass">
|
<a-radio value="PASS">
|
||||||
<div class="inline-flex items-center">
|
<div class="inline-flex items-center">
|
||||||
<MsIcon type="icon-icon_succeed_filled" class="mr-[4px] text-[rgb(var(--success-6))]" />
|
<MsIcon type="icon-icon_succeed_filled" class="mr-[4px] text-[rgb(var(--success-6))]" />
|
||||||
{{ t('caseManagement.caseReview.pass') }}
|
{{ t('caseManagement.caseReview.pass') }}
|
||||||
</div>
|
</div>
|
||||||
</a-radio>
|
</a-radio>
|
||||||
<a-radio value="fail">
|
<a-radio value="UN_PASS">
|
||||||
<div class="inline-flex items-center">
|
<div class="inline-flex items-center">
|
||||||
<MsIcon type="icon-icon_close_filled" class="mr-[4px] text-[rgb(var(--danger-6))]" />
|
<MsIcon type="icon-icon_close_filled" class="mr-[4px] text-[rgb(var(--danger-6))]" />
|
||||||
{{ t('caseManagement.caseReview.fail') }}
|
{{ t('caseManagement.caseReview.fail') }}
|
||||||
|
@ -155,10 +158,11 @@
|
||||||
asterisk-position="end"
|
asterisk-position="end"
|
||||||
class="mb-0"
|
class="mb-0"
|
||||||
>
|
>
|
||||||
<a-input
|
<!-- <a-input
|
||||||
v-model:model-value="dialogForm.reason"
|
v-model:model-value="dialogForm.reason"
|
||||||
:placeholder="t('caseManagement.caseReview.reasonPlaceholder')"
|
:placeholder="t('caseManagement.caseReview.reasonPlaceholder')"
|
||||||
/>
|
/> -->
|
||||||
|
<MsRichText v-model:modelValue="dialogForm.reason" class="w-full" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item
|
<a-form-item
|
||||||
v-if="dialogShowType === 'changeReviewer'"
|
v-if="dialogShowType === 'changeReviewer'"
|
||||||
|
@ -171,6 +175,7 @@
|
||||||
<MsSelect
|
<MsSelect
|
||||||
v-model:modelValue="dialogForm.reviewer"
|
v-model:modelValue="dialogForm.reviewer"
|
||||||
mode="static"
|
mode="static"
|
||||||
|
:loading="reviewerLoading"
|
||||||
:placeholder="t('caseManagement.caseReview.reviewerPlaceholder')"
|
:placeholder="t('caseManagement.caseReview.reviewerPlaceholder')"
|
||||||
:options="reviewersOptions"
|
:options="reviewersOptions"
|
||||||
:search-keys="['label']"
|
:search-keys="['label']"
|
||||||
|
@ -195,94 +200,104 @@
|
||||||
/>
|
/>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</div>
|
</div>
|
||||||
<a-button type="secondary" @click="handleDialogCancel">{{ t('common.cancel') }}</a-button>
|
<a-button type="secondary" :disabled="dialogLoading" @click="handleDialogCancel">
|
||||||
<a-button v-if="dialogShowType === 'review'" type="primary" class="ml-[12px]" @click="commitResult">
|
{{ t('common.cancel') }}
|
||||||
|
</a-button>
|
||||||
|
<a-button
|
||||||
|
v-if="dialogShowType === 'review'"
|
||||||
|
type="primary"
|
||||||
|
class="ml-[12px]"
|
||||||
|
:loading="dialogLoading"
|
||||||
|
@click="commitResult"
|
||||||
|
>
|
||||||
{{ t('caseManagement.caseReview.commitResult') }}
|
{{ t('caseManagement.caseReview.commitResult') }}
|
||||||
</a-button>
|
</a-button>
|
||||||
<a-button v-if="dialogShowType === 'changeReviewer'" type="primary" class="ml-[12px]" @click="changeReviewer">
|
<a-button
|
||||||
|
v-if="dialogShowType === 'changeReviewer'"
|
||||||
|
type="primary"
|
||||||
|
class="ml-[12px]"
|
||||||
|
:loading="dialogLoading"
|
||||||
|
@click="changeReviewer"
|
||||||
|
>
|
||||||
{{ t('common.update') }}
|
{{ t('common.update') }}
|
||||||
</a-button>
|
</a-button>
|
||||||
<a-button v-if="dialogShowType === 'reReview'" type="primary" class="ml-[12px]" @click="reReview">
|
<a-button
|
||||||
|
v-if="dialogShowType === 'reReview'"
|
||||||
|
type="primary"
|
||||||
|
class="ml-[12px]"
|
||||||
|
:loading="dialogLoading"
|
||||||
|
@click="reReview"
|
||||||
|
>
|
||||||
{{ t('caseManagement.caseReview.reReview') }}
|
{{ t('caseManagement.caseReview.reReview') }}
|
||||||
</a-button>
|
</a-button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
<AssociateDrawer
|
|
||||||
v-model:visible="associateDrawerVisible"
|
|
||||||
v-model:project="associateDrawerProject"
|
|
||||||
@success="writeAssociateCases"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useRoute, useRouter } from 'vue-router';
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
import { FormInstance, Message } from '@arco-design/web-vue';
|
import { FormInstance, Message, SelectOptionData } from '@arco-design/web-vue';
|
||||||
|
|
||||||
|
import { MsAdvanceFilter } from '@/components/pure/ms-advance-filter';
|
||||||
|
import { FilterFormItem, FilterResult, FilterType } from '@/components/pure/ms-advance-filter/type';
|
||||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||||
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
|
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
|
||||||
import MsPopconfirm from '@/components/pure/ms-popconfirm/index.vue';
|
import MsPopconfirm from '@/components/pure/ms-popconfirm/index.vue';
|
||||||
|
import MsRichText from '@/components/pure/ms-rich-text/MsRichText.vue';
|
||||||
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
|
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
|
||||||
import type { BatchActionParams, BatchActionQueryParams, MsTableColumn } from '@/components/pure/ms-table/type';
|
import type { BatchActionParams, BatchActionQueryParams, MsTableColumn } from '@/components/pure/ms-table/type';
|
||||||
import useTable from '@/components/pure/ms-table/useTable';
|
import useTable from '@/components/pure/ms-table/useTable';
|
||||||
import MsSelect from '@/components/business/ms-select';
|
import MsSelect from '@/components/business/ms-select';
|
||||||
import AssociateDrawer from '../create/associateDrawer.vue';
|
|
||||||
|
|
||||||
import { getReviewList } from '@/api/modules/case-management/caseReview';
|
import {
|
||||||
|
batchChangeReviewer,
|
||||||
|
batchDisassociateReviewCase,
|
||||||
|
batchReview,
|
||||||
|
disassociateReviewCase,
|
||||||
|
getReviewDetailCasePage,
|
||||||
|
getReviewUsers,
|
||||||
|
} from '@/api/modules/case-management/caseReview';
|
||||||
|
import { reviewResultMap, reviewStatusMap } from '@/config/apiTest';
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
import useModal from '@/hooks/useModal';
|
import useModal from '@/hooks/useModal';
|
||||||
import useTableStore from '@/hooks/useTableStore';
|
import useTableStore from '@/hooks/useTableStore';
|
||||||
|
import useAppStore from '@/store/modules/app';
|
||||||
|
import useUserStore from '@/store/modules/user';
|
||||||
|
|
||||||
|
import { ReviewCaseItem, ReviewItem, ReviewPassRule, ReviewResult } from '@/models/caseManagement/caseReview';
|
||||||
|
import { BatchApiParams } from '@/models/common';
|
||||||
|
import type { ModuleTreeNode } from '@/models/projectManagement/file';
|
||||||
import { CaseManagementRouteEnum } from '@/enums/routeEnum';
|
import { CaseManagementRouteEnum } from '@/enums/routeEnum';
|
||||||
import { TableKeyEnum } from '@/enums/tableEnum';
|
import { TableKeyEnum } from '@/enums/tableEnum';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
activeFolder: string | number;
|
activeFolder: string | number;
|
||||||
|
onlyMine: boolean;
|
||||||
|
reviewPassRule: ReviewPassRule; // 评审规则
|
||||||
|
offspringIds: string[]; // 当前选中节点的所有子节点id
|
||||||
|
moduleTree: ModuleTreeNode[];
|
||||||
}>();
|
}>();
|
||||||
|
const emit = defineEmits(['init', 'refresh']);
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
|
||||||
|
const appStore = useAppStore();
|
||||||
|
const userStore = useUserStore();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const { openModal } = useModal();
|
const { openModal } = useModal();
|
||||||
|
|
||||||
const keyword = ref('');
|
const keyword = ref('');
|
||||||
const showType = ref<'list' | 'mind'>('list');
|
const showType = ref<'list' | 'mind'>('list');
|
||||||
|
const filterRowCount = ref(0);
|
||||||
type ResultMap = 0 | 1 | 2 | 3 | 4;
|
const filterConfigList = ref<FilterFormItem[]>([]);
|
||||||
const resultMap = {
|
const tableParams = ref<Record<string, any>>({});
|
||||||
0: {
|
|
||||||
label: 'caseManagement.caseReview.unReview',
|
|
||||||
color: 'var(--color-text-input-border)',
|
|
||||||
icon: 'icon-icon_block_filled',
|
|
||||||
},
|
|
||||||
1: {
|
|
||||||
label: 'caseManagement.caseReview.reviewing',
|
|
||||||
color: 'rgb(var(--link-6))',
|
|
||||||
icon: 'icon-icon_testing',
|
|
||||||
},
|
|
||||||
2: {
|
|
||||||
label: 'caseManagement.caseReview.reviewPass',
|
|
||||||
color: 'rgb(var(--success-6))',
|
|
||||||
icon: 'icon-icon_succeed_filled',
|
|
||||||
},
|
|
||||||
3: {
|
|
||||||
label: 'caseManagement.caseReview.fail',
|
|
||||||
color: 'rgb(var(--danger-6))',
|
|
||||||
icon: 'icon-icon_close_filled',
|
|
||||||
},
|
|
||||||
4: {
|
|
||||||
label: 'caseManagement.caseReview.reReview',
|
|
||||||
color: 'rgb(var(--warning-6))',
|
|
||||||
icon: 'icon-icon_resubmit_filled',
|
|
||||||
},
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
const columns: MsTableColumn = [
|
const columns: MsTableColumn = [
|
||||||
{
|
{
|
||||||
title: 'ID',
|
title: 'ID',
|
||||||
dataIndex: 'id',
|
dataIndex: 'num',
|
||||||
sortIndex: 1,
|
sortIndex: 1,
|
||||||
showTooltip: true,
|
showTooltip: true,
|
||||||
width: 100,
|
width: 100,
|
||||||
|
@ -298,8 +313,8 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'caseManagement.caseReview.reviewer',
|
title: 'caseManagement.caseReview.reviewer',
|
||||||
dataIndex: 'reviewer',
|
dataIndex: 'reviewNames',
|
||||||
showTooltip: true,
|
slotName: 'reviewNames',
|
||||||
sortable: {
|
sortable: {
|
||||||
sortDirections: ['ascend', 'descend'],
|
sortDirections: ['ascend', 'descend'],
|
||||||
},
|
},
|
||||||
|
@ -307,14 +322,14 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'caseManagement.caseReview.reviewResult',
|
title: 'caseManagement.caseReview.reviewResult',
|
||||||
dataIndex: 'result',
|
dataIndex: 'status',
|
||||||
slotName: 'result',
|
slotName: 'status',
|
||||||
titleSlotName: 'resultColumn',
|
titleSlotName: 'resultColumn',
|
||||||
width: 110,
|
width: 110,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'caseManagement.caseReview.version',
|
title: 'caseManagement.caseReview.version',
|
||||||
dataIndex: 'version',
|
dataIndex: 'versionName',
|
||||||
width: 90,
|
width: 90,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -332,14 +347,17 @@
|
||||||
];
|
];
|
||||||
const tableStore = useTableStore();
|
const tableStore = useTableStore();
|
||||||
tableStore.initColumn(TableKeyEnum.CASE_MANAGEMENT_REVIEW_CASE, columns, 'drawer');
|
tableStore.initColumn(TableKeyEnum.CASE_MANAGEMENT_REVIEW_CASE, columns, 'drawer');
|
||||||
const { propsRes, propsEvent, loadList, setLoadListParams, resetSelector } = useTable(getReviewList, {
|
const { propsRes, propsEvent, loadList, setLoadListParams, resetSelector, getTableQueryParams } = useTable(
|
||||||
scroll: { x: '100%' },
|
getReviewDetailCasePage,
|
||||||
tableKey: TableKeyEnum.CASE_MANAGEMENT_REVIEW_CASE,
|
{
|
||||||
showSetting: true,
|
scroll: { x: '100%' },
|
||||||
selectable: true,
|
tableKey: TableKeyEnum.CASE_MANAGEMENT_REVIEW_CASE,
|
||||||
showSelectAll: true,
|
showSetting: true,
|
||||||
draggable: { type: 'handle', width: 32 },
|
selectable: true,
|
||||||
});
|
showSelectAll: true,
|
||||||
|
draggable: { type: 'handle', width: 32 },
|
||||||
|
}
|
||||||
|
);
|
||||||
const batchActions = {
|
const batchActions = {
|
||||||
baseAction: [
|
baseAction: [
|
||||||
{
|
{
|
||||||
|
@ -361,23 +379,54 @@
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
function searchReview() {
|
function searchCase(filter?: FilterResult) {
|
||||||
setLoadListParams({
|
tableParams.value = {
|
||||||
|
projectId: appStore.currentProjectId,
|
||||||
|
reviewId: route.query.id,
|
||||||
|
moduleIds: props.activeFolder === 'all' ? [] : [props.activeFolder, ...props.offspringIds],
|
||||||
keyword: keyword.value,
|
keyword: keyword.value,
|
||||||
});
|
viewFlag: props.onlyMine,
|
||||||
|
combine: filter
|
||||||
|
? {
|
||||||
|
...filter.combine,
|
||||||
|
}
|
||||||
|
: {},
|
||||||
|
current: propsRes.value.msPagination?.current,
|
||||||
|
pageSize: propsRes.value.msPagination?.pageSize,
|
||||||
|
total: propsRes.value.msPagination?.total,
|
||||||
|
};
|
||||||
|
setLoadListParams(tableParams.value);
|
||||||
loadList();
|
loadList();
|
||||||
|
emit('init', {
|
||||||
|
...tableParams.value,
|
||||||
|
moduleIds: [],
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onBeforeMount(() => {
|
onBeforeMount(() => {
|
||||||
loadList();
|
searchCase();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.onlyMine,
|
||||||
|
() => {
|
||||||
|
searchCase();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.activeFolder,
|
||||||
|
() => {
|
||||||
|
searchCase();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const tableSelected = ref<(string | number)[]>([]);
|
const tableSelected = ref<(string | number)[]>([]);
|
||||||
const batchParams = ref<BatchActionQueryParams>({
|
const batchParams = ref<BatchApiParams>({
|
||||||
selectedIds: [],
|
selectIds: [],
|
||||||
selectAll: false,
|
selectAll: false,
|
||||||
excludeIds: [],
|
excludeIds: [] as string[],
|
||||||
currentSelectCount: 0,
|
condition: {},
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -388,20 +437,16 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const dialogVisible = ref<boolean>(false);
|
const dialogVisible = ref<boolean>(false);
|
||||||
const activeRecord = ref({
|
|
||||||
id: '',
|
|
||||||
name: '',
|
|
||||||
status: 0,
|
|
||||||
});
|
|
||||||
const defaultDialogForm = {
|
const defaultDialogForm = {
|
||||||
result: 'pass',
|
result: 'PASS',
|
||||||
reason: '',
|
reason: '',
|
||||||
reviewer: [],
|
reviewer: [] as string[],
|
||||||
isAppend: false,
|
isAppend: false,
|
||||||
};
|
};
|
||||||
const dialogForm = ref({ ...defaultDialogForm });
|
const dialogForm = ref({ ...defaultDialogForm });
|
||||||
const dialogFormRef = ref<FormInstance>();
|
const dialogFormRef = ref<FormInstance>();
|
||||||
const dialogShowType = ref<'review' | 'changeReviewer' | 'reReview'>('review');
|
const dialogShowType = ref<'review' | 'changeReviewer' | 'reReview'>('review'); // 弹窗类型,review: 批量评审,changeReviewer: 批量更换评审人,reReview: 批量重新评审
|
||||||
|
const dialogLoading = ref(false);
|
||||||
const dialogTitle = computed(() => {
|
const dialogTitle = computed(() => {
|
||||||
switch (dialogShowType.value) {
|
switch (dialogShowType.value) {
|
||||||
case 'review':
|
case 'review':
|
||||||
|
@ -414,20 +459,6 @@
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const reviewersOptions = ref([
|
|
||||||
{
|
|
||||||
label: '张三',
|
|
||||||
value: '1',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '李四',
|
|
||||||
value: '2',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '王五',
|
|
||||||
value: '3',
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
function handleDialogCancel() {
|
function handleDialogCancel() {
|
||||||
dialogVisible.value = false;
|
dialogVisible.value = false;
|
||||||
|
@ -435,8 +466,30 @@
|
||||||
dialogForm.value = { ...defaultDialogForm };
|
dialogForm.value = { ...defaultDialogForm };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const disassociateLoading = ref(false);
|
||||||
/**
|
/**
|
||||||
* 拦截切换最新版确认
|
* 解除关联
|
||||||
|
* @param record 关联用例项
|
||||||
|
* @param done 关闭弹窗
|
||||||
|
*/
|
||||||
|
async function handleDisassociateReviewCase(record: ReviewCaseItem, done) {
|
||||||
|
try {
|
||||||
|
disassociateLoading.value = true;
|
||||||
|
await disassociateReviewCase(route.query.id as string, record.caseId);
|
||||||
|
emit('refresh');
|
||||||
|
done();
|
||||||
|
Message.success(t('caseManagement.caseReview.disassociateSuccess'));
|
||||||
|
loadList();
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(error);
|
||||||
|
} finally {
|
||||||
|
disassociateLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除拦截
|
||||||
* @param done 关闭弹窗
|
* @param done 关闭弹窗
|
||||||
*/
|
*/
|
||||||
async function handleDeleteConfirm(done: (closed: boolean) => void) {
|
async function handleDeleteConfirm(done: (closed: boolean) => void) {
|
||||||
|
@ -456,7 +509,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleArchive(record: any) {
|
function handleArchive(record: ReviewItem) {
|
||||||
openModal({
|
openModal({
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
title: t('caseManagement.caseReview.archivedTitle', { name: record.name }),
|
title: t('caseManagement.caseReview.archivedTitle', { name: record.name }),
|
||||||
|
@ -482,16 +535,8 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectedModuleKeys = ref<(string | number)[]>([]);
|
// 批量解除关联用例拦截
|
||||||
|
function batchDisassociate() {
|
||||||
/**
|
|
||||||
* 处理文件夹树节点选中事件
|
|
||||||
*/
|
|
||||||
function folderNodeSelect(keys: (string | number)[]) {
|
|
||||||
selectedModuleKeys.value = keys;
|
|
||||||
}
|
|
||||||
|
|
||||||
function disassociate() {
|
|
||||||
openModal({
|
openModal({
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
title: t('caseManagement.caseReview.disassociateConfirmTitle', { count: tableSelected.value.length }),
|
title: t('caseManagement.caseReview.disassociateConfirmTitle', { count: tableSelected.value.length }),
|
||||||
|
@ -500,82 +545,157 @@
|
||||||
cancelText: t('common.cancel'),
|
cancelText: t('common.cancel'),
|
||||||
onBeforeOk: async () => {
|
onBeforeOk: async () => {
|
||||||
try {
|
try {
|
||||||
// await resetUserPassword({
|
dialogLoading.value = true;
|
||||||
// selectIds,
|
await batchDisassociateReviewCase({
|
||||||
// selectAll: !!params?.selectAll,
|
reviewId: route.query.id as string,
|
||||||
// excludeIds: params?.excludeIds || [],
|
userId: props.onlyMine ? userStore.id || '' : '',
|
||||||
// condition: { keyword: keyword.value },
|
selectIds: batchParams.value.selectIds,
|
||||||
// });
|
selectAll: batchParams.value.selectAll,
|
||||||
|
excludeIds: batchParams.value.excludeIds,
|
||||||
|
condition: batchParams.value.condition,
|
||||||
|
});
|
||||||
Message.success(t('common.updateSuccess'));
|
Message.success(t('common.updateSuccess'));
|
||||||
resetSelector();
|
resetSelector();
|
||||||
|
loadList();
|
||||||
|
emit('refresh');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log(error);
|
console.log(error);
|
||||||
|
} finally {
|
||||||
|
dialogLoading.value = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
hideCancel: false,
|
hideCancel: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function reReview() {
|
// 批量重新评审
|
||||||
|
async function reReview() {
|
||||||
try {
|
try {
|
||||||
|
dialogLoading.value = true;
|
||||||
|
await batchReview({
|
||||||
|
reviewId: route.query.id as string,
|
||||||
|
userId: props.onlyMine ? userStore.id || '' : '',
|
||||||
|
reviewPassRule: props.reviewPassRule,
|
||||||
|
status: 'RE_REVIEWED',
|
||||||
|
content: dialogForm.value.reason,
|
||||||
|
notifier: '', // TODO: 通知人
|
||||||
|
selectIds: batchParams.value.selectIds,
|
||||||
|
selectAll: batchParams.value.selectAll,
|
||||||
|
excludeIds: batchParams.value.excludeIds,
|
||||||
|
condition: batchParams.value.condition,
|
||||||
|
});
|
||||||
Message.success(t('common.updateSuccess'));
|
Message.success(t('common.updateSuccess'));
|
||||||
dialogVisible.value = false;
|
dialogVisible.value = false;
|
||||||
resetSelector();
|
resetSelector();
|
||||||
|
emit('refresh');
|
||||||
|
loadList();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log(error);
|
console.log(error);
|
||||||
|
} finally {
|
||||||
|
dialogLoading.value = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 批量更换评审人
|
||||||
function changeReviewer() {
|
function changeReviewer() {
|
||||||
dialogFormRef.value?.validate(async (errors) => {
|
dialogFormRef.value?.validate(async (errors) => {
|
||||||
if (!errors) {
|
if (!errors) {
|
||||||
try {
|
try {
|
||||||
|
dialogLoading.value = true;
|
||||||
|
await batchChangeReviewer({
|
||||||
|
reviewId: route.query.id as string,
|
||||||
|
userId: props.onlyMine ? userStore.id || '' : '',
|
||||||
|
reviewerId: dialogForm.value.reviewer,
|
||||||
|
append: dialogForm.value.isAppend, // 是否追加
|
||||||
|
selectIds: batchParams.value.selectIds,
|
||||||
|
selectAll: batchParams.value.selectAll,
|
||||||
|
excludeIds: batchParams.value.excludeIds,
|
||||||
|
condition: batchParams.value.condition,
|
||||||
|
});
|
||||||
Message.success(t('common.updateSuccess'));
|
Message.success(t('common.updateSuccess'));
|
||||||
dialogVisible.value = false;
|
dialogVisible.value = false;
|
||||||
resetSelector();
|
resetSelector();
|
||||||
|
loadList();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log(error);
|
console.log(error);
|
||||||
|
} finally {
|
||||||
|
dialogLoading.value = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 提交评审结果
|
||||||
function commitResult() {
|
function commitResult() {
|
||||||
dialogFormRef.value?.validate(async (errors) => {
|
dialogFormRef.value?.validate(async (errors) => {
|
||||||
if (!errors) {
|
if (!errors) {
|
||||||
try {
|
try {
|
||||||
|
dialogLoading.value = true;
|
||||||
|
await batchReview({
|
||||||
|
reviewId: route.query.id as string,
|
||||||
|
userId: props.onlyMine ? userStore.id || '' : '',
|
||||||
|
reviewPassRule: props.reviewPassRule,
|
||||||
|
status: dialogForm.value.result as ReviewResult,
|
||||||
|
content: dialogForm.value.reason,
|
||||||
|
notifier: '', // TODO: 通知人
|
||||||
|
selectIds: batchParams.value.selectIds,
|
||||||
|
selectAll: batchParams.value.selectAll,
|
||||||
|
excludeIds: batchParams.value.excludeIds,
|
||||||
|
condition: batchParams.value.condition,
|
||||||
|
});
|
||||||
Message.success(t('common.updateSuccess'));
|
Message.success(t('common.updateSuccess'));
|
||||||
dialogVisible.value = false;
|
dialogVisible.value = false;
|
||||||
resetSelector();
|
resetSelector();
|
||||||
|
emit('refresh');
|
||||||
|
loadList();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log(error);
|
console.log(error);
|
||||||
|
} finally {
|
||||||
|
dialogLoading.value = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const reviewersOptions = ref<SelectOptionData[]>([]);
|
||||||
|
const reviewerLoading = ref(false);
|
||||||
|
|
||||||
|
async function initReviewers() {
|
||||||
|
try {
|
||||||
|
reviewerLoading.value = true;
|
||||||
|
const res = await getReviewUsers(appStore.currentProjectId, '');
|
||||||
|
reviewersOptions.value = res.map((e) => ({ label: e.name, value: e.id }));
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(error);
|
||||||
|
} finally {
|
||||||
|
reviewerLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理表格选中后批量操作
|
* 处理表格选中后批量操作
|
||||||
* @param event 批量操作事件对象
|
* @param event 批量操作事件对象
|
||||||
*/
|
*/
|
||||||
function handleTableBatch(event: BatchActionParams, params: BatchActionQueryParams) {
|
function handleTableBatch(event: BatchActionParams, params: BatchActionQueryParams) {
|
||||||
tableSelected.value = params?.selectedIds || [];
|
tableSelected.value = params?.selectedIds || [];
|
||||||
batchParams.value = params;
|
batchParams.value = { ...params, selectIds: params?.selectedIds || [], condition: {} };
|
||||||
switch (event.eventTag) {
|
switch (event.eventTag) {
|
||||||
case 'review':
|
case 'review':
|
||||||
dialogVisible.value = true;
|
dialogVisible.value = true;
|
||||||
dialogShowType.value = 'review';
|
dialogShowType.value = 'review';
|
||||||
break;
|
break;
|
||||||
case 'changeReviewer':
|
case 'changeReviewer':
|
||||||
|
initReviewers();
|
||||||
dialogVisible.value = true;
|
dialogVisible.value = true;
|
||||||
dialogShowType.value = 'changeReviewer';
|
dialogShowType.value = 'changeReviewer';
|
||||||
break;
|
break;
|
||||||
case 'disassociate':
|
case 'disassociate':
|
||||||
disassociate();
|
batchDisassociate();
|
||||||
break;
|
break;
|
||||||
case 'reReview':
|
case 'reReview':
|
||||||
dialogVisible.value = true;
|
dialogVisible.value = true;
|
||||||
|
@ -586,20 +706,20 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function openDetail(id: string) {
|
// 去用例评审页面
|
||||||
|
function review(record: ReviewCaseItem) {
|
||||||
router.push({
|
router.push({
|
||||||
name: CaseManagementRouteEnum.CASE_MANAGEMENT_REVIEW_DETAIL_CASE_DETAIL,
|
name: CaseManagementRouteEnum.CASE_MANAGEMENT_REVIEW_DETAIL_CASE_DETAIL,
|
||||||
query: {
|
query: {
|
||||||
...route.query,
|
...route.query,
|
||||||
caseId: id,
|
caseId: record.caseId,
|
||||||
|
},
|
||||||
|
state: {
|
||||||
|
params: JSON.stringify(getTableQueryParams()),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function review(record: any) {
|
|
||||||
console.log('review');
|
|
||||||
}
|
|
||||||
|
|
||||||
function createCase() {
|
function createCase() {
|
||||||
router.push({
|
router.push({
|
||||||
name: CaseManagementRouteEnum.CASE_MANAGEMENT_CASE_DETAIL,
|
name: CaseManagementRouteEnum.CASE_MANAGEMENT_CASE_DETAIL,
|
||||||
|
@ -609,12 +729,130 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const associateDrawerVisible = ref(false);
|
onBeforeMount(async () => {
|
||||||
const associateDrawerProject = ref('');
|
await initReviewers();
|
||||||
|
filterConfigList.value = [
|
||||||
|
{
|
||||||
|
title: 'ID',
|
||||||
|
dataIndex: 'ID',
|
||||||
|
type: FilterType.INPUT,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'caseManagement.caseReview.name',
|
||||||
|
dataIndex: 'name',
|
||||||
|
type: FilterType.INPUT,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'caseManagement.caseReview.caseCount',
|
||||||
|
dataIndex: 'caseCount',
|
||||||
|
type: FilterType.NUMBER,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'caseManagement.caseReview.status',
|
||||||
|
dataIndex: 'status',
|
||||||
|
type: FilterType.SELECT,
|
||||||
|
selectProps: {
|
||||||
|
mode: 'static',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: t(reviewStatusMap.PREPARED.label),
|
||||||
|
value: 'PREPARED',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t(reviewStatusMap.UNDERWAY.label),
|
||||||
|
value: 'UNDERWAY',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t(reviewStatusMap.COMPLETED.label),
|
||||||
|
value: 'COMPLETED',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t(reviewStatusMap.ARCHIVED.label),
|
||||||
|
value: 'ARCHIVED',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'caseManagement.caseReview.passRate',
|
||||||
|
dataIndex: 'passRate',
|
||||||
|
type: FilterType.NUMBER,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'caseManagement.caseReview.type',
|
||||||
|
dataIndex: 'reviewPassRule',
|
||||||
|
type: FilterType.SELECT,
|
||||||
|
selectProps: {
|
||||||
|
mode: 'static',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: t('caseManagement.caseReview.single'),
|
||||||
|
value: 'SINGLE',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('caseManagement.caseReview.multi'),
|
||||||
|
value: 'MULTIPLE',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'caseManagement.caseReview.reviewer',
|
||||||
|
dataIndex: 'reviewers',
|
||||||
|
type: FilterType.SELECT,
|
||||||
|
selectProps: {
|
||||||
|
mode: 'static',
|
||||||
|
options: reviewersOptions.value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'caseManagement.caseReview.creator',
|
||||||
|
dataIndex: 'createUser',
|
||||||
|
type: FilterType.SELECT,
|
||||||
|
selectProps: {
|
||||||
|
mode: 'static',
|
||||||
|
options: reviewersOptions.value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'caseManagement.caseReview.module',
|
||||||
|
dataIndex: 'module',
|
||||||
|
type: FilterType.TREE_SELECT,
|
||||||
|
treeSelectData: props.moduleTree,
|
||||||
|
treeSelectProps: {
|
||||||
|
fieldNames: {
|
||||||
|
title: 'name',
|
||||||
|
key: 'id',
|
||||||
|
children: 'children',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'caseManagement.caseReview.tag',
|
||||||
|
dataIndex: 'tags',
|
||||||
|
type: FilterType.TAGS_INPUT,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'caseManagement.caseReview.desc',
|
||||||
|
dataIndex: 'description',
|
||||||
|
type: FilterType.INPUT,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'caseManagement.caseReview.startTime',
|
||||||
|
dataIndex: 'startTime',
|
||||||
|
type: FilterType.DATE_PICKER,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'caseManagement.caseReview.endTime',
|
||||||
|
dataIndex: 'endTime',
|
||||||
|
type: FilterType.DATE_PICKER,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
function writeAssociateCases(ids: string[]) {
|
defineExpose({
|
||||||
console.log('writeAssociateCases', ids);
|
searchCase,
|
||||||
}
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
<div :class="getFolderClass('all')" @click="setActiveFolder('all')">
|
<div :class="getFolderClass('all')" @click="setActiveFolder('all')">
|
||||||
<MsIcon type="icon-icon_folder_filled1" class="folder-icon" />
|
<MsIcon type="icon-icon_folder_filled1" class="folder-icon" />
|
||||||
<div class="folder-name">{{ t('caseManagement.caseReview.allCases') }}</div>
|
<div class="folder-name">{{ t('caseManagement.caseReview.allCases') }}</div>
|
||||||
<div class="folder-count">({{ allFileCount }})</div>
|
<div class="folder-count">({{ allCount }})</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<a-divider class="my-[8px]" />
|
<a-divider class="my-[8px]" />
|
||||||
|
@ -47,12 +47,14 @@
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onBeforeMount, ref, watch } from 'vue';
|
import { computed, onBeforeMount, ref, watch } from 'vue';
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
|
import { useVModel } from '@vueuse/core';
|
||||||
|
|
||||||
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
|
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
|
||||||
import MsTree from '@/components/business/ms-tree/index.vue';
|
import MsTree from '@/components/business/ms-tree/index.vue';
|
||||||
import type { MsTreeNodeData } from '@/components/business/ms-tree/types';
|
import type { MsTreeNodeData } from '@/components/business/ms-tree/types';
|
||||||
|
|
||||||
import { getModules } from '@/api/modules/project-management/fileManagement';
|
import { getReviewDetailModuleTree } from '@/api/modules/case-management/caseReview';
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
import useAppStore from '@/store/modules/app';
|
import useAppStore from '@/store/modules/app';
|
||||||
import { mapTree } from '@/utils';
|
import { mapTree } from '@/utils';
|
||||||
|
@ -63,9 +65,11 @@
|
||||||
modulesCount?: Record<string, number>; // 模块数量统计对象
|
modulesCount?: Record<string, number>; // 模块数量统计对象
|
||||||
showType?: string; // 显示类型
|
showType?: string; // 显示类型
|
||||||
isExpandAll?: boolean; // 是否展开所有节点
|
isExpandAll?: boolean; // 是否展开所有节点
|
||||||
|
selectedKeys: string[]; // 选中的节点 key
|
||||||
}>();
|
}>();
|
||||||
const emit = defineEmits(['init', 'folderNodeSelect']);
|
const emit = defineEmits(['init', 'folderNodeSelect']);
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
@ -76,7 +80,7 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
const activeFolder = ref<string>('all');
|
const activeFolder = ref<string>('all');
|
||||||
const allFileCount = ref(0);
|
const allCount = ref(0);
|
||||||
const isExpandAll = ref(props.isExpandAll);
|
const isExpandAll = ref(props.isExpandAll);
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
|
@ -99,31 +103,22 @@
|
||||||
const folderTree = ref<ModuleTreeNode[]>([]);
|
const folderTree = ref<ModuleTreeNode[]>([]);
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
|
||||||
const selectedKeys = ref<string[]>([]);
|
const selectedKeys = useVModel(props, 'selectedKeys', emit);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 初始化模块树
|
* 初始化模块树
|
||||||
* @param isSetDefaultKey 是否设置第一个节点为选中节点
|
|
||||||
*/
|
*/
|
||||||
async function initModules(isSetDefaultKey = false) {
|
async function initModules() {
|
||||||
try {
|
try {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
const res = await getModules(appStore.currentProjectId);
|
const res = await getReviewDetailModuleTree(appStore.currentProjectId, route.query.id as string);
|
||||||
folderTree.value = res;
|
folderTree.value = mapTree<ModuleTreeNode>(res, (node) => {
|
||||||
if (isSetDefaultKey) {
|
return {
|
||||||
selectedKeys.value = [folderTree.value[0].id];
|
...node,
|
||||||
const offspringIds: string[] = [];
|
count: props.modulesCount?.[node.id] || 0,
|
||||||
mapTree(folderTree.value[0].children || [], (e) => {
|
};
|
||||||
offspringIds.push(e.id);
|
});
|
||||||
return e;
|
emit('init', folderTree.value);
|
||||||
});
|
|
||||||
|
|
||||||
emit('folderNodeSelect', selectedKeys.value, offspringIds);
|
|
||||||
}
|
|
||||||
emit(
|
|
||||||
'init',
|
|
||||||
folderTree.value.map((e) => e.name)
|
|
||||||
);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log(error);
|
console.log(error);
|
||||||
|
@ -141,7 +136,7 @@
|
||||||
offspringIds.push(e.id);
|
offspringIds.push(e.id);
|
||||||
return e;
|
return e;
|
||||||
});
|
});
|
||||||
|
activeFolder.value = node.id;
|
||||||
emit('folderNodeSelect', _selectedKeys, offspringIds);
|
emit('folderNodeSelect', _selectedKeys, offspringIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,6 +156,7 @@
|
||||||
count: obj?.[node.id] || 0,
|
count: obj?.[node.id] || 0,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
allCount.value = obj?.all || 0;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,104 @@
|
||||||
|
<template>
|
||||||
|
<a-modal v-model:visible="dialogVisible" class="p-[4px]" title-align="start" body-class="p-0" :mask-closable="false">
|
||||||
|
<template #title>
|
||||||
|
<div class="flex items-center justify-start">
|
||||||
|
<icon-exclamation-circle-fill size="20" class="mr-[8px] text-[rgb(var(--danger-6))]" />
|
||||||
|
<div class="text-[var(--color-text-1)]">
|
||||||
|
{{ t('caseManagement.caseReview.deleteReviewTitle', { name: props.record.name }) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div v-if="props.record.status === 'COMPLETED'" class="mb-[10px]">
|
||||||
|
<div>{{ t('caseManagement.caseReview.deleteFinishedReviewContent1') }}</div>
|
||||||
|
<div>{{ t('caseManagement.caseReview.deleteFinishedReviewContent2') }}</div>
|
||||||
|
</div>
|
||||||
|
<div v-else class="mb-[10px]">
|
||||||
|
{{
|
||||||
|
props.record.status === 'UNDERWAY'
|
||||||
|
? t('caseManagement.caseReview.deleteReviewingContent')
|
||||||
|
: t('caseManagement.caseReview.deleteReviewContent', {
|
||||||
|
status: t(reviewStatusMap[props.record.status as ReviewStatus].label),
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
</div>
|
||||||
|
<a-input
|
||||||
|
v-model:model-value="confirmReviewName"
|
||||||
|
:placeholder="t('caseManagement.caseReview.deleteReviewPlaceholder')"
|
||||||
|
/>
|
||||||
|
<template #footer>
|
||||||
|
<div class="flex items-center justify-end">
|
||||||
|
<a-button type="secondary" @click="handleDialogCancel">{{ t('common.cancel') }}</a-button>
|
||||||
|
<a-button
|
||||||
|
type="primary"
|
||||||
|
status="danger"
|
||||||
|
:disabled="confirmReviewName !== props.record.name"
|
||||||
|
class="ml-[12px]"
|
||||||
|
@click="handleDeleteConfirm"
|
||||||
|
>
|
||||||
|
{{ t('common.confirmDelete') }}
|
||||||
|
</a-button>
|
||||||
|
<a-button
|
||||||
|
v-if="props.record.status === 'COMPLETED'"
|
||||||
|
type="primary"
|
||||||
|
class="ml-[12px]"
|
||||||
|
@click="handleDeleteConfirm"
|
||||||
|
>
|
||||||
|
{{ t('caseManagement.caseReview.archive') }}
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useVModel } from '@vueuse/core';
|
||||||
|
import { Message } from '@arco-design/web-vue';
|
||||||
|
|
||||||
|
import { deleteReview } from '@/api/modules/case-management/caseReview';
|
||||||
|
import { reviewStatusMap } from '@/config/apiTest';
|
||||||
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
|
import useAppStore from '@/store/modules/app';
|
||||||
|
|
||||||
|
import { ReviewStatus } from '@/models/caseManagement/caseReview';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
visible: boolean;
|
||||||
|
record: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
status: ReviewStatus;
|
||||||
|
};
|
||||||
|
}>();
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'update:visible', val: boolean): void;
|
||||||
|
(e: 'success'): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const appStore = useAppStore();
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const dialogVisible = useVModel(props, 'visible', emit);
|
||||||
|
const confirmReviewName = ref('');
|
||||||
|
|
||||||
|
function handleDialogCancel() {
|
||||||
|
dialogVisible.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除确认
|
||||||
|
* @param done 关闭弹窗
|
||||||
|
*/
|
||||||
|
async function handleDeleteConfirm() {
|
||||||
|
try {
|
||||||
|
await deleteReview(props.record.id, appStore.currentProjectId);
|
||||||
|
Message.success(t('common.deleteSuccess'));
|
||||||
|
dialogVisible.value = false;
|
||||||
|
emit('success');
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped></style>
|
|
@ -199,6 +199,7 @@
|
||||||
hideMoreAction: e.id === 'root',
|
hideMoreAction: e.id === 'root',
|
||||||
draggable: e.id !== 'root' && !props.isModal,
|
draggable: e.id !== 'root' && !props.isModal,
|
||||||
disabled: e.id === activeFolder.value && props.isModal,
|
disabled: e.id === activeFolder.value && props.isModal,
|
||||||
|
count: props.modulesCount?.[e.id] || 0, // 避免模块数量先初始化完成了,数量没更新
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
if (isSetDefaultKey) {
|
if (isSetDefaultKey) {
|
||||||
|
@ -265,8 +266,8 @@
|
||||||
offspringIds.push(e.id);
|
offspringIds.push(e.id);
|
||||||
return e;
|
return e;
|
||||||
});
|
});
|
||||||
setActiveFolder(node.id);
|
activeFolder.value = node.id;
|
||||||
emit('folderNodeSelect', _selectedKeys, offspringIds);
|
emit('folderNodeSelect', [node.id], offspringIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -342,6 +343,7 @@
|
||||||
count: obj?.[node.id] || 0,
|
count: obj?.[node.id] || 0,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
allFileCount.value = obj?.all || 0;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -86,7 +86,7 @@
|
||||||
field: props.fieldConfig?.field || '',
|
field: props.fieldConfig?.field || '',
|
||||||
});
|
});
|
||||||
const formRef = ref<FormInstance>();
|
const formRef = ref<FormInstance>();
|
||||||
const loading = ref(false);
|
const loading = ref(true);
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.fieldConfig?.field,
|
() => props.fieldConfig?.field,
|
||||||
|
|
|
@ -6,8 +6,9 @@
|
||||||
:filter-config-list="filterConfigList"
|
:filter-config-list="filterConfigList"
|
||||||
:row-count="filterRowCount"
|
:row-count="filterRowCount"
|
||||||
:search-placeholder="t('caseManagement.caseReview.searchPlaceholder')"
|
:search-placeholder="t('caseManagement.caseReview.searchPlaceholder')"
|
||||||
@keyword-search="searchReview"
|
@keyword-search="() => searchReview()"
|
||||||
@adv-search="searchReview"
|
@adv-search="searchReview"
|
||||||
|
@reset="searchReview"
|
||||||
>
|
>
|
||||||
<template #left>
|
<template #left>
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
|
@ -29,23 +30,23 @@
|
||||||
<!-- <template #status-filter>
|
<!-- <template #status-filter>
|
||||||
<a-checkbox-group>
|
<a-checkbox-group>
|
||||||
<a-checkbox :value="0">
|
<a-checkbox :value="0">
|
||||||
<a-tag :color="statusMap[0].color" :class="statusMap[0].class">
|
<a-tag :color="reviewStatusMap[0].color" :class="reviewStatusMap[0].class">
|
||||||
{{ t(statusMap[0].label) }}
|
{{ t(reviewStatusMap[0].label) }}
|
||||||
</a-tag>
|
</a-tag>
|
||||||
</a-checkbox>
|
</a-checkbox>
|
||||||
<a-checkbox :value="1">
|
<a-checkbox :value="1">
|
||||||
<a-tag :color="statusMap[1].color" :class="statusMap[1].class">
|
<a-tag :color="reviewStatusMap[1].color" :class="reviewStatusMap[1].class">
|
||||||
{{ t(statusMap[1].label) }}
|
{{ t(reviewStatusMap[1].label) }}
|
||||||
</a-tag>
|
</a-tag>
|
||||||
</a-checkbox>
|
</a-checkbox>
|
||||||
<a-checkbox :value="2">
|
<a-checkbox :value="2">
|
||||||
<a-tag :color="statusMap[2].color" :class="statusMap[2].class">
|
<a-tag :color="reviewStatusMap[2].color" :class="reviewStatusMap[2].class">
|
||||||
{{ t(statusMap[2].label) }}
|
{{ t(reviewStatusMap[2].label) }}
|
||||||
</a-tag>
|
</a-tag>
|
||||||
</a-checkbox>
|
</a-checkbox>
|
||||||
<a-checkbox :value="3">
|
<a-checkbox :value="3">
|
||||||
<a-tag :color="statusMap[3].color" :class="statusMap[3].class">
|
<a-tag :color="reviewStatusMap[3].color" :class="reviewStatusMap[3].class">
|
||||||
{{ t(statusMap[3].label) }}
|
{{ t(reviewStatusMap[3].label) }}
|
||||||
</a-tag>
|
</a-tag>
|
||||||
</a-checkbox>
|
</a-checkbox>
|
||||||
</a-checkbox-group>
|
</a-checkbox-group>
|
||||||
|
@ -71,16 +72,28 @@
|
||||||
<template #status="{ record }">
|
<template #status="{ record }">
|
||||||
<statusTag :status="record.status" />
|
<statusTag :status="record.status" />
|
||||||
</template>
|
</template>
|
||||||
|
<template #reviewPassRule="{ record }">
|
||||||
|
{{
|
||||||
|
record.reviewPassRule === 'SINGLE'
|
||||||
|
? t('caseManagement.caseReview.single')
|
||||||
|
: t('caseManagement.caseReview.multi')
|
||||||
|
}}
|
||||||
|
</template>
|
||||||
|
<template #reviewers="{ record }">
|
||||||
|
<a-tooltip :content="record.reviewers.join('、')">
|
||||||
|
<div class="one-line-text">{{ record.reviewers.join('、') }}</div>
|
||||||
|
</a-tooltip>
|
||||||
|
</template>
|
||||||
<template #passRate="{ record }">
|
<template #passRate="{ record }">
|
||||||
<div class="mr-[8px] w-[100px]">
|
<div class="mr-[8px] w-[100px]">
|
||||||
<passRateLine :review-detail="record" height="5px" />
|
<passRateLine :review-detail="record" height="5px" />
|
||||||
</div>
|
</div>
|
||||||
<div class="text-[var(--color-text-1)]">
|
<div class="text-[var(--color-text-1)]">
|
||||||
{{ `${(((record.passCount + record.failCount) / record.caseCount) * 100).toFixed(2)}%` }}
|
{{ `${record.passRate}%` }}
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #action="{ record }">
|
<template #action="{ record }">
|
||||||
<MsButton type="text" class="!mr-0">
|
<MsButton type="text" class="!mr-0" @click="() => editReview(record)">
|
||||||
{{ t('common.edit') }}
|
{{ t('common.edit') }}
|
||||||
</MsButton>
|
</MsButton>
|
||||||
<a-divider direction="vertical" :margin="8"></a-divider>
|
<a-divider direction="vertical" :margin="8"></a-divider>
|
||||||
|
@ -99,57 +112,7 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</ms-base-table>
|
</ms-base-table>
|
||||||
<a-modal
|
<deleteReviewModal v-model:visible="dialogVisible" :record="activeRecord" @success="loadList" />
|
||||||
v-model:visible="dialogVisible"
|
|
||||||
:on-before-ok="handleDeleteConfirm"
|
|
||||||
class="p-[4px]"
|
|
||||||
title-align="start"
|
|
||||||
body-class="p-0"
|
|
||||||
:mask-closable="false"
|
|
||||||
>
|
|
||||||
<template #title>
|
|
||||||
<div class="flex items-center justify-start">
|
|
||||||
<icon-exclamation-circle-fill size="20" class="mr-[8px] text-[rgb(var(--danger-6))]" />
|
|
||||||
<div class="text-[var(--color-text-1)]">
|
|
||||||
{{ t('caseManagement.caseReview.deleteReviewTitle', { name: activeRecord.name }) }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<div v-if="activeRecord.status === 2" class="mb-[10px]">
|
|
||||||
<div>{{ t('caseManagement.caseReview.deleteFinishedReviewContent1') }}</div>
|
|
||||||
<div>{{ t('caseManagement.caseReview.deleteFinishedReviewContent2') }}</div>
|
|
||||||
</div>
|
|
||||||
<div v-else class="mb-[10px]">
|
|
||||||
{{
|
|
||||||
activeRecord.status === 1
|
|
||||||
? t('caseManagement.caseReview.deleteReviewingContent')
|
|
||||||
: t('caseManagement.caseReview.deleteReviewContent', {
|
|
||||||
status: t(statusMap[activeRecord.status as StatusMap].label),
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
</div>
|
|
||||||
<a-input
|
|
||||||
v-model:model-value="confirmReviewName"
|
|
||||||
:placeholder="t('caseManagement.caseReview.deleteReviewPlaceholder')"
|
|
||||||
/>
|
|
||||||
<template #footer>
|
|
||||||
<div class="flex items-center justify-end">
|
|
||||||
<a-button type="secondary" @click="handleDialogCancel">{{ t('common.cancel') }}</a-button>
|
|
||||||
<a-button
|
|
||||||
type="primary"
|
|
||||||
status="danger"
|
|
||||||
:disabled="confirmReviewName !== activeRecord.name"
|
|
||||||
class="ml-[12px]"
|
|
||||||
@click="handleDialogCancel"
|
|
||||||
>
|
|
||||||
{{ t('common.confirmDelete') }}
|
|
||||||
</a-button>
|
|
||||||
<a-button v-if="activeRecord.status === 2" type="primary" class="ml-[12px]" @click="handleDialogCancel">
|
|
||||||
{{ t('caseManagement.caseReview.archive') }}
|
|
||||||
</a-button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</a-modal>
|
|
||||||
<a-modal
|
<a-modal
|
||||||
v-model:visible="moveModalVisible"
|
v-model:visible="moveModalVisible"
|
||||||
title-align="start"
|
title-align="start"
|
||||||
|
@ -188,7 +151,7 @@
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
import { MsAdvanceFilter } from '@/components/pure/ms-advance-filter';
|
import { MsAdvanceFilter } from '@/components/pure/ms-advance-filter';
|
||||||
import { FilterFormItem, FilterType } from '@/components/pure/ms-advance-filter/type';
|
import { FilterFormItem, FilterResult, FilterType } from '@/components/pure/ms-advance-filter/type';
|
||||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||||
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
|
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
|
||||||
import type { BatchActionParams, BatchActionQueryParams, MsTableColumn } from '@/components/pure/ms-table/type';
|
import type { BatchActionParams, BatchActionQueryParams, MsTableColumn } from '@/components/pure/ms-table/type';
|
||||||
|
@ -197,27 +160,40 @@
|
||||||
import type { ActionsItem } from '@/components/pure/ms-table-more-action/types';
|
import type { ActionsItem } from '@/components/pure/ms-table-more-action/types';
|
||||||
import passRateLine from '../passRateLine.vue';
|
import passRateLine from '../passRateLine.vue';
|
||||||
import statusTag from '../statusTag.vue';
|
import statusTag from '../statusTag.vue';
|
||||||
|
import deleteReviewModal from './deleteReviewModal.vue';
|
||||||
import ModuleTree from './moduleTree.vue';
|
import ModuleTree from './moduleTree.vue';
|
||||||
|
|
||||||
import { getReviewList, getReviewUsers } from '@/api/modules/case-management/caseReview';
|
import { getReviewList, getReviewUsers } from '@/api/modules/case-management/caseReview';
|
||||||
|
import { reviewStatusMap } from '@/config/apiTest';
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
import useModal from '@/hooks/useModal';
|
import useModal from '@/hooks/useModal';
|
||||||
import useTableStore from '@/hooks/useTableStore';
|
import useTableStore from '@/hooks/useTableStore';
|
||||||
import useAppStore from '@/store/modules/app';
|
import useAppStore from '@/store/modules/app';
|
||||||
|
import useUserStore from '@/store/modules/user';
|
||||||
|
|
||||||
|
import {
|
||||||
|
ReviewDetailReviewersItem,
|
||||||
|
ReviewItem,
|
||||||
|
ReviewListQueryParams,
|
||||||
|
ReviewStatus,
|
||||||
|
} from '@/models/caseManagement/caseReview';
|
||||||
import type { ModuleTreeNode } from '@/models/projectManagement/file';
|
import type { ModuleTreeNode } from '@/models/projectManagement/file';
|
||||||
import { CaseManagementRouteEnum } from '@/enums/routeEnum';
|
import { CaseManagementRouteEnum } from '@/enums/routeEnum';
|
||||||
import { TableKeyEnum } from '@/enums/tableEnum';
|
import { TableKeyEnum } from '@/enums/tableEnum';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
activeFolder: string | number;
|
activeFolder: string;
|
||||||
moduleTree: ModuleTreeNode[];
|
moduleTree: ModuleTreeNode[];
|
||||||
|
showType: string;
|
||||||
|
offspringIds: string[];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'goCreate'): void;
|
(e: 'goCreate'): void;
|
||||||
|
(e: 'init', params: ReviewListQueryParams): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
const userStore = useUserStore();
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
@ -225,35 +201,6 @@
|
||||||
|
|
||||||
const keyword = ref('');
|
const keyword = ref('');
|
||||||
|
|
||||||
type StatusMap = 0 | 1 | 2 | 3;
|
|
||||||
const statusMap = {
|
|
||||||
0: {
|
|
||||||
label: 'caseManagement.caseReview.unStart',
|
|
||||||
color: 'var(--color-text-n8)',
|
|
||||||
class: '!text-[var(--color-text-1)]',
|
|
||||||
},
|
|
||||||
1: {
|
|
||||||
label: 'caseManagement.caseReview.going',
|
|
||||||
color: 'rgb(var(--link-2))',
|
|
||||||
class: '!text-[rgb(var(--link-6))]',
|
|
||||||
},
|
|
||||||
2: {
|
|
||||||
label: 'caseManagement.caseReview.finished',
|
|
||||||
color: 'rgb(var(--success-2))',
|
|
||||||
class: '!text-[rgb(var(--success-6))]',
|
|
||||||
},
|
|
||||||
3: {
|
|
||||||
label: 'caseManagement.caseReview.archived',
|
|
||||||
color: 'var(--color-text-n8)',
|
|
||||||
class: '!text-[var(--color-text-4)]',
|
|
||||||
},
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
const typeMap = {
|
|
||||||
single: 'caseManagement.caseReview.single',
|
|
||||||
multi: 'caseManagement.caseReview.multi',
|
|
||||||
};
|
|
||||||
|
|
||||||
const filterRowCount = ref(0);
|
const filterRowCount = ref(0);
|
||||||
const filterConfigList = ref<FilterFormItem[]>([]);
|
const filterConfigList = ref<FilterFormItem[]>([]);
|
||||||
|
|
||||||
|
@ -285,19 +232,19 @@
|
||||||
mode: 'static',
|
mode: 'static',
|
||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
label: t(statusMap[0].label),
|
label: t(reviewStatusMap.PREPARED.label),
|
||||||
value: 'PREPARED',
|
value: 'PREPARED',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t(statusMap[1].label),
|
label: t(reviewStatusMap.UNDERWAY.label),
|
||||||
value: 'UNDERWAY',
|
value: 'UNDERWAY',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t(statusMap[2].label),
|
label: t(reviewStatusMap.COMPLETED.label),
|
||||||
value: 'COMPLETED',
|
value: 'COMPLETED',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t(statusMap[3].label),
|
label: t(reviewStatusMap.ARCHIVED.label),
|
||||||
value: 'ARCHIVED',
|
value: 'ARCHIVED',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -310,7 +257,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'caseManagement.caseReview.type',
|
title: 'caseManagement.caseReview.type',
|
||||||
dataIndex: 'type',
|
dataIndex: 'reviewPassRule',
|
||||||
type: FilterType.SELECT,
|
type: FilterType.SELECT,
|
||||||
selectProps: {
|
selectProps: {
|
||||||
mode: 'static',
|
mode: 'static',
|
||||||
|
@ -328,7 +275,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'caseManagement.caseReview.reviewer',
|
title: 'caseManagement.caseReview.reviewer',
|
||||||
dataIndex: 'reviewer',
|
dataIndex: 'reviewers',
|
||||||
type: FilterType.SELECT,
|
type: FilterType.SELECT,
|
||||||
selectProps: {
|
selectProps: {
|
||||||
mode: 'static',
|
mode: 'static',
|
||||||
|
@ -337,7 +284,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'caseManagement.caseReview.creator',
|
title: 'caseManagement.caseReview.creator',
|
||||||
dataIndex: 'creator',
|
dataIndex: 'createUser',
|
||||||
type: FilterType.SELECT,
|
type: FilterType.SELECT,
|
||||||
selectProps: {
|
selectProps: {
|
||||||
mode: 'static',
|
mode: 'static',
|
||||||
|
@ -364,12 +311,17 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'caseManagement.caseReview.desc',
|
title: 'caseManagement.caseReview.desc',
|
||||||
dataIndex: 'desc',
|
dataIndex: 'description',
|
||||||
type: FilterType.INPUT,
|
type: FilterType.INPUT,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'caseManagement.caseReview.cycle',
|
title: 'caseManagement.caseReview.startTime',
|
||||||
dataIndex: 'cycle',
|
dataIndex: 'startTime',
|
||||||
|
type: FilterType.DATE_PICKER,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'caseManagement.caseReview.endTime',
|
||||||
|
dataIndex: 'endTime',
|
||||||
type: FilterType.DATE_PICKER,
|
type: FilterType.DATE_PICKER,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
@ -382,10 +334,10 @@
|
||||||
const columns: MsTableColumn = [
|
const columns: MsTableColumn = [
|
||||||
{
|
{
|
||||||
title: 'ID',
|
title: 'ID',
|
||||||
dataIndex: 'id',
|
dataIndex: 'num',
|
||||||
sortIndex: 1,
|
sortIndex: 1,
|
||||||
showTooltip: true,
|
showTooltip: true,
|
||||||
width: 90,
|
width: 100,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'caseManagement.caseReview.name',
|
title: 'caseManagement.caseReview.name',
|
||||||
|
@ -415,13 +367,14 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'caseManagement.caseReview.type',
|
title: 'caseManagement.caseReview.type',
|
||||||
dataIndex: 'type',
|
slotName: 'reviewPassRule',
|
||||||
|
dataIndex: 'reviewPassRule',
|
||||||
width: 90,
|
width: 90,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'caseManagement.caseReview.reviewer',
|
title: 'caseManagement.caseReview.reviewer',
|
||||||
dataIndex: 'reviewer',
|
slotName: 'reviewers',
|
||||||
showTooltip: true,
|
dataIndex: 'reviewers',
|
||||||
sortable: {
|
sortable: {
|
||||||
sortDirections: ['ascend', 'descend'],
|
sortDirections: ['ascend', 'descend'],
|
||||||
},
|
},
|
||||||
|
@ -429,7 +382,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'caseManagement.caseReview.creator',
|
title: 'caseManagement.caseReview.creator',
|
||||||
dataIndex: 'creator',
|
dataIndex: 'createUser',
|
||||||
width: 90,
|
width: 90,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -445,14 +398,14 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'caseManagement.caseReview.desc',
|
title: 'caseManagement.caseReview.desc',
|
||||||
dataIndex: 'desc',
|
dataIndex: 'description',
|
||||||
width: 150,
|
width: 150,
|
||||||
showTooltip: true,
|
showTooltip: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'caseManagement.caseReview.cycle',
|
title: 'caseManagement.caseReview.cycle',
|
||||||
dataIndex: 'cycle',
|
dataIndex: 'cycle',
|
||||||
width: 340,
|
width: 350,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'common.operation',
|
title: 'common.operation',
|
||||||
|
@ -475,9 +428,9 @@
|
||||||
(item) => {
|
(item) => {
|
||||||
return {
|
return {
|
||||||
...item,
|
...item,
|
||||||
type: t(typeMap[item.type as keyof typeof typeMap]),
|
tags: (item.tags || []).map((e: string) => ({ id: e, name: e })),
|
||||||
tags: item.tags?.map((e: string) => ({ id: e, name: e })) || [],
|
reviewers: item.reviewers.map((e: ReviewDetailReviewersItem) => e.userName),
|
||||||
cycle: `${dayjs(item.cycle[0]).format('YYYY-MM-DD HH:mm:ss')} - ${dayjs(item.cycle[1]).format(
|
cycle: `${dayjs(item.startTime).format('YYYY-MM-DD HH:mm:ss')} - ${dayjs(item.endTime).format(
|
||||||
'YYYY-MM-DD HH:mm:ss'
|
'YYYY-MM-DD HH:mm:ss'
|
||||||
)}`,
|
)}`,
|
||||||
};
|
};
|
||||||
|
@ -492,19 +445,43 @@
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
function searchReview() {
|
const tableQueryParams = ref<any>();
|
||||||
setLoadListParams({
|
function searchReview(filter?: FilterResult) {
|
||||||
|
const params = {
|
||||||
keyword: keyword.value,
|
keyword: keyword.value,
|
||||||
projectId: appStore.currentProjectId,
|
projectId: appStore.currentProjectId,
|
||||||
moduleIds: props.activeFolder === 'all' ? [] : [props.activeFolder],
|
moduleIds: props.activeFolder === 'all' ? [] : [props.activeFolder, ...props.offspringIds],
|
||||||
});
|
createByMe: props.showType === 'createByMe' ? userStore.id : undefined,
|
||||||
|
reviewByMe: props.showType === 'reviewByMe' ? userStore.id : undefined,
|
||||||
|
combine: filter
|
||||||
|
? {
|
||||||
|
...filter.combine,
|
||||||
|
}
|
||||||
|
: {},
|
||||||
|
};
|
||||||
|
setLoadListParams(params);
|
||||||
loadList();
|
loadList();
|
||||||
|
tableQueryParams.value = {
|
||||||
|
...params,
|
||||||
|
current: propsRes.value.msPagination?.current,
|
||||||
|
pageSize: propsRes.value.msPagination?.pageSize,
|
||||||
|
};
|
||||||
|
emit('init', {
|
||||||
|
...tableQueryParams.value,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onBeforeMount(() => {
|
onBeforeMount(() => {
|
||||||
searchReview();
|
searchReview();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.showType,
|
||||||
|
() => {
|
||||||
|
searchReview();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const tableSelected = ref<(string | number)[]>([]);
|
const tableSelected = ref<(string | number)[]>([]);
|
||||||
const batchParams = ref<BatchActionQueryParams>({
|
const batchParams = ref<BatchActionQueryParams>({
|
||||||
selectedIds: [],
|
selectedIds: [],
|
||||||
|
@ -524,35 +501,9 @@
|
||||||
const activeRecord = ref({
|
const activeRecord = ref({
|
||||||
id: '',
|
id: '',
|
||||||
name: '',
|
name: '',
|
||||||
status: 0,
|
status: 'PREPARED' as ReviewStatus,
|
||||||
});
|
});
|
||||||
const confirmReviewName = ref('');
|
const confirmReviewName = ref('');
|
||||||
|
|
||||||
function handleDialogCancel() {
|
|
||||||
dialogVisible.value = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 删除确认
|
|
||||||
* @param done 关闭弹窗
|
|
||||||
*/
|
|
||||||
async function handleDeleteConfirm(done: (closed: boolean) => void) {
|
|
||||||
try {
|
|
||||||
// if (replaceVersion.value !== '') {
|
|
||||||
// await useLatestVersion(replaceVersion.value);
|
|
||||||
// }
|
|
||||||
// await toggleVersionStatus(activeRecord.value.id);
|
|
||||||
// Message.success(t('caseManagement.caseReview.close', { name: activeRecord.value.name }));
|
|
||||||
loadList();
|
|
||||||
} catch (error) {
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.log(error);
|
|
||||||
done(false);
|
|
||||||
} finally {
|
|
||||||
done(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据评审状态获取更多按钮列表
|
* 根据评审状态获取更多按钮列表
|
||||||
* @param status 评审状态
|
* @param status 评审状态
|
||||||
|
@ -583,7 +534,16 @@
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleArchive(record: any) {
|
function editReview(record: ReviewItem) {
|
||||||
|
router.push({
|
||||||
|
name: CaseManagementRouteEnum.CASE_MANAGEMENT_REVIEW_CREATE,
|
||||||
|
query: {
|
||||||
|
id: record.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleArchive(record: ReviewItem) {
|
||||||
openModal({
|
openModal({
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
title: t('caseManagement.caseReview.archivedTitle', { name: record.name }),
|
title: t('caseManagement.caseReview.archivedTitle', { name: record.name }),
|
||||||
|
@ -670,7 +630,7 @@
|
||||||
* 处理表格更多按钮事件
|
* 处理表格更多按钮事件
|
||||||
* @param item
|
* @param item
|
||||||
*/
|
*/
|
||||||
function handleMoreActionSelect(item: ActionsItem, record: any) {
|
function handleMoreActionSelect(item: ActionsItem, record: ReviewItem) {
|
||||||
switch (item.eventTag) {
|
switch (item.eventTag) {
|
||||||
case 'delete':
|
case 'delete':
|
||||||
activeRecord.value = record;
|
activeRecord.value = record;
|
||||||
|
|
|
@ -1,26 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<MsColorLine
|
<MsColorLine :color-data="colorData" :height="props.height" :radius="props.radius">
|
||||||
:color-data="[
|
|
||||||
{
|
|
||||||
percentage: (props.reviewDetail.passCount / props.reviewDetail.caseCount) * 100,
|
|
||||||
color: 'rgb(var(--success-6))',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
percentage: (props.reviewDetail.failCount / props.reviewDetail.caseCount) * 100,
|
|
||||||
color: 'rgb(var(--danger-6))',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
percentage: (props.reviewDetail.reviewCount / props.reviewDetail.caseCount) * 100,
|
|
||||||
color: 'rgb(var(--warning-6))',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
percentage: (props.reviewDetail.reviewingCount / props.reviewDetail.caseCount) * 100,
|
|
||||||
color: 'rgb(var(--link-6))',
|
|
||||||
},
|
|
||||||
]"
|
|
||||||
:height="props.height"
|
|
||||||
:radius="props.radius"
|
|
||||||
>
|
|
||||||
<template #popoverContent>
|
<template #popoverContent>
|
||||||
<table>
|
<table>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -28,15 +7,13 @@
|
||||||
<td class="font-medium text-[var(--color-text-1)]">
|
<td class="font-medium text-[var(--color-text-1)]">
|
||||||
{{
|
{{
|
||||||
`${(
|
`${(
|
||||||
((props.reviewDetail.passCount + props.reviewDetail.failCount) / props.reviewDetail.caseCount) *
|
((props.reviewDetail.passCount + props.reviewDetail.unPassCount) / props.reviewDetail.caseCount) *
|
||||||
100
|
100
|
||||||
).toFixed(2)}%`
|
).toFixed(2)}%`
|
||||||
}}
|
}}
|
||||||
<span
|
<span>
|
||||||
>({{
|
({{ `${props.reviewDetail.passCount + props.reviewDetail.unPassCount}/${props.reviewDetail.caseCount}` }})
|
||||||
`${props.reviewDetail.passCount + props.reviewDetail.failCount}/${props.reviewDetail.caseCount}`
|
</span>
|
||||||
}})</span
|
|
||||||
>
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -54,7 +31,7 @@
|
||||||
<div>{{ t('caseManagement.caseReview.fail') }}</div>
|
<div>{{ t('caseManagement.caseReview.fail') }}</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="popover-value-td">
|
<td class="popover-value-td">
|
||||||
{{ props.reviewDetail.failCount }}
|
{{ props.reviewDetail.unPassCount }}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -63,7 +40,7 @@
|
||||||
<div>{{ t('caseManagement.caseReview.reReview') }}</div>
|
<div>{{ t('caseManagement.caseReview.reReview') }}</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="popover-value-td">
|
<td class="popover-value-td">
|
||||||
{{ props.reviewDetail.reviewCount }}
|
{{ props.reviewDetail.reviewedCount }}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -72,7 +49,7 @@
|
||||||
<div>{{ t('caseManagement.caseReview.reviewing') }}</div>
|
<div>{{ t('caseManagement.caseReview.reviewing') }}</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="popover-value-td">
|
<td class="popover-value-td">
|
||||||
{{ props.reviewDetail.reviewingCount }}
|
{{ props.reviewDetail.underReviewedCount }}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
@ -88,9 +65,9 @@
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
reviewDetail: {
|
reviewDetail: {
|
||||||
passCount: number;
|
passCount: number;
|
||||||
failCount: number;
|
unPassCount: number;
|
||||||
reviewCount: number;
|
reviewedCount: number;
|
||||||
reviewingCount: number;
|
underReviewedCount: number;
|
||||||
caseCount: number;
|
caseCount: number;
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
};
|
};
|
||||||
|
@ -98,6 +75,41 @@
|
||||||
radius?: string;
|
radius?: string;
|
||||||
}>();
|
}>();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const colorData = computed(() => {
|
||||||
|
if (
|
||||||
|
props.reviewDetail.status === 'PREPARED' ||
|
||||||
|
(props.reviewDetail.passCount === 0 &&
|
||||||
|
props.reviewDetail.unPassCount === 0 &&
|
||||||
|
props.reviewDetail.reviewedCount === 0 &&
|
||||||
|
props.reviewDetail.underReviewedCount === 0)
|
||||||
|
) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
percentage: 100,
|
||||||
|
color: 'var(--color-text-n8)',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
percentage: (props.reviewDetail.passCount / props.reviewDetail.caseCount) * 100,
|
||||||
|
color: 'rgb(var(--success-6))',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
percentage: (props.reviewDetail.unPassCount / props.reviewDetail.caseCount) * 100,
|
||||||
|
color: 'rgb(var(--danger-6))',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
percentage: (props.reviewDetail.reviewedCount / props.reviewDetail.caseCount) * 100,
|
||||||
|
color: 'rgb(var(--warning-6))',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
percentage: (props.reviewDetail.underReviewedCount / props.reviewDetail.caseCount) * 100,
|
||||||
|
color: 'rgb(var(--link-6))',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
|
|
@ -7,30 +7,30 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
|
|
||||||
export type StatusMap = 0 | 1 | 2 | 3;
|
import type { ReviewStatus } from '@/models/caseManagement/caseReview';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
status: StatusMap;
|
status: ReviewStatus;
|
||||||
}>();
|
}>();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
const statusMap = {
|
const statusMap = {
|
||||||
0: {
|
PREPARED: {
|
||||||
label: 'caseManagement.caseReview.unStart',
|
label: 'caseManagement.caseReview.unStart',
|
||||||
color: 'var(--color-text-n8)',
|
color: 'var(--color-text-n8)',
|
||||||
class: '!text-[var(--color-text-1)]',
|
class: '!text-[var(--color-text-1)]',
|
||||||
},
|
},
|
||||||
1: {
|
UNDERWAY: {
|
||||||
label: 'caseManagement.caseReview.going',
|
label: 'caseManagement.caseReview.going',
|
||||||
color: 'rgb(var(--link-2))',
|
color: 'rgb(var(--link-2))',
|
||||||
class: '!text-[rgb(var(--link-6))]',
|
class: '!text-[rgb(var(--link-6))]',
|
||||||
},
|
},
|
||||||
2: {
|
COMPLETED: {
|
||||||
label: 'caseManagement.caseReview.finished',
|
label: 'caseManagement.caseReview.finished',
|
||||||
color: 'rgb(var(--success-2))',
|
color: 'rgb(var(--success-2))',
|
||||||
class: '!text-[rgb(var(--success-6))]',
|
class: '!text-[rgb(var(--success-6))]',
|
||||||
},
|
},
|
||||||
3: {
|
ARCHIVED: {
|
||||||
label: 'caseManagement.caseReview.archived',
|
label: 'caseManagement.caseReview.archived',
|
||||||
color: 'var(--color-text-n8)',
|
color: 'var(--color-text-n8)',
|
||||||
class: '!text-[var(--color-text-4)]',
|
class: '!text-[var(--color-text-4)]',
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<!-- eslint-disable vue/no-v-html -->
|
<!-- eslint-disable vue/no-v-html -->
|
||||||
<template>
|
<template>
|
||||||
<MsCard
|
<MsCard
|
||||||
|
:loading="loading"
|
||||||
:title="isEdit ? t('menu.caseManagement.caseManagementCaseReviewEdit') : t('caseManagement.caseReview.create')"
|
:title="isEdit ? t('menu.caseManagement.caseManagementCaseReviewEdit') : t('caseManagement.caseReview.create')"
|
||||||
>
|
>
|
||||||
<a-form ref="reviewFormRef" class="w-[732px]" :model="reviewForm" layout="vertical">
|
<a-form ref="reviewFormRef" class="w-[732px]" :model="reviewForm" layout="vertical">
|
||||||
|
@ -26,18 +27,19 @@
|
||||||
/>
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item field="folderId" :label="t('caseManagement.caseReview.belongModule')">
|
<a-form-item field="folderId" :label="t('caseManagement.caseReview.belongModule')">
|
||||||
<a-select
|
<a-tree-select
|
||||||
v-model:modelValue="reviewForm.folderId"
|
v-model:modelValue="reviewForm.folderId"
|
||||||
:placeholder="t('caseManagement.caseReview.belongModulePlaceholder')"
|
:placeholder="t('caseManagement.caseReview.belongModulePlaceholder')"
|
||||||
:options="moduleOptions"
|
:data="moduleOptions"
|
||||||
class="w-[436px]"
|
class="w-[436px]"
|
||||||
|
:field-names="{ title: 'name', key: 'id', children: 'children' }"
|
||||||
|
:loading="moduleLoading"
|
||||||
allow-search
|
allow-search
|
||||||
multiple
|
|
||||||
/>
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item field="type" :label="t('caseManagement.caseReview.type')">
|
<a-form-item field="type" :label="t('caseManagement.caseReview.type')">
|
||||||
<a-radio-group v-model:modelValue="reviewForm.type">
|
<a-radio-group v-model:modelValue="reviewForm.type" :disabled="isEdit">
|
||||||
<a-radio value="single">
|
<a-radio value="SINGLE">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
{{ t('caseManagement.caseReview.single') }}
|
{{ t('caseManagement.caseReview.single') }}
|
||||||
<a-tooltip :content="t('caseManagement.caseReview.singleTip')" position="right">
|
<a-tooltip :content="t('caseManagement.caseReview.singleTip')" position="right">
|
||||||
|
@ -48,7 +50,7 @@
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</div>
|
</div>
|
||||||
</a-radio>
|
</a-radio>
|
||||||
<a-radio value="multi">
|
<a-radio value="MULTIPLE">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
{{ t('caseManagement.caseReview.multi') }}
|
{{ t('caseManagement.caseReview.multi') }}
|
||||||
<a-tooltip :content="t('caseManagement.caseReview.multiTip')" position="right">
|
<a-tooltip :content="t('caseManagement.caseReview.multiTip')" position="right">
|
||||||
|
@ -83,10 +85,19 @@
|
||||||
:placeholder="t('caseManagement.caseReview.reviewerPlaceholder')"
|
:placeholder="t('caseManagement.caseReview.reviewerPlaceholder')"
|
||||||
:options="reviewersOptions"
|
:options="reviewersOptions"
|
||||||
:search-keys="['label']"
|
:search-keys="['label']"
|
||||||
|
allow-clear
|
||||||
allow-search
|
allow-search
|
||||||
multiple
|
multiple
|
||||||
:loading="reviewerLoading"
|
:loading="reviewerLoading"
|
||||||
/>
|
class="reviewer-select"
|
||||||
|
>
|
||||||
|
<template #label="data">
|
||||||
|
<div class="flex items-center gap-[2px]">
|
||||||
|
<MsAvatar :avatar="reviewersOptions.find((e) => e.value === data.value)?.avatar" :size="20" />
|
||||||
|
{{ data.label }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</MsSelect>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item field="tags" :label="t('caseManagement.caseReview.tag')">
|
<a-form-item field="tags" :label="t('caseManagement.caseReview.tag')">
|
||||||
<MsTagsInput v-model:model-value="reviewForm.tags" />
|
<MsTagsInput v-model:model-value="reviewForm.tags" />
|
||||||
|
@ -95,6 +106,7 @@
|
||||||
<a-range-picker
|
<a-range-picker
|
||||||
v-model:model-value="reviewForm.cycle"
|
v-model:model-value="reviewForm.cycle"
|
||||||
show-time
|
show-time
|
||||||
|
value-format="timestamp"
|
||||||
:time-picker-props="{
|
:time-picker-props="{
|
||||||
defaultValue: ['00:00:00', '00:00:00'],
|
defaultValue: ['00:00:00', '00:00:00'],
|
||||||
}"
|
}"
|
||||||
|
@ -105,8 +117,13 @@
|
||||||
<template #label>
|
<template #label>
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<div>{{ t('caseManagement.caseReview.pickCases') }}</div>
|
<div>{{ t('caseManagement.caseReview.pickCases') }}</div>
|
||||||
<a-divider margin="4px" direction="vertical" />
|
<a-divider v-if="!isCopy" margin="4px" direction="vertical" />
|
||||||
<MsButton type="text" :disabled="selectedAssociateCases.length === 0" @click="clearSelectedCases">
|
<MsButton
|
||||||
|
v-if="!isCopy"
|
||||||
|
type="text"
|
||||||
|
:disabled="selectedAssociateCasesParams.selectIds.length === 0"
|
||||||
|
@click="clearSelectedCases"
|
||||||
|
>
|
||||||
{{ t('caseManagement.caseReview.clearSelectedCases') }}
|
{{ t('caseManagement.caseReview.clearSelectedCases') }}
|
||||||
</MsButton>
|
</MsButton>
|
||||||
</div>
|
</div>
|
||||||
|
@ -114,10 +131,14 @@
|
||||||
<div class="bg-[var(--color-text-n9)] p-[12px]">
|
<div class="bg-[var(--color-text-n9)] p-[12px]">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<div class="text-[var(--color-text-2)]">
|
<div class="text-[var(--color-text-2)]">
|
||||||
{{ t('caseManagement.caseReview.selectedCases', { count: selectedAssociateCases.length }) }}
|
{{
|
||||||
|
t('caseManagement.caseReview.selectedCases', {
|
||||||
|
count: isCopy ? reviewForm.caseCount : selectedAssociateCasesParams.selectIds.length,
|
||||||
|
})
|
||||||
|
}}
|
||||||
</div>
|
</div>
|
||||||
<a-divider margin="8px" direction="vertical" />
|
<a-divider v-if="!isCopy" margin="8px" direction="vertical" />
|
||||||
<MsButton type="text" class="font-medium" @click="caseAssociateVisible = true">
|
<MsButton v-if="!isCopy" type="text" class="font-medium" @click="caseAssociateVisible = true">
|
||||||
{{ t('ms.case.associate.title') }}
|
{{ t('ms.case.associate.title') }}
|
||||||
</MsButton>
|
</MsButton>
|
||||||
</div>
|
</div>
|
||||||
|
@ -126,13 +147,15 @@
|
||||||
</a-form>
|
</a-form>
|
||||||
<template #footerRight>
|
<template #footerRight>
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<a-button type="secondary" @click="cancelCreate">{{ t('common.cancel') }}</a-button>
|
<a-button type="secondary" :disabled="saveLoading" @click="cancelCreate">{{ t('common.cancel') }}</a-button>
|
||||||
<a-button v-if="isEdit" type="primary" class="ml-[16px]" @click="updateReview">
|
<a-button v-if="isEdit" type="primary" class="ml-[16px]" :loading="saveLoading" @click="updateReview">
|
||||||
{{ t('common.update') }}
|
{{ t('common.update') }}
|
||||||
</a-button>
|
</a-button>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<a-button type="secondary" class="mx-[16px]" @click="() => saveReview()">{{ t('common.save') }}</a-button>
|
<a-button type="secondary" class="mx-[16px]" :loading="saveLoading" @click="() => saveReview()">
|
||||||
<a-button type="primary" @click="() => saveReview(true)">
|
{{ t('common.save') }}
|
||||||
|
</a-button>
|
||||||
|
<a-button type="primary" :disabled="saveLoading" @click="() => saveReview(true)">
|
||||||
{{ t('caseManagement.caseReview.review') }}
|
{{ t('caseManagement.caseReview.review') }}
|
||||||
</a-button>
|
</a-button>
|
||||||
</template>
|
</template>
|
||||||
|
@ -154,16 +177,25 @@
|
||||||
import { useRoute, useRouter } from 'vue-router';
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
import { Message, SelectOptionData } from '@arco-design/web-vue';
|
import { Message, SelectOptionData } from '@arco-design/web-vue';
|
||||||
|
|
||||||
|
import MsAvatar from '@/components/pure/ms-avatar/index.vue';
|
||||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||||
import MsCard from '@/components/pure/ms-card/index.vue';
|
import MsCard from '@/components/pure/ms-card/index.vue';
|
||||||
import MsTagsInput from '@/components/pure/ms-tags-input/index.vue';
|
import MsTagsInput from '@/components/pure/ms-tags-input/index.vue';
|
||||||
import MsSelect from '@/components/business/ms-select';
|
import MsSelect from '@/components/business/ms-select';
|
||||||
import AssociateDrawer from './components/create/associateDrawer.vue';
|
import AssociateDrawer from './components/create/associateDrawer.vue';
|
||||||
|
|
||||||
import { getReviewUsers } from '@/api/modules/case-management/caseReview';
|
import {
|
||||||
|
addReview,
|
||||||
|
copyReview,
|
||||||
|
editReview,
|
||||||
|
getReviewDetail,
|
||||||
|
getReviewModules,
|
||||||
|
getReviewUsers,
|
||||||
|
} from '@/api/modules/case-management/caseReview';
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
import useAppStore from '@/store/modules/app';
|
import useAppStore from '@/store/modules/app';
|
||||||
|
|
||||||
|
import type { BaseAssociateCaseRequest, ReviewPassRule } from '@/models/caseManagement/caseReview';
|
||||||
import { CaseManagementRouteEnum } from '@/enums/routeEnum';
|
import { CaseManagementRouteEnum } from '@/enums/routeEnum';
|
||||||
|
|
||||||
import type { FormInstance } from '@arco-design/web-vue';
|
import type { FormInstance } from '@arco-design/web-vue';
|
||||||
|
@ -174,30 +206,36 @@
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
const isEdit = ref(!!route.query.id);
|
const isEdit = ref(!!route.query.id);
|
||||||
|
const isCopy = ref(!!route.query.copyId);
|
||||||
const reviewFormRef = ref<FormInstance>();
|
const reviewFormRef = ref<FormInstance>();
|
||||||
const reviewForm = ref({
|
const reviewForm = ref({
|
||||||
name: '',
|
name: '',
|
||||||
desc: '',
|
desc: '',
|
||||||
folderId: '',
|
folderId: (route.query.moduleId as string) || 'root',
|
||||||
type: 'single',
|
type: 'SINGLE' as ReviewPassRule,
|
||||||
reviewers: [],
|
reviewers: [] as string[],
|
||||||
tags: [],
|
tags: [] as string[],
|
||||||
cycle: [],
|
cycle: [] as number[],
|
||||||
|
caseCount: 0,
|
||||||
});
|
});
|
||||||
const moduleOptions = ref([
|
const moduleOptions = ref<SelectOptionData[]>([]);
|
||||||
{
|
const moduleLoading = ref(false);
|
||||||
label: '全部',
|
|
||||||
value: 'all',
|
/**
|
||||||
},
|
* 初始化模块选择
|
||||||
{
|
*/
|
||||||
label: '模块1',
|
async function initModules() {
|
||||||
value: '1',
|
try {
|
||||||
},
|
moduleLoading.value = true;
|
||||||
{
|
moduleOptions.value = await getReviewModules(appStore.currentProjectId);
|
||||||
label: '模块2',
|
} catch (error) {
|
||||||
value: '2',
|
// eslint-disable-next-line no-console
|
||||||
},
|
console.log(error);
|
||||||
]);
|
} finally {
|
||||||
|
moduleLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const reviewersOptions = ref<SelectOptionData[]>([]);
|
const reviewersOptions = ref<SelectOptionData[]>([]);
|
||||||
const reviewerLoading = ref(false);
|
const reviewerLoading = ref(false);
|
||||||
|
|
||||||
|
@ -205,7 +243,7 @@
|
||||||
try {
|
try {
|
||||||
reviewerLoading.value = true;
|
reviewerLoading.value = true;
|
||||||
const res = await getReviewUsers(appStore.currentProjectId, '');
|
const res = await getReviewUsers(appStore.currentProjectId, '');
|
||||||
reviewersOptions.value = res.map((e) => ({ label: e.name, value: e.id }));
|
reviewersOptions.value = res.map((e) => ({ label: e.name, value: e.id, avatar: e.avatar }));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log(error);
|
console.log(error);
|
||||||
|
@ -214,33 +252,94 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectedAssociateCases = ref<string[]>([]);
|
// 批量关联用例表格参数
|
||||||
|
const selectedAssociateCasesParams = ref<BaseAssociateCaseRequest>({
|
||||||
|
excludeIds: [],
|
||||||
|
selectIds: [],
|
||||||
|
selectAll: false,
|
||||||
|
condition: {},
|
||||||
|
moduleIds: [],
|
||||||
|
versionId: '',
|
||||||
|
refId: '',
|
||||||
|
projectId: '',
|
||||||
|
});
|
||||||
|
|
||||||
function writeAssociateCases(ids: string[]) {
|
function writeAssociateCases(param: BaseAssociateCaseRequest) {
|
||||||
selectedAssociateCases.value = [...ids];
|
selectedAssociateCasesParams.value = { ...param };
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearSelectedCases() {
|
function clearSelectedCases() {
|
||||||
selectedAssociateCases.value = [];
|
selectedAssociateCasesParams.value = {
|
||||||
|
excludeIds: [],
|
||||||
|
selectIds: [],
|
||||||
|
selectAll: false,
|
||||||
|
condition: {},
|
||||||
|
moduleIds: [],
|
||||||
|
versionId: '',
|
||||||
|
refId: '',
|
||||||
|
projectId: '',
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function cancelCreate() {
|
function cancelCreate() {
|
||||||
router.back();
|
router.back();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const saveLoading = ref(false);
|
||||||
function saveReview(isGoReview = false) {
|
function saveReview(isGoReview = false) {
|
||||||
reviewFormRef.value?.validate(async (errors) => {
|
reviewFormRef.value?.validate(async (errors) => {
|
||||||
if (!errors) {
|
if (!errors) {
|
||||||
Message.success(t('common.createSuccess'));
|
try {
|
||||||
if (isGoReview) {
|
saveLoading.value = true;
|
||||||
// 是否去评审,是的话先保存然后直接跳转至该评审详情页进行评审
|
const { name, folderId, type, cycle, tags, desc, reviewers } = reviewForm.value;
|
||||||
router.replace({
|
let res = '';
|
||||||
name: CaseManagementRouteEnum.CASE_MANAGEMENT_REVIEW,
|
if (isCopy.value) {
|
||||||
});
|
// 复制评审场景
|
||||||
} else {
|
res = await copyReview({
|
||||||
router.replace({
|
copyId: route.query.copyId as string,
|
||||||
name: CaseManagementRouteEnum.CASE_MANAGEMENT_REVIEW,
|
projectId: appStore.currentProjectId,
|
||||||
});
|
name,
|
||||||
|
moduleId: folderId,
|
||||||
|
reviewPassRule: type, // 评审通过规则
|
||||||
|
startTime: cycle[0],
|
||||||
|
endTime: cycle[1],
|
||||||
|
tags,
|
||||||
|
description: desc,
|
||||||
|
reviewers, // 评审人员
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
res = await addReview({
|
||||||
|
projectId: appStore.currentProjectId,
|
||||||
|
name,
|
||||||
|
moduleId: folderId,
|
||||||
|
reviewPassRule: type, // 评审通过规则
|
||||||
|
startTime: cycle[0],
|
||||||
|
endTime: cycle[1],
|
||||||
|
tags,
|
||||||
|
description: desc,
|
||||||
|
reviewers, // 评审人员
|
||||||
|
baseAssociateCaseRequest: selectedAssociateCasesParams.value, // 关联用例
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Message.success(t('common.createSuccess'));
|
||||||
|
if (isGoReview) {
|
||||||
|
// 是否去评审,是的话先保存然后直接跳转至该评审详情页进行评审
|
||||||
|
router.replace({
|
||||||
|
name: CaseManagementRouteEnum.CASE_MANAGEMENT_REVIEW_DETAIL,
|
||||||
|
query: {
|
||||||
|
id: res,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
router.replace({
|
||||||
|
name: CaseManagementRouteEnum.CASE_MANAGEMENT_REVIEW,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(error);
|
||||||
|
} finally {
|
||||||
|
saveLoading.value = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -249,19 +348,65 @@
|
||||||
function updateReview() {
|
function updateReview() {
|
||||||
reviewFormRef.value?.validate(async (errors) => {
|
reviewFormRef.value?.validate(async (errors) => {
|
||||||
if (!errors) {
|
if (!errors) {
|
||||||
Message.success(t('common.updateSuccess'));
|
try {
|
||||||
router.replace({
|
saveLoading.value = true;
|
||||||
name: CaseManagementRouteEnum.CASE_MANAGEMENT_REVIEW,
|
const { name, folderId, type, cycle, tags, desc, reviewers } = reviewForm.value;
|
||||||
});
|
await editReview({
|
||||||
|
id: route.query.id as string,
|
||||||
|
projectId: appStore.currentProjectId,
|
||||||
|
name,
|
||||||
|
moduleId: folderId,
|
||||||
|
reviewPassRule: type, // 评审通过规则
|
||||||
|
startTime: cycle[0],
|
||||||
|
endTime: cycle[1],
|
||||||
|
tags,
|
||||||
|
description: desc,
|
||||||
|
reviewers, // 评审人员
|
||||||
|
});
|
||||||
|
Message.success(t('common.updateSuccess'));
|
||||||
|
router.back();
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(error);
|
||||||
|
} finally {
|
||||||
|
saveLoading.value = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const caseAssociateVisible = ref<boolean>(false);
|
const caseAssociateVisible = ref<boolean>(false);
|
||||||
const caseAssociateProject = ref('');
|
const caseAssociateProject = ref('');
|
||||||
|
const loading = ref(false);
|
||||||
|
async function initReviewDetail() {
|
||||||
|
try {
|
||||||
|
loading.value = true;
|
||||||
|
const res = await getReviewDetail((route.query.copyId as string) || (route.query.id as string) || '');
|
||||||
|
reviewForm.value = {
|
||||||
|
name: res.name,
|
||||||
|
desc: res.description,
|
||||||
|
folderId: res.moduleId,
|
||||||
|
type: res.reviewPassRule,
|
||||||
|
reviewers: res.reviewers.map((e) => e.userId),
|
||||||
|
tags: res.tags,
|
||||||
|
cycle: [res.startTime, res.endTime],
|
||||||
|
caseCount: res.caseCount,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(error);
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onBeforeMount(() => {
|
onBeforeMount(() => {
|
||||||
|
initModules();
|
||||||
initReviewers();
|
initReviewers();
|
||||||
|
if (isEdit.value || isCopy.value) {
|
||||||
|
// 编辑评审场景、复制评审场景初始化评审数据
|
||||||
|
initReviewDetail();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -269,4 +414,11 @@
|
||||||
:deep(.arco-form-item-label-col) {
|
:deep(.arco-form-item-label-col) {
|
||||||
@apply w-auto flex-none;
|
@apply w-auto flex-none;
|
||||||
}
|
}
|
||||||
|
:deep(.reviewer-select) {
|
||||||
|
.arco-select-view-tag {
|
||||||
|
@apply rounded-full;
|
||||||
|
|
||||||
|
padding-left: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<MsCard :min-width="1100" auto-height hide-footer no-bottom-radius no-content-padding hide-divider>
|
<MsCard :loading="loading" :min-width="1100" auto-height hide-footer no-bottom-radius no-content-padding hide-divider>
|
||||||
<template #headerLeft>
|
<template #headerLeft>
|
||||||
<a-tooltip :content="reviewDetail.name">
|
<a-tooltip :content="reviewDetail.name">
|
||||||
<div class="one-line-text mr-[8px] max-w-[260px] font-medium text-[var(--color-text-000)]">
|
<div class="one-line-text mr-[8px] max-w-[260px] font-medium text-[var(--color-text-000)]">
|
||||||
|
@ -10,43 +10,39 @@
|
||||||
class="rounded-[0_999px_999px_0] border border-solid border-[text-[rgb(var(--primary-5))]] px-[8px] py-[2px] text-[12px] leading-[16px] text-[rgb(var(--primary-5))]"
|
class="rounded-[0_999px_999px_0] border border-solid border-[text-[rgb(var(--primary-5))]] px-[8px] py-[2px] text-[12px] leading-[16px] text-[rgb(var(--primary-5))]"
|
||||||
>
|
>
|
||||||
<MsIcon type="icon-icon-contacts" size="13" />
|
<MsIcon type="icon-icon-contacts" size="13" />
|
||||||
{{ t('caseManagement.caseReview.single') }}
|
{{
|
||||||
|
reviewDetail.reviewPassRule === 'SINGLE'
|
||||||
|
? t('caseManagement.caseReview.single')
|
||||||
|
: t('caseManagement.caseReview.multi')
|
||||||
|
}}
|
||||||
</div>
|
</div>
|
||||||
<statusTag :status="(reviewDetail.status as StatusMap)" class="mx-[16px]" />
|
<statusTag :status="(reviewDetail.status as ReviewStatus)" class="mx-[16px]" />
|
||||||
<MsPrevNextButton
|
|
||||||
ref="prevNextButtonRef"
|
|
||||||
v-model:loading="loading"
|
|
||||||
:page-change="pageChange"
|
|
||||||
:pagination="pagination"
|
|
||||||
:get-detail-func="getDetailFunc"
|
|
||||||
:detail-id="route.query.id as string"
|
|
||||||
:detail-index="detailIndex"
|
|
||||||
:table-data="tableData"
|
|
||||||
@loaded="loaded"
|
|
||||||
/>
|
|
||||||
</template>
|
</template>
|
||||||
<template #headerRight>
|
<template #headerRight>
|
||||||
<div class="mr-[16px] flex items-center">
|
<div class="mr-[16px] flex items-center">
|
||||||
<a-switch v-model:model-value="onlyMine" size="small" class="mr-[8px]" />
|
<a-switch v-model:model-value="onlyMine" size="small" class="mr-[8px]" />
|
||||||
{{ t('caseManagement.caseReview.onlyMine') }}
|
{{ t('caseManagement.caseReview.onlyMine') }}
|
||||||
</div>
|
</div>
|
||||||
<MsButton type="button" status="default">
|
<MsButton type="button" status="default" @click="associateDrawerVisible = true">
|
||||||
<MsIcon type="icon-icon_link-record_outlined1" class="mr-[8px]" />
|
<MsIcon type="icon-icon_link-record_outlined1" class="mr-[8px]" />
|
||||||
{{ t('ms.case.associate.title') }}
|
{{ t('ms.case.associate.title') }}
|
||||||
</MsButton>
|
</MsButton>
|
||||||
<MsButton type="button" status="default">
|
<MsButton type="button" status="default" @click="editReview">
|
||||||
<MsIcon type="icon-icon_edit_outlined" class="mr-[8px]" />
|
<MsIcon type="icon-icon_edit_outlined" class="mr-[8px]" />
|
||||||
{{ t('common.edit') }}
|
{{ t('common.edit') }}
|
||||||
</MsButton>
|
</MsButton>
|
||||||
<MsButton type="button" status="default">
|
<MsButton type="button" status="default" @click="copyReview">
|
||||||
<MsIcon type="icon-icon_copy_outlined" class="mr-[8px]" />
|
<MsIcon type="icon-icon_copy_outlined" class="mr-[8px]" />
|
||||||
{{ t('common.copy') }}
|
{{ t('common.copy') }}
|
||||||
</MsButton>
|
</MsButton>
|
||||||
<MsButton type="button" status="default">
|
<MsButton type="button" status="default" :loading="followLoading" @click="toggleFollowReview">
|
||||||
<MsIcon type="icon-icon_collection_outlined" class="mr-[8px]" />
|
<MsIcon
|
||||||
{{ t('common.fork') }}
|
:type="reviewDetail.followFlag ? 'icon-icon_collect_filled' : 'icon-icon_collection_outlined'"
|
||||||
|
:class="`mr-[8px] ${reviewDetail.followFlag ? 'text-[rgb(var(--warning-6))]' : ''}`"
|
||||||
|
/>
|
||||||
|
{{ t(reviewDetail.followFlag ? 'common.forked' : 'common.fork') }}
|
||||||
</MsButton>
|
</MsButton>
|
||||||
<MsTableMoreAction :list="moreAction">
|
<MsTableMoreAction :list="moreAction" @select="handleMoreSelect">
|
||||||
<MsButton type="button" status="default">
|
<MsButton type="button" status="default">
|
||||||
<MsIcon type="icon-icon_more_outlined" class="mr-[8px]" />
|
<MsIcon type="icon-icon_more_outlined" class="mr-[8px]" />
|
||||||
{{ t('common.more') }}
|
{{ t('common.more') }}
|
||||||
|
@ -58,19 +54,17 @@
|
||||||
<div class="mb-[4px] flex items-center gap-[24px]">
|
<div class="mb-[4px] flex items-center gap-[24px]">
|
||||||
<div class="text-[var(--color-text-4)]">
|
<div class="text-[var(--color-text-4)]">
|
||||||
<span class="mr-[8px]">{{ t('caseManagement.caseReview.reviewedCase') }}</span>
|
<span class="mr-[8px]">{{ t('caseManagement.caseReview.reviewedCase') }}</span>
|
||||||
<span v-if="reviewDetail.status === 0" class="text-[var(--color-text-1)]">-</span>
|
<span v-if="reviewDetail.status === 'PREPARED'" class="text-[var(--color-text-1)]">-</span>
|
||||||
<span v-else>
|
<span v-else>
|
||||||
<span class="text-[var(--color-text-1)]">{{ reviewDetail.reviewCount }}/</span
|
<span class="text-[var(--color-text-1)]"> {{ reviewDetail.reviewedCount }}/ </span
|
||||||
>{{ reviewDetail.caseCount }}
|
>{{ reviewDetail.caseCount }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-[var(--color-text-4)]">
|
<div class="text-[var(--color-text-4)]">
|
||||||
<span class="mr-[8px]">{{ t('caseManagement.caseReview.passRate') }}</span>
|
<span class="mr-[8px]">{{ t('caseManagement.caseReview.passRate') }}</span>
|
||||||
<span v-if="reviewDetail.status === 0" class="text-[var(--color-text-1)]">-</span>
|
<span v-if="reviewDetail.status === 'PREPARED'" class="text-[var(--color-text-1)]">-</span>
|
||||||
<span v-else>
|
<span v-else>
|
||||||
<span class="text-[var(--color-text-1)]">
|
<span class="text-[var(--color-text-1)]"> {{ reviewDetail.passRate }}% </span>
|
||||||
{{ ((reviewDetail.reviewCount / reviewDetail.caseCount) * 100).toFixed(2) }}%
|
|
||||||
</span>
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -84,18 +78,39 @@
|
||||||
</a-tabs>
|
</a-tabs>
|
||||||
</div>
|
</div>
|
||||||
</MsCard>
|
</MsCard>
|
||||||
<MsCard class="mt-[16px]" :special-height="180" simple has-breadcrumb no-content-padding>
|
<MsCard :loading="loading" class="mt-[16px]" :special-height="180" simple has-breadcrumb no-content-padding>
|
||||||
<MsSplitBox>
|
<MsSplitBox>
|
||||||
<template #first>
|
<template #first>
|
||||||
<div class="p-[24px]">
|
<div class="p-[24px]">
|
||||||
<CaseTree ref="folderTreeRef" @folder-node-select="handleFolderNodeSelect" />
|
<CaseTree
|
||||||
|
ref="folderTreeRef"
|
||||||
|
:modules-count="modulesCount"
|
||||||
|
:selected-keys="selectedKeys"
|
||||||
|
@folder-node-select="handleFolderNodeSelect"
|
||||||
|
@init="initModuleTree"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #second>
|
<template #second>
|
||||||
<CaseTable :active-folder="activeFolderId"></CaseTable>
|
<CaseTable
|
||||||
|
ref="caseTableRef"
|
||||||
|
:active-folder="activeFolderId"
|
||||||
|
:only-mine="onlyMine"
|
||||||
|
:review-pass-rule="reviewDetail.reviewPassRule"
|
||||||
|
:offspring-ids="offspringIds"
|
||||||
|
:module-tree="moduleTree"
|
||||||
|
@init="initModulesCount"
|
||||||
|
@refresh="handleRefresh"
|
||||||
|
></CaseTable>
|
||||||
</template>
|
</template>
|
||||||
</MsSplitBox>
|
</MsSplitBox>
|
||||||
</MsCard>
|
</MsCard>
|
||||||
|
<AssociateDrawer
|
||||||
|
v-model:visible="associateDrawerVisible"
|
||||||
|
v-model:project="associateDrawerProject"
|
||||||
|
@success="writeAssociateCases"
|
||||||
|
/>
|
||||||
|
<deleteReviewModal v-model:visible="deleteModalVisible" :record="reviewDetail" @success="handleDeleteSuccess" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
@ -103,6 +118,7 @@
|
||||||
* @description 功能测试-用例评审-评审详情
|
* @description 功能测试-用例评审-评审详情
|
||||||
*/
|
*/
|
||||||
import { useRoute, useRouter } from 'vue-router';
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
|
import { Message } from '@arco-design/web-vue';
|
||||||
|
|
||||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||||
import MsCard from '@/components/pure/ms-card/index.vue';
|
import MsCard from '@/components/pure/ms-card/index.vue';
|
||||||
|
@ -110,41 +126,163 @@
|
||||||
import MsSplitBox from '@/components/pure/ms-split-box/index.vue';
|
import MsSplitBox from '@/components/pure/ms-split-box/index.vue';
|
||||||
import MsTableMoreAction from '@/components/pure/ms-table-more-action/index.vue';
|
import MsTableMoreAction from '@/components/pure/ms-table-more-action/index.vue';
|
||||||
import { ActionsItem } from '@/components/pure/ms-table-more-action/types';
|
import { ActionsItem } from '@/components/pure/ms-table-more-action/types';
|
||||||
import MsPrevNextButton from '@/components/business/ms-prev-next-button/index.vue';
|
import AssociateDrawer from './components/create/associateDrawer.vue';
|
||||||
import CaseTable from './components/detail/caseTable.vue';
|
import CaseTable from './components/detail/caseTable.vue';
|
||||||
import CaseTree from './components/detail/caseTree.vue';
|
import CaseTree from './components/detail/caseTree.vue';
|
||||||
|
import deleteReviewModal from './components/index/deleteReviewModal.vue';
|
||||||
import passRateLine from './components/passRateLine.vue';
|
import passRateLine from './components/passRateLine.vue';
|
||||||
import statusTag, { StatusMap } from './components/statusTag.vue';
|
import statusTag from './components/statusTag.vue';
|
||||||
|
|
||||||
|
import {
|
||||||
|
associateReviewCase,
|
||||||
|
followReview,
|
||||||
|
getReviewDetail,
|
||||||
|
getReviewDetailModuleCount,
|
||||||
|
} from '@/api/modules/case-management/caseReview';
|
||||||
|
import { reviewDefaultDetail } from '@/config/apiTest';
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
|
import useAppStore from '@/store/modules/app';
|
||||||
|
import useUserStore from '@/store/modules/user';
|
||||||
|
|
||||||
|
import type {
|
||||||
|
BaseAssociateCaseRequest,
|
||||||
|
ReviewDetailCaseListQueryParams,
|
||||||
|
ReviewItem,
|
||||||
|
ReviewStatus,
|
||||||
|
} from '@/models/caseManagement/caseReview';
|
||||||
|
import type { ModuleTreeNode } from '@/models/projectManagement/file';
|
||||||
|
import { CaseManagementRouteEnum } from '@/enums/routeEnum';
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
const userStore = useUserStore();
|
||||||
|
const appStore = useAppStore();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const reviewDetail = ref({
|
const reviewDetail = ref<ReviewItem>({
|
||||||
name: '具体的用例评审的名称,最大宽度260px,超过展示省略号啦',
|
...reviewDefaultDetail,
|
||||||
status: 2,
|
|
||||||
caseCount: 100,
|
|
||||||
passCount: 0,
|
|
||||||
failCount: 10,
|
|
||||||
reviewCount: 20,
|
|
||||||
reviewingCount: 25,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
async function initDetail() {
|
||||||
|
try {
|
||||||
|
loading.value = true;
|
||||||
|
const res = await getReviewDetail(route.query.id as string);
|
||||||
|
reviewDetail.value = res;
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(error);
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const onlyMine = ref(false);
|
const onlyMine = ref(false);
|
||||||
const moreAction = ref<ActionsItem[]>([]);
|
|
||||||
const fullActions = [
|
const showTab = ref(0);
|
||||||
|
const tabList = ref([
|
||||||
{
|
{
|
||||||
label: t('caseManagement.caseReview.archive'),
|
key: 0,
|
||||||
eventTag: 'archive',
|
title: t('menu.caseManagement.featureCase'),
|
||||||
icon: 'icon-icon-draft',
|
|
||||||
},
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const modulesCount = ref<Record<string, any>>({});
|
||||||
|
|
||||||
|
async function getModuleCount(params: ReviewDetailCaseListQueryParams) {
|
||||||
|
try {
|
||||||
|
modulesCount.value = await getReviewDetailModuleCount({
|
||||||
|
...params,
|
||||||
|
viewFlag: onlyMine.value,
|
||||||
|
reviewId: route.query.id as string,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const folderTreeRef = ref<InstanceType<typeof CaseTree>>();
|
||||||
|
const activeFolderId = ref<string>('all');
|
||||||
|
const offspringIds = ref<string[]>([]);
|
||||||
|
const selectedKeys = computed({
|
||||||
|
get: () => [activeFolderId.value],
|
||||||
|
set: (val) => val,
|
||||||
|
});
|
||||||
|
|
||||||
|
function handleFolderNodeSelect(ids: string[], _offspringIds: string[]) {
|
||||||
|
[activeFolderId.value] = ids;
|
||||||
|
offspringIds.value = [..._offspringIds];
|
||||||
|
}
|
||||||
|
|
||||||
|
function initModulesCount(params: ReviewDetailCaseListQueryParams) {
|
||||||
|
getModuleCount(params);
|
||||||
|
}
|
||||||
|
|
||||||
|
const caseTableRef = ref<InstanceType<typeof CaseTable>>();
|
||||||
|
const associateDrawerVisible = ref(false);
|
||||||
|
const associateDrawerProject = ref('');
|
||||||
|
|
||||||
|
// 关联用例
|
||||||
|
async function writeAssociateCases(params: BaseAssociateCaseRequest & { reviewers: string[] }) {
|
||||||
|
try {
|
||||||
|
loading.value = true;
|
||||||
|
await associateReviewCase({
|
||||||
|
reviewId: route.query.id as string,
|
||||||
|
projectId: appStore.currentProjectId,
|
||||||
|
reviewers: params.reviewers,
|
||||||
|
baseAssociateCaseRequest: params,
|
||||||
|
});
|
||||||
|
Message.success(t('caseManagement.caseReview.associateSuccess'));
|
||||||
|
initDetail();
|
||||||
|
folderTreeRef.value?.initModules();
|
||||||
|
caseTableRef.value?.searchCase();
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(error);
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function editReview() {
|
||||||
|
router.push({
|
||||||
|
name: CaseManagementRouteEnum.CASE_MANAGEMENT_REVIEW_CREATE,
|
||||||
|
query: {
|
||||||
|
id: route.query.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function createCase() {
|
||||||
|
router.push({
|
||||||
|
name: CaseManagementRouteEnum.CASE_MANAGEMENT_CASE_DETAIL,
|
||||||
|
query: {
|
||||||
|
reviewId: route.query.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteModalVisible = ref(false);
|
||||||
|
function handleDeleteSuccess() {
|
||||||
|
router.back();
|
||||||
|
}
|
||||||
|
|
||||||
|
const fullActions = [
|
||||||
|
// {
|
||||||
|
// label: t('caseManagement.caseReview.archive'),
|
||||||
|
// eventTag: 'archive',
|
||||||
|
// icon: 'icon-icon-draft',
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// label: t('common.export'),
|
||||||
|
// eventTag: 'export',
|
||||||
|
// icon: 'icon-icon_upload_outlined',
|
||||||
|
// },
|
||||||
{
|
{
|
||||||
label: t('common.export'),
|
label: t('caseManagement.caseReview.quickCreate'),
|
||||||
eventTag: 'export',
|
eventTag: 'createCase',
|
||||||
icon: 'icon-icon_upload_outlined',
|
icon: 'icon-icon_add_outlined-1',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t('caseManagement.caseReview.createTestPlan'),
|
label: t('caseManagement.caseReview.createTestPlan'),
|
||||||
|
@ -161,52 +299,72 @@
|
||||||
danger: true,
|
danger: true,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
const moreAction = computed(() => {
|
||||||
onBeforeMount(() => {
|
if (reviewDetail.value.status === 'COMPLETED') {
|
||||||
if (reviewDetail.value.status === 2) {
|
return [...fullActions];
|
||||||
moreAction.value = [...fullActions];
|
|
||||||
} else if (reviewDetail.value.status === 3) {
|
|
||||||
moreAction.value = fullActions.filter((e) => e.eventTag === 'delete');
|
|
||||||
} else {
|
|
||||||
moreAction.value = fullActions.filter((e) => e.eventTag !== 'archive');
|
|
||||||
}
|
}
|
||||||
|
if (reviewDetail.value.status === 'ARCHIVED') {
|
||||||
|
return fullActions.filter((e) => e.eventTag === 'delete');
|
||||||
|
}
|
||||||
|
return fullActions.filter((e) => e.eventTag !== 'archive');
|
||||||
});
|
});
|
||||||
|
|
||||||
const showTab = ref(0);
|
function handleMoreSelect(item: ActionsItem) {
|
||||||
const tabList = ref([
|
switch (item.eventTag) {
|
||||||
{
|
case 'createCase':
|
||||||
key: 0,
|
createCase();
|
||||||
title: t('menu.caseManagement.featureCase'),
|
break;
|
||||||
},
|
case 'delete':
|
||||||
]);
|
deleteModalVisible.value = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const pagination = ref({
|
const followLoading = ref(false);
|
||||||
current: 1,
|
async function toggleFollowReview() {
|
||||||
pageSize: 10,
|
try {
|
||||||
total: 0,
|
followLoading.value = true;
|
||||||
|
await followReview({
|
||||||
|
userId: userStore.id || '',
|
||||||
|
caseReviewId: route.query.id as string,
|
||||||
|
});
|
||||||
|
Message.success(
|
||||||
|
reviewDetail.value.followFlag
|
||||||
|
? t('caseManagement.caseReview.unFollowSuccess')
|
||||||
|
: t('caseManagement.caseReview.followSuccess')
|
||||||
|
);
|
||||||
|
reviewDetail.value.followFlag = !reviewDetail.value.followFlag;
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(error);
|
||||||
|
} finally {
|
||||||
|
followLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyReview() {
|
||||||
|
router.push({
|
||||||
|
name: CaseManagementRouteEnum.CASE_MANAGEMENT_REVIEW_CREATE,
|
||||||
|
query: {
|
||||||
|
copyId: route.query.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleRefresh() {
|
||||||
|
initDetail();
|
||||||
|
}
|
||||||
|
|
||||||
|
const moduleTree = ref<ModuleTreeNode[]>([]);
|
||||||
|
function initModuleTree(tree: ModuleTreeNode[]) {
|
||||||
|
moduleTree.value = unref(tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
initDetail();
|
||||||
});
|
});
|
||||||
const detailIndex = ref(0);
|
|
||||||
const tableData = ref([]);
|
|
||||||
|
|
||||||
async function getDetailFunc() {
|
|
||||||
console.log('getDetailFunc');
|
|
||||||
}
|
|
||||||
|
|
||||||
async function pageChange() {
|
|
||||||
console.log('page');
|
|
||||||
}
|
|
||||||
|
|
||||||
function loaded(e: any) {
|
|
||||||
loading.value = false;
|
|
||||||
reviewDetail.value = e;
|
|
||||||
}
|
|
||||||
|
|
||||||
const folderTreeRef = ref<InstanceType<typeof CaseTree>>();
|
|
||||||
const activeFolderId = ref<string | number>('all');
|
|
||||||
|
|
||||||
function handleFolderNodeSelect(ids: (string | number)[]) {
|
|
||||||
[activeFolderId.value] = ids;
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<MsCard simple no-content-padding>
|
<MsCard simple no-content-padding>
|
||||||
<div class="flex items-center justify-between border-b border-[var(--color-text-n8)] p-[24px_24px_16px_24px]">
|
<div class="flex items-center justify-between border-b border-[var(--color-text-n8)] p-[24px_24px_16px_24px]">
|
||||||
<a-button type="primary" @click="goCreateReview">{{ t('caseManagement.caseReview.create') }}</a-button>
|
<a-button type="primary" @click="goCreateReview">{{ t('caseManagement.caseReview.create') }}</a-button>
|
||||||
<a-radio-group v-model:model-value="showType" type="button" class="file-show-type" @change="changeShowType">
|
<a-radio-group v-model:model-value="showType" type="button" class="file-show-type">
|
||||||
<a-radio value="all">{{ t('common.all') }}</a-radio>
|
<a-radio value="all">{{ t('common.all') }}</a-radio>
|
||||||
<a-radio value="reviewByMe">{{ t('caseManagement.caseReview.waitMyReview') }}</a-radio>
|
<a-radio value="reviewByMe">{{ t('caseManagement.caseReview.waitMyReview') }}</a-radio>
|
||||||
<a-radio value="createByMe">{{ t('caseManagement.caseReview.myCreate') }}</a-radio>
|
<a-radio value="createByMe">{{ t('caseManagement.caseReview.myCreate') }}</a-radio>
|
||||||
|
@ -12,11 +12,24 @@
|
||||||
<MsSplitBox>
|
<MsSplitBox>
|
||||||
<template #first>
|
<template #first>
|
||||||
<div class="px-[24px] py-[16px]">
|
<div class="px-[24px] py-[16px]">
|
||||||
<ModuleTree ref="folderTreeRef" @folder-node-select="handleFolderNodeSelect" @init="initModuleTree" />
|
<ModuleTree
|
||||||
|
ref="folderTreeRef"
|
||||||
|
:show-type="showType"
|
||||||
|
:modules-count="modulesCount"
|
||||||
|
@folder-node-select="handleFolderNodeSelect"
|
||||||
|
@init="initModuleTree"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #second>
|
<template #second>
|
||||||
<ReviewTable :active-folder="activeFolderId" :module-tree="moduleTree" @go-create="goCreateReview" />
|
<ReviewTable
|
||||||
|
:active-folder="activeFolderId"
|
||||||
|
:module-tree="moduleTree"
|
||||||
|
:show-type="showType"
|
||||||
|
:offspring-ids="offspringIds"
|
||||||
|
@go-create="goCreateReview"
|
||||||
|
@init="initModuleCount"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
</MsSplitBox>
|
</MsSplitBox>
|
||||||
</div>
|
</div>
|
||||||
|
@ -34,8 +47,10 @@
|
||||||
import ModuleTree from './components/index/moduleTree.vue';
|
import ModuleTree from './components/index/moduleTree.vue';
|
||||||
import ReviewTable from './components/index/reviewTable.vue';
|
import ReviewTable from './components/index/reviewTable.vue';
|
||||||
|
|
||||||
|
import { reviewModuleCount } from '@/api/modules/case-management/caseReview';
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
|
|
||||||
|
import { ReviewListQueryParams } from '@/models/caseManagement/caseReview';
|
||||||
import type { ModuleTreeNode } from '@/models/projectManagement/file';
|
import type { ModuleTreeNode } from '@/models/projectManagement/file';
|
||||||
import { CaseManagementRouteEnum } from '@/enums/routeEnum';
|
import { CaseManagementRouteEnum } from '@/enums/routeEnum';
|
||||||
|
|
||||||
|
@ -46,20 +61,29 @@
|
||||||
|
|
||||||
const showType = ref<ShowType>('all');
|
const showType = ref<ShowType>('all');
|
||||||
|
|
||||||
function changeShowType(val: string | number | boolean) {
|
|
||||||
console.log('changeShowType', val);
|
|
||||||
}
|
|
||||||
|
|
||||||
const folderTreeRef = ref<InstanceType<typeof ModuleTree>>();
|
const folderTreeRef = ref<InstanceType<typeof ModuleTree>>();
|
||||||
const activeFolderId = ref<string | number>('all');
|
const activeFolderId = ref<string>('all');
|
||||||
|
const offspringIds = ref<string[]>([]);
|
||||||
const moduleTree = ref<ModuleTreeNode[]>([]);
|
const moduleTree = ref<ModuleTreeNode[]>([]);
|
||||||
|
const modulesCount = ref<Record<string, number>>({});
|
||||||
|
|
||||||
function initModuleTree(tree: ModuleTreeNode[]) {
|
function initModuleTree(tree: ModuleTreeNode[]) {
|
||||||
moduleTree.value = unref(tree);
|
moduleTree.value = unref(tree);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleFolderNodeSelect(ids: (string | number)[]) {
|
function handleFolderNodeSelect(ids: string[], _offspringIds: string[]) {
|
||||||
[activeFolderId.value] = ids;
|
[activeFolderId.value] = ids;
|
||||||
|
offspringIds.value = [..._offspringIds];
|
||||||
|
}
|
||||||
|
|
||||||
|
async function initModuleCount(params: ReviewListQueryParams) {
|
||||||
|
try {
|
||||||
|
const res = await reviewModuleCount(params);
|
||||||
|
modulesCount.value = res;
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function goCreateReview() {
|
function goCreateReview() {
|
||||||
|
|
|
@ -65,7 +65,7 @@ export default {
|
||||||
'caseManagement.caseReview.reviewNameRequired': 'Review name cannot be empty',
|
'caseManagement.caseReview.reviewNameRequired': 'Review name cannot be empty',
|
||||||
'caseManagement.caseReview.descPlaceholder': 'Please describe this review',
|
'caseManagement.caseReview.descPlaceholder': 'Please describe this review',
|
||||||
'caseManagement.caseReview.belongModule': 'Belonging module',
|
'caseManagement.caseReview.belongModule': 'Belonging module',
|
||||||
'caseManagement.caseReview.belongModulePlaceholder': 'Please select the module to which the use case belongs',
|
'caseManagement.caseReview.belongModulePlaceholder': 'Please select the module to which the review belongs',
|
||||||
'caseManagement.caseReview.reviewerPlaceholder': 'Please select a reviewer',
|
'caseManagement.caseReview.reviewerPlaceholder': 'Please select a reviewer',
|
||||||
'caseManagement.caseReview.defaultReviewer': 'Default reviewer',
|
'caseManagement.caseReview.defaultReviewer': 'Default reviewer',
|
||||||
'caseManagement.caseReview.defaultReviewerRequired': 'The default reviewer cannot be empty',
|
'caseManagement.caseReview.defaultReviewerRequired': 'The default reviewer cannot be empty',
|
||||||
|
|
|
@ -60,7 +60,7 @@ export default {
|
||||||
'caseManagement.caseReview.reviewNameRequired': '评审名称不能为空',
|
'caseManagement.caseReview.reviewNameRequired': '评审名称不能为空',
|
||||||
'caseManagement.caseReview.descPlaceholder': '请对该评审进行描述',
|
'caseManagement.caseReview.descPlaceholder': '请对该评审进行描述',
|
||||||
'caseManagement.caseReview.belongModule': '所属模块',
|
'caseManagement.caseReview.belongModule': '所属模块',
|
||||||
'caseManagement.caseReview.belongModulePlaceholder': '请选择该用例所属模块',
|
'caseManagement.caseReview.belongModulePlaceholder': '请选择该评审所属模块',
|
||||||
'caseManagement.caseReview.reviewerPlaceholder': '请选择评审人',
|
'caseManagement.caseReview.reviewerPlaceholder': '请选择评审人',
|
||||||
'caseManagement.caseReview.defaultReviewer': '默认评审人',
|
'caseManagement.caseReview.defaultReviewer': '默认评审人',
|
||||||
'caseManagement.caseReview.defaultReviewerRequired': '默认评审人',
|
'caseManagement.caseReview.defaultReviewerRequired': '默认评审人',
|
||||||
|
@ -122,4 +122,13 @@ export default {
|
||||||
'caseManagement.caseReview.crateCase': '创建用例',
|
'caseManagement.caseReview.crateCase': '创建用例',
|
||||||
'caseManagement.caseReview.demandCases': '需求关联列表',
|
'caseManagement.caseReview.demandCases': '需求关联列表',
|
||||||
'caseManagement.caseReview.demandSearchPlaceholder': '通过名称搜索',
|
'caseManagement.caseReview.demandSearchPlaceholder': '通过名称搜索',
|
||||||
|
'caseManagement.caseReview.quickCreate': '快捷创建',
|
||||||
|
'caseManagement.caseReview.followSuccess': '关注成功',
|
||||||
|
'caseManagement.caseReview.unFollowSuccess': '取消关注成功',
|
||||||
|
'caseManagement.caseReview.disassociateSuccess': '取消关联成功',
|
||||||
|
'caseManagement.caseReview.startTime': '开始时间',
|
||||||
|
'caseManagement.caseReview.endTime': '结束时间',
|
||||||
|
'caseManagement.caseReview.associateSuccess': '关联成功',
|
||||||
|
'caseManagement.caseReview.reviewSuccess': '评审成功',
|
||||||
|
'caseManagement.caseReview.updateCase': '更新用例',
|
||||||
};
|
};
|
||||||
|
|
|
@ -68,6 +68,7 @@
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onBeforeMount, ref, watch } from 'vue';
|
import { computed, onBeforeMount, ref, watch } from 'vue';
|
||||||
|
import { useVModel } from '@vueuse/core';
|
||||||
import { Message } from '@arco-design/web-vue';
|
import { Message } from '@arco-design/web-vue';
|
||||||
|
|
||||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||||
|
@ -88,7 +89,7 @@
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
isExpandAll: boolean;
|
isExpandAll: boolean;
|
||||||
activeFolder?: string; // 当前选中的文件夹,弹窗模式下需要使用
|
activeFolder?: string; // 当前选中的文件夹,弹窗模式下需要使用
|
||||||
selectedKeys?: Array<string | number>; // 选中的节点 key
|
selectedKeys: Array<string | number>; // 选中的节点 key
|
||||||
isModal?: boolean; // 是否是弹窗模式
|
isModal?: boolean; // 是否是弹窗模式
|
||||||
modulesCount?: Record<string, number>; // 模块数量统计对象
|
modulesCount?: Record<string, number>; // 模块数量统计对象
|
||||||
showType?: string; // 显示类型
|
showType?: string; // 显示类型
|
||||||
|
@ -131,21 +132,7 @@
|
||||||
];
|
];
|
||||||
const renamePopVisible = ref(false);
|
const renamePopVisible = ref(false);
|
||||||
|
|
||||||
const selectedKeys = ref(props.selectedKeys || []);
|
const selectedKeys = useVModel(props, 'selectedKeys', emit);
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.selectedKeys,
|
|
||||||
(val) => {
|
|
||||||
selectedKeys.value = val || [];
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => selectedKeys.value,
|
|
||||||
(val) => {
|
|
||||||
emit('update:selectedKeys', val);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 初始化模块树
|
* 初始化模块树
|
||||||
|
|
|
@ -11,7 +11,6 @@
|
||||||
allow-clear
|
allow-clear
|
||||||
class="mr-[8px] w-[240px]"
|
class="mr-[8px] w-[240px]"
|
||||||
:prefix="t('project.messageManagement.robot')"
|
:prefix="t('project.messageManagement.robot')"
|
||||||
value-key="id"
|
|
||||||
:multiple="true"
|
:multiple="true"
|
||||||
:has-all-select="true"
|
:has-all-select="true"
|
||||||
:default-all-select="true"
|
:default-all-select="true"
|
||||||
|
@ -77,6 +76,7 @@
|
||||||
label: (val as Record<string, any>).name,
|
label: (val as Record<string, any>).name,
|
||||||
value: val,
|
value: val,
|
||||||
})"
|
})"
|
||||||
|
:object-value="true"
|
||||||
@remove="changeMessageReceivers(false, record, dataIndex as string)"
|
@remove="changeMessageReceivers(false, record, dataIndex as string)"
|
||||||
@popup-visible-change="changeMessageReceivers($event, record, dataIndex as string)"
|
@popup-visible-change="changeMessageReceivers($event, record, dataIndex as string)"
|
||||||
/>
|
/>
|
||||||
|
@ -148,7 +148,7 @@
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
const robotFilters = ref<RobotItem[]>([]);
|
const robotFilters = ref<string[]>([]);
|
||||||
const robotOptions = ref<(SelectOptionData & RobotItem)[]>([]);
|
const robotOptions = ref<(SelectOptionData & RobotItem)[]>([]);
|
||||||
const fullRef = ref<HTMLElement | null>();
|
const fullRef = ref<HTMLElement | null>();
|
||||||
|
|
||||||
|
@ -193,7 +193,7 @@
|
||||||
}
|
}
|
||||||
const tempArr = [...staticColumns];
|
const tempArr = [...staticColumns];
|
||||||
for (let i = 0; i < robotFilters.value.length; i++) {
|
for (let i = 0; i < robotFilters.value.length; i++) {
|
||||||
const robotId = robotFilters.value[i].id;
|
const robotId = robotFilters.value[i];
|
||||||
tempArr.push({
|
tempArr.push({
|
||||||
title: robotOptions.value.find((e) => e.id === robotId)?.label,
|
title: robotOptions.value.find((e) => e.id === robotId)?.label,
|
||||||
dataIndex: robotId,
|
dataIndex: robotId,
|
||||||
|
@ -325,6 +325,7 @@
|
||||||
.filter((e) => e.enable)
|
.filter((e) => e.enable)
|
||||||
.map((e) => ({
|
.map((e) => ({
|
||||||
label: e.name,
|
label: e.name,
|
||||||
|
value: e.id,
|
||||||
...e,
|
...e,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
|
@ -568,4 +568,3 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@/components/business/ms-select/index
|
|
||||||
|
|
Loading…
Reference in New Issue