parent
8d87368469
commit
89bd4836d2
|
@ -1,7 +1,8 @@
|
|||
import MSR from '@/api/http/index';
|
||||
import * as bugURL from '@/api/requrls/bug-management';
|
||||
|
||||
import { BugExportParams, BugListItem, DefaultTemplate } from '@/models/bug-management';
|
||||
import { BugEditFormObject, BugExportParams, BugListItem } from '@/models/bug-management';
|
||||
import { AssociatedList } from '@/models/caseManagement/featureCase';
|
||||
import { CommonList, TableQueryParams, TemplateOption } from '@/models/common';
|
||||
|
||||
/**
|
||||
|
@ -12,17 +13,35 @@ import { CommonList, TableQueryParams, TemplateOption } from '@/models/common';
|
|||
export function getBugList(data: TableQueryParams) {
|
||||
return MSR.post<CommonList<BugListItem>>({ url: bugURL.postTableListUrl, data });
|
||||
}
|
||||
|
||||
export function updateBug(data: TableQueryParams) {
|
||||
return MSR.post({ url: bugURL.postUpdateBugUrl, data });
|
||||
/**
|
||||
* 更新Bug
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
export function updateBug(data: { request: BugEditFormObject; fileList: File[] }) {
|
||||
return MSR.uploadFile({ url: bugURL.postUpdateBugUrl }, data, '', true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量更新
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
export function updateBatchBug(data: TableQueryParams) {
|
||||
return MSR.post({ url: bugURL.postBatchUpdateBugUrl, data });
|
||||
}
|
||||
|
||||
export function createBug(data: TableQueryParams) {
|
||||
return MSR.uploadFile({ url: bugURL.postCreateBugUrl }, { request: data.request, fileList: data.fileList }, '');
|
||||
/**
|
||||
* 创建Bug
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
export function createBug(data: { request: BugEditFormObject; fileList: File[] }) {
|
||||
return MSR.uploadFile({ url: bugURL.postCreateBugUrl }, data, '', true);
|
||||
}
|
||||
/**
|
||||
* 获取 bug 详情
|
||||
*/
|
||||
export function getBugDetail(id: string) {
|
||||
return MSR.get({ url: `${bugURL.getBugDetailUrl}${id}` });
|
||||
}
|
||||
|
||||
export function deleteSingleBug(data: TableQueryParams) {
|
||||
|
@ -38,14 +57,14 @@ export function getTemplageOption(params: { projectId: string }) {
|
|||
}
|
||||
|
||||
export function getTemplateById(data: TableQueryParams) {
|
||||
return MSR.get({ url: bugURL.getTemplateUrl, data });
|
||||
return MSR.post({ url: bugURL.getTemplateUrl, data });
|
||||
}
|
||||
// 获取导出字段配置
|
||||
export function getExportConfig(projectId: string) {
|
||||
return MSR.get({ url: `${bugURL.getExportConfigUrl}${projectId}` });
|
||||
}
|
||||
// 获取模版详情
|
||||
export function getTemplateDetailInfo(data: DefaultTemplate) {
|
||||
export function getTemplateDetailInfo(data: { id: string; projectId: string }) {
|
||||
return MSR.post({ url: `${bugURL.getTemplateDetailUrl}`, data });
|
||||
}
|
||||
|
||||
|
@ -58,3 +77,7 @@ export function syncBugOpenSource(params: { projectId: string }) {
|
|||
export function exportBug(data: BugExportParams) {
|
||||
return MSR.post({ url: bugURL.postExportBugUrl, data });
|
||||
}
|
||||
// 获取关联文件列表
|
||||
export function getAssociatedFileList(data: TableQueryParams) {
|
||||
return MSR.post<CommonList<AssociatedList>>({ url: bugURL.postAssociatedFileListUrl, data });
|
||||
}
|
||||
|
|
|
@ -4,9 +4,11 @@ export const postBatchUpdateBugUrl = '/bug/batch-update';
|
|||
export const postCreateBugUrl = '/bug/add';
|
||||
export const getDeleteBugUrl = '/bug/delete/';
|
||||
export const postBatchDeleteBugUrl = '/bug/batch-delete';
|
||||
export const getTemplateUrl = '/bug/template';
|
||||
export const getTemplateUrl = '/bug/template/detail';
|
||||
export const getTemplageOption = '/bug/template/option';
|
||||
export const getExportConfigUrl = '/bug/export/columns/';
|
||||
export const getTemplateDetailUrl = '/bug/template/detail';
|
||||
export const getSyncBugOpenSourceUrl = '/bug/sync/';
|
||||
export const postExportBugUrl = '/bug/export';
|
||||
export const postAssociatedFileListUrl = '/bug/relate/case/page';
|
||||
export const getBugDetailUrl = '/bug/detail/';
|
||||
|
|
|
@ -140,6 +140,7 @@
|
|||
couplingConfig: {
|
||||
...item.props.couplingConfig,
|
||||
},
|
||||
sourceType: item.sourceType || '',
|
||||
};
|
||||
return formItemRule;
|
||||
});
|
||||
|
|
|
@ -93,6 +93,15 @@ export interface FormItem {
|
|||
[key: string]: any;
|
||||
}
|
||||
|
||||
export type FormValueType =
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| string[]
|
||||
| number[]
|
||||
| Record<string, any>
|
||||
| Record<string, any>[];
|
||||
|
||||
interface FomItemSelect extends FormItemComplexCommonConfig {
|
||||
selectMultiple?: boolean; // 选择器是否多选
|
||||
selectMultipleLimit?: [number, number]; // 选择器多选时最少和最多可选项数,如:[1, 3],表示最少选1项,最多选3项;[0, 3]表示最多选3项,可不选;[1, 0]表示最少选1项,不限制最大可选数
|
||||
|
|
|
@ -11,6 +11,8 @@ export enum FormCreateKeyEnum {
|
|||
CASE_CUSTOM_ATTRS = 'caseCustomAttributes',
|
||||
// 用例tab详情字段
|
||||
CASE_CUSTOM_ATTRS_DETAIL = 'caseCustomAttributesDetail',
|
||||
// bug 详情
|
||||
BUG_DETAIL = 'bugDetail',
|
||||
}
|
||||
|
||||
export default {};
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { FormItemType } from '@/components/pure/ms-form-create/types';
|
||||
|
||||
import { BatchApiParams } from './common';
|
||||
|
||||
export interface BugListItem {
|
||||
|
@ -25,12 +27,21 @@ export interface BugExportColumn {
|
|||
export interface BugExportParams extends BatchApiParams {
|
||||
bugExportColumns: BugExportColumn[]; // 导出字段
|
||||
}
|
||||
|
||||
// 获取默认模版缺陷
|
||||
export interface DefaultTemplate {
|
||||
export interface BugEditCustomField {
|
||||
type: FormItemType; // 表单项类型
|
||||
fieldId: string;
|
||||
fieldName: string;
|
||||
value: string;
|
||||
platformOptionJson?: string; // 选项的 Json
|
||||
required: boolean;
|
||||
}
|
||||
export interface BugEditFormObject {
|
||||
[key: string]: any;
|
||||
}
|
||||
export interface BugEditCustomFieldItem {
|
||||
id: string;
|
||||
projectId: string;
|
||||
fromStatusId?: string;
|
||||
platformBugKey?: string;
|
||||
name: string;
|
||||
type: string;
|
||||
value: string;
|
||||
}
|
||||
export default {};
|
||||
|
|
|
@ -35,7 +35,12 @@ const useFormCreateStore = defineStore('form-create', {
|
|||
(formItemType: any) => item.type?.toUpperCase() === formItemType
|
||||
);
|
||||
if (currentTypeForm) {
|
||||
if (currentTypeForm === 'INPUT' && item.subDesc) {
|
||||
// 如果是input类型并且有subDesc说明是JiraKey 类型
|
||||
fieldType = 'JiraKey';
|
||||
} else {
|
||||
fieldType = FieldTypeFormRules[currentTypeForm].type;
|
||||
}
|
||||
const options = item?.options;
|
||||
const currentOptions = options?.map((optionsItem) => {
|
||||
return {
|
||||
|
@ -55,6 +60,7 @@ const useFormCreateStore = defineStore('form-create', {
|
|||
options: !item.optionMethod ? currentOptions : [],
|
||||
link: item.couplingConfig?.cascade,
|
||||
rule: item.validate || [],
|
||||
sourceType: item.type, // 原始表单类型
|
||||
// 梳理表单所需要属性
|
||||
props: {
|
||||
...FieldTypeFormRules[currentTypeForm].props,
|
||||
|
|
|
@ -0,0 +1,155 @@
|
|||
<template>
|
||||
<a-input
|
||||
v-model:model-value="moduleKeyword"
|
||||
:placeholder="t('project.fileManagement.folderSearchPlaceholder')"
|
||||
allow-clear
|
||||
class="mb-[16px]"
|
||||
></a-input>
|
||||
<a-spin class="min-h-[400px] w-full" :loading="loading">
|
||||
<MsTree
|
||||
v-model:focus-node-key="focusNodeKey"
|
||||
:selected-keys="props.selectedKeys"
|
||||
:data="folderTree"
|
||||
:keyword="moduleKeyword"
|
||||
:expand-all="props.isExpandAll"
|
||||
:empty-text="t('project.fileManagement.noFolder')"
|
||||
:virtual-list-props="virtualListProps"
|
||||
:draggable="false"
|
||||
:field-names="{
|
||||
title: 'name',
|
||||
key: 'id',
|
||||
children: 'children',
|
||||
count: 'count',
|
||||
}"
|
||||
block-node
|
||||
title-tooltip-position="left"
|
||||
@select="folderNodeSelect"
|
||||
>
|
||||
<template #title="nodeData">
|
||||
<div class="inline-flex w-full">
|
||||
<div class="one-line-text w-[calc(100%-32px)] text-[var(--color-text-1)]">
|
||||
<MsIcon type="icon-icon_folder_filled1" size="14" class="mr-1 text-[var(--color-text-4)]" />{{
|
||||
nodeData.name
|
||||
}}</div
|
||||
>
|
||||
<div class="ml-[4px] text-[var(--color-text-4)]">({{ nodeData.count || 0 }})</div>
|
||||
</div>
|
||||
</template>
|
||||
</MsTree>
|
||||
</a-spin>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onBeforeMount, ref, watch } from 'vue';
|
||||
|
||||
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
|
||||
import MsTree from '@/components/business/ms-tree/index.vue';
|
||||
import type { MsTreeNodeData } from '@/components/business/ms-tree/types';
|
||||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
import { mapTree } from '@/utils';
|
||||
|
||||
import { ModuleTreeNode } from '@/models/projectManagement/file';
|
||||
|
||||
const appStore = useAppStore();
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const props = defineProps<{
|
||||
isExpandAll: boolean;
|
||||
selectedKeys?: Array<string | number>; // 选中的节点 key
|
||||
modulesCount?: Record<string, number>; // 模块数量统计对象
|
||||
showType?: string; // 显示类型
|
||||
getTreeRequest: (params: any) => Promise<ModuleTreeNode[]>; // 获取模块树接口
|
||||
activeFolder: string | number;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits(['update:selectedKeys', 'init', 'folderNodeSelect', 'update:activeFolder']);
|
||||
|
||||
const moduleKeyword = ref('');
|
||||
const folderTree = ref<ModuleTreeNode[]>([]);
|
||||
const focusNodeKey = ref<string | number>('');
|
||||
|
||||
const loading = ref(false);
|
||||
|
||||
const virtualListProps = computed(() => {
|
||||
return {
|
||||
height: 'calc(100vh - 296px)',
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* 处理文件夹树节点选中事件
|
||||
*/
|
||||
function folderNodeSelect(_selectedKeys: (string | number)[], node: MsTreeNodeData) {
|
||||
const offspringIds: string[] = [];
|
||||
mapTree(node.children || [], (e) => {
|
||||
offspringIds.push(e.id);
|
||||
return e;
|
||||
});
|
||||
|
||||
emit('folderNodeSelect', _selectedKeys, offspringIds);
|
||||
}
|
||||
|
||||
const selectedKeys = ref(props.selectedKeys || []);
|
||||
|
||||
/**
|
||||
* 初始化模块树
|
||||
* @param isSetDefaultKey 是否设置第一个节点为选中节点
|
||||
*/
|
||||
async function initModules(isSetDefaultKey = false) {
|
||||
try {
|
||||
loading.value = true;
|
||||
const res = await props.getTreeRequest(appStore.currentProjectId);
|
||||
folderTree.value = mapTree<ModuleTreeNode>(res, (e) => {
|
||||
return {
|
||||
...e,
|
||||
hideMoreAction: e.id === 'root',
|
||||
draggable: false,
|
||||
disabled: false,
|
||||
count: props.modulesCount?.[e.id] || 0,
|
||||
};
|
||||
});
|
||||
if (isSetDefaultKey) {
|
||||
selectedKeys.value = [folderTree.value[0].id];
|
||||
emit('update:activeFolder', folderTree.value[0].id);
|
||||
}
|
||||
emit('init', folderTree.value);
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.showType,
|
||||
(val) => {
|
||||
if (val === 'Module') {
|
||||
initModules(true);
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* 初始化模块文件数量
|
||||
*/
|
||||
watch(
|
||||
() => props.modulesCount,
|
||||
(obj) => {
|
||||
folderTree.value = mapTree<ModuleTreeNode>(folderTree.value, (node) => {
|
||||
return {
|
||||
...node,
|
||||
count: obj?.[node.id] || 0,
|
||||
};
|
||||
});
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<style scoped lang="less"></style>
|
|
@ -0,0 +1,313 @@
|
|||
<template>
|
||||
<div class="pl-4">
|
||||
<div class="header">
|
||||
<div
|
||||
><span class="one-line-text max-w-[300px]">{{ moduleInfo.name }}</span
|
||||
><span class="ml-[4px] text-[var(--color-text-4)]">({{ moduleInfo.count }})</span></div
|
||||
>
|
||||
<div class="header-right">
|
||||
<a-select v-model="tableFileType" class="w-[240px]" :loading="fileTypeLoading" @change="searchList">
|
||||
<a-option key="" value="">{{ t('common.all') }}</a-option>
|
||||
<a-option v-for="item of tableFileTypeOptions" :key="item" :value="item">
|
||||
{{ item }}
|
||||
</a-option>
|
||||
</a-select>
|
||||
<a-input-search
|
||||
v-model:model-value="keyword"
|
||||
:placeholder="t('project.fileManagement.folderSearchPlaceholder')"
|
||||
allow-clear
|
||||
class="w-[240px]"
|
||||
@search="searchList"
|
||||
@press-enter="searchList"
|
||||
/></div>
|
||||
</div>
|
||||
<ms-base-table v-bind="propsRes" ref="tableRef" no-disable v-on="propsEvent">
|
||||
<template #name="{ record }">
|
||||
<MsTag
|
||||
v-if="record.fileType.toLowerCase() === 'jar'"
|
||||
theme="light"
|
||||
type="success"
|
||||
:self-style="
|
||||
record.enable
|
||||
? {}
|
||||
: {
|
||||
color: 'var(--color-text-4)',
|
||||
backgroundColor: 'var(--color-text-n9)',
|
||||
}
|
||||
"
|
||||
>
|
||||
{{ t(record.enable ? 'common.enable' : 'common.disable') }}
|
||||
</MsTag>
|
||||
<a-tooltip :content="record.name">
|
||||
<div class="one-line-text max-w-[168px]">{{ record.name }}</div>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<template #size="{ record }">
|
||||
<span>{{ formatFileSize(record.size) }}</span>
|
||||
</template>
|
||||
</ms-base-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { debounce } from 'lodash-es';
|
||||
|
||||
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 MsTag from '@/components/pure/ms-tag/ms-tag.vue';
|
||||
|
||||
import { getFileTypes, getRepositoryFileTypes } from '@/api/modules/project-management/fileManagement';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
import useUserStore from '@/store/modules/user';
|
||||
import { findNodeByKey, formatFileSize } from '@/utils';
|
||||
|
||||
import type { AssociatedList } from '@/models/caseManagement/featureCase';
|
||||
import type { CommonList, TableQueryParams } from '@/models/common';
|
||||
import type { FileListQueryParams, ModuleTreeNode } from '@/models/projectManagement/file';
|
||||
import { Repository } from '@/models/projectManagement/file';
|
||||
import { TableKeyEnum } from '@/enums/tableEnum';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const props = defineProps<{
|
||||
activeFolder: string;
|
||||
activeFolderType: 'folder' | 'module' | 'storage';
|
||||
offspringIds: string[]; // 当前选中文件夹的所有子孙节点id
|
||||
modulesCount: Record<string, any>; // 模块数量
|
||||
folderTree: ModuleTreeNode[];
|
||||
selectFile: AssociatedList[]; // 表格选中项
|
||||
getListRequest: (params: TableQueryParams) => Promise<CommonList<AssociatedList>>;
|
||||
showType: 'Module' | 'Storage'; // 展示类型
|
||||
storageList: Repository[]; // 存储库列表
|
||||
}>();
|
||||
const emit = defineEmits<{
|
||||
(e: 'init', params: FileListQueryParams): void;
|
||||
(e: 'update:selectFile', val: AssociatedList[]): void;
|
||||
}>();
|
||||
|
||||
const tableFileTypeOptions = ref<string[]>([]);
|
||||
const tableFileType = ref(''); // 文件格式筛选
|
||||
const keyword = ref('');
|
||||
const fileTypeLoading = ref(false);
|
||||
const fileType = ref('module'); // 当前查看的文件类型,模块/存储库
|
||||
const appStore = useAppStore();
|
||||
const userStore = useUserStore();
|
||||
const combine = ref<Record<string, any>>({});
|
||||
const isMyOrAllFolder = computed(() => ['my', 'all'].includes(props.activeFolder)); // 是否是我的文件/全部文件
|
||||
|
||||
const columns: MsTableColumn = [
|
||||
{
|
||||
title: 'project.fileManagement.name',
|
||||
slotName: 'name',
|
||||
dataIndex: 'name',
|
||||
width: 270,
|
||||
},
|
||||
{
|
||||
title: 'project.fileManagement.type',
|
||||
dataIndex: 'fileType',
|
||||
width: 90,
|
||||
},
|
||||
{
|
||||
title: 'project.fileManagement.tag',
|
||||
dataIndex: 'tags',
|
||||
isTag: true,
|
||||
},
|
||||
{
|
||||
title: 'project.fileManagement.creator',
|
||||
dataIndex: 'creator',
|
||||
showTooltip: true,
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: 'project.fileManagement.updater',
|
||||
dataIndex: 'updateUser',
|
||||
showTooltip: true,
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: 'project.fileManagement.updateTime',
|
||||
dataIndex: 'updateTime',
|
||||
width: 180,
|
||||
},
|
||||
];
|
||||
|
||||
const { propsRes, propsEvent, loadList, setLoadListParams, resetSelector } = useTable(
|
||||
props.getListRequest,
|
||||
{
|
||||
columns,
|
||||
tableKey: TableKeyEnum.FILE_MANAGEMENT_FILE,
|
||||
showSetting: false,
|
||||
selectable: true,
|
||||
showSelectAll: true,
|
||||
heightUsed: 300,
|
||||
},
|
||||
(item) => {
|
||||
return {
|
||||
...item,
|
||||
tags: item.tags?.map((e: string) => ({ id: e, name: e })) || [],
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
function emitTableParams() {
|
||||
emit('init', {
|
||||
keyword: keyword.value,
|
||||
fileType: tableFileType.value,
|
||||
moduleIds: [],
|
||||
projectId: appStore.currentProjectId,
|
||||
current: propsRes.value.msPagination?.current,
|
||||
pageSize: propsRes.value.msPagination?.pageSize,
|
||||
combine: combine.value,
|
||||
});
|
||||
}
|
||||
|
||||
function setTableParams() {
|
||||
if (props.activeFolder === 'my') {
|
||||
combine.value.createUser = userStore.id;
|
||||
} else {
|
||||
combine.value.createUser = '';
|
||||
}
|
||||
if (fileType.value === 'storage') {
|
||||
combine.value.storage = 'git';
|
||||
} else {
|
||||
combine.value.storage = 'minio';
|
||||
}
|
||||
let moduleIds: string[] = [props.activeFolder, ...props.offspringIds];
|
||||
|
||||
if (isMyOrAllFolder.value) {
|
||||
moduleIds = [];
|
||||
}
|
||||
setLoadListParams({
|
||||
keyword: keyword.value,
|
||||
fileType: tableFileType.value,
|
||||
moduleIds,
|
||||
projectId: appStore.currentProjectId,
|
||||
combine: combine.value,
|
||||
});
|
||||
}
|
||||
|
||||
const searchList = debounce(() => {
|
||||
setTableParams();
|
||||
loadList();
|
||||
emitTableParams();
|
||||
}, 300);
|
||||
|
||||
/**
|
||||
* 初始化文件类型筛选选项
|
||||
*/
|
||||
async function initFileTypes() {
|
||||
try {
|
||||
fileTypeLoading.value = true;
|
||||
let res = null;
|
||||
if (fileType.value === 'storage') {
|
||||
res = await getRepositoryFileTypes(appStore.currentProjectId);
|
||||
} else {
|
||||
res = await getFileTypes(appStore.currentProjectId);
|
||||
}
|
||||
tableFileType.value = '';
|
||||
tableFileTypeOptions.value = res;
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
} finally {
|
||||
fileTypeLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.activeFolderType,
|
||||
() => {
|
||||
initFileTypes();
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.activeFolderType,
|
||||
(val) => {
|
||||
if (val === 'folder') {
|
||||
fileType.value = 'module';
|
||||
} else {
|
||||
fileType.value = val;
|
||||
}
|
||||
setTableParams();
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.activeFolder,
|
||||
() => {
|
||||
keyword.value = '';
|
||||
searchList();
|
||||
resetSelector();
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
const moduleInfo = computed(() => {
|
||||
if (props.showType === 'Module') {
|
||||
return {
|
||||
name: findNodeByKey<Record<string, any>>(props.folderTree, props.activeFolder, 'id')?.name,
|
||||
count: props.modulesCount[props.activeFolder],
|
||||
};
|
||||
}
|
||||
const storageItem = props.storageList.find((item) => item.id === props.activeFolder);
|
||||
return {
|
||||
name: storageItem?.name,
|
||||
count: storageItem?.count,
|
||||
};
|
||||
});
|
||||
|
||||
const tableSelected = ref<AssociatedList[]>([]);
|
||||
|
||||
const selectedIds = computed(() => {
|
||||
return [...propsRes.value.selectedKeys];
|
||||
});
|
||||
|
||||
watch(
|
||||
() => selectedIds.value,
|
||||
() => {
|
||||
tableSelected.value = propsRes.value.data.filter((item: any) => selectedIds.value.indexOf(item.id) > -1);
|
||||
emit('update:selectFile', tableSelected.value);
|
||||
}
|
||||
);
|
||||
|
||||
defineExpose({
|
||||
resetSelector,
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
resetSelector();
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
resetSelector();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.header {
|
||||
@apply flex items-center justify-between;
|
||||
|
||||
margin-bottom: 16px;
|
||||
.header-right {
|
||||
@apply ml-auto flex items-center justify-end;
|
||||
|
||||
width: 70%;
|
||||
gap: 8px;
|
||||
.show-type-icon {
|
||||
:deep(.arco-radio-button-content) {
|
||||
@apply flex;
|
||||
|
||||
padding: 4px;
|
||||
line-height: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,273 @@
|
|||
<template>
|
||||
<MsDrawer
|
||||
v-model:visible="showDrawer"
|
||||
:mask="false"
|
||||
:title="t('caseManagement.featureCase.associatedFile')"
|
||||
:ok-text="t('caseManagement.featureCase.associated')"
|
||||
:ok-loading="drawerLoading"
|
||||
:ok-disabled="selectFile.length < 1"
|
||||
:width="1200"
|
||||
unmount-on-close
|
||||
:show-continue="false"
|
||||
@confirm="handleDrawerConfirm"
|
||||
@cancel="handleDrawerCancel"
|
||||
>
|
||||
<MsSplitBox>
|
||||
<template #left>
|
||||
<div class="p-[16px] pt-0">
|
||||
<div class="folder">
|
||||
<div class="folder-text">
|
||||
<MsIcon type="icon-icon_folder_filled1" class="folder-icon" />
|
||||
<div class="folder-name">{{ t('project.fileManagement.allFile') }}</div>
|
||||
<div class="folder-count">({{ allFileCount }})</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="changeExpand">
|
||||
<MsIcon :type="isExpandAll ? 'icon-icon_folder_collapse1' : 'icon-icon_folder_expansion1'" />
|
||||
</MsButton>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<a-divider class="my-[8px]" />
|
||||
<a-radio-group v-model:model-value="showType" type="button" class="file-show-type" @change="changeShowType">
|
||||
<a-radio value="Module">{{ t('project.fileManagement.module') }}</a-radio>
|
||||
<a-radio value="Storage">{{ t('project.fileManagement.storage') }}</a-radio>
|
||||
</a-radio-group>
|
||||
<div v-show="showType === 'Module'">
|
||||
<FileTree
|
||||
ref="folderTreeRef"
|
||||
v-model:selected-keys="selectedKeys"
|
||||
v-model:active-folder="activeFolder"
|
||||
:is-expand-all="isExpandAll"
|
||||
:modules-count="modulesCount"
|
||||
:show-type="showType"
|
||||
:get-tree-request="props.getTreeRequest"
|
||||
@init="setRootModules"
|
||||
@folder-node-select="folderNodeSelect"
|
||||
/>
|
||||
</div>
|
||||
<div v-show="showType === 'Storage'">
|
||||
<StorageList
|
||||
v-model:drawer-visible="storageDrawerVisible"
|
||||
v-model:active-folder="activeFolder"
|
||||
:modules-count="modulesCount"
|
||||
:show-type="showType"
|
||||
@item-click="storageItemSelect"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #right>
|
||||
<LinkFileTable
|
||||
v-model:selectFile="selectFile"
|
||||
:active-folder="activeFolder"
|
||||
:active-folder-type="activeFolderType"
|
||||
:offspring-ids="offspringIds"
|
||||
:modules-count="modulesCount"
|
||||
:folder-tree="folderTree"
|
||||
:storage-list="storageList"
|
||||
:show-type="showType"
|
||||
:get-list-request="props.getListRequest"
|
||||
@init="handleModuleTableInit"
|
||||
/>
|
||||
</template>
|
||||
</MsSplitBox>
|
||||
</MsDrawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
|
||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
|
||||
import MsSplitBox from '@/components/pure/ms-split-box/index.vue';
|
||||
import FileTree from './fileTree.vue';
|
||||
import LinkFileTable from './linkFileTable.vue';
|
||||
import StorageList from './storageList.vue';
|
||||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
import type { AssociatedList } from '@/models/caseManagement/featureCase';
|
||||
import type { CommonList, TableQueryParams } from '@/models/common';
|
||||
import { FileListQueryParams, ModuleTreeNode, Repository } from '@/models/projectManagement/file';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const props = defineProps<{
|
||||
visible: boolean;
|
||||
getTreeRequest: (params: any) => Promise<ModuleTreeNode[]>; // 获取左侧树请求
|
||||
getCountRequest: (params: any) => Promise<Record<string, any>>; // 获取左侧树模块数量请求
|
||||
getListRequest: (params: TableQueryParams) => Promise<CommonList<AssociatedList>>; // 获取表格请求
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'save', val: AssociatedList[]): void;
|
||||
(e: 'update:visible', val: boolean): void;
|
||||
}>();
|
||||
const showDrawer = computed({
|
||||
get() {
|
||||
return props.visible;
|
||||
},
|
||||
set(val) {
|
||||
emit('update:visible', val);
|
||||
},
|
||||
});
|
||||
|
||||
const drawerLoading = ref<boolean>(false);
|
||||
|
||||
const activeFolderType = ref<'folder' | 'module' | 'storage'>('module');
|
||||
|
||||
const activeFolder = ref<string>('root');
|
||||
const selectedKeys = computed({
|
||||
get: () => [activeFolder.value],
|
||||
set: (val) => val,
|
||||
});
|
||||
const offspringIds = ref<string[]>([]);
|
||||
|
||||
const modulesCount = ref<Record<string, number>>({});
|
||||
const myFileCount = ref(0);
|
||||
const allFileCount = ref(0);
|
||||
|
||||
const isExpandAll = ref(false);
|
||||
|
||||
function changeExpand() {
|
||||
isExpandAll.value = !isExpandAll.value;
|
||||
}
|
||||
|
||||
type FileShowType = 'Module' | 'Storage';
|
||||
const showType = ref<FileShowType>('Module');
|
||||
|
||||
/**
|
||||
* 处理文件夹树节点选中事件
|
||||
*/
|
||||
function folderNodeSelect(keys: string[], _offspringIds: string[]) {
|
||||
[activeFolder.value] = keys;
|
||||
activeFolderType.value = 'module';
|
||||
offspringIds.value = [..._offspringIds];
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置根模块名称列表
|
||||
* @param names 根模块名称列表
|
||||
*/
|
||||
|
||||
const folderTree = ref<ModuleTreeNode[]>([]);
|
||||
const rootModulesName = ref<string[]>([]); // 根模块名称列表
|
||||
function setRootModules(treeNode: ModuleTreeNode[]) {
|
||||
folderTree.value = treeNode;
|
||||
rootModulesName.value = treeNode.map((e) => e.name);
|
||||
}
|
||||
|
||||
/*
|
||||
* 初始化模块文件数量
|
||||
*/
|
||||
async function initModulesCount(params: FileListQueryParams) {
|
||||
try {
|
||||
modulesCount.value = await props.getCountRequest(params);
|
||||
myFileCount.value = modulesCount.value.my || 0;
|
||||
allFileCount.value = modulesCount.value.all || 0;
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
const tableFilterParams = ref<FileListQueryParams>({
|
||||
moduleIds: [],
|
||||
fileType: '',
|
||||
projectId: '',
|
||||
});
|
||||
|
||||
function changeShowType(val: string | number | boolean) {
|
||||
showType.value = val as FileShowType;
|
||||
if (val === 'Storage') {
|
||||
initModulesCount({
|
||||
...tableFilterParams.value,
|
||||
combine: {
|
||||
...tableFilterParams.value.combine,
|
||||
storage: 'git',
|
||||
},
|
||||
});
|
||||
} else {
|
||||
initModulesCount(tableFilterParams.value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 右侧表格数据刷新后,若当前展示的是模块,则刷新模块树的统计数量
|
||||
*/
|
||||
function handleModuleTableInit(params: FileListQueryParams) {
|
||||
initModulesCount(params);
|
||||
tableFilterParams.value = { ...params };
|
||||
}
|
||||
|
||||
const storageDrawerVisible = ref(false);
|
||||
|
||||
/**
|
||||
* 处理存储库列表项选中事件
|
||||
*/
|
||||
const storageList = ref<Repository[]>([]);
|
||||
function storageItemSelect(key: string, storages: Repository[]) {
|
||||
storageList.value = storages;
|
||||
activeFolder.value = key;
|
||||
activeFolderType.value = 'storage';
|
||||
}
|
||||
|
||||
const selectFile = ref<AssociatedList[]>([]);
|
||||
|
||||
function handleDrawerConfirm() {
|
||||
emit('save', selectFile.value);
|
||||
showDrawer.value = false;
|
||||
}
|
||||
|
||||
function handleDrawerCancel() {
|
||||
showDrawer.value = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.folder {
|
||||
@apply flex cursor-pointer items-center justify-between;
|
||||
|
||||
padding: 8px 4px;
|
||||
border-radius: var(--border-radius-small);
|
||||
&:hover {
|
||||
background-color: rgb(var(--primary-1));
|
||||
}
|
||||
.folder-text {
|
||||
@apply flex cursor-pointer items-center;
|
||||
.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);
|
||||
}
|
||||
}
|
||||
.folder-text--active {
|
||||
.folder-icon,
|
||||
.folder-name,
|
||||
.folder-count {
|
||||
color: rgb(var(--primary-5));
|
||||
}
|
||||
}
|
||||
}
|
||||
.file-show-type {
|
||||
@apply grid grid-cols-2;
|
||||
|
||||
margin-bottom: 8px;
|
||||
:deep(.arco-radio-button-content) {
|
||||
@apply text-center;
|
||||
}
|
||||
}
|
||||
:deep(.arco-drawer-body) {
|
||||
padding: 0 16px !important;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,169 @@
|
|||
<template>
|
||||
<a-input
|
||||
v-model:model-value="storageKeyword"
|
||||
:placeholder="t('project.fileManagement.folderSearchPlaceholder')"
|
||||
allow-clear
|
||||
class="mb-[8px]"
|
||||
></a-input>
|
||||
<a-spin class="h-full w-full" :loading="loading">
|
||||
<MsList
|
||||
v-model:focus-item-key="focusItemKey"
|
||||
:virtual-list-props="{
|
||||
height: 'calc(100vh - 325px)',
|
||||
}"
|
||||
:data="storageList"
|
||||
:bordered="false"
|
||||
:split="false"
|
||||
:empty-text="t('project.fileManagement.noStorage')"
|
||||
item-key-field="id"
|
||||
class="mr-[-6px]"
|
||||
>
|
||||
<template #title="{ item, index }">
|
||||
<div :key="index" class="storage" @click="setActiveFolder(item.id)">
|
||||
<div :class="activeStorageNode === item.id ? 'storage-text storage-text--active' : 'storage-text'">
|
||||
<MsIcon type="icon-icon_git" class="storage-icon" />
|
||||
<div class="storage-name">{{ item.name }}</div>
|
||||
<div class="storage-count">({{ item.count }})</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</MsList>
|
||||
</a-spin>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue';
|
||||
import { debounce } from 'lodash-es';
|
||||
|
||||
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
|
||||
import MsList from '@/components/pure/ms-list/index.vue';
|
||||
|
||||
import { getRepositories } from '@/api/modules/project-management/fileManagement';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
|
||||
import { Repository } from '@/models/projectManagement/file';
|
||||
|
||||
const props = defineProps<{
|
||||
activeFolder: string | number;
|
||||
drawerVisible: boolean;
|
||||
showType: string;
|
||||
modulesCount?: Record<string, number>; // 模块数量统计对象
|
||||
}>();
|
||||
const emit = defineEmits(['update:drawerVisible', 'itemClick', 'update:activeFolder']);
|
||||
|
||||
const { t } = useI18n();
|
||||
const appStore = useAppStore();
|
||||
|
||||
const activeStorageNode = computed({
|
||||
get() {
|
||||
return props.activeFolder;
|
||||
},
|
||||
set(val) {
|
||||
emit('update:activeFolder', val);
|
||||
},
|
||||
});
|
||||
|
||||
const storageKeyword = ref('');
|
||||
const originStorageList = ref<Repository[]>([]);
|
||||
const storageList = ref(originStorageList.value);
|
||||
const loading = ref(false);
|
||||
|
||||
const searchStorage = debounce(() => {
|
||||
storageList.value = originStorageList.value.filter((item) => item.name.includes(storageKeyword.value));
|
||||
}, 300);
|
||||
|
||||
watch(
|
||||
() => storageKeyword.value,
|
||||
() => {
|
||||
if (storageKeyword.value === '') {
|
||||
storageList.value = [...originStorageList.value];
|
||||
}
|
||||
searchStorage();
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* 初始化存储库列表
|
||||
*/
|
||||
async function initRepositories(setDefaultKeys = false) {
|
||||
try {
|
||||
loading.value = true;
|
||||
const res = await getRepositories(appStore.currentProjectId);
|
||||
originStorageList.value = res;
|
||||
storageList.value = originStorageList.value.map((e) => ({
|
||||
...e,
|
||||
count: props.modulesCount?.[e.id] || 0,
|
||||
}));
|
||||
if (setDefaultKeys) {
|
||||
activeStorageNode.value = storageList.value[0].id;
|
||||
emit('itemClick', storageList.value[0].id, storageList.value);
|
||||
}
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.showType,
|
||||
(val) => {
|
||||
if (val === 'Storage') {
|
||||
initRepositories(true);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* 初始化模块文件数量
|
||||
*/
|
||||
watch(
|
||||
() => props.modulesCount,
|
||||
(obj) => {
|
||||
storageList.value = originStorageList.value.map((e) => ({
|
||||
...e,
|
||||
count: obj?.[e.id] || 0,
|
||||
}));
|
||||
}
|
||||
);
|
||||
|
||||
const focusItemKey = ref('');
|
||||
|
||||
function setActiveFolder(id: string) {
|
||||
emit('itemClick', id, storageList.value);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.storage {
|
||||
@apply flex cursor-pointer items-center justify-between;
|
||||
|
||||
border-radius: var(--border-radius-small);
|
||||
&:hover {
|
||||
background-color: rgb(var(--primary-1));
|
||||
}
|
||||
.storage-text {
|
||||
@apply flex cursor-pointer items-center;
|
||||
.storage-icon {
|
||||
margin-right: 4px;
|
||||
color: var(--color-text-4);
|
||||
}
|
||||
.storage-name {
|
||||
color: var(--color-text-1);
|
||||
}
|
||||
.storage-count {
|
||||
margin-left: 4px;
|
||||
color: var(--color-text-4);
|
||||
}
|
||||
}
|
||||
.storage-text--active {
|
||||
.storage-icon,
|
||||
.storage-name,
|
||||
.storage-count {
|
||||
color: rgb(var(--primary-5));
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,53 +1,71 @@
|
|||
<template>
|
||||
<MsCard :special-height="-54" no-content-padding divider-has-p-x has-breadcrumb :title="title">
|
||||
<MsCard
|
||||
:special-height="-54"
|
||||
no-content-padding
|
||||
divider-has-p-x
|
||||
has-breadcrumb
|
||||
:title="title"
|
||||
:loading="loading"
|
||||
@save="saveHandler"
|
||||
@save-and-continue="saveHandler"
|
||||
>
|
||||
<template #headerRight>
|
||||
<a-select
|
||||
v-model="templateId"
|
||||
v-model="form.templateId"
|
||||
class="w-[240px]"
|
||||
:options="templateOption"
|
||||
allow-search
|
||||
:placeholder="t('bugManagement.edit.defaultSystemTemplate')"
|
||||
@change="templateChange"
|
||||
/>
|
||||
</template>
|
||||
<a-form ref="formRef" :model="form" layout="vertical">
|
||||
<div class="flex flex-row" style="height: calc(100vh - 224px)">
|
||||
<div class="left mt-[16px] min-w-[732px] grow pl-[24px]">
|
||||
<a-form-item
|
||||
field="name"
|
||||
field="title"
|
||||
:label="t('bugManagement.bugName')"
|
||||
:rules="[{ required: true, message: t('bugManagement.edit.nameIsRequired') }]"
|
||||
:placeholder="t('bugManagement.edit.pleaseInputBugName')"
|
||||
>
|
||||
<a-input v-model="form.name" :max-length="255" show-word-limit />
|
||||
<a-input v-model="form.title" :max-length="255" show-word-limit />
|
||||
</a-form-item>
|
||||
<a-form-item :label="t('bugManagement.edit.content')">
|
||||
<MsRichText v-model="form.content" />
|
||||
<a-form-item field="description" :label="t('bugManagement.edit.content')">
|
||||
<MsRichText v-model="form.description" />
|
||||
</a-form-item>
|
||||
<div class="mb-[8px] text-[var(--color-text-1)]">{{ t('bugManagement.edit.file') }}</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-form-item field="attachment" :label="t('bugManagement.edit.file')">
|
||||
<div class="flex flex-col">
|
||||
<div class="mb-1">
|
||||
<a-dropdown position="tr" trigger="hover">
|
||||
<a-button type="outline">
|
||||
<template #icon>
|
||||
<icon-plus />
|
||||
</template>
|
||||
<template #icon> <icon-plus class="text-[14px]" /> </template>
|
||||
{{ t('bugManagement.edit.uploadFile') }}
|
||||
</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('bugManagement.edit.localUpload') }}</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('bugManagement.edit.linkFile')
|
||||
}}</a-button
|
||||
>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</a-form-item>
|
||||
<div class="mb-[8px] mt-[2px] text-[var(--color-text-4)]">{{ t('bugManagement.edit.fileExtra') }}</div>
|
||||
<FileList
|
||||
:show-tab="false"
|
||||
|
@ -60,33 +78,25 @@
|
|||
</FileList>
|
||||
</div>
|
||||
<a-divider class="ml-[16px]" direction="vertical" />
|
||||
<div class="right mt-[16px] grow pr-[24px]">
|
||||
<a-form-item
|
||||
<div class="right mt-[16px] max-w-[433px] grow pr-[24px]">
|
||||
<!-- <a-form-item
|
||||
:label="t('bugManagement.handleMan')"
|
||||
field="handleMan"
|
||||
:rules="[{ required: true, message: t('bugManagement.edit.handleManIsRequired') }]"
|
||||
>
|
||||
<MsUserSelector
|
||||
v-model:model-value="form.handleMan"
|
||||
:type="UserRequestTypeEnum.PROJECT_PERMISSION_MEMBER"
|
||||
:load-option-params="{ projectId: appStore.currentProjectId }"
|
||||
placeholder="bugManagement.edit.handleManPlaceholder"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
field="status"
|
||||
:label="t('bugManagement.status')"
|
||||
:rules="[{ required: true, message: t('bugManagement.edit.statusIsRequired') }]"
|
||||
>
|
||||
<a-select
|
||||
v-model:model-value="form.status"
|
||||
:placeholder="t('bugManagement.edit.statusPlaceholder')"
|
||||
></a-select>
|
||||
</a-form-item>
|
||||
<a-form-item field="severity" :label="t('bugManagement.severity')">
|
||||
<a-select
|
||||
v-model:model-value="form.severity"
|
||||
:placeholder="t('bugManagement.edit.severityPlaceholder')"
|
||||
></a-select>
|
||||
</a-form-item>
|
||||
</a-form-item> -->
|
||||
<MsFormCreate
|
||||
v-if="formRules.length"
|
||||
ref="formCreateRef"
|
||||
:form-rule="formRules"
|
||||
:form-create-key="FormCreateKeyEnum.BUG_DETAIL"
|
||||
/>
|
||||
<a-form-item field="tag" :label="t('bugManagement.tag')">
|
||||
<a-input-tag
|
||||
v-model:model-value="form.tag"
|
||||
|
@ -98,21 +108,64 @@
|
|||
</div>
|
||||
</a-form>
|
||||
</MsCard>
|
||||
<div>
|
||||
<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>
|
||||
<RelateFileDrawer
|
||||
v-model:visible="associatedDrawer"
|
||||
:get-tree-request="getModules"
|
||||
:get-count-request="getModulesCount"
|
||||
:get-list-request="getAssociatedFileList"
|
||||
@save="saveSelectAssociatedFile"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useRoute } from 'vue-router';
|
||||
import { FileItem } from '@arco-design/web-vue';
|
||||
import { FileItem, Message } from '@arco-design/web-vue';
|
||||
|
||||
import MsCard from '@/components/pure/ms-card/index.vue';
|
||||
import MsFormCreate from '@/components/pure/ms-form-create/form-create.vue';
|
||||
import { FormItem } from '@/components/pure/ms-form-create/types';
|
||||
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 { MsUserSelector } from '@/components/business/ms-user-selector';
|
||||
import { MsFileItem } from '@/components/pure/ms-upload/types';
|
||||
import RelateFileDrawer from './components/relateFile/relateFileDrawer.vue';
|
||||
|
||||
import { getTemplageOption } from '@/api/modules/bug-management';
|
||||
// import { MsUserSelector } from '@/components/business/ms-user-selector';
|
||||
// import { UserRequestTypeEnum } from '@/components/business/ms-user-selector/utils';
|
||||
import {
|
||||
createBug,
|
||||
getAssociatedFileList,
|
||||
getBugDetail,
|
||||
getTemplageOption,
|
||||
getTemplateById,
|
||||
} from '@/api/modules/bug-management';
|
||||
import { getModules, getModulesCount } from '@/api/modules/project-management/fileManagement';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import { useAppStore } from '@/store';
|
||||
import useFormCreateStore from '@/store/modules/form-create/form-create';
|
||||
import { scrollIntoView } from '@/utils/dom';
|
||||
|
||||
import { BugEditCustomField, BugEditCustomFieldItem, BugEditFormObject } from '@/models/bug-management';
|
||||
import { AssociatedList } from '@/models/caseManagement/featureCase';
|
||||
import { SelectValue } from '@/models/projectManagement/menuManagement';
|
||||
import { FormCreateKeyEnum } from '@/enums/formCreateEnum';
|
||||
|
||||
import { convertToFile } from '../case-management/caseManagementFeature/components/utils';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
|
@ -122,36 +175,73 @@
|
|||
}
|
||||
|
||||
const appStore = useAppStore();
|
||||
const formCreateStore = useFormCreateStore();
|
||||
|
||||
const route = useRoute();
|
||||
const templateOption = ref<TemplateOption[]>([]);
|
||||
const form = ref({
|
||||
name: '',
|
||||
content: '',
|
||||
const form = ref<BugEditFormObject>({
|
||||
projectId: appStore.currentProjectId,
|
||||
title: '',
|
||||
description: '',
|
||||
templateId: '',
|
||||
handleMan: [],
|
||||
status: '',
|
||||
severity: '',
|
||||
tag: [],
|
||||
});
|
||||
const formRef = ref<any>(null);
|
||||
const formRef = ref();
|
||||
const formCreateRef = ref();
|
||||
|
||||
const fileList = ref<FileItem[]>([]);
|
||||
const formRules = ref<FormItem[]>([]);
|
||||
const associatedDrawer = ref(false);
|
||||
const loading = ref(false);
|
||||
const acceptType = ref('none'); // 模块-上传文件类型
|
||||
|
||||
// 模板id
|
||||
const templateId = ref<string>('');
|
||||
const isEdit = computed(() => !!route.query.id);
|
||||
|
||||
const title = computed(() => {
|
||||
return isEdit.value ? t('bugManagement.editBug') : t('bugManagement.createBug');
|
||||
});
|
||||
|
||||
// 处理表单格式
|
||||
const getFormRules = (arr: BugEditCustomField[]) => {
|
||||
formRules.value = [];
|
||||
if (Array.isArray(arr) && arr.length) {
|
||||
formRules.value = arr.map((item) => {
|
||||
return {
|
||||
type: item.type,
|
||||
name: item.fieldId,
|
||||
label: item.fieldName,
|
||||
value: item.value,
|
||||
options: item.platformOptionJson ? JSON.parse(item.platformOptionJson) : [],
|
||||
required: item.required as boolean,
|
||||
props: {
|
||||
modelValue: item.value,
|
||||
options: item.platformOptionJson ? JSON.parse(item.platformOptionJson) : [],
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const templateChange = async (v: SelectValue) => {
|
||||
if (v) {
|
||||
try {
|
||||
const res = await getTemplateById({ projectId: appStore.currentProjectId, id: v });
|
||||
getFormRules(res.customFields);
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const getTemplateOptions = async () => {
|
||||
try {
|
||||
const res = await getTemplageOption({ projectId: appStore.currentProjectId });
|
||||
templateOption.value = res.map((item) => {
|
||||
if (item.enableDefault && !isEdit.value) {
|
||||
templateId.value = item.id;
|
||||
// 当创建时 选中默认模板
|
||||
form.value.templateId = item.id;
|
||||
templateChange(item.id);
|
||||
}
|
||||
return {
|
||||
label: item.name,
|
||||
|
@ -196,10 +286,95 @@
|
|||
return Promise.resolve(fileItem);
|
||||
};
|
||||
|
||||
const handleLineFile = () => {};
|
||||
function beforeUpload(file: File) {
|
||||
const _maxSize = 50 * 1024 * 1024;
|
||||
if (file.size > _maxSize) {
|
||||
Message.warning(t('ms.upload.overSize'));
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
function associatedFile() {
|
||||
associatedDrawer.value = true;
|
||||
}
|
||||
|
||||
function handleChange(_fileList: MsFileItem[]) {
|
||||
fileList.value = _fileList.map((e) => {
|
||||
return {
|
||||
...e,
|
||||
enable: true, // 是否启用
|
||||
local: true, // 是否本地文件
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// 处理关联文件
|
||||
function saveSelectAssociatedFile(fileData: AssociatedList[]) {
|
||||
const fileResultList = fileData.map((fileInfo) => convertToFile(fileInfo));
|
||||
fileList.value.push(...fileResultList);
|
||||
}
|
||||
|
||||
// 保存
|
||||
const saveHandler = async () => {
|
||||
formRef.value.validate((error: any) => {
|
||||
if (!error) {
|
||||
formCreateRef.value.formApi.validate(async (valid: any) => {
|
||||
if (valid === true) {
|
||||
try {
|
||||
loading.value = true;
|
||||
const customFields: BugEditCustomFieldItem[] = [];
|
||||
const formRuleList = formCreateStore.formCreateRuleMap.get(FormCreateKeyEnum.BUG_DETAIL);
|
||||
if (formRuleList && formRuleList.length) {
|
||||
formRuleList.forEach((item) => {
|
||||
customFields.push({
|
||||
id: item.field as string,
|
||||
name: item.title as string,
|
||||
type: item.sourceType as string,
|
||||
value: item.value as string,
|
||||
});
|
||||
});
|
||||
}
|
||||
const tmpObj = {
|
||||
...form.value,
|
||||
tag: form.value.tag.join(',') || '',
|
||||
customFields,
|
||||
};
|
||||
await createBug({ request: tmpObj, fileList: fileList.value as unknown as File[] });
|
||||
Message.success(t('common.createSuccess'));
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(err);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
scrollIntoView(document.querySelector('.arco-form-item-message'), { block: 'center' });
|
||||
};
|
||||
|
||||
const getDetailInfo = async () => {
|
||||
const id = route.query.id as string;
|
||||
// TODO: 等后端接口
|
||||
const res = await getBugDetail(id);
|
||||
const { customFields, file } = res;
|
||||
formRules.value = customFields;
|
||||
fileList.value = file;
|
||||
};
|
||||
|
||||
const initDefaultFields = () => {
|
||||
getTemplateOptions();
|
||||
};
|
||||
|
||||
onBeforeMount(() => {
|
||||
getTemplateOptions();
|
||||
if (isEdit.value) {
|
||||
// 详情
|
||||
getDetailInfo();
|
||||
} else {
|
||||
initDefaultFields();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
|
@ -181,6 +181,7 @@
|
|||
noDisable: false,
|
||||
size: 'default',
|
||||
showSetting: true,
|
||||
heightUsed: 286,
|
||||
},
|
||||
undefined,
|
||||
(record) => handleNameChange(record)
|
||||
|
|
|
@ -166,6 +166,7 @@
|
|||
noDisable: false,
|
||||
size: 'default',
|
||||
showSetting: true,
|
||||
heightUsed: 286,
|
||||
},
|
||||
undefined,
|
||||
handleNameChange
|
||||
|
|
|
@ -161,6 +161,7 @@
|
|||
{
|
||||
tableKey: TableKeyEnum.SYSTEM_PROJECT,
|
||||
scroll: { x: '1600px' },
|
||||
heightUsed: 286,
|
||||
selectable: false,
|
||||
noDisable: false,
|
||||
size: 'default',
|
||||
|
|
Loading…
Reference in New Issue