fix(产品需求): 完善产品需求的诸多细节。
This commit is contained in:
@@ -75,3 +75,11 @@ export const RDMS_PROJECT_TYPE_DICT_CODE = 'rdms_project_type';
|
||||
* 来源口径:`rdms-project-boot-执行任务接口API文档.md` 明确 executionType 来自字典 rdms_project_execution_type
|
||||
*/
|
||||
export const RDMS_PROJECT_EXECUTION_TYPE_DICT_CODE = 'rdms_project_execution_type';
|
||||
|
||||
/**
|
||||
* 需求允许删除的状态字典编码
|
||||
*
|
||||
* 对应业务字段:需求删除功能中判断 statusCode 是否允许删除
|
||||
* 来源口径:用户在系统字典管理页中创建的字典 rdms_req_can_delete_status
|
||||
*/
|
||||
export const RDMS_REQ_CAN_DELETE_STATUS_DICT_CODE = 'rdms_req_can_delete_status';
|
||||
|
||||
@@ -179,6 +179,7 @@ type RequirementResponse = Omit<
|
||||
proposerId: string | number;
|
||||
currentHandlerUserId?: string | number | null;
|
||||
implementProjectId?: string | number | null;
|
||||
implementProjectName?: string | null;
|
||||
sourceBizId?: string | number | null;
|
||||
children?: RequirementResponse[];
|
||||
};
|
||||
@@ -194,6 +195,7 @@ function normalizeRequirement(requirement: RequirementResponse): Api.Product.Req
|
||||
proposerId: normalizeStringId(requirement.proposerId),
|
||||
currentHandlerUserId: normalizeNullableStringId(requirement.currentHandlerUserId),
|
||||
implementProjectId: normalizeNullableStringId(requirement.implementProjectId),
|
||||
implementProjectName: requirement.implementProjectName ?? null,
|
||||
sourceBizId: normalizeNullableStringId(requirement.sourceBizId),
|
||||
children: requirement.children?.map(normalizeRequirement)
|
||||
};
|
||||
|
||||
@@ -135,6 +135,18 @@ export async function fetchGetProject(id: string) {
|
||||
return mapServiceResult(result as ServiceRequestResult<ProjectResponse>, normalizeProject);
|
||||
}
|
||||
|
||||
/** 根据产品ID获取产品下的所有项目 */
|
||||
export async function fetchGetProjectListByProductId(productId: string) {
|
||||
const result = await request<ProjectResponse[]>({
|
||||
...safeJsonRequestConfig,
|
||||
url: `${PROJECT_PREFIX}/list-by-product`,
|
||||
method: 'get',
|
||||
params: { productId }
|
||||
});
|
||||
|
||||
return mapServiceResult(result as ServiceRequestResult<ProjectResponse[]>, data => data.map(normalizeProject));
|
||||
}
|
||||
|
||||
/** 创建项目 */
|
||||
export async function fetchCreateProject(data: Api.Project.SaveProjectParams) {
|
||||
const result = await request<string | number>({
|
||||
|
||||
@@ -795,5 +795,3 @@ export async function fetchGetCandidateSubordinateUsers() {
|
||||
mapServiceResult(result as ServiceRequestResult<UserSimpleResponse[]>, data => data.map(normalizeUserSimple))
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
2
src/typings/api/product.d.ts
vendored
2
src/typings/api/product.d.ts
vendored
@@ -288,6 +288,8 @@ declare namespace Api {
|
||||
currentHandlerUserNickname?: string | null;
|
||||
/** 默认实现项目编号 */
|
||||
implementProjectId?: string | null;
|
||||
/** 默认实现项目名称 */
|
||||
implementProjectName?: string | null;
|
||||
/** 所需工时(小时) */
|
||||
workHours: number;
|
||||
/** 排序值 */
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
<script setup lang="tsx">
|
||||
import { computed, onMounted, reactive, ref, watch } from 'vue';
|
||||
import { computed, markRaw, onMounted, reactive, ref, watch } from 'vue';
|
||||
import type { TableInstance } from 'element-plus';
|
||||
import { ElButton, ElTag, ElTooltip } from 'element-plus';
|
||||
import dayjs from 'dayjs';
|
||||
import {
|
||||
RDMS_REQ_CAN_DELETE_STATUS_DICT_CODE,
|
||||
RDMS_REQ_CATEGORY_DICT_CODE,
|
||||
RDMS_REQ_PRIORITY_DICT_CODE,
|
||||
RDMS_REQ_SOURCE_TYPE_DICT_CODE
|
||||
@@ -12,15 +13,16 @@ import {
|
||||
fetchChangeRequirementStatus,
|
||||
fetchDeleteRequirement,
|
||||
fetchGetProductMembers,
|
||||
fetchGetProjectListByProductId,
|
||||
fetchGetRequirementAllowedTransitions,
|
||||
fetchGetRequirementStatusDict,
|
||||
fetchGetRequirementTerminalStatusDict,
|
||||
fetchGetRequirementTree
|
||||
} from '@/service/api';
|
||||
import { useAuth } from '@/hooks/business/auth';
|
||||
import { useDict } from '@/hooks/business/dict';
|
||||
import DictTag from '@/components/custom/dict-tag.vue';
|
||||
import DictText from '@/components/custom/dict-text.vue';
|
||||
import SvgIcon from '@/components/custom/svg-icon.vue';
|
||||
import { useCurrentProduct } from '../shared/use-current-product';
|
||||
import {
|
||||
type RequirementStatusActionCode,
|
||||
@@ -36,6 +38,16 @@ import RequirementCreateDialog from './modules/requirement-create-dialog.vue';
|
||||
import RequirementDetailDialog from './modules/requirement-detail-dialog.vue';
|
||||
import RequirementSplitDialog from './modules/requirement-split-dialog.vue';
|
||||
import RequirementActionDialog from './modules/requirement-action-dialog.vue';
|
||||
import IconMdiPencilOutline from '~icons/mdi/pencil-outline';
|
||||
import IconMdiCheckOutline from '~icons/mdi/check-outline';
|
||||
import IconMdiCheckCircleOutline from '~icons/mdi/check-circle-outline';
|
||||
import IconMdiSync from '~icons/mdi/sync';
|
||||
import IconMdiPowerSettingsNew from '~icons/mdi/power-settings-new';
|
||||
import IconMdiArrowSplitVertical from '~icons/mdi/arrow-split-vertical';
|
||||
import IconMdiBookOpenPageVariantOutline from '~icons/mdi/book-open-page-variant-outline';
|
||||
import IconMdiDeleteOutline from '~icons/mdi/delete-outline';
|
||||
import IconMdiCloseCircleOutline from '~icons/mdi/close-circle-outline';
|
||||
import IconTablerSitemap from '~icons/tabler/sitemap';
|
||||
|
||||
defineOptions({ name: 'ProductRequirement' });
|
||||
|
||||
@@ -45,43 +57,50 @@ const { hasObjectAuth } = useAuth();
|
||||
/**
|
||||
* 操作按钮图标映射
|
||||
*
|
||||
* 将操作类型映射到对应的 Iconify 图标
|
||||
* 将操作类型映射到对应的 Iconify 图标组件
|
||||
*/
|
||||
const actionIconMap: Record<string, string> = {
|
||||
split: 'ic:round-call-split',
|
||||
edit: 'ic:round-edit',
|
||||
claim_to_review: 'ic:round-check',
|
||||
claim_to_dispatch: 'ic:round-check',
|
||||
to_dispatch: 'ic:round-verified',
|
||||
dispatch: 'ic:round-merge-type',
|
||||
accept: 'ic:round-check-circle',
|
||||
reject: 'ic:round-cancel',
|
||||
cancel: 'ic:round-close',
|
||||
close: 'ic:round-power-settings-new',
|
||||
delete: 'ic:round-delete'
|
||||
const ACTION_ICON_MAP: Record<string, object> = {
|
||||
split: markRaw(IconTablerSitemap),
|
||||
edit: markRaw(IconMdiPencilOutline),
|
||||
claim_to_review: markRaw(IconMdiCheckOutline),
|
||||
claim_to_dispatch: markRaw(IconMdiCheckOutline),
|
||||
to_dispatch: markRaw(IconMdiBookOpenPageVariantOutline),
|
||||
dispatch: markRaw(IconMdiArrowSplitVertical),
|
||||
accept: markRaw(IconMdiCheckCircleOutline),
|
||||
reject: markRaw(IconMdiCloseCircleOutline),
|
||||
cancel: markRaw(IconMdiCloseCircleOutline),
|
||||
close: markRaw(IconMdiPowerSettingsNew),
|
||||
delete: markRaw(IconMdiDeleteOutline)
|
||||
};
|
||||
|
||||
/**
|
||||
* 操作按钮颜色映射
|
||||
* 操作按钮颜色类型映射
|
||||
*
|
||||
* 将操作类型映射到对应的图标颜色(Element Plus 主题色)
|
||||
* 审批/成功类操作 → success,危险操作 → danger,其他 → primary
|
||||
*/
|
||||
const actionColorMap: Record<string, string> = {
|
||||
split: 'var(--el-color-primary)',
|
||||
edit: 'var(--el-color-info)',
|
||||
claim_to_review: 'var(--el-color-primary)',
|
||||
claim_to_dispatch: 'var(--el-color-primary)',
|
||||
to_dispatch: 'var(--el-color-success)',
|
||||
dispatch: 'var(--el-color-primary)',
|
||||
accept: 'var(--el-color-success)',
|
||||
reject: 'var(--el-color-danger)',
|
||||
cancel: 'var(--el-color-danger)',
|
||||
close: 'var(--el-color-danger)',
|
||||
delete: 'var(--el-color-danger)'
|
||||
const ACTION_TYPE_MAP: Record<string, 'primary' | 'success' | 'danger'> = {
|
||||
split: 'primary',
|
||||
edit: 'primary',
|
||||
claim_to_review: 'primary',
|
||||
claim_to_dispatch: 'primary',
|
||||
to_dispatch: 'primary',
|
||||
dispatch: 'primary',
|
||||
accept: 'primary',
|
||||
reject: 'danger',
|
||||
cancel: 'danger',
|
||||
close: 'danger',
|
||||
delete: 'danger'
|
||||
};
|
||||
|
||||
const statusOptions = ref<Array<{ label: string; value: string }>>([]);
|
||||
const terminalStatusOptions = ref<string[]>([]);
|
||||
const projectOptions = ref<Api.Project.Project[]>([]);
|
||||
|
||||
const projectNameMap = computed(() => {
|
||||
return new Map(projectOptions.value.map(item => [item.id, item.projectName]));
|
||||
});
|
||||
|
||||
const { hasValue: canDeleteStatusHasValue } = useDict(RDMS_REQ_CAN_DELETE_STATUS_DICT_CODE);
|
||||
|
||||
async function loadStatusOptions() {
|
||||
const { error, data } = await fetchGetRequirementStatusDict();
|
||||
@@ -108,6 +127,22 @@ async function loadTerminalStatusOptions() {
|
||||
terminalStatusOptions.value = data.map(item => item.statusCode);
|
||||
}
|
||||
|
||||
async function loadProjectOptions() {
|
||||
if (!currentObjectId.value) {
|
||||
projectOptions.value = [];
|
||||
return;
|
||||
}
|
||||
|
||||
const { error, data } = await fetchGetProjectListByProductId(currentObjectId.value);
|
||||
|
||||
if (error || !data) {
|
||||
projectOptions.value = [];
|
||||
return;
|
||||
}
|
||||
|
||||
projectOptions.value = data;
|
||||
}
|
||||
|
||||
function getStatusLabel(statusCode: string) {
|
||||
const item = statusOptions.value.find(opt => opt.value === statusCode);
|
||||
return item ? item.label : statusCode;
|
||||
@@ -129,7 +164,7 @@ function formatDateTime(value?: string | null) {
|
||||
}
|
||||
|
||||
function isTerminalStatus(statusCode: string) {
|
||||
return terminalStatusOptions.value.some(option => option === statusCode);
|
||||
return terminalStatusOptions.value.includes(statusCode);
|
||||
}
|
||||
|
||||
function canSplitRequirement(row: Api.Product.Requirement) {
|
||||
@@ -137,12 +172,7 @@ function canSplitRequirement(row: Api.Product.Requirement) {
|
||||
}
|
||||
|
||||
function canDeleteRequirement(row: Api.Product.Requirement) {
|
||||
const allowedStatusCodes: Api.Product.RequirementStatusCode[] = [
|
||||
'pending_confirm',
|
||||
'pending_review',
|
||||
'pending_dispatch'
|
||||
];
|
||||
const isStatusAllowed = allowedStatusCodes.includes(row.statusCode);
|
||||
const isStatusAllowed = canDeleteStatusHasValue(row.statusCode);
|
||||
const hasNoChildren = !row.children || row.children.length === 0;
|
||||
return isStatusAllowed && hasNoChildren;
|
||||
}
|
||||
@@ -289,16 +319,41 @@ const columns = computed(() => [
|
||||
const className = 'requirement-title';
|
||||
|
||||
return (
|
||||
<ElButton link type='primary' class={className} onClick={() => openView(row)}>
|
||||
<ElButton link type="primary" class={className} onClick={() => openView(row)}>
|
||||
{row.title}
|
||||
</ElButton>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: 'priority',
|
||||
label: '优先级',
|
||||
width: 75,
|
||||
align: 'center',
|
||||
formatter: (row: Api.Product.Requirement) => (
|
||||
<DictTag dictCode={RDMS_REQ_PRIORITY_DICT_CODE} value={row.priority} type={getPriorityTagType(row.priority)} />
|
||||
)
|
||||
},
|
||||
{
|
||||
prop: 'statusCode',
|
||||
label: '状态',
|
||||
width: 100,
|
||||
align: 'center',
|
||||
formatter: (row: Api.Product.Requirement) => (
|
||||
<ElTag type={getRequirementStatusTagType(row.statusCode)}>{getStatusLabel(row.statusCode)}</ElTag>
|
||||
)
|
||||
},
|
||||
{
|
||||
prop: 'workHours',
|
||||
label: '所需工时',
|
||||
width: 75,
|
||||
align: 'center',
|
||||
formatter: (row: Api.Product.Requirement) => (row.workHours !== null ? `${row.workHours}h` : '--')
|
||||
},
|
||||
{
|
||||
prop: 'category',
|
||||
label: '需求类型',
|
||||
minWidth: 120,
|
||||
minWidth: 100,
|
||||
formatter: (row: Api.Product.Requirement) => row.category
|
||||
},
|
||||
{
|
||||
@@ -319,33 +374,6 @@ const columns = computed(() => [
|
||||
// return row.description?.replace(/<[^>]+>/g, '').trim() || '--';
|
||||
// }
|
||||
// },
|
||||
{
|
||||
prop: 'priority',
|
||||
label: '优先级',
|
||||
width: 100,
|
||||
align: 'center',
|
||||
formatter: (row: Api.Product.Requirement) => (
|
||||
<DictTag dictCode={RDMS_REQ_PRIORITY_DICT_CODE} value={row.priority} type={getPriorityTagType(row.priority)} />
|
||||
)
|
||||
},
|
||||
{
|
||||
prop: 'workHours',
|
||||
label: '所需工时',
|
||||
width: 75,
|
||||
align: 'center',
|
||||
formatter: (row: Api.Product.Requirement) => (row.workHours != null ? `${row.workHours}h` : '--')
|
||||
},
|
||||
{
|
||||
prop: 'statusCode',
|
||||
label: '状态',
|
||||
width: 100,
|
||||
align: 'center',
|
||||
formatter: (row: Api.Product.Requirement) => (
|
||||
<ElTag type={getRequirementStatusTagType(row.statusCode)}>
|
||||
{getStatusLabel(row.statusCode)}
|
||||
</ElTag>
|
||||
)
|
||||
},
|
||||
{
|
||||
prop: 'proposerNickname',
|
||||
label: '提出人',
|
||||
@@ -378,7 +406,10 @@ const columns = computed(() => [
|
||||
prop: 'implementProjectId',
|
||||
label: '实现项目',
|
||||
minWidth: 140,
|
||||
formatter: (row: Api.Product.Requirement) => row.implementProjectId || '--'
|
||||
formatter: (row: Api.Product.Requirement) => {
|
||||
if (!row.implementProjectId) return '--';
|
||||
return projectNameMap.value.get(row.implementProjectId) || row.implementProjectName || '--';
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: 'createTime',
|
||||
@@ -396,8 +427,8 @@ const columns = computed(() => [
|
||||
const actions: {
|
||||
key: string;
|
||||
label: string;
|
||||
icon: string;
|
||||
color: string;
|
||||
icon: object;
|
||||
type: 'primary' | 'success' | 'danger';
|
||||
disabled?: boolean;
|
||||
onClick: () => void;
|
||||
}[] = [];
|
||||
@@ -406,8 +437,8 @@ const columns = computed(() => [
|
||||
actions.push({
|
||||
key: 'split',
|
||||
label: '拆分',
|
||||
icon: actionIconMap.split,
|
||||
color: actionColorMap.split,
|
||||
icon: ACTION_ICON_MAP.split,
|
||||
type: ACTION_TYPE_MAP.split,
|
||||
onClick: () => openSplit(row)
|
||||
});
|
||||
}
|
||||
@@ -416,8 +447,8 @@ const columns = computed(() => [
|
||||
actions.push({
|
||||
key: 'edit',
|
||||
label: '编辑',
|
||||
icon: actionIconMap.edit,
|
||||
color: actionColorMap.edit,
|
||||
icon: ACTION_ICON_MAP.edit,
|
||||
type: ACTION_TYPE_MAP.edit,
|
||||
onClick: () => openEdit(row)
|
||||
});
|
||||
}
|
||||
@@ -442,8 +473,8 @@ const columns = computed(() => [
|
||||
actions.push({
|
||||
key: `action-${action.actionCode}`,
|
||||
label: getRequirementActionDisplayName(action),
|
||||
icon: actionIconMap[action.actionCode] || 'ic:round-play-arrow',
|
||||
color: actionColorMap[action.actionCode] || 'var(--el-color-primary)',
|
||||
icon: ACTION_ICON_MAP[action.actionCode] ?? markRaw(IconMdiSync),
|
||||
type: ACTION_TYPE_MAP[action.actionCode] ?? 'primary',
|
||||
onClick: () => handleActionClick(row, action)
|
||||
});
|
||||
}
|
||||
@@ -453,26 +484,30 @@ const columns = computed(() => [
|
||||
actions.push({
|
||||
key: 'delete',
|
||||
label: '删除',
|
||||
icon: actionIconMap.delete,
|
||||
color: actionColorMap.delete,
|
||||
icon: ACTION_ICON_MAP.delete,
|
||||
type: ACTION_TYPE_MAP.delete,
|
||||
onClick: () => handleDelete(row)
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div class="requirement-action-cell" onClick={event => event.stopPropagation()}>
|
||||
{actions.map(action => (
|
||||
<ElTooltip key={action.key} content={action.label} placement="top">
|
||||
<ElButton
|
||||
text
|
||||
size="small"
|
||||
class="requirement-action-icon-btn"
|
||||
onClick={() => action.onClick()}
|
||||
>
|
||||
<SvgIcon icon={action.icon} class="text-18px" style={{ color: action.color }} />
|
||||
</ElButton>
|
||||
</ElTooltip>
|
||||
))}
|
||||
{actions.map(action => {
|
||||
const IconComponent = action.icon as any;
|
||||
return (
|
||||
<ElTooltip key={action.key} content={action.label} placement="top">
|
||||
<ElButton
|
||||
link
|
||||
size="small"
|
||||
class="requirement-action-icon-btn"
|
||||
type={action.type}
|
||||
onClick={() => action.onClick()}
|
||||
>
|
||||
<IconComponent class="text-18px" />
|
||||
</ElButton>
|
||||
</ElTooltip>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -727,11 +762,12 @@ watch(
|
||||
() => currentObjectId.value,
|
||||
async id => {
|
||||
if (id) {
|
||||
await Promise.all([loadMembers(), loadTreeData()]);
|
||||
await Promise.all([loadMembers(), loadTreeData(), loadProjectOptions()]);
|
||||
await loadAllowedTransitionsForAll();
|
||||
} else {
|
||||
memberOptions.value = [];
|
||||
treeData.value = [];
|
||||
projectOptions.value = [];
|
||||
allowedTransitionsMap.value = new Map();
|
||||
}
|
||||
},
|
||||
@@ -855,6 +891,7 @@ onMounted(async () => {
|
||||
v-model:visible="actionVisible"
|
||||
:action="currentAction"
|
||||
:requirement-title="actionRequirement?.title || ''"
|
||||
:project-options="projectOptions"
|
||||
@submitted="handleActionSubmitted"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, inject, ref, type Ref } from 'vue';
|
||||
import { type Ref, computed, inject, ref } from 'vue';
|
||||
|
||||
defineOptions({ name: 'ModuleTreeNode' });
|
||||
|
||||
@@ -40,7 +40,9 @@ const isSelected = computed(() => props.selectedModuleId === props.module.id);
|
||||
const isEditing = computed(() => props.editingNodeId === props.module.id);
|
||||
const isAddingChild = computed(() => props.addingChildParentId === props.module.id);
|
||||
const hasChildren = computed(() => props.module.children && props.module.children.length > 0);
|
||||
const isCollapsed = computed(() => hasChildren.value && props.module.id ? collapsedModuleIds.value.has(props.module.id) : false);
|
||||
const isCollapsed = computed(() =>
|
||||
hasChildren.value && props.module.id ? collapsedModuleIds.value.has(props.module.id) : false
|
||||
);
|
||||
|
||||
const hasRequirements = computed(() => {
|
||||
const moduleId = props.module.id;
|
||||
@@ -111,7 +113,11 @@ function handleToggle() {
|
||||
:style="indentStyle"
|
||||
@click="handleClick"
|
||||
>
|
||||
<div class="module-tree-item__toggle" :class="{ 'is-expanded': hasChildren && !isCollapsed }" @click.stop="handleToggle">
|
||||
<div
|
||||
class="module-tree-item__toggle"
|
||||
:class="{ 'is-expanded': hasChildren && !isCollapsed }"
|
||||
@click.stop="handleToggle"
|
||||
>
|
||||
<icon-ic-round-chevron-right v-if="hasChildren" class="text-14px" />
|
||||
</div>
|
||||
<div class="module-tree-item__icon">
|
||||
|
||||
@@ -14,6 +14,7 @@ defineOptions({ name: 'RequirementActionDialog' });
|
||||
interface Props {
|
||||
action: Api.Product.RequirementLifecycleAction | null;
|
||||
requirementTitle: string;
|
||||
projectOptions: Api.Project.Project[];
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
@@ -57,8 +58,6 @@ const reviewChoiceOptions = [
|
||||
{ label: '不需要评审', value: 'claim_to_dispatch', description: '认领后直接进入分流' }
|
||||
];
|
||||
|
||||
const projectOptions = [{ label: 'NPQS-10086', value: '202642910086' }];
|
||||
|
||||
const rules = computed(() => {
|
||||
const baseRules: Record<string, App.Global.FormRule[]> = {};
|
||||
|
||||
@@ -118,8 +117,7 @@ async function handleSubmit() {
|
||||
@confirm="handleSubmit"
|
||||
>
|
||||
<ElForm ref="formRef" :model="model" :rules="rules" label-position="top">
|
||||
<ElFormItem :label="`需求名称:${requirementTitle}`">
|
||||
</ElFormItem>
|
||||
<ElFormItem :label="`需求名称:${requirementTitle}`"></ElFormItem>
|
||||
|
||||
<ElFormItem v-if="isClaimAction" label="是否需要评审" prop="reviewChoice">
|
||||
<ElRadioGroup v-model="model.reviewChoice" class="business-form-radio-group">
|
||||
@@ -134,7 +132,7 @@ async function handleSubmit() {
|
||||
|
||||
<ElFormItem v-if="isDispatchAction" label="实现项目" prop="implementProjectId">
|
||||
<ElSelect v-model="model.implementProjectId" class="w-full" filterable placeholder="请选择实现项目(必选)">
|
||||
<ElOption v-for="item in projectOptions" :key="item.value" :label="item.label" :value="item.value" />
|
||||
<ElOption v-for="item in projectOptions" :key="item.id" :label="item.projectName" :value="item.id" />
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import { fetchCreateRequirement, fetchGetRequirementModuleTree } from '@/service
|
||||
import { useForm, useFormRules } from '@/hooks/common/form';
|
||||
import { useDict } from '@/hooks/business/dict';
|
||||
import BusinessFormDialog from '@/components/custom/business-form-dialog.vue';
|
||||
import BusinessRichTextEditor from '@/components/custom/business-rich-text-editor.vue';
|
||||
import DictSelect from '@/components/custom/dict-select.vue';
|
||||
import MemberSelectOption from './member-select-option.vue';
|
||||
|
||||
@@ -216,6 +217,23 @@ watch(
|
||||
<ElInput v-model="model.title" clearable maxlength="256" placeholder="请输入需求名称" />
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="模块">
|
||||
<ElSelect v-model="model.moduleId" class="w-full" filterable placeholder="请选择所属模块">
|
||||
<ElOption v-for="item in flatModuleOptions" :key="item.value" :label="item.label" :value="item.value" />
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="24">
|
||||
<ElFormItem label="内容">
|
||||
<BusinessRichTextEditor
|
||||
v-model="model.description"
|
||||
height="240px"
|
||||
upload-directory="requirement"
|
||||
placeholder="请输入需求内容"
|
||||
/>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="是否需要评审">
|
||||
<ElSelect v-model="model.reviewRequired" class="w-full" placeholder="请选择">
|
||||
@@ -228,35 +246,6 @@ watch(
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="24">
|
||||
<ElFormItem label="内容">
|
||||
<ElInput
|
||||
v-model="model.description"
|
||||
type="textarea"
|
||||
:rows="6"
|
||||
maxlength="2000"
|
||||
show-word-limit
|
||||
placeholder="请输入需求内容"
|
||||
/>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="模块">
|
||||
<ElSelect v-model="model.moduleId" class="w-full" filterable placeholder="请选择所属模块">
|
||||
<ElOption
|
||||
v-for="item in flatModuleOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="需求类型" prop="category">
|
||||
<DictSelect v-model="model.category" :dict-code="categoryDictCode" filterable placeholder="请选择需求类型" />
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="优先级" prop="priority">
|
||||
<ElSelect v-model="model.priority" class="w-full" filterable placeholder="请选择优先级">
|
||||
@@ -264,6 +253,28 @@ watch(
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="所需工时" prop="workHours">
|
||||
<ElInputNumber
|
||||
v-model="model.workHours"
|
||||
class="w-full"
|
||||
:min="0"
|
||||
:max="9999"
|
||||
:precision="1"
|
||||
placeholder="请输入所需工时"
|
||||
/>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="需求类型" prop="category">
|
||||
<DictSelect
|
||||
v-model="model.category"
|
||||
:dict-code="categoryDictCode"
|
||||
filterable
|
||||
placeholder="请选择需求类型"
|
||||
/>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="提出人" prop="proposerId">
|
||||
<ElSelect v-model="model.proposerId" class="w-full" filterable placeholder="请选择提出人">
|
||||
@@ -292,18 +303,6 @@ watch(
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="所需工时" prop="workHours">
|
||||
<ElInputNumber
|
||||
v-model="model.workHours"
|
||||
class="w-full"
|
||||
:min="0"
|
||||
:max="9999"
|
||||
:precision="1"
|
||||
placeholder="请输入所需工时"
|
||||
/>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="排序值">
|
||||
<ElInputNumber v-model="model.sort" class="w-full" :min="0" :max="9999" placeholder="请输入排序值" />
|
||||
|
||||
@@ -1,13 +1,19 @@
|
||||
<script setup lang="ts">
|
||||
import {computed, nextTick, ref, watch} from 'vue';
|
||||
import {fetchGetRequirement, fetchGetRequirementModuleTree, fetchUpdateRequirement} from '@/service/api';
|
||||
import {useForm, useFormRules} from '@/hooks/common/form';
|
||||
import {useDict} from '@/hooks/business/dict';
|
||||
import { computed, nextTick, ref, watch } from 'vue';
|
||||
import {
|
||||
fetchGetProjectListByProductId,
|
||||
fetchGetRequirement,
|
||||
fetchGetRequirementModuleTree,
|
||||
fetchUpdateRequirement
|
||||
} 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';
|
||||
import BusinessRichTextEditor from '@/components/custom/business-rich-text-editor.vue';
|
||||
import ReadonlyField from '@/components/custom/readonly-field.vue';
|
||||
import MemberSelectOption from './member-select-option.vue';
|
||||
|
||||
defineOptions({name: 'RequirementDetailDialog'});
|
||||
defineOptions({ name: 'RequirementDetailDialog' });
|
||||
|
||||
type DialogMode = 'view' | 'edit';
|
||||
|
||||
@@ -32,11 +38,11 @@ const visible = defineModel<boolean>('visible', {
|
||||
default: false
|
||||
});
|
||||
|
||||
const {formRef, validate} = useForm();
|
||||
const {createRequiredRule} = useFormRules();
|
||||
const { formRef, validate } = useForm();
|
||||
const { createRequiredRule } = useFormRules();
|
||||
|
||||
const {getLabel: getCategoryLabel} = useDict(() => props.categoryDictCode);
|
||||
const {getLabel: getPriorityLabel, enabledDictData: priorityDictData} = useDict(() => props.priorityDictCode);
|
||||
const { getLabel: getCategoryLabel } = useDict(() => props.categoryDictCode);
|
||||
const { getLabel: getPriorityLabel, enabledDictData: priorityDictData } = useDict(() => props.priorityDictCode);
|
||||
|
||||
const priorityOptions = computed(() => {
|
||||
return priorityDictData.value.map(item => ({
|
||||
@@ -65,6 +71,7 @@ interface Model {
|
||||
const loading = ref(false);
|
||||
const submitting = ref(false);
|
||||
const moduleTree = ref<Api.Product.RequirementModule[]>([]);
|
||||
const projectOptions = ref<Api.Project.Project[]>([]);
|
||||
|
||||
const model = ref<Model>(createDefaultModel());
|
||||
|
||||
@@ -101,6 +108,10 @@ const moduleLabelMap = computed(() => {
|
||||
return map;
|
||||
});
|
||||
|
||||
const projectOptionsMap = computed(() => {
|
||||
return new Map(projectOptions.value.map(item => [String(item.id), item.projectName]));
|
||||
});
|
||||
|
||||
const flatModuleOptions = computed<Array<{ label: string; value: string }>>(() => {
|
||||
const options: Array<{ label: string; value: string }> = [];
|
||||
|
||||
@@ -125,8 +136,8 @@ const flatModuleOptions = computed<Array<{ label: string; value: string }>>(() =
|
||||
});
|
||||
|
||||
const reviewRequiredOptions = [
|
||||
{label: '不需要', value: 0},
|
||||
{label: '需要', value: 1}
|
||||
{ label: '不需要', value: 0 },
|
||||
{ label: '需要', value: 1 }
|
||||
];
|
||||
|
||||
const rules = computed(() => {
|
||||
@@ -195,7 +206,7 @@ async function handleSubmit() {
|
||||
sort: model.value.sort
|
||||
};
|
||||
|
||||
const {error} = await fetchUpdateRequirement(updatePayload);
|
||||
const { error } = await fetchUpdateRequirement(updatePayload);
|
||||
|
||||
submitting.value = false;
|
||||
|
||||
@@ -214,7 +225,7 @@ async function loadModuleTree() {
|
||||
return;
|
||||
}
|
||||
|
||||
const {error, data} = await fetchGetRequirementModuleTree(props.productId);
|
||||
const { error, data } = await fetchGetRequirementModuleTree(props.productId);
|
||||
|
||||
if (error || !data) {
|
||||
moduleTree.value = [];
|
||||
@@ -224,6 +235,22 @@ async function loadModuleTree() {
|
||||
moduleTree.value = data;
|
||||
}
|
||||
|
||||
async function loadProjectOptions() {
|
||||
if (!props.productId) {
|
||||
projectOptions.value = [];
|
||||
return;
|
||||
}
|
||||
|
||||
const { error, data } = await fetchGetProjectListByProductId(props.productId);
|
||||
|
||||
if (error || !data) {
|
||||
projectOptions.value = [];
|
||||
return;
|
||||
}
|
||||
|
||||
projectOptions.value = data;
|
||||
}
|
||||
|
||||
async function loadRequirementDetail() {
|
||||
if (!props.productId || !props.requirement?.id) {
|
||||
return;
|
||||
@@ -231,7 +258,7 @@ async function loadRequirementDetail() {
|
||||
|
||||
loading.value = true;
|
||||
|
||||
const {error, data} = await fetchGetRequirement(props.requirement.id, props.productId);
|
||||
const { error, data } = await fetchGetRequirement(props.requirement.id, props.productId);
|
||||
|
||||
loading.value = false;
|
||||
|
||||
@@ -264,7 +291,7 @@ watch(
|
||||
return;
|
||||
}
|
||||
|
||||
await loadModuleTree();
|
||||
await Promise.all([loadModuleTree(), loadProjectOptions()]);
|
||||
|
||||
if (props.requirement?.id) {
|
||||
await loadRequirementDetail();
|
||||
@@ -293,47 +320,33 @@ watch(
|
||||
<ReadonlyField :value="model.title" />
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="是否需要评审">
|
||||
<ReadonlyField :value="reviewRequiredOptions.find(opt => opt.value === model.reviewRequired)?.label"/>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="24">
|
||||
<ElFormItem label="内容">
|
||||
<template v-if="isViewMode">
|
||||
<div class="readonly-textarea">
|
||||
{{ model.description || '--' }}
|
||||
</div>
|
||||
</template>
|
||||
<ElInput
|
||||
v-else
|
||||
v-model="model.description"
|
||||
type="textarea"
|
||||
:rows="6"
|
||||
maxlength="2000"
|
||||
show-word-limit
|
||||
placeholder="请输入需求内容"
|
||||
/>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="模块">
|
||||
<template v-if="isViewMode">
|
||||
<ReadonlyField :value="moduleLabelMap.get(model.moduleId) || '--'" />
|
||||
</template>
|
||||
<ElSelect v-else v-model="model.moduleId" class="w-full" filterable placeholder="请选择所属模块">
|
||||
<ElOption
|
||||
v-for="item in flatModuleOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
<ElOption v-for="item in flatModuleOptions" :key="item.value" :label="item.label" :value="item.value" />
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="24">
|
||||
<ElFormItem label="内容">
|
||||
<template v-if="isViewMode">
|
||||
<div class="readonly-textarea" v-html="model.description || '--'"></div>
|
||||
</template>
|
||||
<BusinessRichTextEditor
|
||||
v-else
|
||||
v-model="model.description"
|
||||
height="240px"
|
||||
upload-directory="requirement"
|
||||
placeholder="请输入需求内容"
|
||||
/>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="需求类型" prop="category">
|
||||
<ReadonlyField :value="getCategoryLabel(model.category) || '--'"/>
|
||||
<ElFormItem label="是否需要评审">
|
||||
<ReadonlyField :value="reviewRequiredOptions.find(opt => opt.value === model.reviewRequired)?.label" />
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="12">
|
||||
@@ -344,36 +357,14 @@ watch(
|
||||
/>
|
||||
</template>
|
||||
<ElSelect v-else 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>
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="提出人" prop="proposerId">
|
||||
<ReadonlyField :value="memberLabelMap.get(model.proposerId) || '--'"/>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="负责人" prop="currentHandlerUserId">
|
||||
<template v-if="isViewMode">
|
||||
<ReadonlyField :value="memberLabelMap.get(model.currentHandlerUserId) || '--'"/>
|
||||
</template>
|
||||
<ElSelect v-else 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>
|
||||
<ElOption v-for="item in priorityOptions" :key="item.value" :label="item.label" :value="item.value" />
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="所需工时">
|
||||
<template v-if="isViewMode">
|
||||
<ReadonlyField :value="model.workHours != null ? `${model.workHours}小时` : '--'"/>
|
||||
<ReadonlyField :value="model.workHours != null ? `${model.workHours}小时` : '--'" />
|
||||
</template>
|
||||
<ElInputNumber
|
||||
v-else
|
||||
@@ -386,17 +377,56 @@ watch(
|
||||
/>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol v-if="isViewMode" :span="12">
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="需求类型" prop="category">
|
||||
<ReadonlyField :value="getCategoryLabel(model.category) || '--'" />
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="提出人" prop="proposerId">
|
||||
<ReadonlyField :value="memberLabelMap.get(model.proposerId) || '--'" />
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="负责人" prop="currentHandlerUserId">
|
||||
<template v-if="isViewMode">
|
||||
<ReadonlyField :value="memberLabelMap.get(model.currentHandlerUserId) || '--'" />
|
||||
</template>
|
||||
<ElSelect v-else 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="实现项目">
|
||||
<ReadonlyField :value="model.implementProjectId || '--'"/>
|
||||
<template v-if="isViewMode">
|
||||
<ReadonlyField :value="projectOptionsMap.get(model.implementProjectId || '') || '--'" />
|
||||
</template>
|
||||
<ElSelect
|
||||
v-else
|
||||
v-model="model.implementProjectId"
|
||||
class="w-full"
|
||||
filterable
|
||||
clearable
|
||||
placeholder="请选择实现项目"
|
||||
>
|
||||
<ElOption v-for="item in projectOptions" :key="item.id" :label="item.projectName" :value="item.id" />
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="排序值">
|
||||
<template v-if="isViewMode">
|
||||
<ReadonlyField :value="model.sort"/>
|
||||
<ReadonlyField :value="model.sort" />
|
||||
</template>
|
||||
<ElInputNumber v-else v-model="model.sort" class="w-full" :min="0" :max="9999" placeholder="请输入排序值"/>
|
||||
<ElInputNumber v-else v-model="model.sort" class="w-full" :min="0" :max="9999" placeholder="请输入排序值" />
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol v-if="isViewMode && model.lastStatusReason" :span="24">
|
||||
|
||||
@@ -4,6 +4,7 @@ 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';
|
||||
import BusinessRichTextEditor from '@/components/custom/business-rich-text-editor.vue';
|
||||
import MemberSelectOption from './member-select-option.vue';
|
||||
|
||||
defineOptions({ name: 'RequirementSplitDialog' });
|
||||
@@ -192,12 +193,10 @@ watch(
|
||||
</ElCol>
|
||||
<ElCol :span="24">
|
||||
<ElFormItem label="内容">
|
||||
<ElInput
|
||||
<BusinessRichTextEditor
|
||||
v-model="model.description"
|
||||
type="textarea"
|
||||
:rows="6"
|
||||
maxlength="2000"
|
||||
show-word-limit
|
||||
height="240px"
|
||||
upload-directory="requirement"
|
||||
placeholder="请输入需求内容"
|
||||
/>
|
||||
</ElFormItem>
|
||||
@@ -209,6 +208,18 @@ watch(
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="所需工时" prop="workHours">
|
||||
<ElInputNumber
|
||||
v-model="model.workHours"
|
||||
class="w-full"
|
||||
:min="0"
|
||||
:max="9999"
|
||||
:precision="1"
|
||||
placeholder="请输入所需工时"
|
||||
/>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="负责人" prop="currentHandlerUserId">
|
||||
<ElSelect v-model="model.currentHandlerUserId" class="w-full" filterable placeholder="请选择负责人">
|
||||
@@ -223,18 +234,6 @@ watch(
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="所需工时" prop="workHours">
|
||||
<ElInputNumber
|
||||
v-model="model.workHours"
|
||||
class="w-full"
|
||||
:min="0"
|
||||
:max="9999"
|
||||
:precision="1"
|
||||
placeholder="请输入所需工时"
|
||||
/>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="排序值">
|
||||
<ElInputNumber v-model="model.sort" class="w-full" :min="0" :max="9999" placeholder="请输入排序值" />
|
||||
|
||||
@@ -22,7 +22,8 @@ import { useBoolean } from '@sa/hooks';
|
||||
import {
|
||||
fetchBatchDeleteUserManagementRelation,
|
||||
fetchDeleteUserManagementRelation,
|
||||
fetchGetUserListByDeptId, fetchGetUserManagementRelation,
|
||||
fetchGetUserListByDeptId,
|
||||
fetchGetUserManagementRelation,
|
||||
fetchGetUserManagementRelationQuery,
|
||||
fetchGetUserManagementRelationTree
|
||||
} from '@/service/api';
|
||||
@@ -358,6 +359,12 @@ async function handleSearch() {
|
||||
relationTreeRef.value?.setCheckedKeys([]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存当前展开的节点 ID 列表
|
||||
* 用于在刷新数据后恢复展开状态
|
||||
*/
|
||||
const expandedNodeKeys = ref<string[]>([]);
|
||||
|
||||
/**
|
||||
* 重置搜索
|
||||
*
|
||||
@@ -365,10 +372,10 @@ async function handleSearch() {
|
||||
*/
|
||||
async function resetSearchParams() {
|
||||
Object.assign(searchParams, getInitSearchParams());
|
||||
|
||||
|
||||
// 清空保存的展开状态,让 reloadTreeData 后展开前两层
|
||||
expandedNodeKeys.value = [];
|
||||
|
||||
|
||||
await reloadTreeData();
|
||||
}
|
||||
|
||||
@@ -503,12 +510,6 @@ function handleNodeCheck(checkedData: any, checkedInfo: any) {
|
||||
.filter((id: string | null): id is string => Boolean(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存当前展开的节点 ID 列表
|
||||
* 用于在刷新数据后恢复展开状态
|
||||
*/
|
||||
const expandedNodeKeys = ref<string[]>([]);
|
||||
|
||||
/**
|
||||
* 保存当前展开的节点状态
|
||||
*/
|
||||
@@ -658,7 +659,7 @@ onMounted(async () => {
|
||||
/>
|
||||
|
||||
<!-- 树形卡片区域 -->
|
||||
<ElCard class="flex-1-hidden card-wrapper min-h-0">
|
||||
<ElCard class="min-h-0 flex-1-hidden card-wrapper">
|
||||
<template #header>
|
||||
<div class="flex items-center justify-between gap-12px">
|
||||
<div class="flex items-center gap-10px">
|
||||
|
||||
@@ -82,12 +82,12 @@ const isEdit = computed(() => props.operateType === 'edit');
|
||||
|
||||
/**
|
||||
* 加载候选下级用户列表
|
||||
*
|
||||
*
|
||||
* 获取所有还未绑定上级的用户,用于新增时的下级用户下拉框
|
||||
*/
|
||||
async function loadCandidateSubordinateUsers() {
|
||||
const { error, data } = await fetchGetCandidateSubordinateUsers();
|
||||
|
||||
|
||||
if (error) {
|
||||
candidateSubordinateUsers.value = [];
|
||||
return;
|
||||
@@ -98,7 +98,7 @@ async function loadCandidateSubordinateUsers() {
|
||||
|
||||
/**
|
||||
* 计算下级用户下拉框选项
|
||||
*
|
||||
*
|
||||
* 新增模式使用候选下级用户列表,编辑模式使用所有用户列表
|
||||
*/
|
||||
const subordinateUserOptions = computed<Api.SystemManage.UserSimple[]>(() => {
|
||||
@@ -318,7 +318,7 @@ async function handleSubmit() {
|
||||
watch(visible, async value => {
|
||||
if (value) {
|
||||
initModel();
|
||||
|
||||
|
||||
// 如果是新增模式,加载候选下级用户(每次打开都重新加载,避免缓存)
|
||||
if (!isEdit.value) {
|
||||
await loadCandidateSubordinateUsers();
|
||||
|
||||
Reference in New Issue
Block a user