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:
17
src/typings/api/dict.d.ts
vendored
17
src/typings/api/dict.d.ts
vendored
@@ -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
15
src/typings/api/object-context.d.ts
vendored
Normal 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
164
src/typings/api/product.d.ts
vendored
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
71
src/typings/api/system-manage.d.ts
vendored
71
src/typings/api/system-manage.d.ts
vendored
@@ -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
47
src/typings/app.d.ts
vendored
@@ -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;
|
||||
|
||||
5
src/typings/components.d.ts
vendored
5
src/typings/components.d.ts
vendored
@@ -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']
|
||||
|
||||
10
src/typings/elegant-router.d.ts
vendored
10
src/typings/elegant-router.d.ts
vendored
@@ -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
57
src/typings/object-context.d.ts
vendored
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user