feat(个人中心): 个人中心部分接口&组件调整&用例评审部分接口

This commit is contained in:
baiqi 2023-12-06 17:03:33 +08:00 committed by Craftsman
parent 6e57a9df97
commit 8a56091639
22 changed files with 603 additions and 195 deletions

View File

@ -1,110 +1,109 @@
// import MSR from '@/api/http/index';
import MSR from '@/api/http/index';
import {
AddReviewModuleUrl,
AddReviewUrl,
AssociateReviewUrl,
CopyReviewUrl,
DeleteReviewModuleUrl,
EditReviewUrl,
FollowReviewUrl,
GetReviewDetailUrl,
GetReviewListUrl,
GetReviewModulesUrl,
GetReviewUsersUrl,
MoveReviewModuleUrl,
MoveReviewUrl,
SortReviewUrl,
UpdateReviewModuleUrl,
} from '@/api/requrls/case-management/caseReview';
export const getCaseList = () => {
// return MSR.post<CommonList<FileItem>>({ url: FilePageUrl, data });
return Promise.resolve({
list: [
{
id: 'ded3d43',
name: '测试评审1',
creator: '张三',
reviewer: '李四',
module: '模块1',
status: 0, // 未开始、进行中、已完成、已归档
result: 0, // 通过、不通过、评审中
caseCount: 100,
passCount: 0,
failCount: 10,
reviewCount: 20,
reviewingCount: 25,
tags: ['标签1', '标签2'],
type: 'single',
desc: 'douifd9304',
cycle: [1700200794229, 1700200994229],
},
{
id: 'g545hj4',
name: '测试评审2',
creator: '张三',
reviewer: '李四',
module: '模块1',
status: 1, // 未开始、进行中、已完成、已归档
result: 1, // 通过、不通过、评审中
caseCount: 105,
passCount: 50,
failCount: 10,
reviewCount: 20,
reviewingCount: 25,
tags: ['标签1', '标签2'],
type: 'single',
desc: 'douifd9304',
cycle: [1700200794229, 1700200994229],
},
{
id: 'hj65b54',
name: '测试评审3',
creator: '张三',
reviewer: '李四',
module: '模块1',
status: 2, // 未开始、进行中、已完成、已归档
result: 2, // 通过、不通过、评审中
caseCount: 125,
passCount: 70,
failCount: 10,
reviewCount: 20,
reviewingCount: 25,
passRate: '80%',
tags: ['标签1', '标签2'],
type: 'single',
desc: 'douifd9304',
cycle: [1700200794229, 1700200994229],
},
{
id: 'wefwefw',
name: '测试评审4',
creator: '张三',
reviewer: '李四',
module: '模块1',
status: 3, // 未开始、进行中、已完成、已归档
result: 3, // 通过、不通过、评审中
caseCount: 130,
passCount: 70,
failCount: 10,
reviewCount: 0,
reviewingCount: 50,
passRate: '80%',
tags: ['标签1', '标签2'],
type: 'single',
desc: 'douifd9304',
cycle: [1700200794229, 1700200994229],
},
{
id: 'g4ggtrgrtg',
name: '测试评审5',
creator: '张三',
reviewer: '李四',
module: '模块1',
status: 3, // 未开始、进行中、已完成、已归档
result: 4, // 通过、不通过、评审中
caseCount: 130,
passCount: 70,
failCount: 10,
reviewCount: 0,
reviewingCount: 50,
passRate: '80%',
tags: ['标签1', '标签2'],
type: 'single',
desc: 'douifd9304',
cycle: [1700200794229, 1700200994229],
},
],
current: 1,
pageSize: 10,
total: 2,
});
import {
AssociateReviewCaseParams,
BatchMoveReviewParams,
FollowReviewParams,
Review,
ReviewItem,
ReviewListQueryParams,
ReviewModule,
ReviewModuleItem,
ReviewUserItem,
SortReviewParams,
UpdateReviewModuleParams,
UpdateReviewParams,
} from '@/models/caseManagement/caseReview';
import { CommonList, MoveModules } from '@/models/common';
// 新增评审模块
export const addReviewModule = (data: ReviewModule) => {
return MSR.post({ url: AddReviewModuleUrl, data });
};
export const getCaseDetail = () => {
// return MSR.post<CommonList<FileItem>>({ url: FilePageUrl, data });
return new Promise((resolve) => {});
// 更新评审模块
export const updateReviewModule = (data: UpdateReviewModuleParams) => {
return MSR.post({ url: UpdateReviewModuleUrl, data });
};
// 移动评审模块
export const moveReviewModule = (data: MoveModules) => {
return MSR.post({ url: MoveReviewModuleUrl, data });
};
// 获取评审模块树
export const getReviewModules = (projectId: string) => {
return MSR.get<ReviewModuleItem[]>({ url: GetReviewModulesUrl, params: projectId });
};
// 删除评审模块
export const deleteReviewModule = (id: string) => {
return MSR.get({ url: DeleteReviewModuleUrl, params: id });
};
// 新增评审
export const addReview = (data: Review) => {
return MSR.post({ url: AddReviewUrl, data });
};
// 关联用例
export const associateReviewCase = (data: AssociateReviewCaseParams) => {
return MSR.post({ url: AssociateReviewUrl, data });
};
// 复制评审
export const copyReview = (data: Review) => {
return MSR.post({ url: CopyReviewUrl, data });
};
// 编辑评审
export const editReview = (data: UpdateReviewParams) => {
return MSR.post({ url: EditReviewUrl, data });
};
// 关注/取消关注评审
export const followReview = (data: FollowReviewParams) => {
return MSR.post({ url: FollowReviewUrl, data });
};
// 移动评审
export const moveReview = (data: BatchMoveReviewParams) => {
return MSR.post({ url: MoveReviewUrl, data });
};
// 评审拖拽排序
export const sortReview = (data: SortReviewParams) => {
return MSR.post({ url: SortReviewUrl, data });
};
// 获取评审列表
export const getReviewList = (data: ReviewListQueryParams) => {
return MSR.post<CommonList<ReviewItem>>({ url: GetReviewListUrl, data });
};
// 获取评审详情
export const getReviewDetail = (id: string) => {
return MSR.get<ReviewItem>({ url: GetReviewDetailUrl, params: id });
};
// 获取评审人员列表
export const getReviewUsers = (projectId: string, keyword: string) => {
return MSR.get<ReviewUserItem[]>({ url: `${GetReviewUsersUrl}/${projectId}`, params: { keyword } });
};

View File

@ -49,11 +49,10 @@ import type {
DeleteCaseType,
DemandItem,
ModulesTreeType,
MoveModules,
OperationFile,
UpdateModule,
} from '@/models/caseManagement/featureCase';
import type { CommonList, TableQueryParams } from '@/models/common';
import type { CommonList, MoveModules, TableQueryParams } from '@/models/common';
// 获取模块树
export function getCaseModuleTree(projectId: string) {
return MSR.get<ModulesTreeType[]>({ url: `${GetCaseModuleTreeUrl}/${projectId}` });

View File

@ -8,6 +8,7 @@ import {
EnableAPIKEYUrl,
EnableLocalConfigUrl,
GetAPIKEYListUrl,
GetInfoUrl,
GetLocalConfigUrl,
GetMenuListUrl,
GetPublicKeyUrl,
@ -15,7 +16,9 @@ import {
LoginUrl,
LogoutUrl,
UpdateAPIKEYUrl,
UpdateInfoUrl,
UpdateLocalConfigUrl,
UpdatePswUrl,
ValidAPIKEYUrl,
ValidLocalConfigUrl,
} from '@/api/requrls/user';
@ -26,8 +29,11 @@ import type {
LocalConfig,
LoginData,
LoginRes,
PersonalInfo,
UpdateAPIKEYParams,
UpdateBaseInfo,
UpdateLocalConfigParams,
UpdatePswParams,
} from '@/models/user';
import type { RouteRecordNormalized } from 'vue-router';
@ -116,3 +122,18 @@ export function deleteAPIKEY(id: string) {
export function addAPIKEY() {
return MSR.get({ url: AddAPIKEYUrl });
}
// 个人信息-获取基本信息
export function getBaseInfo(id: string) {
return MSR.get<PersonalInfo>({ url: GetInfoUrl, params: id });
}
// 个人信息-修改基本信息
export function updateBaseInfo(data: UpdateBaseInfo) {
return MSR.post({ url: UpdateInfoUrl, data });
}
// 个人信息-修改密码
export function updatePsw(data: UpdatePswParams) {
return MSR.post({ url: UpdatePswUrl, data });
}

View File

@ -0,0 +1,15 @@
export const GetReviewListUrl = '/case/review/page'; // 获取评审列表
export const EditReviewUrl = '/case/review/edit'; // 编辑评审
export const SortReviewUrl = '/case/review/edit/pos'; // 评审拖拽排序
export const FollowReviewUrl = '/case/review/edit/follower'; // 关注/取消关注评审
export const CopyReviewUrl = '/case/review/copy'; // 复制评审
export const MoveReviewUrl = '/case/review/batch/move'; // 移动评审
export const AssociateReviewUrl = '/case/review/associate'; // 关联用例
export const AddReviewUrl = '/case/review/add'; // 新增评审
export const GetReviewUsersUrl = '/case/review/user-option'; // 获取评审人员列表
export const GetReviewDetailUrl = '/case/review/detail'; // 获取评审详情
export const UpdateReviewModuleUrl = '/case/review/module/update'; // 更新评审模块
export const MoveReviewModuleUrl = '/case/review/module/move'; // 移动评审模块
export const AddReviewModuleUrl = '/case/review/module/add'; // 新增评审模块
export const GetReviewModulesUrl = '/case/review/module/tree'; // 获取评审模块树
export const DeleteReviewModuleUrl = '/case/review/module/delete'; // 删除评审模块

View File

@ -16,3 +16,6 @@ export const EnableAPIKEYUrl = '/user/api/key/enable'; // 个人设置-开启 AP
export const DisableAPIKEYUrl = '/user/api/key/disable'; // 个人设置-关闭 APIKEY
export const DeleteAPIKEYUrl = '/user/api/key/delete'; // 个人设置-删除 APIKEY
export const AddAPIKEYUrl = '/user/api/key/add'; // 个人设置-生成 APIKEY
export const UpdatePswUrl = '/personal/update-password'; // 个人信息-修改密码
export const UpdateInfoUrl = '/personal/update-info'; // 个人信息-修改信息
export const GetInfoUrl = '/personal/get'; // 个人信息-获取信息

View File

@ -227,6 +227,11 @@
color: var(--color-text-brand);
}
}
.arco-select-view-disabled,
.arco-input-disabled {
border-color: var(--color-text-n8) !important;
background-color: var(--color-text-n8) !important;
}
.arco-select,
.arco-input-tag {
.arco-icon {

View File

@ -81,25 +81,24 @@
export type CascaderModelValue = string | number | Record<string, any> | (string | number | Record<string, any>)[];
const props = withDefaults(
defineProps<{
modelValue: CascaderModelValue;
options: CascaderOption[];
mode?: 'MS' | 'native'; // MS;使 arco-design cascader getOptionComputedStyle
prefix?: string; //
levelTop?: string[]; //
level?: string; //
multiple?: boolean; //
strictly?: boolean; //
virtualListProps?: VirtualListProps; //
panelWidth?: number; // 150px
placeholder?: string;
loading?: boolean;
}>(),
{
mode: 'MS',
}
);
export interface MsCascaderProps {
modelValue: CascaderModelValue;
options: CascaderOption[];
mode?: 'MS' | 'native'; // MS;使 arco-design cascader getOptionComputedStyle
prefix?: string; //
levelTop?: string[]; //
level?: string; //
multiple?: boolean; //
strictly?: boolean; //
virtualListProps?: VirtualListProps; //
panelWidth?: number; // 150px
placeholder?: string;
loading?: boolean;
}
const props = withDefaults(defineProps<MsCascaderProps>(), {
mode: 'MS',
});
const emit = defineEmits(['update:modelValue', 'update:level']);
const innerValue = ref<CascaderModelValue>([]);

View File

@ -74,6 +74,10 @@
/>
</div>
</div>
<div v-if="apiKeyList.length === 0" class="col-span-2 flex w-full items-center justify-center p-[44px]">
{{ t('ms.personal.nodata') }}
<MsButton type="text" class="ml-[8px]" @click="newApiKey">{{ t('common.new') }}</MsButton>
</div>
</a-spin>
</div>
<a-modal
@ -127,6 +131,7 @@
import { FormInstance, Message } from '@arco-design/web-vue';
import dayjs from 'dayjs';
import MsButton from '@/components/pure/ms-button/index.vue';
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
import MsTableMoreAction from '@/components/pure/ms-table-more-action/index.vue';
import { ActionsItem } from '@/components/pure/ms-table-more-action/types';
@ -331,7 +336,7 @@
<style lang="less" scoped>
.api-list-content {
@apply grid flex-1 overflow-auto;
@apply grid overflow-auto;
.ms-scroll-bar();
gap: 16px;

View File

@ -26,6 +26,8 @@ export default {
'ms.personal.currentPsw': 'Current Password',
'ms.personal.newPsw': 'New Password',
'ms.personal.changePswTip': 'After changing the password, you need to use the new email to log in to the system',
'ms.personal.updatePswSuccess':
'The password has been modified successfully and will automatically log out in {count} seconds. Please log in with the new password.',
'ms.personal.apiKeyTip': 'After adding, you can access MeterSphere',
'ms.personal.expireTime': 'Expiration',
'ms.personal.expired': 'Expired',
@ -84,4 +86,5 @@ export default {
'ms.personal.azureTip':
'This information is the user token information for submitting defects through Azure Devops. If not filled in, the default information configured by the organization will be used.',
'ms.personal.azurePlaceholder': 'Please enter Personal Access Tokens',
'ms.personal.nodata': 'No data yet, please ',
};

View File

@ -25,6 +25,7 @@ export default {
'ms.personal.currentPsw': '当前密码',
'ms.personal.newPsw': '新密码',
'ms.personal.changePswTip': '修改密码后,需要使用新的邮箱登录系统',
'ms.personal.updatePswSuccess': '密码修改成功,将在 {count} 秒后自动退出,请使用新密码登录',
'ms.personal.apiKeyTip': '新增后,可访问 MeterSphere',
'ms.personal.expireTime': '过期时间',
'ms.personal.expired': '已到期',
@ -77,4 +78,5 @@ export default {
'ms.personal.zendaoTip': '该信息为通过禅道提交缺陷的的用户名、密码,若未填写,则使用组织配置的默认信息',
'ms.personal.azureTip': '该信息为通过Azure Devops提交缺陷的用户令牌信息若未填写则使用组织配置的默认信息',
'ms.personal.azurePlaceholder': '请输入 Personal Access Tokens',
'ms.personal.nodata': '暂无数据,请 ',
};

View File

@ -77,8 +77,16 @@
:disabled="!item.dataIndex"
:max-length="60"
/>
<MsTagsInput
v-else-if="item.type === FilterType.TAGS_INPUT"
v-model:model-value="item.value"
:disabled="!item.dataIndex"
allow-clear
unique-value
retain-input-value
/>
<a-input-number
v-if="item.type === FilterType.NUMBER"
v-else-if="item.type === FilterType.NUMBER"
v-model:model-value="item.value"
class="w-full"
allow-clear
@ -95,7 +103,14 @@
:disabled="!item.dataIndex"
:options="item.selectProps?.options || []"
v-bind="item.selectProps"
></MsSelect>
/>
<a-tree-select
v-else-if="item.type === FilterType.TREE_SELECT"
v-model:model-value="item.value"
:data="item.treeSelectData"
:disabled="!item.dataIndex"
v-bind="(item.treeSelectProps as any)"
/>
<a-date-picker
v-else-if="item.type === FilterType.DATE_PICKER && item.operator !== 'between'"
v-model:model-value="item.value"
@ -112,6 +127,13 @@
format="YYYY-MM-DD HH:mm"
:disabled="!item.dataIndex"
/>
<MsCascader
v-else-if="item.type === FilterType.CASCADER"
v-model:model-value="item.value"
:options="item.cascaderOptions || []"
:disabled="!item.dataIndex"
v-bind="item.cascaderProps"
/>
</a-form-item>
</div>
<div class="delete-btn" :class="{ 'delete-btn:disabled': idx === 0 }" @click="handleDeleteItem(idx)">
@ -143,6 +165,8 @@
<script lang="ts" setup>
import { FormInstance } from '@arco-design/web-vue';
import MsTagsInput from '@/components/pure/ms-tags-input/index.vue';
import MsCascader from '@/components/business/ms-cascader/index.vue';
import MsSelect from '@/components/business/ms-select';
import { useI18n } from '@/hooks/useI18n';
@ -166,10 +190,10 @@
(e: 'update:rowCount', value: number): void; // MsBaseTable
}>();
const isMutipleSelect = (dataIndex: string) => {
const isMultipleSelect = (dataIndex: string) => {
const tmpObj = props.configList.find((item) => item.dataIndex === dataIndex);
if (tmpObj) {
return tmpObj.selectProps?.multiple;
return tmpObj.selectProps?.multiple || tmpObj.type === FilterType.TAGS_INPUT;
}
return false;
};
@ -184,7 +208,7 @@
result = OPERATOR_MAP.date;
break;
case FilterType.SELECT:
result = isMutipleSelect(dataIndex) ? OPERATOR_MAP.array : OPERATOR_MAP.string;
result = isMultipleSelect(dataIndex) ? OPERATOR_MAP.array : OPERATOR_MAP.string;
break;
default:
result = OPERATOR_MAP.string;
@ -281,11 +305,8 @@
if (!tmpObj) {
return;
}
const { type, backendType } = tmpObj;
formModel.list[idx].operator = '';
formModel.list[idx].backendType = backendType;
formModel.list[idx].type = type;
formModel.list[idx].value = isMutipleSelect(dataIndex as string) ? [] : '';
formModel.list[idx] = { ...tmpObj };
formModel.list[idx].value = isMultipleSelect(dataIndex as string) ? [] : '';
emit('dataIndexChange', dataIndex as string);
};
@ -294,7 +315,7 @@
if (v === 'between') {
formModel.list[idx].value = [];
} else {
formModel.list[idx].value = isMutipleSelect(dataIndex) ? [] : '';
formModel.list[idx].value = isMultipleSelect(dataIndex) ? [] : '';
}
};

View File

@ -3,13 +3,13 @@
<slot name="left"></slot>
<div class="flex flex-row gap-[8px]">
<a-input-search
v-model="keyword"
v-model="innerKeyword"
size="small"
:placeholder="t('system.user.searchUser')"
:placeholder="props.searchPlaceholder"
class="w-[240px]"
allow-clear
@press-enter="emit('keywordSearch', keyword)"
@search="emit('keywordSearch', keyword)"
@press-enter="emit('keywordSearch', innerKeyword)"
@search="emit('keywordSearch', innerKeyword)"
></a-input-search>
<MsTag
:type="visible ? 'primary' : 'default'"
@ -45,6 +45,8 @@
</template>
<script setup lang="ts">
import { useVModel } from '@vueuse/core';
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
import MsTag from '../ms-tag/ms-tag.vue';
import FilterForm from './FilterForm.vue';
@ -53,22 +55,27 @@
import { FilterFormItem, FilterResult } from './type';
const { t } = useI18n();
const keyword = ref('');
const props = defineProps<{
rowCount: number;
filterConfigList: FilterFormItem[];
searchPlaceholder?: string;
keyword?: string;
}>();
const visible = ref(false);
const filterCount = ref(0);
const emit = defineEmits<{
(e: 'keywordSearch', value: string): void; // keyword
(e: 'update:keyword', value: string): void;
(e: 'keywordSearch', value: string | undefined): void; // innerKeyword
(e: 'advSearch', value: FilterResult): void; //
(e: 'dataIndexChange', value: string): void; //
}>();
const { t } = useI18n();
const innerKeyword = useVModel(props, 'keyword', emit);
const visible = ref(false);
const filterCount = ref(0);
const handleResetSearch = () => {
keyword.value = '';
innerKeyword.value = '';
emit('keywordSearch', '');
};

View File

@ -1,4 +1,8 @@
import { MsSearchSelectProps } from '@/components/business/ms-select';
import { MsCascaderProps } from '@/components/business/ms-cascader/index.vue';
import type { MsSearchSelectProps } from '@/components/business/ms-select';
import type { CascaderOption, TreeNodeData } from '@arco-design/web-vue';
import type { TreeSelectProps } from '@arco-design/web-vue/es/tree-select/interface';
/* eslint-disable no-shadow */
export enum BackEndEnum {
@ -12,6 +16,9 @@ export enum FilterType {
NUMBER = 'Number',
SELECT = 'Select',
DATE_PICKER = 'DatePicker',
CASCADER = 'Cascader',
TAGS_INPUT = 'TagsInput',
TREE_SELECT = 'TreeSelect',
}
export interface FilterFormItem {
@ -20,9 +27,12 @@ export interface FilterFormItem {
type: FilterType; // 类型Input,Select,DatePicker,RangePicker
value?: any; // 值 字符串 和 数组
operator?: string; // 运算符号
options?: any[]; // 下拉框的选项
cascaderOptions?: CascaderOption[]; // 级联选择的选项
backendType?: BackEndEnum; // 后端类型 string array time
selectProps?: Partial<MsSearchSelectProps>; // select的props, 参考 MsSelect
cascaderProps?: Partial<MsCascaderProps>; // cascader的props, 参考 MsCascader
treeSelectData?: TreeNodeData[];
treeSelectProps?: Partial<TreeSelectProps>;
}
export type AccordBelowType = 'all' | 'any';

View File

@ -0,0 +1,120 @@
import { BatchApiParams, TableQueryParams } from '@/models/common';
// 评审模块
export interface ReviewModule {
projectId: string;
name: string;
parentId: string;
}
// 更新评审模块入参
export interface UpdateReviewModuleParams {
id: string;
name: string;
}
// 评审模块列表项
export interface ReviewModuleItem {
id: string;
name: string;
type: string;
parentId: string;
children: ReviewModuleItem[];
count: number; // 模块内评审数量
}
// 评审类型
export type ReviewPassRule = 'SINGLE' | 'MULTIPLE';
// 评审
export interface Review {
projectId: string;
name: string;
moduleId: string;
reviewPassRule: ReviewPassRule; // 评审通过规则
startTime: number;
endTime: number;
tags: string[];
description: string;
reviewers: string[]; // 评审人员
caseIds: string[]; // 关联用例
}
// 更新评审入参
export interface UpdateReviewParams extends Omit<Review, 'caseIds'> {
id: string;
}
// 关联用例入参
export interface AssociateReviewCaseParams {
reviewId: string;
projectId: string;
caseIds: string[];
reviewers: string[];
}
// 关注/取消关注评审入参
export interface FollowReviewParams {
userId: string; // 用户id
caseReviewId: string;
}
// 批量操作评审参数
export interface BatchMoveReviewParams extends BatchApiParams {
projectId: string;
moveModuleId: string; // 移动到的评审模块id
moduleIds: string[];
}
// 评审拖拽排序类型
export type ReviewMoveMode = 'BEFORE' | 'AFTER' | 'APPEND';
// 评审拖拽排序入参
export interface SortReviewParams {
projectId: string;
targetId: string; // 目标评审id
moveMode: ReviewMoveMode;
moveId: string; // 被移动的评审id
}
// 文件列表查询参数
export interface ReviewListQueryParams extends TableQueryParams {
moduleIds: string[];
projectId: string;
}
export type ReviewStatus = 'PREPARED' | 'UNDERWAY' | 'COMPLETED' | 'ARCHIVED'; // 评审状态, PREPARED: 待开始, UNDERWAY: 进行中, COMPLETED: 已完成, ARCHIVED: 已归档
// 评审列表项
export interface ReviewItem {
id: string;
num: number;
name: string;
moduleId: string;
projectId: string;
status: ReviewStatus;
reviewPassRule: ReviewPassRule;
pos: number; // 自定义排序间隔5000
startTime: number;
endTime: number;
caseCount: number;
passRate: number;
tags: string;
description: string;
createTime: number;
createUser: string;
updateTime: number;
updateUser: string;
reviewers: string[];
passCount: number;
unPassCount: number;
reReviewedCount: number;
underReviewedCount: number;
reviewedCount: number;
followFlag: boolean; // 关注标识
}
// 评审人员列表项
export interface ReviewUserItem {
id: string;
name: string;
email: string;
password: string;
enable: boolean;
createTime: number;
updateTime: number;
language: string;
lastOrganizationId: string;
phone: string;
source: string;
lastProjectId: string;
createUser: string;
updateUser: string;
deleted: boolean;
}

View File

@ -21,12 +21,6 @@ export interface UpdateModule {
id: string;
name: string;
}
// 移动模块树
export interface MoveModules {
dragNodeId: string; // 被拖拽的节点
dropNodeId: string; // 放入的节点
dropPosition: number; // 放入的位置(取值:-1,0,1。 -1dropNodeId节点之前。 0:dropNodeId节点内。 1dropNodeId节点后
}
export interface customFieldsItem {
caseId?: string; // 用例id

View File

@ -42,3 +42,10 @@ export interface BatchApiParams {
selectAll: boolean; // 是否跨页全选,即选择当前筛选条件下的全部表格数据
condition: Record<string, any>; // 当前表格查询的筛选条件
}
// 移动模块树
export interface MoveModules {
dragNodeId: string; // 被拖拽的节点
dropNodeId: string; // 放入的节点
dropPosition: number; // 放入的位置(取值:-1,0,1。 -1dropNodeId节点之前。 0:dropNodeId节点内。 1dropNodeId节点后
}

View File

@ -76,3 +76,71 @@ export interface APIKEY {
expireTime: number;
description: string;
}
// 更新密码入参
export interface UpdatePswParams {
id: string;
oldPassword: string;
newPassword: string;
}
export interface Permission {
id: string;
roleId: string;
permissionId: string;
}
export interface Resource {
id: string;
name: string;
license: boolean;
}
export interface UserRolePermission {
resource: Resource;
permissions: Permission[];
type: string;
userRole: UserRole;
userRolePermissions: Permission[];
}
export interface UserRoleRelation {
id: string;
userId: string;
roleId: string;
sourceId: string;
organizationId: string;
createTime: number;
createUser: string;
}
// 个人信息
export interface PersonalInfo {
id: string;
name: string;
email: string;
password: string;
enable: boolean;
createTime: number;
updateTime: number;
language: string;
lastOrganizationId: string;
phone: string;
source: string;
lastProjectId: string;
createUser: string;
updateUser: string;
deleted: boolean;
userRoles: UserRole[];
userRoleRelations: UserRoleRelation[];
userRolePermissions: UserRolePermission[];
platformInfo: string;
seleniumServer: string;
apiServer: string;
avatar: string;
}
export interface UpdateBaseInfo {
id: string;
username: string;
phone: string;
email: string;
avatar: string;
}

View File

@ -229,7 +229,7 @@
import MsSelect from '@/components/business/ms-select';
import AssociateDrawer from '../create/associateDrawer.vue';
import { getCaseList } from '@/api/modules/case-management/caseReview';
import { getReviewList } from '@/api/modules/case-management/caseReview';
import { useI18n } from '@/hooks/useI18n';
import useModal from '@/hooks/useModal';
import useTableStore from '@/hooks/useTableStore';
@ -332,7 +332,7 @@
];
const tableStore = useTableStore();
tableStore.initColumn(TableKeyEnum.CASE_MANAGEMENT_REVIEW_CASE, columns, 'drawer');
const { propsRes, propsEvent, loadList, setLoadListParams, resetSelector } = useTable(getCaseList, {
const { propsRes, propsEvent, loadList, setLoadListParams, resetSelector } = useTable(getReviewList, {
scroll: { x: '100%' },
tableKey: TableKeyEnum.CASE_MANAGEMENT_REVIEW_CASE,
showSetting: true,

View File

@ -104,7 +104,7 @@
import type { MsTreeNodeData } from '@/components/business/ms-tree/types';
import popConfirm from './popConfirm.vue';
import { deleteModule, getModules, moveModule } from '@/api/modules/project-management/fileManagement';
import { deleteReviewModule, getReviewModules, moveReviewModule } from '@/api/modules/case-management/caseReview';
import { useI18n } from '@/hooks/useI18n';
import useModal from '@/hooks/useModal';
import useAppStore from '@/store/modules/app';
@ -191,7 +191,7 @@
async function initModules(isSetDefaultKey = false) {
try {
loading.value = true;
const res = await getModules(appStore.currentProjectId);
const res = await getReviewModules(appStore.currentProjectId);
folderTree.value = mapTree<ModuleTreeNode>(res, (e) => {
return {
...e,
@ -210,10 +210,7 @@
emit('folderNodeSelect', selectedKeys.value, offspringIds);
}
emit(
'init',
folderTree.value.map((e) => e.name)
);
emit('init', folderTree.value);
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
@ -238,7 +235,7 @@
maskClosable: false,
onBeforeOk: async () => {
try {
await deleteModule(node.id);
await deleteReviewModule(node.id);
Message.success(t('caseManagement.caseReview.deleteSuccess'));
initModules(selectedKeys.value[0] === node.id);
} catch (error) {
@ -306,7 +303,7 @@
) {
try {
loading.value = true;
await moveModule({
await moveReviewModule({
dragNodeId: dragNode.id as string,
dropNodeId: dropNode.id || '',
dropPosition,

View File

@ -1,26 +1,20 @@
<template>
<div class="px-[24px] py-[16px]">
<div class="mb-[16px] flex items-center justify-between">
<div class="flex items-center">
<div class="mr-[4px] text-[var(--color-text-1)]">全部评审</div>
<div class="text-[var(--color-text-4)]">(2)</div>
</div>
<div class="flex items-center gap-[8px]">
<a-input-search
v-model="keyword"
:placeholder="t('caseManagement.caseReview.searchPlaceholder')"
allow-clear
@press-enter="searchReview"
@search="searchReview"
/>
<a-button type="outline" class="arco-btn-outline--secondary px-[8px]">
<MsIcon type="icon-icon-filter" class="mr-[4px] text-[var(--color-text-4)]" />
<div class="text-[var(--color-text-4)]">{{ t('common.filter') }}</div>
</a-button>
<a-button type="outline" class="arco-btn-outline--secondary p-[10px]">
<icon-refresh class="text-[var(--color-text-4)]" />
</a-button>
</div>
<div class="mb-[16px]">
<MsAdvanceFilter
v-model:keyword="keyword"
:filter-config-list="filterConfigList"
:row-count="filterRowCount"
:search-placeholder="t('caseManagement.caseReview.searchPlaceholder')"
@keyword-search="searchReview"
>
<template #left>
<div class="flex items-center">
<div class="mr-[4px] text-[var(--color-text-1)]">{{ t('caseManagement.caseReview.allReviews') }}</div>
<div class="text-[var(--color-text-4)]">({{ propsRes.msPagination?.total }})</div>
</div>
</template>
</MsAdvanceFilter>
</div>
<ms-base-table
v-bind="propsRes"
@ -187,12 +181,14 @@
</template>
<script setup lang="ts">
import { onBeforeMount } from 'vue';
import { useRouter } from 'vue-router';
import { Message } from '@arco-design/web-vue';
import dayjs from 'dayjs';
import { MsAdvanceFilter } from '@/components/pure/ms-advance-filter';
import { FilterFormItem, FilterType } from '@/components/pure/ms-advance-filter/type';
import MsButton from '@/components/pure/ms-button/index.vue';
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
import type { BatchActionParams, BatchActionQueryParams, MsTableColumn } from '@/components/pure/ms-table/type';
import useTable from '@/components/pure/ms-table/useTable';
@ -202,18 +198,22 @@
import statusTag from '../statusTag.vue';
import ModuleTree from './moduleTree.vue';
import { getCaseList } from '@/api/modules/case-management/caseReview';
import { getReviewList, getReviewUsers } from '@/api/modules/case-management/caseReview';
import { useI18n } from '@/hooks/useI18n';
import useModal from '@/hooks/useModal';
import useTableStore from '@/hooks/useTableStore';
import useAppStore from '@/store/modules/app';
import type { ModuleTreeNode } from '@/models/projectManagement/file';
import { CaseManagementRouteEnum } from '@/enums/routeEnum';
import { TableKeyEnum } from '@/enums/tableEnum';
const props = defineProps<{
activeFolder: string | number;
moduleTree: ModuleTreeNode[];
}>();
const appStore = useAppStore();
const router = useRouter();
const { t } = useI18n();
const { openModal } = useModal();
@ -249,6 +249,131 @@
multi: 'caseManagement.caseReview.multi',
};
const filterRowCount = ref(0);
const filterConfigList = ref<FilterFormItem[]>([]);
onBeforeMount(async () => {
try {
const res = await getReviewUsers(appStore.currentProjectId, keyword.value);
const userOptions = res.map((e) => ({ label: e.name, value: e.id }));
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(statusMap[0].label),
value: 'PREPARED',
},
{
label: t(statusMap[1].label),
value: 'UNDERWAY',
},
{
label: t(statusMap[2].label),
value: 'COMPLETED',
},
{
label: t(statusMap[3].label),
value: 'ARCHIVED',
},
],
},
},
{
title: 'caseManagement.caseReview.passRate',
dataIndex: 'passRate',
type: FilterType.NUMBER,
},
{
title: 'caseManagement.caseReview.type',
dataIndex: 'type',
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: 'reviewer',
type: FilterType.SELECT,
selectProps: {
mode: 'static',
options: userOptions,
},
},
{
title: 'caseManagement.caseReview.creator',
dataIndex: 'creator',
type: FilterType.SELECT,
selectProps: {
mode: 'static',
options: userOptions,
},
},
{
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: 'desc',
type: FilterType.INPUT,
},
{
title: 'caseManagement.caseReview.cycle',
dataIndex: 'cycle',
type: FilterType.DATE_PICKER,
},
];
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
}
});
const columns: MsTableColumn = [
{
title: 'ID',
@ -335,7 +460,7 @@
const tableStore = useTableStore();
tableStore.initColumn(TableKeyEnum.CASE_MANAGEMENT_REVIEW, columns, 'drawer');
const { propsRes, propsEvent, loadList, setLoadListParams, resetSelector } = useTable(
getCaseList,
getReviewList,
{
tableKey: TableKeyEnum.CASE_MANAGEMENT_REVIEW,
showSetting: true,
@ -365,12 +490,13 @@
function searchReview() {
setLoadListParams({
keyword: keyword.value,
projectId: appStore.currentProjectId,
});
loadList();
}
onBeforeMount(() => {
loadList();
searchReview();
});
const tableSelected = ref<(string | number)[]>([]);

View File

@ -12,11 +12,11 @@
<MsSplitBox>
<template #left>
<div class="px-[24px] py-[16px]">
<ModuleTree ref="folderTreeRef" @folder-node-select="handleFolderNodeSelect" />
<ModuleTree ref="folderTreeRef" @folder-node-select="handleFolderNodeSelect" @init="initModuleTree" />
</div>
</template>
<template #right>
<ReviewTable :active-folder="activeFolderId" />
<ReviewTable :active-folder="activeFolderId" :module-tree="moduleTree" />
</template>
</MsSplitBox>
</div>
@ -36,6 +36,7 @@
import { useI18n } from '@/hooks/useI18n';
import type { ModuleTreeNode } from '@/models/projectManagement/file';
import { CaseManagementRouteEnum } from '@/enums/routeEnum';
const router = useRouter();
@ -51,6 +52,11 @@
const folderTreeRef = ref<InstanceType<typeof ModuleTree>>();
const activeFolderId = ref<string | number>('all');
const moduleTree = ref<ModuleTreeNode[]>([]);
function initModuleTree(tree: ModuleTreeNode[]) {
moduleTree.value = unref(tree);
}
function handleFolderNodeSelect(ids: (string | number)[]) {
[activeFolderId.value] = ids;

View File

@ -539,7 +539,7 @@
];
const tableStore = useTableStore();
tableStore.initColumn(TableKeyEnum.FILE_MANAGEMENT_FILE, columns, 'drawer');
const { propsRes, propsEvent, loadList, setLoadListParams, resetSelector } = useTable(
const { propsRes, propsEvent, loadList, setLoadListParams, resetSelector, resetPagination } = useTable(
getFileList,
{
tableKey: TableKeyEnum.FILE_MANAGEMENT_FILE,
@ -842,6 +842,7 @@
() => props.activeFolder,
() => {
keyword.value = '';
resetPagination();
searchList();
resetSelector();
},