feat(product): 新增产品管理模块与字典组件功能

- 新增产品管理相关路由和页面(dashboard、list、requirement、setting)
- 实现产品基础信息编辑弹窗组件(base-info-dialog.vue)
- 添加运行时字典功能(dict-select、dict-text、dict-tag组件)
- 集成字典管理store和API调用
- 规范ID类型定义为string避免精度丢失问题
- 完善国际化资源文件支持中英文对照
- 新增对象上下文业务域入口页导航实现说明
- 添加Vue DevTools浮动入口注释说明
- 统一权限控制支持全局和对象作用域区分
- 规范分页查询参数类型定义与使用方式
This commit is contained in:
2026-04-23 09:05:55 +08:00
parent c5911ea34b
commit 4122dfa50d
95 changed files with 9581 additions and 801 deletions

View File

@@ -54,10 +54,6 @@ const { fromUserIndex = false, deptId = 100, orgType = 'company' } = defineProps
*
* @param data 节点数据
*/
function isRootNode(data: Api.SystemManage.UserManagementRelationTreeRespVO): boolean {
return treeData.value.some(node => node.userId === data.userId);
}
/**
* 判断母节点的编辑按钮是否应该隐藏
*
@@ -89,11 +85,15 @@ const userList = ref<Api.SystemManage.UserSimple[]>([]);
const relationTreeRef = ref<InstanceType<typeof ElTree>>();
// 已选中的节点 ID 列表
const checkedNodeKeys = ref<number[]>([]);
const checkedNodeKeys = ref<string[]>([]);
// 树形数据
const treeData = ref<Api.SystemManage.UserManagementRelationTreeRespVO[]>([]);
function isRootNode(data: Api.SystemManage.UserManagementRelationTreeRespVO): boolean {
return treeData.value.some(node => node.userId === data.userId);
}
// 加载状态
const loading = ref(false);
@@ -225,7 +225,8 @@ function openAdd(item?: Api.SystemManage.UserManagementRelationTreeRespVO) {
operateType.value = 'add';
// 如果是从某一行的新增按钮触发,则默认管理者为当前节点用户
// 否则默认管理者为当前登录用户(在对话框组件中处理)
editingData.value = item ? {
editingData.value = item
? {
id: null,
managerUserId: item.userId,
subordinateUserId: null,
@@ -233,7 +234,8 @@ function openAdd(item?: Api.SystemManage.UserManagementRelationTreeRespVO) {
effectiveUntil: null,
remark: null,
createTime: Date.now()
} : null;
}
: null;
openOperateModal();
}
@@ -245,7 +247,8 @@ function openAdd(item?: Api.SystemManage.UserManagementRelationTreeRespVO) {
function openEdit(item: Api.SystemManage.UserManagementRelationTreeRespVO) {
operateType.value = 'edit';
// 构建树节点数据为编辑所需格式
editingData.value = item.id ? {
editingData.value = item.id
? {
id: item.id,
managerUserId: item.managerUserId,
subordinateUserId: item.userId,
@@ -253,7 +256,8 @@ function openEdit(item: Api.SystemManage.UserManagementRelationTreeRespVO) {
effectiveUntil: null,
remark: null,
createTime: Date.now()
} : null;
}
: null;
openOperateModal();
}
@@ -300,7 +304,9 @@ async function handleBatchDelete() {
* @param checkedInfo 包含 checkedKeys 和 halfCheckedKeys 的对象
*/
function handleNodeCheck(checkedData: any, checkedInfo: any) {
checkedNodeKeys.value = checkedInfo.checkedNodes.map((node: any) => node.id);
checkedNodeKeys.value = checkedInfo.checkedNodes
.map((node: any) => node.id)
.filter((id: string | null): id is string => Boolean(id));
}
/**
@@ -308,7 +314,7 @@ function handleNodeCheck(checkedData: any, checkedInfo: any) {
*
* @param relationId 提交后的关系 ID
*/
function handleSubmitted(relationId: number) {
function handleSubmitted(_relationId: string) {
closeOperateModal();
reloadTreeData();
}
@@ -411,14 +417,20 @@ onMounted(async () => {
<span>{{ node.label }}</span>
<!-- <ElTag v-if="data.managerNickname" size="small" type="info">上级{{ data.managerNickname }}</ElTag>-->
</span>
<div class="flex items-center" style="min-width: 200px;">
<div class="flex items-center" style="min-width: 200px">
<ElButton link type="primary" size="default" @click.stop="openAdd(data)">
<template #icon>
<icon-ic-round-plus class="text-icon" />
</template>
新增
</ElButton>
<ElButton v-if="!(isRootNode(data) && shouldHideRootEdit)" link type="primary" size="small" @click.stop="openEdit(data)">
<ElButton
v-if="!(isRootNode(data) && shouldHideRootEdit)"
link
type="primary"
size="small"
@click.stop="openEdit(data)"
>
<template #icon>
<icon-ic-round-edit class="text-icon" />
</template>

View File

@@ -50,7 +50,7 @@ const props = defineProps<Props>();
*/
const emit = defineEmits<{
/** 提交事件:返回提交后的关系 ID */
submitted: [relationId: number];
submitted: [relationId: string];
}>();
/**
@@ -126,11 +126,38 @@ function closeModal() {
visible.value = false;
}
async function resetValidateState() {
await nextTick();
formRef.value?.clearValidate();
}
function resolveDefaultManagerUserId() {
if (props.rowData?.managerUserId) {
return props.rowData.managerUserId;
}
const currentUserId = authStore.userInfo.userId;
const currentUserName = authStore.userInfo.userName;
if (!currentUserId) {
return undefined;
}
const matchedById = props.userList.find(user => user.id === currentUserId);
if (matchedById) {
return matchedById.id;
}
return currentUserName ? props.userList.find(user => user.nickname === currentUserName)?.id : undefined;
}
/**
* 初始化表单模型
*
* 编辑模式下加载详情数据,新增模式下设置默认值
*/
// eslint-disable-next-line complexity
async function initModel() {
model.value = createDefaultModel();
@@ -138,18 +165,18 @@ async function initModel() {
// 新增模式:设置管理者用户
// 优先使用 rowData 中传入的管理者用户 ID如从树形节点新增
// 否则使用当前登录用户
let managerUserIdToSet: number | undefined;
let managerUserIdToSet = resolveDefaultManagerUserId();
if (props.rowData && props.rowData.managerUserId) {
// 从树形节点点击新增,管理者为当前节点用户
managerUserIdToSet = props.rowData.managerUserId;
} else if (authStore.userInfo.userId) {
// 头部新增,管理者为当前登录用户
const currentUserId = Number(authStore.userInfo.userId);
const currentUserId = authStore.userInfo.userId;
const currentUserName = authStore.userInfo.userName;
// 先尝试通过 ID 匹配
let currentUser = props.userList.find(user => Number(user.id) === currentUserId);
let currentUser = props.userList.find(user => user.id === currentUserId);
// 如果 ID 匹配失败,尝试通过用户名匹配
if (!currentUser && currentUserName) {
@@ -165,26 +192,31 @@ async function initModel() {
model.value.managerUserId = managerUserIdToSet;
}
await nextTick();
formRef.value?.clearValidate();
await resetValidateState();
return;
}
// 编辑模式:加载详情数据
if (!props.rowData) {
await nextTick();
formRef.value?.clearValidate();
await resetValidateState();
return;
}
const relationId = props.rowData.id;
if (!relationId) {
await resetValidateState();
return;
}
detailLoading.value = true;
try {
const { error, data } = await fetchGetUserManagementRelation(props.rowData.id);
const { error, data } = await fetchGetUserManagementRelation(relationId);
if (data !== null && !error) {
model.value = {
id: data.id,
id: data.id ?? undefined,
managerUserId: data.managerUserId,
subordinateUserId: data.subordinateUserId,
effectiveFrom: data.effectiveFrom ? new Date(data.effectiveFrom).getTime() : null,
@@ -196,8 +228,7 @@ async function initModel() {
detailLoading.value = false;
}
await nextTick();
formRef.value?.clearValidate();
await resetValidateState();
}
/**
@@ -215,23 +246,31 @@ async function handleSubmit() {
...model.value
};
const request =
isEdit.value && props.rowData
? await fetchUpdateUserManagementRelation({ id: props.rowData.id, ...submitData })
: await fetchCreateUserManagementRelation(submitData);
const editRelationId = props.rowData?.id ?? null;
const { error, data } = request;
if (isEdit.value && editRelationId) {
const { error } = await fetchUpdateUserManagementRelation({ ...submitData, id: editRelationId });
if (error) {
if (error) {
return;
}
window.$message?.success('淇敼鎴愬姛');
closeModal();
emit('submitted', editRelationId);
return;
}
const relationId = isEdit.value && props.rowData ? props.rowData.id : Number(data);
const { error, data } = await fetchCreateUserManagementRelation(submitData);
if (error || !data) {
return;
}
window.$message?.success(isEdit.value ? '修改成功' : '新增成功');
closeModal();
emit('submitted', relationId);
emit('submitted', data);
} finally {
submitting.value = false;
}