Commit c8b8e306 authored by van.chen's avatar van.chen

init

parent 4820739f
# Editor configuration, see http://editorconfig.org
root = true
[*]
end_of_line = lf
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
max_line_length = off
trim_trailing_whitespace = false
# See http://help.github.com/ignore-files/ for more about ignoring files.
# compiled output
/dist
/tmp
/out-tsc
/src/assets/alain-*.less
# dependencies
/node_modules
/yarn.lock
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
# misc
/.sass-cache
/connect.lock
/coverage
/libpeerconnection.log
npm-debug.log
testem.log
/typings
yarn-error.log
/_all.less
# e2e
/e2e/*.map
# System Files
.DS_Store
Thumbs.db
.github
node_modules
dist
tmp
# add files you wish to ignore here
{
"printWidth": 80,
"singleQuote": true,
"trailingComma": "all"
}
{
"extends": [
"stylelint-config-standard",
"./node_modules/prettier-stylelint/config.js"
],
"rules": {
"comment-empty-line-before": null,
"declaration-empty-line-before": null,
"function-comma-newline-after": null,
"function-name-case": null,
"function-parentheses-newline-inside": null,
"function-max-empty-lines": null,
"function-whitespace-after": null,
"indentation": null,
"number-leading-zero": null,
"number-no-trailing-zeros": null,
"rule-empty-line-before": null,
"selector-combinator-space-after": null,
"selector-list-comma-newline-after": null,
"selector-pseudo-element-colon-notation": null,
"unit-no-unknown": null,
"value-list-max-empty-lines": null,
"string-no-newline": null,
"selector-type-no-unknown": null,
"no-descending-specificity": null,
"selector-pseudo-element-no-unknown": [
true,
{
"ignorePseudoElements": [
"ng-deep"
]
}
]
}
}
sudo: required
dist: trusty
language: node_js
node_js:
- '10.9.0'
addons:
apt:
sources:
- google-chrome
packages:
- google-chrome-stable
- google-chrome-beta
git:
depth: 1
jobs:
include:
- env: "MODE=build"
- env: "MODE=lint"
- env: "MODE=test-coverage"
- env: "DEPLOY_MODE=artifacts"
if: branch = master
matrix:
allow_failures:
- env: "DEPLOY_MODE=artifacts"
before_install:
- export CHROME_BIN=chromium-browser
- export DISPLAY=:99.0
- sh -e /etc/init.d/xvfb start
install:
- chmod +x -R scripts
- yarn
script:
- ./scripts/_ci/travis.sh
cache:
yarn: true
directories:
- ./node_modules/
MIT License
Copyright (c) 2018-present 卡色<cipchk@qq.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
# towerFront
铁塔前端
\ No newline at end of file
铁塔前端
基于ng-alain框架
## 链接
+ [文档](https://ng-alain.com)
+ [@delon](https://github.com/ng-alain/delon)
+ [DEMO](https://ng-alain.github.io/ng-alain/)
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { <%= componentName %> } from './<%= dasherize(name) %>.component';
describe('<%= componentName %>', () => {
let component: <%= componentName %>;
let fixture: ComponentFixture<<%= componentName %>>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ <%= componentName %> ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(<%= componentName %>);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component, OnInit<% if(!!viewEncapsulation) { %>, ViewEncapsulation<% }%><% if(changeDetection !== 'Default') { %>, ChangeDetectionStrategy<% }%> } from '@angular/core';
import { _HttpClient } from '@delon/theme';
import { NzMessageService } from 'ng-zorro-antd';
@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() { }
}
[Document](https://ng-alain.com/mock)
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 = [];
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(),
'/api/401': () => { throw new MockStatusError(401); },
'/api/403': () => { throw new MockStatusError(403); },
'/api/404': () => { throw new MockStatusError(404); },
'/api/500': () => { throw new MockStatusError(500); }
};
// tslint:disable
import * as Mock from 'mockjs';
import { format } from 'date-fns';
import { deepCopy } from '@delon/util';
// region: mock data
const visitData = [];
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 = [];
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 = [];
for (let i = 0; i < 12; i += 1) {
salesData.push({
x: `${i + 1}月`,
y: Math.floor(Math.random() * 1000) + 200,
});
}
const searchData = [];
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 = [];
for (let i = 0; i < 10; i += 1) {
offlineData.push({
name: `门店${i}`,
cvr: Math.ceil(Math.random() * 9) / 10,
});
}
const offlineChartData = [];
for (let i = 0; i < 20; i += 1) {
offlineChartData.push({
x: 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 = [];
const radarTitleMap = {
ref: '引用',
koubei: '口碑',
output: '产量',
contribute: '贡献',
hot: '热度',
};
radarOriginData.forEach(item => {
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': [{ x: '@city', 'value|1-100': 150, 'category|0-2': 1 }],
}),
};
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'));
},
};
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,
},
],
},
};
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,
},
};
import { HttpRequest } from '@angular/common/http';
import { MockRequest } from '@delon/mock';
const list = [];
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) {
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) {
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),
};
import { MockRequest } from '@delon/mock';
const list = [];
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) {
let ret = [...list];
const pi = +params.pi,
ps = +params.ps,
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) {
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 password(admin/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',
},
};
export * from './_profile';
export * from './_rule';
export * from './_api';
export * from './_chart';
export * from './_pois';
export * from './_user';
export * from './_geo';
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"ng-alain": {
"root": "",
"sourceRoot": "src",
"projectType": "application",
"prefix": "app",
"schematics": {
"@schematics/angular:component": {
"styleext": "less"
}
},
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist",
"index": "src/index.html",
"main": "src/main.ts",
"tsConfig": "src/tsconfig.app.json",
"polyfills": "src/polyfills.ts",
"assets": [
"src/assets",
"src/favicon.ico"
],
"styles": [
"src/styles.less"
],
"scripts": [
"node_modules/@antv/g2/dist/g2.min.js",
"node_modules/@antv/data-set/dist/data-set.min.js",
"node_modules/@antv/g2-plugin-slider/dist/g2-plugin-slider.min.js",
"node_modules/ajv/dist/ajv.bundle.js",
"node_modules/qrious/dist/qrious.min.js"
]
},
"configurations": {
"production": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"extractCss": true,
"namedChunks": false,
"aot": true,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
"budgets": [
{
"type": "initial",
"maximumWarning": "2mb",
"maximumError": "5mb"
}
]
},
"hmr": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.hmr.ts"
}
]
}
}
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"browserTarget": "ng-alain:build"
},
"configurations": {
"production": {
"browserTarget": "ng-alain:build:production"
},
"hmr": {
"browserTarget": "ng-alain:build:hmr",
"hmr": true
}
}
},
"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",
"karmaConfig": "./src/karma.conf.js",
"polyfills": "src/polyfills.ts",
"tsConfig": "src/tsconfig.spec.json",
"scripts": [
"node_modules/@antv/g2/dist/g2.min.js",
"node_modules/@antv/data-set/dist/data-set.min.js",
"node_modules/@antv/g2-plugin-slider/dist/g2-plugin-slider.min.js",
"node_modules/ajv/dist/ajv.bundle.js",
"node_modules/qrious/dist/qrious.min.js"
],
"styles": [
],
"assets": [
"src/assets"
]
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"src/tsconfig.app.json",
"src/tsconfig.spec.json"
],
"exclude": [
"**/node_modules/**"
]
}
}
}
},
"ng-alain-e2e": {
"root": "e2e/",
"projectType": "application",
"prefix": "",
"architect": {
"e2e": {
"builder": "@angular-devkit/build-angular:protractor",
"options": {
"protractorConfig": "e2e/protractor.conf.js",
"devServerTarget": "ng-alain:serve"
},
"configurations": {
"production": {
"devServerTarget": "ng-alain:serve:production"
}
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": "e2e/tsconfig.e2e.json",
"exclude": [
"**/node_modules/**"
]
}
}
}
}
},
"defaultProject": "ng-alain"
}
// Protractor configuration file, see link for more information
// https://github.com/angular/protractor/blob/master/lib/config.ts
const { SpecReporter } = require('jasmine-spec-reporter');
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.e2e.json')
});
jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
}
};
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 ng7!');
});
});
import { browser, by, element } from 'protractor';
export class AppPage {
navigateTo() {
return browser.get('/');
}
getTitleText() {
return element(by.css('app-root h1')).getText();
}
}
{
"extends": "../tsconfig.json",
"compilerOptions": {
"baseUrl": "./",
"outDir": "../out-tsc/app",
"module": "commonjs",
"target": "es5",
"types": [
"jasmine",
"jasminewd2",
"node"
]
}
}
{
"name": "tower-front",
"version": "7.0.0-rc.6",
"author": "chendaye",
"license": "MIT",
"scripts": {
"ng": "ng",
"start": "npm run color-less && ng serve -o",
"hmr": "npm run color-less && ng serve -c=hmr",
"build": "npm run color-less && ng build --prod --build-optimizer",
"analyze": "ng build --prod --build-optimizer --stats-json",
"lint": "npm run lint:ts && npm run lint:style",
"lint:ts": "tslint -p src/tsconfig.app.json -c tslint.json 'src/**/*.ts'",
"lint:style": "stylelint \"{src}/**/*.less\" --syntax less",
"lint-staged": "lint-staged",
"tslint-check": "tslint-config-prettier-check ./tslint.json",
"e2e": "ng e2e",
"test": "ng test --watch",
"test-coverage": "ng test --code-coverage --watch=false",
"color-less": "node scripts/color-less.js",
"icon": "ng g ng-alain:plugin icon"
},
"dependencies": {
"@angular/animations": "~7.2.0",
"@angular/common": "~7.2.0",
"@angular/compiler": "~7.2.0",
"@angular/core": "~7.2.0",
"@angular/forms": "~7.2.0",
"@angular/platform-browser": "~7.2.0",
"@angular/platform-browser-dynamic": "~7.2.0",
"@angular/router": "~7.2.0",
"core-js": "^2.5.4",
"rxjs": "~6.3.3",
"tslib": "^1.9.0",
"zone.js": "~0.8.26",
"@antv/data-set": "^0.10.1",
"@antv/g2": "^3.4.8",
"@antv/g2-plugin-slider": "^2.1.1",
"@ngx-translate/core": "^11.0.1",
"@ngx-translate/http-loader": "^4.0.0",
"ajv": "^6.6.1",
"@delon/abc": "7.0.0-rc.6",
"@delon/acl": "7.0.0-rc.6",
"@delon/auth": "7.0.0-rc.6",
"@delon/cache": "7.0.0-rc.6",
"@delon/chart": "7.0.0-rc.6",
"@delon/form": "7.0.0-rc.6",
"@delon/mock": "7.0.0-rc.6",
"@delon/theme": "7.0.0-rc.6",
"@delon/util": "7.0.0-rc.6",
"file-saver": "^2.0.0",
"ng-zorro-antd": "^7.0.0-rc.3",
"ngx-countdown": "^3.1.0",
"ngx-tinymce": "^7.0.0",
"ngx-ueditor": "^2.1.3",
"screenfull": "^4.0.0",
"qrious": "^4.0.2"
},
"devDependencies": {
"@angular-devkit/build-angular": "~0.12.0",
"@angular/cli": "~7.2.0",
"@angular/compiler-cli": "~7.2.0",
"@angular/language-service": "~7.2.0",
"@types/node": "~8.9.4",
"@types/jasmine": "~2.8.8",
"@types/jasminewd2": "~2.0.3",
"codelyzer": "~4.5.0",
"jasmine-core": "~2.99.1",
"jasmine-spec-reporter": "~4.2.1",
"karma": "~3.1.1",
"karma-chrome-launcher": "~2.2.0",
"karma-coverage-istanbul-reporter": "~2.0.1",
"karma-jasmine": "~1.1.2",
"karma-jasmine-html-reporter": "^0.2.2",
"protractor": "~5.4.0",
"ts-node": "~7.0.0",
"tslint": "~5.11.0",
"typescript": "~3.2.2",
"@angularclass/hmr": "^2.1.3",
"@types/jszip": "^3.1.4",
"@types/mockjs": "^1.0.2",
"codecov": "^3.1.0",
"tslint-config-prettier": "^1.17.0",
"tslint-language-service": "^0.9.9",
"editorconfig-tools": "^0.1.1",
"husky": "^1.3.1",
"gh-pages": "^2.0.1",
"lint-staged": "^8.1.0",
"less-bundle-promise": "^1.0.7",
"mockjs": "^1.0.1-beta3",
"prettier": "^1.15.3",
"prettier-stylelint": "^0.4.2",
"stylelint": "^9.9.0",
"stylelint-config-standard": "^18.2.0",
"webpack-bundle-analyzer": "^3.0.3",
"xlsx": "^0.14.1",
"@delon/testing": "7.0.0-rc.6",
"ng-alain": "7.0.0-rc.6",
"ng-alain-codelyzer": "^0.0.1"
},
"husky": {
"hooks": {
"pre-commit": "npm run lint-staged"
}
},
"lint-staged": {
"*.{cmd,html,json,md,sh,txt,xml,yml}": [
"editorconfig-tools fix",
"git add"
],
"*.ts": [
"npm run lint:ts",
"prettier --write",
"git add"
],
"*.less": [
"npm run lint:style",
"prettier --write",
"git add"
],
"ignore": [
"src/assets/*"
]
}
}
# Only for CI, you can delete it
#!/usr/bin/env bash
set -e
GH=false
DAY_RELEASE=false
for ARG in "$@"; do
case "$ARG" in
-gh)
GH=true
;;
-dr)
DAY_RELEASE=true
;;
esac
done
cd $(dirname $0)/../..
ROOT_DIR="$(pwd)"
DIST_DIR="$(pwd)/dist"
VERSION=$(node -p "require('./package.json').version")
echo "Start build version: ${VERSION}"
echo ""
echo "Generate color less"
echo ""
node ./scripts/color-less.js
echo '===== need mock'
sed -i "s/const MOCK_MODULES = !environment.production/const MOCK_MODULES = true/g" ${ROOT_DIR}/src/app/delon.module.ts
sed -i "s/if (!environment.production)/if (true)/g" ${ROOT_DIR}/src/app/layout/default/default.component.ts
if [[ ${DAY_RELEASE} == true ]]; then
NG_ALAIN_VERSION=$(node -p "require('./node_modules/ng-alain/package.json').version")
echo "Current ng-alain version: ${NG_ALAIN_VERSION}"
echo ""
echo "Day Build, Muse be download @delon build packages"
git clone --depth 1 https://github.com/ng-alain/delon-builds.git
rm -rf node_modules/@delon
rm -rf node_modules/ng-alain
echo "Copies"
rsync -am delon-builds/ node_modules/
NG_ALAIN_VERSION=$(node -p "require('./node_modules/ng-alain/package.json').version")
echo "After ng-alain version: ${NG_ALAIN_VERSION}"
fi
echo ""
echo "Build angular"
echo ""
if [[ ${GH} == true ]]; then
$(npm bin)/ng build --prod --build-optimizer --base-href /ng-alain/
else
$(npm bin)/ng build --prod --build-optimizer
fi
cp -f ${DIST_DIR}/index.html ${DIST_DIR}/404.html
if [[ ${GH} == true ]]; then
commitAuthorName=$(git --no-pager show -s --format='%an' HEAD)
if [ ${commitAuthorName} != '卡色' ]; then
echo "Warning: Just only 卡色 user (current: ${commitAuthorName})"
exit 0
fi
if [ -z ${NG_ALAIN_BUILDS_TOKEN} ]; then
echo "Error: No access token for GitHub could be found." \
"Please set the environment variable 'NG_ALAIN_BUILDS_TOKEN'."
exit 0
fi
echo ""
echo "Deploy by gh-pages"
echo ""
$(npm bin)/gh-pages -d dist -r "https://${NG_ALAIN_BUILDS_TOKEN}@github.com/ng-alain/ng-alain.git"
fi
echo "Finished"
#!/bin/bash
set -e
readonly thisDir=$(cd $(dirname $0); pwd)
cd ${thisDir}
if [[ "${MODE}" ]]; then
echo ""
echo "Running mode: ${MODE}"
echo ""
npm run ${MODE}
elif [[ "${DEPLOY_MODE}" ]]; then
./deploy.sh -gh -dr
fi
const path = require('path');
const fs = require('fs');
const bundle = require('less-bundle-promise');
const root = path.resolve(__dirname, '../');
const allLessPath = path.join(root, '_all.less');
const target = path.join(root, 'src/assets/alain-default.less');
const content = `
@import 'node_modules/@delon/theme/styles/index';
@import 'node_modules/@delon/abc/index';
@import 'node_modules/@delon/chart/index';
@import 'node_modules/@delon/theme/styles/layout/default/index';
@import 'node_modules/@delon/theme/styles/layout/fullscreen/index';
@import 'src/styles/index';
@import 'src/styles/theme';
`;
fs.writeFileSync(allLessPath, content);
bundle({
src: allLessPath,
}).then(colorsLess => {
fs.writeFileSync(target, colorsLess);
fs.unlinkSync(allLessPath);
});
import { Component, OnInit, Renderer2, ElementRef } from '@angular/core';
import { Router, NavigationEnd } from '@angular/router';
import { filter } from 'rxjs/operators';
import { TitleService } from '@delon/theme';
import { VERSION as VERSION_ALAIN } from '@delon/theme';
import { VERSION as VERSION_ZORRO, NzModalService } from 'ng-zorro-antd';
@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() {
this.router.events
.pipe(filter(evt => evt instanceof NavigationEnd))
.subscribe(() => {
this.titleSrv.setTitle();
this.modalSrv.closeAll();
});
}
}
import { NgModule, LOCALE_ID, APP_INITIALIZER } from '@angular/core';
import { HttpClient, HttpClientModule } from '@angular/common/http';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
// #region default language
// 参考:https://ng-alain.com/docs/i18n
import { default as ngLang } from '@angular/common/locales/zh';
import { NZ_I18N, zh_CN as zorroLang } from 'ng-zorro-antd';
import { DELON_LOCALE, zh_CN as delonLang } from '@delon/theme';
const LANG = {
abbr: 'zh',
ng: ngLang,
zorro: zorroLang,
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: DELON_LOCALE, useValue: LANG.delon },
];
// #endregion
// #region i18n services
import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
import { TranslateHttpLoader } from '@ngx-translate/http-loader';
import { ALAIN_I18N_TOKEN } from '@delon/theme';
import { I18NService } from '@core';
// 加载i18n语言文件
export function I18nHttpLoaderFactory(http: HttpClient) {
return new TranslateHttpLoader(http, `assets/tmp/i18n/`, '.json');
}
const I18NSERVICE_MODULES = [
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useFactory: I18nHttpLoaderFactory,
deps: [HttpClient],
},
}),
];
const I18NSERVICE_PROVIDES = [
{ provide: ALAIN_I18N_TOKEN, useClass: I18NService, multi: false },
];
// #endregion
// #region global third module
const GLOBAL_THIRD_MODULES = [
];
// #endregion
// #region JSON Schema form (using @delon/form)
import { JsonSchemaModule } from '@shared/json-schema/json-schema.module';
const FORM_MODULES = [JsonSchemaModule];
// #endregion
// #region Http Interceptors
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { SimpleInterceptor } from '@delon/auth';
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,
): Function {
return () => startupService.load();
}
const APPINIT_PROVIDES = [
StartupService,
{
provide: APP_INITIALIZER,
useFactory: StartupServiceFactory,
deps: [StartupService],
multi: true,
},
];
// #endregion
import { DelonModule } from './delon.module';
import { CoreModule } from './core/core.module';
import { SharedModule } from './shared/shared.module';
import { AppComponent } from './app.component';
import { RoutesModule } from './routes/routes.module';
import { LayoutModule } from './layout/layout.module';
@NgModule({
declarations: [AppComponent],
imports: [
BrowserModule,
BrowserAnimationsModule,
HttpClientModule,
DelonModule.forRoot(),
CoreModule,
SharedModule,
LayoutModule,
RoutesModule,
...I18NSERVICE_MODULES,
...GLOBAL_THIRD_MODULES,
...FORM_MODULES,
],
providers: [
...LANG_PROVIDES,
...INTERCEPTOR_PROVIDES,
...I18NSERVICE_PROVIDES,
...APPINIT_PROVIDES,
],
bootstrap: [AppComponent],
})
export class AppModule {}
### CoreModule
**应** 仅只留 `providers` 属性。
**作用:** 一些通用服务,例如:用户消息、HTTP数据访问。
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');
}
}
import { TestBed, TestBedStatic } from '@angular/core/testing';
import { TranslateService } from '@ngx-translate/core';
import { SettingsService, DelonLocaleService } from '@delon/theme';
import { NzI18nService } from 'ng-zorro-antd';
import { of } from 'rxjs';
import { I18NService } from './i18n.service';
describe('Service: I18n', () => {
let injector: TestBedStatic;
let srv: I18NService;
const MockSettingsService = {
layout: {
lang: null
}
};
const MockNzI18nService = {
setLocale: () => {}
};
const MockDelonLocaleService = {
setLocale: () => {}
};
const MockTranslateService = {
getBrowserLang: jasmine.createSpy('getBrowserLang'),
addLangs: () => {},
setLocale: () => {},
getDefaultLang: () => '',
use: () => of(),
instant: jasmine.createSpy('instant')
};
function genModule() {
injector = TestBed.configureTestingModule({
providers: [
I18NService,
{ provide: SettingsService, useValue: MockSettingsService },
{ provide: NzI18nService, useValue: MockNzI18nService },
{ provide: DelonLocaleService, useValue: MockDelonLocaleService },
{ provide: TranslateService, useValue: MockTranslateService },
],
});
srv = injector.get(I18NService);
}
it('should working', () => {
genModule();
expect(srv).toBeTruthy();
expect(srv.defaultLang).toBe('zh-CN');
srv.fanyi('a');
srv.fanyi('a', {});
const t = injector.get(TranslateService) as TranslateService;
expect(t.instant).toHaveBeenCalled();
});
it('should be used layout as default language', () => {
MockSettingsService.layout.lang = 'en-US';
genModule();
expect(srv.defaultLang).toBe('en-US');
MockSettingsService.layout.lang = null;
});
it('should be used browser as default language', () => {
MockTranslateService.getBrowserLang.and.returnValue('zh-TW');
genModule();
expect(srv.defaultLang).toBe('zh-TW');
});
it('should be trigger notify when changed language', () => {
genModule();
srv.use('en-US');
srv.change.subscribe(lang => {
expect(lang).toBe('en-US');
});
});
});
// 请参考:https://ng-alain.com/docs/i18n
import { Injectable } from '@angular/core';
import { Observable, BehaviorSubject } from 'rxjs';
import { filter } from 'rxjs/operators';
import { registerLocaleData } from '@angular/common';
import ngZh from '@angular/common/locales/zh';
import ngEn from '@angular/common/locales/en';
import ngZhTw from '@angular/common/locales/zh-Hant';
import { en_US, zh_CN, zh_TW, NzI18nService } from 'ng-zorro-antd';
import * as df_en from 'date-fns/locale/en';
import * as df_zh_cn from 'date-fns/locale/zh_cn';
import * as df_zh_tw from 'date-fns/locale/zh_tw';
import { TranslateService } from '@ngx-translate/core';
import {
SettingsService,
AlainI18NService,
DelonLocaleService,
en_US as delonEnUS,
zh_CN as delonZhCn,
zh_TW as delonZhTw,
} from '@delon/theme';
interface LangData {
text: string;
ng: any;
zorro: any;
dateFns: any;
delon: any;
abbr: string;
}
const DEFAULT = 'zh-CN';
const LANGS: { [key: string]: LangData } = {
'zh-CN': {
text: '简体中文',
ng: ngZh,
zorro: zh_CN,
dateFns: df_zh_cn,
delon: delonZhCn,
abbr: '🇨🇳',
},
'zh-TW': {
text: '繁体中文',
ng: ngZhTw,
zorro: zh_TW,
dateFns: df_zh_tw,
delon: delonZhTw,
abbr: '🇭🇰',
},
'en-US': {
text: 'English',
ng: ngEn,
zorro: en_US,
dateFns: df_en,
delon: delonEnUS,
abbr: '🇬🇧',
},
};
@Injectable({ providedIn: 'root' })
export class I18NService implements AlainI18NService {
private _default = DEFAULT;
private change$ = new BehaviorSubject<string>(null);
private _langs = Object.keys(LANGS).map(code => {
const item = LANGS[code];
return { code, text: item.text, abbr: item.abbr };
});
constructor(
settings: SettingsService,
private nzI18nService: NzI18nService,
private delonLocaleService: DelonLocaleService,
private translate: TranslateService,
) {
const defaultLan = settings.layout.lang || translate.getBrowserLang();
// `@ngx-translate/core` 预先知道支持哪些语言
const lans = this._langs.map(item => item.code);
translate.addLangs(lans);
this._default = lans.includes(defaultLan) ? defaultLan : lans[0];
this.updateLangData(this._default);
}
private updateLangData(lang: string) {
const item = LANGS[lang];
registerLocaleData(item.ng);
this.nzI18nService.setLocale(item.zorro);
(window as any).__locale__ = item.dateFns;
this.delonLocaleService.setLocale(item.delon);
}
get change(): Observable<string> {
return this.change$.asObservable().pipe(filter(w => w != null));
}
use(lang: string): void {
lang = lang || this.translate.getDefaultLang();
if (this.currentLang === lang) return;
this.updateLangData(lang);
this.translate.use(lang).subscribe(() => this.change$.next(lang));
}
/** 获取语言列表 */
getLangs() {
return this._langs;
}
/** 翻译 */
fanyi(key: string, interpolateParams?: Object) {
return this.translate.instant(key, interpolateParams);
}
/** 默认语言 */
get defaultLang() {
return this._default;
}
/** 当前语言 */
get currentLang() {
return (
this.translate.currentLang ||
this.translate.getDefaultLang() ||
this._default
);
}
}
export * from './i18n/i18n.service';
export * from './module-import-guard';
export * from './net/default.interceptor';
export * from './startup/startup.service';
// https://angular.io/guide/styleguide#style-04-12
export function throwIfAlreadyLoaded(parentModule: any, moduleName: string) {
if (parentModule) {
throw new Error(`${moduleName} has already been loaded. Import Core modules in the AppModule only.`);
}
}
import { Injectable, Injector } from '@angular/core';
import { Router } from '@angular/router';
import { HttpInterceptor, HttpRequest, HttpHandler, HttpErrorResponse, HttpEvent, HttpResponseBase } from '@angular/common/http';
import { Observable, of, throwError } from 'rxjs';
import { mergeMap, catchError } from 'rxjs/operators';
import { NzMessageService, NzNotificationService } from 'ng-zorro-antd';
import { _HttpClient } from '@delon/theme';
import { environment } from '@env/environment';
import { DA_SERVICE_TOKEN, ITokenService } from '@delon/auth';
const CODEMESSAGE = {
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 {
constructor(private injector: Injector) { }
get msg(): NzMessageService {
return this.injector.get(NzMessageService);
}
private goTo(url: string) {
setTimeout(() => this.injector.get(Router).navigateByUrl(url));
}
private checkStatus(ev: HttpResponseBase) {
if (ev.status >= 200 && ev.status < 300) return;
const errortext = CODEMESSAGE[ev.status] || ev.statusText;
this.injector.get(NzNotificationService).error(
`请求错误 ${ev.status}: ${ev.url}`,
errortext
);
}
private handleData(ev: HttpResponseBase): Observable<any> {
// 可能会因为 `throw` 导出无法执行 `_HttpClient` 的 `end()` 操作
if (ev.status > 0) {
this.injector.get(_HttpClient).end();
}
this.checkStatus(ev);
// 业务处理:一些通用操作
switch (ev.status) {
case 200:
// 业务层级错误处理,以下是假定restful有一套统一输出格式(指不管成功与否都有相应的数据格式)情况下进行处理
// 例如响应内容:
// 错误内容:{ status: 1, msg: '非法参数' }
// 正确内容:{ status: 0, response: { } }
// 则以下代码片断可直接适用
// if (event instanceof HttpResponse) {
// const body: any = event.body;
// if (body && body.status !== 0) {
// this.msg.error(body.msg);
// // 继续抛出错误中断后续所有 Pipe、subscribe 操作,因此:
// // this.http.get('/').subscribe() 并不会触发
// return throwError({});
// } else {
// // 重新修改 `body` 内容为 `response` 内容,对于绝大多数场景已经无须再关心业务状态码
// return of(new HttpResponse(Object.assign(event, { body: body.response })));
// // 或者依然保持完整的格式
// return of(event);
// }
// }
break;
case 401: // 未登录状态码
// 请求错误 401: https://preview.pro.ant.design/api/401 用户没有权限(令牌、用户名、密码错误)。
(this.injector.get(DA_SERVICE_TOKEN) as ITokenService).clear();
this.goTo('/passport/login');
break;
case 403:
case 404:
case 500:
this.goTo(`/exception/${ev.status}`);
break;
default:
if (ev instanceof HttpErrorResponse) {
return throwError(ev);
}
break;
}
return of(ev);
}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
// 统一加上服务端前缀
let url = req.url;
if (!url.startsWith('https://') && !url.startsWith('http://')) {
url = environment.SERVER_URL + url;
}
const newReq = req.clone({ url });
return next.handle(newReq).pipe(
mergeMap((event: any) => {
// 允许统一对请求错误处理
if (event instanceof HttpResponseBase)
return this.handleData(event);
// 若一切都正常,则后续操作
return of(event);
}),
catchError((err: HttpErrorResponse) => this.handleData(err)),
);
}
}
import { Injectable, Inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { zip } from 'rxjs';
import { catchError } from 'rxjs/operators';
import {
MenuService,
SettingsService,
TitleService,
ALAIN_I18N_TOKEN,
} from '@delon/theme';
import { ACLService } from '@delon/acl';
import { TranslateService } from '@ngx-translate/core';
import { I18NService } from '../i18n/i18n.service';
import { NzIconService } from 'ng-zorro-antd';
import { ICONS_AUTO } from '../../../style-icons-auto';
import { ICONS } from '../../../style-icons';
/**
* 用于应用启动时
* 一般用来获取应用所需要的基础数据等
*/
@Injectable()
export class StartupService {
constructor(
iconSrv: NzIconService,
private menuService: MenuService,
private translate: TranslateService,
@Inject(ALAIN_I18N_TOKEN) private i18n: I18NService,
private settingService: SettingsService,
private aclService: ACLService,
private titleService: TitleService,
private httpClient: HttpClient,
) {
iconSrv.addIcon(...ICONS_AUTO, ...ICONS);
}
load(): Promise<any> {
// only works with promises
// https://github.com/angular/angular/issues/15088
return new Promise((resolve) => {
zip(
this.httpClient.get(`assets/tmp/i18n/${this.i18n.defaultLang}.json`),
this.httpClient.get('assets/tmp/app-data.json'),
)
.pipe(
// 接收其他拦截器后产生的异常消息
catchError(([langData, appData]) => {
resolve(null);
return [langData, appData];
}),
)
.subscribe(
([langData, appData]) => {
// setting language data
this.translate.setTranslation(this.i18n.defaultLang, langData);
this.translate.setDefaultLang(this.i18n.defaultLang);
// application data
const res: any = appData;
// 应用信息:包括站点名、描述、年份
this.settingService.setApp(res.app);
// 用户信息:包括姓名、头像、邮箱地址
this.settingService.setUser(res.user);
// ACL:设置权限为全量
this.aclService.setFull(true);
// 初始化菜单
this.menuService.add(res.menu);
// 设置页面标题的后缀
this.titleService.default = '';
this.titleService.suffix = res.app.name;
},
() => {},
() => {
resolve(null);
},
);
});
}
}
/**
* 进一步对基础模块的导入提炼
* 有关模块注册指导原则请参考:https://github.com/ng-alain/ng-alain/issues/180
*/
import { NgModule, Optional, SkipSelf, ModuleWithProviders } from '@angular/core';
import { throwIfAlreadyLoaded } from '@core';
import { AlainThemeModule } from '@delon/theme';
// #region mock
import { DelonMockModule } from '@delon/mock';
import * as MOCKDATA from '../../_mock';
import { environment } from '@env/environment';
const MOCK_MODULES = !environment.production
? [DelonMockModule.forRoot({ data: MOCKDATA })]
: [];
// #endregion
// #region reuse-tab
/**
* 若需要[路由复用](https://ng-alain.com/components/reuse-tab)需要:
* 1、增加 `REUSETAB_PROVIDES`
* 2、在 `src/app/layout/default/default.component.html` 修改:
* ```html
* <section class="alain-default__content">
* <reuse-tab></reuse-tab>
* <router-outlet></router-outlet>
* </section>
* ```
*/
import { RouteReuseStrategy } from '@angular/router';
import { ReuseTabService, ReuseTabStrategy } from '@delon/abc/reuse-tab';
const REUSETAB_PROVIDES = [
// {
// provide: RouteReuseStrategy,
// useClass: ReuseTabStrategy,
// deps: [ReuseTabService],
// },
];
// #endregion
// #region global config functions
import { PageHeaderConfig } from '@delon/abc';
export function fnPageHeaderConfig(): PageHeaderConfig {
return {
...new PageHeaderConfig(),
...{ homeI18n: 'home' } as PageHeaderConfig
};
}
import { DelonAuthConfig } from '@delon/auth';
export function fnDelonAuthConfig(): DelonAuthConfig {
return {
...new DelonAuthConfig(),
...{ login_url: '/passport/login', token_send_key: 'AccountToken' } as DelonAuthConfig
};
}
import { STConfig } from '@delon/abc';
export function fnSTConfig(): STConfig {
return {
...new STConfig(),
...{
modal: { size: 'lg' }
} as STConfig
};
}
const GLOBAL_CONFIG_PROVIDES = [
// TIPS:@delon/abc 有大量的全局配置信息,例如设置所有 `st` 的页码默认为 `20` 行
{ provide: STConfig, useFactory: fnSTConfig },
{ provide: PageHeaderConfig, useFactory: fnPageHeaderConfig },
{ provide: DelonAuthConfig, useFactory: fnDelonAuthConfig },
];
// #endregion
@NgModule({
imports: [
AlainThemeModule.forRoot(),
...MOCK_MODULES,
],
})
export class DelonModule {
constructor(@Optional() @SkipSelf() parentModule: DelonModule) {
throwIfAlreadyLoaded(parentModule, 'DelonModule');
}
static forRoot(): ModuleWithProviders {
return {
ngModule: DelonModule,
providers: [...REUSETAB_PROVIDES, ...GLOBAL_CONFIG_PROVIDES],
};
}
}
<div class="alain-default__progress-bar" *ngIf="isFetching"></div>
<layout-header class="alain-default__header"></layout-header>
<!--<layout-sidebar class="alain-default__aside"></layout-sidebar>-->
<section class="alain-default__content">
<router-outlet></router-outlet>
</section>
<ng-template #settingHost></ng-template>
import {
Component,
ViewChild,
ComponentFactoryResolver,
ViewContainerRef,
AfterViewInit,
OnInit,
OnDestroy,
ElementRef,
Renderer2,
Inject,
} from '@angular/core';
import { DOCUMENT } from '@angular/common';
import {
Router,
NavigationEnd,
RouteConfigLoadStart,
NavigationError,
NavigationCancel,
} from '@angular/router';
import { NzMessageService } from 'ng-zorro-antd';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { updateHostClass } from '@delon/util';
import { SettingsService } from '@delon/theme';
import { environment } from '@env/environment';
import { SettingDrawerComponent } from './setting-drawer/setting-drawer.component';
@Component({
selector: 'layout-default',
templateUrl: './default.component.html',
})
export class LayoutDefaultComponent implements OnInit, AfterViewInit, OnDestroy {
private unsubscribe$ = new Subject<void>();
@ViewChild('settingHost', { read: ViewContainerRef })
private settingHost: ViewContainerRef;
isFetching = false;
constructor(
router: Router,
_message: NzMessageService,
private resolver: ComponentFactoryResolver,
private settings: SettingsService,
private el: ElementRef,
private renderer: Renderer2,
@Inject(DOCUMENT) private doc: any,
) {
// scroll to top in change page
router.events.pipe(takeUntil(this.unsubscribe$)).subscribe(evt => {
if (!this.isFetching && evt instanceof RouteConfigLoadStart) {
this.isFetching = true;
}
if (evt instanceof NavigationError || evt instanceof NavigationCancel) {
this.isFetching = false;
if (evt instanceof NavigationError) {
_message.error(`无法加载${evt.url}路由`, { nzDuration: 1000 * 3 });
}
return;
}
if (!(evt instanceof NavigationEnd)) {
return;
}
setTimeout(() => {
this.isFetching = false;
}, 100);
});
}
private setClass() {
const { el, doc, renderer, settings } = this;
const layout = settings.layout;
updateHostClass(
el.nativeElement,
renderer,
{
['alain-default']: true,
[`alain-default__fixed`]: layout.fixed,
[`alain-default__collapsed`]: layout.collapsed,
},
);
doc.body.classList[layout.colorWeak ? 'add' : 'remove']('color-weak');
}
ngAfterViewInit(): void {
// Setting componet for only developer
if (!environment.production) {
setTimeout(() => {
const settingFactory = this.resolver.resolveComponentFactory(SettingDrawerComponent);
this.settingHost.createComponent(settingFactory);
}, 22);
}
}
ngOnInit() {
const { settings, unsubscribe$ } = this;
settings.notify.pipe(takeUntil(unsubscribe$)).subscribe(() => this.setClass());
this.setClass();
}
ngOnDestroy() {
const { unsubscribe$ } = this;
unsubscribe$.next();
unsubscribe$.complete();
}
}
import { Component, HostListener, ChangeDetectionStrategy } from '@angular/core';
import * as screenfull from 'screenfull';
@Component({
selector: 'header-fullscreen',
template: `
<i nz-icon [type]="status ? 'fullscreen-exit' : 'fullscreen'"></i>
{{(status ? 'menu.fullscreen.exit' : 'menu.fullscreen') | translate }}
`,
host: {
'[class.d-block]': 'true',
},
changeDetection: ChangeDetectionStrategy.OnPush
})
export class HeaderFullScreenComponent {
status = false;
@HostListener('window:resize')
_resize() {
this.status = screenfull.isFullscreen;
}
@HostListener('click')
_click() {
if (screenfull.enabled) {
screenfull.toggle();
}
}
}
import { Component, Inject, Input, ChangeDetectionStrategy } from '@angular/core';
import { DOCUMENT } from '@angular/common';
import { SettingsService, ALAIN_I18N_TOKEN } from '@delon/theme';
import { InputBoolean } from '@delon/util';
import { I18NService } from '@core';
@Component({
selector: 'header-i18n',
template: `
<nz-dropdown nzPlacement="bottomRight">
<div *ngIf="showLangText" nz-dropdown>
<i nz-icon type="global"></i>
{{ 'menu.lang' | translate}}
<i nz-icon type="down"></i>
</div>
<i *ngIf="!showLangText" nz-dropdown nz-icon type="global"></i>
<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>
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class HeaderI18nComponent {
/** Whether to display language text */
@Input() @InputBoolean() showLangText = true;
get langs() {
return this.i18n.getLangs();
}
get curLangCode() {
return this.settings.layout.lang;
}
constructor(
private settings: SettingsService,
@Inject(ALAIN_I18N_TOKEN) private i18n: I18NService,
@Inject(DOCUMENT) private doc: any,
) {
}
change(lang: string) {
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.use(lang);
this.settings.setLayout('lang', lang);
setTimeout(() => this.doc.location.reload());
}
}
import { Component, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core';
@Component({
selector: 'header-icon',
template: `
<nz-dropdown nzTrigger="click" nzPlacement="bottomRight" (nzVisibleChange)="change()">
<div class="alain-default__nav-item" nz-dropdown>
<i class="anticon anticon-appstore"></i>
</div>
<div nz-menu class="wd-xl animated jello">
<nz-spin [nzSpinning]="loading" [nzTip]="'正在读取数据...'">
<div nz-row [nzType]="'flex'" [nzJustify]="'center'" [nzAlign]="'middle'" class="app-icons">
<div nz-col [nzSpan]="6">
<i class="anticon anticon-calendar bg-error text-white"></i>
<small>Calendar</small>
</div>
<div nz-col [nzSpan]="6">
<i class="anticon anticon-file bg-geekblue text-white"></i>
<small>Files</small>
</div>
<div nz-col [nzSpan]="6">
<i class="anticon anticon-cloud bg-success text-white"></i>
<small>Cloud</small>
</div>
<div nz-col [nzSpan]="6">
<i class="anticon anticon-star-o bg-magenta text-white"></i>
<small>Star</small>
</div>
<div nz-col [nzSpan]="6">
<i class="anticon anticon-team bg-purple text-white"></i>
<small>Team</small>
</div>
<div nz-col [nzSpan]="6">
<i class="anticon anticon-scan bg-warning text-white"></i>
<small>QR</small>
</div>
<div nz-col [nzSpan]="6">
<i class="anticon anticon-pay-circle-o bg-cyan text-white"></i>
<small>Pay</small>
</div>
<div nz-col [nzSpan]="6">
<i class="anticon anticon-printer bg-grey text-white"></i>
<small>Print</small>
</div>
</div>
</nz-spin>
</div>
</nz-dropdown>
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class HeaderIconComponent {
loading = true;
constructor(private cdr: ChangeDetectorRef) { }
change() {
setTimeout(() => {
this.loading = false;
this.cdr.detectChanges();
}, 500);
}
}
import { Component, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core';
import * as distanceInWordsToNow from 'date-fns/distance_in_words_to_now';
import { NzMessageService } from 'ng-zorro-antd';
import { NoticeItem, NoticeIconList } from '@delon/abc';
/**
* 菜单通知
*/
@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 cdr: ChangeDetectorRef) {}
private updateNoticeData(notices: NoticeIconList[]): NoticeItem[] {
const data = this.data.slice();
data.forEach(i => (i.list = []));
notices.forEach(item => {
const newItem = { ...item };
if (newItem.datetime)
newItem.datetime = distanceInWordsToNow(item.datetime, {
locale: (window as any).__locale__,
});
if (newItem.extra && newItem.status) {
newItem.color = {
todo: undefined,
processing: 'blue',
urgent: 'red',
doing: 'gold',
}[newItem.status];
}
data.find(w => w.title === newItem.type).list.push(newItem);
});
return data;
}
loadData() {
if (this.loading) return;
this.loading = true;
setTimeout(() => {
this.data = this.updateNoticeData([
{
id: '000000001',
avatar:
'https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png',
title: '你收到了 14 份新周报',
datetime: '2017-08-09',
type: '通知',
},
{
id: '000000002',
avatar:
'https://gw.alipayobjects.com/zos/rmsportal/OKJXDXrmkNshAMvwtvhu.png',
title: '你推荐的 曲妮妮 已通过第三轮面试',
datetime: '2017-08-08',
type: '通知',
},
{
id: '000000003',
avatar:
'https://gw.alipayobjects.com/zos/rmsportal/kISTdvpyTAhtGxpovNWd.png',
title: '这种模板可以区分多种通知类型',
datetime: '2017-08-07',
read: true,
type: '通知',
},
{
id: '000000004',
avatar:
'https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png',
title: '左侧图标用于区分不同的类型',
datetime: '2017-08-07',
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();
}, 1000);
}
clear(type: string) {
this.msg.success(`清空了 ${type}`);
}
select(res: any) {
this.msg.success(`点击了 ${res.title}${res.item.title}`);
}
}
import {
Component,
HostBinding,
Input,
ElementRef,
AfterViewInit,
ChangeDetectionStrategy,
} from '@angular/core';
@Component({
selector: 'header-search',
template: `
<nz-input-group [nzAddOnBeforeIcon]="focus ? 'anticon anticon-arrow-down' : 'anticon anticon-search'">
<input nz-input [(ngModel)]="q" (focus)="qFocus()" (blur)="qBlur()"
[placeholder]="'menu.search.placeholder' | translate">
</nz-input-group>
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class HeaderSearchComponent implements AfterViewInit {
q: string;
qIpt: HTMLInputElement;
@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 = true;
this.focus = true;
setTimeout(() => this.qIpt.focus(), 300);
}
constructor(private el: ElementRef) {}
ngAfterViewInit() {
this.qIpt = (this.el.nativeElement as HTMLElement).querySelector('.ant-input');
}
qFocus() {
this.focus = true;
}
qBlur() {
this.focus = false;
this.searchToggled = false;
}
}
import { Component, HostListener, ChangeDetectionStrategy } from '@angular/core';
import { NzModalService, NzMessageService } from 'ng-zorro-antd';
@Component({
selector: 'header-storage',
template: `
<i nz-icon type="tool"></i>
{{ 'menu.clear.local.storage' | translate}}
`,
host: {
'[class.d-block]': 'true',
},
changeDetection: ChangeDetectionStrategy.OnPush
})
export class HeaderStorageComponent {
constructor(private modalSrv: NzModalService, private messageSrv: NzMessageService) {}
@HostListener('click')
_click() {
this.modalSrv.confirm({
nzTitle: 'Make sure clear all local storage?',
nzOnOk: () => {
localStorage.clear();
this.messageSrv.success('Clear Finished!');
},
});
}
}
import { Component, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core';
@Component({
selector: 'header-task',
template: `
<nz-dropdown nzTrigger="click" nzPlacement="bottomRight" (nzVisibleChange)="change()">
<div class="alain-default__nav-item" nz-dropdown>
<nz-badge [nzDot]="true">
<i nz-icon type="bell" class="alain-default__nav-item-icon"></i>
</nz-badge>
</div>
<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 type="plus"></i></ng-template>
<div nz-row [nzType]="'flex'" [nzJustify]="'center'" [nzAlign]="'middle'" class="py-sm bg-grey-lighter-h point">
<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 [nzType]="'flex'" [nzJustify]="'center'" [nzAlign]="'middle'" class="py-sm bg-grey-lighter-h point">
<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 [nzType]="'flex'" [nzJustify]="'center'" [nzAlign]="'middle'" class="py-sm bg-grey-lighter-h point">
<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 [nzType]="'flex'" [nzJustify]="'center'" [nzAlign]="'middle'" class="py-sm bg-grey-lighter-h point">
<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 [nzType]="'flex'" [nzJustify]="'center'" [nzAlign]="'middle'" class="py-sm bg-grey-lighter-h point">
<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>
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class HeaderTaskComponent {
loading = true;
constructor(private cdr: ChangeDetectorRef) {}
change() {
setTimeout(() => {
this.loading = false;
this.cdr.detectChanges();
}, 500);
}
}
import { Component, Inject, ChangeDetectionStrategy } from '@angular/core';
import { Router } from '@angular/router';
import { SettingsService } from '@delon/theme';
import { DA_SERVICE_TOKEN, ITokenService } from '@delon/auth';
@Component({
selector: 'header-user',
template: `
<nz-dropdown nzPlacement="bottomRight">
<div class="alain-default__nav-item d-flex align-items-center px-sm" nz-dropdown>
<nz-avatar [nzSrc]="settings.user.avatar" nzSize="small" class="mr-sm"></nz-avatar>
{{settings.user.name}}
</div>
<div nz-menu class="width-sm">
<div nz-menu-item routerLink="/pro/account/center"><i nz-icon type="user" class="mr-sm"></i>
{{ 'menu.account.center' | translate }}
</div>
<div nz-menu-item routerLink="/pro/account/settings"><i nz-icon type="setting" class="mr-sm"></i>
{{ 'menu.account.settings' | translate }}
</div>
<!--<div nz-menu-item routerLink="/exception/trigger"><i nz-icon type="close-circle" class="mr-sm"></i>-->
<!--{{ 'menu.account.trigger' | translate }}-->
<!--</div>-->
<li nz-menu-divider></li>
<div nz-menu-item (click)="logout()"><i nz-icon type="logout" class="mr-sm"></i>
{{ 'menu.account.logout' | translate }}
</div>
</div>
</nz-dropdown>
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class HeaderUserComponent {
constructor(
public settings: SettingsService,
private router: Router,
@Inject(DA_SERVICE_TOKEN) private tokenService: ITokenService,
) {}
logout() {
this.tokenService.clear();
this.router.navigateByUrl(this.tokenService.login_url);
}
}
<!--<div class="alain-default__header-logo">-->
<!--<a [routerLink]="['/']" class="alain-default__header-logo-link">-->
<!--<img class="alain-default__header-logo-expanded" src="./assets/img/logo.png" alt="{{settings.app.name}}" style="max-height:40px;padding-right: 10px;" />-->
<!--<img class="alain-default__header-logo-collapsed" src="./assets/img/logo.png" alt="{{settings.app.name}}" style="max-height:40px;padding-right: 10px;" />-->
<!--</a>-->
<!--</div>-->
<div class="alain-default__nav-wrap">
<ul class="alain-default__nav-item alain-default__nav">
<img class="alain-default__header-logo-expanded" src="./assets/img/logo.png" alt="{{settings.app.name}}" style="max-height:40px;padding-right: 10px;" />
<img class="alain-default__header-logo-collapsed" src="./assets/img/logo.png" alt="{{settings.app.name}}" style="max-height:40px;padding-right: 10px;" />
<!-- Menu -->
<!--<li>-->
<!--<div class="alain-default__nav-item" (click)="toggleCollapsedSidebar()">-->
<!--<i nz-icon type="menu-{{settings.layout.collapsed ? 'unfold' : 'fold'}}"></i>-->
<!--</div>-->
<!--</li>-->
<!-- Github Page -->
<!--<li>-->
<!--<a class="alain-default__nav-item" href="//github.com/ng-alain/ng-alain" target="_blank">-->
<!--<i nz-icon type="github"></i>-->
<!--</a>-->
<!--</li>-->
<!-- Lock Page -->
<!--<li class="hidden-mobile">-->
<!--<div class="alain-default__nav-item" routerLink="/passport/lock">-->
<!--<i nz-icon type="lock"></i>-->
<!--</div>-->
<!--</li>-->
<!-- Search Button -->
<!--<li class="hidden-pc" (click)="searchToggleChange()">-->
<!--<div class="alain-default__nav-item">-->
<!--<i nz-icon type="search"></i>-->
<!--</div>-->
<!--</li>-->
</ul>
<!--<header-search class="alain-default__search" [toggleChange]="searchToggleStatus"></header-search>-->
<ul class="alain-default__nav">
<!-- Notify -->
<!--<li>-->
<!--<header-notify></header-notify>-->
<!--</li>-->
<!-- Task -->
<!--<li class="hidden-mobile">-->
<!--<header-task></header-task>-->
<!--</li>-->
<!-- App Icons -->
<!--<li class="hidden-mobile alain-default__nav-item">-->
<!--&lt;!&ndash;<header-icon></header-icon>&ndash;&gt;-->
<!--<i nz-icon type="user" class="mr-sm"></i>-->
<!--</li>-->
<li class="hidden-mobile alain-default__nav-item" (click)="logout()">
<!--<header-icon></header-icon>-->
<i nz-icon type="logout" class="mr-sm"></i>退出登录
</li>
<!--<li class="hidden-mobile alain-default__nav-item" style="letter-spacing: 1px;">-->
<!--{{dt | date: 'yyyy-MM-dd HH:mm:ss'}}-->
<!--</li>-->
<!-- Settings -->
<!--<li class="hidden-mobile">-->
<!--<nz-dropdown nzTrigger="click" nzPlacement="bottomRight">-->
<!--<div class="alain-default__nav-item" nz-dropdown>-->
<!--<i nz-icon type="setting"></i>-->
<!--</div>-->
<!--<div nz-menu style="width:200px;">-->
<!--<div nz-menu-item>-->
<!--<header-fullscreen></header-fullscreen>-->
<!--</div>-->
<!--<div nz-menu-item>-->
<!--<header-storage></header-storage>-->
<!--</div>-->
<!--<div nz-menu-item>-->
<!--<header-i18n></header-i18n>-->
<!--</div>-->
<!--</div>-->
<!--</nz-dropdown>-->
<!--</li>-->
<!--<li class="hidden-mobile">-->
<!--<header-user></header-user>-->
<!--</li>-->
</ul>
</div>
import { Component, ChangeDetectionStrategy, Inject, OnInit, OnDestroy } from '@angular/core';
import { SettingsService } from '@delon/theme';
import { Router } from '@angular/router';
import { _HttpClient } from '@delon/theme';
import { DA_SERVICE_TOKEN, ITokenService } from '@delon/auth';
import { environment } from '@env/environment';
@Component({
selector: 'layout-header',
templateUrl: './header.component.html',
styles: [`
.alain-default__nav-item, .alain-default__nav nz-badge {
color: rgba(0, 0, 0, 0.85);
}
`],
// changeDetection: ChangeDetectionStrategy.OnPush
})
export class HeaderComponent implements OnInit, OnDestroy {
searchToggleStatus: boolean;
dt = new Date();
timer;
constructor(public settings: SettingsService,
private router: Router,
private http: _HttpClient,
@Inject(DA_SERVICE_TOKEN) private tokenService: ITokenService) { }
ngOnInit(): void {
// this.timer = setInterval(() => {
// this.dt = new Date();
// }, 1000);
}
ngOnDestroy(): void {
// clearInterval(this.timer);
}
toggleCollapsedSidebar() {
this.settings.setLayout('collapsed', !this.settings.layout.collapsed);
}
searchToggleChange() {
this.searchToggleStatus = !this.searchToggleStatus;
}
logout() {
this.http.get(environment.baseUrl + '/login/logout').subscribe((res: any) => {
console.log(res);
// this.data = res;
// this.change({ index: 0, tab: null });
// this.cdr.detectChanges();
});
this.tokenService.clear();
this.router.navigateByUrl(this.tokenService.login_url);
}
}
---
component: app-header
title: 顶部菜单
---
顶部菜单组件允许通过 `components` 目录下的组件进行按需组装。
## 组件列表
组件名 | 说明
----|------
`header-fullscreen` | 全屏切换
`header-icon` | 应用图标
`header-langs` | 语言切换
`header-notify` | 菜单通知
`header-search` | 搜索框
`header-storage` | 清除 LocalStorage 缓存
`header-task` | 任务通知
`header-theme` | 主题切换
`header-user` | 用户菜单
<span>{{i.label}}<span class="pl-sm text-grey">{{i.tip}}</span></span>
<div [ngSwitch]="i.type">
<ng-container *ngSwitchCase="'color'">
<input nz-input type="color" style="min-width: 88px" [(ngModel)]="i.value" [ngModelOptions]="{standalone: true}">
</ng-container>
<ng-container *ngSwitchCase="'input'">
<input nz-input style="min-width: 88px" [(ngModel)]="i.value" [ngModelOptions]="{standalone: true}">
</ng-container>
<ng-container *ngSwitchCase="'px'">
<nz-input-number [(ngModel)]="pxVal" (ngModelChange)="pxChange($event)" [nzMin]="i.min" [nzMax]="i.max"
[nzStep]="i.step || 2" [nzFormatter]="format"></nz-input-number>
</ng-container>
<ng-container *ngSwitchCase="'switch'">
<nz-switch nzSize="small" [(ngModel)]="i.value" [ngModelOptions]="{standalone: true}"></nz-switch>
</ng-container>
<ng-container *ngSwitchDefault>
<ng-content></ng-content>
</ng-container>
</div>
import { Component, Input } from '@angular/core';
@Component({
// tslint:disable-next-line:component-selector
selector: 'setting-drawer-item',
templateUrl: './setting-drawer-item.component.html',
host: {
'[class.setting-drawer__body-item]': 'true',
},
})
export class SettingDrawerItemComponent {
i: any = {};
@Input()
set data(val: any) {
this.i = val;
if (val.type === 'px') {
this.pxVal = +val.value.replace('px', '');
}
}
pxVal: number;
pxChange(val: number) {
this.i.value = `${val}px`;
}
format = value => `${value} px`;
}
<!--<nz-drawer [(nzVisible)]="collapse" [nzWidth]="500" (nzOnClose)="toggle()">-->
<!--<div class="setting-drawer__content">-->
<!--<div class="setting-drawer__body setting-drawer__theme">-->
<!--<h3 class="setting-drawer__title">主题色</h3>-->
<!--<span *ngFor="let c of colors" nz-tooltip [ngStyle]="{ 'background-color': c.color }" (click)="changeColor(c.color)"-->
<!--nz-tooltip [nzTitle]="c.key" class="setting-drawer__theme-tag"><i *ngIf="color === c.color"-->
<!--class="anticon anticon-check"></i></span>-->
<!--</div>-->
<!--<nz-divider></nz-divider>-->
<!--<div class="setting-drawer__body">-->
<!--<h3 class="setting-drawer__title">设置</h3>-->
<!--<nz-tabset>-->
<!--<nz-tab nzTitle="顶部">-->
<!--<div class="setting-drawer__body">-->
<!--<setting-drawer-item [data]="data['alain-default-header-hg']"></setting-drawer-item>-->
<!--<setting-drawer-item [data]="data['alain-default-header-bg']"></setting-drawer-item>-->
<!--<setting-drawer-item [data]="data['alain-default-header-padding']"></setting-drawer-item>-->
<!--</div>-->
<!--</nz-tab>-->
<!--<nz-tab nzTitle="侧边栏">-->
<!--<setting-drawer-item [data]="data['alain-default-aside-wd']"></setting-drawer-item>-->
<!--<setting-drawer-item [data]="data['alain-default-aside-bg']"></setting-drawer-item>-->
<!--<setting-drawer-item [data]="data['alain-default-aside-collapsed-wd']"></setting-drawer-item>-->
<!--<setting-drawer-item [data]="data['alain-default-aside-nav-padding-top-bottom']"></setting-drawer-item>-->
<!--</nz-tab>-->
<!--<nz-tab nzTitle="内容">-->
<!--<setting-drawer-item [data]="data['alain-default-content-bg']"></setting-drawer-item>-->
<!--<setting-drawer-item [data]="data['alain-default-content-heading-bg']"></setting-drawer-item>-->
<!--<setting-drawer-item [data]="data['alain-default-content-heading-border']"></setting-drawer-item>-->
<!--<setting-drawer-item [data]="data['alain-default-content-padding']"></setting-drawer-item>-->
<!--</nz-tab>-->
<!--<nz-tab nzTitle="其它">-->
<!--<setting-drawer-item [data]="data['form-state-visual-feedback-enabled']"></setting-drawer-item>-->
<!--<setting-drawer-item [data]="data['preserve-white-spaces-enabled']"></setting-drawer-item>-->
<!--<setting-drawer-item [data]="data['nz-table-img-radius']"></setting-drawer-item>-->
<!--<setting-drawer-item [data]="data['nz-table-img-margin-right']"></setting-drawer-item>-->
<!--<setting-drawer-item [data]="data['nz-table-img-max-width']"></setting-drawer-item>-->
<!--<setting-drawer-item [data]="data['nz-table-img-max-height']"></setting-drawer-item>-->
<!--</nz-tab>-->
<!--</nz-tabset>-->
<!--</div>-->
<!--<nz-divider></nz-divider>-->
<!--<div class="setting-drawer__body">-->
<!--<div class="setting-drawer__body-item">-->
<!--固定头和侧边栏-->
<!--<nz-switch nzSize="small" [(ngModel)]="layout.fixed" (ngModelChange)="setLayout('fixed', layout.fixed)"></nz-switch>-->
<!--</div>-->
<!--<div class="setting-drawer__body-item">-->
<!--色弱模式-->
<!--<nz-switch nzSize="small" [(ngModel)]="layout.colorWeak" (ngModelChange)="setLayout('colorWeak', layout.colorWeak)"></nz-switch>-->
<!--</div>-->
<!--</div>-->
<!--<nz-divider></nz-divider>-->
<!--<button (click)="apply()" type="button" nz-button nzType="primary">预览</button>-->
<!--<button (click)="reset()" type="button" nz-button>重置</button>-->
<!--<button (click)="copyVar()" type="button" nz-button>拷贝</button>-->
<!--<nz-alert class="mt-md" nzType="warning" nzMessage="配置栏只在开发环境用于预览,生产环境不会展现,请拷贝后手动修改参数配置文件 src/styles/theme.less"></nz-alert>-->
<!--</div>-->
<!--</nz-drawer>-->
<!--<div class="setting-drawer__handle" [ngClass]="{'setting-drawer__handle-opened': collapse}" (click)="toggle()">-->
<!--<i nz-icon [type]="!collapse ? 'setting' : 'close'" class="setting-drawer__handle-icon"></i>-->
<!--</div>-->
import {
Component,
ChangeDetectionStrategy,
NgZone,
Inject,
ChangeDetectorRef,
} from '@angular/core';
// import { DOCUMENT } from '@angular/common';
// import { NzMessageService } from 'ng-zorro-antd';
// import { LazyService, copy, deepCopy } from '@delon/util';
// import { SettingsService } from '@delon/theme';
// const ALAINDEFAULTVAR = 'alain-default-vars';
// const DEFAULT_COLORS = [
// {
// key: 'dust',
// color: '#F5222D',
// },
// {
// key: 'volcano',
// color: '#FA541C',
// },
// {
// key: 'sunset',
// color: '#FAAD14',
// },
// {
// key: 'cyan',
// color: '#13C2C2',
// },
// {
// key: 'green',
// color: '#52C41A',
// },
// {
// key: 'daybreak',
// color: '#1890ff',
// },
// {
// key: 'geekblue',
// color: '#2F54EB',
// },
// {
// key: 'purple',
// color: '#722ED1',
// },
// ];
// const DEFAULT_VARS = {
// 'primary-color': { label: '主颜色', type: 'color', default: '#1890ff' },
// 'alain-default-header-hg': {
// label: '高',
// type: 'px',
// default: '64px',
// max: 300,
// min: 24,
// },
// 'alain-default-header-bg': {
// label: '背景色',
// type: 'color',
// default: '@primary-color',
// tip: '默认同主色系',
// },
// 'alain-default-header-padding': {
// label: '顶部左右内边距',
// type: 'px',
// default: '16px',
// },
// // 侧边栏
// 'alain-default-aside-wd': { label: '宽度', type: 'px', default: '200px' },
// 'alain-default-aside-bg': {
// label: '背景',
// type: 'color',
// default: '#ffffff',
// },
// 'alain-default-aside-collapsed-wd': {
// label: '收缩宽度',
// type: 'px',
// default: '64px',
// },
// 'alain-default-aside-nav-padding-top-bottom': {
// label: '项上下内边距',
// type: 'px',
// default: '8px',
// step: 8,
// },
// // 主菜单
// 'alain-default-aside-nav-fs': {
// label: '菜单字号',
// type: 'px',
// default: '14px',
// min: 14,
// max: 30,
// },
// 'alain-default-aside-collapsed-nav-fs': {
// label: '收缩菜单字号',
// type: 'px',
// default: '24px',
// min: 24,
// max: 32,
// },
// 'alain-default-aside-nav-item-height': {
// label: '菜单项高度',
// type: 'px',
// default: '38px',
// min: 24,
// max: 64,
// },
// 'alain-default-aside-nav-text-color': {
// label: '菜单文本颜色',
// type: 'color',
// default: 'rgba(0, 0, 0, 0.65)',
// rgba: true,
// },
// 'alain-default-aside-nav-text-hover-color': {
// label: '菜单文本悬停颜色',
// type: 'color',
// default: '@primary-color',
// tip: '默认同主色系',
// },
// 'alain-default-aside-nav-group-text-color': {
// label: '菜单分组文本颜色',
// type: 'color',
// default: 'rgba(0, 0, 0, 0.43)',
// rgba: true,
// },
// 'alain-default-aside-nav-selected-text-color': {
// label: '菜单激活时文本颜色',
// type: 'color',
// default: '@primary-color',
// tip: '默认同主色系',
// },
// 'alain-default-aside-nav-selected-bg': {
// label: '菜单激活时背景颜色',
// type: 'color',
// default: '#fcfcfc',
// },
// // 内容
// 'alain-default-content-bg': {
// label: '背景色',
// type: 'color',
// default: '#f5f7fa',
// },
// 'alain-default-content-heading-bg': {
// label: '标题背景色',
// type: 'color',
// default: '#fafbfc',
// },
// 'alain-default-content-heading-border': {
// label: '标题底部边框色',
// type: 'color',
// default: '#efe3e5',
// },
// 'alain-default-content-padding': {
// label: '内边距',
// type: 'px',
// default: '24px',
// min: 0,
// max: 128,
// step: 8,
// },
// // zorro组件修正
// 'form-state-visual-feedback-enabled': {
// label: '开启表单元素的视觉反馈',
// type: 'switch',
// default: true,
// },
// 'preserve-white-spaces-enabled': {
// label: '开启 preserveWhitespaces',
// type: 'switch',
// default: true,
// },
// 'nz-table-img-radius': {
// label: '表格中:图片圆角',
// type: 'px',
// default: '4px',
// min: 0,
// max: 128,
// },
// 'nz-table-img-margin-right': {
// label: '表格中:图片右外边距',
// type: 'px',
// default: '4px',
// min: 0,
// max: 128,
// },
// 'nz-table-img-max-width': {
// label: '表格中:图片最大宽度',
// type: 'px',
// default: '32px',
// min: 8,
// max: 128,
// },
// 'nz-table-img-max-height': {
// label: '表格中:图片最大高度',
// type: 'px',
// default: '32px',
// min: 8,
// max: 128,
// },
// };
@Component({
// tslint:disable-next-line:component-selector
selector: 'setting-drawer',
templateUrl: './setting-drawer.component.html',
host: {
'[class.setting-drawer]': 'true',
},
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class SettingDrawerComponent {
// private loadedLess = false;
//
// collapse = false;
// get layout() {
// return this.settingSrv.layout;
// }
// data: any = {};
// color: string;
// colors = DEFAULT_COLORS;
//
// constructor(
// private cdr: ChangeDetectorRef,
// private msg: NzMessageService,
// private settingSrv: SettingsService,
// private lazy: LazyService,
// private zone: NgZone,
// @Inject(DOCUMENT) private doc: any,
// ) {
// this.color = this.cachedData['@primary-color'] || this.DEFAULT_PRIMARY;
// this.resetData(this.cachedData, false);
// }
//
// private get cachedData() {
// return this.settingSrv.layout[ALAINDEFAULTVAR] || {};
// }
//
// private get DEFAULT_PRIMARY() {
// return DEFAULT_VARS['primary-color'].default;
// }
//
// private loadLess(): Promise<void> {
// if (this.loadedLess) return Promise.resolve();
// return this.lazy
// .loadStyle('./assets/alain-default.less', 'stylesheet/less')
// .then(() => {
// const lessConfigNode = this.doc.createElement('script');
// lessConfigNode.innerHTML = `
// window.less = {
// async: true,
// env: 'production',
// javascriptEnabled: true
// };
// `;
// this.doc.body.appendChild(lessConfigNode);
// })
// .then(() =>
// this.lazy.loadScript(
// 'https://gw.alipayobjects.com/os/lib/less.js/3.8.1/less.min.js',
// ),
// )
// .then(() => {
// this.loadedLess = true;
// });
// }
//
// private genVars() {
// const { data, color, validKeys } = this;
// const vars: any = {
// [`@primary-color`]: color,
// };
// validKeys
// .filter(key => key !== 'primary-color')
// .forEach(key => (vars[`@${key}`] = data[key].value));
// this.setLayout(ALAINDEFAULTVAR, vars);
// return vars;
// }
//
// private runLess() {
// const { zone, msg, cdr } = this;
// const msgId = msg.loading(`正在编译主题!`, { nzDuration: 0 }).messageId;
// setTimeout(() => {
// zone.runOutsideAngular(() => {
// this.loadLess().then(() => {
// (window as any).less.modifyVars(this.genVars()).then(() => {
// msg.success('成功');
// msg.remove(msgId);
// zone.run(() => cdr.detectChanges());
// });
// });
// });
// }, 200);
// }
//
// toggle() {
// this.collapse = !this.collapse;
// }
//
// changeColor(color: string) {
// this.color = color;
// Object.keys(DEFAULT_VARS)
// .filter(key => DEFAULT_VARS[key].default === '@primary-color')
// .forEach(key => delete this.cachedData[`@${key}`]);
// this.resetData(this.cachedData, false);
// }
//
// setLayout(name: string, value: any) {
// this.settingSrv.setLayout(name, value);
// }
//
// private resetData(nowData?: Object, run = true) {
// nowData = nowData || {};
// const data = deepCopy(DEFAULT_VARS);
// Object.keys(data).forEach(key => {
// const value = nowData[`@${key}`] || data[key].default || '';
// data[key].value = value === `@primary-color` ? this.color : value;
// });
// this.data = data;
// if (run) {
// this.cdr.detectChanges();
// this.runLess();
// }
// }
//
// private get validKeys(): string[] {
// return Object.keys(this.data).filter(
// key => this.data[key].value !== this.data[key].default,
// );
// }
//
// apply() {
// this.runLess();
// }
//
// reset() {
// this.color = this.DEFAULT_PRIMARY;
// this.settingSrv.setLayout(ALAINDEFAULTVAR, {});
// this.resetData({});
// }
//
// copyVar() {
// const vars = this.genVars();
// const copyContent = Object.keys(vars)
// .map(key => `${key}: ${vars[key]};`)
// .join('\n');
// copy(copyContent);
// this.msg.success('Copy success');
// }
}
<div class="alain-default__aside-inner">
<nz-dropdown nzTrigger="click" class="alain-default__aside-user">
<div nz-dropdown class="user-block-dropdown">
<nz-avatar class="alain-default__aside-user-avatar" [nzSrc]="settings.user.avatar"></nz-avatar>
<div class="alain-default__aside-user-info">
<strong>{{settings.user.name}}</strong>
<p class="text-truncate mb0">{{settings.user.email}}</p>
</div>
</div>
<ul nz-menu>
<li nz-menu-item routerLink="/pro/account/center">{{ 'menu.account.center' | translate }}</li>
<li nz-menu-item routerLink="/pro/account/settings">{{ 'menu.account.settings' | translate }}</li>
</ul>
</nz-dropdown>
<sidebar-nav class="d-block py-lg"></sidebar-nav>
</div>
import { Component, ChangeDetectionStrategy } from '@angular/core';
import { SettingsService } from '@delon/theme';
@Component({
selector: 'layout-sidebar',
templateUrl: './sidebar.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class SidebarComponent {
constructor(public settings: SettingsService) {}
}
import { Component } from '@angular/core';
@Component({
selector: 'layout-fullscreen',
templateUrl: './fullscreen.component.html',
host: {
'[class.alain-fullscreen]': 'true',
},
})
export class LayoutFullScreenComponent {}
import { NgModule } from '@angular/core';
import { SharedModule } from '@shared';
import { LayoutDefaultComponent } from './default/default.component';
import { LayoutFullScreenComponent } from './fullscreen/fullscreen.component';
import { HeaderComponent } from './default/header/header.component';
import { SidebarComponent } from './default/sidebar/sidebar.component';
import { HeaderSearchComponent } from './default/header/components/search.component';
import { HeaderNotifyComponent } from './default/header/components/notify.component';
import { HeaderTaskComponent } from './default/header/components/task.component';
import { HeaderIconComponent } from './default/header/components/icon.component';
import { HeaderFullScreenComponent } from './default/header/components/fullscreen.component';
import { HeaderI18nComponent } from './default/header/components/i18n.component';
import { HeaderStorageComponent } from './default/header/components/storage.component';
import { HeaderUserComponent } from './default/header/components/user.component';
import { SettingDrawerComponent } from './default/setting-drawer/setting-drawer.component';
import { SettingDrawerItemComponent } from './default/setting-drawer/setting-drawer-item.component';
const SETTINGDRAWER = [SettingDrawerComponent, SettingDrawerItemComponent];
const COMPONENTS = [
LayoutDefaultComponent,
LayoutFullScreenComponent,
HeaderComponent,
SidebarComponent,
...SETTINGDRAWER,
];
const HEADERCOMPONENTS = [
HeaderSearchComponent,
HeaderNotifyComponent,
HeaderTaskComponent,
HeaderIconComponent,
HeaderFullScreenComponent,
HeaderI18nComponent,
HeaderStorageComponent,
HeaderUserComponent,
];
// passport
import { LayoutPassportComponent } from './passport/passport.component';
const PASSPORT = [LayoutPassportComponent];
@NgModule({
imports: [SharedModule],
entryComponents: SETTINGDRAWER,
declarations: [...COMPONENTS, ...HEADERCOMPONENTS, ...PASSPORT],
exports: [...COMPONENTS, ...PASSPORT],
})
export class LayoutModule {}
<div class="container">
<header-i18n showLangText="false" class="langs"></header-i18n>
<div class="wrap">
<div class="top">
<div class="head">
<img class="logo" src="./assets/img/logo.png">
<span class="title"></span>
</div>
</div>
<router-outlet></router-outlet>
<global-footer [links]="links">
Copyright
<i class="anticon anticon-copyright"></i> 2019 Witium
</global-footer>
</div>
</div>
@import '~@delon/theme/styles/default';
:host {
::ng-deep {
.container {
display: flex;
flex-direction: column;
min-height: 100%;
background: #f0f2f5;
}
.langs {
text-align: right;
width: 100%;
height: 40px;
line-height: 44px;
.anticon {
margin-top: 24px;
margin-right: 24px;
font-size: 14px;
vertical-align: top;
cursor: pointer;
}
}
.wrap {
padding: 32px 0;
flex: 1;
}
.ant-form-item {
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;
.head {
padding: 7%;
max-height: 220px;
}
}
.header {
height: 44px;
line-height: 44px;
a {
text-decoration: none;
}
}
.logo {
height: 80px;
margin-right: 25px;
}
//.title {
// font-size: 33px;
// color: @heading-color;
// font-family: 'Myriad Pro', 'Helvetica Neue', Arial, Helvetica, sans-serif;
// font-weight: 600;
// position: relative;
// vertical-align: middle;
//}
//.desc {
// font-size: @font-size-base;
// color: @text-color-secondary;
// margin-top: 12px;
// margin-bottom: 40px;
//}
}
}
import { Component } from '@angular/core';
@Component({
selector: 'layout-passport',
templateUrl: './passport.component.html',
styleUrls: ['./passport.component.less'],
})
export class LayoutPassportComponent {
links = [];
// {
// title: '帮助',
// href: '',
// },
// {
// title: '隐私',
// href: '',
// },
// {
// title: '条款',
// href: '',
// },
// ];
}
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { SocialService } from '@delon/auth';
import { SettingsService } from '@delon/theme';
@Component({
selector: 'app-callback',
template: ``,
providers: [SocialService],
})
export class CallbackComponent implements OnInit {
type: string;
constructor(
private socialService: SocialService,
private settingsSrv: SettingsService,
private route: ActivatedRoute,
) {}
ngOnInit(): void {
this.type = this.route.snapshot.params['type'];
this.mockModel();
}
private mockModel() {
const info = {
token: '123456789',
name: 'cipchk',
email: `${this.type}@${this.type}.com`,
id: 10000,
time: +new Date(),
};
this.settingsSrv.setUser({
...this.settingsSrv.user,
...info,
});
this.socialService.callback(info);
}
}
<div nz-row [nzGutter]="24" class="pt-lg">
<div nz-col nzXs="24" nzSm="12" nzMd="12" nzLg="6">
<g2-card [title]="'app.analysis.total-sales' | translate" total="¥ 126,560" contentHeight="44px" [action]="action1"
[footer]="footer1">
<ng-template #action1>
<nz-tooltip [nzTitle]="'app.analysis.introduce' | translate">
<i nz-tooltip nz-icon type="info-circle"></i>
</nz-tooltip>
</ng-template>
<trend flag="up" style="display:block; margin-top:2px;">
{{'app.analysis.week' | translate}}
<span class="pl-sm">12%</span>
</trend>
<trend flag="down">
{{'app.analysis.day' | translate}}
<span class="pl-sm">11%</span>
</trend>
<ng-template #footer1>
<p class="text-truncate mb0">
{{'app.analysis.day-sales' | translate}}
<span class="ml-sm">¥12,423</span>
</p>
</ng-template>
</g2-card>
</div>
<div nz-col nzXs="24" nzSm="12" nzMd="12" nzLg="6">
<g2-card [title]="'app.analysis.visits' | translate" total="8,848" contentHeight="46px" [action]="action2" [footer]="footer2">
<ng-template #action2>
<nz-tooltip [nzTitle]="'app.analysis.introduce' | translate">
<i nz-tooltip nz-icon type="info-circle"></i>
</nz-tooltip>
</ng-template>
<g2-mini-area *ngIf="data.visitData" color="#975FE4" height="46" [data]="data.visitData"></g2-mini-area>
<ng-template #footer2>
<p class="text-truncate mb0">
{{'app.analysis.day-visits' | translate}}
<span class="ml-sm">1,234</span>
</p>
</ng-template>
</g2-card>
</div>
<div nz-col nzXs="24" nzSm="12" nzMd="12" nzLg="6">
<g2-card [title]="'app.analysis.payments' | translate" total="6,560" contentHeight="46px" [action]="action3"
[footer]="footer3">
<ng-template #action3>
<nz-tooltip [nzTitle]="'app.analysis.introduce' | translate">
<i nz-tooltip nz-icon type="info-circle"></i>
</nz-tooltip>
</ng-template>
<g2-mini-bar *ngIf="data.visitData" height="46" [data]="data.visitData"></g2-mini-bar>
<ng-template #footer3>
<p class="text-truncate mb0">
{{'app.analysis.conversion-rate' | translate}}
<span class="ml-sm">60%</span>
</p>
</ng-template>
</g2-card>
</div>
<div nz-col nzXs="24" nzSm="12" nzMd="12" nzLg="6">
<g2-card [title]="'app.analysis.operational-effect' | translate" total="78%" contentHeight="46px" [action]="action4"
[footer]="footer4">
<ng-template #action4>
<nz-tooltip [nzTitle]="'app.analysis.introduce' | translate">
<i nz-tooltip nz-icon type="info-circle"></i>
</nz-tooltip>
</ng-template>
<g2-mini-progress height="46" percent="78" strokeWidth="8" target="80" color="#13C2C2"></g2-mini-progress>
<ng-template #footer4>
<div class="d-flex justify-content-between text-truncate">
<trend flag="up">
{{'app.analysis.week' | translate}}
<span class="pl-sm">12%</span>
</trend>
<trend flag="down">
{{'app.analysis.day' | translate}}
<span class="pl-sm">11%</span>
</trend>
</div>
</ng-template>
</g2-card>
</div>
</div>
<nz-card [nzLoading]="loading" [nzBordered]="false" class="ant-card__body-nopadding sales-card">
<nz-tabset [nzTabBarExtraContent]="extraTemplate" *ngIf="data.salesData" (nzSelectedIndexChange)="salesChange($event)">
<nz-tab *ngFor="let tab of saleTabs" [nzTitle]="('app.analysis.' + tab.key) | translate">
<div nz-row>
<div nz-col nzXs="24" nzSm="24" nzMd="12" nzLg="16">
<div class="bar">
<g2-bar *ngIf="tab.show" height="295" style="width: 100%" [title]="('app.analysis.' + tab.key + '-trend') | translate" [data]="data.salesData"></g2-bar>
</div>
</div>
<div nz-col nzXs="24" nzSm="24" nzMd="12" nzLg="8">
<div class="rank">
<h4 class="ranking-title">{{ ('app.analysis.' + tab.key + '-ranking') | translate}}</h4>
<ul class="rank-list">
<li *ngFor="let i of rankingListData; let idx = index">
<span class="number" [ngClass]="{'active': idx < 3}">{{idx+1}}</span>
<span class="title">{{i.title}}</span>
<span class="value">{{i.total | number: '3.0'}}</span>
</li>
</ul>
</div>
</div>
</div>
</nz-tab>
<ng-template #extraTemplate>
<div class="sales-extra-wrap">
<div class="sales-extra">
<a (click)="setDate('today')">{{'app.analysis.all-day' | translate}}</a>
<a (click)="setDate('week')">{{'app.analysis.all-week' | translate}}</a>
<a (click)="setDate('month')">{{'app.analysis.all-month' | translate}}</a>
<a (click)="setDate('year')">{{'app.analysis.all-year' | translate}}</a>
</div>
<nz-range-picker [(ngModel)]="date_range" style="display:inline-block; width: 256px;"></nz-range-picker>
</div>
</ng-template>
</nz-tabset>
</nz-card>
<div nz-row [nzGutter]="24">
<div nz-col nzXs="24" nzSm="24" nzMd="24" nzLg="12">
<nz-card [nzLoading]="loading" [nzBordered]="false" [nzTitle]="'app.analysis.online-top-search' | translate" [nzExtra]="extraOp" class="mb-0">
<ng-template #extraOp>
<nz-dropdown class="icon-group">
<i nz-dropdown nz-icon type="ellipsis"></i>
<ul nz-menu>
<li nz-menu-item>操作一</li>
<li nz-menu-item>操作二</li>
</ul>
</nz-dropdown>
</ng-template>
<div nz-row [nzGutter]="64">
<div nz-col nzXs="24" nzSm="12" class="mb-md">
<number-info total="12,321" subTotal="17.1" status="up" [subTitle]="subTitle">
<ng-template #subTitle>
{{'app.analysis.search-users' | translate}}
<nz-tooltip [nzTitle]="'app.analysis.introduce' | translate">
<i nz-tooltip nz-icon type="info-circle" class="ml-sm"></i>
</nz-tooltip>
</ng-template>
</number-info>
<g2-mini-area *ngIf="data.visitData2" [line]="true" height="45" [data]="data.visitData2"></g2-mini-area>
</div>
<div nz-col nzXs="24" nzSm="12" class="mb-md">
<number-info [subTitle]="'app.analysis.per-capita-search' | translate" total="2.7" subTotal="26.2" status="down"></number-info>
<g2-mini-area *ngIf="data.visitData2" [line]="true" height="45" [data]="data.visitData2"></g2-mini-area>
</div>
</div>
<st [data]="data.searchData" [columns]="searchColumn" size="small" ps="5" [page]="{toTopInChange:false}">
<ng-template st-row="range" let-i>
<trend [flag]="i.status === 1 ? 'down' : 'up'">
<span>{{i.range}}%</span>
</trend>
</ng-template>
</st>
</nz-card>
</div>
<div nz-col nzXs="24" nzSm="24" nzMd="24" nzLg="12">
<nz-card [nzLoading]="loading" [nzBordered]="false" [nzTitle]="'app.analysis.the-proportion-of-sales' | translate" [nzBodyStyle]="{'padding.px': 24}" [nzExtra]="extra"
class="sales-card mb-0" style="min-height: 502.5px">
<ng-template #extra>
<div class="sales-card-extra">
<nz-dropdown class="icon-group" nzPlacement="bottomLeft">
<i nz-dropdown nz-icon type="ellipsis"></i>
<ul nz-menu>
<li nz-menu-item>操作一</li>
<li nz-menu-item>操作二</li>
</ul>
</nz-dropdown>
<div class="sales-type-radio">
<nz-radio-group [(ngModel)]="salesType" (ngModelChange)="changeSaleType()">
<label nz-radio-button [nzValue]="'all'">
{{ 'app.analysis.channel.all' | translate }}
</label>
<label nz-radio-button [nzValue]="'online'">
{{ 'app.analysis.channel.online' | translate }}
</label>
<label nz-radio-button [nzValue]="'offline'">
{{ 'app.analysis.channel.stores' | translate }}
</label>
</nz-radio-group>
</div>
</div>
</ng-template>
<h4 class="margin:8px 0 32px 0;">{{'app.analysis.sales' | translate}}</h4>
<g2-pie *ngIf="salesPieData" [data]="salesPieData" [hasLegend]="true" [subTitle]="'app.analysis.sales' | translate" [height]="248" [lineWidth]="4" [total]="salesTotal"
[valueFormat]="handlePieValueFormat">
</g2-pie>
</nz-card>
</div>
</div>
<nz-card [nzLoading]="loading" [nzBordered]="false" [nzBodyStyle]="{'padding': '0 0 32px'}" class="offline-card mt-lg">
<nz-tabset *ngIf="data.offlineData" [(nzSelectedIndex)]="offlineIdx" (nzSelectedIndexChange)="offlineChange($event)">
<nz-tab *ngFor="let tab of data.offlineData; let i = index;" [nzTitle]="nzTabHeading">
<ng-template #nzTabHeading>
<div nz-row [nzGutter]="8" style="width: 138px; margin: 8px 0;">
<div nz-col [nzSpan]="12">
<number-info [title]="tab.name" [subTitle]="'app.analysis.conversion-rate' | translate" gap="2" [total]="(tab.cvr * 100) + '%'" [theme]="i !== offlineIdx && 'light'"></number-info>
</div>
<div nz-col [nzSpan]="12" style="padding-top: 36px;">
<g2-pie [animate]="false" [color]="i !== offlineIdx && '#BDE4FF'"
[inner]="0.55" [tooltip]="false"
[padding]="[0, 0, 0, 0]" [percent]="tab.cvr * 100" [height]="64">
</g2-pie>
</div>
</div>
</ng-template>
<div class="px-lg">
<g2-timeline *ngIf="tab.show" [data]="tab.chart" [titleMap]="titleMap"></g2-timeline>
</div>
</nz-tab>
</nz-tabset>
</nz-card>
@import '~@delon/theme/styles/default';
:host ::ng-deep {
.icon-group {
i {
transition: color 0.32s;
color: @text-color-secondary;
cursor: pointer;
margin-left: 16px;
&:hover {
color: @text-color;
}
}
}
.rank-list {
margin: 25px 0 0;
padding: 0;
list-style: none;
li {
.clearfix();
margin-top: 16px;
display: flex;
align-items: center;
span {
color: @text-color;
font-size: 14px;
line-height: 22px;
}
.number {
background-color: @background-color-base;
border-radius: 20px;
display: inline-block;
font-size: 12px;
font-weight: 600;
margin-right: 16px;
height: 20px;
line-height: 20px;
width: 20px;
text-align: center;
margin-top: 1.5px;
&.active {
background-color: #314659;
color: #fff;
}
}
.title {
flex: 1;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
margin-right: 8px;
}
}
}
.sales-extra {
display: inline-block;
margin-right: 24px;
a {
color: @text-color;
margin-left: 24px;
&:hover {
color: @primary-color;
}
&.currentDate {
color: @primary-color;
}
}
}
.sales-card {
.bar {
padding: 0 0 32px 32px;
}
.rank {
padding: 0 32px 32px 72px;
}
.ant-tabs-bar {
padding-left: 16px;
.ant-tabs-nav .ant-tabs-tab {
padding-top: 16px;
padding-bottom: 14px;
line-height: 24px;
}
}
.ant-tabs-extra-content {
padding-right: 24px;
line-height: 55px;
}
.ant-card-head {
position: relative;
}
.ant-card-head-title {
align-items: normal;
}
}
.sales-card-extra {
height: inherit;
}
.sales-type-radio {
position: absolute;
right: 54px;
bottom: 12px;
}
.offline-card {
.ant-tabs-ink-bar {
bottom: auto;
}
.ant-tabs-bar {
border-bottom: none;
}
.ant-tabs-nav-container-scrolling {
padding-left: 40px;
padding-right: 40px;
}
.ant-tabs-tab-prev-icon:before {
position: relative;
left: 6px;
}
.ant-tabs-tab-next-icon:before {
position: relative;
right: 6px;
}
.ant-tabs-tab-active h4 {
color: @primary-color;
}
}
.trend-text {
margin-left: 8px;
color: @heading-color;
}
@media screen and (max-width: @screen-lg) {
.sales-extra {
display: none;
}
.rank-list {
li {
span:first-child {
margin-right: 8px;
}
}
}
}
@media screen and (max-width: @screen-md) {
.rank-title {
margin-top: 16px;
}
.sales-card .bar {
padding: 16px;
}
}
@media screen and (max-width: @screen-sm) {
.sales-extra-wrap {
display: none;
}
.sales-card {
.ant-tabs-content {
padding-top: 30px;
}
}
}
// fix pagination bottom
.ant-table-pagination {
margin-bottom: 0;
}
.g2-pie__legend-block .g2-pie__chart {
margin: 0;
}
}
import {
Component,
ChangeDetectionStrategy,
OnInit,
ChangeDetectorRef,
} from '@angular/core';
import { NzMessageService } from 'ng-zorro-antd';
import { STColumn } from '@delon/abc';
import { getTimeDistance } from '@delon/util';
import { _HttpClient } from '@delon/theme';
import { I18NService } from '@core';
import { yuan } from '@shared';
@Component({
selector: 'app-dashboard-analysis',
templateUrl: './analysis.component.html',
styleUrls: ['./analysis.component.less'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class DashboardAnalysisComponent implements OnInit {
data: any = {};
loading = true;
date_range: Date[] = [];
rankingListData: any[] = Array(7)
.fill({})
.map((item, i) => {
return {
title: this.i18n.fanyi('app.analysis.test', { no: i }),
total: 323234,
};
});
titleMap = {
y1: this.i18n.fanyi('app.analysis.traffic'),
y2: this.i18n.fanyi('app.analysis.payments'),
};
searchColumn: STColumn[] = [
{ title: '排名', i18n: 'app.analysis.table.rank', index: 'index' },
{
title: '搜索关键词',
i18n: 'app.analysis.table.search-keyword',
index: 'keyword',
click: (item: any) => this.msg.success(item.keyword),
},
{
type: 'number',
title: '用户数',
i18n: 'app.analysis.table.users',
index: 'count',
sorter: (a, b) => a.count - b.count,
},
{
type: 'number',
title: '周涨幅',
i18n: 'app.analysis.table.weekly-range',
index: 'range',
render: 'range',
sorter: (a, b) => a.range - b.range,
},
];
constructor(
private http: _HttpClient,
public msg: NzMessageService,
private i18n: I18NService,
private cdr: ChangeDetectorRef,
) {}
ngOnInit() {
this.http.get('/chart').subscribe((res: any) => {
res.offlineData.forEach((item: any, idx: number) => {
item.show = idx === 0;
item.chart = Object.assign([], res.offlineChartData);
});
this.data = res;
this.loading = false;
this.changeSaleType();
});
}
setDate(type: any) {
this.date_range = getTimeDistance(type);
setTimeout(() => this.cdr.detectChanges());
}
salesType = 'all';
salesPieData: any;
salesTotal = 0;
changeSaleType() {
this.salesPieData =
this.salesType === 'all'
? this.data.salesTypeData
: this.salesType === 'online'
? this.data.salesTypeDataOnline
: this.data.salesTypeDataOffline;
if (this.salesPieData) {
this.salesTotal = this.salesPieData.reduce((pre, now) => now.y + pre, 0);
}
this.cdr.detectChanges();
}
handlePieValueFormat(value: any) {
return yuan(value);
}
saleTabs: any[] = [
{ key: 'sales', show: true },
{ key: 'visits' },
];
salesChange(idx: number) {
if (this.saleTabs[idx].show !== true) {
this.saleTabs[idx].show = true;
this.cdr.detectChanges();
}
}
offlineIdx = 0;
offlineChange(idx: number) {
if (this.data.offlineData[idx].show !== true) {
this.data.offlineData[idx].show = true;
this.cdr.detectChanges();
}
}
}
<div nz-row [nzGutter]="24" class="pt-lg">
<div nz-col nzXs="24" nzSm="24" nzMd="24" nzLg="18">
<nz-card [nzTitle]="'app.monitor.trading-activity' | translate" [nzBordered]="false" class="mb-lg">
<div nz-row>
<div nz-col nzXs="24" nzSm="12" nzMd="6">
<number-info [subTitle]="'app.monitor.total-transactions' | translate" [total]="'124,543,233'" suffix="元"></number-info>
</div>
<div nz-col nzXs="24" nzSm="12" nzMd="6">
<number-info [subTitle]="'app.monitor.sales-target' | translate" [total]="'92%'"></number-info>
</div>
<div nz-col nzXs="24" nzSm="12" nzMd="6">
<number-info [subTitle]="'app.monitor.remaining-time' | translate" [total]="lastTotalTime">
<ng-template #lastTotalTime>
<count-down [target]="30"></count-down>
</ng-template>
</number-info>
</div>
<div nz-col nzXs="24" nzSm="12" nzMd="6">
<number-info [subTitle]="'app.monitor.total-transactions-per-second' | translate" [total]="234" suffix="元"></number-info>
</div>
</div>
<div class="map-chart">
<nz-tooltip [nzTitle]="'app.monitor.waiting-for-implementation' | translate">
<img nz-tooltip nzPlacement="top" src="https://gw.alipayobjects.com/zos/rmsportal/HBWnDEUXCnGnGrRfrpKa.png" alt="map" />
</nz-tooltip>
</div>
</nz-card>
</div>
<div nz-col nzXs="24" nzSm="24" nzMd="24" nzLg="6">
<nz-card [nzTitle]="'app.monitor.activity-forecast' | translate" [nzBordered]="false" class="mb-lg">
<div class="active-chart" *ngIf="activeData">
<number-info subTitle="目标评估" total="有望达到预期"></number-info>
<g2-mini-area [animate]="false" line [borderWidth]="2" [height]="84" padding="0" [data]="activeData"></g2-mini-area>
<div class="active-grid">
<p>{{activeStat.max}} 亿元</p>
<p>{{activeStat.min}} 亿元</p>
</div>
<div class="active-legend">
<span>00:00</span>
<span>{{activeStat.t1}}</span>
<span>{{activeStat.t2}}</span>
</div>
</div>
</nz-card>
<nz-card [nzTitle]="'app.monitor.efficiency' | translate" [nzBordered]="false" [nzBodyStyle]="{'text-align': 'center'}" class="mb-lg">
<g2-gauge *ngIf="percent" [title]="'app.monitor.ratio' | translate" [height]="180" [percent]="percent" [format]="couponFormat"></g2-gauge>
</nz-card>
</div>
</div>
<div nz-row [nzGutter]="24">
<div nz-col nzXs="24" nzSm="24" nzLg="12" class="mb-lg">
<nz-card [nzTitle]="'app.monitor.proportion-per-category' | translate" [nzBordered]="false" class="pie-card">
<div nz-row [nzGutter]="4" style="padding:16px 0">
<div nz-col [nzSpan]="8">
<g2-pie [animate]="false" [percent]="28" [subTitle]="'app.monitor.fast-food' | translate" total="28%" [height]="128" [lineWidth]="2"></g2-pie>
</div>
<div nz-col [nzSpan]="8">
<g2-pie [animate]="false" color="#5DDECF" [percent]="22" [subTitle]="'app.monitor.western-food' | translate" total="22%" [height]="128" [lineWidth]="2"></g2-pie>
</div>
<div nz-col [nzSpan]="8">
<g2-pie [animate]="false" color="#2FC25B" [percent]="32" [subTitle]="'app.monitor.hot-pot' | translate" total="32%" [height]="128" [lineWidth]="2"></g2-pie>
</div>
</div>
</nz-card>
</div>
<div nz-col nzXs="24" nzSm="24" nzLg="6" class="mb-lg">
<nz-card [nzTitle]="'app.monitor.popular-searches' | translate" [nzBordered]="false">
<g2-tag-cloud [data]="tags" [height]="165"></g2-tag-cloud>
</nz-card>
</div>
<div nz-col nzXs="24" nzSm="24" nzLg="6" class="mb-lg">
<nz-card [nzTitle]="'app.monitor.resource-surplus' | translate" [nzBordered]="false">
<div class="text-center">
<g2-water-wave [title]="'app.monitor.fund-surplus' | translate" [percent]="34" [height]="165"></g2-water-wave>
</div>
</nz-card>
</div>
</div>
@import '~@delon/theme/styles/default';
:host ::ng-deep {
.map-chart {
padding-top: 24px;
height: 457px;
text-align: center;
img {
display: inline-block;
max-width: 100%;
max-height: 437px;
}
}
.pie-card {
.pie-stat {
font-size: 24px !important;
}
}
.active-chart {
position: relative;
g2-mini-area {
margin-top: 32px;
}
.active-grid {
p {
position: absolute;
top: 80px;
border-bottom: 1px dashed #e9e9e9;
padding-bottom: 4px;
width: 100%;
}
p:last-child {
top: 115px;
}
}
.active-legend {
position: relative;
font-size: 0;
margin-top: 8px;
height: 20px;
line-height: 20px;
span {
display: inline-block;
font-size: 12px;
text-align: center;
width: 33.33%;
}
span:first-child {
text-align: left;
}
span:last-child {
text-align: right;
}
}
}
@media screen and (max-width: @screen-lg) {
.map-chart {
height: auto;
}
}
}
import {
Component,
OnInit,
OnDestroy,
ChangeDetectionStrategy,
ChangeDetectorRef,
} from '@angular/core';
import { NzMessageService } from 'ng-zorro-antd';
import { zip } from 'rxjs';
import { _HttpClient } from '@delon/theme';
@Component({
selector: 'app-dashboard-monitor',
templateUrl: './monitor.component.html',
styleUrls: ['./monitor.component.less'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class DashboardMonitorComponent implements OnInit, OnDestroy {
data: any = {};
tags = [];
loading = true;
q: any = {
start: null,
end: null,
};
percent = null;
constructor(
private http: _HttpClient,
public msg: NzMessageService,
private cdr: ChangeDetectorRef,
) {}
ngOnInit() {
zip(this.http.get('/chart'), this.http.get('/chart/tags')).subscribe(
([res, tags]: [any, any]) => {
this.data = res;
tags.list[
Math.floor(Math.random() * tags.list.length) + 1
].value = 1000;
this.tags = tags.list;
this.loading = false;
this.cdr.detectChanges();
},
);
// active chart
this.refData();
this.activeTime$ = setInterval(() => this.refData(), 1000 * 2);
}
// region: active chart
activeTime$: any;
activeData: any[];
activeStat = {
max: 0,
min: 0,
t1: '',
t2: '',
};
refData() {
const activeData = [];
for (let i = 0; i < 24; i += 1) {
activeData.push({
x: `${i.toString().padStart(2, '0')}:00`,
y: i * 50 + Math.floor(Math.random() * 200),
});
}
this.activeData = activeData;
// stat
this.activeStat.max = [...activeData].sort()[activeData.length - 1].y + 200;
this.activeStat.min = [...activeData].sort()[Math.floor(activeData.length / 2)].y;
this.activeStat.t1 = activeData[Math.floor(activeData.length / 2)].x;
this.activeStat.t2 = activeData[activeData.length - 1].x;
// percent
this.percent = Math.floor(Math.random() * 100);
this.cdr.detectChanges();
}
// endregion
couponFormat(val: any) {
switch (parseInt(val, 10)) {
case 20:
return '差';
case 40:
return '中';
case 60:
return '良';
case 80:
return '优';
default:
return '';
}
}
ngOnDestroy(): void {
if (this.activeTime$) clearInterval(this.activeTime$);
}
}
<div class="alain-default__content-title">
<h1>
Dashboard
<small>Welcome !</small>
</h1>
</div>
<!--<quick-menu>-->
<!--<nz-list [nzBordered]="false" [nzSplit]="false">-->
<!--<nz-list-item>-->
<!--<a routerLink="/">Home</a>-->
<!--</nz-list-item>-->
<!--<nz-list-item>-->
<!--<a routerLink="/widgets">Widgets</a>-->
<!--</nz-list-item>-->
<!--<nz-list-item>-->
<!--<a routerLink="/style/typography">typography</a>-->
<!--</nz-list-item>-->
<!--<nz-list-item>-->
<!--<a routerLink="/style/gridmasonry">gridmasonry</a>-->
<!--</nz-list-item>-->
<!--<nz-list-item>-->
<!--<a routerLink="/pro/result/success">success result</a>-->
<!--</nz-list-item>-->
<!--</nz-list>-->
<!--</quick-menu>-->
<div nz-row nzGutter="16">
<div nz-col nzXs="24" nzSm="12" nzMd="6" class="mb-md">
<div nz-row nzType="flex" nzAlign="middle" class="bg-primary rounded-md">
<div nz-col nzSpan="12" class="p-md text-white">
<div class="h2 mt0">123,456</div>
<p class="text-nowrap mb0">Website Traffics</p>
</div>
<div nz-col nzSpan="12">
<g2-mini-bar *ngIf="webSite" height="35" color="#fff" borderWidth="3" [padding]="[36, 30, 30, 30]" [data]="webSite" tooltipType="mini"></g2-mini-bar>
</div>
</div>
</div>
<div nz-col nzXs="24" nzSm="12" nzMd="6" class="mb-md">
<div nz-row nzType="flex" nzAlign="middle" class="bg-success rounded-md">
<div nz-col nzSpan="12" class="p-md text-white">
<div class="h2 mt0">234,567K</div>
<p class="text-nowrap mb0">Website Impressions</p>
</div>
<div nz-col nzSpan="12">
<g2-mini-bar *ngIf="webSite" height="35" color="#fff" borderWidth="3" [padding]="[36, 30, 30, 30]" [data]="webSite" tooltipType="mini"></g2-mini-bar>
</div>
</div>
</div>
<div nz-col nzXs="24" nzSm="12" nzMd="6" class="mb-md">
<div nz-row nzType="flex" nzAlign="middle" class="bg-orange rounded-md">
<div nz-col nzSpan="12" class="p-md text-white">
<div class="h2 mt0">$458,778</div>
<p class="text-nowrap mb0">Total Sales</p>
</div>
<div nz-col nzSpan="12">
<g2-mini-bar *ngIf="webSite" height="35" color="#fff" borderWidth="3" [padding]="[36, 30, 30, 30]" [data]="webSite" tooltipType="mini"></g2-mini-bar>
</div>
</div>
</div>
<div nz-col nzXs="24" nzSm="12" nzMd="6" class="mb-md">
<div nz-row nzType="flex" nzAlign="middle" class="bg-magenta rounded-md">
<div nz-col nzSpan="12" class="p-md text-white">
<div class="h2 mt0">456</div>
<p class="text-nowrap mb0">Support Tickets</p>
</div>
<div nz-col nzSpan="12">
<g2-mini-bar *ngIf="webSite" height="35" color="#fff" borderWidth="3" [padding]="[36, 30, 30, 30]" [data]="webSite" tooltipType="mini"></g2-mini-bar>
</div>
</div>
</div>
</div>
<div nz-row nzGutter="16">
<div nz-col nzXs="24" nzMd="12">
<nz-card [nzBordered]="false" [nzTitle]="salesTitle">
<ng-template #salesTitle>
Sales Statistics
<small class="text-sm font-weight-normal">Business Expectations & Retail Sales Statistics</small>
</ng-template>
<g2-bar *ngIf="salesData" height="275" [data]="salesData"></g2-bar>
</nz-card>
</div>
<div nz-col nzXs="24" nzMd="12">
<nz-card [nzTitle]="growthTitle" [nzBordered]="false">
<ng-template #growthTitle>
Growth Rate
<small class="text-sm font-weight-normal">Business Expectations & Retail Sales Statistics</small>
</ng-template>
<g2-timeline *ngIf="offlineChartData" [data]="offlineChartData" [height]="239" [padding]="[0, 0, 0, 0]" [titleMap]="{ y1: '客流量', y2: '支付笔数' }"></g2-timeline>
</nz-card>
</div>
</div>
<div nz-row nzGutter="16">
<div nz-col nzXs="24" nzMd="12">
<nz-card [nzBordered]="false" [nzCover]="coverTpl">
<ng-template #coverTpl>
<img class="img" src="//os.alipayobjects.com/rmsportal/GhjqstwSgxBXrZS.png">
</ng-template>
<h3>ANT DESIGN</h3>
<p class="text-grey">A UI Design Language</p>
<ol class="list-styled text-lg pt-md">
<li>Designed by experienced team, and showcase dozens of inspiring projects.</li>
<li>Provide solutions for usual problems that may be encountered while developing enterprise-like complex UIs.</li>
<li>Dozens of flexible and practical reusable components that increase your productivity.</li>
</ol>
<p class="pt-md mb0">
<a class="text-grey" href="//ng.ant.design" target="_blank">View Site...</a>
</p>
</nz-card>
</div>
<div nz-col nzXs="24" nzMd="12">
<nz-card [nzTitle]="recentTitle" [nzBordered]="false" class="ant-card__body-nopadding">
<ng-template #recentTitle>
Recent Posts
<small class="text-sm font-weight-normal">Venenatis portauam Inceptos ameteiam</small>
</ng-template>
<div nz-row [nzType]="'flex'" [nzJustify]="'center'" [nzAlign]="'middle'" class="py-sm bg-grey-lighter-h point" *ngFor="let item of todoData">
<div nz-col [nzSpan]="4" class="text-center">
<nz-avatar [nzSrc]="'./assets/tmp/img/' + item.avatar + '.png'"></nz-avatar>
</div>
<div nz-col [nzSpan]="20">
<strong>{{item.name}}</strong>
<p class="mb0">{{item.content}}</p>
</div>
</div>
</nz-card>
</div>
<div nz-col nzXs="24" nzMd="12">
<nz-card nzTitle="Todo lists" [nzBordered]="false" class="ant-card__body-nopadding">
<div nz-row [nzType]="'flex'" [nzJustify]="'center'" [nzAlign]="'middle'" class="py-sm bg-grey-lighter-h point" *ngFor="let item of todoData">
<div nz-col [nzSpan]="4" class="text-center">
<nz-avatar [nzSrc]="'./assets/tmp/img/' + item.avatar + '.png'"></nz-avatar>
</div>
<div nz-col [nzSpan]="18">
<strong>{{item.name}}</strong>
<p [class.text-deleted]="item.completed" class="mb0">{{item.content}}</p>
</div>
<div nz-col [nzSpan]="2" class="text-right pr-md">
<nz-dropdown [nzPlacement]="'topRight'">
<i nz-dropdown nz-icon type="ellipsis" class="rotate-90"></i>
<ul nz-menu>
<li nz-menu-item *ngIf="item.completed" (click)="item.completed=false">Active</li>
<li nz-menu-item *ngIf="!item.completed" (click)="item.completed=true">Completed</li>
<li nz-menu-item (click)="todoData.splice(todoData.indexOf(item), 1)">Delted</li>
</ul>
</nz-dropdown>
</div>
</div>
</nz-card>
</div>
</div>
import {
Component,
OnInit,
ChangeDetectionStrategy,
ChangeDetectorRef,
} from '@angular/core';
import { _HttpClient } from '@delon/theme';
@Component({
selector: 'app-dashboard-v1',
templateUrl: './v1.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class DashboardV1Component implements OnInit {
todoData: any[] = [
{
completed: true,
avatar: '1',
name: '苏先生',
content: `请告诉我,我应该说点什么好?`,
},
{
completed: false,
avatar: '2',
name: 'はなさき',
content: `ハルカソラトキヘダツヒカリ`,
},
{
completed: false,
avatar: '3',
name: 'cipchk',
content: `this world was never meant for one as beautiful as you.`,
},
{
completed: false,
avatar: '4',
name: 'Kent',
content: `my heart is beating with hers`,
},
{
completed: false,
avatar: '5',
name: 'Are you',
content: `They always said that I love beautiful girl than my friends`,
},
{
completed: false,
avatar: '6',
name: 'Forever',
content: `Walking through green fields ,sunshine in my eyes.`,
},
];
webSite: any[];
salesData: any[];
offlineChartData: any[];
constructor(private http: _HttpClient, private cdr: ChangeDetectorRef) {}
ngOnInit() {
this.http.get('/chart').subscribe((res: any) => {
this.webSite = res.visitData.slice(0, 10);
this.salesData = res.salesData;
this.offlineChartData = res.offlineChartData;
this.cdr.detectChanges();
});
}
}
<page-header [breadcrumb]="breadcrumb" [content]="content" [extra]="extra">
<ng-template #breadcrumb>
<nz-breadcrumb>
<nz-breadcrumb-item>
<a [routerLink]="['/']">首页</a>
</nz-breadcrumb-item>
<nz-breadcrumb-item>
<a [routerLink]="['/']">Dashboard</a>
</nz-breadcrumb-item>
<nz-breadcrumb-item>工作台</nz-breadcrumb-item>
</nz-breadcrumb>
</ng-template>
<ng-template #content>
<div class="content">
<div class="avatar">
<nz-avatar nzSrc="https://gw.alipayobjects.com/zos/rmsportal/lctvVCLfRpYCkYxAsiVQ.png"></nz-avatar>
</div>
<div class="desc">
<div class="desc-title">早安,山治,我要吃肉!</div>
<div>假砖家 | 地球-伟大航道-黄金梅丽号-厨房-小强部门</div>
</div>
</div>
</ng-template>
<ng-template #extra>
<div class="page-extra">
<div>
<p>项目数</p>
<p>56</p>
</div>
<div>
<p>团队内排名</p>
<p>8
<span> / 24</span>
</p>
</div>
<div>
<p>项目访问</p>
<p>2,223</p>
</div>
</div>
</ng-template>
</page-header>
<div nz-row [nzGutter]="24">
<div nz-col nzXs="24" nzSm="24" nzMd="16">
<nz-card nzTitle="进行中的项目" [nzExtra]="ingExtra" [nzBordered]="false" [nzLoading]="loading" class="ant-card__body-nopadding mb-lg project-list">
<ng-template #ingExtra>
<a (click)="msg.success('to')">全部项目</a>
</ng-template>
<div *ngFor="let item of notice" nz-card-grid class="project-grid">
<nz-card [nzBordered]="false" class="ant-card__body-nopadding mb0">
<nz-card-meta [nzTitle]="noticeTitle" [nzDescription]="item.description">
<ng-template #noticeTitle>
<div class="card-title">
<nz-avatar [nzSrc]="item.logo" [nzSize]="'small'"></nz-avatar>
<a (click)="msg.info('to' + item.href)">{{item.title}}</a>
</div>
</ng-template>
</nz-card-meta>
<div class="project-item">
<a (click)="msg.info('show user: ' + item.member)">{{item.member}}</a>
<span *ngIf="item.updatedAt" class="datetime" title="{{item.updatedAt}}">
{{item.updatedAt | _date: 'fn' }}
</span>
</div>
</nz-card>
</div>
</nz-card>
<nz-card nzTitle="动态" [nzBordered]="false" [nzLoading]="loading" class="ant-card__body-nopadding mb-lg active-card">
<nz-list nzSize="large" class="activities">
<nz-list-item *ngFor="let item of activities">
<nz-list-item-meta [nzAvatar]="item.user.avatar" [nzTitle]="activeTitle" [nzDescription]="activeDescription">
<ng-template #activeTitle>
<a (click)="msg.success(item.user.name)" class="username">{{item.user.name}}</a>
&nbsp;
<span class="event" [innerHTML]="item.template"></span>
</ng-template>
<ng-template #activeDescription>
<span class="datetime" title="{{item.updatedAt}}">{{item.updatedAt | _date: 'fn'}}</span>
</ng-template>
</nz-list-item-meta>
</nz-list-item>
</nz-list>
</nz-card>
</div>
<div nz-col nzXs="24" nzSm="24" nzMd="8">
<nz-card nzTitle="快速开始 / 便捷导航" [nzBordered]="false" class="ant-card__body-nopadding mb-lg">
<div class="links">
<a *ngFor="let item of links" (click)="msg.success(item.title)">{{item.title}}</a>
<button nz-button (click)="links.push({title: 'new titel', href: 'href'})" [nzType]="'dashed'" [nzSize]="'small'">
<i nz-icon type="plus"></i>
<span>添加</span>
</button>
</div>
</nz-card>
<nz-card nzTitle="XX 指数" [nzBordered]="false" [nzLoading]="loading" class="mb-lg">
<g2-radar *ngIf="radarData" [data]="radarData" [height]="343" [hasLegend]="true"></g2-radar>
</nz-card>
<nz-card nzTitle="团队" [nzBordered]="false" [nzBodyStyle]="{'padding-top.px': 12, 'padding-bottom.px': 12 }" class="mb-lg">
<div class="members">
<div nz-row [nzGutter]="48">
<div nz-col [nzSpan]="12" *ngFor="let i of members">
<a (click)="msg.success(i.title)">
<nz-avatar [nzSrc]="i.logo" [nzSize]="'small'"></nz-avatar>
<span class="member">{{i.title}}</span>
</a>
</div>
</div>
</div>
</nz-card>
</div>
</div>
This diff is collapsed.
This diff is collapsed.
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { RelationComponent } from './relation/relation.component';
const routes: Routes = [{ path: 'relation', component: RelationComponent }];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class DataVRoutingModule {}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment