feat: 缺陷管理评论组件
This commit is contained in:
parent
3a42e95796
commit
8b54ddab04
|
@ -60,6 +60,7 @@
|
|||
"resize-observer-polyfill": "^1.5.1",
|
||||
"sortablejs": "^1.15.0",
|
||||
"vue": "^3.3.4",
|
||||
"vue-dompurify-html": "^4.1.4",
|
||||
"vue-draggable-plus": "^0.2.7",
|
||||
"vue-echarts": "^6.6.1",
|
||||
"vue-i18n": "^9.3.0",
|
||||
|
|
|
@ -0,0 +1,107 @@
|
|||
<template>
|
||||
<div class="flex flex-col">
|
||||
<div class="h-[40px] w-[40px] gap-[8px] rounded-full">
|
||||
<img
|
||||
src="https://p6-passport.byteacctimg.com/img/user-avatar/9a6e39ea689600e70175649a8cd14913~200x200.awebp"
|
||||
alt="User avatar"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<div class="text-[var(--color-text-1)]">{{ props.element.createUser }}</div>
|
||||
<div v-dompurify-html="props.element.content" class="mt-[4px]"></div>
|
||||
<div class="mt-[16px] flex flex-row items-center">
|
||||
<div class="text-[var(--color-text-4)]">{{
|
||||
dayjs(props.element.updateTime).format('YYYY-MM-DD HH:mm:ss')
|
||||
}}</div>
|
||||
<div class="ml-[24px] flex flex-row gap-[16px]">
|
||||
<div class="comment-btn" @click="expendChange">
|
||||
<MsIconfont type="icon-icon_comment_outlined" />
|
||||
<span>{{ !expendComment ? t('comment.expendComment') : t('comment.collapseComment') }}</span>
|
||||
<span class="text-[var(--color-text-4)]">({{ element.children?.length }})</span>
|
||||
</div>
|
||||
<div class="comment-btn" @click="replyClick">
|
||||
<MsIconfont type="icon-icon_reply" />
|
||||
<span>{{ t('comment.reply') }}</span>
|
||||
</div>
|
||||
<div class="comment-btn" @click="editClick">
|
||||
<MsIconfont type="icon-icon_edit_outlined" />
|
||||
<span>{{ t('comment.edit') }}</span>
|
||||
</div>
|
||||
<div class="comment-btn" @click="deleteClick">
|
||||
<MsIconfont type="icon-icon_delete-trash_outlined" />
|
||||
<span>{{ t('comment.delete') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
import MsIconfont from '@/components/pure/ms-icon-font/index.vue';
|
||||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
export interface CommentItem {
|
||||
id: string; // 评论id
|
||||
bugId: string; // bug id
|
||||
createUser: string; // 创建人
|
||||
updateTime: number; // 更新时间
|
||||
content: string;
|
||||
replyUser?: string; // 回复人
|
||||
notifier?: string; // 通知人
|
||||
children?: CommentItem[];
|
||||
}
|
||||
// 仅评论: ’COMMENT‘; 评论并@: ’AT‘; 回复评论/回复并@: ’REPLAY‘;)
|
||||
export type commentEvent = 'COMMENT' | 'AT' | 'REPLAY';
|
||||
|
||||
const props = defineProps<{
|
||||
element: CommentItem;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: 'reply'): void;
|
||||
(event: 'edit'): void;
|
||||
(event: 'delete'): void;
|
||||
}>();
|
||||
|
||||
const expendComment = ref(false);
|
||||
const isEdit = ref(false);
|
||||
|
||||
const expendChange = () => {
|
||||
expendComment.value = !expendComment.value;
|
||||
};
|
||||
const replyClick = () => {
|
||||
emit('reply');
|
||||
};
|
||||
|
||||
const editClick = () => {
|
||||
isEdit.value = true;
|
||||
emit('edit');
|
||||
};
|
||||
|
||||
const deleteClick = () => {
|
||||
emit('delete');
|
||||
};
|
||||
|
||||
const { t } = useI18n();
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.comment-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
color: var(--color-text-1);
|
||||
flex-direction: row;
|
||||
gap: 4px;
|
||||
cursor: pointer;
|
||||
:hover {
|
||||
background-color: var(--color-bg-2);
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,7 @@
|
|||
export default {
|
||||
'comment.expendComment': 'Expand comment',
|
||||
'comment.collapseComment': 'Collapse comment',
|
||||
'comment.reply': 'Reply',
|
||||
'comment.delete': 'Delete',
|
||||
'comment.edit': 'Edit',
|
||||
};
|
|
@ -0,0 +1,7 @@
|
|||
export default {
|
||||
'comment.expendComment': '展开评论',
|
||||
'comment.collapseComment': '收起评论',
|
||||
'comment.edit': '编辑',
|
||||
'comment.reply': '回复',
|
||||
'comment.delete': '删除',
|
||||
};
|
|
@ -132,6 +132,7 @@
|
|||
const uiTestSpan = ref(1);
|
||||
const apiTestSpan = ref(1);
|
||||
const loadTestSpan = ref(1);
|
||||
const personalSpan = ref(1);
|
||||
|
||||
// 表格的总全选
|
||||
const allChecked = ref(false);
|
||||
|
@ -203,6 +204,11 @@
|
|||
rowspan: loadTestSpan.value,
|
||||
};
|
||||
}
|
||||
if (record.isPersonal) {
|
||||
return {
|
||||
rowspan: personalSpan.value,
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -235,7 +241,7 @@
|
|||
permissions: child?.permissions,
|
||||
indeterminate,
|
||||
perChecked,
|
||||
ability: index === 0 ? t(`system.userGroup.${type}`) : undefined,
|
||||
ability: index === 0 ? item.name : undefined,
|
||||
operationObject: t(child.name),
|
||||
isSystem: index === 0 && type === 'SYSTEM',
|
||||
isOrganization: index === 0 && type === 'ORGANIZATION',
|
||||
|
@ -247,6 +253,7 @@
|
|||
isUiTest: index === 0 && type === 'UI_TEST',
|
||||
isLoadTest: index === 0 && type === 'LOAD_TEST',
|
||||
isApiTest: index === 0 && type === 'API_TEST',
|
||||
isPersonal: index === 0 && type === 'PERSONAL',
|
||||
});
|
||||
});
|
||||
return result;
|
||||
|
@ -255,26 +262,28 @@
|
|||
const transformData = (data: UserGroupAuthSetting[]) => {
|
||||
const result: AuthTableItem[] = [];
|
||||
data.forEach((item) => {
|
||||
if (item.type === 'SYSTEM') {
|
||||
if (item.id === 'SYSTEM') {
|
||||
systemSpan.value = item.children?.length || 0;
|
||||
} else if (item.type === 'PROJECT') {
|
||||
} else if (item.id === 'PROJECT') {
|
||||
projectSpan.value = item.children?.length || 0;
|
||||
} else if (item.type === 'ORGANIZATION') {
|
||||
} else if (item.id === 'ORGANIZATION') {
|
||||
organizationSpan.value = item.children?.length || 0;
|
||||
} else if (item.type === 'WORKSTATION') {
|
||||
} else if (item.id === 'WORKSTATION') {
|
||||
workstationSpan.value = item.children?.length || 0;
|
||||
} else if (item.type === 'TEST_PLAN') {
|
||||
} else if (item.id === 'TEST_PLAN') {
|
||||
testPlanSpan.value = item.children?.length || 0;
|
||||
} else if (item.type === 'BUG_MANAGEMENT') {
|
||||
} else if (item.id === 'BUG_MANAGEMENT') {
|
||||
bugManagementSpan.value = item.children?.length || 0;
|
||||
} else if (item.type === 'CASE_MANAGEMENT') {
|
||||
} else if (item.id === 'CASE_MANAGEMENT') {
|
||||
caseManagementSpan.value = item.children?.length || 0;
|
||||
} else if (item.type === 'UI_TEST') {
|
||||
} else if (item.id === 'UI_TEST') {
|
||||
uiTestSpan.value = item.children?.length || 0;
|
||||
} else if (item.type === 'API_TEST') {
|
||||
} else if (item.id === 'API_TEST') {
|
||||
apiTestSpan.value = item.children?.length || 0;
|
||||
} else if (item.type === 'LOAD_TEST') {
|
||||
} else if (item.id === 'LOAD_TEST') {
|
||||
loadTestSpan.value = item.children?.length || 0;
|
||||
} else if (item.id === 'PERSONAL') {
|
||||
personalSpan.value = item.children?.length || 0;
|
||||
}
|
||||
result.push(...makeData(item, item.id));
|
||||
});
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
:key="item.key"
|
||||
:value="item.key"
|
||||
class="mt-[8px] w-[95px] pl-[0px]"
|
||||
:disabled="item.key === 'name'"
|
||||
>
|
||||
<a-tooltip :content="item.text">
|
||||
<span class="one-line-text">{{ item.text }}</span>
|
||||
|
@ -77,10 +78,10 @@
|
|||
</a-checkbox-group>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-[270px]">
|
||||
<div class="optional-header">
|
||||
<div>
|
||||
<div class="optional-header min-w-[270px]">
|
||||
<div class="font-medium">{{ t('system.orgTemplate.selectedField') }}</div>
|
||||
<MsButton @click="clearHandler">{{ t('system.orgTemplate.clear') }}</MsButton>
|
||||
<MsButton @click="handleReset">{{ t('system.orgTemplate.clear') }}</MsButton>
|
||||
</div>
|
||||
<div class="p-[16px]">
|
||||
<VueDraggable v-model="selectedList" ghost-class="ghost">
|
||||
|
@ -94,6 +95,7 @@
|
|||
<div class="one-line-text ml-2 w-[178px]">{{ element.text }}</div>
|
||||
</a-tooltip>
|
||||
<icon-close
|
||||
v-if="element.key !== 'name'"
|
||||
:style="{ 'font-size': '14px' }"
|
||||
class="cursor-pointer text-[var(--color-text-3)]"
|
||||
@click="removeSelectedField(element.key)"
|
||||
|
@ -132,9 +134,14 @@
|
|||
interface MsExportDrawerProps {
|
||||
visible: boolean;
|
||||
allData: MsExportDrawerMap;
|
||||
// 默认选中的字段 keys
|
||||
defaultSelectedKeys?: string[];
|
||||
}
|
||||
|
||||
const props = defineProps<MsExportDrawerProps>();
|
||||
const props = withDefaults(defineProps<MsExportDrawerProps>(), {
|
||||
visible: false,
|
||||
defaultSelectedKeys: () => ['name', 'id', 'title', 'status', 'handle_user', 'content'],
|
||||
});
|
||||
|
||||
const selectedList = ref<MsExportDrawerOption[]>([]); // 已选字段
|
||||
const selectedIds = ref<string[]>([]); // 已选字段id
|
||||
|
@ -199,13 +206,17 @@
|
|||
return [...systemList.value, ...customList.value, ...otherList.value];
|
||||
});
|
||||
|
||||
const handleReset = () => {
|
||||
selectedList.value = allList.value.filter((item) => props.defaultSelectedKeys.includes(item.key));
|
||||
};
|
||||
|
||||
const handleDrawerConfirm = () => {
|
||||
emit('confirm', selectedList.value);
|
||||
};
|
||||
|
||||
const handleDrawerCancel = () => {
|
||||
visible.value = false;
|
||||
selectedList.value = [];
|
||||
handleReset();
|
||||
};
|
||||
|
||||
const isCheckedAll = computed(() => {
|
||||
|
@ -216,10 +227,6 @@
|
|||
return selectedList.value.length > 0 && selectedList.value.length < allList.value.length;
|
||||
});
|
||||
|
||||
const clearHandler = () => {
|
||||
selectedList.value = [];
|
||||
};
|
||||
|
||||
const handleChangeAll = (value: boolean | (string | number | boolean)[]) => {
|
||||
if (value) {
|
||||
selectedList.value = allList.value;
|
||||
|
@ -239,6 +246,10 @@
|
|||
watchEffect(() => {
|
||||
selectedIds.value = selectedList.value.map((item) => item.key);
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
handleReset();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
|
|
|
@ -16,6 +16,7 @@ import store from './store';
|
|||
import ArcoVueIcon from '@arco-design/web-vue/es/icon';
|
||||
import '@/assets/style/global.less';
|
||||
import localforage from 'localforage';
|
||||
import VueDOMPurifyHTML from 'vue-dompurify-html';
|
||||
|
||||
async function bootstrap() {
|
||||
const app = createApp(App);
|
||||
|
@ -26,6 +27,7 @@ async function bootstrap() {
|
|||
app.use(router);
|
||||
app.use(ArcoVue);
|
||||
app.use(ArcoVueIcon);
|
||||
app.use(VueDOMPurifyHTML);
|
||||
app.component('SvgIcon', SvgIcon);
|
||||
app.component('MsIcon', MsIcon);
|
||||
|
||||
|
|
|
@ -27,7 +27,8 @@ export type AuthScopeType =
|
|||
| 'CASE_MANAGEMENT'
|
||||
| 'API_TEST'
|
||||
| 'UI_TEST'
|
||||
| 'LOAD_TEST';
|
||||
| 'LOAD_TEST'
|
||||
| 'PERSONAL';
|
||||
|
||||
export interface UserGroupItem {
|
||||
// 组ID
|
||||
|
@ -121,7 +122,7 @@ export interface AuthTableItem {
|
|||
isApiTest?: boolean;
|
||||
isUiTest?: boolean;
|
||||
isLoadTest?: boolean;
|
||||
|
||||
isPersonal?: boolean;
|
||||
indeterminate?: boolean;
|
||||
}
|
||||
export interface SavePermissions {
|
||||
|
|
|
@ -0,0 +1,417 @@
|
|||
<template>
|
||||
<MsDetailDrawer
|
||||
ref="detailDrawerRef"
|
||||
v-model:visible="showDrawerVisible"
|
||||
:width="1200"
|
||||
:footer="false"
|
||||
:title="t('caseManagement.featureCase.caseDetailTitle', { id: detailInfo?.id, name: detailInfo?.name })"
|
||||
:detail-id="props.detailId"
|
||||
:detail-index="props.detailIndex"
|
||||
:get-detail-func="getCaseDetail"
|
||||
:pagination="props.pagination"
|
||||
:table-data="props.tableData"
|
||||
:page-change="props.pageChange"
|
||||
@loaded="loadedCase"
|
||||
>
|
||||
<template #titleRight="{ loading }">
|
||||
<div class="rightButtons flex items-center">
|
||||
<MsButton
|
||||
type="icon"
|
||||
status="secondary"
|
||||
class="mr-4 !rounded-[var(--border-radius-small)]"
|
||||
:disabled="loading"
|
||||
:loading="editLoading"
|
||||
@click="updateHandler('edit')"
|
||||
>
|
||||
<MsIcon type="icon-icon_edit_outlined" class="mr-1 font-[16px]" />
|
||||
{{ t('common.edit') }}
|
||||
</MsButton>
|
||||
<MsButton
|
||||
type="icon"
|
||||
status="secondary"
|
||||
class="mr-4 !rounded-[var(--border-radius-small)]"
|
||||
:disabled="loading"
|
||||
:loading="shareLoading"
|
||||
@click="shareHandler"
|
||||
>
|
||||
<MsIcon type="icon-icon_share1" class="mr-1 font-[16px]" />
|
||||
{{ t('caseManagement.featureCase.share') }}
|
||||
</MsButton>
|
||||
<MsButton
|
||||
type="icon"
|
||||
status="secondary"
|
||||
class="mr-4 !rounded-[var(--border-radius-small)]"
|
||||
:disabled="loading"
|
||||
:loading="followLoading"
|
||||
@click="followHandler"
|
||||
>
|
||||
<MsIcon
|
||||
:type="detailInfo.followFlag ? 'icon-icon_collect_filled' : 'icon-icon_collection_outlined'"
|
||||
class="mr-1 font-[16px]"
|
||||
:class="[detailInfo.followFlag ? 'text-[rgb(var(--warning-6))]' : '']"
|
||||
/>
|
||||
{{ t('caseManagement.featureCase.follow') }}
|
||||
</MsButton>
|
||||
<MsButton type="icon" status="secondary" class="!rounded-[var(--border-radius-small)]">
|
||||
<a-dropdown position="br" :hide-on-select="false">
|
||||
<div>
|
||||
<icon-more class="mr-1" />
|
||||
<span> {{ t('caseManagement.featureCase.more') }}</span>
|
||||
</div>
|
||||
<template #content>
|
||||
<a-doption class="error-6 text-[rgb(var(--danger-6))]" @click="deleteHandler()">
|
||||
<MsIcon type="icon-icon_delete-trash_outlined" class="font-[16px] text-[rgb(var(--danger-6))]" />
|
||||
{{ t('common.delete') }}
|
||||
</a-doption>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</MsButton>
|
||||
<MsButton type="icon" status="secondary" class="!rounded-[var(--border-radius-small)]" @click="toggle">
|
||||
<MsIcon :type="isFullscreen ? 'icon-icon_off_screen' : 'icon-icon_full_screen_one'" class="mr-1" size="16" />
|
||||
{{ t('caseManagement.featureCase.fullScreen') }}
|
||||
</MsButton>
|
||||
</div>
|
||||
</template>
|
||||
<template #default>
|
||||
<div ref="wrapperRef" class="h-full bg-white">
|
||||
<MsSplitBox ref="wrapperRef" expand-direction="right" :max="0.7" :min="0.7" :size="900">
|
||||
<template #left>
|
||||
<div class="leftWrapper h-full">
|
||||
<div class="header h-[50px]">
|
||||
<a-tabs>
|
||||
<a-tab-pane key="detail">
|
||||
<BugDetailTab :detail-info="detailInfo" />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="case">
|
||||
<BugCaseTab :detail-info="detailInfo" />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="comment">
|
||||
<BugCommentTab :detail-info="detailInfo" />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="history">
|
||||
<BugHistoryTab :detail-info="detailInfo" />
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #right>
|
||||
<div class="rightWrapper p-[24px]">
|
||||
<div class="mb-4 font-medium">{{ t('caseManagement.featureCase.basicInfo') }}</div>
|
||||
<div class="baseItem">
|
||||
<span class="label"> {{ t('caseManagement.featureCase.tableColumnModule') }}</span>
|
||||
<span>{{ moduleName }}</span>
|
||||
</div>
|
||||
<!-- 自定义字段开始 -->
|
||||
<MsFormCreate
|
||||
v-if="formRules.length"
|
||||
ref="formCreateRef"
|
||||
class="w-full"
|
||||
:option="options"
|
||||
:form-rule="formRules"
|
||||
:form-create-key="FormCreateKeyEnum.CASE_CUSTOM_ATTRS_DETAIL"
|
||||
/>
|
||||
<!-- 自定义字段结束 -->
|
||||
<div class="baseItem">
|
||||
<span class="label"> {{ t('caseManagement.featureCase.tableColumnCreateUser') }}</span>
|
||||
<span>{{ detailInfo?.createUser }}</span>
|
||||
</div>
|
||||
<div class="baseItem">
|
||||
<span class="label"> {{ t('caseManagement.featureCase.tableColumnCreateTime') }}</span>
|
||||
<span>{{ dayjs(detailInfo?.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</MsSplitBox>
|
||||
</div>
|
||||
</template>
|
||||
</MsDetailDrawer>
|
||||
<SettingDrawer v-model:visible="showSettingDrawer" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useFullscreen } from '@vueuse/core';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
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 MsSplitBox from '@/components/pure/ms-split-box/index.vue';
|
||||
import type { MsPaginationI } from '@/components/pure/ms-table/type';
|
||||
import MsDetailDrawer from '@/components/business/ms-detail-drawer/index.vue';
|
||||
import BugDetailTab from './bugDetailTab.vue';
|
||||
|
||||
import { deleteCaseRequest, followerCaseRequest, getCaseDetail } from '@/api/modules/case-management/featureCase';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useModal from '@/hooks/useModal';
|
||||
import { useAppStore } from '@/store';
|
||||
import useFeatureCaseStore from '@/store/modules/case/featureCase';
|
||||
import useUserStore from '@/store/modules/user';
|
||||
import { characterLimit, findNodeByKey } from '@/utils';
|
||||
|
||||
import type { CaseManagementTable, CustomAttributes, TabItemType } from '@/models/caseManagement/featureCase';
|
||||
import { FormCreateKeyEnum } from '@/enums/formCreateEnum';
|
||||
import { CaseManagementRouteEnum } from '@/enums/routeEnum';
|
||||
|
||||
const router = useRouter();
|
||||
const detailDrawerRef = ref<InstanceType<typeof MsDetailDrawer>>();
|
||||
const wrapperRef = ref();
|
||||
const { isFullscreen, toggle } = useFullscreen(wrapperRef);
|
||||
const featureCaseStore = useFeatureCaseStore();
|
||||
const userStore = useUserStore();
|
||||
const { t } = useI18n();
|
||||
const { openModal } = useModal();
|
||||
|
||||
const props = defineProps<{
|
||||
visible: boolean;
|
||||
detailId: string; // 详情 id
|
||||
detailIndex: number; // 详情 下标
|
||||
tableData: any[]; // 表格数据
|
||||
pagination: MsPaginationI; // 分页器对象
|
||||
pageChange: (page: number) => Promise<void>; // 分页变更函数
|
||||
}>();
|
||||
|
||||
const emit = defineEmits(['update:visible']);
|
||||
|
||||
const userId = computed(() => userStore.userInfo.id);
|
||||
const appStore = useAppStore();
|
||||
|
||||
const currentProjectId = computed(() => appStore.currentProjectId);
|
||||
|
||||
const showDrawerVisible = ref<boolean>(false);
|
||||
|
||||
const showSettingDrawer = ref<boolean>(false);
|
||||
function showMenuSetting() {
|
||||
showSettingDrawer.value = true;
|
||||
}
|
||||
|
||||
const tabSettingList = computed(() => {
|
||||
return featureCaseStore.tabSettingList;
|
||||
});
|
||||
|
||||
const tabSetting = ref<TabItemType[]>([...tabSettingList.value]);
|
||||
const activeTab = ref<string | number>('detail');
|
||||
function changeTabs(key: string | number) {
|
||||
activeTab.value = key;
|
||||
switch (activeTab.value) {
|
||||
case 'setting':
|
||||
showMenuSetting();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const detailInfo = ref<Record<string, any>>({});
|
||||
const customFields = ref<CustomAttributes[]>([]);
|
||||
|
||||
function loadedCase(detail: CaseManagementTable) {
|
||||
detailInfo.value = { ...detail };
|
||||
customFields.value = detailInfo.value.customFields;
|
||||
}
|
||||
|
||||
const moduleName = computed(() => {
|
||||
return findNodeByKey<Record<string, any>>(featureCaseStore.caseTree, detailInfo.value?.moduleId as string, 'id')
|
||||
?.name;
|
||||
});
|
||||
|
||||
const editLoading = ref<boolean>(false);
|
||||
|
||||
function updateSuccess() {
|
||||
detailDrawerRef.value?.initDetail();
|
||||
}
|
||||
|
||||
function updateHandler(type: string) {
|
||||
router.push({
|
||||
name: CaseManagementRouteEnum.CASE_MANAGEMENT_CASE_DETAIL,
|
||||
query: {
|
||||
id: detailInfo.value.id,
|
||||
},
|
||||
params: {
|
||||
mode: type,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const shareLoading = ref<boolean>(false);
|
||||
|
||||
function shareHandler() {}
|
||||
|
||||
const followLoading = ref<boolean>(false);
|
||||
// 关注
|
||||
async function followHandler() {
|
||||
followLoading.value = true;
|
||||
try {
|
||||
await followerCaseRequest({ userId: userId.value as string, functionalCaseId: detailInfo.value.id });
|
||||
updateSuccess();
|
||||
Message.success(
|
||||
detailInfo.value.followFlag
|
||||
? t('caseManagement.featureCase.cancelFollowSuccess')
|
||||
: t('caseManagement.featureCase.followSuccess')
|
||||
);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
} finally {
|
||||
followLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 删除用例
|
||||
function deleteHandler() {
|
||||
const { id, name } = detailInfo.value;
|
||||
openModal({
|
||||
type: 'error',
|
||||
title: t('caseManagement.featureCase.deleteCaseTitle', { name: characterLimit(name) }),
|
||||
content: t('caseManagement.featureCase.beforeDeleteCase'),
|
||||
okText: t('common.confirmDelete'),
|
||||
cancelText: t('common.cancel'),
|
||||
okButtonProps: {
|
||||
status: 'danger',
|
||||
},
|
||||
onBeforeOk: async () => {
|
||||
try {
|
||||
const params = {
|
||||
id,
|
||||
deleteAll: false,
|
||||
projectId: currentProjectId.value,
|
||||
};
|
||||
await deleteCaseRequest(params);
|
||||
Message.success(t('common.deleteSuccess'));
|
||||
updateSuccess();
|
||||
detailDrawerRef.value?.openPrevDetail();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
},
|
||||
hideCancel: false,
|
||||
});
|
||||
}
|
||||
|
||||
const formRules = ref<FormItem[]>([]);
|
||||
|
||||
const isDisabled = ref<boolean>(false);
|
||||
|
||||
// 表单配置项
|
||||
const options = {
|
||||
resetBtn: false, // 不展示默认配置的重置和提交
|
||||
submitBtn: false,
|
||||
on: false, // 取消绑定on事件
|
||||
form: {
|
||||
layout: 'horizontal',
|
||||
labelAlign: 'left',
|
||||
labelColProps: {
|
||||
span: 9,
|
||||
},
|
||||
wrapperColProps: {
|
||||
span: 15,
|
||||
},
|
||||
},
|
||||
// 暂时默认
|
||||
row: {
|
||||
gutter: 0,
|
||||
},
|
||||
wrap: {
|
||||
'asterisk-position': 'end',
|
||||
'validate-trigger': ['change'],
|
||||
'hide-asterisk': true,
|
||||
},
|
||||
};
|
||||
|
||||
// 初始化表单
|
||||
function initForm() {
|
||||
formRules.value = customFields.value.map((item: any) => {
|
||||
return {
|
||||
type: item.type,
|
||||
name: item.fieldId,
|
||||
label: item.fieldName,
|
||||
value: JSON.parse(item.defaultValue),
|
||||
required: item.required,
|
||||
options: item.options || [],
|
||||
props: {
|
||||
modelValue: JSON.parse(item.defaultValue),
|
||||
disabled: isDisabled.value,
|
||||
options: item.options || [],
|
||||
},
|
||||
};
|
||||
}) as FormItem[];
|
||||
}
|
||||
|
||||
watch(
|
||||
() => customFields.value,
|
||||
() => {
|
||||
initForm();
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(val) => {
|
||||
showDrawerVisible.value = val;
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => showDrawerVisible.value,
|
||||
(val) => {
|
||||
emit('update:visible', val);
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => tabSettingList.value,
|
||||
() => {
|
||||
tabSetting.value = featureCaseStore.getTab();
|
||||
},
|
||||
{ deep: true, immediate: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.leftWrapper {
|
||||
.header {
|
||||
padding: 0 16px;
|
||||
border-bottom: 1px solid var(--color-text-n8);
|
||||
}
|
||||
}
|
||||
.rightWrapper {
|
||||
.baseItem {
|
||||
margin-bottom: 16px;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
@apply flex;
|
||||
.label {
|
||||
width: 38%;
|
||||
color: var(--color-text-3);
|
||||
}
|
||||
}
|
||||
:deep(.arco-form-item-layout-horizontal) {
|
||||
margin-bottom: 16px !important;
|
||||
}
|
||||
:deep(.arco-form-item-label-col > .arco-form-item-label) {
|
||||
color: var(--color-text-3) !important;
|
||||
}
|
||||
}
|
||||
.rightButtons {
|
||||
:deep(.ms-button--secondary):hover,
|
||||
:hover > .arco-icon {
|
||||
color: rgb(var(--primary-5)) !important;
|
||||
background: var(--color-bg-3);
|
||||
.arco-icon:hover {
|
||||
color: rgb(var(--primary-5)) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
.error-6 {
|
||||
color: rgb(var(--danger-6));
|
||||
&:hover {
|
||||
color: rgb(var(--danger-6));
|
||||
}
|
||||
}
|
||||
:deep(.active .arco-badge-number) {
|
||||
background: rgb(var(--primary-5));
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,186 @@
|
|||
<template>
|
||||
<MsDrawer
|
||||
:width="680"
|
||||
:visible="currentVisible"
|
||||
unmount-on-close
|
||||
:footer="false"
|
||||
:title="t('system.organization.addMember')"
|
||||
:mask="false"
|
||||
@cancel="handleCancel"
|
||||
>
|
||||
<div>
|
||||
<div class="flex flex-row justify-between">
|
||||
<a-dropdown trigger="hover">
|
||||
<template #content>
|
||||
<a-doption @click="showRelatedDrawer('api')">{{ t('bugManagement.detail.apiCase') }}</a-doption>
|
||||
<a-doption @click="showRelatedDrawer('scenario')">{{ t('bugManagement.detail.scenarioCase') }}</a-doption>
|
||||
<a-doption @click="showRelatedDrawer('ui')">{{ t('bugManagement.detail.uiCase') }}</a-doption>
|
||||
<a-doption @click="showRelatedDrawer('performance')">{{
|
||||
t('bugManagement.detail.performanceCase')
|
||||
}}</a-doption>
|
||||
</template>
|
||||
<a-button type="primary">{{ t('bugManagement.edit.linkCase') }}</a-button>
|
||||
</a-dropdown>
|
||||
<a-input-search
|
||||
v-model:model-value="keyword"
|
||||
allow-clear
|
||||
:placeholder="t('bugManagement.detail.searchCase')"
|
||||
class="w-[230px]"
|
||||
@search="searchUser"
|
||||
@press-enter="searchUser"
|
||||
></a-input-search>
|
||||
</div>
|
||||
<ms-base-table class="mt-[16px]" v-bind="propsRes" v-on="propsEvent">
|
||||
<template #name="{ record }">
|
||||
<span>{{ record.name }}</span>
|
||||
<span v-if="record.adminFlag" class="ml-[4px] text-[var(--color-text-4)]">{{
|
||||
`(${t('common.admin')})`
|
||||
}}</span>
|
||||
</template>
|
||||
<template #operation="{ record }">
|
||||
<MsRemoveButton
|
||||
:title="t('system.organization.removeName', { name: record.name })"
|
||||
:sub-title-tip="t('system.organization.removeTip')"
|
||||
@ok="handleRemove(record)"
|
||||
/>
|
||||
</template>
|
||||
</ms-base-table>
|
||||
</div>
|
||||
<MsDrawer
|
||||
:width="680"
|
||||
:visible="relatedVisible"
|
||||
unmount-on-close
|
||||
:footer="false"
|
||||
:mask="false"
|
||||
@cancel="relatedVisible = false"
|
||||
>
|
||||
<template #title>
|
||||
<div class="flex flex-row items-center gap-[4px]">
|
||||
<div>{{ t('bugManagement.detail.relatedCase') }}</div>
|
||||
<a-select>
|
||||
<a-option></a-option>
|
||||
<a-option></a-option>
|
||||
<a-option></a-option>
|
||||
</a-select>
|
||||
</div>
|
||||
</template>
|
||||
</MsDrawer>
|
||||
</MsDrawer>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch } from 'vue';
|
||||
import { Message, TableData } from '@arco-design/web-vue';
|
||||
|
||||
import MsDrawer from '@/components/pure/ms-drawer/index.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 MsRemoveButton from '@/components/business/ms-remove-button/MsRemoveButton.vue';
|
||||
|
||||
import { deleteProjectMemberByOrg, postProjectMemberByProjectId } from '@/api/modules/setting/organizationAndProject';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
export interface projectDrawerProps {
|
||||
visible: boolean;
|
||||
organizationId?: string;
|
||||
projectId?: string;
|
||||
}
|
||||
const { t } = useI18n();
|
||||
const props = defineProps<projectDrawerProps>();
|
||||
const emit = defineEmits<{
|
||||
(e: 'cancel'): void;
|
||||
(e: 'requestFetchData'): void;
|
||||
}>();
|
||||
|
||||
const relatedVisible = ref(false);
|
||||
const relatedType = ref('api');
|
||||
|
||||
const showRelatedDrawer = (type: string) => {
|
||||
relatedType.value = type;
|
||||
};
|
||||
|
||||
const currentVisible = ref(props.visible);
|
||||
|
||||
const keyword = ref('');
|
||||
|
||||
const projectColumn: MsTableColumn = [
|
||||
{
|
||||
title: 'system.organization.userName',
|
||||
slotName: 'name',
|
||||
dataIndex: 'name',
|
||||
showTooltip: true,
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: 'system.organization.email',
|
||||
dataIndex: 'email',
|
||||
showTooltip: true,
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: 'system.organization.phone',
|
||||
dataIndex: 'phone',
|
||||
},
|
||||
{ title: 'system.organization.operation', slotName: 'operation' },
|
||||
];
|
||||
|
||||
const { propsRes, propsEvent, loadList, setLoadListParams, setKeyword } = useTable(postProjectMemberByProjectId, {
|
||||
heightUsed: 240,
|
||||
columns: projectColumn,
|
||||
scroll: { x: '100%' },
|
||||
selectable: false,
|
||||
noDisable: false,
|
||||
pageSimple: true,
|
||||
});
|
||||
|
||||
async function searchUser() {
|
||||
setKeyword(keyword.value);
|
||||
await loadList();
|
||||
}
|
||||
|
||||
const handleCancel = () => {
|
||||
keyword.value = '';
|
||||
emit('cancel');
|
||||
};
|
||||
|
||||
const fetchData = async () => {
|
||||
await loadList();
|
||||
};
|
||||
|
||||
const handleRemove = async (record: TableData) => {
|
||||
try {
|
||||
if (props.projectId) {
|
||||
await deleteProjectMemberByOrg(props.projectId, record.id);
|
||||
}
|
||||
Message.success(t('common.removeSuccess'));
|
||||
fetchData();
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
watch(
|
||||
() => props.projectId,
|
||||
() => {
|
||||
setLoadListParams({ projectId: props.projectId });
|
||||
fetchData();
|
||||
}
|
||||
);
|
||||
watch(
|
||||
() => props.visible,
|
||||
(visible) => {
|
||||
currentVisible.value = visible;
|
||||
if (visible) {
|
||||
fetchData();
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
:deep(.custom-height) {
|
||||
height: 100vh !important;
|
||||
border: 1px solid red;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,36 @@
|
|||
<template>
|
||||
<div class="bug-comment">
|
||||
<div class="header-icon">
|
||||
<img :src="comment.user.avatar" alt="User avatar" />
|
||||
</div>
|
||||
<div class="content">
|
||||
<h3>{{ comment.user.name }}</h3>
|
||||
<MsRichText :text="comment.content" />
|
||||
</div>
|
||||
<div class="footer">
|
||||
<span>{{ comment.date }}</span>
|
||||
<button @click="toggleShowComment">{{ showComment ? '收起评论' : '展开评论' }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
|
||||
import MsRichText from '@/components/pure/ms-rich-text/MsRichText.vue';
|
||||
|
||||
const showComment = ref(false);
|
||||
|
||||
const toggleShowComment = () => {
|
||||
showComment.value = !showComment.value;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.bug-comment {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 16px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,124 @@
|
|||
<template>
|
||||
<div class="p-[16px]">
|
||||
<div class="header">
|
||||
<div class="header-title">{{ t('bugManagement.edit.content') }}</div>
|
||||
<div class="header-action">
|
||||
<a-button>
|
||||
<template #icon> <MsIconfont type="icon-icon_edit_outlined" /> </template>
|
||||
{{ t('bugManagement.edit.contentEdit') }}
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="header">
|
||||
<div class="header-title">{{ t('bugManagement.edit.content') }}</div>
|
||||
</div>
|
||||
<div class="mt-[8]" :class="{ 'max-h-[260px]': contentEditAble }">
|
||||
<MsRichText
|
||||
v-if="form.content"
|
||||
v-model:model-value="form.content"
|
||||
:disabled="!contentEditAble"
|
||||
:placeholder="t('bugManagement.edit.contentPlaceholder')"
|
||||
/>
|
||||
<div v-else>-</div>
|
||||
</div>
|
||||
</div>
|
||||
<a-dropdown trigger="hover">
|
||||
<template #content>
|
||||
<MsUpload
|
||||
v-model:file-list="fileList"
|
||||
:auto-upload="false"
|
||||
multiple
|
||||
draggable
|
||||
accept="unknown"
|
||||
is-limit
|
||||
size-unit="MB"
|
||||
:max-size="500"
|
||||
>
|
||||
<a-doption>{{ t('bugManagement.edit.localUpload') }}</a-doption>
|
||||
</MsUpload>
|
||||
<a-doption @click="handleLineFile">{{ t('bugManagement.edit.linkFile') }}</a-doption>
|
||||
</template>
|
||||
<a-button type="outline">
|
||||
<template #icon>
|
||||
<icon-plus />
|
||||
</template>
|
||||
{{ t('bugManagement.edit.uploadFile') }}
|
||||
</a-button>
|
||||
</a-dropdown>
|
||||
<div class="mb-[8px] mt-[2px] text-[var(--color-text-4)]">{{ t('bugManagement.edit.fileExtra') }}</div>
|
||||
<FileList
|
||||
:show-tab="false"
|
||||
:file-list="fileList"
|
||||
:upload-func="uploadFile"
|
||||
@delete-file="deleteFile"
|
||||
@reupload="reupload"
|
||||
@handle-preview="handlePreview"
|
||||
>
|
||||
</FileList>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { FileItem } from '@arco-design/web-vue';
|
||||
|
||||
import MsIconfont from '@/components/pure/ms-icon-font/index.vue';
|
||||
import MsRichText from '@/components/pure/ms-rich-text/MsRichText.vue';
|
||||
import FileList from '@/components/pure/ms-upload/fileList.vue';
|
||||
import MsUpload from '@/components/pure/ms-upload/index.vue';
|
||||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const contentEditAble = ref(false);
|
||||
const fileList = ref<FileItem[]>([]);
|
||||
const form = ref({
|
||||
content: '',
|
||||
fileList: [],
|
||||
});
|
||||
const uploadFile = (file: File) => {
|
||||
const fileItem: FileItem = {
|
||||
uid: `${Date.now()}`,
|
||||
name: file.name,
|
||||
status: 'init',
|
||||
file,
|
||||
};
|
||||
fileList.value.push(fileItem);
|
||||
return Promise.resolve(fileItem);
|
||||
};
|
||||
const handlePreview = (item: FileItem) => {
|
||||
const { url } = item;
|
||||
window.open(url);
|
||||
};
|
||||
|
||||
const deleteFile = (item: FileItem) => {
|
||||
fileList.value = fileList.value.filter((e) => e.uid !== item.uid);
|
||||
};
|
||||
|
||||
const reupload = (item: FileItem) => {
|
||||
fileList.value = fileList.value.map((e) => {
|
||||
if (e.uid === item.uid) {
|
||||
return {
|
||||
...e,
|
||||
status: 'init',
|
||||
};
|
||||
}
|
||||
return e;
|
||||
});
|
||||
};
|
||||
|
||||
const handleLineFile = () => {};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
&-title {
|
||||
color: var(--color-text-1);
|
||||
}
|
||||
&-action {
|
||||
color: rgb(var(--primary-7));
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -24,23 +24,30 @@
|
|||
<MsRichText v-model="form.content" />
|
||||
</a-form-item>
|
||||
<div class="mb-[8px] text-[var(--color-text-1)]">{{ t('bugManagement.edit.file') }}</div>
|
||||
<MsUpload
|
||||
v-model:file-list="fileList"
|
||||
:auto-upload="false"
|
||||
multiple
|
||||
draggable
|
||||
accept="unknown"
|
||||
is-limit
|
||||
size-unit="MB"
|
||||
:max-size="500"
|
||||
>
|
||||
|
||||
<a-dropdown trigger="hover">
|
||||
<template #content>
|
||||
<MsUpload
|
||||
v-model:file-list="fileList"
|
||||
:auto-upload="false"
|
||||
multiple
|
||||
draggable
|
||||
accept="unknown"
|
||||
is-limit
|
||||
size-unit="MB"
|
||||
:max-size="500"
|
||||
>
|
||||
<a-doption>{{ t('bugManagement.edit.localUpload') }}</a-doption>
|
||||
</MsUpload>
|
||||
<a-doption @click="handleLineFile">{{ t('bugManagement.edit.linkFile') }}</a-doption>
|
||||
</template>
|
||||
<a-button type="outline">
|
||||
<template #icon>
|
||||
<icon-plus />
|
||||
</template>
|
||||
{{ t('bugManagement.edit.uploadFile') }}
|
||||
</a-button>
|
||||
</MsUpload>
|
||||
</a-dropdown>
|
||||
<div class="mb-[8px] mt-[2px] text-[var(--color-text-4)]">{{ t('bugManagement.edit.fileExtra') }}</div>
|
||||
<FileList
|
||||
:show-tab="false"
|
||||
|
@ -189,6 +196,8 @@
|
|||
return Promise.resolve(fileItem);
|
||||
};
|
||||
|
||||
const handleLineFile = () => {};
|
||||
|
||||
onBeforeMount(() => {
|
||||
getTemplateOptions();
|
||||
});
|
||||
|
|
|
@ -10,6 +10,9 @@
|
|||
</template>
|
||||
</MsAdvanceFilter>
|
||||
<MsBaseTable v-bind="propsRes" v-on="propsEvent">
|
||||
<template #name="{ record, rowIndex }">
|
||||
<a-button type="text" class="px-0" @click="handleShowDetail(record.id, rowIndex)">{{ record.name }}</a-button>
|
||||
</template>
|
||||
<template #numberOfCase="{ record }">
|
||||
<span class="cursor-pointer text-[rgb(var(--primary-5))]" @click="jumpToTestPlan(record)">{{
|
||||
record.memberCount
|
||||
|
@ -67,6 +70,14 @@
|
|||
</div>
|
||||
</a-modal>
|
||||
<MsExportDrawer v-model:visible="exportVisible" :all-data="exportOptionData" />
|
||||
<BugDetailDrawer
|
||||
v-model:visible="detailVisible"
|
||||
:detail-id="activeDetailId"
|
||||
:detail-index="activeCaseIndex"
|
||||
:table-data="propsRes.data"
|
||||
:page-change="propsEvent.pageChange"
|
||||
:pagination="propsRes.msPagination!"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
@ -81,6 +92,7 @@
|
|||
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 BugDetailDrawer from './components/bug-detail-drawer.vue';
|
||||
|
||||
import { getBugList, getExportConfig } from '@/api/modules/bug-management';
|
||||
import { updateOrAddProjectUserGroup } from '@/api/modules/project-management/usergroup';
|
||||
|
@ -101,6 +113,10 @@
|
|||
const syncVisible = ref(false);
|
||||
const exportVisible = ref(false);
|
||||
const exportOptionData = ref<MsExportDrawerMap>({});
|
||||
const detailVisible = ref(false);
|
||||
const activeDetailId = ref<string>('');
|
||||
const activeCaseIndex = ref<number>(0);
|
||||
|
||||
const syncObject = reactive({
|
||||
time: '',
|
||||
operator: '',
|
||||
|
@ -259,6 +275,12 @@
|
|||
syncVisible.value = true;
|
||||
};
|
||||
|
||||
const handleShowDetail = (id: string, rowIndex: number) => {
|
||||
detailVisible.value = true;
|
||||
activeDetailId.value = id;
|
||||
activeCaseIndex.value = rowIndex;
|
||||
};
|
||||
|
||||
const handleCopy = (record: BugListItem) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('create', record);
|
||||
|
|
|
@ -37,6 +37,17 @@ export default {
|
|||
statusIsRequired: '状态不能为空',
|
||||
severityPlaceholder: '请选择严重程度',
|
||||
uploadFile: '添加附件',
|
||||
localUpload: '本地上传',
|
||||
linkFile: '关联文件',
|
||||
contentEdit: '内容编辑',
|
||||
linkCase: '关联用例',
|
||||
},
|
||||
detail: {
|
||||
apiCase: '接口用例',
|
||||
scenarioCase: '场景用例',
|
||||
uiCase: 'UI用例',
|
||||
performanceCase: '性能用例',
|
||||
searchCase: '通过名称搜索',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -47,6 +47,7 @@ export default {
|
|||
UI_TEST: 'UI test',
|
||||
API_TEST: 'API test',
|
||||
LOAD_TEST: 'Performance test',
|
||||
PERSONAL: 'Personal',
|
||||
isDeleteUserGroup: 'Delete or not: {name}?',
|
||||
beforeDeleteUserGroup:
|
||||
'After deletion, the project data under the organization will be deleted together. Please operate with caution!',
|
||||
|
|
|
@ -47,6 +47,7 @@ export default {
|
|||
UI_TEST: 'UI测试',
|
||||
API_TEST: 'API测试',
|
||||
LOAD_TEST: '性能测试',
|
||||
PERSONAL: '我的设置',
|
||||
isDeleteUserGroup: '是否删除: {name}?',
|
||||
beforeDeleteUserGroup: '删除后,该组织下的项目数据将一起删除,请谨慎操作!',
|
||||
confirmDelete: '确认删除',
|
||||
|
|
Loading…
Reference in New Issue