├── .angular-cli.json ├── .editorconfig ├── .gitignore ├── README.md ├── deploy.md ├── doc └── screen-snapshots │ ├── change-pwd.png │ ├── left-menu-snapshot.png │ ├── left-menu-snapshot2.png │ ├── login-snapshot.png │ └── top-memu-snapshot.png ├── e2e ├── app.e2e-spec.ts ├── app.po.ts └── tsconfig.e2e.json ├── karma.conf.js ├── package.json ├── protractor.conf.js ├── src ├── app │ ├── app-routing.module.ts │ ├── app.component.ts │ ├── app.module.ts │ ├── base │ │ ├── change-password │ │ │ ├── change-password-routing.module.ts │ │ │ ├── change-password.component.html │ │ │ ├── change-password.component.ts │ │ │ └── change-password.module.ts │ │ ├── frame │ │ │ ├── frame-routing.module.ts │ │ │ ├── frame.component.css │ │ │ ├── frame.component.html │ │ │ ├── frame.component.ts │ │ │ └── frame.module.ts │ │ ├── login │ │ │ ├── login-routing.module.ts │ │ │ ├── login.component.css │ │ │ ├── login.component.html │ │ │ ├── login.component.ts │ │ │ ├── login.module.ts │ │ │ └── login.service.ts │ │ └── shared │ │ │ ├── form-util.ts │ │ │ ├── heard │ │ │ ├── head-user.component.ts │ │ │ ├── head.component.css │ │ │ ├── head.component.html │ │ │ └── head.component.ts │ │ │ ├── menu │ │ │ ├── menu.component.html │ │ │ └── menu.component.ts │ │ │ ├── model │ │ │ ├── http-result.ts │ │ │ ├── page-filter.ts │ │ │ ├── page.ts │ │ │ └── session.ts │ │ │ └── session.service.ts │ ├── config │ │ └── contents-modules.ts │ ├── contents │ │ ├── role │ │ │ ├── role-edit │ │ │ │ ├── role-edit.component.css │ │ │ │ ├── role-edit.component.html │ │ │ │ └── role-edit.component.ts │ │ │ ├── role-list │ │ │ │ ├── role-list.component.css │ │ │ │ ├── role-list.component.html │ │ │ │ └── role-list.component.ts │ │ │ ├── role-routes.module.ts │ │ │ ├── role-view │ │ │ │ ├── permission-edit.component.css │ │ │ │ ├── permission-edit.component.html │ │ │ │ ├── permission-edit.component.ts │ │ │ │ ├── permission-group.ts │ │ │ │ ├── role-view.component.css │ │ │ │ ├── role-view.component.html │ │ │ │ └── role-view.component.ts │ │ │ ├── role.module.ts │ │ │ └── role.service.ts │ │ └── user │ │ │ ├── user-edit │ │ │ ├── user-edit.component.css │ │ │ ├── user-edit.component.html │ │ │ └── user-edit.component.ts │ │ │ ├── user-list │ │ │ ├── user-list.component.css │ │ │ ├── user-list.component.html │ │ │ └── user-list.component.ts │ │ │ ├── user-routes.module.ts │ │ │ ├── user-view │ │ │ ├── user-reset-password.component.css │ │ │ ├── user-reset-password.component.html │ │ │ ├── user-reset-password.component.ts │ │ │ ├── user-role-edit.component.css │ │ │ ├── user-role-edit.component.html │ │ │ ├── user-role-edit.component.ts │ │ │ ├── user-view.component.css │ │ │ ├── user-view.component.html │ │ │ └── user-view.component.ts │ │ │ ├── user.module.ts │ │ │ └── user.service.ts │ ├── permission.gurid.ts │ └── providers │ │ └── login.service-impl.ts ├── assets │ ├── .gitkeep │ └── imgs │ │ ├── login_bk.jpg │ │ └── logo.png ├── config │ └── global-config.ts ├── environments │ ├── environment.prod.ts │ └── environment.ts ├── favicon.ico ├── index.html ├── main.ts ├── polyfills.ts ├── styles.css ├── tsconfig.app.json ├── tsconfig.spec.json └── typings.d.ts ├── tsconfig.json └── tslint.json /.angular-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "project": { 4 | "name": "ng-zorro-demo" 5 | }, 6 | "apps": [ 7 | { 8 | "root": "src", 9 | "outDir": "dist", 10 | "assets": [ 11 | "assets", 12 | "favicon.ico" 13 | ], 14 | "index": "index.html", 15 | "main": "main.ts", 16 | "polyfills": "polyfills.ts", 17 | "test": "test.ts", 18 | "tsconfig": "tsconfig.app.json", 19 | "testTsconfig": "tsconfig.spec.json", 20 | "prefix": "app", 21 | "styles": [ 22 | "styles.css" 23 | ], 24 | "scripts": [], 25 | "environmentSource": "environments/environment.ts", 26 | "environments": { 27 | "dev": "environments/environment.ts", 28 | "prod": "environments/environment.prod.ts" 29 | } 30 | } 31 | ], 32 | "e2e": { 33 | "protractor": { 34 | "config": "./protractor.conf.js" 35 | } 36 | }, 37 | "lint": [ 38 | { 39 | "project": "src/tsconfig.app.json" 40 | }, 41 | { 42 | "project": "src/tsconfig.spec.json" 43 | }, 44 | { 45 | "project": "e2e/tsconfig.e2e.json" 46 | } 47 | ], 48 | "test": { 49 | "karma": { 50 | "config": "./karma.conf.js" 51 | } 52 | }, 53 | "defaults": { 54 | "styleExt": "css", 55 | "component": {} 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | 8 | # dependencies 9 | /node_modules 10 | 11 | # IDEs and editors 12 | /.idea 13 | .project 14 | .classpath 15 | .c9/ 16 | *.launch 17 | .settings/ 18 | *.sublime-workspace 19 | 20 | # IDE - VSCode 21 | .vscode/* 22 | !.vscode/settings.json 23 | !.vscode/tasks.json 24 | !.vscode/launch.json 25 | !.vscode/extensions.json 26 | 27 | # misc 28 | /.sass-cache 29 | /connect.lock 30 | /coverage 31 | /libpeerconnection.log 32 | npm-debug.log 33 | testem.log 34 | /typings 35 | 36 | # e2e 37 | /e2e/*.js 38 | /e2e/*.map 39 | 40 | # System Files 41 | .DS_Store 42 | Thumbs.db 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NG-ZORRO-TEMPLATE 2 | NG-ZORRO-TEMPLATE是基于阿里angular组件[NG-ZORRO](https://ng.ant.design/#/docs/angular/introduce)开发的一套企业后台管理系统模板。目的是简单、方便、快速的搭建一个企业后台管理系统。 3 | 4 | [在线Deom](https://alexspring123.github.io/NG-ZORRO-TEMPLATE) 5 | 6 | 模板项目提供了登录页面、动态菜单、session存储、权限路由守卫等功能,您只需要开发对应的后台服务,即可实现一个简单的后台管理系统; 7 | 8 | 为了方便大家在没有后台服务时也能使用,项目提供了一些fake和demo,可以直接使用。 9 | 10 | # 目录 11 | * [截图](#截图) 12 | * [安装运行](#安装运行) 13 | * [配置说明](#us配置说明age) 14 | * [系统配置](#系统配置) 15 | * [登录页面配置](#登录页面配置) 16 | * [菜单配置](#菜单配置) 17 | * [版权配置](#版权配置) 18 | * [本地存储配置](#本地存储配置) 19 | * [对接自己后台登录服务](#对接自己后台登录服务) 20 | * [设置浏览器标题](#设置浏览器标题) 21 | * [给路由添加权限守卫](#给路由添加权限守卫) 22 | * [增加按钮权限控制](#增加按钮权限控制) 23 | * [目录说明](#目录说明) 24 | * [自定义模块注册](#自定义模块注册) 25 | * [升级模板说明](#升级模板说明) 26 | * [部署说明](#部署说明) 27 | 28 | # 截图 29 | 登录界面 30 | ![登录界面](/doc/screen-snapshots/login-snapshot.png "登录界面") 31 | 顶部菜单 32 | ![顶部菜单](/doc/screen-snapshots/top-memu-snapshot.png "顶部菜单") 33 | 左边菜单-展开 34 | ![左边菜单-展开](/doc/screen-snapshots/left-menu-snapshot.png "左边菜单-展开") 35 | 左边菜单-折叠 36 | ![左边菜单-折叠](/doc/screen-snapshots/left-menu-snapshot2.png "左边菜单-折叠") 37 | 修改密码 38 | ![修改密码](/doc/screen-snapshots/change-pwd.png "修改密码") 39 | 40 | # 安装运行 41 | ## 准备工作 42 | - 安装git 43 | - 安装npm 44 | - 安装最新版[angular-cli](https://github.com/angular/angular-cli) 45 | 46 | ## 下载模板代码 47 | 直接clone代码 48 | ``` 49 | git clone https://github.com/alexspring123/NG-ZORRO-TEMPLATE.git 50 | npm install 51 | ``` 52 | > 如果npm速度较慢,大家可以安装使用[cnpm](https://github.com/cnpm/cnpm) 53 | 54 | 然后在console中执行下面命令 55 | ``` 56 | ng s --open 57 | ``` 58 | 此时浏览器就自动打开了模板网站,是否特别简单。 59 | 60 | # 配置说明 61 | 模板网站允许通过配置来更改网站的外观。所有配置项都存放在/src/config/global-config.ts文件中 62 | 63 | ## 系统配置 64 | 配置项 | 说明 65 | --- | ------ 66 | app_title | 系统名称,显示在浏览器的title中(浏览器的标签)
如果路由中配置了data:{title:'xxxx'}参数,系统激活路由时会自动替换为title参数的取值,例如模板中角色样例模块RoleRoutes 67 | 68 | ## 登录页面配置 69 | 登录页面配置对应配置文件loginConfig类中 70 | 71 | 配置项 | 说明 72 | --- | ------ 73 | background_image | 登录页面背景图片,图片一般放在/src/assets目录中 74 | form_title | 登录表单的标题,最好控制在8个汉字以内 75 | 76 | ## 菜单配置 77 | 所有菜单相关配置存放在配置文件menuConfig类中 78 | 79 | 配置项 | 说明 80 | --- | ------ 81 | placement | 取值'left'或'top'。
设置为'left',菜单显示在左侧
设置为'top',菜单显示在顶部 82 | 83 | ## 版权配置 84 | 配置项 | 说明 85 | --- | ------ 86 | copyright | 版权信息。目前仅支持字符串
配置后会在主窗口最下方显示版权信息
如果值为空或空字符串,则不显示版权信息。 87 | 88 | ## 本地存储配置 89 | 模板项目为大家提供了本地session的存储功能,存放在配置文件的sessionConfig类中 90 | 91 | 配置项 | 说明 92 | --- | ------ 93 | store_key | 本地session存储的key
系统会自动将session存放在本地的localstorage中 94 | 95 | # 对接自己后台登录服务 96 | 目前系统严格定义了后台服务需要返回的接口格式如下: 97 | ```js 98 | /**预定义服务 */ 99 | export interface LoginService { 100 | /** 101 | * 登录 102 | */ 103 | login(loginData: { login: string, password: string }): Observable>; 104 | 105 | /** 106 | * 注销 107 | * userCode:当前登录用户代码 108 | * token:当前登录的token 109 | * userCode和token必须有一个不为Null。 110 | * 登录后服务端会生成一个token并返回给界面,此时只需要传入token一个参数服务端即可完成注销 111 | */ 112 | logout(token: string): Observable>; 113 | 114 | /** 115 | * 修改密码 116 | * 处于安全,session不会保存用户的密码,因此修改密码时需要传入原始密码,服务端需要对原始密码和token进行验证 117 | * 服务端成功后不用返回data 118 | */ 119 | changePassword(data: { token: string, oldPassword: string, newPassword: string }): Observable>; 120 | } 121 | ``` 122 | http远程调用返回angular的Observable异步对象(建议大家也采用此方式) 123 | Ï 124 | 其中的HttpResult和Session是系统定义的数据格式: 125 | ```js 126 | export class HttpResult { 127 | constructor(public code: number, public message: string, public data: T) { 128 | this.code = code; 129 | this.message = message; 130 | this.data = data; 131 | } 132 | } 133 | ``` 134 | 135 | ```js 136 | //菜单 137 | export interface Menu { 138 | title: string;//菜单标题 139 | path: string;//路由路径 140 | icon?: string; //图片 141 | subMenus?: Array; //子菜单 142 | } 143 | 144 | //权限 145 | export interface Permission { 146 | code: string; //权限代码 147 | name: string; //权限名称 148 | } 149 | 150 | /**登录后返回的Session */ 151 | export interface Session { 152 | userCode: string; 153 | userName: string; 154 | token?: string; 155 | menus: Array; 156 | permissions: Array; 157 | } 158 | ``` 159 | 上面只是解释了登录用到的接口和数据结果,实际开发中大家只需要替换/src/app/providers/login.service-impl.ts文件的实现即可 160 | > **特别注意:** 此文件名称和位置不能更改 161 | 162 | # 设置浏览器标题 163 | 模板提供了为每个路由页面单独设置浏览器标题的功能。 164 | 设置方法:在路由定义中增加data参数,并在其中定义title属性,例如 165 | ```typescript 166 | export const RoleRoutes: Route[] = [ 167 | { 168 | path: 'role', component: RoleComponent, data: { title: '角色' }, 169 | children: [ 170 | { path: 'list', component: RoleListComponent, data: { title: '角色列表', permission: ['/role1/view'] } }, 171 | ] 172 | }, 173 | ]; 174 | ``` 175 | 当路由导航到 *role/list* 页面时,浏览器标题显示为:**智能照明管理系统-角色列表** 176 | 其中“智能照明管理系统”全局选项app_title的值 177 | 如果某个路由未设置data['title']属性,那么浏览器标题默认为app_title的值 178 | 179 | # 给路由添加权限守卫 180 | 管理系统一般会根据用户权限来控制能够进入某个模块,通常做法是编写一个路由守卫,然后添加到路由定义中; 181 | 为了方便大家使用,模板提供了默认的路由权限守卫[PermissionGurid](./src/app/permission.gurid.ts),大家只需要在路由定义中增加data参数,并配置permission字段即可,例如 182 | ```typescript 183 | export const RoleRoutes: Route[] = [ 184 | { path: 'role', redirectTo: '/frame/role/list', pathMatch: 'full' }, 185 | { 186 | path: 'role', component: RoleComponent, data: { title: '角色' }, 187 | children: [ 188 | { path: 'list', component: RoleListComponent, data: { title: '角色列表', permission: ['/role/view'] } }, 189 | ] 190 | }, 191 | ]; 192 | ``` 193 | permission字段是一个字符串数组,可以配置多个权限,目前实现的功能为,只要用户包含其中一个权限,守卫即放行,而不是同时具有所有权限。 194 | 如果用户没有permission中定义的任一个权限,那么系统将不跳转,并且谈框提示缺少权限。 195 | 196 | # 增加按钮权限控制 197 | 在SessionService类中提供了 198 | ``` 199 | hasPermission(permissionCode: string): boolean { 200 | if (!permissionCode) return false; 201 | 202 | let session = this.getSession(); 203 | return session ? session.permissions.some(p => p.code == permissionCode) : false; 204 | } 205 | ``` 206 | 因此在模块中仅需要调用此方法来判断是否具有权限,然后控制界面按钮是否可见 207 | ```typescript 208 | _canCreate = false; 209 | _canEdit = false; 210 | _canDelete = false; 211 | 212 | constructor(private sessionService: SessionService, 213 | ......) { 214 | } 215 | 216 | ngOnInit(): void { 217 | this.refreshPermission(); 218 | this.search(true); 219 | } 220 | 221 | private refreshPermission(): void { 222 | this._canCreate = this.sessionService.hasPermission('/role/create'); 223 | this._canEdit = this.sessionService.hasPermission('/role/edit'); 224 | this._canDelete = this.sessionService.hasPermission('/role/delete'); 225 | } 226 | ``` 227 | ```html 228 | 232 | ``` 233 | 234 | # 目录说明 235 | 模板项目的目录结构定义为 236 | 237 | 目录或文件 | 用户是否可更改 | 说明 238 | --- | --- | ------ 239 | /doc/\*.\* | 可以删除 | 记录项目相关的文档 240 | /src/\*.\* | 不可更改 | src下面的文件(不包含子目录)属于模板项目 241 | /src/assets/ | 可以更改 | 此目录下面是用户的静态资源,比如图片、文件等,用户可以自由修改 242 | /src/config/ | 可以更改 | 此目录下存放的是系统配置文件,用户可以更改,但是不能删除任何文件 243 | /src/environments/ | 可以更改| 此目录是anguler-cli标准的环境变量文件,可以按需修改 244 | /src/app/base/ | 不可更改 | 存放项目的登录页面、主页面、修改密码页面、注销页面等,用户不可修改 245 | /src/app/providers/ | 可以修改,但是不能删除 | 存放模板定义好的接口实现,用户可以修改实现,但是不能修改文件名、类名和实现的接口 246 | /src/app/contents/ | 可以任何修改 | 存放用户自己的模块代码,可以任意修改 247 | /src/app/config/ | 可以修改,但是不能删除 | 存放用户模块和路由的注册文件 248 | 249 | # 自定义模块注册 250 | 自定义的模块通过在/src/app/config/contents-modules.ts中注册。 251 | ```typescript 252 | //客户自定的模块注册 253 | export const ContentsRoutes: Route[] = [ 254 | { path: '', loadChildren: getRoleModule }, 255 | ]; 256 | 257 | export function getRoleModule(): any { return RoleModule; } 258 | ``` 259 | 由于整个模板使用了异步路由,因此新增的模块不再需要在其他模块中import,仅需要在此配置中增加一行异步路由即可; 260 | 261 | # 升级模板说明 262 | - 下载最新的项目文件 263 | 264 | - 替换如下文件 265 | > /src/\*.html \*.ts \*.css \*.json
266 | > /src/app/base/,整个目录覆盖 267 | 268 | - 比对并合并如下文件 269 | > /src/config/global-config.ts 270 | 271 | # 部署说明 272 | 开发调试好后需要部署到服务器上运行,我这里仅介绍apache的部署方式(其他如Nginx,IIS等类似) 273 | ## 部署在服务器根目录 274 | 编译制品 275 | ``` 276 | ng build -prod -aot 277 | ``` 278 | 将编译结果/dist目录中的文件直接复制到apache的根目录(linux上的默认目录为/var/www/html) 279 | 启动apache,打开浏览器访问http://IP:端口,就可以看到你的项目界面了 280 | 281 | ## 部署在服务器的子目录 282 | 比如想将项目部署到apache的subDir子目录中 283 | 编译制品 284 | ``` 285 | ng build -prod -aot -bh /subDir/ -d /subDir/ 286 | ``` 287 | **其中subDir是apache中的目录名称,你可以替换成你自己的目录名称** 288 | 将编译结果/dist目录中的文件直接复制到apache的subDir目录(linux上的默认目录为/var/www/html/subDir) 289 | 290 | 配置项目目录 291 | 打开apache的配置文件,ubuntu上是/etc/apache2/apache2.conf,添加如下代码 292 | ``` 293 | 294 | Options FollowSymLinks 295 | AllowOverride all 296 | allow from all 297 | 298 | ``` 299 | 启动apache,打开浏览器访问:http://IP:端口/subDir 可以正常访问 300 | 301 | ## 解决浏览器刷新问题 302 | 上面部署可以正常访问,但是刷新浏览器时会报错 303 | > The requested URL /***** was not found on this server 304 | 原因是,正常访问时url被angular接管,[浏览器并没有向服务器发送请求](https://angular.cn/guide/router#附录:locationstrategy以及浏览器url样式)。当刷新浏览器时,由浏览器接管url,会向服务端发送请求。而服务器上并没有人处理,从而报Not found错误。 305 | 要解决这个问题,我们只需要让服务器处理这个请求即可,下面以ubuntu上的apache为例。 306 | 307 | ### 打开apache的路径重写功能 308 | 激活mod_rewrite模块 309 | ``` 310 | sudo a2enmod rewrite 311 | ``` 312 | 重新apache生效 313 | ``` 314 | sudo systemctl restart apache2 315 | ``` 316 | ### 打开apache目录的重写开关 317 | 修改/etc/apache2/apache2.conf文件 318 | 如果部署在根目录,找到下面的配置 319 | ``` 320 | 321 | Options Indexes FollowSymLinks 322 | AllowOverride None 323 | Require all granted 324 | 325 | ``` 326 | 将其中 *AllowOverride None* 改为 **AllowOverride all**,如果没有就增加一条。 327 | 如果部署在子目录subDir中,找到下面的配置 328 | ``` 329 | 330 | Options FollowSymLinks 331 | AllowOverride all 332 | allow from all 333 | 334 | ``` 335 | 将其中 *AllowOverride None* 改为 **AllowOverride all**,如果没有就增加一条。 336 | 337 | ### 增加.htaccess配置 338 | 如果部署在根目录,在/var/www/html/下新建.htaccess文件,内容为 339 | ``` 340 | RewriteEngine On 341 | # If an existing asset or directory is requested go to it as it is 342 | RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -f [OR] 343 | RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -d 344 | RewriteRule ^ - [L] 345 | 346 | # If the requested resource doesn't exist, use index.html 347 | RewriteRule ^ /index.html 348 | ``` 349 | 如果部署在子目录/var/www/html/subDir中,则在subDir目录中新增.htaccess文件,内容为 350 | ``` 351 | RewriteEngine On 352 | # If an existing asset or directory is requested go to it as it is 353 | RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -f [OR] 354 | RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -d 355 | RewriteRule ^ - [L] 356 | 357 | # If the requested resource doesn't exist, use index.html 358 | RewriteRule ^ /subDir/index.html 359 | ``` 360 | *修改.htaccess文件不需要重启apache* 361 | 362 | 363 | -------------------------------------------------------------------------------- /deploy.md: -------------------------------------------------------------------------------- 1 | 编译 2 | ```shell 3 | ng build -prod -aot --base-href "https://alexspring123.github.io/NG-ZORRO-TEMPLATE/" 4 | ``` 5 | 使用ngh插件发布到github的gh-pages分支 6 | ```shell 7 | ngh 8 | ``` 9 | 10 | demo访问地址:https://alexspring123.github.io/NG-ZORRO-TEMPLATE/ 11 | 12 | 参考文章: 13 | https://alexspring123.github.io/2017/09/25/201709/%E9%83%A8%E7%BD%B2Angular%E5%BA%94%E7%94%A8%E5%88%B0Github%20pages/#more -------------------------------------------------------------------------------- /doc/screen-snapshots/change-pwd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexspring123/NG-ZORRO-TEMPLATE/7371c6bab332e95793e56f4bd8ebb28a427d95f4/doc/screen-snapshots/change-pwd.png -------------------------------------------------------------------------------- /doc/screen-snapshots/left-menu-snapshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexspring123/NG-ZORRO-TEMPLATE/7371c6bab332e95793e56f4bd8ebb28a427d95f4/doc/screen-snapshots/left-menu-snapshot.png -------------------------------------------------------------------------------- /doc/screen-snapshots/left-menu-snapshot2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexspring123/NG-ZORRO-TEMPLATE/7371c6bab332e95793e56f4bd8ebb28a427d95f4/doc/screen-snapshots/left-menu-snapshot2.png -------------------------------------------------------------------------------- /doc/screen-snapshots/login-snapshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexspring123/NG-ZORRO-TEMPLATE/7371c6bab332e95793e56f4bd8ebb28a427d95f4/doc/screen-snapshots/login-snapshot.png -------------------------------------------------------------------------------- /doc/screen-snapshots/top-memu-snapshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexspring123/NG-ZORRO-TEMPLATE/7371c6bab332e95793e56f4bd8ebb28a427d95f4/doc/screen-snapshots/top-memu-snapshot.png -------------------------------------------------------------------------------- /e2e/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { NgZorroDemoPage } from './app.po'; 2 | 3 | describe('ng-zorro-demo App', () => { 4 | let page: NgZorroDemoPage; 5 | 6 | beforeEach(() => { 7 | page = new NgZorroDemoPage(); 8 | }); 9 | 10 | it('should display welcome message', () => { 11 | page.navigateTo(); 12 | expect(page.getParagraphText()).toEqual('Welcome to app!!'); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /e2e/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class NgZorroDemoPage { 4 | navigateTo() { 5 | return browser.get('/'); 6 | } 7 | 8 | getParagraphText() { 9 | return element(by.css('app-root h1')).getText(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /e2e/tsconfig.e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/e2e", 5 | "module": "commonjs", 6 | "target": "es5", 7 | "types": [ 8 | "jasmine", 9 | "node" 10 | ] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/0.13/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular/cli'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular/cli/plugins/karma') 14 | ], 15 | client:{ 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | reports: [ 'html', 'lcovonly' ], 20 | fixWebpackSourcePaths: true 21 | }, 22 | angularCli: { 23 | environment: 'dev' 24 | }, 25 | reporters: ['progress', 'kjhtml'], 26 | port: 9876, 27 | colors: true, 28 | logLevel: config.LOG_INFO, 29 | autoWatch: true, 30 | browsers: ['Chrome'], 31 | singleRun: false 32 | }); 33 | }; 34 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ng-zorro-demo", 3 | "version": "0.0.0", 4 | "license": "MIT", 5 | "scripts": { 6 | "ng": "ng", 7 | "start": "ng serve", 8 | "build": "ng build", 9 | "test": "ng test", 10 | "lint": "ng lint", 11 | "e2e": "ng e2e" 12 | }, 13 | "private": true, 14 | "dependencies": { 15 | "@angular/animations": "^5.0.0", 16 | "@angular/common": "^5.0.0", 17 | "@angular/compiler": "^5.0.0", 18 | "@angular/core": "^5.0.0", 19 | "@angular/forms": "^5.0.0", 20 | "@angular/http": "^5.0.0", 21 | "@angular/platform-browser": "^5.0.0", 22 | "@angular/platform-browser-dynamic": "^5.0.0", 23 | "@angular/router": "^5.0.0", 24 | "core-js": "^2.4.1", 25 | "ng-zorro-antd": "^0.6.0", 26 | "rxjs": "^5.5.2", 27 | "zone.js": "^0.8.17" 28 | }, 29 | "devDependencies": { 30 | "@angular/cli": "^1.5.0", 31 | "@angular/compiler-cli": "^5.0.0", 32 | "@angular/language-service": "^5.0.0", 33 | "@types/jasmine": "2.5.53", 34 | "@types/node": "~6.0.60", 35 | "codelyzer": "~4.0.1", 36 | "jasmine-core": "~2.6.2", 37 | "jasmine-spec-reporter": "~4.1.0", 38 | "karma": "~1.7.0", 39 | "karma-chrome-launcher": "~2.1.1", 40 | "karma-cli": "~1.0.1", 41 | "karma-coverage-istanbul-reporter": "^1.2.1", 42 | "karma-jasmine": "~1.1.0", 43 | "karma-jasmine-html-reporter": "^0.2.2", 44 | "protractor": "~5.1.2", 45 | "ts-node": "~3.2.0", 46 | "tslint": "~5.7.0", 47 | "typescript": "~2.4.2" 48 | } 49 | } -------------------------------------------------------------------------------- /protractor.conf.js: -------------------------------------------------------------------------------- 1 | // Protractor configuration file, see link for more information 2 | // https://github.com/angular/protractor/blob/master/lib/config.ts 3 | 4 | const { SpecReporter } = require('jasmine-spec-reporter'); 5 | 6 | exports.config = { 7 | allScriptsTimeout: 11000, 8 | specs: [ 9 | './e2e/**/*.e2e-spec.ts' 10 | ], 11 | capabilities: { 12 | 'browserName': 'chrome' 13 | }, 14 | directConnect: true, 15 | baseUrl: 'http://localhost:4200/', 16 | framework: 'jasmine', 17 | jasmineNodeOpts: { 18 | showColors: true, 19 | defaultTimeoutInterval: 30000, 20 | print: function() {} 21 | }, 22 | onPrepare() { 23 | require('ts-node').register({ 24 | project: 'e2e/tsconfig.e2e.json' 25 | }); 26 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { Routes, RouterModule } from '@angular/router'; 2 | import { NgModule } from '@angular/core'; 3 | 4 | const appRoutes: Routes = [ 5 | { path: '', redirectTo: '/login', pathMatch: 'full' }, 6 | { path: '', loadChildren: './base/login/login.module#LoginModule' }, 7 | { path: '', loadChildren: './base/frame/frame.module#FrameModule' }, 8 | ]; 9 | 10 | @NgModule({ 11 | imports: [RouterModule.forRoot(appRoutes)], 12 | exports: [RouterModule] 13 | }) 14 | export class AppRoutingModule { } 15 | -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { Router, ActivatedRoute, NavigationEnd, Route } from '@angular/router'; 3 | import { Title } from '@angular/platform-browser'; 4 | import { app_title } from 'config/global-config'; 5 | import 'rxjs/add/operator/filter'; 6 | import 'rxjs/add/operator/map'; 7 | import 'rxjs/add/operator/mergeMap'; 8 | 9 | @Component({ 10 | selector: 'app-root', 11 | template: '' 12 | }) 13 | export class AppComponent implements OnInit { 14 | 15 | constructor(private router: Router, 16 | private activatedRoute: ActivatedRoute, 17 | private titleService: Title) { 18 | } 19 | 20 | ngOnInit() { 21 | this.addTitle(); 22 | } 23 | 24 | // 动态修改窗口标题(从路由的data['title']读取,如果路由没有配置data或title属性,默认取app_title) 25 | // 参考文章https://toddmotto.com/dynamic-page-titles-angular-2-router-events 26 | private addTitle(): void { 27 | this.router.events 28 | .filter(event => event instanceof NavigationEnd) 29 | .map(() => this.activatedRoute) 30 | .map(route => { 31 | while (route.firstChild) { 32 | route = route.firstChild; 33 | } 34 | return route; 35 | }) 36 | .filter(route => route.outlet === 'primary') 37 | .mergeMap(route => route.data) 38 | .subscribe((event) => { 39 | if (event && event['title']) { 40 | return this.titleService.setTitle(app_title + '-' + event['title']); 41 | } else { 42 | return this.titleService.setTitle(app_title); 43 | } 44 | }); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { BrowserModule } from '@angular/platform-browser'; 2 | import { NgModule } from '@angular/core'; 3 | import { RouterModule } from '@angular/router'; 4 | import { HttpModule } from '@angular/http'; 5 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; 6 | 7 | import { NgZorroAntdModule } from 'ng-zorro-antd'; 8 | import { AppComponent } from './app.component'; 9 | import { PermissionGurid } from 'app/permission.gurid'; 10 | import { AppRoutingModule } from 'app/app-routing.module'; 11 | import { SessionService } from 'app/base/shared/session.service'; 12 | import { GlobalProviders, GlobalImportModule } from 'app/config/contents-modules'; 13 | 14 | @NgModule({ 15 | imports: [ 16 | BrowserModule, 17 | RouterModule, 18 | HttpModule, 19 | BrowserAnimationsModule, 20 | ...GlobalImportModule, 21 | NgZorroAntdModule.forRoot(), 22 | AppRoutingModule, 23 | ], 24 | declarations: [ 25 | AppComponent 26 | ], 27 | providers: [ 28 | SessionService, 29 | PermissionGurid, 30 | ...GlobalProviders 31 | ], 32 | bootstrap: [AppComponent] 33 | }) 34 | export class AppModule { } 35 | -------------------------------------------------------------------------------- /src/app/base/change-password/change-password-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { RouterModule, Routes } from '@angular/router'; 2 | import { ChangePasswordComponent } from 'app/base/change-password/change-password.component'; 3 | import { NgModule } from '@angular/core'; 4 | 5 | const routes: Routes = [ 6 | { path: 'changePwd', component: ChangePasswordComponent, data: { title: '修改密码' } } 7 | ]; 8 | 9 | @NgModule({ 10 | imports: [RouterModule.forChild(routes)], 11 | exports: [RouterModule] 12 | }) 13 | export class ChangePasswordRoutingModule { } 14 | -------------------------------------------------------------------------------- /src/app/base/change-password/change-password.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 |
5 | 6 |
7 |
8 | 9 |
原始密码必须填写
10 |
11 |
12 |
13 |
14 | 15 |
16 |
17 | 18 |
请输入新密码
19 |
20 |
21 |
22 |
23 | 24 |
25 |
26 | 27 |
请再次输入新密码
28 |
两次输入密码不同,请重新输入
29 |
30 |
31 | 32 |
33 |
34 | 35 | 36 |
37 |
38 |
39 |
-------------------------------------------------------------------------------- /src/app/base/change-password/change-password.component.ts: -------------------------------------------------------------------------------- 1 | import { Location } from '@angular/common'; 2 | import { Component, OnInit } from '@angular/core'; 3 | import { FormGroup, FormBuilder, Validators, FormControl } from '@angular/forms'; 4 | import { Router } from '@angular/router'; 5 | import { SessionService } from 'app/base/shared/session.service'; 6 | import { LoginServiceImpl } from 'app/providers/login.service-impl'; 7 | import { Session } from 'app/base/shared/model/session'; 8 | import { HttpResult } from 'app/base/shared/model/http-result'; 9 | 10 | @Component({ 11 | selector: 'app-change-password', 12 | templateUrl: './change-password.component.html', 13 | styleUrls: [] 14 | }) 15 | export class ChangePasswordComponent implements OnInit { 16 | validateForm: FormGroup; 17 | 18 | constructor(private fb: FormBuilder, 19 | private router: Router, 20 | private sessionService: SessionService, 21 | private loginService: LoginServiceImpl, 22 | private location: Location) { 23 | 24 | } 25 | 26 | ngOnInit(): void { 27 | this.validateForm = this.fb.group({ 28 | oldPassword: [null, [Validators.required]], 29 | password: [null, [Validators.required]], 30 | checkPassword: [null, [Validators.required, this.confirmationValidator]], 31 | }); 32 | } 33 | 34 | updateConfirmValidator() { 35 | setTimeout(_ => { 36 | this.validateForm.controls['checkPassword'].updateValueAndValidity(); 37 | }); 38 | } 39 | 40 | confirmationValidator = (control: FormControl): { [s: string]: boolean } => { 41 | if (!control.value) { 42 | return { required: true }; 43 | } else if (control.value !== this.validateForm.controls['password'].value) { 44 | return { confirm: true, error: true }; 45 | } 46 | } 47 | 48 | getFormControl(name) { 49 | return this.validateForm.controls[name]; 50 | } 51 | 52 | cancel() { 53 | this.location.back(); 54 | } 55 | 56 | _submitForm() { 57 | if (this.validateForm.invalid) { 58 | return; 59 | } 60 | 61 | const session: Session = this.sessionService.getSession(); 62 | 63 | this.loginService.changePassword( 64 | { 65 | token: session.token, 66 | oldPassword: this.validateForm.controls['oldPassword'].value, 67 | newPassword: this.validateForm.controls['password'].value 68 | }).subscribe( 69 | result => { 70 | window.alert('密码修改成功,请重新登录'); 71 | this.router.navigateByUrl('/login'); 72 | }, 73 | error => window.alert('修改密码失败:' + error)); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/app/base/change-password/change-password.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { NgZorroAntdModule } from 'ng-zorro-antd'; 3 | import { CommonModule } from '@angular/common'; 4 | import { RouterModule } from '@angular/router'; 5 | import { LoginServiceImpl } from 'app/providers/login.service-impl'; 6 | import { ChangePasswordComponent } from 'app/base/change-password/change-password.component'; 7 | import { FormsModule, ReactiveFormsModule } from '@angular/forms'; 8 | import { ChangePasswordRoutingModule } from 'app/base/change-password/change-password-routing.module'; 9 | 10 | @NgModule({ 11 | imports: [ 12 | CommonModule, 13 | RouterModule, 14 | FormsModule, 15 | ReactiveFormsModule, 16 | NgZorroAntdModule, 17 | ChangePasswordRoutingModule 18 | ], 19 | declarations: [ 20 | ChangePasswordComponent, 21 | ], 22 | 23 | exports: [ChangePasswordComponent], 24 | providers: [LoginServiceImpl] 25 | }) 26 | export class ChangePasswordModule { } 27 | -------------------------------------------------------------------------------- /src/app/base/frame/frame-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { RouterModule, Routes } from '@angular/router'; 2 | import { FrameComponent } from 'app/base/frame/frame.component'; 3 | import { NgModule } from '@angular/core'; 4 | import { ContentsRoutes } from 'app/config/contents-modules'; 5 | import { PermissionGurid } from 'app/permission.gurid'; 6 | 7 | const routes: Routes = [ 8 | { 9 | path: 'frame', 10 | component: FrameComponent, 11 | canActivateChild: [PermissionGurid], 12 | children: [ 13 | { path: '', loadChildren: '../change-password/change-password.module#ChangePasswordModule' }, 14 | ...ContentsRoutes 15 | ] 16 | } 17 | ]; 18 | 19 | @NgModule({ 20 | imports: [RouterModule.forChild(routes)], 21 | exports: [RouterModule] 22 | }) 23 | export class FrameRoutingModule { } 24 | -------------------------------------------------------------------------------- /src/app/base/frame/frame.component.css: -------------------------------------------------------------------------------- 1 | :host ::ng-deep .ant-layout-sider-collapsed .nav-text { 2 | display: none; 3 | } 4 | 5 | :host ::ng-deep .ant-layout-sider-collapsed .ant-menu-submenu-title:after { 6 | display: none; 7 | } 8 | 9 | :host ::ng-deep .ant-layout-sider-collapsed .anticon { 10 | font-size: 16px; 11 | margin-left: 8px; 12 | } 13 | 14 | :host ::ng-deep .ant-layout-sider-trigger{ 15 | position:inherit; 16 | } -------------------------------------------------------------------------------- /src/app/base/frame/frame.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | {{copyright}} 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/app/base/frame/frame.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { menuConfig, loginConfig, copyright } from 'config/global-config'; 3 | 4 | @Component({ 5 | selector: 'app-frame', 6 | templateUrl: './frame.component.html', 7 | styleUrls: ['./frame.component.css'] 8 | }) 9 | export class FrameComponent implements OnInit { 10 | menuPlacement = 'top'; 11 | copyright: string; 12 | isCollapsed = false; 13 | 14 | constructor() { 15 | 16 | } 17 | 18 | ngOnInit(): void { 19 | const placement = menuConfig.placement; 20 | if (placement) { 21 | if (placement.trim().toLowerCase() === 'left') { 22 | this.menuPlacement = 'left'; 23 | } else if (placement.trim().toLowerCase() === 'top') { 24 | this.menuPlacement = 'top'; 25 | } 26 | } 27 | this.copyright = copyright; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/app/base/frame/frame.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { RouterModule } from '@angular/router'; 4 | import { NgZorroAntdModule } from 'ng-zorro-antd'; 5 | import { FrameComponent } from 'app/base/frame/frame.component'; 6 | import { HeadUserComponent } from 'app/base/shared/heard/head-user.component'; 7 | import { HeadComponent } from 'app/base/shared/heard/head.component'; 8 | import { MenuComponent } from 'app/base/shared/menu/menu.component'; 9 | import { LoginServiceImpl } from 'app/providers/login.service-impl'; 10 | import { FrameRoutingModule } from 'app/base/frame/frame-routing.module'; 11 | 12 | @NgModule({ 13 | imports: [ 14 | CommonModule, 15 | RouterModule, 16 | NgZorroAntdModule, 17 | FrameRoutingModule, 18 | ], 19 | declarations: [ 20 | FrameComponent, 21 | HeadComponent, 22 | HeadUserComponent, 23 | MenuComponent, 24 | ], 25 | 26 | exports: [FrameComponent], 27 | providers: [LoginServiceImpl] 28 | }) 29 | export class FrameModule { } 30 | -------------------------------------------------------------------------------- /src/app/base/login/login-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { RouterModule, Routes } from '@angular/router'; 2 | import { LoginComponent } from './login.component'; 3 | import { NgModule } from '@angular/core'; 4 | 5 | const routes: Routes = [ 6 | { path: 'login', component: LoginComponent, data: { title: '登录' } }, 7 | ]; 8 | 9 | @NgModule({ 10 | imports: [RouterModule.forChild(routes)], 11 | exports: [RouterModule] 12 | }) 13 | export class LoginRoutingModule { } 14 | -------------------------------------------------------------------------------- /src/app/base/login/login.component.css: -------------------------------------------------------------------------------- 1 | .login{ 2 | height: 100%; 3 | background-size: cover; 4 | display: flex; 5 | justify-content: center; 6 | align-items: center; 7 | } 8 | 9 | login-title{ 10 | flex: top; 11 | } 12 | 13 | .login-form { 14 | padding: 20px 50px; 15 | border-radius: 10px; 16 | background-color: rgba(255,255,255,0.5); 17 | } 18 | 19 | .login-form-title{ 20 | font-size: 3em; 21 | text-align: center; 22 | color: #ffffff; 23 | } 24 | 25 | .login-form-forgot { 26 | float: right; 27 | } 28 | 29 | .login-form-button { 30 | width: 100%; 31 | } -------------------------------------------------------------------------------- /src/app/base/login/login.component.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/base/login/login.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { LoginService } from './login.service'; 3 | import { Router } from '@angular/router'; 4 | import { FormGroup, FormBuilder, Validators } from '@angular/forms'; 5 | import { LoginServiceImpl } from 'app/providers/login.service-impl'; 6 | import { loginConfig } from 'config/global-config'; 7 | import { SessionService } from 'app/base/shared/session.service'; 8 | import { Session } from 'app/base/shared/model/session'; 9 | import { HttpResult } from 'app/base/shared/model/http-result'; 10 | import { FormUtil } from 'app/base/shared/form-util'; 11 | 12 | @Component({ 13 | moduleId: module.id, 14 | selector: 'app-login', 15 | styleUrls: ['login.component.css'], 16 | templateUrl: 'login.component.html' 17 | }) 18 | export class LoginComponent implements OnInit { 19 | formErrors = { 20 | 'userName': '', 21 | 'password': '', 22 | }; 23 | 24 | validationMessages = { 25 | 'userName': { 'required': '代码不能为空' }, 26 | 'password': { 'required': '名称不能为空' } 27 | }; 28 | 29 | backgroundImage: string = loginConfig.background_image; 30 | formTitle: string = loginConfig.form_title; 31 | 32 | validateForm: FormGroup; 33 | userName: string; 34 | password: string; 35 | 36 | _logining = false; 37 | 38 | constructor(private fb: FormBuilder, 39 | private router: Router, 40 | private sessionService: SessionService, 41 | private loginService: LoginServiceImpl) { 42 | } 43 | 44 | ngOnInit() { 45 | this.validateForm = this.fb.group({ 46 | userName: [this.userName, [Validators.required]], 47 | password: [this.password, [Validators.required]], 48 | }); 49 | 50 | this.validateForm.valueChanges.subscribe((value) => this.checkForm()); 51 | } 52 | 53 | private checkForm(): void { 54 | FormUtil.valid(this.validateForm, this.formErrors, this.validationMessages); 55 | } 56 | 57 | submitForm() { 58 | if (this.validateForm.invalid) { 59 | FormUtil.markAsDirtyDeep(this.validateForm); 60 | this.checkForm(); 61 | return; 62 | } 63 | const loginData = { 64 | login: this.validateForm.get('userName').value, 65 | password: this.validateForm.get('password').value 66 | }; 67 | this._logining = true; 68 | this.loginService.login(loginData).subscribe( 69 | result => { 70 | this._logining = false; 71 | // 加入本地缓存 72 | this.sessionService.putSession(result); 73 | // 跳转页面 74 | this.router.navigateByUrl('/frame'); 75 | }, 76 | error => { 77 | this._logining = false; 78 | window.alert('登录失败:' + error); 79 | }); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/app/base/login/login.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { LoginComponent } from './login.component'; 3 | import { LoginService } from './login.service'; 4 | import { FormsModule, ReactiveFormsModule } from '@angular/forms'; 5 | import { NgZorroAntdModule } from 'ng-zorro-antd'; 6 | import { CommonModule } from '@angular/common'; 7 | import { LoginServiceImpl } from 'app/providers/login.service-impl'; 8 | import { SessionService } from 'app/base/shared/session.service'; 9 | import { LoginRoutingModule } from 'app/base/login/login-routing.module'; 10 | 11 | @NgModule({ 12 | imports: [ 13 | CommonModule, 14 | FormsModule, 15 | ReactiveFormsModule, 16 | NgZorroAntdModule, 17 | LoginRoutingModule 18 | ], 19 | declarations: [LoginComponent], 20 | exports: [LoginComponent], 21 | providers: [LoginServiceImpl] 22 | }) 23 | export class LoginModule { } 24 | -------------------------------------------------------------------------------- /src/app/base/login/login.service.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from 'rxjs/Observable'; 2 | import { Session } from '../shared/model/session'; 3 | import { HttpResult } from '../shared/model/http-result'; 4 | 5 | /**预定义服务 */ 6 | export interface LoginService { 7 | /** 8 | * 登录 9 | */ 10 | login(loginData: { login: string, password: string }): Observable; 11 | 12 | /** 13 | * 注销 14 | * userCode:当前登录用户代码 15 | * token:当前登录的token 16 | * userCode和token必须有一个不为Null。 17 | * 登录后服务端会生成一个token并返回给界面,此时只需要传入token一个参数服务端即可完成注销 18 | */ 19 | logout(token: string): Observable; 20 | 21 | /** 22 | * 修改密码 23 | * 处于安全,session不会保存用户的密码,因此修改密码时需要传入原始密码,服务端需要对原始密码和token进行验证 24 | * 服务端成功后不用返回data 25 | */ 26 | changePassword(data: { token: string, oldPassword: string, newPassword: string }): Observable; 27 | } 28 | -------------------------------------------------------------------------------- /src/app/base/shared/form-util.ts: -------------------------------------------------------------------------------- 1 | import { AbstractControl, FormGroup, FormArray } from '@angular/forms'; 2 | 3 | /** 4 | * 表单工具类 5 | */ 6 | export class FormUtil { 7 | /** 8 | * 设置指定控制器以及子控制器为dirty 9 | */ 10 | public static markAsDirtyDeep(control: AbstractControl): void { 11 | if (!control) { 12 | return; 13 | } 14 | 15 | control.markAsDirty(); 16 | 17 | if (control instanceof FormGroup) { 18 | const ctl = control; 19 | for (const inner in ctl.controls) { 20 | if (inner) { 21 | this.markAsDirtyDeep(ctl.get(inner)); 22 | } 23 | } 24 | } else if (control instanceof FormArray) { 25 | const ctl = control; 26 | for (const inner in ctl.controls) { 27 | if (inner) { 28 | this.markAsDirtyDeep(ctl.get(inner)); 29 | } 30 | } 31 | } 32 | } 33 | 34 | /** 35 | * 验证表单 36 | * form:表单 37 | * formErrors:表单验证结果(结构必须与form中的控制器结构一致) 38 | * validationMessages:表单检查项以及错误消息定义(数组特殊处理) 39 | */ 40 | public static valid(form: FormGroup, formErrors: {}, validationMessages: {}): void { 41 | if (!form) { 42 | return; 43 | } 44 | for (const field in formErrors) { 45 | if (field) { 46 | this.megerError(field, formErrors, form.get(field), validationMessages[field]); 47 | } 48 | } 49 | } 50 | 51 | /** 52 | * field:错误信息ID 53 | * errors:field对应的错误内容上一层对象 54 | * control:表单控件(可能是FormGroup、FormArray、FormControl) 55 | * messages:错误信息定义 56 | */ 57 | private static megerError(field: any, errors: any, control: AbstractControl, messages: any): void { 58 | if (!field || !control) { 59 | return; 60 | } 61 | 62 | // errors中对应field的内容是字符串,表示已经是叶子节点 63 | if (typeof errors[field] === 'string') { 64 | errors[field] = ''; 65 | if (control && !control.valid && (control.dirty || control.touched)) { 66 | for (const key in control.errors) { 67 | if (key) { 68 | errors[field] += messages[key] + ' '; 69 | } 70 | } 71 | } 72 | } else if (errors[field] instanceof Array) {// errors中对应field的内容是数组,需要循环处理每一个原始,数组时messages不是数组,因此原样返回 73 | const arr = >errors[field]; 74 | arr.forEach((f, index) => { 75 | // TODO 此处只考虑数组元素是对象,还需要增加string类型的判断 76 | for (const ff in f) { 77 | if (ff) { 78 | this.megerError(ff, errors[field][index], control.get(index.toString()).get(ff), messages[ff]); 79 | } 80 | } 81 | }); 82 | } else {// errors中对应field的内容是对象,需要循环每一个字段 83 | for (const f in errors[field]) { 84 | if (f) { 85 | this.megerError(f, errors[field], control.get(f), messages[f]); 86 | } 87 | } 88 | } 89 | } 90 | } 91 | 92 | -------------------------------------------------------------------------------- /src/app/base/shared/heard/head-user.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input } from '@angular/core'; 2 | import { NzModalService } from 'ng-zorro-antd'; 3 | import { SessionService } from 'app/base/shared/session.service'; 4 | import { Session } from 'app/base/shared/model/session'; 5 | import { HttpResult } from 'app/base/shared/model/http-result'; 6 | import { LoginServiceImpl } from 'app/providers/login.service-impl'; 7 | import { Router } from '@angular/router'; 8 | 9 | @Component({ 10 | selector: 'app-head-user', 11 | template: ` 12 | 13 | 欢迎您! 14 | {{session.userName}} 15 | 23 | 24 | `, 25 | styles: [` 26 | .welcome{ 27 | margin-right:5px; 28 | color:rgba(255,255,255,0.67) 29 | } 30 | `] 31 | }) 32 | export class HeadUserComponent implements OnInit { 33 | session: Session; 34 | 35 | constructor( 36 | private router: Router, 37 | private confirmServ: NzModalService, 38 | private sessionService: SessionService, 39 | private loginService: LoginServiceImpl) { 40 | 41 | } 42 | 43 | ngOnInit() { 44 | this.session = this.sessionService.getSession(); 45 | } 46 | 47 | logout(): void { 48 | this.confirmServ.confirm({ 49 | title: '您是否确认要注销?', 50 | onOk: () => this.doLogout(), 51 | onCancel() { } 52 | }); 53 | } 54 | 55 | changePassword(): void { 56 | this.router.navigateByUrl('/frame/changePwd'); 57 | } 58 | 59 | private doLogout(): void { 60 | this.loginService.logout(this.session.token).subscribe( 61 | result => { 62 | this.sessionService.removeSession(); 63 | this.router.navigateByUrl('/login'); 64 | }, 65 | error => window.alert('注销失败:' + error)); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/app/base/shared/heard/head.component.css: -------------------------------------------------------------------------------- 1 | :host ::ng-deep .logo { 2 | width: 120px; 3 | height: 31px; 4 | border-radius: 6px; 5 | margin: 16px 24px 16px 0; 6 | float: left; 7 | } 8 | 9 | :host ::ng-deep .head-title{ 10 | line-height: 64px; 11 | font-size: 2.5em; 12 | color:#dddddd; 13 | } 14 | 15 | :host ::ng-deep .ant-menu.ant-menu-vertical.ant-menu-sub { 16 | margin-top: 0; 17 | } 18 | 19 | -------------------------------------------------------------------------------- /src/app/base/shared/heard/head.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | {{title}} 6 | 7 |
8 | 9 | 10 |
    11 |
    12 |
  • 13 | {{menu.title}} 14 |
  • 15 | 16 |
  • 17 | {{menu.title}} 18 |
      19 |
    • 20 | {{subMenu.title}}
    • 21 |
    22 |
  • 23 |
    24 | 25 |
26 | 27 | 28 |
-------------------------------------------------------------------------------- /src/app/base/shared/heard/head.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input } from '@angular/core'; 2 | import { loginConfig } from 'config/global-config'; 3 | import { Router } from '@angular/router'; 4 | import { Session } from 'app/base/shared/model/session'; 5 | import { SessionService } from 'app/base/shared/session.service'; 6 | 7 | 8 | @Component({ 9 | selector: 'app-head', 10 | templateUrl: './head.component.html', 11 | styleUrls: ['./head.component.css'] 12 | }) 13 | export class HeadComponent implements OnInit { 14 | 15 | @Input() // 是否显示菜单 16 | showMenu = false; 17 | 18 | @Input() // 当showMenu=false时,显示的标题 19 | title: string; 20 | 21 | session: Session; 22 | 23 | constructor( 24 | private router: Router, 25 | private sessionService: SessionService) { 26 | this.title = loginConfig.form_title; 27 | this.session = sessionService.getSession(); 28 | } 29 | 30 | ngOnInit() { 31 | } 32 | 33 | public goPage(path: string): void { 34 | this.router.navigateByUrl('/frame' + path); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/app/base/shared/menu/menu.component.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
  • 4 | 5 | {{menu.title}} 6 |
  • 7 | 8 |
  • 9 | 10 | 11 | {{menu.title}} 12 | 13 |
      14 |
    • 15 | {{subMenu.title}} 16 |
    • 17 |
    18 |
  • 19 |
    20 |
-------------------------------------------------------------------------------- /src/app/base/shared/menu/menu.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input } from '@angular/core'; 2 | import { menuConfig } from 'config/global-config'; 3 | import { Router } from '@angular/router'; 4 | import { Session } from 'app/base/shared/model/session'; 5 | import { SessionService } from 'app/base/shared/session.service'; 6 | 7 | @Component({ 8 | selector: 'app-menu', 9 | templateUrl: './menu.component.html', 10 | styleUrls: [] 11 | }) 12 | export class MenuComponent implements OnInit { 13 | @Input() 14 | isCollapsed: boolean; 15 | session: Session; 16 | 17 | constructor(private router: Router, private sessionService: SessionService) { 18 | 19 | } 20 | 21 | ngOnInit() { 22 | this.session = this.sessionService.getSession(); 23 | } 24 | 25 | public goPage(path: string): void { 26 | this.router.navigateByUrl('/frame' + path); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/app/base/shared/model/http-result.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Htt调用返回结果对象 3 | * code:http请求返回码,0表示成功,非0表示失败 4 | * message:当code!=0时,message表示失败说明 5 | * data:当code==0时,data是返回的数据,当code!=0时,data为null 6 | */ 7 | export class HttpResult { 8 | constructor(public code: number, public message: string, public data: T) { 9 | this.code = code; 10 | this.message = message; 11 | this.data = data; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/app/base/shared/model/page-filter.ts: -------------------------------------------------------------------------------- 1 | export class PageFilter { 2 | pageNumber = 0; 3 | pageSize = 10; 4 | } 5 | -------------------------------------------------------------------------------- /src/app/base/shared/model/page.ts: -------------------------------------------------------------------------------- 1 | export class Page { 2 | totalPages = 0; 3 | totalElements = 0; 4 | pageNumber = 0; 5 | pageSize = 10; 6 | hasContent = false; 7 | hasNext = false; 8 | content: Array = []; 9 | } 10 | -------------------------------------------------------------------------------- /src/app/base/shared/model/session.ts: -------------------------------------------------------------------------------- 1 | // 菜单 2 | export interface Menu { 3 | title: string; // 菜单标题 4 | path: string; // 路由路径 5 | icon?: string; // 图片 6 | subMenus?: Array; // 子菜单 7 | } 8 | 9 | // 权限 10 | export interface Permission { 11 | code: string; // 权限代码 12 | name: string; // 权限名称 13 | } 14 | 15 | /**登录后返回的Session */ 16 | export interface Session { 17 | userCode: string; 18 | userName: string; 19 | token: string; 20 | menus: Array; 21 | permissions: Array; 22 | } 23 | -------------------------------------------------------------------------------- /src/app/base/shared/session.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { sessionConfig } from 'config/global-config'; 3 | import { Session } from 'app/base/shared/model/session'; 4 | 5 | @Injectable() 6 | export class SessionService { 7 | private session: Session; 8 | 9 | isLogin() { 10 | return this.getSession() != null; 11 | } 12 | 13 | getUserCode(): string { 14 | if (this.getSession()) { 15 | return this.session.userCode; 16 | } else { 17 | return null; 18 | } 19 | } 20 | 21 | putSession(session: Session) { 22 | this.session = session; 23 | if (this.session) { 24 | window.sessionStorage.setItem(sessionConfig.store_key, JSON.stringify(session)); 25 | } else { 26 | window.sessionStorage.removeItem(sessionConfig.store_key); 27 | } 28 | } 29 | 30 | getSession(): Session { 31 | if (!this.session) { 32 | this.session = JSON.parse(window.sessionStorage.getItem(sessionConfig.store_key)); 33 | } 34 | return this.session; 35 | } 36 | 37 | removeSession() { 38 | this.session = null; 39 | window.sessionStorage.removeItem(sessionConfig.store_key); 40 | } 41 | 42 | hasPermission(permissionCode: string): boolean { 43 | if (!permissionCode) { 44 | return false; 45 | } 46 | 47 | const session = this.getSession(); 48 | return session ? session.permissions.some(p => p.code === permissionCode) : false; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/app/config/contents-modules.ts: -------------------------------------------------------------------------------- 1 | import { Route } from '@angular/router'; 2 | 3 | // 注入的全局模块 4 | export const GlobalImportModule: any[] = []; 5 | 6 | // 注入的全局服务 7 | export const GlobalProviders: any[] = []; 8 | 9 | // 客户自定的模块注册 10 | export const ContentsRoutes: Route[] = [ 11 | { path: '', loadChildren: 'app/contents/role/role.module#RoleModule' }, 12 | { path: '', loadChildren: 'app/contents/user/user.module#UserModule' }, 13 | ]; 14 | -------------------------------------------------------------------------------- /src/app/contents/role/role-edit/role-edit.component.css: -------------------------------------------------------------------------------- 1 | :host ::ng-deep .ant-advanced-form { 2 | padding: 24px; 3 | background: #fbfbfb; 4 | border: 1px solid #d9d9d9; 5 | border-radius: 6px; 6 | } -------------------------------------------------------------------------------- /src/app/contents/role/role-edit/role-edit.component.html: -------------------------------------------------------------------------------- 1 | 2 | 基本资料 3 | 角色 4 | {{_isNew ? '新建':'修改'}} 5 | 6 | 7 | 8 | 9 |
10 |
11 |
12 | 13 |
14 |
15 | 16 |
{{formErrors.name}}
17 |
18 |
19 |
20 |
21 | 22 |
23 |
24 | 25 |
{{formErrors.remark}}
26 |
27 |
28 | 29 |
30 |
31 | 32 |
33 |
34 | 35 |
36 |
37 |
38 |
39 |
-------------------------------------------------------------------------------- /src/app/contents/role/role-edit/role-edit.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { Router, ActivatedRoute, Params } from '@angular/router'; 3 | import { SessionService } from 'app/base/shared/session.service'; 4 | import { NzMessageService, NzModalService } from 'ng-zorro-antd'; 5 | import { Session } from 'app/base/shared/model/session'; 6 | import { FormGroup, FormBuilder, FormControl, Validators } from '@angular/forms'; 7 | import { FormUtil } from 'app/base/shared/form-util'; 8 | import { Role, RoleService } from 'app/contents/role/role.service'; 9 | 10 | @Component({ 11 | moduleId: module.id, 12 | selector: 'app-cmall-role-edit', 13 | templateUrl: 'role-edit.component.html', 14 | styleUrls: ['role-edit.component.css'] 15 | }) 16 | export class RoleEditComponent implements OnInit { 17 | formErrors = { 18 | 'name': '', 19 | 'projectCode': '', 20 | 'remark': '', 21 | }; 22 | 23 | validationMessages = { 24 | 'name': { 'required': '代码不能为空' }, 25 | 'projectCode': { 'required': '必须选择项目' }, 26 | 'remark': { 'maxlength': '说明长度必须小于等于256' } 27 | }; 28 | 29 | session: Session; 30 | validateForm: FormGroup = new FormGroup({}); 31 | role: Role = new Role(); 32 | _isNew = true; 33 | _loading = false; 34 | _saving = false; 35 | 36 | constructor(private router: Router, 37 | private route: ActivatedRoute, 38 | private fb: FormBuilder, 39 | private messageService: NzMessageService, 40 | private modalService: NzModalService, 41 | private roleService: RoleService, 42 | private sessionService: SessionService) { 43 | this.session = sessionService.getSession(); 44 | } 45 | 46 | ngOnInit(): void { 47 | this.route.params.forEach((params: Params) => { 48 | const name = params['name']; 49 | 50 | this.buildForm(); 51 | if (name) { 52 | this._isNew = false; 53 | this.loadRole(name); 54 | } 55 | }); 56 | } 57 | 58 | private buildForm(): void { 59 | this.validateForm = this.fb.group({ 60 | 'name': this.fb.control(this.role.name, [Validators.required]), 61 | 'remark': this.fb.control(this.role.remark, [Validators.maxLength(256)]), 62 | }); 63 | this.validateForm.valueChanges.subscribe(value => this.checkForm()); 64 | } 65 | 66 | private checkForm(): void { 67 | FormUtil.valid(this.validateForm, this.formErrors, this.validationMessages); 68 | } 69 | 70 | save(): void { 71 | if (this.validateForm.invalid) { 72 | FormUtil.markAsDirtyDeep(this.validateForm); 73 | this.checkForm(); 74 | return; 75 | } 76 | 77 | this.role = this.validateForm.value; 78 | 79 | if (this._isNew) { 80 | this.doSaveNew(this.role); 81 | } else { 82 | this.doSaveModify(this.role); 83 | } 84 | } 85 | 86 | private doSaveNew(role: Role): void { 87 | this._saving = true; 88 | this.roleService.saveRole(role).subscribe( 89 | result => { 90 | this._saving = false; 91 | if (result.code !== 0) { 92 | this.messageService.error('保存角色失败:' + result.message); 93 | return; 94 | } 95 | this.messageService.error('保存成功'); 96 | this.router.navigate(['/frame/basic/role/view', role.name]); 97 | }, 98 | error => { 99 | this._saving = false; 100 | this.messageService.error('执行失败:' + error); 101 | }); 102 | } 103 | 104 | private doSaveModify(role: Role): void { 105 | this._saving = true; 106 | this.roleService.saveModify(role).subscribe( 107 | result => { 108 | this._saving = false; 109 | if (result.code !== 0) { 110 | this.messageService.error('修改角色失败:' + result.message); 111 | return; 112 | } 113 | this.messageService.error('保存成功'); 114 | this.router.navigate(['/frame/basic/role/view', role.name]); 115 | }, 116 | error => { 117 | this._saving = false; 118 | this.messageService.error('执行失败:' + error); 119 | }); 120 | } 121 | 122 | private loadRole(name: string) { 123 | this._loading = true; 124 | this.roleService.getRole(name).subscribe( 125 | result => { 126 | this._loading = false; 127 | if (result.code !== 0) { 128 | this.messageService.error('加载角色失败:' + result.message); 129 | return; 130 | } 131 | this.role = result.data; 132 | if (this.role) { 133 | this.buildForm(); 134 | } else { 135 | this.modalService.warning({ 136 | title: '加载角色失败', 137 | content: '不存在角色' + name 138 | }); 139 | this.router.navigateByUrl('/frame/role'); 140 | } 141 | }, 142 | error => { 143 | this._loading = false; 144 | this.messageService.error('执行失败:' + error); 145 | }); 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/app/contents/role/role-list/role-list.component.css: -------------------------------------------------------------------------------- 1 | :host ::ng-deep .ant-advanced-search-form { 2 | padding: 24px; 3 | background: #fbfbfb; 4 | border: 1px solid #d9d9d9; 5 | border-radius: 6px; 6 | } 7 | 8 | :host ::ng-deep .search-result-list { 9 | margin-top: 16px; 10 | border: 1px dashed #e9e9e9; 11 | border-radius: 6px; 12 | min-height: 200px; 13 | } -------------------------------------------------------------------------------- /src/app/contents/role/role-list/role-list.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 基础管理 5 | 角色 6 | 列表 7 | 8 |
9 |
10 | 14 |
15 |
16 | 17 | 18 |
19 | 20 |
21 |
22 |
23 |
24 | 25 |
26 |
27 | 28 |
29 |
30 |
31 |
32 |
33 | 34 | 35 |
36 |
37 |
38 |
39 | 40 |
41 | 42 | 45 | 46 | 47 | 48 | 名称 49 | 50 | 51 | 备注 52 | 53 | 54 | 操作 55 | 56 | 57 | 58 | 59 | 60 | 61 | {{role.name}} 62 | 63 | {{role.remark}} 64 | 65 | 修改  66 | 67 | 删除 68 | 69 | 70 | 71 | 72 | 73 | 74 |
75 |
-------------------------------------------------------------------------------- /src/app/contents/role/role-list/role-list.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { Router } from '@angular/router'; 3 | 4 | import { Page } from 'app/base/shared/model/page'; 5 | import { NzModalService, NzMessageService } from 'ng-zorro-antd'; 6 | import { SessionService } from 'app/base/shared/session.service'; 7 | import { RoleFilter, Role, RoleService } from 'app/contents/role/role.service'; 8 | 9 | @Component({ 10 | moduleId: module.id, 11 | selector: 'app-cmall-role-list', 12 | templateUrl: 'role-list.component.html', 13 | styleUrls: ['role-list.component.css'] 14 | }) 15 | export class RoleListComponent implements OnInit { 16 | _canCreate = false; 17 | _canEdit = false; 18 | _canDelete = false; 19 | 20 | _pageIndex = 1; 21 | _pageSize = 10; 22 | filter: RoleFilter = new RoleFilter(); 23 | page: Page = new Page(); 24 | _loading = false; 25 | _deleting = false; 26 | 27 | constructor( 28 | private router: Router, 29 | private modalService: NzModalService, 30 | private messageService: NzMessageService, 31 | private roleService: RoleService, 32 | private sessionService: SessionService) { 33 | } 34 | 35 | ngOnInit(): void { 36 | this.refreshPermission(); 37 | this.search(true); 38 | } 39 | 40 | private refreshPermission(): void { 41 | this._canCreate = this.sessionService.hasPermission('/role/create'); 42 | this._canEdit = this.sessionService.hasPermission('/role/edit'); 43 | this._canDelete = this.sessionService.hasPermission('/role/delete'); 44 | } 45 | 46 | resetForm(): void { 47 | this.filter = new RoleFilter(); 48 | } 49 | 50 | search(reset: boolean = false): void { 51 | if (reset) { 52 | this._pageIndex = 1; 53 | } 54 | 55 | this.filter.pageNumber = this._pageIndex - 1; 56 | this.filter.pageSize = this._pageSize; 57 | 58 | this._loading = true; 59 | this.roleService.getList(this.filter).subscribe( 60 | result => { 61 | this._loading = false; 62 | if (result.code !== 0) { 63 | this.messageService.error('加载角色列表失败:' + result.message); 64 | return; 65 | } 66 | this.page = result.data; 67 | }, 68 | error => { 69 | this._loading = false; 70 | this.messageService.error('执行失败:' + error); 71 | }); 72 | } 73 | 74 | delete(role: Role): void { 75 | this._deleting = true; 76 | this.roleService.delete(role.name).subscribe( 77 | result => { 78 | this._deleting = false; 79 | if (result.code !== 0) { 80 | this.messageService.error('删除失败:' + result.message); 81 | return; 82 | } 83 | this.messageService.success('删除成功'); 84 | this._deleting = false; 85 | this.search(); 86 | }, 87 | (error) => { 88 | this._deleting = false; 89 | this.messageService.error('执行失败:' + error); 90 | }); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/app/contents/role/role-routes.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | 4 | import { RoleListComponent } from './role-list/role-list.component'; 5 | import { RoleEditComponent } from 'app/contents/role/role-edit/role-edit.component'; 6 | import { RoleViewComponent } from 'app/contents/role/role-view/role-view.component'; 7 | 8 | const routes: Routes = [ 9 | { 10 | path: 'basic/role', 11 | children: [ 12 | { path: '', component: RoleListComponent }, 13 | { path: 'create', component: RoleEditComponent }, 14 | { path: 'view/:name', component: RoleViewComponent }, 15 | { path: 'edit/:name', component: RoleEditComponent } 16 | ] 17 | } 18 | ]; 19 | 20 | @NgModule({ 21 | imports: [RouterModule.forChild(routes)], 22 | exports: [RouterModule] 23 | }) 24 | export class RoleRoutingModule { } 25 | -------------------------------------------------------------------------------- /src/app/contents/role/role-view/permission-edit.component.css: -------------------------------------------------------------------------------- 1 | :host ::ng-deep .permission-result-list{ 2 | border: 1px dashed #e9e9e9; 3 | border-radius: 6px; 4 | } 5 | 6 | :host ::ng-deep .customize-footer { 7 | border-top: 1px solid #e9e9e9; 8 | padding: 10px 18px 0 10px; 9 | text-align: center; 10 | border-radius: 0 0 0px 0px; 11 | margin: 15px -16px -5px -16px; 12 | } -------------------------------------------------------------------------------- /src/app/contents/role/role-view/permission-edit.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | 6 | 模块 7 | 8 | 9 | 权限 10 | 11 | 12 | 13 | 14 | 15 | {{group.menuName}} 16 | 17 |
18 |
19 | 22 |
23 |
24 | 25 | 26 | 27 |
28 |
29 | 30 | -------------------------------------------------------------------------------- /src/app/contents/role/role-view/permission-edit.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from '@angular/core'; 2 | import { PermissionGroup } from './permission-group'; 3 | import { NzModalSubject, NzMessageService } from 'ng-zorro-antd'; 4 | import { Role, RoleService } from 'app/contents/role/role.service'; 5 | 6 | @Component({ 7 | moduleId: module.id, 8 | selector: 'app-cmall-role-permission-edit', 9 | styleUrls: ['permission-edit.component.css'], 10 | templateUrl: 'permission-edit.component.html' 11 | }) 12 | export class PermissionEditComponent { 13 | @Input() 14 | role: Role; 15 | 16 | @Input() 17 | permissionGroups: Array = []; 18 | 19 | _saving = false; 20 | 21 | constructor(private subject: NzModalSubject, 22 | private messageService: NzMessageService, 23 | private roleService: RoleService) { 24 | } 25 | 26 | cancel(e): void { 27 | this.subject.destroy('cancel'); 28 | } 29 | 30 | save() { 31 | this._saving = true; 32 | 33 | const permissionCodes: Set = new Set(); 34 | this.permissionGroups.forEach(g => { 35 | g.permissions.forEach(p => { 36 | if (p.has) { 37 | permissionCodes.add(p.permission.code); 38 | } 39 | }); 40 | }); 41 | 42 | this.roleService.coverRolePermissions(this.role.name, permissionCodes).subscribe( 43 | result => { 44 | this._saving = false; 45 | if (result.code !== 0) { 46 | this.messageService.error('保存角色权限失败:' + result.message); 47 | return; 48 | } 49 | this.subject.next('OK'); 50 | }, 51 | error => { 52 | this._saving = false; 53 | this.messageService.error('执行失败:' + error); 54 | } 55 | ); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/app/contents/role/role-view/permission-group.ts: -------------------------------------------------------------------------------- 1 | import { Permission } from 'app/base/shared/model/session'; 2 | 3 | 4 | export class PermissionWithState { 5 | permission: Permission; 6 | has: boolean; 7 | 8 | constructor(permission: Permission, has: boolean) { 9 | this.permission = permission; 10 | this.has = has; 11 | } 12 | } 13 | 14 | export class PermissionGroup { 15 | menuCode: string; 16 | menuName: string; 17 | permissions: Array = []; 18 | 19 | constructor(menuCode: string, menuName: string, permissions: Array) { 20 | this.menuCode = menuCode; 21 | this.menuName = menuName; 22 | this.permissions = permissions; 23 | } 24 | 25 | public appendPermission(permission: PermissionWithState) { 26 | if (!this.permissions) { 27 | this.permissions = []; 28 | } 29 | this.permissions.push(permission); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/app/contents/role/role-view/role-view.component.css: -------------------------------------------------------------------------------- 1 | :host ::ng-deep .viewContent{ 2 | line-height: 3em; 3 | } 4 | 5 | :host ::ng-deep .search-result-list { 6 | border: 1px dashed #e9e9e9; 7 | border-radius: 6px; 8 | width: 60%; 9 | } 10 | 11 | :host ::ng-deep .permission-result-list{ 12 | border: 1px dashed #e9e9e9; 13 | border-radius: 6px; 14 | } 15 | 16 | :host ::ng-deep .permissionName{ 17 | color: rgba(0, 0, 0, 0.65); 18 | } -------------------------------------------------------------------------------- /src/app/contents/role/role-view/role-view.component.html: -------------------------------------------------------------------------------- 1 | 2 | 基本资料 3 | 角色 4 | 详情 5 | 6 | 7 | 8 | 9 | 10 | {{role.name}} 11 | 12 | 返回  13 | 修改  14 | 15 | 删除 16 | 17 | 18 | 19 |
20 |
21 |
名称:
22 |
{{role.name}}
23 |
24 |
25 |
所属项目:
26 |
{{role.projectName}}
27 |
28 |
29 |
说明:
30 |
{{role.remark}}
31 |
32 |
33 |
34 |
35 | 36 | 37 | 38 | 包含的用户 39 | 40 |
41 | 42 | 43 | 44 | 45 | 员工代码 46 | 47 | 48 | 员工姓名 49 | 50 | 51 | 52 | 53 | 54 | {{user.code}} 55 | {{user.name}} 56 | 57 | 58 | 59 |
60 |
61 |
62 | 63 | 64 | 拥有的权限 65 | 66 | 修改权限 67 | 68 | 69 |
70 | 71 | 72 | 73 | 74 | 模块 75 | 76 | 77 | 权限 78 | 79 | 80 | 81 | 82 | 83 | {{group.menuName}} 84 | 85 |
86 |
87 | 90 |
91 |
92 | 93 | 94 | 95 |
96 |
97 |
98 |
99 |
100 |
-------------------------------------------------------------------------------- /src/app/contents/role/role-view/role-view.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, ViewContainerRef } from '@angular/core'; 2 | import { Router, Params, ActivatedRoute } from '@angular/router'; 3 | import { NzMessageService, NzModalService, NzModalSubject } from 'ng-zorro-antd'; 4 | import { Permission } from 'app/base/shared/model/session'; 5 | import { PermissionGroup, PermissionWithState } from 'app/contents/role/role-view/permission-group'; 6 | import { PermissionEditComponent } from 'app/contents/role/role-view/permission-edit.component'; 7 | import { SessionService } from 'app/base/shared/session.service'; 8 | import { Role, RoleUser, RoleService, AllPermissions } from 'app/contents/role/role.service'; 9 | 10 | @Component({ 11 | moduleId: module.id, 12 | selector: 'app-cmall-role-view', 13 | templateUrl: 'role-view.component.html', 14 | styleUrls: ['role-view.component.css'] 15 | }) 16 | export class RoleViewComponent implements OnInit { 17 | role: Role = new Role(); 18 | _loading = false; 19 | _loading_users = false; 20 | _loading_permissions = false; 21 | _deleting = false; 22 | 23 | users: Array = []; 24 | permissionGroups: Array = []; 25 | sessionPermissions: Array = []; // 当前登录员工具有的权限 26 | 27 | constructor(private router: Router, 28 | private route: ActivatedRoute, 29 | private messageService: NzMessageService, 30 | private modalService: NzModalService, 31 | private roleService: RoleService, 32 | private sessionService: SessionService) { } 33 | 34 | ngOnInit(): void { 35 | this.sessionPermissions = this.sessionService.getSession().permissions; 36 | 37 | this.route.params.forEach((params: Params) => { 38 | const name = params['name']; 39 | this.loadRole(name); 40 | this.loadRolePermissions(name); 41 | this.loadUsers(name); 42 | }); 43 | } 44 | 45 | private loadRole(roleName: string) { 46 | this._loading = true; 47 | this.roleService.getRole(roleName).subscribe( 48 | result => { 49 | this._loading = false; 50 | if (result.code !== 0) { 51 | this.messageService.error('加载角色失败:' + result.message); 52 | return; 53 | } 54 | this.role = result.data; 55 | if (!this.role) { 56 | this.modalService.warning({ 57 | title: '加载角色失败', 58 | content: '不存在角色' + name 59 | }); 60 | this.router.navigateByUrl('/frame/role'); 61 | } 62 | }, 63 | error => { 64 | this._loading = false; 65 | this.messageService.error('执行失败:' + error); 66 | }); 67 | } 68 | 69 | private loadUsers(roleName: string) { 70 | this._loading_users = true; 71 | this.roleService.getRoleUsers(roleName).subscribe( 72 | result => { 73 | this._loading_users = false; 74 | if (result.code !== 0) { 75 | this.messageService.error('加载用户列表失败:' + result.message); 76 | return; 77 | } 78 | this.users = result.data; 79 | }, 80 | error => { 81 | this._loading_users = false; 82 | this.messageService.error('执行失败:' + error); 83 | }); 84 | } 85 | 86 | private loadRolePermissions(roleName: string) { 87 | this._loading_permissions = true; 88 | this.roleService.getRolePermissions(roleName).subscribe( 89 | result => { 90 | this._loading_permissions = false; 91 | if (result.code !== 0) { 92 | this.messageService.error('加载角色权限列表失败:' + result.message); 93 | return; 94 | } 95 | this.permissionGroups = []; 96 | const permissionsList: Array = result.data; 97 | this.sessionPermissions.forEach(p => { 98 | const webPer = AllPermissions.find(wp => wp.code === p.code); 99 | if (webPer) { 100 | let group: PermissionGroup = this.permissionGroups.find(g => g.menuCode == webPer.menuCode); 101 | if (!group) { 102 | group = new PermissionGroup(webPer.menuCode, webPer.menuName, []); 103 | this.permissionGroups.push(group); 104 | } 105 | const per: Permission = permissionsList.find(pp => pp.code == p.code); 106 | group.appendPermission(new PermissionWithState(p, per != null)); 107 | } 108 | } 109 | ); 110 | }, 111 | error => { 112 | this._loading_permissions = false; 113 | this.messageService.error('执行失败:' + error); 114 | }); 115 | } 116 | 117 | showPermissionEditModal() { 118 | // 复制一份,防止弹出框修改时详情页跟随联动 119 | const permGroups: Array = []; 120 | this.permissionGroups.forEach(g => { 121 | const group = new PermissionGroup(g.menuCode, g.menuName, []); 122 | if (g.permissions) { 123 | g.permissions.forEach(p => { 124 | group.appendPermission(new PermissionWithState(p.permission, p.has)); 125 | }); 126 | } 127 | permGroups.push(group); 128 | }); 129 | 130 | const subscription = this.modalService.open({ 131 | title: '修改权限', 132 | content: PermissionEditComponent, 133 | footer: false, 134 | componentParams: { 135 | role: this.role, 136 | permissionGroups: permGroups 137 | } 138 | }); 139 | 140 | subscription.subscribe(result => { 141 | if (result === 'cancel') { 142 | subscription.destroy(); 143 | } else if (result === 'OK') { 144 | subscription.destroy(); 145 | this.loadRolePermissions(this.role.name); 146 | } 147 | }); 148 | } 149 | 150 | 151 | delete() { 152 | this._deleting = true; 153 | this.roleService.delete(this.role.name).subscribe( 154 | result => { 155 | this._deleting = false; 156 | if (result.code !== 0) { 157 | this.messageService.error('删除角色失败:' + result.message); 158 | return; 159 | } 160 | this.messageService.success('删除成功'); 161 | this.router.navigate(['/frame/basic/role']); 162 | }, 163 | error => { 164 | this._deleting = false; 165 | this.messageService.error('执行失败:' + error); 166 | }); 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /src/app/contents/role/role.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { RouterModule } from '@angular/router'; 4 | import { FormsModule, ReactiveFormsModule } from '@angular/forms'; 5 | import { RoleListComponent } from './role-list/role-list.component'; 6 | import { RoleRoutingModule } from 'app/contents/role/role-routes.module'; 7 | import { NgZorroAntdModule } from 'ng-zorro-antd'; 8 | import { RoleEditComponent } from 'app/contents/role/role-edit/role-edit.component'; 9 | import { RoleViewComponent } from 'app/contents/role/role-view/role-view.component'; 10 | import { PermissionEditComponent } from 'app/contents/role/role-view/permission-edit.component'; 11 | import { RoleService } from 'app/contents/role/role.service'; 12 | 13 | @NgModule({ 14 | imports: [ 15 | CommonModule, 16 | RouterModule, 17 | FormsModule, 18 | ReactiveFormsModule, 19 | NgZorroAntdModule, 20 | RoleRoutingModule 21 | ], 22 | declarations: [ 23 | RoleListComponent, 24 | RoleViewComponent, 25 | RoleEditComponent, 26 | PermissionEditComponent 27 | ], 28 | exports: [], 29 | entryComponents: [PermissionEditComponent], 30 | providers: [RoleService] 31 | }) 32 | export class RoleModule { } 33 | -------------------------------------------------------------------------------- /src/app/contents/role/role.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Observable } from 'rxjs/Observable'; 3 | 4 | import { HttpResult } from 'app/base/shared/model/http-result'; 5 | import { Page } from 'app/base/shared/model/page'; 6 | import { Permission } from 'app/base/shared/model/session'; 7 | import { SessionService } from 'app/base/shared/session.service'; 8 | 9 | // 角色 10 | export class Role { 11 | name: string; 12 | remark?: string; 13 | } 14 | 15 | // 查询条件 16 | export class RoleFilter { 17 | nameLk?: string; 18 | pageNumber = 0; 19 | pageSize = 10; 20 | } 21 | 22 | // 角色用户 23 | export class RoleUser { 24 | constructor(public code: string, public name: string) { 25 | this.code = code; 26 | this.name = name; 27 | } 28 | } 29 | 30 | @Injectable() 31 | export class RoleService { 32 | 33 | constructor(private sessionService: SessionService) { } 34 | 35 | getList(filter: RoleFilter): Observable>> { 36 | const roles: Role[] = RoleData.filter(r => { 37 | if (filter.nameLk != null && !r.name.includes(filter.nameLk)) { 38 | return false; 39 | } 40 | return true; 41 | }); 42 | 43 | return new Observable(observer => { 44 | observer.next(new HttpResult(0, 'OK', this.getPageRole(roles, filter.pageNumber, filter.pageSize))); 45 | }); 46 | } 47 | 48 | getRole(name: string): Observable> { 49 | const role = RoleData.find(r => name === r.name); 50 | return new Observable(observer => { 51 | observer.next(new HttpResult(0, 'OK', role)); 52 | }); 53 | } 54 | 55 | saveRole(role: Role): Observable> { 56 | const duplicate = RoleData.find(r => name === r.name); 57 | if (duplicate) { 58 | return new Observable(observer => { 59 | observer.next(new HttpResult(-1, '已经存在名称为' + role.name + '的角色', null)); 60 | }); 61 | } 62 | RoleData.push(role); 63 | return new Observable(observer => { 64 | observer.next(new HttpResult(0, 'OK', null)); 65 | }); 66 | } 67 | 68 | saveModify(role: Role): Observable> { 69 | const exists = RoleData.find(r => name === r.name); 70 | if (exists) { 71 | exists.remark = role.remark; 72 | } 73 | return new Observable(observer => { 74 | observer.next(new HttpResult(0, 'OK', null)); 75 | }); 76 | } 77 | 78 | delete(name: string): Observable> { 79 | const index = RoleData.findIndex(r => name === r.name); 80 | if (index >= 0) { 81 | RoleData.splice(index, 1); 82 | } 83 | return new Observable(observer => { 84 | observer.next(new HttpResult(0, 'OK', null)); 85 | }); 86 | } 87 | 88 | getRoleUsers(roleName: string): Observable>> { 89 | return new Observable(observer => { 90 | observer.next(new HttpResult(0, 'OK', RoleUserData)); 91 | }); 92 | } 93 | 94 | getRolePermissions(roleName: string): Observable>> { 95 | return new Observable(observer => { 96 | observer.next(new HttpResult(0, 'OK', this.sessionService.getSession().permissions)); 97 | }); 98 | } 99 | 100 | coverRolePermissions(roleName: string, permissionCodes: Set): Observable> { 101 | return new Observable(observer => { 102 | observer.next(new HttpResult(0, 'OK', null)); 103 | }); 104 | } 105 | 106 | private getPageRole(roles?: Role[], pageNumber?: number, pageSize?: number): Page { 107 | pageNumber = pageNumber ? pageNumber : 0; 108 | pageSize = pageSize ? pageSize : 10; 109 | roles = roles ? roles : []; 110 | 111 | const page: Page = new Page(); 112 | page.totalElements = roles.length; 113 | page.totalPages = Math.floor(roles.length / pageSize); 114 | page.pageNumber = pageNumber; 115 | page.pageSize = pageSize; 116 | 117 | const startIndex = pageNumber * pageSize; 118 | const endIndex = Math.min(startIndex + pageSize, roles.length); 119 | page.content = roles.slice(startIndex, endIndex); 120 | page.hasContent = page.content.length > 0; 121 | page.hasNext = endIndex < roles.length; 122 | return page; 123 | } 124 | } 125 | 126 | const RoleData: Role[] = [ 127 | { name: '管理员' }, 128 | { name: '采购员' }, 129 | { name: '补货员' }, 130 | { name: '理货员' }, 131 | { name: '仓管' }, 132 | { name: '品管' }, 133 | { name: '仓库管理员' }, 134 | { name: '品管经理' }, 135 | { name: '仓管经理' }, 136 | { name: '补货经理' }, 137 | { name: '采购经理' }, 138 | { name: '品控' }, 139 | { name: '品控经理' }, 140 | { name: '总经理' }, 141 | { name: 'BI管理员' }, 142 | { name: 'BI分析员' }, 143 | { name: '战略分析员' }, 144 | { name: '董事会秘书1' }, 145 | { name: '董事会秘书2' }, 146 | { name: '董事会秘书3' }, 147 | { name: '董事会秘书4' }, 148 | { name: '董事会秘书5' }, 149 | { name: '董事会秘书6' }, 150 | ]; 151 | 152 | const RoleUserData: RoleUser[] = [ 153 | { code: '001', name: '员工01' }, 154 | { code: '002', name: '员工02' }, 155 | { code: '003', name: '员工03' }, 156 | { code: '004', name: '员工04' }, 157 | { code: '005', name: '员工05' }, 158 | { code: '006', name: '员工06' }, 159 | { code: '007', name: '员工07' }, 160 | { code: '008', name: '员工08' }, 161 | 162 | ]; 163 | 164 | export interface CPermission extends Permission { 165 | menuCode: string; 166 | menuName: string; 167 | } 168 | 169 | export const AllPermissions: Array = [ 170 | { menuCode: '0101', menuName: '角色', code: '/role/view', name: '查看权' }, 171 | { menuCode: '0101', menuName: '角色', code: '/role/create', name: '新建权' }, 172 | { menuCode: '0101', menuName: '角色', code: '/role/edit', name: '修改权' }, 173 | { menuCode: '0101', menuName: '角色', code: '/role/delete', name: '删除权' }, 174 | 175 | { menuCode: '0102', menuName: '员工', code: '/user/view', name: '查看权' }, 176 | { menuCode: '0102', menuName: '员工', code: '/user/create', name: '新建权' }, 177 | { menuCode: '0102', menuName: '员工', code: '/user/edit', name: '编辑权' }, 178 | { menuCode: '0102', menuName: '员工', code: '/user/delete', name: '删除权' }, 179 | { menuCode: '0102', menuName: '员工', code: '/user/resetPwd', name: '重置密码权' }, 180 | ]; 181 | -------------------------------------------------------------------------------- /src/app/contents/user/user-edit/user-edit.component.css: -------------------------------------------------------------------------------- 1 | :host ::ng-deep .error{ 2 | color: red; 3 | padding-top: 7px; 4 | } -------------------------------------------------------------------------------- /src/app/contents/user/user-edit/user-edit.component.html: -------------------------------------------------------------------------------- 1 | 2 | 基本资料 3 | 用户 4 | {{_isNew ? '新建':'修改'}} 5 | 6 | 7 | 8 | 9 |
10 |
11 |
12 | 13 |
14 |
{{user.code}}
15 |
16 |
17 |
18 | 19 |
20 |
21 | 22 |
{{formErrors.name}}
23 |
24 |
25 |
26 |
27 | 28 |
29 |
30 | 31 |
{{formErrors.mobile}}
32 |
33 |
34 |
35 |
36 | 37 |
38 |
39 | 40 |
{{formErrors.login}}
41 |
42 |
43 |
44 |
45 | 46 |
47 |
48 | 51 | 54 | 57 |
58 |
59 |
60 |
61 | 62 |
63 |
64 | 65 |
{{formErrors.remark}}
66 |
67 |
68 |
69 |
70 | 71 |
72 |
73 | 74 |
75 |
76 |
77 |
78 |
-------------------------------------------------------------------------------- /src/app/contents/user/user-edit/user-edit.component.ts: -------------------------------------------------------------------------------- 1 | import { Location } from '@angular/common'; 2 | import { Component, OnInit } from '@angular/core'; 3 | import { Router, ActivatedRoute, Params } from '@angular/router'; 4 | 5 | import { SessionService } from 'app/base/shared/session.service'; 6 | import { NzMessageService } from 'ng-zorro-antd'; 7 | import { FormGroup, FormBuilder, Validators } from '@angular/forms'; 8 | import { FormUtil } from 'app/base/shared/form-util'; 9 | import { User, UserService } from 'app/contents/user/user.service'; 10 | 11 | @Component({ 12 | moduleId: module.id, 13 | selector: 'app-cmall-user-edit', 14 | styleUrls: ['user-edit.component.css'], 15 | templateUrl: 'user-edit.component.html' 16 | }) 17 | export class UserEditComponent implements OnInit { 18 | formErrors = { 19 | 'login': '', 20 | 'name': '', 21 | 'mobile': '', 22 | 'remark': '', 23 | }; 24 | 25 | validationMessages = { 26 | 'login': { 27 | 'required': '登录名不能为空', 28 | 'maxlength': '登录名长度必须小于等于15' 29 | }, 30 | 'name': { 31 | 'required': '名称不能为空', 32 | 'maxlength': '名称长度必须小于等于20' 33 | }, 34 | 'mobile': { 'maxlength': '手机号长度必须小于等于15' }, 35 | 'remark': { 'maxlength': '说明长度必须小于等于256' } 36 | }; 37 | 38 | validateForm: FormGroup = new FormGroup({}); 39 | user: User = new User(); 40 | 41 | _isNew = true; 42 | _loading = false; 43 | _saving = false; 44 | 45 | constructor( 46 | private fb: FormBuilder, 47 | private router: Router, 48 | private location: Location, 49 | private route: ActivatedRoute, 50 | private messageService: NzMessageService, 51 | private userService: UserService, 52 | private sessionService: SessionService) { 53 | } 54 | 55 | ngOnInit(): void { 56 | this.buildForm(); 57 | this.route.params.forEach((params: Params) => { 58 | const code = params['code']; 59 | if (code) { 60 | this._isNew = false; 61 | this.load(code); 62 | } 63 | }); 64 | } 65 | 66 | private buildForm(): void { 67 | this.validateForm = this.fb.group({ 68 | 'code': this.fb.control(this.user.code, []), 69 | 'login': this.fb.control(this.user.login, [Validators.required, Validators.maxLength(15)]), 70 | 'name': this.fb.control(this.user.name, [Validators.required, Validators.maxLength(20)]), 71 | 'mobile': this.fb.control(this.user.mobile, [Validators.maxLength(15)]), 72 | 'hasOperateView': this.fb.control(this.user.hasOperateView), 73 | 'hasPropertyView': this.fb.control(this.user.hasPropertyView), 74 | 'hasBrandView': this.fb.control(this.user.hasBrandView), 75 | 'remark': this.fb.control(this.user.remark, [Validators.maxLength(256)]), 76 | }); 77 | this.validateForm.valueChanges.subscribe(value => this.checkForm()); 78 | } 79 | 80 | private checkForm(): void { 81 | FormUtil.valid(this.validateForm, this.formErrors, this.validationMessages); 82 | } 83 | 84 | cancel(): void { 85 | this.location.back(); 86 | } 87 | 88 | save(): void { 89 | if (this.validateForm.invalid) { 90 | FormUtil.markAsDirtyDeep(this.validateForm); 91 | this.checkForm(); 92 | return; 93 | } 94 | 95 | this.user = this.validateForm.value; 96 | 97 | this._saving = true; 98 | this.userService.save(this.user).subscribe( 99 | result => { 100 | this._saving = false; 101 | if (result.code !== 0) { 102 | this.messageService.error('保存用户失败:' + result.message); 103 | return; 104 | } 105 | this.messageService.success('保存成功'); 106 | this.router.navigate(['/frame/basic/user/view', result.data]); 107 | }, 108 | error => { 109 | this._saving = false; 110 | this.messageService.error('执行失败:' + error); 111 | } 112 | ); 113 | } 114 | 115 | private load(code: string) { 116 | this._loading = true; 117 | this.userService.getUser(code).subscribe( 118 | result => { 119 | this._loading = false; 120 | if (result.code !== 0) { 121 | this.messageService.error('加载用户失败:' + result.message); 122 | return; 123 | } 124 | this.user = result.data; 125 | this.buildForm(); 126 | }, 127 | error => { 128 | this._loading = false; 129 | this.messageService.error('执行失败:' + error); 130 | } 131 | ); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/app/contents/user/user-list/user-list.component.css: -------------------------------------------------------------------------------- 1 | :host ::ng-deep .ant-advanced-search-form { 2 | padding: 24px; 3 | background: #fbfbfb; 4 | border: 1px solid #d9d9d9; 5 | border-radius: 6px; 6 | } 7 | 8 | :host ::ng-deep .search-result-list { 9 | margin-top: 16px; 10 | border: 1px dashed #e9e9e9; 11 | border-radius: 6px; 12 | min-height: 200px; 13 | } -------------------------------------------------------------------------------- /src/app/contents/user/user-list/user-list.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 基础管理 5 | 员工 6 | 列表 7 | 8 |
9 |
10 | 14 |
15 |
16 | 17 | 18 |
19 | 20 |
21 |
22 |
23 |
24 | 25 |
26 |
27 | 28 |
29 |
30 |
31 |
32 |
33 |
34 | 35 |
36 |
37 | 38 |
39 |
40 |
41 |
42 |
43 | 44 | 45 |
46 |
47 |
48 |
49 | 50 |
51 | 52 | 55 | 56 | 57 | 58 | 代码 59 | 60 | 61 | 名称 62 | 63 | 64 | 电话 65 | 66 | 67 | 操作 68 | 69 | 70 | 71 | 72 | 73 | 74 | {{user.code}} 75 | 76 | {{user.name}} 77 | {{user.mobile}} 78 | 79 | 修改  80 | 81 | 删除 82 | 83 | 84 | 85 | 86 | 87 | 88 |
89 |
-------------------------------------------------------------------------------- /src/app/contents/user/user-list/user-list.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { Router } from '@angular/router'; 3 | import { NzModalService, NzMessageService } from 'ng-zorro-antd'; 4 | 5 | import { Page } from 'app/base/shared/model/page'; 6 | import { UserFilter, QUser, UserService } from 'app/contents/user/user.service'; 7 | 8 | @Component({ 9 | moduleId: module.id, 10 | selector: 'app-cmall-user-list', 11 | templateUrl: 'user-list.component.html', 12 | styleUrls: ['user-list.component.css'] 13 | }) 14 | export class UserListComponent implements OnInit { 15 | filter: UserFilter = new UserFilter(); 16 | page: Page = new Page(); 17 | 18 | _pageIndex = 1; 19 | _pageSize = 10; 20 | _loading = false; 21 | _deleting = false; 22 | 23 | constructor( 24 | private modalService: NzModalService, 25 | private messageService: NzMessageService, 26 | private userService: UserService) { 27 | } 28 | 29 | ngOnInit(): void { 30 | this.search(true); 31 | } 32 | 33 | resetForm(): void { 34 | this.filter = new UserFilter(); 35 | } 36 | 37 | search(reset: boolean = false): void { 38 | if (reset) { 39 | this._pageIndex = 1; 40 | } 41 | 42 | this.filter.pageNumber = this._pageIndex - 1; 43 | this.filter.pageSize = this._pageSize; 44 | 45 | this._loading = true; 46 | this.userService.getList(this.filter).subscribe( 47 | result => { 48 | this._loading = false; 49 | if (result.code !== 0) { 50 | this.messageService.error('加载用户列表失败:' + result.message); 51 | return; 52 | } 53 | this.page = result.data; 54 | }, 55 | error => { 56 | this._loading = false; 57 | this.messageService.error('执行失败:' + error); 58 | } 59 | ); 60 | } 61 | 62 | delete(user: QUser): void { 63 | this._deleting = true; 64 | this.userService.delete(user.code).subscribe( 65 | result => { 66 | this._deleting = false; 67 | if (result.code !== 0) { 68 | this.messageService.error('删除用户失败:' + result.message); 69 | return; 70 | } 71 | this.messageService.success('删除用户成功'); 72 | this.search(); 73 | }, 74 | error => { 75 | this._deleting = false; 76 | this.messageService.error('执行失败:' + error); 77 | } 78 | ); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/app/contents/user/user-routes.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | 4 | import { UserListComponent } from './user-list/user-list.component'; 5 | import { UserViewComponent } from 'app/contents/user/user-view/user-view.component'; 6 | import { UserEditComponent } from 'app/contents/user/user-edit/user-edit.component'; 7 | 8 | const routes: Routes = [ 9 | { 10 | path: 'basic/user', 11 | children: [ 12 | { path: '', component: UserListComponent }, 13 | { path: 'create', component: UserEditComponent }, 14 | { path: 'edit/:code', component: UserEditComponent }, 15 | { path: 'view/:code', component: UserViewComponent }, 16 | ] 17 | } 18 | ]; 19 | 20 | @NgModule({ 21 | imports: [RouterModule.forChild(routes)], 22 | exports: [RouterModule] 23 | }) 24 | export class UserRoutingModule { } 25 | -------------------------------------------------------------------------------- /src/app/contents/user/user-view/user-reset-password.component.css: -------------------------------------------------------------------------------- 1 | :host ::ng-deep .customize-footer { 2 | border-top: 1px solid #e9e9e9; 3 | padding: 10px 18px 0 10px; 4 | text-align: center; 5 | border-radius: 0 0 0px 0px; 6 | margin: 15px -16px -5px -16px; 7 | } -------------------------------------------------------------------------------- /src/app/contents/user/user-view/user-reset-password.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/app/contents/user/user-view/user-reset-password.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from '@angular/core'; 2 | 3 | import { NzModalSubject, NzMessageService } from 'ng-zorro-antd'; 4 | import { User, UserService } from 'app/contents/user/user.service'; 5 | 6 | @Component({ 7 | moduleId: module.id, 8 | selector: 'app-cmall-user-reset-password', 9 | templateUrl: 'user-reset-password.component.html', 10 | styleUrls: ['user-reset-password.component.css'] 11 | }) 12 | export class UserResetPasswordComponent { 13 | @Input() 14 | user: User = new User(); 15 | 16 | password: string; 17 | _saving = false; 18 | 19 | constructor(private subject: NzModalSubject, 20 | private messageService: NzMessageService, 21 | private userService: UserService) { } 22 | 23 | cancel() { 24 | this.subject.destroy('cancel'); 25 | } 26 | 27 | confirm() { 28 | this._saving = true; 29 | this.userService.resetPassword(this.user.code, this.password).subscribe( 30 | result => { 31 | this._saving = false; 32 | if (result.code !== 0) { 33 | this.messageService.error('重置用户密码失败:' + result.message); 34 | return; 35 | } 36 | this.messageService.success('重置用户成功'); 37 | this.subject.next('OK'); 38 | }, 39 | error => { 40 | this._saving = false; 41 | this.messageService.error('执行失败:' + error); 42 | } 43 | ); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/app/contents/user/user-view/user-role-edit.component.css: -------------------------------------------------------------------------------- 1 | :host ::ng-deep .ant-advanced-search-form { 2 | padding: 24px; 3 | background: #fbfbfb; 4 | border: 1px solid #d9d9d9; 5 | border-radius: 6px; 6 | } 7 | 8 | :host ::ng-deep .search-result-list { 9 | margin-top: 16px; 10 | border: 1px dashed #e9e9e9; 11 | border-radius: 6px; 12 | min-height: 200px; 13 | } 14 | 15 | :host ::ng-deep .customize-footer { 16 | border-top: 1px solid #e9e9e9; 17 | padding: 10px 18px 0 10px; 18 | text-align: center; 19 | border-radius: 0 0 0px 0px; 20 | margin: 15px -16px -5px -16px; 21 | } -------------------------------------------------------------------------------- /src/app/contents/user/user-view/user-role-edit.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |
5 |
6 |
7 |
8 | 9 |
10 |
11 | 12 |
13 |
14 |
15 |
16 | 17 | 18 |
19 |
20 |
21 | 22 |
23 | 26 | 27 | 28 | 29 | 31 | 32 | 33 | 名称 34 | 35 | 36 | 37 | 38 | 39 | 40 | 42 | 43 | {{role.name}} 44 | 45 | 46 | 47 |
48 |
49 | 50 | 54 |
-------------------------------------------------------------------------------- /src/app/contents/user/user-view/user-role-edit.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, OnInit } from '@angular/core'; 2 | import { NzModalSubject, NzMessageService } from 'ng-zorro-antd'; 3 | import { Page } from 'app/base/shared/model/page'; 4 | import { User, UserRole, UserService } from 'app/contents/user/user.service'; 5 | import { RoleFilter, RoleService } from 'app/contents/role/role.service'; 6 | 7 | @Component({ 8 | moduleId: module.id, 9 | selector: 'app-cmall-user-role-edit', 10 | styleUrls: ['user-role-edit.component.css'], 11 | templateUrl: 'user-role-edit.component.html' 12 | }) 13 | export class UserRoleEditComponent implements OnInit { 14 | @Input() 15 | user: User = new User(); 16 | 17 | filter: RoleFilter = new RoleFilter(); 18 | page: Page = new Page(); 19 | 20 | _allChecked = false; 21 | _indeterminate = false; 22 | 23 | _pageIndex = 1; 24 | _pageSize = 5; 25 | 26 | _loading = false; 27 | _saving = false; 28 | 29 | constructor(private subject: NzModalSubject, 30 | private messageService: NzMessageService, 31 | private roleService: RoleService, 32 | private userService: UserService) { 33 | } 34 | 35 | ngOnInit(): void { 36 | this.search(true); 37 | } 38 | 39 | resetForm(): void { 40 | this.filter = new RoleFilter(); 41 | } 42 | 43 | search(reset: boolean = false) { 44 | if (reset) { 45 | this._pageIndex = 1; 46 | } 47 | 48 | this.filter.pageNumber = this._pageIndex - 1; 49 | this.filter.pageSize = this._pageSize; 50 | 51 | this._loading = true; 52 | this.roleService.getList(this.filter).subscribe( 53 | result => { 54 | this._loading = false; 55 | if (result.code !== 0) { 56 | this.messageService.error('加载角色列表失败:' + result.message); 57 | return; 58 | } 59 | this.page = result.data; 60 | }, 61 | error => { 62 | this._loading = false; 63 | this.messageService.error('执行失败:' + error); 64 | }); 65 | } 66 | 67 | _refreshStatus() { 68 | const allChecked = this.page.content.every(value => value.checked === true); 69 | const allUnChecked = this.page.content.every(value => !value.checked); 70 | this._allChecked = allChecked; 71 | this._indeterminate = (!allChecked) && (!allUnChecked); 72 | }; 73 | 74 | _checkAll(value) { 75 | this.page.content.forEach(data => data.checked = value); 76 | this._refreshStatus(); 77 | }; 78 | 79 | 80 | cancel() { 81 | this.subject.destroy('cancel'); 82 | } 83 | 84 | confirm() { 85 | this._saving = true; 86 | const roles: Array = []; 87 | this.page.content.forEach(line => { 88 | if (line.checked) { 89 | roles.push(line.name); 90 | } 91 | }); 92 | this.userService.addRoles(this.user.code, roles).subscribe( 93 | result => { 94 | this._saving = false; 95 | if (result.code !== 0) { 96 | this.messageService.error('添加角色失败:' + result.message); 97 | return; 98 | } 99 | this.messageService.success('添加角色成功'); 100 | this.subject.next('OK'); 101 | }, 102 | error => { 103 | this._saving = false; 104 | this.messageService.error('执行失败:' + error); 105 | } 106 | ); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/app/contents/user/user-view/user-view.component.css: -------------------------------------------------------------------------------- 1 | :host ::ng-deep .viewContent{ 2 | line-height: 3em; 3 | } 4 | 5 | :host ::ng-deep .search-result-list { 6 | border: 1px dashed #e9e9e9; 7 | border-radius: 6px; 8 | width: 60%; 9 | } 10 | 11 | :host ::ng-deep .appView{ 12 | color: rgba(0, 0, 0, 0.65); 13 | } -------------------------------------------------------------------------------- /src/app/contents/user/user-view/user-view.component.html: -------------------------------------------------------------------------------- 1 | 2 | 基本资料 3 | 用户 4 | 详情 5 | 6 | 7 | 8 | 9 | 10 | 11 | {{user.name}} 12 | 13 | 返回  14 | 重置密码  15 | 修改  16 | 17 | 删除 18 | 19 | 20 | 21 |
22 |
23 |
代码:
24 |
{{user.code}}
25 |
26 |
27 |
名称:
28 |
{{user.name}}
29 |
30 |
31 |
手机:
32 |
{{user.mobile}}
33 |
34 |
35 |
登录名:
36 |
{{user.login}}
37 |
38 |
39 |
APP视图:
40 |
41 | 44 | 47 | 50 |
51 |
52 |
53 |
说明:
54 |
{{user.remark}}
55 |
56 |
57 |
58 |
59 |
60 | 61 | 62 | 63 | 拥有的角色 64 | 65 | 添加角色 66 | 67 | 68 |
69 | 70 | 71 | 72 | 73 | 角色名称 74 | 75 | 76 | 所属项目 77 | 78 | 79 | 操作 80 | 81 | 82 | 83 | 84 | 85 | {{role.name}} 86 | {{role.projectName}} 87 | 88 | 删除 89 | 90 | 91 | 92 | 93 |
94 |
95 |
96 |
97 |
98 |
-------------------------------------------------------------------------------- /src/app/contents/user/user-view/user-view.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { ActivatedRoute, Params, Router } from '@angular/router'; 3 | import { NzModalService, NzMessageService } from 'ng-zorro-antd'; 4 | import { UserResetPasswordComponent } from 'app/contents/user/user-view/user-reset-password.component'; 5 | import { UserRoleEditComponent } from 'app/contents/user/user-view/user-role-edit.component'; 6 | import { User, UserService, UserRole } from 'app/contents/user/user.service'; 7 | 8 | @Component({ 9 | moduleId: module.id, 10 | selector: 'app-cmall-user-view', 11 | styleUrls: ['user-view.component.css'], 12 | templateUrl: 'user-view.component.html' 13 | }) 14 | export class UserViewComponent implements OnInit { 15 | user: User = new User(); 16 | roles: Array = []; 17 | 18 | _loading = false; 19 | _deleting = false; 20 | _roleRemoving = false; 21 | _roleLoading = false; 22 | 23 | constructor(private router: Router, 24 | private route: ActivatedRoute, 25 | private modalService: NzModalService, 26 | private messageService: NzMessageService, 27 | private userService: UserService) { } 28 | 29 | ngOnInit(): void { 30 | this.route.params.forEach((params: Params) => { 31 | const code = params['code']; 32 | this.load(code); 33 | this.loadRoles(code); 34 | }); 35 | } 36 | 37 | private load(code: string) { 38 | this._loading = true; 39 | this.userService.getUser(code).subscribe( 40 | result => { 41 | this._loading = false; 42 | if (result.code !== 0) { 43 | this.messageService.error('加载用户失败:' + result.message); 44 | return; 45 | } 46 | this.user = result.data; 47 | if (!this.user) { 48 | this.messageService.warning('用户' + this.user.code + '不存在'); 49 | this.router.navigate(['/frame/basic/user']); 50 | } 51 | }, 52 | error => { 53 | this._loading = false; 54 | this.messageService.error('执行失败:' + error); 55 | } 56 | ); 57 | } 58 | 59 | private loadRoles(code: string) { 60 | this._roleLoading = true; 61 | this.userService.getUserRoles(code).subscribe( 62 | result => { 63 | this._roleLoading = false; 64 | if (result.code !== 0) { 65 | this.messageService.error('加载用户角色列表失败:' + result.message); 66 | } 67 | this.roles = result.data; 68 | }, 69 | error => { 70 | this._roleLoading = false; 71 | this.messageService.error('执行失败:' + error); 72 | } 73 | ); 74 | } 75 | 76 | delete(user: User): void { 77 | this._deleting = true; 78 | this.userService.delete(user.code).subscribe( 79 | result => { 80 | this._deleting = false; 81 | if (result.code !== 0) { 82 | this.messageService.error('删除用户失败:' + result.message); 83 | return; 84 | } 85 | this.messageService.success('删除用户成功'); 86 | this.router.navigate(['/frame/basic/user']); 87 | }, 88 | error => { 89 | this._deleting = false; 90 | this.messageService.error('执行失败:' + error); 91 | } 92 | ); 93 | } 94 | 95 | removeRole(role: UserRole): void { 96 | this.modalService.confirm({ 97 | title: '您是否确认要移除角色' + role.name, 98 | onOk: () => this.doRemoveRole(role), 99 | onCancel() { } 100 | }); 101 | } 102 | 103 | doRemoveRole(role: UserRole) { 104 | this._roleRemoving = true; 105 | this.userService.removeRole(this.user.code, role.name).subscribe( 106 | result => { 107 | this._roleRemoving = false; 108 | if (result.code !== 0) { 109 | this.messageService.error('删除用户角色失败:' + result.message); 110 | return; 111 | } 112 | this.loadRoles(this.user.code); 113 | }, 114 | error => { 115 | this._roleRemoving = false; 116 | this.messageService.error('执行失败:' + error); 117 | } 118 | ); 119 | } 120 | 121 | showResetPaswordModal(): void { 122 | const subscription = this.modalService.open({ 123 | title: '重置密码', 124 | content: UserResetPasswordComponent, 125 | footer: false, 126 | componentParams: { user: this.user } 127 | }); 128 | 129 | subscription.subscribe(result => { 130 | if (result === 'cancel' || result === 'OK') { 131 | subscription.destroy(); 132 | } 133 | }); 134 | } 135 | 136 | showAddRoleModal(): void { 137 | const subscription = this.modalService.open({ 138 | title: '添加角色', 139 | content: UserRoleEditComponent, 140 | footer: false, 141 | componentParams: { user: this.user } 142 | }); 143 | 144 | subscription.subscribe(result => { 145 | if (result === 'cancel') { 146 | subscription.destroy(); 147 | } else if (result === 'OK') { 148 | subscription.destroy(); 149 | this.loadRoles(this.user.code); 150 | } 151 | }); 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/app/contents/user/user.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { FormsModule, ReactiveFormsModule } from '@angular/forms'; 3 | import { CommonModule } from '@angular/common'; 4 | import { RouterModule } from '@angular/router'; 5 | import { NgZorroAntdModule } from 'ng-zorro-antd'; 6 | 7 | import { UserListComponent } from './user-list/user-list.component'; 8 | import { UserRoutingModule } from 'app/contents/user/user-routes.module'; 9 | import { UserViewComponent } from 'app/contents/user/user-view/user-view.component'; 10 | import { UserResetPasswordComponent } from 'app/contents/user/user-view/user-reset-password.component'; 11 | import { UserRoleEditComponent } from 'app/contents/user/user-view/user-role-edit.component'; 12 | import { UserEditComponent } from 'app/contents/user/user-edit/user-edit.component'; 13 | import { RoleService } from 'app/contents/role/role.service'; 14 | import { UserService } from 'app/contents/user/user.service'; 15 | 16 | 17 | @NgModule({ 18 | imports: [ 19 | RouterModule, 20 | FormsModule, 21 | ReactiveFormsModule, 22 | CommonModule, 23 | NgZorroAntdModule, 24 | UserRoutingModule 25 | ], 26 | declarations: [ 27 | UserListComponent, 28 | UserViewComponent, 29 | UserEditComponent, 30 | UserResetPasswordComponent, 31 | UserRoleEditComponent 32 | ], 33 | exports: [], 34 | entryComponents: [UserResetPasswordComponent, UserRoleEditComponent], 35 | providers: [RoleService, UserService] 36 | }) 37 | export class UserModule { } 38 | -------------------------------------------------------------------------------- /src/app/contents/user/user.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Observable } from 'rxjs/Observable'; 3 | 4 | import { HttpResult } from 'app/base/shared/model/http-result'; 5 | import { Page } from 'app/base/shared/model/page'; 6 | 7 | export class UserFilter { 8 | codeEq?: string; 9 | nameLk?: string; 10 | mobileLk?: string; 11 | pageNumber = 0; 12 | pageSize = 10; 13 | } 14 | 15 | export class QUser { 16 | code?: string; 17 | name?: string; 18 | mobile?: string; 19 | checked?= false; 20 | } 21 | 22 | export class User { 23 | code?: string; 24 | login?: string; 25 | name?: string; 26 | mobile?: string; 27 | hasOperateView?= false; 28 | hasPropertyView?= false; 29 | hasBrandView?= false; 30 | remark?: string; 31 | } 32 | 33 | export class UserRole { 34 | name?: string; 35 | checked?= false; 36 | remark?: string; 37 | } 38 | 39 | @Injectable() 40 | export class UserService { 41 | 42 | constructor() { } 43 | 44 | public getList(filter: UserFilter): Observable>> { 45 | const roles: QUser[] = userData.filter(r => { 46 | if (filter.codeEq != null && r.code !== filter.codeEq) { 47 | return false; 48 | } 49 | if (filter.nameLk != null && !r.name.includes(filter.nameLk)) { 50 | return false; 51 | } 52 | return true; 53 | }); 54 | 55 | return new Observable(observer => { 56 | observer.next(new HttpResult(0, 'OK', this.getPageRole(roles, filter.pageNumber, filter.pageSize))); 57 | }); 58 | } 59 | 60 | public save(user: User): Observable> { 61 | if (user.code) { 62 | const u = userData.find(us => us.code === user.code); 63 | if (u) { 64 | u.name = user.name; 65 | u.login = user.login; 66 | u.mobile = user.mobile; 67 | u.hasOperateView = user.hasOperateView; 68 | u.hasPropertyView = user.hasPropertyView; 69 | u.hasBrandView = user.hasBrandView; 70 | u.remark = user.remark; 71 | } 72 | 73 | return new Observable(observer => { 74 | observer.next(new HttpResult(0, 'OK', user.code)); 75 | }); 76 | } else { 77 | user.code = (parseInt(userData[userData.length - 1].code, 10) + 1).toString(); 78 | userData.push(user); 79 | return new Observable(observer => { 80 | observer.next(new HttpResult(0, 'OK', user.code)); 81 | }); 82 | } 83 | } 84 | 85 | public delete(userCode: string): Observable> { 86 | const index = userData.findIndex(u => u.code === userCode); 87 | if (index >= 0) { 88 | userData.splice(index, 1); 89 | } 90 | return new Observable(observer => { 91 | observer.next(new HttpResult(0, 'OK', true)); 92 | }); 93 | } 94 | 95 | public getUser(code: string): Observable> { 96 | const user = userData.find(u => u.code === code); 97 | return new Observable(observer => { 98 | observer.next(new HttpResult(0, 'OK', user)); 99 | }); 100 | } 101 | 102 | public addRoles(code: string, roles: Array): Observable> { 103 | return new Observable(observer => { 104 | observer.next(new HttpResult(0, 'OK', true)); 105 | }); 106 | } 107 | 108 | public removeRole(userCode: string, roleName: string): Observable> { 109 | const index = userRoleData.findIndex(r => r.name === roleName); 110 | if (index >= 0) { 111 | userRoleData.splice(index, 1); 112 | } 113 | 114 | return new Observable(observer => { 115 | observer.next(new HttpResult(0, 'OK', true)); 116 | }); 117 | } 118 | 119 | public getUserRoles(code: string): Observable>> { 120 | return new Observable(observer => { 121 | observer.next(new HttpResult(0, 'OK', userRoleData)); 122 | }); 123 | } 124 | 125 | public resetPassword(code: string, password: string): Observable> { 126 | return new Observable(observer => { 127 | observer.next(new HttpResult(0, 'OK', true)); 128 | }); 129 | } 130 | 131 | private getPageRole(roles?: QUser[], pageNumber?: number, pageSize?: number): Page { 132 | pageNumber = pageNumber ? pageNumber : 0; 133 | pageSize = pageSize ? pageSize : 10; 134 | roles = roles ? roles : []; 135 | 136 | const page: Page = new Page(); 137 | page.totalElements = roles.length; 138 | page.totalPages = Math.floor(roles.length / pageSize); 139 | page.pageNumber = pageNumber; 140 | page.pageSize = pageSize; 141 | 142 | const startIndex = pageNumber * pageSize; 143 | const endIndex = Math.min(startIndex + pageSize, roles.length); 144 | page.content = roles.slice(startIndex, endIndex); 145 | page.hasContent = page.content.length > 0; 146 | page.hasNext = endIndex < roles.length; 147 | return page; 148 | } 149 | } 150 | 151 | const userData: User[] = [ 152 | { code: '001', name: '员工001', login: 'login001' }, 153 | { code: '002', name: '员工002', login: 'login002' }, 154 | { code: '003', name: '员工003', login: 'login003' }, 155 | { code: '004', name: '员工004', login: 'login004' }, 156 | { code: '005', name: '员工005', login: 'login005' }, 157 | { code: '006', name: '员工006', login: 'login006' }, 158 | { code: '007', name: '员工007', login: 'login007' }, 159 | { code: '008', name: '员工008', login: 'login008' }, 160 | { code: '009', name: '员工009', login: 'login009' }, 161 | { code: '010', name: '员工010', login: 'login010' }, 162 | { code: '011', name: '员工011', login: 'login011' }, 163 | { code: '012', name: '员工012', login: 'login012' }, 164 | { code: '013', name: '员工013', login: 'login013' }, 165 | { code: '014', name: '员工014', login: 'login014' }, 166 | { code: '015', name: '员工015', login: 'login015' }, 167 | ]; 168 | 169 | const userRoleData: UserRole[] = [ 170 | { name: '管理员' }, 171 | { name: '采购员' }, 172 | { name: '补货员' }, 173 | { name: '哈哈哈' }, 174 | ]; 175 | -------------------------------------------------------------------------------- /src/app/permission.gurid.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, CanActivateChild } from '@angular/router'; 3 | import { SessionService } from 'app/base/shared/session.service'; 4 | import { NzModalService } from 'ng-zorro-antd'; 5 | 6 | // 路由权限守卫 7 | @Injectable() 8 | export class PermissionGurid implements CanActivate, CanActivateChild { 9 | constructor(private sessionService: SessionService, 10 | private confirmServ: NzModalService) { } 11 | 12 | canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean { 13 | const pCodes: string[] = route.data ? route.data['permission'] : null; 14 | // 路由未配置permission属性时直接返回true 15 | if (!pCodes) { 16 | return true 17 | }; 18 | 19 | const permissions = this.sessionService.getSession().permissions; 20 | const success = permissions.some(p => pCodes.some(code => p.code === code)); 21 | 22 | if (!success) { 23 | this.confirmServ.error({ 24 | title: '缺少权限', 25 | content: '缺少权限:' + pCodes.join(',') 26 | }); 27 | } 28 | return success; 29 | } 30 | 31 | canActivateChild(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean { 32 | return this.canActivate(route, state); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/app/providers/login.service-impl.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Observable } from 'rxjs/Observable'; 3 | import { Observer } from 'rxjs/Observer'; 4 | import { LoginService } from 'app/base/login/login.service'; 5 | import { Session } from 'app/base/shared/model/session'; 6 | import { HttpResult } from 'app/base/shared/model/http-result'; 7 | 8 | @Injectable() 9 | export class LoginServiceImpl implements LoginService { 10 | 11 | login(loginData: { login: string; password: string; }): Observable { 12 | return new Observable(observer => { 13 | observer.next(this.getDemoSession()); 14 | }); 15 | } 16 | 17 | logout(token: string): Observable { 18 | return new Observable(observer => { 19 | observer.next(null); 20 | }); 21 | } 22 | 23 | changePassword(data: { token: string; oldPassword: string; newPassword: string; }): Observable { 24 | return new Observable(observer => { 25 | observer.next(null); 26 | }); 27 | } 28 | 29 | private getDemoSession(): Session { 30 | const result: Session = { 31 | userCode: '001', 32 | userName: '程新文', 33 | token: '1234567890', 34 | menus: [ 35 | { 36 | title: '控制台', 37 | path: '', 38 | icon: 'home', 39 | subMenus: [] 40 | }, 41 | { 42 | title: '基本资料', 43 | path: '', 44 | subMenus: [ 45 | { title: '角色', path: '/basic/role', icon: 'user', subMenus: [] }, 46 | { title: '员工', path: '/basic/user', subMenus: [] }, 47 | { title: '商品', path: '', subMenus: [] }, 48 | { title: '门店', path: '', subMenus: [] }, 49 | ] 50 | }, 51 | { 52 | title: '采购管理', 53 | path: '', 54 | icon: 'home', 55 | subMenus: [ 56 | { title: '采购订单', path: '', subMenus: [] }, 57 | { title: '自营进货单', path: '', subMenus: [] }, 58 | { title: '采购退货单', path: '', subMenus: [] }, 59 | { title: '采购权限', path: '', subMenus: [] }, 60 | ] 61 | }, 62 | { 63 | title: '零售管理', 64 | path: '', 65 | subMenus: [ 66 | { title: '门店', path: '', subMenus: [] }, 67 | { title: '补货策略', path: '', subMenus: [] }, 68 | { title: '补货单', path: '', subMenus: [] }, 69 | { title: '配货单', path: '', subMenus: [] }, 70 | { title: '配货退货单', path: '', subMenus: [] }, 71 | ] 72 | }, 73 | { 74 | title: '财务管理', 75 | path: '', 76 | subMenus: [ 77 | { title: '结算单位', path: '', subMenus: [] }, 78 | { title: '财务科目', path: '', subMenus: [] }, 79 | { title: '对账', path: '', subMenus: [] }, 80 | { title: '发票登记', path: '', subMenus: [] }, 81 | { title: '应收款管理', path: '', subMenus: [] }, 82 | { title: '应付款管理', path: '', subMenus: [] }, 83 | ] 84 | } 85 | ], 86 | permissions: [ 87 | { code: '/role/view', name: '角色查看权' }, 88 | { code: '/role/create', name: '角色新建权' }, 89 | { code: '/role/edit', name: '角色编辑权' }, 90 | { code: '/role/delete', name: '角色删除权' }, 91 | 92 | { code: '/user/view', name: '用户查看权' }, 93 | { code: '/user/create', name: '用户新建权' }, 94 | { code: '/user/edit', name: '用户编辑权' }, 95 | { code: '/user/delete', name: '用户删除权' }, 96 | { code: '/user/resetPwd', name: '重置密码权' } 97 | ] 98 | }; 99 | return result; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexspring123/NG-ZORRO-TEMPLATE/7371c6bab332e95793e56f4bd8ebb28a427d95f4/src/assets/.gitkeep -------------------------------------------------------------------------------- /src/assets/imgs/login_bk.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexspring123/NG-ZORRO-TEMPLATE/7371c6bab332e95793e56f4bd8ebb28a427d95f4/src/assets/imgs/login_bk.jpg -------------------------------------------------------------------------------- /src/assets/imgs/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexspring123/NG-ZORRO-TEMPLATE/7371c6bab332e95793e56f4bd8ebb28a427d95f4/src/assets/imgs/logo.png -------------------------------------------------------------------------------- /src/config/global-config.ts: -------------------------------------------------------------------------------- 1 | export const app_title = '智能照明管理系统'; 2 | 3 | // 登录页面 4 | export const loginConfig = { 5 | // 背景图片 6 | background_image: 'assets/imgs/login_bk.jpg', 7 | // 登录表单标题 8 | form_title: '智能照明管理系统' 9 | }; 10 | 11 | // menu配置 12 | export const menuConfig = { 13 | // 菜单位置,可选值为 'left'、'top' 14 | placement: 'top' 15 | }; 16 | 17 | // 版权配置 18 | export const copyright = '智能照明管理系统 ©2017 Implement By Alex'; 19 | 20 | // session配置 21 | export const sessionConfig = { 22 | // session在浏览器的storerage中的存储key 23 | store_key: '__my-user-session__' 24 | }; 25 | -------------------------------------------------------------------------------- /src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // The file contents for the current environment will overwrite these during build. 2 | // The build system defaults to the dev environment which uses `environment.ts`, but if you do 3 | // `ng build --env=prod` then `environment.prod.ts` will be used instead. 4 | // The list of which env maps to which file can be found in `.angular-cli.json`. 5 | 6 | export const environment = { 7 | production: false 8 | }; 9 | -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexspring123/NG-ZORRO-TEMPLATE/7371c6bab332e95793e56f4bd8ebb28a427d95f4/src/favicon.ico -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NgZorroDemo 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic().bootstrapModule(AppModule); 12 | -------------------------------------------------------------------------------- /src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** IE9, IE10 and IE11 requires all of the following polyfills. **/ 22 | // import 'core-js/es6/symbol'; 23 | // import 'core-js/es6/object'; 24 | // import 'core-js/es6/function'; 25 | // import 'core-js/es6/parse-int'; 26 | // import 'core-js/es6/parse-float'; 27 | // import 'core-js/es6/number'; 28 | // import 'core-js/es6/math'; 29 | // import 'core-js/es6/string'; 30 | // import 'core-js/es6/date'; 31 | // import 'core-js/es6/array'; 32 | // import 'core-js/es6/regexp'; 33 | // import 'core-js/es6/map'; 34 | // import 'core-js/es6/weak-map'; 35 | // import 'core-js/es6/set'; 36 | 37 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 38 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 39 | 40 | /** IE10 and IE11 requires the following to support `@angular/animation`. */ 41 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 42 | 43 | 44 | /** Evergreen browsers require these. **/ 45 | import 'core-js/es6/reflect'; 46 | import 'core-js/es7/reflect'; 47 | 48 | 49 | /** ALL Firefox browsers require the following to support `@angular/animation`. **/ 50 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 51 | 52 | 53 | 54 | /*************************************************************************************************** 55 | * Zone JS is required by Angular itself. 56 | */ 57 | import 'zone.js/dist/zone'; // Included with Angular CLI. 58 | 59 | 60 | 61 | /*************************************************************************************************** 62 | * APPLICATION IMPORTS 63 | */ 64 | 65 | /** 66 | * Date, currency, decimal and percent pipes. 67 | * Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10 68 | */ 69 | // import 'intl'; // Run `npm install --save intl`. 70 | /** 71 | * Need to import at least one locale-data with intl. 72 | */ 73 | // import 'intl/locale-data/jsonp/en'; 74 | -------------------------------------------------------------------------------- /src/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | -------------------------------------------------------------------------------- /src/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "module": "es2015", 6 | "baseUrl": "", 7 | "types": [] 8 | }, 9 | "exclude": [ 10 | "test.ts", 11 | "**/*.spec.ts" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /src/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/spec", 5 | "module": "commonjs", 6 | "target": "es5", 7 | "baseUrl": "", 8 | "types": [ 9 | "jasmine", 10 | "node" 11 | ] 12 | }, 13 | "files": [ 14 | "test.ts" 15 | ], 16 | "include": [ 17 | "**/*.spec.ts", 18 | "**/*.d.ts" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /src/typings.d.ts: -------------------------------------------------------------------------------- 1 | /* SystemJS module definition */ 2 | declare var module: NodeModule; 3 | interface NodeModule { 4 | id: string; 5 | } 6 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "outDir": "./dist/out-tsc", 5 | "baseUrl": "src", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "moduleResolution": "node", 9 | "emitDecoratorMetadata": true, 10 | "experimentalDecorators": true, 11 | "target": "es5", 12 | "typeRoots": [ 13 | "node_modules/@types" 14 | ], 15 | "lib": [ 16 | "es2016", 17 | "dom" 18 | ] 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": [ 3 | "node_modules/codelyzer" 4 | ], 5 | "rules": { 6 | "arrow-return-shorthand": true, 7 | "callable-types": true, 8 | "class-name": true, 9 | "comment-format": [ 10 | true, 11 | "check-space" 12 | ], 13 | "curly": true, 14 | "eofline": true, 15 | "forin": true, 16 | "import-blacklist": [ 17 | true, 18 | "rxjs" 19 | ], 20 | "import-spacing": true, 21 | "indent": [ 22 | true, 23 | "spaces" 24 | ], 25 | "interface-over-type-literal": true, 26 | "label-position": true, 27 | "max-line-length": [ 28 | true, 29 | 140 30 | ], 31 | "member-access": false, 32 | "member-ordering": [ 33 | true, 34 | { 35 | "order": [ 36 | "static-before-instance", 37 | "variables-before-functions" 38 | ] 39 | } 40 | ], 41 | "no-arg": true, 42 | "no-bitwise": true, 43 | "no-console": [ 44 | true, 45 | "debug", 46 | "info", 47 | "time", 48 | "timeEnd", 49 | "trace" 50 | ], 51 | "no-construct": true, 52 | "no-debugger": true, 53 | "no-duplicate-super": true, 54 | "no-empty": false, 55 | "no-empty-interface": true, 56 | "no-eval": true, 57 | "no-inferrable-types": [ 58 | true, 59 | "ignore-params" 60 | ], 61 | "no-misused-new": true, 62 | "no-non-null-assertion": true, 63 | "no-shadowed-variable": true, 64 | "no-string-literal": false, 65 | "no-string-throw": true, 66 | "no-switch-case-fall-through": true, 67 | "no-trailing-whitespace": true, 68 | "no-unnecessary-initializer": true, 69 | "no-unused-expression": true, 70 | "no-use-before-declare": true, 71 | "no-var-keyword": true, 72 | "object-literal-sort-keys": false, 73 | "one-line": [ 74 | true, 75 | "check-open-brace", 76 | "check-catch", 77 | "check-else", 78 | "check-whitespace" 79 | ], 80 | "prefer-const": true, 81 | "quotemark": [ 82 | true, 83 | "single" 84 | ], 85 | "radix": true, 86 | "semicolon": [ 87 | true, 88 | "always" 89 | ], 90 | "triple-equals": [ 91 | true, 92 | "allow-null-check" 93 | ], 94 | "typedef-whitespace": [ 95 | true, 96 | { 97 | "call-signature": "nospace", 98 | "index-signature": "nospace", 99 | "parameter": "nospace", 100 | "property-declaration": "nospace", 101 | "variable-declaration": "nospace" 102 | } 103 | ], 104 | "typeof-compare": true, 105 | "unified-signatures": true, 106 | "variable-name": false, 107 | "whitespace": [ 108 | true, 109 | "check-branch", 110 | "check-decl", 111 | "check-operator", 112 | "check-separator", 113 | "check-type" 114 | ], 115 | "directive-selector": [ 116 | true, 117 | "attribute", 118 | "app", 119 | "camelCase" 120 | ], 121 | "component-selector": [ 122 | true, 123 | "element", 124 | "app", 125 | "kebab-case" 126 | ], 127 | "use-input-property-decorator": true, 128 | "use-output-property-decorator": true, 129 | "use-host-property-decorator": true, 130 | "no-input-rename": true, 131 | "no-output-rename": true, 132 | "use-life-cycle-interface": true, 133 | "use-pipe-transform-interface": true, 134 | "component-class-suffix": true, 135 | "directive-class-suffix": true, 136 | "no-access-missing-member": true, 137 | "templates-use-public": true, 138 | "invoke-injectable": true 139 | } 140 | } --------------------------------------------------------------------------------