feat(功能用例): 功能用例和用例回收站页面和本地联调

This commit is contained in:
xinxin.wu 2023-11-25 18:00:46 +08:00 committed by Craftsman
parent 86ce86649a
commit 700003a01f
58 changed files with 5333 additions and 766 deletions

View File

@ -79,18 +79,26 @@ export class MSAxios {
/** /**
* @description: * @description:
*/ */
uploadFile<T = any>(config: AxiosRequestConfig, params: UploadFileParams, customFileKey = ''): Promise<T> { uploadFile<T = any>(
config: AxiosRequestConfig,
params: UploadFileParams,
customFileKey = '',
isMultiple = false
): Promise<T> {
const formData = new window.FormData(); const formData = new window.FormData();
const fileName = params.fileList.length === 1 ? 'file' : 'files'; const fileName = isMultiple ? 'files' : 'file';
if (customFileKey !== '') { if (customFileKey !== '') {
params.fileList.forEach((file: File) => { params.fileList.forEach((file: File) => {
formData.append(customFileKey, file); formData.append(customFileKey, file);
}); });
} else { } else if (!isMultiple && !customFileKey) {
params.fileList.forEach((file: File) => { params.fileList.forEach((file: File) => {
formData.append(fileName, file); formData.append(fileName, file);
}); });
} else {
params.fileList.forEach((item: any) => {
formData.append(fileName, item.file, item.file.name);
});
} }
if (params.request) { if (params.request) {
const requestData = JSON.stringify(params.request); const requestData = JSON.stringify(params.request);

View File

@ -0,0 +1,149 @@
import MSR from '@/api/http/index';
import {
BatchCopyCaseUrl,
BatchDeleteCaseUrl,
BatchDeleteRecycleCaseListUrl,
BatchEditCaseUrl,
BatchMoveCaseUrl,
CreateCaseModuleTreeUrl,
CreateCaseUrl,
DeleteCaseModuleTreeUrl,
DeleteCaseUrl,
DeleteRecycleCaseListUrl,
DetailCaseUrl,
GetAssociatedFilePageUrl,
GetCaseListUrl,
GetCaseModulesCountUrl,
GetCaseModuleTreeUrl,
GetDefaultTemplateFieldsUrl,
GetRecycleCaseListUrl,
GetRecycleCaseModulesCountUrl,
GetTrashCaseModuleTreeUrl,
MoveCaseModuleTreeUrl,
RecoverRecycleCaseListUrl,
RestoreCaseListUrl,
UpdateCaseModuleTreeUrl,
UpdateCaseUrl,
} from '@/api/requrls/case-management/featureCase';
import type {
AssociatedList,
BatchDeleteType,
BatchEditCaseType,
BatchMoveOrCopyType,
CaseManagementTable,
CaseModuleQueryParams,
CreateOrUpdateModule,
DeleteCaseType,
ModulesTreeType,
MoveModules,
UpdateModule,
} from '@/models/caseManagement/featureCase';
import type { CommonList, TableQueryParams } from '@/models/common';
// 获取模块树
export function getCaseModuleTree(projectId: string) {
return MSR.get<ModulesTreeType[]>({ url: `${GetCaseModuleTreeUrl}/${projectId}` });
}
// 创建模块树
export function createCaseModuleTree(data: CreateOrUpdateModule) {
return MSR.post({ url: CreateCaseModuleTreeUrl, data });
}
// 更新模块树
export function updateCaseModuleTree(data: UpdateModule) {
return MSR.post({ url: UpdateCaseModuleTreeUrl, data });
}
// 移动模块树
export function moveCaseModuleTree(data: MoveModules) {
return MSR.post({ url: MoveCaseModuleTreeUrl, data });
}
// 回收站-模块-获取模块树
export function getTrashCaseModuleTree(projectId: string) {
return MSR.get<ModulesTreeType[]>({ url: `${GetTrashCaseModuleTreeUrl}/${projectId}` });
}
// 删除模块
export function deleteCaseModuleTree(id: string) {
return MSR.get({ url: `${DeleteCaseModuleTreeUrl}/${id}` });
}
// 用例分页表
export function getCaseList(data: TableQueryParams) {
return MSR.post<CommonList<CaseManagementTable>>({ url: GetCaseListUrl, data });
}
// 删除用例
export function deleteCaseRequest(data: DeleteCaseType) {
return MSR.post({ url: `${DeleteCaseUrl}`, data });
}
// 获取默认模版自定义字段
export function getCaseDefaultFields(projectId: string) {
return MSR.get({ url: `${GetDefaultTemplateFieldsUrl}/${projectId}` });
}
// 获取关联文件列表
export function getAssociatedFileListUrl(data: TableQueryParams) {
return MSR.post<CommonList<AssociatedList>>({ url: GetAssociatedFilePageUrl, data });
}
// 创建用例
export function createCaseRequest(data: Record<string, any>) {
return MSR.uploadFile({ url: CreateCaseUrl }, { request: data.request, fileList: data.fileList }, '', true);
}
// 编辑用例
export function updateCaseRequest(data: Record<string, any>) {
return MSR.uploadFile({ url: UpdateCaseUrl }, { request: data.request, fileList: data.fileList }, '', true);
}
// 用例详情
export function getCaseDetail(id: string) {
return MSR.get({ url: `${DetailCaseUrl}/${id}` });
}
// 批量删除用例
export function batchDeleteCase(data: BatchDeleteType) {
return MSR.post({ url: `${BatchDeleteCaseUrl}`, data });
}
// 批量编辑属性
export function batchEditAttrs(data: BatchEditCaseType) {
return MSR.post({ url: `${BatchEditCaseUrl}`, data });
}
// 批量移动到模块
export function batchMoveToModules(data: BatchMoveOrCopyType) {
return MSR.post({ url: `${BatchMoveCaseUrl}`, data });
}
// 批量复制到模块
export function batchCopyToModules(data: BatchMoveOrCopyType) {
return MSR.post({ url: `${BatchCopyCaseUrl}`, data });
}
// 回收站
// 回收站用例分页表
export function getRecycleListRequest(data: TableQueryParams) {
return MSR.post<CommonList<CaseManagementTable>>({ url: GetRecycleCaseListUrl, data });
}
// 获取回收站模块数量
export function getRecycleModulesCounts(data: CaseModuleQueryParams) {
return MSR.post({ url: GetRecycleCaseModulesCountUrl, data });
}
// 获取全部用例模块数量
export function getCaseModulesCounts(data: CaseModuleQueryParams) {
return MSR.post({ url: GetCaseModulesCountUrl, data });
}
// 批量恢复回收站用例表
export function restoreCaseList(data: BatchMoveOrCopyType) {
return MSR.post({ url: RestoreCaseListUrl, data });
}
// 批量彻底删除回收站用例表
export function batchDeleteRecycleCase(data: BatchMoveOrCopyType) {
return MSR.post({ url: BatchDeleteRecycleCaseListUrl, data });
}
// 恢复回收站单个用例
export function recoverRecycleCase(id: string) {
return MSR.get({ url: `${RecoverRecycleCaseListUrl}/${id}` });
}
// 删除回收站单个用例
export function deleteRecycleCaseList(id: string) {
return MSR.get({ url: `${DeleteRecycleCaseListUrl}/${id}` });
}
export default {};

View File

@ -12,13 +12,13 @@ import {
} from '@/api/requrls/setting/member'; } from '@/api/requrls/setting/member';
import type { CommonList, TableQueryParams } from '@/models/common'; import type { CommonList, TableQueryParams } from '@/models/common';
import type { AddorUpdateMemberModel, BatchAddProjectModel, LinkItem, MemberItem } from '@/models/setting/member'; import type { AddOrUpdateMemberModel, BatchAddProjectModel, LinkItem, MemberItem } from '@/models/setting/member';
// 获取成员列表 // 获取成员列表
export function getMemberList(data: TableQueryParams) { export function getMemberList(data: TableQueryParams) {
return MSR.post<CommonList<MemberItem>>({ url: GetMemberListUrl, data }); return MSR.post<CommonList<MemberItem>>({ url: GetMemberListUrl, data });
} }
// 添加成员 // 添加成员
export function addOrUpdate(data: AddorUpdateMemberModel, type: string) { export function addOrUpdate(data: AddOrUpdateMemberModel, type: string) {
if (type === 'add') { if (type === 'add') {
return MSR.post({ url: AddMemberUrl, data }); return MSR.post({ url: AddMemberUrl, data });
} }

View File

@ -0,0 +1,62 @@
// 用例管理列表
export const GetCaseListUrl = '/functional/case/page';
// 用例管理-添加
export const CreateCaseUrl = '/functional/case/add';
// 用例管理-更新
export const UpdateCaseUrl = '/functional/case/update';
// 用例管理-删除
export const DeleteCaseUrl = '/functional/case/delete';
// 用例管理-详情
export const DetailCaseUrl = '/functional/case/detail';
// 用例管理-批量移动用例
export const BatchMoveCaseUrl = '/functional/case/batch/move';
// 用例管理-批量删除用例
export const BatchDeleteCaseUrl = '/functional/case/batch/delete-to-gc';
// 用例管理-批量删除用例
export const BatchEditCaseUrl = '/functional/case/batch/edit';
// 用例管理-批量复制
export const BatchCopyCaseUrl = '/functional/case/batch/copy';
// 用例管理-关注/取消关注用例
export const FollowerCaseUrl = '/functional/case/edit/follower';
// 获取用例关注人
export const GetCaseFollowerUrl = '/functional/case/follower';
// 获取用例模板自定义字段
export const GetCaseCustomFieldsUrl = '/functional/case/default/template/field';
// 获取表头自定义字段(高级搜索中的自定义字段)
export const GetSearchCustomFieldsUrl = '/functional/case/custom/field';
// 关联文件列表
export const GetAssociatedFilePageUrl = '/attachment/page';
// 获取模块树
export const GetCaseModuleTreeUrl = '/functional/case/module/tree';
// 创建模块树
export const CreateCaseModuleTreeUrl = '/functional/case/module/add';
// 更新模块树
export const UpdateCaseModuleTreeUrl = '/functional/case/module/update';
// 移动模块
export const MoveCaseModuleTreeUrl = '/functional/case/module/move';
// 回收站-模块-获取模块树
export const GetTrashCaseModuleTreeUrl = '/functional/case/module/trash/tree';
// 删除模块
export const DeleteCaseModuleTreeUrl = '/functional/case/module/delete';
// 获取默认模版自定义字段
export const GetDefaultTemplateFieldsUrl = '/functional/case/default/template/field';
// 回收站
// 回收站分页
export const GetRecycleCaseListUrl = '/functional/case/trash/page';
// 获取回收站模块数量
export const GetRecycleCaseModulesCountUrl = '/functional/case/trash/module/count';
// 获取全部用例模块数量
export const GetCaseModulesCountUrl = '/functional/case/module/count';
// 恢复回收站用例表
export const RestoreCaseListUrl = '/functional/case/trash/batch/recover';
// 批量彻底删除回收站用例表
export const BatchDeleteRecycleCaseListUrl = '/functional/case/trash/batch/delete';
// 恢复回收站单个用例
export const RecoverRecycleCaseListUrl = '/functional/case/trash/recover';
// 删除回收站单个用例
export const DeleteRecycleCaseListUrl = '/functional/case/trash/delete';
export default {};

View File

@ -1,7 +1,7 @@
@font-face { @font-face {
font-family: iconfont; /* Project id 3462279 */ font-family: iconfont; /* Project id 3462279 */
src: url('iconfont.woff2?t=1697180140168') format('woff2'), url('iconfont.woff?t=1697180140168') format('woff'), src: url('iconfont.woff2?t=1700905969825') format('woff2'), url('iconfont.woff?t=1700905969825') format('woff'),
url('iconfont.ttf?t=1697180140168') format('truetype'), url('iconfont.svg?t=1697180140168#iconfont') format('svg'); url('iconfont.ttf?t=1700905969825') format('truetype'), url('iconfont.svg?t=1700905969825#iconfont') format('svg');
} }
.iconfont { .iconfont {
font-size: 16px; font-size: 16px;
@ -10,6 +10,102 @@
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
.icon-icon_text-wrap-overflow::before {
content: '\e78f';
}
.icon-icon_template_filled::before {
content: '\e78e';
}
.icon-icon_laser_filled::before {
content: '\e78b';
}
.icon-icon_stop_outlined::before {
content: '\e78c';
}
.icon-icon_card_filled::before {
content: '\e78d';
}
.icon-icon_community-tab_outlined::before {
content: '\e77e';
}
.icon-icon_play-round_filled1::before {
content: '\e77f';
}
.icon-icon_thumbdown_outlined::before {
content: '\e780';
}
.icon-icon_file-doc_colorful::before {
content: '\e781';
}
.icon-icon-product-forum_filled::before {
content: '\e782';
}
.icon-icon_file-add_colorful::before {
content: '\e783';
}
.icon-icon_group_filled::before {
content: '\e784';
}
.icon-icon_send_colorful::before {
content: '\e785';
}
.icon-icon-product-forum_colorful::before {
content: '\e786';
}
.icon-icon_nearby-group_outlined::before {
content: '\e787';
}
.icon-icon_thumbdown_filled::before {
content: '\e788';
}
.icon-icon_thumbsup_filled::before {
content: '\e789';
}
.icon-icon_file-add_outlined::before {
content: '\e78a';
}
.icon-icon_thumbsup_outlined::before {
content: '\e77b';
}
.icon-icon-product-forum_outlined::before {
content: '\e77c';
}
.icon-icon_test-tracking_colorful::before {
content: '\e77d';
}
.icon-icon_reply::before {
content: '\e778';
}
.icon-icon_comment_collapse_text_input::before {
content: '\e779';
}
.icon-icon_comment_expand_text_input::before {
content: '\e77a';
}
.icon-icon_chart_graph::before {
content: '\e777';
}
.icon-icon_share1::before {
content: '\e776';
}
.icon-icon_text::before {
content: '\e775';
}
.icon-icon_click::before {
content: '\e697';
}
.icon-icon_feedback_outlined::before {
content: '\e773';
}
.icon-icon_done_outlined::before {
content: '\e774';
}
.icon-icon_dataset_outlined::before {
content: '\e772';
}
.icon-icon_outer-borders_outlined::before {
content: '\e771';
}
.icon-icon_checkbox::before { .icon-icon_checkbox::before {
content: '\e76e'; content: '\e76e';
} }

File diff suppressed because one or more lines are too long

View File

@ -5,6 +5,230 @@
"css_prefix_text": "icon-", "css_prefix_text": "icon-",
"description": "DE、MS项目icon管理", "description": "DE、MS项目icon管理",
"glyphs": [ "glyphs": [
{
"icon_id": "38195845",
"name": "icon_text-wrap-overflow",
"font_class": "icon_text-wrap-overflow",
"unicode": "e78f",
"unicode_decimal": 59279
},
{
"icon_id": "38036023",
"name": "icon_template_filled",
"font_class": "icon_template_filled",
"unicode": "e78e",
"unicode_decimal": 59278
},
{
"icon_id": "38020820",
"name": "icon_laser_filled",
"font_class": "icon_laser_filled",
"unicode": "e78b",
"unicode_decimal": 59275
},
{
"icon_id": "38020819",
"name": "icon_stop_outlined",
"font_class": "icon_stop_outlined",
"unicode": "e78c",
"unicode_decimal": 59276
},
{
"icon_id": "38020817",
"name": "icon_card_filled",
"font_class": "icon_card_filled",
"unicode": "e78d",
"unicode_decimal": 59277
},
{
"icon_id": "38020824",
"name": "icon_community-tab_outlined",
"font_class": "icon_community-tab_outlined",
"unicode": "e77e",
"unicode_decimal": 59262
},
{
"icon_id": "38020833",
"name": "icon_play-round_filled",
"font_class": "icon_play-round_filled1",
"unicode": "e77f",
"unicode_decimal": 59263
},
{
"icon_id": "38020826",
"name": "icon_thumbdown_outlined",
"font_class": "icon_thumbdown_outlined",
"unicode": "e780",
"unicode_decimal": 59264
},
{
"icon_id": "38020832",
"name": "icon_file-doc_colorful",
"font_class": "icon_file-doc_colorful",
"unicode": "e781",
"unicode_decimal": 59265
},
{
"icon_id": "38020828",
"name": "icon-product-forum_filled",
"font_class": "icon-product-forum_filled",
"unicode": "e782",
"unicode_decimal": 59266
},
{
"icon_id": "38020827",
"name": "icon_file-add_colorful",
"font_class": "icon_file-add_colorful",
"unicode": "e783",
"unicode_decimal": 59267
},
{
"icon_id": "38020830",
"name": "icon_group_filled",
"font_class": "icon_group_filled",
"unicode": "e784",
"unicode_decimal": 59268
},
{
"icon_id": "38020829",
"name": "icon_send_colorful",
"font_class": "icon_send_colorful",
"unicode": "e785",
"unicode_decimal": 59269
},
{
"icon_id": "38020823",
"name": "icon-product-forum_colorful",
"font_class": "icon-product-forum_colorful",
"unicode": "e786",
"unicode_decimal": 59270
},
{
"icon_id": "38020822",
"name": "icon_nearby-group_outlined",
"font_class": "icon_nearby-group_outlined",
"unicode": "e787",
"unicode_decimal": 59271
},
{
"icon_id": "38020825",
"name": "icon_thumbdown_filled",
"font_class": "icon_thumbdown_filled",
"unicode": "e788",
"unicode_decimal": 59272
},
{
"icon_id": "38020821",
"name": "icon_thumbsup_filled",
"font_class": "icon_thumbsup_filled",
"unicode": "e789",
"unicode_decimal": 59273
},
{
"icon_id": "38020818",
"name": "icon_file-add_outlined",
"font_class": "icon_file-add_outlined",
"unicode": "e78a",
"unicode_decimal": 59274
},
{
"icon_id": "38020835",
"name": "icon_thumbsup_outlined",
"font_class": "icon_thumbsup_outlined",
"unicode": "e77b",
"unicode_decimal": 59259
},
{
"icon_id": "38020834",
"name": "icon-product-forum_outlined",
"font_class": "icon-product-forum_outlined",
"unicode": "e77c",
"unicode_decimal": 59260
},
{
"icon_id": "38020831",
"name": "icon_test-tracking_colorful",
"font_class": "icon_test-tracking_colorful",
"unicode": "e77d",
"unicode_decimal": 59261
},
{
"icon_id": "37903599",
"name": "icon_reply",
"font_class": "icon_reply",
"unicode": "e778",
"unicode_decimal": 59256
},
{
"icon_id": "37903597",
"name": "icon_comment_collapse_text_input",
"font_class": "icon_comment_collapse_text_input",
"unicode": "e779",
"unicode_decimal": 59257
},
{
"icon_id": "37903598",
"name": "icon_comment_expand_text_input",
"font_class": "icon_comment_expand_text_input",
"unicode": "e77a",
"unicode_decimal": 59258
},
{
"icon_id": "37878729",
"name": "icon_chart_graph",
"font_class": "icon_chart_graph",
"unicode": "e777",
"unicode_decimal": 59255
},
{
"icon_id": "37848043",
"name": "icon_share",
"font_class": "icon_share1",
"unicode": "e776",
"unicode_decimal": 59254
},
{
"icon_id": "37842152",
"name": "icon_text",
"font_class": "icon_text",
"unicode": "e775",
"unicode_decimal": 59253
},
{
"icon_id": "37841481",
"name": "icon_click",
"font_class": "icon_click",
"unicode": "e697",
"unicode_decimal": 59031
},
{
"icon_id": "37789808",
"name": "icon_feedback_outlined",
"font_class": "icon_feedback_outlined",
"unicode": "e773",
"unicode_decimal": 59251
},
{
"icon_id": "37789807",
"name": "icon_done_outlined",
"font_class": "icon_done_outlined",
"unicode": "e774",
"unicode_decimal": 59252
},
{
"icon_id": "37789689",
"name": "icon_dataset_outlined",
"font_class": "icon_dataset_outlined",
"unicode": "e772",
"unicode_decimal": 59250
},
{
"icon_id": "37715855",
"name": "icon_outer-borders_outlined",
"font_class": "icon_outer-borders_outlined",
"unicode": "e771",
"unicode_decimal": 59249
},
{ {
"icon_id": "37662517", "icon_id": "37662517",
"name": "icon_checkbox", "name": "icon_checkbox",
@ -1608,13 +1832,6 @@
"unicode": "e695", "unicode": "e695",
"unicode_decimal": 59029 "unicode_decimal": 59029
}, },
{
"icon_id": "32849836",
"name": "icon_logs_outlined-1",
"font_class": "icon_logs_outlined-1",
"unicode": "e697",
"unicode_decimal": 59031
},
{ {
"icon_id": "32849837", "icon_id": "32849837",
"name": "icon_refresh_outlined", "name": "icon_refresh_outlined",

View File

@ -14,6 +14,70 @@
/> />
<missing-glyph /> <missing-glyph />
<glyph glyph-name="icon_text-wrap-overflow" unicode="&#59279;" d="M746.666667 488.32a234.666667 234.666667 0 0 0 0-469.333333h-78.208v-52.181334a26.069333 26.069333 0 0 0-41.728-20.821333l-111.232 83.413333a52.138667 52.138667 0 0 0 0 83.456l111.232 83.413334a26.069333 26.069333 0 0 0 41.728-20.864v-52.138667H746.666667a130.389333 130.389333 0 1 1 0 260.736H94.805333a52.138667 52.138667 0 0 0 0 104.32H746.666667zM303.402667 123.264a52.138667 52.138667 0 0 0 0-104.277333H94.805333a52.138667 52.138667 0 0 0 0 104.277333h208.64zM94.805333 853.333333h834.389334a52.138667 52.138667 0 0 0 0-104.277333H94.805333a52.138667 52.138667 0 1 0 0 104.277333z" horiz-adv-x="1024" />
<glyph glyph-name="icon_template_filled" unicode="&#59278;" d="M728.96 853.333333H170.666667a42.666667 42.666667 0 0 1-42.666667-42.666666v-853.333334a42.666667 42.666667 0 0 1 42.666667-42.666666h682.666666a42.666667 42.666667 0 0 1 42.666667 42.666666V686.250667a42.666667 42.666667 0 0 1-12.501333 30.165333l-124.330667 124.416A42.666667 42.666667 0 0 1 729.002667 853.333333zM298.666667 533.333333a21.333333 21.333333 0 0 0 21.333333 21.333334h384a21.333333 21.333333 0 0 0 21.333333-21.333334v-42.666666a21.333333 21.333333 0 0 0-21.333333-21.333334h-384a21.333333 21.333333 0 0 0-21.333333 21.333334v42.666666z m170.666666-170.666666a21.333333 21.333333 0 0 0 21.333334 21.333333h213.333333a21.333333 21.333333 0 0 0 21.333333-21.333333v-213.333334a21.333333 21.333333 0 0 0-21.333333-21.333333h-213.333333a21.333333 21.333333 0 0 0-21.333334 21.333333v213.333334z m-170.666666 0a21.333333 21.333333 0 0 0 21.333333 21.333333h42.666667a21.333333 21.333333 0 0 0 21.333333-21.333333v-213.333334a21.333333 21.333333 0 0 0-21.333333-21.333333h-42.666667a21.333333 21.333333 0 0 0-21.333333 21.333333v213.333334z" horiz-adv-x="1024" />
<glyph glyph-name="icon_laser_filled" unicode="&#59275;" d="M533.248 604.245333a234.666667 234.666667 0 1 1-241.664-241.493333l-36.693333 91.776a149.333333 149.333333 0 1 0 186.666666 186.368l91.690667-36.693333zM523.818667-45.397333c14.464-36.096 65.664-35.669333 79.488 0.64l92.714666 242.858666 244.864 94.592c36.138667 13.994667 36.437333 65.024 0.426667 79.445334L337.962667 613.504c-34.858667 13.909333-69.418667-20.650667-55.466667-55.466667l241.322667-603.434666z" horiz-adv-x="1024" />
<glyph glyph-name="icon_stop_outlined" unicode="&#59276;" d="M981.333333 384c0-259.2-210.133333-469.333333-469.333333-469.333333S42.666667 124.8 42.666667 384 252.8 853.333333 512 853.333333s469.333333-210.133333 469.333333-469.333333z m-85.333333 0a384 384 0 1 1-768 0 384 384 0 0 1 768 0zM384 554.666667h256c23.466667 0 42.666667-19.072 42.666667-42.666667v-256c0-23.552-19.2-42.666667-42.666667-42.666667H384c-23.466667 0-42.666667 19.114667-42.666667 42.666667V512c0 23.594667 19.2 42.666667 42.666667 42.666667z" horiz-adv-x="1024" />
<glyph glyph-name="icon_card_filled" unicode="&#59277;" d="M426.666667 810.666667H128a42.666667 42.666667 0 0 1-42.666667-42.666667v-298.666667a42.666667 42.666667 0 0 1 42.666667-42.666666h298.666667a42.666667 42.666667 0 0 1 42.666666 42.666666V768a42.666667 42.666667 0 0 1-42.666666 42.666667zM426.666667 341.333333H128a42.666667 42.666667 0 0 1-42.666667-42.666666v-298.666667a42.666667 42.666667 0 0 1 42.666667-42.666667h298.666667a42.666667 42.666667 0 0 1 42.666666 42.666667v298.666667a42.666667 42.666667 0 0 1-42.666666 42.666666zM896 810.666667h-298.666667a42.666667 42.666667 0 0 1-42.666666-42.666667v-298.666667a42.666667 42.666667 0 0 1 42.666666-42.666666h298.666667a42.666667 42.666667 0 0 1 42.666667 42.666666V768a42.666667 42.666667 0 0 1-42.666667 42.666667zM893.482667 341.333333H599.893333c-24.96 0-45.184-20.224-45.184-45.184v-293.632c0-24.96 20.224-45.184 45.184-45.184h293.632c24.96 0 45.184 20.224 45.184 45.184V296.106667c0 24.96-20.224 45.184-45.184 45.184z" horiz-adv-x="1024" />
<glyph glyph-name="icon_community-tab_outlined" unicode="&#59262;" d="M213.333333 167.808c0 142.933333 120.021333 258.816 258.901334 258.816L552.149333 426.666667c138.922667 0 258.56-115.882667 258.56-258.858667V21.333333a42.666667 42.666667 0 0 0-42.666666-42.666666H256a42.666667 42.666667 0 0 0-42.666667 42.666666v146.474667z m258.901334 173.482667C379.605333 341.333333 298.666667 262.784 298.666667 167.808V64h426.666666v103.808C725.333333 262.912 644.693333 341.333333 552.192 341.333333h-79.914667zM617.6 555.733333A149.333333 149.333333 0 0 0 513.152 512H510.976a149.333333 149.333333 0 1 0 106.666667 43.733333zM512 725.333333a64 64 0 1 1 0-128 64 64 0 0 1 0 128zM320 522.666667a96 96 0 1 0-192 0 96 96 0 0 0 192 0z m-64 0a32 32 0 1 1-64 0 32 32 0 0 1 64 0zM215.253333 384C111.061333 384 64 297.088 64 189.866667v-115.2c0-17.706667 14.336-32 32-32H149.333333v128c0 154.837333 72.96 189.781333 116.309334 210.602666l4.266666 2.048A183.850667 183.850667 0 0 1 253.781333 384h-38.570666zM808.704 384c104.192 0 151.210667-86.912 151.210667-194.133333v-115.2a32 32 0 0 0-32-32h-53.333334v128c0 154.837333-72.874667 189.781333-116.266666 210.602666l-4.266667 2.048c5.333333 0.426667 10.666667 0.682667 16.085333 0.682667h38.570667zM800 426.666667a96 96 0 1 1 0 192 96 96 0 0 1 0-192z m0 64a32 32 0 1 0 0 64 32 32 0 0 0 0-64z" horiz-adv-x="1024" />
<glyph glyph-name="icon_play-round_filled1" unicode="&#59263;" d="M512 853.333333c259.2 0 469.333333-210.133333 469.333333-469.333333s-210.133333-469.333333-469.333333-469.333333S42.666667 124.8 42.666667 384 252.8 853.333333 512 853.333333zM421.077333 597.333333c-9.813333 0-19.2-3.84-26.24-10.666666A35.797333 35.797333 0 0 1 384 560.896v-353.92c0-6.826667 1.962667-13.44 5.632-19.242667a37.674667 37.674667 0 0 1 51.072-11.562666l288.554667 176.938666A36.352 36.352 0 0 1 746.666667 384a36.352 36.352 0 0 1-17.408 30.890667L440.746667 591.786667A37.589333 37.589333 0 0 1 421.077333 597.333333z" horiz-adv-x="1024" />
<glyph glyph-name="icon_thumbdown_outlined" unicode="&#59264;" d="M128.341333 814.08H42.666667v-599.68h85.674666C128 213.333333 128 810.666667 128.341333 814.08z m492.544-599.722667h183.722667c127.786667 0 157.141333 112.810667 127.786667 197.333334l-127.786667 336.469333A90.88 90.88 0 0 1 716.8 814.421333H213.674667a21.418667 21.418667 0 0 1-21.376-21.376v-557.312c0-11.818667 9.557333-21.418667 21.376-21.418666H257.706667c13.952 0 27.008-6.784 35.029333-18.176l177.834667-252.970667c12.032-19.157333 44.544-33.834667 79.146666-18.048 51.968 23.637333 114.133333 75.264 114.133334 160.853333 0 32.256-14.336 75.050667-42.965334 128.341334z m183.722667 85.632h-327.04l67.84-126.208c22.570667-41.984 32.768-72.490667 32.768-87.808 0-24.874667-5.205333-52.992-49.834667-76.032l-191.104 271.872a42.837333 42.837333 0 0 1-35.029333 18.176h-24.277333V728.789333H716.8c2.645333 0 4.693333-1.578667 5.205333-3.413333l1.066667-3.882667 128.768-338.944c16.938667-49.877333 0.042667-82.56-47.232-82.56z" horiz-adv-x="1024" />
<glyph glyph-name="icon_file-doc_colorful" unicode="&#59265;" d="M170.666667 789.333333a42.666667 42.666667 0 0 0 42.666666 42.666667h420.693334a21.333333 21.333333 0 0 0 15.232-6.4l197.973333-201.386667a21.333333 21.333333 0 0 0 6.101333-14.933333V-21.333333a42.666667 42.666667 0 0 0-42.666666-42.666667H213.333333a42.666667 42.666667 0 0 0-42.666666 42.666667v810.666666zM640 831.146667a21.333333 21.333333 0 0 0 9.258667-5.546667l197.973333-201.386667a21.333333 21.333333 0 0 0 3.925333-5.546666h-162.730666A48.426667 48.426667 0 0 0 640 667.093333V831.146667zM344.448 461.568h323.498667c3.84 0 6.954667-3.114667 6.954666-6.954667v-32.597333c0-3.84-3.114667-6.997333-6.954666-6.997333H344.448a6.997333 6.997333 0 0 0-6.997333 6.997333v32.597333c0 3.84 3.114667 6.954667 6.997333 6.954667z m0-139.648h323.498667c3.84 0 6.954667-3.114667 6.954666-6.954667v-32.597333c0-3.84-3.114667-6.997333-6.954666-6.997333H344.448a6.997333 6.997333 0 0 0-6.997333 6.997333v32.597333c0 3.84 3.114667 6.954667 6.997333 6.954667z m0-139.605333h172.202667c3.84 0 6.997333-3.114667 6.997333-6.997334v-32.597333c0-3.84-3.114667-6.954667-6.997333-6.954667H344.448a6.997333 6.997333 0 0 0-6.997333 6.954667v32.597333c0 3.84 3.114667 6.997333 6.997333 6.997334z" horiz-adv-x="1024" />
<glyph glyph-name="icon-product-forum_filled" unicode="&#59266;" d="M725.333333 426.666667a42.666667 42.666667 0 0 0 42.666667 42.666666h170.666667a42.666667 42.666667 0 0 0 42.666666-42.666666v-341.333334a42.666667 42.666667 0 0 0-42.666666-42.666666h-89.002667l-51.498667-51.498667a42.666667 42.666667 0 0 0-60.330666 0L686.336 42.666667H469.333333a42.666667 42.666667 0 0 0-42.666666 42.666666v170.666667a42.666667 42.666667 0 0 0 42.666666 42.666667h256v128z m85.333334-42.666667v-128a42.666667 42.666667 0 0 0-42.666667-42.666667h-256v-85.333333h192a42.666667 42.666667 0 0 0 30.165333-12.501333l33.834667-33.834667 33.834667 33.834667A42.666667 42.666667 0 0 0 832 128H896v256h-85.333333zM85.333333 810.666667a42.666667 42.666667 0 0 1-42.666666-42.666667v-512a42.666667 42.666667 0 0 1 42.666666-42.666667h89.002667l72.832-72.832a42.666667 42.666667 0 0 1 60.330667 0L380.330667 213.333333H768a42.666667 42.666667 0 0 1 42.666667 42.666667V768a42.666667 42.666667 0 0 1-42.666667 42.666667H85.333333z m213.333334-298.666667a42.666667 42.666667 0 1 0-85.333334 0 42.666667 42.666667 0 0 0 85.333334 0z m170.666666 0a42.666667 42.666667 0 1 0-85.333333 0 42.666667 42.666667 0 0 0 85.333333 0z m170.666667 0a42.666667 42.666667 0 1 0-85.333333 0 42.666667 42.666667 0 0 0 85.333333 0z" horiz-adv-x="1024" />
<glyph glyph-name="icon_file-add_colorful" unicode="&#59267;" d="M170.666667 783.573333C170.666667 810.325333 191.744 832 217.728 832h425.130667c6.229333 0 12.202667-2.56 16.597333-7.04l186.88-190.634667a24.576 24.576 0 0 0 6.997333-17.194666v-632.704c0-26.752-21.077333-48.426667-47.061333-48.426667H217.728C191.744-64 170.666667-42.325333 170.666667-15.573333V783.573333zM652.586667 829.866667a23.466667 23.466667 0 0 0 6.869333-4.906667l186.922667-190.634667a24.32 24.32 0 0 0 5.290666-8.149333h-152.021333c-25.984 0-47.061333 21.674667-47.061333 48.426667V829.866667zM512 469.333333a21.333333 21.333333 0 0 1-21.333333-21.333333v-128h-128a21.333333 21.333333 0 0 1 0-42.666667h128v-128a21.333333 21.333333 0 0 1 42.666666 0v128h128a21.333333 21.333333 0 0 1 0 42.666667h-128v128a21.333333 21.333333 0 0 1-21.333333 21.333333z" horiz-adv-x="1024" />
<glyph glyph-name="icon_group_filled" unicode="&#59268;" d="M362.666667 384a192 192 0 1 1 0 384 192 192 0 0 1 0-384zM64-42.666667a42.666667 42.666667 0 0 0-42.666667 42.666667v82.474667C21.333333 225.408 141.354667 341.333333 280.234667 341.333333h165.248c138.88 0 258.517333-115.882667 258.517333-258.858666V0a42.666667 42.666667 0 0 0-42.666667-42.666667h-597.333333zM789.333333 0v12.629333c0 98.645333 0 200.704-85.333333 264.704 3.584 0.298667 9.557333 0.213333 15.36 0.085334l7.850667-0.085334H810.666667c104.192 0 192-65.578667 192-172.8v-72.533333a32 32 0 0 0-32-32H789.333333zM746.666667 341.333333a128 128 0 1 1 0 256 128 128 0 0 1 0-256z" horiz-adv-x="1024" />
<glyph glyph-name="icon_send_colorful" unicode="&#59269;" d="M512 332.373333v-267.349333l146.773333-132.266667a43.477333 43.477333 0 0 1 72.661334 19.541334l220.373333 780.544c6.058667 21.376-20.522667 36.608-35.925333 20.565333L512 332.373333zM881.706667 788.181333c16.213333 15.573333 0.426667 42.410667-21.034667 35.797334L81.92 583.424a43.434667 43.434667 0 0 1-19.712-72.704l133.888-146.645333h243.413333l442.154667 424.106666z" horiz-adv-x="1024" />
<glyph glyph-name="icon-product-forum_colorful" unicode="&#59270;" d="M725.333333 426.666667a42.666667 42.666667 0 0 0 42.666667 42.666666h170.666667a42.666667 42.666667 0 0 0 42.666666-42.666666v-341.333334a42.666667 42.666667 0 0 0-42.666666-42.666666h-89.002667l-51.498667-51.498667a42.666667 42.666667 0 0 0-60.330666 0L686.336 42.666667H469.333333a42.666667 42.666667 0 0 0-42.666666 42.666666v170.666667a42.666667 42.666667 0 0 0 42.666666 42.666667h256v128z m85.333334-42.666667v-128a42.666667 42.666667 0 0 0-42.666667-42.666667h-256v-85.333333h192a42.666667 42.666667 0 0 0 30.165333-12.501333l33.834667-33.834667 33.834667 33.834667A42.666667 42.666667 0 0 0 832 128H896v256h-85.333333zM85.333333 810.666667a42.666667 42.666667 0 0 1-42.666666-42.666667v-512a42.666667 42.666667 0 0 1 42.666666-42.666667h89.002667l72.832-72.832a42.666667 42.666667 0 0 1 60.330667 0L380.330667 213.333333H768a42.666667 42.666667 0 0 1 42.666667 42.666667V768a42.666667 42.666667 0 0 1-42.666667 42.666667H85.333333z m213.333334-298.666667a42.666667 42.666667 0 1 0-85.333334 0 42.666667 42.666667 0 0 0 85.333334 0z m170.666666 0a42.666667 42.666667 0 1 0-85.333333 0 42.666667 42.666667 0 0 0 85.333333 0z m170.666667 0a42.666667 42.666667 0 1 0-85.333333 0 42.666667 42.666667 0 0 0 85.333333 0z" horiz-adv-x="1024" />
<glyph glyph-name="icon_nearby-group_outlined" unicode="&#59271;" d="M256 426.666667a170.666667 170.666667 0 1 1 0 341.333333 170.666667 170.666667 0 0 1 0-341.333333z m0 85.333333a85.333333 85.333333 0 1 0 0 170.666667 85.333333 85.333333 0 0 0 0-170.666667zM42.666667 213.333333a170.666667 170.666667 0 0 0 170.666666 170.666667h85.333334a170.666667 170.666667 0 0 0 170.666666-170.666667v-170.666666a42.666667 42.666667 0 0 0-42.666666-42.666667H85.333333a42.666667 42.666667 0 0 0-42.666666 42.666667v170.666666z m256 85.333334H213.333333a85.333333 85.333333 0 0 1-85.333333-85.333334v-128h256v128a85.333333 85.333333 0 0 1-85.333333 85.333334zM554.666667 213.333333a170.666667 170.666667 0 0 0 170.666666 170.666667h85.333334a170.666667 170.666667 0 0 0 170.666666-170.666667v-170.666666a42.666667 42.666667 0 0 0-42.666666-42.666667h-341.333334a42.666667 42.666667 0 0 0-42.666666 42.666667v170.666666z m85.333333 0v-128h256v128a85.333333 85.333333 0 0 1-85.333333 85.333334h-85.333334a85.333333 85.333333 0 0 1-85.333333-85.333334zM938.666667 597.333333a170.666667 170.666667 0 1 1-341.333334 0 170.666667 170.666667 0 0 1 341.333334 0z m-85.333334 0a85.333333 85.333333 0 1 0-170.666666 0 85.333333 85.333333 0 0 0 170.666666 0z" horiz-adv-x="1024" />
<glyph glyph-name="icon_thumbdown_filled" unicode="&#59272;" d="M620.842667 210.56h183.722666c127.829333 0 157.184 112.810667 127.829334 197.376l-127.829334 336.426667A90.88 90.88 0 0 1 716.8 810.666667H213.76a21.418667 21.418667 0 0 1-21.461333-21.418667v-557.269333c0-11.818667 9.6-21.418667 21.418666-21.418667H257.706667c13.952 0 27.008-6.826667 35.029333-18.218667l177.834667-252.928c12.032-19.2 44.501333-33.834667 79.146666-18.090666 51.925333 23.68 114.133333 75.306667 114.133334 160.853333 0 32.256-14.336 75.093333-43.008 128.384zM128 810.666667H42.325333v-599.68H128C127.658667 212.053333 127.658667 807.253333 128 810.666667z" horiz-adv-x="1024" />
<glyph glyph-name="icon_thumbsup_filled" unicode="&#59273;" d="M128.298667-42.282667H42.666667V557.354667h85.674666C128 554.666667 126.72-40.618667 128.341333-42.24zM804.565333 557.44h-183.722666c28.672 53.333333 43.008 96.128 43.008 128.384 0 85.546667-62.208 137.173333-114.133334 160.853333-34.645333 15.786667-67.114667 1.066667-79.146666-18.090666L292.693333 575.658667a42.837333 42.837333 0 0 0-35.029333-18.218667H213.674667a21.418667 21.418667 0 0 1-21.418667-21.418667v-557.269333c0-11.818667 9.6-21.418667 21.418667-21.418667H716.8a90.88 90.88 0 0 1 87.808 66.261334l127.829333 336.469333c29.354667 84.565333 0 197.376-127.829333 197.376z" horiz-adv-x="1024" />
<glyph glyph-name="icon_file-add_outlined" unicode="&#59274;" d="M213.333333 768h469.333334v-107.52a21.333333 21.333333 0 0 1 21.333333-21.333333H810.666667V341.333333h85.333333V686.293333a42.666667 42.666667 0 0 1-12.501333 30.165334l-124.330667 124.373333A42.666667 42.666667 0 0 1 729.002667 853.333333H170.666667a42.666667 42.666667 0 0 1-42.666667-42.666666v-853.333334a42.666667 42.666667 0 0 1 42.666667-42.666666h341.333333v85.333333H213.333333V768zM746.666667 256a21.333333 21.333333 0 0 1-21.333334-21.333333V128h-106.666666a21.333333 21.333333 0 0 1-21.333334-21.333333v-42.666667a21.333333 21.333333 0 0 1 21.333334-21.333333H725.333333v-106.666667a21.333333 21.333333 0 0 1 21.333334-21.333333h42.666666a21.333333 21.333333 0 0 1 21.333334 21.333333V42.666667h106.666666a21.333333 21.333333 0 0 1 21.333334 21.333333v42.666667a21.333333 21.333333 0 0 1-21.333334 21.333333H810.666667v106.666667a21.333333 21.333333 0 0 1-21.333334 21.333333h-42.666666z" horiz-adv-x="1024" />
<glyph glyph-name="icon_thumbsup_outlined" unicode="&#59259;" d="M128.341333-42.282667H42.666667V557.354667h85.674666C128 554.666667 126.72-43.946667 128.341333-42.24zM620.8 557.44h183.722667c127.829333 0 157.184-112.810667 127.829333-197.333333l-127.829333-336.469334A90.88 90.88 0 0 0 716.8-42.666667H213.76a21.418667 21.418667 0 0 0-21.461333 21.418667V536.021333c0 11.818667 9.6 21.418667 21.418666 21.418667H257.706667c13.952 0 27.008 6.826667 35.029333 18.218667L470.613333 828.586667c12.032 19.2 44.501333 33.834667 79.146667 18.090666 51.925333-23.68 114.133333-75.306667 114.133333-160.853333 0-32.256-14.336-75.093333-43.008-128.384z m183.722667-85.674667h-326.997334l67.84 126.208c22.528 41.984 32.768 72.533333 32.768 87.850667 0 24.832-5.248 52.949333-49.834666 76.032L337.237333 489.984a42.837333 42.837333 0 0 0-35.029333-18.218667h-24.277333v-428.8H716.8c2.688 0 4.736 1.578667 5.248 3.413334l1.066667 3.882666 128.768 338.944c16.896 49.92 0 82.56-47.274667 82.56z" horiz-adv-x="1024" />
<glyph glyph-name="icon-product-forum_outlined" unicode="&#59260;" d="M725.333333 426.666667a42.666667 42.666667 0 0 0 42.666667 42.666666h170.666667a42.666667 42.666667 0 0 0 42.666666-42.666666v-341.333334a42.666667 42.666667 0 0 0-42.666666-42.666666h-89.002667l-51.498667-51.498667a42.666667 42.666667 0 0 0-60.330666 0L686.336 42.666667H469.333333a42.666667 42.666667 0 0 0-42.666666 42.666666v170.666667a42.666667 42.666667 0 0 0 42.666666 42.666667h256v128z m85.333334-42.666667v-128a42.666667 42.666667 0 0 0-42.666667-42.666667h-256v-85.333333h192a42.666667 42.666667 0 0 0 30.165333-12.501333l33.834667-33.834667 33.834667 33.834667A42.666667 42.666667 0 0 0 832 128H896v256h-85.333333zM42.666667 768a42.666667 42.666667 0 0 0 42.666666 42.666667h682.666667a42.666667 42.666667 0 0 0 42.666667-42.666667v-512a42.666667 42.666667 0 0 0-42.666667-42.666667H380.330667l-72.832-72.832a42.666667 42.666667 0 0 0-60.330667 0L174.336 213.333333H85.333333a42.666667 42.666667 0 0 0-42.666666 42.666667V768z m85.333333-42.666667v-426.666666h64a42.666667 42.666667 0 0 0 30.165333-12.501334L277.333333 230.997333l55.168 55.168A42.666667 42.666667 0 0 0 362.666667 298.666667H725.333333V725.333333H128zM298.666667 512a42.666667 42.666667 0 1 0-85.333334 0 42.666667 42.666667 0 0 0 85.333334 0z m170.666666 0a42.666667 42.666667 0 1 0-85.333333 0 42.666667 42.666667 0 0 0 85.333333 0z m128-42.666667a42.666667 42.666667 0 1 1 0 85.333334 42.666667 42.666667 0 0 1 0-85.333334z" horiz-adv-x="1024" />
<glyph glyph-name="icon_test-tracking_colorful" unicode="&#59261;" d="M172.714667 808.618667H298.666667V640a42.666667 42.666667 0 0 1 42.666666-42.666667h341.333334a42.666667 42.666667 0 0 1 42.666666 42.666667V808.618667h125.952c30.677333 0 44.714667-17.237333 44.714667-44.672v-804.565334c0-27.434667-14.037333-44.714667-44.714667-44.714666H172.714667C141.994667-85.333333 128-68.096 128-40.618667V763.946667c0 27.392 14.037333 44.672 44.714667 44.672zM321.024 426.666667a22.357333 22.357333 0 0 1-22.357333-22.357334v-44.714666c0-12.330667 9.984-22.314667 22.357333-22.314667h381.952c12.373333 0 22.357333 9.984 22.357333 22.314667v44.714666a22.357333 22.357333 0 0 1-22.357333 22.357334H321.024z m0-213.333334a22.357333 22.357333 0 0 1-22.357333-22.357333v-44.672c0-12.373333 9.984-22.357333 22.357333-22.357333h394.154667c12.373333 0 22.357333 9.984 22.357333 22.357333v44.672a22.357333 22.357333 0 0 1-22.357333 22.357333H321.024zM405.333333 853.333333a21.333333 21.333333 0 0 1-21.333333-21.333333v-128a21.333333 21.333333 0 0 1 21.333333-21.333333h213.333334a21.333333 21.333333 0 0 1 21.333333 21.333333v128a21.333333 21.333333 0 0 1-21.333333 21.333333h-213.333334z" horiz-adv-x="1024" />
<glyph glyph-name="icon_reply" unicode="&#59256;" d="M938.666667 768a42.666667 42.666667 0 0 0 42.666666-42.666667v-640a42.666667 42.666667 0 0 0-42.666666-42.666666H500.693333l-204.288-102.144a42.666667 42.666667 0 0 0-61.44 33.237333L234.666667-21.333333V42.666667H85.333333a42.666667 42.666667 0 0 0-42.368 37.674666L42.666667 85.333333V725.333333a42.666667 42.666667 0 0 0 42.666666 42.666667h853.333334z m-42.666667-85.333333H128v-554.666667h149.333333a42.666667 42.666667 0 0 0 42.368-37.674667L320 85.333333v-37.589333l151.594667 75.733333a42.666667 42.666667 0 0 0 14.122666 4.266667L490.666667 128H896V682.666667z m-170.666667-234.666667a42.666667 42.666667 0 0 0 0-85.333333H298.666667a42.666667 42.666667 0 0 0 0 85.333333h426.666666z" horiz-adv-x="1024" />
<glyph glyph-name="icon_comment_collapse_text_input" unicode="&#59257;" d="M938.666667 768a42.666667 42.666667 0 0 0 42.666666-42.666667v-234.666666a42.666667 42.666667 0 0 0-85.333333 0V682.666667H128v-554.666667h106.666667a42.666667 42.666667 0 0 0 42.368-37.674667L277.333333 85.333333v-37.589333l151.594667 75.733333a42.666667 42.666667 0 0 0 14.122667 4.266667L448 128h96a42.666667 42.666667 0 0 0 0-85.333333h-85.973333l-204.288-102.144a42.666667 42.666667 0 0 0-61.44 33.237333L192-21.333333V42.666667H85.333333a42.666667 42.666667 0 0 0-42.368 37.674666L42.666667 85.333333V725.333333a42.666667 42.666667 0 0 0 42.666666 42.666667h853.333334z m-204.8-546.133333a42.666667 42.666667 0 0 0 42.666666-42.666667V42.666667a42.666667 42.666667 0 0 0-85.333333 0v93.866666H597.333333a42.666667 42.666667 0 0 0-42.368 37.674667L554.666667 179.2a42.666667 42.666667 0 0 0 42.666666 42.666667h136.533334z m68.266666 204.8a42.666667 42.666667 0 0 0 42.666667-42.666667v-93.866667H938.666667a42.666667 42.666667 0 0 0 42.368-37.674666L981.333333 247.466667a42.666667 42.666667 0 0 0-42.666666-42.666667h-136.533334a42.666667 42.666667 0 0 0-42.666666 42.666667V384a42.666667 42.666667 0 0 0 42.666666 42.666667zM512 469.333333a42.666667 42.666667 0 0 0 0-85.333333H256a42.666667 42.666667 0 0 0 0 85.333333h256zM384 597.333333a42.666667 42.666667 0 1 0 0-85.333333H256a42.666667 42.666667 0 1 0 0 85.333333h128z" horiz-adv-x="1024" />
<glyph glyph-name="icon_comment_expand_text_input" unicode="&#59258;" d="M938.666667 768a42.666667 42.666667 0 0 0 42.666666-42.666667v-234.666666a42.666667 42.666667 0 0 0-85.333333 0V682.666667H128v-554.666667h106.666667a42.666667 42.666667 0 0 0 42.368-37.674667L277.333333 85.333333v-37.589333l151.594667 75.733333a42.666667 42.666667 0 0 0 14.122667 4.266667L448 128h96a42.666667 42.666667 0 0 0 0-85.333333h-85.973333l-204.288-102.144a42.666667 42.666667 0 0 0-61.44 33.237333L192-21.333333V42.666667H85.333333a42.666667 42.666667 0 0 0-42.368 37.674666L42.666667 85.333333V725.333333a42.666667 42.666667 0 0 0 42.666666 42.666667h853.333334z m-298.666667-507.264a42.666667 42.666667 0 0 0 42.666667-42.666667v-90.026666l90.069333-0.042667a42.666667 42.666667 0 0 0 42.368-37.674667l0.298667-4.992a42.666667 42.666667 0 0 0-42.666667-42.666666H640a42.666667 42.666667 0 0 0-42.666667 42.666666v132.736a42.666667 42.666667 0 0 0 42.666667 42.666667zM938.666667 426.666667a42.666667 42.666667 0 0 0 42.666666-42.666667v-132.736a42.666667 42.666667 0 1 0-85.333333 0V341.333333h-90.069333a42.666667 42.666667 0 0 0-42.368 37.674667L763.264 384a42.666667 42.666667 0 0 0 42.666667 42.666667H938.666667z m-426.666667 42.666666a42.666667 42.666667 0 0 0 0-85.333333H256a42.666667 42.666667 0 0 0 0 85.333333h256zM384 597.333333a42.666667 42.666667 0 1 0 0-85.333333H256a42.666667 42.666667 0 1 0 0 85.333333h128z" horiz-adv-x="1024" />
<glyph glyph-name="icon_chart_graph" unicode="&#59255;" d="M661.333333 810.666667a42.666667 42.666667 0 0 0 42.666667-42.666667v-192a42.666667 42.666667 0 0 0-42.666667-42.666667H554.666667V426.666667h192a42.666667 42.666667 0 0 0 42.666666-42.666667v-149.333333H896a42.666667 42.666667 0 0 0 42.666667-42.666667V0a42.666667 42.666667 0 0 0-42.666667-42.666667h-298.666667a42.666667 42.666667 0 0 0-42.666666 42.666667v192a42.666667 42.666667 0 0 0 42.666666 42.666667h106.666667V341.333333h-384v-106.666666H426.666667a42.666667 42.666667 0 0 0 42.666666-42.666667V0a42.666667 42.666667 0 0 0-42.666666-42.666667H128a42.666667 42.666667 0 0 0-42.666667 42.666667v192a42.666667 42.666667 0 0 0 42.666667 42.666667h106.666667V384a42.666667 42.666667 0 0 0 42.666666 42.666667H469.333333V533.333333H362.666667a42.666667 42.666667 0 0 0-42.666667 42.666667V768a42.666667 42.666667 0 0 0 42.666667 42.666667h298.666666zM384 149.333333H170.666667V42.666667h213.333333v106.666666z m469.333333 0h-213.333333V42.666667h213.333333v106.666666zM618.666667 725.333333h-213.333334v-106.666666h213.333334V725.333333z" horiz-adv-x="1024" />
<glyph glyph-name="icon_share1" unicode="&#59254;" d="M746.666667 810.666667a149.333333 149.333333 0 1 0-106.453334-254.037334l-218.965333-132.608a149.461333 149.461333 0 0 0 1.024-76.117333l222.890667-131.712a149.333333 149.333333 0 1 0-43.434667-73.472l-222.890667 131.754667a149.333333 149.333333 0 1 0-2.986666 221.824l224.341333 135.893333A149.333333 149.333333 0 0 0 746.666667 810.666667z m0-640a64 64 0 1 1 0-128 64 64 0 0 1 0 128z m-469.333334 277.333333a64 64 0 1 1 0-128 64 64 0 0 1 0 128z m469.333334 277.333333a64 64 0 1 1 0-128 64 64 0 0 1 0 128z" horiz-adv-x="1024" />
<glyph glyph-name="icon_text" unicode="&#59253;" d="M576 810.666667l3.114667-0.128 1.322666-0.085334L576 810.666667c2.986667 0 5.888-0.298667 8.704-0.853334l0.725333-0.213333 4.778667-1.365333a42.538667 42.538667 0 0 0 17.237333-11.392l234.666667-256 0.682667-0.768c0.768-0.853333 1.493333-1.749333 2.133333-2.688l-2.816 3.413333a43.008 43.008 0 0 0 10.88-23.466667L853.333333 512v-512a42.666667 42.666667 0 0 0-42.666666-42.666667H213.333333a42.666667 42.666667 0 0 0-42.666666 42.666667V768a42.666667 42.666667 0 0 0 42.666666 42.666667h362.666667z m-42.666667-85.333334H256v-682.666666h512V469.333333h-192a42.666667 42.666667 0 0 0-42.368 37.674667L533.333333 512V725.333333zM597.333333 298.666667a42.666667 42.666667 0 1 0 0-85.333334H341.333333a42.666667 42.666667 0 0 0 0 85.333334h256z m-170.666666 170.666666a42.666667 42.666667 0 1 0 0-85.333333H341.333333a42.666667 42.666667 0 0 0 0 85.333333h85.333334z m192 188.970667V554.666667h95.018666L618.666667 658.304z" horiz-adv-x="1024" />
<glyph glyph-name="icon_click" unicode="&#59031;" d="M554.666667 853.333333a42.666667 42.666667 0 0 0 42.666666-42.666666v-170.666667a42.666667 42.666667 0 0 0-85.333333 0V810.666667a42.666667 42.666667 0 0 0 42.666667 42.666666z m-127.061334-439.296a46.208 46.208 0 0 0 54.357334 54.357334l462.208-92.416c40.362667-8.106667 50.773333-60.928 16.554666-83.797334l-91.733333-61.141333 98.773333-98.773333c16.64-16.682667 17.92-42.88 3.84-61.013334l-3.84-4.352-138.666666-138.666666a46.208 46.208 0 0 0-65.365334 0l-98.773333 98.773333-61.141333-91.733333c-21.802667-32.682667-70.954667-24.661333-82.432 11.264l-1.365334 5.290666-92.416 462.208z m104.192-49.834666l56.96-284.8 30.549334 45.781333a46.208 46.208 0 0 0 66.858666 10.88l4.266667-3.84 105.984-105.898667 73.258667 73.258667-105.941334 105.941333a46.208 46.208 0 0 0 2.474667 67.669334l4.608 3.498666 45.781333 30.549334-284.8 56.96z m288.768 348.16a44.202667 44.202667 0 0 0 62.506667-62.464l-125.013333-124.970667a44.202667 44.202667 0 0 0-62.464 62.506667l125.013333 124.970666z m-512-512a44.202667 44.202667 0 1 0 62.506667-62.464l-125.013333-124.970667a44.202667 44.202667 0 1 0-62.464 62.506667l125.013333 124.970666zM256 384a42.666667 42.666667 0 0 0 0-85.333333H85.333333a42.666667 42.666667 0 0 0 0 85.333333h170.666667zM183.594667 712.405333a44.202667 44.202667 0 0 0 62.506666 0l124.970667-125.013333a44.202667 44.202667 0 0 0-62.506667-62.464l-124.970666 125.013333a44.202667 44.202667 0 0 0 0 62.464z" horiz-adv-x="1024" />
<glyph glyph-name="icon_feedback_outlined" unicode="&#59251;" d="M840.832 840.832A42.666667 42.666667 0 0 0 853.333333 810.666667v-341.333334h-85.333333V768H170.666667v-768h213.333333v-85.333333H128a42.666667 42.666667 0 0 0-42.666667 42.666666V810.666667a42.666667 42.666667 0 0 0 42.666667 42.666666h682.666667a42.666667 42.666667 0 0 0 30.165333-12.501333zM277.333333 640a21.333333 21.333333 0 0 1-21.333333-21.333333v-42.666667a21.333333 21.333333 0 0 1 21.333333-21.333333h384a21.333333 21.333333 0 0 1 21.333334 21.333333v42.666667a21.333333 21.333333 0 0 1-21.333334 21.333333h-384zM868.138667 158.848l-120.32 120.32-264.448-266.112L469.333333-100.949333a21.333333 21.333333 0 0 1 21.333334-21.333334l114.176 18.133334 263.296 262.997333zM899.114667 370.602667c-16.64 16.64-42.368 17.92-57.472 2.901333l-63.786667-64 120.490667-120.490667 63.872 63.744 2.005333 2.133334c12.970667 15.317333 11.093333 39.509333-4.778667 55.424l-60.330666 60.288zM256 448a21.333333 21.333333 0 0 0 21.333333 21.333333h298.666667a21.333333 21.333333 0 0 0 21.333333-21.333333v-42.666667a21.333333 21.333333 0 0 0-21.333333-21.333333h-298.666667a21.333333 21.333333 0 0 0-21.333333 21.333333v42.666667z" horiz-adv-x="1024" />
<glyph glyph-name="icon_done_outlined" unicode="&#59252;" d="M840.832 840.832A42.666667 42.666667 0 0 0 853.333333 810.666667v-341.333334h-85.333333V768H170.666667v-768h213.333333v-85.333333H128a42.666667 42.666667 0 0 0-42.666667 42.666666V810.666667a42.666667 42.666667 0 0 0 42.666667 42.666666h682.666667a42.666667 42.666667 0 0 0 30.165333-12.501333zM277.333333 640a21.333333 21.333333 0 0 1-21.333333-21.333333v-42.666667a21.333333 21.333333 0 0 1 21.333333-21.333333h384a21.333333 21.333333 0 0 1 21.333334 21.333333v42.666667a21.333333 21.333333 0 0 1-21.333334 21.333333h-384zM256 448a21.333333 21.333333 0 0 0 21.333333 21.333333h298.666667a21.333333 21.333333 0 0 0 21.333333-21.333333v-42.666667a21.333333 21.333333 0 0 0-21.333333-21.333333h-298.666667a21.333333 21.333333 0 0 0-21.333333 21.333333v42.666667zM859.648 235.264l30.165333-30.165333a21.333333 21.333333 0 0 0 0-30.165334l-211.2-211.2a21.205333 21.205333 0 0 0-11.306666-5.930666l-2.474667-0.298667h-2.56a21.248 21.248 0 0 0-13.824 6.229333l-120.661333 120.661334a21.333333 21.333333 0 0 0 0 30.165333l30.165333 30.165333a21.333333 21.333333 0 0 0 30.165333 0l75.434667-75.392 165.973333 165.930667a21.333333 21.333333 0 0 0 30.122667 0z" horiz-adv-x="1024" />
<glyph glyph-name="icon_dataset_outlined" unicode="&#59250;" d="M42.666667 569.856a42.666667 42.666667 0 0 0 25.002666 38.826667l426.666667 193.962666a42.666667 42.666667 0 0 0 35.328 0l426.666667-193.92a42.666667 42.666667 0 0 0 25.002666-38.826666v-415.530667a42.666667 42.666667 0 0 0-23.594666-38.144l-426.666667-213.333333a42.666667 42.666667 0 0 0-38.144 0l-426.666667 213.333333A42.666667 42.666667 0 0 0 42.666667 154.368V569.856z m777.301333 7.082667L512 716.928 202.368 576.213333l307.925333-132.693333 309.674667 133.418667zM554.666667 369.749333v-359.68l341.333333 170.666667V516.778667l-341.333333-147.029334zM128 515.328v-334.592l341.333333-170.666667v358.229334L128 515.328z" horiz-adv-x="1024" />
<glyph glyph-name="icon_outer-borders_outlined" unicode="&#59249;" d="M910.208 736.725333c0 25.130667-20.352 45.482667-45.482667 45.482667H159.274667c-25.130667 0-45.525333-20.352-45.525334-45.482667v-705.450666c0-25.130667 20.394667-45.482667 45.525334-45.482667h705.450666c25.130667 0 45.482667 20.352 45.482667 45.482667V736.725333z m-711.082667-665.6V696.874667h625.749334v-625.749334H199.125333zM492.074667 663.722667a22.741333 22.741333 0 0 1-22.741334-22.784v-39.808c0-12.586667 10.197333-22.741333 22.741334-22.741334h39.850666A22.741333 22.741333 0 0 1 554.666667 601.130667v39.808a22.741333 22.741333 0 0 1-22.741334 22.784h-39.850666zM469.333333 522.410667c0 12.586667 10.197333 22.784 22.741334 22.784h39.850666c12.544 0 22.741333-10.197333 22.741334-22.784v-39.808a22.741333 22.741333 0 0 0-22.741334-22.741334h-39.850666A22.741333 22.741333 0 0 0 469.333333 482.56v39.808z m0-118.485334c0 12.544 10.197333 22.741333 22.741334 22.741334h39.850666c12.544 0 22.741333-10.197333 22.741334-22.741334v-39.850666a22.741333 22.741333 0 0 0-22.741334-22.741334h-39.850666a22.741333 22.741333 0 0 0-22.741334 22.741334v39.850666z m0-118.528c0 12.544 10.197333 22.741333 22.741334 22.741334h39.850666c12.544 0 22.741333-10.197333 22.741334-22.741334v-39.808a22.741333 22.741333 0 0 0-22.741334-22.784h-39.850666a22.741333 22.741333 0 0 0-22.741334 22.784v39.808z m22.741334-95.744a22.741333 22.741333 0 0 1-22.741334-22.784v-39.808c0-12.586667 10.197333-22.784 22.741334-22.784h39.850666a22.741333 22.741333 0 0 1 22.741334 22.784v39.808a22.741333 22.741333 0 0 1-22.741334 22.784h-39.850666z m214.272 214.272c0 12.544 10.24 22.741333 22.784 22.741334h39.808c12.586667 0 22.741333-10.197333 22.741333-22.741334v-39.850666a22.741333 22.741333 0 0 0-22.741333-22.741334h-39.808a22.741333 22.741333 0 0 0-22.784 22.741334v39.850666zM610.602667 426.666667a22.741333 22.741333 0 0 1-22.741334-22.741334v-39.850666c0-12.544 10.154667-22.741333 22.741334-22.741334h39.808a22.741333 22.741333 0 0 1 22.784 22.741334v39.850666a22.741333 22.741333 0 0 1-22.784 22.741334h-39.808z m-259.797334-22.741334c0 12.544 10.197333 22.741333 22.741334 22.741334h39.850666c12.544 0 22.741333-10.197333 22.741334-22.741334v-39.850666A22.741333 22.741333 0 0 0 413.44 341.333333h-39.850667a22.741333 22.741333 0 0 0-22.741333 22.741334v39.850666zM255.061333 426.666667a22.741333 22.741333 0 0 1-22.784-22.741334v-39.850666c0-12.544 10.24-22.741333 22.784-22.741334h39.808a22.741333 22.741333 0 0 1 22.741334 22.741334v39.850666A22.741333 22.741333 0 0 1 294.869333 426.666667h-39.808z" horiz-adv-x="1024" />
<glyph glyph-name="icon_checkbox" unicode="&#59246;" d="M640 810.666667a42.666667 42.666667 0 0 0 0-85.333334H192a21.333333 21.333333 0 0 1-21.333333-21.333333v-640a21.333333 21.333333 0 0 1 21.333333-21.333333h640a21.333333 21.333333 0 0 1 21.333333 21.333333V469.333333a42.666667 42.666667 0 0 0 85.333334 0v-405.333333a106.666667 106.666667 0 0 0-106.666667-106.666667h-640A106.666667 106.666667 0 0 0 85.333333 64v640A106.666667 106.666667 0 0 0 192 810.666667H640z m259.456-29.269334a42.666667 42.666667 0 0 0 9.941333-59.52l-320-448a42.666667 42.666667 0 0 0-61.397333-8.533333l-213.333333 170.666667a42.666667 42.666667 0 1 0 53.333333 66.645333l178.090667-142.506667 293.845333 411.306667a42.666667 42.666667 0 0 0 59.52 9.941333z" horiz-adv-x="1024" /> <glyph glyph-name="icon_checkbox" unicode="&#59246;" d="M640 810.666667a42.666667 42.666667 0 0 0 0-85.333334H192a21.333333 21.333333 0 0 1-21.333333-21.333333v-640a21.333333 21.333333 0 0 1 21.333333-21.333333h640a21.333333 21.333333 0 0 1 21.333333 21.333333V469.333333a42.666667 42.666667 0 0 0 85.333334 0v-405.333333a106.666667 106.666667 0 0 0-106.666667-106.666667h-640A106.666667 106.666667 0 0 0 85.333333 64v640A106.666667 106.666667 0 0 0 192 810.666667H640z m259.456-29.269334a42.666667 42.666667 0 0 0 9.941333-59.52l-320-448a42.666667 42.666667 0 0 0-61.397333-8.533333l-213.333333 170.666667a42.666667 42.666667 0 1 0 53.333333 66.645333l178.090667-142.506667 293.845333 411.306667a42.666667 42.666667 0 0 0 59.52 9.941333z" horiz-adv-x="1024" />
<glyph glyph-name="icon_radio" unicode="&#59247;" d="M512 853.333333c259.2 0 469.333333-210.133333 469.333333-469.333333s-210.133333-469.333333-469.333333-469.333333S42.666667 124.8 42.666667 384 252.8 853.333333 512 853.333333z m0-85.333333a384 384 0 1 1 0-768 384 384 0 0 1 0 768z m0-170.666667a213.333333 213.333333 0 1 0 0-426.666666 213.333333 213.333333 0 0 0 0 426.666666z m0-85.333333a128 128 0 1 1 0-256 128 128 0 0 1 0 256z" horiz-adv-x="1024" /> <glyph glyph-name="icon_radio" unicode="&#59247;" d="M512 853.333333c259.2 0 469.333333-210.133333 469.333333-469.333333s-210.133333-469.333333-469.333333-469.333333S42.666667 124.8 42.666667 384 252.8 853.333333 512 853.333333z m0-85.333333a384 384 0 1 1 0-768 384 384 0 0 1 0 768z m0-170.666667a213.333333 213.333333 0 1 0 0-426.666666 213.333333 213.333333 0 0 0 0 426.666666z m0-85.333333a128 128 0 1 1 0-256 128 128 0 0 1 0 256z" horiz-adv-x="1024" />
@ -374,7 +438,7 @@
<glyph glyph-name="icon_file-sketch_colorful" unicode="&#59138;" d="M170.666667 789.333333a42.666667 42.666667 0 0 0 42.666666 42.666667h417.834667a21.333333 21.333333 0 0 0 15.061333-6.229333l200.874667-200.874667a21.333333 21.333333 0 0 0 6.229333-15.061333V-21.333333a42.666667 42.666667 0 0 0-42.666666-42.666667H213.333333a42.666667 42.666667 0 0 0-42.666666 42.666667v810.666666zM170.666667 789.333333a42.666667 42.666667 0 0 0 42.666666 42.666667h417.834667a21.333333 21.333333 0 0 0 15.061333-6.229333l200.874667-200.874667a21.333333 21.333333 0 0 0 6.229333-15.061333V-21.333333a42.666667 42.666667 0 0 0-42.666666-42.666667H213.333333a42.666667 42.666667 0 0 0-42.666666 42.666667v810.666666zM640 830.08a21.290667 21.290667 0 0 0 6.229333-4.309333l200.874667-200.874667a21.376 21.376 0 0 0 4.309333-6.229333H682.666667a42.666667 42.666667 0 0 0-42.666667 42.666666V830.08zM428.202667 426.666667a31.018667 31.018667 0 0 1-24.618667-12.16l-55.893333-73.045334a31.018667 31.018667 0 0 1 0.426666-38.186666l139.605334-175.232a31.018667 31.018667 0 0 1 48.554666 0l139.605334 175.232a31.018667 31.018667 0 0 1 0.426666 38.186666l-55.893333 73.045334a31.018667 31.018667 0 0 1-24.618667 12.16h-167.594666z" horiz-adv-x="1024" /> <glyph glyph-name="icon_file-sketch_colorful" unicode="&#59138;" d="M170.666667 789.333333a42.666667 42.666667 0 0 0 42.666666 42.666667h417.834667a21.333333 21.333333 0 0 0 15.061333-6.229333l200.874667-200.874667a21.333333 21.333333 0 0 0 6.229333-15.061333V-21.333333a42.666667 42.666667 0 0 0-42.666666-42.666667H213.333333a42.666667 42.666667 0 0 0-42.666666 42.666667v810.666666zM170.666667 789.333333a42.666667 42.666667 0 0 0 42.666666 42.666667h417.834667a21.333333 21.333333 0 0 0 15.061333-6.229333l200.874667-200.874667a21.333333 21.333333 0 0 0 6.229333-15.061333V-21.333333a42.666667 42.666667 0 0 0-42.666666-42.666667H213.333333a42.666667 42.666667 0 0 0-42.666666 42.666667v810.666666zM640 830.08a21.290667 21.290667 0 0 0 6.229333-4.309333l200.874667-200.874667a21.376 21.376 0 0 0 4.309333-6.229333H682.666667a42.666667 42.666667 0 0 0-42.666667 42.666666V830.08zM428.202667 426.666667a31.018667 31.018667 0 0 1-24.618667-12.16l-55.893333-73.045334a31.018667 31.018667 0 0 1 0.426666-38.186666l139.605334-175.232a31.018667 31.018667 0 0 1 48.554666 0l139.605334 175.232a31.018667 31.018667 0 0 1 0.426666 38.186666l-55.893333 73.045334a31.018667 31.018667 0 0 1-24.618667 12.16h-167.594666z" horiz-adv-x="1024" />
<glyph glyph-name="icon_block_filled" unicode="&#59115;" d="M512-85.333333c259.2 0 469.333333 210.133333 469.333333 469.333333S771.2 853.333333 512 853.333333 42.666667 643.2 42.666667 384s210.133333-469.333333 469.333333-469.333333zM298.666667 405.333333a21.333333 21.333333 0 0 0 21.333333 21.333334h384a21.333333 21.333333 0 0 0 21.333333-21.333334v-42.666666a21.333333 21.333333 0 0 0-21.333333-21.333334h-384a21.333333 21.333333 0 0 0-21.333333 21.333334v42.666666z" horiz-adv-x="1024" /> <glyph glyph-name="icon_block_filled" unicode="&#59115;" d="M981.333333 384c0-259.2-210.133333-469.333333-469.333333-469.333333S42.666667 124.79999999999995 42.666667 384 252.8 853.333333 512 853.333333s469.333333-210.133333 469.333333-469.333333zM320 426.666667a21.333333 21.333333 0 0 1-21.333333-21.333334v-42.666666a21.333333 21.333333 0 0 1 21.333333-21.333334h384a21.333333 21.333333 0 0 1 21.333333 21.333334v42.666666a21.333333 21.333333 0 0 1-21.333333 21.333334h-384z" horiz-adv-x="1024" />
<glyph glyph-name="icon_not-started_filled" unicode="&#59124;" d="M981.333333 384c0-259.2-210.133333-469.333333-469.333333-469.333333S42.666667 124.79999999999995 42.666667 384 252.8 853.333333 512 853.333333s469.333333-210.133333 469.333333-469.333333z m-213.333333 64h-85.333333a21.248 21.248 0 0 1-21.333334-21.333333v-85.333334a21.333333 21.333333 0 0 1 21.333334-21.333333h85.333333a21.248 21.248 0 0 1 21.333333 21.333333v85.333334a21.248 21.248 0 0 1-21.333333 21.333333z m-426.666667 0H256a21.290667 21.290667 0 0 1-21.333333-21.333333v-85.333334a21.333333 21.333333 0 0 1 21.333333-21.333333h85.333333a21.333333 21.333333 0 0 1 21.333334 21.333333v85.333334a21.333333 21.333333 0 0 1-21.333334 21.333333z m221.482667-1.621333A21.248 21.248 0 0 1 554.666667 448h-85.333334a21.248 21.248 0 0 1-21.333333-21.333333v-85.333334a21.333333 21.333333 0 0 1 21.333333-21.333333h85.333334a21.248 21.248 0 0 1 21.333333 21.333333v85.333334a21.248 21.248 0 0 1-13.184 19.712z" horiz-adv-x="1024" /> <glyph glyph-name="icon_not-started_filled" unicode="&#59124;" d="M981.333333 384c0-259.2-210.133333-469.333333-469.333333-469.333333S42.666667 124.79999999999995 42.666667 384 252.8 853.333333 512 853.333333s469.333333-210.133333 469.333333-469.333333z m-213.333333 64h-85.333333a21.248 21.248 0 0 1-21.333334-21.333333v-85.333334a21.333333 21.333333 0 0 1 21.333334-21.333333h85.333333a21.248 21.248 0 0 1 21.333333 21.333333v85.333334a21.248 21.248 0 0 1-21.333333 21.333333z m-426.666667 0H256a21.290667 21.290667 0 0 1-21.333333-21.333333v-85.333334a21.333333 21.333333 0 0 1 21.333333-21.333333h85.333333a21.333333 21.333333 0 0 1 21.333334 21.333333v85.333334a21.333333 21.333333 0 0 1-21.333334 21.333333z m221.482667-1.621333A21.248 21.248 0 0 1 554.666667 448h-85.333334a21.248 21.248 0 0 1-21.333333-21.333333v-85.333334a21.333333 21.333333 0 0 1 21.333333-21.333333h85.333334a21.248 21.248 0 0 1 21.333333 21.333333v85.333334a21.248 21.248 0 0 1-13.184 19.712z" horiz-adv-x="1024" />
@ -472,8 +536,6 @@
<glyph glyph-name="icon_new-item_outlined" unicode="&#59029;" d="M896 810.666667a42.666667 42.666667 0 0 0 42.666667-42.666667v-768a42.666667 42.666667 0 0 0-42.666667-42.666667H128a42.666667 42.666667 0 0 0-42.666667 42.666667V768a42.666667 42.666667 0 0 0 42.666667 42.666667h768z m-64-85.333334h-640a21.333333 21.333333 0 0 1-21.205333-18.816L170.666667 704v-640a21.333333 21.333333 0 0 1 18.816-21.162667L192 42.66666699999996h640a21.333333 21.333333 0 0 1 21.205333 18.858666L853.333333 64v640a21.333333 21.333333 0 0 1-21.333333 21.333333z m-298.666667-128a21.333333 21.333333 0 0 0 21.333334-21.333333V426.666667h149.333333a21.333333 21.333333 0 0 0 21.333333-21.333334v-42.666666a21.248 21.248 0 0 0-21.333333-21.333334H554.666667v-149.333333a21.248 21.248 0 0 0-21.333334-21.333333h-42.666666a21.333333 21.333333 0 0 0-21.333334 21.333333V341.33333300000004H320a21.333333 21.333333 0 0 0-21.333333 21.333334v42.666666a21.333333 21.333333 0 0 0 21.333333 21.333334H469.333333V576a21.290667 21.290667 0 0 0 21.333334 21.333333h42.666666z" horiz-adv-x="1024" /> <glyph glyph-name="icon_new-item_outlined" unicode="&#59029;" d="M896 810.666667a42.666667 42.666667 0 0 0 42.666667-42.666667v-768a42.666667 42.666667 0 0 0-42.666667-42.666667H128a42.666667 42.666667 0 0 0-42.666667 42.666667V768a42.666667 42.666667 0 0 0 42.666667 42.666667h768z m-64-85.333334h-640a21.333333 21.333333 0 0 1-21.205333-18.816L170.666667 704v-640a21.333333 21.333333 0 0 1 18.816-21.162667L192 42.66666699999996h640a21.333333 21.333333 0 0 1 21.205333 18.858666L853.333333 64v640a21.333333 21.333333 0 0 1-21.333333 21.333333z m-298.666667-128a21.333333 21.333333 0 0 0 21.333334-21.333333V426.666667h149.333333a21.333333 21.333333 0 0 0 21.333333-21.333334v-42.666666a21.248 21.248 0 0 0-21.333333-21.333334H554.666667v-149.333333a21.248 21.248 0 0 0-21.333334-21.333333h-42.666666a21.333333 21.333333 0 0 0-21.333334 21.333333V341.33333300000004H320a21.333333 21.333333 0 0 0-21.333333 21.333334v42.666666a21.333333 21.333333 0 0 0 21.333333 21.333334H469.333333V576a21.290667 21.290667 0 0 0 21.333334 21.333333h42.666666z" horiz-adv-x="1024" />
<glyph glyph-name="icon_logs_outlined-1" unicode="&#59031;" d="M682.666667 768H213.333333v-768h597.333334V639.146667h-106.666667a21.333333 21.333333 0 0 0-21.333333 21.333333V768zM170.666667 853.333333h558.293333a42.666667 42.666667 0 0 0 30.208-12.501333l124.373333-124.373333a42.666667 42.666667 0 0 0 12.458667-30.165334V-42.666667a42.666667 42.666667 0 0 0-42.666667-42.666666H170.666667a42.666667 42.666667 0 0 0-42.666667 42.666666V810.666667a42.666667 42.666667 0 0 0 42.666667 42.666666z m170.666666-384h341.333334a21.333333 21.333333 0 0 0 21.333333-21.333333v-42.666667a21.333333 21.333333 0 0 0-21.333333-21.333333H341.333333a21.333333 21.333333 0 0 0-21.333333 21.333333v42.666667A21.333333 21.333333 0 0 0 341.333333 469.333333z m0-213.333333h192a21.248 21.248 0 0 0 21.333334-21.333333v-42.666667a21.248 21.248 0 0 0-21.333334-21.333333H341.333333a21.333333 21.333333 0 0 0-21.333333 21.333333v42.666667A21.333333 21.333333 0 0 0 341.333333 256z" horiz-adv-x="1024" />
<glyph glyph-name="icon_refresh_outlined" unicode="&#59032;" d="M757.12 554.666667a298.666667 298.666667 0 1 1 41.173333-256h88.192A384 384 0 1 0 810.666667 625.365333V746.666667a21.333333 21.333333 0 0 0 21.333333 21.333333h42.666667a21.333333 21.333333 0 0 0 21.333333-21.333333V512a42.666667 42.666667 0 0 0-42.666667-42.666667h-234.666666a21.333333 21.333333 0 0 0-21.333334 21.333334v42.666666a21.333333 21.333333 0 0 0 21.333334 21.333334h138.453333z" horiz-adv-x="1024" /> <glyph glyph-name="icon_refresh_outlined" unicode="&#59032;" d="M757.12 554.666667a298.666667 298.666667 0 1 1 41.173333-256h88.192A384 384 0 1 0 810.666667 625.365333V746.666667a21.333333 21.333333 0 0 0 21.333333 21.333333h42.666667a21.333333 21.333333 0 0 0 21.333333-21.333333V512a42.666667 42.666667 0 0 0-42.666667-42.666667h-234.666666a21.333333 21.333333 0 0 0-21.333334 21.333334v42.666666a21.333333 21.333333 0 0 0 21.333334 21.333334h138.453333z" horiz-adv-x="1024" />
<glyph glyph-name="icon_font-color_outlined" unicode="&#59033;" d="M128 19.925333c0 12.544 9.813333 22.741333 21.930667 22.741334h724.138666c12.117333 0 21.930667-10.197333 21.930667-22.741334v-125.184c0-12.544-9.813333-22.741333-21.930667-22.741333H149.930667A22.357333 22.357333 0 0 0 128-105.258667v125.184zM462.634667 768a21.333333 21.333333 0 0 1-19.882667-13.653333L224.512 187.733333a21.333333 21.333333 0 0 1 19.882667-29.013333H297.813333a21.333333 21.333333 0 0 1 19.882667 13.525333l61.013333 155.562667A21.333333 21.333333 0 0 0 398.549333 341.333333h211.114667a21.333333 21.333333 0 0 0 20.138667-14.293333l53.845333-154.026667a21.333333 21.333333 0 0 1 20.138667-14.293333h53.632a21.333333 21.333333 0 0 1 19.882666 29.013333L559.061333 754.346667a21.333333 21.333333 0 0 1-19.882666 13.653333h-76.544z m-52.394667-341.333333l88.362667 239.914666a2.432 2.432 0 0 0 4.608 0L590.848 426.666667H410.24z" horiz-adv-x="1024" /> <glyph glyph-name="icon_font-color_outlined" unicode="&#59033;" d="M128 19.925333c0 12.544 9.813333 22.741333 21.930667 22.741334h724.138666c12.117333 0 21.930667-10.197333 21.930667-22.741334v-125.184c0-12.544-9.813333-22.741333-21.930667-22.741333H149.930667A22.357333 22.357333 0 0 0 128-105.258667v125.184zM462.634667 768a21.333333 21.333333 0 0 1-19.882667-13.653333L224.512 187.733333a21.333333 21.333333 0 0 1 19.882667-29.013333H297.813333a21.333333 21.333333 0 0 1 19.882667 13.525333l61.013333 155.562667A21.333333 21.333333 0 0 0 398.549333 341.333333h211.114667a21.333333 21.333333 0 0 0 20.138667-14.293333l53.845333-154.026667a21.333333 21.333333 0 0 1 20.138667-14.293333h53.632a21.333333 21.333333 0 0 1 19.882666 29.013333L559.061333 754.346667a21.333333 21.333333 0 0 1-19.882666 13.653333h-76.544z m-52.394667-341.333333l88.362667 239.914666a2.432 2.432 0 0 0 4.608 0L590.848 426.666667H410.24z" horiz-adv-x="1024" />
@ -482,7 +544,7 @@
<glyph glyph-name="icon_moments-categories_outlined" unicode="&#59036;" d="M523.477333 782.08l429.568-273.408a21.333333 21.333333 0 0 0 0-36.010667L523.52 199.296a21.333333 21.333333 0 0 0-22.912 0L70.954667 472.661333a21.333333 21.333333 0 0 0 0 36.010667l429.610666 273.365333a21.333333 21.333333 0 0 0 22.912 0zM201.6 490.666667L512 293.12l310.4 197.546667L512 688.213333 201.6 490.666667zM110.805333 303.530667a21.333333 21.333333 0 0 1-29.354666-7.04l-22.314667-36.394667a21.333333 21.333333 0 0 1 7.04-29.312l390.613333-239.530667a84.992 84.992 0 0 1 89.088 0l390.613334 239.530667a21.333333 21.333333 0 0 1 7.04 29.312l-22.314667 36.394667a21.333333 21.333333 0 0 1-29.312 7.04L506.88 67.413333a10.666667 10.666667 0 0 0-11.136 0l-384.981333 236.074667z" horiz-adv-x="1024" /> <glyph glyph-name="icon_moments-categories_outlined" unicode="&#59036;" d="M523.477333 782.08l429.568-273.408a21.333333 21.333333 0 0 0 0-36.010667L523.52 199.296a21.333333 21.333333 0 0 0-22.912 0L70.954667 472.661333a21.333333 21.333333 0 0 0 0 36.010667l429.610666 273.365333a21.333333 21.333333 0 0 0 22.912 0zM201.6 490.666667L512 293.12l310.4 197.546667L512 688.213333 201.6 490.666667zM110.805333 303.530667a21.333333 21.333333 0 0 1-29.354666-7.04l-22.314667-36.394667a21.333333 21.333333 0 0 1 7.04-29.312l390.613333-239.530667a84.992 84.992 0 0 1 89.088 0l390.613334 239.530667a21.333333 21.333333 0 0 1 7.04 29.312l-22.314667 36.394667a21.333333 21.333333 0 0 1-29.312 7.04L506.88 67.413333a10.666667 10.666667 0 0 0-11.136 0l-384.981333 236.074667z" horiz-adv-x="1024" />
<glyph glyph-name="icon_doc-replace_outlined" unicode="&#59037;" d="M85.333333-1.066667a45.653333 45.653333 0 0 1 45.482667-45.738666h295.68v85.76H170.581333V724.906667H767.573333v-300.202667h85.333334V764.928A45.653333 45.653333 0 0 1 807.338667 810.666667H130.816A45.653333 45.653333 0 0 1 85.333333 764.928v-765.994667zM255.914667 585.002667c0 6.357333 5.077333 11.477333 11.349333 11.477333h403.669333c6.272 0 11.392-5.12 11.392-11.477333v-62.890667c0-6.314667-5.12-11.434667-11.392-11.434667h-403.626666a11.392 11.392 0 0 0-11.392 11.434667V585.002667zM267.264 424.704a11.392 11.392 0 0 1-11.349333-11.434667v-62.890666c0-6.314667 5.077333-11.434667 11.349333-11.434667h173.397333c6.314667 0 11.392 5.12 11.392 11.434667v62.890666a11.392 11.392 0 0 1-11.392 11.434667H267.264zM891.52 170.752h-349.738667a21.333333 21.333333 0 0 0-21.333333 21.333333v42.666667a21.333333 21.333333 0 0 0 21.333333 21.333333h192v58.752a21.333333 21.333333 0 0 0 35.626667 15.872l136.405333-122.794666a21.333333 21.333333 0 0 0-14.293333-37.162667zM691.114667 0.085333v-58.794666a21.333333 21.333333 0 0 0-35.626667-15.829334l-136.405333 122.752a21.333333 21.333333 0 0 0 14.293333 37.205334h349.738667a21.333333 21.333333 0 0 0 21.333333-21.333334v-42.666666a21.333333 21.333333 0 0 0-21.333333-21.333334h-192z" horiz-adv-x="1024" /> <glyph glyph-name="icon_doc-replace_outlined" unicode="&#59037;" d="M891.52 170.75199999999995h-349.738667a21.333333 21.333333 0 0 0-21.333333 21.333333v42.666667a21.333333 21.333333 0 0 0 21.333333 21.333333h192v58.752a21.333333 21.333333 0 0 0 35.626667 15.872l136.405333-122.794666a21.333333 21.333333 0 0 0-14.293333-37.162667zM691.114667 0.08533299999999144v-58.794666a21.333333 21.333333 0 0 0-35.626667-15.829334l-136.405333 122.752a21.333333 21.333333 0 0 0 14.293333 37.205334h349.738667a21.333333 21.333333 0 0 0 21.333333-21.333334v-42.666666a21.333333 21.333333 0 0 0-21.333333-21.333334h-192zM840.832 840.832A42.666667 42.666667 0 0 0 853.333333 810.666667v-341.333334h-85.333333V768H170.666667v-768h213.333333v-85.333333H128a42.666667 42.666667 0 0 0-42.666667 42.666666V810.666667a42.666667 42.666667 0 0 0 42.666667 42.666666h682.666667a42.666667 42.666667 0 0 0 30.165333-12.501333zM277.333333 640a21.333333 21.333333 0 0 1-21.333333-21.333333v-42.666667a21.333333 21.333333 0 0 1 21.333333-21.333333h384a21.333333 21.333333 0 0 1 21.333334 21.333333v42.666667a21.333333 21.333333 0 0 1-21.333334 21.333333h-384zM256 448a21.333333 21.333333 0 0 0 21.333333 21.333333h298.666667a21.333333 21.333333 0 0 0 21.333333-21.333333v-42.666667a21.333333 21.333333 0 0 0-21.333333-21.333333h-298.666667a21.333333 21.333333 0 0 0-21.333333 21.333333v42.666667z" horiz-adv-x="1024" />
<glyph glyph-name="icon_italic_outlined" unicode="&#59038;" d="M341.333333 746.666667a21.333333 21.333333 0 0 0 21.333334 21.333333h469.333333a21.333333 21.333333 0 0 0 21.333333-21.333333v-42.666667a21.333333 21.333333 0 0 0-21.333333-21.333333H640l-170.666667-597.333334h192a21.333333 21.333333 0 0 0 21.333334-21.333333v-42.666667a21.333333 21.333333 0 0 0-21.333334-21.333333h-469.333333a21.333333 21.333333 0 0 0-21.333333 21.333333v42.666667a21.333333 21.333333 0 0 0 21.333333 21.333333H384l170.666667 597.333334H362.666667a21.333333 21.333333 0 0 0-21.333334 21.333333v42.666667z" horiz-adv-x="1024" /> <glyph glyph-name="icon_italic_outlined" unicode="&#59038;" d="M341.333333 746.666667a21.333333 21.333333 0 0 0 21.333334 21.333333h469.333333a21.333333 21.333333 0 0 0 21.333333-21.333333v-42.666667a21.333333 21.333333 0 0 0-21.333333-21.333333H640l-170.666667-597.333334h192a21.333333 21.333333 0 0 0 21.333334-21.333333v-42.666667a21.333333 21.333333 0 0 0-21.333334-21.333333h-469.333333a21.333333 21.333333 0 0 0-21.333333 21.333333v42.666667a21.333333 21.333333 0 0 0 21.333333 21.333333H384l170.666667 597.333334H362.666667a21.333333 21.333333 0 0 0-21.333334 21.333333v42.666667z" horiz-adv-x="1024" />

Before

Width:  |  Height:  |  Size: 381 KiB

After

Width:  |  Height:  |  Size: 413 KiB

View File

@ -6,7 +6,7 @@
{{ t('msBatchModal.batchModalSubTitle', { count: (props.selectData || []).length }) }} {{ t('msBatchModal.batchModalSubTitle', { count: (props.selectData || []).length }) }}
</div> </div>
</template> </template>
<a-spin :loading="loading"> <a-spin :loading="loading" class="w-full">
<a-alert v-if="props.action === 'batchAddProject'" class="mb-[16px]"> <a-alert v-if="props.action === 'batchAddProject'" class="mb-[16px]">
{{ t('msBatchModal.batchModalTip') }} {{ t('msBatchModal.batchModalTip') }}
</a-alert> </a-alert>
@ -19,8 +19,9 @@
title: 'name', title: 'name',
children: 'children', children: 'children',
disabled: 'disabled', disabled: 'disabled',
isLeaf: 'isLeaf',
}" }"
height="370px"
show-search
/> />
</a-spin> </a-spin>
<template #footer> <template #footer>

View File

@ -135,7 +135,7 @@ export const MULTIPLE_INPUT = {
title: '', title: '',
value: [], value: [],
props: { props: {
placeholder: t('formCreate.PleaseSelect'), placeholder: t('formCreate.PleaseEnter'),
}, },
}; };

View File

@ -152,4 +152,3 @@
</script> </script>
<style scoped></style> <style scoped></style>
@/store/modules/form-create/form-create

View File

@ -116,7 +116,7 @@
} }
); );
const emits = defineEmits<{ const emits = defineEmits<{
(e: 'confirm', isPass: boolean): void; (e: 'confirm', formValue?: { field: string }, cancel?: () => void): void;
(e: 'cancel'): void; (e: 'cancel'): void;
(e: 'update:visible', visible: boolean): void; (e: 'update:visible', visible: boolean): void;
}>(); }>();
@ -131,6 +131,11 @@
isPass.value = isValidatePass; isPass.value = isValidatePass;
}; };
//
const form = ref({
field: props.fieldConfig?.field || '',
});
// //
const validateForm = async () => { const validateForm = async () => {
await formRef.value?.validate((errors) => { await formRef.value?.validate((errors) => {
@ -142,16 +147,6 @@
}); });
}; };
const handleConfirm = async () => {
await validateForm();
emits('confirm', isPass.value);
};
//
const form = ref({
field: props.fieldConfig?.field || '',
});
// //
const reset = () => { const reset = () => {
form.value.field = ''; form.value.field = '';
@ -164,6 +159,14 @@
reset(); reset();
}; };
const handleConfirm = async () => {
await validateForm();
if (props.isDelete) {
emits('confirm');
} else {
emits('confirm', form.value, handleCancel);
}
};
// //
const titleClass = computed(() => { const titleClass = computed(() => {
return props.isDelete return props.isDelete
@ -208,6 +211,7 @@
defineExpose({ defineExpose({
form, form,
isPass,
}); });
</script> </script>

View File

@ -595,4 +595,9 @@
:deep(.arco-table .arco-table-expand-btn:hover) { :deep(.arco-table .arco-table-expand-btn:hover) {
border-color: transparent; border-color: transparent;
} }
:deep(.arco-table-drag-handle) {
.arco-icon-drag-dot-vertical {
color: var(--color-text-brand);
}
}
</style> </style>

View File

@ -4,7 +4,7 @@
<template v-for="(element, idx) in baseAction" :key="element.label"> <template v-for="(element, idx) in baseAction" :key="element.label">
<a-divider v-if="element.isDivider" class="divider mx-0 my-[6px]" /> <a-divider v-if="element.isDivider" class="divider mx-0 my-[6px]" />
<a-button <a-button
v-else v-if="!element.isDivider && !element.children"
class="ml-[12px]" class="ml-[12px]"
:class="{ :class="{
'arco-btn-outline--danger': element.danger, 'arco-btn-outline--danger': element.danger,
@ -14,6 +14,28 @@
@click="handleSelect(element)" @click="handleSelect(element)"
>{{ t(element.label as string) }}</a-button >{{ t(element.label as string) }}</a-button
> >
<!-- baseAction多菜单选择 -->
<a-dropdown v-if="!element.isDivider && element.children" position="tr" @select="handleSelect">
<a-button
class="ml-[12px]"
:class="{
'arco-btn-outline--danger': element.danger,
'ml-[16px]': idx === 0,
}"
type="outline"
@click="handleSelect"
>{{ t(element.label as string) }}</a-button
>
<template #content>
<template v-for="item in element.children" :key="item.label">
<a-divider v-if="element.isDivider" margin="4px" />
<a-doption v-else :value="item" :class="{ delete: item.danger }">
{{ t(item.label as string) }}
</a-doption>
</template>
</template>
</a-dropdown>
<!-- baseAction多菜单选择 -->
</template> </template>
<div v-if="moreAction?.length" class="drop-down relative ml-[16px] inline-block"> <div v-if="moreAction?.length" class="drop-down relative ml-[16px] inline-block">
<a-dropdown position="tr" @select="handleSelect"> <a-dropdown position="tr" @select="handleSelect">

View File

@ -117,6 +117,7 @@ export interface BatchActionParams {
eventTag?: string; eventTag?: string;
isDivider?: boolean; isDivider?: boolean;
danger?: boolean; danger?: boolean;
children?: BatchActionParams[];
} }
export interface BatchActionConfig { export interface BatchActionConfig {
baseAction: BatchActionParams[]; baseAction: BatchActionParams[];

View File

@ -32,7 +32,7 @@
const attrs = useAttrs(); const attrs = useAttrs();
const filterTagList = computed(() => { const filterTagList = computed(() => {
return props.tagList.filter((item: any) => item) || []; return (props.tagList || []).filter((item: any) => item) || [];
}); });
const showTagList = computed(() => { const showTagList = computed(() => {

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="sticky top-[0] z-[9999] mb-[8px] flex justify-between bg-white"> <div v-if="props.mode === 'remote'" class="sticky top-[0] z-[9999] mb-[8px] flex justify-between bg-white">
<a-radio-group v-model:model-value="fileListTab" type="button" size="small"> <a-radio-group v-model:model-value="fileListTab" type="button" size="small">
<a-radio value="all">{{ `${t('ms.upload.all')} (${innerFileList.length})` }}</a-radio> <a-radio value="all">{{ `${t('ms.upload.all')} (${innerFileList.length})` }}</a-radio>
<a-radio value="waiting">{{ `${t('ms.upload.uploading')} (${totalWaitingFileList.length})` }}</a-radio> <a-radio value="waiting">{{ `${t('ms.upload.uploading')} (${totalWaitingFileList.length})` }}</a-radio>
@ -105,15 +105,21 @@
import { getFileEnum, getFileIcon } from './iconMap'; import { getFileEnum, getFileIcon } from './iconMap';
import type { MsFileItem } from './types'; import type { MsFileItem } from './types';
const props = defineProps<{ const props = withDefaults(
fileList: MsFileItem[]; defineProps<{
uploadFunc: (params: any) => Promise<any>; // mode?: 'static' | 'remote'; // |
requestParams?: Record<string, any>; // fileList: MsFileItem[];
route?: string; // uploadFunc?: (params: any) => Promise<any>; //
routeQuery?: Record<string, string>; // requestParams?: Record<string, any>; //
handleDelete?: (item: MsFileItem) => void; route?: string; //
handleReupload?: (item: MsFileItem) => void; routeQuery?: Record<string, string>; //
}>(); handleDelete?: (item: MsFileItem) => void;
handleReupload?: (item: MsFileItem) => void;
}>(),
{
mode: 'remote',
}
);
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'update:fileList', fileList: MsFileItem[]): void; (e: 'update:fileList', fileList: MsFileItem[]): void;
(e: 'delete', item: MsFileItem): void; (e: 'delete', item: MsFileItem): void;
@ -179,8 +185,10 @@
*/ */
function startUpload() { function startUpload() {
emit('start'); emit('start');
asyncTaskStore.setUploadFunc(props.uploadFunc, props.requestParams); if (props.mode === 'remote' && props.uploadFunc) {
asyncTaskStore.startUpload(innerFileList.value, props.route, props.routeQuery); asyncTaskStore.setUploadFunc(props.uploadFunc, props.requestParams);
asyncTaskStore.startUpload(innerFileList.value, props.route, props.routeQuery);
}
} }
/** /**

View File

@ -1,5 +1,6 @@
<template> <template>
<a-upload <a-upload
v-if="showDropArea"
v-bind="{ ...props }" v-bind="{ ...props }"
v-model:file-list="fileList" v-model:file-list="fileList"
:accept=" :accept="
@ -9,6 +10,10 @@
" "
:multiple="props.multiple" :multiple="props.multiple"
:disabled="props.disabled" :disabled="props.disabled"
:class="getAllScreenClass"
:style="{
width: props.isAllScreen ? `calc(100% - ${menuWidth}px - 16px)` : '100%',
}"
@change="handleChange" @change="handleChange"
@before-upload="beforeUpload" @before-upload="beforeUpload"
> >
@ -80,6 +85,7 @@
sizeUnit: 'MB' | 'KB'; // sizeUnit: 'MB' | 'KB'; //
isLimit: boolean; // isLimit: boolean; //
draggable: boolean; // draggable: boolean; //
isAllScreen?: boolean; //
}> & { }> & {
accept: UploadType; accept: UploadType;
fileList: MsFileItem[]; fileList: MsFileItem[];
@ -88,7 +94,9 @@
const props = withDefaults(defineProps<UploadProps>(), { const props = withDefaults(defineProps<UploadProps>(), {
showSubText: true, showSubText: true,
isLimit: true, isLimit: true,
isAllScreen: false,
}); });
const emit = defineEmits(['update:fileList', 'change']); const emit = defineEmits(['update:fileList', 'change']);
const defaultMaxSize = 50; const defaultMaxSize = 50;
@ -126,16 +134,97 @@
function handleChange(_fileList: MsFileItem[], fileItem: MsFileItem) { function handleChange(_fileList: MsFileItem[], fileItem: MsFileItem) {
emit('change', _fileList, fileItem); emit('change', _fileList, fileItem);
} }
const total = ref(''); //
const other = ref(''); //
const showDropArea = ref(false);
watch(
() => props.isAllScreen,
(val) => {
if (val) {
total.value = '100vh';
other.value = '110px';
showDropArea.value = false;
} else {
total.value = '154px';
other.value = '0px';
showDropArea.value = true;
}
},
{ immediate: true }
);
const getAllScreenClass = computed(() => {
return props.isAllScreen ? ['!fixed', 'right-[16px]', '-bottom-[10px]', 'z-[999]', 'opacity-90'] : [];
});
//
function disableDefaultEvents() {
const doc = document.documentElement;
doc.addEventListener('dragleave', (e) => e.preventDefault()); //
doc.addEventListener('drop', (e) => e.preventDefault()); //
doc.addEventListener('dragenter', (e) => e.preventDefault()); //
doc.addEventListener('dragover', (e) => e.preventDefault()); //
}
const menuWidth = ref<number>();
const resizeObserver = ref();
const targetElement = ref();
function init() {
const ele = document.querySelector('body');
targetElement.value = document.querySelector('.menu-wrapper');
resizeObserver.value = new ResizeObserver((entries) => {
entries.forEach((item) => {
menuWidth.value = item.contentRect.width;
});
});
resizeObserver.value.observe(targetElement.value);
menuWidth.value = targetElement.value.getBoundingClientRect().width;
if (ele) {
ele.addEventListener('dragenter', () => {
showDropArea.value = true;
});
//
ele.addEventListener('dragleave', (e: any) => {
if (
e.target.nodeName === 'HTML' ||
e.target === e.explicitOriginalTarget ||
(!e.fromElement &&
(e.clientX <= 0 || e.clientY <= 0 || e.clientX >= window.innerWidth || e.clientY >= window.innerHeight))
) {
showDropArea.value = false;
}
});
//
ele.addEventListener('drop', (e) => {
showDropArea.value = false;
e.preventDefault();
});
}
}
onMounted(() => {
disableDefaultEvents();
init();
});
onBeforeUnmount(() => {
resizeObserver.value.disconnect();
});
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
.ms-upload-area { .ms-upload-area {
@apply flex w-full flex-col items-center justify-center; height: calc(v-bind(total) - v-bind(other));
height: 154px;
border: 1px dashed var(--color-text-input-border); border: 1px dashed var(--color-text-input-border);
border-color: rgb(var(--primary-5)) !important;
border-radius: var(--border-radius-small); border-radius: var(--border-radius-small);
background-color: var(--color-text-n9); background-color: var(--color-text-n9);
@apply flex flex-col items-center justify-center;
.ms-upload-icon-box { .ms-upload-icon-box {
@apply rounded-full bg-white; @apply rounded-full bg-white;

View File

@ -11,4 +11,5 @@ export type MsFileItem = FileItem & {
enable?: boolean; // jar类型文件是否可用 enable?: boolean; // jar类型文件是否可用
uploadedTime?: string | number; // 上传完成时间 uploadedTime?: string | number; // 上传完成时间
errMsg?: string; // 上传失败的错误信息 errMsg?: string; // 上传失败的错误信息
[key: string]: any;
}; };

View File

@ -337,4 +337,34 @@ export const pathMap: PathMapItem[] = [
}, },
], ],
}, },
{
key: 'FEATURE_TEST', // 功能测试
locale: 'menu.featureTest',
route: RouteEnum.FEATURE_TEST,
permission: [],
level: MENU_LEVEL[2],
children: [
{
key: 'FEATURE_TEST_CASE', // 功能测试-功能用例
locale: 'menu.featureTest.featureCase',
route: RouteEnum.FEATURE_TEST_CASE,
permission: [],
level: MENU_LEVEL[2],
},
{
key: 'FEATURE_TEST_CASE_DETAIL', // 功能测试-功能用例
locale: 'menu.featureTest.featureCaseDetail',
route: RouteEnum.FEATURE_TEST_CASE_DETAIL,
permission: [],
level: MENU_LEVEL[2],
},
{
key: 'FEATURE_TEST_CASE_RECYCLE', // 功能测试-功能用例-回收站
locale: 'menu.featureTest.featureCaseRecycle',
route: RouteEnum.FEATURE_TEST_CASE_RECYCLE,
permission: [],
level: MENU_LEVEL[2],
},
],
},
]; ];

View File

@ -0,0 +1,14 @@
export enum StatusType {
UN_REVIEWED = 'icon-icon_block_filled', // 未评审
UNDER_REVIEWED = 'icon-icon_testing', // 评审中
PASS = 'icon-icon_succeed_colorful', // 已通过
UN_PASS = 'icon-icon_close_colorful', // 未通过
RE_REVIEWED = 'icon-icon_resubmit_filled', // 重新提审
UN_EXECUTED = 'icon-icon_block_filled', // 未执行
PASSED = 'icon-icon_succeed_colorful', // 已执行
FAILED = 'icon-icon_close_colorful', // 失败
BLOCKED = 'icon-icon_block_filled', // 阻塞
SKIPPED = 'icon-icon_skip_planarity', // 跳过
}
export default {};

View File

@ -5,6 +5,10 @@ export enum FormCreateKeyEnum {
PROJECT_DEFECT_SYNC_TEMPLATE = 'ProjectDefectSyncTemplate', PROJECT_DEFECT_SYNC_TEMPLATE = 'ProjectDefectSyncTemplate',
// 关联需求 // 关联需求
PROJECT__RELATED_TEMPLATE = 'ProjectRelatedTemplate', PROJECT__RELATED_TEMPLATE = 'ProjectRelatedTemplate',
// 功能用例自定义字段
CASE_MANAGEMENT_FIELD = 'caseManagementFields',
// 自定义属性
CASE_CUSTOM_ATTRS = 'caseCustomAttributes',
} }
export default {}; export default {};

View File

@ -9,6 +9,8 @@ export enum BugManagementRouteEnum {
export enum FeatureTestRouteEnum { export enum FeatureTestRouteEnum {
FEATURE_TEST = 'featureTest', FEATURE_TEST = 'featureTest',
FEATURE_TEST_CASE = 'featureTestCase', FEATURE_TEST_CASE = 'featureTestCase',
FEATURE_TEST_CASE_RECYCLE = 'featureTestCaseRecycle',
FEATURE_TEST_CASE_DETAIL = 'featureTestCaseDetail',
} }
export enum PerformanceTestRouteEnum { export enum PerformanceTestRouteEnum {

View File

@ -25,9 +25,13 @@ export enum TableKeyEnum {
ORGANIZATION_PROJECT_USER_DRAWER = 'organizationProjectUserDrawer', ORGANIZATION_PROJECT_USER_DRAWER = 'organizationProjectUserDrawer',
FILE_MANAGEMENT_FILE = 'fileManagementFile', FILE_MANAGEMENT_FILE = 'fileManagementFile',
FILE_MANAGEMENT_CASE = 'fileManagementCase', FILE_MANAGEMENT_CASE = 'fileManagementCase',
FILE_MANAGEMENT_CASE_RECYCLE = 'fileManagementCaseRecycle',
FILE_MANAGEMENT_VERSION = 'fileManagementVersion', FILE_MANAGEMENT_VERSION = 'fileManagementVersion',
PROJECT_MANAGEMENT_MENU_FALSE_ALERT = 'projectManagementMenuFalseAlert', PROJECT_MANAGEMENT_MENU_FALSE_ALERT = 'projectManagementMenuFalseAlert',
ORGANIZATION_TEMPLATE_DEFECT_TABLE = 'organizationTemplateManagementDefect', ORGANIZATION_TEMPLATE_DEFECT_TABLE = 'organizationTemplateManagementDefect',
CASE_MANAGEMENT_TABLE = 'caseManagement',
CASE_MANAGEMENT_DETAIL_TABLE = 'caseManagementDetailTable',
CASE_MANAGEMENT_ASSOCIATED_TABLE = 'caseManagementAssociatedFileTable',
BUG_MANAGEMENT = 'bugManagement', BUG_MANAGEMENT = 'bugManagement',
} }

View File

@ -35,6 +35,9 @@ export default {
'menu.projectManagement.messageManagement': '消息管理', 'menu.projectManagement.messageManagement': '消息管理',
'menu.projectManagement.messageManagementEdit': '更新模板', 'menu.projectManagement.messageManagementEdit': '更新模板',
'menu.featureTest.featureCase': '功能用例', 'menu.featureTest.featureCase': '功能用例',
'menu.featureTest.featureCaseRecycle': '回收站',
'menu.featureTest.featureCaseList': '用例列表',
'menu.featureTest.featureCaseDetail': '创建用例',
'menu.projectManagement.projectPermission': '项目与权限', 'menu.projectManagement.projectPermission': '项目与权限',
'menu.settings': '系统设置', 'menu.settings': '系统设置',
'menu.settings.system': '系统', 'menu.settings.system': '系统',

View File

@ -0,0 +1,184 @@
import { TableQueryParams } from '@/models/common';
import { StatusType } from '@/enums/caseEnum';
export interface ModulesTreeType {
id: string;
name: string;
type: string;
parentId: string;
children: ModulesTreeType[];
attachInfo: Record<string, any>; // 附加信息
count: number; // 节点资源数量(多数情况下不会随着节点信息返回,视接口而定)
}
// 创建模块
export interface CreateOrUpdateModule {
projectId: string;
name: string;
parentId: string;
}
// 更新模块
export interface UpdateModule {
id: string;
name: string;
}
// 移动模块树
export interface MoveModules {
dragNodeId: string; // 被拖拽的节点
dropNodeId: string; // 放入的节点
dropPosition: number; // 放入的位置(取值:-1,0,1。 -1dropNodeId节点之前。 0:dropNodeId节点内。 1dropNodeId节点后
}
export interface customFieldsItem {
caseId?: string; // 用例id
fieldId: string;
value: string;
}
// 功能用例表
export interface CaseManagementTable {
id: string;
num: number;
moduleId: string; // 模块ID
projectId: string;
templateId: string; // 模板ID
name: string; // 名称
reviewStatus: StatusType[keyof StatusType]; // 评审状态:未评审/评审中/通过/不通过/重新提审
tags: any; // 标签JSON)
caseEditType: string; // 编辑模式:步骤模式/文本模式
pos: number; // 自定义排序间隔5000
versionId: string; // 版本ID
refId: string; // 指向初始版本ID
lastExecuteResult: string; // 最近的执行结果:未执行/通过/失败/阻塞/跳过
deleted: true; // 是否在回收站0-否1-是
publicCase: true; // 是否是公共用例0-否1-是
latest: true; // 是否为最新版本0-否1-是
createUser: string;
updateUser: string;
deleteUser: string;
createTime: string;
updateTime: string;
deleteTime: string;
customFields: customFieldsItem[]; // 自定义字段集合
}
// 选择类型步骤和预期结果列表
export interface StepList {
id: string;
step: string; // 步骤
expected: string; // 预期
showStep: boolean; // 编辑步骤模式
showExpected: boolean; // 编辑预期模式
}
// 关联文件列表
export interface AssociatedList {
id: string;
name: string;
fileType: string; // 文件类型
projectId: string;
tags: any;
description: string;
moduleName: string; // 模块名称
moduleId: string;
createUser: string;
createTime: number | string;
updateUser: string;
updateTime: number;
storage: string;
size: number;
enable: true;
refId: string;
filePath: string; // 文件路径
[key: string]: any;
}
export interface CreateCaseType {
request: CaseManagementTable;
files: File[];
}
export interface FileListQueryParams extends TableQueryParams {
moduleIds: string[];
versionId: string;
projectId: string;
name: string;
}
export interface DeleteCaseType {
id: string;
deleteAll?: boolean;
projectId: string;
}
export interface BatchDeleteType {
selectIds: string[];
projectId: string;
}
export interface OptionsFieldId {
fieldId: string;
value: string;
text: string;
internal: boolean; // 是否是内置
}
export interface CustomAttributes {
fieldId: string;
fieldName: string;
required: boolean;
apiFieldId: null | undefined | 'string'; // 三方API
defaultValue: string;
type: string;
options: OptionsFieldId[];
}
// 批量编辑
export interface BatchEditCaseType {
selectIds: string[];
projectId: string;
append: boolean; // 是否追加标签
tags: string[];
customField: {
fieldId: string;
value: string;
};
}
// 批量移动
export interface BatchMoveOrCopyType {
selectIds: string[] | undefined;
projectId: string;
moduleId?: string;
moduleIds: string[];
selectAll: boolean;
excludeIds: string[] | undefined;
condition: Record<string, any>;
}
export interface CreateCase {
projectId: string;
templateId: string;
name: string;
prerequisite: string; // prerequisite
caseEditType: string; // 编辑模式:步骤模式/文本模式
steps: string;
textDescription: string;
expectedResult: string; // 预期结果
description: string;
publicCase: boolean; // 是否公共用例
moduleId: string;
versionId: string;
tags: any;
customFields: Record<string, any>; // 自定义字段集合
relateFileMetaIds: string[]; // 关联文件ID集合
[key: string]: any;
}
// 回收站
export interface CaseModuleQueryParams extends TableQueryParams {
moduleIds: string[];
projectId: string;
}
// export interface BatchParams extends BatchMoveOrCopyType {
// selectIds: string[];
// selectAll: boolean;
// moduleIds: string[];
// projectId: string;
// }

View File

@ -36,7 +36,7 @@ export interface MemberItem {
// 成员列表 // 成员列表
export type MemberList = MemberItem[]; export type MemberList = MemberItem[];
// 添加成员 // 添加成员
export interface AddorUpdateMemberModel { export interface AddOrUpdateMemberModel {
id?: string; id?: string;
organizationId?: string; organizationId?: string;
memberIds?: string[]; memberIds?: string[];

View File

@ -101,7 +101,8 @@ export interface CustomField {
fieldId: string; fieldId: string;
required?: boolean; // 是否必填 required?: boolean; // 是否必填
apiFieldId?: string; // api字段名 apiFieldId?: string; // api字段名
defaultValue: string | string[] | null | number; // 默认值 defaultValue?: string | string[] | null | number; // 默认值
[key: string]: any;
} }
export interface ActionTemplateManage { export interface ActionTemplateManage {

View File

@ -0,0 +1,72 @@
import { FeatureTestRouteEnum } from '@/enums/routeEnum';
import { DEFAULT_LAYOUT } from '../base';
import type { AppRouteRecordRaw } from '../types';
const FeatureTest: AppRouteRecordRaw = {
path: '/feature-test',
name: FeatureTestRouteEnum.FEATURE_TEST,
redirect: '/feature-test/featureCase',
component: DEFAULT_LAYOUT,
meta: {
locale: 'menu.featureTest',
icon: 'icon-icon_functional_testing',
order: 3,
hideChildrenInMenu: true,
},
children: [
// 功能用例
{
path: 'featureCase',
name: FeatureTestRouteEnum.FEATURE_TEST_CASE,
component: () => import('@/views/case-management/caseManagementFeature/index.vue'),
meta: {
locale: 'menu.featureTest.featureCase',
roles: ['*'],
isTopMenu: true,
},
},
// 功能用例回收站
{
path: 'featureCaseRecycle',
name: FeatureTestRouteEnum.FEATURE_TEST_CASE_RECYCLE,
component: () => import('@/views/case-management/caseManagementFeature/components/recycleCaseTable.vue'),
meta: {
locale: 'menu.featureTest.featureCaseRecycle',
roles: ['*'],
breadcrumbs: [
{
name: FeatureTestRouteEnum.FEATURE_TEST_CASE,
locale: 'menu.featureTest.featureCaseList',
},
{
name: FeatureTestRouteEnum.FEATURE_TEST_CASE_RECYCLE,
locale: 'menu.featureTest.featureCaseRecycle',
},
],
},
},
// 创建用例&编辑用例
{
path: 'featureCaseDetail',
name: FeatureTestRouteEnum.FEATURE_TEST_CASE_DETAIL,
component: () => import('@/views/case-management/caseManagementFeature/components/caseDetail.vue'),
meta: {
locale: 'menu.featureTest.featureCaseDetail',
roles: ['*'],
breadcrumbs: [
{
name: FeatureTestRouteEnum.FEATURE_TEST_CASE,
locale: 'menu.featureTest.featureCase',
},
{
name: FeatureTestRouteEnum.FEATURE_TEST_CASE_DETAIL,
locale: 'menu.featureTest.featureCaseDetail',
},
],
},
},
],
};
export default FeatureTest;

View File

@ -1,32 +0,0 @@
import { FeatureTestRouteEnum } from '@/enums/routeEnum';
import { DEFAULT_LAYOUT } from '../base';
import type { AppRouteRecordRaw } from '../types';
const FeatureTest: AppRouteRecordRaw = {
path: '/feature-test',
name: FeatureTestRouteEnum.FEATURE_TEST,
redirect: '/feature-test/featureCase',
component: DEFAULT_LAYOUT,
meta: {
locale: 'menu.featureTest',
icon: 'icon-icon_functional_testing',
order: 3,
hideChildrenInMenu: true,
},
children: [
// 功能用例
{
path: 'featureCase',
name: FeatureTestRouteEnum.FEATURE_TEST_CASE,
component: () => import('@/views/feature-test/featureCase/index.vue'),
meta: {
locale: 'menu.featureTest.featureCase',
roles: ['*'],
isTopMenu: true,
},
},
],
};
export default FeatureTest;

View File

@ -0,0 +1,60 @@
import { defineStore } from 'pinia';
import { getCaseModulesCounts, getRecycleModulesCounts } from '@/api/modules/case-management/featureCase';
import type { CaseModuleQueryParams } from '@/models/caseManagement/featureCase';
import { ModuleTreeNode } from '@/models/projectManagement/file';
const useFeatureCaseStore = defineStore('featureCase', {
persist: true,
state: (): {
moduleId: string[]; // 当前选中模块
allModuleId: string[]; // 所有模块
caseTree: ModuleTreeNode[]; // 用例树
modulesCount: Record<string, any>; // 用例树模块数量
recycleModulesCount: Record<string, any>; // 回收站模块数量
operatingState: boolean; // 操作状态
} => ({
moduleId: [],
allModuleId: [],
caseTree: [],
modulesCount: {},
recycleModulesCount: {},
operatingState: false,
}),
actions: {
// 设置选择moduleId
setModuleId(currentModuleId: string[], offspringIds: string[]) {
this.moduleId = currentModuleId;
if (offspringIds.length > 0) {
this.allModuleId = offspringIds;
}
},
// 设置用例树
setModulesTree(tree: ModuleTreeNode[]) {
this.caseTree = tree;
},
// 获取模块数量
async getCaseModulesCountCount(params: CaseModuleQueryParams) {
try {
this.modulesCount = await getCaseModulesCounts(params);
} catch (error) {
console.log(error);
}
},
// 获取模块数量
async getRecycleMModulesCountCount(params: CaseModuleQueryParams) {
try {
this.recycleModulesCount = await getRecycleModulesCounts(params);
} catch (error) {
console.log(error);
}
},
// 设置是否是编辑或者新增成功状态
setIsAlreadySuccess(state: boolean) {
this.operatingState = state;
},
},
});
export default useFeatureCaseStore;

View File

@ -0,0 +1,216 @@
<template>
<MsDrawer
v-model:visible="showDrawer"
:mask="false"
:title="t('featureTest.featureCase.associatedFile')"
:ok-text="t('featureTest.featureCase.associated')"
:ok-loading="drawerLoading"
:width="960"
unmount-on-close
:show-continue="false"
@confirm="handleDrawerConfirm"
@cancel="handleDrawerCancel"
>
<div class="mb-4 grid grid-cols-4 gap-2">
<div class="col-end-4">
<a-select v-model="fileType" @change="changeSelect">
<a-option key="" value="">{{ t('common.all') }}</a-option>
<a-option v-for="item of fileTypeList" :key="item" :value="item">{{ item }}</a-option>
<template #prefix
><span>{{ t('featureTest.featureCase.fileType') }}</span></template
>
</a-select></div
>
<div>
<a-input-search
v-model="searchParams.keyword"
:max-length="250"
:placeholder="t('project.member.searchMember')"
allow-clear
@search="searchHandler"
@press-enter="searchHandler"
></a-input-search
></div>
</div>
<MsBaseTable v-bind="propsRes" v-on="propsEvent">
<template #name="{ record }">
<div class="flex items-center">
<span> <MsIcon class="mr-1" :type="getFileType(record.fileType)" /></span>
<span>{{ record.name }}</span>
</div>
</template>
</MsBaseTable>
</MsDrawer>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
import type { MsTableColumn } from '@/components/pure/ms-table/type';
import useTable from '@/components/pure/ms-table/useTable';
import { FileIconMap, getFileEnum } from '@/components/pure/ms-upload/iconMap';
import { getAssociatedFileListUrl } from '@/api/modules/case-management/featureCase';
import { getFileTypes } from '@/api/modules/project-management/fileManagement';
import { useI18n } from '@/hooks/useI18n';
import { useAppStore } from '@/store';
import type { AssociatedList } from '@/models/caseManagement/featureCase';
import { TableQueryParams } from '@/models/common';
import { TableKeyEnum } from '@/enums/tableEnum';
import { UploadStatus } from '@/enums/uploadEnum';
const { t } = useI18n();
const showDrawer = ref<boolean>(false);
const drawerLoading = ref<boolean>(false);
const appStore = useAppStore();
const getCurrentProjectId = computed(() => appStore.getCurrentProjectId);
const fileTypeList = ref<string[]>([]);
const fileType = ref('');
const props = defineProps<{
visible: boolean;
}>();
const emit = defineEmits<{
(e: 'update:visible', val: boolean): void;
(e: 'save', selectList: AssociatedList[]): void;
}>();
const columns: MsTableColumn = [
{
title: 'featureTest.featureCase.fileName',
dataIndex: 'name',
slotName: 'name',
showInTable: true,
showTooltip: true,
showDrag: false,
width: 300,
},
{
title: 'featureTest.featureCase.description',
dataIndex: 'description',
showInTable: true,
showTooltip: true,
showDrag: true,
},
{
title: 'featureTest.featureCase.tags',
dataIndex: 'tags',
isTag: true,
showDrag: true,
},
{
title: 'featureTest.featureCase.tableColumnCreateUser',
dataIndex: 'createUser',
sortable: {
sortDirections: ['ascend', 'descend'],
},
showTooltip: true,
showDrag: true,
},
{
title: 'featureTest.featureCase.tableColumnUpdateUser',
dataIndex: 'updateUser',
sortable: {
sortDirections: ['ascend', 'descend'],
},
showTooltip: true,
showInTable: true,
},
{
title: 'featureTest.featureCase.tableColumnUpdateTime',
dataIndex: 'updateTime',
sortable: {
sortDirections: ['ascend', 'descend'],
},
showTooltip: true,
showInTable: true,
},
];
const { propsRes, propsEvent, loadList, setLoadListParams, resetSelector } = useTable(
getAssociatedFileListUrl,
{
tableKey: TableKeyEnum.CASE_MANAGEMENT_ASSOCIATED_TABLE,
selectable: true,
noDisable: true,
columns,
scroll: {
x: 1400,
},
heightUsed: 300,
},
(record) => ({
...record,
tags: record.tags || [],
})
);
const searchParams = ref<TableQueryParams>({
keyword: '',
filter: {},
moduleIds: [],
fileType: fileType.value,
projectId: getCurrentProjectId.value,
});
const initData = async () => {
setLoadListParams({ ...searchParams.value });
await loadList();
};
const searchHandler = () => {
initData();
resetSelector();
};
function changeSelect(value: string | number | boolean | Record<string, any>) {
searchParams.value.fileType = value;
initData();
}
const tableSelected = ref<AssociatedList[]>([]);
function handleDrawerConfirm() {
const selectedIds = [...propsRes.value.selectedKeys];
tableSelected.value = propsRes.value.data.filter((item: any) => selectedIds.indexOf(item.id) > -1);
emit('save', tableSelected.value);
showDrawer.value = false;
propsRes.value.selectedKeys.clear();
}
function handleDrawerCancel() {
showDrawer.value = false;
resetSelector();
}
function getFileType(type: string) {
const fileTypes = type ? getFileEnum(`/${type.toLowerCase()}`) : 'unknown';
return FileIconMap[fileTypes][UploadStatus.done];
}
watch(
() => props.visible,
(val) => {
showDrawer.value = val;
if (val) {
initData();
}
}
);
watch(
() => showDrawer.value,
(val) => {
emit('update:visible', val);
}
);
onBeforeMount(async () => {
fileTypeList.value = await getFileTypes(appStore.currentProjectId);
});
</script>
<style scoped></style>

View File

@ -0,0 +1,222 @@
<template>
<MsDialog
v-model:visible="isVisible"
dialog-size="small"
:title="t('featureTest.featureCase.batchEdit', { number: props.batchParams.currentSelectCount })"
ok-text="common.confirm"
:confirm="confirmHandler"
:close="closeHandler"
:switch-props="{
switchName: t('featureTest.featureCase.appendTag'),
switchTooltip: t('featureTest.featureCase.enableTags'),
showSwitch: form.selectedAttrsId === 'systemTags' ? true : false,
enable: form.append,
}"
>
<div class="form">
<a-form ref="formRef" :model="form" size="large" layout="vertical">
<a-form-item
field="selectedAttrsId"
:label="t('featureTest.featureCase.selectAttrs')"
asterisk-position="end"
:rules="[{ required: true, message: t('system.orgTemplate.stateNameNotNull') }]"
>
<a-select v-model="form.selectedAttrsId" :placeholder="t('featureTest.featureCase.PleaseSelect')">
<a-option v-for="item of totalAttrs" :key="item.fieldId" :value="item.fieldId">{{
item.fieldName
}}</a-option>
<a-option key="systemTags" value="systemTags">{{ t('featureTest.featureCase.tags') }}</a-option>
</a-select>
</a-form-item>
<a-form-item
v-if="form.selectedAttrsId === 'systemTags'"
field="tags"
:label="t('featureTest.featureCase.batchUpdate')"
asterisk-position="end"
:rules="[{ required: true, message: t('featureTest.featureCase.PleaseInputTags') }]"
>
<a-input-tag
v-model="form.tags"
:placeholder="t('featureTest.featureCase.pleaseEnterInputTags')"
allow-clear
/>
</a-form-item>
<MsFormCreate
v-if="formRules.length && form.selectedAttrsId !== 'systemTags'"
ref="formCreateRef"
v-model:api="fApi"
:form-rule="formRules"
:form-create-key="FormCreateKeyEnum.CASE_CUSTOM_ATTRS"
/>
</a-form>
</div>
</MsDialog>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { FormInstance } from '@arco-design/web-vue';
import MsDialog from '@/components/pure/ms-dialog/index.vue';
import MsFormCreate from '@/components/pure/ms-form-create/form-create.vue';
import type { FormItem } from '@/components/pure/ms-form-create/types';
import type { BatchActionQueryParams } from '@/components/pure/ms-table/type';
import { batchEditAttrs, getCaseDefaultFields } from '@/api/modules/case-management/featureCase';
import { useI18n } from '@/hooks/useI18n';
import useAppStore from '@/store/modules/app';
import useFormCreateStore from '@/store/modules/form-create/form-create';
import type { BatchEditCaseType, CustomAttributes } from '@/models/caseManagement/featureCase';
import { FormCreateKeyEnum } from '@/enums/formCreateEnum';
import Message from '@arco-design/web-vue/es/message';
const isVisible = ref<boolean>(false);
const appStore = useAppStore();
const formCreateStore = useFormCreateStore();
const { t } = useI18n();
const props = defineProps<{
visible: boolean;
batchParams: BatchActionQueryParams;
}>();
const emits = defineEmits<{
(e: 'update:visible', visible: boolean): void;
(e: 'success'): void;
}>();
const currentProjectId = computed(() => appStore.currentProjectId);
const initForm = {
selectedAttrsId: '',
append: false,
tags: [],
};
const form = ref({ ...initForm });
const formRef = ref<FormInstance | null>(null);
const initDefaultForm: FormItem = {
type: 'SELECT',
name: 'name',
label: 'featureTest.featureCase.batchUpdate',
value: '',
options: [],
props: {
modelValue: '',
options: [],
disabled: true,
},
required: true,
};
/**
* 初始化批量编辑属性
*/
const totalAttrs = ref<CustomAttributes[]>([]);
async function initDefaultFields() {
try {
const res = await getCaseDefaultFields(currentProjectId.value);
totalAttrs.value = res.customFields;
} catch (error) {
console.log(error);
}
}
const formRules = ref<FormItem[]>([{ ...initDefaultForm }]);
const fApi = ref<any>({});
const formCreateValue = computed(() => formCreateStore.formCreateRuleMap.get(FormCreateKeyEnum.CASE_CUSTOM_ATTRS));
const updateType = computed(() => {
return totalAttrs.value.find((item: any) => item.fieldId === form.value.selectedAttrsId)?.type;
});
watch(
() => updateType.value,
(val) => {
const currentAttrs = totalAttrs.value.filter((item: any) => item.fieldId === form.value.selectedAttrsId);
if (val) {
formRules.value = currentAttrs.map((item: CustomAttributes) => {
return {
type: val,
name: item.fieldId,
label: 'featureTest.featureCase.batchUpdate',
value: item.defaultValue,
options: item.options,
props: {
modelValue: item.defaultValue,
options: item.options,
disabled: !form.value.selectedAttrsId,
},
required: item.required,
};
}) as FormItem[];
}
}
);
function closeHandler() {
isVisible.value = false;
formRef.value?.resetFields();
form.value = { ...initForm };
formRules.value = [{ ...initDefaultForm }];
}
async function confirmHandler(enable: boolean | undefined) {
await formRef.value?.validate().then(async (error) => {
if (!error) {
try {
const customField = {
fieldId: '',
value: '',
};
formCreateValue.value?.forEach((item: any) => {
customField.fieldId = item.field;
customField.value = JSON.stringify(item.value);
});
const params: BatchEditCaseType = {
selectIds: props.batchParams.selectedIds as string[],
projectId: currentProjectId.value,
append: enable as boolean,
tags: form.value.tags,
customField,
};
await batchEditAttrs(params);
Message.success(t('featureTest.featureCase.editSuccess'));
closeHandler();
emits('success');
} catch (e) {
console.log(e);
}
} else {
return false;
}
});
}
watch(
() => isVisible.value,
(val) => {
emits('update:visible', val);
}
);
watch(
() => props.visible,
(val) => {
isVisible.value = val;
if (val) {
initDefaultFields();
}
}
);
onBeforeUnmount(() => {
formRules.value = [];
});
</script>
<style scoped></style>

View File

@ -0,0 +1,143 @@
<template>
<MsCard
:loading="loading"
:title="title"
:is-edit="isEdit"
has-breadcrumb
@save="saveHandler"
@save-and-continue="saveHandler(true)"
>
<template #headerRight>
<a-select class="w-[240px]" :placeholder="t('featureTest.featureCase.versionPlaceholder')">
<a-option v-for="template of versionOptions" :key="template.id" :value="template.id">{{
template.name
}}</a-option>
</a-select>
</template>
<CaseTemplateDetail ref="caseModuleDetailRef" v-model:form-mode-value="caseDetailInfo" />
</MsCard>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import MsCard from '@/components/pure/ms-card/index.vue';
import CaseTemplateDetail from './caseTemplateDetail.vue';
import { createCaseRequest, updateCaseRequest } from '@/api/modules/case-management/featureCase';
import { useI18n } from '@/hooks/useI18n';
import useFeatureCaseStore from '@/store/modules/case/featureCase';
import { scrollIntoView } from '@/utils/dom';
import { FeatureTestRouteEnum } from '@/enums/routeEnum';
import Message from '@arco-design/web-vue/es/message';
const { t } = useI18n();
const route = useRoute();
const router = useRouter();
const featureCaseStore = useFeatureCaseStore();
const caseDetailInfo = ref<Record<string, any>>({
request: {},
fileList: [],
});
const versionOptions = ref([
{
id: '1001',
name: '模板01',
},
]);
const title = ref('');
const loading = ref(false);
const isEdit = computed(() => !!route.query.id);
const isContinueFlag = ref(false);
async function save() {
try {
loading.value = true;
if (isEdit.value) {
await updateCaseRequest(caseDetailInfo.value);
Message.success(t('featureTest.featureCase.editSuccess'));
} else {
await createCaseRequest(caseDetailInfo.value);
Message.success(t('common.addSuccess'));
}
router.push({ name: FeatureTestRouteEnum.FEATURE_TEST_CASE, query: { ...route.query } });
featureCaseStore.setIsAlreadySuccess(true);
} catch (error) {
console.log(error);
} finally {
loading.value = false;
}
}
const caseModuleDetailRef = ref();
//
function saveHandler(isContinue = false) {
const { caseFormRef, formRef, fApi } = caseModuleDetailRef.value;
isContinueFlag.value = isContinue;
caseFormRef?.validate().then((res: any) => {
if (!res) {
fApi.validate((valid: any) => {
if (valid === true) {
formRef?.validate().then((result: any) => {
if (!result) {
return save();
}
});
}
});
}
return scrollIntoView(document.querySelector('.arco-form-item-message'), { block: 'center' });
});
}
watchEffect(() => {
if (isEdit.value) {
title.value = t('featureTest.featureCase.updateCase');
} else {
title.value = t('featureTest.featureCase.creatingCase');
}
});
</script>
<style scoped lang="less">
.wrapper-preview {
display: flex;
.preview-left {
width: 100%;
border-right: 1px solid var(--color-text-n8);
.changeType {
padding: 2px 4px;
border-radius: 4px;
color: var(--color-text-4);
:deep(.arco-icon-down) {
font-size: 14px;
}
&:hover {
color: rgb(var(--primary-5));
background: rgb(var(--primary-1));
cursor: pointer;
}
}
}
.preview-right {
width: 428px;
}
}
.circle {
width: 16px;
height: 16px;
line-height: 16px;
border-radius: 50%;
text-align: center;
color: var(--color-text-4);
background: var(--color-text-n8);
}
</style>

View File

@ -0,0 +1,779 @@
<template>
<div class="page-header mb-4 h-[34px]">
<div class="text-[var(--color-text-1)]"
>{{ t('featureTest.featureCase.allCase') }}
<span class="text-[var(--color-text-4)]"> ({{ props.modulesCount.all }})</span></div
>
<div class="flex w-[80%] items-center justify-end">
<a-select class="w-[240px]" :placeholder="t('featureTest.featureCase.versionPlaceholder')">
<a-option v-for="version of versionOptions" :key="version.id" :value="version.id">{{ version.name }}</a-option>
</a-select>
<a-input-search
v-model:model-value="keyword"
:placeholder="t('featureTest.featureCase.searchByNameAndId')"
allow-clear
class="mx-[8px] w-[240px]"
@search="searchList"
@press-enter="searchList"
></a-input-search>
<MsTag
:type="isExpandFilter ? 'primary' : 'default'"
:theme="isExpandFilter ? 'lightOutLine' : 'outline'"
size="large"
class="-mt-[3px] min-w-[64px] cursor-pointer"
>
<span :class="!isExpandFilter ? 'text-[var(--color-text-4)]' : ''" @click="isExpandFilterHandler"
><icon-filter class="mr-[4px]" :style="{ 'font-size': '16px' }" />{{
t('featureTest.featureCase.filter')
}}</span
>
</MsTag>
<a-radio-group v-model:model-value="showType" type="button" class="file-show-type ml-[4px]">
<a-radio value="list" class="show-type-icon p-[2px]"><MsIcon type="icon-icon_view-list_outlined" /></a-radio>
<a-radio value="xMind" class="show-type-icon p-[2px]"><MsIcon type="icon-icon_mindnote_outlined" /></a-radio>
</a-radio-group>
</div>
</div>
<FilterPanel v-show="isExpandFilter"></FilterPanel>
<!-- 脑图开始 -->
<MinderEditor
v-if="showType === 'xMind'"
:import-json="importJson"
:tags="['模块', '用例', '前置条件', '备注', '步骤', '预期结果']"
tag-enable
sequence-enable
@node-click="handleNodeClick"
/>
<MsDrawer v-model:visible="visible" :width="480" :mask="false">
{{ nodeData.text }}
</MsDrawer>
<!-- 脑图结束 -->
<!-- 用例表开始 -->
<ms-base-table
v-bind="propsRes"
:action-config="tableBatchActions"
@selected-change="handleTableSelect"
v-on="propsEvent"
@batch-action="handleTableBatch"
>
<template #name="{ record }">
<a-button type="text" class="px-0" @click="showCaseDetail(record.id)">{{ record.name }}</a-button>
</template>
<template #reviewStatus="{ record }">
<MsIcon
:type="getStatusText(record.reviewStatus)?.iconType || ''"
class="mr-1"
:class="[getReviewStatusClass(record.reviewStatus)]"
></MsIcon>
<span>{{ getStatusText(record.reviewStatus)?.statusType || '' }} </span>
</template>
<template #lastExecuteResult="{ record }">
<MsIcon
:type="getStatusText(record.lastExecuteResult)?.iconType || ''"
class="mr-1"
:class="[getReviewStatusClass(record.lastExecuteResult)]"
></MsIcon>
<span>{{ getStatusText(record.lastExecuteResult)?.statusType || '' }}</span>
</template>
<template #moduleId="{ record }">
<a-tooltip :content="getModules(record.moduleId)" position="top">
<span class="one-line-text inline-block">{{ getModules(record.moduleId) }}</span>
</a-tooltip>
</template>
<template #operation="{ record }">
<MsButton @click="editCase(record)">{{ t('common.edit') }}</MsButton>
<MsButton class="!mr-0" @click="deleteCase(record)">{{ t('common.delete') }}</MsButton>
</template>
</ms-base-table>
<!-- 用例表结束 -->
<a-modal
v-model:visible="showBatchMoveDrawer"
title-align="start"
class="ms-modal-no-padding ms-modal-small"
:mask-closable="false"
:ok-text="
t(
isMove
? 'featureTest.featureCase.batchMoveSelectedModules'
: 'featureTest.featureCase.batchCopySelectedModules',
{
number: batchParams?.currentSelectCount || batchParams?.selectedIds?.length,
}
)
"
:ok-button-props="{ disabled: selectedModuleKeys.length === 0 }"
:cancel-button-props="{ disabled: batchMoveCaseLoading }"
:on-before-ok="handleCaseMoveOrCopy"
@close="handleMoveCaseModalCancel"
>
<template #title>
<div class="flex w-full items-center justify-between">
<div>
{{ isMove ? t('featureTest.featureCase.batchMoveTitle') : t('featureTest.featureCase.batchCopyTitle') }}
<span class="ml-[4px] text-[var(--color-text-4)]">
{{ t('featureTest.featureCase.batchMove', { number: batchParams.currentSelectCount }) }}
</span>
</div>
<div class="mr-2">
<a-select class="w-[120px]" placeholder="请选择版本">
<a-option v-for="item of versionOptions" :key="item.id" :value="item.id">{{ item.name }}</a-option>
</a-select>
</div>
</div>
</template>
<FeatureCaseTree
v-if="showBatchMoveDrawer"
ref="caseTreeRef"
v-model:selected-keys="selectedModuleKeys"
:active-folder="props.activeFolder"
:is-expand-all="true"
is-modal
@case-node-select="caseNodeSelect"
></FeatureCaseTree>
</a-modal>
<ExportExcelDrawer v-model:visible="showExportExcelVisible" />
<BatchEditModal v-model:visible="showEditModel" :batch-params="batchParams" @success="successHandler" />
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { useRouter } from 'vue-router';
import { Message } from '@arco-design/web-vue';
import MinderEditor from '@/components/pure/minder-editor/minderEditor.vue';
import MsButton from '@/components/pure/ms-button/index.vue';
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
import type { BatchActionParams, BatchActionQueryParams, MsTableColumn } from '@/components/pure/ms-table/type';
import useTable from '@/components/pure/ms-table/useTable';
import MsTag from '@/components/pure/ms-tag/ms-tag.vue';
import FilterPanel from '@/components/business/ms-filter-panel/searchForm.vue';
import BatchEditModal from './batchEditModal.vue';
import FeatureCaseTree from './caseTree.vue';
import ExportExcelDrawer from './exportExcelDrawer.vue';
import {
batchCopyToModules,
batchDeleteCase,
batchMoveToModules,
deleteCaseRequest,
getCaseDetail,
getCaseList,
updateCaseRequest,
} from '@/api/modules/case-management/featureCase';
import { useI18n } from '@/hooks/useI18n';
import useModal from '@/hooks/useModal';
import { useAppStore, useTableStore } from '@/store';
import useFeatureCaseStore from '@/store/modules/case/featureCase';
import { characterLimit, findNodePathByKey } from '@/utils';
import type { CaseManagementTable, CaseModuleQueryParams } from '@/models/caseManagement/featureCase';
import type { TableQueryParams } from '@/models/common';
import { FeatureTestRouteEnum } from '@/enums/routeEnum';
import { ColumnEditTypeEnum, TableKeyEnum } from '@/enums/tableEnum';
import { getReviewStatusClass, getStatusText } from './utils';
import debounce from 'lodash-es/debounce';
const { openModal } = useModal();
const { t } = useI18n();
const router = useRouter();
const appStore = useAppStore();
const featureCaseStore = useFeatureCaseStore();
const tableStore = useTableStore();
const props = defineProps<{
activeFolder: string;
activeFolderType: 'folder' | 'module';
offspringIds: string[]; // id
modulesCount: Record<string, number>; //
}>();
const emit = defineEmits<{
(e: 'init', params: CaseModuleQueryParams): void;
}>();
const keyword = ref<string>();
const showType = ref<string>('list');
const isExpandFilter = ref<boolean>(false);
const versionOptions = ref([
{
id: '1001',
name: 'v_1.0',
},
]);
const caseTreeData = computed(() => featureCaseStore.caseTree);
const moduleId = computed(() => featureCaseStore.moduleId[0]);
const currentProjectId = computed(() => appStore.currentProjectId);
// ||
const isExpandFilterHandler = () => {
isExpandFilter.value = !isExpandFilter.value;
};
const visible = ref<boolean>(false);
const nodeData = ref<any>({});
const importJson = ref<any>({});
function handleNodeClick(data: any) {
if (data.resource && data.resource.includes('用例')) {
visible.value = true;
nodeData.value = data;
}
}
onBeforeMount(() => {
importJson.value = {
root: {
data: {
text: '测试用例',
id: 'xxxx',
},
children: [
{
data: {
id: 'sdasdas',
text: '模块 1',
resource: ['模块'],
},
},
{
data: {
id: 'dasdasda',
text: '模块 2',
expandState: 'collapse',
},
children: [
{
data: {
id: 'frihofiuho3f',
text: '用例 1',
resource: ['用例'],
},
},
{
data: {
id: 'df09348f034f',
text: ' 用例 2',
resource: ['用例'],
},
},
],
},
],
},
template: 'default',
};
});
const columns: MsTableColumn = [
{
title: 'featureTest.featureCase.tableColumnID',
dataIndex: 'id',
width: 200,
showInTable: true,
sortable: {
sortDirections: ['ascend', 'descend'],
},
showTooltip: true,
ellipsis: true,
showDrag: false,
},
{
title: 'featureTest.featureCase.tableColumnName',
slotName: 'name',
dataIndex: 'name',
showInTable: true,
showTooltip: true,
width: 300,
editType: ColumnEditTypeEnum.INPUT,
sortable: {
sortDirections: ['ascend', 'descend'],
},
ellipsis: true,
showDrag: false,
},
{
title: 'featureTest.featureCase.tableColumnLevel',
dataIndex: 'level',
showInTable: true,
width: 200,
showTooltip: true,
ellipsis: true,
showDrag: true,
},
{
title: 'featureTest.featureCase.tableColumnCaseState',
dataIndex: 'caseState',
showInTable: true,
width: 200,
showTooltip: true,
ellipsis: true,
showDrag: true,
},
{
title: 'featureTest.featureCase.tableColumnReviewResult',
dataIndex: 'reviewStatus',
slotName: 'reviewStatus',
showInTable: true,
width: 200,
showDrag: true,
},
{
title: 'featureTest.featureCase.tableColumnExecutionResult',
dataIndex: 'lastExecuteResult',
slotName: 'lastExecuteResult',
showInTable: true,
width: 200,
showDrag: true,
},
{
title: 'featureTest.featureCase.tableColumnVersion',
slotName: 'versionId',
dataIndex: 'versionId',
width: 300,
showTooltip: true,
showInTable: true,
showDrag: true,
},
{
title: 'featureTest.featureCase.tableColumnModule',
slotName: 'moduleId',
showInTable: true,
width: 300,
showDrag: true,
},
{
title: 'featureTest.featureCase.tableColumnTag',
slotName: 'tags',
dataIndex: 'tags',
showInTable: true,
isTag: true,
showDrag: true,
},
{
title: 'featureTest.featureCase.tableColumnCreateUser',
slotName: 'createUser',
dataIndex: 'createUser',
showInTable: true,
showDrag: true,
},
{
title: 'featureTest.featureCase.tableColumnCreateTime',
slotName: 'createTime',
dataIndex: 'createTime',
showInTable: true,
sortable: {
sortDirections: ['ascend', 'descend'],
},
width: 200,
showDrag: true,
},
{
title: 'featureTest.featureCase.tableColumnUpdateUser',
slotName: 'updateUser',
dataIndex: 'updateUser',
showInTable: true,
width: 200,
showDrag: true,
},
{
title: 'featureTest.featureCase.tableColumnUpdateTime',
slotName: 'updateTime',
dataIndex: 'updateTime',
sortable: {
sortDirections: ['ascend', 'descend'],
},
showInTable: true,
width: 200,
showDrag: true,
},
{
title: 'featureTest.featureCase.tableColumnActions',
slotName: 'operation',
dataIndex: 'operation',
fixed: 'right',
width: 140,
showInTable: true,
showDrag: false,
},
];
const tableBatchActions = {
baseAction: [
{
label: 'featureTest.featureCase.export',
eventTag: 'export',
children: [
{
label: 'featureTest.featureCase.exportExcel',
eventTag: 'exportExcel',
},
{
label: 'featureTest.featureCase.exportXMind',
eventTag: 'exportXMind',
},
],
},
{
label: 'common.edit',
eventTag: 'batchEdit',
},
{
label: 'featureTest.featureCase.moveTo',
eventTag: 'batchMoveTo',
},
{
label: 'featureTest.featureCase.copyTo',
eventTag: 'batchCopyTo',
},
],
moreAction: [
{
label: 'featureTest.featureCase.associatedDemand',
eventTag: 'associatedDemand',
},
{
label: 'featureTest.featureCase.generatingDependencies',
eventTag: 'generatingDependencies',
},
{
label: 'featureTest.featureCase.addToPublic',
eventTag: 'addToPublic',
},
{
isDivider: true,
},
{
label: 'common.delete',
eventTag: 'delete',
danger: true,
},
],
};
/**
* 处理更新用例参数
* @param detailResult 详情字段
*/
function getUpdateParams(detailResult: Record<string, any>, name: string) {
const { customFields } = detailResult;
const customFieldsMaps: Record<string, any> = {};
customFields.forEach((item: any) => {
customFieldsMaps[item.fieldId] = JSON.parse(item.defaultValue);
});
return {
request: {
...detailResult,
name,
customFields: customFieldsMaps,
tags: JSON.parse(detailResult.tags),
},
fileList: [],
};
}
/**
* 更新用例名称
*/
async function updateCaseName(record: CaseManagementTable) {
try {
const detailResult = await getCaseDetail(record.id);
const params = await getUpdateParams(detailResult, record.name);
await updateCaseRequest(params);
Message.success(t('common.updateSuccess'));
return Promise.resolve(true);
} catch (error) {
console.log(error);
return Promise.resolve(false);
}
}
const { propsRes, propsEvent, loadList, setLoadListParams, resetSelector, setProps } = useTable(
getCaseList,
{
tableKey: TableKeyEnum.CASE_MANAGEMENT_TABLE,
scroll: { x: 3200 },
selectable: true,
showSetting: true,
heightUsed: 340,
enableDrag: true,
},
(record) => ({
...record,
tags: (JSON.parse(record.tags) || []).map((item: string, i: number) => {
return {
id: `${record.id}-${i}`,
name: item,
};
}),
}),
updateCaseName
);
//
function emitTableParams() {
emit('init', {
keyword: keyword.value,
moduleIds: [],
projectId: currentProjectId.value,
current: propsRes.value.msPagination?.current,
pageSize: propsRes.value.msPagination?.pageSize,
});
}
const tableSelected = ref<(string | number)[]>([]);
function handleTableSelect(selectArr: (string | number)[]) {
tableSelected.value = selectArr;
}
const searchParams = ref<TableQueryParams>({
projectId: currentProjectId.value,
moduleIds: [],
});
function getLoadListParams() {
if (props.activeFolder === 'all') {
searchParams.value.moduleIds = [];
} else {
searchParams.value.moduleIds = [moduleId.value, ...props.offspringIds];
}
setLoadListParams({
...searchParams.value,
keyword: keyword.value,
});
}
//
async function initData() {
getLoadListParams();
loadList();
emitTableParams();
}
//
function editCase(record: CaseManagementTable) {
router.push({
name: FeatureTestRouteEnum.FEATURE_TEST_CASE_DETAIL,
query: {
id: record.id,
},
});
}
//
function deleteCase(record: CaseManagementTable) {
openModal({
type: 'error',
title: t('featureTest.featureCase.deleteCaseTitle', { name: characterLimit(record.name) }),
content: t('featureTest.featureCase.beforeDeleteCase'),
okText: t('common.confirmDelete'),
cancelText: t('common.cancel'),
okButtonProps: {
status: 'danger',
},
onBeforeOk: async () => {
try {
const params = {
id: record.id,
deleteAll: false,
projectId: currentProjectId.value,
};
await deleteCaseRequest(params);
Message.success(t('common.deleteSuccess'));
emitTableParams();
loadList();
} catch (error) {
console.log(error);
}
},
hideCancel: false,
});
}
const showExportExcelVisible = ref<boolean>(false);
// Excel
function handleShowExportExcel() {
showExportExcelVisible.value = true;
}
const selectData = ref<string[] | undefined>([]);
const showEditModel = ref<boolean>(false);
//
function batchEdit() {
showEditModel.value = true;
}
const showBatchMoveDrawer = ref<boolean>(false);
/**
* 处理文件夹树节点选中事件
*/
const selectedModuleKeys = ref<string[]>([]); //
const batchMoveCaseLoading = ref(false);
const batchParams = ref<BatchActionQueryParams>({
selectedIds: [],
selectAll: false,
excludeIds: [],
currentSelectCount: 0,
});
const isMove = ref<boolean>(false);
//
async function handleCaseMoveOrCopy() {
batchMoveCaseLoading.value = true;
try {
const params = {
selectIds: batchParams.value.selectedIds || [],
selectAll: !!batchParams.value?.selectAll,
excludeIds: batchParams.value?.excludeIds || [],
condition: { keyword: keyword.value },
projectId: currentProjectId.value,
moduleIds: props.activeFolder === 'all' ? [] : [props.activeFolder],
moduleId: selectedModuleKeys.value[0],
};
if (isMove.value) {
await batchMoveToModules(params);
Message.success(t('featureTest.featureCase.batchMoveSuccess'));
} else {
await batchCopyToModules(params);
Message.success(t('featureTest.featureCase.batchCopySuccess'));
}
isMove.value = false;
emitTableParams();
loadList();
resetSelector();
} catch (error) {
console.log(error);
} finally {
batchMoveCaseLoading.value = false;
}
}
function handleMoveCaseModalCancel() {
showBatchMoveDrawer.value = false;
selectedModuleKeys.value = [];
}
function caseNodeSelect(keys: string[]) {
selectedModuleKeys.value = keys;
}
//
function batchMoveOrCopy() {
showBatchMoveDrawer.value = true;
}
// name
function getModules(moduleIds: string) {
const modules = findNodePathByKey(caseTreeData.value, moduleIds, undefined, 'id');
const moduleName = (modules || []).treePath.map((item: any) => item.name);
if (moduleName.length === 1) {
return moduleName[0];
}
return `/${moduleName.join('/')}`;
}
//
async function batchDelete() {
openModal({
type: 'error',
title: t('featureTest.featureCase.batchDelete', { number: (selectData.value || []).length }),
content: t('featureTest.featureCase.beforeDeleteCase'),
okText: t('common.confirmDelete'),
cancelText: t('common.cancel'),
okButtonProps: {
status: 'danger',
},
onBeforeOk: async () => {
try {
await batchDeleteCase({
selectIds: batchParams.value.selectedIds as string[],
projectId: currentProjectId.value,
});
resetSelector();
Message.success(t('common.deleteSuccess'));
emitTableParams();
loadList();
} catch (error) {
console.log(error);
}
},
hideCancel: false,
});
}
function handleTableBatch(event: BatchActionParams, params: BatchActionQueryParams) {
batchParams.value = params;
if (event.eventTag === 'exportExcel') {
handleShowExportExcel();
} else if (event.eventTag === 'batchEdit') {
batchEdit();
} else if (event.eventTag === 'delete') {
batchDelete();
} else if (event.eventTag === 'batchMoveTo') {
batchMoveOrCopy();
isMove.value = true;
} else if (event.eventTag === 'batchCopyTo') {
batchMoveOrCopy();
isMove.value = false;
}
}
const searchList = debounce(() => {
getLoadListParams();
loadList();
}, 100);
function successHandler() {
loadList();
emitTableParams();
resetSelector();
}
//
function showCaseDetail(id: string) {}
watch(
() => showType.value,
() => {
initData();
}
);
watch(
() => props.activeFolder,
() => {
keyword.value = '';
initData();
resetSelector();
},
{ immediate: true }
);
tableStore.initColumn(TableKeyEnum.CASE_MANAGEMENT_TABLE, columns, 'drawer');
</script>
<style scoped lang="less">
.page-header {
@apply flex items-center justify-between;
}
.filter-panel {
background: var(--color-text-n9);
@apply mt-1 rounded-md p-3;
.condition-text {
color: var(--color-text-2);
}
}
</style>

View File

@ -0,0 +1,891 @@
<template>
<div class="wrapper-preview">
<div class="preview-left pr-4">
<a-form ref="caseFormRef" class="rounded-[4px]" :model="form" layout="vertical">
<a-form-item
field="name"
:label="t('system.orgTemplate.caseName')"
:rules="[{ required: true, message: t('system.orgTemplate.caseNamePlaceholder') }]"
required
asterisk-position="end"
>
<a-input
v-model="form.name"
:max-length="255"
:placeholder="t('system.orgTemplate.caseNamePlaceholder')"
show-word-limit
allow-clear
></a-input>
</a-form-item>
<a-form-item field="precondition" :label="t('system.orgTemplate.precondition')" asterisk-position="end">
<MsRichText v-model:model-value="form.prerequisite" />
</a-form-item>
<a-form-item
field="step"
:label="
form.caseEditType === 'STEP'
? t('system.orgTemplate.stepDescription')
: t('system.orgTemplate.textDescription')
"
class="relative"
>
<div class="absolute left-16 top-0">
<a-divider direction="vertical" />
<a-dropdown :popup-max-height="false" @select="handleSelectType">
<span class="changeType">{{ t('system.orgTemplate.changeType') }} <icon-down /></span>
<template #content>
<a-doption value="STEP" :class="getSelectTypeClass('STEP')">
{{ t('system.orgTemplate.stepDescription') }}</a-doption
>
<a-doption value="TEXT" :class="getSelectTypeClass('TEXT')">{{
t('system.orgTemplate.textDescription')
}}</a-doption>
</template>
</a-dropdown>
</div>
<!-- 步骤描述 -->
<div v-if="form.caseEditType === 'STEP'" class="w-full">
<MsBaseTable v-bind="propsRes" ref="stepTableRef" v-on="propsEvent">
<template #index="{ rowIndex }">
<div class="circle text-xs font-medium"> {{ rowIndex + 1 }}</div>
</template>
<template #caseStep="{ record }">
<a-textarea
v-if="record.showStep"
v-model="record.step"
size="mini"
:auto-size="true"
class="w-max-[267px]"
:placeholder="t('system.orgTemplate.stepTip')"
@blur="blurHandler(record, 'step')"
/>
<div
v-else-if="record.step && !record.showStep"
class="w-full cursor-pointer"
@click="edit(record, 'step')"
>{{ record.step }}</div
>
<div
v-else-if="!record.caseStep && !record.showStep"
class="placeholder w-full cursor-pointer text-[var(--color-text-brand)]"
@click="edit(record, 'step')"
>{{ t('system.orgTemplate.stepTip') }}</div
>
</template>
<template #expectedResult="{ record }">
<a-textarea
v-if="record.showExpected"
v-model="record.expected"
size="mini"
:auto-size="true"
class="w-max-[267px]"
:placeholder="t('system.orgTemplate.expectationTip')"
@blur="blurHandler(record, 'expected')"
/>
<div
v-else-if="record.expected && !record.showExpected"
class="w-full cursor-pointer"
@click="edit(record, 'expected')"
>{{ record.expected }}</div
>
<div
v-else-if="!record.expected && !record.showExpected"
class="placeholder w-full cursor-pointer text-[var(--color-text-brand)]"
@click="edit(record, 'expected')"
>{{ t('system.orgTemplate.expectationTip') }}</div
>
</template>
<template #operation="{ record }">
<MsTableMoreAction
v-if="!record.internal"
:list="moreActions"
@select="(item:ActionsItem) => handleMoreActionSelect(item,record)"
/>
</template>
</MsBaseTable>
<a-button class="mt-2 px-0" type="text" @click="addStep">
<template #icon>
<icon-plus class="text-[14px]" />
</template>
{{ t('system.orgTemplate.addStep') }}
</a-button>
</div>
<!-- 文本描述 -->
<MsRichText v-else v-model:modelValue="form.textDescription" />
</a-form-item>
<a-form-item
v-if="form.caseEditType === 'TEXT'"
field="remark"
:label="t('featureTest.featureCase.expectedResult')"
>
<MsRichText v-model:modelValue="form.expectedResult" />
</a-form-item>
<a-form-item field="remark" :label="t('featureTest.featureCase.remark')">
<MsRichText v-model:modelValue="form.description" />
</a-form-item>
<a-form-item field="attachment" :label="t('featureTest.featureCase.addAttachment')">
<div class="flex flex-col">
<div class="mb-1">
<a-dropdown position="tr" trigger="hover">
<a-button type="outline">
<template #icon> <icon-plus class="text-[14px]" /> </template
>{{ t('system.orgTemplate.addAttachment') }}</a-button
>
<template #content>
<a-upload
ref="uploadRef"
v-model:file-list="fileList"
:auto-upload="false"
:show-file-list="false"
:before-upload="beforeUpload"
@change="handleChange"
>
<template #upload-button>
<a-button type="text" class="!text-[var(--color-text-1)]">
<icon-upload />{{ t('featureTest.featureCase.uploadFile') }}</a-button
>
</template>
</a-upload>
<a-button type="text" class="!text-[var(--color-text-1)]" @click="associatedFile">
<MsIcon type="icon-icon_link-copy_outlined" size="16" />{{
t('featureTest.featureCase.associatedFile')
}}</a-button
>
</template>
</a-dropdown>
</div>
<div class="!hover:bg-[rgb(var(--primary-1))] !text-[var(--color-text-4)]">{{
t('system.orgTemplate.addAttachmentTip')
}}</div>
</div>
</a-form-item>
</a-form>
<!-- 文件列表开始 -->
<div class="w-[90%]">
<MsFileList ref="fileListRef" v-model:file-list="fileList" mode="static">
<template #actions="{ item }">
<!-- 本地文件 -->
<div v-if="item.local || item.status === 'init'" class="flex flex-nowrap">
<MsButton type="button" status="danger" class="!mr-[4px]" @click="transferFile(item)">
{{ t('featureTest.featureCase.storage') }}
</MsButton>
<MsButton type="button" status="primary" class="!mr-[4px]" @click="downloadFile(item)">
{{ t('featureTest.featureCase.download') }}
</MsButton>
</div>
<!-- 关联文件 -->
<div v-else class="flex flex-nowrap">
<MsButton type="button" status="primary" class="!mr-[4px]" @click="cancelAssociated(item)">
{{ t('featureTest.featureCase.cancelLink') }}
</MsButton>
<MsButton type="button" status="primary" class="!mr-[4px]" @click="downloadFile(item)">
{{ t('featureTest.featureCase.download') }}
</MsButton>
</div>
</template>
</MsFileList>
</div>
<!-- 文件列表结束 -->
</div>
<!-- 自定义字段开始 -->
<div class="preview-right px-4">
<div>
<a-skeleton v-if="isLoading" :loading="isLoading" :animation="true">
<a-space direction="vertical" class="w-full" size="large">
<a-skeleton-line :rows="rowLength" :line-height="30" :line-spacing="30" />
</a-space>
</a-skeleton>
<a-form v-else ref="formRef" class="rounded-[4px]" :model="form" layout="vertical">
<a-form-item
field="moduleId"
asterisk-position="end"
:label="t('system.orgTemplate.modules')"
:rules="[{ required: true, message: t('system.orgTemplate.moduleRuleTip') }]"
>
<a-tree-select
v-model="form.moduleId"
:allow-search="true"
:data="caseTree"
:field-names="{
title: 'name',
key: 'id',
children: 'children',
}"
:tree-props="{
virtualListProps: {
height: 200,
},
}"
></a-tree-select>
</a-form-item>
<MsFormCreate
v-if="formRules.length"
ref="formCreateRef"
v-model:api="fApi"
:form-rule="formRules"
:form-create-key="FormCreateKeyEnum.CASE_MANAGEMENT_FIELD"
/>
<a-form-item field="tags" :label="t('system.orgTemplate.tags')">
<a-input-tag v-model="form.tags" :placeholder="t('formCreate.PleaseEnter')" allow-clear />
</a-form-item>
</a-form>
</div>
</div>
<!-- 自定义字段结束 -->
</div>
<div class=" ">
<MsUpload
v-model:file-list="fileList"
accept="none"
:auto-upload="false"
:sub-text="acceptType === 'jar' ? '' : t('project.fileManagement.normalFileSubText', { size: 50 })"
multiple
draggable
size-unit="MB"
:max-size="50"
:is-all-screen="true"
class="mb-[16px]"
@change="handleChange"
/>
</div>
<AssociatedFileDrawer v-model:visible="showDrawer" @save="saveSelectAssociatedFile" />
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { useRoute } from 'vue-router';
import { FormInstance } from '@arco-design/web-vue';
import MsButton from '@/components/pure/ms-button/index.vue';
import MsFormCreate from '@/components/pure/ms-form-create/form-create.vue';
import type { FormItem } from '@/components/pure/ms-form-create/types';
import MsRichText from '@/components/pure/ms-rich-text/MsRichText.vue';
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
import { MsTableColumn } from '@/components/pure/ms-table/type';
import useTable from '@/components/pure/ms-table/useTable';
import MsTableMoreAction from '@/components/pure/ms-table-more-action/index.vue';
import { ActionsItem } from '@/components/pure/ms-table-more-action/types';
import MsFileList from '@/components/pure/ms-upload/fileList.vue';
import MsUpload from '@/components/pure/ms-upload/index.vue';
import type { MsFileItem } from '@/components/pure/ms-upload/types';
import AssociatedFileDrawer from './associatedFileDrawer.vue';
import { getCaseDefaultFields, getCaseDetail } from '@/api/modules/case-management/featureCase';
import { getProjectFieldList } from '@/api/modules/setting/template';
import { useI18n } from '@/hooks/useI18n';
import useAppStore from '@/store/modules/app';
import useFeatureCaseStore from '@/store/modules/case/featureCase';
import useFormCreateStore from '@/store/modules/form-create/form-create';
import { getGenerateId } from '@/utils';
import type { AssociatedList, CreateCase, StepList } from '@/models/caseManagement/featureCase';
import type { CustomField, DefinedFieldItem } from '@/models/setting/template';
import { FormCreateKeyEnum } from '@/enums/formCreateEnum';
import { TableKeyEnum } from '@/enums/tableEnum';
import {
getCustomDetailFields,
getTotalFieldOptionList,
} from '@/views/setting/organization/template/components/fieldSetting';
const { t } = useI18n();
const route = useRoute();
const appStore = useAppStore();
const formCreateStore = useFormCreateStore();
const currentProjectId = computed(() => appStore.currentProjectId);
const props = defineProps<{
formModeValue: Record<string, any>; //
}>();
const emit = defineEmits(['update:formModeValue', 'changeFile']);
const acceptType = ref('none'); // -
const templateFieldColumns: MsTableColumn = [
{
title: 'system.orgTemplate.numberIndex',
dataIndex: 'index',
slotName: 'index',
width: 100,
showDrag: false,
showInTable: true,
},
{
title: 'system.orgTemplate.useCaseStep',
slotName: 'caseStep',
dataIndex: 'caseStep',
showDrag: true,
showInTable: true,
},
{
title: 'system.orgTemplate.expectedResult',
dataIndex: 'expectedResult',
slotName: 'expectedResult',
showDrag: true,
showInTable: true,
},
{
title: 'system.orgTemplate.operation',
slotName: 'operation',
fixed: 'right',
width: 200,
showInTable: true,
showDrag: false,
},
];
const { propsRes, propsEvent, setProps } = useTable(undefined, {
tableKey: TableKeyEnum.CASE_MANAGEMENT_DETAIL_TABLE,
columns: templateFieldColumns,
scroll: { x: '800px' },
selectable: false,
noDisable: true,
size: 'default',
showSetting: false,
showPagination: false,
enableDrag: true,
});
const moreActions: ActionsItem[] = [
{
label: 'featureTest.featureCase.copyStep',
eventTag: 'copyStep',
},
{
label: 'featureTest.featureCase.InsertStepsBefore',
eventTag: 'InsertStepsBefore',
},
{
label: 'featureTest.featureCase.afterInsertingSteps',
eventTag: 'afterInsertingSteps',
},
{
isDivider: true,
},
{
label: 'common.delete',
danger: true,
eventTag: 'delete',
},
];
const formRef = ref<FormInstance>();
const caseFormRef = ref<FormInstance>();
//
const selectData = ref<DefinedFieldItem[]>([]);
//
const stepData = ref<StepList[]>([
{
id: getGenerateId(),
step: '',
expected: '',
showStep: false,
showExpected: false,
},
]);
const featureCaseStore = useFeatureCaseStore();
const modelId = computed(() => featureCaseStore.moduleId[0]);
const caseTree = computed(() => featureCaseStore.caseTree);
const initForm: CreateCase = {
projectId: currentProjectId.value,
templateId: '',
name: '',
prerequisite: '',
caseEditType: 'STEP',
steps: '',
textDescription: '',
expectedResult: '',
description: '',
publicCase: false,
moduleId: modelId.value,
versionId: '',
tags: [],
customFields: [],
relateFileMetaIds: [],
};
const form = ref<CreateCase>({ ...initForm });
watch(
() => stepData.value,
() => {
const res = stepData.value.map((item, index) => {
return {
num: index,
desc: item.step,
result: item.expected,
};
});
form.value.steps = JSON.stringify(res);
},
{ deep: true }
);
//
const params = ref<Record<string, any>>({
request: {},
fileList: [], //
});
//
function getSelectTypeClass(type: string) {
return form.value.caseEditType === type ? ['bg-[rgb(var(--primary-1))]', '!text-[rgb(var(--primary-5))]'] : [];
}
//
const handleSelectType = (value: string | number | Record<string, any> | undefined) => {
form.value.caseEditType = value as string;
};
//
const addStep = () => {
stepData.value.push({
id: getGenerateId(),
step: '',
expected: '',
showStep: false,
showExpected: false,
});
};
//
function copyStep(record: StepList) {
stepData.value.push({
...record,
id: getGenerateId(),
});
}
//
function deleteStep(record: StepList) {
stepData.value = stepData.value.filter((item: any) => item.id !== record.id);
}
//
function insertStepsBefore(record: StepList) {
const index = stepData.value.map((item: any) => item.id).indexOf(record.id);
const insertItem = {
id: getGenerateId(),
step: '',
expected: '',
showStep: false,
showExpected: false,
};
stepData.value.splice(index, 0, insertItem);
}
//
function afterInsertingSteps(record: StepList) {
const index = stepData.value.map((item: any) => item.id).indexOf(record.id);
const insertItem = {
id: getGenerateId(),
step: '',
expected: '',
showStep: false,
showExpected: false,
};
stepData.value.splice(index + 1, 0, insertItem);
}
//
function edit(record: StepList, type: string) {
if (type === 'step') {
record.showStep = true;
} else {
record.showExpected = true;
}
}
//
function blurHandler(record: StepList, type: string) {
if (type === 'step') {
record.showStep = false;
} else {
record.showExpected = false;
}
}
//
const handleMoreActionSelect = (item: ActionsItem, record: StepList) => {
switch (item.eventTag) {
case 'copyStep':
copyStep(record);
break;
case 'InsertStepsBefore':
insertStepsBefore(record);
break;
case 'afterInsertingSteps':
afterInsertingSteps(record);
break;
default:
deleteStep(record);
break;
}
};
//
const totalTemplateField = ref<DefinedFieldItem[]>([]);
const isLoading = ref<boolean>(true);
//
async function getAllCaseFields() {
try {
totalTemplateField.value = await getProjectFieldList({ scopedId: currentProjectId.value, scene: 'FUNCTIONAL' });
totalTemplateField.value = getTotalFieldOptionList(totalTemplateField.value as DefinedFieldItem[]);
} catch (error) {
console.log(error);
}
}
//
async function initDefaultFields() {
try {
isLoading.value = true;
await getAllCaseFields();
const res = await getCaseDefaultFields(currentProjectId.value);
const { customFields, id } = res;
form.value.templateId = id;
selectData.value = getCustomDetailFields(totalTemplateField.value as DefinedFieldItem[], customFields);
isLoading.value = false;
} catch (error) {
console.log(error);
}
}
const fileList = ref<MsFileItem[]>([]);
function beforeUpload() {
return Promise.resolve(true);
}
//
function convertToFile(fileInfo: AssociatedList): MsFileItem {
const fileName = fileInfo.fileType ? `${fileInfo.name}.${fileInfo.fileType || ''}` : `${fileInfo.name}`;
const type = fileName.split('.')[1];
const file = new File([new Blob()], `${fileName}`, {
type: `application/${type}`,
});
Object.defineProperty(file, 'size', { value: fileInfo.size });
return {
enable: fileInfo.enable || false,
file,
name: fileName,
percent: 0,
status: 'done',
uid: fileInfo.id,
url: `http://172.16.200.18:8081/${fileInfo.filePath || ''}`,
local: fileInfo.local,
};
}
//
function saveSelectAssociatedFile(fileData: AssociatedList[]) {
const fileResultList = fileData.map((fileInfo) => convertToFile(fileInfo));
fileList.value.push(...fileResultList);
}
const title = ref('');
const isEdit = computed(() => !!route.query.id);
const attachmentsList = ref([]);
// localitem
const oldLocalFileList = computed(() => {
return attachmentsList.value.filter((item: any) => item.local);
});
//
const currentOldLocalFileList = computed(() => {
return fileList.value.filter((item) => item.local && item.status !== 'init').map((item: any) => item.uid);
});
// id
const associateFileIds = computed(() => {
return attachmentsList.value.filter((item: any) => !item.local).map((item: any) => item.id);
});
// list
const currentAlreadyAssociateFileList = computed(() => {
return fileList.value
.filter((item) => !item.local && !associateFileIds.value.includes(item.uid))
.map((item: any) => item.uid);
});
// ID
const newAssociateFileListIds = computed(() => {
return fileList.value
.filter((item: any) => !item.local && !associateFileIds.value.includes(item.uid))
.map((item: any) => item.uid);
});
// id
const deleteFileMetaIds = computed(() => {
return oldLocalFileList.value
.filter((item: any) => !currentOldLocalFileList.value.includes(item.id))
.map((item: any) => item.id);
});
// id
const unLinkFilesIds = computed(() => {
return associateFileIds.value.filter((id: string) => !currentAlreadyAssociateFileList.value.includes(id));
});
//
function getDetailData(detailResult: CreateCase) {
const { customFields, attachments, steps, tags } = detailResult;
form.value = {
...detailResult,
tags: JSON.parse(tags as string),
};
//
selectData.value = getCustomDetailFields(
totalTemplateField.value as DefinedFieldItem[],
customFields as CustomField[]
);
//
if (steps) {
stepData.value = JSON.parse(steps).map((item: any) => {
return {
step: item.desc,
expected: item.result,
};
});
}
attachmentsList.value = attachments;
//
fileList.value = attachments
.map((fileInfo: any) => {
return {
...fileInfo,
name: fileInfo.fileName,
};
})
.map((fileInfo: any) => {
return convertToFile(fileInfo);
});
// id
}
//
async function getCaseInfo() {
try {
isLoading.value = true;
await getAllCaseFields();
const detailResult = await getCaseDetail(route.query.id as string);
getDetailData(detailResult);
} catch (error) {
console.log(error);
} finally {
isLoading.value = false;
}
}
watchEffect(() => {
if (isEdit.value) {
title.value = t('featureTest.featureCase.updateCase');
//
getCaseInfo();
} else {
title.value = t('featureTest.featureCase.creatingCase');
initDefaultFields();
}
});
//
function getFilesParams() {
form.value.deleteFileMetaIds = deleteFileMetaIds.value;
form.value.unLinkFilesIds = unLinkFilesIds.value;
params.value.fileList = fileList.value.filter((item: any) => item.status === 'init');
form.value.relateFileMetaIds = newAssociateFileListIds.value;
}
//
watch(
() => fileList.value,
(val) => {
if (val) {
form.value.relateFileMetaIds = fileList.value.filter((item) => !item.local).map((item) => item.uid);
params.value.fileList = fileList.value.filter((item) => item.local && item.status === 'init');
if (isEdit.value) {
getFilesParams();
}
}
},
{ deep: true }
);
//
watch(
() => form.value,
(val) => {
if (val) {
if (val) {
params.value.request = { ...form.value };
emit('update:formModeValue', params.value);
featureCaseStore.setModuleId([form.value.moduleId], []);
}
}
},
{ deep: true }
);
//
watch(
() => props.formModeValue,
() => {
// params
params.value = {
...props.formModeValue,
};
},
{ deep: true }
);
const showDrawer = ref<boolean>(false);
function associatedFile() {
showDrawer.value = true;
}
watch(
() => stepData.value,
(val) => {
setProps({ data: val });
},
{ deep: true }
);
function handleChange(_fileList: MsFileItem[], fileItem: MsFileItem) {
fileList.value = _fileList.map((e) => {
return {
...e,
enable: true, //
local: true, //
};
});
}
const rowLength = ref<number>(0);
const formRuleField = ref<FormItem[][]>([]);
const formRules = ref<FormItem[]>([]);
const fApi = ref<any>({});
const formRuleList = computed(() => formCreateStore.formCreateRuleMap.get(FormCreateKeyEnum.CASE_MANAGEMENT_FIELD));
//
const getFormRules = () => {
formRuleField.value = [];
formRules.value = [];
if (selectData.value && selectData.value.length) {
selectData.value.forEach((item: any) => {
const currentFormItem = item.formRules?.map((rule: any) => {
let optionsItem = [];
if (rule.options && rule.options.length) {
optionsItem = rule.options.map((opt: any) => {
return {
text: opt.label,
value: opt.value,
};
});
}
return {
type: item.type,
name: item.id,
label: item.name,
value: isEdit.value ? JSON.parse(rule.value) : rule.value,
options: optionsItem,
required: item.required,
props: {
modelValue: isEdit.value ? JSON.parse(rule.value) : rule.value,
options: optionsItem,
},
};
});
formRuleField.value.push(currentFormItem as FormItem[]);
});
formRules.value = formRuleField.value.flatMap((item) => item);
}
};
//
watch(
() => formRuleList.value,
() => {
const customFieldsMaps: Record<string, any> = {};
formRuleList.value?.forEach((item) => {
customFieldsMaps[item.field as string] = item.value;
});
form.value.customFields = customFieldsMaps as Record<string, any>;
},
{ deep: true }
);
// formCreate
watch(
() => selectData.value,
() => {
getFormRules();
rowLength.value = formRules.value.length + 2;
},
{ deep: true }
);
onMounted(() => {
setProps({ data: stepData.value });
});
onBeforeUnmount(() => {
formRules.value = [];
formRuleField.value = [];
});
//
function transferFile(item: any) {}
//
function downloadFile(item: any) {}
//
function cancelAssociated(item: any) {}
defineExpose({
caseFormRef,
formRef,
fApi,
});
</script>
<style scoped lang="less">
.wrapper-preview {
display: flex;
.preview-left {
width: calc(100% - 396px);
border-right: 1px solid var(--color-text-n8);
.changeType {
padding: 2px 4px;
border-radius: 4px;
color: var(--color-text-4);
:deep(.arco-icon-down) {
font-size: 14px;
}
&:hover {
color: rgb(var(--primary-5));
background: rgb(var(--primary-1));
cursor: pointer;
}
}
}
.preview-right {
width: 428px;
}
}
.circle {
width: 16px;
height: 16px;
line-height: 16px;
border-radius: 50%;
text-align: center;
color: var(--color-text-4);
background: var(--color-text-n8);
}
</style>

View File

@ -0,0 +1,366 @@
<template>
<a-input-search
v-model:model-value="groupKeyword"
:placeholder="t('featureTest.featureCase.searchTip')"
allow-clear
class="mb-[16px]"
></a-input-search>
<a-spin class="w-full" :style="{ height: `calc(100vh - 356px)` }" :loading="loading">
<MsTree
v-model:focus-node-key="focusNodeKey"
:selected-keys="props.selectedKeys"
:data="caseTree"
:keyword="groupKeyword"
:node-more-actions="caseMoreActions"
:expand-all="props.isExpandAll"
:empty-text="t('featureTest.featureCase.caseEmptyContent')"
draggable
:virtual-list-props="virtualListProps"
block-node
:field-names="{
title: 'name',
key: 'id',
children: 'children',
count: 'count',
}"
title-tooltip-position="left"
@select="caseNodeSelect"
@more-action-select="handleCaseMoreSelect"
@more-actions-close="moreActionsClose"
@drop="handleDrag"
>
<template #title="nodeData">
<span class="text-[var(--color-text-1)]">{{ nodeData.name }}</span>
<span class="ml-[4px] text-[var(--color-text-4)]">({{ nodeData.count || 0 }})</span>
</template>
<template v-if="!props.isModal" #extra="nodeData">
<MsPopConfirm
:visible="addSubVisible"
:is-delete="false"
:all-names="[]"
:title="t('featureTest.featureCase.addSubModule')"
:ok-text="t('common.confirm')"
:field-config="{
placeholder: t('featureTest.featureCase.addGroupTip'),
}"
:loading="confirmLoading"
@confirm="addSubModule"
@cancel="resetFocusNodeKey"
>
<MsButton type="icon" size="mini" class="ms-tree-node-extra__btn !mr-0" @click="setFocusKey(nodeData)">
<MsIcon type="icon-icon_add_outlined" size="14" class="text-[var(--color-text-4)]" />
</MsButton>
</MsPopConfirm>
<MsPopConfirm
:title="t('featureTest.featureCase.rename')"
:all-names="[]"
:is-delete="false"
:ok-text="t('common.confirm')"
:field-config="{ field: renameCaseName }"
:loading="confirmLoading"
@confirm="updateNameModule"
@cancel="resetFocusNodeKey"
>
<span :id="`renameSpan${nodeData.id}`" class="relative"></span>
</MsPopConfirm>
</template>
</MsTree>
</a-spin>
</template>
<script setup lang="ts">
import { computed, ref, watch } from 'vue';
import { Message } from '@arco-design/web-vue';
import MsButton from '@/components/pure/ms-button/index.vue';
import MsPopConfirm from '@/components/pure/ms-popconfirm/index.vue';
import type { ActionsItem } from '@/components/pure/ms-table-more-action/types';
import MsTree from '@/components/business/ms-tree/index.vue';
import type { MsTreeNodeData } from '@/components/business/ms-tree/types';
import {
createCaseModuleTree,
deleteCaseModuleTree,
getCaseModuleTree,
moveCaseModuleTree,
updateCaseModuleTree,
} from '@/api/modules/case-management/featureCase';
import { useI18n } from '@/hooks/useI18n';
import useModal from '@/hooks/useModal';
import useAppStore from '@/store/modules/app';
import useFeatureCaseStore from '@/store/modules/case/featureCase';
import { mapTree } from '@/utils';
import type { CreateOrUpdateModule, UpdateModule } from '@/models/caseManagement/featureCase';
import { ModuleTreeNode } from '@/models/projectManagement/file';
const { t } = useI18n();
const { openModal } = useModal();
const appStore = useAppStore();
const focusNodeKey = ref<string>('');
const loading = ref(false);
const props = defineProps<{
isModal?: boolean; //
activeFolder?: string; // 使
selectedKeys?: Array<string | number>; // key
isExpandAll: boolean; //
allNames?: string[]; // name
modulesCount?: Record<string, number>; //
}>();
const emits = defineEmits(['update:selectedKeys', 'caseNodeSelect', 'init']);
const currentProjectId = computed(() => appStore.currentProjectId);
const groupKeyword = ref<string>('');
const caseTree = ref<ModuleTreeNode[]>([]);
const setFocusKey = (node: MsTreeNodeData) => {
focusNodeKey.value = node.id || '';
};
const caseMoreActions: ActionsItem[] = [
{
label: 'featureTest.featureCase.rename',
eventTag: 'rename',
},
{
label: 'featureTest.featureCase.delete',
eventTag: 'delete',
danger: true,
},
];
const selectedNodeKeys = ref(props.selectedKeys || []);
watch(
() => props.selectedKeys,
(val) => {
selectedNodeKeys.value = val || [];
}
);
watch(
() => selectedNodeKeys.value,
(val) => {
emits('update:selectedKeys', val);
}
);
const featureCaseStore = useFeatureCaseStore();
/**
* 初始化模块树
* @param isSetDefaultKey 是否设置第一个节点为选中节点
*/
async function initModules(isSetDefaultKey = false) {
try {
loading.value = true;
const res = await getCaseModuleTree(currentProjectId.value);
caseTree.value = mapTree<ModuleTreeNode>(res, (e) => {
return {
...e,
hideMoreAction: e.id === 'root',
draggable: e.id !== 'root' && !props.isModal,
disabled: e.id === props.activeFolder && props.isModal,
count: props.modulesCount?.[e.id] || 0,
};
});
featureCaseStore.setModulesTree(caseTree.value);
if (isSetDefaultKey) {
selectedNodeKeys.value = [caseTree.value[0].id];
}
emits(
'init',
caseTree.value.map((e) => e.name)
);
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
} finally {
loading.value = false;
}
}
//
const deleteHandler = (node: MsTreeNodeData) => {
openModal({
type: 'error',
title: t('featureTest.featureCase.deleteTipTitle', { name: node.name }),
content: t('featureTest.featureCase.deleteCaseTipContent'),
okText: t('featureTest.featureCase.deleteConfirm'),
okButtonProps: {
status: 'danger',
},
maskClosable: false,
onBeforeOk: async () => {
try {
await deleteCaseModuleTree(node.id);
Message.success(t('featureTest.featureCase.deleteSuccess'));
initModules(selectedNodeKeys.value[0] === node.id);
} catch (error) {
console.log(error);
}
},
hideCancel: false,
});
};
const renamePopVisible = ref(false);
const renameCaseName = ref('');
function resetFocusNodeKey() {
focusNodeKey.value = '';
renamePopVisible.value = false;
renameCaseName.value = '';
}
//
const caseNodeSelect = (selectedKeys: (string | number)[], node: MsTreeNodeData) => {
const offspringIds: string[] = [];
mapTree(node.children || [], (e) => {
offspringIds.push(e.id);
return e;
});
emits('caseNodeSelect', selectedKeys, offspringIds);
};
//
const handleCaseMoreSelect = (item: ActionsItem, node: MsTreeNodeData) => {
switch (item.eventTag) {
case 'delete':
deleteHandler(node);
resetFocusNodeKey();
break;
case 'rename':
renameCaseName.value = node.name || '';
renamePopVisible.value = true;
document.querySelector(`#renameSpan${node.id}`)?.dispatchEvent(new Event('click'));
break;
default:
break;
}
};
/**
* 处理文件夹树节点拖拽事件
* @param tree 树数据
* @param dragNode 拖拽节点
* @param dropNode 释放节点
* @param dropPosition 释放位置
*/
async function handleDrag(
tree: MsTreeNodeData[],
dragNode: MsTreeNodeData,
dropNode: MsTreeNodeData,
dropPosition: number
) {
try {
loading.value = true;
await moveCaseModuleTree({
dragNodeId: dragNode.id as string,
dropNodeId: dropNode.id || '',
dropPosition,
});
Message.success(t('featureTest.featureCase.moduleMoveSuccess'));
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
} finally {
loading.value = false;
initModules();
}
}
const moreActionsClose = () => {
if (!renamePopVisible.value) {
resetFocusNodeKey();
}
};
const addSubVisible = ref(false);
const confirmLoading = ref(false);
//
async function addSubModule(formValue?: { field: string }, cancel?: () => void) {
try {
confirmLoading.value = true;
const params: CreateOrUpdateModule = {
projectId: currentProjectId.value,
name: formValue?.field as string,
parentId: focusNodeKey.value,
};
await createCaseModuleTree(params);
Message.success(t('featureTest.featureCase.addSuccess'));
if (cancel) {
cancel();
}
initModules();
} catch (error) {
console.log(error);
} finally {
confirmLoading.value = false;
}
}
//
async function updateNameModule(formValue?: { field: string }, cancel?: () => void) {
try {
confirmLoading.value = true;
const params: UpdateModule = {
id: focusNodeKey.value,
name: formValue?.field as string,
};
await updateCaseModuleTree(params);
Message.success(t('featureTest.featureCase.addSuccess'));
if (cancel) {
cancel();
}
initModules();
} catch (error) {
console.log(error);
} finally {
confirmLoading.value = false;
}
}
const virtualListProps = computed(() => {
return {
height: 'calc(100vh - 360px)',
};
});
watch(
() => props.activeFolder,
(val) => {
if (val === 'all') {
initModules();
}
}
);
/**
* 初始化模块文件数量
*/
watch(
() => props.modulesCount,
(obj) => {
caseTree.value = mapTree<ModuleTreeNode>(caseTree.value, (node) => {
return {
...node,
count: obj?.[node.id] || 0,
};
});
}
);
onBeforeMount(() => {
initModules();
});
defineExpose({
initModules,
});
</script>
<style scoped lang="less"></style>

View File

@ -0,0 +1,208 @@
<template>
<MsDrawer
v-model:visible="showDrawer"
:mask="false"
:title="t('featureTest.featureCase.associatedFile')"
:ok-text="t('featureTest.featureCase.associated')"
:ok-loading="drawerLoading"
:width="480"
unmount-on-close
:show-continue="false"
@confirm="handleDrawerConfirm"
@cancel="handleDrawerCancel"
>
<div class="header mb-6 flex justify-between">
<span class="font-medium">{{ t('featureTest.featureCase.SelectExportRange') }}</span>
<span class="text-[rgb(var(--primary-5))]">{{ t('featureTest.featureCase.clear') }}</span>
</div>
<div>
<a-checkbox class="mb-4" :model-value="checkedAll" :indeterminate="indeterminate" @change="handleChangeAll"
><div class="flex items-center">
<span class="mr-1">{{ t('featureTest.featureCase.baseField') }}</span
><span
><icon-up
v-if="foldBaseFields"
class="text-[12px] text-[var(--color-text-brand)]"
@click="toggle('base')" /><icon-right
v-else
class="text-[12px] text-[var(--color-text-brand)]"
@click="toggle('base')"
/></span>
</div>
</a-checkbox>
</div>
<a-checkbox-group v-if="foldBaseFields" v-model="baseFields" class="checkboxContainer" @change="handleChange">
<div class="item checkbox">
<a-checkbox value="1">Option 1</a-checkbox>
</div>
<div class="item checkbox">
<a-checkbox value="1">Option 1</a-checkbox>
</div>
<div class="item checkbox">
<a-checkbox value="1">Option 1</a-checkbox>
</div>
<div class="item checkbox">
<a-checkbox value="1">Option 1</a-checkbox>
</div>
<div class="item checkbox">
<a-checkbox value="1">Option 1</a-checkbox>
</div>
</a-checkbox-group>
<!-- 自定义字段 -->
<div>
<a-checkbox class="mb-4" :model-value="checkedAll" :indeterminate="indeterminate" @change="handleChangeAll"
><div class="flex items-center">
<span class="mr-1">{{ t('featureTest.featureCase.customField') }}</span
><span
><icon-up
v-if="foldCustomFields"
class="text-[12px] text-[var(--color-text-brand)]"
@click="toggle('custom')" /><icon-right
v-else
class="text-[12px] text-[var(--color-text-brand)]"
@click="toggle('custom')"
/></span>
</div>
</a-checkbox>
</div>
<a-checkbox-group v-if="foldCustomFields" v-model="customFields" class="checkboxContainer" @change="handleChange">
<div class="item checkbox">
<a-checkbox value="1">Option 1</a-checkbox>
</div>
<div class="item checkbox">
<a-checkbox value="1">Option 1</a-checkbox>
</div>
<div class="item checkbox">
<a-checkbox value="1">Option 1</a-checkbox>
</div>
<div class="item checkbox">
<a-checkbox value="1">Option 1</a-checkbox>
</div>
<div class="item checkbox">
<a-checkbox value="1">Option 1</a-checkbox>
</div>
</a-checkbox-group>
<!-- 其他字段 -->
<div>
<a-checkbox class="mb-4" :model-value="checkedAll" :indeterminate="indeterminate" @change="handleChangeAll"
><div class="flex items-center">
<span class="mr-1 flex items-center"
>{{ t('featureTest.featureCase.otherFields') }}<span></span>
<a-tooltip
:content="t('featureTest.featureCase.otherFieldsToolTip')"
position="top"
:mouse-enter-delay="500"
mini
>
<icon-question-circle
class="mx-1 text-[16px] text-[var(--color-text-brand)] hover:text-[rgb(var(--primary-5))]" /></a-tooltip
><icon-up
v-if="foldCustomFields"
class="text-[12px] text-[var(--color-text-brand)]"
@click="toggle('other')" /><icon-right
v-else
class="text-[12px] text-[var(--color-text-brand)]"
@click="toggle('other')"
/></span>
</div>
</a-checkbox>
</div>
<a-checkbox-group v-if="foldOtherFields" v-model="otherFields" class="checkboxContainer" @change="handleChange">
<div class="item checkbox">
<a-checkbox value="1">Option 1</a-checkbox>
</div>
<div class="item checkbox">
<a-checkbox value="1">Option 1</a-checkbox>
</div>
<div class="item checkbox">
<a-checkbox value="1">Option 1</a-checkbox>
</div>
<div class="item checkbox">
<a-checkbox value="1">Option 1</a-checkbox>
</div>
<div class="item checkbox">
<a-checkbox value="1">Option 1</a-checkbox>
</div>
</a-checkbox-group>
</MsDrawer>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
import { useI18n } from '@/hooks/useI18n';
const { t } = useI18n();
const props = defineProps<{
visible: boolean;
}>();
const emit = defineEmits<{
(e: 'update:visible', val: boolean): void;
}>();
const showDrawer = ref<boolean>(false);
const drawerLoading = ref<boolean>(false);
const checkedAll = ref<boolean>(false);
const indeterminate = ref<boolean>(false);
function handleChangeAll() {}
function handleChange() {}
function handleDrawerConfirm() {}
function handleDrawerCancel() {
showDrawer.value = false;
}
const foldBaseFields = ref<boolean>(true); //
const baseFields = ref<string[]>([]);
const foldCustomFields = ref<boolean>(true);
const customFields = ref<string[]>([]);
const foldOtherFields = ref<boolean>(true);
const otherFields = ref<string[]>([]);
function toggle(foldType: string) {
if (foldType === 'base') {
foldBaseFields.value = !foldBaseFields.value;
} else if (foldType === 'custom') {
foldCustomFields.value = !foldCustomFields.value;
} else {
foldOtherFields.value = !foldOtherFields.value;
}
}
watch(
() => props.visible,
(val) => {
showDrawer.value = val;
}
);
watch(
() => showDrawer.value,
(val) => {
emit('update:visible', val);
}
);
</script>
<style scoped lang="less">
.checkboxContainer {
display: grid;
margin-bottom: 16px;
grid-template-columns: repeat(auto-fit, minmax(116px, 1fr));
grid-gap: 16px;
.checkbox {
width: 90px;
white-space: nowrap;
@apply overflow-hidden text-ellipsis;
}
}
</style>

View File

@ -0,0 +1,706 @@
<template>
<div class="pageWrap">
<MsSplitBox>
<template #left>
<div class="p-[24px]">
<div class="mb-4 flex items-center">
<div class="back" @click="handleBack"><icon-arrow-left /></div>
<div
>{{ t('featureTest.featureCase.recycle')
}}<span class="ml-1 text-[var(--color-text-4)]">({{ recycleModulesCount.all }})</span></div
>
</div>
<a-divider class="my-[4px]" />
<div class="feature-case">
<div class="case h-[38px]">
<div class="flex items-center" :class="getActiveClass('all')" @click="setActiveFolder('all')">
<MsIcon type="icon-icon_folder_filled1" class="folder-icon" />
<div class="folder-name mx-[4px]">{{ t('featureTest.featureCase.allCase') }}</div>
<div class="folder-count">({{ allCaseCount }})</div></div
>
<div class="ml-auto flex items-center">
<a-tooltip
:content="
isExpandAll ? t('project.fileManagement.collapseAll') : t('project.fileManagement.expandAll')
"
>
<MsButton type="icon" status="secondary" class="!mr-0 p-[4px]" @click="expandHandler">
<MsIcon :type="isExpandAll ? 'icon-icon_folder_collapse1' : 'icon-icon_folder_expansion1'" />
</MsButton>
</a-tooltip>
</div>
</div>
<a-input-search
v-model:model-value="groupKeyword"
:placeholder="t('featureTest.featureCase.searchTip')"
allow-clear
class="mb-[16px]"
></a-input-search>
<a-spin class="w-full" :loading="loading">
<MsTree
v-model:focus-node-key="focusNodeKey"
:selected-keys="selectedKeys"
:data="caseTree"
:keyword="groupKeyword"
:expand-all="isExpandAll"
:empty-text="t('featureTest.featureCase.caseEmptyContent')"
draggable
:virtual-list-props="virtualListProps"
block-node
:field-names="{
title: 'name',
key: 'id',
children: 'children',
count: 'count',
}"
title-tooltip-position="left"
@select="caseNodeSelect"
>
<template #title="nodeData">
<div @click="setFocusKey(nodeData)">
<span class="text-[var(--color-text-1)]">{{ nodeData.name }}</span>
<span class="ml-[4px] text-[var(--color-text-4)]">({{ nodeData.count || 0 }})</span>
</div>
</template>
</MsTree>
</a-spin>
</div>
</div>
</template>
<template #right>
<div class="p-[24px]">
<div class="page-header mb-4 h-[34px]">
<div class="text-[var(--color-text-1)]"
>{{ t('featureTest.featureCase.allCase') }}
<span class="text-[var(--color-text-4)]"> ({{ recycleModulesCount.all }})</span></div
>
<div class="flex w-[80%] items-center justify-end">
<a-select class="w-[240px]" :placeholder="t('featureTest.featureCase.versionPlaceholder')">
<a-option v-for="version of versionOptions" :key="version.id" :value="version.id">{{
version.name
}}</a-option>
</a-select>
<a-input-search
v-model="keyword"
:placeholder="t('featureTest.featureCase.searchByNameAndId')"
allow-clear
class="mx-[8px] w-[240px]"
@search="searchList"
@press-enter="searchList"
></a-input-search>
</div>
</div>
<ms-base-table
v-bind="propsRes"
:action-config="tableBatchActions"
@selected-change="handleTableSelect"
v-on="propsEvent"
@batch-action="handleTableBatch"
>
<template #reviewStatus="{ record }">
<MsIcon
:type="getStatusText(record.reviewStatus)?.iconType || ''"
class="mr-1"
:class="[getReviewStatusClass(record.reviewStatus)]"
></MsIcon>
<span>{{ getStatusText(record.reviewStatus)?.statusType || '' }} </span>
</template>
<template #lastExecuteResult="{ record }">
<MsIcon
:type="getStatusText(record.lastExecuteResult)?.iconType || ''"
class="mr-1"
:class="[getReviewStatusClass(record.lastExecuteResult)]"
></MsIcon>
<span>{{ getStatusText(record.lastExecuteResult)?.statusType || '' }}</span>
</template>
<template #moduleId="{ record }">
<a-tooltip :content="getModules(record.moduleId)" position="top">
<span class="one-line-text inline-block">{{ getModules(record.moduleId) }}</span>
</a-tooltip>
</template>
<template #operation="{ record }">
<MsButton @click="recoverCase(record.id)">{{ t('featureTest.featureCase.batchRecover') }}</MsButton>
<MsButton class="!mr-0" @click="handleBatchCleanOut(record)">{{
t('featureTest.featureCase.batchCleanOut')
}}</MsButton>
</template>
</ms-base-table>
</div>
</template>
</MsSplitBox>
</div>
</template>
<script setup lang="ts">
/*
* @description 功能用例-回收站
*/
import { computed, ref } from 'vue';
import { useRouter } from 'vue-router';
import { Message } from '@arco-design/web-vue';
import MsButton from '@/components/pure/ms-button/index.vue';
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
import MsSplitBox from '@/components/pure/ms-split-box/index.vue';
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
import type { BatchActionParams, BatchActionQueryParams, MsTableColumn } from '@/components/pure/ms-table/type';
import useTable from '@/components/pure/ms-table/useTable';
import MsTree from '@/components/business/ms-tree/index.vue';
import type { MsTreeNodeData } from '@/components/business/ms-tree/types';
import {
batchDeleteRecycleCase,
deleteRecycleCaseList,
getRecycleListRequest,
getTrashCaseModuleTree,
recoverRecycleCase,
restoreCaseList,
} from '@/api/modules/case-management/featureCase';
import { useI18n } from '@/hooks/useI18n';
import useModal from '@/hooks/useModal';
import { useAppStore, useTableStore } from '@/store';
import useFeatureCaseStore from '@/store/modules/case/featureCase';
import { characterLimit, findNodePathByKey, mapTree } from '@/utils';
import type {
BatchMoveOrCopyType,
CaseManagementTable,
CaseModuleQueryParams,
} from '@/models/caseManagement/featureCase';
import type { TableQueryParams } from '@/models/common';
import { ModuleTreeNode } from '@/models/projectManagement/file';
import { StatusType } from '@/enums/caseEnum';
import { FeatureTestRouteEnum } from '@/enums/routeEnum';
import { TableKeyEnum } from '@/enums/tableEnum';
import { getReviewStatusClass, getStatusText } from './utils';
import debounce from 'lodash-es/debounce';
const tableStore = useTableStore();
const featureCaseStore = useFeatureCaseStore();
const { t } = useI18n();
const router = useRouter();
const { openModal } = useModal();
const allCaseCount = ref<number>(0);
const activeCaseType = ref<'folder' | 'module'>('folder'); //
const appStore = useAppStore();
const currentProjectId = computed(() => appStore.currentProjectId);
const versionOptions = ref([
{
id: '1001',
name: 'v_1.0',
},
]);
const { propsRes, propsEvent, loadList, setLoadListParams, resetSelector } = useTable(
getRecycleListRequest,
{
tableKey: TableKeyEnum.FILE_MANAGEMENT_CASE_RECYCLE,
scroll: { x: 3200 },
selectable: true,
showSetting: true,
heightUsed: 340,
enableDrag: true,
},
(record) => ({
...record,
tags: (JSON.parse(record.tags) || []).map((item: string, i: number) => {
return {
id: `${record.id}-${i}`,
name: item,
};
}),
})
);
const columns: MsTableColumn = [
{
title: 'featureTest.featureCase.tableColumnID',
dataIndex: 'id',
width: 200,
showInTable: true,
sortable: {
sortDirections: ['ascend', 'descend'],
},
showTooltip: true,
ellipsis: true,
showDrag: false,
},
{
title: 'featureTest.featureCase.tableColumnName',
slotName: 'name',
dataIndex: 'name',
showInTable: true,
showTooltip: true,
width: 300,
sortable: {
sortDirections: ['ascend', 'descend'],
},
ellipsis: true,
showDrag: false,
},
{
title: 'featureTest.featureCase.tableColumnLevel',
dataIndex: 'level',
showInTable: true,
width: 200,
showTooltip: true,
ellipsis: true,
showDrag: true,
},
{
title: 'featureTest.featureCase.tableColumnCaseState',
dataIndex: 'caseState',
showInTable: true,
width: 200,
showTooltip: true,
ellipsis: true,
showDrag: true,
},
{
title: 'featureTest.featureCase.tableColumnReviewResult',
dataIndex: 'reviewStatus',
slotName: 'reviewStatus',
showInTable: true,
width: 200,
showDrag: true,
},
{
title: 'featureTest.featureCase.tableColumnExecutionResult',
dataIndex: 'lastExecuteResult',
slotName: 'lastExecuteResult',
showInTable: true,
width: 200,
showDrag: true,
},
{
title: 'featureTest.featureCase.tableColumnVersion',
slotName: 'versionId',
dataIndex: 'versionId',
width: 300,
showTooltip: true,
showInTable: true,
showDrag: true,
},
{
title: 'featureTest.featureCase.tableColumnModule',
slotName: 'moduleId',
showInTable: true,
width: 300,
showDrag: true,
},
{
title: 'featureTest.featureCase.tableColumnTag',
slotName: 'tags',
dataIndex: 'tags',
showInTable: true,
isTag: true,
showDrag: true,
},
{
title: 'featureTest.featureCase.tableColumnCreateUser',
slotName: 'createUser',
dataIndex: 'createUser',
showInTable: true,
showDrag: true,
},
{
title: 'featureTest.featureCase.tableColumnCreateTime',
slotName: 'createTime',
dataIndex: 'createTime',
showInTable: true,
sortable: {
sortDirections: ['ascend', 'descend'],
},
width: 200,
showDrag: true,
},
{
title: 'featureTest.featureCase.tableColumnUpdateUser',
slotName: 'updateUser',
dataIndex: 'updateUser',
showInTable: true,
width: 200,
showDrag: true,
},
{
title: 'featureTest.featureCase.tableColumnUpdateTime',
slotName: 'updateTime',
dataIndex: 'updateTime',
sortable: {
sortDirections: ['ascend', 'descend'],
},
showInTable: true,
width: 200,
showDrag: true,
},
{
title: 'featureTest.featureCase.tableColumnActions',
slotName: 'operation',
dataIndex: 'operation',
fixed: 'right',
width: 140,
showInTable: true,
showDrag: false,
},
];
const tableBatchActions = {
baseAction: [
{
label: 'featureTest.featureCase.batchRecover',
eventTag: 'batchRecover',
},
{
label: 'featureTest.featureCase.batchCleanOut',
eventTag: 'batchCleanOut',
danger: true,
},
],
};
const tableSelected = ref<(string | number)[]>([]);
function handleTableSelect(selectArr: (string | number)[]) {
tableSelected.value = selectArr;
}
const isExpandAll = ref(false);
//
function expandHandler() {
isExpandAll.value = !isExpandAll.value;
}
const activeFolder = ref<string>('all');
//
function getActiveClass(type: string) {
return activeFolder.value === type ? 'folder-text case-active' : 'folder-text';
}
//
function setActiveFolder(type: string) {
activeFolder.value = type;
if (type === 'all') {
activeCaseType.value = 'folder';
}
}
//
const selectedKeys = computed({
get: () => [activeFolder.value],
set: (val) => val,
});
const offspringIds = ref<string[]>([]);
const selectedKeysNode = ref<(string | number)[]>([]);
const focusNodeKey = ref<string>('');
//
const caseNodeSelect = (selectedNodeKeys: (string | number)[] | string[], node: MsTreeNodeData) => {
[activeFolder.value] = selectedNodeKeys as string[];
offspringIds.value = [];
mapTree(node.children || [], (e) => {
offspringIds.value.push(e.id);
return e;
});
focusNodeKey.value = '';
};
const setFocusKey = (node: MsTreeNodeData) => {
focusNodeKey.value = node.id || '';
};
const virtualListProps = computed(() => {
return {
height: 'calc(100vh - 316px)',
};
});
const loading = ref(false);
const caseTree = ref<ModuleTreeNode[]>([]);
const recycleModulesCount = ref<Record<string, number>>({});
const groupKeyword = ref<string>('');
/**
* @param 获取回收站模块
*/
async function getRecycleModules(isSetDefaultKey = false) {
try {
loading.value = true;
const res = await getTrashCaseModuleTree(currentProjectId.value);
caseTree.value = mapTree<ModuleTreeNode>(res, (e) => {
return {
...e,
hideMoreAction: e.id === 'root',
draggable: false,
disabled: false,
count: recycleModulesCount.value?.[e.id] || 0,
};
});
if (isSetDefaultKey) {
selectedKeysNode.value = [caseTree.value[0].id];
}
} catch (error) {
console.log(error);
} finally {
loading.value = false;
}
}
const keyword = ref<string>('');
const searchParams = ref<TableQueryParams>({
projectId: currentProjectId.value,
moduleIds: [],
});
// count
const emitTableParams: CaseModuleQueryParams = {
keyword: keyword.value,
moduleIds: [],
projectId: currentProjectId.value,
current: propsRes.value.msPagination?.current,
pageSize: propsRes.value.msPagination?.pageSize,
};
//
function initRecycleModulesCount() {
featureCaseStore.getRecycleMModulesCountCount(emitTableParams);
}
const batchParams = ref<BatchActionQueryParams>({
selectedIds: [],
selectAll: false,
excludeIds: [],
currentSelectCount: 0,
});
//
function getBatchParams(): BatchMoveOrCopyType {
return {
excludeIds: batchParams.value.excludeIds,
selectAll: batchParams.value.selectAll,
selectIds: batchParams.value.selectedIds,
condition: {
keyword: keyword.value,
},
moduleIds: searchParams.value.moduleIds,
projectId: currentProjectId.value,
};
}
//
async function handleBatchRecover() {
try {
await restoreCaseList(getBatchParams());
Message.success(t('featureTest.featureCase.recoveredSuccessfully'));
loadList();
resetSelector();
initRecycleModulesCount();
} catch (error) {
console.log(error);
}
}
//
async function handleBatchDelete() {
openModal({
type: 'error',
title: t('featureTest.featureCase.batchDelete', { number: batchParams.value.currentSelectCount }),
content: t('featureTest.featureCase.cleanOutDeleteTip'),
okText: t('common.confirmDelete'),
cancelText: t('common.cancel'),
okButtonProps: {
status: 'danger',
},
onBeforeOk: async () => {
try {
await batchDeleteRecycleCase(getBatchParams());
Message.success(t('common.deleteSuccess'));
loadList();
resetSelector();
initRecycleModulesCount();
} catch (error) {
console.log(error);
}
},
hideCancel: false,
});
}
//
function handleTableBatch(event: BatchActionParams, params: BatchActionQueryParams) {
batchParams.value = { ...params };
if (event.eventTag === 'batchRecover') {
handleBatchRecover();
} else {
handleBatchDelete();
}
}
// name
function getModules(moduleIds: string) {
const modules = findNodePathByKey(caseTree.value, moduleIds, undefined, 'id');
if (modules) {
const moduleName = (modules || []).treePath.map((item: any) => item.name);
if (moduleName.length === 1) {
return moduleName[0];
}
return `/${moduleName.join('/')}`;
}
}
//
function getLoadListParams() {
if (activeFolder.value === 'all') {
searchParams.value.moduleIds = [];
} else {
searchParams.value.moduleIds = [activeFolder.value, ...offspringIds.value];
}
setLoadListParams({
...searchParams.value,
keyword: keyword.value,
});
}
//
function initRecycleList() {
getLoadListParams();
loadList();
}
const searchList = debounce(() => {
getLoadListParams();
loadList();
}, 100);
//
async function recoverCase(id: string) {
try {
await recoverRecycleCase(id);
Message.success(t('featureTest.featureCase.recoveredSuccessfully'));
loadList();
resetSelector();
initRecycleModulesCount();
} catch (error) {
console.log(error);
}
}
//
function handleBatchCleanOut(record: CaseManagementTable) {
openModal({
type: 'error',
title: t('featureTest.featureCase.deleteCaseTitle', { name: characterLimit(record.name) }),
content: t('featureTest.featureCase.cleanOutDeleteTip'),
okText: t('common.confirmDelete'),
cancelText: t('common.cancel'),
okButtonProps: {
status: 'danger',
},
onBeforeOk: async () => {
try {
await deleteRecycleCaseList(record.id);
Message.success(t('common.deleteSuccess'));
loadList();
initRecycleModulesCount();
} catch (error) {
console.log(error);
}
},
hideCancel: false,
});
}
//
function handleBack() {
router.push({
name: FeatureTestRouteEnum.FEATURE_TEST_CASE,
});
}
watch(
() => activeFolder.value,
() => {
initRecycleList();
}
);
watch(
() => featureCaseStore.recycleModulesCount,
(val) => {
recycleModulesCount.value = { ...val };
caseTree.value = mapTree<ModuleTreeNode>(caseTree.value, (node) => {
return {
...node,
count: val?.[node.id] || 0,
};
});
}
);
onMounted(() => {
initRecycleList();
getRecycleModules();
initRecycleModulesCount();
});
tableStore.initColumn(TableKeyEnum.FILE_MANAGEMENT_CASE_RECYCLE, columns, 'drawer');
</script>
<style scoped lang="less">
.pageWrap {
min-width: 1000px;
height: calc(100vh - 136px);
border-radius: var(--border-radius-large);
@apply bg-white;
.back {
margin-right: 8px;
width: 20px;
height: 20px;
border: 1px solid #ffffff;
background: linear-gradient(90deg, rgb(var(--primary-9)) 3.36%, #ffffff 100%);
box-shadow: 0 0 7px rgb(15 0 78 / 9%);
.arco-icon {
color: rgb(var(--primary-5));
}
@apply flex cursor-pointer items-center rounded-full;
}
.case {
padding: 8px 4px;
border-radius: var(--border-radius-small);
@apply flex cursor-pointer items-center justify-between;
&:hover {
background-color: rgb(var(--primary-1));
}
.folder-icon {
margin-right: 4px;
color: var(--color-text-4);
}
.folder-name {
color: var(--color-text-1);
}
.folder-count {
margin-left: 4px;
color: var(--color-text-4);
}
.case-active {
.folder-icon,
.folder-name,
.folder-count {
color: rgb(var(--primary-5));
}
}
}
}
.page-header {
@apply flex items-center justify-between;
}
</style>

View File

@ -0,0 +1,93 @@
import { useI18n } from '@/hooks/useI18n';
import { StatusType } from '@/enums/caseEnum';
const { t } = useI18n();
// 获取列表对应的状态图标
const statusIconMap = [
{
key: 'UN_REVIEWED',
icon: StatusType.UN_REVIEWED,
statusText: t('featureTest.featureCase.notReviewed'),
},
{
key: 'UNDER_REVIEWED',
icon: StatusType.UNDER_REVIEWED,
statusText: t('featureTest.featureCase.reviewing'),
},
{
key: 'PASS',
icon: StatusType.PASS,
statusText: t('featureTest.featureCase.passed'),
},
{
key: 'UN_PASS',
icon: StatusType.UN_PASS,
statusText: t('featureTest.featureCase.notPass'),
},
{
key: 'RE_REVIEWED',
icon: StatusType.RE_REVIEWED,
statusText: t('featureTest.featureCase.retrial'),
},
{
key: 'UN_EXECUTED',
icon: StatusType.UN_EXECUTED,
statusText: t('featureTest.featureCase.nonExecution'),
},
{
key: 'PASSED',
icon: StatusType.PASSED,
statusText: t('featureTest.featureCase.passed'),
},
{
key: 'FAILED',
icon: StatusType.FAILED,
statusText: t('featureTest.featureCase.failure'),
},
{
key: 'BLOCKED',
icon: StatusType.BLOCKED,
statusText: t('featureTest.featureCase.chokeUp'),
},
{
key: 'SKIPPED',
icon: StatusType.SKIPPED,
statusText: t('featureTest.featureCase.skip'),
},
];
/** *
*
* @description
* @param {status}
*/
export function getStatusText(status: keyof typeof StatusType) {
const currentStatus = statusIconMap.find((item) => item.key === status);
return {
iconType: currentStatus?.icon,
statusType: currentStatus?.statusText,
};
}
/** *
*
* @description
* @param {status}
*/
export function getReviewStatusClass(status: keyof typeof StatusType) {
const grayColor = ['UN_REVIEWED', 'UN_EXECUTED'];
const yellowColor = ['RE_REVIEWED', 'BLOCKED'];
const blueColor = ['UNDER_REVIEWED', 'SKIPPED'];
if (grayColor.includes(status)) {
return 'text-[var(--color-text-brand)]';
}
if (yellowColor.includes(status)) {
return 'text-[rgb(var(--warning-6))]';
}
if (blueColor.includes(status)) {
return 'text-[rgb(var(--link-6))]';
}
}
export default {};

View File

@ -0,0 +1,319 @@
<template>
<div class="mb-[16px]">
<a-button type="primary" class="mr-[12px]" @click="caseDetail">
{{ t('featureTest.featureCase.creatingCase') }}
</a-button>
<a-button type="outline"> {{ t('featureTest.featureCase.importCase') }} </a-button>
</div>
<div class="pageWrap">
<MsSplitBox>
<template #left>
<div class="p-[24px] pb-0">
<div class="feature-case h-[100%]">
<div class="case h-[38px]">
<div class="flex items-center" :class="getActiveClass('public')">
<MsIcon type="icon-icon_folder_outlined-1" class="folder-icon" />
<div class="folder-name mx-[4px]">{{ t('featureTest.featureCase.publicCase') }}</div>
<div class="folder-count">({{ publicCaseCount }})</div></div
>
<div class="back"><icon-arrow-right /></div>
</div>
<div class="case h-[38px]">
<div class="flex items-center" :class="getActiveClass('all')" @click="setActiveFolder('all')">
<MsIcon type="icon-icon_folder_filled1" class="folder-icon" />
<div class="folder-name mx-[4px]">{{ t('featureTest.featureCase.allCase') }}</div>
<div class="folder-count">({{ modulesCount.all }})</div></div
>
<div class="ml-auto flex items-center">
<a-tooltip
:content="
isExpandAll ? t('project.fileManagement.collapseAll') : t('project.fileManagement.expandAll')
"
>
<MsButton type="icon" status="secondary" class="!mr-0 p-[4px]" @click="expandHandler">
<MsIcon :type="isExpandAll ? 'icon-icon_folder_collapse1' : 'icon-icon_folder_expansion1'" />
</MsButton>
</a-tooltip>
<MsPopConfirm
ref="confirmRef"
v-model:visible="addSubVisible"
:is-delete="false"
:title="t('featureTest.featureCase.addSubModule')"
:all-names="rootModulesName"
:loading="confirmLoading"
:ok-text="t('common.confirm')"
:field-config="{
placeholder: t('featureTest.featureCase.addGroupTip'),
}"
@confirm="confirmHandler"
>
<MsButton type="icon" class="!mr-0 p-[2px]">
<MsIcon
type="icon-icon_create_planarity"
size="18"
class="text-[rgb(var(--primary-5))] hover:text-[rgb(var(--primary-4))]"
/>
</MsButton>
</MsPopConfirm>
</div>
</div>
<a-divider class="my-[8px]" />
<FeatureCaseTree
ref="caseTreeRef"
v-model:selected-keys="selectedKeys"
:all-names="rootModulesName"
:active-folder="activeFolder"
:is-expand-all="isExpandAll"
:modules-count="modulesCount"
@case-node-select="caseNodeSelect"
@init="setRootModules"
></FeatureCaseTree>
<div class="b-0 absolute w-[88%]">
<a-divider class="!my-0 !mb-2" />
<div class="case h-[38px]">
<div class="flex items-center" :class="getActiveClass('recycle')" @click="setActiveFolder('recycle')">
<MsIcon type="icon-icon_delete-trash_outlined" class="folder-icon" />
<div class="folder-name mx-[4px]">{{ t('featureTest.featureCase.recycle') }}</div>
<div class="folder-count">({{ recycleModulesCount.all }})</div></div
>
</div>
</div>
</div>
</div>
</template>
<template #right>
<div class="p-[24px]">
<CaseTable
:active-folder="activeFolder"
:offspring-ids="offspringIds"
:active-folder-type="activeCaseType"
:modules-count="modulesCount"
@init="initModulesCount"
></CaseTable>
</div>
</template>
</MsSplitBox>
</div>
</template>
<script setup lang="ts">
/**
* @description 功能测试-功能用例
*/
import { computed, ref } from 'vue';
import { useRouter } from 'vue-router';
import MsButton from '@/components/pure/ms-button/index.vue';
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
import MsPopConfirm from '@/components/pure/ms-popconfirm/index.vue';
import MsSplitBox from '@/components/pure/ms-split-box/index.vue';
import CaseTable from './components/caseTable.vue';
import FeatureCaseTree from './components/caseTree.vue';
import { createCaseModuleTree } from '@/api/modules/case-management/featureCase';
import { useI18n } from '@/hooks/useI18n';
import useAppStore from '@/store/modules/app';
import useFeatureCaseStore from '@/store/modules/case/featureCase';
import type { CaseModuleQueryParams, CreateOrUpdateModule } from '@/models/caseManagement/featureCase';
import { FeatureTestRouteEnum } from '@/enums/routeEnum';
import Message from '@arco-design/web-vue/es/message';
const router = useRouter();
const appStore = useAppStore();
const { t } = useI18n();
const currentProjectId = computed(() => appStore.currentProjectId);
const isExpandAll = ref(false);
const activeCaseType = ref<'folder' | 'module'>('folder'); //
const rootModulesName = ref<string[]>([]); //
const publicCaseCount = ref<number>(0); //
//
const expandHandler = () => {
isExpandAll.value = !isExpandAll.value;
};
const activeFolder = ref<string>('all');
//
const selectedKeys = computed({
get: () => [activeFolder.value],
set: (val) => val,
});
const offspringIds = ref<string[]>([]);
// ||
const setActiveFolder = (type: string) => {
activeFolder.value = type;
if (['public', 'all', 'recycle'].includes(type)) {
activeCaseType.value = 'folder';
}
if (type === 'recycle') {
router.push({
name: FeatureTestRouteEnum.FEATURE_TEST_CASE_RECYCLE,
});
}
};
//
const getActiveClass = (type: string) => {
return activeFolder.value === type ? 'folder-text case-active' : 'folder-text';
};
const featureCaseStore = useFeatureCaseStore();
//
function caseNodeSelect(keys: string[], _offspringIds: string[]) {
[activeFolder.value] = keys;
activeCaseType.value = 'module';
offspringIds.value = [..._offspringIds];
featureCaseStore.setModuleId(keys, offspringIds.value);
}
const confirmLoading = ref(false);
const confirmRef = ref();
const addSubVisible = ref(false);
const caseTreeRef = ref();
//
const confirmHandler = async () => {
try {
confirmLoading.value = true;
const { field } = confirmRef.value.form;
if (!confirmRef.value.isPass) {
return;
}
const params: CreateOrUpdateModule = {
projectId: currentProjectId.value,
name: field,
parentId: 'none',
};
await createCaseModuleTree(params);
Message.success(t('featureTest.featureCase.addSuccess'));
caseTreeRef.value.initModules();
addSubVisible.value = false;
} catch (error) {
console.log(error);
} finally {
confirmLoading.value = false;
}
};
/**
* 设置根模块名称列表
* @param names 根模块名称列表
*/
function setRootModules(names: string[]) {
rootModulesName.value = names;
}
//
const tableFilterParams = ref<CaseModuleQueryParams>({
moduleIds: [],
projectId: '',
});
const modulesCount = computed(() => {
return featureCaseStore.modulesCount;
});
const recycleModulesCount = computed(() => {
return featureCaseStore.recycleModulesCount;
});
/**
* 右侧表格数据刷新后若当前展示的是模块则刷新模块树的统计数量
*/
function initModulesCount(params: CaseModuleQueryParams) {
featureCaseStore.getCaseModulesCountCount(params);
featureCaseStore.getRecycleMModulesCountCount(params);
tableFilterParams.value = { ...params };
}
//
function caseDetail() {
router.push({
name: FeatureTestRouteEnum.FEATURE_TEST_CASE_DETAIL,
});
}
//
router.beforeEach((to: any, from: any, next) => {
const routeEnumValues = Object.values(FeatureTestRouteEnum);
if (!routeEnumValues.includes(to.name)) {
//
featureCaseStore.setIsAlreadySuccess(false);
}
next();
});
onMounted(() => {
if (featureCaseStore.operatingState) {
[activeFolder.value] = featureCaseStore.moduleId;
}
});
</script>
<style scoped lang="less">
.pageWrap {
min-width: 1000px;
height: calc(100vh - 136px);
border-radius: var(--border-radius-large);
@apply bg-white;
.case {
padding: 8px 4px;
border-radius: var(--border-radius-small);
@apply flex cursor-pointer items-center justify-between;
&:hover {
background-color: rgb(var(--primary-1));
}
.folder-icon {
margin-right: 4px;
color: var(--color-text-4);
}
.folder-name {
color: var(--color-text-1);
}
.folder-count {
margin-left: 4px;
color: var(--color-text-4);
}
.case-active {
.folder-icon,
.folder-name,
.folder-count {
color: rgb(var(--primary-5));
}
}
.back {
margin-right: 8px;
width: 20px;
height: 20px;
border: 1px solid #ffffff;
background: linear-gradient(90deg, rgb(var(--primary-9)) 3.36%, #ffffff 100%);
box-shadow: 0 0 7px rgb(15 0 78 / 9%);
.arco-icon {
color: rgb(var(--primary-5));
}
@apply flex cursor-pointer items-center rounded-full;
}
}
}
.recycle {
@apply absolute bottom-0 bg-white pb-4;
:deep(.arco-divider-horizontal) {
margin: 8px 0;
}
.recycle-bin {
@apply bottom-0 flex items-center bg-white;
.recycle-count {
margin-left: 4px;
color: var(--color-text-4);
}
}
}
</style>

View File

@ -0,0 +1,108 @@
export default {
'featureTest.featureCase.creatingCase': 'Create Case',
'featureTest.featureCase.importCase': 'Import Case',
'featureTest.featureCase.publicCase': 'Public of Cases',
'featureTest.featureCase.allCase': 'All of Cases',
'featureTest.featureCase.searchTip': 'Please enter a group name',
'featureTest.featureCase.caseEmptyContent': 'No use case data yet, please click the button above to create or import',
'featureTest.featureCase.addSubModule': 'Add submodules',
'featureTest.featureCase.rename': 'rename',
'featureTest.featureCase.recycle': 'Recycle',
'featureTest.featureCase.versionPlaceholder': 'The default is the latest version',
'featureTest.featureCase.searchByNameAndId': 'Search by ID or name',
'featureTest.featureCase.filter': 'filter',
'featureTest.featureCase.setFilterCondition': 'Set filters',
'featureTest.featureCase.followingCondition': 'Conform to the following',
'featureTest.featureCase.condition': 'Condition',
'featureTest.featureCase.delete': 'delete',
'featureTest.featureCase.addSubModuleSuccess': 'Add submodule successfully',
'featureTest.featureCase.renameSuccess': 'Rename successful',
'featureTest.featureCase.nameNotNullTip': 'The name can not be null',
'featureTest.featureCase.deleteTipTitle': 'Do you want to delete: {name} use case?',
'featureTest.featureCase.deleteCaseTipContent':
'After the node is deleted, all resources under the node will be deleted. Exercise caution when performing this operation.',
'featureTest.featureCase.deleteConfirm': 'Confirm',
'featureTest.featureCase.deleteSuccess': 'Delete Successfully',
'featureTest.featureCase.addSuccess': 'Add Successfully',
'featureTest.featureCase.addGroupTip': 'Please enter the group name and press enter to save',
'featureTest.featureCase.tableColumnID': 'ID',
'featureTest.featureCase.tableColumnName': 'Case Name',
'featureTest.featureCase.tableColumnLevel': 'Case Level',
'featureTest.featureCase.tableColumnCaseState': 'Case State',
'featureTest.featureCase.tableColumnReviewResult': 'Review Result',
'featureTest.featureCase.tableColumnExecutionResult': 'Execution Result',
'featureTest.featureCase.tableColumnVersion': 'version',
'featureTest.featureCase.tableColumnModule': 'Module',
'featureTest.featureCase.tableColumnTag': 'Tag',
'featureTest.featureCase.tableColumnCreateUser': 'CreateUser',
'featureTest.featureCase.tableColumnCreateTime': 'CreateTime',
'featureTest.featureCase.tableColumnUpdateUser': 'UpdateUser',
'featureTest.featureCase.tableColumnUpdateTime': 'UpdateTime',
'featureTest.featureCase.tableColumnActions': 'operation',
'featureTest.featureCase.beforeDeleteCase':
'The deleted content will be put into the recycle bin, where data can be recovered',
'featureTest.featureCase.deleteCaseTitle': 'Are you sure to delete the {name} use case?',
'featureTest.featureCase.export': 'Export',
'featureTest.featureCase.exportExcel': 'Export spreadsheet (xlsx)',
'featureTest.featureCase.exportXMind': 'Exporting Mind (xmind)',
'featureTest.featureCase.moveTo': 'Move to',
'featureTest.featureCase.copyTo': 'Copy to',
'featureTest.featureCase.associatedDemand': 'Associated demand',
'featureTest.featureCase.generatingDependencies': 'Generative dependency',
'featureTest.featureCase.addToPublic': 'Add to public case',
'featureTest.featureCase.updateCase': 'Update Case',
'featureTest.featureCase.latestTemplate': 'The default is the latest template',
'featureTest.featureCase.copyStep': 'Copy Step',
'featureTest.featureCase.InsertStepsBefore': 'Insert steps before',
'featureTest.featureCase.afterInsertingSteps': 'After inserting steps',
'featureTest.featureCase.associatedFile': 'Associated File',
'featureTest.featureCase.associated': 'Associated',
'featureTest.featureCase.fileType': 'File Type',
'featureTest.featureCase.fileName': 'fileName',
'featureTest.featureCase.description': 'Description',
'featureTest.featureCase.tags': 'Tags',
'featureTest.featureCase.enableTags': `On: adds a label`,
'featureTest.featureCase.closeTags': 'Off: overwrites an existing label',
'featureTest.featureCase.appendTag': 'appendTag',
'featureTest.featureCase.batchEdit': 'Batch editing ({number} use cases selected)',
'featureTest.featureCase.selectAttrs': 'select attributes',
'featureTest.featureCase.batchUpdate': 'Batch update to',
'featureTest.featureCase.batchDelete': 'Are you sure to delete the use case {number} ?',
'featureTest.featureCase.batchMoveTitle': 'Batch Move',
'featureTest.featureCase.batchMove': '({number} selected use cases)',
'featureTest.featureCase.batchMoveSelectedModules': 'Batch Move',
'featureTest.featureCase.batchCopyTitle': 'Batch Copy',
'featureTest.featureCase.batchCopySelectedModules': 'Batch Copy',
'featureTest.featureCase.batchCopy': 'Copy Successfully!',
'featureTest.featureCase.editSuccess': 'update Successfully',
'featureTest.featureCase.PleaseSelect': 'Please select',
'featureTest.featureCase.PleaseInputTags': 'Please enter the update tag to add',
'featureTest.featureCase.expectedResult': 'Expected Result',
'featureTest.featureCase.remark': 'Remark',
'featureTest.featureCase.addAttachment': 'Add attachment',
'featureTest.featureCase.uploadFile': 'Upload File',
'featureTest.featureCase.storage': 'storage',
'featureTest.featureCase.download': 'download',
'featureTest.featureCase.cancelLink': 'Cancel link',
'featureTest.featureCase.SelectExportRange': 'Select export range',
'featureTest.featureCase.clear': 'Clear',
'featureTest.featureCase.baseField': 'Base Field',
'featureTest.featureCase.customField': 'Custom Field',
'featureTest.featureCase.otherFields': 'Other Fields',
'featureTest.featureCase.otherFieldsToolTip': 'Other fields do not support import after export',
'featureTest.featureCase.notReviewed': 'Not reviewed',
'featureTest.featureCase.reviewing': 'In the review',
'featureTest.featureCase.passed': 'Have passed',
'featureTest.featureCase.notPass': 'Not pass',
'featureTest.featureCase.retrial': 'retrial',
'featureTest.featureCase.nonExecution': 'non-execution',
'featureTest.featureCase.failure': 'Failure',
'featureTest.featureCase.chokeUp': 'Choke up',
'featureTest.featureCase.skip': 'skip',
'featureTest.featureCase.batchRecover': 'recover',
'featureTest.featureCase.batchCleanOut': 'Clean out',
'featureTest.featureCase.recoveredSuccessfully': 'Recovered successfully',
'featureTest.featureCase.cleanOutDeleteTip':
'After deletion, the data will not be recovered, please operate with caution!',
'featureTest.featureCase.pleaseEnterInputTags': 'Please enter content Enter add label',
};

View File

@ -0,0 +1,107 @@
export default {
'featureTest.featureCase.creatingCase': '创建用例',
'featureTest.featureCase.importCase': '导入用例',
'featureTest.featureCase.publicCase': '公共用例库',
'featureTest.featureCase.allCase': '全部用例',
'featureTest.featureCase.searchTip': '请输入分组名称',
'featureTest.featureCase.caseEmptyContent': '暂无用例数据,请点击上方按钮创建或导入',
'featureTest.featureCase.addSubModule': '添加子模块',
'featureTest.featureCase.rename': '重命名',
'featureTest.featureCase.recycle': '回收站',
'featureTest.featureCase.versionPlaceholder': '默认为最新版本',
'featureTest.featureCase.searchByNameAndId': '通过 ID 或名称搜索',
'featureTest.featureCase.filter': '筛选',
'featureTest.featureCase.setFilterCondition': '设置筛选条件',
'featureTest.featureCase.followingCondition': '符合以下',
'featureTest.featureCase.condition': '条件',
'featureTest.featureCase.delete': '删除',
'featureTest.featureCase.addSubModuleSuccess': '添加子模块成功',
'featureTest.featureCase.renameSuccess': '重命名成功',
'featureTest.featureCase.nameNotNullTip': '名称不能为空',
'featureTest.featureCase.deleteTipTitle': '是否删除 {name} 用例 ',
'featureTest.featureCase.deleteCaseTipContent': '删除后,此节点下的所有资源都会被删除,请谨慎操作。',
'featureTest.featureCase.deleteConfirm': '确认删除',
'featureTest.featureCase.deleteSuccess': '删除成功',
'featureTest.featureCase.addSuccess': '添加成功',
'featureTest.featureCase.addGroupTip': '请输入分组名称,按回车键保存',
'featureTest.featureCase.moduleMoveSuccess': '请输入分组名称,按回车键保存',
'featureTest.featureCase.tableColumnID': 'ID',
'featureTest.featureCase.tableColumnName': '用例名称',
'featureTest.featureCase.tableColumnLevel': '用例等级',
'featureTest.featureCase.tableColumnCaseState': '用例状态',
'featureTest.featureCase.tableColumnReviewResult': '评审结果',
'featureTest.featureCase.tableColumnExecutionResult': '执行结果',
'featureTest.featureCase.tableColumnVersion': '版本',
'featureTest.featureCase.tableColumnModule': '所属模块',
'featureTest.featureCase.tableColumnTag': '标签',
'featureTest.featureCase.tableColumnCreateUser': '创建人',
'featureTest.featureCase.tableColumnCreateTime': '创建时间',
'featureTest.featureCase.tableColumnUpdateUser': '更新人',
'featureTest.featureCase.tableColumnUpdateTime': '更新时间',
'featureTest.featureCase.tableColumnActions': '操作',
'featureTest.featureCase.beforeDeleteCase': '删除后的内容将放入回收站,可在回收站内进行恢复数据',
'featureTest.featureCase.deleteCaseTitle': '确认删除 {name} 用例 ',
'featureTest.featureCase.export': '导出',
'featureTest.featureCase.exportExcel': '导出 Excel 表格 (xlsx)',
'featureTest.featureCase.exportXMind': '导出思维导图 (xmind)',
'featureTest.featureCase.moveTo': '移动到',
'featureTest.featureCase.copyTo': '复制到',
'featureTest.featureCase.associatedDemand': '关联需求',
'featureTest.featureCase.generatingDependencies': '生成依赖关系',
'featureTest.featureCase.addToPublic': '添加到公共用例库',
'featureTest.featureCase.updateCase': '更新用例',
'featureTest.featureCase.latestTemplate': '默认为最新模版',
'featureTest.featureCase.copyStep': '复制该步骤',
'featureTest.featureCase.InsertStepsBefore': '在之前插入步骤',
'featureTest.featureCase.afterInsertingSteps': '在之后插入步骤',
'featureTest.featureCase.associatedFile': '关联文件',
'featureTest.featureCase.associated': '关联',
'featureTest.featureCase.fileType': '文件类型',
'featureTest.featureCase.fileName': '文件名',
'featureTest.featureCase.description': '描述',
'featureTest.featureCase.tags': '标签',
'featureTest.featureCase.enableTags': `开启:新增标签,关闭:覆盖原有标签`,
'featureTest.featureCase.closeTags': '关闭:覆盖原有标签',
'featureTest.featureCase.appendTag': '追加标签',
'featureTest.featureCase.batchEdit': '批量编辑 (已选 { number } 条用例)',
'featureTest.featureCase.selectAttrs': '选择属性',
'featureTest.featureCase.batchUpdate': '批量更新为',
'featureTest.featureCase.batchDelete': '确认删除 {number} 条用例吗?',
'featureTest.featureCase.batchMoveTitle': '批量移动',
'featureTest.featureCase.batchMove': '(已选 { number } 条用例)',
'featureTest.featureCase.batchMoveSelectedModules': '移动 { number } 个用例至已选模块',
'featureTest.featureCase.batchCopyTitle': '批量复制',
'featureTest.featureCase.batchCopySelectedModules': '复制 { number } 个用例至已选模块',
'featureTest.featureCase.batchCopySuccess': '复制成功',
'featureTest.featureCase.batchMoveSuccess': '移动成功',
'featureTest.featureCase.editSuccess': '更新成功',
'featureTest.featureCase.PleaseSelect': '请选择',
'featureTest.featureCase.PleaseInputTags': '请输入更新标签回车添加',
'featureTest.featureCase.expectedResult': '预期结果',
'featureTest.featureCase.remark': '备注',
'featureTest.featureCase.addAttachment': '添加附件',
'featureTest.featureCase.uploadFile': '上传文件',
'featureTest.featureCase.storage': '转存',
'featureTest.featureCase.download': '下载',
'featureTest.featureCase.cancelLink': '取消关联',
'featureTest.featureCase.SelectExportRange': '选择导出范围',
'featureTest.featureCase.clear': '清空',
'featureTest.featureCase.baseField': '基础字段',
'featureTest.featureCase.customField': '自定义字段',
'featureTest.featureCase.otherFields': '其他字段',
'featureTest.featureCase.otherFieldsToolTip': '其他字段 导出后不支持导入',
'featureTest.featureCase.notReviewed': '未评审',
'featureTest.featureCase.reviewing': '评审中',
'featureTest.featureCase.passed': '已通过',
'featureTest.featureCase.notPass': '未通过',
'featureTest.featureCase.retrial': '重新提审',
'featureTest.featureCase.nonExecution': '未执行',
'featureTest.featureCase.failure': '失败',
'featureTest.featureCase.chokeUp': '阻塞',
'featureTest.featureCase.skip': '跳过',
'featureTest.featureCase.batchRecover': '恢复',
'featureTest.featureCase.batchCleanOut': '彻底删除',
'featureTest.featureCase.recoveredSuccessfully': '恢复成功',
'featureTest.featureCase.cleanOutDeleteTip': '删除后,数据将无法恢复,请谨慎操作!',
'featureTest.featureCase.pleaseEnterInputTags': '请输入内容回车添加标签',
};

View File

@ -1,147 +0,0 @@
<template>
<div class="page-header h-[34px]">
<div class="text-[var(--color-text-1)]"
>{{ t('featureTest.featureCase.allCase') }}
<span class="text-[var(--color-text-4)]"> ({{ allCaseCount }})</span></div
>
<div class="flex w-[80%] items-center justify-end">
<a-select class="w-[240px]" :placeholder="t('featureTest.featureCase.versionPlaceholder')">
<a-option v-for="version of versionOptions" :key="version.id" :value="version.id">{{ version.name }}</a-option>
</a-select>
<a-input-search
v-model:model-value="keyword"
:placeholder="t('featureTest.featureCase.searchByNameAndId')"
allow-clear
class="mx-[8px] w-[240px]"
></a-input-search>
<MsTag
:type="isExpandFilter ? 'primary' : 'default'"
:theme="isExpandFilter ? 'lightOutLine' : 'outline'"
size="large"
class="-mt-[3px] min-w-[64px] cursor-pointer"
>
<span :class="!isExpandFilter ? 'text-[var(--color-text-4)]' : ''" @click="isExpandFilterHandler"
><icon-filter class="mr-[4px]" :style="{ 'font-size': '16px' }" />{{
t('featureTest.featureCase.filter')
}}</span
>
</MsTag>
<a-radio-group v-model:model-value="showType" type="button" class="file-show-type ml-[4px]">
<a-radio value="list" class="show-type-icon p-[2px]"><MsIcon type="icon-icon_view-list_outlined" /></a-radio>
<a-radio value="xmind" class="show-type-icon p-[2px]"><MsIcon type="icon-icon_mindnote_outlined" /></a-radio>
</a-radio-group>
</div>
</div>
<FilterPanel v-show="isExpandFilter"></FilterPanel>
<MinderEditor
:import-json="importJson"
:tags="['模块', '用例', '前置条件', '备注', '步骤', '预期结果']"
tag-enable
sequence-enable
@node-click="handleNodeClick"
/>
<MsDrawer v-model:visible="visible" :width="480" :mask="false">
{{ nodeData.text }}
</MsDrawer>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import MinderEditor from '@/components/pure/minder-editor/minderEditor.vue';
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
import MsTag from '@/components/pure/ms-tag/ms-tag.vue';
import FilterPanel from '@/components/business/ms-filter-panel/searchForm.vue';
import { useI18n } from '@/hooks/useI18n';
const { t } = useI18n();
const versionOptions = ref([
{
id: '1001',
name: 'v_1.0',
},
]);
const keyword = ref<string>();
const showType = ref<string>('list');
const allCaseCount = ref<number>(100);
const isExpandFilter = ref<boolean>(false);
// ||
const isExpandFilterHandler = () => {
isExpandFilter.value = !isExpandFilter.value;
};
const visible = ref<boolean>(false);
const nodeData = ref<any>({});
const importJson = ref<any>({});
function handleNodeClick(data: any) {
if (data.resource && data.resource.includes('用例')) {
visible.value = true;
nodeData.value = data;
}
}
onBeforeMount(() => {
importJson.value = {
root: {
data: {
text: '测试用例',
id: 'xxxx',
},
children: [
{
data: {
id: 'sdasdas',
text: '模块 1',
resource: ['模块'],
},
},
{
data: {
id: 'dasdasda',
text: '模块 2',
expandState: 'collapse',
},
children: [
{
data: {
id: 'frihofiuho3f',
text: '用例 1',
resource: ['用例'],
},
},
{
data: {
id: 'df09348f034f',
text: ' 用例 2',
resource: ['用例'],
},
},
],
},
],
},
template: 'default',
};
});
</script>
<style scoped lang="less">
.page-header {
@apply flex items-center justify-between;
}
.filter-panel {
background: var(--color-text-n9);
@apply mt-1 rounded-md p-3;
.condition-text {
color: var(--color-text-2);
}
}
</style>

View File

@ -1,293 +0,0 @@
<template>
<MsTree
v-model:focus-node-key="focusNodeKey"
:selected-keys="props.selectedKeys"
:data="caseTree"
:keyword="groupKeyword"
:node-more-actions="caseMoreActions"
:expand-all="props.isExpandAll"
:empty-text="t('featureTest.featureCase.caseEmptyContent')"
draggable
:virtual-list-props="virtualListProps"
block-node
@select="caseNodeSelect"
@more-action-select="handleCaseMoreSelect"
@more-actions-close="moreActionsClose"
>
<template #title="nodeData">
<span class="text-[var(--color-text-1)]">{{ nodeData.title }}</span>
<span class="ml-[4px] text-[var(--color-text-4)]">({{ nodeData.count }})</span>
</template>
<template #extra="nodeData">
<MsPopConfirm
:is-delete="false"
:all-names="[]"
:title="t('featureTest.featureCase.addSubModule')"
@cancel="resetFocusNodeKey"
>
<MsButton type="icon" size="mini" class="ms-tree-node-extra__btn !mr-0" @click="setFocusKey(nodeData)">
<MsIcon type="icon-icon_add_outlined" size="14" class="text-[var(--color-text-4)]" />
</MsButton>
</MsPopConfirm>
<MsPopConfirm
:title="t('featureTest.featureCase.rename')"
:all-names="[]"
:is-delete="false"
:field-config="{ field: renameCaseName }"
@cancel="resetFocusNodeKey"
>
<span :id="`renameSpan${nodeData.key}`" class="relative"></span>
</MsPopConfirm>
</template>
</MsTree>
<div class="recycle w-[88%]">
<a-divider class="mb-[16px]" />
<div class="recycle-bin pt-2">
<MsIcon type="icon-icon_delete-trash_outlined" size="16" class="mx-[10px] text-[var(--color-text-4)]" />
<div class="text-[var(--color-text-1)]">{{ t('featureTest.featureCase.recycle') }}</div>
<div class="recycle-count">({{ recycleCount }})</div>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, ref, watch } from 'vue';
import { Message } from '@arco-design/web-vue';
import MsButton from '@/components/pure/ms-button/index.vue';
import MsPopConfirm from '@/components/pure/ms-popconfirm/index.vue';
import type { ActionsItem } from '@/components/pure/ms-table-more-action/types';
import MsTree from '@/components/business/ms-tree/index.vue';
import type { MsTreeNodeData } from '@/components/business/ms-tree/types';
import { useI18n } from '@/hooks/useI18n';
import useModal from '@/hooks/useModal';
const { t } = useI18n();
const { openModal } = useModal();
const focusNodeKey = ref<string | number>('');
const props = defineProps<{
selectedKeys?: Array<string | number>; // key
isExpandAll: boolean; //
}>();
const emits = defineEmits(['update:selectedKeys', 'caseNodeSelect']);
const groupKeyword = ref<string>('');
const caseTree = ref([
{
title: 'Trunk',
key: 'node1',
count: 18,
children: [
{
title: 'Leaf',
key: 'node2',
count: 28,
},
{
title: 'Leaf',
key: 'node4',
count: 138,
},
{
title: 'Leaf',
key: 'node5',
count: 108,
},
{
title: 'Leaf',
key: 'node4',
count: 138,
},
{
title: 'Leaf',
key: 'node5',
count: 108,
},
{
title: 'Leaf',
key: 'node4',
count: 138,
},
{
title: 'Leaf',
key: 'node5',
count: 108,
},
],
},
{
title: 'Trunk',
key: 'node3',
count: 180,
children: [
{
title: 'Leaf',
key: 'node4',
count: 138,
},
{
title: 'Leaf',
key: 'node5',
count: 108,
},
{
title: 'Leaf',
key: 'node4',
count: 138,
},
{
title: 'Leaf',
key: 'node5',
count: 108,
},
{
title: 'Leaf',
key: 'node4',
count: 138,
},
{
title: 'Leaf',
key: 'node5',
count: 108,
},
{
title: 'Leaf',
key: 'node4',
count: 138,
},
{
title: 'Leaf',
key: 'node5',
count: 108,
},
],
},
{
title: 'Trunk',
key: 'node6',
children: [],
count: 0,
},
]);
const caseMoreActions: ActionsItem[] = [
{
label: 'featureTest.featureCase.rename',
eventTag: 'rename',
},
{
label: 'featureTest.featureCase.delete',
eventTag: 'delete',
danger: true,
},
];
const renameCaseName = ref('');
const selectedNodeKeys = ref(props.selectedKeys || []);
const renamePopVisible = ref(false);
//
const caseNodeSelect = (selectedKeys: (string | number)[]) => {
emits('caseNodeSelect', selectedKeys);
};
//
const deleteHandler = (node: MsTreeNodeData) => {
openModal({
type: 'error',
title: t('featureTest.featureCase.deleteTipTitle', { name: node.title }),
content: t('featureTest.featureCase.deleteCaseTipContent'),
okText: t('featureTest.featureCase.deleteConfirm'),
okButtonProps: {
status: 'danger',
},
maskClosable: false,
onBeforeOk: async () => {
try {
Message.success(t('featureTest.featureCase.deleteSuccess'));
} catch (error) {
console.log(error);
}
},
hideCancel: false,
});
};
function resetFocusNodeKey() {
focusNodeKey.value = '';
renamePopVisible.value = false;
renameCaseName.value = '';
}
//
const handleCaseMoreSelect = (item: ActionsItem, node: MsTreeNodeData) => {
switch (item.eventTag) {
case 'delete':
deleteHandler(node);
resetFocusNodeKey();
break;
case 'rename':
renameCaseName.value = node.title || '';
renamePopVisible.value = true;
document.querySelector(`#renameSpan${node.key}`)?.dispatchEvent(new Event('click'));
break;
default:
break;
}
};
const moreActionsClose = () => {
if (!renamePopVisible.value) {
resetFocusNodeKey();
}
};
const setFocusKey = (node: MsTreeNodeData) => {
focusNodeKey.value = node.key || '';
};
const recycleCount = ref<number>(100);
const virtualListProps = computed(() => {
return {
height: 'calc(100vh - 376px)',
};
});
watch(
() => props.selectedKeys,
(val) => {
selectedNodeKeys.value = val || [];
}
);
watch(
() => selectedNodeKeys.value,
(val) => {
emits('update:selectedKeys', val);
}
);
</script>
<style scoped lang="less">
.recycle {
@apply absolute bottom-0 bg-white pb-4;
:deep(.arco-divider-horizontal) {
margin: 8px 0;
}
.recycle-bin {
@apply bottom-0 flex items-center bg-white;
.recycle-count {
margin-left: 4px;
color: var(--color-text-4);
}
}
}
</style>

View File

@ -1,173 +0,0 @@
<template>
<div class="mb-[16px]">
<a-button type="primary" class="mr-[12px]"> {{ t('featureTest.featureCase.creatingCase') }} </a-button>
<a-button type="outline"> {{ t('featureTest.featureCase.importCase') }} </a-button>
</div>
<div class="pageWrap">
<MsSplitBox>
<template #left>
<div class="p-[24px]">
<div class="feature-case">
<div class="case h-[38px]">
<div class="flex items-center" :class="getActiveClass('public')" @click="selectActive('public')">
<MsIcon type="icon-icon_folder_outlined-1" class="folder-icon" />
<div class="folder-name mx-[4px]">{{ t('featureTest.featureCase.publicCase') }}</div>
<div class="folder-count">({{ publicCaseCount }})</div></div
>
<div class="back"><icon-arrow-right /></div>
</div>
<a-divider class="my-[8px]" />
<a-input-search class="mb-4" :placeholder="t('featureTest.featureCase.searchTip')" />
<div class="case h-[38px]">
<div class="flex items-center" :class="getActiveClass('all')" @click="selectActive('all')">
<MsIcon type="icon-icon_folder_filled1" class="folder-icon" />
<div class="folder-name mx-[4px]">{{ t('featureTest.featureCase.allCase') }}</div>
<div class="folder-count">(100)</div></div
>
<div class="ml-auto flex items-center">
<a-tooltip
:content="
isExpandAll ? t('project.fileManagement.collapseAll') : t('project.fileManagement.expandAll')
"
>
<MsButton type="icon" status="secondary" class="!mr-0 p-[4px]" @click="expandHandler">
<MsIcon :type="isExpandAll ? 'icon-icon_folder_collapse1' : 'icon-icon_folder_expansion1'" />
</MsButton>
</a-tooltip>
<MsPopConfirm
:is-delete="false"
:title="t('featureTest.featureCase.addSubModule')"
:all-names="[]"
:loading="confirmLoading"
@confirm="confirmHandler"
>
<MsButton type="icon" class="!mr-0 p-[2px]">
<MsIcon
type="icon-icon_create_planarity"
size="18"
class="text-[rgb(var(--primary-5))] hover:text-[rgb(var(--primary-4))]"
/>
</MsButton>
</MsPopConfirm>
</div>
</div>
<a-divider class="my-[8px]" />
<FeatureCaseTree
v-model:selected-keys="selectedKeys"
:is-expand-all="isExpandAll"
@case-node-select="caseNodeSelect"
></FeatureCaseTree>
</div>
</div>
</template>
<template #right>
<div class="p-[24px]">
<CaseTable></CaseTable>
</div>
</template>
</MsSplitBox>
</div>
</template>
<script setup lang="ts">
/**
* @description 功能测试-功能用例
*/
import { computed, ref } from 'vue';
import MsButton from '@/components/pure/ms-button/index.vue';
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
import MsPopConfirm from '@/components/pure/ms-popconfirm/index.vue';
import MsSplitBox from '@/components/pure/ms-split-box/index.vue';
import CaseTable from './components/caseTable.vue';
import FeatureCaseTree from './components/featureCaseTree.vue';
import { useI18n } from '@/hooks/useI18n';
const { t } = useI18n();
const isExpandAll = ref(false);
const activeCase = ref<string | number>('public'); //
const publicCaseCount = ref<number>(100); //
//
const selectActive = (type: string) => {
activeCase.value = type;
};
//
const getActiveClass = (type: string) => {
return activeCase.value === type ? 'folder-text case-active' : 'folder-text';
};
const expandHandler = () => {
isExpandAll.value = !isExpandAll.value;
};
//
const selectedKeys = computed({
get: () => [activeCase.value],
set: (val) => val,
});
//
function caseNodeSelect(keys: (string | number)[]) {
[activeCase.value] = keys;
}
const confirmLoading = ref(false);
const confirmHandler = () => {
try {
confirmLoading.value = true;
} catch (error) {
console.log(error);
} finally {
confirmLoading.value = false;
}
};
</script>
<style scoped lang="less">
.pageWrap {
min-width: 1000px;
height: calc(100vh - 136px);
border-radius: var(--border-radius-large);
@apply bg-white;
.case {
@apply flex cursor-pointer items-center justify-between;
.folder-icon {
margin-right: 4px;
color: var(--color-text-4);
}
.folder-name {
color: var(--color-text-1);
}
.folder-count {
margin-left: 4px;
color: var(--color-text-4);
}
.case-active {
.folder-icon,
.folder-name,
.folder-count {
color: rgb(var(--primary-5));
}
}
.back {
margin-right: 8px;
width: 20px;
height: 20px;
border: 1px solid #ffffff;
background: linear-gradient(90deg, rgb(var(--primary-9)) 3.36%, #ffffff 100%);
box-shadow: 0 0 7px rgb(15 0 78 / 9%);
.arco-icon {
color: rgb(var(--primary-5));
}
@apply flex cursor-pointer items-center rounded-full;
}
}
}
</style>

View File

@ -1,26 +0,0 @@
export default {
'featureTest.featureCase.creatingCase': 'Create Case',
'featureTest.featureCase.importCase': 'Import Case',
'featureTest.featureCase.publicCase': 'Public of Cases',
'featureTest.featureCase.allCase': 'All of Cases',
'featureTest.featureCase.searchTip': 'Please enter a group name',
'featureTest.featureCase.caseEmptyContent': 'No use case data yet, please click the button above to create or import',
'featureTest.featureCase.addSubModule': 'Add submodules',
'featureTest.featureCase.rename': 'rename',
'featureTest.featureCase.recycle': 'Recycle',
'featureTest.featureCase.versionPlaceholder': 'The default is the latest version',
'featureTest.featureCase.searchByNameAndId': 'Search by ID or name',
'featureTest.featureCase.filter': 'filter',
'featureTest.featureCase.setFilterCondition': 'Set filters',
'featureTest.featureCase.followingCondition': 'Conform to the following',
'featureTest.featureCase.condition': 'Condition',
'featureTest.featureCase.delete': 'delete',
'featureTest.featureCase.addSubModuleSuccess': 'Add submodule successfully',
'featureTest.featureCase.renameSuccess': 'Rename successful',
'featureTest.featureCase.nameNotNullTip': 'The name can not be null',
'featureTest.featureCase.deleteTipTitle': 'Do you want to delete: {name} use case?',
'featureTest.featureCase.deleteCaseTipContent':
'After the node is deleted, all resources under the node will be deleted. Exercise caution when performing this operation.',
'featureTest.featureCase.deleteConfirm': 'Confirm',
'featureTest.featureCase.deleteSuccess': 'Delete Successfully',
};

View File

@ -1,25 +0,0 @@
export default {
'featureTest.featureCase.creatingCase': '创建用例',
'featureTest.featureCase.importCase': '导入用例',
'featureTest.featureCase.publicCase': '公共用例库',
'featureTest.featureCase.allCase': '全部用例',
'featureTest.featureCase.searchTip': '请输入分组名称',
'featureTest.featureCase.caseEmptyContent': '暂无用例数据,请点击上方按钮创建或导入',
'featureTest.featureCase.addSubModule': '添加子模块',
'featureTest.featureCase.rename': '重命名',
'featureTest.featureCase.recycle': '回收站',
'featureTest.featureCase.versionPlaceholder': '默认为最新版本',
'featureTest.featureCase.searchByNameAndId': '通过 ID 或名称搜索',
'featureTest.featureCase.filter': '筛选',
'featureTest.featureCase.setFilterCondition': '设置筛选条件',
'featureTest.featureCase.followingCondition': '符合以下',
'featureTest.featureCase.condition': '条件',
'featureTest.featureCase.delete': '删除',
'featureTest.featureCase.addSubModuleSuccess': '添加子模块成功',
'featureTest.featureCase.renameSuccess': '重命名成功',
'featureTest.featureCase.nameNotNullTip': '名称不能为空',
'featureTest.featureCase.deleteTipTitle': '是否删除:{name} 用例?',
'featureTest.featureCase.deleteCaseTipContent': '删除后,此节点下的所有资源都会被删除,请谨慎操作。',
'featureTest.featureCase.deleteConfirm': '确认删除',
'featureTest.featureCase.deleteSuccess': '删除成功',
};

View File

@ -1,3 +0,0 @@
<template> Feature Test is waiting for development </template>
<script setup></script>

View File

@ -211,9 +211,9 @@
openModal({ openModal({
type: 'error', type: 'error',
title: t('system.orgTemplate.deleteTemplateTitle', { name: characterLimit(record.name) }), title: t('system.orgTemplate.deleteTemplateTitle', { name: characterLimit(record.name) }),
content: t('system.userGroup.beforeDeleteUserGroup'), content: t('system.orgTemplate.deleteProjectTemplateTip'),
okText: t('system.userGroup.confirmDelete'), okText: t('common.confirmDelete'),
cancelText: t('system.userGroup.cancel'), cancelText: t('common.cancel'),
okButtonProps: { okButtonProps: {
status: 'danger', status: 'danger',
}, },

View File

@ -136,7 +136,7 @@
import { useAppStore, useTableStore } from '@/store'; import { useAppStore, useTableStore } from '@/store';
import { characterLimit } from '@/utils'; import { characterLimit } from '@/utils';
import type { AddorUpdateMemberModel, BatchAddProjectModel, LinkList, MemberItem } from '@/models/setting/member'; import type { AddOrUpdateMemberModel, BatchAddProjectModel, LinkList, MemberItem } from '@/models/setting/member';
import { TableKeyEnum } from '@/enums/tableEnum'; import { TableKeyEnum } from '@/enums/tableEnum';
const tableStore = useTableStore(); const tableStore = useTableStore();
@ -153,7 +153,7 @@
showTooltip: true, showTooltip: true,
ellipsis: true, ellipsis: true,
sortIndex: 0, sortIndex: 0,
showDrag: true, showDrag: false,
}, },
{ {
title: 'organization.member.tableColunmName', title: 'organization.member.tableColunmName',
@ -243,7 +243,7 @@
const addMemberVisible = ref<boolean>(false); const addMemberVisible = ref<boolean>(false);
const AddMemberRef = ref(); const AddMemberRef = ref();
const addOrEditMember = (type: string, record: AddorUpdateMemberModel = {}) => { const addOrEditMember = (type: string, record: AddOrUpdateMemberModel = {}) => {
addMemberVisible.value = true; addMemberVisible.value = true;
AddMemberRef.value.type = type; AddMemberRef.value.type = type;
if (type === 'edit') { if (type === 'edit') {

View File

@ -72,7 +72,7 @@
> >
<div v-if="sceneType === 'BUG'" class="optionsKey"> <div v-if="sceneType === 'BUG'" class="optionsKey">
<a-checkbox v-model="fieldForm.enableOptionKey" <a-checkbox v-model="fieldForm.enableOptionKey"
>选项KEY值 >{{ t('system.orgTemplate.optionKeyValue') }}
<a-tooltip :content="t('system.orgTemplate.thirdPartyPlatforms')" <a-tooltip :content="t('system.orgTemplate.thirdPartyPlatforms')"
><icon-question-circle ><icon-question-circle
:style="{ 'font-size': '16px' }" :style="{ 'font-size': '16px' }"

View File

@ -118,7 +118,6 @@
() => props.cardItem, () => props.cardItem,
(val) => { (val) => {
if (val) { if (val) {
debugger;
templateCardInfo.value = { ...props.cardItem }; templateCardInfo.value = { ...props.cardItem };
} }
}, },

View File

@ -164,4 +164,10 @@ export default {
'system.orgTemplate.defectContentTip': 'system.orgTemplate.defectContentTip':
'You can set the default value for the defect content and use it when creating', 'You can set the default value for the defect content and use it when creating',
'system.orgTemplate.templateNameRules': 'Please enter a template name', 'system.orgTemplate.templateNameRules': 'Please enter a template name',
'system.orgTemplate.deleteProjectTemplateTip':
'The existing use cases of this template will inherit the default template after deleting it. Are you sure to delete it',
'system.orgTemplate.moduleRuleTip': 'Please select module',
'system.orgTemplate.modules': 'module',
'system.orgTemplate.tags': 'tags',
'system.orgTemplate.optionKeyValue': 'Option KEY value',
}; };

View File

@ -156,4 +156,9 @@ export default {
'system.orgTemplate.defectNameTip': '可为缺陷名称设置默认值,创建时统一使用', 'system.orgTemplate.defectNameTip': '可为缺陷名称设置默认值,创建时统一使用',
'system.orgTemplate.defectContentTip': '可为缺陷内容设置默认值,创建时统一使用', 'system.orgTemplate.defectContentTip': '可为缺陷内容设置默认值,创建时统一使用',
'system.orgTemplate.templateNameRules': '请输入模板名称', 'system.orgTemplate.templateNameRules': '请输入模板名称',
'system.orgTemplate.deleteProjectTemplateTip': '删除后,该模版已有的用例会继承默认模版,确认删除吗?',
'system.orgTemplate.moduleRuleTip': '请选择模块',
'system.orgTemplate.modules': '模块',
'system.orgTemplate.tags': '标签',
'system.orgTemplate.optionKeyValue': '选项KEY值',
}; };