mirror of
https://gitee.com/iioter/iotgateway.git
synced 2024-11-29 18:28:09 +08:00
angular前端初始化
This commit is contained in:
parent
2c11c50c84
commit
c753aba123
16
IoTGateway/ClientApp/.browserslistrc
Normal file
16
IoTGateway/ClientApp/.browserslistrc
Normal file
@ -0,0 +1,16 @@
|
||||
# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
|
||||
# For additional information regarding the format and rule options, please see:
|
||||
# https://github.com/browserslist/browserslist#queries
|
||||
|
||||
# For the full list of supported browsers by the Angular framework, please see:
|
||||
# https://angular.io/guide/browser-support
|
||||
|
||||
# You can see what browsers were selected by your queries by running:
|
||||
# npx browserslist
|
||||
|
||||
last 1 Chrome version
|
||||
last 1 Firefox version
|
||||
last 2 Edge major versions
|
||||
last 2 Safari major versions
|
||||
last 2 iOS major versions
|
||||
Firefox ESR
|
10
IoTGateway/ClientApp/.dockerignore
Normal file
10
IoTGateway/ClientApp/.dockerignore
Normal file
@ -0,0 +1,10 @@
|
||||
node_modules
|
||||
npm-debug.log
|
||||
Dockerfile*
|
||||
docker-compose*
|
||||
.dockerignore
|
||||
.git
|
||||
.gitignore
|
||||
README.md
|
||||
LICENSE
|
||||
.vscode
|
16
IoTGateway/ClientApp/.editorconfig
Normal file
16
IoTGateway/ClientApp/.editorconfig
Normal file
@ -0,0 +1,16 @@
|
||||
# Editor configuration, see https://editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.ts]
|
||||
quote_type = single
|
||||
|
||||
[*.md]
|
||||
max_line_length = off
|
||||
trim_trailing_whitespace = false
|
34
IoTGateway/ClientApp/.eslintignore
Normal file
34
IoTGateway/ClientApp/.eslintignore
Normal file
@ -0,0 +1,34 @@
|
||||
_cli-tpl/
|
||||
dist/
|
||||
coverage/
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
.env.test
|
||||
|
||||
.cache/
|
||||
|
||||
# yarn v2
|
||||
.yarn
|
126
IoTGateway/ClientApp/.eslintrc.js
Normal file
126
IoTGateway/ClientApp/.eslintrc.js
Normal file
@ -0,0 +1,126 @@
|
||||
const prettierConfig = require('./.prettierrc.js');
|
||||
|
||||
module.exports = {
|
||||
root: true,
|
||||
parserOptions: { ecmaVersion: 2021 },
|
||||
overrides: [
|
||||
{
|
||||
files: ['*.ts'],
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: {
|
||||
tsconfigRootDir: __dirname,
|
||||
project: ['tsconfig.json'],
|
||||
createDefaultProgram: true
|
||||
},
|
||||
plugins: ['@typescript-eslint', 'jsdoc', 'import'],
|
||||
extends: [
|
||||
'plugin:@angular-eslint/recommended',
|
||||
'plugin:@angular-eslint/template/process-inline-templates',
|
||||
'plugin:prettier/recommended'
|
||||
],
|
||||
rules: {
|
||||
'prettier/prettier': ['error', prettierConfig],
|
||||
'jsdoc/newline-after-description': 1,
|
||||
'@angular-eslint/component-class-suffix': [
|
||||
'error',
|
||||
{
|
||||
suffixes: ['Directive', 'Component', 'Base', 'Widget']
|
||||
}
|
||||
],
|
||||
'@angular-eslint/directive-class-suffix': [
|
||||
'error',
|
||||
{
|
||||
suffixes: ['Directive', 'Component', 'Base', 'Widget']
|
||||
}
|
||||
],
|
||||
'@angular-eslint/component-selector': [
|
||||
'off',
|
||||
{
|
||||
type: ['element', 'attribute'],
|
||||
prefix: ['app', 'test'],
|
||||
style: 'kebab-case'
|
||||
}
|
||||
],
|
||||
'@angular-eslint/directive-selector': [
|
||||
'off',
|
||||
{
|
||||
type: 'attribute',
|
||||
prefix: ['app']
|
||||
}
|
||||
],
|
||||
'@angular-eslint/no-attribute-decorator': 'error',
|
||||
'@angular-eslint/no-conflicting-lifecycle': 'off',
|
||||
'@angular-eslint/no-forward-ref': 'off',
|
||||
'@angular-eslint/no-host-metadata-property': 'off',
|
||||
'@angular-eslint/no-lifecycle-call': 'off',
|
||||
'@angular-eslint/no-pipe-impure': 'error',
|
||||
'@angular-eslint/prefer-output-readonly': 'error',
|
||||
'@angular-eslint/use-component-selector': 'off',
|
||||
'@angular-eslint/use-component-view-encapsulation': 'off',
|
||||
'@angular-eslint/no-input-rename': 'off',
|
||||
'@angular-eslint/no-output-native': 'off',
|
||||
'@typescript-eslint/array-type': [
|
||||
'error',
|
||||
{
|
||||
default: 'array-simple'
|
||||
}
|
||||
],
|
||||
'@typescript-eslint/ban-types': [
|
||||
'off',
|
||||
{
|
||||
types: {
|
||||
String: {
|
||||
message: 'Use string instead.'
|
||||
},
|
||||
Number: {
|
||||
message: 'Use number instead.'
|
||||
},
|
||||
Boolean: {
|
||||
message: 'Use boolean instead.'
|
||||
},
|
||||
Function: {
|
||||
message: 'Use specific callable interface instead.'
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
'import/no-duplicates': 'error',
|
||||
'import/no-unused-modules': 'error',
|
||||
'import/no-unassigned-import': 'error',
|
||||
'import/order': [
|
||||
'error',
|
||||
{
|
||||
alphabetize: { order: 'asc', caseInsensitive: false },
|
||||
'newlines-between': 'always',
|
||||
groups: ['external', 'internal', ['parent', 'sibling', 'index']],
|
||||
pathGroups: [],
|
||||
pathGroupsExcludedImportTypes: []
|
||||
}
|
||||
],
|
||||
'@typescript-eslint/no-this-alias': 'error',
|
||||
'@typescript-eslint/member-ordering': 'off',
|
||||
'no-irregular-whitespace': 'error',
|
||||
'no-multiple-empty-lines': 'error',
|
||||
'no-sparse-arrays': 'error',
|
||||
'prefer-object-spread': 'error',
|
||||
'prefer-template': 'error',
|
||||
'prefer-const': 'off',
|
||||
'max-len': 'off'
|
||||
}
|
||||
},
|
||||
{
|
||||
files: ['*.html'],
|
||||
extends: ['plugin:@angular-eslint/template/recommended'],
|
||||
rules: {}
|
||||
},
|
||||
{
|
||||
files: ['*.html'],
|
||||
excludedFiles: ['*inline-template-*.component.html'],
|
||||
extends: ['plugin:prettier/recommended'],
|
||||
rules: {
|
||||
'prettier/prettier': ['error', { parser: 'angular' }],
|
||||
'@angular-eslint/template/eqeqeq': 'off'
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
42
IoTGateway/ClientApp/.gitignore
vendored
Normal file
42
IoTGateway/ClientApp/.gitignore
vendored
Normal file
@ -0,0 +1,42 @@
|
||||
# See http://help.github.com/ignore-files/ for more about ignoring files.
|
||||
|
||||
# Compiled output
|
||||
/dist
|
||||
/tmp
|
||||
/out-tsc
|
||||
/bazel-out
|
||||
|
||||
# Node
|
||||
/node_modules
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
|
||||
# IDEs and editors
|
||||
.idea/
|
||||
.project
|
||||
.classpath
|
||||
.c9/
|
||||
*.launch
|
||||
.settings/
|
||||
*.sublime-workspace
|
||||
|
||||
# Visual Studio Code
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
.history/*
|
||||
|
||||
# Miscellaneous
|
||||
/.angular/cache
|
||||
.sass-cache/
|
||||
/connect.lock
|
||||
/coverage
|
||||
/libpeerconnection.log
|
||||
testem.log
|
||||
/typings
|
||||
|
||||
# System files
|
||||
.DS_Store
|
||||
Thumbs.db
|
5
IoTGateway/ClientApp/.husky/pre-commit
Normal file
5
IoTGateway/ClientApp/.husky/pre-commit
Normal file
@ -0,0 +1,5 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
export NODE_OPTIONS="--max-old-space-size=4096"
|
||||
npx --no-install tsc -p tsconfig.app.json --noEmit
|
||||
npx --no-install lint-staged
|
1
IoTGateway/ClientApp/.nvmrc
Normal file
1
IoTGateway/ClientApp/.nvmrc
Normal file
@ -0,0 +1 @@
|
||||
12.14.1
|
18
IoTGateway/ClientApp/.prettierignore
Normal file
18
IoTGateway/ClientApp/.prettierignore
Normal file
@ -0,0 +1,18 @@
|
||||
# add files you wish to ignore here
|
||||
**/*.md
|
||||
**/*.svg
|
||||
**/test.ts
|
||||
|
||||
.stylelintrc
|
||||
.prettierrc
|
||||
|
||||
src/assets/*
|
||||
src/index.html
|
||||
node_modules/
|
||||
.vscode/
|
||||
coverage/
|
||||
dist/
|
||||
package.json
|
||||
tslint.json
|
||||
|
||||
_cli-tpl/**/*
|
13
IoTGateway/ClientApp/.prettierrc.js
Normal file
13
IoTGateway/ClientApp/.prettierrc.js
Normal file
@ -0,0 +1,13 @@
|
||||
module.exports = {
|
||||
singleQuote: true,
|
||||
useTabs: false,
|
||||
printWidth: 140,
|
||||
tabWidth: 2,
|
||||
semi: true,
|
||||
htmlWhitespaceSensitivity: 'strict',
|
||||
arrowParens: 'avoid',
|
||||
bracketSpacing: true,
|
||||
proseWrap: 'preserve',
|
||||
trailingComma: 'none',
|
||||
endOfLine: 'lf'
|
||||
};
|
38
IoTGateway/ClientApp/.stylelintrc
Normal file
38
IoTGateway/ClientApp/.stylelintrc
Normal file
@ -0,0 +1,38 @@
|
||||
{
|
||||
"extends": [
|
||||
"stylelint-config-standard",
|
||||
"stylelint-config-rational-order",
|
||||
"stylelint-config-prettier"
|
||||
],
|
||||
"customSyntax": "postcss-less",
|
||||
"plugins": [
|
||||
"stylelint-order",
|
||||
"stylelint-declaration-block-no-ignored-properties"
|
||||
],
|
||||
"rules": {
|
||||
"function-no-unknown": null,
|
||||
"no-descending-specificity": null,
|
||||
"plugin/declaration-block-no-ignored-properties": true,
|
||||
"selector-type-no-unknown": [
|
||||
true,
|
||||
{
|
||||
"ignoreTypes": [
|
||||
"/^g2-/",
|
||||
"/^nz-/",
|
||||
"/^app-/"
|
||||
]
|
||||
}
|
||||
],
|
||||
"selector-pseudo-element-no-unknown": [
|
||||
true,
|
||||
{
|
||||
"ignorePseudoElements": [
|
||||
"ng-deep"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"ignoreFiles": [
|
||||
"src/assets/**/*"
|
||||
]
|
||||
}
|
5
IoTGateway/ClientApp/.vscode/extensions.json
vendored
Normal file
5
IoTGateway/ClientApp/.vscode/extensions.json
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"cipchk.ng-alain-extension-pack"
|
||||
]
|
||||
}
|
16
IoTGateway/ClientApp/.vscode/launch.json
vendored
Normal file
16
IoTGateway/ClientApp/.vscode/launch.json
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "chrome",
|
||||
"request": "launch",
|
||||
"name": "Launch Chrome against localhost",
|
||||
"url": "http://localhost:4200",
|
||||
"webRoot": "${workspaceRoot}",
|
||||
"sourceMaps": true
|
||||
}
|
||||
]
|
||||
}
|
37
IoTGateway/ClientApp/.vscode/settings.json
vendored
Normal file
37
IoTGateway/ClientApp/.vscode/settings.json
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
{
|
||||
"typescript.tsdk": "./node_modules/typescript/lib",
|
||||
"editor.formatOnSave": true,
|
||||
"editor.codeActionsOnSave": {
|
||||
// For ESLint
|
||||
"source.fixAll.eslint": true,
|
||||
// For Stylelint
|
||||
"source.fixAll.stylelint": true
|
||||
},
|
||||
"[markdown]": {
|
||||
"editor.formatOnSave": false
|
||||
},
|
||||
"[javascript]": {
|
||||
"editor.formatOnSave": false
|
||||
},
|
||||
"[json]": {
|
||||
"editor.formatOnSave": false
|
||||
},
|
||||
"[jsonc]": {
|
||||
"editor.formatOnSave": false
|
||||
},
|
||||
"files.watcherExclude": {
|
||||
"**/.git/*/**": true,
|
||||
"**/node_modules/*/**": true,
|
||||
"**/dist/*/**": true,
|
||||
"**/coverage/*/**": true
|
||||
},
|
||||
"files.associations": {
|
||||
"*.json": "jsonc",
|
||||
".prettierrc": "jsonc",
|
||||
".stylelintrc": "jsonc"
|
||||
},
|
||||
// Angular schematics 插件: https://marketplace.visualstudio.com/items?itemName=cyrilletuzi.angular-schematics
|
||||
"ngschematics.schematics": [
|
||||
"ng-alain"
|
||||
]
|
||||
}
|
42
IoTGateway/ClientApp/.vscode/tasks.json
vendored
Normal file
42
IoTGateway/ClientApp/.vscode/tasks.json
vendored
Normal file
@ -0,0 +1,42 @@
|
||||
{
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"type": "npm",
|
||||
"script": "start",
|
||||
"isBackground": true,
|
||||
"problemMatcher": {
|
||||
"owner": "typescript",
|
||||
"pattern": "$tsc",
|
||||
"background": {
|
||||
"activeOnStart": true,
|
||||
"beginsPattern": {
|
||||
"regexp": "(.*?)"
|
||||
},
|
||||
"endsPattern": {
|
||||
"regexp": "bundle generation complete"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "npm",
|
||||
"script": "test",
|
||||
"isBackground": true,
|
||||
"problemMatcher": {
|
||||
"owner": "typescript",
|
||||
"pattern": "$tsc",
|
||||
"background": {
|
||||
"activeOnStart": true,
|
||||
"beginsPattern": {
|
||||
"regexp": "(.*?)"
|
||||
},
|
||||
"endsPattern": {
|
||||
"regexp": "bundle generation complete"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
27
IoTGateway/ClientApp/Dockerfile
Normal file
27
IoTGateway/ClientApp/Dockerfile
Normal file
@ -0,0 +1,27 @@
|
||||
# STEP 1: Build
|
||||
FROM node:10 as builder
|
||||
|
||||
LABEL authors="iotgateway"
|
||||
|
||||
COPY package.json package-lock.json ./
|
||||
|
||||
RUN npm set progress=false && npm config set depth 0 && npm cache clean --force
|
||||
RUN npm i && mkdir /iotgateway && cp -R ./node_modules ./iotgateway
|
||||
|
||||
WORKDIR /iotgateway
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN npm run build
|
||||
|
||||
# STEP 2: Setup
|
||||
FROM nginx:alpine
|
||||
|
||||
COPY --from=builder /iotgateway/_nginx/default.conf /etc/nginx/conf.d/default.conf
|
||||
# COPY --from=builder /iotgateway/_nginx/ssl/* /etc/nginx/ssl/
|
||||
|
||||
RUN rm -rf /usr/share/nginx/html/*
|
||||
|
||||
COPY --from=builder /iotgateway/dist /usr/share/nginx/html
|
||||
|
||||
CMD [ "nginx", "-g", "daemon off;"]
|
1
IoTGateway/ClientApp/_mock/README.md
Normal file
1
IoTGateway/ClientApp/_mock/README.md
Normal file
@ -0,0 +1 @@
|
||||
[Document](https://ng-alain.com/mock)
|
122
IoTGateway/ClientApp/_mock/_user.ts
Normal file
122
IoTGateway/ClientApp/_mock/_user.ts
Normal file
@ -0,0 +1,122 @@
|
||||
import { MockRequest } from '@delon/mock';
|
||||
|
||||
const list: any[] = [];
|
||||
const total = 50;
|
||||
|
||||
for (let i = 0; i < total; i += 1) {
|
||||
list.push({
|
||||
id: i + 1,
|
||||
disabled: i % 6 === 0,
|
||||
href: 'https://ant.design',
|
||||
avatar: [
|
||||
'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png',
|
||||
'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png'
|
||||
][i % 2],
|
||||
no: `TradeCode ${i}`,
|
||||
title: `一个任务名称 ${i}`,
|
||||
owner: '曲丽丽',
|
||||
description: '这是一段描述',
|
||||
callNo: Math.floor(Math.random() * 1000),
|
||||
status: Math.floor(Math.random() * 10) % 4,
|
||||
updatedAt: new Date(`2017-07-${Math.floor(i / 2) + 1}`),
|
||||
createdAt: new Date(`2017-07-${Math.floor(i / 2) + 1}`),
|
||||
progress: Math.ceil(Math.random() * 100)
|
||||
});
|
||||
}
|
||||
|
||||
function genData(params: any): { total: number; list: any[] } {
|
||||
let ret = [...list];
|
||||
const pi = +params.pi;
|
||||
const ps = +params.ps;
|
||||
const start = (pi - 1) * ps;
|
||||
|
||||
if (params.no) {
|
||||
ret = ret.filter(data => data.no.indexOf(params.no) > -1);
|
||||
}
|
||||
|
||||
return { total: ret.length, list: ret.slice(start, ps * pi) };
|
||||
}
|
||||
|
||||
function saveData(id: number, value: any): { msg: string } {
|
||||
const item = list.find(w => w.id === id);
|
||||
if (!item) {
|
||||
return { msg: '无效用户信息' };
|
||||
}
|
||||
Object.assign(item, value);
|
||||
return { msg: 'ok' };
|
||||
}
|
||||
|
||||
export const USERS = {
|
||||
'/user': (req: MockRequest) => genData(req.queryString),
|
||||
'/user/:id': (req: MockRequest) => list.find(w => w.id === +req.params.id),
|
||||
'POST /user/:id': (req: MockRequest) => saveData(+req.params.id, req.body),
|
||||
'/user/current': {
|
||||
name: 'IoTGateway',
|
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png',
|
||||
userid: '00000001',
|
||||
email: '535915157@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.password !== '000000') {
|
||||
return { msg: `Invalid username or password(admin/000000)` };
|
||||
}
|
||||
return {
|
||||
msg: 'ok',
|
||||
user: {
|
||||
token: '123456789',
|
||||
name: data.userName,
|
||||
email: `535915157@qq.com`,
|
||||
id: 10000,
|
||||
time: +new Date()
|
||||
}
|
||||
};
|
||||
},
|
||||
'POST /register': {
|
||||
msg: 'ok'
|
||||
}
|
||||
};
|
1
IoTGateway/ClientApp/_mock/index.ts
Normal file
1
IoTGateway/ClientApp/_mock/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './_user';
|
27
IoTGateway/ClientApp/_nginx/default.conf
Normal file
27
IoTGateway/ClientApp/_nginx/default.conf
Normal file
@ -0,0 +1,27 @@
|
||||
server {
|
||||
listen 80;
|
||||
# listen 443;
|
||||
# ssl on;
|
||||
# ssl_certificate /etc/nginx/ssl/server.crt;
|
||||
# ssl_certificate_key /etc/nginx/ssl/server.key;
|
||||
|
||||
server_name localhost;
|
||||
|
||||
#charset koi8-r;
|
||||
#access_log /var/log/nginx/host.access.log main;
|
||||
|
||||
location / {
|
||||
root /usr/share/nginx/html;
|
||||
index index.html index.htm;
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
#error_page 404 /404.html;
|
||||
|
||||
# redirect server error pages to the static page /50x.html
|
||||
#
|
||||
error_page 500 502 503 504 /50x.html;
|
||||
location = /50x.html {
|
||||
root /usr/share/nginx/html;
|
||||
}
|
||||
}
|
171
IoTGateway/ClientApp/angular.json
Normal file
171
IoTGateway/ClientApp/angular.json
Normal file
@ -0,0 +1,171 @@
|
||||
{
|
||||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||
"version": 1,
|
||||
"cli": {
|
||||
"packageManager": "yarn"
|
||||
},
|
||||
"newProjectRoot": "projects",
|
||||
"projects": {
|
||||
"iotgateway": {
|
||||
"projectType": "application",
|
||||
"schematics": {
|
||||
"@schematics/angular:component": {
|
||||
"skipTests": false,
|
||||
"flat": false,
|
||||
"inlineStyle": true,
|
||||
"inlineTemplate": false,
|
||||
"style": "less"
|
||||
},
|
||||
"@schematics/angular:application": {
|
||||
"strict": true
|
||||
},
|
||||
"ng-alain:module": {
|
||||
"routing": true,
|
||||
"skipTests": false
|
||||
},
|
||||
"ng-alain:list": {
|
||||
"skipTests": false
|
||||
},
|
||||
"ng-alain:edit": {
|
||||
"skipTests": false,
|
||||
"modal": true
|
||||
},
|
||||
"ng-alain:view": {
|
||||
"skipTests": false,
|
||||
"modal": true
|
||||
},
|
||||
"ng-alain:curd": {
|
||||
"skipTests": false
|
||||
},
|
||||
"@schematics/angular:module": {
|
||||
"routing": true,
|
||||
"skipTests": false
|
||||
},
|
||||
"@schematics/angular:directive": {
|
||||
"skipTests": false
|
||||
},
|
||||
"@schematics/angular:service": {
|
||||
"skipTests": false
|
||||
}
|
||||
},
|
||||
"root": "",
|
||||
"sourceRoot": "src",
|
||||
"prefix": "app",
|
||||
"architect": {
|
||||
"build": {
|
||||
"builder": "@angular-devkit/build-angular:browser",
|
||||
"options": {
|
||||
"outputPath": "dist/iotgateway",
|
||||
"index": "src/index.html",
|
||||
"main": "src/main.ts",
|
||||
"polyfills": "src/polyfills.ts",
|
||||
"tsConfig": "tsconfig.app.json",
|
||||
"inlineStyleLanguage": "less",
|
||||
"assets": [
|
||||
"src/favicon.ico",
|
||||
"src/assets"
|
||||
],
|
||||
"styles": [
|
||||
"src/styles.less"
|
||||
],
|
||||
"scripts": [
|
||||
"node_modules/qrious/dist/qrious.min.js"],
|
||||
"allowedCommonJsDependencies": [
|
||||
"@antv/g2",
|
||||
"ajv",
|
||||
"ajv-formats",
|
||||
"date-fns",
|
||||
"file-saver"
|
||||
],
|
||||
"stylePreprocessorOptions": {
|
||||
"includePaths": [
|
||||
"node_modules/"
|
||||
]
|
||||
}
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"budgets": [
|
||||
{
|
||||
"type": "initial",
|
||||
"maximumWarning": "2mb",
|
||||
"maximumError": "3mb"
|
||||
},
|
||||
{
|
||||
"type": "anyComponentStyle",
|
||||
"maximumWarning": "2kb",
|
||||
"maximumError": "4kb"
|
||||
}
|
||||
],
|
||||
"fileReplacements": [
|
||||
{
|
||||
"replace": "src/environments/environment.ts",
|
||||
"with": "src/environments/environment.prod.ts"
|
||||
}
|
||||
],
|
||||
"outputHashing": "all"
|
||||
},
|
||||
"development": {
|
||||
"buildOptimizer": false,
|
||||
"optimization": false,
|
||||
"vendorChunk": true,
|
||||
"extractLicenses": false,
|
||||
"sourceMap": true,
|
||||
"namedChunks": true
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "production"
|
||||
},
|
||||
"serve": {
|
||||
"builder": "@angular-devkit/build-angular:dev-server",
|
||||
"configurations": {
|
||||
"production": {
|
||||
"browserTarget": "iotgateway:build:production"
|
||||
},
|
||||
"development": {
|
||||
"browserTarget": "iotgateway:build:development"
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "development",
|
||||
"options": {
|
||||
"proxyConfig": "proxy.conf.js"
|
||||
}
|
||||
},
|
||||
"extract-i18n": {
|
||||
"builder": "@angular-devkit/build-angular:extract-i18n",
|
||||
"options": {
|
||||
"browserTarget": "iotgateway:build"
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"builder": "@angular-devkit/build-angular:karma",
|
||||
"options": {
|
||||
"main": "src/test.ts",
|
||||
"polyfills": "src/polyfills.ts",
|
||||
"tsConfig": "tsconfig.spec.json",
|
||||
"karmaConfig": "karma.conf.js",
|
||||
"inlineStyleLanguage": "less",
|
||||
"assets": [
|
||||
"src/favicon.ico",
|
||||
"src/assets"
|
||||
],
|
||||
"styles": [
|
||||
"src/styles.less"
|
||||
],
|
||||
"scripts": []
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"builder": "@angular-eslint/builder:lint",
|
||||
"options": {
|
||||
"lintFilePatterns": [
|
||||
"src/**/*.ts",
|
||||
"src/**/*.html"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"defaultProject": "iotgateway"
|
||||
}
|
10
IoTGateway/ClientApp/docker-compose.yml
Normal file
10
IoTGateway/ClientApp/docker-compose.yml
Normal file
@ -0,0 +1,10 @@
|
||||
version: '2.1'
|
||||
|
||||
services:
|
||||
iotgateway:
|
||||
image: iotgateway
|
||||
build: .
|
||||
environment:
|
||||
NODE_ENV: production
|
||||
ports:
|
||||
- 80:80
|
44
IoTGateway/ClientApp/karma.conf.js
Normal file
44
IoTGateway/ClientApp/karma.conf.js
Normal file
@ -0,0 +1,44 @@
|
||||
// Karma configuration file, see link for more information
|
||||
// https://karma-runner.github.io/1.0/config/configuration-file.html
|
||||
|
||||
module.exports = function (config) {
|
||||
config.set({
|
||||
basePath: '',
|
||||
frameworks: ['jasmine', '@angular-devkit/build-angular'],
|
||||
plugins: [
|
||||
require('karma-jasmine'),
|
||||
require('karma-chrome-launcher'),
|
||||
require('karma-jasmine-html-reporter'),
|
||||
require('karma-coverage'),
|
||||
require('@angular-devkit/build-angular/plugins/karma')
|
||||
],
|
||||
client: {
|
||||
jasmine: {
|
||||
// you can add configuration options for Jasmine here
|
||||
// the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html
|
||||
// for example, you can disable the random execution with `random: false`
|
||||
// or set a specific seed with `seed: 4321`
|
||||
},
|
||||
clearContext: false // leave Jasmine Spec Runner output visible in browser
|
||||
},
|
||||
jasmineHtmlReporter: {
|
||||
suppressAll: true // removes the duplicated traces
|
||||
},
|
||||
coverageReporter: {
|
||||
dir: require('path').join(__dirname, './coverage/iotgateway'),
|
||||
subdir: '.',
|
||||
reporters: [
|
||||
{ type: 'html' },
|
||||
{ type: 'text-summary' }
|
||||
]
|
||||
},
|
||||
reporters: ['progress', 'kjhtml'],
|
||||
port: 9876,
|
||||
colors: true,
|
||||
logLevel: config.LOG_INFO,
|
||||
autoWatch: true,
|
||||
browsers: ['Chrome'],
|
||||
singleRun: false,
|
||||
restartOnFileChange: true
|
||||
});
|
||||
};
|
13
IoTGateway/ClientApp/ng-alain.json
Normal file
13
IoTGateway/ClientApp/ng-alain.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"$schema": "./node_modules/ng-alain/schema.json",
|
||||
"theme": {
|
||||
"list": [
|
||||
{
|
||||
"theme": "dark"
|
||||
},
|
||||
{
|
||||
"theme": "compact"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
36597
IoTGateway/ClientApp/package-lock.json
generated
Normal file
36597
IoTGateway/ClientApp/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
103
IoTGateway/ClientApp/package.json
Normal file
103
IoTGateway/ClientApp/package.json
Normal file
@ -0,0 +1,103 @@
|
||||
{
|
||||
"name": "iotgateway",
|
||||
"version": "0.0.0",
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
"start": "ng s -o",
|
||||
"build": "npm run ng-high-memory build",
|
||||
"watch": "ng build --watch --configuration development",
|
||||
"test": "ng test",
|
||||
"ng-high-memory": "node --max_old_space_size=8000 ./node_modules/@angular/cli/bin/ng",
|
||||
"hmr": "ng s -o --hmr",
|
||||
"analyze": "npm run ng-high-memory build -- --source-map",
|
||||
"analyze:view": "source-map-explorer dist/**/*.js",
|
||||
"test-coverage": "ng test --code-coverage --watch=false",
|
||||
"color-less": "ng-alain-plugin-theme -t=colorLess",
|
||||
"theme": "ng-alain-plugin-theme -t=themeCss",
|
||||
"icon": "ng g ng-alain:plugin icon",
|
||||
"prepare": "husky install",
|
||||
"lint": "npm run lint:ts && npm run lint:style",
|
||||
"lint:ts": "ng lint --fix",
|
||||
"lint:style": "npx stylelint \"src/**/*.less\" --fix"
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "~13.3.0",
|
||||
"@angular/common": "~13.3.0",
|
||||
"@angular/compiler": "~13.3.0",
|
||||
"@angular/core": "~13.3.0",
|
||||
"@angular/forms": "~13.3.0",
|
||||
"@angular/platform-browser": "~13.3.0",
|
||||
"@angular/platform-browser-dynamic": "~13.3.0",
|
||||
"@angular/router": "~13.3.0",
|
||||
"@delon/abc": "^13.4.2",
|
||||
"@delon/acl": "^13.4.2",
|
||||
"@delon/auth": "^13.4.2",
|
||||
"@delon/cache": "^13.4.2",
|
||||
"@delon/chart": "^13.4.2",
|
||||
"@delon/form": "^13.4.2",
|
||||
"@delon/mock": "^13.4.2",
|
||||
"@delon/theme": "^13.4.2",
|
||||
"@delon/util": "^13.4.2",
|
||||
"ajv": "^8.10.0",
|
||||
"ajv-formats": "^2.1.1",
|
||||
"monaco-editor": "^0.33.0",
|
||||
"ng-alain": "13.4.2",
|
||||
"ng-zorro-antd": "^13.1.1",
|
||||
"ngx-tinymce": "^13.0.0",
|
||||
"qrious": "^4.0.2",
|
||||
"rxjs": "~7.5.0",
|
||||
"screenfull": "^6.0.1",
|
||||
"tslib": "^2.3.0",
|
||||
"zone.js": "~0.11.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "~13.3.3",
|
||||
"@angular-eslint/builder": "~13.1.0",
|
||||
"@angular-eslint/eslint-plugin": "~13.1.0",
|
||||
"@angular-eslint/eslint-plugin-template": "~13.1.0",
|
||||
"@angular-eslint/schematics": "~13.1.0",
|
||||
"@angular-eslint/template-parser": "~13.1.0",
|
||||
"@angular/cli": "~13.3.3",
|
||||
"@angular/compiler-cli": "~13.3.0",
|
||||
"@angular/language-service": "~13.3.0",
|
||||
"@delon/testing": "^13.4.2",
|
||||
"@types/jasmine": "~3.10.0",
|
||||
"@types/node": "^12.11.1",
|
||||
"@typescript-eslint/eslint-plugin": "~5.15.0",
|
||||
"@typescript-eslint/parser": "~5.15.0",
|
||||
"eslint": "^8.11.0",
|
||||
"eslint-config-prettier": "^2.6.0",
|
||||
"eslint-plugin-import": "~2.25.4",
|
||||
"eslint-plugin-jsdoc": "~38.0.4",
|
||||
"eslint-plugin-prefer-arrow": "~1.2.3",
|
||||
"eslint-plugin-prettier": "^2.6.0",
|
||||
"husky": "^7.0.4",
|
||||
"jasmine-core": "~4.0.0",
|
||||
"karma": "~6.3.0",
|
||||
"karma-chrome-launcher": "~3.1.0",
|
||||
"karma-coverage": "~2.1.0",
|
||||
"karma-jasmine": "~4.0.0",
|
||||
"karma-jasmine-html-reporter": "~1.7.0",
|
||||
"lint-staged": "^12.3.7",
|
||||
"ng-alain": "^13.4.2",
|
||||
"ng-alain-plugin-theme": "^13.0.3",
|
||||
"prettier": "^2.6.0",
|
||||
"source-map-explorer": "^2.5.2",
|
||||
"stylelint": "^14.6.0",
|
||||
"stylelint-config-prettier": "^9.0.3",
|
||||
"stylelint-config-rational-order": "^0.1.2",
|
||||
"stylelint-config-standard": "^25.0.0",
|
||||
"stylelint-declaration-block-no-ignored-properties": "^2.5.0",
|
||||
"stylelint-order": "^5.0.0",
|
||||
"typescript": "~4.6.2"
|
||||
},
|
||||
"lint-staged": {
|
||||
"(src)/**/*.{html,ts}": [
|
||||
"eslint --fix"
|
||||
],
|
||||
"(src)/**/*.less": [
|
||||
"npm run lint:style"
|
||||
]
|
||||
}
|
||||
}
|
17
IoTGateway/ClientApp/proxy.conf.js
Normal file
17
IoTGateway/ClientApp/proxy.conf.js
Normal file
@ -0,0 +1,17 @@
|
||||
/**
|
||||
* For more configuration, please refer to https://angular.io/guide/build#proxying-to-a-backend-server
|
||||
*
|
||||
* 更多配置描述请参考 https://angular.cn/guide/build#proxying-to-a-backend-server
|
||||
*
|
||||
* Note: The proxy is only valid for real requests, Mock does not actually generate requests, so the priority of Mock will be higher than the proxy
|
||||
*/
|
||||
module.exports = {
|
||||
/**
|
||||
* The following means that all requests are directed to the backend `https://localhost:9000/`
|
||||
*/
|
||||
// '/': {
|
||||
// target: 'https://localhost:9000/',
|
||||
// secure: false, // Ignore invalid SSL certificates
|
||||
// changeOrigin: true
|
||||
// }
|
||||
};
|
46
IoTGateway/ClientApp/src/app/app.component.ts
Normal file
46
IoTGateway/ClientApp/src/app/app.component.ts
Normal file
@ -0,0 +1,46 @@
|
||||
import { Component, ElementRef, OnInit, Renderer2 } from '@angular/core';
|
||||
import { NavigationEnd, NavigationError, RouteConfigLoadStart, Router } from '@angular/router';
|
||||
import { TitleService, VERSION as VERSION_ALAIN } from '@delon/theme';
|
||||
import { environment } from '@env/environment';
|
||||
import { NzModalService } from 'ng-zorro-antd/modal';
|
||||
import { VERSION as VERSION_ZORRO } from 'ng-zorro-antd/version';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
template: ` <router-outlet></router-outlet> `
|
||||
})
|
||||
export class AppComponent implements OnInit {
|
||||
constructor(
|
||||
el: ElementRef,
|
||||
renderer: Renderer2,
|
||||
private router: Router,
|
||||
private titleSrv: TitleService,
|
||||
private modalSrv: NzModalService
|
||||
) {
|
||||
renderer.setAttribute(el.nativeElement, 'ng-alain-version', VERSION_ALAIN.full);
|
||||
renderer.setAttribute(el.nativeElement, 'ng-zorro-version', VERSION_ZORRO.full);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
let configLoad = false;
|
||||
this.router.events.subscribe(ev => {
|
||||
if (ev instanceof RouteConfigLoadStart) {
|
||||
configLoad = true;
|
||||
}
|
||||
if (configLoad && ev instanceof NavigationError) {
|
||||
this.modalSrv.confirm({
|
||||
nzTitle: `提醒`,
|
||||
nzContent: environment.production ? `应用可能已发布新版本,请点击刷新才能生效。` : `无法加载路由:${ev.url}`,
|
||||
nzCancelDisabled: false,
|
||||
nzOkText: '刷新',
|
||||
nzCancelText: '忽略',
|
||||
nzOnOk: () => location.reload()
|
||||
});
|
||||
}
|
||||
if (ev instanceof NavigationEnd) {
|
||||
this.titleSrv.setTitle();
|
||||
this.modalSrv.closeAll();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
108
IoTGateway/ClientApp/src/app/app.module.ts
Normal file
108
IoTGateway/ClientApp/src/app/app.module.ts
Normal file
@ -0,0 +1,108 @@
|
||||
/* eslint-disable import/order */
|
||||
/* eslint-disable import/no-duplicates */
|
||||
import { HttpClient, HttpClientModule } from '@angular/common/http';
|
||||
import { APP_INITIALIZER, Injector, LOCALE_ID, NgModule, Type } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { NzMessageModule } from 'ng-zorro-antd/message';
|
||||
import { NzNotificationModule } from 'ng-zorro-antd/notification';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
// #region default language
|
||||
// Reference: https://ng-alain.com/docs/i18n
|
||||
import { default as ngLang } from '@angular/common/locales/zh';
|
||||
import { DELON_LOCALE, zh_CN as delonLang } from '@delon/theme';
|
||||
import { zhCN as dateLang } from 'date-fns/locale';
|
||||
import { NZ_DATE_LOCALE, NZ_I18N, zh_CN as zorroLang } from 'ng-zorro-antd/i18n';
|
||||
const LANG = {
|
||||
abbr: 'zh',
|
||||
ng: ngLang,
|
||||
zorro: zorroLang,
|
||||
date: dateLang,
|
||||
delon: delonLang
|
||||
};
|
||||
// register angular
|
||||
import { registerLocaleData } from '@angular/common';
|
||||
registerLocaleData(LANG.ng, LANG.abbr);
|
||||
const LANG_PROVIDES = [
|
||||
{ provide: LOCALE_ID, useValue: LANG.abbr },
|
||||
{ provide: NZ_I18N, useValue: LANG.zorro },
|
||||
{ provide: NZ_DATE_LOCALE, useValue: LANG.date },
|
||||
{ provide: DELON_LOCALE, useValue: LANG.delon }
|
||||
];
|
||||
// #endregion
|
||||
// #region i18n services
|
||||
import { ALAIN_I18N_TOKEN } from '@delon/theme';
|
||||
import { I18NService } from '@core';
|
||||
|
||||
const I18NSERVICE_PROVIDES = [{ provide: ALAIN_I18N_TOKEN, useClass: I18NService, multi: false }];
|
||||
// #region
|
||||
|
||||
// #region JSON Schema form (using @delon/form)
|
||||
import { JsonSchemaModule } from '@shared';
|
||||
const FORM_MODULES = [JsonSchemaModule];
|
||||
// #endregion
|
||||
|
||||
// #region Http Interceptors
|
||||
import { HTTP_INTERCEPTORS } from '@angular/common/http';
|
||||
import { DefaultInterceptor } from '@core';
|
||||
import { SimpleInterceptor } from '@delon/auth';
|
||||
const INTERCEPTOR_PROVIDES = [
|
||||
{ provide: HTTP_INTERCEPTORS, useClass: SimpleInterceptor, multi: true },
|
||||
{ provide: HTTP_INTERCEPTORS, useClass: DefaultInterceptor, multi: true }
|
||||
];
|
||||
// #endregion
|
||||
|
||||
// #region global third module
|
||||
const GLOBAL_THIRD_MODULES: Array<Type<void>> = [];
|
||||
// #endregion
|
||||
|
||||
// #region Startup Service
|
||||
import { StartupService } from '@core';
|
||||
export function StartupServiceFactory(startupService: StartupService): () => Observable<void> {
|
||||
return () => startupService.load();
|
||||
}
|
||||
const APPINIT_PROVIDES = [
|
||||
StartupService,
|
||||
{
|
||||
provide: APP_INITIALIZER,
|
||||
useFactory: StartupServiceFactory,
|
||||
deps: [StartupService],
|
||||
multi: true
|
||||
}
|
||||
];
|
||||
// #endregion
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
import { CoreModule } from './core/core.module';
|
||||
import { GlobalConfigModule } from './global-config.module';
|
||||
import { LayoutModule } from './layout/layout.module';
|
||||
import { RoutesModule } from './routes/routes.module';
|
||||
import { SharedModule } from './shared/shared.module';
|
||||
import { STWidgetModule } from './shared/st-widget/st-widget.module';
|
||||
import { NgxTinymceModule } from 'ngx-tinymce';
|
||||
|
||||
@NgModule({
|
||||
declarations: [AppComponent],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
BrowserAnimationsModule,
|
||||
HttpClientModule,
|
||||
GlobalConfigModule.forRoot(),
|
||||
CoreModule,
|
||||
SharedModule,
|
||||
LayoutModule,
|
||||
RoutesModule,
|
||||
STWidgetModule,
|
||||
NzMessageModule,
|
||||
NzNotificationModule,
|
||||
...FORM_MODULES,
|
||||
...GLOBAL_THIRD_MODULES,
|
||||
NgxTinymceModule.forRoot({
|
||||
baseURL: '//cdn.bootcss.com/tinymce/4.7.13/'
|
||||
})
|
||||
],
|
||||
providers: [...LANG_PROVIDES, ...INTERCEPTOR_PROVIDES, ...I18NSERVICE_PROVIDES, ...APPINIT_PROVIDES],
|
||||
bootstrap: [AppComponent]
|
||||
})
|
||||
export class AppModule {}
|
5
IoTGateway/ClientApp/src/app/core/README.md
Normal file
5
IoTGateway/ClientApp/src/app/core/README.md
Normal file
@ -0,0 +1,5 @@
|
||||
### CoreModule
|
||||
|
||||
**应** 仅只留 `providers` 属性。
|
||||
|
||||
**作用:** 一些通用服务,例如:用户消息、HTTP数据访问。
|
13
IoTGateway/ClientApp/src/app/core/core.module.ts
Normal file
13
IoTGateway/ClientApp/src/app/core/core.module.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { NgModule, Optional, SkipSelf } from '@angular/core';
|
||||
|
||||
import { I18NService } from './i18n/i18n.service';
|
||||
import { throwIfAlreadyLoaded } from './module-import-guard';
|
||||
|
||||
@NgModule({
|
||||
providers: [I18NService]
|
||||
})
|
||||
export class CoreModule {
|
||||
constructor(@Optional() @SkipSelf() parentModule: CoreModule) {
|
||||
throwIfAlreadyLoaded(parentModule, 'CoreModule');
|
||||
}
|
||||
}
|
84
IoTGateway/ClientApp/src/app/core/i18n/i18n.service.spec.ts
Normal file
84
IoTGateway/ClientApp/src/app/core/i18n/i18n.service.spec.ts
Normal file
@ -0,0 +1,84 @@
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing';
|
||||
import { TestBed, TestBedStatic } from '@angular/core/testing';
|
||||
import { DelonLocaleService, SettingsService } from '@delon/theme';
|
||||
import { NzSafeAny } from 'ng-zorro-antd/core/types';
|
||||
import { NzI18nService } from 'ng-zorro-antd/i18n';
|
||||
import { of } from 'rxjs';
|
||||
|
||||
import { I18NService } from './i18n.service';
|
||||
|
||||
describe('Service: I18n', () => {
|
||||
let injector: TestBedStatic;
|
||||
let srv: I18NService;
|
||||
const MockSettingsService: NzSafeAny = {
|
||||
layout: {
|
||||
lang: null
|
||||
}
|
||||
};
|
||||
const MockNzI18nService = {
|
||||
setLocale: () => {},
|
||||
setDateLocale: () => {}
|
||||
};
|
||||
const MockDelonLocaleService = {
|
||||
setLocale: () => {}
|
||||
};
|
||||
const MockTranslateService = {
|
||||
getBrowserLang: jasmine.createSpy('getBrowserLang'),
|
||||
addLangs: () => {},
|
||||
setLocale: () => {},
|
||||
getDefaultLang: () => '',
|
||||
use: (lang: string) => of(lang),
|
||||
instant: jasmine.createSpy('instant')
|
||||
};
|
||||
|
||||
function genModule(): void {
|
||||
injector = TestBed.configureTestingModule({
|
||||
imports: [HttpClientTestingModule],
|
||||
providers: [
|
||||
I18NService,
|
||||
{ provide: SettingsService, useValue: MockSettingsService },
|
||||
{ provide: NzI18nService, useValue: MockNzI18nService },
|
||||
{ provide: DelonLocaleService, useValue: MockDelonLocaleService }
|
||||
]
|
||||
});
|
||||
srv = TestBed.inject(I18NService);
|
||||
}
|
||||
|
||||
it('should working', () => {
|
||||
spyOnProperty(navigator, 'languages').and.returnValue(['zh-CN']);
|
||||
genModule();
|
||||
expect(srv).toBeTruthy();
|
||||
expect(srv.defaultLang).toBe('zh-CN');
|
||||
srv.fanyi('a');
|
||||
srv.fanyi('a', {});
|
||||
});
|
||||
|
||||
it('should be used layout as default language', () => {
|
||||
MockSettingsService.layout.lang = 'en-US';
|
||||
const navSpy = spyOnProperty(navigator, 'languages');
|
||||
genModule();
|
||||
expect(navSpy).not.toHaveBeenCalled();
|
||||
expect(srv.defaultLang).toBe('en-US');
|
||||
MockSettingsService.layout.lang = null;
|
||||
});
|
||||
|
||||
it('should be used browser as default language', () => {
|
||||
spyOnProperty(navigator, 'languages').and.returnValue(['zh-TW']);
|
||||
genModule();
|
||||
expect(srv.defaultLang).toBe('zh-TW');
|
||||
});
|
||||
|
||||
it('should be use default language when the browser language is not in the list', () => {
|
||||
spyOnProperty(navigator, 'languages').and.returnValue(['es-419']);
|
||||
genModule();
|
||||
expect(srv.defaultLang).toBe('zh-CN');
|
||||
});
|
||||
|
||||
it('should be trigger notify when changed language', () => {
|
||||
genModule();
|
||||
srv.use('en-US', {});
|
||||
srv.change.subscribe(lang => {
|
||||
expect(lang).toBe('en-US');
|
||||
});
|
||||
});
|
||||
});
|
116
IoTGateway/ClientApp/src/app/core/i18n/i18n.service.ts
Normal file
116
IoTGateway/ClientApp/src/app/core/i18n/i18n.service.ts
Normal file
@ -0,0 +1,116 @@
|
||||
// 请参考:https://ng-alain.com/docs/i18n
|
||||
import { Platform } from '@angular/cdk/platform';
|
||||
import { registerLocaleData } from '@angular/common';
|
||||
import ngEn from '@angular/common/locales/en';
|
||||
import ngZh from '@angular/common/locales/zh';
|
||||
import ngZhTw from '@angular/common/locales/zh-Hant';
|
||||
import { Injectable } from '@angular/core';
|
||||
import {
|
||||
DelonLocaleService,
|
||||
en_US as delonEnUS,
|
||||
SettingsService,
|
||||
zh_CN as delonZhCn,
|
||||
zh_TW as delonZhTw,
|
||||
_HttpClient,
|
||||
AlainI18nBaseService
|
||||
} from '@delon/theme';
|
||||
import { AlainConfigService } from '@delon/util/config';
|
||||
import { enUS as dfEn, zhCN as dfZhCn, zhTW as dfZhTw } from 'date-fns/locale';
|
||||
import { NzSafeAny } from 'ng-zorro-antd/core/types';
|
||||
import { en_US as zorroEnUS, NzI18nService, zh_CN as zorroZhCN, zh_TW as zorroZhTW } from 'ng-zorro-antd/i18n';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
interface LangConfigData {
|
||||
abbr: string;
|
||||
text: string;
|
||||
ng: NzSafeAny;
|
||||
zorro: NzSafeAny;
|
||||
date: NzSafeAny;
|
||||
delon: NzSafeAny;
|
||||
}
|
||||
|
||||
const DEFAULT = 'zh-CN';
|
||||
const LANGS: { [key: string]: LangConfigData } = {
|
||||
'zh-CN': {
|
||||
text: '简体中文',
|
||||
ng: ngZh,
|
||||
zorro: zorroZhCN,
|
||||
date: dfZhCn,
|
||||
delon: delonZhCn,
|
||||
abbr: '🇨🇳'
|
||||
},
|
||||
'zh-TW': {
|
||||
text: '繁体中文',
|
||||
ng: ngZhTw,
|
||||
zorro: zorroZhTW,
|
||||
date: dfZhTw,
|
||||
delon: delonZhTw,
|
||||
abbr: '🇭🇰'
|
||||
},
|
||||
'en-US': {
|
||||
text: 'English',
|
||||
ng: ngEn,
|
||||
zorro: zorroEnUS,
|
||||
date: dfEn,
|
||||
delon: delonEnUS,
|
||||
abbr: '🇬🇧'
|
||||
}
|
||||
};
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class I18NService extends AlainI18nBaseService {
|
||||
protected override _defaultLang = DEFAULT;
|
||||
private _langs = Object.keys(LANGS).map(code => {
|
||||
const item = LANGS[code];
|
||||
return { code, text: item.text, abbr: item.abbr };
|
||||
});
|
||||
|
||||
constructor(
|
||||
private http: _HttpClient,
|
||||
private settings: SettingsService,
|
||||
private nzI18nService: NzI18nService,
|
||||
private delonLocaleService: DelonLocaleService,
|
||||
private platform: Platform,
|
||||
cogSrv: AlainConfigService
|
||||
) {
|
||||
super(cogSrv);
|
||||
|
||||
const defaultLang = this.getDefaultLang();
|
||||
this._defaultLang = this._langs.findIndex(w => w.code === defaultLang) === -1 ? DEFAULT : defaultLang;
|
||||
}
|
||||
|
||||
private getDefaultLang(): string {
|
||||
if (!this.platform.isBrowser) {
|
||||
return DEFAULT;
|
||||
}
|
||||
if (this.settings.layout.lang) {
|
||||
return this.settings.layout.lang;
|
||||
}
|
||||
let res = (navigator.languages ? navigator.languages[0] : null) || navigator.language;
|
||||
const arr = res.split('-');
|
||||
return arr.length <= 1 ? res : `${arr[0]}-${arr[1].toUpperCase()}`;
|
||||
}
|
||||
|
||||
loadLangData(lang: string): Observable<NzSafeAny> {
|
||||
return this.http.get(`assets/tmp/i18n/${lang}.json`);
|
||||
}
|
||||
|
||||
use(lang: string, data: Record<string, unknown>): void {
|
||||
if (this._currentLang === lang) return;
|
||||
|
||||
this._data = this.flatData(data, []);
|
||||
|
||||
const item = LANGS[lang];
|
||||
registerLocaleData(item.ng);
|
||||
this.nzI18nService.setLocale(item.zorro);
|
||||
this.nzI18nService.setDateLocale(item.date);
|
||||
this.delonLocaleService.setLocale(item.delon);
|
||||
this._currentLang = lang;
|
||||
|
||||
this._change$.next(lang);
|
||||
}
|
||||
|
||||
getLangs(): Array<{ code: string; text: string; abbr: string }> {
|
||||
return this._langs;
|
||||
}
|
||||
}
|
4
IoTGateway/ClientApp/src/app/core/index.ts
Normal file
4
IoTGateway/ClientApp/src/app/core/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export * from './i18n/i18n.service';
|
||||
export * from './module-import-guard';
|
||||
export * from './net/default.interceptor';
|
||||
export * from './startup/startup.service';
|
6
IoTGateway/ClientApp/src/app/core/module-import-guard.ts
Normal file
6
IoTGateway/ClientApp/src/app/core/module-import-guard.ts
Normal file
@ -0,0 +1,6 @@
|
||||
// https://angular.io/guide/styleguide#style-04-12
|
||||
export function throwIfAlreadyLoaded(parentModule: any, moduleName: string): void {
|
||||
if (parentModule) {
|
||||
throw new Error(`${moduleName} has already been loaded. Import Core modules in the AppModule only.`);
|
||||
}
|
||||
}
|
261
IoTGateway/ClientApp/src/app/core/net/default.interceptor.ts
Normal file
261
IoTGateway/ClientApp/src/app/core/net/default.interceptor.ts
Normal file
@ -0,0 +1,261 @@
|
||||
import {
|
||||
HttpErrorResponse,
|
||||
HttpEvent,
|
||||
HttpHandler,
|
||||
HttpHeaders,
|
||||
HttpInterceptor,
|
||||
HttpRequest,
|
||||
HttpResponseBase
|
||||
} from '@angular/common/http';
|
||||
import { Injectable, Injector } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { DA_SERVICE_TOKEN, ITokenService } from '@delon/auth';
|
||||
import { ALAIN_I18N_TOKEN, _HttpClient } from '@delon/theme';
|
||||
import { environment } from '@env/environment';
|
||||
import { NzNotificationService } from 'ng-zorro-antd/notification';
|
||||
import { BehaviorSubject, Observable, of, throwError } from 'rxjs';
|
||||
import { catchError, filter, mergeMap, switchMap, take } from 'rxjs/operators';
|
||||
|
||||
const CODEMESSAGE: { [key: number]: string } = {
|
||||
200: '服务器成功返回请求的数据。',
|
||||
201: '新建或修改数据成功。',
|
||||
202: '一个请求已经进入后台排队(异步任务)。',
|
||||
204: '删除数据成功。',
|
||||
400: '发出的请求有错误,服务器没有进行新建或修改数据的操作。',
|
||||
401: '用户没有权限(令牌、用户名、密码错误)。',
|
||||
403: '用户得到授权,但是访问是被禁止的。',
|
||||
404: '发出的请求针对的是不存在的记录,服务器没有进行操作。',
|
||||
406: '请求的格式不可得。',
|
||||
410: '请求的资源被永久删除,且不会再得到的。',
|
||||
422: '当创建一个对象时,发生一个验证错误。',
|
||||
500: '服务器发生错误,请检查服务器。',
|
||||
502: '网关错误。',
|
||||
503: '服务不可用,服务器暂时过载或维护。',
|
||||
504: '网关超时。'
|
||||
};
|
||||
|
||||
/**
|
||||
* 默认HTTP拦截器,其注册细节见 `app.module.ts`
|
||||
*/
|
||||
@Injectable()
|
||||
export class DefaultInterceptor implements HttpInterceptor {
|
||||
private refreshTokenEnabled = environment.api.refreshTokenEnabled;
|
||||
private refreshTokenType: 're-request' | 'auth-refresh' = environment.api.refreshTokenType;
|
||||
private refreshToking = false;
|
||||
private refreshToken$: BehaviorSubject<any> = new BehaviorSubject<any>(null);
|
||||
|
||||
constructor(private injector: Injector) {
|
||||
if (this.refreshTokenType === 'auth-refresh') {
|
||||
this.buildAuthRefresh();
|
||||
}
|
||||
}
|
||||
|
||||
private get notification(): NzNotificationService {
|
||||
return this.injector.get(NzNotificationService);
|
||||
}
|
||||
|
||||
private get tokenSrv(): ITokenService {
|
||||
return this.injector.get(DA_SERVICE_TOKEN);
|
||||
}
|
||||
|
||||
private get http(): _HttpClient {
|
||||
return this.injector.get(_HttpClient);
|
||||
}
|
||||
|
||||
private goTo(url: string): void {
|
||||
setTimeout(() => this.injector.get(Router).navigateByUrl(url));
|
||||
}
|
||||
|
||||
private checkStatus(ev: HttpResponseBase): void {
|
||||
if ((ev.status >= 200 && ev.status < 300) || ev.status === 401) {
|
||||
return;
|
||||
}
|
||||
|
||||
const errortext = CODEMESSAGE[ev.status] || ev.statusText;
|
||||
this.notification.error(`请求错误 ${ev.status}: ${ev.url}`, errortext);
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新 Token 请求
|
||||
*/
|
||||
private refreshTokenRequest(): Observable<any> {
|
||||
const model = this.tokenSrv.get();
|
||||
return this.http.post(`/api/auth/refresh`, null, null, { headers: { refresh_token: model?.['refresh_token'] || '' } });
|
||||
}
|
||||
|
||||
// #region 刷新Token方式一:使用 401 重新刷新 Token
|
||||
|
||||
private tryRefreshToken(ev: HttpResponseBase, req: HttpRequest<any>, next: HttpHandler): Observable<any> {
|
||||
// 1、若请求为刷新Token请求,表示来自刷新Token可以直接跳转登录页
|
||||
if ([`/api/auth/refresh`].some(url => req.url.includes(url))) {
|
||||
this.toLogin();
|
||||
return throwError(ev);
|
||||
}
|
||||
// 2、如果 `refreshToking` 为 `true` 表示已经在请求刷新 Token 中,后续所有请求转入等待状态,直至结果返回后再重新发起请求
|
||||
if (this.refreshToking) {
|
||||
return this.refreshToken$.pipe(
|
||||
filter(v => !!v),
|
||||
take(1),
|
||||
switchMap(() => next.handle(this.reAttachToken(req)))
|
||||
);
|
||||
}
|
||||
// 3、尝试调用刷新 Token
|
||||
this.refreshToking = true;
|
||||
this.refreshToken$.next(null);
|
||||
|
||||
return this.refreshTokenRequest().pipe(
|
||||
switchMap(res => {
|
||||
// 通知后续请求继续执行
|
||||
this.refreshToking = false;
|
||||
this.refreshToken$.next(res);
|
||||
// 重新保存新 token
|
||||
this.tokenSrv.set(res);
|
||||
// 重新发起请求
|
||||
return next.handle(this.reAttachToken(req));
|
||||
}),
|
||||
catchError(err => {
|
||||
this.refreshToking = false;
|
||||
this.toLogin();
|
||||
return throwError(err);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 重新附加新 Token 信息
|
||||
*
|
||||
* > 由于已经发起的请求,不会再走一遍 `@delon/auth` 因此需要结合业务情况重新附加新的 Token
|
||||
*/
|
||||
private reAttachToken(req: HttpRequest<any>): HttpRequest<any> {
|
||||
// 以下示例是以 NG-ALAIN 默认使用 `SimpleInterceptor`
|
||||
const token = this.tokenSrv.get()?.token;
|
||||
return req.clone({
|
||||
setHeaders: {
|
||||
token: `Bearer ${token}`
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// #endregion
|
||||
|
||||
// #region 刷新Token方式二:使用 `@delon/auth` 的 `refresh` 接口
|
||||
|
||||
private buildAuthRefresh(): void {
|
||||
if (!this.refreshTokenEnabled) {
|
||||
return;
|
||||
}
|
||||
this.tokenSrv.refresh
|
||||
.pipe(
|
||||
filter(() => !this.refreshToking),
|
||||
switchMap(res => {
|
||||
console.log(res);
|
||||
this.refreshToking = true;
|
||||
return this.refreshTokenRequest();
|
||||
})
|
||||
)
|
||||
.subscribe(
|
||||
res => {
|
||||
// TODO: Mock expired value
|
||||
res.expired = +new Date() + 1000 * 60 * 5;
|
||||
this.refreshToking = false;
|
||||
this.tokenSrv.set(res);
|
||||
},
|
||||
() => this.toLogin()
|
||||
);
|
||||
}
|
||||
|
||||
// #endregion
|
||||
|
||||
private toLogin(): void {
|
||||
this.notification.error(`未登录或登录已过期,请重新登录。`, ``);
|
||||
this.goTo(this.tokenSrv.login_url!);
|
||||
}
|
||||
|
||||
private handleData(ev: HttpResponseBase, req: HttpRequest<any>, next: HttpHandler): Observable<any> {
|
||||
this.checkStatus(ev);
|
||||
// 业务处理:一些通用操作
|
||||
switch (ev.status) {
|
||||
case 200:
|
||||
// 业务层级错误处理,以下是假定restful有一套统一输出格式(指不管成功与否都有相应的数据格式)情况下进行处理
|
||||
// 例如响应内容:
|
||||
// 错误内容:{ status: 1, msg: '非法参数' }
|
||||
// 正确内容:{ status: 0, response: { } }
|
||||
// 则以下代码片断可直接适用
|
||||
// if (ev instanceof HttpResponse) {
|
||||
// const body = ev.body;
|
||||
// if (body && body.status !== 0) {
|
||||
// this.injector.get(NzMessageService).error(body.msg);
|
||||
// // 注意:这里如果继续抛出错误会被行254的 catchError 二次拦截,导致外部实现的 Pipe、subscribe 操作被中断,例如:this.http.get('/').subscribe() 不会触发
|
||||
// // 如果你希望外部实现,需要手动移除行254
|
||||
// return throwError({});
|
||||
// } else {
|
||||
// // 忽略 Blob 文件体
|
||||
// if (ev.body instanceof Blob) {
|
||||
// return of(ev);
|
||||
// }
|
||||
// // 重新修改 `body` 内容为 `response` 内容,对于绝大多数场景已经无须再关心业务状态码
|
||||
// return of(new HttpResponse(Object.assign(ev, { body: body.response })));
|
||||
// // 或者依然保持完整的格式
|
||||
// return of(ev);
|
||||
// }
|
||||
// }
|
||||
break;
|
||||
case 401:
|
||||
if (this.refreshTokenEnabled && this.refreshTokenType === 're-request') {
|
||||
return this.tryRefreshToken(ev, req, next);
|
||||
}
|
||||
this.toLogin();
|
||||
break;
|
||||
case 403:
|
||||
case 404:
|
||||
case 500:
|
||||
// this.goTo(`/exception/${ev.status}?url=${req.urlWithParams}`);
|
||||
break;
|
||||
default:
|
||||
if (ev instanceof HttpErrorResponse) {
|
||||
console.warn(
|
||||
'未可知错误,大部分是由于后端不支持跨域CORS或无效配置引起,请参考 https://ng-alain.com/docs/server 解决跨域问题',
|
||||
ev
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (ev instanceof HttpErrorResponse) {
|
||||
return throwError(ev);
|
||||
} else {
|
||||
return of(ev);
|
||||
}
|
||||
}
|
||||
|
||||
private getAdditionalHeaders(headers?: HttpHeaders): { [name: string]: string } {
|
||||
const res: { [name: string]: string } = {};
|
||||
const lang = this.injector.get(ALAIN_I18N_TOKEN).currentLang;
|
||||
if (!headers?.has('Accept-Language') && lang) {
|
||||
res['Accept-Language'] = lang;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
|
||||
// 统一加上服务端前缀
|
||||
let url = req.url;
|
||||
if (!url.startsWith('https://') && !url.startsWith('http://')) {
|
||||
const { baseUrl } = environment.api;
|
||||
url = baseUrl + (baseUrl.endsWith('/') && url.startsWith('/') ? url.substring(1) : url);
|
||||
}
|
||||
|
||||
const newReq = req.clone({ url, setHeaders: this.getAdditionalHeaders(req.headers) });
|
||||
return next.handle(newReq).pipe(
|
||||
mergeMap(ev => {
|
||||
// 允许统一对请求错误处理
|
||||
if (ev instanceof HttpResponseBase) {
|
||||
return this.handleData(ev, newReq, next);
|
||||
}
|
||||
// 若一切都正常,则后续操作
|
||||
return of(ev);
|
||||
}),
|
||||
catchError((err: HttpErrorResponse) => this.handleData(err, newReq, next))
|
||||
);
|
||||
}
|
||||
}
|
52
IoTGateway/ClientApp/src/app/core/net/simple.interceptor.ts
Normal file
52
IoTGateway/ClientApp/src/app/core/net/simple.interceptor.ts
Normal file
@ -0,0 +1,52 @@
|
||||
import { HttpRequest } from '@angular/common/http';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { AlainAuthConfig } from '@delon/util/config';
|
||||
import type { NzSafeAny } from 'ng-zorro-antd/core/types';
|
||||
|
||||
import { BaseInterceptor } from '../base.interceptor';
|
||||
import { CheckSimple } from '../helper';
|
||||
import { DA_SERVICE_TOKEN } from '../interface';
|
||||
import { SimpleTokenModel } from './simple.model';
|
||||
|
||||
/**
|
||||
* Simple 拦截器
|
||||
*
|
||||
* ```
|
||||
* // app.module.ts
|
||||
* { provide: HTTP_INTERCEPTORS, useClass: SimpleInterceptor, multi: true}
|
||||
* ```
|
||||
*/
|
||||
@Injectable()
|
||||
export class SimpleInterceptor extends BaseInterceptor {
|
||||
isAuth(_options: AlainAuthConfig): boolean {
|
||||
this.model = this.injector.get(DA_SERVICE_TOKEN).get() as SimpleTokenModel;
|
||||
return CheckSimple(this.model as SimpleTokenModel);
|
||||
}
|
||||
|
||||
setReq(req: HttpRequest<NzSafeAny>, options: AlainAuthConfig): HttpRequest<NzSafeAny> {
|
||||
const { token_send_template, token_send_key } = options;
|
||||
const token = token_send_template!.replace(/\$\{([\w]+)\}/g, (_: string, g) => this.model[g]);
|
||||
switch (options.token_send_place) {
|
||||
case 'header':
|
||||
const obj: NzSafeAny = {};
|
||||
obj[token_send_key!] = token;
|
||||
req = req.clone({
|
||||
setHeaders: obj
|
||||
});
|
||||
break;
|
||||
case 'body':
|
||||
const body = req.body || {};
|
||||
body[token_send_key!] = token;
|
||||
req = req.clone({
|
||||
body
|
||||
});
|
||||
break;
|
||||
case 'url':
|
||||
req = req.clone({
|
||||
params: req.params.append(token_send_key!, token)
|
||||
});
|
||||
break;
|
||||
}
|
||||
return req;
|
||||
}
|
||||
}
|
129
IoTGateway/ClientApp/src/app/core/startup/startup.service.ts
Normal file
129
IoTGateway/ClientApp/src/app/core/startup/startup.service.ts
Normal file
@ -0,0 +1,129 @@
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Injectable, Inject } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { ACLService } from '@delon/acl';
|
||||
import { DA_SERVICE_TOKEN, ITokenService } from '@delon/auth';
|
||||
import { ALAIN_I18N_TOKEN, MenuService, SettingsService, TitleService } from '@delon/theme';
|
||||
import type { NzSafeAny } from 'ng-zorro-antd/core/types';
|
||||
import { NzIconService } from 'ng-zorro-antd/icon';
|
||||
import { Observable, zip, of } from 'rxjs';
|
||||
import { catchError, map } from 'rxjs/operators';
|
||||
|
||||
import { ICONS } from '../../../style-icons';
|
||||
import { ICONS_AUTO } from '../../../style-icons-auto';
|
||||
import { I18NService } from '../i18n/i18n.service';
|
||||
|
||||
/**
|
||||
* Used for application startup
|
||||
* Generally used to get the basic data of the application, like: Menu Data, User Data, etc.
|
||||
*/
|
||||
@Injectable()
|
||||
export class StartupService {
|
||||
constructor(
|
||||
iconSrv: NzIconService,
|
||||
private menuService: MenuService,
|
||||
@Inject(ALAIN_I18N_TOKEN) private i18n: I18NService,
|
||||
private settingService: SettingsService,
|
||||
private aclService: ACLService,
|
||||
private titleService: TitleService,
|
||||
@Inject(DA_SERVICE_TOKEN) private tokenService: ITokenService,
|
||||
private httpClient: HttpClient,
|
||||
private router: Router
|
||||
) {
|
||||
iconSrv.addIcon(...ICONS_AUTO, ...ICONS);
|
||||
}
|
||||
|
||||
private viaHttp(): Observable<void> {
|
||||
const defaultLang = this.i18n.defaultLang;
|
||||
return zip(this.i18n.loadLangData(defaultLang), this.httpClient.get('assets/tmp/app-data.json')).pipe(
|
||||
catchError((res: NzSafeAny) => {
|
||||
console.warn(`StartupService.load: Network request failed`, res);
|
||||
setTimeout(() => this.router.navigateByUrl(`/exception/500`));
|
||||
return [];
|
||||
}),
|
||||
map(([langData, appData]: [Record<string, string>, NzSafeAny]) => {
|
||||
// setting language data
|
||||
this.i18n.use(defaultLang, langData);
|
||||
|
||||
// Application data
|
||||
// Application information: including site name, description, year
|
||||
this.settingService.setApp(appData.app);
|
||||
// User information: including name, avatar, email address
|
||||
this.settingService.setUser(appData.user);
|
||||
// ACL: Set the permissions to full, https://ng-alain.com/acl/getting-started
|
||||
this.aclService.setFull(true);
|
||||
// Menu data, https://ng-alain.com/theme/menu
|
||||
this.menuService.add(appData.menu);
|
||||
// Can be set page suffix title, https://ng-alain.com/theme/title
|
||||
this.titleService.suffix = appData.app.name;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private viaMockI18n(): Observable<void> {
|
||||
const defaultLang = this.i18n.defaultLang;
|
||||
return this.i18n.loadLangData(defaultLang).pipe(
|
||||
map((langData: NzSafeAny) => {
|
||||
this.i18n.use(defaultLang, langData);
|
||||
|
||||
this.viaMock();
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private viaMock(): Observable<void> {
|
||||
// const tokenData = this.tokenService.get();
|
||||
// if (!tokenData.token) {
|
||||
// this.router.navigateByUrl(this.tokenService.login_url!);
|
||||
// return;
|
||||
// }
|
||||
// mock
|
||||
const app: any = {
|
||||
name: `iotgateway`,
|
||||
description: `Ng-zorro admin panel front-end framework`
|
||||
};
|
||||
const user: any = {
|
||||
name: 'Admin',
|
||||
avatar: './assets/tmp/img/avatar.jpg',
|
||||
email: '535915157@qq.com',
|
||||
token: '123456789'
|
||||
};
|
||||
// Application information: including site name, description, year
|
||||
this.settingService.setApp(app);
|
||||
// User information: including name, avatar, email address
|
||||
this.settingService.setUser(user);
|
||||
// ACL: Set the permissions to full, https://ng-alain.com/acl/getting-started
|
||||
this.aclService.setFull(true);
|
||||
// Menu data, https://ng-alain.com/theme/menu
|
||||
this.menuService.add([
|
||||
{
|
||||
text: 'Main',
|
||||
group: true,
|
||||
children: [
|
||||
{
|
||||
text: 'Dashboard',
|
||||
link: '/dashboard',
|
||||
icon: { type: 'icon', value: 'appstore' }
|
||||
},
|
||||
{
|
||||
text: 'Sys',
|
||||
link: '/sys/log',
|
||||
icon: { type: 'icon', value: 'appstore' }
|
||||
}
|
||||
]
|
||||
}
|
||||
]);
|
||||
// Can be set page suffix title, https://ng-alain.com/theme/title
|
||||
this.titleService.suffix = app.name;
|
||||
|
||||
return of();
|
||||
}
|
||||
|
||||
load(): Observable<void> {
|
||||
// http
|
||||
// return this.viaHttp();
|
||||
// mock: Don’t use it in a production environment. ViaMock is just to simulate some data to make the scaffolding work normally
|
||||
// mock:请勿在生产环境中这么使用,viaMock 单纯只是为了模拟一些数据使脚手架一开始能正常运行
|
||||
return this.viaMockI18n();
|
||||
}
|
||||
}
|
77
IoTGateway/ClientApp/src/app/global-config.module.ts
Normal file
77
IoTGateway/ClientApp/src/app/global-config.module.ts
Normal file
@ -0,0 +1,77 @@
|
||||
/* eslint-disable import/order */
|
||||
import { ModuleWithProviders, NgModule, Optional, SkipSelf } from '@angular/core';
|
||||
import { DelonACLModule } from '@delon/acl';
|
||||
import { AlainThemeModule } from '@delon/theme';
|
||||
import { AlainConfig, ALAIN_CONFIG } from '@delon/util/config';
|
||||
|
||||
import { throwIfAlreadyLoaded } from '@core';
|
||||
|
||||
import { environment } from '@env/environment';
|
||||
|
||||
// Please refer to: https://ng-alain.com/docs/global-config
|
||||
// #region NG-ALAIN Config
|
||||
|
||||
const alainConfig: AlainConfig = {
|
||||
st: { modal: { size: 'lg' } },
|
||||
pageHeader: { homeI18n: 'home' },
|
||||
lodop: {
|
||||
license: `A59B099A586B3851E0F0D7FDBF37B603`,
|
||||
licenseA: `C94CEE276DB2187AE6B65D56B3FC2848`
|
||||
},
|
||||
auth: { login_url: '/passport/login' }
|
||||
};
|
||||
|
||||
const alainModules: any[] = [AlainThemeModule.forRoot(), DelonACLModule.forRoot()];
|
||||
const alainProvides = [{ provide: ALAIN_CONFIG, useValue: alainConfig }];
|
||||
|
||||
// #region reuse-tab
|
||||
/**
|
||||
* 若需要[路由复用](https://ng-alain.com/components/reuse-tab)需要:
|
||||
* 1、在 `shared-delon.module.ts` 导入 `ReuseTabModule` 模块
|
||||
* 2、注册 `RouteReuseStrategy`
|
||||
* 3、在 `src/app/layout/default/default.component.html` 修改:
|
||||
* ```html
|
||||
* <section class="alain-default__content">
|
||||
* <reuse-tab #reuseTab></reuse-tab>
|
||||
* <router-outlet (activate)="reuseTab.activate($event)"></router-outlet>
|
||||
* </section>
|
||||
* ```
|
||||
*/
|
||||
// import { RouteReuseStrategy } from '@angular/router';
|
||||
// import { ReuseTabService, ReuseTabStrategy } from '@delon/abc/reuse-tab';
|
||||
// alainProvides.push({
|
||||
// provide: RouteReuseStrategy,
|
||||
// useClass: ReuseTabStrategy,
|
||||
// deps: [ReuseTabService],
|
||||
// } as any);
|
||||
|
||||
// #endregion
|
||||
|
||||
// #endregion
|
||||
|
||||
// Please refer to: https://ng.ant.design/docs/global-config/en#how-to-use
|
||||
// #region NG-ZORRO Config
|
||||
|
||||
import { NzConfig, NZ_CONFIG } from 'ng-zorro-antd/core/config';
|
||||
|
||||
const ngZorroConfig: NzConfig = {};
|
||||
|
||||
const zorroProvides = [{ provide: NZ_CONFIG, useValue: ngZorroConfig }];
|
||||
|
||||
// #endregion
|
||||
|
||||
@NgModule({
|
||||
imports: [...alainModules, ...(environment.modules || [])]
|
||||
})
|
||||
export class GlobalConfigModule {
|
||||
constructor(@Optional() @SkipSelf() parentModule: GlobalConfigModule) {
|
||||
throwIfAlreadyLoaded(parentModule, 'GlobalConfigModule');
|
||||
}
|
||||
|
||||
static forRoot(): ModuleWithProviders<GlobalConfigModule> {
|
||||
return {
|
||||
ngModule: GlobalConfigModule,
|
||||
providers: [...alainProvides, ...zorroProvides]
|
||||
};
|
||||
}
|
||||
}
|
1
IoTGateway/ClientApp/src/app/layout/basic/README.md
Normal file
1
IoTGateway/ClientApp/src/app/layout/basic/README.md
Normal file
@ -0,0 +1 @@
|
||||
[Document](https://ng-alain.com/theme/default)
|
85
IoTGateway/ClientApp/src/app/layout/basic/basic.component.ts
Normal file
85
IoTGateway/ClientApp/src/app/layout/basic/basic.component.ts
Normal file
@ -0,0 +1,85 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { SettingsService, User } from '@delon/theme';
|
||||
import { LayoutDefaultOptions } from '@delon/theme/layout-default';
|
||||
import { environment } from '@env/environment';
|
||||
|
||||
@Component({
|
||||
selector: 'layout-basic',
|
||||
template: `
|
||||
<layout-default [options]="options" [asideUser]="asideUserTpl" [content]="contentTpl" [customError]="null">
|
||||
<layout-default-header-item direction="left">
|
||||
<a layout-default-header-item-trigger href="//github.com/iioter/iotgateway" target="_blank">
|
||||
<i nz-icon nzType="github"></i>
|
||||
</a>
|
||||
</layout-default-header-item>
|
||||
<layout-default-header-item direction="left" hidden="mobile">
|
||||
<a layout-default-header-item-trigger routerLink="/passport/lock">
|
||||
<i nz-icon nzType="lock"></i>
|
||||
</a>
|
||||
</layout-default-header-item>
|
||||
<layout-default-header-item direction="left" hidden="pc">
|
||||
<div layout-default-header-item-trigger (click)="searchToggleStatus = !searchToggleStatus">
|
||||
<i nz-icon nzType="search"></i>
|
||||
</div>
|
||||
</layout-default-header-item>
|
||||
<layout-default-header-item direction="middle">
|
||||
<header-search class="alain-default__search" [toggleChange]="searchToggleStatus"></header-search>
|
||||
</layout-default-header-item>
|
||||
<layout-default-header-item direction="right" hidden="mobile">
|
||||
<div layout-default-header-item-trigger nz-dropdown [nzDropdownMenu]="settingsMenu" nzTrigger="click" nzPlacement="bottomRight">
|
||||
<i nz-icon nzType="setting"></i>
|
||||
</div>
|
||||
<nz-dropdown-menu #settingsMenu="nzDropdownMenu">
|
||||
<div nz-menu style="width: 200px;">
|
||||
<div nz-menu-item>
|
||||
<header-fullscreen></header-fullscreen>
|
||||
</div>
|
||||
<div nz-menu-item>
|
||||
<header-clear-storage></header-clear-storage>
|
||||
</div>
|
||||
<div nz-menu-item>
|
||||
<header-i18n></header-i18n>
|
||||
</div>
|
||||
</div>
|
||||
</nz-dropdown-menu>
|
||||
</layout-default-header-item>
|
||||
<layout-default-header-item direction="right">
|
||||
<header-user></header-user>
|
||||
</layout-default-header-item>
|
||||
<ng-template #asideUserTpl>
|
||||
<div nz-dropdown nzTrigger="click" [nzDropdownMenu]="userMenu" class="alain-default__aside-user">
|
||||
<nz-avatar class="alain-default__aside-user-avatar" [nzSrc]="user.avatar"></nz-avatar>
|
||||
<div class="alain-default__aside-user-info">
|
||||
<strong>{{ user.name }}</strong>
|
||||
<p class="mb0">{{ user.email }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<nz-dropdown-menu #userMenu="nzDropdownMenu">
|
||||
<ul nz-menu>
|
||||
<li nz-menu-item routerLink="/pro/account/center">{{ 'menu.account.center' | i18n }}</li>
|
||||
<li nz-menu-item routerLink="/pro/account/settings">{{ 'menu.account.settings' | i18n }}</li>
|
||||
</ul>
|
||||
</nz-dropdown-menu>
|
||||
</ng-template>
|
||||
<ng-template #contentTpl>
|
||||
<router-outlet></router-outlet>
|
||||
</ng-template>
|
||||
</layout-default>
|
||||
|
||||
<setting-drawer *ngIf="showSettingDrawer"></setting-drawer>
|
||||
<theme-btn></theme-btn>
|
||||
`
|
||||
})
|
||||
export class LayoutBasicComponent {
|
||||
options: LayoutDefaultOptions = {
|
||||
logoExpanded: `./assets/logo-full.svg`,
|
||||
logoCollapsed: `./assets/logo.svg`
|
||||
};
|
||||
searchToggleStatus = false;
|
||||
showSettingDrawer = !environment.production;
|
||||
get user(): User {
|
||||
return this.settings.user;
|
||||
}
|
||||
|
||||
constructor(private settings: SettingsService) {}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
import { ChangeDetectionStrategy, Component, HostListener } from '@angular/core';
|
||||
import { NzMessageService } from 'ng-zorro-antd/message';
|
||||
import { NzModalService } from 'ng-zorro-antd/modal';
|
||||
|
||||
@Component({
|
||||
selector: 'header-clear-storage',
|
||||
template: `
|
||||
<i nz-icon nzType="tool"></i>
|
||||
{{ 'menu.clear.local.storage' | i18n }}
|
||||
`,
|
||||
host: {
|
||||
'[class.flex-1]': 'true'
|
||||
},
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class HeaderClearStorageComponent {
|
||||
constructor(private modalSrv: NzModalService, private messageSrv: NzMessageService) {}
|
||||
|
||||
@HostListener('click')
|
||||
_click(): void {
|
||||
this.modalSrv.confirm({
|
||||
nzTitle: 'Make sure clear all local storage?',
|
||||
nzOnOk: () => {
|
||||
localStorage.clear();
|
||||
this.messageSrv.success('Clear Finished!');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
import { ChangeDetectionStrategy, Component, HostListener } from '@angular/core';
|
||||
import screenfull from 'screenfull';
|
||||
|
||||
@Component({
|
||||
selector: 'header-fullscreen',
|
||||
template: `
|
||||
<i nz-icon [nzType]="status ? 'fullscreen-exit' : 'fullscreen'"></i>
|
||||
{{ (status ? 'menu.fullscreen.exit' : 'menu.fullscreen') | i18n }}
|
||||
`,
|
||||
host: {
|
||||
'[class.flex-1]': 'true'
|
||||
},
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class HeaderFullScreenComponent {
|
||||
status = false;
|
||||
|
||||
@HostListener('window:resize')
|
||||
_resize(): void {
|
||||
this.status = screenfull.isFullscreen;
|
||||
}
|
||||
|
||||
@HostListener('click')
|
||||
_click(): void {
|
||||
if (screenfull.isEnabled) {
|
||||
screenfull.toggle();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
import { DOCUMENT } from '@angular/common';
|
||||
import { ChangeDetectionStrategy, Component, Inject, Input } from '@angular/core';
|
||||
import { I18NService } from '@core';
|
||||
import { ALAIN_I18N_TOKEN, SettingsService } from '@delon/theme';
|
||||
import { BooleanInput, InputBoolean } from '@delon/util/decorator';
|
||||
|
||||
@Component({
|
||||
selector: 'header-i18n',
|
||||
template: `
|
||||
<div *ngIf="showLangText" nz-dropdown [nzDropdownMenu]="langMenu" nzPlacement="bottomRight">
|
||||
<i nz-icon nzType="global"></i>
|
||||
{{ 'menu.lang' | i18n }}
|
||||
<i nz-icon nzType="down"></i>
|
||||
</div>
|
||||
<i *ngIf="!showLangText" nz-dropdown [nzDropdownMenu]="langMenu" nzPlacement="bottomRight" nz-icon nzType="global"></i>
|
||||
<nz-dropdown-menu #langMenu="nzDropdownMenu">
|
||||
<ul nz-menu>
|
||||
<li nz-menu-item *ngFor="let item of langs" [nzSelected]="item.code === curLangCode" (click)="change(item.code)">
|
||||
<span role="img" [attr.aria-label]="item.text" class="pr-xs">{{ item.abbr }}</span>
|
||||
{{ item.text }}
|
||||
</li>
|
||||
</ul>
|
||||
</nz-dropdown-menu>
|
||||
`,
|
||||
host: {
|
||||
'[class.flex-1]': 'true'
|
||||
},
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class HeaderI18nComponent {
|
||||
static ngAcceptInputType_showLangText: BooleanInput;
|
||||
/** Whether to display language text */
|
||||
@Input() @InputBoolean() showLangText = true;
|
||||
|
||||
get langs(): Array<{ code: string; text: string; abbr: string }> {
|
||||
return this.i18n.getLangs();
|
||||
}
|
||||
|
||||
get curLangCode(): string {
|
||||
return this.settings.layout.lang;
|
||||
}
|
||||
|
||||
constructor(private settings: SettingsService, @Inject(ALAIN_I18N_TOKEN) private i18n: I18NService, @Inject(DOCUMENT) private doc: any) {}
|
||||
|
||||
change(lang: string): void {
|
||||
const spinEl = this.doc.createElement('div');
|
||||
spinEl.setAttribute('class', `page-loading ant-spin ant-spin-lg ant-spin-spinning`);
|
||||
spinEl.innerHTML = `<span class="ant-spin-dot ant-spin-dot-spin"><i></i><i></i><i></i><i></i></span>`;
|
||||
this.doc.body.appendChild(spinEl);
|
||||
|
||||
this.i18n.loadLangData(lang).subscribe(res => {
|
||||
this.i18n.use(lang, res);
|
||||
this.settings.setLayout('lang', lang);
|
||||
setTimeout(() => this.doc.location.reload());
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,108 @@
|
||||
import {
|
||||
AfterViewInit,
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
ElementRef,
|
||||
EventEmitter,
|
||||
HostBinding,
|
||||
Input,
|
||||
OnDestroy,
|
||||
Output
|
||||
} from '@angular/core';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { debounceTime, distinctUntilChanged, tap } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'header-search',
|
||||
template: `
|
||||
<nz-input-group [nzPrefix]="iconTpl" [nzSuffix]="loadingTpl">
|
||||
<ng-template #iconTpl>
|
||||
<i nz-icon [nzType]="focus ? 'arrow-down' : 'search'"></i>
|
||||
</ng-template>
|
||||
<ng-template #loadingTpl>
|
||||
<i *ngIf="loading" nz-icon nzType="loading"></i>
|
||||
</ng-template>
|
||||
<input
|
||||
type="text"
|
||||
nz-input
|
||||
[(ngModel)]="q"
|
||||
[nzAutocomplete]="auto"
|
||||
(input)="search($event)"
|
||||
(focus)="qFocus()"
|
||||
(blur)="qBlur()"
|
||||
[attr.placeholder]="'' | i18n"
|
||||
/>
|
||||
</nz-input-group>
|
||||
<nz-autocomplete nzBackfill #auto>
|
||||
<nz-auto-option *ngFor="let i of options" [nzValue]="i">{{ i }}</nz-auto-option>
|
||||
</nz-autocomplete>
|
||||
`,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class HeaderSearchComponent implements AfterViewInit, OnDestroy {
|
||||
q = '';
|
||||
qIpt: HTMLInputElement | null = null;
|
||||
options: string[] = [];
|
||||
search$ = new BehaviorSubject('');
|
||||
loading = false;
|
||||
|
||||
@HostBinding('class.alain-default__search-focus')
|
||||
focus = false;
|
||||
@HostBinding('class.alain-default__search-toggled')
|
||||
searchToggled = false;
|
||||
|
||||
@Input()
|
||||
set toggleChange(value: boolean) {
|
||||
if (typeof value === 'undefined') {
|
||||
return;
|
||||
}
|
||||
this.searchToggled = value;
|
||||
this.focus = value;
|
||||
if (value) {
|
||||
setTimeout(() => this.qIpt!.focus());
|
||||
}
|
||||
}
|
||||
@Output() readonly toggleChangeChange = new EventEmitter<boolean>();
|
||||
|
||||
constructor(private el: ElementRef<HTMLElement>, private cdr: ChangeDetectorRef) {}
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
this.qIpt = this.el.nativeElement.querySelector('.ant-input') as HTMLInputElement;
|
||||
this.search$
|
||||
.pipe(
|
||||
debounceTime(500),
|
||||
distinctUntilChanged(),
|
||||
tap({
|
||||
complete: () => {
|
||||
this.loading = true;
|
||||
}
|
||||
})
|
||||
)
|
||||
.subscribe(value => {
|
||||
this.options = value ? [value, value + value, value + value + value] : [];
|
||||
this.loading = false;
|
||||
this.cdr.detectChanges();
|
||||
});
|
||||
}
|
||||
|
||||
qFocus(): void {
|
||||
this.focus = true;
|
||||
}
|
||||
|
||||
qBlur(): void {
|
||||
this.focus = false;
|
||||
this.searchToggled = false;
|
||||
this.options.length = 0;
|
||||
this.toggleChangeChange.emit(false);
|
||||
}
|
||||
|
||||
search(ev: Event): void {
|
||||
this.search$.next((ev.target as HTMLInputElement).value);
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.search$.complete();
|
||||
this.search$.unsubscribe();
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
import { ChangeDetectionStrategy, Component, Inject } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { DA_SERVICE_TOKEN, ITokenService } from '@delon/auth';
|
||||
import { SettingsService, User } from '@delon/theme';
|
||||
|
||||
@Component({
|
||||
selector: 'header-user',
|
||||
template: `
|
||||
<div class="alain-default__nav-item d-flex align-items-center px-sm" nz-dropdown nzPlacement="bottomRight" [nzDropdownMenu]="userMenu">
|
||||
<nz-avatar [nzSrc]="user.avatar" nzSize="small" class="mr-sm"></nz-avatar>
|
||||
{{ user.name }}
|
||||
</div>
|
||||
<nz-dropdown-menu #userMenu="nzDropdownMenu">
|
||||
<div nz-menu class="width-sm">
|
||||
<div nz-menu-item routerLink="/pro/account/center">
|
||||
<i nz-icon nzType="user" class="mr-sm"></i>
|
||||
{{ 'menu.account.center' | i18n }}
|
||||
</div>
|
||||
<div nz-menu-item routerLink="/pro/account/settings">
|
||||
<i nz-icon nzType="setting" class="mr-sm"></i>
|
||||
{{ 'menu.account.settings' | i18n }}
|
||||
</div>
|
||||
<div nz-menu-item routerLink="/exception/trigger">
|
||||
<i nz-icon nzType="close-circle" class="mr-sm"></i>
|
||||
{{ 'menu.account.trigger' | i18n }}
|
||||
</div>
|
||||
<li nz-menu-divider></li>
|
||||
<div nz-menu-item (click)="logout()">
|
||||
<i nz-icon nzType="logout" class="mr-sm"></i>
|
||||
{{ 'menu.account.logout' | i18n }}
|
||||
</div>
|
||||
</div>
|
||||
</nz-dropdown-menu>
|
||||
`,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class HeaderUserComponent {
|
||||
get user(): User {
|
||||
return this.settings.user;
|
||||
}
|
||||
|
||||
constructor(private settings: SettingsService, private router: Router, @Inject(DA_SERVICE_TOKEN) private tokenService: ITokenService) {}
|
||||
|
||||
logout(): void {
|
||||
this.tokenService.clear();
|
||||
this.router.navigateByUrl(this.tokenService.login_url!);
|
||||
}
|
||||
}
|
1
IoTGateway/ClientApp/src/app/layout/blank/README.md
Normal file
1
IoTGateway/ClientApp/src/app/layout/blank/README.md
Normal file
@ -0,0 +1 @@
|
||||
[Document](https://ng-alain.com/theme/blank)
|
10
IoTGateway/ClientApp/src/app/layout/blank/blank.component.ts
Normal file
10
IoTGateway/ClientApp/src/app/layout/blank/blank.component.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'layout-blank',
|
||||
template: `<router-outlet></router-outlet> `,
|
||||
host: {
|
||||
'[class.alain-blank]': 'true'
|
||||
}
|
||||
})
|
||||
export class LayoutBlankComponent {}
|
68
IoTGateway/ClientApp/src/app/layout/layout.module.ts
Normal file
68
IoTGateway/ClientApp/src/app/layout/layout.module.ts
Normal file
@ -0,0 +1,68 @@
|
||||
/* eslint-disable import/order */
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { GlobalFooterModule } from '@delon/abc/global-footer';
|
||||
import { NoticeIconModule } from '@delon/abc/notice-icon';
|
||||
import { AlainThemeModule } from '@delon/theme';
|
||||
import { LayoutDefaultModule } from '@delon/theme/layout-default';
|
||||
import { SettingDrawerModule } from '@delon/theme/setting-drawer';
|
||||
import { ThemeBtnModule } from '@delon/theme/theme-btn';
|
||||
import { NzAutocompleteModule } from 'ng-zorro-antd/auto-complete';
|
||||
import { NzAvatarModule } from 'ng-zorro-antd/avatar';
|
||||
import { NzBadgeModule } from 'ng-zorro-antd/badge';
|
||||
import { NzDropDownModule } from 'ng-zorro-antd/dropdown';
|
||||
import { NzFormModule } from 'ng-zorro-antd/form';
|
||||
import { NzGridModule } from 'ng-zorro-antd/grid';
|
||||
import { NzIconModule } from 'ng-zorro-antd/icon';
|
||||
import { NzInputModule } from 'ng-zorro-antd/input';
|
||||
import { NzSpinModule } from 'ng-zorro-antd/spin';
|
||||
|
||||
import { LayoutBasicComponent } from './basic/basic.component';
|
||||
import { HeaderClearStorageComponent } from './basic/widgets/clear-storage.component';
|
||||
import { HeaderFullScreenComponent } from './basic/widgets/fullscreen.component';
|
||||
import { HeaderI18nComponent } from './basic/widgets/i18n.component';
|
||||
import { HeaderSearchComponent } from './basic/widgets/search.component';
|
||||
import { HeaderUserComponent } from './basic/widgets/user.component';
|
||||
import { LayoutBlankComponent } from './blank/blank.component';
|
||||
|
||||
const COMPONENTS = [LayoutBasicComponent, LayoutBlankComponent];
|
||||
|
||||
const HEADERCOMPONENTS = [
|
||||
HeaderSearchComponent,
|
||||
HeaderFullScreenComponent,
|
||||
HeaderI18nComponent,
|
||||
HeaderClearStorageComponent,
|
||||
HeaderUserComponent
|
||||
];
|
||||
|
||||
// passport
|
||||
import { LayoutPassportComponent } from './passport/passport.component';
|
||||
const PASSPORT = [LayoutPassportComponent];
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
RouterModule,
|
||||
AlainThemeModule.forChild(),
|
||||
ThemeBtnModule,
|
||||
SettingDrawerModule,
|
||||
LayoutDefaultModule,
|
||||
NoticeIconModule,
|
||||
GlobalFooterModule,
|
||||
NzDropDownModule,
|
||||
NzInputModule,
|
||||
NzAutocompleteModule,
|
||||
NzGridModule,
|
||||
NzFormModule,
|
||||
NzSpinModule,
|
||||
NzBadgeModule,
|
||||
NzAvatarModule,
|
||||
NzIconModule
|
||||
],
|
||||
declarations: [...COMPONENTS, ...HEADERCOMPONENTS, ...PASSPORT],
|
||||
exports: [...COMPONENTS, ...PASSPORT]
|
||||
})
|
||||
export class LayoutModule {}
|
@ -0,0 +1,17 @@
|
||||
<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/logo.png" />
|
||||
<span class="title">IoTGateway</span>
|
||||
</div>
|
||||
<div class="desc">angular版本的前端暂不可用</div>
|
||||
</div>
|
||||
<router-outlet></router-outlet>
|
||||
<global-footer>
|
||||
Copyright
|
||||
<i class="anticon anticon-copyright"></i> 2022 <a href="//github.com/iioter/iotgateway" target="_blank">iioter</a>出品
|
||||
</global-footer>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,112 @@
|
||||
@import '@delon/theme/index';
|
||||
|
||||
:host ::ng-deep {
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100%;
|
||||
background: #f0f2f5;
|
||||
}
|
||||
|
||||
.langs {
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
line-height: 44px;
|
||||
text-align: right;
|
||||
|
||||
.anticon {
|
||||
margin-top: 24px;
|
||||
margin-right: 24px;
|
||||
font-size: 14px;
|
||||
vertical-align: top;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.wrap {
|
||||
flex: 1;
|
||||
padding: 32px 0;
|
||||
}
|
||||
|
||||
.ant-form-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
@media (min-width: @screen-md-min) {
|
||||
.container {
|
||||
background-image: url('https://gw.alipayobjects.com/zos/rmsportal/TVYTbAXWheQpRcWDaDMu.svg');
|
||||
background-repeat: no-repeat;
|
||||
background-position: center 110px;
|
||||
background-size: 100%;
|
||||
}
|
||||
|
||||
.wrap {
|
||||
padding: 32px 0 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.top {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.header {
|
||||
height: 44px;
|
||||
line-height: 44px;
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
.logo {
|
||||
height: 44px;
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
.title {
|
||||
position: relative;
|
||||
color: @heading-color;
|
||||
font-weight: 600;
|
||||
font-size: 33px;
|
||||
font-family: 'Myriad Pro', 'Helvetica Neue', Arial, Helvetica, sans-serif;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.desc {
|
||||
margin-top: 12px;
|
||||
margin-bottom: 40px;
|
||||
color: @text-color-secondary;
|
||||
font-size: @font-size-base;
|
||||
}
|
||||
}
|
||||
|
||||
[data-theme='dark'] {
|
||||
:host ::ng-deep {
|
||||
.container {
|
||||
background: #141414;
|
||||
}
|
||||
|
||||
.title {
|
||||
color: fade(@white, 85%);
|
||||
}
|
||||
|
||||
.desc {
|
||||
color: fade(@white, 45%);
|
||||
}
|
||||
@media (min-width: @screen-md-min) {
|
||||
.container {
|
||||
background-image: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[data-theme='compact'] {
|
||||
:host ::ng-deep {
|
||||
.ant-form-item {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
import { Component, Inject, OnInit } from '@angular/core';
|
||||
import { DA_SERVICE_TOKEN, ITokenService } from '@delon/auth';
|
||||
|
||||
@Component({
|
||||
selector: 'layout-passport',
|
||||
templateUrl: './passport.component.html',
|
||||
styleUrls: ['./passport.component.less']
|
||||
})
|
||||
export class LayoutPassportComponent implements OnInit {
|
||||
constructor(@Inject(DA_SERVICE_TOKEN) private tokenService: ITokenService) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.tokenService.clear();
|
||||
}
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
<page-header></page-header>
|
||||
|
||||
<div>
|
||||
<h2>dashboard content</h2>
|
||||
</div>
|
@ -0,0 +1,8 @@
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-dashboard',
|
||||
templateUrl: './dashboard.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class DashboardComponent {}
|
@ -0,0 +1,18 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
|
||||
import { ExceptionComponent } from './exception.component';
|
||||
import { ExceptionTriggerComponent } from './trigger.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{ path: '403', component: ExceptionComponent, data: { type: 403 } },
|
||||
{ path: '404', component: ExceptionComponent, data: { type: 404 } },
|
||||
{ path: '500', component: ExceptionComponent, data: { type: 500 } },
|
||||
{ path: 'trigger', component: ExceptionTriggerComponent }
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class ExceptionRoutingModule {}
|
@ -0,0 +1,16 @@
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { ExceptionType } from '@delon/abc/exception';
|
||||
|
||||
@Component({
|
||||
selector: 'app-exception',
|
||||
template: ` <exception [type]="type" style="min-height: 500px; height: 80%;"> </exception> `,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class ExceptionComponent {
|
||||
get type(): ExceptionType {
|
||||
return this.route.snapshot.data['type'];
|
||||
}
|
||||
|
||||
constructor(private route: ActivatedRoute) {}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { ExceptionModule as DelonExceptionModule } from '@delon/abc/exception';
|
||||
import { NzButtonModule } from 'ng-zorro-antd/button';
|
||||
import { NzCardModule } from 'ng-zorro-antd/card';
|
||||
|
||||
import { ExceptionRoutingModule } from './exception-routing.module';
|
||||
import { ExceptionComponent } from './exception.component';
|
||||
import { ExceptionTriggerComponent } from './trigger.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, DelonExceptionModule, NzButtonModule, NzCardModule, ExceptionRoutingModule],
|
||||
declarations: [ExceptionComponent, ExceptionTriggerComponent]
|
||||
})
|
||||
export class ExceptionModule {}
|
@ -0,0 +1,35 @@
|
||||
import { Component, Inject } from '@angular/core';
|
||||
import { DA_SERVICE_TOKEN, ITokenService } from '@delon/auth';
|
||||
import { _HttpClient } from '@delon/theme';
|
||||
|
||||
@Component({
|
||||
selector: 'exception-trigger',
|
||||
template: `
|
||||
<div class="pt-lg">
|
||||
<nz-card>
|
||||
<button *ngFor="let t of types" (click)="go(t)" nz-button nzDanger>触发{{ t }}</button>
|
||||
<button nz-button nzType="link" (click)="refresh()">触发刷新Token</button>
|
||||
</nz-card>
|
||||
</div>
|
||||
`
|
||||
})
|
||||
export class ExceptionTriggerComponent {
|
||||
types = [401, 403, 404, 500];
|
||||
|
||||
constructor(private http: _HttpClient, @Inject(DA_SERVICE_TOKEN) private tokenService: ITokenService) {}
|
||||
|
||||
go(type: number): void {
|
||||
this.http.get(`/api/${type}`).subscribe();
|
||||
}
|
||||
|
||||
refresh(): void {
|
||||
this.tokenService.set({ token: 'invalid-token' });
|
||||
// 必须提供一个后端地址,无法通过 Mock 来模拟
|
||||
this.http.post(`https://localhost:5001/auth`).subscribe(
|
||||
res => console.warn('成功', res),
|
||||
err => {
|
||||
console.log('最后结果失败', err);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
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 = '';
|
||||
|
||||
constructor(private socialService: SocialService, private settingsSrv: SettingsService, private route: ActivatedRoute) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.type = this.route.snapshot.params['type'];
|
||||
this.mockModel();
|
||||
}
|
||||
|
||||
private mockModel(): void {
|
||||
const info = {
|
||||
token: '123456789',
|
||||
name: 'iotgateway',
|
||||
email: `${this.type}@${this.type}.com`,
|
||||
id: 10000,
|
||||
time: +new Date()
|
||||
};
|
||||
this.settingsSrv.setUser({
|
||||
...this.settingsSrv.user,
|
||||
...info
|
||||
});
|
||||
this.socialService.callback(info);
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
<div class="ant-card width-lg" style="margin: 0 auto">
|
||||
<div class="ant-card-body">
|
||||
<div class="avatar">
|
||||
<nz-avatar [nzSrc]="user.avatar" nzIcon="user" nzSize="large"></nz-avatar>
|
||||
</div>
|
||||
<form nz-form [formGroup]="f" (ngSubmit)="submit()" role="form" class="mt-md">
|
||||
<nz-form-item>
|
||||
<nz-form-control [nzErrorTip]="'validation.password.required' | i18n">
|
||||
<nz-input-group nzSuffixIcon="lock">
|
||||
<input type="password" nz-input formControlName="password" />
|
||||
</nz-input-group>
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
<nz-row nzType="flex" nzAlign="middle">
|
||||
<nz-col [nzOffset]="12" [nzSpan]="12" style="text-align: right">
|
||||
<button nz-button [disabled]="!f.valid" nzType="primary">{{ 'app.lock' | i18n }}</button>
|
||||
</nz-col>
|
||||
</nz-row>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,13 @@
|
||||
:host ::ng-deep {
|
||||
.ant-card-body {
|
||||
position: relative;
|
||||
margin-top: 80px;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
position: absolute;
|
||||
top: -20px;
|
||||
left: 50%;
|
||||
margin-left: -20px;
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
import { Component, Inject } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { Router } from '@angular/router';
|
||||
import { DA_SERVICE_TOKEN, ITokenService } from '@delon/auth';
|
||||
import { SettingsService, User } from '@delon/theme';
|
||||
|
||||
@Component({
|
||||
selector: 'passport-lock',
|
||||
templateUrl: './lock.component.html',
|
||||
styleUrls: ['./lock.component.less']
|
||||
})
|
||||
export class UserLockComponent {
|
||||
f: FormGroup;
|
||||
|
||||
get user(): User {
|
||||
return this.settings.user;
|
||||
}
|
||||
|
||||
constructor(
|
||||
fb: FormBuilder,
|
||||
@Inject(DA_SERVICE_TOKEN) private tokenService: ITokenService,
|
||||
private settings: SettingsService,
|
||||
private router: Router
|
||||
) {
|
||||
this.f = fb.group({
|
||||
password: [null, Validators.required]
|
||||
});
|
||||
}
|
||||
|
||||
submit(): void {
|
||||
for (const i in this.f.controls) {
|
||||
this.f.controls[i].markAsDirty();
|
||||
this.f.controls[i].updateValueAndValidity();
|
||||
}
|
||||
if (this.f.valid) {
|
||||
console.log('Valid!');
|
||||
console.log(this.f.value);
|
||||
this.tokenService.set({
|
||||
token: '123'
|
||||
});
|
||||
this.router.navigate(['dashboard']);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
<form nz-form [formGroup]="form" (ngSubmit)="submit()" role="form">
|
||||
<nz-tabset [nzAnimated]="false" class="tabs" (nzSelectChange)="switch($event)">
|
||||
<nz-tab [nzTitle]="'app.login.tab-login-credentials' | i18n">
|
||||
<nz-alert *ngIf="error" [nzType]="'error'" [nzMessage]="error" [nzShowIcon]="true" class="mb-lg"></nz-alert>
|
||||
<nz-form-item>
|
||||
<nz-form-control nzErrorTip="Please enter mobile number, muse be: admin">
|
||||
<nz-input-group nzSize="large" nzPrefixIcon="user">
|
||||
<input nz-input formControlName="userName" placeholder="username: admin" />
|
||||
</nz-input-group>
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
<nz-form-item>
|
||||
<nz-form-control nzErrorTip="Please enter password, muse be: 000000">
|
||||
<nz-input-group nzSize="large" nzPrefixIcon="lock">
|
||||
<input nz-input type="password" formControlName="password" placeholder="password: 000000" />
|
||||
</nz-input-group>
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
</nz-tab>
|
||||
</nz-tabset>
|
||||
<nz-form-item>
|
||||
<nz-col [nzSpan]="12">
|
||||
<label nz-checkbox formControlName="remember">{{ 'app.login.remember-me' | i18n }}</label>
|
||||
</nz-col>
|
||||
</nz-form-item>
|
||||
<nz-form-item>
|
||||
<button nz-button type="submit" nzType="primary" nzSize="large" [nzLoading]="loading" nzBlock>
|
||||
{{ 'app.login.login' | i18n }}
|
||||
</button>
|
||||
</nz-form-item>
|
||||
</form>
|
||||
<div class="other">
|
||||
{{ 'app.login.sign-in-with' | i18n }}
|
||||
<!-- <a class="register" routerLink="/passport/register">{{ 'app.login.signup' | i18n }}</a> -->
|
||||
</div>
|
@ -0,0 +1,63 @@
|
||||
@import '@delon/theme/index';
|
||||
|
||||
:host {
|
||||
display: block;
|
||||
width: 368px;
|
||||
margin: 0 auto;
|
||||
|
||||
::ng-deep {
|
||||
.ant-tabs .ant-tabs-bar {
|
||||
margin-bottom: 24px;
|
||||
text-align: center;
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.ant-tabs-tab {
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.ant-input-affix-wrapper .ant-input:not(:first-child) {
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
||||
.icon {
|
||||
margin-left: 16px;
|
||||
color: rgb(0 0 0 / 20%);
|
||||
font-size: 24px;
|
||||
vertical-align: middle;
|
||||
cursor: pointer;
|
||||
transition: color 0.3s;
|
||||
|
||||
&:hover {
|
||||
color: @primary-color;
|
||||
}
|
||||
}
|
||||
|
||||
.other {
|
||||
margin-top: 24px;
|
||||
line-height: 22px;
|
||||
text-align: left;
|
||||
|
||||
nz-tooltip {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.register {
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[data-theme='dark'] {
|
||||
:host ::ng-deep {
|
||||
.icon {
|
||||
color: rgb(255 255 255 / 20%);
|
||||
|
||||
&:hover {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,196 @@
|
||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Inject, OnDestroy, Optional } from '@angular/core';
|
||||
import { AbstractControl, FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { Router } from '@angular/router';
|
||||
import { StartupService } from '@core';
|
||||
import { ReuseTabService } from '@delon/abc/reuse-tab';
|
||||
import { DA_SERVICE_TOKEN, ITokenService, SocialOpenType, SocialService } from '@delon/auth';
|
||||
import { SettingsService, _HttpClient } from '@delon/theme';
|
||||
import { environment } from '@env/environment';
|
||||
import { NzTabChangeEvent } from 'ng-zorro-antd/tabs';
|
||||
import { finalize } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'passport-login',
|
||||
templateUrl: './login.component.html',
|
||||
styleUrls: ['./login.component.less'],
|
||||
providers: [SocialService],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class UserLoginComponent implements OnDestroy {
|
||||
constructor(
|
||||
fb: FormBuilder,
|
||||
private router: Router,
|
||||
private settingsService: SettingsService,
|
||||
private socialService: SocialService,
|
||||
@Optional()
|
||||
@Inject(ReuseTabService)
|
||||
private reuseTabService: ReuseTabService,
|
||||
@Inject(DA_SERVICE_TOKEN) private tokenService: ITokenService,
|
||||
private startupSrv: StartupService,
|
||||
private http: _HttpClient,
|
||||
private cdr: ChangeDetectorRef
|
||||
) {
|
||||
this.form = fb.group({
|
||||
userName: [null, [Validators.required, Validators.pattern(/^(admin)$/)]],
|
||||
password: [null, [Validators.required, Validators.pattern(/^(000000)$/)]],
|
||||
mobile: [null, [Validators.required, Validators.pattern(/^1\d{10}$/)]],
|
||||
captcha: [null, [Validators.required]],
|
||||
remember: [true]
|
||||
});
|
||||
}
|
||||
|
||||
// #region fields
|
||||
|
||||
get userName(): AbstractControl {
|
||||
return this.form.get('userName')!;
|
||||
}
|
||||
get password(): AbstractControl {
|
||||
return this.form.get('password')!;
|
||||
}
|
||||
get mobile(): AbstractControl {
|
||||
return this.form.get('mobile')!;
|
||||
}
|
||||
get captcha(): AbstractControl {
|
||||
return this.form.get('captcha')!;
|
||||
}
|
||||
form: FormGroup;
|
||||
error = '';
|
||||
type = 0;
|
||||
loading = false;
|
||||
|
||||
// #region get captcha
|
||||
|
||||
count = 0;
|
||||
interval$: any;
|
||||
|
||||
// #endregion
|
||||
|
||||
switch({ index }: NzTabChangeEvent): void {
|
||||
this.type = index!;
|
||||
}
|
||||
|
||||
getCaptcha(): void {
|
||||
if (this.mobile.invalid) {
|
||||
this.mobile.markAsDirty({ onlySelf: true });
|
||||
this.mobile.updateValueAndValidity({ onlySelf: true });
|
||||
return;
|
||||
}
|
||||
this.count = 59;
|
||||
this.interval$ = setInterval(() => {
|
||||
this.count -= 1;
|
||||
if (this.count <= 0) {
|
||||
clearInterval(this.interval$);
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
// #endregion
|
||||
|
||||
submit(): void {
|
||||
this.error = '';
|
||||
if (this.type === 0) {
|
||||
this.userName.markAsDirty();
|
||||
this.userName.updateValueAndValidity();
|
||||
this.password.markAsDirty();
|
||||
this.password.updateValueAndValidity();
|
||||
if (this.userName.invalid || this.password.invalid) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
this.mobile.markAsDirty();
|
||||
this.mobile.updateValueAndValidity();
|
||||
this.captcha.markAsDirty();
|
||||
this.captcha.updateValueAndValidity();
|
||||
if (this.mobile.invalid || this.captcha.invalid) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 默认配置中对所有HTTP请求都会强制 [校验](https://ng-alain.com/auth/getting-started) 用户 Token
|
||||
// 然一般来说登录请求不需要校验,因此可以在请求URL加上:`/login?_allow_anonymous=true` 表示不触发用户 Token 校验
|
||||
this.loading = true;
|
||||
this.cdr.detectChanges();
|
||||
this.http
|
||||
.post('/login/account?_allow_anonymous=true', {
|
||||
type: this.type,
|
||||
userName: this.userName.value,
|
||||
password: this.password.value
|
||||
})
|
||||
.pipe(
|
||||
finalize(() => {
|
||||
this.loading = true;
|
||||
this.cdr.detectChanges();
|
||||
})
|
||||
)
|
||||
.subscribe(res => {
|
||||
if (res.msg !== 'ok') {
|
||||
this.error = res.msg;
|
||||
this.cdr.detectChanges();
|
||||
return;
|
||||
}
|
||||
// 清空路由复用信息
|
||||
this.reuseTabService.clear();
|
||||
// 设置用户Token信息
|
||||
// TODO: Mock expired value
|
||||
res.user.expired = +new Date() + 1000 * 60 * 5;
|
||||
this.tokenService.set(res.user);
|
||||
// 重新获取 StartupService 内容,我们始终认为应用信息一般都会受当前用户授权范围而影响
|
||||
this.startupSrv.load().subscribe(() => {
|
||||
let url = this.tokenService.referrer!.url || '/';
|
||||
if (url.includes('/passport')) {
|
||||
url = '/';
|
||||
}
|
||||
this.router.navigateByUrl(url);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// #region social
|
||||
|
||||
open(type: string, openType: SocialOpenType = 'href'): void {
|
||||
let url = ``;
|
||||
let callback = ``;
|
||||
if (environment.production) {
|
||||
callback = `https://ng-alain.github.io/ng-alain/#/passport/callback/${type}`;
|
||||
} else {
|
||||
callback = `http://localhost:4200/#/passport/callback/${type}`;
|
||||
}
|
||||
switch (type) {
|
||||
case 'auth0':
|
||||
url = `//cipchk.auth0.com/login?client=8gcNydIDzGBYxzqV0Vm1CX_RXH-wsWo5&redirect_uri=${decodeURIComponent(callback)}`;
|
||||
break;
|
||||
case 'github':
|
||||
url = `//github.com/login/oauth/authorize?client_id=9d6baae4b04a23fcafa2&response_type=code&redirect_uri=${decodeURIComponent(
|
||||
callback
|
||||
)}`;
|
||||
break;
|
||||
case 'weibo':
|
||||
url = `https://api.weibo.com/oauth2/authorize?client_id=1239507802&response_type=code&redirect_uri=${decodeURIComponent(callback)}`;
|
||||
break;
|
||||
}
|
||||
if (openType === 'window') {
|
||||
this.socialService
|
||||
.login(url, '/', {
|
||||
type: 'window'
|
||||
})
|
||||
.subscribe(res => {
|
||||
if (res) {
|
||||
this.settingsService.setUser(res);
|
||||
this.router.navigateByUrl('/');
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.socialService.login(url, '/', {
|
||||
type: 'href'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// #endregion
|
||||
|
||||
ngOnDestroy(): void {
|
||||
if (this.interval$) {
|
||||
clearInterval(this.interval$);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
<result type="success" [title]="title" description="{{ 'app.register-result.activation-email' | i18n }}">
|
||||
<ng-template #title>
|
||||
<div class="title" style="font-size: 20px">
|
||||
{{ 'app.register-result.msg' | i18n: params }}
|
||||
</div>
|
||||
</ng-template>
|
||||
<button (click)="msg.success('email')" nz-button nzSize="large" [nzType]="'primary'">
|
||||
{{ 'app.register-result.view-mailbox' | i18n }}
|
||||
</button>
|
||||
<button routerLink="/" nz-button nzSize="large">
|
||||
{{ 'app.register-result.back-home' | i18n }}
|
||||
</button>
|
||||
</result>
|
@ -0,0 +1,15 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { NzMessageService } from 'ng-zorro-antd/message';
|
||||
|
||||
@Component({
|
||||
selector: 'passport-register-result',
|
||||
templateUrl: './register-result.component.html'
|
||||
})
|
||||
export class UserRegisterResultComponent {
|
||||
params = { email: '' };
|
||||
email = '';
|
||||
constructor(route: ActivatedRoute, public msg: NzMessageService) {
|
||||
this.params.email = this.email = route.snapshot.queryParams['email'] || 'ng-alain@example.com';
|
||||
}
|
||||
}
|
@ -0,0 +1,100 @@
|
||||
<h3>{{ 'app.register.register' | i18n }}</h3>
|
||||
<form nz-form [formGroup]="form" (ngSubmit)="submit()" role="form">
|
||||
<nz-alert *ngIf="error" [nzType]="'error'" [nzMessage]="error" [nzShowIcon]="true" class="mb-lg"></nz-alert>
|
||||
<nz-form-item>
|
||||
<nz-form-control [nzErrorTip]="mailErrorTip">
|
||||
<nz-input-group nzSize="large" nzAddonBeforeIcon="user">
|
||||
<input nz-input formControlName="mail" placeholder="Email" />
|
||||
</nz-input-group>
|
||||
<ng-template #mailErrorTip let-i>
|
||||
<ng-container *ngIf="i.errors?.required">{{ 'validation.email.required' | i18n }}</ng-container>
|
||||
<ng-container *ngIf="i.errors?.email">{{ 'validation.email.wrong-format' | i18n }}</ng-container>
|
||||
</ng-template>
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
<nz-form-item>
|
||||
<nz-form-control [nzErrorTip]="'validation.password.required' | i18n">
|
||||
<nz-input-group
|
||||
nzSize="large"
|
||||
nzAddonBeforeIcon="lock"
|
||||
nz-popover
|
||||
nzPopoverPlacement="right"
|
||||
nzPopoverTrigger="focus"
|
||||
[(nzPopoverVisible)]="visible"
|
||||
nzPopoverOverlayClassName="register-password-cdk"
|
||||
[nzPopoverOverlayStyle]="{ 'width.px': 240 }"
|
||||
[nzPopoverContent]="pwdCdkTpl"
|
||||
>
|
||||
<input nz-input type="password" formControlName="password" placeholder="Password" />
|
||||
</nz-input-group>
|
||||
<ng-template #pwdCdkTpl>
|
||||
<div style="padding: 4px 0">
|
||||
<ng-container [ngSwitch]="status">
|
||||
<div *ngSwitchCase="'ok'" class="success">{{ 'validation.password.strength.strong' | i18n }}</div>
|
||||
<div *ngSwitchCase="'pass'" class="warning">{{ 'validation.password.strength.medium' | i18n }}</div>
|
||||
<div *ngSwitchDefault class="error">{{ 'validation.password.strength.short' | i18n }}</div>
|
||||
</ng-container>
|
||||
<div class="progress-{{ status }}">
|
||||
<nz-progress
|
||||
[nzPercent]="progress"
|
||||
[nzStatus]="passwordProgressMap[status]"
|
||||
[nzStrokeWidth]="6"
|
||||
[nzShowInfo]="false"
|
||||
></nz-progress>
|
||||
</div>
|
||||
<p class="mt-sm">{{ 'validation.password.strength.msg' | i18n }}</p>
|
||||
</div>
|
||||
</ng-template>
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
<nz-form-item>
|
||||
<nz-form-control [nzErrorTip]="confirmErrorTip">
|
||||
<nz-input-group nzSize="large" nzAddonBeforeIcon="lock">
|
||||
<input nz-input type="password" formControlName="confirm" placeholder="Confirm Password" />
|
||||
</nz-input-group>
|
||||
<ng-template #confirmErrorTip let-i>
|
||||
<ng-container *ngIf="i.errors?.required">{{ 'validation.confirm-password.required' | i18n }}</ng-container>
|
||||
<ng-container *ngIf="i.errors?.matchControl">{{ 'validation.password.twice' | i18n }}</ng-container>
|
||||
</ng-template>
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
<nz-form-item>
|
||||
<nz-form-control [nzErrorTip]="mobileErrorTip">
|
||||
<nz-input-group nzSize="large" [nzAddOnBefore]="addOnBeforeTemplate">
|
||||
<ng-template #addOnBeforeTemplate>
|
||||
<nz-select formControlName="mobilePrefix" style="width: 100px">
|
||||
<nz-option [nzLabel]="'+86'" [nzValue]="'+86'"></nz-option>
|
||||
<nz-option [nzLabel]="'+87'" [nzValue]="'+87'"></nz-option>
|
||||
</nz-select>
|
||||
</ng-template>
|
||||
<input formControlName="mobile" nz-input placeholder="Phone number" />
|
||||
</nz-input-group>
|
||||
<ng-template #mobileErrorTip let-i>
|
||||
<ng-container *ngIf="i.errors?.required">{{ 'validation.phone-number.required' | i18n }}</ng-container>
|
||||
<ng-container *ngIf="i.errors?.pattern">{{ 'validation.phone-number.wrong-format' | i18n }}</ng-container>
|
||||
</ng-template>
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
<nz-form-item>
|
||||
<nz-form-control [nzErrorTip]="'validation.verification-code.required' | i18n">
|
||||
<nz-row [nzGutter]="8">
|
||||
<nz-col [nzSpan]="16">
|
||||
<nz-input-group nzSize="large" nzAddonBeforeIcon="mail">
|
||||
<input nz-input formControlName="captcha" placeholder="Captcha" />
|
||||
</nz-input-group>
|
||||
</nz-col>
|
||||
<nz-col [nzSpan]="8">
|
||||
<button type="button" nz-button nzSize="large" (click)="getCaptcha()" [disabled]="count > 0" nzBlock [nzLoading]="loading">
|
||||
{{ count ? count + 's' : ('app.register.get-verification-code' | i18n) }}
|
||||
</button>
|
||||
</nz-col>
|
||||
</nz-row>
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
<nz-form-item>
|
||||
<button nz-button nzType="primary" nzSize="large" type="submit" [nzLoading]="loading" class="submit">
|
||||
{{ 'app.register.register' | i18n }}
|
||||
</button>
|
||||
<a class="login" routerLink="/passport/login">{{ 'app.register.sign-in' | i18n }}</a>
|
||||
</nz-form-item>
|
||||
</form>
|
@ -0,0 +1,51 @@
|
||||
@import '@delon/theme/index';
|
||||
|
||||
:host {
|
||||
display: block;
|
||||
width: 368px;
|
||||
margin: 0 auto;
|
||||
|
||||
::ng-deep {
|
||||
h3 {
|
||||
margin-bottom: 20px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.submit {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.login {
|
||||
float: right;
|
||||
line-height: @btn-height-lg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
::ng-deep {
|
||||
.register-password-cdk {
|
||||
.success,
|
||||
.warning,
|
||||
.error {
|
||||
transition: color 0.3s;
|
||||
}
|
||||
|
||||
.success {
|
||||
color: @success-color;
|
||||
}
|
||||
|
||||
.warning {
|
||||
color: @warning-color;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: @error-color;
|
||||
}
|
||||
|
||||
.progress-pass > .progress {
|
||||
.ant-progress-bg {
|
||||
background-color: @warning-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,139 @@
|
||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy } from '@angular/core';
|
||||
import { AbstractControl, FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms';
|
||||
import { Router } from '@angular/router';
|
||||
import { _HttpClient } from '@delon/theme';
|
||||
import { MatchControl } from '@delon/util/form';
|
||||
import { NzSafeAny } from 'ng-zorro-antd/core/types';
|
||||
import { finalize } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'passport-register',
|
||||
templateUrl: './register.component.html',
|
||||
styleUrls: ['./register.component.less'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class UserRegisterComponent implements OnDestroy {
|
||||
constructor(fb: FormBuilder, private router: Router, private http: _HttpClient, private cdr: ChangeDetectorRef) {
|
||||
this.form = fb.group(
|
||||
{
|
||||
mail: [null, [Validators.required, Validators.email]],
|
||||
password: [null, [Validators.required, Validators.minLength(6), UserRegisterComponent.checkPassword.bind(this)]],
|
||||
confirm: [null, [Validators.required, Validators.minLength(6)]],
|
||||
mobilePrefix: ['+86'],
|
||||
mobile: [null, [Validators.required, Validators.pattern(/^1\d{10}$/)]],
|
||||
captcha: [null, [Validators.required]]
|
||||
},
|
||||
{
|
||||
validators: MatchControl('password', 'confirm')
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// #region fields
|
||||
|
||||
get mail(): AbstractControl {
|
||||
return this.form.get('mail')!;
|
||||
}
|
||||
get password(): AbstractControl {
|
||||
return this.form.get('password')!;
|
||||
}
|
||||
get confirm(): AbstractControl {
|
||||
return this.form.get('confirm')!;
|
||||
}
|
||||
get mobile(): AbstractControl {
|
||||
return this.form.get('mobile')!;
|
||||
}
|
||||
get captcha(): AbstractControl {
|
||||
return this.form.get('captcha')!;
|
||||
}
|
||||
form: FormGroup;
|
||||
error = '';
|
||||
type = 0;
|
||||
loading = false;
|
||||
visible = false;
|
||||
status = 'pool';
|
||||
progress = 0;
|
||||
passwordProgressMap: { [key: string]: 'success' | 'normal' | 'exception' } = {
|
||||
ok: 'success',
|
||||
pass: 'normal',
|
||||
pool: 'exception'
|
||||
};
|
||||
|
||||
// #endregion
|
||||
|
||||
// #region get captcha
|
||||
|
||||
count = 0;
|
||||
interval$: any;
|
||||
|
||||
static checkPassword(control: FormControl): NzSafeAny {
|
||||
if (!control) {
|
||||
return null;
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
||||
const self: any = this;
|
||||
self.visible = !!control.value;
|
||||
if (control.value && control.value.length > 9) {
|
||||
self.status = 'ok';
|
||||
} else if (control.value && control.value.length > 5) {
|
||||
self.status = 'pass';
|
||||
} else {
|
||||
self.status = 'pool';
|
||||
}
|
||||
|
||||
if (self.visible) {
|
||||
self.progress = control.value.length * 10 > 100 ? 100 : control.value.length * 10;
|
||||
}
|
||||
}
|
||||
|
||||
getCaptcha(): void {
|
||||
if (this.mobile.invalid) {
|
||||
this.mobile.markAsDirty({ onlySelf: true });
|
||||
this.mobile.updateValueAndValidity({ onlySelf: true });
|
||||
return;
|
||||
}
|
||||
this.count = 59;
|
||||
this.cdr.detectChanges();
|
||||
this.interval$ = setInterval(() => {
|
||||
this.count -= 1;
|
||||
this.cdr.detectChanges();
|
||||
if (this.count <= 0) {
|
||||
clearInterval(this.interval$);
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
// #endregion
|
||||
|
||||
submit(): void {
|
||||
this.error = '';
|
||||
Object.keys(this.form.controls).forEach(key => {
|
||||
this.form.controls[key].markAsDirty();
|
||||
this.form.controls[key].updateValueAndValidity();
|
||||
});
|
||||
if (this.form.invalid) {
|
||||
return;
|
||||
}
|
||||
|
||||
const data = this.form.value;
|
||||
this.loading = true;
|
||||
this.cdr.detectChanges();
|
||||
this.http
|
||||
.post('/register?_allow_anonymous=true', data)
|
||||
.pipe(
|
||||
finalize(() => {
|
||||
this.loading = false;
|
||||
this.cdr.detectChanges();
|
||||
})
|
||||
)
|
||||
.subscribe(() => {
|
||||
this.router.navigate(['passport', 'register-result'], { queryParams: { email: data.mail } });
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
if (this.interval$) {
|
||||
clearInterval(this.interval$);
|
||||
}
|
||||
}
|
||||
}
|
67
IoTGateway/ClientApp/src/app/routes/routes-routing.module.ts
Normal file
67
IoTGateway/ClientApp/src/app/routes/routes-routing.module.ts
Normal file
@ -0,0 +1,67 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { SimpleGuard } from '@delon/auth';
|
||||
import { environment } from '@env/environment';
|
||||
|
||||
// layout
|
||||
import { LayoutBasicComponent } from '../layout/basic/basic.component';
|
||||
import { LayoutPassportComponent } from '../layout/passport/passport.component';
|
||||
// dashboard pages
|
||||
import { DashboardComponent } from './dashboard/dashboard.component';
|
||||
// single pages
|
||||
import { CallbackComponent } from './passport/callback.component';
|
||||
import { UserLockComponent } from './passport/lock/lock.component';
|
||||
// passport pages
|
||||
import { UserLoginComponent } from './passport/login/login.component';
|
||||
import { UserRegisterResultComponent } from './passport/register-result/register-result.component';
|
||||
import { UserRegisterComponent } from './passport/register/register.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: LayoutBasicComponent,
|
||||
canActivate: [SimpleGuard],
|
||||
children: [
|
||||
{ path: '', redirectTo: 'dashboard', pathMatch: 'full' },
|
||||
{ path: 'dashboard', component: DashboardComponent, data: { title: '仪表盘' } },
|
||||
{ path: 'exception', loadChildren: () => import('./exception/exception.module').then(m => m.ExceptionModule) },
|
||||
// 业务子模块
|
||||
// { path: 'widgets', loadChildren: () => import('./widgets/widgets.module').then(m => m.WidgetsModule) },
|
||||
{ path: 'sys', loadChildren: () => import('./sys/sys.module').then(m => m.SysModule) }
|
||||
]
|
||||
},
|
||||
// 空白布局
|
||||
// {
|
||||
// path: 'blank',
|
||||
// component: LayoutBlankComponent,
|
||||
// children: [
|
||||
// ]
|
||||
// },
|
||||
// passport
|
||||
{
|
||||
path: 'passport',
|
||||
component: LayoutPassportComponent,
|
||||
children: [
|
||||
{ path: 'login', component: UserLoginComponent, data: { title: '登录' } },
|
||||
{ path: 'register', component: UserRegisterComponent, data: { title: '注册' } },
|
||||
{ path: 'register-result', component: UserRegisterResultComponent, data: { title: '注册结果' } },
|
||||
{ path: 'lock', component: UserLockComponent, data: { title: '锁屏' } }
|
||||
]
|
||||
},
|
||||
// 单页不包裹Layout
|
||||
{ path: 'passport/callback/:type', component: CallbackComponent },
|
||||
{ path: '**', redirectTo: 'exception/404' }
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forRoot(routes, {
|
||||
useHash: environment.useHash,
|
||||
// NOTICE: If you use `reuse-tab` component and turn on keepingScroll you can set to `disabled`
|
||||
// Pls refer to https://ng-alain.com/components/reuse-tab
|
||||
scrollPositionRestoration: 'top'
|
||||
})
|
||||
],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class RouteRoutingModule {}
|
30
IoTGateway/ClientApp/src/app/routes/routes.module.ts
Normal file
30
IoTGateway/ClientApp/src/app/routes/routes.module.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import { NgModule, Type } from '@angular/core';
|
||||
import { SharedModule } from '@shared';
|
||||
|
||||
// dashboard pages
|
||||
import { DashboardComponent } from './dashboard/dashboard.component';
|
||||
// single pages
|
||||
import { CallbackComponent } from './passport/callback.component';
|
||||
import { UserLockComponent } from './passport/lock/lock.component';
|
||||
// passport pages
|
||||
import { UserLoginComponent } from './passport/login/login.component';
|
||||
import { UserRegisterResultComponent } from './passport/register-result/register-result.component';
|
||||
import { UserRegisterComponent } from './passport/register/register.component';
|
||||
import { RouteRoutingModule } from './routes-routing.module';
|
||||
|
||||
const COMPONENTS: Array<Type<void>> = [
|
||||
DashboardComponent,
|
||||
// passport pages
|
||||
UserLoginComponent,
|
||||
UserRegisterComponent,
|
||||
UserRegisterResultComponent,
|
||||
// single pages
|
||||
CallbackComponent,
|
||||
UserLockComponent
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [SharedModule, RouteRoutingModule],
|
||||
declarations: COMPONENTS
|
||||
})
|
||||
export class RoutesModule {}
|
@ -0,0 +1,11 @@
|
||||
<page-header [action]="phActionTpl">
|
||||
<ng-template #phActionTpl>
|
||||
<button (click)="add()" nz-button nzType="primary">新建</button>
|
||||
</ng-template>
|
||||
</page-header>
|
||||
<nz-card>
|
||||
<sf mode="search" [schema]="searchSchema" (formSubmit)="st.reset($event)" (formReset)="st.reset($event)"></sf>
|
||||
<st #st [data]="url" [columns]="columns"></st>
|
||||
</nz-card>
|
||||
<tinymce [(ngModel)]="html"></tinymce>
|
||||
<!-- <image-wrapper src="https://os.alipayobjects.com/rmsportal/mgesTPFxodmIwpi.png" desc="示意图"></image-wrapper> -->
|
@ -0,0 +1,24 @@
|
||||
import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { SysLogComponent } from './log.component';
|
||||
|
||||
describe('SysLogComponent', () => {
|
||||
let component: SysLogComponent;
|
||||
let fixture: ComponentFixture<SysLogComponent>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [SysLogComponent]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(SysLogComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
47
IoTGateway/ClientApp/src/app/routes/sys/log/log.component.ts
Normal file
47
IoTGateway/ClientApp/src/app/routes/sys/log/log.component.ts
Normal file
@ -0,0 +1,47 @@
|
||||
import { Component, OnInit, ViewChild } from '@angular/core';
|
||||
import { STColumn, STComponent } from '@delon/abc/st';
|
||||
import { SFSchema } from '@delon/form';
|
||||
import { ModalHelper, _HttpClient } from '@delon/theme';
|
||||
|
||||
@Component({
|
||||
selector: 'app-sys-log',
|
||||
templateUrl: './log.component.html'
|
||||
})
|
||||
export class SysLogComponent implements OnInit {
|
||||
url = `/user`;
|
||||
searchSchema: SFSchema = {
|
||||
properties: {
|
||||
no: {
|
||||
type: 'string',
|
||||
title: '编号'
|
||||
}
|
||||
}
|
||||
};
|
||||
@ViewChild('st') private readonly st!: STComponent;
|
||||
columns: STColumn[] = [
|
||||
{ title: '编号', index: 'no' },
|
||||
{ title: '调用次数', type: 'number', index: 'callNo' },
|
||||
{ title: '头像', type: 'img', width: '50px', index: 'avatar' },
|
||||
{ title: '时间', type: 'date', index: 'updatedAt' },
|
||||
{
|
||||
title: '',
|
||||
buttons: [
|
||||
// { text: '查看', click: (item: any) => `/form/${item.id}` },
|
||||
// { text: '编辑', type: 'static', component: FormEditComponent, click: 'reload' },
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
constructor(private http: _HttpClient, private modal: ModalHelper) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
console.log(this.html);
|
||||
}
|
||||
html: any;
|
||||
add(): void {
|
||||
console.log(this.html);
|
||||
// this.modal
|
||||
// .createStatic(FormEditComponent, { i: { id: 0 } })
|
||||
// .subscribe(() => this.st.reload());
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
|
||||
import { SysLogComponent } from './log/log.component';
|
||||
|
||||
const routes: Routes = [{ path: 'log', component: SysLogComponent }];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class SysRoutingModule {}
|
13
IoTGateway/ClientApp/src/app/routes/sys/sys.module.ts
Normal file
13
IoTGateway/ClientApp/src/app/routes/sys/sys.module.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { NgModule, Type } from '@angular/core';
|
||||
import { SharedModule } from '@shared';
|
||||
|
||||
import { SysLogComponent } from './log/log.component';
|
||||
import { SysRoutingModule } from './sys-routing.module';
|
||||
|
||||
const COMPONENTS: Array<Type<void>> = [SysLogComponent];
|
||||
|
||||
@NgModule({
|
||||
imports: [SharedModule, SysRoutingModule],
|
||||
declarations: COMPONENTS
|
||||
})
|
||||
export class SysModule {}
|
@ -0,0 +1,16 @@
|
||||
:host {
|
||||
width: 200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 20px 8px;
|
||||
text-align: center;
|
||||
background: #f2f4f5;
|
||||
|
||||
::ng-deep {
|
||||
.img {
|
||||
max-width: calc(100% - 32px);
|
||||
margin: 2.4em 1em;
|
||||
vertical-align: middle;
|
||||
box-shadow: 0 8px 20px rgb(143 168 191 / 35%);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
/* eslint-disable prettier/prettier */
|
||||
import { Component, Input } from '@angular/core';
|
||||
@Component({
|
||||
selector: 'image-wrapper',
|
||||
template: `
|
||||
<div [ngStyle]="style">
|
||||
<img class="img" [src]="src" [alt]="desc" />
|
||||
<div *ngIf="desc" class="desc">{{ desc }}</div>
|
||||
</div>
|
||||
`,
|
||||
styleUrls: ['./index.less']
|
||||
})
|
||||
export class ImageWrapperComponent {
|
||||
@Input()
|
||||
style!: { [key: string]: string };
|
||||
@Input() src: string | undefined;
|
||||
@Input() desc: string | undefined;
|
||||
}
|
8
IoTGateway/ClientApp/src/app/shared/index.ts
Normal file
8
IoTGateway/ClientApp/src/app/shared/index.ts
Normal file
@ -0,0 +1,8 @@
|
||||
// Components
|
||||
|
||||
// Utils
|
||||
export * from './utils/yuan';
|
||||
|
||||
// Module
|
||||
export * from './shared.module';
|
||||
export * from './json-schema/json-schema.module';
|
@ -0,0 +1,3 @@
|
||||
# 建议统一在 `widgets` 目录下自定义小部件
|
||||
|
||||
> 注:@delon/form 本身提供 nz-zorro-antd 数据录入组件的全部实现,以及若干第三方组件的代码,可从[widgets-third](https://github.com/ng-alain/delon/tree/master/packages/form/widgets-third)中获取并放置 `widgets` 目录下注册即可。
|
@ -0,0 +1,18 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { DelonFormModule, WidgetRegistry } from '@delon/form';
|
||||
|
||||
import { SharedModule } from '../shared.module';
|
||||
import { TestWidget } from './test/test.widget';
|
||||
|
||||
export const SCHEMA_THIRDS_COMPONENTS = [TestWidget];
|
||||
|
||||
@NgModule({
|
||||
declarations: SCHEMA_THIRDS_COMPONENTS,
|
||||
imports: [SharedModule, DelonFormModule.forRoot()],
|
||||
exports: SCHEMA_THIRDS_COMPONENTS
|
||||
})
|
||||
export class JsonSchemaModule {
|
||||
constructor(widgetRegistry: WidgetRegistry) {
|
||||
widgetRegistry.register(TestWidget.KEY, TestWidget);
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
||||
import { ControlWidget } from '@delon/form';
|
||||
|
||||
@Component({
|
||||
selector: 'test',
|
||||
template: `
|
||||
<sf-item-wrap [id]="id" [schema]="schema" [ui]="ui" [showError]="showError" [error]="error" [showTitle]="schema.title">
|
||||
test widget
|
||||
</sf-item-wrap>
|
||||
`,
|
||||
preserveWhitespaces: false,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class TestWidget extends ControlWidget implements OnInit {
|
||||
static readonly KEY = 'test';
|
||||
|
||||
ngOnInit(): void {
|
||||
console.warn('init test widget');
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
import { PageHeaderModule } from '@delon/abc/page-header';
|
||||
import { ResultModule } from '@delon/abc/result';
|
||||
import { SEModule } from '@delon/abc/se';
|
||||
import { STModule } from '@delon/abc/st';
|
||||
import { SVModule } from '@delon/abc/sv';
|
||||
|
||||
export const SHARED_DELON_MODULES = [PageHeaderModule, STModule, SEModule, SVModule, ResultModule];
|
45
IoTGateway/ClientApp/src/app/shared/shared-zorro.module.ts
Normal file
45
IoTGateway/ClientApp/src/app/shared/shared-zorro.module.ts
Normal file
@ -0,0 +1,45 @@
|
||||
import { NzAlertModule } from 'ng-zorro-antd/alert';
|
||||
import { NzAvatarModule } from 'ng-zorro-antd/avatar';
|
||||
import { NzButtonModule } from 'ng-zorro-antd/button';
|
||||
import { NzCardModule } from 'ng-zorro-antd/card';
|
||||
import { NzCheckboxModule } from 'ng-zorro-antd/checkbox';
|
||||
import { NzDrawerModule } from 'ng-zorro-antd/drawer';
|
||||
import { NzDropDownModule } from 'ng-zorro-antd/dropdown';
|
||||
import { NzFormModule } from 'ng-zorro-antd/form';
|
||||
import { NzGridModule } from 'ng-zorro-antd/grid';
|
||||
import { NzIconModule } from 'ng-zorro-antd/icon';
|
||||
import { NzInputModule } from 'ng-zorro-antd/input';
|
||||
import { NzInputNumberModule } from 'ng-zorro-antd/input-number';
|
||||
import { NzModalModule } from 'ng-zorro-antd/modal';
|
||||
import { NzPopconfirmModule } from 'ng-zorro-antd/popconfirm';
|
||||
import { NzPopoverModule } from 'ng-zorro-antd/popover';
|
||||
import { NzProgressModule } from 'ng-zorro-antd/progress';
|
||||
import { NzSelectModule } from 'ng-zorro-antd/select';
|
||||
import { NzSpinModule } from 'ng-zorro-antd/spin';
|
||||
import { NzTableModule } from 'ng-zorro-antd/table';
|
||||
import { NzTabsModule } from 'ng-zorro-antd/tabs';
|
||||
import { NzToolTipModule } from 'ng-zorro-antd/tooltip';
|
||||
|
||||
export const SHARED_ZORRO_MODULES = [
|
||||
NzFormModule,
|
||||
NzGridModule,
|
||||
NzButtonModule,
|
||||
NzInputModule,
|
||||
NzInputNumberModule,
|
||||
NzAlertModule,
|
||||
NzProgressModule,
|
||||
NzSelectModule,
|
||||
NzAvatarModule,
|
||||
NzCardModule,
|
||||
NzDropDownModule,
|
||||
NzPopconfirmModule,
|
||||
NzTableModule,
|
||||
NzPopoverModule,
|
||||
NzDrawerModule,
|
||||
NzModalModule,
|
||||
NzTabsModule,
|
||||
NzToolTipModule,
|
||||
NzIconModule,
|
||||
NzCheckboxModule,
|
||||
NzSpinModule
|
||||
];
|
63
IoTGateway/ClientApp/src/app/shared/shared.module.ts
Normal file
63
IoTGateway/ClientApp/src/app/shared/shared.module.ts
Normal file
@ -0,0 +1,63 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule, Type } from '@angular/core';
|
||||
import { ReactiveFormsModule, FormsModule } from '@angular/forms';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { DelonACLModule } from '@delon/acl';
|
||||
import { DelonFormModule } from '@delon/form';
|
||||
import { AlainThemeModule } from '@delon/theme';
|
||||
import { NgxTinymceModule } from 'ngx-tinymce';
|
||||
|
||||
import { ImageWrapperComponent } from './components/image-wrapper';
|
||||
import { SHARED_DELON_MODULES } from './shared-delon.module';
|
||||
import { SHARED_ZORRO_MODULES } from './shared-zorro.module';
|
||||
|
||||
// #region third libs
|
||||
|
||||
const THIRDMODULES: Array<Type<void>> = [NgxTinymceModule];
|
||||
|
||||
// #endregion
|
||||
|
||||
// #region your componets & directives
|
||||
|
||||
const COMPONENTS: Array<Type<void>> = [ImageWrapperComponent];
|
||||
const DIRECTIVES: Array<Type<void>> = [];
|
||||
|
||||
// #endregion
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
RouterModule,
|
||||
ReactiveFormsModule,
|
||||
AlainThemeModule.forChild(),
|
||||
DelonACLModule,
|
||||
DelonFormModule,
|
||||
...SHARED_DELON_MODULES,
|
||||
...SHARED_ZORRO_MODULES,
|
||||
// third libs
|
||||
...THIRDMODULES
|
||||
],
|
||||
declarations: [
|
||||
// your components
|
||||
...COMPONENTS,
|
||||
...DIRECTIVES
|
||||
],
|
||||
exports: [
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
RouterModule,
|
||||
AlainThemeModule,
|
||||
DelonACLModule,
|
||||
DelonFormModule,
|
||||
...SHARED_DELON_MODULES,
|
||||
...SHARED_ZORRO_MODULES,
|
||||
// third libs
|
||||
...THIRDMODULES,
|
||||
// your components
|
||||
...COMPONENTS,
|
||||
...DIRECTIVES
|
||||
]
|
||||
})
|
||||
export class SharedModule {}
|
@ -0,0 +1,17 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
|
||||
// import { STWidgetRegistry } from '@delon/abc/st';
|
||||
import { SharedModule } from '../shared.module';
|
||||
|
||||
export const STWIDGET_COMPONENTS = [];
|
||||
|
||||
@NgModule({
|
||||
declarations: STWIDGET_COMPONENTS,
|
||||
imports: [SharedModule],
|
||||
exports: [...STWIDGET_COMPONENTS]
|
||||
})
|
||||
export class STWidgetModule {
|
||||
// constructor(widgetRegistry: STWidgetRegistry) {
|
||||
// widgetRegistry.register(STImgWidget.KEY, STImgWidget);
|
||||
// }
|
||||
}
|
11
IoTGateway/ClientApp/src/app/shared/utils/yuan.ts
Normal file
11
IoTGateway/ClientApp/src/app/shared/utils/yuan.ts
Normal file
@ -0,0 +1,11 @@
|
||||
/**
|
||||
* 转化成RMB元字符串
|
||||
*
|
||||
* @param digits 当数字类型时,允许指定小数点后数字的个数,默认2位小数
|
||||
*/
|
||||
export function yuan(value: number | string, digits: number = 2): string {
|
||||
if (typeof value === 'number') {
|
||||
value = value.toFixed(digits);
|
||||
}
|
||||
return `¥ ${value}`;
|
||||
}
|
0
IoTGateway/ClientApp/src/assets/.gitkeep
Normal file
0
IoTGateway/ClientApp/src/assets/.gitkeep
Normal file
3461
IoTGateway/ClientApp/src/assets/color.less
Normal file
3461
IoTGateway/ClientApp/src/assets/color.less
Normal file
File diff suppressed because it is too large
Load Diff
25
IoTGateway/ClientApp/src/assets/logo-color.svg
Normal file
25
IoTGateway/ClientApp/src/assets/logo-color.svg
Normal file
@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 15.0.0, SVG Export Plug-In -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
|
||||
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
|
||||
]>
|
||||
<svg version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
|
||||
x="0px" y="0px" width="222px" height="222px" viewBox="-4.092 0 222 222" enable-background="new -4.092 0 222 222"
|
||||
xml:space="preserve">
|
||||
<defs>
|
||||
</defs>
|
||||
<path fill="#F0776F" d="M195.333,129.506c-0.446,0-0.893,0-1.488-0.148c-6.399-0.744-11.013-6.548-10.269-12.947l6.994-57.891
|
||||
c0.447-3.721-1.785-7.293-5.208-8.483l-47.176-16.816c-6.103-2.232-9.228-8.78-7.144-14.882c2.232-6.102,8.78-9.227,14.882-7.144
|
||||
l47.176,16.816c13.989,4.911,22.472,18.603,20.687,33.336l-6.995,57.891C206.197,125.042,201.137,129.506,195.333,129.506z"/>
|
||||
<path fill="#F0776F" d="M104.851,222.222c-5.209,0-10.417-1.34-15.329-4.019l-61.016-33.931
|
||||
c-8.781-4.911-14.733-13.691-15.924-23.663L0.23,60.007c-1.786-14.733,6.995-28.723,20.983-33.634L96.665,0.627
|
||||
c6.102-2.083,12.799,1.19,14.882,7.292c2.084,6.103-1.19,12.799-7.292,14.883L28.803,48.547c-3.572,1.191-5.804,4.763-5.357,8.632
|
||||
l12.352,100.603c0.298,2.53,1.786,4.762,4.018,6.102l61.016,33.931c2.381,1.34,5.358,1.34,7.739,0l66.076-36.163
|
||||
c2.232-1.19,3.87-3.571,4.167-6.102c0.744-6.399,6.549-11.013,12.948-10.269c6.398,0.744,11.012,6.548,10.269,12.947
|
||||
c-1.191,9.971-7.293,18.9-16.073,23.812l-66.076,36.163C115.268,220.882,110.059,222.222,104.851,222.222z"/>
|
||||
<path fill="#F0776F" d="M157.086,131.441l-37.8-68.309l-0.149-0.149c-2.679-4.613-7.738-7.59-13.096-7.59s-10.417,2.977-13.096,7.59
|
||||
l-37.8,68.458c-2.828,5.208-1.042,11.607,4.167,14.436c5.208,2.827,11.608,1.041,14.436-4.167l7.292-13.245h50.004l7.292,13.245
|
||||
c1.935,3.571,5.506,5.506,9.376,5.506c1.785,0,3.571-0.446,5.06-1.339C157.979,143.049,159.914,136.501,157.086,131.441z
|
||||
M92.796,107.183l13.245-23.96l13.245,23.96H92.796z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 2.0 KiB |
47
IoTGateway/ClientApp/src/assets/logo-full.svg
Normal file
47
IoTGateway/ClientApp/src/assets/logo-full.svg
Normal file
@ -0,0 +1,47 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 15.0.0, SVG Export Plug-In -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
|
||||
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
|
||||
]>
|
||||
<svg version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
|
||||
x="0px" y="0px" width="552px" height="222px" viewBox="-4.092 0 552 222" enable-background="new -4.092 0 552 222"
|
||||
xml:space="preserve">
|
||||
<defs>
|
||||
</defs>
|
||||
<path fill="#FFFFFF" d="M195.333,129.506c-0.446,0-0.893,0-1.488-0.148c-6.399-0.744-11.013-6.548-10.269-12.947l6.994-57.891
|
||||
c0.447-3.721-1.785-7.293-5.208-8.483l-47.176-16.816c-6.103-2.232-9.228-8.78-7.144-14.882c2.232-6.102,8.78-9.227,14.882-7.144
|
||||
l47.176,16.816c13.989,4.911,22.472,18.603,20.687,33.336l-6.995,57.891C206.197,125.042,201.137,129.506,195.333,129.506z"/>
|
||||
<path fill="#FFFFFF" d="M104.851,222.222c-5.209,0-10.417-1.34-15.329-4.019l-61.016-33.931
|
||||
c-8.781-4.911-14.733-13.691-15.924-23.663L0.23,60.007c-1.786-14.733,6.995-28.723,20.983-33.634L96.665,0.627
|
||||
c6.102-2.083,12.799,1.19,14.882,7.292c2.084,6.103-1.19,12.799-7.292,14.883L28.803,48.547c-3.572,1.191-5.804,4.763-5.357,8.632
|
||||
l12.352,100.603c0.298,2.53,1.786,4.762,4.018,6.102l61.016,33.931c2.381,1.34,5.358,1.34,7.739,0l66.076-36.163
|
||||
c2.232-1.19,3.87-3.571,4.167-6.102c0.744-6.399,6.549-11.013,12.948-10.269c6.398,0.744,11.012,6.548,10.269,12.947
|
||||
c-1.191,9.971-7.293,18.9-16.073,23.812l-66.076,36.163C115.268,220.882,110.059,222.222,104.851,222.222z"/>
|
||||
<path fill="#FFFFFF" d="M157.086,131.441l-37.8-68.309l-0.149-0.149c-2.679-4.613-7.738-7.59-13.096-7.59s-10.417,2.977-13.096,7.59
|
||||
l-37.8,68.458c-2.828,5.208-1.042,11.607,4.167,14.436c5.208,2.827,11.608,1.041,14.436-4.167l7.292-13.245h50.004l7.292,13.245
|
||||
c1.935,3.571,5.506,5.506,9.376,5.506c1.785,0,3.571-0.446,5.06-1.339C157.979,143.049,159.914,136.501,157.086,131.441z
|
||||
M92.796,107.183l13.245-23.96l13.245,23.96H92.796z"/>
|
||||
<path fill="#FFFFFF" d="M252.497,113.075c17.46-47.694,32.79-79.42,47.481-97.305c6.813,1.277,21.506,9.794,23.848,13.414
|
||||
c-22.144,26.189-38.113,54.934-52.166,93.898c-11.923,33.216-18.311,60.896-18.311,78.78c0,4.897,0,5.11,0.213,6.175
|
||||
c-3.833-0.426-7.026-2.129-7.878-4.045c-0.64-1.491-3.833-4.897-5.75-6.175c-2.555-1.703-4.471-9.795-4.471-18.95
|
||||
C235.464,166.518,241.639,142.884,252.497,113.075z"/>
|
||||
<path fill="#FFFFFF" d="M374.289,113.926c-11.498,23.847-19.376,48.972-19.376,63.451c0,7.878,1.704,13.414,5.536,15.543
|
||||
c-3.406,2.129-9.581,3.833-13.84,3.833c-7.665,0-11.498-5.11-11.498-15.757c0-7.878,2.769-19.376,8.092-33.216
|
||||
c-11.498,25.977-30.235,47.269-43.437,49.398c-10.433-0.426-17.672-11.072-17.672-25.764c0-31.726,30.66-75.161,53.655-76.439
|
||||
c5.749,1.491,15.331,11.498,15.97,16.821c-22.783,3.62-50.676,41.307-50.676,67.496c0,2.981,1.065,4.472,2.981,5.11
|
||||
c8.304-1.703,18.524-13.627,33.854-39.178c5.962-10.007,14.479-25.977,20.653-38.751
|
||||
C364.495,106.474,374.502,111.158,374.289,113.926z"/>
|
||||
<path fill="#FFFFFF" d="M391.749,137.773c1.277-5.536,7.452-27.68,20.228-34.067c4.685,1.064,14.266,5.749,19.802,14.053
|
||||
c-12.988,14.479-16.82,29.596-24.061,48.333c-2.98,7.878-6.174,17.034-6.387,23.209c0,5.109,0,7.026-1.278,7.026
|
||||
c-2.129,0-15.543-5.536-15.543-18.099C384.51,173.331,386.213,163.324,391.749,137.773z M425.816,81.988
|
||||
c-1.49-1.491-2.555-4.685-2.555-7.026c0-4.897,2.98-18.737,6.601-27.893c6.601,0,21.505,8.517,25.764,14.905
|
||||
c-6.388,5.961-21.932,24.912-24.061,29.596c-3.407-0.639-6.388-3.407-6.388-5.749C425.178,84.969,425.391,83.691,425.816,81.988z"/>
|
||||
<path fill="#FFFFFF" d="M486.712,109.668c-6.388,15.543-28.745,45.778-30.448,58.766c8.943-9.581,23.422-27.467,28.318-32.576
|
||||
c10.859-11.498,30.874-32.577,44.714-32.577c7.239,0,12.35,6.175,15.117,11.498c0,0.639-2.342,0.852-6.175,5.323
|
||||
c-7.665,10.007-22.144,38.113-22.144,55.573c0,7.026,1.916,8.304,5.323,8.304c2.98,0,7.026-2.13,8.942-2.13
|
||||
c1.064,0.64,1.916,2.981,1.916,4.259c-4.258,3.833-13.414,8.729-18.311,8.729c-16.396,0-20.228-10.007-20.228-22.569
|
||||
c0-7.026,1.916-15.756,11.71-38.326c-0.639,0-8.942,4.046-17.246,12.137c-13.201,12.775-23.209,24.486-34.28,39.816
|
||||
c-6.175,8.729-10.859,15.33-14.266,17.247c-5.962-3.407-8.517-10.221-9.795-16.608c0-10.646,6.388-34.493,15.97-56.424
|
||||
c10.858-24.912,20.866-39.816,23.208-40.881C476.491,91.569,483.73,100.299,486.712,109.668z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 4.3 KiB |
BIN
IoTGateway/ClientApp/src/assets/logo.png
Normal file
BIN
IoTGateway/ClientApp/src/assets/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 10 KiB |
25
IoTGateway/ClientApp/src/assets/logo.svg
Normal file
25
IoTGateway/ClientApp/src/assets/logo.svg
Normal file
@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 15.0.0, SVG Export Plug-In -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
|
||||
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
|
||||
]>
|
||||
<svg version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
|
||||
x="0px" y="0px" width="222px" height="222px" viewBox="-4.092 0 222 222" enable-background="new -4.092 0 222 222"
|
||||
xml:space="preserve">
|
||||
<defs>
|
||||
</defs>
|
||||
<path fill="#FFFFFF" d="M195.333,129.506c-0.446,0-0.893,0-1.488-0.148c-6.399-0.744-11.013-6.548-10.269-12.947l6.994-57.891
|
||||
c0.447-3.721-1.785-7.293-5.208-8.483l-47.176-16.816c-6.103-2.232-9.228-8.78-7.144-14.882c2.232-6.102,8.78-9.227,14.882-7.144
|
||||
l47.176,16.816c13.989,4.911,22.472,18.603,20.687,33.336l-6.995,57.891C206.197,125.042,201.137,129.506,195.333,129.506z"/>
|
||||
<path fill="#FFFFFF" d="M104.851,222.222c-5.209,0-10.417-1.34-15.329-4.019l-61.016-33.931
|
||||
c-8.781-4.911-14.733-13.691-15.924-23.663L0.23,60.007c-1.786-14.733,6.995-28.723,20.983-33.634L96.665,0.627
|
||||
c6.102-2.083,12.799,1.19,14.882,7.292c2.084,6.103-1.19,12.799-7.292,14.883L28.803,48.547c-3.572,1.191-5.804,4.763-5.357,8.632
|
||||
l12.352,100.603c0.298,2.53,1.786,4.762,4.018,6.102l61.016,33.931c2.381,1.34,5.358,1.34,7.739,0l66.076-36.163
|
||||
c2.232-1.19,3.87-3.571,4.167-6.102c0.744-6.399,6.549-11.013,12.948-10.269c6.398,0.744,11.012,6.548,10.269,12.947
|
||||
c-1.191,9.971-7.293,18.9-16.073,23.812l-66.076,36.163C115.268,220.882,110.059,222.222,104.851,222.222z"/>
|
||||
<path fill="#FFFFFF" d="M157.086,131.441l-37.8-68.309l-0.149-0.149c-2.679-4.613-7.738-7.59-13.096-7.59s-10.417,2.977-13.096,7.59
|
||||
l-37.8,68.458c-2.828,5.208-1.042,11.607,4.167,14.436c5.208,2.827,11.608,1.041,14.436-4.167l7.292-13.245h50.004l7.292,13.245
|
||||
c1.935,3.571,5.506,5.506,9.376,5.506c1.785,0,3.571-0.446,5.06-1.339C157.979,143.049,159.914,136.501,157.086,131.441z
|
||||
M92.796,107.183l13.245-23.96l13.245,23.96H92.796z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 2.0 KiB |
1
IoTGateway/ClientApp/src/assets/style.compact.css
Normal file
1
IoTGateway/ClientApp/src/assets/style.compact.css
Normal file
File diff suppressed because one or more lines are too long
1
IoTGateway/ClientApp/src/assets/style.dark.css
Normal file
1
IoTGateway/ClientApp/src/assets/style.dark.css
Normal file
File diff suppressed because one or more lines are too long
353
IoTGateway/ClientApp/src/assets/tmp/app-data.json
Normal file
353
IoTGateway/ClientApp/src/assets/tmp/app-data.json
Normal file
@ -0,0 +1,353 @@
|
||||
{
|
||||
"app": {
|
||||
"name": "Alain",
|
||||
"description": "Ng-zorro admin panel front-end framework"
|
||||
},
|
||||
"user": {
|
||||
"name": "Admin",
|
||||
"avatar": "./assets/tmp/img/avatar.jpg",
|
||||
"email": "535915157@qq.com"
|
||||
},
|
||||
"menu": [
|
||||
{
|
||||
"text": "主导航",
|
||||
"i18n": "menu.main",
|
||||
"group": true,
|
||||
"hideInBreadcrumb": true,
|
||||
"children": [
|
||||
{
|
||||
"text": "仪表盘",
|
||||
"i18n": "menu.dashboard",
|
||||
"icon": "anticon-dashboard",
|
||||
"children": [
|
||||
{
|
||||
"text": "仪表盘V1",
|
||||
"link": "/dashboard/v1",
|
||||
"i18n": "menu.dashboard.v1"
|
||||
},
|
||||
{
|
||||
"text": "分析页",
|
||||
"link": "/dashboard/analysis",
|
||||
"i18n": "menu.dashboard.analysis"
|
||||
},
|
||||
{
|
||||
"text": "监控页",
|
||||
"link": "/dashboard/monitor",
|
||||
"i18n": "menu.dashboard.monitor"
|
||||
},
|
||||
{
|
||||
"text": "工作台",
|
||||
"link": "/dashboard/workplace",
|
||||
"i18n": "menu.dashboard.workplace"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"text": "快捷菜单",
|
||||
"i18n": "menu.shortcut",
|
||||
"icon": "anticon-rocket",
|
||||
"shortcutRoot": true,
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"text": "小部件",
|
||||
"i18n": "menu.widgets",
|
||||
"link": "/widgets",
|
||||
"icon": "anticon-appstore",
|
||||
"badge": 2
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"text": "Alain",
|
||||
"i18n": "menu.alain",
|
||||
"group": true,
|
||||
"hideInBreadcrumb": true,
|
||||
"children": [
|
||||
{
|
||||
"text": "样式",
|
||||
"i18n": "menu.style",
|
||||
"icon": "anticon-info",
|
||||
"children": [
|
||||
{
|
||||
"text": "Typography",
|
||||
"link": "/style/typography",
|
||||
"i18n": "menu.style.typography",
|
||||
"shortcut": true
|
||||
},
|
||||
{
|
||||
"text": "Grid Masonry",
|
||||
"link": "/style/gridmasonry",
|
||||
"i18n": "menu.style.gridmasonry"
|
||||
},
|
||||
{
|
||||
"text": "Colors",
|
||||
"link": "/style/colors",
|
||||
"i18n": "menu.style.colors"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"text": "Delon",
|
||||
"i18n": "menu.delon",
|
||||
"icon": "anticon-bulb",
|
||||
"children": [
|
||||
{
|
||||
"text": "Dynamic Form",
|
||||
"link": "/delon/form",
|
||||
"i18n": "menu.delon.form"
|
||||
},
|
||||
{
|
||||
"text": "Simple Table",
|
||||
"link": "/delon/st",
|
||||
"i18n": "menu.delon.table"
|
||||
},
|
||||
{
|
||||
"text": "Util",
|
||||
"link": "/delon/util",
|
||||
"i18n": "menu.delon.util",
|
||||
"acl": "role-a"
|
||||
},
|
||||
{
|
||||
"text": "Print",
|
||||
"link": "/delon/print",
|
||||
"i18n": "menu.delon.print",
|
||||
"acl": "role-b"
|
||||
},
|
||||
{
|
||||
"text": "QR",
|
||||
"link": "/delon/qr",
|
||||
"i18n": "menu.delon.qr"
|
||||
},
|
||||
{
|
||||
"text": "ACL",
|
||||
"link": "/delon/acl",
|
||||
"i18n": "menu.delon.acl"
|
||||
},
|
||||
{
|
||||
"text": "Route Guard",
|
||||
"link": "/delon/guard",
|
||||
"i18n": "menu.delon.guard"
|
||||
},
|
||||
{
|
||||
"text": "Cache",
|
||||
"link": "/delon/cache",
|
||||
"i18n": "menu.delon.cache"
|
||||
},
|
||||
{
|
||||
"text": "Down File",
|
||||
"link": "/delon/downfile",
|
||||
"i18n": "menu.delon.downfile"
|
||||
},
|
||||
{
|
||||
"text": "Xlsx",
|
||||
"link": "/delon/xlsx",
|
||||
"i18n": "menu.delon.xlsx"
|
||||
},
|
||||
{
|
||||
"text": "Zip",
|
||||
"link": "/delon/zip",
|
||||
"i18n": "menu.delon.zip"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"text": "Pro",
|
||||
"i18n": "menu.pro",
|
||||
"group": true,
|
||||
"hideInBreadcrumb": true,
|
||||
"children": [
|
||||
{
|
||||
"text": "Form Page",
|
||||
"i18n": "menu.form",
|
||||
"link": "/pro/form",
|
||||
"icon": "anticon-edit",
|
||||
"children": [
|
||||
{
|
||||
"text": "Basic Form",
|
||||
"link": "/pro/form/basic-form",
|
||||
"i18n": "menu.form.basicform",
|
||||
"shortcut": true
|
||||
},
|
||||
{
|
||||
"text": "Step Form",
|
||||
"link": "/pro/form/step-form",
|
||||
"i18n": "menu.form.stepform"
|
||||
},
|
||||
{
|
||||
"text": "Advanced Form",
|
||||
"link": "/pro/form/advanced-form",
|
||||
"i18n": "menu.form.advancedform"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"text": "List",
|
||||
"i18n": "menu.list",
|
||||
"icon": "anticon-appstore",
|
||||
"children": [
|
||||
{
|
||||
"text": "Table List",
|
||||
"link": "/pro/list/table-list",
|
||||
"i18n": "menu.list.searchtable",
|
||||
"shortcut": true
|
||||
},
|
||||
{
|
||||
"text": "Basic List",
|
||||
"link": "/pro/list/basic-list",
|
||||
"i18n": "menu.list.basiclist"
|
||||
},
|
||||
{
|
||||
"text": "Card List",
|
||||
"link": "/pro/list/card-list",
|
||||
"i18n": "menu.list.cardlist"
|
||||
},
|
||||
{
|
||||
"text": "Search List",
|
||||
"i18n": "menu.list.searchlist",
|
||||
"children": [
|
||||
{
|
||||
"link": "/pro/list/articles",
|
||||
"i18n": "menu.list.searchlist.articles"
|
||||
},
|
||||
{
|
||||
"link": "/pro/list/projects",
|
||||
"i18n": "menu.list.searchlist.projects",
|
||||
"shortcut": true
|
||||
},
|
||||
{
|
||||
"link": "/pro/list/applications",
|
||||
"i18n": "menu.list.searchlist.applications"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"text": "Profile",
|
||||
"i18n": "menu.profile",
|
||||
"icon": "anticon-profile",
|
||||
"children": [
|
||||
{
|
||||
"text": "Basic",
|
||||
"link": "/pro/profile/basic",
|
||||
"i18n": "menu.profile.basic"
|
||||
},
|
||||
{
|
||||
"text": "Advanced",
|
||||
"link": "/pro/profile/advanced",
|
||||
"i18n": "menu.profile.advanced",
|
||||
"shortcut": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"text": "Result",
|
||||
"i18n": "menu.result",
|
||||
"icon": "anticon-check-circle",
|
||||
"children": [
|
||||
{
|
||||
"text": "Success",
|
||||
"link": "/pro/result/success",
|
||||
"i18n": "menu.result.success"
|
||||
},
|
||||
{
|
||||
"text": "Fail",
|
||||
"link": "/pro/result/fail",
|
||||
"i18n": "menu.result.fail"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"text": "Exception",
|
||||
"i18n": "menu.exception",
|
||||
"link": "/",
|
||||
"icon": "anticon-exception",
|
||||
"children": [
|
||||
{
|
||||
"text": "403",
|
||||
"link": "/exception/403",
|
||||
"i18n": "menu.exception.not-permission",
|
||||
"reuse": false
|
||||
},
|
||||
{
|
||||
"text": "404",
|
||||
"link": "/exception/404",
|
||||
"i18n": "menu.exception.not-find",
|
||||
"reuse": false
|
||||
},
|
||||
{
|
||||
"text": "500",
|
||||
"link": "/exception/500",
|
||||
"i18n": "menu.exception.server-error",
|
||||
"reuse": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"text": "Account",
|
||||
"i18n": "menu.account",
|
||||
"icon": "anticon-user",
|
||||
"children": [
|
||||
{
|
||||
"text": "center",
|
||||
"link": "/pro/account/center",
|
||||
"i18n": "menu.account.center"
|
||||
},
|
||||
{
|
||||
"text": "settings",
|
||||
"link": "/pro/account/settings",
|
||||
"i18n": "menu.account.settings"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"text": "More",
|
||||
"i18n": "menu.more",
|
||||
"group": true,
|
||||
"hideInBreadcrumb": true,
|
||||
"children": [
|
||||
{
|
||||
"text": "Report",
|
||||
"i18n": "menu.report",
|
||||
"icon": "anticon-cloud",
|
||||
"children": [
|
||||
{
|
||||
"text": "Relation",
|
||||
"link": "/data-v/relation",
|
||||
"i18n": "menu.report.relation",
|
||||
"reuse": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"text": "Extras",
|
||||
"i18n": "menu.extras",
|
||||
"link": "/extras",
|
||||
"icon": "anticon-link",
|
||||
"children": [
|
||||
{
|
||||
"text": "Help Center",
|
||||
"link": "/extras/helpcenter",
|
||||
"i18n": "menu.extras.helpcenter"
|
||||
},
|
||||
{
|
||||
"text": "Settings",
|
||||
"link": "/extras/settings",
|
||||
"i18n": "menu.extras.settings"
|
||||
},
|
||||
{
|
||||
"text": "Poi",
|
||||
"link": "/extras/poi",
|
||||
"i18n": "menu.extras.poi"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
151
IoTGateway/ClientApp/src/assets/tmp/i18n/el-GR.json
Normal file
151
IoTGateway/ClientApp/src/assets/tmp/i18n/el-GR.json
Normal file
@ -0,0 +1,151 @@
|
||||
{
|
||||
"menu.search.placeholder": "Αναζήτηση ατόμων, αρχείων, φωτογραφιών...",
|
||||
"menu.fullscreen": "Πλήρης οθόνη",
|
||||
"menu.fullscreen.exit": "Έξοδος από πλήρη οθόνη",
|
||||
"menu.clear.local.storage": "Καθαρισμός τοπικής μνήμης",
|
||||
"menu.lang": "Γλώσσα",
|
||||
"menu.main": "Κύριο μενού",
|
||||
"menu.dashboard": "Πίνακας διαχείρισης",
|
||||
"menu.dashboard.v1": "Προεπιλογή",
|
||||
"menu.dashboard.analysis": "Ανάλυση",
|
||||
"menu.dashboard.monitor": "Εποπτεία",
|
||||
"menu.dashboard.workplace": "Χώρος εργασίας",
|
||||
"menu.shortcut": "Συντομεύσεις",
|
||||
"menu.widgets": "Γραφικά στοιχεία",
|
||||
"menu.alain": "Alain",
|
||||
"menu.style": "Στυλ",
|
||||
"menu.style.typography": "Τυπογραφία",
|
||||
"menu.style.gridmasonry": "Πλέγμα Masonry",
|
||||
"menu.style.colors": "Χρώματα",
|
||||
"menu.delon": "Βιβλιοθήκη Delon",
|
||||
"menu.delon.form": "Δυναμική φόρμα",
|
||||
"menu.delon.table": "Απλός πίνακας",
|
||||
"menu.delon.util": "Εργαλεία",
|
||||
"menu.delon.print": "Εκτύπωση",
|
||||
"menu.delon.guard": "Προστασία διαδρομής",
|
||||
"menu.delon.cache": "Προσωρινή μνήμη",
|
||||
"menu.delon.qr": "QR",
|
||||
"menu.delon.acl": "ACL",
|
||||
"menu.delon.downfile": "Λήψη αρχείου",
|
||||
"menu.delon.xlsx": "Excel",
|
||||
"menu.delon.zip": "Zip",
|
||||
"menu.pro": "Antd Pro",
|
||||
"menu.form": "Φόρμα",
|
||||
"menu.form.basicform": "Βασική φόρμα",
|
||||
"menu.form.stepform": "Φόρμα βημάτων",
|
||||
"menu.form.stepform.info": "Φόρμα βημάτων(γράψτε πληροφορίες μεταφοράς)",
|
||||
"menu.form.stepform.confirm": "Φόρμα βημάτων(επιβεβαιώστε τις πληροφορίες μεταφοράς)",
|
||||
"menu.form.stepform.result": "Φόρμα βημάτων(ολοκληρωμένη)",
|
||||
"menu.form.advancedform": "Σύνθετη φόρμα",
|
||||
"menu.list": "Λίστα",
|
||||
"menu.list.searchtable": "Πίνακας αναζήτησης",
|
||||
"menu.list.basiclist": "Βασική λίστα",
|
||||
"menu.list.cardlist": "Λίστα καρτών",
|
||||
"menu.list.searchlist": "Λίστα αναζήτησης",
|
||||
"menu.list.searchlist.articles": "Λίστα αναζήτησης (άρθρα)",
|
||||
"menu.list.searchlist.projects": "Λίστα αναζήτησης (έργα)",
|
||||
"menu.list.searchlist.applications": "Λίστα αναζήτησης (εφαρμογές)",
|
||||
"menu.profile": "Προφίλ",
|
||||
"menu.profile.basic": "Βασικό προφίλ",
|
||||
"menu.profile.advanced": "Σύνθετο προφίλ",
|
||||
"menu.result": "Αποτέλεσμα",
|
||||
"menu.result.success": "Επιτυχία",
|
||||
"menu.result.fail": "Αποτυχία",
|
||||
"menu.exception": "Εξαίρεση",
|
||||
"menu.exception.not-permission": "403",
|
||||
"menu.exception.not-find": "404",
|
||||
"menu.exception.server-error": "500",
|
||||
"menu.account": "Λογαριασμός",
|
||||
"menu.account.center": "Κέντρο διαχείρισης λογαριασμού",
|
||||
"menu.account.settings": "Ρυθμίσεις λογαριασμού",
|
||||
"menu.account.trigger": "Πρόκληση σφάλματος",
|
||||
"menu.account.logout": "Αποσύνδεση",
|
||||
"menu.more": "Περισσότερα",
|
||||
"menu.report": "Αναφορά",
|
||||
"menu.report.relation": "Χάρτης συσχετίσεων",
|
||||
"menu.extras": "Επιπλέον",
|
||||
"menu.extras.helpcenter": "Κέντρο βοήθειας",
|
||||
"menu.extras.settings": "Ρυθμίσεις",
|
||||
"menu.extras.poi": "Poi",
|
||||
"app.analysis.test": "Gongzhuan Αρ.{{no}} κατάστημα",
|
||||
"app.analysis.introduce": "Εισαγωγή",
|
||||
"app.analysis.total-sales": "Σύνολο πωλήσεων",
|
||||
"app.analysis.day-sales": "Ημερήσιες πωλήσεις",
|
||||
"app.analysis.visits": "Επισκέψεις",
|
||||
"app.analysis.visits-trend": "Τάση επισκεψιμότητας",
|
||||
"app.analysis.visits-ranking": "Κατατάξη επισκεψιμότητας",
|
||||
"app.analysis.day-visits": "Ημερήσια επισκεψιμότητα",
|
||||
"app.analysis.week": "Εβδομαδιαία αναλογία",
|
||||
"app.analysis.day": "Ημερήσια αναλογία",
|
||||
"app.analysis.payments": "Πληρωμές",
|
||||
"app.analysis.conversion-rate": "Συναλλαγματική Ισοτιμία",
|
||||
"app.analysis.operational-effect": "Λειτουργική επίδραση",
|
||||
"app.analysis.sales-trend": "Τάση πωλήσεων καταστημάτων",
|
||||
"app.analysis.sales-ranking": "Κατάταξη πωλήσεων",
|
||||
"app.analysis.all-year": "Όλο τον χρόνο",
|
||||
"app.analysis.all-month": "Όλο τον μήνα",
|
||||
"app.analysis.all-week": "Όλη την εβδομάδα",
|
||||
"app.analysis.all-today": "Όλη μέρα",
|
||||
"app.analysis.search-users": "Αναζήτηση χρηστών",
|
||||
"app.analysis.per-capita-search": "Αναζήτηση ανά κεφάλαιο",
|
||||
"app.analysis.online-top-search": "(Ζωντανά) Κορυφαία αναζήτηση",
|
||||
"app.analysis.the-proportion-of-sales": "Το ποσοστό των πωλήσεων",
|
||||
"app.analysis.channel.all": "ΟΛΑ",
|
||||
"app.analysis.channel.online": "Ζωντανά",
|
||||
"app.analysis.channel.stores": "Καταστήματα",
|
||||
"app.analysis.sales": "Πωλήσεις",
|
||||
"app.analysis.traffic": "Κίνηση",
|
||||
"app.analysis.table.rank": "Κατάταξη",
|
||||
"app.analysis.table.search-keyword": "Λέξη κλειδί",
|
||||
"app.analysis.table.users": "Χρήστες",
|
||||
"app.analysis.table.weekly-range": "Εβδομαδιαίο εύρος",
|
||||
"app.monitor.trading-activity": "Δραστηριότητα συναλλαγών σε πραγματικό χρόνο",
|
||||
"app.monitor.total-transactions": "Σύνολο ημερήσιων συναλλαγών",
|
||||
"app.monitor.sales-target": "Ποσοστό ολοκλήρωσης στόχου πωλήσεων",
|
||||
"app.monitor.remaining-time": "Εναπομείναν χρόνος δραστηριότητας",
|
||||
"app.monitor.total-transactions-per-second": "Σύνολο συναλλαγών ανά δευτερόλεπτο",
|
||||
"app.monitor.activity-forecast": "Πρόβλεψη δραστηριότητας",
|
||||
"app.monitor.efficiency": "Αποδοτικότητα",
|
||||
"app.monitor.ratio": "Αναλογία",
|
||||
"app.monitor.proportion-per-category": "Ποσοστό ανά κατηγορία",
|
||||
"app.monitor.fast-food": "Γρήγορο φαγητό",
|
||||
"app.monitor.western-food": "Δυτικό φαγητό",
|
||||
"app.monitor.hot-pot": "Ζεστό φαγητό",
|
||||
"app.monitor.waiting-for-implementation": "Αναμονή για υλοποίηση",
|
||||
"app.monitor.popular-searches": "Δημοφιλείς αναζητήσεις",
|
||||
"app.monitor.resource-surplus": "Πλεόνασμα πόρων",
|
||||
"app.monitor.fund-surplus": "Κεφαλαιακό πλεόνασμα",
|
||||
"app.lock": "Κλείδωμα",
|
||||
"app.login.message-invalid-credentials": "Λάθος όνομα χρήστη ή κωδικός πρόσβασης(admin/ant.design)",
|
||||
"app.login.message-invalid-verification-code": "Μη έγκυρος κωδικός επιβεβαίωσης",
|
||||
"app.login.tab-login-credentials": "Στοιχεία σύνδεσης",
|
||||
"app.login.tab-login-mobile": "Αριθμός κινητού",
|
||||
"app.login.remember-me": "Να με θυμάσαι",
|
||||
"app.login.forgot-password": "Ξέχασα τον κωδικό μου",
|
||||
"app.login.sign-in-with": "Σύνδεση με",
|
||||
"app.login.signup": "Εγγραφή",
|
||||
"app.login.login": "Σύνδεση",
|
||||
"app.register.register": "Εγγραφή",
|
||||
"app.register.get-verification-code": "Λήψη κωδικού",
|
||||
"app.register.sign-in": "Εχετε ήδη λογαριασμό;",
|
||||
"app.register-result.msg": "Λογαριασμός:εγγεγραμμένος ως {{email}}",
|
||||
"app.register-result.activation-email": "Το email ενεργοποίησης έχει σταλεί στο email σας και έχει ισχύ εώς 24 ώρες. Παρακαλούμε συνδεθείτε στο email σας εγκαίρως και κάντε κλικ στο σύνδεσμο του email για να ενεργοποιήσετε το λογαριασμό.",
|
||||
"app.register-result.back-home": "Επιστροφή στην αρχική σελίδα",
|
||||
"app.register-result.view-mailbox": "Προβολή αλληλογραφίας",
|
||||
"validation.email.required": "Παρακαλώ εισάγετε το email σας!",
|
||||
"validation.email.wrong-format": "Η μορφή της διεύθυνση email δεν είναι έγκυρη!",
|
||||
"validation.password.required": "Παρακαλώ εισάγετε τον κωδικό πρόσβασης!",
|
||||
"validation.password.twice": "Οι κωδικοί πρόσβασης που εισαγάγατε δεν ταιριάζουν!",
|
||||
"validation.password.strength.msg": "Παρακαλώ εισάγετε τουλάχιστον 6 χαρακτήρες, μην χρησιμοποιείτε αδύναμους κωδικούς",
|
||||
"validation.password.strength.strong": "Ισχύς κωδικού: ισχυρός",
|
||||
"validation.password.strength.medium": "Ισχύς κωδικού: μέτριος",
|
||||
"validation.password.strength.short": "Ισχύς κωδικού: αδύναμος",
|
||||
"validation.confirm-password.required": "Παρακαλώ επιβεβαιώστε τον κωδικό πρόσβασης!",
|
||||
"validation.phone-number.required": "Παρακαλω εισάγετε τον αριθμό τηλεφώνου σας!",
|
||||
"validation.phone-number.wrong-format": "O αριθμός τηλεφώνου δέν είναι έγκυρος!",
|
||||
"validation.verification-code.required": "Παρακαλώ εισάγετε τον κωδικό επιβεβαίωσης!",
|
||||
"validation.title.required": "Παρακαλώ εισάγετε έναν τίτλο",
|
||||
"validation.date.required": "Παρακαλώ επιλέξτε την ημερομηνία έναρξης και λήξης",
|
||||
"validation.goal.required": "Παρακαλώ εισάγετε την περιγραφή του στόχου",
|
||||
"validation.standard.required": "Παρακαλώ εισάγετε μια μέτρηση"
|
||||
}
|
153
IoTGateway/ClientApp/src/assets/tmp/i18n/en-US.json
Normal file
153
IoTGateway/ClientApp/src/assets/tmp/i18n/en-US.json
Normal file
@ -0,0 +1,153 @@
|
||||
{
|
||||
"menu.search.placeholder": "Search for people, file, photos...",
|
||||
"menu.fullscreen": "Fullscreen",
|
||||
"menu.fullscreen.exit": "Exit Fullscreen",
|
||||
"menu.clear.local.storage": "Clear Local Storage",
|
||||
"menu.lang": "Language",
|
||||
"menu.main": "Main Navigation",
|
||||
"menu.dashboard": "Dashboard",
|
||||
"menu.dashboard.v1": "Default",
|
||||
"menu.dashboard.analysis": "Analysis",
|
||||
"menu.dashboard.monitor": "Monitor",
|
||||
"menu.dashboard.workplace": "Workplace",
|
||||
"menu.shortcut": "Shortcut",
|
||||
"menu.widgets": "Widgets",
|
||||
"menu.alain": "Alain",
|
||||
"menu.style": "Style",
|
||||
"menu.style.typography": "Typography",
|
||||
"menu.style.gridmasonry": "Grid Masonry",
|
||||
"menu.style.colors": "Colors",
|
||||
"menu.delon": "Delon Lib",
|
||||
"menu.delon.form": "Dynamic Form",
|
||||
"menu.delon.table": "Simple table",
|
||||
"menu.delon.util": "Util",
|
||||
"menu.delon.print": "Print",
|
||||
"menu.delon.guard": "Route Guard",
|
||||
"menu.delon.cache": "Cache",
|
||||
"menu.delon.qr": "QR",
|
||||
"menu.delon.acl": "ACL",
|
||||
"menu.delon.downfile": "Download File",
|
||||
"menu.delon.xlsx": "Excel",
|
||||
"menu.delon.zip": "Zip",
|
||||
"menu.pro": "Antd Pro",
|
||||
"menu.form": "Form",
|
||||
"menu.form.basicform": "Basic Form",
|
||||
"menu.form.stepform": "Step Form",
|
||||
"menu.form.stepform.info": "Step Form(write transfer information)",
|
||||
"menu.form.stepform.confirm": "Step Form(confirm transfer information)",
|
||||
"menu.form.stepform.result": "Step Form(finished)",
|
||||
"menu.form.advancedform": "Advanced Form",
|
||||
"menu.list": "List",
|
||||
"menu.list.searchtable": "Search Table",
|
||||
"menu.list.basiclist": "Basic List",
|
||||
"menu.list.cardlist": "Card List",
|
||||
"menu.list.searchlist": "Search List",
|
||||
"menu.list.searchlist.articles": "Search List(articles)",
|
||||
"menu.list.searchlist.projects": "Search List(projects)",
|
||||
"menu.list.searchlist.applications": "Search List(applications)",
|
||||
"menu.profile": "Profile",
|
||||
"menu.profile.basic": "Basic Profile",
|
||||
"menu.profile.advanced": "Advanced Profile",
|
||||
"menu.result": "Result",
|
||||
"menu.result.success": "Success",
|
||||
"menu.result.fail": "Fail",
|
||||
"menu.exception": "Exception",
|
||||
"menu.exception.not-permission": "403",
|
||||
"menu.exception.not-find": "404",
|
||||
"menu.exception.server-error": "500",
|
||||
"menu.account": "Account",
|
||||
"menu.account.center": "Account Center",
|
||||
"menu.account.settings": "Account Settings",
|
||||
"menu.account.trigger": "Trigger Error",
|
||||
"menu.account.logout": "Logout",
|
||||
"menu.more": "More",
|
||||
"menu.report": "Report",
|
||||
"menu.report.relation": "Relation Map",
|
||||
"menu.extras": "Extra",
|
||||
"menu.extras.helpcenter": "Help Center",
|
||||
"menu.extras.settings": "Settings",
|
||||
"menu.extras.poi": "Poi",
|
||||
"app.analysis.test": "Gongzhuan No.{{no}} shop",
|
||||
"app.analysis.introduce": "Introduce",
|
||||
"app.analysis.total-sales": "Total Sales",
|
||||
"app.analysis.day-sales": "Day Sales",
|
||||
"app.analysis.visits": "Visits",
|
||||
"app.analysis.visits-trend": "Visits Trend",
|
||||
"app.analysis.visits-ranking": "Visits Ranking",
|
||||
"app.analysis.day-visits": "Day Visits",
|
||||
"app.analysis.week": "Week Ratio",
|
||||
"app.analysis.day": "Day Ratio",
|
||||
"app.analysis.payments": "Payments",
|
||||
"app.analysis.conversion-rate": "Conversion Rate",
|
||||
"app.analysis.operational-effect": "Operational Effect",
|
||||
"app.analysis.sales-trend": "Stores Sales Trend",
|
||||
"app.analysis.sales-ranking": "Sales Ranking",
|
||||
"app.analysis.all-year": "All Year",
|
||||
"app.analysis.all-month": "All Month",
|
||||
"app.analysis.all-week": "All Week",
|
||||
"app.analysis.all-today": "All day",
|
||||
"app.analysis.search-users": "Search Users",
|
||||
"app.analysis.per-capita-search": "Per Capita Search",
|
||||
"app.analysis.online-top-search": "Online Top Search",
|
||||
"app.analysis.the-proportion-of-sales": "The Proportion Of Sales",
|
||||
"app.analysis.channel.all": "ALL",
|
||||
"app.analysis.channel.online": "Online",
|
||||
"app.analysis.channel.stores": "Stores",
|
||||
"app.analysis.sales": "Sales",
|
||||
"app.analysis.traffic": "Traffic",
|
||||
"app.analysis.table.rank": "Rank",
|
||||
"app.analysis.table.search-keyword": "Keyword",
|
||||
"app.analysis.table.users": "Users",
|
||||
"app.analysis.table.weekly-range": "Weekly Range",
|
||||
"app.monitor.trading-activity": "Real-Time Trading Activity",
|
||||
"app.monitor.total-transactions": "Total transactions today",
|
||||
"app.monitor.sales-target": "Sales target completion rate",
|
||||
"app.monitor.remaining-time": "Remaining time of activity",
|
||||
"app.monitor.total-transactions-per-second": "Total transactions per second",
|
||||
"app.monitor.activity-forecast": "Activity forecast",
|
||||
"app.monitor.efficiency": "Efficiency",
|
||||
"app.monitor.ratio": "Ratio",
|
||||
"app.monitor.proportion-per-category": "Proportion Per Category",
|
||||
"app.monitor.fast-food": "Fast food",
|
||||
"app.monitor.western-food": "Western food",
|
||||
"app.monitor.hot-pot": "Hot pot",
|
||||
"app.monitor.waiting-for-implementation": "Waiting for implementation",
|
||||
"app.monitor.popular-searches": "Popular Searches",
|
||||
"app.monitor.resource-surplus": "Resource Surplus",
|
||||
"app.monitor.fund-surplus": "Fund Surplus",
|
||||
"app.lock": "Lock",
|
||||
"app.login.message-invalid-credentials": "Invalid username or password(admin/ant.design)",
|
||||
"app.login.message-invalid-verification-code": "Invalid verification code",
|
||||
"app.login.tab-login-credentials": "Credentials",
|
||||
"app.login.tab-login-mobile": "Mobile number",
|
||||
"app.login.remember-me": "Remember me",
|
||||
"app.login.forgot-password": "Forgot your password?",
|
||||
"app.login.sign-in-with": "Sign in with",
|
||||
"app.login.signup": "Sign up",
|
||||
"app.login.login": "Login",
|
||||
"app.register.register": "Register",
|
||||
"app.register.get-verification-code": "Get code",
|
||||
"app.register.sign-in": "Already have an account?",
|
||||
"app.register-result.msg": "Account:registered at {{email}}",
|
||||
"app.register-result.activation-email":
|
||||
"The activation email has been sent to your email address and is valid for 24 hours. Please log in to the email in time and click on the link in the email to activate the account.",
|
||||
"app.register-result.back-home": "Back to home",
|
||||
"app.register-result.view-mailbox": "View mailbox",
|
||||
"validation.email.required": "Please enter your email!",
|
||||
"validation.email.wrong-format": "The email address is in the wrong format!",
|
||||
"validation.password.required": "Please enter your password!",
|
||||
"validation.password.twice": "The passwords entered twice do not match!",
|
||||
"validation.password.strength.msg":
|
||||
"Please enter at least 6 characters and don't use passwords that are easy to guess.",
|
||||
"validation.password.strength.strong": "Strength: strong",
|
||||
"validation.password.strength.medium": "Strength: medium",
|
||||
"validation.password.strength.short": "Strength: too short",
|
||||
"validation.confirm-password.required": "Please confirm your password!",
|
||||
"validation.phone-number.required": "Please enter your phone number!",
|
||||
"validation.phone-number.wrong-format": "Malformed phone number!",
|
||||
"validation.verification-code.required": "Please enter the verification code!",
|
||||
"validation.title.required": "Please enter a title",
|
||||
"validation.date.required": "Please select the start and end date",
|
||||
"validation.goal.required": "Please enter a description of the goal",
|
||||
"validation.standard.required": "Please enter a metric"
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user