feat: 编辑组件封装

This commit is contained in:
RubyLiu 2023-12-07 19:52:48 +08:00 committed by 刘瑞斌
parent b44e170c90
commit 3f7b5f133a
13 changed files with 386 additions and 132 deletions

View File

@ -0,0 +1,146 @@
import { PropType } from 'vue';
import { useI18n } from '@/hooks/useI18n';
import './style.less';
import { MsUserSelector } from '../ms-user-selector';
import { UserRequestTypeEnum } from '../ms-user-selector/utils';
import { ModelValueType } from './types';
import Message from '@arco-design/web-vue/es/message';
type EditStatus = 'null' | 'active' | 'hover';
export default defineComponent({
name: 'MsEditComp',
props: {
// 具体绑定的值
modelValue: {
type: [String, Number, Boolean, Object, Array] as PropType<ModelValueType>,
default: '',
},
// 表格默认显示的值
defaultValue: {
type: [String, Number, Boolean, Object, Array] as PropType<ModelValueType>,
default: '',
},
// 组件类型
mode: {
type: String,
default: 'Input',
},
// 选择框的选项
options: {
type: Array as PropType<{ value: string; label: string }[]>,
default: () => [],
},
// 用户选择器的类型
userSelectorType: {
type: String as PropType<UserRequestTypeEnum>,
default: '',
},
},
emits: {
/* eslint-disable @typescript-eslint/no-unused-vars */
change: (value: ModelValueType, cb: (result: boolean, defaultValue: string) => void) => true,
},
setup(props, { emit }) {
const { mode, options, modelValue, defaultValue } = toRefs(props);
const oldModelValue = ref(modelValue.value);
const editStatus = ref<EditStatus>('null');
const { t } = useI18n();
const handleChange = (value: ModelValueType) => {
emit('change', value, (result: boolean, v: string) => {
if (result) {
defaultValue.value = v;
Message.success(t('common.updateSuccess'));
} else {
Message.error(t('common.updateFail'));
modelValue.value = oldModelValue.value;
}
});
};
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Enter') {
handleChange(modelValue.value);
}
};
const handleClick = () => {
editStatus.value = 'active';
};
const handleReset = () => {
editStatus.value = 'null';
modelValue.value = oldModelValue.value;
};
const handleBlur = () => {
handleReset();
};
const renderDiv = () => {
const _defaultValue = Array.isArray(defaultValue.value) ? defaultValue.value.join(',') : defaultValue.value;
return (
<div onClick={handleClick} class={'cursor-pointer'}>
{_defaultValue}
</div>
);
};
const renderPrivew = () => {
const _defaultValue = Array.isArray(defaultValue.value) ? defaultValue.value.join(',') : defaultValue.value;
return (
<div
onClick={handleClick}
onMouseleave={() => {
if (editStatus.value === 'hover') editStatus.value = 'null';
}}
class="gap[8px] flex h-[32px] w-[112px] cursor-pointer flex-row items-center justify-between bg-[var(--color-text-n8)] p-[8px]"
>
<div class="grow">{_defaultValue}</div>
<div>
<icon-down size={16} />
</div>
</div>
);
};
const renderChild = () => {
if (mode.value === 'input') {
return <a-input modelValue={modelValue} onKeyDown={handleKeyDown} onBlur={handleBlur} />;
}
if (mode.value === 'select') {
return <a-select modelValue={modelValue} options={options.value} onSelect={handleChange} />;
}
if (mode.value === 'tagInput') {
return <a-input-tag modelValue={modelValue} onKeyDown={handleKeyDown} onBlur={handleBlur} />;
}
if (mode.value === 'userSelect') {
return (
<MsUserSelector
modelValue={modelValue.value as string[]}
onSelect={handleChange}
type={props.userSelectorType}
/>
);
}
};
return () => (
<div
class="ms-edit-comp"
onMouseenter={() => {
if (editStatus.value === 'null') editStatus.value = 'hover';
}}
>
{editStatus.value === 'null' && renderDiv()}
{editStatus.value === 'hover' && renderPrivew()}
{editStatus.value === 'active' && renderChild()}
</div>
);
},
});

View File

@ -0,0 +1,12 @@
import EditComp from './edit-comp';
import type { App } from 'vue';
const MsEditComp = Object.assign(EditComp, {
install: (app: App) => {
app.component(EditComp.name, EditComp);
},
});
export type CommentInstance = InstanceType<typeof EditComp>;
export default MsEditComp;

View File

@ -0,0 +1,3 @@
.child-bg > :first-child {
background-color: var(--color-text-8);
}

View File

@ -0,0 +1,6 @@
export type ModelValueType =
| string
| number
| boolean
| Record<string, any>
| (string | number | boolean | Record<string, any>)[];

View File

@ -63,6 +63,7 @@
const emit = defineEmits<{
(e: 'update:modelValue', value: string[]): void;
(e: 'select', value: string[]): void;
}>();
const { t } = useI18n();
@ -135,6 +136,10 @@
'update:modelValue',
tmpArr.map((item) => item[valueKey])
);
emit(
'select',
tmpArr.map((item) => item[valueKey])
);
};
onBeforeMount(async () => {

View File

@ -403,11 +403,15 @@
// change
const handleSortChange = (dataIndex: string, direction: string) => {
let firstIndex = 0;
if (attrs.selectable) {
firstIndex = 1;
}
const regex = /^__arco_data_index_(\d+)$/;
const match = dataIndex.match(regex);
const lastDigit = match && Number(match[1]);
if (lastDigit && !Number.isNaN(lastDigit)) {
dataIndex = currentColumns.value[lastDigit].dataIndex as string;
if (lastDigit !== null && !Number.isNaN(lastDigit)) {
dataIndex = currentColumns.value[lastDigit - firstIndex].dataIndex as string;
}
let sortOrder = '';
if (direction === 'ascend') {

View File

@ -7,11 +7,11 @@ export interface BugListItem {
handleUser: string; // 缺陷处理人
relationCaseCount: number; // 关联用例数
platform: string; // 所属平台
tag: string; // 缺陷标签
tag: string[]; // 缺陷标签
createUser: string; // 创建人
updateUser: string; // 更新人
createTime: string; // 创建时间
updateTime: string; // 更新时间
deleted: string; // 删除标志
deleted: boolean; // 删除标志
}
export default {};

View File

@ -78,7 +78,7 @@
<template #left>
<div class="leftWrapper h-full">
<div class="header h-[50px]">
<a-tabs>
<a-tabs v-model:active-key="activeTab">
<a-tab-pane key="detail">
<BugDetailTab :detail-info="detailInfo" />
</a-tab-pane>
@ -88,9 +88,6 @@
<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>
@ -126,7 +123,6 @@
</div>
</template>
</MsDetailDrawer>
<SettingDrawer v-model:visible="showSettingDrawer" />
</template>
<script setup lang="ts">
@ -142,6 +138,8 @@
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 BugCaseTab from './bugCaseTab.vue';
import BugCommentTab from './bugCommentTab.vue';
import BugDetailTab from './bugDetailTab.vue';
import { deleteCaseRequest, followerCaseRequest, getCaseDetail } from '@/api/modules/case-management/featureCase';
@ -183,27 +181,12 @@
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[]>([]);
@ -238,7 +221,9 @@
const shareLoading = ref<boolean>(false);
function shareHandler() {}
function shareHandler() {
Message.info(t('caseManagement.featureCase.share'));
}
const followLoading = ref<boolean>(false);
//
@ -253,6 +238,7 @@
: t('caseManagement.featureCase.followSuccess')
);
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
} finally {
followLoading.value = false;
@ -283,6 +269,7 @@
updateSuccess();
detailDrawerRef.value?.openPrevDetail();
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
}
},

View File

@ -1,14 +1,5 @@
<template>
<MsDrawer
:width="680"
:visible="currentVisible"
unmount-on-close
:footer="false"
:title="t('system.organization.addMember')"
:mask="false"
@cancel="handleCancel"
>
<div>
<div class="p-[16px]">
<div class="flex flex-row justify-between">
<a-dropdown trigger="hover">
<template #content>
@ -33,15 +24,13 @@
<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>
<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)"
@ok="handleRemove()"
/>
</template>
</ms-base-table>
@ -58,19 +47,18 @@
<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-option>1</a-option>
<a-option>2</a-option>
<a-option>3</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 { ref } from 'vue';
import { Message } from '@arco-design/web-vue';
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
@ -78,20 +66,10 @@
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 { 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');
@ -100,8 +78,6 @@
relatedType.value = type;
};
const currentVisible = ref(props.visible);
const keyword = ref('');
const projectColumn: MsTableColumn = [
@ -125,7 +101,7 @@
{ title: 'system.organization.operation', slotName: 'operation' },
];
const { propsRes, propsEvent, loadList, setLoadListParams, setKeyword } = useTable(postProjectMemberByProjectId, {
const { propsRes, propsEvent, loadList, setKeyword } = useTable(postProjectMemberByProjectId, {
heightUsed: 240,
columns: projectColumn,
scroll: { x: '100%' },
@ -139,20 +115,12 @@
await loadList();
}
const handleCancel = () => {
keyword.value = '';
emit('cancel');
};
const fetchData = async () => {
await loadList();
};
const handleRemove = async (record: TableData) => {
const handleRemove = async () => {
try {
if (props.projectId) {
await deleteProjectMemberByOrg(props.projectId, record.id);
}
Message.success(t('common.removeSuccess'));
fetchData();
} catch (error) {
@ -160,22 +128,6 @@
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>

View File

@ -1,6 +1,6 @@
<template>
<MsCard simple>
<MsAdvanceFilter :filter-config-list="filterConfigList" :row-count="filterRowCount">
<MsAdvanceFilter :filter-config-list="filterConfigList" :row-count="filterRowCount" @keyword-search="fetchData">
<template #left>
<div class="flex gap-[12px]">
<a-button type="primary" @click="handleCreate">{{ t('bugManagement.createBug') }} </a-button>
@ -13,6 +13,14 @@
<template #name="{ record, rowIndex }">
<a-button type="text" class="px-0" @click="handleShowDetail(record.id, rowIndex)">{{ record.name }}</a-button>
</template>
<!-- 严重程度 -->
<template #severity="{ record }">
<MsEditComp mode="select" :options="severityOption" :default-value="record.severity" />
</template>
<!-- 状态 -->
<template #status="{ record }">
<MsEditComp mode="select" :options="statusOption" :default-value="record.status" />
</template>
<template #numberOfCase="{ record }">
<span class="cursor-pointer text-[rgb(var(--primary-5))]" @click="jumpToTestPlan(record)">{{
record.memberCount
@ -24,7 +32,7 @@
<a-divider direction="vertical" />
<MsButton class="!mr-0" @click="handleEdit(record)">{{ t('common.edit') }}</MsButton>
<a-divider direction="vertical" />
<MsButton class="!mr-0" status="danger" @click="handleDelete(record)">{{ t('common.delete') }}</MsButton>
<MsTableMoreAction :list="moreActionList" trigger="click" @select="handleMoreActionSelect($event, record)" />
</div>
</template>
<template #empty> </template>
@ -92,6 +100,9 @@
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 MsEditComp from '@/components/business/ms-edit-comp';
import BugDetailDrawer from './components/bug-detail-drawer.vue';
import { getBugList, getExportConfig } from '@/api/modules/bug-management';
@ -170,13 +181,16 @@
},
{
title: 'bugManagement.severity',
slotName: 'memberCount',
slotName: 'severity',
width: 139,
showDrag: true,
dataIndex: 'severity',
},
{
title: 'bugManagement.status',
dataIndex: 'status',
width: 139,
slotName: 'status',
showDrag: true,
},
{
@ -228,7 +242,7 @@
slotName: 'operation',
dataIndex: 'operation',
fixed: 'right',
width: 230,
width: 140,
},
];
await tableStore.initColumn(TableKeyEnum.BUG_MANAGEMENT, columns, 'drawer');
@ -243,11 +257,11 @@
}
};
const { propsRes, propsEvent, loadList, setKeyword, setLoadListParams, setProps } = useTable(
const { propsRes, propsEvent, setKeyword, setLoadListParams, setProps } = useTable(
getBugList,
{
tableKey: TableKeyEnum.BUG_MANAGEMENT,
selectable: false,
selectable: true,
noDisable: false,
showJumpMethod: true,
showSetting: true,
@ -261,9 +275,61 @@
setProps({ heightUsed: heightUsed.value });
});
const data: BugListItem[] = [
{
id: '1',
deleted: false,
num: '1',
name: 'Bug 1',
severity: 'High',
status: 'Open',
handleUser: 'John Doe',
relationCaseCount: 3,
platform: 'Windows',
tag: ['Critical'],
createUser: 'Alice',
updateUser: 'Bob',
createTime: '2023-12-07T10:00:00Z',
updateTime: '2023-12-07T11:30:00Z',
},
{
id: '2',
deleted: false,
num: '2',
name: 'Bug 2',
severity: 'Medium',
status: 'In Progress',
handleUser: 'Jane Smith',
relationCaseCount: 1,
platform: 'Linux',
tag: ['Critical'],
createUser: 'Eve',
updateUser: 'Charlie',
createTime: '2023-12-06T09:15:00Z',
updateTime: '2023-12-06T15:45:00Z',
},
{
id: '3',
deleted: false,
num: '3',
name: 'Bug 3',
severity: 'Low',
status: 'Resolved',
handleUser: 'Alex Johnson',
relationCaseCount: 2,
platform: 'Mac',
tag: ['Critical'],
createUser: 'David',
updateUser: 'Frank',
createTime: '2023-12-05T14:20:00Z',
updateTime: '2023-12-06T10:10:00Z',
},
// Add more data as needed
];
const fetchData = async (v = '') => {
setKeyword(v);
await loadList();
setProps({ data });
};
const handleCreate = () => {
@ -315,9 +381,68 @@
exportOptionData.value = res;
};
const moreActionList: ActionsItem[] = [
{
label: t('common.delete'),
danger: true,
eventTag: 'delete',
},
];
function handleMoreActionSelect(item: ActionsItem, record: BugListItem) {
if (item.eventTag === 'delete') {
handleDelete(record);
}
}
const severityOption = [
{
label: t('bugManagement.severityO.fatal'),
value: 'High',
},
{
label: t('bugManagement.severityO.serious'),
value: 'Medium',
},
{
label: t('bugManagement.severityO.general'),
value: 'Low',
},
{
label: t('bugManagement.severityO.reminder'),
value: 'Info',
},
];
const statusOption = [
{
label: t('bugManagement.statusO.create'),
value: 'Create',
},
{
label: t('bugManagement.statusO.processing'),
value: 'Processing',
},
{
label: t('bugManagement.statusO.resolved'),
value: 'Resolved',
},
{
label: t('bugManagement.statusO.closed'),
value: 'Closed',
},
{ label: t('bugManagement.statusO.refused'), value: 'Refused' },
];
onMounted(() => {
setLoadListParams({ projectId: projectId.value });
fetchData();
setExportOptionData();
});
</script>
<style lang="less" scoped>
:deep(.arco-divider-vertical) {
margin: 0 8px;
}
</style>

View File

@ -49,5 +49,18 @@ export default {
performanceCase: '性能用例',
searchCase: '通过名称搜索',
},
severityO: {
fatal: '致命',
serious: '严重',
general: '一般',
reminder: '提醒',
},
statusO: {
create: '新建',
processing: '处理中',
resolved: '已解决',
closed: '已关闭',
refused: '已拒绝',
},
},
};

View File

@ -359,6 +359,7 @@
{
title: 'caseManagement.featureCase.tableColumnModule',
slotName: 'moduleId',
dataIndex: 'moduleId',
showInTable: true,
width: 300,
showDrag: true,