feat(工作台): 工作台部分指标跳转(不包含接口覆盖)&测试计划概览页面

This commit is contained in:
xinxin.wu 2024-12-04 19:39:21 +08:00 committed by Craftsman
parent 65f2b93057
commit 285259048c
44 changed files with 1485 additions and 144 deletions

View File

@ -1,3 +1,5 @@
import { CascaderOption } from '@arco-design/web-vue';
import MSR from '@/api/http/index'; import MSR from '@/api/http/index';
import type { ApiCaseDetail, ApiDefinitionDetail } from '@/models/apiTest/management'; import type { ApiCaseDetail, ApiDefinitionDetail } from '@/models/apiTest/management';
@ -48,6 +50,8 @@ import {
WorkProOverviewDetailUrl, WorkProOverviewDetailUrl,
WorkReviewListUrl, WorkReviewListUrl,
WorkScenarioCaseCountDetailUrl, WorkScenarioCaseCountDetailUrl,
WorkTestPlanListUrl,
WorkTestPlanOverviewUrl,
WorkTestPlanRageUrl, WorkTestPlanRageUrl,
WorkTodoBugListUrl, WorkTodoBugListUrl,
WorkTodoPlanListUrl, WorkTodoPlanListUrl,
@ -224,3 +228,12 @@ export function workbenchTodoBugList(data: TableQueryParams) {
export function workbenchTodoTestPlanList(data: TableQueryParams) { export function workbenchTodoTestPlanList(data: TableQueryParams) {
return MSR.post<CommonList<TestPlanItem>>({ url: WorkTodoPlanListUrl, data }); return MSR.post<CommonList<TestPlanItem>>({ url: WorkTodoPlanListUrl, data });
} }
// 待办-测试计划列表
export function getWorkTestPlanListUrl(projectId: string) {
return MSR.get<CascaderOption[]>({ url: `${WorkTestPlanListUrl}/${projectId}` });
}
// 工作台-测试计划概览
export function workTestPlanOverviewDetail(data: WorkHomePageDetail) {
return MSR.post<OverViewOfProject>({ url: WorkTestPlanOverviewUrl, data }, { ignoreCancelToken: true });
}

View File

@ -32,3 +32,5 @@ export const WorkPlanLegacyBugUrl = '/dashboard/plan_legacy_bug'; // 工作台-
export const WorkApiCountCoverRateUrl = '/api/definition/rage'; // 工作台-首页-覆盖率 export const WorkApiCountCoverRateUrl = '/api/definition/rage'; // 工作台-首页-覆盖率
export const WorkTestPlanRageUrl = '/test-plan/rage'; // 工作台-首页-测试计划数 export const WorkTestPlanRageUrl = '/test-plan/rage'; // 工作台-首页-测试计划数
export const WorkProjectMemberListUrl = '/dashboard/member/get-project-member/option'; // 工作台-首页-项目成员下拉 export const WorkProjectMemberListUrl = '/dashboard/member/get-project-member/option'; // 工作台-首页-项目成员下拉
export const WorkTestPlanListUrl = '/dashboard/plan/option'; // 工作台-首页-测试计划概览下拉
export const WorkTestPlanOverviewUrl = '/dashboard/plan_view'; // 工作台-首页-测试计划概览

View File

@ -0,0 +1,119 @@
<svg width="108" height="72" viewBox="0 0 108 72" fill="none" xmlns="http://www.w3.org/2000/svg">
<ellipse opacity="0.8" cx="75.9903" cy="26.6645" rx="26.0098" ry="26.6645" fill="#E9E8EC" fill-opacity="0.5"/>
<path d="M13.9951 6.1228C13.9951 5.01823 14.8905 4.1228 15.9951 4.1228H85.4345C86.5391 4.1228 87.4345 5.01823 87.4345 6.1228V52.2235H13.9951V6.1228Z" fill="#DFDEE4"/>
<path d="M13.9951 52.2236H87.4345V58.589C87.4345 59.6935 86.5391 60.589 85.4345 60.589H15.9951C14.8905 60.589 13.9951 59.6935 13.9951 58.589V52.2236Z" fill="#F2F2F4"/>
<ellipse cx="51.2248" cy="55.8834" rx="1.52999" ry="1.5685" fill="#D4D3D7"/>
<rect x="17.0547" y="7.26001" width="67.3195" height="41.8267" rx="1" fill="white"/>
<rect x="19.9927" y="39.1677" width="1" height="8.24584" rx="0.5" fill="#F4EFF7"/>
<rect x="35.9863" y="39.1677" width="1" height="8.24584" rx="0.5" fill="#F4EFF7"/>
<rect x="51.9795" y="39.1677" width="1" height="8.24584" rx="0.5" fill="#F4EFF7"/>
<rect x="66.9736" y="39.1677" width="1" height="8.24584" rx="0.5" fill="#F4EFF7"/>
<rect x="80.9678" y="39.1677" width="1" height="8.24584" rx="0.5" fill="#F4EFF7"/>
<path d="M36.3977 41.2293L19.9927 45.4607V47.4137H81.9674V43.2908L67.3851 40.3613L52.8028 45.4607L36.3977 41.2293Z" fill="url(#paint0_linear_5698_59348)"/>
<path d="M19.9927 45.3522L36.3977 41.2293L52.8028 45.3522L67.3851 40.2778L81.9674 43.2907" stroke="#945DED" stroke-width="0.5"/>
<path d="M27.4894 29.8912C27.4894 31.9067 25.9088 33.5141 23.991 33.5141C22.0733 33.5141 20.4927 31.9067 20.4927 29.8912C20.4927 27.8758 22.0733 26.2683 23.991 26.2683C25.9088 26.2683 27.4894 27.8758 27.4894 29.8912Z" stroke="#F4EFF7"/>
<mask id="path-14-inside-1_5698_59348" fill="white">
<path d="M23.991 25.7683C24.8981 25.7683 25.7782 26.0863 26.4869 26.6702C27.1955 27.254 27.6906 28.069 27.8908 28.9813C28.091 29.8935 27.9844 30.8489 27.5886 31.6904C27.1928 32.532 26.5312 33.2098 25.7125 33.6125L25.2824 32.6828C25.8966 32.3807 26.3929 31.8723 26.6898 31.2409C26.9868 30.6096 27.0667 29.8929 26.9165 29.2086C26.7664 28.5242 26.395 27.9129 25.8634 27.4749C25.3317 27.0369 24.6715 26.7983 23.991 26.7983L23.991 25.7683Z"/>
</mask>
<path d="M23.991 25.7683C24.8981 25.7683 25.7782 26.0863 26.4869 26.6702C27.1955 27.254 27.6906 28.069 27.8908 28.9813C28.091 29.8935 27.9844 30.8489 27.5886 31.6904C27.1928 32.532 26.5312 33.2098 25.7125 33.6125L25.2824 32.6828C25.8966 32.3807 26.3929 31.8723 26.6898 31.2409C26.9868 30.6096 27.0667 29.8929 26.9165 29.2086C26.7664 28.5242 26.395 27.9129 25.8634 27.4749C25.3317 27.0369 24.6715 26.7983 23.991 26.7983L23.991 25.7683Z" stroke="#945DED" stroke-width="2" mask="url(#path-14-inside-1_5698_59348)"/>
<path d="M45.4821 29.8912C45.4821 31.9067 43.9015 33.5141 41.9837 33.5141C40.066 33.5141 38.4854 31.9067 38.4854 29.8912C38.4854 27.8758 40.066 26.2683 41.9837 26.2683C43.9015 26.2683 45.4821 27.8758 45.4821 29.8912Z" stroke="#F4EFF7"/>
<mask id="path-16-inside-2_5698_59348" fill="white">
<path d="M41.9837 25.7683C42.6413 25.7683 43.2886 25.9355 43.8685 26.2551C44.4484 26.5748 44.9429 27.0369 45.3082 27.6007C45.6735 28.1644 45.8984 28.8124 45.9628 29.4871C46.0273 30.1619 45.9294 30.8426 45.6777 31.469C45.4261 32.0954 45.0285 32.6482 44.5203 33.0783C44.012 33.5084 43.4087 33.8027 42.7638 33.9349C42.1189 34.0672 41.4523 34.0334 40.8231 33.8366C40.1938 33.6398 39.6214 33.286 39.1564 32.8066L39.8628 32.0782C40.2116 32.4379 40.641 32.7033 41.113 32.851C41.585 32.9986 42.0851 33.0239 42.5689 32.9247C43.0527 32.8255 43.5053 32.6048 43.8866 32.2821C44.2679 31.9594 44.5661 31.5447 44.7549 31.0748C44.9436 30.6049 45.0171 30.0943 44.9687 29.5881C44.9204 29.0819 44.7517 28.5958 44.4777 28.1729C44.2036 27.75 43.8327 27.4033 43.3977 27.1635C42.9626 26.9238 42.477 26.7983 41.9837 26.7983L41.9837 25.7683Z"/>
</mask>
<path d="M41.9837 25.7683C42.6413 25.7683 43.2886 25.9355 43.8685 26.2551C44.4484 26.5748 44.9429 27.0369 45.3082 27.6007C45.6735 28.1644 45.8984 28.8124 45.9628 29.4871C46.0273 30.1619 45.9294 30.8426 45.6777 31.469C45.4261 32.0954 45.0285 32.6482 44.5203 33.0783C44.012 33.5084 43.4087 33.8027 42.7638 33.9349C42.1189 34.0672 41.4523 34.0334 40.8231 33.8366C40.1938 33.6398 39.6214 33.286 39.1564 32.8066L39.8628 32.0782C40.2116 32.4379 40.641 32.7033 41.113 32.851C41.585 32.9986 42.0851 33.0239 42.5689 32.9247C43.0527 32.8255 43.5053 32.6048 43.8866 32.2821C44.2679 31.9594 44.5661 31.5447 44.7549 31.0748C44.9436 30.6049 45.0171 30.0943 44.9687 29.5881C44.9204 29.0819 44.7517 28.5958 44.4777 28.1729C44.2036 27.75 43.8327 27.4033 43.3977 27.1635C42.9626 26.9238 42.477 26.7983 41.9837 26.7983L41.9837 25.7683Z" stroke="#945DED" stroke-width="2" mask="url(#path-16-inside-2_5698_59348)"/>
<path d="M63.4748 29.8912C63.4748 31.9067 61.8941 33.5141 59.9764 33.5141C58.0586 33.5141 56.478 31.9067 56.478 29.8912C56.478 27.8758 58.0586 26.2683 59.9764 26.2683C61.8941 26.2683 63.4748 27.8758 63.4748 29.8912Z" stroke="#F4EFF7"/>
<mask id="path-18-inside-3_5698_59348" fill="white">
<path d="M62.3385 33.2178C61.6628 33.728 60.8487 34.0065 60.0112 34.014C59.1737 34.0215 58.355 33.7576 57.6707 33.2596C56.9865 32.7616 56.4712 32.0546 56.1975 31.2384C55.9238 30.4221 55.9056 29.5379 56.1455 28.7105L57.1026 29.0054C56.9226 29.6262 56.9363 30.2895 57.1416 30.9018C57.3469 31.5141 57.7334 32.0445 58.2468 32.4181C58.7601 32.7917 59.3742 32.9897 60.0025 32.984C60.6308 32.9784 61.2415 32.7695 61.7484 32.3867L62.3385 33.2178Z"/>
</mask>
<path d="M62.3385 33.2178C61.6628 33.728 60.8487 34.0065 60.0112 34.014C59.1737 34.0215 58.355 33.7576 57.6707 33.2596C56.9865 32.7616 56.4712 32.0546 56.1975 31.2384C55.9238 30.4221 55.9056 29.5379 56.1455 28.7105L57.1026 29.0054C56.9226 29.6262 56.9363 30.2895 57.1416 30.9018C57.3469 31.5141 57.7334 32.0445 58.2468 32.4181C58.7601 32.7917 59.3742 32.9897 60.0025 32.984C60.6308 32.9784 61.2415 32.7695 61.7484 32.3867L62.3385 33.2178Z" stroke="#945DED" stroke-width="2" mask="url(#path-18-inside-3_5698_59348)"/>
<path d="M81.4674 29.8912C81.4674 31.9067 79.8868 33.5141 77.9691 33.5141C76.0513 33.5141 74.4707 31.9067 74.4707 29.8912C74.4707 27.8758 76.0513 26.2683 77.9691 26.2683C79.8868 26.2683 81.4674 27.8758 81.4674 29.8912Z" stroke="#F4EFF7"/>
<mask id="path-20-inside-4_5698_59348" fill="white">
<path d="M80.3312 33.2178C79.8436 33.5859 79.2814 33.8353 78.6869 33.9472C78.0924 34.059 77.4811 34.0305 76.8988 33.8637C76.3165 33.6969 75.7785 33.3962 75.3252 32.9841C74.8718 32.5721 74.515 32.0594 74.2814 31.4847C74.0478 30.91 73.9437 30.2881 73.9767 29.6659C74.0097 29.0437 74.1791 28.4373 74.4721 27.8923C74.7651 27.3473 75.1741 26.8778 75.6683 26.5193C76.1626 26.1607 76.7292 25.9223 77.3257 25.822L77.4864 26.8386C77.039 26.9139 76.6139 27.0927 76.2431 27.3617C75.8723 27.6307 75.5655 27.9828 75.3457 28.3917C75.1259 28.8005 74.9989 29.2554 74.9741 29.7222C74.9493 30.189 75.0275 30.6555 75.2027 31.0866C75.3779 31.5177 75.6456 31.9023 75.9857 32.2114C76.3258 32.5205 76.7294 32.7461 77.1662 32.8713C77.603 32.9964 78.0616 33.0178 78.5076 32.9339C78.9536 32.85 79.3753 32.6629 79.7411 32.3867L80.3312 33.2178Z"/>
</mask>
<path d="M80.3312 33.2178C79.8436 33.5859 79.2814 33.8353 78.6869 33.9472C78.0924 34.059 77.4811 34.0305 76.8988 33.8637C76.3165 33.6969 75.7785 33.3962 75.3252 32.9841C74.8718 32.5721 74.515 32.0594 74.2814 31.4847C74.0478 30.91 73.9437 30.2881 73.9767 29.6659C74.0097 29.0437 74.1791 28.4373 74.4721 27.8923C74.7651 27.3473 75.1741 26.8778 75.6683 26.5193C76.1626 26.1607 76.7292 25.9223 77.3257 25.822L77.4864 26.8386C77.039 26.9139 76.6139 27.0927 76.2431 27.3617C75.8723 27.6307 75.5655 27.9828 75.3457 28.3917C75.1259 28.8005 74.9989 29.2554 74.9741 29.7222C74.9493 30.189 75.0275 30.6555 75.2027 31.0866C75.3779 31.5177 75.6456 31.9023 75.9857 32.2114C76.3258 32.5205 76.7294 32.7461 77.1662 32.8713C77.603 32.9964 78.0616 33.0178 78.5076 32.9339C78.9536 32.85 79.3753 32.6629 79.7411 32.3867L80.3312 33.2178Z" stroke="#945DED" stroke-width="2" mask="url(#path-20-inside-4_5698_59348)"/>
<rect opacity="0.8" x="19.9927" y="18.5532" width="5.99755" height="4.12292" rx="0.5" fill="#959598"/>
<g clip-path="url(#clip0_5698_59348)">
<path d="M24.2411 22.5093C24.2392 22.5093 24.2374 22.5092 24.2355 22.5092C24.2327 22.5091 24.2297 22.5089 24.2267 22.5086C24.2231 22.5083 24.2197 22.5079 24.2162 22.5074L24.2076 22.5058C24.204 22.505 24.2003 22.5042 24.1968 22.5031C24.1942 22.5024 24.1916 22.5016 24.1891 22.5007C24.1858 22.4996 24.1825 22.4984 24.1792 22.4971C24.1763 22.4958 24.1734 22.4946 24.1706 22.4932C24.1671 22.4915 24.1637 22.4897 24.1604 22.4878C24.1584 22.4867 24.1563 22.4855 24.1543 22.4842C24.1501 22.4815 24.1459 22.4786 24.1419 22.4756L24.1383 22.4727C24.133 22.4684 24.128 22.4639 24.1233 22.459L23.7068 22.0295C23.6417 21.9624 23.6417 21.8536 23.7068 21.7865C23.7718 21.7195 23.8773 21.7195 23.9424 21.7865L24.0745 21.9228V21.135C24.0745 20.8605 23.8664 20.6362 23.604 20.6205L23.5747 20.6196H22.2419V21.9226L22.374 21.7865C22.434 21.7246 22.5285 21.7199 22.5939 21.7723L22.6096 21.7865C22.6746 21.8536 22.6746 21.9624 22.6096 22.0295L22.1931 22.459C22.128 22.526 22.0225 22.526 21.9575 22.459L21.541 22.0295C21.4759 21.9624 21.4759 21.8536 21.541 21.7865C21.606 21.7195 21.7115 21.7195 21.7766 21.7865L21.9087 21.9228L21.9087 19.9027C21.6678 19.8288 21.4922 19.5987 21.4922 19.3264C21.4922 18.9943 21.7532 18.7251 22.0753 18.7251C22.3973 18.7251 22.6584 18.9943 22.6584 19.3264C22.6584 19.5987 22.4828 19.8287 22.242 19.9027L22.2419 20.276H23.5747C24.0347 20.276 24.4077 20.6606 24.4077 21.135V21.9226L24.5398 21.7865C24.5998 21.7246 24.6943 21.7199 24.7597 21.7723L24.7754 21.7865C24.8404 21.8536 24.8404 21.9624 24.7754 22.0295L24.3589 22.459L24.3459 22.4706L24.3402 22.4755C24.3362 22.4786 24.3321 22.4815 24.3278 22.4842C24.3258 22.4855 24.3238 22.4867 24.3217 22.4879C24.3184 22.4897 24.315 22.4915 24.3116 22.4932C24.3087 22.4946 24.3058 22.4958 24.3029 22.497C24.2996 22.4984 24.2963 22.4996 24.293 22.5008C24.2905 22.5016 24.288 22.5024 24.2854 22.5031C24.2818 22.5042 24.2782 22.505 24.2745 22.5058L24.2658 22.5074L24.2549 22.5086H24.2546L24.2411 22.5093ZM22.0753 19.0687C21.9373 19.0687 21.8254 19.184 21.8254 19.3264C21.8254 19.4687 21.9373 19.584 22.0753 19.584C22.2133 19.584 22.3252 19.4687 22.3252 19.3264C22.3252 19.184 22.2133 19.0687 22.0753 19.0687Z" fill="white"/>
</g>
<rect opacity="0.8" x="28.9888" y="18.5532" width="52.9784" height="4.12292" rx="0.5" fill="url(#paint1_linear_5698_59348)"/>
<rect x="17.0547" y="14.5796" width="67.3195" height="1.04567" rx="0.1" fill="#F4EFF7"/>
<ellipse cx="22.6648" cy="10.9198" rx="1.52999" ry="1.5685" fill="#ED0303"/>
<ellipse cx="28.7849" cy="10.9198" rx="1.52999" ry="1.5685" fill="#FFA200"/>
<ellipse cx="34.905" cy="10.9198" rx="1.52999" ry="1.5685" fill="#00C261"/>
<path d="M41.5351 60.5891H59.895L61.9349 67.9088H39.4951L41.5351 60.5891Z" fill="url(#paint2_linear_5698_59348)"/>
<rect x="34.395" y="67.9089" width="32.6397" height="2.09133" rx="1.04567" fill="#E9E8EC"/>
<g filter="url(#filter0_d_5698_59348)">
<rect x="44.3994" y="34.0142" width="55.5607" height="29.8912" rx="1" fill="white"/>
<rect x="50.4385" y="47.748" width="4.83136" height="12.118" fill="#EBF1FF"/>
<rect x="60.1011" y="38.0535" width="4.83136" height="21.8125" fill="#3370FF"/>
<rect x="69.7637" y="42.9009" width="4.83136" height="16.9653" fill="#EBF1FF"/>
<rect x="79.4263" y="47.748" width="4.83136" height="12.118" fill="#3370FF"/>
<rect x="89.0894" y="47.748" width="4.83136" height="12.118" fill="#EBF1FF"/>
<path d="M50.4385 54.6147L56.0788 48.7673C59.4202 45.3031 65.0895 45.7366 67.8654 49.6684L68.3978 50.4226C70.7413 53.7418 75.4445 54.2984 78.498 51.6178V51.6178C80.8264 49.5738 84.2379 49.3502 86.8132 51.0727L93.3168 55.4226" stroke="url(#paint3_linear_5698_59348)" stroke-width="0.5"/>
</g>
<g filter="url(#filter1_d_5698_59348)">
<rect x="2" y="8.24585" width="19.5419" height="20.6146" rx="1" fill="white"/>
<g clip-path="url(#clip1_5698_59348)">
<path d="M16.9399 12.7742C17.1989 12.7742 17.3377 12.9149 17.3562 13.1965L17.3583 13.2639C17.3583 13.5904 17.2188 13.7537 16.9399 13.7537H8.52131V21.6088H15.4319L15.4323 20.8453C15.4323 20.7944 15.4541 20.747 15.4904 20.7155L15.5203 20.6951C15.5742 20.6667 15.6387 20.6728 15.6869 20.711L17.2787 21.9787C17.313 22.0106 17.3326 22.0565 17.3326 22.1046C17.3326 22.1528 17.313 22.1987 17.2787 22.2306L15.6869 23.4983C15.6387 23.5365 15.5742 23.5426 15.5203 23.5142C15.4663 23.4858 15.4323 23.4276 15.4323 23.364L15.4319 22.5883L8.25806 22.589C8.21714 22.6022 8.17145 22.6106 8.121 22.6145L8.05704 22.6168C7.74753 22.6168 7.59277 22.4648 7.59277 22.1609V13.2777C7.59277 13.2061 7.60135 13.143 7.61852 13.0882L7.63854 13.0363C7.6936 12.8616 7.82303 12.7742 8.02713 12.7742H16.9399ZM10.7878 15.2229C11.0766 15.2344 11.3285 15.433 11.4208 15.7219L12.5338 19.0315C12.5432 19.1112 12.5595 19.1898 12.5825 19.2663C12.5825 19.4898 12.4521 19.6071 12.2296 19.6238L12.1721 19.6259C11.984 19.6497 11.8062 19.5298 11.7478 19.3397L11.5878 18.7673H10.0017L9.82084 19.3397C9.75156 19.5241 9.57707 19.6398 9.38954 19.6259C9.28563 19.6438 9.17961 19.6111 9.10121 19.537C9.02281 19.4628 8.98048 19.3552 8.98607 19.2443V19.0755C8.99814 19.0642 9.00562 19.0485 9.00694 19.0315L10.1826 15.7219C10.2547 15.4307 10.5025 15.2264 10.7878 15.2229ZM15.2926 15.6353C15.5739 15.9179 15.7328 16.3099 15.7324 16.7197C15.7453 17.1294 15.5931 17.5258 15.3136 17.8107C15.0342 18.0955 14.6534 18.2423 14.2657 18.2146H13.6209V19.1937C13.6282 19.3163 13.5832 19.4359 13.4982 19.5198C13.4131 19.6037 13.297 19.6431 13.1816 19.6272C12.8911 19.6272 12.7494 19.4852 12.7494 19.1937V15.7032C12.7341 15.576 12.7738 15.4484 12.8577 15.3556C12.9295 15.2761 13.0272 15.2294 13.1299 15.224L14.2444 15.2248C14.6324 15.2043 15.0113 15.3527 15.2926 15.6353ZM16.4605 15.2235L16.5105 15.2242C16.6259 15.2193 16.7376 15.2692 16.8167 15.3612C16.8957 15.4532 16.9343 15.5781 16.9225 15.7036V19.1994C16.9316 19.3196 16.8904 19.4379 16.8103 19.5211C16.7436 19.5903 16.6559 19.629 16.5652 19.6306L16.5105 19.6272C16.2337 19.6272 16.0919 19.487 16.0919 19.1994V15.7036C16.0773 15.5762 16.116 15.4484 16.1971 15.3554C16.2667 15.2758 16.3611 15.229 16.4605 15.2235ZM10.7878 16.1769L10.2452 17.9381H11.3304L10.7878 16.1769ZM13.6209 16.0395V17.3475H14.2161C14.3763 17.3538 14.5318 17.2899 14.6459 17.171C14.7599 17.0521 14.8223 16.8888 14.8183 16.7197C14.8221 16.5479 14.7606 16.3817 14.6474 16.2581C14.5343 16.1345 14.3789 16.0639 14.2161 16.0619L13.6209 16.0395Z" fill="#00C261"/>
</g>
</g>
<g filter="url(#filter2_d_5698_59348)">
<rect x="2" y="35.0449" width="19.5419" height="20.6146" rx="1" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.59277 40.5527C7.59277 40.2823 7.80063 40.063 8.05704 40.063H16.4139C16.6703 40.063 16.8781 40.2823 16.8781 40.5527V41.5322H7.59277V40.5527ZM7.59277 42.5117H16.8781V46.5557C16.4366 46.1711 15.8705 45.94 15.2532 45.94C13.8429 45.94 12.6997 47.146 12.6997 48.6336C12.6997 48.8883 12.7332 49.1347 12.7958 49.3683H8.05704C7.80063 49.3683 7.59277 49.149 7.59277 48.8785V42.5117ZM9.14559 43.9457C9.28158 43.8023 9.50204 43.8023 9.63802 43.9457L10.9921 45.3742C11.1281 45.5176 11.1281 45.7502 10.9921 45.8936L9.63802 47.3221C9.50204 47.4655 9.28158 47.4655 9.14559 47.3221C9.00961 47.1786 9.00961 46.9461 9.14559 46.8026L10.2535 45.6339L9.14559 44.4652C9.00961 44.3218 9.00961 44.0892 9.14559 43.9457ZM10.5525 47.3072C10.3602 47.3072 10.2043 47.4717 10.2043 47.6746C10.2043 47.8774 10.3602 48.0419 10.5525 48.0419H12.0033C12.1956 48.0419 12.3515 47.8774 12.3515 47.6746C12.3515 47.4717 12.1956 47.3072 12.0033 47.3072H10.5525Z" fill="#945DED"/>
<path d="M15.2531 50.5926C16.2787 50.5926 17.1101 49.7155 17.1101 48.6336C17.1101 47.5516 16.2787 46.6746 15.2531 46.6746C14.2274 46.6746 13.396 47.5516 13.396 48.6336C13.396 49.7155 14.2274 50.5926 15.2531 50.5926ZM15.3031 47.469L15.4706 47.4907C15.5169 47.4967 15.5498 47.5411 15.5441 47.59L15.2972 49.7111C15.2915 49.7599 15.2494 49.7947 15.2031 49.7887L15.0356 49.7669C14.9893 49.761 14.9564 49.7165 14.9621 49.6677L15.209 47.5465C15.2147 47.4977 15.2568 47.463 15.3031 47.469ZM14.3802 48.6345L14.6199 48.9957C14.6467 49.036 14.6374 49.0915 14.5992 49.1197L14.4609 49.2219C14.4227 49.2501 14.3701 49.2403 14.3434 49.2L14.0044 48.6894C13.9873 48.6636 13.985 48.6315 13.9956 48.6044C13.9979 48.5935 14.0023 48.5829 14.0087 48.5732L14.3476 48.0626C14.3744 48.0223 14.427 48.0125 14.4652 48.0408L14.6035 48.1429C14.6417 48.1711 14.6509 48.2266 14.6242 48.2669L14.3802 48.6345ZM15.8862 48.2714C15.8594 48.2311 15.8687 48.1756 15.9069 48.1474L16.0452 48.0453C16.0834 48.017 16.136 48.0268 16.1628 48.0671L16.5017 48.5777C16.5188 48.6035 16.5212 48.6356 16.5106 48.6628C16.5082 48.6736 16.5039 48.6842 16.4974 48.6939L16.1585 49.2045C16.1318 49.2448 16.0791 49.2546 16.041 49.2264L15.9027 49.1242C15.8645 49.096 15.8552 49.0405 15.8819 49.0002L16.1259 48.6326L15.8862 48.2714Z" fill="#945DED"/>
</g>
<defs>
<filter id="filter0_d_5698_59348" x="44.3994" y="34.0142" width="63.5605" height="37.8911" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dx="4" dy="4"/>
<feGaussianBlur stdDeviation="2"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.05 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_5698_59348"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_5698_59348" result="shape"/>
</filter>
<filter id="filter1_d_5698_59348" x="0" y="7.24585" width="27.542" height="28.6145" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dx="2" dy="3"/>
<feGaussianBlur stdDeviation="2"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.218229 0 0 0 0 0.144106 0 0 0 0 0.014989 0 0 0 0.06 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_5698_59348"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_5698_59348" result="shape"/>
</filter>
<filter id="filter2_d_5698_59348" x="0" y="34.0449" width="27.542" height="28.6145" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dx="2" dy="3"/>
<feGaussianBlur stdDeviation="2"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.218229 0 0 0 0 0.144106 0 0 0 0 0.014989 0 0 0 0.06 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_5698_59348"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_5698_59348" result="shape"/>
</filter>
<linearGradient id="paint0_linear_5698_59348" x1="51.7007" y1="39.9924" x2="51.7358" y2="48.9603" gradientUnits="userSpaceOnUse">
<stop stop-color="#E5CAF9"/>
<stop offset="1" stop-color="white" stop-opacity="0.05"/>
</linearGradient>
<linearGradient id="paint1_linear_5698_59348" x1="12.5907" y1="20.6147" x2="81.9671" y2="20.6147" gradientUnits="userSpaceOnUse">
<stop stop-color="white"/>
<stop offset="1" stop-color="#E5E5E9"/>
</linearGradient>
<linearGradient id="paint2_linear_5698_59348" x1="50.715" y1="68.5742" x2="50.715" y2="59.9237" gradientUnits="userSpaceOnUse">
<stop stop-color="#F9F9FB"/>
<stop offset="1" stop-color="#E9E8EC"/>
</linearGradient>
<linearGradient id="paint3_linear_5698_59348" x1="92.7129" y1="53.8069" x2="50.4491" y2="54.7096" gradientUnits="userSpaceOnUse">
<stop stop-color="#3370FF"/>
<stop offset="1" stop-color="#945DED"/>
</linearGradient>
<clipPath id="clip0_5698_59348">
<rect width="3.99837" height="4.12292" fill="white" transform="translate(20.9922 18.5532)"/>
</clipPath>
<clipPath id="clip1_5698_59348">
<rect width="11.1424" height="11.754" fill="white" transform="translate(6.66406 12.2844)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 20 KiB

View File

@ -55,6 +55,7 @@
:trigger-props="{ contentClass: `ms-cascader-popper-native ms-cascader-popper--${props.optionSize}` }" :trigger-props="{ contentClass: `ms-cascader-popper-native ms-cascader-popper--${props.optionSize}` }"
:multiple="props.multiple" :multiple="props.multiple"
allow-clear allow-clear
:load-more="props.loadMore"
:check-strictly="props.strictly" :check-strictly="props.strictly"
:max-tag-count="props.shouldCalculateMaxTag ? maxTagCount : 1" :max-tag-count="props.shouldCalculateMaxTag ? maxTagCount : 1"
:placeholder="props.placeholder" :placeholder="props.placeholder"
@ -124,6 +125,7 @@
valueKey?: string; valueKey?: string;
labelKey?: string; // labelKey labelKey?: string; // labelKey
shouldCalculateMaxTag?: boolean; // shouldCalculateMaxTag?: boolean; //
loadMore?: (option: CascaderOption, done: (children?: CascaderOption[]) => void) => void;
} }
const props = withDefaults(defineProps<MsCascaderProps>(), { const props = withDefaults(defineProps<MsCascaderProps>(), {

View File

@ -184,6 +184,15 @@
} }
} }
); );
watch(
() => props.filter,
(val) => {
if (val[props.dataIndex as string] && val[props.dataIndex as string]?.length) {
checkedList.value = val[props.dataIndex as string];
}
}
);
</script> </script>
<style scoped lang="less"> <style scoped lang="less">

View File

@ -54,6 +54,7 @@ export const defaultDetailCount: PassRateCountDetail = {
}, },
nextTriggerTime: 0, nextTriggerTime: 0,
status: 'PREPARED', status: 'PREPARED',
pass: false,
}; };
export const defaultExecuteForm = { export const defaultExecuteForm = {

View File

@ -1,10 +1,13 @@
import { cloneDeep } from 'lodash-es'; import { cloneDeep } from 'lodash-es';
import { commonConfig, toolTipConfig } from '@/config/testPlan'; import { commonConfig, toolTipConfig } from '@/config/testPlan';
import { addCommasToNumber } from '@/utils';
import type { ModuleCardItem } from '@/models/workbench/homePage'; import type { ModuleCardItem } from '@/models/workbench/homePage';
import { WorkCardEnum, WorkOverviewEnum, WorkOverviewIconEnum } from '@/enums/workbenchEnum'; import { RequestDefinitionStatus } from '@/enums/apiEnum';
import { LastReviewResult } from '@/enums/caseEnum';
import { ExecuteResultEnum } from '@/enums/taskCenter';
import { TestPlanStatusEnum } from '@/enums/testPlanEnum';
import { WorkCardEnum, WorkNavValueEnum, WorkOverviewEnum, WorkOverviewIconEnum } from '@/enums/workbenchEnum';
export const contentTabList: ModuleCardItem[] = [ export const contentTabList: ModuleCardItem[] = [
{ {
@ -297,4 +300,139 @@ export const commonRatePieOptions = {
}, },
}; };
// 对应标识的入参跳转
export const NAV_NAVIGATION: Record<WorkNavValueEnum, any> = {
[WorkNavValueEnum.CASE_REVIEWED]: { reviewStatus: [LastReviewResult.UN_PASS, LastReviewResult.PASS] }, // 有评审结果
[WorkNavValueEnum.CASE_UN_REVIEWED]: {
reviewStatus: [LastReviewResult.UN_REVIEWED, LastReviewResult.UNDER_REVIEWED, LastReviewResult.RE_REVIEWED], // 没有评审结果
},
[WorkNavValueEnum.CASE_REVIEWED_PASS]: { reviewStatus: [LastReviewResult.PASS] }, // 评审通过
[WorkNavValueEnum.CASE_REVIEWED_UN_PASS]: {
reviewStatus: [
LastReviewResult.UN_REVIEWED,
LastReviewResult.UNDER_REVIEWED,
LastReviewResult.RE_REVIEWED,
LastReviewResult.UN_PASS,
], // 评审不通过
},
[WorkNavValueEnum.CASE_ASSOCIATED]: {
associateCase: [true], // 已关联用例
},
[WorkNavValueEnum.CASE_NOT_ASSOCIATED]: {
associateCase: [false], // 没有关联用例
},
[WorkNavValueEnum.API_COUNT_DONE]: {
status: [RequestDefinitionStatus.DONE], // 接口数-已完成
},
[WorkNavValueEnum.API_COUNT_PROCESSING]: {
status: [RequestDefinitionStatus.PROCESSING], // 接口数-进行中
},
[WorkNavValueEnum.API_COUNT_DEBUGGING]: {
status: [RequestDefinitionStatus.DEBUGGING], // 接口数-联调中
},
[WorkNavValueEnum.API_COUNT_DEPRECATED]: {
status: [RequestDefinitionStatus.DEPRECATED], // 接口数-已废弃
},
[WorkNavValueEnum.API_COUNT_EXECUTE_FAKE_ERROR]: {
lastReportStatus: [ExecuteResultEnum.FAKE_ERROR], // 接口用例-执行结果-误报
},
[WorkNavValueEnum.API_COUNT_EXECUTE_SUCCESS]: {
lastReportStatus: [ExecuteResultEnum.SUCCESS], // 接口用例-执行结果-已通过
},
[WorkNavValueEnum.API_COUNT_EXECUTE_ERROR]: {
lastReportStatus: [ExecuteResultEnum.ERROR, ExecuteResultEnum.FAKE_ERROR], // 接口用例-执行结果-未通过
},
[WorkNavValueEnum.API_COUNT_EXECUTED_RESULT]: {
lastReportStatus: [ExecuteResultEnum.SUCCESS, ExecuteResultEnum.ERROR, ExecuteResultEnum.FAKE_ERROR], // 接口用例-有执行结果
},
[WorkNavValueEnum.API_COUNT_EXECUTED_NOT_RESULT]: {
lastReportStatus: [''], // 接口用例-无执行结果
},
[WorkNavValueEnum.SCENARIO_COUNT_EXECUTE_FAKE_ERROR]: {
lastReportStatus: [ExecuteResultEnum.FAKE_ERROR], // 场景用例-执行结果-误报
},
[WorkNavValueEnum.SCENARIO_COUNT_EXECUTE_SUCCESS]: {
lastReportStatus: [ExecuteResultEnum.SUCCESS], // 场景用例-执行结果-已通过
},
[WorkNavValueEnum.SCENARIO_COUNT_EXECUTE_ERROR]: {
lastReportStatus: [ExecuteResultEnum.ERROR, ExecuteResultEnum.FAKE_ERROR], // 场景用例-执行结果-未通过
},
[WorkNavValueEnum.SCENARIO_COUNT_EXECUTED_RESULT]: {
lastReportStatus: [ExecuteResultEnum.SUCCESS, ExecuteResultEnum.ERROR, ExecuteResultEnum.FAKE_ERROR], // 场景用例-有执行结果
},
/**
*
*/
[WorkNavValueEnum.API_COUNT_COVER]: {
apiDefinition: ['coverFrom'], // 接口覆盖
},
[WorkNavValueEnum.API_COUNT_UN_COVER]: {
apiDefinition: ['unCoverFrom'], // 接口未覆盖
},
/**
*
*/
[WorkNavValueEnum.API_CASE_COUNT_COVER]: {
apiCase: ['coverFrom'], // 接口覆盖
},
[WorkNavValueEnum.API_CASE_COUNT_UN_COVER]: {
apiCase: ['unCoverFrom'], // 接口未覆盖
},
/**
*
*/
[WorkNavValueEnum.SCENARIO_COVER]: {
apiScenario: ['coverFrom'], // 接口场景覆盖
},
[WorkNavValueEnum.SCENARIO_UN_COVER]: {
apiScenario: ['unCoverFrom'], // 接口场景未覆盖
},
[WorkNavValueEnum.SCENARIO_COUNT_EXECUTED_NOT_RESULT]: { lastReportStatus: [''] }, // 场景用例-无执行结果
/**
*
*/
[WorkNavValueEnum.TEST_PLAN_COMPLETED]: { status: [TestPlanStatusEnum.COMPLETED] }, // 测试计划-已完成
[WorkNavValueEnum.TEST_PLAN_UNDERWAY]: { status: [TestPlanStatusEnum.UNDERWAY] }, // 测试计划-进行中
[WorkNavValueEnum.TEST_PLAN_PREPARED]: { status: [TestPlanStatusEnum.PREPARED] }, // 测试计划-未开始
[WorkNavValueEnum.TEST_PLAN_ARCHIVED]: { status: [TestPlanStatusEnum.ARCHIVED] }, // 测试计划-已归档
[WorkNavValueEnum.TEST_PLAN_PASSED]: {
passed: ['PASSED'], // 测试计划-已通过
},
[WorkNavValueEnum.TEST_PLAN_NOT_PASS]: {
passed: ['NOT_PASSED'], // 测试计划-未通过
},
/**
*
*/
[WorkNavValueEnum.TEST_PLAN_LEGACY]: {
relatedToPlan: true,
unresolved: true,
},
[WorkNavValueEnum.TEST_PLAN_BUG]: {
relatedToPlan: true,
unresolved: false,
},
[WorkNavValueEnum.BUG_COUNT]: {},
[WorkNavValueEnum.BUG_COUNT_LEGACY]: {
unresolved: true,
},
[WorkNavValueEnum.BUG_COUNT_BY_ME]: {
createByMe: true,
unresolved: false,
},
[WorkNavValueEnum.BUG_COUNT_BY_ME_LEGACY]: {
createByMe: true,
unresolved: true,
},
[WorkNavValueEnum.BUG_HANDLE_BY_ME]: {
assignedToMe: true,
unresolved: false,
},
[WorkNavValueEnum.BUG_HANDLE_BY_ME_LEGACY]: {
assignedToMe: true,
unresolved: true,
},
};
export default {}; export default {};

View File

@ -34,4 +34,13 @@ export enum CaseLinkEnum {
FUNCTIONAL = 'FUNCTIONAL', FUNCTIONAL = 'FUNCTIONAL',
} }
// 评审结果
export enum LastReviewResult {
UN_REVIEWED = 'UN_REVIEWED', // 未评审
UNDER_REVIEWED = 'UNDER_REVIEWED', // 评审中
PASS = 'PASS', // 通过
UN_PASS = 'UN_PASS', // 未通过
RE_REVIEWED = 'RE_REVIEWED', // 重新评审
}
export default {}; export default {};

View File

@ -22,3 +22,9 @@ export enum PlanMinderCollectionType {
API = 'API', API = 'API',
SCENARIO = 'SCENARIO', SCENARIO = 'SCENARIO',
} }
export enum TestPlanStatusEnum {
PREPARED = 'PREPARED', // 未开始
UNDERWAY = 'UNDERWAY', // 进行中
COMPLETED = 'COMPLETED', // 已完成
ARCHIVED = 'ARCHIVED', // 已归档
}

View File

@ -25,6 +25,7 @@ export enum WorkCardEnum {
CREATE_BY_ME = 'CREATE_BY_ME', // 我创建的 CREATE_BY_ME = 'CREATE_BY_ME', // 我创建的
PROJECT_VIEW = 'PROJECT_VIEW', // 项目概览 PROJECT_VIEW = 'PROJECT_VIEW', // 项目概览
PROJECT_MEMBER_VIEW = 'PROJECT_MEMBER_VIEW', // 项目成员概览 PROJECT_MEMBER_VIEW = 'PROJECT_MEMBER_VIEW', // 项目成员概览
PROJECT_PLAN_VIEW = 'PROJECT_PLAN_VIEW', // 测试计划概览
/** /**
* functional * functional
@ -65,3 +66,48 @@ export enum FeatureEnum {
API_SCENARIO = 'API_SCENARIO', API_SCENARIO = 'API_SCENARIO',
BUG = 'BUG', BUG = 'BUG',
} }
// 工作台跳转KEY
export enum WorkNavValueEnum {
CASE_REVIEWED = 'CASE_REVIEWED', // 有评审结果
CASE_UN_REVIEWED = 'CASE_UN_REVIEWED', // 没有评审结果
CASE_REVIEWED_PASS = 'CASE_REVIEWED_PASS', // // 评审通过
CASE_REVIEWED_UN_PASS = 'CASE_REVIEWED_UN_PASS', // 评审不通过
CASE_ASSOCIATED = 'CASE_ASSOCIATED', // 已关联用例
CASE_NOT_ASSOCIATED = 'CASE_NOT_ASSOCIATED', // 没有关联用例
API_COUNT_DONE = 'API_COUNT_DONE', // 接口数已完成
API_COUNT_PROCESSING = 'API_COUNT_PROCESSING', // 接口数进行中
API_COUNT_DEBUGGING = 'API_COUNT_DEBUGGING', // 接口数联调中
API_COUNT_DEPRECATED = 'API_COUNT_DEPRECATED', // 接口数已废弃
API_COUNT_EXECUTE_FAKE_ERROR = 'API_COUNT_EXECUTE_FAKE_ERROR', // 接口用例执行结果误报
API_COUNT_EXECUTE_SUCCESS = 'API_COUNT_EXECUTE_SUCCESS', // 接口用例执行结果已通过
API_COUNT_EXECUTE_ERROR = 'API_COUNT_EXECUTE_ERROR', // 接口用例执行结果失败
API_COUNT_EXECUTED_RESULT = 'API_COUNT_EXECUTED_RESULT', // 接口用例-有执行结果
API_COUNT_EXECUTED_NOT_RESULT = 'API_COUNT_EXECUTED_NOT_RESULT', // 接口用例-无执行结果
API_COUNT_COVER = 'API_COUNT_COVER', // 接口数-覆盖
API_COUNT_UN_COVER = 'API_COUNT_UN_COVER', // 接口数-未覆盖
API_CASE_COUNT_COVER = 'API_CASE_COUNT_COVER', // 接口-用例-覆盖
API_CASE_COUNT_UN_COVER = 'API_CASE_COUNT_UN_COVER', // 接口数-用例-未覆盖
SCENARIO_COVER = 'SCENARIO_COVER', // 接口数-用例-未覆盖
SCENARIO_UN_COVER = 'SCENARIO_UN_COVER', // 接口数-用例-未覆盖
SCENARIO_COUNT_EXECUTE_FAKE_ERROR = 'SCENARIO_COUNT_EXECUTE_FAKE_ERROR', // 场景用例执行结果误报
SCENARIO_COUNT_EXECUTE_SUCCESS = 'SCENARIO_COUNT_EXECUTE_SUCCESS', // 场景用例执行结果已通过
SCENARIO_COUNT_EXECUTE_ERROR = 'SCENARIO_COUNT_EXECUTE_ERROR', // 场景用例执行结果失败
SCENARIO_COUNT_EXECUTED_RESULT = 'SCENARIO_COUNT_EXECUTED_RESULT', // 场景用例-有执行结果
SCENARIO_COUNT_EXECUTED_NOT_RESULT = 'SCENARIO_COUNT_EXECUTED_NOT_RESULT', // 场景用例-无执行结果
TEST_PLAN_COMPLETED = 'TEST_PLAN_COMPLETED', // 测试计划-已完成
TEST_PLAN_UNDERWAY = 'TEST_PLAN_UNDERWAY', // 测试计划-进行中
TEST_PLAN_PREPARED = 'TEST_PLAN_PREPARED', // 测试计划-未开始
TEST_PLAN_ARCHIVED = 'TEST_PLAN_ARCHIVED', // 测试计划-已归档
TEST_PLAN_PASSED = 'TEST_PLAN_PASSED', // 测试计划已通过
TEST_PLAN_NOT_PASS = 'TEST_PLAN_NOT_PASS', // 测试计划未通过通过
TEST_PLAN_BUG = 'TEST_PLAN_BUG', // 测试计划总缺陷
TEST_PLAN_LEGACY = 'TEST_PLAN_LEGACY', // 测试计划遗留缺陷
BUG_COUNT = 'BUG_COUNT', // 缺陷总数
BUG_COUNT_LEGACY = 'BUG_COUNT_LEGACY', // 缺陷遗留总数
BUG_COUNT_BY_ME = 'BUG_COUNT_BY_ME', // 我创建的缺陷总数
BUG_COUNT_BY_ME_LEGACY = 'BUG_COUNT_BY_ME_LEGACY', // 我创建的遗留缺陷总数
BUG_HANDLE_BY_ME = 'BUG_HANDLE_BY_ME', // 待我处理的遗留缺陷总数
BUG_HANDLE_BY_ME_LEGACY = 'BUG_HANDLE_BY_ME_LEGACY', // 待我处理的遗留缺陷总数
}

View File

@ -1,5 +1,5 @@
import { type MoveMode, TableQueryParams } from '@/models/common'; import { type MoveMode, TableQueryParams } from '@/models/common';
import { StatusType } from '@/enums/caseEnum'; import { LastReviewResult, StatusType } from '@/enums/caseEnum';
import { ReviewResult } from './caseReview'; import { ReviewResult } from './caseReview';
@ -75,6 +75,7 @@ export interface CaseManagementTable {
updateTime: string; updateTime: string;
deleteTime: string; deleteTime: string;
steps: string; steps: string;
status: LastReviewResult;
customFields: customFieldsItem[]; // 自定义字段集合 customFields: customFieldsItem[]; // 自定义字段集合
[key: string]: any; [key: string]: any;
} }

View File

@ -262,6 +262,7 @@ export interface PassRateCountDetail {
}; };
nextTriggerTime: number; nextTriggerTime: number;
status: planStatusType; status: planStatusType;
pass: boolean; // 是否通过
} }
// 执行历史 // 执行历史

View File

@ -28,6 +28,7 @@ export interface SelectedCardItem {
projectIds: string[]; projectIds: string[];
handleUsers: string[]; handleUsers: string[];
selectAll: boolean; // 是否全选项目 selectAll: boolean; // 是否全选项目
planId: string; // 测试计划id
} }
// 查询入参 // 查询入参
@ -118,3 +119,12 @@ export interface ApiCoverageData {
coverWithApiScenario: number; // 覆盖了 API 场景的数量 coverWithApiScenario: number; // 覆盖了 API 场景的数量
scenarioCoverage: string; // API 场景覆盖率 scenarioCoverage: string; // API 场景覆盖率
} }
export interface StatusValueItem {
name: string;
value: number;
status?: string;
route?: string;
tab?: string; // api | case 列表
selected?: boolean;
}

View File

@ -269,6 +269,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { useRoute } from 'vue-router';
import { FormInstance, Message } from '@arco-design/web-vue'; import { FormInstance, Message } from '@arco-design/web-vue';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
@ -300,6 +301,7 @@
sortDefinition, sortDefinition,
updateDefinition, updateDefinition,
} from '@/api/modules/api-test/management'; } from '@/api/modules/api-test/management';
import { NAV_NAVIGATION } from '@/config/workbench';
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';
@ -318,6 +320,7 @@
import { TagUpdateTypeEnum } from '@/enums/commonEnum'; import { TagUpdateTypeEnum } from '@/enums/commonEnum';
import { TableKeyEnum } from '@/enums/tableEnum'; import { TableKeyEnum } from '@/enums/tableEnum';
import { FilterRemoteMethodsEnum, FilterSlotNameEnum } from '@/enums/tableFilterEnum'; import { FilterRemoteMethodsEnum, FilterSlotNameEnum } from '@/enums/tableFilterEnum';
import { WorkNavValueEnum } from '@/enums/workbenchEnum';
import { apiStatusOptions } from '@/views/api-test/components/config'; import { apiStatusOptions } from '@/views/api-test/components/config';
@ -351,7 +354,7 @@
const { t } = useI18n(); const { t } = useI18n();
const { openModal } = useModal(); const { openModal } = useModal();
const tableStore = useTableStore(); const tableStore = useTableStore();
const route = useRoute();
const folderTreePathMap = inject<MsTreeNodeData[]>('folderTreePathMap'); const folderTreePathMap = inject<MsTreeNodeData[]>('folderTreePathMap');
const refreshModuleTree: (() => Promise<any>) | undefined = inject('refreshModuleTree'); const refreshModuleTree: (() => Promise<any>) | undefined = inject('refreshModuleTree');
const refreshModuleTreeCount: ((data: ApiDefinitionGetModuleParams) => Promise<any>) | undefined = const refreshModuleTreeCount: ((data: ApiDefinitionGetModuleParams) => Promise<any>) | undefined =
@ -624,12 +627,22 @@
async function loadApiList(hasRefreshTree: boolean) { async function loadApiList(hasRefreshTree: boolean) {
const moduleIds = await getModuleIds(); const moduleIds = await getModuleIds();
let filterParams = {
...propsRes.value.filter,
};
if (route.query.home) {
filterParams = {
...propsRes.value.filter,
...NAV_NAVIGATION[route.query.home as WorkNavValueEnum],
};
}
const params = { const params = {
keyword: keyword.value, keyword: keyword.value,
projectId: appStore.currentProjectId, projectId: appStore.currentProjectId,
moduleIds, moduleIds,
protocols: isAdvancedSearchMode.value ? protocolList.value.map((item) => item.protocol) : props.selectedProtocols, protocols: isAdvancedSearchMode.value ? protocolList.value.map((item) => item.protocol) : props.selectedProtocols,
filter: propsRes.value.filter, filter: filterParams,
viewId: viewId.value, viewId: viewId.value,
combineSearch: advanceFilter, combineSearch: advanceFilter,
}; };
@ -637,7 +650,7 @@
if (!hasRefreshTree && typeof refreshModuleTreeCount === 'function' && !isAdvancedSearchMode.value) { if (!hasRefreshTree && typeof refreshModuleTreeCount === 'function' && !isAdvancedSearchMode.value) {
refreshModuleTreeCount({ refreshModuleTreeCount({
keyword: keyword.value, keyword: keyword.value,
filter: propsRes.value.filter, filter: filterParams,
moduleIds: [], moduleIds: [],
protocols: props.selectedProtocols, protocols: props.selectedProtocols,
projectId: appStore.currentProjectId, projectId: appStore.currentProjectId,

View File

@ -349,6 +349,7 @@
updateCasePriority, updateCasePriority,
updateCaseStatus, updateCaseStatus,
} from '@/api/modules/api-test/management'; } from '@/api/modules/api-test/management';
import { NAV_NAVIGATION } from '@/config/workbench';
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';
@ -368,6 +369,7 @@
import { ReportEnum } from '@/enums/reportEnum'; import { ReportEnum } from '@/enums/reportEnum';
import { TableKeyEnum } from '@/enums/tableEnum'; import { TableKeyEnum } from '@/enums/tableEnum';
import { FilterRemoteMethodsEnum, FilterSlotNameEnum } from '@/enums/tableFilterEnum'; import { FilterRemoteMethodsEnum, FilterSlotNameEnum } from '@/enums/tableFilterEnum';
import { WorkNavValueEnum } from '@/enums/workbenchEnum';
import { import {
casePriorityOptions, casePriorityOptions,
@ -663,13 +665,22 @@
async function loadCaseList() { async function loadCaseList() {
const selectModules = await getModuleIds(); const selectModules = await getModuleIds();
let filterParams = { ...propsRes.value.filter };
if (route.query.home) {
filterParams = {
...propsRes.value.filter,
...NAV_NAVIGATION[route.query.home as WorkNavValueEnum],
};
}
const params = { const params = {
apiDefinitionId: props.apiDetail?.id, apiDefinitionId: props.apiDetail?.id,
keyword: keyword.value, keyword: keyword.value,
projectId: appStore.currentProjectId, projectId: appStore.currentProjectId,
moduleIds: selectModules, moduleIds: selectModules,
protocols: isAdvancedSearchMode.value ? protocolList.value.map((item) => item.protocol) : props.selectedProtocols, protocols: isAdvancedSearchMode.value ? protocolList.value.map((item) => item.protocol) : props.selectedProtocols,
filter: propsRes.value.filter, filter: filterParams,
viewId: viewId.value, viewId: viewId.value,
combineSearch: advanceFilter, combineSearch: advanceFilter,
}; };

View File

@ -523,6 +523,7 @@
updateScenarioPro, updateScenarioPro,
updateScenarioStatus, updateScenarioStatus,
} from '@/api/modules/api-test/scenario'; } from '@/api/modules/api-test/scenario';
import { NAV_NAVIGATION } from '@/config/workbench';
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';
@ -543,6 +544,7 @@
import { ReportEnum, ReportStatus } from '@/enums/reportEnum'; import { ReportEnum, ReportStatus } from '@/enums/reportEnum';
import { TableKeyEnum } from '@/enums/tableEnum'; import { TableKeyEnum } from '@/enums/tableEnum';
import { FilterRemoteMethodsEnum, FilterSlotNameEnum } from '@/enums/tableFilterEnum'; import { FilterRemoteMethodsEnum, FilterSlotNameEnum } from '@/enums/tableFilterEnum';
import { WorkNavValueEnum } from '@/enums/workbenchEnum';
import { casePriorityOptions } from '@/views/api-test/components/config'; import { casePriorityOptions } from '@/views/api-test/components/config';
import { scenarioStatusOptions } from '@/views/api-test/scenario/components/config'; import { scenarioStatusOptions } from '@/views/api-test/scenario/components/config';
@ -973,10 +975,19 @@
async function loadScenarioList(refreshTreeCount?: boolean) { async function loadScenarioList(refreshTreeCount?: boolean) {
const moduleIds = await getModuleIds(); const moduleIds = await getModuleIds();
let filterParams = { ...propsRes.value.filter };
if (route.query.home) {
filterParams = {
...propsRes.value.filter,
...NAV_NAVIGATION[route.query.home as WorkNavValueEnum],
};
}
const params = { const params = {
keyword: keyword.value, keyword: keyword.value,
projectId: appStore.currentProjectId, projectId: appStore.currentProjectId,
moduleIds, moduleIds,
filter: filterParams,
}; };
setLoadListParams({ ...params, viewId: viewId.value, combineSearch: advanceFilter }); setLoadListParams({ ...params, viewId: viewId.value, combineSearch: advanceFilter });
await loadList(); await loadList();

View File

@ -199,6 +199,7 @@
syncBugEnterprise, syncBugEnterprise,
} from '@/api/modules/bug-management'; } from '@/api/modules/bug-management';
import { getPlatformOptions } from '@/api/modules/project-management/menuManagement'; import { getPlatformOptions } from '@/api/modules/project-management/menuManagement';
import { NAV_NAVIGATION } from '@/config/workbench';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import useModal from '@/hooks/useModal'; import useModal from '@/hooks/useModal';
import router from '@/router'; import router from '@/router';
@ -213,6 +214,7 @@
import { MenuEnum } from '@/enums/commonEnum'; import { MenuEnum } from '@/enums/commonEnum';
import { RouteEnum } from '@/enums/routeEnum'; import { RouteEnum } from '@/enums/routeEnum';
import { TableKeyEnum } from '@/enums/tableEnum'; import { TableKeyEnum } from '@/enums/tableEnum';
import { WorkNavValueEnum } from '@/enums/workbenchEnum';
import { makeColumns } from '@/views/case-management/caseManagementFeature/components/utils'; import { makeColumns } from '@/views/case-management/caseManagementFeature/components/utils';
@ -481,11 +483,19 @@
const isAdvancedSearchMode = computed(() => msAdvanceFilterRef.value?.isAdvancedSearchMode); const isAdvancedSearchMode = computed(() => msAdvanceFilterRef.value?.isAdvancedSearchMode);
function initTableParams() { function initTableParams() {
let workFilterParams = {};
if (route.query.home) {
workFilterParams = {
...NAV_NAVIGATION[route.query.home as WorkNavValueEnum],
};
}
return { return {
keyword: keyword.value, keyword: keyword.value,
projectId: projectId.value, projectId: projectId.value,
viewId: viewId.value, viewId: viewId.value,
combineSearch: advanceFilter, combineSearch: advanceFilter,
...workFilterParams,
}; };
} }

View File

@ -429,6 +429,7 @@
updateCaseRequest, updateCaseRequest,
} from '@/api/modules/case-management/featureCase'; } from '@/api/modules/case-management/featureCase';
import { getCaseRelatedInfo } from '@/api/modules/project-management/menuManagement'; import { getCaseRelatedInfo } from '@/api/modules/project-management/menuManagement';
import { NAV_NAVIGATION } from '@/config/workbench';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import useModal from '@/hooks/useModal'; import useModal from '@/hooks/useModal';
import useWebsocket from '@/hooks/useWebsocket'; import useWebsocket from '@/hooks/useWebsocket';
@ -463,6 +464,7 @@
import { CaseManagementRouteEnum, RouteEnum } from '@/enums/routeEnum'; import { CaseManagementRouteEnum, RouteEnum } from '@/enums/routeEnum';
import { ColumnEditTypeEnum, TableKeyEnum } from '@/enums/tableEnum'; import { ColumnEditTypeEnum, TableKeyEnum } from '@/enums/tableEnum';
import { FilterRemoteMethodsEnum, FilterSlotNameEnum } from '@/enums/tableFilterEnum'; import { FilterRemoteMethodsEnum, FilterSlotNameEnum } from '@/enums/tableFilterEnum';
import { WorkNavValueEnum } from '@/enums/workbenchEnum';
import { executionResultMap, getCaseLevels, getTableFields, statusIconMap } from './utils'; import { executionResultMap, getCaseLevels, getTableFields, statusIconMap } from './utils';
import { LabelValue } from '@arco-design/web-vue/es/tree-select/interface'; import { LabelValue } from '@arco-design/web-vue/es/tree-select/interface';
@ -1021,6 +1023,15 @@
} }
} }
let filterParams = { ...propsRes.value.filter };
if (route.query.home) {
filterParams = {
...propsRes.value.filter,
...NAV_NAVIGATION[route.query.home as WorkNavValueEnum],
};
}
return { return {
moduleIds, moduleIds,
projectId: currentProjectId.value, projectId: currentProjectId.value,
@ -1028,6 +1039,7 @@
selectAll: batchParams.value.selectAll, selectAll: batchParams.value.selectAll,
selectIds: batchParams.value.selectedIds || [], selectIds: batchParams.value.selectedIds || [],
keyword: keyword.value, keyword: keyword.value,
filter: filterParams,
}; };
} }
// //

View File

@ -33,7 +33,7 @@
<MsIcon <MsIcon
:type="statusIconMap[record.status]?.icon || ''" :type="statusIconMap[record.status]?.icon || ''"
class="mr-1" class="mr-1"
:class="[statusIconMap[record.status].color] || ''" :class="statusIconMap[record.status]?.color"
></MsIcon> ></MsIcon>
<span>{{ statusIconMap[record.status]?.statusText || '' }} </span> <span>{{ statusIconMap[record.status]?.statusText || '' }} </span>
</template> </template>

View File

@ -11,7 +11,7 @@ import { hasAnyPermission } from '@/utils/permission';
import type { AssociatedList, CustomAttributes } from '@/models/caseManagement/featureCase'; import type { AssociatedList, CustomAttributes } from '@/models/caseManagement/featureCase';
import { ModuleTreeNode } from '@/models/common'; import { ModuleTreeNode } from '@/models/common';
import { StatusType } from '@/enums/caseEnum'; import { LastReviewResult, StatusType } from '@/enums/caseEnum';
const { t } = useI18n(); const { t } = useI18n();
@ -21,34 +21,42 @@ export interface ReviewResult {
statusText: string; statusText: string;
} }
// 图标评审结果 TODO:TS 类型 key // 图标评审结果
export const statusIconMap: Record<string, any> = { export const statusIconMap: Record<
UN_REVIEWED: { string,
key: 'UN_REVIEWED', {
key: LastReviewResult;
icon: StatusType;
statusText: string;
color: string;
}
> = {
[LastReviewResult.UN_REVIEWED]: {
key: LastReviewResult.UN_REVIEWED,
icon: StatusType.UN_REVIEWED, icon: StatusType.UN_REVIEWED,
statusText: t('caseManagement.featureCase.notReviewed'), statusText: t('caseManagement.featureCase.notReviewed'),
color: 'text-[var(--color-text-brand)]', color: 'text-[var(--color-text-brand)]',
}, },
UNDER_REVIEWED: { [LastReviewResult.UNDER_REVIEWED]: {
key: 'UNDER_REVIEWED', key: LastReviewResult.UNDER_REVIEWED,
icon: StatusType.UNDER_REVIEWED, icon: StatusType.UNDER_REVIEWED,
statusText: t('caseManagement.featureCase.reviewing'), statusText: t('caseManagement.featureCase.reviewing'),
color: 'text-[rgb(var(--link-6))]', color: 'text-[rgb(var(--link-6))]',
}, },
PASS: { [LastReviewResult.PASS]: {
key: 'PASS', key: LastReviewResult.PASS,
icon: StatusType.PASS, icon: StatusType.PASS,
statusText: t('caseManagement.featureCase.passed'), statusText: t('caseManagement.featureCase.passed'),
color: '', color: '',
}, },
UN_PASS: { [LastReviewResult.UN_PASS]: {
key: 'UN_PASS', key: LastReviewResult.UN_PASS,
icon: StatusType.UN_PASS, icon: StatusType.UN_PASS,
statusText: t('caseManagement.featureCase.notPass'), statusText: t('caseManagement.featureCase.notPass'),
color: '', color: '',
}, },
RE_REVIEWED: { [LastReviewResult.RE_REVIEWED]: {
key: 'RE_REVIEWED', key: LastReviewResult.RE_REVIEWED,
icon: StatusType.RE_REVIEWED, icon: StatusType.RE_REVIEWED,
statusText: t('caseManagement.featureCase.retrial'), statusText: t('caseManagement.featureCase.retrial'),
color: 'text-[rgb(var(--warning-6))]', color: 'text-[rgb(var(--warning-6))]',

View File

@ -167,6 +167,9 @@
{{ `${defaultCountDetailMap[record.id]?.passRate ? defaultCountDetailMap[record.id].passRate : '-'}%` }} {{ `${defaultCountDetailMap[record.id]?.passRate ? defaultCountDetailMap[record.id].passRate : '-'}%` }}
</div> </div>
</template> </template>
<template #executeResult="{ record }">
{{ getExecuteResult(record.id) }}
</template>
<template #passRateTitleSlot="{ columnConfig }"> <template #passRateTitleSlot="{ columnConfig }">
<div class="flex items-center text-[var(--color-text-3)]"> <div class="flex items-center text-[var(--color-text-3)]">
{{ t(columnConfig.title as string) }} {{ t(columnConfig.title as string) }}
@ -194,19 +197,20 @@
v-if="isShowExecuteButton(record) && hasAnyPermission(['PROJECT_TEST_PLAN:READ+EXECUTE'])" v-if="isShowExecuteButton(record) && hasAnyPermission(['PROJECT_TEST_PLAN:READ+EXECUTE'])"
direction="vertical" direction="vertical"
:margin="8" :margin="8"
></a-divider> />
<MsButton <MsButton
v-if="hasAnyPermission(['PROJECT_TEST_PLAN:READ+UPDATE']) && getStatus(record.id) !== 'ARCHIVED'" v-if="hasAnyPermission(['PROJECT_TEST_PLAN:READ+UPDATE']) && getStatus(record.id) !== 'ARCHIVED'"
class="!mx-0" class="!mx-0"
@click="emit('edit', record)" @click="emit('edit', record)"
>{{ t('common.edit') }}</MsButton
> >
{{ t('common.edit') }}
</MsButton>
<a-divider <a-divider
v-if="hasAnyPermission(['PROJECT_TEST_PLAN:READ+UPDATE']) && getStatus(record.id) !== 'ARCHIVED'" v-if="hasAnyPermission(['PROJECT_TEST_PLAN:READ+UPDATE']) && getStatus(record.id) !== 'ARCHIVED'"
direction="vertical" direction="vertical"
:margin="8" :margin="8"
></a-divider> />
<MsButton <MsButton
v-if=" v-if="
@ -216,8 +220,9 @@
" "
class="!mx-0" class="!mx-0"
@click="copyTestPlanOrGroup(record.id)" @click="copyTestPlanOrGroup(record.id)"
>{{ t('common.copy') }}</MsButton
> >
{{ t('common.copy') }}
</MsButton>
<a-divider <a-divider
v-if=" v-if="
!isShowExecuteButton(record) && !isShowExecuteButton(record) &&
@ -226,7 +231,7 @@
" "
direction="vertical" direction="vertical"
:margin="8" :margin="8"
></a-divider> />
<MsTableMoreAction :list="getMoreActions(record)" @select="handleMoreActionSelect($event, record)" /> <MsTableMoreAction :list="getMoreActions(record)" @select="handleMoreActionSelect($event, record)" />
</div> </div>
</template> </template>
@ -349,6 +354,7 @@
import caseCountPopper from './caseCountPopper.vue'; import caseCountPopper from './caseCountPopper.vue';
import ScheduledModal from './scheduledModal.vue'; import ScheduledModal from './scheduledModal.vue';
import StatusProgress from './statusProgress.vue'; import StatusProgress from './statusProgress.vue';
import ExecutionStatus from '@/views/api-test/report/component/reportStatus.vue';
import PlanExpandRow from '@/views/test-plan/testPlan/components/planExpandRow.vue'; import PlanExpandRow from '@/views/test-plan/testPlan/components/planExpandRow.vue';
import { import {
@ -371,6 +377,7 @@
testPlanAndGroupCopy, testPlanAndGroupCopy,
updateTestPlan, updateTestPlan,
} from '@/api/modules/test-plan/testPlan'; } from '@/api/modules/test-plan/testPlan';
import { NAV_NAVIGATION } from '@/config/workbench';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import useModal from '@/hooks/useModal'; import useModal from '@/hooks/useModal';
import { useAppStore, useTableStore } from '@/store'; import { useAppStore, useTableStore } from '@/store';
@ -398,7 +405,8 @@
import { ColumnEditTypeEnum, TableKeyEnum } from '@/enums/tableEnum'; import { ColumnEditTypeEnum, TableKeyEnum } from '@/enums/tableEnum';
import { FilterSlotNameEnum } from '@/enums/tableFilterEnum'; import { FilterSlotNameEnum } from '@/enums/tableFilterEnum';
import { TaskCenterEnum } from '@/enums/taskCenter'; import { TaskCenterEnum } from '@/enums/taskCenter';
import { testPlanTypeEnum } from '@/enums/testPlanEnum'; import { TestPlanStatusEnum, testPlanTypeEnum } from '@/enums/testPlanEnum';
import { WorkNavValueEnum } from '@/enums/workbenchEnum';
import { planStatusOptions } from '../config'; import { planStatusOptions } from '../config';
import { getModules } from '@/views/case-management/caseManagementFeature/components/utils'; import { getModules } from '@/views/case-management/caseManagementFeature/components/utils';
@ -489,6 +497,27 @@
width: 200, width: 200,
showDrag: true, showDrag: true,
}, },
{
title: 'testPlan.testPlanIndex.executionResult',
slotName: 'executeResult',
dataIndex: 'passed',
filterConfig: {
options: [
{
value: 'PASSED',
label: t('common.pass'),
},
{
value: 'NOT_PASSED',
label: t('common.unPass'),
},
],
filterSlotName: FilterSlotNameEnum.API_TEST_CASE_API_LAST_EXECUTE_STATUS,
},
width: 150,
showInTable: true,
showDrag: true,
},
{ {
title: 'testPlan.testPlanIndex.useCount', title: 'testPlan.testPlanIndex.useCount',
slotName: 'functionalCaseCount', slotName: 'functionalCaseCount',
@ -870,6 +899,19 @@
if (isSetDefaultKey) { if (isSetDefaultKey) {
moduleIds = []; moduleIds = [];
} }
let filterParams = { ...propsRes.value.filter };
if (route.query.home) {
if (route.query.home === WorkNavValueEnum.TEST_PLAN_ARCHIVED) {
viewId.value = 'archived';
} else {
filterParams = {
...propsRes.value.filter,
...NAV_NAVIGATION[route.query.home as WorkNavValueEnum],
};
}
}
return { return {
type: showType.value, type: showType.value,
moduleIds, moduleIds,
@ -878,7 +920,7 @@
selectAll: !!batchParams.value?.selectAll, selectAll: !!batchParams.value?.selectAll,
selectIds: batchParams.value.selectedIds || [], selectIds: batchParams.value.selectedIds || [],
keyword: keyword.value, keyword: keyword.value,
filter: propsRes.value.filter, filter: filterParams,
}; };
} }
@ -1391,6 +1433,15 @@
showScheduledTaskModal.value = true; showScheduledTaskModal.value = true;
} }
function getExecuteResult(id: string) {
const isPrepared = defaultCountDetailMap.value[id]?.status === TestPlanStatusEnum.PREPARED;
if (isPrepared) {
return '-';
}
return defaultCountDetailMap.value[id]?.pass ? t('common.pass') : t('common.unPass');
}
const showStatusDeleteModal = ref<boolean>(false); const showStatusDeleteModal = ref<boolean>(false);
// : // :

View File

@ -34,7 +34,10 @@
<div class="case-count-item"> <div class="case-count-item">
<div v-for="(ele, index) of apiCountValue" :key="index" class="case-count-item-content"> <div v-for="(ele, index) of apiCountValue" :key="index" class="case-count-item-content">
<div class="case-count-item-title">{{ ele.name }}</div> <div class="case-count-item-title">{{ ele.name }}</div>
<div class="case-count-item-number"> <div
:class="`case-count-item-number ${index !== 0 ? 'cursor-pointer text-[rgb(var(--primary-5))]' : ''}`"
@click="goNavigation"
>
{{ hasPermission ? addCommasToNumber(ele.count as number) : '-' }} {{ hasPermission ? addCommasToNumber(ele.count as number) : '-' }}
</div> </div>
</div> </div>
@ -43,17 +46,31 @@
<div class="case-ratio-wrapper mt-[16px]"> <div class="case-ratio-wrapper mt-[16px]">
<div class="case-ratio-item"> <div class="case-ratio-item">
<RatioPie <RatioPie
:value-key="props.item.key"
:has-permission="hasPermission" :has-permission="hasPermission"
:loading="loading" :loading="loading"
:data="coverData" :data="coverData"
:project-id="projectId"
:rate-config="coverTitleConfig" :rate-config="coverTitleConfig"
/> />
</div> </div>
<div class="case-ratio-item"> <div class="case-ratio-item">
<RatioPie :has-permission="hasPermission" :data="caseExecuteData" :rate-config="executeTitleConfig" /> <RatioPie
:value-key="props.item.key"
:project-id="projectId"
:has-permission="hasPermission"
:data="caseExecuteData"
:rate-config="executeTitleConfig"
/>
</div> </div>
<div class="case-ratio-item"> <div class="case-ratio-item">
<RatioPie :has-permission="hasPermission" :data="casePassData" :rate-config="casePassTitleConfig" /> <RatioPie
:value-key="props.item.key"
:project-id="projectId"
:has-permission="hasPermission"
:data="casePassData"
:rate-config="casePassTitleConfig"
/>
</div> </div>
</div> </div>
</div> </div>
@ -74,11 +91,17 @@
import { workApiCaseCountDetail, workApiCountCoverRage, workScenarioCaseCountDetail } from '@/api/modules/workbench'; import { workApiCaseCountDetail, workApiCountCoverRage, workScenarioCaseCountDetail } from '@/api/modules/workbench';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import useOpenNewPage from '@/hooks/useOpenNewPage';
import useAppStore from '@/store/modules/app'; import useAppStore from '@/store/modules/app';
import { addCommasToNumber } from '@/utils'; import { addCommasToNumber } from '@/utils';
import type { ApiCoverageData, SelectedCardItem, TimeFormParams } from '@/models/workbench/homePage'; import type { ApiCoverageData, SelectedCardItem, StatusValueItem, TimeFormParams } from '@/models/workbench/homePage';
import { WorkCardEnum } from '@/enums/workbenchEnum'; import { ApiTestRouteEnum } from '@/enums/routeEnum';
import { WorkCardEnum, WorkNavValueEnum } from '@/enums/workbenchEnum';
import { routeNavigationMap } from '../utils';
const { openNewPage } = useOpenNewPage();
const appStore = useAppStore(); const appStore = useAppStore();
@ -131,7 +154,7 @@
}) })
); );
const initCoverRate = [ const initCoverRate: StatusValueItem[] = [
{ {
value: 0, value: 0,
name: t('workbench.homePage.coverRate'), name: t('workbench.homePage.coverRate'),
@ -147,8 +170,8 @@
]; ];
// //
const coverData = ref<{ name: string; value: number }[]>(cloneDeep(initCoverRate)); const coverData = ref<StatusValueItem[]>(cloneDeep(initCoverRate));
const caseExecuteData = ref<{ name: string; value: number }[]>([ const caseExecuteData = ref<StatusValueItem[]>([
{ {
value: 0, value: 0,
name: t('common.unExecute'), name: t('common.unExecute'),
@ -159,7 +182,7 @@
}, },
]); ]);
const casePassData = ref<{ name: string; value: number }[]>([ const casePassData = ref<StatusValueItem[]>([
{ {
value: 0, value: 0,
name: t('workbench.homePage.notPass'), name: t('workbench.homePage.notPass'),
@ -223,7 +246,8 @@
const coverWithCase = props.item.key === WorkCardEnum.API_CASE_COUNT ? coverWithApiCase : coverWithApiScenario; const coverWithCase = props.item.key === WorkCardEnum.API_CASE_COUNT ? coverWithApiCase : coverWithApiScenario;
coverData.value = cloneDeep(initCoverRate); coverData.value = cloneDeep(initCoverRate);
coverData.value = [
const coverList: StatusValueItem[] = [
{ {
value: Number(coverage.split('%')[0]), value: Number(coverage.split('%')[0]),
name: t('workbench.homePage.coverRate'), name: t('workbench.homePage.coverRate'),
@ -237,6 +261,18 @@
name: t('workbench.homePage.covered'), name: t('workbench.homePage.covered'),
}, },
]; ];
coverData.value = coverList.map((e, i) => {
if (i === 0) {
return {
...e,
};
}
return {
...e,
route: routeNavigationMap[props.item.key].cover?.route,
status: routeNavigationMap[props.item.key].cover?.status[i - 1],
};
});
} }
async function initApiCountRate() { async function initApiCountRate() {
@ -252,6 +288,26 @@
} }
} }
const generateCaseData = (dataList: { name: string; count: number }[], rateData: Record<string, any>) => {
return (dataList || []).map((e, i) => {
const baseItem = {
...e,
value: e.count,
};
if (i === 0) {
return baseItem;
}
return {
...baseItem,
route: rateData?.route,
status: rateData?.status?.[i - 1],
...(props.item.key === WorkCardEnum.API_CASE_COUNT && { tab: 'case' }),
};
});
};
const showSkeleton = ref(false); const showSkeleton = ref(false);
async function initApiOrScenarioCount() { async function initApiOrScenarioCount() {
@ -279,19 +335,13 @@
hasPermission.value = detail.errorCode !== 109001; hasPermission.value = detail.errorCode !== 109001;
caseExecuteData.value = (detail.statusStatisticsMap?.execRate || []).map((e) => { const execRateData = detail.statusStatisticsMap?.execRate || [];
return { const executeRateConfig = routeNavigationMap[props.item.key]?.executeRate;
...e, const passRateData = detail.statusStatisticsMap?.passRate || [];
value: e.count, const passRateConfig = routeNavigationMap[props.item.key]?.passRate;
};
});
casePassData.value = (detail.statusStatisticsMap?.passRate || []).map((e) => { caseExecuteData.value = generateCaseData(execRateData, executeRateConfig);
return { casePassData.value = generateCaseData(passRateData, passRateConfig);
...e,
value: e.count,
};
});
if (hasPermission.value) { if (hasPermission.value) {
// //
@ -314,6 +364,26 @@
}); });
} }
function goNavigation() {
const route =
props.item.key === WorkCardEnum.API_CASE_COUNT
? ApiTestRouteEnum.API_TEST_MANAGEMENT
: ApiTestRouteEnum.API_TEST_SCENARIO;
const status =
props.item.key === WorkCardEnum.API_CASE_COUNT
? WorkNavValueEnum.API_COUNT_EXECUTE_FAKE_ERROR
: WorkNavValueEnum.SCENARIO_COUNT_EXECUTE_FAKE_ERROR;
const params: { pId: string; home: string; tab?: string } = {
pId: projectId.value,
home: status,
};
if (props.item.key === WorkCardEnum.API_CASE_COUNT) {
params.tab = 'case';
}
openNewPage(route, params);
}
const isInit = ref<boolean>(true); const isInit = ref<boolean>(true);
onMounted(() => { onMounted(() => {
@ -401,7 +471,6 @@
} }
.case-count-item-number { .case-count-item-number {
font-size: 20px; font-size: 20px;
color: var(--color-text-1);
@apply font-medium; @apply font-medium;
} }
} }

View File

@ -1,6 +1,6 @@
<template> <template>
<div class="card-wrapper card-min-height"> <div class="card-wrapper card-min-height">
<CardSkeleton v-if="showSkeleton" :show-skeleton="showSkeleton" /> <CardSkeleton v-if="showSkeleton" :show-line-number="2" :show-skeleton="showSkeleton" />
<div v-else> <div v-else>
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<a-tooltip :content="t(props.item.label)" position="tl"> <a-tooltip :content="t(props.item.label)" position="tl">

View File

@ -30,6 +30,7 @@
> >
<PassRatePie <PassRatePie
:tooltip-text="tabItem.tooltip" :tooltip-text="tabItem.tooltip"
:project-id="projectId"
:options="tabItem.options" :options="tabItem.options"
:loading="tabItem.value === 'cover' ? loading : undefined" :loading="tabItem.value === 'cover' ? loading : undefined"
:has-permission="hasPermission" :has-permission="hasPermission"

View File

@ -127,6 +127,7 @@
WorkCardEnum.PROJECT_VIEW, WorkCardEnum.PROJECT_VIEW,
WorkCardEnum.PROJECT_MEMBER_VIEW, WorkCardEnum.PROJECT_MEMBER_VIEW,
WorkCardEnum.CREATE_BY_ME, WorkCardEnum.CREATE_BY_ME,
WorkCardEnum.PROJECT_PLAN_VIEW,
]; ];
const defaultAllProjectType = [WorkCardEnum.PROJECT_VIEW, WorkCardEnum.CREATE_BY_ME]; const defaultAllProjectType = [WorkCardEnum.PROJECT_VIEW, WorkCardEnum.CREATE_BY_ME];
@ -145,6 +146,7 @@
projectIds: defaultAllProjectType.includes(item.value) ? [] : [appStore.currentProjectId], projectIds: defaultAllProjectType.includes(item.value) ? [] : [appStore.currentProjectId],
handleUsers: [], handleUsers: [],
selectAll: !![WorkCardEnum.PROJECT_VIEW, WorkCardEnum.CREATE_BY_ME].includes(item.value), selectAll: !![WorkCardEnum.PROJECT_VIEW, WorkCardEnum.CREATE_BY_ME].includes(item.value),
planId: '',
}; };
selectedCardList.value.push(newCard); selectedCardList.value.push(newCard);
} }

View File

@ -77,6 +77,12 @@
description: 'workbench.homePage.projectOverviewDesc', description: 'workbench.homePage.projectOverviewDesc',
img: 'project-overview-img', img: 'project-overview-img',
}, },
{
label: 'workbench.homePage.testPlanOverview',
value: WorkCardEnum.PROJECT_PLAN_VIEW,
description: 'workbench.homePage.testPlanOverviewDesc',
img: 'test-plan-overview',
},
{ {
label: 'workbench.homePage.staffOverview', label: 'workbench.homePage.staffOverview',
value: WorkCardEnum.PROJECT_MEMBER_VIEW, value: WorkCardEnum.PROJECT_MEMBER_VIEW,

View File

@ -1,24 +1,28 @@
<template> <template>
<a-skeleton v-if="props.showSkeleton" :loading="props.showSkeleton" :animation="true"> <a-skeleton :loading="props.showSkeleton" :animation="true">
<div class="flex gap-[16px]"> <div class="header-skeleton mb-[24px] flex justify-between">
<div class="flex-1"> <div class="w-[20%]">
<a-skeleton-line :rows="1" :line-height="props.skeletonLine" /> <a-skeleton-line :rows="1" :line-height="24" />
</div> </div>
<div class="flex-1"> <div class="w-[40%]">
<a-skeleton-line :rows="1" :line-height="props.skeletonLine" /> <a-skeleton-line :rows="1" :line-height="24" />
</div> </div>
</div> </div>
<div>
<div v-for="index in props.showLineNumber" :key="index" class="mt-[16px] flex flex-col"> <template v-if="!props.isMemberOverview">
<div class="flex gap-4"> <div v-for="number in showLineNumber" :key="`out-${number}`" class="mb-[24px] flex flex-col gap-[12px]">
<div :style="{ width: `${index * 12}%` }"> <div
<a-skeleton-line :rows="1" :line-height="props.skeletonLine" /> v-for="index in skeletonLine"
</div> :key="`inner-${index}`"
<div :style="{ width: `calc(100% - ${index * 12}%)` }"> :class="`${index === skeletonLine ? 'w-[43%]' : 'w-full'}`"
<a-skeleton-line :rows="1" :line-height="props.skeletonLine" /> >
</div> <a-skeleton-line :rows="1" :line-height="16" />
</div> </div>
</div> </div>
</template>
<div class="content-skeleton">
<a-skeleton-line :rows="1" :line-height="props.contentHeight" />
</div> </div>
</a-skeleton> </a-skeleton>
</template> </template>
@ -29,12 +33,21 @@
showSkeleton: boolean; showSkeleton: boolean;
skeletonLine?: number; skeletonLine?: number;
showLineNumber?: number; showLineNumber?: number;
isMemberOverview?: boolean;
contentHeight?: number; //
}>(), }>(),
{ {
skeletonLine: 24, skeletonLine: 3,
showLineNumber: 7, showLineNumber: 1,
contentHeight: 166,
} }
); );
</script> </script>
<style scoped></style> <style scoped lang="less">
:deep(.content-skeleton) {
.arco-skeleton-line-row {
border-radius: 8px;
}
}
</style>

View File

@ -26,6 +26,7 @@
<div v-for="tabItem of caseCountTabList" :key="tabItem.label" class="flex-1"> <div v-for="tabItem of caseCountTabList" :key="tabItem.label" class="flex-1">
<PassRatePie <PassRatePie
:options="tabItem.options" :options="tabItem.options"
:project-id="projectId"
:tooltip-text="tabItem.tooltip" :tooltip-text="tabItem.tooltip"
:value-list="tabItem.valueList" :value-list="tabItem.valueList"
:has-permission="hasPermission" :has-permission="hasPermission"

View File

@ -26,6 +26,7 @@
<div class="case-count-item"> <div class="case-count-item">
<PassRatePie <PassRatePie
:options="options" :options="options"
:project-id="projectId"
tooltip-text="workbench.homePage.caseReviewCoverRateTooltip" tooltip-text="workbench.homePage.caseReviewCoverRateTooltip"
:value-list="coverValueList" :value-list="coverValueList"
:has-permission="hasPermission" :has-permission="hasPermission"

View File

@ -28,6 +28,7 @@
:has-permission="hasPermission" :has-permission="hasPermission"
:tooltip-text="tooltip" :tooltip-text="tooltip"
:options="legacyOptions" :options="legacyOptions"
:project-id="projectId"
:value-list="legacyValueList" :value-list="legacyValueList"
/> />
</div> </div>
@ -73,7 +74,7 @@
} from '@/models/workbench/homePage'; } from '@/models/workbench/homePage';
import { WorkCardEnum } from '@/enums/workbenchEnum'; import { WorkCardEnum } from '@/enums/workbenchEnum';
import { colorMapConfig, handlePieData, handleUpdateTabPie } from '../utils'; import { colorMapConfig, handlePieData, handleUpdateTabPie, routeNavigationMap } from '../utils';
const appStore = useAppStore(); const appStore = useAppStore();
@ -168,14 +169,17 @@
const { options, valueList } = handleUpdateTabPie(legacyData, hasPermission.value, `${props.item.key}-legacy`); const { options, valueList } = handleUpdateTabPie(legacyData, hasPermission.value, `${props.item.key}-legacy`);
legacyValueList.value = hasPermission.value legacyValueList.value = hasPermission.value
? (statusStatisticsMap?.retentionRate || []).slice(1).map((item) => { ? (statusStatisticsMap?.retentionRate || []).slice(1).map((item, i) => {
return { return {
value: item.count, value: item.count,
label: item.name, label: item.name,
name: item.name, name: item.name,
route: routeNavigationMap[props.item.key].legacy?.route,
status: routeNavigationMap[props.item.key].legacy?.status[i],
}; };
}) })
: valueList; : valueList;
legacyOptions.value = options; legacyOptions.value = options;
} catch (error) { } catch (error) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console

View File

@ -6,7 +6,11 @@
<a-tooltip :content="t(props.item.label)" position="tl"> <a-tooltip :content="t(props.item.label)" position="tl">
<div class="title one-line-text"> {{ t(props.item.label) }} </div> <div class="title one-line-text"> {{ t(props.item.label) }} </div>
</a-tooltip> </a-tooltip>
<div> <div class="flex items-center gap-[12px]">
<div class="text-[var(--color-text-n4)]">
{{ t('workbench.homePage.selected') }}
<span class="text-[rgb(var(--link-6))]">{{ innerProjectIds.length }}</span>
</div>
<MsSelect <MsSelect
v-model:model-value="innerProjectIds" v-model:model-value="innerProjectIds"
:options="appStore.projectList" :options="appStore.projectList"
@ -33,7 +37,7 @@
</div> </div>
<!-- 概览图 --> <!-- 概览图 -->
<div> <div>
<MsChart height="280px" :options="options" /> <MsChart ref="charRef" height="280px" :options="options" />
</div> </div>
</div> </div>
</div> </div>
@ -63,7 +67,7 @@
} from '@/models/workbench/homePage'; } from '@/models/workbench/homePage';
import { WorkCardEnum } from '@/enums/workbenchEnum'; import { WorkCardEnum } from '@/enums/workbenchEnum';
import { getColorScheme, getCommonBarOptions, getSeriesData, handleNoDataDisplay } from '../utils'; import { getColorScheme, getSeriesData } from '../utils';
const { t } = useI18n(); const { t } = useI18n();
@ -102,8 +106,6 @@
} }
); );
const hasRoom = computed(() => innerProjectIds.value.length >= 7 || props.item.projectIds.length === 0);
const options = ref<Record<string, any>>({}); const options = ref<Record<string, any>>({});
const hasPermission = ref<boolean>(false); const hasPermission = ref<boolean>(false);
@ -122,16 +124,7 @@
}) })
.filter((e) => Object.keys(detail.caseCountMap).includes(e.value as string)); .filter((e) => Object.keys(detail.caseCountMap).includes(e.value as string));
options.value = getCommonBarOptions(hasRoom.value, getColorScheme(detail.projectCountList.length)); options.value = getSeriesData(contentTabList, detail, getColorScheme(detail.projectCountList.length));
const { invisible, text } = handleNoDataDisplay(detail.xaxis, hasPermission.value);
options.value.graphic.invisible = invisible;
options.value.graphic.style.text = text;
// x
options.value.xAxis.data = detail.xaxis;
const { maxAxis, data } = getSeriesData(detail.projectCountList);
options.value.series = data;
options.value.yAxis[0].max = maxAxis;
} }
const showSkeleton = ref(false); const showSkeleton = ref(false);

View File

@ -1,6 +1,6 @@
<template> <template>
<div class="card-wrapper"> <div class="card-wrapper">
<CardSkeleton v-if="showSkeleton" :show-skeleton="showSkeleton" /> <CardSkeleton v-if="showSkeleton" :content-height="230" is-member-overview :show-skeleton="showSkeleton" />
<div v-else> <div v-else>
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<a-tooltip :content="t(props.item.label)" position="tl"> <a-tooltip :content="t(props.item.label)" position="tl">
@ -54,13 +54,13 @@
import CardSkeleton from './cardSkeleton.vue'; import CardSkeleton from './cardSkeleton.vue';
import { workMemberViewDetail, workProjectMemberOptions } from '@/api/modules/workbench'; import { workMemberViewDetail, workProjectMemberOptions } from '@/api/modules/workbench';
import { contentTabList } from '@/config/workbench';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import useAppStore from '@/store/modules/app'; import useAppStore from '@/store/modules/app';
import { characterLimit } from '@/utils';
import type { OverViewOfProject, SelectedCardItem, TimeFormParams } from '@/models/workbench/homePage'; import type { SelectedCardItem, TimeFormParams } from '@/models/workbench/homePage';
import { getColorScheme, getCommonBarOptions, getSeriesData, handleNoDataDisplay } from '../utils'; import { getColorScheme, getSeriesData } from '../utils';
const { t } = useI18n(); const { t } = useI18n();
const appStore = useAppStore(); const appStore = useAppStore();
@ -96,20 +96,7 @@
const memberOptions = ref<{ label: string; value: string }[]>([]); const memberOptions = ref<{ label: string; value: string }[]>([]);
const options = ref<Record<string, any>>({}); const options = ref<Record<string, any>>({});
function handleData(detail: OverViewOfProject) {
options.value = getCommonBarOptions(detail.xaxis.length >= 7, getColorScheme(7));
const { invisible, text } = handleNoDataDisplay(detail.xaxis, hasPermission.value);
options.value.graphic.invisible = invisible;
options.value.graphic.style.text = text;
options.value.xAxis.data = detail.xaxis;
const { maxAxis, data } = getSeriesData(detail.projectCountList);
options.value.series = data;
options.value.yAxis[0].max = maxAxis;
}
const showSkeleton = ref(false); const showSkeleton = ref(false);
async function initOverViewMemberDetail() { async function initOverViewMemberDetail() {
try { try {
showSkeleton.value = true; showSkeleton.value = true;
@ -127,7 +114,8 @@
}; };
const detail = await workMemberViewDetail(params); const detail = await workMemberViewDetail(params);
hasPermission.value = detail.errorCode !== 109001; hasPermission.value = detail.errorCode !== 109001;
handleData(detail);
options.value = getSeriesData(contentTabList, detail, getColorScheme(detail.projectCountList.length));
} catch (error) { } catch (error) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log(error); console.log(error);

View File

@ -14,7 +14,9 @@
<div class="pass-rate-title flex-1"> <div class="pass-rate-title flex-1">
<div v-for="item of props.valueList" :key="item.label" class="flex-1"> <div v-for="item of props.valueList" :key="item.label" class="flex-1">
<div class="one-line-text mb-[8px] text-[var(--color-text-4)]">{{ item.label }}</div> <div class="one-line-text mb-[8px] text-[var(--color-text-4)]">{{ item.label }}</div>
<div class="pass-rate-count">{{ hasPermission ? addCommasToNumber(item.value as number) : '-' }}</div> <div class="pass-rate-count" @click="goNavigation(item)">
{{ hasPermission ? addCommasToNumber(item.value as number) : '-' }}
</div>
</div> </div>
</div> </div>
</a-spin> </a-spin>
@ -24,20 +26,33 @@
import MsChart from '@/components/pure/chart/index.vue'; import MsChart from '@/components/pure/chart/index.vue';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import useOpenNewPage from '@/hooks/useOpenNewPage';
import { addCommasToNumber } from '@/utils'; import { addCommasToNumber } from '@/utils';
const { openNewPage } = useOpenNewPage();
const { t } = useI18n(); const { t } = useI18n();
const props = defineProps<{ const props = defineProps<{
options: Record<string, any>; options: Record<string, any>;
projectId: string;
tooltipText?: string; tooltipText?: string;
hasPermission: boolean; hasPermission: boolean;
loading?: boolean; loading?: boolean;
valueList: { valueList: {
label: string; label: string;
value: number | string; value: number | string;
status?: string;
route?: string;
}[]; }[];
}>(); }>();
function goNavigation(item: { label: string; value: number | string; status?: string; route?: string }) {
openNewPage(item.route, {
pId: props.projectId,
home: item.status,
});
}
</script> </script>
<style scoped lang="less"> <style scoped lang="less">
@ -53,7 +68,7 @@
.pass-rate-count { .pass-rate-count {
font-size: 20px; font-size: 20px;
color: rgb(var(--primary-4)); color: rgb(var(--primary-4));
@apply font-medium; @apply cursor-pointer font-medium;
} }
} }
} }

View File

@ -33,7 +33,9 @@
</div> </div>
{{ ele.name }} {{ ele.name }}
</div> </div>
<div class="text-[16px] text-[rgb(var(--primary-5))]">{{ addCommasToNumber(ele.value) }}</div> <div class="cursor-pointer text-[16px] text-[rgb(var(--primary-5))]" @click="goNavigation(ele)">
{{ addCommasToNumber(ele.value) }}
</div>
</div> </div>
</template> </template>
<div v-else class="mt-[16px] flex h-full flex-1 items-center justify-center"> <div v-else class="mt-[16px] flex h-full flex-1 items-center justify-center">
@ -54,14 +56,21 @@
import { toolTipConfig } from '@/config/testPlan'; import { toolTipConfig } from '@/config/testPlan';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import useOpenNewPage from '@/hooks/useOpenNewPage';
import { addCommasToNumber } from '@/utils'; import { addCommasToNumber } from '@/utils';
import type { StatusValueItem } from '@/models/workbench/homePage';
import { WorkCardEnum } from '@/enums/workbenchEnum';
const { t } = useI18n(); const { t } = useI18n();
const { openNewPage } = useOpenNewPage();
const props = defineProps<{ const props = defineProps<{
data: { name: string; value: number }[]; data: StatusValueItem[];
hasPermission: boolean; hasPermission: boolean;
valueKey: WorkCardEnum;
loading?: boolean; loading?: boolean;
projectId: string;
rateConfig: { rateConfig: {
name: string; name: string;
color: string[]; color: string[];
@ -155,20 +164,6 @@
}, },
data: [], data: [],
}, },
// graphic: {
// type: 'text',
// left: 'center',
// bottom: 10,
// style: {
// text: t('workbench.homePage.notHasResPermission'),
// fontSize: 14,
// fill: '#959598',
// backgroundColor: '#F9F9FE',
// padding: [6, 16, 6, 16],
// borderRadius: 4,
// },
// invisible: false,
// },
}); });
const legend = ref<{ name: string; value: number; color: string; selected: boolean }[]>([]); const legend = ref<{ name: string; value: number; color: string; selected: boolean }[]>([]);
@ -258,6 +253,17 @@
} }
} }
function goNavigation(item: StatusValueItem) {
const params: Record<string, any> = {
pId: props.projectId,
home: item.status,
};
if (item.tab) {
params.tab = item.tab;
}
openNewPage(item.route, params);
}
watch( watch(
() => props.data, () => props.data,
(val) => { (val) => {

View File

@ -24,6 +24,7 @@
<div class="case-count-wrapper"> <div class="case-count-wrapper">
<div class="case-count-item"> <div class="case-count-item">
<PassRatePie <PassRatePie
:project-id="projectId"
:options="relatedOptions" :options="relatedOptions"
:has-permission="hasPermission" :has-permission="hasPermission"
tooltip-text="workbench.homePage.associateCaseCoverRateTooltip" tooltip-text="workbench.homePage.associateCaseCoverRateTooltip"

View File

@ -4,7 +4,7 @@
<a-tab-pane v-for="item of props.contentTabList" :key="item.value" :title="`${item.label}`"> <a-tab-pane v-for="item of props.contentTabList" :key="item.value" :title="`${item.label}`">
<template #title> <template #title>
<slot name="item" :item="item"> <slot name="item" :item="item">
<div class="wrapper-card w-full"> <div class="w-full">
<div class="card-title flex items-center gap-[8px]"> <div class="card-title flex items-center gap-[8px]">
<div :class="`card-title-icon bg-[${item?.color}]`"> <div :class="`card-title-icon bg-[${item?.color}]`">
<MsIcon :type="item.icon" class="text-white" size="12" /> <MsIcon :type="item.icon" class="text-white" size="12" />

View File

@ -24,7 +24,12 @@
<div class="mt-[16px]"> <div class="mt-[16px]">
<div class="flex gap-[16px]"> <div class="flex gap-[16px]">
<div v-for="tabItem of testPlanTabList" :key="tabItem.label" class="flex-1"> <div v-for="tabItem of testPlanTabList" :key="tabItem.label" class="flex-1">
<PassRatePie :has-permission="hasPermission" :options="tabItem.options" :value-list="tabItem.valueList" /> <PassRatePie
:project-id="projectId"
:has-permission="hasPermission"
:options="tabItem.options"
:value-list="tabItem.valueList"
/>
</div> </div>
</div> </div>
<div class="mt-[16px] h-[148px]"> <div class="mt-[16px] h-[148px]">

View File

@ -0,0 +1,472 @@
<template>
<div class="card-wrapper">
<CardSkeleton v-if="showSkeleton" :show-skeleton="showSkeleton" />
<div v-else>
<div class="flex items-center justify-between">
<a-tooltip :content="t(props.item.label)" position="tl" :mouse-enter-delay="300">
<div class="title one-line-text"> {{ t(props.item.label) }} </div>
</a-tooltip>
<div>
<!-- TODO 待处理 -->
<MsCascader
v-model:model-value="innerPlanId"
mode="native"
:default-value="innerPlanId"
:options="projectOptions"
:prefix="t('workbench.homePage.plan')"
:placeholder="t('workbench.homePage.planOfPleaseSelect')"
:virtual-list-props="{ height: 200 }"
option-size="small"
class="test-plan-panel filter-item w-[240px]"
label-path-mode
:panel-width="100"
:load-more="loadMore"
@change="changeHandler"
>
<template v-if="labelPath" #label>
<a-tooltip :content="labelPath">
<div class="one-line-text inline-flex w-full items-center justify-between pr-[8px]" title="">
{{ labelPath }}
</div>
</a-tooltip>
</template>
<template #option="{ data }">
<a-tooltip :content="t(data.label)" :mouse-enter-delay="300">
<div class="one-line-text w-[120px]" title="">
{{ t(data.label) }}
</div>
</a-tooltip>
</template>
</MsCascader>
</div>
</div>
<div class="my-[16px] flex items-center">
<div class="threshold-card-item">
<div class="threshold-card-item-name">
<div class="flex items-center gap-[4px]">
<a-tooltip
:disabled="!detail?.caseCountMap?.testPlanName"
:mouse-enter-delay="300"
:content="detail?.caseCountMap?.testPlanName"
>
<div class="one-line-text max-w-[calc(100%-64px)]">{{ detail?.caseCountMap?.testPlanName ?? '-' }}</div>
</a-tooltip>
<MsStatusTag v-if="detail?.caseCountMap?.status" :status="detail?.caseCountMap?.status" />
</div>
<div class="flex w-full items-center gap-[4px]">
<div class="w-[calc(100%-80px)]">
<ThresholdProgress :threshold="passThreshold" :progress="passRate" :progress-color="progressColor" />
</div>
<div class="flex-1 text-[10px] text-[rgb(var(--success-6))]">
{{ t('testPlan.testPlanIndex.threshold') }} {{ `${passThreshold}%` }}
</div>
</div>
</div>
<div class="char-content flex-1">
<MsChart height="76px" width="76px" :options="execOptions" />
</div>
</div>
<div class="card-list">
<div v-for="ele of cardModuleList" :key="ele.icon" class="card-list-item">
<div class="w-full">
<div class="card-title flex items-center gap-[8px]">
<div :class="`card-title-icon bg-[${ele?.color}]`">
<MsIcon :type="ele.icon" class="text-white" size="12" />
</div>
<div class="text-[var(--color-text-1)]"> {{ ele.label }}</div>
</div>
<div class="card-number !text-[20px] !font-medium"> {{ addCommasToNumber(ele.count || 0) }} </div>
</div>
</div>
</div>
</div>
<div>
<MsChart ref="charRef" height="280px" :options="options" />
</div>
</div>
</div>
</template>
<script setup lang="ts">
/**
* @desc 测试计划概览 TODO 待联调
*/
import { ref } from 'vue';
import { CascaderOption } from '@arco-design/web-vue';
import MsChart from '@/components/pure/chart/index.vue';
import MsCascader from '@/components/business/ms-cascader/index.vue';
import MsStatusTag from '@/components/business/ms-status-tag/index.vue';
import CardSkeleton from './cardSkeleton.vue';
import ThresholdProgress from './thresholdProgress.vue';
import { getWorkTestPlanListUrl, workTestPlanOverviewDetail } from '@/api/modules/workbench';
import { commonRatePieOptions } from '@/config/workbench';
import { useI18n } from '@/hooks/useI18n';
import useAppStore from '@/store/modules/app';
import { addCommasToNumber, findNodePathByKey, mapTree } from '@/utils';
import type { ModuleCardItem, SelectedCardItem, TimeFormParams } from '@/models/workbench/homePage';
import { TestPlanStatusEnum } from '@/enums/testPlanEnum';
import { WorkOverviewEnum, WorkOverviewIconEnum } from '@/enums/workbenchEnum';
import { getSeriesData } from '../utils';
const { t } = useI18n();
const props = defineProps<{
item: SelectedCardItem;
refreshKey: number;
}>();
const emit = defineEmits<{
(e: 'change'): void;
}>();
const appStore = useAppStore();
const innerPlanId = defineModel<string>('planId', {
required: true,
});
const innerProjectIds = defineModel<string[]>('projectIds', {
required: true,
});
const timeForm = inject<Ref<TimeFormParams>>(
'timeForm',
ref({
dayNumber: 3,
startTime: 0,
endTime: 0,
})
);
watch(
() => props.item.projectIds,
(val) => {
innerProjectIds.value = val;
}
);
const options = ref<Record<string, any>>({});
const hasPermission = ref<boolean>(false);
const contentTabList: ModuleCardItem[] = [
{
label: t('workbench.homePage.functionalUseCase'),
value: WorkOverviewEnum.FUNCTIONAL,
icon: WorkOverviewIconEnum.FUNCTIONAL,
color: 'rgb(var(--primary-5))',
count: 0,
},
{
label: t('workbench.homePage.interfaceCASE'),
value: WorkOverviewEnum.API_CASE,
icon: WorkOverviewIconEnum.API_CASE,
color: 'rgb(var(--link-6))',
count: 0,
},
{
label: t('workbench.homePage.interfaceScenario'),
value: WorkOverviewEnum.API_SCENARIO,
icon: WorkOverviewIconEnum.API_SCENARIO,
color: 'rgb(var(--link-6))',
count: 0,
},
{
label: t('workbench.homePage.bugCount'),
value: WorkOverviewEnum.BUG_COUNT,
icon: WorkOverviewIconEnum.BUG_COUNT,
color: 'rgb(var(--danger-6))',
count: 0,
},
];
const cardModuleList = ref<ModuleCardItem[]>([...contentTabList]);
const legendContentList = [
{
label: 'workbench.homePage.assigningUseCases',
value: '',
},
{
label: 'workbench.homePage.completeUseCases',
value: '',
},
{
label: 'workbench.homePage.commitDefects',
value: '',
},
{
label: 'workbench.homePage.associatedDefect',
value: '',
},
];
const detail = ref();
const execOptions = ref<Record<string, any>>({
...commonRatePieOptions,
title: {
...commonRatePieOptions.title,
left: 35,
},
series: {
...commonRatePieOptions.series,
center: [40, '50%'],
},
});
const passRate = computed(() => detail.value?.caseCountMap?.passRate ?? 0);
const passThreshold = computed(() => detail.value?.caseCountMap?.passThreshold ?? 0);
function handleData() {
cardModuleList.value = contentTabList
.map((item) => {
return {
...item,
label: t(item.label),
count: detail.value.caseCountMap[item.value],
};
})
.filter((e) => Object.keys(detail.value.caseCountMap).includes(e.value as string));
const testPlanColor = ['#3370FF', '#00C261', '#FFA200', '#811FA3'];
options.value = getSeriesData(legendContentList, detail.value, testPlanColor, true);
execOptions.value.title.text = t('workbench.homePage.executeRate');
execOptions.value.title.subtext = hasPermission.value ? `${detail.value?.caseCountMap?.executeRate ?? 0}%` : '-';
const totalCount = detail.value?.caseCountMap?.totalCount ?? 0;
const executeCount = detail.value?.caseCountMap?.executeCount ?? 0;
const executeData = [
{
name: t('common.executed'),
value: executeCount,
color: '#00C261',
},
{
name: t('common.unExecute'),
value: totalCount - executeCount,
color: '#EDEDF1',
},
];
const pieBorderWidth = executeData.filter((e) => Number(e.value) > 0).length === 1 ? 0 : 1;
execOptions.value.series.data = executeData.map((e) => {
return {
...e,
itemStyle: {
borderWidth: pieBorderWidth,
borderColor: '#ffffff',
color: e.color,
},
};
});
}
const showSkeleton = ref(false);
async function initOverViewDetail() {
try {
showSkeleton.value = true;
const { startTime, endTime, dayNumber } = timeForm.value;
const params = {
current: 1,
pageSize: 5,
startTime: dayNumber ? null : startTime,
endTime: dayNumber ? null : endTime,
dayNumber: dayNumber ?? null,
projectIds: innerProjectIds.value,
organizationId: appStore.currentOrgId,
handleUsers: [],
planId: innerPlanId.value,
};
detail.value = await workTestPlanOverviewDetail(params);
hasPermission.value = detail.value.errorCode !== 109001;
handleData();
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
} finally {
showSkeleton.value = false;
}
}
//
const progressColor = computed(() => {
switch (detail.value?.caseCountMap?.status) {
case TestPlanStatusEnum.PREPARED:
return 'var(--color-text-n8)';
case TestPlanStatusEnum.UNDERWAY:
return 'rgb(var(--link-6))';
case TestPlanStatusEnum.COMPLETED:
return passRate.value < passThreshold.value ? 'rgb(var(--danger-6))' : 'rgb(var(--success-6))';
default:
return 'var(--color-text-n8)';
}
});
const labelPath = ref<string>('');
const projectOptions = ref<{ value: string; label: string }[]>([]);
function getLabelPath(id: string) {
const modules = findNodePathByKey(projectOptions.value, id, undefined, 'value');
if (modules) {
const moduleName = (modules || [])?.treePath.map((item: any) => item.label);
if (moduleName.length === 1) {
return moduleName[0];
}
return `${moduleName.join(' / ')}`;
}
}
async function changeHandler(value: string) {
innerPlanId.value = value;
labelPath.value = getLabelPath(innerPlanId.value);
initOverViewDetail();
emit('change');
}
onMounted(() => {
initOverViewDetail();
});
async function loadMore(option: CascaderOption, done: (children?: CascaderOption[]) => void) {
try {
let testPlanOptionsNode = await getWorkTestPlanListUrl(option.value as string);
innerProjectIds.value = [option.value as string];
testPlanOptionsNode = mapTree<CascaderOption>(testPlanOptionsNode, (node) => {
return {
...node,
isLeaf: true,
};
});
done(testPlanOptionsNode);
projectOptions.value = projectOptions.value.map((item) => {
return {
...item,
children: testPlanOptionsNode,
};
});
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
}
}
let loadingProjectId: string | null = null;
function refreshHandler(newProjectId: string) {
const cascaderOption = projectOptions.value.find((e) => e.value === newProjectId);
if (cascaderOption) {
loadMore(cascaderOption, (children) => {
projectOptions.value = projectOptions.value.map((item) => {
return {
...item,
children: cascaderOption.value === item.value ? children : [],
};
});
labelPath.value = getLabelPath(innerPlanId.value);
});
}
initOverViewDetail();
}
async function handleRefreshKeyChange() {
await nextTick(() => {
innerProjectIds.value = [...props.item.projectIds];
});
const [newProjectId] = innerProjectIds.value;
refreshHandler(newProjectId);
labelPath.value = getLabelPath(innerPlanId.value);
}
watch(
() => props.item.projectIds,
async (val) => {
if (val) {
const [newProjectId] = val;
projectOptions.value = appStore.projectList.map((e) => ({ value: e.id, label: e.name }));
if (loadingProjectId !== newProjectId) {
loadingProjectId = newProjectId;
refreshHandler(newProjectId);
}
}
},
{ immediate: true }
);
watch(
() => timeForm.value,
(val) => {
if (val) {
initOverViewDetail();
}
},
{ deep: true }
);
watch(() => props.refreshKey, handleRefreshKeyChange);
</script>
<style scoped lang="less">
.card-list {
gap: 16px;
@apply flex flex-1;
.card-list-item {
padding: 16px;
border: 1px solid var(--color-text-n8);
border-radius: 4px;
@apply flex-1;
.card-title-icon {
width: 20px;
height: 20px;
border-radius: 50%;
@apply flex items-center justify-center;
}
.card-number {
margin-left: 28px;
font-size: 20px;
}
}
}
.threshold-card-item {
margin-right: 16px;
width: 34%;
height: 76px;
border: 1px solid var(--color-text-n8);
border-radius: 4px;
gap: 12px;
@apply flex;
.threshold-card-item-name {
padding-left: 16px;
width: calc(100% - 100px);
gap: 4px;
@apply flex flex-col justify-center;
}
}
</style>
<style lang="less">
:deep(.arco-select-view-multiple.arco-select-view-size-medium .arco-select-view-tag) {
margin-top: 1px;
margin-bottom: 1px;
max-width: 80px;
height: auto;
min-height: 24px;
line-height: 22px;
vertical-align: middle;
}
</style>

View File

@ -0,0 +1,69 @@
<template>
<a-trigger position="bottom" trigger="hover" align-point>
<div class="progress-container">
<div class="progress-bar-background">
<div :class="`progress-bar w-[${props.progress}%] bg-[${props.progressColor}]`"></div>
</div>
<div class="threshold-line" :style="{ left: `${props.threshold === 100 ? 99 : props.threshold}%` }"></div>
</div>
<template #content>
<div class="popover-trigger-content flex items-center justify-between">
<div class="text-[var(--color-text-2)]">
{{ t('workbench.homePage.passRate') }}
</div>
<div>
{{ `${props.progress}%` }}
</div>
</div>
</template>
</a-trigger>
</template>
<script setup lang="ts">
import { useI18n } from '@/hooks/useI18n';
const { t } = useI18n();
const props = defineProps<{
threshold: number;
progress: number;
progressColor: string;
}>();
</script>
<style scoped lang="less">
.progress-container {
position: relative;
padding: 2px 0;
width: 100%;
}
.progress-bar-background {
position: relative;
overflow: hidden;
width: 100%;
height: 10px;
border-radius: 3px;
background: var(--color-text-n8);
}
.progress-bar {
height: 100%;
border-radius: 3px;
transition: width 0.3s ease;
}
.threshold-line {
position: absolute;
top: 0;
z-index: 2;
width: 2px;
height: 14px;
border-radius: 2px;
background: rgb(var(--success-6));
}
.popover-trigger-content {
padding: 8px 16px;
width: 120px;
border-radius: var(--border-radius-small);
background-color: var(--color-text-fff);
box-shadow: 0 4px 10px -1px rgb(100 100 102 / 15%);
}
</style>

View File

@ -1,6 +1,6 @@
<template> <template>
<div class="card-wrapper"> <div class="card-wrapper">
<CardSkeleton v-if="showSkeleton" :show-skeleton="showSkeleton" /> <CardSkeleton v-if="showSkeleton" :show-skeleton="showSkeleton" :show-line-number="2" />
<div v-else> <div v-else>
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<a-tooltip :content="t(props.item.label)" position="tl"> <a-tooltip :content="t(props.item.label)" position="tl">

View File

@ -147,6 +147,14 @@
:refresh-key="refreshKey" :refresh-key="refreshKey"
@change="changeHandler" @change="changeHandler"
/> />
<TestPlanOverView
v-else-if="item.key === WorkCardEnum.PROJECT_PLAN_VIEW"
v-model:planId="item.planId"
v-model:projectIds="item.projectIds"
:item="item"
:refresh-key="refreshKey"
@change="changeHandler"
/>
</div> </div>
</div> </div>
<NoData <NoData
@ -180,6 +188,7 @@
import WaitReviewList from './components/waitReviewList.vue'; import WaitReviewList from './components/waitReviewList.vue';
import DefectMemberBar from '@/views/workbench/homePage/components/defectMemberBar.vue'; import DefectMemberBar from '@/views/workbench/homePage/components/defectMemberBar.vue';
import OverviewMember from '@/views/workbench/homePage/components/overviewMember.vue'; import OverviewMember from '@/views/workbench/homePage/components/overviewMember.vue';
import TestPlanOverView from '@/views/workbench/homePage/components/testPlanOverview.vue';
import { editDashboardLayout, getDashboardLayout, workApiCountCoverRage } from '@/api/modules/workbench'; import { editDashboardLayout, getDashboardLayout, workApiCountCoverRage } from '@/api/modules/workbench';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';

View File

@ -114,4 +114,12 @@ export default {
'workbench.homePage.completeRate': 'Completion Rate', 'workbench.homePage.completeRate': 'Completion Rate',
'workbench.homePage.legacyRate': 'Legacy Rate', 'workbench.homePage.legacyRate': 'Legacy Rate',
'workbench.homePage.unit': 'Unit: Number', 'workbench.homePage.unit': 'Unit: Number',
'workbench.homePage.selected': 'Selected',
'workbench.homePage.testPlanOverview': 'Test plan overview',
'workbench.homePage.plan': 'Plan',
'workbench.homePage.planOfPleaseSelect': 'Please select a plan',
'workbench.homePage.assigningUseCases': 'Assigning use cases',
'workbench.homePage.completeUseCases': 'Complete use case',
'workbench.homePage.commitDefects': 'Commit defects',
'workbench.homePage.associatedDefect': 'Associated defect',
}; };

View File

@ -100,4 +100,13 @@ export default {
'workbench.homePage.completeRate': '完成率', 'workbench.homePage.completeRate': '完成率',
'workbench.homePage.legacyRate': '遗留率', 'workbench.homePage.legacyRate': '遗留率',
'workbench.homePage.unit': '单位:个', 'workbench.homePage.unit': '单位:个',
'workbench.homePage.selected': '已选',
'workbench.homePage.testPlanOverview': '测试计划概览',
'workbench.homePage.testPlanOverviewDesc': '统计所属测试计划的用例执行数据统计',
'workbench.homePage.plan': '计划',
'workbench.homePage.planOfPleaseSelect': '请选择计划',
'workbench.homePage.assigningUseCases': '分配用例',
'workbench.homePage.completeUseCases': '完成用例',
'workbench.homePage.commitDefects': '提交缺陷',
'workbench.homePage.associatedDefect': '关联缺陷',
}; };

View File

@ -1,11 +1,13 @@
import { cloneDeep } from 'lodash-es'; import { cloneDeep } from 'lodash-es';
import { toolTipConfig } from '@/config/testPlan'; import { toolTipConfig } from '@/config/testPlan';
import { commonRatePieOptions, contentTabList, defaultValueMap } from '@/config/workbench'; import { commonRatePieOptions, defaultValueMap } from '@/config/workbench';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import { addCommasToNumber } from '@/utils'; import { addCommasToNumber } from '@/utils';
import { WorkCardEnum } from '@/enums/workbenchEnum'; import type { ModuleCardItem, OverViewOfProject } from '@/models/workbench/homePage';
import { RouteEnum } from '@/enums/routeEnum';
import { WorkCardEnum, WorkNavValueEnum } from '@/enums/workbenchEnum';
const { t } = useI18n(); const { t } = useI18n();
// TODO 通用颜色配置注: 目前柱状图只用到了7种色阶其他色阶暂时保留 // TODO 通用颜色配置注: 目前柱状图只用到了7种色阶其他色阶暂时保留
@ -77,7 +79,7 @@ export const colorMapConfig: Record<string, string[]> = {
}; };
// 柱状图 // 柱状图
export function getCommonBarOptions(hasRoom: boolean, color: string[]): Record<string, any> { export function getCommonBarOptions(hasRoom: boolean, color: string[], isTestPlan = false): Record<string, any> {
return { return {
tooltip: [ tooltip: [
{ {
@ -98,18 +100,34 @@ export function getCommonBarOptions(hasRoom: boolean, color: string[]): Record<s
axis: 'auto', axis: 'auto',
}, },
formatter(params: any) { formatter(params: any) {
let testPlanHtml = '';
if (isTestPlan) {
const [assigning, complete] = params;
const passRate = assigning.value > 0 ? `${((complete.value / assigning.value) * 100).toFixed(2)}%` : '0%';
testPlanHtml = `<div class="flex items-center justify-between">
<div class="flex items-center gap-[8px] text-[var(--color-text-2)]">
<div style="background:#00C261;" class="flex items-center justify-center w-[11px] h-[11px] rounded-full text-[10px]">
<span class="text-[var(--color-text-fff)] text-center"></span>
</div>
${t('workbench.homePage.completeRate')}
</div>
<div class="text-[rgb(var(--success-6))] font-semibold">${passRate}</div>
</div>`;
}
const html = ` const html = `
<div class="w-[186px] ms-scroll-bar max-h-[236px] overflow-y-auto p-[16px] gap-[8px] flex flex-col"> <div class="w-[186px] ms-scroll-bar max-h-[236px] overflow-y-auto p-[16px] gap-[8px] flex flex-col">
<div class="font-medium max-w-[150px] one-line-text" style="color:#323233">${params[0].axisValueLabel}</div> <div class="font-medium max-w-[150px] one-line-text" style="color:#323233">${params[0].axisValueLabel}</div>
${testPlanHtml}
${params ${params
.map( .map(
(item: any) => ` (item: any) => `
<div class="flex h-[18px] items-center justify-between"> <div class="flex h-[18px] items-center justify-between">
<div class="flex items-center"> <div class="flex items-center">
<div class="mb-[2px] mr-[8px] h-[8px] w-[8px] rounded-sm" style="background:${item.color}"></div> <div class="mb-[2px] mr-[8px] h-[8px] w-[8px] rounded-sm" style="background:${item.color}"></div>
<div class="one-line-text max-w-[100px]" style="color:#959598">${item.seriesName}</div> <div class="one-line-text max-w-[100px] text-[var(--color-text-2)]">${item.seriesName}</div>
</div> </div>
<div class="text-[#323233] font-medium">${addCommasToNumber(item.data.originValue || 0)}</div> <div class="text-[var(--color-text-1)] font-semibold">${addCommasToNumber(item.data.originValue || 0)}</div>
</div> </div>
` `
) )
@ -140,9 +158,17 @@ export function getCommonBarOptions(hasRoom: boolean, color: string[]): Record<s
width: 120, width: 120,
overflow: 'truncate', overflow: 'truncate',
ellipsis: '...', ellipsis: '...',
showMinLabel: true,
showMaxLabel: true,
// TOTO 等待优化
interval: 0,
triggerEvent: true,
},
axisPointer: {
type: 'shadow',
}, },
axisTick: { axisTick: {
show: false, // 隐藏刻度线 alignWithLabel: true,
}, },
axisLine: { axisLine: {
lineStyle: { lineStyle: {
@ -240,12 +266,15 @@ export function getCommonBarOptions(hasRoom: boolean, color: string[]): Record<s
type: 'slider', type: 'slider',
height: 24, height: 24,
bottom: 10, bottom: 10,
// 按照坐标值显示起始位置 // TODO 待优化
minSpan: 1,
maxSpan: 26,
startValue: 0, startValue: 0,
end: 30,
rangeMode: ['value', 'percent'], // 起点按实际值,终点按百分比动态计算 rangeMode: ['value', 'percent'], // 起点按实际值,终点按百分比动态计算
showDataShadow: 'auto', showDataShadow: 'auto',
showDetail: false, showDetail: false,
filterMode: 'filter', filterMode: 'none',
moveOnMouseMove: true, moveOnMouseMove: true,
handleSize: 30, // 手柄的大小 handleSize: 30, // 手柄的大小
moveHandleSize: 0, moveHandleSize: 0,
@ -402,6 +431,141 @@ export function handlePieData(
return options; return options;
} }
export const routeNavigationMap: Record<string, any> = {
// 功能用例数
[WorkCardEnum.CASE_COUNT]: {
review: {
status: [WorkNavValueEnum.CASE_REVIEWED, WorkNavValueEnum.CASE_UN_REVIEWED],
route: RouteEnum.CASE_MANAGEMENT,
},
pass: {
status: [WorkNavValueEnum.CASE_REVIEWED_PASS, WorkNavValueEnum.CASE_REVIEWED_UN_PASS],
route: RouteEnum.CASE_MANAGEMENT,
},
},
// 用例评审数
[WorkCardEnum.REVIEW_CASE_COUNT]: {
cover: {
status: [WorkNavValueEnum.CASE_REVIEWED, WorkNavValueEnum.CASE_UN_REVIEWED],
route: RouteEnum.CASE_MANAGEMENT,
},
},
// 关联用例数
[WorkCardEnum.ASSOCIATE_CASE_COUNT]: {
cover: {
status: [WorkNavValueEnum.CASE_ASSOCIATED, WorkNavValueEnum.CASE_NOT_ASSOCIATED],
route: RouteEnum.CASE_MANAGEMENT,
},
},
// 接口数量
[WorkCardEnum.API_COUNT]: {
cover: {
status: [
WorkNavValueEnum.API_COUNT_COVER, // 已覆盖
WorkNavValueEnum.API_COUNT_UN_COVER, // 未覆盖
],
route: RouteEnum.API_TEST_MANAGEMENT,
},
complete: {
status: [
WorkNavValueEnum.API_COUNT_DONE, // 已完成
WorkNavValueEnum.API_COUNT_PROCESSING, // 进行中
WorkNavValueEnum.API_COUNT_DEBUGGING, // 联调中
WorkNavValueEnum.API_COUNT_DEPRECATED, // 已废弃
],
route: RouteEnum.API_TEST_MANAGEMENT,
},
},
// 接口用例
[WorkCardEnum.API_CASE_COUNT]: {
cover: {
status: [
WorkNavValueEnum.API_CASE_COUNT_UN_COVER, // 未覆盖
WorkNavValueEnum.API_CASE_COUNT_COVER, // 已覆盖
],
route: RouteEnum.API_TEST_MANAGEMENT,
},
passRate: {
status: [
WorkNavValueEnum.API_COUNT_EXECUTE_ERROR, // 执行结果-未通过
WorkNavValueEnum.API_COUNT_EXECUTE_SUCCESS, // 执行结果-已通过
],
route: RouteEnum.API_TEST_MANAGEMENT,
},
executeRate: {
status: [
WorkNavValueEnum.API_COUNT_EXECUTED_NOT_RESULT, // 接口用例-无执行结果
WorkNavValueEnum.API_COUNT_EXECUTED_RESULT, // 接口用例-有执行结果
],
route: RouteEnum.API_TEST_MANAGEMENT,
},
},
// 场景用例
[WorkCardEnum.SCENARIO_COUNT]: {
cover: {
status: [
WorkNavValueEnum.SCENARIO_UN_COVER, // 未覆盖
WorkNavValueEnum.SCENARIO_COVER, // 已覆盖
],
route: RouteEnum.API_TEST_MANAGEMENT,
},
passRate: {
status: [
WorkNavValueEnum.SCENARIO_COUNT_EXECUTE_ERROR, // 场景用例-执行结果-未通过
WorkNavValueEnum.SCENARIO_COUNT_EXECUTE_SUCCESS, // 场景用例-执行结果-已通过
],
route: RouteEnum.API_TEST_SCENARIO,
},
executeRate: {
status: [
WorkNavValueEnum.SCENARIO_COUNT_EXECUTED_NOT_RESULT, // 场景用例-无执行结果
WorkNavValueEnum.SCENARIO_COUNT_EXECUTED_RESULT, // 场景用例-有执行结果
],
route: RouteEnum.API_TEST_SCENARIO,
},
},
// 测试计划数量
[WorkCardEnum.TEST_PLAN_COUNT]: {
pass: {
status: [WorkNavValueEnum.TEST_PLAN_PASSED, WorkNavValueEnum.TEST_PLAN_NOT_PASS],
route: RouteEnum.TEST_PLAN_INDEX,
},
complete: {
status: [
WorkNavValueEnum.TEST_PLAN_COMPLETED, // 测试计划-已完成
WorkNavValueEnum.TEST_PLAN_UNDERWAY, // 测试计划-进行中
WorkNavValueEnum.TEST_PLAN_PREPARED, // 测试计划-未开始
WorkNavValueEnum.TEST_PLAN_ARCHIVED, // 测试计划-已归档
],
route: RouteEnum.TEST_PLAN_INDEX,
},
},
[WorkCardEnum.PLAN_LEGACY_BUG]: {
legacy: {
status: [WorkNavValueEnum.TEST_PLAN_LEGACY, WorkNavValueEnum.TEST_PLAN_BUG],
route: RouteEnum.BUG_MANAGEMENT_INDEX,
},
},
[WorkCardEnum.BUG_COUNT]: {
legacy: {
status: [WorkNavValueEnum.BUG_COUNT, WorkNavValueEnum.BUG_COUNT_LEGACY],
route: RouteEnum.BUG_MANAGEMENT_INDEX,
},
},
[WorkCardEnum.CREATE_BUG_BY_ME]: {
legacy: {
status: [WorkNavValueEnum.BUG_COUNT_BY_ME, WorkNavValueEnum.BUG_COUNT_BY_ME_LEGACY],
route: RouteEnum.BUG_MANAGEMENT_INDEX,
},
},
[WorkCardEnum.HANDLE_BUG_BY_ME]: {
legacy: {
status: [WorkNavValueEnum.BUG_HANDLE_BY_ME, WorkNavValueEnum.BUG_HANDLE_BY_ME_LEGACY],
route: RouteEnum.BUG_MANAGEMENT_INDEX,
},
},
};
// 更新options // 更新options
export function handleUpdateTabPie( export function handleUpdateTabPie(
list: { list: {
@ -449,19 +613,37 @@ export function handleUpdateTabPie(
options.series.color = defaultValueMap[typeKey][valueKey].color; options.series.color = defaultValueMap[typeKey][valueKey].color;
const lastValueList = lastCountList.map((item, index) => {
return {
...item,
route: routeNavigationMap[typeKey][valueKey].route,
status: routeNavigationMap[typeKey][valueKey].status[index],
};
});
return { return {
valueList: lastCountList, valueList: lastValueList,
options, options,
}; };
} }
export function getSeriesData( export function getSeriesData(
projectCountList: { contentTabList: ModuleCardItem[],
id: string; detail: OverViewOfProject,
name: string; colorConfig: string[],
count: number[]; isTestPlan = false
}[]
) { ) {
let options: Record<string, any> = {};
const { projectCountList, xaxis, errorCode } = detail;
const hasPermission = errorCode !== 109001;
options = getCommonBarOptions(xaxis.length >= 7, colorConfig, isTestPlan);
options.xAxis.data = xaxis;
const { invisible, text } = handleNoDataDisplay(xaxis, hasPermission);
options.graphic.invisible = invisible;
options.graphic.style.text = text;
let maxAxis = 5; let maxAxis = 5;
const seriesData = projectCountList.map((item, sid) => { const seriesData = projectCountList.map((item, sid) => {
const countData: Record<string, any>[] = item.count.map((e) => { const countData: Record<string, any>[] = item.count.map((e) => {
@ -482,7 +664,7 @@ export function getSeriesData(
}"></div> }"></div>
<div class="one-line-text max-w-[100px]"" style="color:#959598">${params.name}</div> <div class="one-line-text max-w-[100px]"" style="color:#959598">${params.name}</div>
</div> </div>
<div class="text-[#323233] font-medium">${addCommasToNumber(params.value)}</div> <div class="text-[var(--color-text-1)] font-semibold">${addCommasToNumber(params.value)}</div>
</div> </div>
`; `;
return html; return html;
@ -504,6 +686,7 @@ export function getSeriesData(
itemStyle: { itemStyle: {
borderRadius: [2, 2, 0, 0], borderRadius: [2, 2, 0, 0],
}, },
barCategoryGap: 24,
data: countData, data: countData,
barMinHeight: ((optionData: Record<string, any>[]) => { barMinHeight: ((optionData: Record<string, any>[]) => {
optionData.forEach((itemValue: any, index: number) => { optionData.forEach((itemValue: any, index: number) => {
@ -521,8 +704,21 @@ export function getSeriesData(
}; };
}); });
return { // 动态步长调整函数
data: seriesData, const calculateStep = (num: number) => {
maxAxis: maxAxis < 100 ? 100 : maxAxis + 50, const magnitude = 10 ** Math.floor(Math.log10(num));
const step = num > 2 * magnitude ? magnitude * 2 : magnitude;
return step;
}; };
const roundUpToNearest = (num: number, step: number) => Math.ceil(num / step) * step;
maxAxis = roundUpToNearest(maxAxis, calculateStep(maxAxis));
options.series = hasPermission ? seriesData : [];
options.yAxis[0].max = maxAxis;
options.yAxis[0].nameTextStyle.padding = maxAxis < 10 ? [0, 0, 0, 20] : [0, 0, 0, 0];
return options;
} }