fix(产品需求): 修复产品需求使用状态和终止态字典的问题。

fix(组织): 修复组织编码下拉框的数据显示问题、修复组织编码负责人无法新增的问题。
fix(管理链路): 修复管理链路高度没固定,节点全部收缩等问题。
This commit is contained in:
dk
2026-05-07 17:09:53 +08:00
parent 991cbb5278
commit f4f43814b3
12 changed files with 287 additions and 82 deletions

View File

@@ -36,14 +36,6 @@ export const RDMS_OBJECT_DIRECTION_LEGACY_DICT_CODE = 'rdms_product_direction';
*/ */
export const SYSTEM_USER_COMPANY_DICT_CODE = 'system_user_company'; export const SYSTEM_USER_COMPANY_DICT_CODE = 'system_user_company';
/**
* 需求终态状态字典编码
*
* 对应业务字段:需求相关接口和页面中的 terminal status
* 来源口径:产品需求权限文档中定义,标签包括已关闭、已取消、已拒绝
*/
export const RDMS_REQ_TERMINAL_STATUS_DICT_CODE = 'rdms_req_terminal_status';
/** /**
* 需求来源类型字典编码 * 需求来源类型字典编码
* *
@@ -67,11 +59,3 @@ export const RDMS_REQ_PRIORITY_DICT_CODE = 'rdms_req_priority';
* 来源口径:产品需求文档中定义,标签包括工程需求、用户需求、安全需求、体验优化、功能需求 * 来源口径:产品需求文档中定义,标签包括工程需求、用户需求、安全需求、体验优化、功能需求
*/ */
export const RDMS_REQ_CATEGORY_DICT_CODE = 'rdms_req_category'; export const RDMS_REQ_CATEGORY_DICT_CODE = 'rdms_req_category';
/**
* 需求状态字典编码
*
* 对应业务字段:需求相关接口和页面中的 statusCode
* 来源口径:产品需求文档中定义
*/
export const RDMS_REQ_STATUS_DICT_CODE = 'rdms_req_status';

View File

@@ -320,6 +320,28 @@ export async function fetchGetRequirementLifecycle(requirementId: string, produc
return mapServiceResult(result as ServiceRequestResult<Api.Product.RequirementLifecycleInfo>, data => data); return mapServiceResult(result as ServiceRequestResult<Api.Product.RequirementLifecycleInfo>, data => data);
} }
/** 获取需求所有状态字典 */
export async function fetchGetRequirementStatusDict() {
const result = await request<Api.Product.RequirementStatusDict[]>({
...safeJsonRequestConfig,
url: `${REQUIREMENT_PREFIX}/status/dict`,
method: 'get'
});
return mapServiceResult(result as ServiceRequestResult<Api.Product.RequirementStatusDict[]>, data => data);
}
/** 获取需求终止态状态字典 */
export async function fetchGetRequirementTerminalStatusDict() {
const result = await request<Api.Product.RequirementStatusDict[]>({
...safeJsonRequestConfig,
url: `${REQUIREMENT_PREFIX}/status/dict/terminal`,
method: 'get'
});
return mapServiceResult(result as ServiceRequestResult<Api.Product.RequirementStatusDict[]>, data => data);
}
// ========== 模块管理 API ========== // ========== 模块管理 API ==========
type RequirementModuleResponse = Omit<Api.Product.RequirementModule, 'id' | 'parentId' | 'productId'> & { type RequirementModuleResponse = Omit<Api.Product.RequirementModule, 'id' | 'parentId' | 'productId'> & {
id: string | number; id: string | number;

View File

@@ -319,6 +319,21 @@ declare namespace Api {
children?: RequirementModule[]; children?: RequirementModule[];
} }
// ========== 需求状态字典 ==========
interface RequirementStatusDict {
/** 状态编码 */
statusCode: string;
/** 状态名称 */
statusName: string;
/** 排序值 */
sort: number;
/** 是否初始状态 */
initialFlag: boolean;
/** 是否终态 */
terminalFlag: boolean;
}
// ========== 需求生命周期 ========== // ========== 需求生命周期 ==========
interface RequirementLifecycleAction { interface RequirementLifecycleAction {

View File

@@ -103,7 +103,7 @@ declare namespace Api {
interface OrgLeaderRelation { interface OrgLeaderRelation {
id: number; id: number;
deptId: number; deptId: number;
userId: number; userId: string;
userNickname: string; userNickname: string;
effectiveFrom?: number | null; effectiveFrom?: number | null;
effectiveUntil?: number | null; effectiveUntil?: number | null;
@@ -115,7 +115,7 @@ declare namespace Api {
type OrgLeaderRelationList = OrgLeaderRelation[]; type OrgLeaderRelationList = OrgLeaderRelation[];
interface OrgLeaderCandidateUser { interface OrgLeaderCandidateUser {
id: number; id: string;
nickname: string; nickname: string;
deptId: number; deptId: number;
deptName?: string | null; deptName?: string | null;
@@ -125,7 +125,7 @@ declare namespace Api {
type SaveOrgLeaderRelationParams = { type SaveOrgLeaderRelationParams = {
deptId: number; deptId: number;
userId: number; userId: string | null;
effectiveFrom?: number | null; effectiveFrom?: number | null;
effectiveUntil?: number | null; effectiveUntil?: number | null;
remark?: string | null; remark?: string | null;

View File

@@ -1,23 +1,22 @@
<script setup lang="tsx"> <script setup lang="tsx">
import { computed, reactive, ref, watch } from 'vue'; import { computed, onMounted, reactive, ref, watch } from 'vue';
import type { TableInstance } from 'element-plus'; import type { TableInstance } from 'element-plus';
import { ElButton, ElTag } from 'element-plus'; import { ElButton, ElTag } from 'element-plus';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { import {
RDMS_REQ_CATEGORY_DICT_CODE, RDMS_REQ_CATEGORY_DICT_CODE,
RDMS_REQ_PRIORITY_DICT_CODE, RDMS_REQ_PRIORITY_DICT_CODE
RDMS_REQ_STATUS_DICT_CODE,
RDMS_REQ_TERMINAL_STATUS_DICT_CODE
} from '@/constants/dict'; } from '@/constants/dict';
import { import {
fetchChangeRequirementStatus, fetchChangeRequirementStatus,
fetchDeleteRequirement, fetchDeleteRequirement,
fetchGetProductMembers, fetchGetProductMembers,
fetchGetRequirementAllowedTransitions, fetchGetRequirementAllowedTransitions,
fetchGetRequirementStatusDict,
fetchGetRequirementTerminalStatusDict,
fetchGetRequirementTree fetchGetRequirementTree
} from '@/service/api'; } from '@/service/api';
import { useAuth } from '@/hooks/business/auth'; import { useAuth } from '@/hooks/business/auth';
import { useDict } from '@/hooks/business/dict';
import BusinessTableActionCell from '@/components/custom/business-table-action-cell'; import BusinessTableActionCell from '@/components/custom/business-table-action-cell';
import DictTag from '@/components/custom/dict-tag.vue'; import DictTag from '@/components/custom/dict-tag.vue';
import { useCurrentProduct } from '../shared/use-current-product'; import { useCurrentProduct } from '../shared/use-current-product';
@@ -42,7 +41,38 @@ defineOptions({ name: 'ProductRequirement' });
const { currentObjectId } = useCurrentProduct(); const { currentObjectId } = useCurrentProduct();
const { hasObjectAuth } = useAuth(); const { hasObjectAuth } = useAuth();
const { dictOptions: terminalStatusOptions } = useDict(RDMS_REQ_TERMINAL_STATUS_DICT_CODE); const statusOptions = ref<Array<{ label: string; value: string }>>([]);
const terminalStatusOptions = ref<string[]>([]);
async function loadStatusOptions() {
const { error, data } = await fetchGetRequirementStatusDict();
if (error || !data) {
statusOptions.value = [];
return;
}
statusOptions.value = data.map(item => ({
label: item.statusName,
value: item.statusCode
}));
}
async function loadTerminalStatusOptions() {
const { error, data } = await fetchGetRequirementTerminalStatusDict();
if (error || !data) {
terminalStatusOptions.value = [];
return;
}
terminalStatusOptions.value = data.map(item => item.statusCode);
}
function getStatusLabel(statusCode: string) {
const item = statusOptions.value.find(opt => opt.value === statusCode);
return item ? item.label : statusCode;
}
const priorityTagTypeMap: Record<number, UI.ThemeColor> = { const priorityTagTypeMap: Record<number, UI.ThemeColor> = {
0: 'info', 0: 'info',
@@ -60,7 +90,7 @@ function formatDateTime(value?: string | null) {
} }
function isTerminalStatus(statusCode: string) { function isTerminalStatus(statusCode: string) {
return terminalStatusOptions.value.some(option => option.value === statusCode); return terminalStatusOptions.value.some(option => option === statusCode);
} }
function canSplitRequirement(row: Api.Product.Requirement) { function canSplitRequirement(row: Api.Product.Requirement) {
@@ -233,15 +263,15 @@ const columns = computed(() => [
minWidth: 120, minWidth: 120,
formatter: (row: Api.Product.Requirement) => row.category formatter: (row: Api.Product.Requirement) => row.category
}, },
{ // {
prop: 'description', // prop: 'description',
label: '描述', // label: '描述',
minWidth: 200, // minWidth: 200,
showOverflowTooltip: true, // showOverflowTooltip: true,
formatter: (row: Api.Product.Requirement) => { // formatter: (row: Api.Product.Requirement) => {
return row.description?.replace(/<[^>]+>/g, '').trim() || '--'; // return row.description?.replace(/<[^>]+>/g, '').trim() || '--';
} // }
}, // },
{ {
prop: 'priority', prop: 'priority',
label: '优先级', label: '优先级',
@@ -257,11 +287,9 @@ const columns = computed(() => [
width: 100, width: 100,
align: 'center', align: 'center',
formatter: (row: Api.Product.Requirement) => ( formatter: (row: Api.Product.Requirement) => (
<DictTag <ElTag type={getRequirementStatusTagType(row.statusCode)}>
dictCode={RDMS_REQ_STATUS_DICT_CODE} {getStatusLabel(row.statusCode)}
value={row.statusCode} </ElTag>
type={getRequirementStatusTagType(row.statusCode)}
/>
) )
}, },
{ {
@@ -601,6 +629,10 @@ watch(
}, },
{ immediate: true } { immediate: true }
); );
onMounted(async () => {
await Promise.all([loadStatusOptions(), loadTerminalStatusOptions()]);
});
</script> </script>
<template> <template>

View File

@@ -1,5 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { onMounted, ref } from 'vue';
import { RDMS_REQ_SOURCE_TYPE_DICT_CODE } from '@/constants/dict'; import { RDMS_REQ_SOURCE_TYPE_DICT_CODE } from '@/constants/dict';
import { fetchGetRequirementStatusDict } from '@/service/api';
import DictSelect from '@/components/custom/dict-select.vue'; import DictSelect from '@/components/custom/dict-select.vue';
import TableSearchPanel from '@/components/custom/table-search-panel.vue'; import TableSearchPanel from '@/components/custom/table-search-panel.vue';
import MemberSelectOption from './member-select-option.vue'; import MemberSelectOption from './member-select-option.vue';
@@ -29,16 +31,21 @@ const emit = defineEmits<Emits>();
const model = defineModel<Api.Product.RequirementSearchParams>('model', { required: true }); const model = defineModel<Api.Product.RequirementSearchParams>('model', { required: true });
const requirementStatusOptions = [ const requirementStatusOptions = ref<Array<{ label: string; value: string }>>([]);
{ label: '待确认', value: 'pending_confirm' },
{ label: '待评审', value: 'pending_review' }, async function loadStatusOptions() {
{ label: '待分流', value: 'pending_dispatch' }, const { error, data } = await fetchGetRequirementStatusDict();
{ label: '实施中', value: 'implementing' },
{ label: '已验收', value: 'accepted' }, if (error || !data) {
{ label: '已关闭', value: 'closed' }, requirementStatusOptions.value = [];
{ label: '已拒绝', value: 'rejected' }, return;
{ label: '已取消', value: 'cancelled' } }
];
requirementStatusOptions.value = data.map(item => ({
label: item.statusName,
value: item.statusCode
}));
}
function reset() { function reset() {
emit('reset'); emit('reset');
@@ -47,6 +54,10 @@ function reset() {
function search() { function search() {
emit('search'); emit('search');
} }
onMounted(async () => {
await loadStatusOptions();
});
</script> </script>
<template> <template>

View File

@@ -7,7 +7,7 @@
* - 支持节点的展开/折叠 * - 支持节点的展开/折叠
* - 支持单选/多选节点 * - 支持单选/多选节点
* - 提供新增、编辑、删除(单个/批量)功能 * - 提供新增、编辑、删除(单个/批量)功能
* - 支持按管理者用户 ID 和被管理用户 ID 搜索 * - 支持按上级用户 ID 和下级用户 ID 搜索
* *
* 树形结构特点: * 树形结构特点:
* - 根节点:最高领导,没有上级 * - 根节点:最高领导,没有上级
@@ -134,6 +134,10 @@ async function loadTreeData() {
if (!error) { if (!error) {
treeData.value = data || []; treeData.value = data || [];
// 数据加载完成后,展开前两层节点
await nextTick();
expandFirstTwoLevels();
} }
} finally { } finally {
loading.value = false; loading.value = false;
@@ -167,6 +171,9 @@ async function loadTreeDataByQuery(query: Api.SystemManage.UserManagementRelatio
* 清空选中状态并重新加载数据 * 清空选中状态并重新加载数据
*/ */
async function reloadTreeData() { async function reloadTreeData() {
// 保存当前展开状态
saveExpandedState();
checkedNodeKeys.value = []; checkedNodeKeys.value = [];
await loadTreeData(); await loadTreeData();
await nextTick(); await nextTick();
@@ -216,15 +223,25 @@ const { bool: operateVisible, setTrue: openOperateModal, setFalse: closeOperateM
const operateType = ref<UI.TableOperateType>('add'); const operateType = ref<UI.TableOperateType>('add');
const editingData = ref<Api.SystemManage.UserManagementRelation | null>(null); const editingData = ref<Api.SystemManage.UserManagementRelation | null>(null);
/**
* 是否在管理链路树中选中节点后点击新增
* 用于控制新增对话框中上级用户下拉框是否禁用
*/
const isAddFromTreeNode = ref(false);
/** /**
* 打开新增对话框 * 打开新增对话框
* *
* @param item 当前节点数据,用于设置默认管理者为此节点用户 * @param item 当前节点数据,用于设置默认上级为此节点用户
*/ */
function openAdd(item?: Api.SystemManage.UserManagementRelationTreeRespVO) { function openAdd(item?: Api.SystemManage.UserManagementRelationTreeRespVO) {
operateType.value = 'add'; operateType.value = 'add';
// 如果是从某一行的新增按钮触发,则默认管理者为当前节点用户
// 否则默认管理者为当前登录用户(在对话框组件中处理) // 如果是从树节点点击的新增按钮,标记为来自树节点
isAddFromTreeNode.value = Boolean(item);
// 如果是从某一行的新增按钮触发,则默认上级为当前节点用户
// 否则默认上级为当前登录用户(在对话框组件中处理)
editingData.value = item editingData.value = item
? { ? {
id: null, id: null,
@@ -309,14 +326,127 @@ function handleNodeCheck(checkedData: any, checkedInfo: any) {
.filter((id: string | null): id is string => Boolean(id)); .filter((id: string | null): id is string => Boolean(id));
} }
/**
* 保存当前展开的节点 ID 列表
* 用于在刷新数据后恢复展开状态
*/
const expandedNodeKeys = ref<string[]>([]);
/**
* 保存当前展开的节点状态
*/
function saveExpandedState() {
if (!relationTreeRef.value) {
return;
}
const store = (relationTreeRef.value as any).store;
if (!store) {
return;
}
const allNodes = store.nodesMap || {};
expandedNodeKeys.value = [];
Object.keys(allNodes).forEach(key => {
const node = allNodes[key];
if (node && node.expanded && node.data && node.data.userId) {
expandedNodeKeys.value.push(node.data.userId);
}
});
}
/** /**
* 处理对话框提交事件 * 处理对话框提交事件
* *
* @param relationId 提交后的关系 ID * @param relationId 提交后的关系 ID
*/ */
function handleSubmitted(_relationId: string) { async function handleSubmitted(_relationId: string) {
closeOperateModal(); closeOperateModal();
reloadTreeData(); await reloadTreeData();
// 操作完成后恢复树节点的展开状态
await restoreExpandedState();
// 重置标记
isAddFromTreeNode.value = false;
}
/**
* 展开所有子节点(递归)
*
* @param tree 树形组件实例
* @param nodes 节点数据数组
*/
function expandNodes(tree: InstanceType<typeof ElTree>, nodes: Api.SystemManage.UserManagementRelationTreeRespVO[]) {
if (!tree || !nodes || !nodes.length) {
return;
}
for (const node of nodes) {
// 展开当前节点
const treeNode = tree.getNode(node.userId);
if (treeNode) {
treeNode.expand();
}
// 递归展开子节点
if (node.children && node.children.length > 0) {
expandNodes(tree, node.children);
}
}
}
/**
* 展开树的前两层节点
*
* 只展开根节点和它们的直接子节点,第三层及更深层保持折叠
*/
function expandFirstTwoLevels() {
const tree = relationTreeRef.value;
if (!tree || !treeData.value.length) {
return;
}
// 展开第一层(根节点)
for (const rootNode of treeData.value) {
const treeNode = tree.getNode(rootNode.userId);
if (treeNode) {
treeNode.expand();
}
// 展开第二层(根节点的直接子节点)
if (rootNode.children && rootNode.children.length > 0) {
for (const childNode of rootNode.children) {
const childTreeNode = tree.getNode(childNode.userId);
if (childTreeNode) {
childTreeNode.expand();
}
}
}
}
}
/**
* 恢复树节点的展开状态
*
* 根据之前保存的展开状态,恢复对应的节点展开
*/
async function restoreExpandedState() {
await nextTick();
const tree = relationTreeRef.value;
if (!tree || !expandedNodeKeys.value.length) {
return;
}
// 恢复之前展开的节点
for (const key of expandedNodeKeys.value) {
const node = tree.getNode(key);
if (node) {
node.expand();
}
}
} }
/** /**
@@ -356,7 +486,7 @@ onMounted(async () => {
</script> </script>
<template> <template>
<div class="flex-col-stretch gap-16px overflow-hidden"> <div class="flex-col-stretch gap-16px overflow-hidden" style="height: calc(70vh - 120px)">
<!-- 搜索区域 --> <!-- 搜索区域 -->
<RelationSearch <RelationSearch
v-model:model="searchParams" v-model:model="searchParams"
@@ -366,7 +496,7 @@ onMounted(async () => {
/> />
<!-- 树形卡片区域 --> <!-- 树形卡片区域 -->
<ElCard class="flex-1-hidden card-wrapper"> <ElCard class="flex-1-hidden card-wrapper min-h-0">
<template #header> <template #header>
<div class="flex items-center justify-between gap-12px"> <div class="flex items-center justify-between gap-12px">
<div class="flex items-center gap-10px"> <div class="flex items-center gap-10px">
@@ -463,6 +593,7 @@ onMounted(async () => {
:operate-type="operateType" :operate-type="operateType"
:row-data="editingData" :row-data="editingData"
:user-list="userList" :user-list="userList"
:is-add-from-tree-node="isAddFromTreeNode"
@submitted="handleSubmitted" @submitted="handleSubmitted"
/> />
</div> </div>

View File

@@ -8,8 +8,8 @@
* - 表单验证和提交 * - 表单验证和提交
* *
* 表单字段: * 表单字段:
* - 管理者用户:必填,下拉选择,默认当前登录用户 * - 上级用户:必填,下拉选择,默认当前登录用户
* - 被管理用户:必填,下拉选择,默认空 * - 下级用户用户:必填,下拉选择,默认空
* - 生效开始时间:可选 * - 生效开始时间:可选
* - 生效结束时间:可选 * - 生效结束时间:可选
* - 备注:可选 * - 备注:可选
@@ -39,6 +39,8 @@ interface Props {
rowData?: Api.SystemManage.UserManagementRelation | null; rowData?: Api.SystemManage.UserManagementRelation | null;
/** 用户列表,由父组件统一提供 */ /** 用户列表,由父组件统一提供 */
userList: Api.SystemManage.UserSimple[]; userList: Api.SystemManage.UserSimple[];
/** 是否从树节点点击新增(用于控制上级用户下拉框禁用) */
isAddFromTreeNode?: boolean;
} }
const props = defineProps<Props>(); const props = defineProps<Props>();
@@ -115,8 +117,8 @@ function createDefaultModel(): Model {
* 表单验证规则 * 表单验证规则
*/ */
const rules = { const rules = {
managerUserId: createRequiredRule('请选择管理者用户'), managerUserId: createRequiredRule('请选择上级用户'),
subordinateUserId: createRequiredRule('请选择被管理用户') subordinateUserId: createRequiredRule('请选择下级用户')
} satisfies Record<string, App.Global.FormRule>; } satisfies Record<string, App.Global.FormRule>;
/** /**
@@ -162,16 +164,16 @@ async function initModel() {
model.value = createDefaultModel(); model.value = createDefaultModel();
if (!isEdit.value) { if (!isEdit.value) {
// 新增模式:设置管理者用户 // 新增模式:设置上级用户
// 优先使用 rowData 中传入的管理者用户 ID如从树形节点新增 // 优先使用 rowData 中传入的上级用户 ID如从树形节点新增
// 否则使用当前登录用户 // 否则使用当前登录用户
let managerUserIdToSet = resolveDefaultManagerUserId(); let managerUserIdToSet = resolveDefaultManagerUserId();
if (props.rowData && props.rowData.managerUserId) { if (props.rowData && props.rowData.managerUserId) {
// 从树形节点点击新增,管理者为当前节点用户 // 从树形节点点击新增,上级为当前节点用户
managerUserIdToSet = props.rowData.managerUserId; managerUserIdToSet = props.rowData.managerUserId;
} else if (authStore.userInfo.userId) { } else if (authStore.userInfo.userId) {
// 头部新增,管理者为当前登录用户 // 头部新增,上级为当前登录用户
const currentUserId = authStore.userInfo.userId; const currentUserId = authStore.userInfo.userId;
const currentUserName = authStore.userInfo.userName; const currentUserName = authStore.userInfo.userName;
@@ -301,18 +303,24 @@ watch(visible, value => {
<ElForm ref="formRef" :model="model" :rules="rules" label-position="top"> <ElForm ref="formRef" :model="model" :rules="rules" label-position="top">
<ElRow :gutter="16"> <ElRow :gutter="16">
<ElCol :span="12"> <ElCol :span="12">
<ElFormItem label="管理者用户" prop="managerUserId"> <ElFormItem label="上级用户" prop="managerUserId">
<ElSelect v-model="model.managerUserId" class="w-full" placeholder="请选择管理者用户" filterable> <ElSelect
v-model="model.managerUserId"
class="w-full"
placeholder="请选择上级用户"
filterable
:disabled="props.operateType === 'add' && props.isAddFromTreeNode"
>
<ElOption v-for="user in props.userList" :key="user.id" :label="user.nickname" :value="user.id" /> <ElOption v-for="user in props.userList" :key="user.id" :label="user.nickname" :value="user.id" />
</ElSelect> </ElSelect>
</ElFormItem> </ElFormItem>
</ElCol> </ElCol>
<ElCol :span="12"> <ElCol :span="12">
<ElFormItem label="被管理用户" prop="subordinateUserId"> <ElFormItem label="下级用户" prop="subordinateUserId">
<ElSelect <ElSelect
v-model="model.subordinateUserId" v-model="model.subordinateUserId"
class="w-full" class="w-full"
placeholder="请选择被管理用户" placeholder="请选择下级用户"
filterable filterable
:disabled="isEdit" :disabled="isEdit"
> >

View File

@@ -3,7 +3,7 @@
* 用户管理链路搜索组件 * 用户管理链路搜索组件
* *
* 功能说明: * 功能说明:
* - 提供管理者和被管理者用户下拉选择 * - 提供上级和下级用户下拉选择
* - 支持搜索和重置操作 * - 支持搜索和重置操作
* - 与树形结构数据联动 * - 与树形结构数据联动
* *
@@ -63,11 +63,11 @@ function search() {
<template> <template>
<TableSearchPanel :model="model" :action-col-lg="8" @reset="reset" @search="search"> <TableSearchPanel :model="model" :action-col-lg="8" @reset="reset" @search="search">
<!-- <ElCol :lg="8" :md="12" :sm="12">--> <!-- <ElCol :lg="8" :md="12" :sm="12">-->
<!-- <ElFormItem label="管理者用户" prop="managerUserId">--> <!-- <ElFormItem label="上级用户" prop="managerUserId">-->
<!-- <ElSelect--> <!-- <ElSelect-->
<!-- v-model="model.managerUserId"--> <!-- v-model="model.managerUserId"-->
<!-- class="w-full"--> <!-- class="w-full"-->
<!-- placeholder="请选择管理者用户"--> <!-- placeholder="请选择上级用户"-->
<!-- clearable--> <!-- clearable-->
<!-- filterable--> <!-- filterable-->
<!-- >--> <!-- >-->

View File

@@ -66,7 +66,7 @@ function mapUsersToCandidateUsers(users: Api.SystemManage.User[]): Api.SystemMan
return users return users
.filter(item => !item.resignedAt || item.resignedAt > now) .filter(item => !item.resignedAt || item.resignedAt > now)
.map(item => ({ .map(item => ({
id: item.id, id: String(item.id),
nickname: item.nickname?.trim() || item.username, nickname: item.nickname?.trim() || item.username,
deptId: item.deptId, deptId: item.deptId,
deptName: item.deptName ?? null deptName: item.deptName ?? null

View File

@@ -44,7 +44,7 @@ const title = computed(() => {
}); });
type Model = { type Model = {
userId: number | null; userId: string | null;
effectiveFrom: Date | null; effectiveFrom: Date | null;
effectiveUntil: Date | null; effectiveUntil: Date | null;
remark: string; remark: string;
@@ -119,7 +119,7 @@ async function handleSubmit() {
const payload: Api.SystemManage.SaveOrgLeaderRelationParams = { const payload: Api.SystemManage.SaveOrgLeaderRelationParams = {
deptId: props.dept.id, deptId: props.dept.id,
userId: Number(model.value.userId), userId: model.value.userId,
effectiveFrom, effectiveFrom,
effectiveUntil, effectiveUntil,
remark: model.value.remark.trim() || null remark: model.value.remark.trim() || null
@@ -129,10 +129,10 @@ async function handleSubmit() {
const request = const request =
isEdit.value && props.rowData isEdit.value && props.rowData
? fetchUpdateOrgLeaderRelation({ id: props.rowData.id, ...payload }) ? await fetchUpdateOrgLeaderRelation({ id: props.rowData.id, ...payload })
: fetchCreateOrgLeaderRelation(payload); : await fetchCreateOrgLeaderRelation(payload);
const { error } = await request; const { error } = request;
submitting.value = false; submitting.value = false;
@@ -186,10 +186,11 @@ watch(visible, async value => {
</ElFormItem> </ElFormItem>
</ElCol> </ElCol>
<ElCol :span="12"> <ElCol :span="12">
<ElFormItem :label="$t('page.system.user.effectiveFrom')" prop="effectiveFrom"> <ElFormItem :label="$t('page.system.user.effectiveFrom')" prop="effectiveFrom" style="width: 100%">
<ElDatePicker <ElDatePicker
v-model="model.effectiveFrom" v-model="model.effectiveFrom"
class="w-full" class="w-full"
style="width: 100%"
type="datetime" type="datetime"
clearable clearable
:placeholder="$t('page.system.user.form.effectiveFrom')" :placeholder="$t('page.system.user.form.effectiveFrom')"
@@ -197,10 +198,11 @@ watch(visible, async value => {
</ElFormItem> </ElFormItem>
</ElCol> </ElCol>
<ElCol :span="12"> <ElCol :span="12">
<ElFormItem :label="$t('page.system.user.effectiveUntil')" prop="effectiveUntil"> <ElFormItem :label="$t('page.system.user.effectiveUntil')" prop="effectiveUntil" style="width: 100%">
<ElDatePicker <ElDatePicker
v-model="model.effectiveUntil" v-model="model.effectiveUntil"
class="w-full" class="w-full"
style="width: 100%"
type="datetime" type="datetime"
clearable clearable
:placeholder="$t('page.system.user.form.effectiveUntil')" :placeholder="$t('page.system.user.form.effectiveUntil')"

View File

@@ -232,7 +232,7 @@ watch(visible, async value => {
<ElOption <ElOption
v-for="item in props.orgCodeOptions" v-for="item in props.orgCodeOptions"
:key="item.value" :key="item.value"
:label="item.value" :label="item.label"
:value="item.value" :value="item.value"
/> />
</ElSelect> </ElSelect>