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

@@ -61,6 +61,23 @@ declare namespace Api {
createTime: number;
}
/** frontend runtime dict item */
interface FrontendDictData {
/** dict label */
label: string;
/** dict value */
value: string;
/** display order */
sort: number;
/** dict type code */
dictType?: string;
/** status: 0 enabled, 1 disabled */
status?: DictStatus;
}
/** frontend runtime dict cache map */
type FrontendDictCache = Record<string, FrontendDictData[]>;
/** dict data search params */
type DictDataSearchParams = CommonType.RecordNullable<Pick<DictData, 'label' | 'dictType' | 'status'>> & PageParams;

15
src/typings/api/object-context.d.ts vendored Normal file
View File

@@ -0,0 +1,15 @@
declare namespace Api {
namespace ObjectContext {
interface ContextInfo {
domainKey: App.ObjectContext.DomainKey;
objectType: App.ObjectContext.ObjectType;
objectId: string;
objectName: string;
objectSummary: App.ObjectContext.Summary | null;
contextScopedMenus: App.ObjectContext.Menu[];
buttonCodes: string[];
defaultRouteKey: string;
defaultRoutePath: string;
}
}
}

164
src/typings/api/product.d.ts vendored Normal file
View File

@@ -0,0 +1,164 @@
declare namespace Api {
/**
* namespace Product
*
* backend api module: "project/product"
*/
namespace Product {
type ProductStatusCode = 'active' | 'paused' | 'archived' | 'abandoned';
type ProductStatusActionCode = 'pause' | 'resume' | 'archive' | 'abandon';
type ProductMemberStatus = 0 | 1;
interface PageParams {
pageNo: number;
pageSize: number;
}
interface PageResult<T = any> {
total: number;
list: T[];
}
interface Product {
/** 产品 ID */
id: string;
/** 产品编码 */
code: string;
/** 产品方向字典值 */
directionCode: string;
/** 产品名称 */
name: string;
/** 产品经理用户 ID */
managerUserId: string;
/** 产品描述 */
description?: string | null;
/** 产品状态编码 */
statusCode: ProductStatusCode;
/** 最近一次状态动作原因 */
lastStatusReason?: string | null;
/** 备注 */
remark?: string | null;
/** 创建时间 */
createTime: string;
/** 更新时间 */
updateTime: string;
}
interface ProductSettingBaseInfo {
/** 产品 ID */
id: string;
/** 产品编码 */
code: string;
/** 产品方向字典值 */
directionCode: string;
/** 产品名称 */
name: string;
/** 产品经理用户 ID */
managerUserId: string;
/** 产品经理昵称 */
managerUserNickname: string;
/** 产品描述 */
description?: string | null;
/** 当前产品状态 */
statusCode: ProductStatusCode;
/** 最近一次状态动作原因 */
lastStatusReason?: string | null;
}
interface ProductLifecycleAction {
actionCode: ProductStatusActionCode;
actionName: string;
needReason: boolean;
}
interface ProductLifecycleInfo {
statusCode: ProductStatusCode;
lastStatusReason?: string | null;
availableActions: ProductLifecycleAction[];
}
interface ProductSettings {
baseInfo: ProductSettingBaseInfo;
lifecycle: ProductLifecycleInfo;
}
interface ProductMember {
/** 团队关系 ID */
id: string;
/** 用户 ID */
userId: string;
/** 用户昵称 */
userNickname: string;
/** 角色 ID */
roleId: string;
/** 角色名称 */
roleName: string;
/** 角色编码 */
roleCode: string;
/** 是否当前产品经理 */
managerFlag: boolean;
/** 成员状态 */
status: ProductMemberStatus;
/** 加入时间 */
joinedTime: string;
/** 退出时间 */
leftTime?: string | null;
/** 备注 */
remark?: string | null;
}
type ProductSearchParams = CommonType.RecordNullable<
Pick<PageParams, 'pageNo' | 'pageSize'> &
Pick<Product, 'directionCode' | 'managerUserId' | 'statusCode'> & {
keyword: string;
updateTime: string[];
}
>;
type SaveProductParams = Pick<Product, 'directionCode' | 'name' | 'managerUserId'> & {
code?: string | null;
description?: string | null;
remark?: string | null;
};
type UpdateProductParams = { id: string } & SaveProductParams;
interface ChangeProductStatusParams {
id: string;
actionCode: ProductStatusActionCode;
reason?: string | null;
}
interface DeleteProductParams {
id: string;
productName: string;
reason: string;
}
type UpdateProductSettingBaseInfoParams = Pick<ProductSettingBaseInfo, 'directionCode' | 'name'> & {
description?: string | null;
};
interface CreateProductMemberParams {
userId: string;
roleId: string;
remark?: string | null;
previousManagerUserId?: string | null;
previousManagerRoleId?: string | null;
}
interface UpdateProductMemberParams {
roleId: string;
remark?: string | null;
reason?: string | null;
previousManagerUserId?: string | null;
previousManagerRoleId?: string | null;
}
interface InactiveProductMemberParams {
reason?: string | null;
}
}
}

View File

@@ -19,13 +19,26 @@ declare namespace Api {
type RoleType = 1 | 2;
type ScopeType = 'global' | 'object';
type ObjectType = 'product' | 'project';
interface ScopeQueryParams {
scopeType?: ScopeType;
objectType?: ObjectType;
}
interface Role {
/** role id */
id: number;
id: string;
/** role name */
name: string;
/** role code */
code: string;
/** scope type */
scopeType?: ScopeType;
/** object type */
objectType?: ObjectType | '' | null;
/** display sort */
sort: number;
/** status: 0 enabled, 1 disabled */
@@ -39,13 +52,12 @@ declare namespace Api {
}
type RoleSearchParams = CommonType.RecordNullable<Pick<Role, 'name' | 'code' | 'status'>> &
PageParams & {
createTime?: string[];
};
PageParams & { createTime?: string[] } & ScopeQueryParams;
type SaveRoleParams = Pick<Role, 'name' | 'code' | 'sort' | 'status'> & {
type SaveRoleParams = (Pick<Role, 'name' | 'code' | 'sort' | 'status'> & {
remark?: string | null;
};
}) &
ScopeQueryParams;
type RoleList = PageResult<Role>;
@@ -149,7 +161,7 @@ declare namespace Api {
nickname?: string;
mobile?: string;
deptId?: number;
roleId?: number;
roleId?: string;
company?: string;
}
>;
@@ -218,7 +230,7 @@ declare namespace Api {
interface AssignUserRoleParams {
userId: number;
roleIds: number[];
roleIds: string[];
}
/**
@@ -244,17 +256,21 @@ declare namespace Api {
interface Menu {
/** menu id */
id: number;
id: string;
/** menu name */
name: string;
/** permission code */
permission?: string | null;
/** scope type */
scopeType?: ScopeType;
/** object type */
objectType?: ObjectType | '' | null;
/** menu type */
type: MenuType;
/** display sort */
sort: number;
/** parent menu id */
parentId: number;
parentId: string;
/** route path */
path?: string | null;
/** menu icon */
@@ -281,7 +297,7 @@ declare namespace Api {
children?: Menu[] | null;
}
type MenuSearchParams = CommonType.RecordNullable<Pick<Menu, 'name' | 'status'>>;
type MenuSearchParams = CommonType.RecordNullable<Pick<Menu, 'name' | 'status'>> & ScopeQueryParams;
type SaveMenuParams = Pick<
Menu,
@@ -300,12 +316,15 @@ declare namespace Api {
| 'visible'
| 'keepAlive'
| 'alwaysShow'
>;
> &
ScopeQueryParams;
interface MenuSimple {
id: number;
id: string;
name: string;
parentId: number;
parentId: string;
scopeType?: ScopeType;
objectType?: ObjectType | '' | null;
type: MenuType;
children?: MenuSimple[] | null;
}
@@ -315,8 +334,8 @@ declare namespace Api {
type MenuSimpleList = MenuSimple[];
interface AssignRoleMenuParams {
roleId: number;
menuIds: number[];
roleId: string;
menuIds: string[];
}
/**
@@ -327,11 +346,11 @@ declare namespace Api {
*/
interface UserManagementRelation {
/** 主键 ID */
id: any;
id: string | null;
/** 管理者用户 ID */
managerUserId: any;
managerUserId: string | null;
/** 被管理用户 ID */
subordinateUserId: any;
subordinateUserId: string | null;
/** 生效开始时间 */
effectiveFrom?: number | null;
/** 生效结束时间 */
@@ -350,13 +369,13 @@ declare namespace Api {
*/
interface UserManagementRelationTreeRespVO {
/** 关系记录主键 ID最高领导为 null */
id: number | null;
id: string | null;
/** 用户 ID */
userId: number;
userId: string;
/** 用户昵称 */
userNickname: string;
/** 上级用户 ID最高领导为 null */
managerUserId: number | null;
managerUserId: string | null;
/** 上级用户昵称(最高领导为 null */
managerNickname: string | null;
/** 下级用户列表(基层员工为空列表) */
@@ -371,11 +390,11 @@ declare namespace Api {
*/
interface UserManagementRelationSaveReqVO {
/** 主键 ID更新时需要 */
id?: number;
id?: string;
/** 管理者用户 ID */
managerUserId: any;
managerUserId: string | null;
/** 被管理用户 ID */
subordinateUserId: any;
subordinateUserId: string | null;
/** 生效开始时间 */
effectiveFrom?: number | null;
/** 生效结束时间 */
@@ -406,7 +425,7 @@ declare namespace Api {
*/
interface UserSimple {
/** 用户 ID */
id: number;
id: string;
/** 用户昵称 */
nickname: string;
}

47
src/typings/app.d.ts vendored
View File

@@ -549,6 +549,14 @@ declare namespace App {
enable: string;
disable: string;
};
scopeType: {
global: string;
object: string;
};
objectType: {
product: string;
project: string;
};
};
role: {
title: string;
@@ -569,6 +577,13 @@ declare namespace App {
selectedCount: string;
disabledTip: string;
emptyRole: string;
currentRoleCount: string;
globalRoleTitle: string;
objectRoleTitle: string;
globalRoleSummary: string;
objectRoleSummary: string;
objectRoleSummaryProduct: string;
objectRoleSummaryProject: string;
lastAuthSave: string;
unsavedTip: string;
form: {
@@ -691,11 +706,15 @@ declare namespace App {
menuType: string;
menuName: string;
permission: string;
scopeType: string;
objectType: string;
resourceCode: string;
routeName: string;
routePath: string;
routeKind: string;
routePropsJson: string;
pageResource: string;
boundRoute: string;
component: string;
componentName: string;
iframeUrl: string;
@@ -725,6 +744,29 @@ declare namespace App {
alwaysShow: string;
createTime: string;
topLevel: string;
contextEyebrow: string;
contextTitle: string;
contextDescription: string;
currentContext: string;
currentResourceCount: string;
editorMode: string;
editorModeGlobal: string;
editorModeObject: string;
globalResourceTitle: string;
objectResourceTitle: string;
globalResourceSummary: string;
objectResourceSummary: string;
objectResourceSummaryProduct: string;
objectResourceSummaryProject: string;
scopeHintGlobal: string;
scopeHintObject: string;
objectTypePlaceholder: string;
contextReady: string;
contextPending: string;
objectTypeRequiredTitle: string;
objectTypeRequiredDescription: string;
objectModeTipTitle: string;
objectModeTipDescription: string;
sections: {
basic: string;
route: string;
@@ -736,6 +778,7 @@ declare namespace App {
parentId: string;
menuName: string;
permission: string;
resourceCode: string;
routeName: string;
routePath: string;
path: string;
@@ -743,6 +786,7 @@ declare namespace App {
componentName: string;
routeKind: string;
pageResource: string;
boundRoute: string;
pageResourceParentMismatch: string;
routePropsJson: string;
routePropsJsonHint: string;
@@ -784,6 +828,7 @@ declare namespace App {
};
routePath: string;
pageResource: string;
boundRoute: string;
component: string;
};
addMenu: string;
@@ -793,6 +838,8 @@ declare namespace App {
directory: string;
menu: string;
button: string;
navigation: string;
actionButton: string;
};
iconType: {
iconify: string;

View File

@@ -17,6 +17,10 @@ declare module 'vue' {
CountTo: typeof import('./../components/custom/count-to.vue')['default']
CustomIconSelect: typeof import('./../components/custom/custom-icon-select.vue')['default']
DarkModeContainer: typeof import('./../components/common/dark-mode-container.vue')['default']
DictSelect: typeof import('./../components/custom/dict-select.vue')['default']
DictTag: typeof import('./../components/custom/dict-tag.vue')['default']
DictText: typeof import('./../components/custom/dict-text.vue')['default']
ElAffix: typeof import('element-plus/es')['ElAffix']
ElAlert: typeof import('element-plus/es')['ElAlert']
ElAvatar: typeof import('element-plus/es')['ElAvatar']
ElBreadcrumb: typeof import('element-plus/es')['ElBreadcrumb']
@@ -105,6 +109,7 @@ declare module 'vue' {
IconLocalCast: typeof import('~icons/local/cast')['default']
IconLocalLogo: typeof import('~icons/local/logo')['default']
'IconMaterialSymbolsLight:rotate90DegreesCcwOutlineRounded': typeof import('~icons/material-symbols-light/rotate90-degrees-ccw-outline-rounded')['default']
IconMaterialSymbolsLightCheckCircleRounded: typeof import('~icons/material-symbols-light/check-circle-rounded')['default']
'IconMdi:printer': typeof import('~icons/mdi/printer')['default']
IconMdiAccountTieOutline: typeof import('~icons/mdi/account-tie-outline')['default']
IconMdiArrowDownThin: typeof import('~icons/mdi/arrow-down-thin')['default']

View File

@@ -60,6 +60,11 @@ declare module "@elegant-router/types" {
"plugin_tables_vtable": "/plugin/tables/vtable";
"plugin_typeit": "/plugin/typeit";
"plugin_video": "/plugin/video";
"product": "/product";
"product_dashboard": "/product/dashboard";
"product_list": "/product/list";
"product_requirement": "/product/requirement";
"product_setting": "/product/setting";
"system": "/system";
"system_dict": "/system/dict";
"system_menu": "/system/menu";
@@ -111,6 +116,7 @@ declare module "@elegant-router/types" {
| "iframe-page"
| "login"
| "plugin"
| "product"
| "system"
| "user-center"
>;
@@ -162,6 +168,10 @@ declare module "@elegant-router/types" {
| "plugin_tables_vtable"
| "plugin_typeit"
| "plugin_video"
| "product_dashboard"
| "product_list"
| "product_requirement"
| "product_setting"
| "system_dict"
| "system_menu"
| "system_post"

57
src/typings/object-context.d.ts vendored Normal file
View File

@@ -0,0 +1,57 @@
declare namespace App {
namespace ObjectContext {
type DomainKey = 'project' | 'product' | string;
type ObjectType = 'project' | 'product' | string;
type Menu = {
/** 对象上下文菜单 key优先约定为目标路由 name */
key: string;
/** 菜单文案 */
label: string;
/** 路由 name可为空 */
routeKey?: string | null;
/** 路由 path可为空 */
routePath?: string | null;
/** 子菜单 */
children?: Menu[];
};
interface DomainConfig {
domainKey: DomainKey;
mode: 'object-context';
objectType: ObjectType;
/** 用于识别当前路由是否属于该业务域 */
routePathPrefixes: string[];
/** 业务域入口页 */
entryRouteKey: string;
entryRoutePath: string;
/** 对象默认首页兜底值 */
fallbackDefaultRouteKey: string;
fallbackDefaultRoutePath: string;
/** 上下文接口 */
contextApiPath: string;
contextApiObjectIdParamKey: string;
contextApiObjectIdPlacement?: 'query' | 'path';
/** 第一版固定为 objectId */
objectIdQueryKey: 'objectId';
}
interface Summary {
[key: string]: unknown;
}
interface State {
domainKey: DomainKey;
objectType: ObjectType;
objectId: string;
objectName: string;
objectSummary: Summary | null;
contextScopedMenus: Menu[];
buttonCodes: string[];
defaultRouteKey: string;
defaultRoutePath: string;
isReady: boolean;
}
}
}