feat(用例评审): 用例评审部分接口&个人信息头像

This commit is contained in:
baiqi 2023-12-08 17:37:39 +08:00 committed by rubylliu
parent 07095ecd5a
commit 2c166fa1d7
11 changed files with 110 additions and 32 deletions

View File

@ -170,7 +170,7 @@ const transform: AxiosTransform = {
throw new Error(e as unknown as string); throw new Error(e as unknown as string);
} }
checkStatus(error?.response?.status, msg, errorMessageMode); checkStatus(error?.response?.status, msg, errorMessageMode);
return Promise.reject(error?.response?.data?.message); return Promise.reject(error?.response?.data?.message || error);
}, },
}; };

View File

@ -3,6 +3,7 @@
import { RouteRecordRaw, useRoute, useRouter } from 'vue-router'; import { RouteRecordRaw, useRoute, useRouter } from 'vue-router';
import { Message } from '@arco-design/web-vue'; import { Message } from '@arco-design/web-vue';
import MsAvatar from '@/components/pure/ms-avatar/index.vue';
import MsIcon from '@/components/pure/ms-icon-font/index.vue'; import MsIcon from '@/components/pure/ms-icon-font/index.vue';
import MsTag from '@/components/pure/ms-tag/ms-tag.vue'; import MsTag from '@/components/pure/ms-tag/ms-tag.vue';
import MsPersonInfoDrawer from '@/components/business/ms-personal-drawer/index.vue'; import MsPersonInfoDrawer from '@/components/business/ms-personal-drawer/index.vue';
@ -288,8 +289,8 @@
}} }}
> >
<a-menu-item class="flex items-center justify-between" key="personalInfo"> <a-menu-item class="flex items-center justify-between" key="personalInfo">
<div class="hover:!bg-transparent"> <div class="flex items-center gap-[8px] hover:!bg-transparent">
<MsIcon type="icon-icon_that_person" /> <MsAvatar avatar={userStore.avatar} size={20} />
{userStore.name} {userStore.name}
</div> </div>
<icon-caret-down class="!m-0" /> <icon-caret-down class="!m-0" />

View File

@ -110,9 +110,7 @@
</div> </div>
<div v-show="activeAvatarType === 'word'" class="mb-[8px] flex flex-wrap gap-[24px] pt-[14px]"> <div v-show="activeAvatarType === 'word'" class="mb-[8px] flex flex-wrap gap-[24px] pt-[14px]">
<div class="avatar" @click="changeAvatar('word')"> <div class="avatar" @click="changeAvatar('word')">
<MsAvatar avatar="word" class="mb-[4px]"> <MsAvatar avatar="word" class="mb-[4px]" />
{{ userStore.name?.substring(0, 4) }}
</MsAvatar>
<div class="text-[12px] text-[var(--color-text-1)]">{{ t('ms.personal.wordAvatar') }}</div> <div class="text-[12px] text-[var(--color-text-1)]">{{ t('ms.personal.wordAvatar') }}</div>
<MsIcon <MsIcon
v-if="activeAvatar === 'word'" v-if="activeAvatar === 'word'"
@ -157,6 +155,8 @@
}); });
const baseInfoFormRef = ref<FormInstance>(); const baseInfoFormRef = ref<FormInstance>();
const orgList = ref<OrganizationProjectListItem[]>([]); const orgList = ref<OrganizationProjectListItem[]>([]);
const activeAvatarType = ref<'builtIn' | 'word'>('builtIn');
const activeAvatar = ref('default');
function initBaseInfo() { function initBaseInfo() {
descriptions.value = [ descriptions.value = [
@ -186,6 +186,8 @@
loading.value = true; loading.value = true;
const res = await getBaseInfo(userStore.id || ''); const res = await getBaseInfo(userStore.id || '');
orgList.value = res.orgProjectList; orgList.value = res.orgProjectList;
activeAvatar.value = res.avatar;
activeAvatarType.value = res.avatar === 'word' ? 'word' : 'builtIn';
} catch (error) { } catch (error) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log(error); console.log(error);
@ -251,8 +253,6 @@
} }
const avatarModalVisible = ref(false); const avatarModalVisible = ref(false);
const activeAvatarType = ref<'builtIn' | 'word'>('builtIn');
const activeAvatar = ref('default');
const avatarList = ref<string[]>([]); const avatarList = ref<string[]>([]);
let i = 1; let i = 1;
while (i <= 46) { while (i <= 46) {

View File

@ -1,17 +1,37 @@
<template> <template>
<MsIcon v-if="props.avatar === 'default'" type="icon-icon_that_person" size="40" class="text-[var(--color-text-4)]" /> <MsIcon
<a-avatar v-else-if="props.avatar === 'word'" class="bg-[rgb(var(--primary-1))] text-[rgb(var(--primary-6))]"> v-if="props.avatar === 'default'"
<slot></slot> type="icon-icon_that_person"
:size="props.size"
class="text-[var(--color-text-4)]"
/>
<a-avatar
v-else-if="props.avatar === 'word'"
:size="props.size"
class="bg-[rgb(var(--primary-1))] text-[rgb(var(--primary-6))]"
>
<slot>{{ userStore.name?.substring(0, 4) }}</slot>
</a-avatar> </a-avatar>
<a-avatar v-else :image-url="avatar"></a-avatar> <a-avatar v-else :image-url="avatar" :size="props.size"></a-avatar>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import MsIcon from '@/components/pure/ms-icon-font/index.vue'; import MsIcon from '@/components/pure/ms-icon-font/index.vue';
const props = defineProps<{ import useUserStore from '@/store/modules/user/index';
avatar: 'default' | 'word' | string;
}>(); const userStore = useUserStore();
const props = withDefaults(
defineProps<{
avatar?: 'default' | 'word' | string;
size?: number;
}>(),
{
avatar: 'default',
size: 40,
}
);
</script> </script>
<style lang="less" scoped></style> <style lang="less" scoped></style>

View File

@ -158,9 +158,6 @@
:deep(.arco-split-trigger-icon) { :deep(.arco-split-trigger-icon) {
font-size: 14px; font-size: 14px;
} }
.ms-split-box--top {
height: calc(v-bind(innerSize) - 4px);
}
.ms-split-box--bottom { .ms-split-box--bottom {
@apply h-full; @apply h-full;
} }

View File

@ -19,7 +19,7 @@
</template> </template>
<div class="h-full px-[24px]"> <div class="h-full px-[24px]">
<a-divider class="my-0" /> <a-divider class="my-0" />
<div class="flex h-[calc(100%-1px)]"> <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
@ -54,7 +54,7 @@
</div> </div>
<MsPagination :total="total" :page-size="pageSize" :current="pageCurrent" size="mini" simple /> <MsPagination :total="total" :page-size="pageSize" :current="pageCurrent" size="mini" simple />
</div> </div>
<div class="relative flex flex-1 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">
@ -104,16 +104,31 @@
</div> </div>
</div> </div>
<a-tabs v-model:active-key="showTab" class="no-content"> <a-tabs v-model:active-key="showTab" class="no-content">
<a-tab-pane v-for="item of tabList" :key="item.key" :title="item.title" /> <a-tab-pane :key="tabList[0].key" :title="tabList[0].title" />
<a-tab-pane :key="tabList[1].key" :title="tabList[1].title" />
<a-tab-pane :key="tabList[2].key">
<template #title>
<div class="flex items-center">
{{ tabList[2].title }}
<div
:class="`ml-[4px] rounded-full ${
showTab === tabList[2].key ? 'bg-[rgb(var(--primary-5))]' : 'bg-[var(--color-text-brand)]'
} px-[4px] text-[12px] text-white`"
>
{{ caseDetail.demandCount > 99 ? '99+' : caseDetail.demandCount }}
</div>
</div>
</template>
</a-tab-pane>
</a-tabs> </a-tabs>
</div> </div>
<a-divider class="my-0" /> <a-divider class="my-0" />
<div class="content-center"> <div class="content-center">
<MsDescription v-if="showTab === 'baseInfo'" :descriptions="descriptions" label-width="90px" /> <MsDescription v-if="showTab === 'baseInfo'" :descriptions="descriptions" label-width="90px" />
<div v-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 #top> <template #top>
<div> toptop </div> <caseTabDetail :form="{}" :allow-edit="false" />
</template> </template>
<template #bottom> <template #bottom>
<div class="flex h-full flex-col overflow-hidden"> <div class="flex h-full flex-col overflow-hidden">
@ -153,6 +168,20 @@
</template> </template>
</MsSplitBox> </MsSplitBox>
</div> </div>
<div v-else>
<div class="flex items-center justify-between">
{{ t('caseManagement.caseReview.demandCases') }}
<a-input-search
v-model="demandKeyword"
:placeholder="t('caseManagement.caseReview.demandSearchPlaceholder')"
allow-clear
class="w-[300px]"
@press-enter="searchDemand"
@search="searchDemand"
/>
</div>
<caseTabDemand ref="caseDemandRef" :fun-params="{ caseId: route.query.id, keyword: demandKeyword }" />
</div>
</div> </div>
<div class="content-footer"> <div class="content-footer">
<div class="mb-[16px] flex items-center"> <div class="mb-[16px] flex items-center">
@ -232,6 +261,7 @@
/** /**
* @description 功能测试-用例评审-用例详情 * @description 功能测试-用例评审-用例详情
*/ */
import { useRoute } from 'vue-router';
import { FormInstance } from '@arco-design/web-vue'; import { FormInstance } from '@arco-design/web-vue';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
@ -242,13 +272,18 @@
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 caseTabDemand from '../caseManagementFeature/components/tabContent/tabDemand/associatedDemandTable.vue';
import caseTabDetail from '../caseManagementFeature/components/tabContent/tabDetail.vue';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
const route = useRoute();
const { t } = useI18n(); const { t } = useI18n();
const reviewName = ref('打算肯定还是觉得还是觉得还是计划的'); const reviewName = ref('打算肯定还是觉得还是觉得还是计划的');
const caseDetail = ref({}); const caseDetail = ref({
demandCount: 999,
});
const onlyMine = ref(false); const onlyMine = ref(false);
const keyword = ref(''); const keyword = ref('');
@ -415,6 +450,12 @@
reason: '', reason: '',
}); });
const dialogFormRef = ref<FormInstance>(); const dialogFormRef = ref<FormInstance>();
const demandKeyword = ref('');
const caseDemandRef = ref<InstanceType<typeof caseTabDemand>>();
function searchDemand() {
caseDemandRef.value?.initData();
}
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>

View File

@ -86,8 +86,8 @@
<template v-if="keyword.trim() === ''" #empty> <template v-if="keyword.trim() === ''" #empty>
<div class="flex items-center justify-center p-[8px] text-[var(--color-text-4)]"> <div class="flex items-center justify-center p-[8px] text-[var(--color-text-4)]">
{{ t('caseManagement.caseReview.tableNoData') }} {{ t('caseManagement.caseReview.tableNoData') }}
<MsButton class="ml-[8px]" @click="handleAddClick"> <MsButton class="ml-[8px]" @click="createCase">
{{ t('caseManagement.caseReview.create') }} {{ t('caseManagement.caseReview.crateCase') }}
</MsButton> </MsButton>
</div> </div>
</template> </template>
@ -586,10 +586,6 @@
} }
} }
function handleAddClick() {
console.log('handleAddClick');
}
function openDetail(id: string) { function openDetail(id: string) {
router.push({ router.push({
name: CaseManagementRouteEnum.CASE_MANAGEMENT_REVIEW_DETAIL_CASE_DETAIL, name: CaseManagementRouteEnum.CASE_MANAGEMENT_REVIEW_DETAIL_CASE_DETAIL,
@ -606,7 +602,7 @@
function createCase() { function createCase() {
router.push({ router.push({
name: CaseManagementRouteEnum.CASE_MANAGEMENT_REVIEW_CREATE, name: CaseManagementRouteEnum.CASE_MANAGEMENT_CASE_DETAIL,
query: { query: {
reviewId: route.query.id, reviewId: route.query.id,
}, },

View File

@ -129,4 +129,5 @@ export default {
'caseManagement.caseReview.submitReview': 'Submit review', 'caseManagement.caseReview.submitReview': 'Submit review',
'caseManagement.caseReview.reviewHistory': 'Review history', 'caseManagement.caseReview.reviewHistory': 'Review history',
'caseManagement.caseReview.noMatchReviewer': 'No matching handler, can be set in {menu}', 'caseManagement.caseReview.noMatchReviewer': 'No matching handler, can be set in {menu}',
'caseManagement.caseReview.crateCase': 'Create case',
}; };

View File

@ -119,4 +119,7 @@ export default {
'caseManagement.caseReview.submitReview': '提交评审', 'caseManagement.caseReview.submitReview': '提交评审',
'caseManagement.caseReview.reviewHistory': '评审历史', 'caseManagement.caseReview.reviewHistory': '评审历史',
'caseManagement.caseReview.noMatchReviewer': '无匹配处理人,可在 ', 'caseManagement.caseReview.noMatchReviewer': '无匹配处理人,可在 ',
'caseManagement.caseReview.crateCase': '创建用例',
'caseManagement.caseReview.demandCases': '需求关联列表',
'caseManagement.caseReview.demandSearchPlaceholder': '通过名称搜索',
}; };

View File

@ -239,6 +239,19 @@
} }
); );
/**
* 初始化模块文件数量
*/
watch(
() => props.modulesCount,
(obj) => {
storageList.value = originStorageList.value.map((e) => ({
...e,
count: obj?.[e.id] || 0,
}));
}
);
const focusItemKey = ref(''); const focusItemKey = ref('');
function setActiveFolder(id: string) { function setActiveFolder(id: string) {

View File

@ -229,7 +229,13 @@
* 右侧表格数据刷新后若当前展示的是模块则刷新模块树的统计数量 * 右侧表格数据刷新后若当前展示的是模块则刷新模块树的统计数量
*/ */
function handleModuleTableInit(params: FileListQueryParams) { function handleModuleTableInit(params: FileListQueryParams) {
initModulesCount(params); initModulesCount({
...params,
combine: {
...params.combine,
storage: showType.value === 'Module' ? 'minio' : 'git', //
},
});
tableFilterParams.value = { ...params }; tableFilterParams.value = { ...params };
} }
</script> </script>