[TinyERP: 企业应用程序的 SPA] 管理员工






4.67/5 (2投票s)
添加新“管理员工”功能的步骤
概述
在本文中,我们将通过步骤来添加新的“管理员工”功能。
你们中的一些人可能会对项目结构有疑问,请写下来。我们稍后会修改。
正如上一部分所述,Web 应用程序使用客户端和服务器模块。所以
- 客户端:将负责与最终用户交互。因此,它将在 UI 上显示系统信息(报表、数据等)或接收用户操作(例如:输入值、单击按钮执行操作等)
- 服务器端:处理系统的业务逻辑(应用程序)
例如,使用“发送邮件”功能
- 客户端允许最终用户输入收件人地址、主题、邮件内容、附件(如果有)等,并通过 UI 上的“发送”按钮监听来自最终用户的“发送请求”。
- 服务器端:将处理“发送邮件”逻辑,例如:接收以上信息、验证、发送邮件、存储在必要的存储库中等。
“员工管理”通常是 HRM 模块中的一项功能。在这种情况下,我们将做同样的事情。
对于客户端
在 modules 文件夹下添加“hrm”文件夹,结构如下图所示
模块的起始点位于“hrmModule.ts”
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from "@angular/core";
import { CommonModule } from "@angular/common";
import { FormsModule } from "@angular/forms";
import { AppCommon, BaseModule, ModuleConfig, ModuleNames } from "@app/common";
import { HrmRoute } from "./hrmRoute";
import ioc from "./_share/config/ioc";
import routes from "./_share/config/routes";
import mainMenus from "./_share/config/mainMenus";
@NgModule({
imports: [CommonModule, FormsModule, AppCommon, HrmRoute],
declarations: [],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class HrmModule extends BaseModule {
constructor() {
super(new ModuleConfig(ModuleNames.HRM, ioc, routes));
this.mainMenus = mainMenus;
}
}
在此文件中,我们注册 HRM 模块所需的信息,例如:名称、IOC 注册、路由、菜单。
模块的路由定义在“<moduleName>Route.ts”文件中,在此情况下为 hrmModule.ts
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from "@angular/core";
import { RouterModule } from "@angular/router";
import { CommonModule } from "@angular/common";
import { FormsModule } from "@angular/forms";
import helperFacade, { AppCommon } from "@app/common";
import routes from "./_share/config/routes";
import { Staffs } from "./pages/staffs";
let routeConfigs = [
{ path: "", redirectTo: routes.staffs.path, pathMatch: "full" },
{ path: routes.staffs.path, component: Staffs}
];
@NgModule({
imports: [CommonModule, FormsModule, RouterModule.forChild(routeConfigs), AppCommon],
exports: [RouterModule, Staffs],
declarations: [Staffs],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class HrmRoute { }
在这里,我们将每个相对 URI 与指定的页面进行映射。在此文件中,我们将“<...>/staffs”映射到“Staffs”页面。因此,当用户浏览到“<uri>/staffs”时,Staffs 页面将在浏览器中渲染。
对于“./_share/config/routes.ts”,定义对象比在我们的应用程序中使用“魔术字符串”更简单。默认情况下,我们通常像这样定义路由
let routeConfigs = [
{ path: "", redirectTo: "staffs", pathMatch: "full" },
{ path: "staffs", component: Staffs}
];
使用此代码,我们将“staffs
”用作 string
。如“编码技巧”中所述,这可能会导致潜在的错误。如果您的成员在“staff
”而不是“staffs
”时输入错误,系统将崩溃或抛出异常。
因此,我更喜欢我们使用这种格式
let routeConfigs = [
{ path: "", redirectTo: routes.staffs.path, pathMatch: "full" },
{ path: routes.staffs.path, component: Staffs}
];
这并不能保证 100% 成功,但可以降低系统中潜在错误的百分比。
对于“./_share/config/route.ts”,没有什么特别的
let routes: any = {
staffs: { name: "hrm.staffs", path: "staffs" }
};
export default routes;
对于 ioc.ts 和 mainMenus.ts。它们目前是空的。
继续处理“pages”文件夹中的 Staffs 页面
“pages/staffs.html”包含“Staffs”页面的 HTML,我们目前使用硬编码文本
<page>
<page-header>Manage Staffs</page-header>
<page-content>Content of "manage staffs" page</page-content>
</page>
我们可以看到,“page
”组件已定义。这将处理系统中所有页面的通用行为/UI。因此,在每个页面(在此情况下为 HRM 模块中的Staffs页面)中。我们需要为“Staffs”页面提供必要的功能,而“page/staffs.ts”包含“Staffs”页面的逻辑。
import {Component} from "@angular/core";
import {BasePage} from "@app/common";
@Component({
templateUrl:"src/modules/hrm/pages/staffs.html"
})
export class Staffs extends BasePage<any>{
}
这是一个纯粹的 Angular 组件(请参阅“Angular 组件”了解更多信息。系统中所有页面都需要继承自 BasePage<Model>
类。
好的,到目前为止,我们已经定义了新的 HRM 模块并将“staffs
”映射到了 Staffs 页面。
最后一步是将 HRM 注册到当前应用程序。在 TinyERP 中,我们可以拥有多个应用程序,每个应用程序可以有不同数量的模块。“dashboard
”目前是默认应用程序。
打开“<root>/src/apps/dashboard/modules.ts”并注册 HRM 模块
import { ModuleNames, IModuleConfigItem } from "@app/common";
let modules: Array<IModuleConfigItem> = [
{ name: ModuleNames.Support, urlPrefix: ModuleNames.Support, path: ModuleNames.Support},
{ name: ModuleNames.HRM, urlPrefix: ModuleNames.HRM, path: ModuleNames.HRM}
];
export default modules;
然后,在“<root>/apps/dashboard/config/themes.ts”中声明哪个主题可以使用 HRM 模块(请参阅 modules 属性)
import { ITheme, AppThemeType, ModuleNames } from "@app/common";
let themes: Array<ITheme> = [
{
name: AppThemeType.Default,
isDefault:true,
urlPrefix: AppThemeType.Default,
modules: [
{name: ModuleNames.Support, isDefault:true},
{name:ModuleNames.HRM}
]
}
];
export default themes;
好的,如果您有疑问,请推迟到下一部分“修改管理员工”。
现在,您可以编译并运行。浏览器中的结果如下所示
好的,以上结果只是表明新的 HRM 模块已集成到当前系统中。
继续在 Staffs 页面添加员工列表,我们有一个可用的网格控件(具有基本功能),这是 Jquery Table 控件的包装器。有关“在 Angular 中包装当前 js/jquery 控件”的更多信息,请访问我的博客,一篇关于此主题的新文章将在 http://tranthanhtu.vn 上发布:
<page>
<page-header>Manage Staffs</page-header>
<page-content>
<grid
[options]="model.options"
[fetch]="fetch">
</grid>
</page-content>
</page>
如果将此页面与上一页进行比较,有一个非常简单的更改,即网格控件及其选项和 fetch 属性。
对于 [options]
:这指定了如何初始化网格控件,例如列、标题等。
对于 [fetch]
:这将用于从远程源获取数据并将其绑定到网格控件。
如果您对网格控件有更多功能需求,可以采用相同的方式,将所需的当前网格控件包装到 Angular 中(如果不可用)。
让我们稍微了解一下“model
”,每个页面都应该有自己的模型,其中包含必要的数据并降低“staffs.ts”的复杂性。这很简单
export class StaffsModel{
public options: any = {};
constructor(){
this.options = {
columns: [
{ field: "firstName", title: "First Name"},
{ field: "lastName", title: "Last Name"},
{ field: "department", title: "Department"}
]
};
}
}
在此模型中,我们为网格声明了 3 列:firstName
、lastName
和 department
。
每列都有字段和标题(显示在网格标题上的文本),并且在 staffs.ts 中我们也做了一些小改动
export class Staffs extends BasePage<StaffsModel>{
public model: StaffsModel;
constructor() {
super();
let self = this;
self.model = new StaffsModel();
}
public fetch(): Promise {
let def: Promise = PromiseFactory.create();
let service: IStaffService = window.ioc.resolve(LocalIoCNames.IStaffService);
service.getStaffs().then(function (searchResult: any) {
def.resolve(searchResult.items || []);
});
return def;
}
}
有一个新的模型,类型为 StaffsModel
和 fetch
方法。
在 fetch 中,只需调用相应的服务即可从远程源获取数据(在 IStaffService
接口和 StaffService
类中定义)。目前,网格仅提供示例的基本功能,如果您需要更复杂的功能,请随时在“<root>/src/modules/common/components/grid/”中进行更改,例如:传递分页参数等。
对于 LocalIoCNames.IStaffService
,这只是一个映射到 string
的常量,因为我不喜欢使用魔术代码。有关更多信息,请参阅编码技巧模式。
export const LocalIoCNames = {
IStaffService: "IStaffService"
};
以及 IStaffService
export interface IStaffService{
getStaffs():Promise;
}
这只是一个接口,我们可以在应用程序的任何地方使用这个接口。
而 ServiceStaff
是 IServiceStaff
的实现
import {BaseService, Promise, IConnector, IoCNames} from "@app/common";
import {IStaffService} from "./istaffService";
export class StaffService extends BaseService implements IStaffService{
public getStaffs():Promise{
let uri="/api/hrm/staffs.json";
let iconnector: IConnector = window.ioc.resolve(IoCNames.IConnector);
return iconnector.get(uri);
}
}
我们可以看到 StaffService
继承自 BaseService
,它定义在“app/common”中。
在此类中,我们使用 IConnector
并从“staffs.json”文件获取数据,因为此时 API 尚不可用。我们将用 API URI 替换它。
以及 json 文件的内容
{
"errors":[],
"status":"200",
"data":{
"totalItems":"2",
"items":[
{"firstName":"Tu", "lastName":"Tran", "department":"Department 1"},
{"firstName":"Tu1", "lastName":"Tran", "department":"Department 1"}
]
}
}
这只是为了模拟来自 RESTful Web 服务的响应。
好的,让我们回顾一下
- 定义具有网格的staffs页面:完成
- 从远程源加载数据并绑定到网格:完成
- 实现
IStaffService
和StaffService
:完成 - 调用 json 文件中的硬编码值:完成
好的,让我们编译并运行看看效果
哦,抛出了一个异常。此错误与 IOC 注册有关,我们有 IStaffService
和 StaffService
,但没有将它们映射在一起。让我们将其添加到“<module>/_share/config/ioc.ts”中
import {LocalIoCNames} from "../enum";
import {IoCLifeCycle} from "@app/common";
import {StaffService} from "../services/staffService";
let ioc: Array<IIoCConfigItem> = [
{ name: LocalIoCNames.IStaffService, instance: StaffService, lifeCycle: IoCLifeCycle.Transient }
];
export default ioc;
再次编译并刷新,我们将看到如下结果
好的,很棒。它完美地工作了。
我希望在左侧面板上看到“管理员工”,这样我就可以通过单击此菜单来访问此功能。将以下行添加到“<module folder>/_share/config/mainMenus.ts”
import { IResourceManager, IoCNames } from "@app/common";
let mainMenus: Array<any> = [
{ text: "Manage Staffs", url: "default/hrm/staffs", cls: "" },
];
export default mainMenus;
让我们再次编译并刷新
左侧面板上有“管理员工”。
客户端的内容现在有点长了,所以让我们继续我下一篇文章中的 API 部分,它也可能会很长。
有关此部分中参考源代码,请查看 https://github.com/tranthanhtu0vn/TinyERP (分支:feature/manage_staff)。
系列中的其他文章
- 概述
- “管理员工”示例(本文)
- “管理员工”第 2 部分(API)
- 跨模块/域通信
- 处理错误/验证
感谢您的阅读。
注意:请点赞并与您的朋友分享,我将非常感激。