main
周文涛 1 year ago
parent f6b0036c05
commit 18ef4c6f39

@ -0,0 +1,17 @@
# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
# For additional information regarding the format and rule options, please see:
# https://github.com/browserslist/browserslist#queries
# For the full list of supported browsers by the Angular framework, please see:
# https://angular.io/guide/browser-support
# You can see what browsers were selected by your queries by running:
# npx browserslist
last 1 Chrome version
last 1 Firefox version
last 2 Edge major versions
last 2 Safari major versions
last 2 iOS major versions
Firefox ESR
not IE 11 # Angular supports IE 11 only as an opt-in. To opt-in, remove the 'not' prefix on this line.

@ -0,0 +1,16 @@
# Editor configuration, see https://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.ts]
quote_type = single
[*.md]
max_line_length = off
trim_trailing_whitespace = false

@ -0,0 +1,34 @@
_cli-tpl/
dist/
coverage/
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Dependency directories
node_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
.cache/
# yarn v2
.yarn

@ -0,0 +1,126 @@
const prettierConfig = require('./.prettierrc.js');
module.exports = {
root: true,
parserOptions: { ecmaVersion: 2021 },
overrides: [
{
files: ['*.ts'],
parser: '@typescript-eslint/parser',
parserOptions: {
tsconfigRootDir: __dirname,
project: ['tsconfig.json'],
createDefaultProgram: true
},
plugins: ['@typescript-eslint', 'jsdoc', 'import'],
extends: [
'plugin:@angular-eslint/recommended',
'plugin:@angular-eslint/template/process-inline-templates',
'plugin:prettier/recommended'
],
rules: {
'prettier/prettier': ['error', prettierConfig],
'jsdoc/newline-after-description': 1,
'@angular-eslint/component-class-suffix': [
'error',
{
suffixes: ['Directive', 'Component', 'Base', 'Widget']
}
],
'@angular-eslint/directive-class-suffix': [
'error',
{
suffixes: ['Directive', 'Component', 'Base', 'Widget']
}
],
'@angular-eslint/component-selector': [
'off',
{
type: ['element', 'attribute'],
prefix: ['app', 'test'],
style: 'kebab-case'
}
],
'@angular-eslint/directive-selector': [
'off',
{
type: 'attribute',
prefix: ['app']
}
],
'@angular-eslint/no-attribute-decorator': 'error',
'@angular-eslint/no-conflicting-lifecycle': 'off',
'@angular-eslint/no-forward-ref': 'off',
'@angular-eslint/no-host-metadata-property': 'off',
'@angular-eslint/no-lifecycle-call': 'off',
'@angular-eslint/no-pipe-impure': 'error',
'@angular-eslint/prefer-output-readonly': 'error',
'@angular-eslint/use-component-selector': 'off',
'@angular-eslint/use-component-view-encapsulation': 'off',
'@angular-eslint/no-input-rename': 'off',
'@angular-eslint/no-output-native': 'off',
'@typescript-eslint/array-type': [
'error',
{
default: 'array-simple'
}
],
'@typescript-eslint/ban-types': [
'off',
{
types: {
String: {
message: 'Use string instead.'
},
Number: {
message: 'Use number instead.'
},
Boolean: {
message: 'Use boolean instead.'
},
Function: {
message: 'Use specific callable interface instead.'
}
}
}
],
'import/no-duplicates': 'error',
'import/no-unused-modules': 'error',
'import/no-unassigned-import': 'error',
'import/order': [
'error',
{
alphabetize: { order: 'asc', caseInsensitive: false },
'newlines-between': 'always',
groups: ['external', 'internal', ['parent', 'sibling', 'index']],
pathGroups: [],
pathGroupsExcludedImportTypes: []
}
],
'@typescript-eslint/no-this-alias': 'error',
'@typescript-eslint/member-ordering': 'off',
'no-irregular-whitespace': 'error',
'no-multiple-empty-lines': 'error',
'no-sparse-arrays': 'error',
'prefer-object-spread': 'error',
'prefer-template': 'error',
'prefer-const': 'off',
'max-len': 'off'
}
},
{
files: ['*.html'],
extends: ['plugin:@angular-eslint/template/recommended'],
rules: {}
},
{
files: ['*.html'],
excludedFiles: ['*inline-template-*.component.html'],
extends: ['plugin:prettier/recommended'],
rules: {
'prettier/prettier': ['error', { parser: 'angular' }],
'@angular-eslint/template/eqeqeq': 'off'
}
}
]
};

@ -0,0 +1,5 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
export NODE_OPTIONS="--max-old-space-size=4096"
npx --no-install tsc -p tsconfig.app.json --noEmit
npx --no-install lint-staged

@ -0,0 +1,5 @@
.github
node_modules
dist
tmp

@ -0,0 +1,18 @@
# add files you wish to ignore here
**/*.md
**/*.svg
**/test.ts
.stylelintrc
.prettierrc
src/assets/*
src/index.html
node_modules/
.vscode/
coverage/
dist/
package.json
tslint.json
_cli-tpl/**/*

@ -0,0 +1,13 @@
module.exports = {
singleQuote: true,
useTabs: false,
printWidth: 140,
tabWidth: 2,
semi: true,
htmlWhitespaceSensitivity: 'strict',
arrowParens: 'avoid',
bracketSpacing: true,
proseWrap: 'preserve',
trailingComma: 'none',
endOfLine: 'lf'
};

@ -0,0 +1,38 @@
{
"extends": [
"stylelint-config-standard",
"stylelint-config-rational-order",
"stylelint-config-prettier"
],
"customSyntax": "postcss-less",
"plugins": [
"stylelint-order",
"stylelint-declaration-block-no-ignored-properties"
],
"rules": {
"function-no-unknown": null,
"no-descending-specificity": null,
"plugin/declaration-block-no-ignored-properties": true,
"selector-type-no-unknown": [
true,
{
"ignoreTypes": [
"/^g2-/",
"/^nz-/",
"/^app-/"
]
}
],
"selector-pseudo-element-no-unknown": [
true,
{
"ignorePseudoElements": [
"ng-deep"
]
}
]
},
"ignoreFiles": [
"src/assets/**/*"
]
}

@ -0,0 +1,5 @@
{
"recommendations": [
"cipchk.ng-alain-extension-pack"
]
}

@ -0,0 +1,16 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "chrome",
"request": "launch",
"name": "Launch Chrome against localhost",
"url": "http://localhost:4200",
"webRoot": "${workspaceRoot}",
"sourceMaps": true
}
]
}

@ -0,0 +1,37 @@
{
"typescript.tsdk": "./node_modules/typescript/lib",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
// For ESLint
"source.fixAll.eslint": true,
// For Stylelint
"source.fixAll.stylelint": true
},
"[markdown]": {
"editor.formatOnSave": false
},
"[javascript]": {
"editor.formatOnSave": false
},
"[json]": {
"editor.formatOnSave": false
},
"[jsonc]": {
"editor.formatOnSave": false
},
"files.watcherExclude": {
"**/.git/*/**": true,
"**/node_modules/*/**": true,
"**/dist/*/**": true,
"**/coverage/*/**": true
},
"files.associations": {
"*.json": "jsonc",
".prettierrc": "jsonc",
".stylelintrc": "jsonc"
},
// Angular schematics : https://marketplace.visualstudio.com/items?itemName=cyrilletuzi.angular-schematics
"ngschematics.schematics": [
"ng-alain"
]
}

@ -0,0 +1,29 @@
#YangLiu Frontend Docker Build
FROM node:16.14.2
LABEL authors="YangLiu <YangLiusupport@163.com>"
WORKDIR /usr/src/app
COPY package.json package.json
RUN npm config set registry https://registry.npm.taobao.org \
&& npm i
COPY ./src ./src
RUN npm install -g @angular/cli
RUN ng build --prod --base-href /maxkey/
FROM nginx
COPY ./nginx.conf /etc/nginx/conf.d/default.conf
#RUN rm -rf /usr/share/nginx/html/*
COPY dist /usr/share/nginx/html/maxkey
#CMD ["nginx", "-g", "daemon off;"]
EXPOSE 8527

@ -0,0 +1 @@
[Document](https://ng-alain.com/cli/generate#Custom-template-page)

@ -0,0 +1,24 @@
import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';
import { <%= componentName %> } from './<%= dasherize(name) %>.component';
describe('<%= componentName %>', () => {
let component: <%= componentName %>;
let fixture: ComponentFixture<<%= componentName %>>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [ <%= componentName %> ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(<%= componentName %>);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

@ -0,0 +1,18 @@
import { Component, OnInit<% if(!!viewEncapsulation) { %>, ViewEncapsulation<% }%><% if(changeDetection !== 'Default') { %>, ChangeDetectionStrategy<% }%> } from '@angular/core';
import { _HttpClient } from '@delon/theme';
import { NzMessageService } from 'ng-zorro-antd/message';
@Component({
selector: '<%= selector %>',
templateUrl: './<%= dasherize(name) %>.component.html',<% if(!inlineStyle) { %><% } else { %>
styleUrls: ['./<%= dasherize(name) %>.component.<%= styleext %>']<% } %><% if(!!viewEncapsulation) { %>,
encapsulation: ViewEncapsulation.<%= viewEncapsulation %><% } if (changeDetection !== 'Default') { %>,
changeDetection: ChangeDetectionStrategy.<%= changeDetection %><% } %>
})
export class <%= componentName %> implements OnInit {
constructor(private http: _HttpClient, private msg: NzMessageService) { }
ngOnInit() { }
}

@ -0,0 +1 @@
[Document](https://ng-alain.com/mock)

@ -0,0 +1,282 @@
/*
* Copyright [2022] [YangLiu of copyright http://www.maxkey.top]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { MockRequest, MockStatusError } from '@delon/mock';
// region: mock data
const titles = ['Alipay', 'Angular', 'Ant Design', 'Ant Design Pro', 'Bootstrap', 'React', 'Vue', 'Webpack'];
const avatars = [
'https://gw.alipayobjects.com/zos/rmsportal/WdGqmHpayyMjiEhcKoVE.png', // Alipay
'https://gw.alipayobjects.com/zos/rmsportal/zOsKZmFRdUtvpqCImOVY.png', // Angular
'https://gw.alipayobjects.com/zos/rmsportal/dURIMkkrRFpPgTuzkwnB.png', // Ant Design
'https://gw.alipayobjects.com/zos/rmsportal/sfjbOqnsXXJgNCjCzDBL.png', // Ant Design Pro
'https://gw.alipayobjects.com/zos/rmsportal/siCrBXXhmvTQGWPNLBow.png', // Bootstrap
'https://gw.alipayobjects.com/zos/rmsportal/kZzEzemZyKLKFsojXItE.png', // React
'https://gw.alipayobjects.com/zos/rmsportal/ComBAopevLwENQdKWiIn.png', // Vue
'https://gw.alipayobjects.com/zos/rmsportal/nxkuOJlFJuAUhzlMTCEe.png', // Webpack
];
const covers = [
'https://gw.alipayobjects.com/zos/rmsportal/HrxcVbrKnCJOZvtzSqjN.png',
'https://gw.alipayobjects.com/zos/rmsportal/alaPpKWajEbIYEUvvVNf.png',
'https://gw.alipayobjects.com/zos/rmsportal/RLwlKSYGSXGHuWSojyvp.png',
'https://gw.alipayobjects.com/zos/rmsportal/gLaIAoVWTtLbBWZNYEMg.png',
];
const desc = [
'那是一种内在的东西, 他们到达不了,也无法触及的',
'希望是一个好东西,也许是最好的,好东西是不会消亡的',
'生命就像一盒巧克力,结果往往出人意料',
'城镇中有那么多的酒馆,她却偏偏走进了我的酒馆',
'那时候我只会想自己想要什么,从不想自己拥有什么',
];
const user = ['卡色', 'cipchk', '付小小', '曲丽丽', '林东东', '周星星', '吴加好', '朱偏右', '鱼酱', '乐哥', '谭小仪', '仲尼'];
// endregion
function getFakeList(count: number = 20): any[] {
const list: any[] = [];
for (let i = 0; i < count; i += 1) {
list.push({
id: `fake-list-${i}`,
owner: user[i % 10],
title: titles[i % 8],
avatar: avatars[i % 8],
cover: parseInt((i / 4).toString(), 10) % 2 === 0 ? covers[i % 4] : covers[3 - (i % 4)],
status: ['active', 'exception', 'normal'][i % 3],
percent: Math.ceil(Math.random() * 50) + 50,
logo: avatars[i % 8],
href: 'https://ant.design',
updatedAt: new Date(new Date().getTime() - 1000 * 60 * 60 * 2 * i),
createdAt: new Date(new Date().getTime() - 1000 * 60 * 60 * 2 * i),
subDescription: desc[i % 5],
description:
'在中台产品的研发过程中,会出现不同的设计规范和实现方式,但其中往往存在很多类似的页面和组件,这些类似的组件会被抽离成一套标准规范。',
activeUser: Math.ceil(Math.random() * 100000) + 100000,
newUser: Math.ceil(Math.random() * 1000) + 1000,
star: Math.ceil(Math.random() * 100) + 100,
like: Math.ceil(Math.random() * 100) + 100,
message: Math.ceil(Math.random() * 10) + 10,
content:
'段落示意:蚂蚁金服设计平台 ant.design用最小的工作量无缝接入蚂蚁金服生态提供跨越设计与开发的体验解决方案。蚂蚁金服设计平台 ant.design用最小的工作量无缝接入蚂蚁金服生态提供跨越设计与开发的体验解决方案。',
members: [
{
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ZiESqWwCXBRQoaPONSJe.png',
name: '曲丽丽',
},
{
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/tBOxZPlITHqwlGjsJWaF.png',
name: '王昭君',
},
{
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/sBxjgqiuHMGRkIjqlQCd.png',
name: '董娜娜',
},
],
});
}
return list;
}
function getNotice(): any[] {
return [
{
id: 'xxx1',
title: titles[0],
logo: avatars[0],
description: '那是一种内在的东西, 他们到达不了,也无法触及的',
updatedAt: new Date(),
member: '科学搬砖组',
href: '',
memberLink: '',
},
{
id: 'xxx2',
title: titles[1],
logo: avatars[1],
description: '希望是一个好东西,也许是最好的,好东西是不会消亡的',
updatedAt: new Date('2017-07-24'),
member: '全组都是吴彦祖',
href: '',
memberLink: '',
},
{
id: 'xxx3',
title: titles[2],
logo: avatars[2],
description: '城镇中有那么多的酒馆,她却偏偏走进了我的酒馆',
updatedAt: new Date(),
member: '中二少女团',
href: '',
memberLink: '',
},
{
id: 'xxx4',
title: titles[3],
logo: avatars[3],
description: '那时候我只会想自己想要什么,从不想自己拥有什么',
updatedAt: new Date('2017-07-23'),
member: '程序员日常',
href: '',
memberLink: '',
},
{
id: 'xxx5',
title: titles[4],
logo: avatars[4],
description: '凛冬将至',
updatedAt: new Date('2017-07-23'),
member: '高逼格设计天团',
href: '',
memberLink: '',
},
{
id: 'xxx6',
title: titles[5],
logo: avatars[5],
description: '生命就像一盒巧克力,结果往往出人意料',
updatedAt: new Date('2017-07-23'),
member: '骗你来学计算机',
href: '',
memberLink: '',
},
];
}
function getActivities(): any[] {
return [
{
id: 'trend-1',
updatedAt: new Date(),
user: {
name: '林东东',
avatar: avatars[0],
},
group: {
name: '高逼格设计天团',
link: 'http://github.com/',
},
project: {
name: '六月迭代',
link: 'http://github.com/',
},
template: '在 @{group} 新建项目 @{project}',
},
{
id: 'trend-2',
updatedAt: new Date(),
user: {
name: '付小小',
avatar: avatars[1],
},
group: {
name: '高逼格设计天团',
link: 'http://github.com/',
},
project: {
name: '六月迭代',
link: 'http://github.com/',
},
template: '在 @{group} 新建项目 @{project}',
},
{
id: 'trend-3',
updatedAt: new Date(),
user: {
name: '曲丽丽',
avatar: avatars[2],
},
group: {
name: '中二少女团',
link: 'http://github.com/',
},
project: {
name: '六月迭代',
link: 'http://github.com/',
},
template: '在 @{group} 新建项目 @{project}',
},
{
id: 'trend-4',
updatedAt: new Date(),
user: {
name: '周星星',
avatar: avatars[3],
},
project: {
name: '5 月日常迭代',
link: 'http://github.com/',
},
template: '将 @{project} 更新至已发布状态',
},
{
id: 'trend-5',
updatedAt: new Date(),
user: {
name: '朱偏右',
avatar: avatars[4],
},
project: {
name: '工程效能',
link: 'http://github.com/',
},
comment: {
name: '留言',
link: 'http://github.com/',
},
template: '在 @{project} 发布了 @{comment}',
},
{
id: 'trend-6',
updatedAt: new Date(),
user: {
name: '乐哥',
avatar: avatars[5],
},
group: {
name: '程序员日常',
link: 'http://github.com/',
},
project: {
name: '品牌迭代',
link: 'http://github.com/',
},
template: '在 @{group} 新建项目 @{project}',
},
];
}
export const APIS = {
'/api/list': (req: MockRequest) => getFakeList(req.queryString.count),
'/api/notice': () => getNotice(),
'/api/activities': () => getActivities(),
'POST /api/auth/refresh': { msg: 'ok', token: 'new-token-by-refresh' },
'/api/401': () => {
throw new MockStatusError(401);
},
'/api/403': () => {
throw new MockStatusError(403);
},
'/api/404': () => {
throw new MockStatusError(404);
},
'/api/500': () => {
throw new MockStatusError(500);
},
};

@ -0,0 +1,222 @@
/*
* Copyright [2022] [YangLiu of copyright http://www.maxkey.top]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { format } from 'date-fns';
import * as Mock from 'mockjs';
// region: mock data
const visitData: any[] = [];
const beginDay = new Date().getTime();
const fakeY = [7, 5, 4, 2, 4, 7, 5, 6, 5, 9, 6, 3, 1, 5, 3, 6, 5];
for (let i = 0; i < fakeY.length; i += 1) {
visitData.push({
x: format(new Date(beginDay + 1000 * 60 * 60 * 24 * i), 'yyyy-MM-dd'),
y: fakeY[i]
});
}
const visitData2: any[] = [];
const fakeY2 = [1, 6, 4, 8, 3, 7, 2];
for (let i = 0; i < fakeY2.length; i += 1) {
visitData2.push({
x: format(new Date(beginDay + 1000 * 60 * 60 * 24 * i), 'yyyy-MM-dd'),
y: fakeY2[i]
});
}
const salesData: any[] = [];
for (let i = 0; i < 12; i += 1) {
salesData.push({
x: `${i + 1}`,
y: Math.floor(Math.random() * 1000) + 200
});
}
const searchData: any[] = [];
for (let i = 0; i < 50; i += 1) {
searchData.push({
index: i + 1,
keyword: `搜索关键词-${i}`,
count: Math.floor(Math.random() * 1000),
range: Math.floor(Math.random() * 100),
status: Math.floor((Math.random() * 10) % 2)
});
}
const salesTypeData = [
{
x: '家用电器',
y: 4544
},
{
x: '食用酒水',
y: 3321
},
{
x: '个护健康',
y: 3113
},
{
x: '服饰箱包',
y: 2341
},
{
x: '母婴产品',
y: 1231
},
{
x: '其他',
y: 1231
}
];
const salesTypeDataOnline = [
{
x: '家用电器',
y: 244
},
{
x: '食用酒水',
y: 321
},
{
x: '个护健康',
y: 311
},
{
x: '服饰箱包',
y: 41
},
{
x: '母婴产品',
y: 121
},
{
x: '其他',
y: 111
}
];
const salesTypeDataOffline = [
{
x: '家用电器',
y: 99
},
{
x: '个护健康',
y: 188
},
{
x: '服饰箱包',
y: 344
},
{
x: '母婴产品',
y: 255
},
{
x: '其他',
y: 65
}
];
const offlineData: any[] = [];
for (let i = 0; i < 10; i += 1) {
offlineData.push({
name: `门店${i}`,
cvr: Math.ceil(Math.random() * 9) / 10
});
}
const offlineChartData: any[] = [];
for (let i = 0; i < 20; i += 1) {
offlineChartData.push({
time: new Date().getTime() + 1000 * 60 * 30 * i,
y1: Math.floor(Math.random() * 100) + 10,
y2: Math.floor(Math.random() * 100) + 10
});
}
const radarOriginData = [
{
name: '个人',
ref: 10,
koubei: 8,
output: 4,
contribute: 5,
hot: 7
},
{
name: '团队',
ref: 3,
koubei: 9,
output: 6,
contribute: 3,
hot: 1
},
{
name: '部门',
ref: 4,
koubei: 1,
output: 6,
contribute: 5,
hot: 7
}
];
//
const radarData: any[] = [];
const radarTitleMap: any = {
ref: '引用',
koubei: '口碑',
output: '产量',
contribute: '贡献',
hot: '热度'
};
radarOriginData.forEach((item: any) => {
Object.keys(item).forEach(key => {
if (key !== 'name') {
radarData.push({
name: item.name,
label: radarTitleMap[key],
value: item[key]
});
}
});
});
// endregion
export const CHARTS = {
'/chart': JSON.parse(
JSON.stringify({
visitData,
visitData2,
salesData,
searchData,
offlineData,
offlineChartData,
salesTypeData,
salesTypeDataOnline,
salesTypeDataOffline,
radarData
})
),
'/chart/visit': JSON.parse(JSON.stringify(visitData)),
'/chart/tags': Mock.mock({
'list|100': [{ name: '@city', 'value|1-100': 150 }]
})
};

@ -0,0 +1,93 @@
/*
* Copyright [2022] [YangLiu of copyright http://www.maxkey.top]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { MockRequest } from '@delon/mock';
const DATA = [
{
name: '上海',
id: '310000',
},
{
name: '市辖区',
id: '310100',
},
{
name: '北京',
id: '110000',
},
{
name: '市辖区',
id: '110100',
},
{
name: '浙江省',
id: '330000',
},
{
name: '杭州市',
id: '330100',
},
{
name: '宁波市',
id: '330200',
},
{
name: '温州市',
id: '330300',
},
{
name: '嘉兴市',
id: '330400',
},
{
name: '湖州市',
id: '330500',
},
{
name: '绍兴市',
id: '330600',
},
{
name: '金华市',
id: '330700',
},
{
name: '衢州市',
id: '330800',
},
{
name: '舟山市',
id: '330900',
},
{
name: '台州市',
id: '331000',
},
{
name: '丽水市',
id: '331100',
},
];
export const GEOS = {
'/geo/province': () => DATA.filter(w => w.id.endsWith('0000')),
'/geo/:id': (req: MockRequest) => {
const pid = (req.params.id || '310000').slice(0, 2);
return DATA.filter(w => w.id.slice(0, 2) === pid && !w.id.endsWith('0000'));
},
};

@ -0,0 +1,78 @@
/*
* Copyright [2022] [YangLiu of copyright http://www.maxkey.top]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export const POIS = {
'/pois': {
total: 2,
list: [
{
id: 10000,
user_id: 1,
name: '测试品牌',
branch_name: '测试分店',
geo: 310105,
country: '中国',
province: '上海',
city: '上海市',
district: '长宁区',
address: '中山公园',
tel: '15900000000',
categories: '美食,粤菜,湛江菜',
lng: 121.41707989151003,
lat: 31.218656214644792,
recommend: '推荐品',
special: '特色服务',
introduction: '商户简介',
open_time: '营业时间',
avg_price: 260,
reason: null,
status: 1,
status_str: '待审核',
status_wx: 1,
modified: 1505826527288,
created: 1505826527288,
},
{
id: 10001,
user_id: 2,
name: '测试品牌2',
branch_name: '测试分店2',
geo: 310105,
country: '中国',
province: '上海',
city: '上海市',
district: '长宁区',
address: '中山公园',
tel: '15900000000',
categories: '美食,粤菜,湛江菜',
lng: 121.41707989151003,
lat: 31.218656214644792,
recommend: '推荐品',
special: '特色服务',
introduction: '商户简介',
open_time: '营业时间',
avg_price: 260,
reason: null,
status: 1,
status_str: '待审核',
status_wx: 1,
modified: 1505826527288,
created: 1505826527288,
},
],
},
};

@ -0,0 +1,169 @@
/*
* Copyright [2022] [YangLiu of copyright http://www.maxkey.top]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const basicGoods = [
{
id: '1234561',
name: '矿泉水 550ml',
barcode: '12421432143214321',
price: '2.00',
num: '1',
amount: '2.00',
},
{
id: '1234562',
name: '凉茶 300ml',
barcode: '12421432143214322',
price: '3.00',
num: '2',
amount: '6.00',
},
{
id: '1234563',
name: '好吃的薯片',
barcode: '12421432143214323',
price: '7.00',
num: '4',
amount: '28.00',
},
{
id: '1234564',
name: '特别好吃的蛋卷',
barcode: '12421432143214324',
price: '8.50',
num: '3',
amount: '25.50',
},
];
const basicProgress = [
{
key: '1',
time: '2017-10-01 14:10',
rate: '联系客户',
status: 'processing',
operator: '取货员 ID1234',
cost: '5mins',
},
{
key: '2',
time: '2017-10-01 14:05',
rate: '取货员出发',
status: 'success',
operator: '取货员 ID1234',
cost: '1h',
},
{
key: '3',
time: '2017-10-01 13:05',
rate: '取货员接单',
status: 'success',
operator: '取货员 ID1234',
cost: '5mins',
},
{
key: '4',
time: '2017-10-01 13:00',
rate: '申请审批通过',
status: 'success',
operator: '系统',
cost: '1h',
},
{
key: '5',
time: '2017-10-01 12:00',
rate: '发起退货申请',
status: 'success',
operator: '用户',
cost: '5mins',
},
];
const advancedOperation1 = [
{
key: 'op1',
type: '订购关系生效',
name: '曲丽丽',
status: 'agree',
updatedAt: '2017-10-03 19:23:12',
memo: '-',
},
{
key: 'op2',
type: '财务复审',
name: '付小小',
status: 'reject',
updatedAt: '2017-10-03 19:23:12',
memo: '不通过原因',
},
{
key: 'op3',
type: '部门初审',
name: '周毛毛',
status: 'agree',
updatedAt: '2017-10-03 19:23:12',
memo: '-',
},
{
key: 'op4',
type: '提交订单',
name: '林东东',
status: 'agree',
updatedAt: '2017-10-03 19:23:12',
memo: '很棒',
},
{
key: 'op5',
type: '创建订单',
name: '汗牙牙',
status: 'agree',
updatedAt: '2017-10-03 19:23:12',
memo: '-',
},
];
const advancedOperation2 = [
{
key: 'op1',
type: '订购关系生效',
name: '曲丽丽',
status: 'agree',
updatedAt: '2017-10-03 19:23:12',
memo: '-',
},
];
const advancedOperation3 = [
{
key: 'op1',
type: '创建订单',
name: '汗牙牙',
status: 'agree',
updatedAt: '2017-10-03 19:23:12',
memo: '-',
},
];
export const PROFILES = {
'GET /profile/progress': basicProgress,
'GET /profile/goods': basicGoods,
'GET /profile/advanced': {
advancedOperation1,
advancedOperation2,
advancedOperation3,
},
};

@ -0,0 +1,99 @@
/*
* Copyright [2022] [YangLiu of copyright http://www.maxkey.top]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { HttpRequest } from '@angular/common/http';
import { MockRequest } from '@delon/mock';
const list: any[] = [];
for (let i = 0; i < 46; i += 1) {
list.push({
key: i,
disabled: i % 6 === 0,
href: 'https://ant.design',
avatar: [
'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png',
'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png',
][i % 2],
no: `TradeCode ${i}`,
title: `一个任务名称 ${i}`,
owner: '曲丽丽',
description: '这是一段描述',
callNo: Math.floor(Math.random() * 1000),
status: Math.floor(Math.random() * 10) % 4,
updatedAt: new Date(`2017-07-${i < 18 ? '0' + (Math.floor(i / 2) + 1) : Math.floor(i / 2) + 1}`),
createdAt: new Date(`2017-07-${i < 18 ? '0' + (Math.floor(i / 2) + 1) : Math.floor(i / 2) + 1}`),
progress: Math.ceil(Math.random() * 100),
});
}
function getRule(params: any): any[] {
let ret = [...list];
if (params.sorter) {
const s = params.sorter.split('_');
ret = ret.sort((prev, next) => {
if (s[1] === 'descend') {
return next[s[0]] - prev[s[0]];
}
return prev[s[0]] - next[s[0]];
});
}
if (params.statusList && params.statusList.length > 0) {
ret = ret.filter((data) => params.statusList.indexOf(data.status) > -1);
}
if (params.no) {
ret = ret.filter((data) => data.no.indexOf(params.no) > -1);
}
return ret;
}
function removeRule(nos: string): boolean {
nos.split(',').forEach((no) => {
const idx = list.findIndex((w) => w.no === no);
if (idx !== -1) {
list.splice(idx, 1);
}
});
return true;
}
function saveRule(description: string): void {
const i = Math.ceil(Math.random() * 10000);
list.unshift({
key: i,
href: 'https://ant.design',
avatar: [
'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png',
'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png',
][i % 2],
no: `TradeCode ${i}`,
title: `一个任务名称 ${i}`,
owner: '曲丽丽',
description,
callNo: Math.floor(Math.random() * 1000),
status: Math.floor(Math.random() * 10) % 2,
updatedAt: new Date(),
createdAt: new Date(),
progress: Math.ceil(Math.random() * 100),
});
}
export const RULES = {
'/rule': (req: MockRequest) => getRule(req.queryString),
'DELETE /rule': (req: MockRequest) => removeRule(req.queryString.nos),
'POST /rule': (req: MockRequest) => saveRule(req.body.description),
};

@ -0,0 +1,139 @@
/*
* Copyright [2022] [MaxKey of copyright http://www.maxkey.top]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { MockRequest } from '@delon/mock';
const list: any[] = [];
const total = 50;
for (let i = 0; i < total; i += 1) {
list.push({
id: i + 1,
disabled: i % 6 === 0,
href: 'https://ant.design',
avatar: [
'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png',
'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png',
][i % 2],
no: `TradeCode ${i}`,
title: `一个任务名称 ${i}`,
owner: '曲丽丽',
description: '这是一段描述',
callNo: Math.floor(Math.random() * 1000),
status: Math.floor(Math.random() * 10) % 4,
updatedAt: new Date(`2017-07-${Math.floor(i / 2) + 1}`),
createdAt: new Date(`2017-07-${Math.floor(i / 2) + 1}`),
progress: Math.ceil(Math.random() * 100),
});
}
function genData(params: any): { total: number; list: any[] } {
let ret = [...list];
const pi = +params.pi;
const ps = +params.ps;
const start = (pi - 1) * ps;
if (params.no) {
ret = ret.filter((data) => data.no.indexOf(params.no) > -1);
}
return { total: ret.length, list: ret.slice(start, ps * pi) };
}
function saveData(id: number, value: any): { msg: string } {
const item = list.find((w) => w.id === id);
if (!item) {
return { msg: '无效用户信息' };
}
Object.assign(item, value);
return { msg: 'ok' };
}
export const USERS = {
'/user': (req: MockRequest) => genData(req.queryString),
'/user/:id': (req: MockRequest) => list.find((w) => w.id === +req.params.id),
'POST /user/:id': (req: MockRequest) => saveData(+req.params.id, req.body),
'/user/current': {
name: 'Cipchk',
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png',
userid: '00000001',
email: 'cipchk@qq.com',
signature: '海纳百川,有容乃大',
title: '交互专家',
group: '蚂蚁金服某某某事业群某某平台部某某技术部UED',
tags: [
{
key: '0',
label: '很有想法的',
},
{
key: '1',
label: '专注撩妹',
},
{
key: '2',
label: '帅~',
},
{
key: '3',
label: '通吃',
},
{
key: '4',
label: '专职后端',
},
{
key: '5',
label: '海纳百川',
},
],
notifyCount: 12,
country: 'China',
geographic: {
province: {
label: '上海',
key: '330000',
},
city: {
label: '市辖区',
key: '330100',
},
},
address: 'XX区XXX路 XX 号',
phone: '你猜-你猜你猜猜猜',
},
'POST /user/avatar': 'ok',
'POST /login/account': (req: MockRequest) => {
const data = req.body;
if (!(data.userName === 'admin' || data.userName === 'user') || data.password !== 'ng-alain.com') {
return { msg: `Invalid username or passwordadmin/ng-alain.com` };
}
return {
msg: 'ok',
user: {
token: '123456789',
name: data.userName,
email: `${data.userName}@qq.com`,
id: 10000,
time: +new Date(),
},
};
},
'POST /register': {
msg: 'ok',
},
};

@ -0,0 +1,24 @@
/*
* Copyright [2022] [YangLiu of copyright http://www.maxkey.top]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export * from './_profile';
export * from './_rule';
export * from './_api';
export * from './_chart';
export * from './_pois';
export * from './_user';
export * from './_geo';

@ -0,0 +1,132 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"ng-alain": {
"projectType": "application",
"root": "",
"sourceRoot": "src",
"prefix": "app",
"schematics": {
"@schematics/angular:component": {
"style": "less"
},
"@schematics/angular:application": {
"strict": true
}
},
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"baseHref": "/maxkey/",
"outputPath": "dist",
"index": "src/index.html",
"main": "src/main.ts",
"tsConfig": "tsconfig.app.json",
"polyfills": "src/polyfills.ts",
"assets": ["src/assets", "src/favicon.ico"],
"styles": ["src/styles.less"],
"scripts": [],
"allowedCommonJsDependencies": ["ajv", "ajv-formats"],
"stylePreprocessorOptions": {
"includePaths": [
"node_modules/"
]
}
},
"configurations": {
"production": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"outputHashing": "all",
"budgets": [
{
"type": "initial",
"maximumWarning": "2mb",
"maximumError": "6mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "6kb",
"maximumError": "10kb"
}
]
},
"development": {
"buildOptimizer": false,
"optimization": false,
"vendorChunk": true,
"extractLicenses": false,
"sourceMap": true,
"namedChunks": true
}
},
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"browserTarget": "ng-alain:build",
"disableHostCheck": true,
"proxyConfig": "proxy.conf.js"
},
"configurations": {
"production": {
"browserTarget": "ng-alain:build:production"
},
"development": {
"browserTarget": "ng-alain:build:development"
}
},
"defaultConfiguration": "development"
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "ng-alain:build"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "src/test.ts",
"polyfills": "src/polyfills.ts",
"karmaConfig": "karma.conf.js",
"tsConfig": "tsconfig.spec.json",
"scripts": [],
"styles": [],
"assets": ["src/assets"]
}
},
"lint": {
"builder": "@angular-eslint/builder:lint",
"options": {
"lintFilePatterns": ["src/**/*.ts", "src/**/*.html"]
}
},
"e2e": {
"builder": "@angular-devkit/build-angular:protractor",
"options": {
"protractorConfig": "e2e/protractor.conf.js",
"devServerTarget": "ng-alain:serve"
},
"configurations": {
"production": {
"devServerTarget": "ng-alain:serve:production"
}
}
}
}
}
},
"defaultProject": "ng-alain",
"cli": {
"packageManager": "yarn"
}
}

@ -0,0 +1,156 @@
name: ng-alain
trigger:
- master
pool:
vmImage: 'ubuntu-latest'
pr:
autoCancel: true
branches:
exclude:
- gh-pages
stages:
- stage: Env
jobs:
- job: Nodes
steps:
- task: NodeTool@0
inputs:
versionSpec: '12.14.1'
displayName: 'Install Node.js'
- stage: build
dependsOn: env
jobs:
- job: Build
steps:
- script: yarn install
displayName: 'Install'
- script: |
node ./scripts/_ci/github-comment.js "RELEASE" "[Using release @delon, Preview Preparing...](https://dev.azure.com/ng-alain/ng-alain/_build/results?buildId=$(Build.BuildId))"
displayName: 'Comment on github'
env:
ACCESS_REPO: $(ACCESS_REPO)
ACCESS_TOKEN: $(ACCESS_TOKEN)
- task: Bash@3
displayName: 'Build'
inputs:
targetType: 'filePath'
filePath: './scripts/_ci/deploy-pipelines.sh'
- script: ls -al dist/
displayName: 'List build'
- script: |
export DEPLOY_DOMAIN=https://preview-${SYSTEM_PULLREQUEST_PULLREQUESTNUMBER}-ng-alain.surge.sh
echo "Deploy to $DEPLOY_DOMAIN"
cp ./dist/index.html ./dist/404.html
npx surge --project ./dist --domain $DEPLOY_DOMAIN
displayName: 'Deploy Site'
env:
ACCESS_REPO: $(ACCESS_REPO)
ACCESS_TOKEN: $(ACCESS_TOKEN)
SURGE_LOGIN: $(SURGE_LOGIN)
SURGE_TOKEN: $(SURGE_TOKEN)
- script: |
export DEPLOY_DOMAIN=https://preview-${SYSTEM_PULLREQUEST_PULLREQUESTNUMBER}-ng-alain.surge.sh
node ./scripts/_ci/github-comment.js "RELEASE" "[Using release @delon, Preview is ready!]($DEPLOY_DOMAIN)"
displayName: 'Update comment on github'
env:
ACCESS_REPO: $(ACCESS_REPO)
ACCESS_TOKEN: $(ACCESS_TOKEN)
- job: Build_Failed
dependsOn: Build
condition: failed()
steps:
- checkout: self
displayName: 'Checkout'
clean: true
fetchDepth: 1
- script: yarn install
displayName: 'Install'
- script: |
node ./scripts/_ci/github-comment.js "RELEASE" "[Using release @delon, Preview Failed](https://dev.azure.com/ng-alain/delon/_build/results?buildId=$(Build.BuildId))"
displayName: 'Comment on github'
env:
ACCESS_REPO: $(ACCESS_REPO)
ACCESS_TOKEN: $(ACCESS_TOKEN)
- stage: build_day
dependsOn: env
jobs:
- job: Build
steps:
- script: yarn install
displayName: 'Install'
- script: |
node ./scripts/_ci/github-comment.js "RELEASE_DAY" "[Using day release @delon, Preview Preparing...](https://dev.azure.com/ng-alain/ng-alain/_build/results?buildId=$(Build.BuildId))"
displayName: 'Comment on github'
env:
ACCESS_REPO: $(ACCESS_REPO)
ACCESS_TOKEN: $(ACCESS_TOKEN)
- task: Bash@3
displayName: 'Build'
inputs:
targetType: 'filePath'
filePath: './scripts/_ci/deploy-pipelines.sh'
arguments: '-day'
- script: ls -al dist/
displayName: 'List build'
- script: |
export DEPLOY_DOMAIN=https://preview-${SYSTEM_PULLREQUEST_PULLREQUESTNUMBER}-day-ng-alain.surge.sh
echo "Deploy to $DEPLOY_DOMAIN"
cp ./dist/index.html ./dist/404.html
npx surge --project ./dist --domain $DEPLOY_DOMAIN
displayName: 'Deploy Site'
env:
ACCESS_REPO: $(ACCESS_REPO)
ACCESS_TOKEN: $(ACCESS_TOKEN)
SURGE_LOGIN: $(SURGE_LOGIN)
SURGE_TOKEN: $(SURGE_TOKEN)
- script: |
export DEPLOY_DOMAIN=https://preview-${SYSTEM_PULLREQUEST_PULLREQUESTNUMBER}-day-ng-alain.surge.sh
node ./scripts/_ci/github-comment.js "RELEASE_DAY" "[Using day release @delon, Preview is ready!]($DEPLOY_DOMAIN)"
displayName: 'Update comment on github'
env:
ACCESS_REPO: $(ACCESS_REPO)
ACCESS_TOKEN: $(ACCESS_TOKEN)
- job: Build_Failed
dependsOn: Build
condition: failed()
steps:
- checkout: self
displayName: 'Checkout'
clean: true
fetchDepth: 1
- script: yarn install
displayName: 'Install'
- script: |
node ./scripts/_ci/github-comment.js "RELEASE_DAY" "[Using day release @delon, Preview Failed](https://dev.azure.com/ng-alain/delon/_build/results?buildId=$(Build.BuildId))"
displayName: 'Comment on github'
env:
ACCESS_REPO: $(ACCESS_REPO)
ACCESS_TOKEN: $(ACCESS_TOKEN)
- stage: lint
dependsOn:
- env
jobs:
- job: site
steps:
- script: yarn install
displayName: 'Install'
- script: |
npx stylelint --version
yarn run lint
- stage: test
dependsOn:
- env
jobs:
- job: site
steps:
- script: yarn install
displayName: 'Install'
- script: npx ng test --no-progress --browsers=ChromeHeadlessCI --code-coverage --no-watch

@ -0,0 +1,32 @@
// @ts-check
// Protractor configuration file, see link for more information
// https://github.com/angular/protractor/blob/master/lib/config.ts
const { SpecReporter } = require('jasmine-spec-reporter');
/**
* @type { import("protractor").Config }
*/
exports.config = {
allScriptsTimeout: 11000,
specs: [
'./src/**/*.e2e-spec.ts'
],
capabilities: {
'browserName': 'chrome'
},
directConnect: true,
baseUrl: 'http://localhost:4200/',
framework: 'jasmine',
jasmineNodeOpts: {
showColors: true,
defaultTimeoutInterval: 30000,
print: function() {}
},
onPrepare() {
require('ts-node').register({
project: require('path').join(__dirname, './tsconfig.json')
});
jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
}
};

@ -0,0 +1,45 @@
/*
* Copyright [2022] [YangLiu of copyright http://www.maxkey.top]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { browser, logging } from 'protractor';
import { AppPage } from './app.po';
describe('workspace-project App', () => {
let page: AppPage;
beforeEach(() => {
page = new AppPage();
});
it('should display welcome message', () => {
page.navigateTo();
expect(page.getTitleText()).toEqual('Welcome to ng8!');
});
afterEach(async () => {
// Assert that there are no errors emitted from the browser
const logs = await browser
.manage()
.logs()
.get(logging.Type.BROWSER);
expect(logs).not.toContain(
jasmine.objectContaining({
level: logging.Level.SEVERE,
} as logging.Entry),
);
});
});

@ -0,0 +1,28 @@
/*
* Copyright [2022] [YangLiu of copyright http://www.maxkey.top]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { browser, by, element } from 'protractor';
export class AppPage {
navigateTo(): Promise<any> {
return browser.get(browser.baseUrl) as Promise<any>;
}
getTitleText(): Promise<string> {
return element(by.css('app-root h1')).getText() as Promise<string>;
}
}

@ -0,0 +1,10 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/e2e",
"module": "commonjs",
"target": "es2018",
"types": ["jasmine", "jasminewd2", "node"]
}
}

@ -0,0 +1,38 @@
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage'),
require('@angular-devkit/build-angular/plugins/karma')
],
client: {
clearContext: false // leave Jasmine Spec Runner output visible in browser
},
coverageIstanbulReporter: {
dir: require('path').join(__dirname, './coverage'),
reports: ['html', 'lcovonly', 'text-summary'],
fixWebpackSourcePaths: true
},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
customLaunchers: {
ChromeHeadlessCI: {
base: 'ChromeHeadless',
flags: ['--no-sandbox']
}
},
singleRun: false,
restartOnFileChange: true
});
};

@ -0,0 +1,13 @@
{
"$schema": "./node_modules/ng-alain/schema.json",
"theme": {
"list": [
{
"theme": "dark"
},
{
"theme": "compact"
}
]
}
}

@ -0,0 +1,18 @@
#YangLiu Frontend Server
server {
listen 8527;
server_name localhost;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri$args $uri$args/ /maxkey/index.html;
#try_files $uri $uri/ /index.html;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,130 @@
{
"name": "YangLiu",
"version": "3.5.0",
"description": "Leading-Edge IAM Identity and Access Management",
"author": "YangLiu <support@maxsso.net>",
"repository": {
"type": "git",
"url": "https://gitee.com/dromara/MaxKey"
},
"bugs": {
"url": "https://gitee.com/dromara/MaxKey/issues"
},
"homepage": "https://www.maxkey.top",
"license": "Apache-2.0",
"keywords": [
"maxkey",
"antd",
"ng-zorro-antd",
"angular",
"component",
"scaffold"
],
"scripts": {
"npm:pre": "npm install --ignore-scripts --registry https://registry.npmmirror.com || npm install --ignore-scripts --registry https://registry.npmjs.org",
"ng-high-memory": "node --max_old_space_size=8000 ./node_modules/@angular/cli/bin/ng",
"ng": "ng",
"start": "ng s -o --serve-path=/maxkey/ --port=8527",
"hmr": "ng s -o --hmr",
"build": "npm run ng-high-memory build",
"analyze": "npm run ng-high-memory build -- --source-map",
"analyze:view": "source-map-explorer dist/**/*.js",
"lint": "npm run lint:ts && npm run lint:style",
"lint:ts": "ng lint --fix",
"lint:style": "npx stylelint 'src/**/*.less'",
"e2e": "ng e2e",
"test": "ng test --watch",
"test-coverage": "ng test --code-coverage --watch=false",
"color-less": "ng-alain-plugin-theme -t=colorLess",
"theme": "ng-alain-plugin-theme -t=themeCss",
"icon": "ng g ng-alain:plugin icon",
"prepare": "husky install"
},
"dependencies": {
"@angular/animations": "~13.3.0",
"@angular/common": "~13.3.0",
"@angular/compiler": "~13.3.0",
"@angular/core": "~13.3.0",
"@angular/forms": "~13.3.0",
"@angular/platform-browser": "~13.3.0",
"@angular/platform-browser-dynamic": "~13.3.0",
"@angular/router": "~13.3.0",
"@delon/abc": "^13.4.0",
"@delon/acl": "^13.4.0",
"@delon/auth": "^13.4.0",
"@delon/cache": "^13.4.0",
"@delon/chart": "^13.4.0",
"@delon/form": "^13.4.0",
"@delon/mock": "^13.4.0",
"@delon/theme": "^13.4.0",
"@delon/util": "^13.4.0",
"ajv": "^8.10.0",
"ajv-formats": "^2.1.1",
"crypto-js": "^4.1.1",
"monaco-editor": "^0.33.0",
"ng-zorro-antd": "^13.1.1",
"ngx-cookie-service": "^13.2.0",
"ngx-tinymce": "^13.0.0",
"ngx-ueditor": "^13.0.0",
"rxjs": "~7.5.0",
"screenfull": "^6.0.1",
"tslib": "^2.3.0",
"zone.js": "~0.11.4"
},
"devDependencies": {
"@angular-devkit/build-angular": "~13.3.0",
"@angular-eslint/builder": "~13.1.0",
"@angular-eslint/eslint-plugin": "~13.1.0",
"@angular-eslint/eslint-plugin-template": "~13.1.0",
"@angular-eslint/schematics": "~13.1.0",
"@angular-eslint/template-parser": "~13.1.0",
"@angular/cli": "~13.3.0",
"@angular/compiler-cli": "~13.3.0",
"@angular/language-service": "~13.3.0",
"@delon/testing": "^13.4.0",
"@types/crypto-js": "^4.1.1",
"@types/jasmine": "~3.10.0",
"@types/jasminewd2": "~2.0.10",
"@types/node": "^12.11.1",
"@typescript-eslint/eslint-plugin": "~5.15.0",
"@typescript-eslint/parser": "~5.15.0",
"eslint": "^8.11.0",
"eslint-config-prettier": "~8.5.0",
"eslint-plugin-import": "~2.25.4",
"eslint-plugin-jsdoc": "~38.0.4",
"eslint-plugin-prefer-arrow": "~1.2.3",
"eslint-plugin-prettier": "~4.0.0",
"husky": "^7.0.4",
"jasmine-core": "~4.0.0",
"jasmine-spec-reporter": "^7.0.0",
"karma": "~6.3.0",
"karma-chrome-launcher": "~3.1.0",
"karma-coverage": "~2.1.0",
"karma-jasmine": "~4.0.0",
"karma-jasmine-html-reporter": "~1.7.0",
"lint-staged": "^12.3.7",
"ng-alain": "13.4.0",
"ng-alain-plugin-theme": "^13.0.3",
"ng-alain-sts": "^0.0.2",
"node-fetch": "^2.6.1",
"prettier": "^2.6.0",
"protractor": "~7.0.0",
"source-map-explorer": "^2.5.2",
"stylelint": "^14.6.0",
"stylelint-config-prettier": "^9.0.3",
"stylelint-config-rational-order": "^0.1.2",
"stylelint-config-standard": "^25.0.0",
"stylelint-declaration-block-no-ignored-properties": "^2.5.0",
"stylelint-order": "^5.0.0",
"ts-node": "~9.1.1",
"typescript": "~4.6.2"
},
"lint-staged": {
"(src)/**/*.{html,ts}": [
"eslint --fix"
],
"(src)/**/*.less": [
"npm run lint:style"
]
}
}

@ -0,0 +1,17 @@
/**
* For more configuration, please refer to https://angular.io/guide/build#proxying-to-a-backend-server
*
* 更多配置描述请参考 https://angular.cn/guide/build#proxying-to-a-backend-server
*
* Note: The proxy is only valid for real requests, Mock does not actually generate requests, so the priority of Mock will be higher than the proxy
*/
module.exports = {
/**
* The following means that all requests are directed to the backend `https://localhost:9000/`
*/
// '/': {
// target: 'https://localhost:9000/',
// secure: false, // Ignore invalid SSL certificates
// changeOrigin: true
// }
};

@ -0,0 +1,17 @@
#!/usr/bin/env bash
# bash ./scripts/_ci/delon.sh
set -e
cd $(dirname $0)/../..
echo "Download latest @delon version"
rm -rf delon-builds
git clone --depth 1 https://github.com/ng-alain/delon-builds.git
rm -rf node_modules/@delon
rm -rf node_modules/ng-alain
rsync -am delon-builds/ node_modules/
NG_ALAIN_VERSION=$(node -p "require('./node_modules/ng-alain/package.json').version")
rm -rf delon-builds
echo "Using ng-alain version: ${NG_ALAIN_VERSION}"

@ -0,0 +1,60 @@
#!/usr/bin/env bash
set -e
GH=false
DAY=false
for ARG in "$@"; do
case "$ARG" in
-gh)
GH=true
;;
-day)
DAY=true
;;
esac
done
echo "List:"
ls -al
ROOT_DIR="$(pwd)"
DIST_DIR="$(pwd)/dist"
VERSION=$(node -p "require('./package.json').version")
echo "Start build version: ${VERSION}"
if [[ ${DAY} == true ]]; then
echo ""
echo "Download day @delon/* libs"
echo ""
bash ./scripts/_ci/delon.sh
fi
echo ""
echo "Generate color less"
echo ""
npm run color-less
echo ""
echo "Generate theme files"
echo ""
npm run theme
echo '===== need mock'
cp -f ${ROOT_DIR}/src/environments/environment.ts ${ROOT_DIR}/src/environments/environment.prod.ts
sed -i 's/production: false/production: true/g' ${ROOT_DIR}/src/environments/environment.prod.ts
sed -i 's/showSettingDrawer = !environment.production;/showSettingDrawer = true;/g' ${ROOT_DIR}/src/app/layout/basic/basic.component.ts
if [[ ${GH} == true ]]; then
echo "Build angular [github gh-pages]"
node --max_old_space_size=5120 ./node_modules/@angular/cli/bin/ng build --base-href /ng-alain/
else
echo "Build angular"
node --max_old_space_size=5120 ./node_modules/@angular/cli/bin/ng build
fi
cp -f ${DIST_DIR}/index.html ${DIST_DIR}/404.html
echo "Finished"

@ -0,0 +1,61 @@
const fetch = require('node-fetch');
const REPO = process.env.ACCESS_REPO;
const TOKEN = process.env.ACCESS_TOKEN;
const PR = process.env.SYSTEM_PULLREQUEST_PULLREQUESTNUMBER;
const argv = process.argv;
const tag = argv[argv.length - 2];
const comment = argv[argv.length - 1];
const REPLACE_MARK = `<!-- AZURE_UPDATE_COMMENT_${tag} -->`;
const wrappedComment = `
${REPLACE_MARK}
${comment}
`.trim();
async function withGithub(url, json, method) {
const res = await fetch(url, {
method: method || (json ? 'POST' : 'GET'),
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
Authorization: `Basic ${Buffer.from(TOKEN).toString('base64')}`,
},
body: json ? JSON.stringify(json) : undefined,
});
return res.json();
}
(async function run() {
if (PR == null) {
console.log('未获取到PR忽略处理')
return;
}
const comments = await withGithub(`https://api.github.com/repos/${REPO}/issues/${PR}/comments`);
// Find my comment
const updateComment = comments.find(({ body }) => body.includes(REPLACE_MARK));
// eslint-disable-next-line no-console
console.log('Origin comment:', updateComment);
// Update
let res;
if (!updateComment) {
res = await withGithub(`https://api.github.com/repos/${REPO}/issues/${PR}/comments`, {
body: wrappedComment,
});
} else {
res = await withGithub(
`https://api.github.com/repos/${REPO}/issues/comments/${updateComment.id}`,
{
body: wrappedComment,
},
'PATCH',
);
}
// eslint-disable-next-line no-console
console.log(res);
})();

@ -0,0 +1,62 @@
/*
* Copyright [2022] [YangLiu of copyright http://www.yangliu.top]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Component, ElementRef, OnInit, Renderer2 } from '@angular/core';
import { NavigationEnd, NavigationError, RouteConfigLoadStart, Router } from '@angular/router';
import { TitleService, VERSION as VERSION_ALAIN } from '@delon/theme';
import { environment } from '@env/environment';
import { NzModalService } from 'ng-zorro-antd/modal';
import { VERSION as VERSION_ZORRO } from 'ng-zorro-antd/version';
@Component({
selector: 'app-root',
template: ` <router-outlet></router-outlet> `
})
export class AppComponent implements OnInit {
constructor(
el: ElementRef,
renderer: Renderer2,
private router: Router,
private titleSrv: TitleService,
private modalSrv: NzModalService
) {
renderer.setAttribute(el.nativeElement, 'ng-alain-version', VERSION_ALAIN.full);
renderer.setAttribute(el.nativeElement, 'ng-zorro-version', VERSION_ZORRO.full);
}
ngOnInit(): void {
let configLoad = false;
this.router.events.subscribe(ev => {
if (ev instanceof RouteConfigLoadStart) {
configLoad = true;
}
if (configLoad && ev instanceof NavigationError) {
this.modalSrv.confirm({
nzTitle: `提醒`,
nzContent: environment.production ? `应用可能已发布新版本,请点击刷新才能生效。` : `无法加载路由:${ev.url}`,
nzCancelDisabled: false,
nzOkText: '刷新',
nzCancelText: '忽略',
nzOnOk: () => location.reload()
});
}
if (ev instanceof NavigationEnd) {
this.titleSrv.setTitle();
this.modalSrv.closeAll();
}
});
}
}

@ -0,0 +1,125 @@
/*
* Copyright [2022] [YangLiu of copyright http://www.yangliu.top]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* eslint-disable import/order */
/* eslint-disable import/no-duplicates */
import { HttpClientModule } from '@angular/common/http';
import { default as ngLang } from '@angular/common/locales/zh';
import { APP_INITIALIZER, LOCALE_ID, NgModule, Type } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { SimpleInterceptor } from '@delon/auth';
import { DELON_LOCALE, zh_CN as delonLang, ALAIN_I18N_TOKEN } from '@delon/theme';
import { NZ_DATE_LOCALE, NZ_I18N, zh_CN as zorroLang } from 'ng-zorro-antd/i18n';
import { NzNotificationModule } from 'ng-zorro-antd/notification';
// #region default language
// 参考https://ng-alain.com/docs/i18n
import { I18NService } from '@core';
import { zhCN as dateLang } from 'date-fns/locale';
const LANG = {
abbr: 'zh',
ng: ngLang,
zorro: zorroLang,
date: dateLang,
delon: delonLang
};
// register angular
import { registerLocaleData } from '@angular/common';
registerLocaleData(LANG.ng, LANG.abbr);
const LANG_PROVIDES = [
{ provide: LOCALE_ID, useValue: LANG.abbr },
{ provide: NZ_I18N, useValue: LANG.zorro },
{ provide: NZ_DATE_LOCALE, useValue: LANG.date },
{ provide: DELON_LOCALE, useValue: LANG.delon }
];
// #endregion
// #region i18n services
const I18NSERVICE_PROVIDES = [{ provide: ALAIN_I18N_TOKEN, useClass: I18NService, multi: false }];
// #endregion
// #region global third module
import { BidiModule } from '@angular/cdk/bidi';
const GLOBAL_THIRD_MODULES: Array<Type<any>> = [BidiModule];
// #endregion
// #region JSON Schema form (using @delon/form)
import { JsonSchemaModule } from '@shared';
const FORM_MODULES = [JsonSchemaModule];
// #endregion
// #region Http Interceptors
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { DefaultInterceptor } from '@core';
const INTERCEPTOR_PROVIDES = [
{ provide: HTTP_INTERCEPTORS, useClass: SimpleInterceptor, multi: true },
{ provide: HTTP_INTERCEPTORS, useClass: DefaultInterceptor, multi: true }
];
// #endregion
// #region Startup Service
import { StartupService } from '@core';
export function StartupServiceFactory(startupService: StartupService): () => Observable<void> {
return () => startupService.load();
}
const APPINIT_PROVIDES = [
StartupService,
{
provide: APP_INITIALIZER,
useFactory: StartupServiceFactory,
deps: [StartupService],
multi: true
}
];
// #endregion
import { AppComponent } from './app.component';
import { CoreModule } from './core/core.module';
import { GlobalConfigModule } from './global-config.module';
import { LayoutModule } from './layout/layout.module';
import { RoutesModule } from './routes/routes.module';
import { SharedModule } from './shared/shared.module';
import { STWidgetModule } from './shared/st-widget/st-widget.module';
import { Observable } from 'rxjs';
@NgModule({
declarations: [AppComponent],
imports: [
BrowserModule,
BrowserAnimationsModule,
HttpClientModule,
GlobalConfigModule.forRoot(),
CoreModule,
SharedModule,
LayoutModule,
RoutesModule,
STWidgetModule,
NzNotificationModule,
...GLOBAL_THIRD_MODULES,
...FORM_MODULES
],
providers: [...LANG_PROVIDES, ...INTERCEPTOR_PROVIDES, ...I18NSERVICE_PROVIDES, ...APPINIT_PROVIDES],
bootstrap: [AppComponent]
})
export class AppModule {}

@ -0,0 +1,5 @@
### CoreModule
**应** 仅只留 `providers` 属性。
**作用:** 一些通用服务例如用户消息、HTTP数据访问。

@ -0,0 +1,28 @@
/*
* Copyright [2022] [YangLiu of copyright http://www.maxkey.top]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { NgModule, Optional, SkipSelf } from '@angular/core';
import { throwIfAlreadyLoaded } from './module-import-guard';
@NgModule({
providers: []
})
export class CoreModule {
constructor(@Optional() @SkipSelf() parentModule: CoreModule) {
throwIfAlreadyLoaded(parentModule, 'CoreModule');
}
}

@ -0,0 +1,101 @@
/*
* Copyright [2022] [MaxKey of copyright http://www.maxkey.top]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { TestBed, TestBedStatic } from '@angular/core/testing';
import { DelonLocaleService, SettingsService } from '@delon/theme';
import { NzSafeAny } from 'ng-zorro-antd/core/types';
import { NzI18nService } from 'ng-zorro-antd/i18n';
import { of } from 'rxjs';
import { I18NService } from './i18n.service';
describe('Service: I18n', () => {
let injector: TestBedStatic;
let srv: I18NService;
const MockSettingsService: NzSafeAny = {
layout: {
lang: null
}
};
const MockNzI18nService = {
setLocale: () => {},
setDateLocale: () => {}
};
const MockDelonLocaleService = {
setLocale: () => {}
};
const MockTranslateService = {
getBrowserLang: jasmine.createSpy('getBrowserLang'),
addLangs: () => {},
setLocale: () => {},
getDefaultLang: () => '',
use: (lang: string) => of(lang),
instant: jasmine.createSpy('instant')
};
function genModule(): void {
injector = TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [
I18NService,
{ provide: SettingsService, useValue: MockSettingsService },
{ provide: NzI18nService, useValue: MockNzI18nService },
{ provide: DelonLocaleService, useValue: MockDelonLocaleService }
]
});
srv = TestBed.inject(I18NService);
}
it('should working', () => {
spyOnProperty(navigator, 'languages').and.returnValue(['zh-CN']);
genModule();
expect(srv).toBeTruthy();
expect(srv.defaultLang).toBe('zh-CN');
srv.fanyi('a');
srv.fanyi('a', {});
});
it('should be used layout as default language', () => {
MockSettingsService.layout.lang = 'en-US';
const navSpy = spyOnProperty(navigator, 'languages');
genModule();
expect(navSpy).not.toHaveBeenCalled();
expect(srv.defaultLang).toBe('en-US');
MockSettingsService.layout.lang = null;
});
it('should be used browser as default language', () => {
spyOnProperty(navigator, 'languages').and.returnValue(['zh-TW']);
genModule();
expect(srv.defaultLang).toBe('zh-TW');
});
it('should be use default language when the browser language is not in the list', () => {
spyOnProperty(navigator, 'languages').and.returnValue(['es-419']);
genModule();
expect(srv.defaultLang).toBe('zh-CN');
});
it('should be trigger notify when changed language', () => {
genModule();
srv.use('en-US', {});
srv.change.subscribe(lang => {
expect(lang).toBe('en-US');
});
});
});

@ -0,0 +1,133 @@
/*
* Copyright [2022] [MaxKey of copyright http://www.maxkey.top]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// 请参考https://ng-alain.com/docs/i18n
import { Platform } from '@angular/cdk/platform';
import { registerLocaleData } from '@angular/common';
import { HttpClient } from '@angular/common/http';
import ngEn from '@angular/common/locales/en';
import ngZh from '@angular/common/locales/zh';
import ngZhTw from '@angular/common/locales/zh-Hant';
import { Injectable } from '@angular/core';
import {
DelonLocaleService,
en_US as delonEnUS,
SettingsService,
zh_CN as delonZhCn,
zh_TW as delonZhTw,
_HttpClient,
AlainI18nBaseService
} from '@delon/theme';
import { AlainConfigService } from '@delon/util/config';
import { enUS as dfEn, zhCN as dfZhCn, zhTW as dfZhTw } from 'date-fns/locale';
import { NzSafeAny } from 'ng-zorro-antd/core/types';
import { en_US as zorroEnUS, NzI18nService, zh_CN as zorroZhCN, zh_TW as zorroZhTW } from 'ng-zorro-antd/i18n';
import { Observable } from 'rxjs';
interface LangConfigData {
abbr: string;
text: string;
ng: NzSafeAny;
zorro: NzSafeAny;
date: NzSafeAny;
delon: NzSafeAny;
}
const DEFAULT = 'zh-CN';
const LANGS: { [key: string]: LangConfigData } = {
'zh-CN': {
text: '简体中文',
ng: ngZh,
zorro: zorroZhCN,
date: dfZhCn,
delon: delonZhCn,
abbr: '🇨🇳'
},
'zh-TW': {
text: '繁体中文',
ng: ngZhTw,
zorro: zorroZhTW,
date: dfZhTw,
delon: delonZhTw,
abbr: 'TW'
},
'en-US': {
text: 'English',
ng: ngEn,
zorro: zorroEnUS,
date: dfEn,
delon: delonEnUS,
abbr: '🇬🇧'
}
};
@Injectable({ providedIn: 'root' })
export class I18NService extends AlainI18nBaseService {
protected override _defaultLang = DEFAULT;
private _langs = Object.keys(LANGS).map(code => {
const item = LANGS[code];
return { code, text: item.text, abbr: item.abbr };
});
constructor(
private http: HttpClient,
private settings: SettingsService,
private nzI18nService: NzI18nService,
private delonLocaleService: DelonLocaleService,
private platform: Platform,
cogSrv: AlainConfigService
) {
super(cogSrv);
const defaultLang = this.getDefaultLang();
this._defaultLang = this._langs.findIndex(w => w.code === defaultLang) === -1 ? DEFAULT : defaultLang;
}
private getDefaultLang(): string {
if (!this.platform.isBrowser) {
return DEFAULT;
}
if (this.settings.layout.lang) {
return this.settings.layout.lang;
}
let res = (navigator.languages ? navigator.languages[0] : null) || navigator.language;
const arr = res.split('-');
return arr.length <= 1 ? res : `${arr[0]}-${arr[1].toUpperCase()}`;
}
loadLangData(lang: string): Observable<NzSafeAny> {
return this.http.get(`./assets/i18n/${lang}.json`);
}
use(lang: string, data: Record<string, unknown>): void {
if (this._currentLang === lang) return;
this._data = this.flatData(data, []);
const item = LANGS[lang];
registerLocaleData(item.ng);
this.nzI18nService.setLocale(item.zorro);
this.nzI18nService.setDateLocale(item.date);
this.delonLocaleService.setLocale(item.delon);
this._currentLang = lang;
this._change$.next(lang);
}
getLangs(): Array<{ code: string; text: string; abbr: string }> {
return this._langs;
}
}

@ -0,0 +1,20 @@
/*
* Copyright [2022] [YangLiu of copyright http://www.maxkey.top]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export * from './i18n/i18n.service';
export * from './module-import-guard';
export * from './net/default.interceptor';
export * from './startup/startup.service';

@ -0,0 +1,22 @@
/*
* Copyright [2022] [YangLiu of copyright http://www.maxkey.top]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// https://angular.io/guide/styleguide#style-04-12
export function throwIfAlreadyLoaded(parentModule: any, moduleName: string): void {
if (parentModule) {
throw new Error(`${moduleName} has already been loaded. Import Core modules in the AppModule only.`);
}
}

@ -0,0 +1,300 @@
/*
* Copyright [2022] [MaxKey of copyright http://www.maxkey.top]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
HttpErrorResponse,
HttpEvent,
HttpHandler,
HttpHeaders,
HttpInterceptor,
HttpRequest,
HttpResponseBase
} from '@angular/common/http';
import { Injectable, Injector } from '@angular/core';
import { Router } from '@angular/router';
import { DA_SERVICE_TOKEN, ITokenService } from '@delon/auth';
import { ALAIN_I18N_TOKEN, _HttpClient } from '@delon/theme';
import { environment } from '@env/environment';
import { NzNotificationService } from 'ng-zorro-antd/notification';
import { CookieService } from 'ngx-cookie-service';
import { BehaviorSubject, Observable, of, throwError } from 'rxjs';
import { catchError, filter, mergeMap, switchMap, take } from 'rxjs/operators';
import { CONSTS } from '../../shared/consts';
const CODEMESSAGE: { [key: number]: string } = {
200: '服务器成功返回请求的数据。',
201: '新建或修改数据成功。',
202: '一个请求已经进入后台排队(异步任务)。',
204: '删除数据成功。',
400: '发出的请求有错误,服务器没有进行新建或修改数据的操作。',
401: '用户没有权限(令牌、用户名、密码错误)。',
403: '用户得到授权,但是访问是被禁止的。',
404: '发出的请求针对的是不存在的记录,服务器没有进行操作。',
406: '请求的格式不可得。',
410: '请求的资源被永久删除,且不会再得到的。',
422: '当创建一个对象时,发生一个验证错误。',
500: '服务器发生错误,请检查服务器。',
502: '网关错误。',
503: '服务不可用,服务器暂时过载或维护。',
504: '网关超时。'
};
/**
* HTTP `app.module.ts`
*/
@Injectable()
export class DefaultInterceptor implements HttpInterceptor {
private refreshTokenEnabled = environment.api.refreshTokenEnabled;
private refreshTokenType: 're-request' | 'auth-refresh' = environment.api.refreshTokenType;
private refreshToking = false;
private notified = false;
private refreshToken$: BehaviorSubject<any> = new BehaviorSubject<any>(null);
constructor(private injector: Injector) {
if (this.refreshTokenType === 'auth-refresh') {
this.buildAuthRefresh();
}
}
private get notification(): NzNotificationService {
return this.injector.get(NzNotificationService);
}
private get cookieService(): CookieService {
return this.injector.get(CookieService);
}
private get tokenSrv(): ITokenService {
return this.injector.get(DA_SERVICE_TOKEN);
}
private get http(): _HttpClient {
return this.injector.get(_HttpClient);
}
private goTo(url: string): void {
setTimeout(() => {
this.injector.get(Router).navigateByUrl(url);
this.notified = false;
});
}
private checkStatus(ev: HttpResponseBase): void {
if ((ev.status >= 200 && ev.status < 300) || ev.status === 401) {
return;
}
const errortext = CODEMESSAGE[ev.status] || ev.statusText;
this.notification.error(`请求错误 ${ev.status}: ${ev.url}`, errortext);
}
/**
* Token
*/
private refreshTokenRequest(): Observable<any> {
const model = this.tokenSrv.get();
return this.http.post(`/auth/token/refresh`, null, { refresh_token: model?.['refresh_token'] || '' });
}
// #region 刷新Token方式一使用 401 重新刷新 Token
private tryRefreshToken(ev: HttpResponseBase, req: HttpRequest<any>, next: HttpHandler): Observable<any> {
// 1、若请求为刷新Token请求表示来自刷新Token可以直接跳转登录页
if ([`/auth/token/refresh`].some(url => req.url.includes(url))) {
this.toLogin();
return throwError(ev);
}
// 2、如果 `refreshToking` 为 `true` 表示已经在请求刷新 Token 中,后续所有请求转入等待状态,直至结果返回后再重新发起请求
if (this.refreshToking) {
return this.refreshToken$.pipe(
filter(v => !!v),
take(1),
switchMap(() => next.handle(this.reAttachToken(req)))
);
}
// 3、尝试调用刷新 Token
this.refreshToking = true;
this.refreshToken$.next(null);
return this.refreshTokenRequest().pipe(
switchMap(res => {
console.log(res.data);
// 通知后续请求继续执行
this.refreshToking = false;
this.refreshToken$.next(res.data.refresh_token);
this.cookieService.set(CONSTS.CONGRESS, res.data.token);
// 重新保存新 token
this.tokenSrv.set(res.data);
// 重新发起请求
return next.handle(this.reAttachToken(req));
}),
catchError(err => {
this.refreshToking = false;
this.toLogin();
return throwError(err);
})
);
}
/**
* Token
*
* > `@delon/auth` Token
*/
private reAttachToken(req: HttpRequest<any>): HttpRequest<any> {
//console.log('reAttachToken');
// 以下示例是以 NG-ALAIN 默认使用 `SimpleInterceptor`
const token = this.tokenSrv.get()?.token;
return req.clone({
setHeaders: {
Authorization: `Bearer ${token}`,
hostname: window.location.hostname,
AuthServer: 'MaxKey'
}
});
}
// #endregion
// #region 刷新Token方式二使用 `@delon/auth` 的 `refresh` 接口
private buildAuthRefresh(): void {
if (!this.refreshTokenEnabled) {
return;
}
this.tokenSrv.refresh
.pipe(
filter(() => !this.refreshToking),
switchMap(res => {
console.log(res);
this.refreshToking = true;
return this.refreshTokenRequest();
})
)
.subscribe(
res => {
// TODO: Mock expired value
res.expired = +new Date() + 1000 * 60 * 5;
this.refreshToking = false;
this.tokenSrv.set(res);
},
() => this.toLogin()
);
}
// #endregion
private toLogin(): void {
if (!this.notified) {
this.notified = true;
this.notification.error(`未登录或登录已过期,请重新登录。`, ``);
this.goTo(this.tokenSrv.login_url!);
}
}
private handleData(ev: HttpResponseBase, req: HttpRequest<any>, next: HttpHandler): Observable<any> {
this.checkStatus(ev);
// 业务处理:一些通用操作
switch (ev.status) {
case 200:
// 业务层级错误处理以下是假定restful有一套统一输出格式指不管成功与否都有相应的数据格式情况下进行处理
// 例如响应内容:
// 错误内容:{ status: 1, msg: '非法参数' }
// 正确内容:{ status: 0, response: { } }
// 则以下代码片断可直接适用
// if (ev instanceof HttpResponse) {
// const body = ev.body;
// if (body && body.status !== 0) {
// this.injector.get(NzMessageService).error(body.msg);
// // 注意这里如果继续抛出错误会被行254的 catchError 二次拦截,导致外部实现的 Pipe、subscribe 操作被中断例如this.http.get('/').subscribe() 不会触发
// // 如果你希望外部实现需要手动移除行254
// return throwError({});
// } else {
// // 忽略 Blob 文件体
// if (ev.body instanceof Blob) {
// return of(ev);
// }
// // 重新修改 `body` 内容为 `response` 内容,对于绝大多数场景已经无须再关心业务状态码
// return of(new HttpResponse(Object.assign(ev, { body: body.response })));
// // 或者依然保持完整的格式
// return of(ev);
// }
// }
break;
case 401:
if (this.refreshTokenEnabled && this.refreshTokenType === 're-request') {
return this.tryRefreshToken(ev, req, next);
}
this.toLogin();
break;
case 403:
case 404:
case 500:
// this.goTo(`/exception/${ev.status}?url=${req.urlWithParams}`);
break;
default:
if (ev instanceof HttpErrorResponse) {
console.warn('未可知错误大部分是由于后端不支持跨域CORS或无效配置引起.', ev);
}
break;
}
if (ev instanceof HttpErrorResponse) {
return throwError(ev);
} else {
return of(ev);
}
}
private getAdditionalHeaders(headers?: HttpHeaders): { [name: string]: string } {
const res: { [name: string]: string } = {};
const lang = this.injector.get(ALAIN_I18N_TOKEN).currentLang;
if (!headers?.has('Accept-Language') && lang) {
res['Accept-Language'] = lang;
}
let jwtAuthn = this.tokenSrv.get();
if (jwtAuthn !== null) {
res['Authorization'] = `Bearer ${jwtAuthn.token}`;
}
res['hostname'] = window.location.hostname;
res['AuthServer'] = 'MaxKey';
return res;
}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
// 统一加上服务端前缀
let url = req.url;
if (!url.startsWith('https://') && !url.startsWith('http://') && !url.startsWith('.')) {
const { baseUrl } = environment.api;
url = baseUrl + (baseUrl.endsWith('/') && url.startsWith('/') ? url.substring(1) : url);
}
const newReq = req.clone({ url, setHeaders: this.getAdditionalHeaders(req.headers) });
return next.handle(newReq).pipe(
mergeMap(ev => {
// 允许统一对请求错误处理
if (ev instanceof HttpResponseBase) {
return this.handleData(ev, newReq, next);
}
// 若一切都正常,则后续操作
return of(ev);
}),
catchError((err: HttpErrorResponse) => this.handleData(err, newReq, next))
);
}
}

@ -0,0 +1,78 @@
/*
* Copyright [2022] [MaxKey of copyright http://www.maxkey.top]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { HttpClient } from '@angular/common/http';
import { Inject, Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { ACLService } from '@delon/acl';
import { ALAIN_I18N_TOKEN, MenuService, SettingsService, TitleService } from '@delon/theme';
import { NzSafeAny } from 'ng-zorro-antd/core/types';
import { NzIconService } from 'ng-zorro-antd/icon';
import { Observable, zip } from 'rxjs';
import { catchError, map } from 'rxjs/operators';
import { ICONS } from '../../../style-icons';
import { ICONS_AUTO } from '../../../style-icons-auto';
import { I18NService } from '../i18n/i18n.service';
/**
* Used for application startup
* Generally used to get the basic data of the application, like: Menu Data, User Data, etc.
*/
@Injectable()
export class StartupService {
constructor(
iconSrv: NzIconService,
private menuService: MenuService,
@Inject(ALAIN_I18N_TOKEN) private i18n: I18NService,
private settingService: SettingsService,
private aclService: ACLService,
private titleService: TitleService,
private httpClient: HttpClient,
private router: Router
) {
iconSrv.addIcon(...ICONS_AUTO, ...ICONS);
}
load(): Observable<void> {
const defaultLang = this.i18n.defaultLang;
return zip(this.i18n.loadLangData(defaultLang), this.httpClient.get('./assets/app-data.json')).pipe(
// 接收其他拦截器后产生的异常消息
catchError(res => {
console.warn(`StartupService.load: Network request failed`, res);
setTimeout(() => this.router.navigateByUrl(`/exception/500`));
return [];
}),
map(([langData, appData]: [Record<string, string>, NzSafeAny]) => {
// setting language data
this.i18n.use(defaultLang, langData);
// 应用信息:包括站点名、描述、年份
this.settingService.setApp(appData.app);
// 用户信息:包括姓名、头像、邮箱地址
//this.settingService.setUser(appData.user);
// ACL设置权限为全量
this.aclService.setFull(false);
// 初始化菜单
this.menuService.add(appData.menu);
// 设置页面标题的后缀
this.titleService.default = '';
this.titleService.suffix = appData.app.name;
})
);
}
}

@ -0,0 +1,53 @@
/*
* Copyright [2022] [YangLiu of copyright http://www.maxkey.top]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import format from 'date-fns/format';
import { BaseEntity } from './BaseEntity';
export class Accounts extends BaseEntity {
strategyId!: String;
strategyName!: String;
appId!: String;
appName!: String;
userId!: String;
username!: String;
displayName!: String;
relatedUsername!: String;
relatedPassword!: String;
createType!: String;
confirmPassword!: String;
constructor() {
super();
this.createType = 'manual';
}
override init(data: any): void {
Object.assign(this, data);
if (this.status == 1) {
this.switch_status = true;
}
}
override trans(): void {
if (this.switch_status) {
this.status = 1;
} else {
this.status = 0;
}
}
}

@ -0,0 +1,54 @@
/*
* Copyright [2022] [YangLiu of copyright http://www.maxkey.top]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import format from 'date-fns/format';
import { BaseEntity } from './BaseEntity';
export class AccountsStrategy extends BaseEntity {
name!: String;
appId!: String;
appName!: String;
filters!: String;
orgIdsList!: String;
mapping!: String;
suffixes!: String;
createType!: String;
switch_dynamic: boolean = false;
picker_resumeTime: Date = new Date(format(new Date(), 'yyyy-MM-dd 00:00:00'));
picker_suspendTime: Date = new Date(format(new Date(), 'yyyy-MM-dd 00:00:00'));
constructor() {
super();
this.createType = 'manual';
this.mapping = 'username';
}
override init(data: any): void {
Object.assign(this, data);
if (this.status == 1) {
this.switch_status = true;
}
}
override trans(): void {
if (this.switch_status) {
this.status = 1;
} else {
this.status = 0;
}
}
}

@ -0,0 +1,44 @@
/*
* Copyright [2022] [YangLiu of copyright http://www.maxkey.top]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import format from 'date-fns/format';
import { BaseEntity } from './BaseEntity';
export class Adapters extends BaseEntity {
name!: String;
protocol!: String;
adapter!: String;
switch_dynamic: boolean = false;
constructor() {
super();
}
override init(data: any): void {
Object.assign(this, data);
if (this.status == 1) {
this.switch_status = true;
}
}
override trans(): void {
if (this.switch_status) {
this.status = 1;
} else {
this.status = 0;
}
}
}

@ -0,0 +1,75 @@
/*
* Copyright [2022] [YangLiu of copyright http://www.maxkey.top]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import format from 'date-fns/format';
import { BaseEntity } from './BaseEntity';
export class Apps extends BaseEntity {
name!: String;
loginUrl!: String;
category!: String;
protocol!: String;
secret!: String;
iconBase64!: String;
visible!: String;
inducer!: String;
vendor!: String;
vendorUrl!: String;
credential!: String;
sharedUsername!: String;
sharedPassword!: String;
systemUserAttr!: String;
principal!: String;
credentials!: String;
logoutUrl!: String;
logoutType!: String;
isExtendAttr!: String;
extendAttr!: String;
userPropertys!: String;
isSignature!: String;
isAdapter!: String;
adapterId!: String;
adapterName!: String;
adapter!: String;
iconId!: String;
select_userPropertys!: String[];
constructor() {
super();
this.category = 'none';
this.visible = '0';
this.isAdapter = '0';
this.logoutType = '0';
}
override init(data: any): void {
Object.assign(this, data);
if (this.status == 1) {
this.switch_status = true;
}
}
override trans(): void {
if (this.switch_status) {
this.status = 1;
} else {
this.status = 0;
}
}
}

@ -0,0 +1,44 @@
/*
* Copyright [2022] [YangLiu of copyright http://www.maxkey.top]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export class BaseEntity {
id!: String;
instId!: String;
instName!: String;
sortIndex!: Number;
status!: Number;
description!: String;
switch_status: boolean = false;
str_status!: String;
constructor() {
this.status = 1;
}
init(data: any): void {
Object.assign(this, data);
if (this.status == 1) {
this.switch_status = true;
this.str_status = `${this.status}`;
}
}
trans(): void {
if (this.switch_status) {
this.status = 1;
} else {
this.status = 0;
}
}
}

@ -0,0 +1,29 @@
/*
* Copyright [2022] [YangLiu of copyright http://www.maxkey.top]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { BaseEntity } from './BaseEntity';
export class ChangePassword extends BaseEntity {
userId!: String;
username!: String;
email!: String;
mobile!: String;
displayName!: String;
oldPassword!: String;
password!: String;
confirmPassword!: String;
decipherable!: String;
}

@ -0,0 +1,52 @@
/*
* Copyright [2022] [YangLiu of copyright http://www.maxkey.top]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { BaseEntity } from './BaseEntity';
export class EmailSenders extends BaseEntity {
account!: String;
credentials!: String;
smtpHost!: String;
port!: Number;
sslSwitch!: Number;
sender!: String;
encoding!: String;
protocol!: String;
switch_sslSwitch: boolean = false;
override init(data: any): void {
Object.assign(this, data);
if (this.sslSwitch == 1) {
this.switch_sslSwitch = true;
}
if (this.status == 1) {
this.switch_status = true;
}
}
override trans(): void {
if (this.switch_sslSwitch) {
this.sslSwitch = 1;
} else {
this.sslSwitch = 0;
}
if (this.switch_status) {
this.status = 1;
} else {
this.status = 0;
}
}
}

@ -0,0 +1,19 @@
/*
* Copyright [2022] [YangLiu of copyright http://www.maxkey.top]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { BaseEntity } from './BaseEntity';
export class GroupMembers extends BaseEntity {}

@ -0,0 +1,72 @@
/*
* Copyright [2022] [YangLiu of copyright http://www.maxkey.top]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import format from 'date-fns/format';
import { BaseEntity } from './BaseEntity';
export class Groups extends BaseEntity {
name!: String;
dynamic!: String;
filters!: String;
orgIdsList!: String;
resumeTime!: String;
suspendTime!: String;
isdefault!: String;
switch_dynamic: boolean = false;
picker_resumeTime: Date = new Date(format(new Date(), 'yyyy-MM-dd 00:00:00'));
picker_suspendTime: Date = new Date(format(new Date(), 'yyyy-MM-dd 00:00:00'));
constructor() {
super();
}
override init(data: any): void {
Object.assign(this, data);
if (this.status == 1) {
this.switch_status = true;
}
if (this.dynamic == '1') {
this.switch_dynamic = true;
}
if (this.resumeTime != '') {
this.picker_resumeTime = new Date(format(new Date(), `yyyy-MM-dd ${this.resumeTime}:00`));
}
if (this.suspendTime != '') {
this.picker_suspendTime = new Date(format(new Date(), `yyyy-MM-dd ${this.suspendTime}:00`));
}
}
override trans(): void {
if (this.switch_status) {
this.status = 1;
} else {
this.status = 0;
}
if (this.switch_dynamic) {
this.dynamic = '1';
} else {
this.dynamic = '0';
}
if (this.picker_resumeTime) {
this.resumeTime = format(this.picker_resumeTime, 'HH:mm');
}
if (this.picker_suspendTime) {
this.suspendTime = format(this.picker_suspendTime, 'HH:mm');
}
}
}

@ -0,0 +1,33 @@
/*
* Copyright [2022] [YangLiu of copyright http://www.maxkey.top]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { BaseEntity } from './BaseEntity';
export class Institutions extends BaseEntity {
name!: String;
fullName!: String;
contact!: String;
email!: String;
phone!: String;
address!: String;
logo!: String;
frontTitle!: String;
consoleTitle!: String;
domain!: String;
captchaType!: String;
captchaSupport!: String;
defaultUri!: String;
}

@ -0,0 +1,64 @@
/*
* Copyright [2022] [YangLiu of copyright http://www.maxkey.top]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { BaseEntity } from './BaseEntity';
export class LdapContext extends BaseEntity {
product!: String;
providerUrl!: String;
principal!: String;
credentials!: String;
sslSwitch!: Number;
filters!: String;
basedn!: String;
msadDomain!: String;
accountMapping!: String;
trustStore!: String;
trustStorePassword!: String;
switch_sslSwitch: boolean = false;
switch_accountMapping: boolean = false;
override init(data: any): void {
Object.assign(this, data);
if (this.sslSwitch == 1) {
this.switch_sslSwitch = true;
}
if (this.status == 1) {
this.switch_status = true;
}
if (this.accountMapping == 'YES') {
this.switch_accountMapping = true;
}
}
override trans(): void {
if (this.switch_sslSwitch) {
this.sslSwitch = 1;
} else {
this.sslSwitch = 0;
}
if (this.switch_status) {
this.status = 1;
} else {
this.status = 0;
}
if (this.switch_accountMapping) {
this.accountMapping = 'YES';
} else {
this.accountMapping = 'NO';
}
}
}

@ -0,0 +1,24 @@
/*
* Copyright [2022] [YangLiu of copyright http://www.maxkey.top]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import format from 'date-fns/format';
import { NzSafeAny } from 'ng-zorro-antd/core/types';
export class Message<NzSafeAny> {
code: number = 0;
message: string = '';
data: any;
}

@ -0,0 +1,61 @@
/*
* Copyright [2022] [YangLiu of copyright http://www.maxkey.top]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { BaseEntity } from './BaseEntity';
export class Organizations extends BaseEntity {
code!: String;
name!: String;
fullName!: String;
parentId!: String;
parentCode!: string;
parentName!: String;
type!: String;
codePath!: String;
namePath!: String;
level!: Number;
division!: String;
country!: String;
region!: String;
locality!: String;
street!: String;
address!: String;
contact!: String;
postalCode!: String;
phone!: String;
fax!: String;
email!: String;
switch_dynamic: boolean = false;
constructor() {
super();
this.status = 1;
}
override init(data: any): void {
Object.assign(this, data);
if (this.status == 1) {
this.switch_status = true;
}
}
override trans(): void {
if (this.switch_status) {
this.status = 1;
} else {
this.status = 0;
}
}
}

@ -0,0 +1,30 @@
/*
* Copyright [2022] [YangLiu of copyright http://www.maxkey.top]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import format from 'date-fns/format';
import { NzSafeAny } from 'ng-zorro-antd/core/types';
export class PageResults {
page: number = 0;
total: number = 0;
totalPage: number = 0;
records: number = 0;
rows: any[] = [];
init(data: any): void {
Object.assign(this, data);
}
}

@ -0,0 +1,89 @@
/*
* Copyright [2022] [YangLiu of copyright http://www.maxkey.top]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { BaseEntity } from './BaseEntity';
export class PasswordPolicy extends BaseEntity {
minLength!: Number;
maxLength!: Number;
lowerCase!: Number;
upperCase!: Number;
digits!: Number;
specialChar!: Number;
attempts!: Number;
duration!: Number;
expiration!: Number;
username!: Number;
history!: Number;
dictionary!: Number;
alphabetical!: Number;
numerical!: Number;
qwerty!: Number;
occurances!: Number;
switch_username: boolean = false;
switch_dictionary: boolean = false;
switch_alphabetical: boolean = false;
switch_numerical: boolean = false;
switch_qwerty: boolean = false;
override init(data: any): void {
Object.assign(this, data);
if (this.alphabetical == 1) {
this.switch_alphabetical = true;
}
if (this.username == 1) {
this.switch_username = true;
}
if (this.qwerty == 1) {
this.switch_qwerty = true;
}
if (this.numerical == 1) {
this.switch_numerical = true;
}
if (this.dictionary == 1) {
this.switch_dictionary = true;
}
}
override trans(): void {
if (this.switch_alphabetical) {
this.alphabetical = 1;
} else {
this.alphabetical = 0;
}
if (this.switch_username) {
this.username = 1;
} else {
this.username = 0;
}
if (this.switch_qwerty) {
this.qwerty = 1;
} else {
this.qwerty = 0;
}
if (this.switch_numerical) {
this.numerical = 1;
} else {
this.numerical = 0;
}
if (this.switch_dictionary) {
this.dictionary = 1;
} else {
this.dictionary = 0;
}
}
}

@ -0,0 +1,50 @@
/*
* Copyright [2022] [YangLiu of copyright http://www.maxkey.top]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { BaseEntity } from './BaseEntity';
export class Resources extends BaseEntity {
name!: String;
appId!: String;
appName!: String;
parentId!: String;
parentName!: String;
resourceType!: String;
resourceIcon!: String;
resourceStyle!: String;
resourceUrl!: String;
resourceAction!: String;
switch_dynamic: boolean = false;
constructor() {
super();
this.status = 1;
}
override init(data: any): void {
Object.assign(this, data);
if (this.status == 1) {
this.switch_status = true;
}
}
override trans(): void {
if (this.switch_status) {
this.status = 1;
} else {
this.status = 0;
}
}
}

@ -0,0 +1,19 @@
/*
* Copyright [2022] [YangLiu of copyright http://www.maxkey.top]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { BaseEntity } from './BaseEntity';
export class RoleMembers extends BaseEntity {}

@ -0,0 +1,73 @@
/*
* Copyright [2022] [YangLiu of copyright http://www.maxkey.top]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import format from 'date-fns/format';
import { BaseEntity } from './BaseEntity';
export class Roles extends BaseEntity {
name!: String;
dynamic!: String;
filters!: String;
orgIdsList!: String;
resumeTime!: String;
suspendTime!: String;
isdefault!: String;
switch_dynamic: boolean = false;
picker_resumeTime: Date = new Date(format(new Date(), 'yyyy-MM-dd 00:00:00'));
picker_suspendTime: Date = new Date(format(new Date(), 'yyyy-MM-dd 00:00:00'));
constructor() {
super();
this.status = 1;
}
override init(data: any): void {
Object.assign(this, data);
if (this.status == 1) {
this.switch_status = true;
}
if (this.dynamic == '1') {
this.switch_dynamic = true;
}
if (this.resumeTime != '') {
this.picker_resumeTime = new Date(format(new Date(), `yyyy-MM-dd ${this.resumeTime}:00`));
}
if (this.suspendTime != '') {
this.picker_suspendTime = new Date(format(new Date(), `yyyy-MM-dd ${this.suspendTime}:00`));
}
}
override trans(): void {
if (this.switch_status) {
this.status = 1;
} else {
this.status = 0;
}
if (this.switch_dynamic) {
this.dynamic = '1';
} else {
this.dynamic = '0';
}
if (this.picker_resumeTime) {
this.resumeTime = format(this.picker_resumeTime, 'HH:mm');
}
if (this.picker_suspendTime) {
this.suspendTime = format(this.picker_suspendTime, 'HH:mm');
}
}
}

@ -0,0 +1,43 @@
/*
* Copyright [2022] [YangLiu of copyright http://www.maxkey.top]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { BaseEntity } from './BaseEntity';
export class SmsProvider extends BaseEntity {
provider!: String;
providerName!: String;
message!: String;
appKey!: String;
appSecret!: String;
templateId!: String;
signName!: String;
smsSdkAppId!: String;
override init(data: any): void {
Object.assign(this, data);
if (this.status == 1) {
this.switch_status = true;
}
}
override trans(): void {
if (this.switch_status) {
this.status = 1;
} else {
this.status = 0;
}
}
}

@ -0,0 +1,38 @@
/*
* Copyright [2022] [YangLiu of copyright http://www.maxkey.top]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { BaseEntity } from './BaseEntity';
import { SocialsProvider } from './SocialsProvider';
export class SocialsAssociate extends SocialsProvider {
redirectUri!: String;
accountId!: String;
bindTime!: String;
unBindTime!: String;
lastLoginTime!: String;
state!: String;
userBind!: String;
constructor() {
super();
}
override init(data: any): void {
Object.assign(this, data);
}
override trans(): void {}
}

@ -0,0 +1,58 @@
/*
* Copyright [2022] [YangLiu of copyright http://www.maxkey.top]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { BaseEntity } from './BaseEntity';
export class SocialsProvider extends BaseEntity {
provider!: String;
providerName!: String;
icon!: String;
clientId!: String;
clientSecret!: String;
agentId!: String;
hidden!: String;
scanCode!: String;
switch_hidden: boolean = false;
constructor() {
super();
this.status = 1;
this.scanCode = 'none';
this.sortIndex = 1;
}
override init(data: any): void {
Object.assign(this, data);
if (this.status == 1) {
this.switch_status = true;
}
if (this.hidden == 'true') {
this.switch_hidden = true;
}
}
override trans(): void {
if (this.switch_status) {
this.status = 1;
} else {
this.status = 0;
}
if (this.switch_hidden) {
this.hidden = 'true';
}
}
}

@ -0,0 +1,83 @@
/*
* Copyright [2022] [YangLiu of copyright http://www.maxkey.top]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import format from 'date-fns/format';
import { BaseEntity } from './BaseEntity';
export class Synchronizers extends BaseEntity {
name!: String;
filters!: String;
sourceType!: String;
service!: String;
resumeTime!: String;
suspendTime!: String;
scheduler!: String;
syncStartTime!: Number;
providerUrl!: String;
driverClass!: String;
principal!: String;
credentials!: String;
sslSwitch!: Number;
basedn!: String;
msadDomain!: String;
trustStore!: String;
trustStorePassword!: String;
switch_sslSwitch: boolean = false;
picker_resumeTime: Date = new Date(format(new Date(), 'yyyy-MM-dd 00:00:00'));
picker_suspendTime: Date = new Date(format(new Date(), 'yyyy-MM-dd 00:00:00'));
constructor() {
super();
this.status = 1;
this.sourceType != 'API';
}
override init(data: any): void {
Object.assign(this, data);
if (this.status == 1) {
this.switch_status = true;
}
if (this.sslSwitch == 1) {
this.switch_sslSwitch = true;
}
if (this.resumeTime != '') {
this.picker_resumeTime = new Date(format(new Date(), `yyyy-MM-dd ${this.resumeTime}:00`));
}
if (this.suspendTime != '') {
this.picker_suspendTime = new Date(format(new Date(), `yyyy-MM-dd ${this.suspendTime}:00`));
}
}
override trans(): void {
if (this.switch_status) {
this.status = 1;
} else {
this.status = 0;
}
if (this.switch_sslSwitch) {
this.sslSwitch = 1;
} else {
this.sslSwitch = 0;
}
if (this.picker_resumeTime) {
this.resumeTime = format(this.picker_resumeTime, 'HH:mm');
}
if (this.picker_suspendTime) {
this.suspendTime = format(this.picker_suspendTime, 'HH:mm');
}
}
}

@ -0,0 +1,30 @@
/*
* Copyright [2022] [YangLiu of copyright http://www.maxkey.top]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import format from 'date-fns/format';
import { BaseEntity } from './BaseEntity';
export class TimeBased extends BaseEntity {
displayName!: String;
username!: String;
digits!: String;
period!: String;
sharedSecret!: String;
hexSharedSecret!: String;
rqCode!: String;
otp!: string;
}

@ -0,0 +1,54 @@
/*
* Copyright [2022] [YangLiu of copyright http://www.maxkey.top]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { NzFormatEmitEvent, NzTreeNode, NzTreeNodeOptions } from 'ng-zorro-antd/tree';
export class TreeNodes {
activated!: NzTreeNode;
request!: any[];
nodes!: any[];
checkable!: boolean;
checkedKeys!: any[];
selectedKeys!: any[];
_rootNode!: any;
constructor(checkable: boolean) {
this.checkable = checkable;
this.checkedKeys = [];
this.selectedKeys = [];
}
init(treeAttrs: any) {
this._rootNode = { title: treeAttrs.rootNode.title, key: treeAttrs.rootNode.key, expanded: true, isLeaf: false };
this.request = treeAttrs.nodes;
}
build(): any[] {
return this.buildTree(this._rootNode);
}
buildTree(rootNode: any): any[] {
let treeNodes: any[] = [];
for (let node of this.request) {
if (node.key != rootNode.key && node.parentKey == rootNode.key) {
let treeNode = { title: node.title, key: node.key, expanded: false, isLeaf: true };
this.buildTree(treeNode);
treeNodes.push(treeNode);
rootNode.isLeaf = false;
}
}
rootNode.children = treeNodes;
return [rootNode];
}
}

@ -0,0 +1,192 @@
/*
* Copyright [2022] [YangLiu of copyright http://www.maxkey.top]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { BaseEntity } from './BaseEntity';
export class Users extends BaseEntity {
username!: String;
password!: String;
decipherable!: String;
sharedSecret!: String;
sharedCounter!: String;
/**
* "Employee", "Supplier","Dealer","Contractor",Partner,Customer "Intern",
* "Temp", "External", and "Unknown" .
*/
userType!: String;
userState!: String;
windowsAccount!: String;
// for user name
displayName!: String;
nickName!: String;
nameZhSpell!: String;
nameZhShortSpell!: String;
givenName!: String;
middleName!: String;
familyName!: String;
honorificPrefix!: String;
honorificSuffix!: String;
formattedName!: String;
married!: Number;
gender!: Number;
birthDate!: String;
picture!: String;
pictureId!: String;
pictureBase64!: string;
idType!: Number;
idCardNo!: String;
education!: String;
graduateFrom!: String;
graduateDate!: String;
webSite!: String;
startWorkDate!: String;
// for security
authnType!: String;
email!: String;
emailVerified!: Number;
mobile!: String;
mobileVerified!: String;
passwordQuestion!: String;
passwordAnswer!: String;
// for apps login protected
appLoginAuthnType!: String;
appLoginPassword!: String;
protectedApps!: String;
protectedAppsMap!: String;
passwordLastSetTime!: String;
badPasswordCount!: Number;
badPasswordTime!: String;
unLockTime!: String;
isLocked!: Number;
lastLoginTime!: String;
lastLoginIp!: String;
lastLogoffTime!: String;
passwordSetType!: Number;
loginCount!: Number;
regionHistory!: String;
passwordHistory!: String;
locale!: String;
timeZone!: String;
preferredLanguage!: String;
// for work
workCountry!: String;
workRegion!: String; // province;
workLocality!: String; // city;
workStreetAddress!: String;
workAddressFormatted!: String;
workEmail!: String;
workPhoneNumber!: String;
workPostalCode!: String;
workFax!: String;
workOfficeName!: String;
// for home
homeCountry!: String;
homeRegion!: String; // province;
homeLocality!: String; // city;
homeStreetAddress!: String;
homeAddressFormatted!: String;
homeEmail!: String;
homePhoneNumber!: String;
homePostalCode!: String;
homeFax!: String;
// for company
employeeNumber!: String;
costCenter!: String;
organization!: String;
division!: String;
departmentId!: String;
department!: String;
jobTitle!: String;
jobLevel!: String;
managerId!: String;
manager!: String;
assistantId!: String;
assistant!: String;
entryDate!: String;
quitDate!: String;
// for social contact
defineIm!: String;
theme!: String;
/*
* for extended Attribute from userType extraAttribute for database
* extraAttributeName & extraAttributeValue for page submit
*/
//protected String extraAttribute;
//protected String extraAttributeName;
//protected String extraAttributeValue;
//@JsonIgnore
//protected HashMap<String, String> extraAttributeMap;
online!: Number;
gridList!: Number;
switch_dynamic: boolean = false;
gender_select!: String;
str_married!: String;
str_idType!: String;
constructor() {
super();
this.status = 1;
this.sortIndex = 1;
this.gender = 1;
this.userType = 'EMPLOYEE';
this.userState = 'RESIDENT';
this.gender_select = '1';
this.str_married = '0';
this.str_idType = '0';
}
override init(data: any): void {
Object.assign(this, data);
if (this.status == 1) {
this.switch_status = true;
}
if (this.gender == 1) {
this.gender_select = '1';
} else {
this.gender_select = '2';
}
this.str_status = `${this.status}`;
this.str_married = `${this.married}`;
this.str_idType = `${this.idType}`;
}
override trans(): void {
if (this.switch_status) {
this.status = 1;
} else {
this.status = 0;
}
if (this.gender_select == '1') {
this.gender = 1;
} else {
this.gender = 2;
}
this.status = Number.parseInt(`${this.str_status}`);
this.married = Number.parseInt(`${this.str_married}`);
this.idType = Number.parseInt(`${this.str_idType}`);
}
}

@ -0,0 +1,93 @@
/*
* Copyright [2022] [YangLiu of copyright http://www.maxkey.top]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* eslint-disable import/order */
import { ModuleWithProviders, NgModule, Optional, SkipSelf } from '@angular/core';
import { DelonACLModule } from '@delon/acl';
import { AlainThemeModule } from '@delon/theme';
import { AlainConfig, ALAIN_CONFIG } from '@delon/util/config';
import { throwIfAlreadyLoaded } from '@core';
import { environment } from '@env/environment';
// Please refer to: https://ng-alain.com/docs/global-config
// #region NG-ALAIN Config
const alainConfig: AlainConfig = {
st: { modal: { size: 'lg' } },
pageHeader: { homeI18n: 'home' },
lodop: {
license: `A59B099A586B3851E0F0D7FDBF37B603`,
licenseA: `C94CEE276DB2187AE6B65D56B3FC2848`
},
auth: { login_url: '/passport/login' }
};
const alainModules: any[] = [AlainThemeModule.forRoot(), DelonACLModule.forRoot()];
const alainProvides = [{ provide: ALAIN_CONFIG, useValue: alainConfig }];
// #region reuse-tab
/**
* [](https://ng-alain.com/components/reuse-tab)需要:
* 1 `shared-delon.module.ts` `ReuseTabModule`
* 2 `RouteReuseStrategy`
* 3 `src/app/layout/default/default.component.html`
* ```html
* <section class="alain-default__content">
* <reuse-tab #reuseTab></reuse-tab>
* <router-outlet (activate)="reuseTab.activate($event)"></router-outlet>
* </section>
* ```
*/
// import { RouteReuseStrategy } from '@angular/router';
// import { ReuseTabService, ReuseTabStrategy } from '@delon/abc/reuse-tab';
// alainProvides.push({
// provide: RouteReuseStrategy,
// useClass: ReuseTabStrategy,
// deps: [ReuseTabService],
// } as any);
// #endregion
// #endregion
// Please refer to: https://ng.ant.design/docs/global-config/en#how-to-use
// #region NG-ZORRO Config
import { NzConfig, NZ_CONFIG } from 'ng-zorro-antd/core/config';
const ngZorroConfig: NzConfig = {};
const zorroProvides = [{ provide: NZ_CONFIG, useValue: ngZorroConfig }];
// #endregion
@NgModule({
imports: [...alainModules, ...(environment.modules || [])]
})
export class GlobalConfigModule {
constructor(@Optional() @SkipSelf() parentModule: GlobalConfigModule) {
throwIfAlreadyLoaded(parentModule, 'GlobalConfigModule');
}
static forRoot(): ModuleWithProviders<GlobalConfigModule> {
return {
ngModule: GlobalConfigModule,
providers: [...alainProvides, ...zorroProvides]
};
}
}

@ -0,0 +1 @@
[Document](https://ng-alain.com/theme/default)

@ -0,0 +1,158 @@
/*
* Copyright [2022] [MaxKey of copyright http://www.maxkey.top]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { ACLService } from '@delon/acl';
import { SettingsService, User } from '@delon/theme';
import { environment } from '@env/environment';
import { CONSTS } from 'src/app/shared/consts';
import { AuthnService } from '../../service/authn.service';
import { knowHost } from '../../shared/utils/knowhost';
import { LayoutDefaultOptions } from '../../theme/layout-default';
@Component({
selector: 'layout-basic',
styles: [
`
.alain-default__collapsed .alain-default__aside-user {
width: 64px;
margin-left: 16px;
}
`
],
template: `
<layout-default [options]="options" [asideUser]="asideUserTpl" [content]="contentTpl" [customError]="null">
<layout-default-header-item direction="left" *ngIf="!inst.custom">
<a href="#">
<img src="./assets/logo.png" alt="logo" style="height: 50px;height: 50px;float: left;" />
<div class="alain-default__header-title">
<span style="color: #000099;">Max</span>
<span style="color: #FFD700;">Key</span>
{{ 'mxk.title' | i18n }}
</div>
</a>
</layout-default-header-item>
<layout-default-header-item direction="left" *ngIf="inst.custom">
<a href="#">
<img src="{{ inst.logo }}" alt="logo" style="height: 50px;height: 50px;float: left;" />
<div class="alain-default__header-title"> {{ inst.title }} </div>
</a>
</layout-default-header-item>
<layout-default-header-item direction="right" hidden="mobile">
<div layout-default-header-item-trigger (click)="profile()"> {{ user.name }}</div>
</layout-default-header-item>
<layout-default-header-item direction="right" hidden="mobile">
<div layout-default-header-item-trigger nz-dropdown [nzDropdownMenu]="settingsMenu" nzTrigger="click" nzPlacement="bottomRight">
<i nz-icon nzType="setting"></i>
</div>
<nz-dropdown-menu #settingsMenu="nzDropdownMenu">
<div nz-menu style="width: 200px;">
<div nz-menu-item (click)="changePassword()">
<i nz-icon nzType="key" nzTheme="outline"></i>
{{ 'mxk.menu.config.password' | i18n }}
</div>
<div nz-menu-item>
<header-rtl></header-rtl>
</div>
<div nz-menu-item>
<header-fullscreen></header-fullscreen>
</div>
<div nz-menu-item>
<header-clear-storage></header-clear-storage>
</div>
<div nz-menu-item>
<header-i18n></header-i18n>
</div>
</div>
</nz-dropdown-menu>
</layout-default-header-item>
<layout-default-header-item direction="right">
<header-user></header-user>
</layout-default-header-item>
<ng-template #asideUserTpl>
<div nz-dropdown nzTrigger="click" [nzDropdownMenu]="userMenu" class="alain-default__aside-user">
<div class="alain-default__aside-user-info">
<!--<strong>{{ user.name }}</strong>
<p class="mb0">{{ user.email }}</p>-->
</div>
<!--
<nz-avatar class="alain-default__aside-user-avatar" [nzSrc]="user.avatar"></nz-avatar>
-->
</div>
<nz-dropdown-menu #userMenu="nzDropdownMenu">
<ul nz-menu>
<!--
<li nz-menu-item routerLink="/pro/account/center">{{ 'menu.account.center' | i18n }}</li>
<li nz-menu-item routerLink="/pro/account/settings">{{ 'menu.account.settings' | i18n }}</li>
-->
</ul>
</nz-dropdown-menu>
</ng-template>
<ng-template #contentTpl>
<router-outlet></router-outlet>
</ng-template>
</layout-default>
<global-footer style="border-top: 1px solid #e5e5e5; min-height: 120px; text-shadow: 0 1px 0 #fff;margin:0;">
<div style="margin-top: 30px">
MaxKey {{ version }}<br />
Copyright
<i nz-icon nzType="copyright"></i> {{ copyrightYear }} <a href="//www.maxkey.top" target="_blank">http://www.maxkey.top</a><br />
Licensed under the Apache License, Version 2.0
</div>
</global-footer>
<setting-drawer *ngIf="showSettingDrawer"></setting-drawer>
<theme-btn></theme-btn>
`
})
export class LayoutBasicComponent implements OnInit {
version = CONSTS.VERSION;
copyrightYear = new Date().getFullYear();
inst: any;
options: LayoutDefaultOptions = {
logoExpanded: `./assets/logo-full.svg`,
logoCollapsed: `./assets/logo.svg`,
hideAside: false
};
searchToggleStatus = false;
showSettingDrawer = !environment.production;
get user(): User {
return this.settingsService.user;
}
profile(): void {
this.router.navigateByUrl('/config/profile');
}
changePassword(): void {
this.router.navigateByUrl('/config/password');
}
ngOnInit(): void {
this.inst = this.authnService.getInst();
if (this.inst == null) {
this.inst = { custom: false };
this.authnService.initInst().subscribe(res => {
this.authnService.setInst(res.data, !knowHost());
this.inst = this.authnService.getInst();
});
}
}
constructor(private authnService: AuthnService, private settingsService: SettingsService, private router: Router) {}
}

@ -0,0 +1,45 @@
/*
* Copyright [2022] [MaxKey of copyright http://www.maxkey.top]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ChangeDetectionStrategy, Component, HostListener } from '@angular/core';
import { NzMessageService } from 'ng-zorro-antd/message';
import { NzModalService } from 'ng-zorro-antd/modal';
@Component({
selector: 'header-clear-storage',
template: `
<i nz-icon nzType="tool"></i>
{{ 'menu.clear.local.storage' | i18n }}
`,
host: {
'[class.flex-1]': 'true'
},
changeDetection: ChangeDetectionStrategy.OnPush
})
export class HeaderClearStorageComponent {
constructor(private modalSrv: NzModalService, private messageSrv: NzMessageService) {}
@HostListener('click')
_click(): void {
this.modalSrv.confirm({
nzTitle: 'Make sure clear all local storage?',
nzOnOk: () => {
localStorage.clear();
this.messageSrv.success('Clear Finished!');
}
});
}
}

@ -0,0 +1,45 @@
/*
* Copyright [2022] [MaxKey of copyright http://www.maxkey.top]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ChangeDetectionStrategy, Component, HostListener } from '@angular/core';
import screenfull from 'screenfull';
@Component({
selector: 'header-fullscreen',
template: `
<i nz-icon [nzType]="status ? 'fullscreen-exit' : 'fullscreen'"></i>
{{ (status ? 'menu.fullscreen.exit' : 'menu.fullscreen') | i18n }}
`,
host: {
'[class.flex-1]': 'true'
},
changeDetection: ChangeDetectionStrategy.OnPush
})
export class HeaderFullScreenComponent {
status = false;
@HostListener('window:resize')
_resize(): void {
this.status = screenfull.isFullscreen;
}
@HostListener('click')
_click(): void {
if (screenfull.isEnabled) {
screenfull.toggle();
}
}
}

@ -0,0 +1,73 @@
/*
* Copyright [2022] [MaxKey of copyright http://www.maxkey.top]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { DOCUMENT } from '@angular/common';
import { ChangeDetectionStrategy, Component, Inject, Input } from '@angular/core';
import { I18NService } from '@core';
import { ALAIN_I18N_TOKEN, SettingsService } from '@delon/theme';
import { BooleanInput, InputBoolean } from '@delon/util/decorator';
@Component({
selector: 'header-i18n',
template: `
<div *ngIf="showLangText" nz-dropdown [nzDropdownMenu]="langMenu" nzPlacement="bottomRight">
<i nz-icon nzType="global"></i>
{{ 'menu.lang' | i18n }}
<i nz-icon nzType="down"></i>
</div>
<i *ngIf="!showLangText" nz-dropdown [nzDropdownMenu]="langMenu" nzPlacement="bottomRight" nz-icon nzType="global"></i>
<nz-dropdown-menu #langMenu="nzDropdownMenu">
<ul nz-menu>
<li nz-menu-item *ngFor="let item of langs" [nzSelected]="item.code === curLangCode" (click)="change(item.code)">
<span role="img" [attr.aria-label]="item.text" class="pr-xs">{{ item.abbr }}</span>
{{ item.text }}
</li>
</ul>
</nz-dropdown-menu>
`,
host: {
'[class.flex-1]': 'true'
},
changeDetection: ChangeDetectionStrategy.OnPush
})
export class HeaderI18nComponent {
static ngAcceptInputType_showLangText: BooleanInput;
/** Whether to display language text */
@Input() @InputBoolean() showLangText = true;
get langs(): Array<{ code: string; text: string; abbr: string }> {
return this.i18n.getLangs();
}
get curLangCode(): string {
return this.settings.layout.lang;
}
constructor(private settings: SettingsService, @Inject(ALAIN_I18N_TOKEN) private i18n: I18NService, @Inject(DOCUMENT) private doc: any) {}
change(lang: string): void {
const spinEl = this.doc.createElement('div');
spinEl.setAttribute('class', `page-loading ant-spin ant-spin-lg ant-spin-spinning`);
spinEl.innerHTML = `<span class="ant-spin-dot ant-spin-dot-spin"><i></i><i></i><i></i><i></i></span>`;
this.doc.body.appendChild(spinEl);
this.i18n.loadLangData(lang).subscribe(res => {
this.i18n.use(lang, res);
this.settings.setLayout('lang', lang);
setTimeout(() => this.doc.location.reload());
});
}
}

@ -0,0 +1,86 @@
/*
* Copyright [2022] [MaxKey of copyright http://www.maxkey.top]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ChangeDetectionStrategy, ChangeDetectorRef, Component } from '@angular/core';
@Component({
selector: 'header-icon',
template: `
<div
class="alain-default__nav-item"
nz-dropdown
[nzDropdownMenu]="iconMenu"
nzTrigger="click"
nzPlacement="bottomRight"
(nzVisibleChange)="change()"
>
<i nz-icon nzType="appstore"></i>
</div>
<nz-dropdown-menu #iconMenu="nzDropdownMenu">
<div nz-menu class="wd-xl animated jello">
<nz-spin [nzSpinning]="loading" [nzTip]="'...'">
<div nz-row [nzJustify]="'center'" [nzAlign]="'middle'" class="app-icons">
<div nz-col [nzSpan]="6">
<i nz-icon nzType="calendar" class="bg-error text-white"></i>
<small>Calendar</small>
</div>
<div nz-col [nzSpan]="6">
<i nz-icon nzType="file" class="bg-geekblue text-white"></i>
<small>Files</small>
</div>
<div nz-col [nzSpan]="6">
<i nz-icon nzType="cloud" class="bg-success text-white"></i>
<small>Cloud</small>
</div>
<div nz-col [nzSpan]="6">
<i nz-icon nzType="star" class="bg-magenta text-white"></i>
<small>Star</small>
</div>
<div nz-col [nzSpan]="6">
<i nz-icon nzType="team" class="bg-purple text-white"></i>
<small>Team</small>
</div>
<div nz-col [nzSpan]="6">
<i nz-icon nzType="scan" class="bg-warning text-white"></i>
<small>QR</small>
</div>
<div nz-col [nzSpan]="6">
<i nz-icon nzType="pay-circle" class="bg-cyan text-white"></i>
<small>Pay</small>
</div>
<div nz-col [nzSpan]="6">
<i nz-icon nzType="printer" class="bg-grey text-white"></i>
<small>Print</small>
</div>
</div>
</nz-spin>
</div>
</nz-dropdown-menu>
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class HeaderIconComponent {
loading = true;
constructor(private cdr: ChangeDetectorRef) {}
change(): void {
setTimeout(() => {
this.loading = false;
this.cdr.detectChanges();
}, 500);
}
}

@ -0,0 +1,209 @@
/*
* Copyright [2022] [MaxKey of copyright http://www.maxkey.top]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ChangeDetectionStrategy, ChangeDetectorRef, Component } from '@angular/core';
import { NoticeIconList, NoticeIconSelect, NoticeItem } from '@delon/abc/notice-icon';
import { add, formatDistanceToNow, parse } from 'date-fns';
import { NzI18nService } from 'ng-zorro-antd/i18n';
import { NzMessageService } from 'ng-zorro-antd/message';
@Component({
selector: 'header-notify',
template: `
<notice-icon
[data]="data"
[count]="count"
[loading]="loading"
btnClass="alain-default__nav-item"
btnIconClass="alain-default__nav-item-icon"
(select)="select($event)"
(clear)="clear($event)"
(popoverVisibleChange)="loadData()"
></notice-icon>
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class HeaderNotifyComponent {
data: NoticeItem[] = [
{
title: '通知',
list: [],
emptyText: '你已查看所有通知',
emptyImage: 'https://gw.alipayobjects.com/zos/rmsportal/wAhyIChODzsoKIOBHcBk.svg',
clearText: '清空通知'
},
{
title: '消息',
list: [],
emptyText: '您已读完所有消息',
emptyImage: 'https://gw.alipayobjects.com/zos/rmsportal/sAuJeJzSKbUmHfBQRzmZ.svg',
clearText: '清空消息'
},
{
title: '待办',
list: [],
emptyText: '你已完成所有待办',
emptyImage: 'https://gw.alipayobjects.com/zos/rmsportal/HsIsxMZiWKrNUavQUXqx.svg',
clearText: '清空待办'
}
];
count = 5;
loading = false;
constructor(private msg: NzMessageService, private nzI18n: NzI18nService, private cdr: ChangeDetectorRef) {}
private updateNoticeData(notices: NoticeIconList[]): NoticeItem[] {
const data = this.data.slice();
data.forEach(i => (i.list = []));
notices.forEach(item => {
const newItem = { ...item } as NoticeIconList;
if (typeof newItem.datetime === 'string') {
newItem.datetime = parse(newItem.datetime, 'yyyy-MM-dd', new Date());
}
if (newItem.datetime) {
newItem.datetime = formatDistanceToNow(newItem.datetime as Date, { locale: this.nzI18n.getDateLocale() });
}
if (newItem.extra && newItem['status']) {
newItem['color'] = (
{
todo: undefined,
processing: 'blue',
urgent: 'red',
doing: 'gold'
} as { [key: string]: string | undefined }
)[newItem['status']];
}
data.find(w => w.title === newItem['type'])!.list.push(newItem);
});
return data;
}
loadData(): void {
if (this.loading) {
return;
}
this.loading = true;
setTimeout(() => {
const now = new Date();
this.data = this.updateNoticeData([
{
id: '000000001',
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png',
title: '你收到了 14 份新周报',
datetime: add(now, { days: 10 }),
type: '通知'
},
{
id: '000000002',
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/OKJXDXrmkNshAMvwtvhu.png',
title: '你推荐的 曲妮妮 已通过第三轮面试',
datetime: add(now, { days: -3 }),
type: '通知'
},
{
id: '000000003',
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/kISTdvpyTAhtGxpovNWd.png',
title: '这种模板可以区分多种通知类型',
datetime: add(now, { months: -3 }),
read: true,
type: '通知'
},
{
id: '000000004',
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png',
title: '左侧图标用于区分不同的类型',
datetime: add(now, { years: -1 }),
type: '通知'
},
{
id: '000000005',
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png',
title: '内容不要超过两行字,超出时自动截断',
datetime: '2017-08-07',
type: '通知'
},
{
id: '000000006',
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg',
title: '曲丽丽 评论了你',
description: '描述信息描述信息描述信息',
datetime: '2017-08-07',
type: '消息'
},
{
id: '000000007',
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg',
title: '朱偏右 回复了你',
description: '这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像',
datetime: '2017-08-07',
type: '消息'
},
{
id: '000000008',
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg',
title: '标题',
description: '这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像',
datetime: '2017-08-07',
type: '消息'
},
{
id: '000000009',
title: '任务名称',
description: '任务需要在 2017-01-12 20:00 前启动',
extra: '未开始',
status: 'todo',
type: '待办'
},
{
id: '000000010',
title: '第三方紧急代码变更',
description: '冠霖提交于 2017-01-06需在 2017-01-07 前完成代码变更任务',
extra: '马上到期',
status: 'urgent',
type: '待办'
},
{
id: '000000011',
title: '信息安全考试',
description: '指派竹尔于 2017-01-09 前完成更新并发布',
extra: '已耗时 8 天',
status: 'doing',
type: '待办'
},
{
id: '000000012',
title: 'ABCD 版本发布',
description: '冠霖提交于 2017-01-06需在 2017-01-07 前完成代码变更任务',
extra: '进行中',
status: 'processing',
type: '待办'
}
]);
this.loading = false;
this.cdr.detectChanges();
}, 500);
}
clear(type: string): void {
this.msg.success(`清空了 ${type}`);
}
select(res: NoticeIconSelect): void {
this.msg.success(`点击了 ${res.title}${res.item.title}`);
}
}

@ -0,0 +1,38 @@
/*
* Copyright [2022] [MaxKey of copyright http://www.maxkey.top]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ChangeDetectionStrategy, Component, HostListener } from '@angular/core';
import { RTLService } from '@delon/theme';
@Component({
selector: 'header-rtl',
template: `
<i nz-icon [nzType]="rtl.nextDir === 'rtl' ? 'border-left' : 'border-right'"></i>
{{ rtl.nextDir | uppercase }}
`,
host: {
'[class.flex-1]': 'true'
},
changeDetection: ChangeDetectionStrategy.OnPush
})
export class HeaderRTLComponent {
constructor(public rtl: RTLService) {}
@HostListener('click')
toggleDirection(): void {
this.rtl.toggle();
}
}

@ -0,0 +1,124 @@
/*
* Copyright [2022] [MaxKey of copyright http://www.maxkey.top]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
AfterViewInit,
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
ElementRef,
EventEmitter,
HostBinding,
Input,
OnDestroy,
Output
} from '@angular/core';
import { BehaviorSubject } from 'rxjs';
import { debounceTime, distinctUntilChanged, tap } from 'rxjs/operators';
@Component({
selector: 'header-search',
template: `
<nz-input-group [nzPrefix]="iconTpl" [nzSuffix]="loadingTpl">
<ng-template #iconTpl>
<i nz-icon [nzType]="focus ? 'arrow-down' : 'search'"></i>
</ng-template>
<ng-template #loadingTpl>
<i *ngIf="loading" nz-icon nzType="loading"></i>
</ng-template>
<input
type="text"
nz-input
[(ngModel)]="q"
[nzAutocomplete]="auto"
(input)="search($event)"
(focus)="qFocus()"
(blur)="qBlur()"
[attr.placeholder]="'menu.search.placeholder' | i18n"
/>
</nz-input-group>
<nz-autocomplete nzBackfill #auto>
<nz-auto-option *ngFor="let i of options" [nzValue]="i">{{ i }}</nz-auto-option>
</nz-autocomplete>
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class HeaderSearchComponent implements AfterViewInit, OnDestroy {
q = '';
qIpt: HTMLInputElement | null = null;
options: string[] = [];
search$ = new BehaviorSubject('');
loading = false;
@HostBinding('class.alain-default__search-focus')
focus = false;
@HostBinding('class.alain-default__search-toggled')
searchToggled = false;
@Input()
set toggleChange(value: boolean) {
if (typeof value === 'undefined') {
return;
}
this.searchToggled = value;
this.focus = value;
if (value) {
setTimeout(() => this.qIpt!.focus());
}
}
@Output() readonly toggleChangeChange = new EventEmitter<boolean>();
constructor(private el: ElementRef<HTMLElement>, private cdr: ChangeDetectorRef) {}
ngAfterViewInit(): void {
this.qIpt = this.el.nativeElement.querySelector('.ant-input') as HTMLInputElement;
this.search$
.pipe(
debounceTime(500),
distinctUntilChanged(),
tap({
complete: () => {
this.loading = true;
}
})
)
.subscribe(value => {
this.options = value ? [value, value + value, value + value + value] : [];
this.loading = false;
this.cdr.detectChanges();
});
}
qFocus(): void {
this.focus = true;
}
qBlur(): void {
this.focus = false;
this.searchToggled = false;
this.options.length = 0;
this.toggleChangeChange.emit(false);
}
search(ev: Event): void {
this.search$.next((ev.target as HTMLInputElement).value);
}
ngOnDestroy(): void {
this.search$.complete();
this.search$.unsubscribe();
}
}

@ -0,0 +1,104 @@
/*
* Copyright [2022] [MaxKey of copyright http://www.maxkey.top]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ChangeDetectionStrategy, ChangeDetectorRef, Component } from '@angular/core';
@Component({
selector: 'header-task',
template: `
<div
class="alain-default__nav-item"
nz-dropdown
[nzDropdownMenu]="taskMenu"
nzTrigger="click"
nzPlacement="bottomRight"
(nzVisibleChange)="change()"
>
<nz-badge [nzDot]="true">
<i nz-icon nzType="bell" class="alain-default__nav-item-icon"></i>
</nz-badge>
</div>
<nz-dropdown-menu #taskMenu="nzDropdownMenu">
<div nz-menu class="wd-lg">
<div *ngIf="loading" class="mx-lg p-lg"><nz-spin></nz-spin></div>
<nz-card *ngIf="!loading" nzTitle="Notifications" nzBordered="false" class="ant-card__body-nopadding">
<ng-template #extra><i nz-icon nzType="plus"></i></ng-template>
<div nz-row [nzJustify]="'center'" [nzAlign]="'middle'" class="py-sm pr-md point bg-grey-lighter-h">
<div nz-col [nzSpan]="4" class="text-center">
<nz-avatar [nzSrc]="'./assets/tmp/img/1.png'"></nz-avatar>
</div>
<div nz-col [nzSpan]="20">
<strong>cipchk</strong>
<p class="mb0">Please tell me what happened in a few words, don't go into details.</p>
</div>
</div>
<div nz-row [nzJustify]="'center'" [nzAlign]="'middle'" class="py-sm pr-md point bg-grey-lighter-h">
<div nz-col [nzSpan]="4" class="text-center">
<nz-avatar [nzSrc]="'./assets/tmp/img/2.png'"></nz-avatar>
</div>
<div nz-col [nzSpan]="20">
<strong></strong>
<p class="mb0"></p>
</div>
</div>
<div nz-row [nzJustify]="'center'" [nzAlign]="'middle'" class="py-sm pr-md point bg-grey-lighter-h">
<div nz-col [nzSpan]="4" class="text-center">
<nz-avatar [nzSrc]="'./assets/tmp/img/3.png'"></nz-avatar>
</div>
<div nz-col [nzSpan]="20">
<strong></strong>
<p class="mb0"></p>
</div>
</div>
<div nz-row [nzJustify]="'center'" [nzAlign]="'middle'" class="py-sm pr-md point bg-grey-lighter-h">
<div nz-col [nzSpan]="4" class="text-center">
<nz-avatar [nzSrc]="'./assets/tmp/img/4.png'"></nz-avatar>
</div>
<div nz-col [nzSpan]="20">
<strong>Kent</strong>
<p class="mb0">Please tell me what happened in a few words, don't go into details.</p>
</div>
</div>
<div nz-row [nzJustify]="'center'" [nzAlign]="'middle'" class="py-sm pr-md point bg-grey-lighter-h">
<div nz-col [nzSpan]="4" class="text-center">
<nz-avatar [nzSrc]="'./assets/tmp/img/5.png'"></nz-avatar>
</div>
<div nz-col [nzSpan]="20">
<strong>Jefferson</strong>
<p class="mb0">Please tell me what happened in a few words, don't go into details.</p>
</div>
</div>
<div nz-row>
<div nz-col [nzSpan]="24" class="pt-md border-top-1 text-center text-grey point">See All</div>
</div>
</nz-card>
</div>
</nz-dropdown-menu>
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class HeaderTaskComponent {
loading = true;
constructor(private cdr: ChangeDetectorRef) {}
change(): void {
setTimeout(() => {
this.loading = false;
this.cdr.detectChanges();
}, 500);
}
}

@ -0,0 +1,84 @@
/*
* Copyright [2022] [MaxKey of copyright http://www.maxkey.top]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ChangeDetectionStrategy, Component, Inject } from '@angular/core';
import { Router } from '@angular/router';
import { DA_SERVICE_TOKEN, ITokenService } from '@delon/auth';
import { SettingsService, User } from '@delon/theme';
import { environment } from '@env/environment';
@Component({
selector: 'header-user',
template: `
<!--
<div class="alain-default__nav-item d-flex align-items-center px-sm" nz-dropdown nzPlacement="bottomRight" [nzDropdownMenu]="userMenu">
<nz-avatar [nzSrc]="user.avatar" nzSize="small" class="mr-sm"></nz-avatar>
{{ user.name }}
</div>
<nz-dropdown-menu #userMenu="nzDropdownMenu">
<div nz-menu class="width-sm">
<div nz-menu-item routerLink="/pro/account/center">
<i nz-icon nzType="user" class="mr-sm"></i>
{{ 'menu.account.center' | i18n }}
</div>
<div nz-menu-item routerLink="/pro/account/settings">
<i nz-icon nzType="setting" class="mr-sm"></i>
{{ 'menu.account.settings' | i18n }}
</div>
<div nz-menu-item routerLink="/exception/trigger">
<i nz-icon nzType="close-circle" class="mr-sm"></i>
{{ 'menu.account.trigger' | i18n }}
</div>
<li nz-menu-divider></li>
<div nz-menu-item (click)="logout()">
<i nz-icon nzType="logout" class="mr-sm"></i>
{{ 'menu.account.logout' | i18n }}
</div>
</div>
</nz-dropdown-menu>
<div class="alain-default__nav-item d-flex align-items-center px-sm" nz-dropdown nzPlacement="bottomRight" [nzDropdownMenu]="userMenu">
<nz-avatar [nzSrc]="user.avatar" nzSize="small" class="mr-sm"></nz-avatar>
{{ user.name }}
</div>
<nz-dropdown-menu #userMenu="nzDropdownMenu">
<div nz-menu-item (click)="logout()">
<i nz-icon nzType="logout" class="mr-sm"></i>
{{ 'menu.account.logout' | i18n }}
</div>
</nz-dropdown-menu>-->
<div layout-default-header-item-trigger>
<div (click)="logout()">
<i nz-icon nzType="logout" class="mr-sm"></i>
<!--{{ 'menu.account.logout' | i18n }}-->
</div>
</div>
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class HeaderUserComponent {
get user(): User {
return this.settings.user;
}
constructor(private settings: SettingsService, private router: Router, @Inject(DA_SERVICE_TOKEN) private tokenService: ITokenService) {}
logout(): void {
//this.tokenService.clear();
//this.router.navigateByUrl(this.tokenService.login_url!);
window.location.href = `${environment.api.baseUrl}force/logout`;
}
}

@ -0,0 +1 @@
[Document](https://ng-alain.com/theme/blank)

@ -0,0 +1,26 @@
/*
* Copyright [2022] [MaxKey of copyright http://www.maxkey.top]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Component } from '@angular/core';
@Component({
selector: 'layout-blank',
template: `<router-outlet></router-outlet> `,
host: {
'[class.alain-blank]': 'true'
}
})
export class LayoutBlankComponent {}

@ -0,0 +1,95 @@
/*
* Copyright [2022] [YangLiu of copyright http://www.maxkey.top]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router';
import { GlobalFooterModule } from '@delon/abc/global-footer';
import { NoticeIconModule } from '@delon/abc/notice-icon';
import { AlainThemeModule } from '@delon/theme';
import { SettingDrawerModule } from '@delon/theme/setting-drawer';
//import { ThemeBtnModule } from '@delon/theme/theme-btn';
import { NzAutocompleteModule } from 'ng-zorro-antd/auto-complete';
import { NzAvatarModule } from 'ng-zorro-antd/avatar';
import { NzBadgeModule } from 'ng-zorro-antd/badge';
import { NzCardModule } from 'ng-zorro-antd/card';
import { NzDropDownModule } from 'ng-zorro-antd/dropdown';
import { NzFormModule } from 'ng-zorro-antd/form';
import { NzGridModule } from 'ng-zorro-antd/grid';
import { NzIconModule } from 'ng-zorro-antd/icon';
import { NzInputModule } from 'ng-zorro-antd/input';
import { NzSpinModule } from 'ng-zorro-antd/spin';
import { LayoutDefaultModule } from '../theme/layout-default';
import { ThemeBtnModule } from '../theme/theme-btn';
import { LayoutBasicComponent } from './basic/basic.component';
import { HeaderClearStorageComponent } from './basic/widgets/clear-storage.component';
import { HeaderFullScreenComponent } from './basic/widgets/fullscreen.component';
import { HeaderI18nComponent } from './basic/widgets/i18n.component';
import { HeaderIconComponent } from './basic/widgets/icon.component';
import { HeaderNotifyComponent } from './basic/widgets/notify.component';
import { HeaderRTLComponent } from './basic/widgets/rtl.component';
import { HeaderSearchComponent } from './basic/widgets/search.component';
import { HeaderTaskComponent } from './basic/widgets/task.component';
import { HeaderUserComponent } from './basic/widgets/user.component';
// eslint-disable-next-line import/order
import { LayoutBlankComponent } from './blank/blank.component';
const COMPONENTS = [LayoutBasicComponent, LayoutBlankComponent];
const HEADERCOMPONENTS = [
HeaderSearchComponent,
HeaderNotifyComponent,
HeaderTaskComponent,
HeaderIconComponent,
HeaderFullScreenComponent,
HeaderI18nComponent,
HeaderClearStorageComponent,
HeaderUserComponent,
HeaderRTLComponent
];
// passport
import { LayoutPassportComponent } from './passport/passport.component';
const PASSPORT = [LayoutPassportComponent];
@NgModule({
imports: [
CommonModule,
FormsModule,
RouterModule,
AlainThemeModule.forChild(),
ThemeBtnModule,
SettingDrawerModule,
LayoutDefaultModule,
NoticeIconModule,
GlobalFooterModule,
NzDropDownModule,
NzInputModule,
NzAutocompleteModule,
NzGridModule,
NzFormModule,
NzSpinModule,
NzBadgeModule,
NzAvatarModule,
NzIconModule,
NzCardModule
],
declarations: [...COMPONENTS, ...HEADERCOMPONENTS, ...PASSPORT],
exports: [...COMPONENTS, ...PASSPORT]
})
export class LayoutModule {}

@ -0,0 +1,42 @@
<div class="container">
<div nz-row style="border-bottom: 1px solid #e5e5e5; min-height: 60px; text-shadow: 0 1px 0 #fff">
<div nz-col nzMd="2"></div>
<div nz-col nzMd="2" style="text-align: right">
<img *ngIf="!inst.custom" style="margin-top: 6px" class="logo" src="./assets/logo.png" />
<img *ngIf="inst.custom" style="margin-top: 6px" class="logo" src="{{ inst.logo }}" />
</div>
<div nz-col nzMd="10">
<div *ngIf="!inst.custom" class="title">
<span style="color: #000099">Max</span>
<span style="color: #ffd700">Key</span>
{{ 'mxk.title' | i18n }}
</div>
<div *ngIf="inst.custom" class="title">{{ inst.title }}</div>
</div>
<div nz-col nzMd="6"></div>
<div nz-col nzXs="0" nzSm="0" nzMd="2">
<header-i18n showLangText="false" class="langs"></header-i18n>
</div>
<div nz-col nzMd="2"></div>
</div>
<div class="wrap">
<div class="top" nz-col nzXs="0" nzSm="0" nzMd="24">
<div class="desc" *ngIf="!inst.custom">
<b>{{ 'mxk.login.title.sub' | i18n }}</b>
</div>
</div>
<router-outlet></router-outlet>
<global-footer style="border-top: 1px solid #e5e5e5; min-height: 60px; text-shadow: 0 1px 0 #fff">
<div style="margin-top: 20px">
MaxKey {{ version }}<br />
Copyright
<i nz-icon nzType="copyright"></i>
{{ copyrightYear }}
<a href="//www.maxkey.top" target="_blank"> http://www.maxkey.top </a><br />
Licensed under the Apache License, Version 2.0
</div>
</global-footer>
</div>
</div>
<theme-btn></theme-btn>

@ -0,0 +1,114 @@
@import '@delon/theme/index';
:host ::ng-deep {
.container {
display: flex;
flex-direction: column;
min-height: 100%;
//background: #f0f2f5;
}
.langs {
width: 100%;
height: 40px;
line-height: 44px;
text-align: right;
.anticon {
margin-top: 24px;
margin-right: 24px;
font-size: 14px;
vertical-align: top;
cursor: pointer;
}
}
.wrap {
flex: 1;
padding: 32px 0;
}
.ant-form-item {
display: flex;
justify-content: space-between;
margin-bottom: 24px;
}
@media (min-width: @screen-md-min) {
.container {
//background-image: url('https://gw.alipayobjects.com/zos/rmsportal/TVYTbAXWheQpRcWDaDMu.svg');
background-repeat: no-repeat;
background-position: center 110px;
background-size: 100%;
}
.wrap {
padding: 32px 0 24px;
}
}
.top {
text-align: center;
}
.header {
height: 44px;
line-height: 44px;
a {
text-decoration: none;
}
}
.logo {
height: 44px;
margin-right: 16px;
}
.title {
position: relative;
margin-top: 6px;
color: @heading-color;
font-weight: 600;
font-size: 28px;
font-family: 'Myriad Pro', 'Helvetica Neue', Arial, Helvetica, sans-serif;
vertical-align: middle;
}
.desc {
margin-top: 12px;
margin-bottom: 40px;
//color: @text-color-secondary;
//font-size: @font-size-base;
font-size:17px;
}
}
[data-theme='dark'] {
:host ::ng-deep {
.container {
//background: #141414;
}
.title {
color: fade(@white, 85%);
}
.desc {
color: fade(@white, 45%);
}
@media (min-width: @screen-md-min) {
.container {
background-image: none;
}
}
}
}
[data-theme='compact'] {
:host ::ng-deep {
.ant-form-item {
margin-bottom: 16px;
}
}
}

@ -0,0 +1,62 @@
/*
* Copyright [2022] [MaxKey of copyright http://www.maxkey.top]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Component, Inject, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { DA_SERVICE_TOKEN, ITokenService } from '@delon/auth';
import { CONSTS } from 'src/app/shared/consts';
import { AuthnService } from '../../service/authn.service';
import { knowHost } from '../../shared/utils/knowhost';
@Component({
selector: 'layout-passport',
templateUrl: './passport.component.html',
styleUrls: ['./passport.component.less']
})
export class LayoutPassportComponent implements OnInit {
version = CONSTS.VERSION;
copyrightYear = new Date().getFullYear();
inst: any;
links = [
{
title: '帮助',
href: 'https://www.maxkey.top'
},
{
title: '条款',
href: 'https://www.maxkey.top/zh/about/licenses.html'
}
];
constructor(
private authnService: AuthnService,
@Inject(DA_SERVICE_TOKEN) private tokenService: ITokenService,
private route: ActivatedRoute
) {}
ngOnInit(): void {
this.inst = this.authnService.getInst();
if (this.inst == null) {
this.inst = { custom: false };
this.authnService.initInst().subscribe(res => {
this.authnService.setInst(res.data, !knowHost());
this.inst = this.authnService.getInst();
});
}
}
}

@ -0,0 +1,39 @@
/*
* Copyright [2022] [MaxKey of copyright http://www.maxkey.top]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { SharedModule } from '@shared';
import { NzIconModule } from 'ng-zorro-antd/icon';
import { SessionsComponent } from './sessions/sessions.component';
const routes: Routes = [
{
path: 'sessions',
component: SessionsComponent
}
];
const COMPONENTS = [SessionsComponent];
@NgModule({
declarations: [...COMPONENTS],
imports: [NzIconModule, SharedModule, CommonModule, RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class AccessModule {}

@ -0,0 +1,101 @@
<nz-card [nzBordered]="false">
<form nz-form [nzLayout]="'inline'" (ngSubmit)="onSearch()" class="search__form">
<div nz-row [nzGutter]="{ xs: 8, sm: 8, md: 8, lg: 24, xl: 48, xxl: 48 }">
<div nz-col nzMd="8" nzSm="24">
<nz-form-item>
<nz-form-label nzFor="startDatePicker">{{ 'mxk.text.startDate' | i18n }}</nz-form-label>
<nz-form-control>
<nz-date-picker
nzShowTime
nzFormat="yyyy-MM-dd HH:mm:ss"
[(ngModel)]="query.params.startDatePicker"
[ngModelOptions]="{ standalone: true }"
name="startDatePicker"
nzPlaceHolder="startDatePicker"
>
</nz-date-picker>
</nz-form-control>
</nz-form-item>
</div>
<div nz-col nzMd="8" nzSm="24">
<nz-form-item>
<nz-form-label nzFor="endDatePicker">{{ 'mxk.text.endDate' | i18n }}</nz-form-label>
<nz-form-control>
<nz-date-picker
nzShowTime
nzFormat="yyyy-MM-dd HH:mm:ss"
[(ngModel)]="query.params.endDatePicker"
[ngModelOptions]="{ standalone: true }"
name="endDatePicker"
nzPlaceHolder="endDatePicker"
>
</nz-date-picker>
</nz-form-control>
</nz-form-item>
</div>
<div nz-col [nzSpan]="query.expandForm ? 24 : 8" [class.text-right]="query.expandForm">
<button nz-button type="submit" [nzType]="'primary'" [nzLoading]="query.submitLoading">{{ 'mxk.text.query' | i18n }}</button>
<button nz-button type="reset" (click)="onReset()" class="mx-sm">{{ 'mxk.text.reset' | i18n }}</button>
<button nz-button (click)="query.expandForm = !query.expandForm" class="mx-sm d-none">
{{ query.expandForm ? ('mxk.text.collapse' | i18n) : ('mxk.text.expand' | i18n) }}</button
>
</div>
</div>
</form>
</nz-card>
<nz-card>
<div nz-col [nzSpan]="24" class="table-list-toolbar">
<button nz-button type="button" (click)="onTerminate($event)" [nzType]="'primary'" nzDanger class="mx-sm">{{
'mxk.text.terminate' | i18n
}}</button>
</div>
<div nz-col [nzSpan]="24">
<nz-table
#dynamicTable
nzTableLayout="auto"
nzSize="small"
nzShowSizeChanger
[nzBordered]="true"
[nzData]="query.results.rows"
[nzFrontPagination]="false"
[nzTotal]="query.results.records"
[nzPageSizeOptions]="query.params.pageSizeOptions"
[nzPageSize]="query.params.pageSize"
[nzPageIndex]="query.params.pageNumber"
[nzLoading]="this.query.tableLoading"
(nzQueryParams)="onQueryParamsChange($event)"
>
<thead>
<tr>
<th [nzChecked]="query.checked" [nzIndeterminate]="query.indeterminate" (nzCheckedChange)="onTableAllChecked($event)"></th>
<th nzAlign="center">{{ 'mxk.history.login.sessionId' | i18n }}</th>
<th nzAlign="center">{{ 'mxk.history.login.username' | i18n }}</th>
<th nzAlign="center">{{ 'mxk.history.login.displayName' | i18n }}</th>
<th nzAlign="center">{{ 'mxk.history.login.sourceIp' | i18n }}</th>
<th nzAlign="center">{{ 'mxk.history.login.browser' | i18n }}</th>
<th nzAlign="center">{{ 'mxk.history.login.platform' | i18n }}</th>
<th nzAlign="center">{{ 'mxk.history.login.loginTime' | i18n }}</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let data of query.results.rows">
<td
[nzChecked]="query.tableCheckedId.has(data.sessionId)"
[nzDisabled]="data.disabled"
(nzCheckedChange)="onTableItemChecked(data.sessionId, $event)"
></td>
<td nzAlign="left">
<span>{{ data.sessionId }}</span>
</td>
<td nzAlign="left">{{ data.username }}</td>
<td nzAlign="left">{{ data.displayName }}</td>
<td nzAlign="left">{{ data.sourceIp }}</td>
<td nzAlign="left">{{ data.browser }}</td>
<td nzAlign="left">{{ data.platform }}</td>
<td nzAlign="left">{{ data.loginTime }}</td>
</tr>
</tbody>
</nz-table>
</div>
</nz-card>

@ -0,0 +1,42 @@
/*
* Copyright [2022] [MaxKey of copyright http://www.maxkey.top]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { SessionsComponent } from './sessions.component';
describe('SessionsComponent', () => {
let component: SessionsComponent;
let fixture: ComponentFixture<SessionsComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ SessionsComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(SessionsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save