2026-05-06 17:50:29 +08:00
|
|
|
<script setup lang="ts">
|
|
|
|
|
import { computed, nextTick, ref, watch } from 'vue';
|
|
|
|
|
import { fetchSplitRequirement } from '@/service/api';
|
|
|
|
|
import { useForm, useFormRules } from '@/hooks/common/form';
|
|
|
|
|
import { useDict } from '@/hooks/business/dict';
|
|
|
|
|
import BusinessFormDialog from '@/components/custom/business-form-dialog.vue';
|
2026-05-09 18:15:10 +08:00
|
|
|
import BusinessRichTextEditor from '@/components/custom/business-rich-text-editor.vue';
|
2026-05-06 17:50:29 +08:00
|
|
|
import MemberSelectOption from './member-select-option.vue';
|
|
|
|
|
|
|
|
|
|
defineOptions({ name: 'RequirementSplitDialog' });
|
|
|
|
|
|
|
|
|
|
interface Props {
|
|
|
|
|
parentRequirement: Api.Product.Requirement | null;
|
|
|
|
|
productId: string;
|
|
|
|
|
memberOptions: Api.Product.ProductMember[];
|
|
|
|
|
categoryDictCode: string;
|
|
|
|
|
priorityDictCode: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const props = defineProps<Props>();
|
|
|
|
|
|
|
|
|
|
interface Emits {
|
|
|
|
|
(e: 'submitted'): void;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const emit = defineEmits<Emits>();
|
|
|
|
|
|
|
|
|
|
const visible = defineModel<boolean>('visible', {
|
|
|
|
|
default: false
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const { formRef, validate } = useForm();
|
|
|
|
|
const { createRequiredRule } = useFormRules();
|
|
|
|
|
|
|
|
|
|
const { enabledDictData: priorityDictData } = useDict(() => props.priorityDictCode);
|
|
|
|
|
|
|
|
|
|
const priorityOptions = computed(() => {
|
|
|
|
|
return priorityDictData.value.map(item => ({
|
|
|
|
|
label: item.label,
|
|
|
|
|
value: Number(item.value)
|
|
|
|
|
}));
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
interface Model {
|
|
|
|
|
title: string;
|
|
|
|
|
description: string;
|
|
|
|
|
reviewRequired: number;
|
|
|
|
|
category: string;
|
|
|
|
|
priority: number | null;
|
|
|
|
|
currentHandlerUserId: string;
|
2026-05-09 13:42:04 +08:00
|
|
|
workHours: number | null;
|
2026-05-06 17:50:29 +08:00
|
|
|
sort: number;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const submitting = ref(false);
|
|
|
|
|
const loading = ref(false);
|
|
|
|
|
|
|
|
|
|
const model = ref<Model>(createDefaultModel());
|
|
|
|
|
|
|
|
|
|
const memberUserOptions = computed(() => {
|
|
|
|
|
return props.memberOptions.filter(m => m.status === 0);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const reviewRequiredOptions = [
|
|
|
|
|
{ label: '不需要', value: 0 },
|
|
|
|
|
{ label: '需要', value: 1 }
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const rules = {
|
2026-05-09 13:42:04 +08:00
|
|
|
title: [createRequiredRule('请输入子需求名称')],
|
2026-05-06 17:50:29 +08:00
|
|
|
priority: [createRequiredRule('请选择优先级')],
|
|
|
|
|
currentHandlerUserId: [createRequiredRule('请选择负责人')],
|
2026-05-09 13:42:04 +08:00
|
|
|
workHours: [createRequiredRule('请输入所需工时')]
|
2026-05-06 17:50:29 +08:00
|
|
|
} satisfies Record<string, App.Global.FormRule[]>;
|
|
|
|
|
|
|
|
|
|
function createDefaultModel(): Model {
|
|
|
|
|
return {
|
|
|
|
|
title: '',
|
|
|
|
|
description: '',
|
|
|
|
|
reviewRequired: 0,
|
|
|
|
|
category: '',
|
|
|
|
|
priority: 1,
|
|
|
|
|
currentHandlerUserId: '',
|
2026-05-09 13:42:04 +08:00
|
|
|
workHours: null,
|
2026-05-06 17:50:29 +08:00
|
|
|
sort: 0
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getNullableText(value?: string | null) {
|
|
|
|
|
return value?.trim() || null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function closeDialog() {
|
|
|
|
|
visible.value = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function handleSubmit() {
|
|
|
|
|
await validate();
|
|
|
|
|
|
|
|
|
|
if (!props.productId || !props.parentRequirement?.id) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-09 13:42:04 +08:00
|
|
|
const proposerNickname = props.parentRequirement.proposerNickname || '';
|
|
|
|
|
const currentHandlerUserNickname = props.parentRequirement.currentHandlerUserNickname || '';
|
|
|
|
|
|
2026-05-06 17:50:29 +08:00
|
|
|
const payload: Api.Product.SplitRequirementParams = {
|
|
|
|
|
parentId: props.parentRequirement.id,
|
|
|
|
|
productId: props.productId,
|
|
|
|
|
moduleId: props.parentRequirement.moduleId,
|
|
|
|
|
proposerId: props.parentRequirement.proposerId,
|
2026-05-09 13:42:04 +08:00
|
|
|
proposerNickname,
|
|
|
|
|
currentHandlerUserNickname,
|
2026-05-06 17:50:29 +08:00
|
|
|
title: model.value.title.trim(),
|
|
|
|
|
description: getNullableText(model.value.description),
|
|
|
|
|
reviewRequired: model.value.reviewRequired as Api.Product.RequirementReviewRequired,
|
|
|
|
|
category: model.value.category,
|
|
|
|
|
priority: Number(model.value.priority) as Api.Product.RequirementPriority,
|
|
|
|
|
currentHandlerUserId: model.value.currentHandlerUserId,
|
2026-05-09 13:42:04 +08:00
|
|
|
workHours: model.value.workHours || 0,
|
2026-05-06 17:50:29 +08:00
|
|
|
sort: model.value.sort
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
submitting.value = true;
|
|
|
|
|
|
|
|
|
|
const result = await fetchSplitRequirement(payload);
|
|
|
|
|
|
|
|
|
|
submitting.value = false;
|
|
|
|
|
|
|
|
|
|
if (result.error) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
window.$message?.success('需求拆分成功');
|
|
|
|
|
closeDialog();
|
|
|
|
|
emit('submitted');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
watch(
|
|
|
|
|
() => visible.value,
|
|
|
|
|
async value => {
|
|
|
|
|
if (!value) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
model.value = createDefaultModel();
|
|
|
|
|
|
2026-05-09 13:42:04 +08:00
|
|
|
if (props.parentRequirement?.category) {
|
|
|
|
|
model.value.category = props.parentRequirement.category;
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-13 21:13:21 +08:00
|
|
|
// 默认选中父需求的负责人
|
|
|
|
|
if (props.parentRequirement?.currentHandlerUserId) {
|
|
|
|
|
model.value.currentHandlerUserId = props.parentRequirement.currentHandlerUserId;
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-06 17:50:29 +08:00
|
|
|
await nextTick();
|
|
|
|
|
formRef.value?.clearValidate();
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<template>
|
|
|
|
|
<BusinessFormDialog
|
|
|
|
|
v-model="visible"
|
|
|
|
|
title="拆分需求"
|
|
|
|
|
preset="lg"
|
|
|
|
|
:loading="loading"
|
|
|
|
|
:confirm-loading="submitting"
|
|
|
|
|
@confirm="handleSubmit"
|
|
|
|
|
>
|
|
|
|
|
<ElAlert
|
|
|
|
|
v-if="parentRequirement"
|
|
|
|
|
:title="`正在拆分需求:${parentRequirement.title}`"
|
|
|
|
|
type="info"
|
|
|
|
|
:closable="false"
|
|
|
|
|
class="mb-16px"
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
<ElForm ref="formRef" :model="model" :rules="rules" label-position="top">
|
|
|
|
|
<ElRow :gutter="16">
|
|
|
|
|
<ElCol :span="12">
|
2026-05-09 13:42:04 +08:00
|
|
|
<ElFormItem label="子需求名称" prop="title">
|
|
|
|
|
<ElInput v-model="model.title" clearable maxlength="256" placeholder="请输入子需求名称" />
|
2026-05-06 17:50:29 +08:00
|
|
|
</ElFormItem>
|
|
|
|
|
</ElCol>
|
|
|
|
|
<ElCol :span="12">
|
|
|
|
|
<ElFormItem label="是否需要评审">
|
|
|
|
|
<ElSelect v-model="model.reviewRequired" class="w-full" placeholder="请选择">
|
|
|
|
|
<ElOption
|
|
|
|
|
v-for="item in reviewRequiredOptions"
|
|
|
|
|
:key="item.value"
|
|
|
|
|
:label="item.label"
|
|
|
|
|
:value="item.value"
|
|
|
|
|
/>
|
|
|
|
|
</ElSelect>
|
|
|
|
|
</ElFormItem>
|
|
|
|
|
</ElCol>
|
2026-05-09 13:42:04 +08:00
|
|
|
<ElCol :span="24">
|
|
|
|
|
<ElFormItem label="内容">
|
2026-05-09 18:15:10 +08:00
|
|
|
<BusinessRichTextEditor
|
2026-05-09 13:42:04 +08:00
|
|
|
v-model="model.description"
|
2026-05-09 18:15:10 +08:00
|
|
|
height="240px"
|
|
|
|
|
upload-directory="requirement"
|
2026-05-09 13:42:04 +08:00
|
|
|
placeholder="请输入需求内容"
|
|
|
|
|
/>
|
2026-05-06 17:50:29 +08:00
|
|
|
</ElFormItem>
|
|
|
|
|
</ElCol>
|
|
|
|
|
<ElCol :span="12">
|
|
|
|
|
<ElFormItem label="优先级" prop="priority">
|
|
|
|
|
<ElSelect v-model="model.priority" class="w-full" filterable placeholder="请选择优先级">
|
|
|
|
|
<ElOption v-for="item in priorityOptions" :key="item.value" :label="item.label" :value="item.value" />
|
|
|
|
|
</ElSelect>
|
|
|
|
|
</ElFormItem>
|
|
|
|
|
</ElCol>
|
2026-05-09 18:15:10 +08:00
|
|
|
<ElCol :span="12">
|
|
|
|
|
<ElFormItem label="所需工时" prop="workHours">
|
|
|
|
|
<ElInputNumber
|
|
|
|
|
v-model="model.workHours"
|
|
|
|
|
class="w-full"
|
|
|
|
|
:min="0"
|
|
|
|
|
:max="9999"
|
|
|
|
|
:precision="1"
|
|
|
|
|
placeholder="请输入所需工时"
|
|
|
|
|
/>
|
|
|
|
|
</ElFormItem>
|
|
|
|
|
</ElCol>
|
2026-05-06 17:50:29 +08:00
|
|
|
<ElCol :span="12">
|
|
|
|
|
<ElFormItem label="负责人" prop="currentHandlerUserId">
|
|
|
|
|
<ElSelect v-model="model.currentHandlerUserId" class="w-full" filterable placeholder="请选择负责人">
|
|
|
|
|
<ElOption
|
|
|
|
|
v-for="item in memberUserOptions"
|
|
|
|
|
:key="item.userId"
|
|
|
|
|
:label="item.userNickname"
|
|
|
|
|
:value="item.userId"
|
|
|
|
|
>
|
|
|
|
|
<MemberSelectOption :nickname="item.userNickname" :role-name="item.roleName" />
|
|
|
|
|
</ElOption>
|
|
|
|
|
</ElSelect>
|
|
|
|
|
</ElFormItem>
|
|
|
|
|
</ElCol>
|
|
|
|
|
<ElCol :span="12">
|
|
|
|
|
<ElFormItem label="排序值">
|
|
|
|
|
<ElInputNumber v-model="model.sort" class="w-full" :min="0" :max="9999" placeholder="请输入排序值" />
|
|
|
|
|
</ElFormItem>
|
|
|
|
|
</ElCol>
|
|
|
|
|
</ElRow>
|
|
|
|
|
</ElForm>
|
|
|
|
|
</BusinessFormDialog>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<style scoped></style>
|