| @@ -0,0 +1,16 @@ | |||||
| # Editor configuration, see https://editorconfig.org | |||||
| root = true | |||||
| [*] | |||||
| charset = utf-8 | |||||
| indent_style = space | |||||
| indent_size = 2 | |||||
| insert_final_newline = true | |||||
| trim_trailing_whitespace = true | |||||
| [*.ts] | |||||
| quote_type = single | |||||
| [*.md] | |||||
| max_line_length = off | |||||
| trim_trailing_whitespace = false | |||||
| @@ -0,0 +1,36 @@ | |||||
| _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 | |||||
| **/src/index.html | |||||
| @@ -0,0 +1,130 @@ | |||||
| 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', 'deprecation'], | |||||
| 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', | |||||
| 'deprecation/deprecation': 'warn', | |||||
| 'jsdoc/newline-after-description': 'off' | |||||
| } | |||||
| }, | |||||
| { | |||||
| files: ['*.html'], | |||||
| extends: ['plugin:@angular-eslint/template/recommended'], | |||||
| rules: { | |||||
| "@angular-eslint/template/prefer-self-closing-tags": "error" | |||||
| } | |||||
| }, | |||||
| { | |||||
| files: ['*.html'], | |||||
| excludedFiles: ['*inline-template-*.component.html'], | |||||
| extends: ['plugin:prettier/recommended'], | |||||
| rules: { | |||||
| 'prettier/prettier': ['error', { parser: 'angular' }], | |||||
| '@angular-eslint/template/eqeqeq': 'off' | |||||
| } | |||||
| } | |||||
| ] | |||||
| }; | |||||
| @@ -0,0 +1,3 @@ | |||||
| * text=auto eol=lf | |||||
| *.{cmd,[cC][mM][dD]} text eol=crlf | |||||
| *.{bat,[bB][aA][tT]} text eol=crlf | |||||
| @@ -0,0 +1,86 @@ | |||||
| # 依赖包 | |||||
| node_modules/ | |||||
| # 打包输出目录 | |||||
| /dist | |||||
| /dist-server | |||||
| /tmp | |||||
| /out-tsc | |||||
| /bazel-out | |||||
| # Angular缓存 | |||||
| .angular/ | |||||
| .sass-cache/ | |||||
| # 各种IDE和编辑器配置 | |||||
| .idea/ | |||||
| .project | |||||
| .classpath | |||||
| .c9/ | |||||
| *.launch | |||||
| .settings/ | |||||
| *.sublime-workspace | |||||
| .vscode/* | |||||
| !.vscode/settings.json | |||||
| !.vscode/tasks.json | |||||
| !.vscode/launch.json | |||||
| !.vscode/extensions.json | |||||
| .history/* | |||||
| # 环境配置文件 | |||||
| .env | |||||
| .env.local | |||||
| .env.development.local | |||||
| .env.test.local | |||||
| .env.production.local | |||||
| # 日志文件 | |||||
| logs | |||||
| *.log | |||||
| npm-debug.log* | |||||
| yarn-debug.log* | |||||
| yarn-error.log* | |||||
| pnpm-debug.log* | |||||
| lerna-debug.log* | |||||
| yarn.* | |||||
| # 系统文件 | |||||
| .DS_Store | |||||
| Thumbs.db | |||||
| desktop.ini | |||||
| # 测试覆盖率目录 | |||||
| /coverage | |||||
| /coverage-e2e | |||||
| /.nyc_output | |||||
| # 缓存 | |||||
| .eslintcache | |||||
| .stylelintcache | |||||
| .cache/ | |||||
| .parcel-cache/ | |||||
| .npm/ | |||||
| .pnpm-store/ | |||||
| .yarn/cache | |||||
| .yarn/unplugged | |||||
| .yarn/build-state.yml | |||||
| .yarn/install-state.gz | |||||
| # Misc | |||||
| *.tgz | |||||
| *.zip | |||||
| *.rar | |||||
| *.gz | |||||
| .temp | |||||
| .tmp | |||||
| temp/ | |||||
| tmp/ | |||||
| # 本地配置和密钥文件 | |||||
| *.key | |||||
| *.pem | |||||
| *-key.json | |||||
| *secret* | |||||
| # 特定于项目的忽略 | |||||
| # 如果有其他特定于您项目的文件/目录需要忽略,请在此处添加 | |||||
| @@ -0,0 +1,3 @@ | |||||
| [submodule "src/app/routes/wf"] | |||||
| path = src/app/routes/wf | |||||
| url = http://112.33.111.155:3000/yuhy/auseft.platform.workflow.git | |||||
| @@ -0,0 +1,29 @@ | |||||
| { | |||||
| "extends": [ | |||||
| "development" | |||||
| ], | |||||
| "hints": { | |||||
| "axe/name-role-value": [ | |||||
| "default", | |||||
| { | |||||
| "button-name": "off" | |||||
| } | |||||
| ], | |||||
| "compat-api/css": [ | |||||
| "default", | |||||
| { | |||||
| "ignore": [ | |||||
| "user-select" | |||||
| ] | |||||
| } | |||||
| ], | |||||
| "axe/forms": [ | |||||
| "default", | |||||
| { | |||||
| "label": "off", | |||||
| "select-name": "off" | |||||
| } | |||||
| ], | |||||
| "button-type": "off" | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,5 @@ | |||||
| #!/bin/sh | |||||
| . "$(dirname "$0")/_/husky.sh" | |||||
| export NODE_OPTIONS="--max-old-space-size=4096" | |||||
| npx --no-install tsc -p tsconfig.app.json --noEmit | |||||
| npx --no-install lint-staged | |||||
| @@ -0,0 +1 @@ | |||||
| 18.18.0 | |||||
| @@ -0,0 +1,18 @@ | |||||
| # add files you wish to ignore here | |||||
| **/*.md | |||||
| **/*.svg | |||||
| **/test.ts | |||||
| .stylelintrc | |||||
| .prettierrc | |||||
| src/assets/* | |||||
| src/index.html | |||||
| node_modules/ | |||||
| .vscode/ | |||||
| coverage/ | |||||
| dist/ | |||||
| package.json | |||||
| tslint.json | |||||
| _cli-tpl/**/* | |||||
| @@ -0,0 +1,13 @@ | |||||
| module.exports = { | |||||
| singleQuote: true, | |||||
| useTabs: false, | |||||
| printWidth: 140, | |||||
| tabWidth: 2, | |||||
| semi: true, | |||||
| htmlWhitespaceSensitivity: 'strict', | |||||
| arrowParens: 'avoid', | |||||
| bracketSpacing: true, | |||||
| proseWrap: 'preserve', | |||||
| trailingComma: 'none', | |||||
| endOfLine: 'lf' | |||||
| }; | |||||
| @@ -0,0 +1,63 @@ | |||||
| const { propertyGroups } = require('stylelint-config-clean-order'); | |||||
| const propertiesOrder = propertyGroups.map(properties => ({ | |||||
| noEmptyLineBetween: true, | |||||
| emptyLineBefore: 'never', | |||||
| properties | |||||
| })); | |||||
| module.exports = { | |||||
| extends: ['stylelint-config-standard'], | |||||
| 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'] | |||||
| } | |||||
| ], | |||||
| 'import-notation': 'string', | |||||
| 'media-feature-range-notation': 'prefix', | |||||
| 'media-query-no-invalid': null, | |||||
| 'order/order': [ | |||||
| [ | |||||
| 'dollar-variables', | |||||
| 'at-variables', | |||||
| 'custom-properties', | |||||
| { type: 'at-rule', name: 'custom-media' }, | |||||
| { type: 'at-rule', name: 'function' }, | |||||
| { type: 'at-rule', name: 'mixin' }, | |||||
| { type: 'at-rule', name: 'extend' }, | |||||
| { type: 'at-rule', name: 'include' }, | |||||
| 'declarations', | |||||
| 'less-mixins', | |||||
| { | |||||
| type: 'rule', | |||||
| selector: /^&::[\w-]+/, | |||||
| hasBlock: true | |||||
| }, | |||||
| 'rules', | |||||
| { type: 'at-rule', name: 'media', hasBlock: true } | |||||
| ], | |||||
| { severity: 'warning' } | |||||
| ], | |||||
| 'order/properties-order': [ | |||||
| propertiesOrder, | |||||
| { | |||||
| severity: 'warning', | |||||
| unspecified: 'bottomAlphabetical' | |||||
| } | |||||
| ] | |||||
| }, | |||||
| ignoreFiles: ['src/assets/**/*'] | |||||
| }; | |||||
| @@ -0,0 +1,3 @@ | |||||
| { | |||||
| "i18n-ally.localesPaths": [] | |||||
| } | |||||
| @@ -0,0 +1,6 @@ | |||||
| Merge branch 'master' of http://112.33.111.155:3000/hanxs/auseft.newplatform.web | |||||
| # Please enter a commit message to explain why this merge is necessary, | |||||
| # especially if it merges an updated upstream into a topic branch. | |||||
| # | |||||
| # Lines starting with '#' will be ignored, and an empty message aborts | |||||
| # the commit. | |||||
| @@ -0,0 +1,7 @@ | |||||
| # nginx | |||||
| FROM nginx:1.19.0-alpine as final | |||||
| #RUN mkdir -p /usr/share/nginx/html/dist | |||||
| COPY ./dist/himp.platform.angular/browser /usr/share/nginx/html/ | |||||
| COPY ./nginx.conf /etc/nginx/conf.d/default.conf | |||||
| EXPOSE 80 | |||||
| CMD ["nginx","-g","daemon off;"] | |||||
| @@ -0,0 +1,21 @@ | |||||
| MIT License | |||||
| Copyright (c) 2018-present 卡色<cipchk@qq.com> | |||||
| Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
| of this software and associated documentation files (the "Software"), to deal | |||||
| in the Software without restriction, including without limitation the rights | |||||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
| copies of the Software, and to permit persons to whom the Software is | |||||
| furnished to do so, subject to the following conditions: | |||||
| The above copyright notice and this permission notice shall be included in all | |||||
| copies or substantial portions of the Software. | |||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||||
| SOFTWARE. | |||||
| @@ -0,0 +1 @@ | |||||
| [Document](https://ng-alain.com/mock) | |||||
| @@ -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: 'Cipchk', | |||||
| avatar: 'https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png', | |||||
| userid: '00000001', | |||||
| email: 'cipchk@qq.com', | |||||
| signature: '海纳百川,有容乃大', | |||||
| title: '交互专家', | |||||
| group: '蚂蚁金服-某某某事业群-某某平台部-某某技术部-UED', | |||||
| tags: [ | |||||
| { | |||||
| key: '0', | |||||
| label: '很有想法的', | |||||
| }, | |||||
| { | |||||
| key: '1', | |||||
| label: '专注撩妹', | |||||
| }, | |||||
| { | |||||
| key: '2', | |||||
| label: '帅~', | |||||
| }, | |||||
| { | |||||
| key: '3', | |||||
| label: '通吃', | |||||
| }, | |||||
| { | |||||
| key: '4', | |||||
| label: '专职后端', | |||||
| }, | |||||
| { | |||||
| key: '5', | |||||
| label: '海纳百川', | |||||
| }, | |||||
| ], | |||||
| notifyCount: 12, | |||||
| country: 'China', | |||||
| geographic: { | |||||
| province: { | |||||
| label: '上海', | |||||
| key: '330000', | |||||
| }, | |||||
| city: { | |||||
| label: '市辖区', | |||||
| key: '330100', | |||||
| }, | |||||
| }, | |||||
| address: 'XX区XXX路 XX 号', | |||||
| phone: '你猜-你猜你猜猜猜', | |||||
| }, | |||||
| 'POST /user/avatar': 'ok', | |||||
| 'POST /login/account': (req: MockRequest) => { | |||||
| const data = req.body; | |||||
| if (!(data.userName === 'admin' || data.userName === 'user') || data.password !== 'ng-alain.com') { | |||||
| return { msg: `Invalid username or password(admin/ng-alain.com)` }; | |||||
| } | |||||
| return { | |||||
| msg: 'ok', | |||||
| user: { | |||||
| token: '123456789', | |||||
| name: data.userName, | |||||
| email: `${data.userName}@qq.com`, | |||||
| id: 10000, | |||||
| time: +new Date(), | |||||
| }, | |||||
| }; | |||||
| }, | |||||
| 'POST /register': { | |||||
| msg: 'ok', | |||||
| }, | |||||
| }; | |||||
| @@ -0,0 +1 @@ | |||||
| export * from './_user'; | |||||
| @@ -0,0 +1,216 @@ | |||||
| { | |||||
| "$schema": "./node_modules/@angular/cli/lib/config/schema.json", | |||||
| "version": 1, | |||||
| "newProjectRoot": "projects", | |||||
| "projects": { | |||||
| "himp.platform.angular": { | |||||
| "projectType": "application", | |||||
| "schematics": { | |||||
| "@schematics/angular:component": { | |||||
| "skipTests": false, | |||||
| "flat": false, | |||||
| "inlineStyle": true, | |||||
| "inlineTemplate": false, | |||||
| "style": "less" | |||||
| }, | |||||
| "ng-alain:module": { | |||||
| "routing": true | |||||
| }, | |||||
| "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 | |||||
| }, | |||||
| "@schematics/angular:directive": { | |||||
| "skipTests": false | |||||
| }, | |||||
| "@schematics/angular:service": { | |||||
| "skipTests": false | |||||
| } | |||||
| }, | |||||
| "root": "", | |||||
| "sourceRoot": "src", | |||||
| "prefix": "app", | |||||
| "architect": { | |||||
| "build": { | |||||
| "builder": "@angular-devkit/build-angular:application", | |||||
| "options": { | |||||
| "outputPath": "dist/himp.platform.angular", | |||||
| "index": "src/index.html", | |||||
| "browser": "src/main.ts", | |||||
| "polyfills": [ | |||||
| "zone.js" | |||||
| ], | |||||
| "tsConfig": "tsconfig.app.json", | |||||
| "inlineStyleLanguage": "less", | |||||
| "assets": [ | |||||
| "src/favicon.ico", | |||||
| "src/assets", | |||||
| "src/assets/menu/", | |||||
| "src/mobile.html", | |||||
| "src/assets/menu/diqiu.svg", | |||||
| { | |||||
| "glob": "**/*.woff2", | |||||
| "input": "src/assets/fonts", | |||||
| "output": "/assets/fonts" | |||||
| } | |||||
| ], | |||||
| "styles": [ | |||||
| "src/styles.less", | |||||
| "src/styles/googleapiscss2.css" | |||||
| ], | |||||
| "scripts": [ | |||||
| ], | |||||
| "allowedCommonJsDependencies": [ | |||||
| "ajv", | |||||
| "ajv-formats", | |||||
| "extend", | |||||
| "file-saver", | |||||
| "mockjs" | |||||
| ], | |||||
| "stylePreprocessorOptions": { | |||||
| "includePaths": [ | |||||
| "node_modules/" | |||||
| ] | |||||
| } | |||||
| }, | |||||
| "configurations": { | |||||
| "production": { | |||||
| "budgets": [ | |||||
| { | |||||
| "type": "initial", | |||||
| "maximumWarning": "2mb", | |||||
| "maximumError": "30mb" | |||||
| }, | |||||
| { | |||||
| "type": "anyComponentStyle", | |||||
| "maximumWarning": "2kb", | |||||
| "maximumError": "300kb" | |||||
| } | |||||
| ], | |||||
| "outputHashing": "all", | |||||
| "fileReplacements": [ | |||||
| { | |||||
| "replace": "src/environments/environment.ts", | |||||
| "with": "src/environments/environment.prod.ts" | |||||
| } | |||||
| ] | |||||
| }, | |||||
| "linhe": { | |||||
| "budgets": [ | |||||
| { | |||||
| "type": "initial", | |||||
| "maximumWarning": "2mb", | |||||
| "maximumError": "30mb" | |||||
| }, | |||||
| { | |||||
| "type": "anyComponentStyle", | |||||
| "maximumWarning": "2kb", | |||||
| "maximumError": "300kb" | |||||
| } | |||||
| ], | |||||
| "outputHashing": "all", | |||||
| "fileReplacements": [ | |||||
| { | |||||
| "replace": "src/environments/environment.ts", | |||||
| "with": "src/environments/environment.linhe.ts" | |||||
| } | |||||
| ] | |||||
| }, | |||||
| "development": { | |||||
| "optimization": false, | |||||
| "extractLicenses": false, | |||||
| "sourceMap": true | |||||
| } | |||||
| }, | |||||
| "defaultConfiguration": "production" | |||||
| }, | |||||
| "serve": { | |||||
| "builder": "@angular-devkit/build-angular:dev-server", | |||||
| "configurations": { | |||||
| "production": { | |||||
| "buildTarget": "himp.platform.angular:build:production" | |||||
| }, | |||||
| "linhe": { | |||||
| "buildTarget": "himp.platform.angular:build:linhe" | |||||
| }, | |||||
| "development": { | |||||
| "buildTarget": "himp.platform.angular:build:development" | |||||
| } | |||||
| }, | |||||
| "defaultConfiguration": "development", | |||||
| "options": { | |||||
| "proxyConfig": "proxy.conf.js" | |||||
| } | |||||
| }, | |||||
| "extract-i18n": { | |||||
| "builder": "@angular-devkit/build-angular:extract-i18n", | |||||
| "options": { | |||||
| "buildTarget": "himp.platform.angular:build" | |||||
| } | |||||
| }, | |||||
| "test": { | |||||
| "builder": "@angular-devkit/build-angular:karma", | |||||
| "options": { | |||||
| "polyfills": [ | |||||
| "zone.js", | |||||
| "zone.js/testing" | |||||
| ], | |||||
| "tsConfig": "tsconfig.spec.json", | |||||
| "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" | |||||
| ] | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| }, | |||||
| "cli": { | |||||
| "analytics": false, | |||||
| "schematicCollections": [ | |||||
| "@schematics/angular", | |||||
| "ng-alain" | |||||
| ] | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,19 @@ | |||||
| -----BEGIN CERTIFICATE----- | |||||
| MIIDJTCCAg0CFHxH0VEVUwceRTXO6UiW7AKa4TLKMA0GCSqGSIb3DQEBCwUAME8x | |||||
| CzAJBgNVBAYTAmNoMQ4wDAYDVQQIDAVjaGluYTEPMA0GA1UEBwwGc3V6aG91MQ8w | |||||
| DQYDVQQKDAZhdXNlZnQxDjAMBgNVBAMMBW9jZWFuMB4XDTI0MDUwNjA3MDczNVoX | |||||
| DTI0MDYwNTA3MDczNVowTzELMAkGA1UEBhMCY2gxDjAMBgNVBAgMBWNoaW5hMQ8w | |||||
| DQYDVQQHDAZzdXpob3UxDzANBgNVBAoMBmF1c2VmdDEOMAwGA1UEAwwFb2NlYW4w | |||||
| ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDHkoVKhHqw7HUq9wEKVID8 | |||||
| /9UbZyZkA61uZ6OlHcKcP4DWxPLACH4yHhkludVuGF0XBh9V+l/CeGRGgzKd7AK+ | |||||
| sNG3MKxUcStIBPQr1WyyDAm62TjuK4frqaeQzV6PRJ/4t/JXcSoKJ7ZpGe7wWjYy | |||||
| 7YzcsAqQ6ZYIbgFccQUJbZ1xUXghyumo5epNlS/pbTajOMbwBG5gom1DwhVSqGGw | |||||
| iOOynmXS+iZKbWoiYWdVf++SRL3niag2AwUTmxsDwu3FZtjedR4++EK6YRCOIl9P | |||||
| 4tR8yke+JQ+q24H+/5W/4ekwxmYhaAe5dpnThly9x5tS0NrG84PNbr+tgVi10zAB | |||||
| AgMBAAEwDQYJKoZIhvcNAQELBQADggEBAHv3nlXcPLKXKozLStPWkz/kE3nSwSoM | |||||
| rXCK4qWDbtEr51HiAvEq58A0NEJ8pYl0PowKd2cYBgAONfMQZcUnXqvn0jAHjgvv | |||||
| QxNJavfzs9IoWwuCx4wxJjWdju3+/Ah4WV3jWUmgQsXzH5s0sExFc76OID09huAU | |||||
| 1dMvW+1JohR3fnKRDTjVsQTRpmAAYY0vq3YCsXhzQoJD/bH0flaTd79MUeQvXEcg | |||||
| fs86YcnypueNgXVlORIHtNTG6Qyf7wMmSy1Jt378pB5ApVNh+/oVev2t9oVNDftS | |||||
| WWRAzHxSONuvgv6AHs5BQp8sklwaFe25+hl0ZnScxvjG1ynBrgj/VqU= | |||||
| -----END CERTIFICATE----- | |||||
| @@ -0,0 +1,16 @@ | |||||
| { | |||||
| "$schema": "./node_modules/ng-alain/schema.json", | |||||
| "theme": { | |||||
| "list": [ | |||||
| { | |||||
| "theme": "dark" | |||||
| }, | |||||
| { | |||||
| "theme": "compact" | |||||
| } | |||||
| ] | |||||
| }, | |||||
| "projects": { | |||||
| "himp.platform.angular": {} | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,84 @@ | |||||
| upstream api{ | |||||
| server 112.33.111.160:8081; | |||||
| } | |||||
| upstream bm{ | |||||
| server 112.33.111.160:8085; | |||||
| } | |||||
| upstream pneumatic{ | |||||
| server 112.33.111.160:8085; | |||||
| } | |||||
| upstream system{ | |||||
| server 112.33.111.160:8085; | |||||
| } | |||||
| upstream pur{ | |||||
| server 112.33.111.160:8085; | |||||
| } | |||||
| server { | |||||
| listen 80; | |||||
| listen [::]:80; | |||||
| server_name localhost; | |||||
| # access_log /var/log/nginx/host.access.log main; | |||||
| location /api/ { | |||||
| proxy_pass http://api; | |||||
| } | |||||
| location /bm/ { | |||||
| proxy_pass http://bm; | |||||
| } | |||||
| location /pneumatic/ { | |||||
| proxy_pass http://pneumatic; | |||||
| } | |||||
| location /system/ { | |||||
| proxy_pass http://system; | |||||
| } | |||||
| location /pur/ { | |||||
| proxy_pass http://pur; | |||||
| } | |||||
| location / { | |||||
| root /usr/share/nginx/html; | |||||
| index index.html index.htm; | |||||
| } | |||||
| #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; | |||||
| } | |||||
| # proxy the PHP scripts to Apache listening on 127.0.0.1:80 | |||||
| # | |||||
| #location ~ \.php$ { | |||||
| # proxy_pass http://127.0.0.1; | |||||
| #} | |||||
| # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000 | |||||
| # | |||||
| #location ~ \.php$ { | |||||
| # root html; | |||||
| # fastcgi_pass 127.0.0.1:9000; | |||||
| # fastcgi_index index.php; | |||||
| # fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name; | |||||
| # include fastcgi_params; | |||||
| #} | |||||
| # deny access to .htaccess files, if Apache's document root | |||||
| # concurs with nginx's one | |||||
| # | |||||
| #location ~ /\.ht { | |||||
| # deny all; | |||||
| #} | |||||
| } | |||||
| @@ -0,0 +1,139 @@ | |||||
| { | |||||
| "name": "himp.platform.angular", | |||||
| "version": "0.0.0", | |||||
| "browserslist": [ | |||||
| "> 0.5%", | |||||
| "last 2 versions", | |||||
| "not dead", | |||||
| "Android >= 4.4" | |||||
| ], | |||||
| "scripts": { | |||||
| "ng": "ng", | |||||
| "start": "ng s -o", | |||||
| "dev": "ng serve --host 0.0.0.0 --port 4200", | |||||
| "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", | |||||
| "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": "^18.0.1", | |||||
| "@angular/common": "^18.0.1", | |||||
| "@angular/compiler": "^18.0.1", | |||||
| "@angular/core": "^18.0.1", | |||||
| "@angular/forms": "^18.0.1", | |||||
| "@angular/platform-browser": "^18.0.1", | |||||
| "@angular/platform-browser-dynamic": "^18.0.1", | |||||
| "@angular/router": "^18.0.1", | |||||
| "@antv/x6": "^2.18.1", | |||||
| "@antv/x6-plugin-clipboard": "^2.1.6", | |||||
| "@antv/x6-plugin-dnd": "^2.1.1", | |||||
| "@antv/x6-plugin-history": "^2.2.4", | |||||
| "@antv/x6-plugin-keyboard": "^2.2.3", | |||||
| "@antv/x6-plugin-selection": "^2.2.2", | |||||
| "@antv/x6-plugin-snapline": "^2.1.7", | |||||
| "@antv/x6-plugin-stencil": "^2.1.5", | |||||
| "@antv/x6-plugin-transform": "^2.1.8", | |||||
| "@ctrl/tinycolor": "^4.1.0", | |||||
| "@delon/abc": "^18.2.0", | |||||
| "@delon/acl": "^17.1.0", | |||||
| "@delon/auth": "^17.1.0", | |||||
| "@delon/cache": "^17.1.0", | |||||
| "@delon/chart": "^18.2.0", | |||||
| "@delon/form": "^18.2.0", | |||||
| "@delon/mock": "^17.1.0", | |||||
| "@delon/theme": "^18.2.0", | |||||
| "@delon/util": "^17.1.0", | |||||
| "@microsoft/signalr": "^8.0.0", | |||||
| "@ngx-translate/core": "^15.0.0", | |||||
| "@ngx-translate/http-loader": "^8.0.0", | |||||
| "@scena/guides": "^0.29.2", | |||||
| "@tauri-apps/api": "^2.2.0", | |||||
| "@types/d3": "^7.4.3", | |||||
| "@types/uuid": "^10.0.0", | |||||
| "ag-grid-angular": "^31.2.1", | |||||
| "ag-grid-community": "^31.2.1", | |||||
| "autofit.js": "^3.2.8", | |||||
| "chart.js": "^4.4.2", | |||||
| "d3": "^7.9.0", | |||||
| "dayjs": "^1.11.11", | |||||
| "echarts": "^5.5.0", | |||||
| "echarts-gl": "^2.0.9", | |||||
| "fabric": "^6.4.3", | |||||
| "highcharts": "^11.4.1", | |||||
| "highcharts-angular": "^4.0.0", | |||||
| "hls.js": "^1.5.8", | |||||
| "i": "^0.3.7", | |||||
| "infinite-viewer": "^0.29.1", | |||||
| "jsbarcode": "^3.12.1", | |||||
| "konva": "^9.3.15", | |||||
| "lz-string": "^1.5.0", | |||||
| "mermaid": "^11.6.0", | |||||
| "moment": "^2.30.1", | |||||
| "ng-alain": "^17.1.0", | |||||
| "ng-zorro-antd": "^18.1.1", | |||||
| "ng2-konva": "^9.0.0", | |||||
| "ngx-mqtt": "^17.0.0", | |||||
| "react-device-detect": "^2.2.3", | |||||
| "rxjs": "~7.8.0", | |||||
| "screenfull": "^6.0.2", | |||||
| "tslib": "^2.3.0", | |||||
| "uninstall": "^0.0.0", | |||||
| "zone.js": "~0.14.3" | |||||
| }, | |||||
| "devDependencies": { | |||||
| "@angular-devkit/build-angular": "^18.2.19", | |||||
| "@angular-eslint/builder": "^17.2.0", | |||||
| "@angular-eslint/eslint-plugin": "^17.2.0", | |||||
| "@angular-eslint/eslint-plugin-template": "^17.2.0", | |||||
| "@angular-eslint/schematics": "^18.2.0", | |||||
| "@angular-eslint/template-parser": "^17.2.0", | |||||
| "@angular/cli": "^18.0.2", | |||||
| "@angular/compiler-cli": "^18.0.1", | |||||
| "@angular/language-service": "^18.0.1", | |||||
| "@delon/testing": "^17.1.0", | |||||
| "@ng-util/monaco-editor": "^17.0.1", | |||||
| "@types/file-saver": "^2.0.7", | |||||
| "@types/jasmine": "~5.1.0", | |||||
| "@types/jsbarcode": "^3.11.4", | |||||
| "@types/opentype.js": "^1.3.8", | |||||
| "@typescript-eslint/eslint-plugin": "^6.19.0", | |||||
| "@typescript-eslint/parser": "^6.19.0", | |||||
| "eslint": "^8.56.0", | |||||
| "eslint-config-prettier": "~9.1.0", | |||||
| "eslint-plugin-deprecation": "~2.0.0", | |||||
| "eslint-plugin-import": "~2.29.1", | |||||
| "eslint-plugin-jsdoc": "~48.0.2", | |||||
| "eslint-plugin-prefer-arrow": "~1.2.3", | |||||
| "eslint-plugin-prettier": "~5.1.3", | |||||
| "jasmine-core": "~5.1.0", | |||||
| "karma": "~6.4.0", | |||||
| "karma-chrome-launcher": "~3.2.0", | |||||
| "karma-coverage": "~2.2.0", | |||||
| "karma-jasmine": "~5.1.0", | |||||
| "karma-jasmine-html-reporter": "~2.1.0", | |||||
| "lint-staged": "^15.2.0", | |||||
| "ng-alain": "^17.1.0", | |||||
| "ng-alain-plugin-theme": "^16.0.2", | |||||
| "ngx-tinymce": "^17.0.0", | |||||
| "prettier": "^3.2.4", | |||||
| "raw-loader": "^4.0.2", | |||||
| "source-map-explorer": "^2.5.3", | |||||
| "stylelint": "^16.1.0", | |||||
| "stylelint-config-clean-order": "^5.4.0", | |||||
| "stylelint-config-standard": "^36.0.0", | |||||
| "stylelint-declaration-block-no-ignored-properties": "^2.8.0", | |||||
| "typescript": "~5.4.5" | |||||
| } | |||||
| } | |||||
| @@ -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/` | |||||
| */ | |||||
| // '/api': { | |||||
| // target: 'https://localhost:9000/', | |||||
| // secure: false, // Ignore invalid SSL certificates | |||||
| // changeOrigin: true | |||||
| // } | |||||
| }; | |||||
| @@ -0,0 +1,22 @@ | |||||
| import { Routes } from '@angular/router'; | |||||
| import { WfBizflowConfigComponent } from './bizflow-config/bizflow-config.component'; | |||||
| import { ChouYangComponent } from './chouyang/chouyang.component'; | |||||
| import { WorkflowInstanceStartComponent } from './instance-start/instance-start.component'; | |||||
| import { WorkflowProcessDefinitionComponent } from './process-definition/process-definition.component'; | |||||
| import { WorkflowProcessInstanceComponent } from './process-instance/process-instance.component'; | |||||
| import { ProcessMonitoringComponent } from './process-monitoring/process-monitoring.component'; | |||||
| import { WorkflowProcessNodeComponent } from './process-node/process-node.component'; | |||||
| import { WorkflowProcessTypeComponent } from './process-type/process-type.component'; | |||||
| import { TodoTasksComponent } from './todo-tasks/todo-tasks.component'; // xxxx | |||||
| export const routes: Routes = [ | |||||
| { path: 'process-definition', component: WorkflowProcessDefinitionComponent }, | |||||
| { path: 'process-type', component: WorkflowProcessTypeComponent }, | |||||
| { path: 'process-instance', component: WorkflowProcessInstanceComponent }, | |||||
| { path: 'process-node', component: WorkflowProcessNodeComponent }, | |||||
| { path: 'instance-start', component: WorkflowInstanceStartComponent }, | |||||
| { path: 'bizflow-config', component: WfBizflowConfigComponent }, | |||||
| { path: 'process-monitoring', component: ProcessMonitoringComponent }, | |||||
| { path: 'todo-tasks', component: TodoTasksComponent }, | |||||
| { path: 'chouyang', component: ChouYangComponent } | |||||
| ]; | |||||
| @@ -0,0 +1,157 @@ | |||||
| import { NgComponentOutlet, CommonModule } from '@angular/common'; | |||||
| import { Component, ElementRef, OnInit, Renderer2, inject, RendererType2 } from '@angular/core'; | |||||
| import { NavigationEnd, NavigationError, RouteConfigLoadStart, Router, RouterOutlet } from '@angular/router'; | |||||
| import { TitleService, VERSION as VERSION_ALAIN, stepPreloader, SettingsService } from '@delon/theme'; | |||||
| import { environment } from '@env/environment'; | |||||
| import { NzIconModule, NzIconService } from 'ng-zorro-antd/icon'; | |||||
| import { NzModalService } from 'ng-zorro-antd/modal'; | |||||
| import { VERSION as VERSION_ZORRO } from 'ng-zorro-antd/version'; | |||||
| // 引入阿里矢量图标库的链接 | |||||
| const ICONFONT_URL = '../assets/fonts/zi/iconfont.js'; | |||||
| // <router-outlet (activate)="onActivate($event)" (deactivate)="onDeactivate($event)"> | |||||
| // <ng-container *ngComponentOutlet="currentComponent"></ng-container> | |||||
| // </router-outlet> | |||||
| @Component({ | |||||
| selector: 'app-root', | |||||
| template: ` <router-outlet /> `, | |||||
| standalone: true, | |||||
| imports: [RouterOutlet, NgComponentOutlet, CommonModule] | |||||
| }) | |||||
| export class AppComponent implements OnInit { | |||||
| private readonly router = inject(Router); | |||||
| private readonly titleSrv = inject(TitleService); | |||||
| private readonly modalSrv = inject(NzModalService); | |||||
| private donePreloader = stepPreloader(); | |||||
| routeHistory: string[] = []; | |||||
| private readonly settings = inject(SettingsService); | |||||
| // 用于 keep-alive 的组件缓存 | |||||
| private componentCache = new Map<string, any>(); | |||||
| currentComponent: any = null; | |||||
| constructor( | |||||
| el: ElementRef, | |||||
| renderer: Renderer2, | |||||
| private iconService: NzIconService | |||||
| ) { | |||||
| // 在根模块的构造函数中加载图标字体 | |||||
| this.iconService.fetchFromIconfont({ | |||||
| scriptUrl: ICONFONT_URL | |||||
| }); | |||||
| renderer.setAttribute(el.nativeElement, 'ng-alain-version', VERSION_ALAIN.full); | |||||
| renderer.setAttribute(el.nativeElement, 'ng-zorro-version', VERSION_ZORRO.full); | |||||
| } | |||||
| getHistory(): string[] { | |||||
| return this.routeHistory; | |||||
| } | |||||
| ngOnInit(): void { | |||||
| const xhr = new XMLHttpRequest(); | |||||
| const url = `${environment.api['fuelUrl']}system/config/configKey/pcSystemTitile`; | |||||
| xhr.open('GET', url, true); | |||||
| xhr.onreadystatechange = () => { | |||||
| if (xhr.readyState === 4) { | |||||
| if (xhr.status === 200) { | |||||
| try { | |||||
| const res = JSON.parse(xhr.responseText); | |||||
| console.log(res.msg, 'ress'); | |||||
| if (res.code === 200) { | |||||
| this.titleSrv.setTitle(res.msg); | |||||
| } | |||||
| } catch (error) { | |||||
| console.error('解析响应数据时出错:', error); | |||||
| } | |||||
| } else { | |||||
| console.error('请求失败,状态码:', xhr.status); | |||||
| } | |||||
| } | |||||
| }; | |||||
| xhr.send(); | |||||
| let configLoad = false; | |||||
| if (window.performance.navigation.type === window.performance.navigation.TYPE_RELOAD) { | |||||
| if (window.location.search === '') { | |||||
| if (!this.settings.user.name) { | |||||
| var ua = navigator.userAgent.toLowerCase(); | |||||
| // 简易判断移动端关键字 | |||||
| var isMobile = /android|iphone|ipod|ipad|windows phone|blackberry|mobile/.test(ua); | |||||
| if (isMobile) { | |||||
| if (!window.sessionStorage.getItem('token')) { | |||||
| this.router.navigateByUrl('/mobile'); | |||||
| } | |||||
| } else { | |||||
| this.router.navigateByUrl('/passport/login'); | |||||
| } | |||||
| } | |||||
| } | |||||
| } else { | |||||
| // 记录来源 | |||||
| if (document.referrer) { | |||||
| console.log('页面是通过链接跳转访问的,来源是:', document.referrer); | |||||
| } else { | |||||
| console.log('页面可能是通过直接输入地址或书签访问的'); | |||||
| // **🔹 判断是否为移动端** | |||||
| var ua = navigator.userAgent.toLowerCase(); | |||||
| var isMobile = /android|iphone|ipod|ipad|windows phone|blackberry|mobile/.test(ua); | |||||
| // **🔹 进行路由跳转** | |||||
| if (isMobile) { | |||||
| this.router.navigateByUrl('/mobile').then(success => { | |||||
| if (success) { | |||||
| console.log('成功跳转到 /mobile'); | |||||
| } else { | |||||
| console.error('跳转失败,可能是路由未正确加载'); | |||||
| } | |||||
| }); | |||||
| } else { | |||||
| this.router.navigate(['/passport/login']).then(success => { | |||||
| if (success) { | |||||
| console.log('成功跳转到 /passport/login'); | |||||
| } else { | |||||
| console.error('跳转失败,可能是路由未正确加载'); | |||||
| } | |||||
| }); | |||||
| } | |||||
| } | |||||
| } | |||||
| 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.donePreloader(); | |||||
| // this.titleSrv.setTitle(); | |||||
| this.modalSrv.closeAll(); | |||||
| } | |||||
| }); | |||||
| } | |||||
| // 激活路由时的处理 | |||||
| onActivate(component: any) { | |||||
| const currentRoute = this.router.url; | |||||
| if (this.componentCache.has(currentRoute)) { | |||||
| this.currentComponent = this.componentCache.get(currentRoute); | |||||
| } else { | |||||
| this.currentComponent = component; | |||||
| this.componentCache.set(currentRoute, component); | |||||
| } | |||||
| } | |||||
| // 路由离开时的处理 | |||||
| onDeactivate(component: any) { | |||||
| // 可以在这里添加组件销毁前的清理逻辑 | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,102 @@ | |||||
| import { provideHttpClient, withInterceptors } from '@angular/common/http'; | |||||
| import { default as ngLang } from '@angular/common/locales/zh'; | |||||
| import { ApplicationConfig, EnvironmentProviders, Provider } from '@angular/core'; | |||||
| import { provideAnimations } from '@angular/platform-browser/animations'; | |||||
| import { | |||||
| provideRouter, | |||||
| withComponentInputBinding, | |||||
| withViewTransitions, | |||||
| withInMemoryScrolling, | |||||
| withHashLocation, | |||||
| RouterFeatures, | |||||
| RouteReuseStrategy | |||||
| } from '@angular/router'; | |||||
| import { defaultInterceptor, provideStartup } from '@core'; | |||||
| import { provideCellWidgets } from '@delon/abc/cell'; | |||||
| import { provideSTWidgets } from '@delon/abc/st'; | |||||
| import { authSimpleInterceptor, provideAuth } from '@delon/auth'; | |||||
| import { provideSFConfig } from '@delon/form'; | |||||
| import { AlainProvideLang, provideAlain, zh_CN as delonLang } from '@delon/theme'; | |||||
| import { AlainConfig } from '@delon/util/config'; | |||||
| import { environment } from '@env/environment'; | |||||
| import { CELL_WIDGETS, ST_WIDGETS, SF_WIDGETS } from '@shared'; | |||||
| import { zhCN as dateLang } from 'date-fns/locale'; | |||||
| import { NzConfig, provideNzConfig } from 'ng-zorro-antd/core/config'; | |||||
| import { zh_CN as zorroLang } from 'ng-zorro-antd/i18n'; | |||||
| import { MqttModule, IMqttServiceOptions, MqttService } from 'ngx-mqtt'; | |||||
| import { provideBindAuthRefresh } from './core/net'; | |||||
| import { CustomRouteReuseStrategy } from './core/route-reuse-strategy'; | |||||
| import { routes } from './routes/routes'; | |||||
| import { ICONS } from '../style-icons'; | |||||
| import { ICONS_AUTO } from '../style-icons-auto'; | |||||
| export const MQTT_SERVICE_OPTIONS: IMqttServiceOptions = { | |||||
| // hostname:'112.33.111.160', | |||||
| // port: 8096, | |||||
| // protocol: 'wss', | |||||
| // path: '/mqtt', | |||||
| hostname: '192.168.35.11', //112.33.111.160 | |||||
| // hostname: '10.96.96.13', | |||||
| port: 8083, | |||||
| protocol: 'ws', | |||||
| path: '/mqtt', | |||||
| username: 'auseft', | |||||
| password: '1q2w3E**' | |||||
| }; | |||||
| const defaultLang: AlainProvideLang = { | |||||
| abbr: 'zh-CN', | |||||
| ng: ngLang, | |||||
| zorro: zorroLang, | |||||
| date: dateLang, | |||||
| delon: delonLang | |||||
| }; | |||||
| const alainConfig: AlainConfig = { | |||||
| auth: { login_url: '/passport/register' } | |||||
| }; | |||||
| const ngZorroConfig: NzConfig = {}; | |||||
| const routerFeatures: RouterFeatures[] = [ | |||||
| withComponentInputBinding(), | |||||
| withViewTransitions(), | |||||
| withInMemoryScrolling({ scrollPositionRestoration: 'top' }) | |||||
| ]; | |||||
| if (environment.useHash) routerFeatures.push(withHashLocation()); | |||||
| const providers: Array<Provider | EnvironmentProviders> = [ | |||||
| provideHttpClient(withInterceptors([...(environment.interceptorFns ?? []), authSimpleInterceptor, defaultInterceptor])), | |||||
| provideAnimations(), | |||||
| provideRouter(routes, ...routerFeatures), | |||||
| provideAlain({ config: alainConfig, defaultLang, icons: [...ICONS_AUTO, ...ICONS] }), | |||||
| provideNzConfig(ngZorroConfig), | |||||
| provideAuth(), | |||||
| provideCellWidgets(...CELL_WIDGETS), | |||||
| provideSTWidgets(...ST_WIDGETS), | |||||
| provideSFConfig({ | |||||
| widgets: [...SF_WIDGETS] | |||||
| }), | |||||
| { | |||||
| provide: RouteReuseStrategy, | |||||
| useClass: CustomRouteReuseStrategy | |||||
| }, | |||||
| { | |||||
| provide: MqttService, | |||||
| useFactory: () => { | |||||
| return new MqttService(MQTT_SERVICE_OPTIONS); | |||||
| } | |||||
| }, | |||||
| provideStartup(), | |||||
| ...(environment.providers || []) | |||||
| ]; | |||||
| // If you use `@delon/auth` to refresh the token, additional registration `provideBindAuthRefresh` is required | |||||
| if (environment.api?.refreshTokenEnabled && environment.api.refreshTokenType === 'auth-refresh') { | |||||
| providers.push(provideBindAuthRefresh()); | |||||
| } | |||||
| export const appConfig: ApplicationConfig = { | |||||
| providers: providers | |||||
| }; | |||||
| @@ -0,0 +1,18 @@ | |||||
| import { NgModule } from '@angular/core'; | |||||
| import { BrowserModule } from '@angular/platform-browser'; | |||||
| import { NgxEchartsModule } from 'ngx-echarts'; | |||||
| import { AppComponent } from './app.component'; | |||||
| @NgModule({ | |||||
| declarations: [AppComponent], | |||||
| imports: [ | |||||
| BrowserModule, | |||||
| NgxEchartsModule.forRoot({ | |||||
| echarts: () => import('echarts') | |||||
| }) | |||||
| ], | |||||
| providers: [], | |||||
| bootstrap: [AppComponent] | |||||
| }) | |||||
| export class AppModule {} | |||||
| @@ -0,0 +1,68 @@ | |||||
| // tslint:disable-next-line:ban-types | |||||
| const Messages: any = { | |||||
| T0001: '提示信息', | |||||
| C0001: '确定要删除这条数据吗?', | |||||
| C0002: '确定要把这条数据无效吗?', | |||||
| C0003: '保存成功', | |||||
| C0004: '确定要作废该药品及药品目录信息吗?', | |||||
| C0005: '确定要启用该药品信息吗?', | |||||
| C0006: '删除成功', | |||||
| C0007: '确定要进行审核操作吗?', | |||||
| E0001: '请输入{0}', | |||||
| E0002: '{0}的最大长度是{1}', | |||||
| E0003: '{0}的格式不正确', | |||||
| E0004: '请输入数字', | |||||
| E0005: '请输入{0}', | |||||
| E0006: '{0}的最小长度是{1}', | |||||
| E0007: '', | |||||
| E0008: '请选择{0}', | |||||
| E0009: '密码必须包含大小写字母、数字和特殊字符,且长度不少于8位', | |||||
| I0001: '操作完成', | |||||
| W0001: '' | |||||
| }; | |||||
| class MessageUtil { | |||||
| public devMode = false; | |||||
| public getMessage(messageId: string, params: string[]): string { | |||||
| const message = this.replaceMessage(messageId, params); | |||||
| if (this.devMode) { | |||||
| return `${message}(${messageId})`; | |||||
| } else { | |||||
| return message; | |||||
| } | |||||
| } | |||||
| public replaceMessage(messageId: string, params: string[]): string { | |||||
| let message: string = Messages[messageId] || messageId; | |||||
| if (params) { | |||||
| params.forEach((param: string, index: number) => { | |||||
| const reg = new RegExp(`\\{${index}\\}`, 'g'); | |||||
| message = message.replace(reg, param); | |||||
| }); | |||||
| } | |||||
| return message; | |||||
| } | |||||
| public convertToXML(): any { | |||||
| if (document.implementation && document.implementation.createDocument) { | |||||
| const xmlDom = document.implementation.createDocument('', '', null); | |||||
| const messageListEl = xmlDom.createElement('MessageList'); | |||||
| // tslint:disable-next-line: forin | |||||
| for (const key in Messages) { | |||||
| const messageEl = xmlDom.createElement('Message'); | |||||
| const idEl = xmlDom.createElement('Id'); | |||||
| idEl.textContent = key; | |||||
| const textEl = xmlDom.createElement('Text'); | |||||
| textEl.textContent = Messages[key]; | |||||
| messageEl.appendChild(idEl); | |||||
| messageEl.appendChild(textEl); | |||||
| messageListEl.appendChild(messageEl); | |||||
| } | |||||
| return messageListEl; | |||||
| } | |||||
| return null; | |||||
| } | |||||
| } | |||||
| export const MESSAGE_UTIL: MessageUtil = new MessageUtil(); | |||||
| @@ -0,0 +1,5 @@ | |||||
| ### CoreModule | |||||
| **应** 仅只留 `providers` 属性。 | |||||
| **作用:** 一些通用服务,例如:用户消息、HTTP数据访问。 | |||||
| @@ -0,0 +1,3 @@ | |||||
| export * from './net/default.interceptor'; | |||||
| export * from './startup/startup.service'; | |||||
| export * from './start-page.guard'; | |||||
| @@ -0,0 +1,82 @@ | |||||
| import { HttpErrorResponse, HttpHandlerFn, HttpInterceptorFn, HttpRequest, HttpResponseBase } from '@angular/common/http'; | |||||
| import { Injector, inject } from '@angular/core'; | |||||
| import { IGNORE_BASE_URL, _HttpClient } from '@delon/theme'; | |||||
| import { environment } from '@env/environment'; | |||||
| import { NzMessageService } from 'ng-zorro-antd/message'; | |||||
| import { NzNotificationService } from 'ng-zorro-antd/notification'; | |||||
| import { Observable, of, throwError, mergeMap, catchError } from 'rxjs'; | |||||
| import { ReThrowHttpError, checkStatus, getAdditionalHeaders, toLogin } from './helper'; | |||||
| import { tryRefreshToken } from './refresh-token'; | |||||
| function handleData(injector: Injector, ev: HttpResponseBase, req: HttpRequest<any>, next: HttpHandlerFn): Observable<any> { | |||||
| checkStatus(injector, ev); | |||||
| let num = 0; | |||||
| // 业务处理:一些通用操作 | |||||
| switch (ev.status) { | |||||
| case 200: | |||||
| const response = ev as any; | |||||
| num++; | |||||
| if (response.body && response.body.code === 401) { | |||||
| if (environment.api.refreshTokenEnabled && environment.api.refreshTokenType === 're-request') { | |||||
| return tryRefreshToken(injector, ev, req, next); | |||||
| } | |||||
| toLogin(injector); | |||||
| } | |||||
| if (response.body && response.body.code === 403) { | |||||
| injector.get(NzMessageService).error('没有权限,请联系管理员授权'); | |||||
| } | |||||
| // | |||||
| break; | |||||
| case 401: | |||||
| if (environment.api.refreshTokenEnabled && environment.api.refreshTokenType === 're-request') { | |||||
| return tryRefreshToken(injector, ev, req, next); | |||||
| } | |||||
| toLogin(injector); | |||||
| break; | |||||
| case 403: | |||||
| case 404: | |||||
| case 500: | |||||
| 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 if ((ev as unknown as ReThrowHttpError)._throw === true) { | |||||
| return throwError(() => (ev as unknown as ReThrowHttpError).body); | |||||
| } else { | |||||
| return of(ev); | |||||
| } | |||||
| } | |||||
| export const defaultInterceptor: HttpInterceptorFn = (req, next) => { | |||||
| // 统一加上服务端前缀 | |||||
| let url = req.url; | |||||
| if (!req.context.get(IGNORE_BASE_URL) && !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: getAdditionalHeaders(req.headers) }); | |||||
| const injector = inject(Injector); | |||||
| const notification = inject(NzNotificationService); | |||||
| return next(newReq).pipe( | |||||
| mergeMap(ev => { | |||||
| // 允许统一对请求错误处理 | |||||
| if (ev instanceof HttpResponseBase) { | |||||
| return handleData(injector, ev, newReq, next); | |||||
| } | |||||
| // 若一切都正常,则后续操作 | |||||
| return of(ev); | |||||
| }), | |||||
| catchError((err: HttpErrorResponse) => handleData(injector, err, newReq, next)) | |||||
| ); | |||||
| }; | |||||
| @@ -0,0 +1,68 @@ | |||||
| import { HttpErrorResponse, HttpHeaders, HttpResponseBase } from '@angular/common/http'; | |||||
| import { Injector, inject } from '@angular/core'; | |||||
| import { Router } from '@angular/router'; | |||||
| import { DA_SERVICE_TOKEN } from '@delon/auth'; | |||||
| import { ALAIN_I18N_TOKEN } from '@delon/theme'; | |||||
| import { NzNotificationService } from 'ng-zorro-antd/notification'; | |||||
| export interface ReThrowHttpError { | |||||
| body: any; | |||||
| _throw: true; | |||||
| } | |||||
| export const CODEMESSAGE: { [key: number]: string } = { | |||||
| 200: '服务器成功返回请求的数据。', | |||||
| 201: '新建或修改数据成功。', | |||||
| 202: '一个请求已经进入后台排队(异步任务)。', | |||||
| 204: '删除数据成功。', | |||||
| 400: '发出的请求有错误,服务器没有进行新建或修改数据的操作。', | |||||
| 401: '用户没有权限(令牌、用户名、密码错误)。', | |||||
| 403: '用户得到授权,但是访问是被禁止的。', | |||||
| 404: '发出的请求针对的是不存在的记录,服务器没有进行操作。', | |||||
| 406: '请求的格式不可得。', | |||||
| 410: '请求的资源被永久删除,且不会再得到的。', | |||||
| 422: '当创建一个对象时,发生一个验证错误。', | |||||
| 500: '服务器发生错误,请检查服务器。', | |||||
| 502: '网关错误。', | |||||
| 503: '服务不可用,服务器暂时过载或维护。', | |||||
| 504: '网关超时。' | |||||
| }; | |||||
| export function goTo(injector: Injector, url: string): void { | |||||
| setTimeout(() => injector.get(Router).navigateByUrl(url)); | |||||
| } | |||||
| export function toLogin(injector: Injector): void { | |||||
| // injector.get(NzNotificationService).error(`未登录或登录已过期,请重新登录。`, ``); | |||||
| goTo(injector, injector.get(DA_SERVICE_TOKEN).login_url!); | |||||
| } | |||||
| export function getAdditionalHeaders(headers?: HttpHeaders): { [name: string]: string } { | |||||
| const res: { [name: string]: string } = {}; | |||||
| const lang = inject(ALAIN_I18N_TOKEN).currentLang; | |||||
| if (!headers?.has('Accept-Language') && lang) { | |||||
| res['Accept-Language'] = lang; | |||||
| } | |||||
| if (headers?.has('Token')) { | |||||
| res['Authorization'] = `Bearer ${headers.get('Token')}`; | |||||
| } | |||||
| return res; | |||||
| } | |||||
| export function checkStatus(injector: Injector, ev: HttpResponseBase): void { | |||||
| return; | |||||
| // if ((ev.status >= 200 && ev.status < 300) || ev.status === 401) { | |||||
| // return; | |||||
| // } | |||||
| // if (ev.status == 0) { | |||||
| // return; | |||||
| // } | |||||
| // const errortext = CODEMESSAGE[ev.status] || ev.statusText; | |||||
| // if (ev instanceof HttpErrorResponse && ev.status === 403) { | |||||
| // injector.get(NzNotificationService).error(`${ev.error.error.code} - ${ev.error.error.message}`, errortext); | |||||
| // } else { | |||||
| // injector.get(NzNotificationService).error(`请求错误 ${ev.status}: ${ev.url}`, errortext); | |||||
| // } | |||||
| } | |||||
| @@ -0,0 +1,2 @@ | |||||
| export { provideBindAuthRefresh } from './refresh-token'; | |||||
| export * from './default.interceptor'; | |||||
| @@ -0,0 +1,103 @@ | |||||
| import { HttpClient, HttpHandlerFn, HttpRequest, HttpResponseBase } from '@angular/common/http'; | |||||
| import { APP_INITIALIZER, Injector, Provider } from '@angular/core'; | |||||
| import { DA_SERVICE_TOKEN } from '@delon/auth'; | |||||
| import { BehaviorSubject, Observable, catchError, filter, switchMap, take, throwError } from 'rxjs'; | |||||
| import { toLogin } from './helper'; | |||||
| let refreshToking = false; | |||||
| let refreshToken$: BehaviorSubject<any> = new BehaviorSubject<any>(null); | |||||
| /** | |||||
| * 重新附加新 Token 信息 | |||||
| * | |||||
| * > 由于已经发起的请求,不会再走一遍 `@delon/auth` 因此需要结合业务情况重新附加新的 Token | |||||
| */ | |||||
| function reAttachToken(injector: Injector, req: HttpRequest<any>): HttpRequest<any> { | |||||
| const token = injector.get(DA_SERVICE_TOKEN).get()?.token; | |||||
| return req.clone({ | |||||
| setHeaders: { | |||||
| token: `Bearer ${token}` | |||||
| } | |||||
| }); | |||||
| } | |||||
| function refreshTokenRequest(injector: Injector): Observable<any> { | |||||
| const model = injector.get(DA_SERVICE_TOKEN).get(); | |||||
| return injector.get(HttpClient).post(`/api/auth/refresh`, { headers: { refresh_token: model?.['refresh_token'] || '' } }); | |||||
| } | |||||
| /** | |||||
| * 刷新Token方式一:使用 401 重新刷新 Token | |||||
| */ | |||||
| export function tryRefreshToken(injector: Injector, ev: HttpResponseBase, req: HttpRequest<any>, next: HttpHandlerFn): Observable<any> { | |||||
| // 1、若请求为刷新Token请求,表示来自刷新Token可以直接跳转登录页 | |||||
| if ([`/api/auth/refresh`].some(url => req.url.includes(url))) { | |||||
| toLogin(injector); | |||||
| return throwError(() => ev); | |||||
| } | |||||
| // 2、如果 `refreshToking` 为 `true` 表示已经在请求刷新 Token 中,后续所有请求转入等待状态,直至结果返回后再重新发起请求 | |||||
| if (refreshToking) { | |||||
| return refreshToken$.pipe( | |||||
| filter(v => !!v), | |||||
| take(1), | |||||
| switchMap(() => next(reAttachToken(injector, req))) | |||||
| ); | |||||
| } | |||||
| // 3、尝试调用刷新 Token | |||||
| refreshToking = true; | |||||
| refreshToken$.next(null); | |||||
| return refreshTokenRequest(injector).pipe( | |||||
| switchMap(res => { | |||||
| // 通知后续请求继续执行 | |||||
| refreshToking = false; | |||||
| refreshToken$.next(res); | |||||
| // 重新保存新 token | |||||
| injector.get(DA_SERVICE_TOKEN).set(res); | |||||
| // 重新发起请求 | |||||
| return next(reAttachToken(injector, req)); | |||||
| }), | |||||
| catchError(err => { | |||||
| refreshToking = false; | |||||
| toLogin(injector); | |||||
| return throwError(() => err); | |||||
| }) | |||||
| ); | |||||
| } | |||||
| function buildAuthRefresh(injector: Injector) { | |||||
| const tokenSrv = injector.get(DA_SERVICE_TOKEN); | |||||
| tokenSrv.refresh | |||||
| .pipe( | |||||
| filter(() => !refreshToking), | |||||
| switchMap(res => { | |||||
| console.log(res); | |||||
| refreshToking = true; | |||||
| return refreshTokenRequest(injector); | |||||
| }) | |||||
| ) | |||||
| .subscribe({ | |||||
| next: res => { | |||||
| // TODO: Mock expired value | |||||
| res.expired = +new Date() + 1000 * 60 * 5; | |||||
| refreshToking = false; | |||||
| tokenSrv.set(res); | |||||
| }, | |||||
| error: () => toLogin(injector) | |||||
| }); | |||||
| } | |||||
| /** | |||||
| * 刷新Token方式二:使用 `@delon/auth` 的 `refresh` 接口,需要在 `app.config.ts` 中注册 `provideBindAuthRefresh` | |||||
| */ | |||||
| export function provideBindAuthRefresh(): Provider[] { | |||||
| return [ | |||||
| { | |||||
| provide: APP_INITIALIZER, | |||||
| useFactory: (injector: Injector) => () => buildAuthRefresh(injector), | |||||
| deps: [Injector], | |||||
| multi: true | |||||
| } | |||||
| ]; | |||||
| } | |||||
| @@ -0,0 +1,36 @@ | |||||
| import { RouteReuseStrategy, DetachedRouteHandle, ActivatedRouteSnapshot } from '@angular/router'; | |||||
| export class CustomRouteReuseStrategy implements RouteReuseStrategy { | |||||
| private storedRoutes: any = new Map<string, DetachedRouteHandle>(); | |||||
| // 判断是否要分离当前路由的组件 | |||||
| shouldDetach(route: ActivatedRouteSnapshot): boolean { | |||||
| return route.data['reuse'] === true; | |||||
| } | |||||
| // 存储分离的组件 | |||||
| store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle): void { | |||||
| if (handle) { | |||||
| this.storedRoutes.set(this.getRouteKey(route), handle); | |||||
| } | |||||
| } | |||||
| // 判断是否要重新挂载之前保存的组件 | |||||
| shouldAttach(route: ActivatedRouteSnapshot): boolean { | |||||
| return this.storedRoutes.has(this.getRouteKey(route)); | |||||
| } | |||||
| // 获取之前保存的组件 | |||||
| retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle { | |||||
| return this.storedRoutes.get(this.getRouteKey(route)) || null; | |||||
| } | |||||
| // 决定是否要复用当前路由 | |||||
| shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean { | |||||
| return future.routeConfig === curr.routeConfig && JSON.stringify(future.params) === JSON.stringify(curr.params); | |||||
| } | |||||
| private getRouteKey(route: ActivatedRouteSnapshot): string { | |||||
| return route.pathFromRoot.map(r => r.url.map(u => u.path).join('/')).join('/'); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,39 @@ | |||||
| /* eslint-disable prettier/prettier */ | |||||
| import { inject } from '@angular/core'; | |||||
| import { Router, CanActivateFn } from '@angular/router'; | |||||
| import { MenuService } from '@delon/theme'; | |||||
| import { Observable } from 'rxjs'; | |||||
| /** | |||||
| * Dynamically load the start page | |||||
| * | |||||
| * 动态加载启动页 | |||||
| */ | |||||
| export const startPageGuard: CanActivateFn = (a, b): boolean | Observable<boolean> => { | |||||
| const menuService = inject(MenuService); | |||||
| const router = inject(Router); | |||||
| const url: string = a.url.map(seg => seg.path).join('/'); | |||||
| if (b.url == "/" || b.url == '/datav/dashboard-v') { | |||||
| return true; | |||||
| } | |||||
| const menu = findNodeByPath(menuService['data'], b.url); | |||||
| return true; | |||||
| }; | |||||
| const findNodeByPath = (data: any, path: any) => { | |||||
| for (let item of data) { | |||||
| if (item.link === path) { | |||||
| return item; | |||||
| } else if (item.children && item.children.length > 0) { | |||||
| let result: any = findNodeByPath(item.children, path); | |||||
| if (result) { | |||||
| return result; | |||||
| } | |||||
| } | |||||
| } | |||||
| return null; | |||||
| } | |||||
| @@ -0,0 +1,178 @@ | |||||
| import { HttpClient } from '@angular/common/http'; | |||||
| import { APP_INITIALIZER, Injectable, Provider, inject } from '@angular/core'; | |||||
| import { Router } from '@angular/router'; | |||||
| import { ACLService } from '@delon/acl'; | |||||
| import { DA_SERVICE_TOKEN } from '@delon/auth'; | |||||
| import { ALAIN_I18N_TOKEN, MenuService, SettingsService, TitleService } from '@delon/theme'; | |||||
| import type { NzSafeAny } from 'ng-zorro-antd/core/types'; | |||||
| import { Observable, zip, of, catchError, map } from 'rxjs'; | |||||
| /** | |||||
| * Used for application startup | |||||
| * Generally used to get the basic data of the application, like: Menu Data, User Data, etc. | |||||
| */ | |||||
| export function provideStartup(): Provider[] { | |||||
| return [ | |||||
| StartupService, | |||||
| { | |||||
| provide: APP_INITIALIZER, | |||||
| useFactory: (startupService: StartupService) => () => startupService.load(), | |||||
| deps: [StartupService], | |||||
| multi: true | |||||
| } | |||||
| ]; | |||||
| } | |||||
| import { environment } from '@env/environment'; | |||||
| @Injectable() | |||||
| export class StartupService { | |||||
| private menuService = inject(MenuService); | |||||
| private settingService = inject(SettingsService); | |||||
| private httpClient = inject(HttpClient); | |||||
| private router = inject(Router); | |||||
| private initData$ = zip( | |||||
| this.httpClient.get(`${environment.api['fuelUrl']}auseftApi/getInfo`), | |||||
| this.httpClient.get(`${environment.api['fuelUrl']}auseftApi/getRouters`) | |||||
| ).pipe( | |||||
| catchError((res: NzSafeAny) => { | |||||
| console.warn(`StartupService.load: Network request failed`, res); | |||||
| setTimeout(() => this.router.navigateByUrl(`/passport/login`)); | |||||
| return of([res]); | |||||
| }) | |||||
| ); | |||||
| private handleAppData(res: NzSafeAny): void { | |||||
| const app: any = { | |||||
| name: `NG-ALAIN`, | |||||
| description: `NG-ZORRO admin panel front-end framework` | |||||
| }; | |||||
| this.settingService.setApp(app); | |||||
| if (res.length == 2) { | |||||
| this.addConvertedMenu(res[1]); | |||||
| } | |||||
| } | |||||
| private addConvertedMenu(res: any) { | |||||
| if (res && res.code == 200) { | |||||
| if (res.data && res.data.length && res.data[0]['children'][0].meta.title === '个人中心') { | |||||
| res.data[0]['children'][0]['children'].push({ | |||||
| component: 'Layout', | |||||
| hidden: true, | |||||
| meta: { title: '待办详情', icon: '3column', noCache: false, link: null }, | |||||
| name: '/workflow/task-framework', | |||||
| path: '/workflow/task-framework' | |||||
| }); | |||||
| console.log(res.data[0]['children'][0]['children'], 'ssss'); | |||||
| } | |||||
| this.processData(res.data, 0); | |||||
| } | |||||
| } | |||||
| objModel: any = { | |||||
| id: 0, | |||||
| menuCodg: '/', | |||||
| menuName: '智慧燃料', | |||||
| text: '智慧燃料', | |||||
| menuType: 1, | |||||
| menuCatgeCodg: '', | |||||
| menuCatgeName: '', | |||||
| i18n: null, | |||||
| group: false, | |||||
| hideInBreadcrumbFlag: false, | |||||
| visble: false, | |||||
| shortcutFlag: false, | |||||
| link: '//', | |||||
| externalLink: null, | |||||
| target: null, | |||||
| icon: { | |||||
| type: 'iconfont', | |||||
| value: 'icon-yonghu' | |||||
| }, | |||||
| openFlag: false, | |||||
| valiFalg: false, | |||||
| hideFlag: false, | |||||
| reuseFlag: false, | |||||
| srtno: 0, | |||||
| parentId: 0, | |||||
| acl: null, | |||||
| applicationId: null, | |||||
| creationTime: null, | |||||
| stas: 0, | |||||
| stasName: null | |||||
| }; | |||||
| private processData(data: any[], level: number) { | |||||
| data.forEach( | |||||
| (item: { name: any; path: any; hidden: any; meta: any; component: any; redirect: any; alwaysShow: any; children: any[] }) => { | |||||
| const newItem: any = { | |||||
| ...this.objModel, | |||||
| menuCode: item?.path, | |||||
| menuName: item?.meta?.title, | |||||
| text: item?.meta?.title, | |||||
| link: item?.path | |||||
| // icon: item?.meta?.icon, | |||||
| }; | |||||
| if (item.children) { | |||||
| newItem.children = this.processChildren(item.children, level + 1); | |||||
| } else { | |||||
| newItem.isLeaf = true; | |||||
| } | |||||
| this.menuService.add(Array.of(newItem)); | |||||
| } | |||||
| ); | |||||
| } | |||||
| private processChildren(children: any[], level: number): any[] { | |||||
| return children.map( | |||||
| (child: { name: any; path: any; hidden: any; meta: any; component: any; redirect: any; alwaysShow: any; children: any[] }) => { | |||||
| if (child.meta.icon.indexOf('anticon') != -1) { | |||||
| this.objModel.icon = child.meta.icon; | |||||
| } else { | |||||
| this.objModel.icon = { | |||||
| type: 'iconfont', | |||||
| value: `icon-${child.meta.icon}`, | |||||
| iconfont: `icon-${child.meta.icon}`, | |||||
| color: 'red' | |||||
| }; | |||||
| } | |||||
| const newChild: any = { | |||||
| ...this.objModel, | |||||
| menuCode: child?.path, | |||||
| menuName: child?.meta?.title, | |||||
| text: child?.meta?.title, | |||||
| link: child?.path, | |||||
| hide: child?.hidden | |||||
| }; | |||||
| if (child.children) { | |||||
| newChild.children = this.processChildren(child.children, level + 1); | |||||
| } else { | |||||
| newChild.isLeaf = true; | |||||
| } | |||||
| return newChild; | |||||
| } | |||||
| ); | |||||
| } | |||||
| private viaHttp(): Observable<void> { | |||||
| return this.initData$.pipe( | |||||
| map((res: NzSafeAny) => { | |||||
| window.sessionStorage.setItem('info', JSON.stringify(res[0])); | |||||
| res.forEach((data: any) => { | |||||
| if (data.code === 401) { | |||||
| console.log('401'); | |||||
| this.router.navigateByUrl(`/passport/login`); | |||||
| } | |||||
| }); | |||||
| this.handleAppData(res); | |||||
| }) | |||||
| ); | |||||
| } | |||||
| load(): Observable<void> { | |||||
| return this.viaHttp(); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,80 @@ | |||||
| import moment from 'moment'; | |||||
| export class AppUtils { | |||||
| public static dateFormat(date: Date, format: string): any { | |||||
| if (!date) { | |||||
| return null; | |||||
| } | |||||
| return moment(date).format(format); | |||||
| } | |||||
| public static convertDate(str: string, format?: string, substring?: boolean) { | |||||
| if (str) { | |||||
| const data = moment(substring && format ? str.substring(0, format.length) : str, format); | |||||
| if (data.isValid()) { | |||||
| return data.toDate(); | |||||
| } | |||||
| } | |||||
| return null; | |||||
| } | |||||
| public static calAge(birth: string | Date, format?: string) { | |||||
| let birthday; | |||||
| if (typeof birth == 'string') { | |||||
| birthday = this.convertDate(birth, format); | |||||
| } else { | |||||
| birthday = birth; | |||||
| } | |||||
| if (birthday) { | |||||
| const now = new Date(); | |||||
| let age = now.getFullYear() - birthday.getFullYear(); | |||||
| if (now.getMonth() < birthday.getMonth() || (now.getMonth() == birthday.getMonth() && now.getDay() < birthday.getDay())) { | |||||
| age--; | |||||
| } | |||||
| return age < 0 ? 0 : age; | |||||
| } | |||||
| return null; | |||||
| } | |||||
| public static isNull(val: any) { | |||||
| if (val === null || val === undefined || val === '') { | |||||
| return true; | |||||
| } | |||||
| return false; | |||||
| } | |||||
| public static convertToCheck(val: any) { | |||||
| if (val) { | |||||
| return '√'; | |||||
| } | |||||
| return ''; | |||||
| } | |||||
| public static convertToBool(val: any) { | |||||
| if (val) { | |||||
| return true; | |||||
| } | |||||
| return false; | |||||
| } | |||||
| public static boolConvertToNumber(val: any) { | |||||
| if (val) { | |||||
| return 1; | |||||
| } | |||||
| return 0; | |||||
| } | |||||
| public static convertEnumToList(obj: any) { | |||||
| const list: any = []; | |||||
| if (obj) { | |||||
| for (const key in obj) { | |||||
| const val = obj[key]; | |||||
| list.push({ value: Number.parseInt(key), label: val }); | |||||
| } | |||||
| } | |||||
| return list; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,140 @@ | |||||
| import { AbstractControl, ValidatorFn, Validators } from '@angular/forms'; | |||||
| import { _Validators } from '@delon/util'; | |||||
| import { MESSAGE_UTIL } from 'src/app/conf/message'; | |||||
| import { AppUtils } from './app-utils'; | |||||
| export type AppValidatorsOptions = { errorTip: string } & Record<string, any>; | |||||
| export type AppValidatorsErrors = Record<string, AppValidatorsOptions>; | |||||
| export class AppValidators { | |||||
| static idCard(id: string, ...params: string[]): ValidatorFn { | |||||
| const messageParams = params.length === 0 ? [] : params; | |||||
| return (control: AbstractControl): AppValidatorsErrors | null => { | |||||
| if (AppUtils.isNull(control.value)) { | |||||
| return {}; | |||||
| } | |||||
| if (_Validators.idCard(control) === null) { | |||||
| return null; | |||||
| } | |||||
| return { idcard: { errorTip: MESSAGE_UTIL.getMessage(id, messageParams) } }; | |||||
| }; | |||||
| } | |||||
| static maxLength(maxLength: number, id: string, ...params: string[]): ValidatorFn { | |||||
| const messageParams = params.length === 0 ? [maxLength.toString()] : params; | |||||
| return (control: AbstractControl): AppValidatorsErrors | null => { | |||||
| if (Validators.maxLength(maxLength)(control) === null) { | |||||
| return null; | |||||
| } | |||||
| return { maxlength: { errorTip: MESSAGE_UTIL.getMessage(id, messageParams) } }; | |||||
| }; | |||||
| } | |||||
| static minLength(minLength: number, id: string, ...params: string[]): ValidatorFn { | |||||
| const messageParams = params.length === 0 ? [minLength.toString()] : params; | |||||
| return (control: AbstractControl): AppValidatorsErrors | null => { | |||||
| if (Validators.minLength(minLength)(control) === null) { | |||||
| return null; | |||||
| } | |||||
| return { minLength: { errorTip: MESSAGE_UTIL.getMessage(id, messageParams) } }; | |||||
| }; | |||||
| } | |||||
| static required(id: string, ...params: string[]): ValidatorFn { | |||||
| const messageParams = params.length === 0 ? [] : params; | |||||
| return (control: AbstractControl): AppValidatorsErrors | null => { | |||||
| if (Validators.required(control) === null) { | |||||
| return null; | |||||
| } | |||||
| return { required: { errorTip: MESSAGE_UTIL.getMessage(id, messageParams) } }; | |||||
| }; | |||||
| } | |||||
| static email(id: string, ...params: string[]): ValidatorFn { | |||||
| const messageParams = params.length === 0 ? [] : params; | |||||
| return (control: AbstractControl): AppValidatorsErrors | null => { | |||||
| if (Validators.required(control) !== null) { | |||||
| return { required: { errorTip: MESSAGE_UTIL.getMessage(id, messageParams) } }; | |||||
| } | |||||
| if (Validators.email(control) !== null) { | |||||
| return { email: { errorTip: MESSAGE_UTIL.getMessage(id, messageParams) } }; | |||||
| } | |||||
| return null; | |||||
| }; | |||||
| } | |||||
| static mobile(id: string, ...params: string[]): ValidatorFn { | |||||
| const messageParams = params.length === 0 ? [] : params; | |||||
| return (control: AbstractControl): AppValidatorsErrors | null => { | |||||
| // if (!control.value) { | |||||
| // return null; | |||||
| // } | |||||
| if (Validators.required(control) !== null) { | |||||
| return { required: { errorTip: MESSAGE_UTIL.getMessage(id, messageParams) } }; | |||||
| } | |||||
| if (_Validators.mobile(control) !== null) { | |||||
| // return null; | |||||
| return { mobile: { errorTip: MESSAGE_UTIL.getMessage(id, messageParams) } }; | |||||
| } | |||||
| return null; | |||||
| }; | |||||
| } | |||||
| static pattern(patternStr: RegExp, id: string, ...params: string[]): ValidatorFn { | |||||
| const messageParams = params.length === 0 ? [] : params; | |||||
| return (control: AbstractControl): AppValidatorsErrors | null => { | |||||
| patternStr.lastIndex = 0; | |||||
| if (Validators.pattern(patternStr)(control) === null) { | |||||
| return null; | |||||
| } | |||||
| return { pattern: { errorTip: MESSAGE_UTIL.getMessage(id, messageParams) } }; | |||||
| }; | |||||
| } | |||||
| static strongPassword(id: string, ...params: string[]): ValidatorFn { | |||||
| const messageParams = params.length === 0 ? [] : params; | |||||
| return (control: AbstractControl): AppValidatorsErrors | null => { | |||||
| if (!control.value) { | |||||
| return null; | |||||
| } | |||||
| const value = control.value; | |||||
| // 检查密码长度 | |||||
| if (value.length < 8) { | |||||
| return { strongPassword: { errorTip: MESSAGE_UTIL.getMessage(id, messageParams) } }; | |||||
| } | |||||
| // 检查是否包含大写字母 | |||||
| if (!/[A-Z]/.test(value)) { | |||||
| return { strongPassword: { errorTip: MESSAGE_UTIL.getMessage(id, messageParams) } }; | |||||
| } | |||||
| // 检查是否包含小写字母 | |||||
| if (!/[a-z]/.test(value)) { | |||||
| return { strongPassword: { errorTip: MESSAGE_UTIL.getMessage(id, messageParams) } }; | |||||
| } | |||||
| // 检查是否包含数字 | |||||
| if (!/[0-9]/.test(value)) { | |||||
| return { strongPassword: { errorTip: MESSAGE_UTIL.getMessage(id, messageParams) } }; | |||||
| } | |||||
| // 检查是否包含特殊字符 | |||||
| if (!/[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]/.test(value)) { | |||||
| return { strongPassword: { errorTip: MESSAGE_UTIL.getMessage(id, messageParams) } }; | |||||
| } | |||||
| return null; | |||||
| }; | |||||
| } | |||||
| static other(fn: (control: AbstractControl) => AppValidatorsErrors | null): ValidatorFn { | |||||
| return (control: AbstractControl): AppValidatorsErrors | null => { | |||||
| return fn(control); | |||||
| }; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,192 @@ | |||||
| import { EventEmitter, Injectable, TemplateRef, Type } from '@angular/core'; | |||||
| import { FormArray, FormGroup } from '@angular/forms'; | |||||
| import { FormProperty, retrieveSchema } from '@delon/form'; | |||||
| import { _HttpClient } from '@delon/theme'; | |||||
| import { NzSafeAny } from 'ng-zorro-antd/core/types'; | |||||
| import { NzMessageService } from 'ng-zorro-antd/message'; | |||||
| import { ModalButtonOptions, NzModalService } from 'ng-zorro-antd/modal'; | |||||
| import { MESSAGE_UTIL } from '../../conf/message'; | |||||
| declare var FakeServer: any; | |||||
| @Injectable({ | |||||
| providedIn: 'root' | |||||
| }) | |||||
| export class BaseService { | |||||
| public drawerWidth = 550; | |||||
| deptmentServiceUrl = ''; // | |||||
| constructor( | |||||
| private http: _HttpClient, | |||||
| private modalService: NzModalService, | |||||
| private message: NzMessageService | |||||
| ) {} | |||||
| hasError(...args: Array<FormGroup | FormProperty | null>): boolean { | |||||
| const result = this.hasErrorBase(args); | |||||
| return result; | |||||
| } | |||||
| hasErrorBase(args: Array<FormGroup | FormProperty | null>): boolean { | |||||
| let result = false; | |||||
| if (!args) { | |||||
| return result; | |||||
| } | |||||
| args.forEach((val: any) => { | |||||
| if (val instanceof FormGroup) { | |||||
| for (const key in val.controls) { | |||||
| const ctl = val.controls[key]; | |||||
| if (ctl instanceof FormGroup) { | |||||
| result = this.hasErrorBase([ctl]) || result; | |||||
| } else if (ctl instanceof FormArray) { | |||||
| (ctl as FormArray).controls.forEach(c => { | |||||
| c.markAsDirty(); | |||||
| c.updateValueAndValidity(); | |||||
| result = c.invalid || result; | |||||
| }); | |||||
| } else { | |||||
| ctl.markAsDirty(); | |||||
| ctl.updateValueAndValidity(); | |||||
| result = ctl.invalid || result; | |||||
| } | |||||
| } | |||||
| } else if (val instanceof FormProperty) { | |||||
| const properties = val.root.properties as FormProperty[]; | |||||
| if (properties) { | |||||
| for (const key in properties) { | |||||
| const ctl = properties[key]; | |||||
| if (ctl instanceof FormProperty) { | |||||
| ctl.updateValueAndValidity(); | |||||
| result = ctl.valid || result; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| }); | |||||
| return result; | |||||
| } | |||||
| public getMessage(messageId: string, ...params: string[]): string { | |||||
| return this.getMessageBase(messageId, params); | |||||
| } | |||||
| private getMessageBase(messageId: string, params: string[]): string { | |||||
| return MESSAGE_UTIL.getMessage(messageId, params); | |||||
| } | |||||
| /** | |||||
| * ocean 2023-4-17 | |||||
| * message提示框 | |||||
| */ | |||||
| public showMessage(state: { messageId: string; content?: string }) { | |||||
| if (state.content) { | |||||
| this.message.info(state.content); | |||||
| } else { | |||||
| this.message.info(this.getMessage(state.messageId)); | |||||
| } | |||||
| } | |||||
| public showConfirm(state: { title?: string; message?: string; okCallback?: () => void; cancelCallback?: () => void }) { | |||||
| console.log(state.message, ' state.message'); | |||||
| this.modalService.create({ | |||||
| nzTitle: state.title, | |||||
| nzContent: state.message, | |||||
| nzClosable: false, | |||||
| nzBodyStyle: { 'font-size': '1rem' }, | |||||
| nzFooter: [ | |||||
| { | |||||
| label: '确定', | |||||
| type: 'primary', | |||||
| onClick: e => { | |||||
| let del: any = state; | |||||
| setTimeout(() => { | |||||
| del.okCallback(); | |||||
| }, 100); | |||||
| this.modalService.ngOnDestroy(); | |||||
| } | |||||
| }, | |||||
| { | |||||
| label: '取消', | |||||
| onClick: () => { | |||||
| this.modalService.ngOnDestroy(); | |||||
| console.log(2); | |||||
| } | |||||
| } | |||||
| ], | |||||
| nzOnOk: () => { | |||||
| if (state.okCallback) { | |||||
| state.okCallback(); | |||||
| } | |||||
| }, | |||||
| nzOnCancel: () => { | |||||
| if (state.cancelCallback) { | |||||
| state.cancelCallback(); | |||||
| } | |||||
| } | |||||
| }); | |||||
| } | |||||
| public showModal(config: AppModalConfig) { | |||||
| let width = 600; | |||||
| switch (config.widthClass) { | |||||
| case 'xxl': | |||||
| width = 1200; | |||||
| break; | |||||
| case 'xl': | |||||
| width = 1000; | |||||
| break; | |||||
| case 'lg': | |||||
| width = 800; | |||||
| break; | |||||
| case 'sm': | |||||
| width = 600; | |||||
| break; | |||||
| case 'xs': | |||||
| width = 400; | |||||
| break; | |||||
| } | |||||
| const modal = this.modalService.create({ | |||||
| nzTitle: config.title, | |||||
| nzContent: config.component, | |||||
| nzData: config.componentParams, | |||||
| nzClosable: true, | |||||
| nzWidth: width, | |||||
| nzOnOk: config.okCallBack, | |||||
| nzFooter: config.footer, | |||||
| nzStyle: config.style, | |||||
| nzMaskClosable: false | |||||
| }); | |||||
| return modal; | |||||
| } | |||||
| public async post(url: string, method: 'put' | 'post' | 'get' | 'delete', params?: any) { | |||||
| switch (method) { | |||||
| case 'put': | |||||
| return this.http.put(url, params).subscribe(res => {}); | |||||
| break; | |||||
| case 'post': | |||||
| return this.http.post(url, params).subscribe(res => {}); | |||||
| break; | |||||
| case 'get': | |||||
| return this.http.get(url, params).subscribe(res => {}); | |||||
| break; | |||||
| case 'delete': | |||||
| return this.http.delete(url, params).subscribe(res => {}); | |||||
| break; | |||||
| } | |||||
| } | |||||
| } | |||||
| export class AppModalConfig { | |||||
| title?: string | TemplateRef<{}>; | |||||
| component?: string | TemplateRef<NzSafeAny> | Type<any>; | |||||
| componentParams?: any; | |||||
| okCallBack?: () => void; | |||||
| widthClass?: 'xxl' | 'xl' | 'lg' | 'sm' | 'xs'; | |||||
| footer?: string | TemplateRef<{}> | Array<ModalButtonOptions<any>> | null; | |||||
| style?: Object; | |||||
| } | |||||
| @@ -0,0 +1,9 @@ | |||||
| import { Pipe, PipeTransform } from '@angular/core'; | |||||
| @Pipe({ name: 'nl2br' }) | |||||
| export class Nl2BrPipe implements PipeTransform { | |||||
| transform(value: string): string { | |||||
| if (!value) return value; | |||||
| return value.replace(/\n/g, '<br/>'); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,43 @@ | |||||
| import { RouteReuseStrategy, DetachedRouteHandle, ActivatedRouteSnapshot } from '@angular/router'; | |||||
| export class KeepAliveReuseStrategy implements RouteReuseStrategy { | |||||
| private cachedRoutes: { [key: string]: DetachedRouteHandle } = {}; | |||||
| // 是否允许复用路由(默认逻辑) | |||||
| shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean { | |||||
| return future.routeConfig === curr.routeConfig; | |||||
| } | |||||
| // 是否允许分离路由(标记为keepAlive的路由) | |||||
| shouldDetach(route: ActivatedRouteSnapshot): boolean { | |||||
| return route.data['keepAlive'] || false; | |||||
| } | |||||
| // 存储分离的路由实例 | |||||
| store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle): void { | |||||
| const path = this.getRoutePath(route); | |||||
| this.cachedRoutes[path] = handle; | |||||
| } | |||||
| // 是否允许附加缓存的路由 | |||||
| shouldAttach(route: ActivatedRouteSnapshot): boolean { | |||||
| const path = this.getRoutePath(route); | |||||
| return !!this.cachedRoutes[path]; | |||||
| } | |||||
| // 获取缓存的实例 | |||||
| retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle | null { | |||||
| const path = this.getRoutePath(route); | |||||
| return this.cachedRoutes[path] || null; | |||||
| } | |||||
| // 清理指定缓存 | |||||
| clearCache(path: string): void { | |||||
| delete this.cachedRoutes[path]; | |||||
| } | |||||
| // 获取路由唯一标识(支持带参数的路由) | |||||
| private getRoutePath(route: ActivatedRouteSnapshot): string { | |||||
| return route.routeConfig?.path || ''; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1 @@ | |||||
| [Document](https://ng-alain.com/theme/layout-default) | |||||
| @@ -0,0 +1,34 @@ | |||||
| import { ChangeDetectionStrategy, Component, HostListener, inject } from '@angular/core'; | |||||
| import { I18nPipe } from '@delon/theme'; | |||||
| import { NzIconModule } from 'ng-zorro-antd/icon'; | |||||
| 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> | |||||
| 清理本地缓存 | |||||
| `, | |||||
| host: { | |||||
| '[class.flex-1]': 'true' | |||||
| }, | |||||
| changeDetection: ChangeDetectionStrategy.OnPush, | |||||
| standalone: true, | |||||
| imports: [NzIconModule, I18nPipe] | |||||
| }) | |||||
| export class HeaderClearStorageComponent { | |||||
| private readonly modalSrv = inject(NzModalService); | |||||
| private readonly messageSrv = inject(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,40 @@ | |||||
| import { CommonModule } from '@angular/common'; | |||||
| import { ChangeDetectionStrategy, Component, HostListener, OnInit, inject } from '@angular/core'; | |||||
| import { I18nPipe } from '@delon/theme'; | |||||
| import { SHARED_IMPORTS } from '@shared'; | |||||
| import { NzIconModule } from 'ng-zorro-antd/icon'; | |||||
| import { NzMessageService } from 'ng-zorro-antd/message'; | |||||
| import { NzModalService } from 'ng-zorro-antd/modal'; | |||||
| @Component({ | |||||
| selector: 'header-date', | |||||
| template: ` | |||||
| <div class="data-container"> | |||||
| {{ currentTime | date: 'yyyy-MM-dd HH:mm:ss' }} | |||||
| </div> | |||||
| `, | |||||
| styles: ` | |||||
| .data-container { | |||||
| align-items: center; | |||||
| color: white; | |||||
| padding-left: 1rem; | |||||
| font-size: 15px; | |||||
| } | |||||
| `, | |||||
| standalone: true, | |||||
| imports: [CommonModule, ...SHARED_IMPORTS] | |||||
| }) | |||||
| export class HeaderDateComponent implements OnInit { | |||||
| currentTime: any; | |||||
| ngOnInit(): void { | |||||
| this.updateCurrentTime(); | |||||
| setInterval(() => { | |||||
| this.updateCurrentTime(); | |||||
| }, 1000); | |||||
| } | |||||
| updateCurrentTime() { | |||||
| this.currentTime = new Date(); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,33 @@ | |||||
| import { ChangeDetectionStrategy, Component, HostListener } from '@angular/core'; | |||||
| import { I18nPipe } from '@delon/theme'; | |||||
| import { NzIconModule } from 'ng-zorro-antd/icon'; | |||||
| import screenfull from 'screenfull'; | |||||
| @Component({ | |||||
| selector: 'header-fullscreen', | |||||
| template: ` | |||||
| <i nz-icon [nzType]="status ? 'fullscreen-exit' : 'fullscreen'"></i> | |||||
| {{ status ? '' : '' }} | |||||
| `, | |||||
| host: { | |||||
| '[class.flex-1]': 'true' | |||||
| }, | |||||
| changeDetection: ChangeDetectionStrategy.OnPush, | |||||
| standalone: true, | |||||
| imports: [NzIconModule, I18nPipe] | |||||
| }) | |||||
| 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,121 @@ | |||||
| import { NgTemplateOutlet } from '@angular/common'; | |||||
| import { | |||||
| AfterViewInit, | |||||
| ChangeDetectionStrategy, | |||||
| ChangeDetectorRef, | |||||
| Component, | |||||
| ElementRef, | |||||
| EventEmitter, | |||||
| HostBinding, | |||||
| Input, | |||||
| OnDestroy, | |||||
| Output, | |||||
| inject | |||||
| } from '@angular/core'; | |||||
| import { FormsModule } from '@angular/forms'; | |||||
| import { I18nPipe } from '@delon/theme'; | |||||
| import { NzAutocompleteModule } from 'ng-zorro-antd/auto-complete'; | |||||
| import { NzIconModule } from 'ng-zorro-antd/icon'; | |||||
| import { NzInputModule } from 'ng-zorro-antd/input'; | |||||
| import { BehaviorSubject, debounceTime, distinctUntilChanged, tap } from 'rxjs'; | |||||
| @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> | |||||
| @if (loading) { | |||||
| <i nz-icon nzType="loading"></i> | |||||
| } | |||||
| </ng-template> | |||||
| <input | |||||
| type="text" | |||||
| nz-input | |||||
| [(ngModel)]="q" | |||||
| [nzAutocomplete]="auto" | |||||
| (input)="search($event)" | |||||
| (focus)="qFocus()" | |||||
| (blur)="qBlur()" | |||||
| hotkey="F1" | |||||
| [attr.placeholder]="'搜索:关键字'" | |||||
| /> | |||||
| </nz-input-group> | |||||
| <nz-autocomplete nzBackfill #auto> | |||||
| @for (i of options; track $index) { | |||||
| <nz-auto-option [nzValue]="i">{{ i }}</nz-auto-option> | |||||
| } | |||||
| </nz-autocomplete> | |||||
| `, | |||||
| changeDetection: ChangeDetectionStrategy.OnPush, | |||||
| standalone: true, | |||||
| imports: [FormsModule, I18nPipe, NgTemplateOutlet, NzInputModule, NzIconModule, NzAutocompleteModule] | |||||
| }) | |||||
| export class HeaderSearchComponent implements AfterViewInit, OnDestroy { | |||||
| private readonly el = inject<ElementRef<HTMLElement>>(ElementRef).nativeElement; | |||||
| private readonly cdr = inject(ChangeDetectorRef); | |||||
| 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>(); | |||||
| ngAfterViewInit(): void { | |||||
| this.qIpt = this.el.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,150 @@ | |||||
| import { ChangeDetectionStrategy, Component, inject, OnInit } from '@angular/core'; | |||||
| import { FormBuilder, FormGroup } from '@angular/forms'; | |||||
| import { Router } from '@angular/router'; | |||||
| import { DA_SERVICE_TOKEN } from '@delon/auth'; | |||||
| import { I18nPipe, SettingsService, User, _HttpClient } from '@delon/theme'; | |||||
| import { environment } from '@env/environment'; | |||||
| import { NzAvatarModule } from 'ng-zorro-antd/avatar'; | |||||
| import { NzDropDownModule } from 'ng-zorro-antd/dropdown'; | |||||
| import { NzIconModule } from 'ng-zorro-antd/icon'; | |||||
| import { NzMenuModule } from 'ng-zorro-antd/menu'; | |||||
| import { NzMessageService } from 'ng-zorro-antd/message'; | |||||
| import { AppValidators } from 'src/app/core/utils/app-validators'; | |||||
| import { BaseService } from 'src/app/core/utils/base.service'; | |||||
| import { HeaderClearStorageComponent } from './clear-storage.component'; | |||||
| import { HeaderFullScreenComponent } from './fullscreen.component'; | |||||
| import { SharedModule } from '../../../shared/shared.module'; | |||||
| @Component({ | |||||
| selector: 'header-user', | |||||
| template: ` | |||||
| <div class="alain-default__nav-item d-flex align-items-center px-sm" nz-dropdown nzPlacement="bottomRight" [nzDropdownMenu]="userMenu"> | |||||
| <i nz-icon nzType="user"></i><span style="margin-left:8px;">{{ user.name }}</span> | |||||
| </div> | |||||
| <nz-dropdown-menu #userMenu="nzDropdownMenu"> | |||||
| <div nz-menu class="width-sm"> | |||||
| <div nz-menu-item (click)="center()"> | |||||
| <i nz-icon nzType="user" class="mr-sm"></i> | |||||
| 修改密码 | |||||
| </div> | |||||
| <li nz-menu-divider></li> | |||||
| <div nz-menu-item (click)="logout()"> | |||||
| <i nz-icon nzType="logout" class="mr-sm"></i> | |||||
| 退出登录 | |||||
| </div> | |||||
| </div> | |||||
| </nz-dropdown-menu> | |||||
| <nz-modal [nzWidth]="400" [nzStyle]="modalStyles" [nzVisible]="visible" [nzTitle]="detailTitle" (nzOnCancel)="close()"> | |||||
| <form *nzModalContent nz-form [formGroup]="form" se-container="1" labelWidth="120"> | |||||
| <se label="用户名" autoErrorTip> | |||||
| {{ name }} | |||||
| </se> | |||||
| <se label="密码" autoErrorTip required> | |||||
| <input nz-input formControlName="password" type="password" formControlName="password" placeholder="密码" /> | |||||
| </se> | |||||
| <se label="是否是管理员" autoErrorTip> | |||||
| {{ administrators ? '是' : '否' }} | |||||
| </se> | |||||
| </form> | |||||
| <div *nzModalFooter style="margin-right: 0.6rem"> | |||||
| <button nz-button nzType="primary" (click)="save()" nzSize="large">确认</button> | |||||
| <button nz-button (click)="close()" nzSize="large">取消</button> | |||||
| </div> | |||||
| </nz-modal> | |||||
| `, | |||||
| changeDetection: ChangeDetectionStrategy.OnPush, | |||||
| standalone: true, | |||||
| imports: [ | |||||
| NzDropDownModule, | |||||
| NzMenuModule, | |||||
| NzIconModule, | |||||
| I18nPipe, | |||||
| NzAvatarModule, | |||||
| HeaderFullScreenComponent, | |||||
| HeaderClearStorageComponent, | |||||
| SharedModule | |||||
| ] | |||||
| }) | |||||
| export class HeaderUserComponent implements OnInit { | |||||
| private readonly http = inject(_HttpClient); | |||||
| private readonly settings = inject(SettingsService); | |||||
| private readonly router = inject(Router); | |||||
| private readonly tokenService = inject(DA_SERVICE_TOKEN); | |||||
| private readonly baseService = inject(BaseService); | |||||
| userId: any = ''; | |||||
| get user(): User { | |||||
| return this.settings.user; | |||||
| } | |||||
| url = `${environment.api['fuelUrl']}system/user/resetPwd`; | |||||
| form!: FormGroup; | |||||
| private readonly fb = inject(FormBuilder); | |||||
| detailTitle: string = '修改密码'; | |||||
| visible: boolean = false; | |||||
| modalStyles = { | |||||
| top: 200 | |||||
| }; | |||||
| name: string = ''; | |||||
| administrators: boolean = false; | |||||
| constructor(private message: NzMessageService) {} | |||||
| close() { | |||||
| this.visible = false; | |||||
| } | |||||
| logout(): void { | |||||
| if (window.localStorage.getItem('theme')) { | |||||
| window.localStorage.removeItem('theme'); | |||||
| document.body.classList.remove('white'); | |||||
| } | |||||
| this.tokenService.clear(); | |||||
| this.router.navigateByUrl(`/passport${this.tokenService.login_url!}`); | |||||
| } | |||||
| center(): void { | |||||
| const info: any = window.sessionStorage.getItem('info'); | |||||
| const data: any = JSON.parse(info); | |||||
| console.log(data.roles.indexOf('admin'), data, 'infoooo'); | |||||
| this.administrators = data.roles.indexOf('admin') != -1 ? true : false; | |||||
| this.userId = data.user.userId; | |||||
| this.form.reset({ | |||||
| password: '' | |||||
| }); | |||||
| this.visible = true; | |||||
| } | |||||
| save() { | |||||
| if (this.baseService.hasError(this.form)) { | |||||
| return; | |||||
| } | |||||
| const req = Object.assign(this.form.getRawValue(), { userId: this.userId, iaAdmin: this.administrators }); | |||||
| this.http.put(`${this.url}`, req).subscribe(res => { | |||||
| if (res.code === 403) { | |||||
| this.message.warning(res.msg); | |||||
| } | |||||
| if (res.code === 500) { | |||||
| this.message.warning(res.msg); | |||||
| } | |||||
| if (res.code === 200) { | |||||
| this.message.success('修改成功'); | |||||
| } | |||||
| }); | |||||
| } | |||||
| ngOnInit(): void { | |||||
| this.createForm(); | |||||
| this.name = this.settings.user.name; | |||||
| } | |||||
| /** | |||||
| * 创建Form | |||||
| * | |||||
| */ | |||||
| createForm() { | |||||
| this.form = this.fb.group({ | |||||
| password: [, AppValidators.required('E0001', '请输入密码')] | |||||
| }); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1 @@ | |||||
| [Document](https://ng-alain.com/theme/blank) | |||||
| @@ -0,0 +1,13 @@ | |||||
| import { Component } from '@angular/core'; | |||||
| import { RouterOutlet } from '@angular/router'; | |||||
| @Component({ | |||||
| selector: 'layout-blank', | |||||
| template: `<router-outlet />`, | |||||
| host: { | |||||
| '[class.alain-blank]': 'true' | |||||
| }, | |||||
| standalone: true, | |||||
| imports: [RouterOutlet] | |||||
| }) | |||||
| export class LayoutBlankComponent {} | |||||
| @@ -0,0 +1,77 @@ | |||||
| <layout-default [options]="options" [nav]="null" [content]="contentTpl" [customError]="null" style="align-items: flex-start"> | |||||
| <layout-default-header-item direction="left"> | |||||
| <span class="sys_title"> | |||||
| <div class="head-left" style="position: absolute; top: 0px; left: 14px; z-index: 999; display: flex"> </div> </span | |||||
| ></layout-default-header-item> | |||||
| <div class="top" | |||||
| ><img id="bg-image" src="../../../assets/datav/header.png" alt="" /><span>{{ title }}</span></div | |||||
| > | |||||
| <header-date class="date" /> | |||||
| <layout-default-header-item direction="middle" /> | |||||
| <layout-default-header-item direction="right" hidden="mobile"> | |||||
| <div | |||||
| class="setting" | |||||
| 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 /> | |||||
| </div> | |||||
| <div nz-menu-item> | |||||
| <header-clear-storage /> | |||||
| </div> | |||||
| </div> | |||||
| </nz-dropdown-menu> | |||||
| </layout-default-header-item> | |||||
| <layout-default-header-item direction="right"> | |||||
| <header-user class="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" /> | |||||
| <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">个人中心</li> | |||||
| <li nz-menu-item routerLink="/pro/account/settings">个人设置</li> | |||||
| </ul> | |||||
| </nz-dropdown-menu> | |||||
| </ng-template> | |||||
| <layout-default-header-item direction="right"> | |||||
| <div data-tauri-drag-region class="titlebar" id="titlebar" [hidden]="!isTauri"> | |||||
| <div class="titlebar-button" id="titlebar-minimize" (click)="minimize()"> | |||||
| <img src="../../../assets/window/minsize.svg" alt="minimize" /> | |||||
| </div> | |||||
| <div class="titlebar-button" id="titlebar-maximize" (click)="toggleMaximize()" [hidden]="!isMax"> | |||||
| <img src="../../../assets/window/window-window_line.svg" alt="maximize" /> | |||||
| </div> | |||||
| <div class="titlebar-button" id="titlebar-maximize" (click)="toggleMaximize()" [hidden]="isMax"> | |||||
| <img src="../../../assets/window/max-size.svg" alt="maximize" /> | |||||
| </div> | |||||
| <div class="titlebar-button" id="titlebar-close" (click)="close()"> | |||||
| <img src="../../../assets/window/Close.svg" alt="close" /> | |||||
| </div> | |||||
| </div> | |||||
| </layout-default-header-item> | |||||
| <layout-default-header-item direction="right"> | |||||
| <div data-tauri-drag-region class="titleDragger" id="titlebar" [hidden]="!isTauri"> </div> | |||||
| </layout-default-header-item> | |||||
| <ng-template #contentTpl> | |||||
| <datav-menu /> | |||||
| <router-outlet /> | |||||
| <div id="container"></div> | |||||
| </ng-template> | |||||
| </layout-default> | |||||
| @@ -0,0 +1,211 @@ | |||||
| ::ng-deep .ul-v1 { | |||||
| display: flex; | |||||
| transition: transform 0.3s ease-in-out; | |||||
| flex-wrap: nowrap; | |||||
| li { | |||||
| min-width: 120px !important; | |||||
| } | |||||
| } | |||||
| ::ng-deep .date { | |||||
| position: absolute; | |||||
| z-index: 9999; | |||||
| top: 5px; | |||||
| left: 9px; | |||||
| } | |||||
| ::ng-deep .sys_title { | |||||
| color: #fff; | |||||
| font-size: 16px; | |||||
| position: absolute; | |||||
| top: 0px; | |||||
| left: 0px; | |||||
| } | |||||
| nz-header { | |||||
| position: fixed; | |||||
| top: 0; | |||||
| right: 0; | |||||
| left: 0; | |||||
| color: #fff; | |||||
| background: rgb(4 5 26); | |||||
| width: 104vw; | |||||
| margin: 0 auto; | |||||
| padding: 0; | |||||
| z-index: 9; | |||||
| img { | |||||
| display: block; | |||||
| margin-top: 10px; | |||||
| } | |||||
| span { | |||||
| margin: 0 auto; | |||||
| font-size: 30px; | |||||
| line-height: 60px; | |||||
| color: rgb(102, 255, 255); | |||||
| } | |||||
| } | |||||
| ::ng-deep .alain-default__content { | |||||
| margin-top: 6.5rem !important; | |||||
| } | |||||
| ::ng-deep .alain-default__header { | |||||
| box-shadow: none !important; | |||||
| } | |||||
| ::ng-deep datav-menu { | |||||
| margin-bottom: 59px; | |||||
| display: block; | |||||
| width: 100%; | |||||
| } | |||||
| ::ng-deep .alain-default__nav { | |||||
| position: absolute; | |||||
| right: 36px; | |||||
| z-index: 999; | |||||
| } | |||||
| // 缩放样式 | |||||
| ::ng-deep { | |||||
| html, | |||||
| body { | |||||
| background-color: rgb(11, 36, 65) !important; | |||||
| } | |||||
| .top { | |||||
| color: #fff; | |||||
| span { | |||||
| position: absolute; | |||||
| color: #fff; | |||||
| text-align: center; | |||||
| width: 100%; | |||||
| height: 50px; | |||||
| font-weight: 600; | |||||
| top: 14px; | |||||
| font-size: 24px; | |||||
| } | |||||
| } | |||||
| #bg-image { | |||||
| position: absolute; | |||||
| top: 32px; | |||||
| left: 0; | |||||
| width: 100% !important; | |||||
| height: 49px !important; | |||||
| } | |||||
| .alain-default__nav-item { | |||||
| padding: 5px 2px; | |||||
| font-size: 18px; | |||||
| } | |||||
| .alain-default { | |||||
| height: 100% !important; | |||||
| } | |||||
| .ant-row.container, | |||||
| .contain-box { | |||||
| height: calc(100% - 37px) !important; | |||||
| max-width: none; | |||||
| } | |||||
| .left-item { | |||||
| flex: 1; | |||||
| height: auto; | |||||
| } | |||||
| .center-item { | |||||
| height: 100% !important; | |||||
| } | |||||
| .right-item { | |||||
| height: calc(100% - 273px) !important; | |||||
| .box { | |||||
| overflow: auto; | |||||
| height: calc(100% - 3px); | |||||
| } | |||||
| } | |||||
| .car-status-container { | |||||
| height: calc(100% - 76px) !important; | |||||
| overflow: auto !important; | |||||
| } | |||||
| .alain-default__content { | |||||
| margin-top: 0 !important; | |||||
| margin: 0 !important; | |||||
| margin-left: 0 !important; | |||||
| } | |||||
| .alain-default__hide-aside { | |||||
| .alain-default__content { | |||||
| height: calc(100% - 118px); | |||||
| margin-left: 0 !important; | |||||
| } | |||||
| } | |||||
| .alain-default__fixed { | |||||
| .alain-default__header { | |||||
| position: static; | |||||
| } | |||||
| } | |||||
| // 全水检测内容部分样式 | |||||
| .total-moisture-monitoring { | |||||
| height: calc(100% - 39px) !important; | |||||
| } | |||||
| } | |||||
| .titlebar { | |||||
| height: 30px; | |||||
| user-select: none; | |||||
| display: flex; | |||||
| justify-content: flex-end; | |||||
| padding-left: 15px; | |||||
| top: 0; | |||||
| left: 0; | |||||
| right: 0; | |||||
| z-index: 1000; | |||||
| } | |||||
| .titleDragger { | |||||
| height: 30px; | |||||
| user-select: none; | |||||
| display: flex; | |||||
| justify-content: flex-end; | |||||
| position: fixed; | |||||
| top: 0; | |||||
| left: 0; | |||||
| right: 0; | |||||
| z-index: 1001; | |||||
| width: 200px; | |||||
| left: 50%; | |||||
| margin-left: -100px; | |||||
| } | |||||
| .titlebar-button { | |||||
| display: inline-flex; | |||||
| justify-content: center; | |||||
| align-items: center; | |||||
| width: 30px; | |||||
| height: 30px; | |||||
| user-select: none; | |||||
| -webkit-user-select: none; | |||||
| } | |||||
| .titlebar-button img { | |||||
| font-size: 14px; | |||||
| width: 18px; | |||||
| height: 18px; | |||||
| } | |||||
| @@ -0,0 +1,149 @@ | |||||
| import { Location } from '@angular/common'; | |||||
| import { Component, inject, HostListener, OnInit } from '@angular/core'; | |||||
| import { Router, RouterLink, RouterOutlet } from '@angular/router'; | |||||
| import { QuickMenuModule } from '@delon/abc/quick-menu'; | |||||
| import { I18nPipe, SettingsService, User, _HttpClient } from '@delon/theme'; | |||||
| import { LayoutDefaultModule, LayoutDefaultOptions } from '@delon/theme/layout-default'; | |||||
| import { SettingDrawerModule } from '@delon/theme/setting-drawer'; | |||||
| import { ThemeBtnComponent } from '@delon/theme/theme-btn'; | |||||
| import { environment } from '@env/environment'; | |||||
| import { isTauri } from '@tauri-apps/api/core'; | |||||
| import { Window } from '@tauri-apps/api/window'; | |||||
| import { NzAvatarModule } from 'ng-zorro-antd/avatar'; | |||||
| import { NzDropDownModule } from 'ng-zorro-antd/dropdown'; | |||||
| import { NzIconModule } from 'ng-zorro-antd/icon'; | |||||
| import { NzLayoutModule } from 'ng-zorro-antd/layout'; | |||||
| import { NzListItemComponent, NzListModule } from 'ng-zorro-antd/list'; | |||||
| import { NzMenuModule } from 'ng-zorro-antd/menu'; | |||||
| import { NzSelectModule } from 'ng-zorro-antd/select'; | |||||
| import { LayoutDatavMenuComponent } from './menu/datav-menu.component'; | |||||
| import { HeaderClearStorageComponent } from '../basic/widgets/clear-storage.component'; | |||||
| import { HeaderDateComponent } from '../basic/widgets/date.component'; | |||||
| import { HeaderFullScreenComponent } from '../basic/widgets/fullscreen.component'; | |||||
| import { HeaderUserComponent } from '../basic/widgets/user.component'; | |||||
| @Component({ | |||||
| selector: 'layout-datav', | |||||
| templateUrl: './datav.component.html', | |||||
| styleUrls: ['./datav.component.less'], | |||||
| standalone: true, | |||||
| imports: [ | |||||
| RouterOutlet, | |||||
| RouterLink, | |||||
| I18nPipe, | |||||
| LayoutDefaultModule, | |||||
| SettingDrawerModule, | |||||
| NzSelectModule, | |||||
| ThemeBtnComponent, | |||||
| NzIconModule, | |||||
| NzMenuModule, | |||||
| NzDropDownModule, | |||||
| NzAvatarModule, | |||||
| HeaderUserComponent, | |||||
| HeaderClearStorageComponent, | |||||
| HeaderFullScreenComponent, | |||||
| NzLayoutModule, | |||||
| LayoutDatavMenuComponent, | |||||
| HeaderDateComponent, | |||||
| QuickMenuModule, | |||||
| NzListItemComponent, | |||||
| NzListModule | |||||
| ] | |||||
| }) | |||||
| export class LayoutDatavComponent implements OnInit { | |||||
| private readonly settings = inject(SettingsService); | |||||
| private readonly router = inject(Router); | |||||
| private readonly http = inject(_HttpClient); | |||||
| windowWidth: number | undefined; | |||||
| @HostListener('window:resize', ['$event']) | |||||
| options: LayoutDefaultOptions = { | |||||
| logoExpanded: `./assets/datav/trn.png`, | |||||
| logoCollapsed: `./assets/datav/trn.png`, | |||||
| hideAside: true | |||||
| }; | |||||
| scaleView(dom: any, width: number, height: number) { | |||||
| dom.style.width = `${width}px`; | |||||
| dom.style.height = `${height}px`; | |||||
| } | |||||
| title = ''; | |||||
| getSystemInfo() { | |||||
| this.http.get(`${environment.api['fuelUrl']}system/config/configKey/pcSystemTitile`).subscribe((res: any) => { | |||||
| if (res.code == 200) { | |||||
| this.title = res.msg; | |||||
| } | |||||
| }); | |||||
| } | |||||
| ngOnInit(): void { | |||||
| document.body.className = 'dark'; | |||||
| // 根据设备分辨率动态设置容易宽度 | |||||
| const html: any = document.querySelector('html'); | |||||
| html.style.fontSize = '16px'; | |||||
| document.body.style.fontSize = '16px'; | |||||
| const width: number = window.screen.width; | |||||
| this.getSystemInfo(); | |||||
| } | |||||
| searchToggleStatus = false; | |||||
| currentOrgan: any; | |||||
| organs: any; | |||||
| appWindow = new Window('main'); | |||||
| isTauri = false; | |||||
| isMax = true; | |||||
| showSettingDrawer = !environment.production; | |||||
| get user(): User { | |||||
| return this.settings.user; | |||||
| } | |||||
| @HostListener('window:resize', ['$event']) | |||||
| onResize(event: any) { | |||||
| const innerWidth: any = event.target.innerWidth; | |||||
| const innerHeight: any = event.target.innerHeight; | |||||
| const node: any = document.querySelector('html'); | |||||
| } | |||||
| constructor(private location: Location) { | |||||
| this.organs = this.settings.user['organs']; | |||||
| const c = this.settings.user['currentOrgan']; | |||||
| if (c) { | |||||
| const organ = this.organs.find((o: { id: any }) => o.id == c.id); | |||||
| this.currentOrgan = organ; | |||||
| } | |||||
| this.initMicroApps(); | |||||
| this.obtainIsTauri(); | |||||
| } | |||||
| obtainIsTauri() { | |||||
| try { | |||||
| this.isTauri = isTauri(); | |||||
| } catch { | |||||
| this.isTauri = false; | |||||
| } | |||||
| } | |||||
| minimize() { | |||||
| this.appWindow.minimize(); | |||||
| } | |||||
| toggleMaximize() { | |||||
| this.appWindow.isMaximized().then(p => { | |||||
| this.isMax = !p; | |||||
| }); | |||||
| this.appWindow.toggleMaximize(); | |||||
| } | |||||
| close() { | |||||
| this.appWindow.close(); | |||||
| } | |||||
| organSelectOptionChange(e: any) { | |||||
| const u = this.settings.user; | |||||
| u['currentOrgan'] = e; | |||||
| this.settings.setUser(u); | |||||
| } | |||||
| goBack(): void { | |||||
| this.location.back(); | |||||
| } | |||||
| initMicroApps() {} | |||||
| } | |||||
| @@ -0,0 +1,43 @@ | |||||
| <div class="container-box"> | |||||
| <div class="project">燃料</div> | |||||
| <div class="menu"> | |||||
| <div *ngFor="let item of menuLv1List"> | |||||
| <button | |||||
| class="menu-item" | |||||
| nz-button | |||||
| nzType="text" | |||||
| nz-dropdown | |||||
| [nzDropdownMenu]="menu" | |||||
| nzPlacement="bottomCenter" | |||||
| (click)="menuItem(item)" | |||||
| [class.active]="item.isSelected" | |||||
| > | |||||
| <span | |||||
| *ngIf="item.icon.type === 'iconfont' && item.icon.value != ''" | |||||
| class="iconfont" | |||||
| [innerHTML]="item.icon.value === 'icon-#' ? '' : item.icon.value" | |||||
| style="margin-right: 4px" | |||||
| ></span> | |||||
| <span | |||||
| *ngIf="item.icon.type !== 'iconfont' && item.icon.value != ''" | |||||
| nz-icon | |||||
| [nzType]="item.icon.value" | |||||
| style="vertical-align: middle; margin-right: -5px" | |||||
| ></span> | |||||
| {{ item.menuName }} | |||||
| </button> | |||||
| <nz-dropdown-menu #menu="nzDropdownMenu"> | |||||
| <ul nz-menu *ngFor="let zitem of item.children"> | |||||
| <li *ngIf="zitem.children.length === 0 && !zitem.hide" class="menu-title" nz-menu-item (click)="nav(zitem)" | |||||
| >{{ zitem.menuName }} | |||||
| </li> | |||||
| <li *ngIf="zitem.children.length" class="menu-title" nz-submenu [nzTitle]="zitem.menuName"> | |||||
| <ul> | |||||
| <li nz-menu-item *ngFor="let mitem of zitem.children" class="menu-title" (click)="nav(mitem)"> {{ mitem.menuName }}</li> | |||||
| </ul> | |||||
| </li> | |||||
| </ul> | |||||
| </nz-dropdown-menu> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| @@ -0,0 +1,302 @@ | |||||
| ::ng-deep .cdk-overlay-pane { | |||||
| margin-top: -4px !important; | |||||
| .ant-dropdown-menu-sub { | |||||
| li { | |||||
| padding: 8px 16px; | |||||
| } | |||||
| } | |||||
| .ant-dropdown-menu-item, | |||||
| .ant-dropdown-menu-submenu-title { | |||||
| text-align: center; | |||||
| } | |||||
| } | |||||
| ::ng-deep .ant-dropdown-menu-item:hover, | |||||
| .ant-dropdown-menu-submenu-title:hover, | |||||
| .ant-dropdown-menu-item.ant-dropdown-menu-item-active, | |||||
| .ant-dropdown-menu-item.ant-dropdown-menu-submenu-title-active, | |||||
| .ant-dropdown-menu-submenu-title.ant-dropdown-menu-item-active, | |||||
| .ant-dropdown-menu-submenu-title.ant-dropdown-menu-submenu-title-active { | |||||
| background-color: rgb(24, 144, 255) !important; | |||||
| } | |||||
| ::ng-deep .ant-dropdown-menu { | |||||
| background-color: rgb(11, 36, 66) !important; | |||||
| box-shadow: none !important; | |||||
| } | |||||
| ::ng-deep .ant-dropdown-menu-item, | |||||
| .ant-dropdown-menu-submenu-title { | |||||
| font-size: 14px !important; | |||||
| } | |||||
| ::ng-deep .menu .ant-btn:hover { | |||||
| border-bottom: 3px solid rgb(24, 144, 255); | |||||
| } | |||||
| ::ng-deep .ant-btn>span { | |||||
| font-weight: bold !important; | |||||
| } | |||||
| ::ng-deep .ant-dropdown-menu-item, | |||||
| .ant-dropdown-menu-submenu-title { | |||||
| clear: both; | |||||
| margin: 0; | |||||
| padding: 5px 0; | |||||
| color: rgba(255, 255, 255, 0.85); | |||||
| font-weight: normal; | |||||
| font-size: 14px; | |||||
| line-height: 22px; | |||||
| cursor: pointer; | |||||
| transition: all 0.3s; | |||||
| text-align: center; | |||||
| width: 100%; | |||||
| display: block; | |||||
| } | |||||
| ::ng-deep .ant-dropdown { | |||||
| border: 1px solid rgb(166, 177, 187); | |||||
| } | |||||
| .container-box { | |||||
| position: absolute; | |||||
| width: 100%; | |||||
| height: 48px; | |||||
| margin: 5px auto; | |||||
| top: 84px; | |||||
| left: 0; | |||||
| background: #172f4b; | |||||
| display: flex; | |||||
| .project { | |||||
| width: 100px; | |||||
| font-size: 20px !important; | |||||
| line-height: 43px; | |||||
| text-align: center; | |||||
| margin-left: 20px; | |||||
| cursor: pointer; | |||||
| } | |||||
| .menu { | |||||
| display: flex; | |||||
| margin: 2px 60px 0px 0px; | |||||
| overflow-y: hidden; | |||||
| overflow-x: auto; | |||||
| .menu-item { | |||||
| margin-right: 0px; | |||||
| font-size: 18px; | |||||
| color: rgb(174, 183, 192); | |||||
| height: 38px; | |||||
| padding: 4px 17px; | |||||
| } | |||||
| .active { | |||||
| color: #fff; | |||||
| border-bottom: 3px solid rgb(24, 144, 255); | |||||
| } | |||||
| } | |||||
| } | |||||
| .nav-block { | |||||
| width: calc(100% - 4rem); | |||||
| margin: 0 auto; | |||||
| overflow: hidden; | |||||
| padding-left: 2px; | |||||
| } | |||||
| /* 设置滚动条的宽度 */ | |||||
| ::ng-deep .container-box .menu:-webkit-scrollbar { | |||||
| width: 6px; | |||||
| } | |||||
| /* 设置滚动条滑块的样式 */ | |||||
| ::ng-deep .container-box .menu::-webkit-scrollbar-thumb { | |||||
| // margin-left: 0px; | |||||
| background-color: rgb(24, 144, 255); | |||||
| border: 2px solid rgb(24, 144, 255); | |||||
| border-radius: 10px; | |||||
| } | |||||
| /* 设置滚动条滑块悬浮时的样式 */ | |||||
| ::ng-deep .container-box .menu::-webkit-scrollbar-thumb:hover { | |||||
| // margin-left: 0px; | |||||
| background-color: rgb(24, 144, 255); | |||||
| } | |||||
| /* 设置滚动条滑块激活(即正在拖动滑块)时的样式 */ | |||||
| ::ng-deep .container-box .menu::-webkit-scrollbar-thumb:active { | |||||
| background-color: rgb(24, 144, 255); | |||||
| } | |||||
| //横向 | |||||
| ::ng-deep .container-box { | |||||
| scrollbar-color: auto; | |||||
| } | |||||
| /* 设置滚动条的宽度 */ | |||||
| ::ng-deep .container-box .menu::-webkit-scrollbar { | |||||
| height: 6px; | |||||
| } | |||||
| /* 设置横向滚动条轨道的样式 */ | |||||
| ::ng-deep .container-box .menu::-webkit-scrollbar-track { | |||||
| background-color: transparent; | |||||
| border-color: rgb(24, 144, 255); | |||||
| } | |||||
| /* 设置横向滚动条滑块悬浮时的样式 */ | |||||
| ::ng-deep .container-box .menu::-webkit-scrollbar-thumb:hover { | |||||
| background-color: rgb(24, 144, 255); | |||||
| } | |||||
| /* 设置横向滚动条滑块激活时的样式 */ | |||||
| ::ng-deep .container-box .menu::-webkit-scrollbar-thumb:active { | |||||
| background-color: rgb(24, 144, 255); | |||||
| } | |||||
| /* 设置横向滚动条滑块的颜色 */ | |||||
| ::ng-deep .container-box .menu::-webkit-scrollbar-thumb { | |||||
| background-color: rgb(24, 144, 255); | |||||
| } | |||||
| .ul-v1 { | |||||
| padding-top: 0.2rem; | |||||
| padding-bottom: 0.6rem; | |||||
| padding-right: 0.3rem; | |||||
| border: none !important; | |||||
| font-size: 16px; | |||||
| background: none !important; | |||||
| } | |||||
| .arrow { | |||||
| width: 1.5rem; | |||||
| height: 46px; | |||||
| position: absolute; | |||||
| top: 82px; | |||||
| background-color: rgba(5, 66, 84, 1); | |||||
| border: none; | |||||
| cursor: pointer; | |||||
| color: rgb(100, 255, 255); | |||||
| } | |||||
| .show { | |||||
| display: inline-flex !important; | |||||
| background-color: #054254 !important; | |||||
| min-width: 142px !important; | |||||
| margin-right: 8px !important; | |||||
| font-weight: 600 !important; | |||||
| color: #74FAFB !important; | |||||
| text-align: inherit !important; | |||||
| white-space: nowrap !important; | |||||
| } | |||||
| .hide { | |||||
| display: none !important; | |||||
| } | |||||
| .left { | |||||
| left: 29px; | |||||
| z-index: 99999; | |||||
| } | |||||
| .right { | |||||
| right: 29px; | |||||
| z-index: 99999; | |||||
| } | |||||
| .li-v1 { | |||||
| display: inline-flex; | |||||
| background-color: #054254; | |||||
| min-width: 142px !important; | |||||
| margin-right: 8px; | |||||
| font-weight: 600; | |||||
| color: #74FAFB; | |||||
| text-align: inherit; | |||||
| white-space: nowrap; | |||||
| &:hover, | |||||
| &:active, | |||||
| &.active {} | |||||
| } | |||||
| .li-v_2_3 { | |||||
| display: inline-flex; | |||||
| background-color: #054254; | |||||
| min-width: 4rem; | |||||
| margin-right: 8px; | |||||
| font-weight: 600; | |||||
| color: #74FAFB; | |||||
| text-align: inherit; | |||||
| white-space: nowrap; | |||||
| font-size: 15px; | |||||
| } | |||||
| .li-divider { | |||||
| padding: -1rem; | |||||
| margin-left: -1.2rem; | |||||
| margin-right: -0.5rem; | |||||
| align-items: start; | |||||
| font-size: 1.5rem; | |||||
| min-width: auto !important; | |||||
| color: #13c2c2; | |||||
| text-align: top; | |||||
| } | |||||
| .li-selected { | |||||
| background: linear-gradient(to bottom, #054254 0%, #054254 94%, #13c2c2 94%, #13c2c2 100%); | |||||
| } | |||||
| .li-cur { | |||||
| display: inline-flex; | |||||
| background-color: #054254; | |||||
| margin-right: 0.8rem; | |||||
| padding-left: 1rem; | |||||
| font-weight: 600; | |||||
| padding: 0; | |||||
| color: #13c2c2; | |||||
| //color: #fff; | |||||
| text-align: center; | |||||
| white-space: nowrap; | |||||
| } | |||||
| .icon-border { | |||||
| position: relative; | |||||
| } | |||||
| .icon-border-row11 { | |||||
| position: absolute; | |||||
| top: 0; | |||||
| left: -0.8rem; | |||||
| color: #74FAFB; | |||||
| } | |||||
| .icon-border-row12 { | |||||
| position: absolute; | |||||
| top: 0; | |||||
| right: -0.8rem; | |||||
| color: #74FAFB; | |||||
| } | |||||
| .icon-border-row21 { | |||||
| position: absolute; | |||||
| bottom: 0; | |||||
| left: -0.8rem; | |||||
| color: #74FAFB; | |||||
| } | |||||
| .icon-border-row22 { | |||||
| position: absolute; | |||||
| bottom: 0; | |||||
| right: -0.8rem; | |||||
| color: #74FAFB; | |||||
| } | |||||
| @@ -0,0 +1,286 @@ | |||||
| import { ChangeDetectorRef, Component, inject, OnInit } from '@angular/core'; | |||||
| import { ActivatedRoute, Router, RouterLink, RouterOutlet } from '@angular/router'; | |||||
| import { _HttpClient, I18nPipe, MenuService, ModalHelper } from '@delon/theme'; | |||||
| import { LayoutDefaultModule } from '@delon/theme/layout-default'; | |||||
| import { SettingDrawerModule } from '@delon/theme/setting-drawer'; | |||||
| import { ThemeBtnComponent } from '@delon/theme/theme-btn'; | |||||
| import { SharedModule } from '@shared'; | |||||
| import { NzAvatarModule } from 'ng-zorro-antd/avatar'; | |||||
| import { NzDropDownModule } from 'ng-zorro-antd/dropdown'; | |||||
| import { NzIconModule } from 'ng-zorro-antd/icon'; | |||||
| import { NzMenuModule } from 'ng-zorro-antd/menu'; | |||||
| import { NzSelectModule } from 'ng-zorro-antd/select'; | |||||
| import zi from '../../../../assets/fonts/zi/iconfont'; | |||||
| @Component({ | |||||
| selector: 'datav-menu', | |||||
| templateUrl: './datav-menu.component.html', | |||||
| styleUrls: ['./datav-menu.component.less'], | |||||
| standalone: true, | |||||
| imports: [ | |||||
| RouterOutlet, | |||||
| RouterLink, | |||||
| I18nPipe, | |||||
| LayoutDefaultModule, | |||||
| SettingDrawerModule, | |||||
| SharedModule, | |||||
| NzSelectModule, | |||||
| ThemeBtnComponent, | |||||
| NzIconModule, | |||||
| NzMenuModule, | |||||
| NzDropDownModule, | |||||
| NzAvatarModule | |||||
| ] | |||||
| }) | |||||
| export class LayoutDatavMenuComponent implements OnInit { | |||||
| private readonly http = inject(_HttpClient); | |||||
| private readonly modal = inject(ModalHelper); | |||||
| private readonly router = inject(Router); | |||||
| private readonly cdr = inject(ChangeDetectorRef); | |||||
| dropdownList = [ | |||||
| { | |||||
| name: 'Dropdown 1', | |||||
| isSelected: false, | |||||
| menuItems: [{ label: 'Option 1-1' }, { label: 'Option 1-2' }, { label: 'Option 1-3' }] | |||||
| }, | |||||
| { | |||||
| name: 'Dropdown 2', | |||||
| isSelected: false, | |||||
| menuItems: [{ label: 'Option 2-1' }, { label: 'Option 2-2' }, { label: 'Option 2-3' }] | |||||
| } | |||||
| ]; | |||||
| showHome: boolean = false; | |||||
| menuList: any[] = []; | |||||
| menuData: any[] = []; | |||||
| menuLv1List: any[] = []; | |||||
| menuLv1Cur: any[] = []; | |||||
| menuLv2List: any[] = []; | |||||
| menuLv2Cur: any[] = []; | |||||
| menuLv3List: any[] = []; | |||||
| selectedMenu: string = ''; | |||||
| menuLv = 1; | |||||
| contentWidth = 0; | |||||
| scrollX = 0; | |||||
| ulWidth = 0; | |||||
| showArrow = true; | |||||
| constructor( | |||||
| private menuService: MenuService, | |||||
| private route: ActivatedRoute | |||||
| ) { | |||||
| menuService.menus.forEach((menu: any) => { | |||||
| menu.children.forEach((zitem: any) => { | |||||
| // const sonMenu = zitem.children.filter((item:any)=>{ | |||||
| // return !item.hide | |||||
| // }) | |||||
| // zitem.children = sonMenu | |||||
| }); | |||||
| this.menuList = this.menuList.concat(menu.children); | |||||
| this.menuData = this.menuList; | |||||
| }); | |||||
| this.menuList.forEach((item: any) => { | |||||
| if (item.icon.type === 'iconfont') { | |||||
| const obj: any = zi.glyphs.find((li: any) => { | |||||
| return li['font_class'] === item['icon']['value'].split('-')[1]; | |||||
| }); | |||||
| if (obj) { | |||||
| item['icon']['value'] = `&#x${obj.unicode};`; | |||||
| } | |||||
| } | |||||
| item['isSelected'] = false; | |||||
| }); | |||||
| this.menuLv1List = this.menuList.filter(item => !item.hide); | |||||
| } | |||||
| fontObj: any = { | |||||
| yonghu: ``, | |||||
| sousuowenjian: '', | |||||
| xinjian: '', | |||||
| tianjia: '', | |||||
| diannao: '' | |||||
| }; | |||||
| ngOnInit() {} | |||||
| nav(item: any) { | |||||
| console.log('Navigation called with item:', item); | |||||
| console.log('Item link:', item.link); | |||||
| if (!item.link) { | |||||
| console.warn('No link found for menu item:', item); | |||||
| return; | |||||
| } else { | |||||
| try { | |||||
| // 检查是否是hy路由(化验相关),需要重定向到传统布局 | |||||
| if (item.link.startsWith('hy/') || item.link.startsWith('/hy/')) { | |||||
| console.log('Hy route detected, redirecting to traditional layout'); | |||||
| // 清理URL,确保格式正确 | |||||
| let cleanLink = item.link; | |||||
| if (cleanLink.startsWith('/')) { | |||||
| cleanLink = cleanLink.substring(1); // 移除开头的斜杠 | |||||
| } | |||||
| if (cleanLink.endsWith('/')) { | |||||
| cleanLink = cleanLink.substring(0, cleanLink.length - 1); // 移除末尾的斜杠 | |||||
| } | |||||
| // 构建完整的URL,重定向到传统布局(动态获取当前host和port) | |||||
| const currentProtocol = window.location.protocol; | |||||
| const currentHost = window.location.hostname; | |||||
| const currentPort = window.location.port; | |||||
| const baseUrl = `${currentProtocol}//${currentHost}${currentPort ? `:${currentPort}` : ''}`; | |||||
| const fullUrl = `${baseUrl}/#/${cleanLink}`; | |||||
| console.log('Redirecting to traditional layout URL:', fullUrl); | |||||
| window.location.href = fullUrl; | |||||
| return; | |||||
| } | |||||
| // 对于datav布局内的其他路由 | |||||
| if (item.link.includes('?')) { | |||||
| // 如果链接已经包含参数,使用window.location进行导航 | |||||
| console.log('Navigating with parameters using window.location'); | |||||
| const fullUrl = `${window.location.origin + window.location.pathname}#${item.link}`; | |||||
| console.log('Full URL:', fullUrl); | |||||
| window.location.href = fullUrl; | |||||
| } else { | |||||
| // 如果链接没有参数,使用router导航 | |||||
| console.log('Navigating without parameters using router'); | |||||
| this.router.navigateByUrl(item.link).then( | |||||
| success => console.log('Navigation success:', success), | |||||
| error => console.error('Navigation error:', error) | |||||
| ); | |||||
| } | |||||
| } catch (error) { | |||||
| console.error('Navigation failed:', error); | |||||
| } | |||||
| } | |||||
| } | |||||
| // 下拉切换 | |||||
| menuItem(item: any) { | |||||
| console.log(item, 'item'); | |||||
| this.menuLv1List.forEach(item => (item.isSelected = false)); | |||||
| // 将当前点击对应的按钮选中状态设为 true | |||||
| item.isSelected = true; | |||||
| if (item.link) { | |||||
| this.router.navigateByUrl(item.link); | |||||
| } | |||||
| } | |||||
| // 递归找到路由对应的父 | |||||
| findAncestorByLink(link: string, menuItems: any[]) { | |||||
| for (const item of menuItems) { | |||||
| if (item.link === link) { | |||||
| // 如果找到的是直接匹配的链接,向上追溯最顶层的祖先 | |||||
| return this.traceAncestor(item, []); | |||||
| } else if (item.children) { | |||||
| const found: any = this.findAncestorByLink(link, item.children); | |||||
| if (found) { | |||||
| return found; | |||||
| } | |||||
| } | |||||
| } | |||||
| return undefined; | |||||
| } | |||||
| traceAncestor(item: any, ancestors: any[]): any { | |||||
| ancestors.push(item); | |||||
| if (!item.children || !item.children.length) { | |||||
| // 如果当前项没有孩子,返回祖先链中的最顶层 | |||||
| return ancestors[0]; | |||||
| } else { | |||||
| // 否则,继续向上追溯 | |||||
| return this.traceAncestor(item.children[0], ancestors); | |||||
| } | |||||
| } | |||||
| // 获取菜单容器宽度集合 | |||||
| getWidth() { | |||||
| setTimeout(() => { | |||||
| const node: any = document.querySelector('.ul-v1 li.show'); | |||||
| const ul: any = document.querySelector('.ul-v1'); | |||||
| this.ulWidth = ul.offsetWidth; | |||||
| this.contentWidth = (node.offsetWidth + 8) * this.menuList.length; | |||||
| }, 0); | |||||
| } | |||||
| scrollLeft() { | |||||
| this.scrollX += 150; // Move left by the width of one item plus margin | |||||
| this.updateScrollPosition(); | |||||
| } | |||||
| scrollRight() { | |||||
| this.scrollX -= 150; // Move right by the width of one item plus margin | |||||
| this.updateScrollPosition(); | |||||
| } | |||||
| canScrollLeft() { | |||||
| return this.scrollX < 0; | |||||
| } | |||||
| canScrollRight() { | |||||
| return this.scrollX > -(this.contentWidth - this.ulWidth); | |||||
| } | |||||
| updateScrollPosition() { | |||||
| const scrollableContent: any = document.querySelector('.ul-v1'); | |||||
| if (scrollableContent) { | |||||
| scrollableContent.style.transform = `translateX(${this.scrollX}px)`; | |||||
| } | |||||
| } | |||||
| redirectToRoute(menu: any): void { | |||||
| window.sessionStorage.setItem('link', menu.link); | |||||
| if (menu.children && menu.children.length > 0) { | |||||
| this.menuLv2Cur = [menu]; | |||||
| this.menuLv2List = []; | |||||
| this.menuLv3List = [...menu.children]; | |||||
| } | |||||
| if (menu.link) { | |||||
| this.router.navigate([menu.link]); | |||||
| this.selectedMenu = menu.link; // 以文本为键值来区分是否选中 | |||||
| } | |||||
| } | |||||
| routeLv1() { | |||||
| this.router.navigateByUrl('/datav/dashboard-v'); | |||||
| this.showHome = false; | |||||
| this.selectedMenu = ''; | |||||
| this.menuLv1List = this.menuList; | |||||
| this.menuLv1Cur = []; | |||||
| this.menuLv2List = []; | |||||
| this.menuLv2Cur = []; | |||||
| this.menuLv3List = []; | |||||
| this.showArrow = true; | |||||
| setTimeout(() => { | |||||
| const node: any = document.querySelector('.nav-block'); | |||||
| node.style.margin = '0px auto'; | |||||
| node.style.paddingLeft = '2px'; | |||||
| }, 0); | |||||
| this.getWidth(); | |||||
| } | |||||
| routeLv2(menu: any) { | |||||
| this.showHome = true; | |||||
| this.menuLv1Cur = []; | |||||
| this.menuLv1List = []; | |||||
| this.menuLv1Cur = [menu]; | |||||
| this.menuLv2List = [...menu.children]; | |||||
| this.showArrow = false; | |||||
| setTimeout(() => { | |||||
| const node: any = document.querySelector('.nav-block'); | |||||
| node.style.margin = '0px'; | |||||
| node.style.paddingLeft = '0px'; | |||||
| }, 0); | |||||
| const node: any = document.querySelector('.ul-v1'); | |||||
| node.style.transform = 'translateX(0)'; | |||||
| } | |||||
| backLv2(menu: any) { | |||||
| this.showHome = true; | |||||
| this.selectedMenu = ''; | |||||
| this.menuLv2Cur = []; | |||||
| this.menuLv3List = []; | |||||
| this.menuLv2List = []; | |||||
| this.menuLv2List = [...menu.children]; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,2 @@ | |||||
| export * from './blank/blank.component'; | |||||
| export * from './passport/passport.component'; | |||||
| @@ -0,0 +1,135 @@ | |||||
| <div class="scroll-container" (wheel)="handleScroll($event)"> | |||||
| <a class="full-button" style="color: #fff"> | |||||
| <header-fullscreen /> | |||||
| </a> | |||||
| <a class="back-home" style="color: #fff" (click)="backHome()"> | |||||
| <i class="material-icons first-name">home</i> | |||||
| </a> | |||||
| <img id="overflow-image" src="../../../assets/datav/overView.png" alt="Large-Image" /> | |||||
| <!-- 汽车计划 --> | |||||
| <div class="count-view left1 vehicle-batch">{{vehicleOverView.planBatch||0}}</div> | |||||
| <div class="count-view left1 vehicle-count">{{vehicleOverView.planVehicle||0}}</div> | |||||
| <!-- 汽车入厂 --> | |||||
| <div class="count-view left2 vehicle-center-completed">{{vehicleOverView.enterCompleted||0}}</div> | |||||
| <div class="count-view left2 vehicle-center-pending">{{vehicleOverView.enterPending||0}}</div> | |||||
| <div class="count-view left2 vehicle-center-now">{{vehicleOverView.enterNow||0}}</div> | |||||
| <!-- 汽车采样机#1 --> | |||||
| <div class="count-view left3 vehicle-top-second-completed">{{vehicleOverView.samplingCompleted1||0}}</div> | |||||
| <div class="count-view left3 vehicle-top-second-pending">{{vehicleOverView.samplingPending1||0}}</div> | |||||
| <div class="count-view left3 vehicle-top-second-now">{{vehicleOverView.samplingNow1||0}}</div> | |||||
| <div class="count-view click-area vehicleSampling1" (click)="onClick('vehicleSampling')"></div> | |||||
| <!-- 汽车采样机#2 --> | |||||
| <div class="count-view left3 vehicle-bottom-first-completed">{{vehicleOverView.samplingCompleted2||0}}</div> | |||||
| <div class="count-view left3 vehicle-bottom-first-pending">{{vehicleOverView.samplingPending2||0}}</div> | |||||
| <div class="count-view left3 vehicle-bottom-first-now">{{vehicleOverView.samplingNow2||0}}</div> | |||||
| <div class="count-view click-area vehicleSampling2" (click)="onClick('vehicleSampling')"></div> | |||||
| <!-- 汽车重磅#1 --> | |||||
| <div class="count-view left4 vehicle-top-first-completed">{{vehicleOverView.heavyCompleted1||0}}</div> | |||||
| <div class="count-view left4 vehicle-top-first-pending">{{vehicleOverView.heavyPending1||0}}</div> | |||||
| <div class="count-view left4 vehicle-top-first-now">{{vehicleOverView.heavyNow1||0}}</div> | |||||
| <div class="count-view click-area vehicleHeavy1" (click)="onClick('vehicleHeavy')"></div> | |||||
| <!-- 汽车重磅#2 --> | |||||
| <div class="count-view left4 vehicle-center-completed">{{vehicleOverView.heavyCompleted2||0}}</div> | |||||
| <div class="count-view left4 vehicle-center-pending">{{vehicleOverView.heavyPending2||0}}</div> | |||||
| <div class="count-view left4 vehicle-center-now">{{vehicleOverView.heavyNow2||0}}</div> | |||||
| <div class="count-view click-area vehicleHeavy2" (click)="onClick('vehicleHeavy')"></div> | |||||
| <!-- 汽车重磅#3 --> | |||||
| <div class="count-view left4 vehicle-bottom-second-completed">{{vehicleOverView.heavyCompleted3||0}}</div> | |||||
| <div class="count-view left4 vehicle-bottom-second-pending">{{vehicleOverView.heavyPending3||0}}</div> | |||||
| <div class="count-view left4 vehicle-bottom-second-now">{{vehicleOverView.heavyNow3||0}}</div> | |||||
| <div class="count-view click-area vehicleHeavy3" (click)="onClick('vehicleHeavy')"></div> | |||||
| <!-- 汽车卸煤 --> | |||||
| <div class="count-view left5 vehicle-center-completed">{{vehicleOverView.coalCompleted||0}}</div> | |||||
| <div class="count-view left5 vehicle-center-pending">{{vehicleOverView.coalPending||0}}</div> | |||||
| <div class="count-view left5 vehicle-center-now">{{vehicleOverView.coalNow||0}}</div> | |||||
| <!-- 汽车轻磅#1 --> | |||||
| <div class="count-view left6 vehicle-top-second-completed">{{vehicleOverView.lightCompleted1||0}}</div> | |||||
| <div class="count-view left6 vehicle-top-second-pending">{{vehicleOverView.lightPending1||0}}</div> | |||||
| <div class="count-view left6 vehicle-top-second-now">{{vehicleOverView.lightNow1||0}}</div> | |||||
| <div class="count-view click-area vehicleLight1" (click)="onClick('vehicleLight')"></div> | |||||
| <!-- 汽车轻磅#4 --> | |||||
| <div class="count-view left6 vehicle-bottom-first-completed">{{vehicleOverView.lightCompleted4||0}}</div> | |||||
| <div class="count-view left6 vehicle-bottom-first-pending">{{vehicleOverView.lightPending4||0}}</div> | |||||
| <div class="count-view left6 vehicle-bottom-first-now">{{vehicleOverView.lightNow4||0}}</div> | |||||
| <div class="count-view click-area vehicleLight2" (click)="onClick('vehicleLight')"></div> | |||||
| <!-- 汽车出厂 --> | |||||
| <div class="count-view left7 vehicle-center-completed">{{vehicleOverView.outCompleted||0}}</div> | |||||
| <div class="count-view left7 vehicle-center-pending">{{vehicleOverView.outPending||0}}</div> | |||||
| <div class="count-view left7 vehicle-center-now">{{vehicleOverView.outNow||0}}</div> | |||||
| <!-- 在线全水 --> | |||||
| <div class="count-view left2 tasks-completed">{{water.completedTasks||0}}</div> | |||||
| <div class="count-view left2 tasks-pending">{{water.pendingTasks||0}}</div> | |||||
| <div class="count-view left2 tasks-now">{{water.nowTasks||0}}</div> | |||||
| <!-- 自动制样 --> | |||||
| <div class="count-view left3 tasks-completed">{{autoSampling.completedTasks||0}}</div> | |||||
| <div class="count-view left3 tasks-pending">{{autoSampling.pendingTasks||0}}</div> | |||||
| <div class="count-view left3 tasks-now">{{autoSampling.nowTasks||0}}</div> | |||||
| <div class="count-view click-area autoSampling" (click)="onClick('autoSampling')"></div> | |||||
| <!-- 自动存样 --> | |||||
| <div class="count-view left4 tasks-completed">{{storage.completedTasks||0}}</div> | |||||
| <div class="count-view left4 tasks-pending">{{storage.pendingTasks||0}}</div> | |||||
| <div class="count-view left4 tasks-now">{{storage.nowTasks||0}}</div> | |||||
| <div class="count-view click-area storage" (click)="onClick('storage')"></div> | |||||
| <!-- 自动化验 --> | |||||
| <div class="count-view left5 tasks-completed">{{assay.completedTasks||0}}</div> | |||||
| <div class="count-view left5 tasks-pending">{{assay.pendingTasks||0}}</div> | |||||
| <div class="count-view left5 tasks-now">{{assay.nowTasks||0}}</div> | |||||
| <div class="count-view click-area assay" (click)="onClick('assay')"></div> | |||||
| <!-- 火车计划 --> | |||||
| <div class="count-view left1 train-batch">{{trainOverView.planBatchCount||0}}</div> | |||||
| <div class="count-view left1 train-count">{{trainOverView.planVehicleCount ||0}}</div> | |||||
| <!-- 火车重车过衡 --> | |||||
| <div class="count-view left2 train-center-completed">{{trainOverView.heavyWeighCompleted||0}}</div> | |||||
| <div class="count-view left2 train-center-pending">{{trainOverView.heavyWeighPending||0}}</div> | |||||
| <div class="count-view left2 train-center-now">{{trainOverView.heavyWeighNow||0}}</div> | |||||
| <!-- 火车采样机#1 --> | |||||
| <div class="count-view left3 train-top-completed">{{sampling.completedCount1||0}}</div> | |||||
| <div class="count-view left3 train-top-pending">{{sampling.pendingCount1||0}}</div> | |||||
| <div class="count-view left3 train-top-now">{{sampling.nowCount1||0}}</div> | |||||
| <!-- 火车采样机#2 --> | |||||
| <div class="count-view left3 train-bottom-completed">{{sampling.completedCount2||0}}</div> | |||||
| <div class="count-view left3 train-bottom-pending">{{sampling.pendingCount2||0}}</div> | |||||
| <div class="count-view left3 train-bottom-now">{{sampling.nowCount2||0}}</div> | |||||
| <!-- 火车翻车机#1 --> | |||||
| <div class="count-view left4 train-top-completed">{{tipplers.completedCount1||0}}</div> | |||||
| <div class="count-view left4 train-top-pending">{{tipplers.pendingCount1||0}}</div> | |||||
| <div class="count-view left4 train-top-now">{{tipplers.nowCount1||0}}</div> | |||||
| <!-- 火车翻车机#2 --> | |||||
| <div class="count-view left4 train-bottom-completed">{{tipplers.completedCount2||0}}</div> | |||||
| <div class="count-view left4 train-bottom-pending">{{tipplers.pendingCount2||0}}</div> | |||||
| <div class="count-view left4 train-bottom-now">{{tipplers.nowCount2||0}}</div> | |||||
| <!-- 火车皮带采样机#1 --> | |||||
| <div class="count-view left5 train-top-completed">{{samplers.completedCount1||0}}</div> | |||||
| <div class="count-view left5 train-top-pending">{{samplers.pendingCount1||0}}</div> | |||||
| <div class="count-view left5 train-top-now">{{samplers.nowCount1||0}}</div> | |||||
| <div class="count-view click-area samplers1" (click)="onClick('samplers')"></div> | |||||
| <!-- 火车皮带采样机#2 --> | |||||
| <div class="count-view left5 train-bottom-completed">{{samplers.completedCount2||0}}</div> | |||||
| <div class="count-view left5 train-bottom-pending">{{samplers.pendingCount2||0}}</div> | |||||
| <div class="count-view left5 train-bottom-now">{{samplers.nowCount2||0}}</div> | |||||
| <div class="count-view click-area samplers2" (click)="onClick('samplers')"></div> | |||||
| <!-- 火车出厂 --> | |||||
| <div class="count-view left6 train-center-completed">{{trainOut.exitCount||0}}</div> | |||||
| <div class="count-view left6 train-center-pending">{{(trainOut.arrivedCount||0) - (trainOut.exitCount||0)}}</div> | |||||
| <div class="count-view left6 train-center-now">{{trainOut.nowCount||0}}</div> | |||||
| <nz-modal | |||||
| [nzWidth]="1200" | |||||
| [nzCentered]="true" | |||||
| [nzVisible]="editVisible" | |||||
| (nzOnCancel)="close()" | |||||
| [nzFooter]="null" | |||||
| [nzMaskClosable]="false" | |||||
| [nzMaskStyle]="{'background': '#000', 'opacity': '0', 'animation': 'none'}" | |||||
| > | |||||
| <div class="content" *nzModalContent> | |||||
| <iframe class="iframe" [src]="src"></iframe> | |||||
| </div> | |||||
| </nz-modal> | |||||
| </div> | |||||
| @@ -0,0 +1,229 @@ | |||||
| .scroll-container{ | |||||
| overflow: scroll; /* 允许滚动 */ | |||||
| width: 100%; | |||||
| height: 100vh; | |||||
| position: absolute; /* 相对于定位 */ | |||||
| .full-button{ | |||||
| position: fixed; | |||||
| top: 8px; | |||||
| left: 32px; | |||||
| z-index: 999; | |||||
| } | |||||
| .back-home{ | |||||
| position: fixed; | |||||
| top: 6px; | |||||
| left: 64px; | |||||
| z-index: 999; | |||||
| } | |||||
| #overflow-image { | |||||
| position: absolute; | |||||
| } | |||||
| .count-view{ | |||||
| position: absolute; | |||||
| z-index: 999; | |||||
| color: #fff; | |||||
| } | |||||
| .vehicle-batch{ | |||||
| margin-top: 354px; | |||||
| } | |||||
| .vehicle-count{ | |||||
| margin-top: 394px; | |||||
| } | |||||
| .vehicle-top-first-completed{ | |||||
| margin-top: 163px; | |||||
| } | |||||
| .vehicle-top-first-pending{ | |||||
| margin-top: 203px; | |||||
| } | |||||
| .vehicle-top-first-now{ | |||||
| margin-top: 243px; | |||||
| } | |||||
| .vehicle-top-second-completed{ | |||||
| margin-top: 238px; | |||||
| } | |||||
| .vehicle-top-second-pending{ | |||||
| margin-top: 278px; | |||||
| } | |||||
| .vehicle-top-second-now{ | |||||
| margin-top: 318px; | |||||
| } | |||||
| .vehicle-center-completed{ | |||||
| margin-top: 308px; | |||||
| } | |||||
| .vehicle-center-pending{ | |||||
| margin-top: 348px; | |||||
| } | |||||
| .vehicle-center-now{ | |||||
| margin-top: 388px; | |||||
| } | |||||
| .vehicle-bottom-first-completed{ | |||||
| margin-top: 378px; | |||||
| } | |||||
| .vehicle-bottom-first-pending{ | |||||
| margin-top: 418px; | |||||
| } | |||||
| .vehicle-bottom-first-now{ | |||||
| margin-top: 458px; | |||||
| } | |||||
| .vehicle-bottom-second-completed{ | |||||
| margin-top: 448px; | |||||
| } | |||||
| .vehicle-bottom-second-pending{ | |||||
| margin-top: 488px; | |||||
| } | |||||
| .vehicle-bottom-second-now{ | |||||
| margin-top: 528px; | |||||
| } | |||||
| .tasks-completed{ | |||||
| margin-top: 638px; | |||||
| } | |||||
| .tasks-pending{ | |||||
| margin-top: 678px; | |||||
| } | |||||
| .tasks-now{ | |||||
| margin-top: 718px; | |||||
| } | |||||
| .train-batch{ | |||||
| margin-top: 1013px; | |||||
| } | |||||
| .train-count{ | |||||
| margin-top: 1053px; | |||||
| } | |||||
| .train-top-completed{ | |||||
| margin-top: 898px; | |||||
| } | |||||
| .train-top-pending{ | |||||
| margin-top: 938px; | |||||
| } | |||||
| .train-top-now{ | |||||
| margin-top: 978px; | |||||
| } | |||||
| .train-center-completed{ | |||||
| margin-top: 970px; | |||||
| } | |||||
| .train-center-pending{ | |||||
| margin-top: 1010px; | |||||
| } | |||||
| .train-center-now{ | |||||
| margin-top: 1050px; | |||||
| } | |||||
| .train-bottom-completed{ | |||||
| margin-top: 1040px; | |||||
| } | |||||
| .train-bottom-pending{ | |||||
| margin-top: 1080px; | |||||
| } | |||||
| .train-bottom-now{ | |||||
| margin-top: 1120px; | |||||
| } | |||||
| .left1{ | |||||
| margin-left: 365px; | |||||
| } | |||||
| .left2{ | |||||
| margin-left: 1160px; | |||||
| } | |||||
| .left3{ | |||||
| margin-left: 2125px; | |||||
| } | |||||
| .left4{ | |||||
| margin-left: 3085px; | |||||
| } | |||||
| .left5{ | |||||
| margin-left: 4050px; | |||||
| } | |||||
| .left6{ | |||||
| margin-left: 5010px; | |||||
| } | |||||
| .left7{ | |||||
| margin-left: 5970px; | |||||
| } | |||||
| .click-area{ | |||||
| cursor: pointer; | |||||
| width: 432px; | |||||
| height: 123px; | |||||
| } | |||||
| .assay{ | |||||
| margin: 628px 0px 0px 3778px; | |||||
| } | |||||
| .storage{ | |||||
| margin: 628px 0px 0px 2817px; | |||||
| } | |||||
| .autoSampling{ | |||||
| margin: 628px 0px 0px 1855px; | |||||
| } | |||||
| .samplers1{ | |||||
| margin: 888px 0px 0px 3778px; | |||||
| } | |||||
| .samplers2{ | |||||
| margin: 1028px 0px 0px 3778px; | |||||
| } | |||||
| .vehicleSampling1{ | |||||
| margin: 227px 0px 0px 1855px; | |||||
| } | |||||
| .vehicleSampling2{ | |||||
| margin: 368px 0px 0px 1855px; | |||||
| } | |||||
| .vehicleHeavy1{ | |||||
| margin: 156px 0px 0px 2817px; | |||||
| } | |||||
| .vehicleHeavy2{ | |||||
| margin: 296px 0px 0px 2817px; | |||||
| } | |||||
| .vehicleHeavy3{ | |||||
| margin: 438px 0px 0px 2817px; | |||||
| } | |||||
| .vehicleLight1{ | |||||
| margin: 227px 0px 0px 4740px; | |||||
| } | |||||
| .vehicleLight2{ | |||||
| margin: 368px 0px 0px 4740px; | |||||
| } | |||||
| } | |||||
| ::ng-deep { | |||||
| .ant-modal { | |||||
| top: 50%; | |||||
| padding-bottom: 0; | |||||
| .ant-modal-content { | |||||
| //border-radius: 12px !important; | |||||
| box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12) !important; | |||||
| overflow: hidden !important; | |||||
| background-color: #f9f9f9 !important; | |||||
| .ant-modal-header { | |||||
| background: #fff; | |||||
| display: none; | |||||
| .ant-modal-title { | |||||
| font-weight: 500; | |||||
| font-size: 16px; | |||||
| line-height: 22px; | |||||
| color: rgba(0, 0, 0, 0.85); | |||||
| } | |||||
| } | |||||
| .ant-modal-close{ | |||||
| color: #fff !important; | |||||
| } | |||||
| .ant-modal-close-x{ | |||||
| width: auto; | |||||
| line-height: 0px; | |||||
| margin: 8px; | |||||
| } | |||||
| .ant-modal-body { | |||||
| padding: 1px !important; | |||||
| //background-color: #0b2441 !important; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| .content{ | |||||
| width: 100%; | |||||
| height: 750px; | |||||
| .iframe{ | |||||
| width: 100%; | |||||
| height: 100%; | |||||
| border-width: 0px; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,340 @@ | |||||
| import { Component, HostListener, inject } from '@angular/core'; | |||||
| import { Router } from '@angular/router'; | |||||
| import { environment } from '@env/environment'; | |||||
| import { HeaderFullScreenComponent } from '../basic/widgets/fullscreen.component'; | |||||
| import { ModalHelper, _HttpClient } from '@delon/theme'; | |||||
| import { NzMessageService } from 'ng-zorro-antd/message'; | |||||
| import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser'; | |||||
| import { NzModalService } from 'ng-zorro-antd/modal'; | |||||
| import { CommonModule } from '@angular/common'; | |||||
| import { SHARED_IMPORTS } from '@shared'; | |||||
| import { NzDividerModule } from 'ng-zorro-antd/divider'; | |||||
| import { STModule } from '@delon/abc/st'; | |||||
| import { XlsxModule } from '@delon/abc/xlsx'; | |||||
| import { NzPaginationModule } from 'ng-zorro-antd/pagination'; | |||||
| import { NzTagModule } from 'ng-zorro-antd/tag'; | |||||
| import { NzTreeSelectModule } from 'ng-zorro-antd/tree-select'; | |||||
| import { DelonFormModule } from '@delon/form'; | |||||
| @Component({ | |||||
| selector: 'layout-overflow', | |||||
| templateUrl: './overflow.component.html', | |||||
| styleUrls: ['./overflow.component.less'], | |||||
| standalone: true, | |||||
| imports: [ | |||||
| HeaderFullScreenComponent, | |||||
| CommonModule, | |||||
| ...SHARED_IMPORTS, | |||||
| STModule, | |||||
| XlsxModule, | |||||
| NzPaginationModule, | |||||
| NzTreeSelectModule, | |||||
| NzDividerModule, | |||||
| NzTagModule, | |||||
| DelonFormModule | |||||
| ], | |||||
| }) | |||||
| export class LayoutOverflowComponent { | |||||
| private readonly router = inject(Router); | |||||
| private readonly http = inject(_HttpClient); | |||||
| private readonly modal = inject(ModalHelper); | |||||
| private readonly modalService = inject(NzModalService); | |||||
| constructor(private msg: NzMessageService,private sanitizer: DomSanitizer) {} | |||||
| vehicleOverViewUrl = `${environment.api['fuelUrl']}hy/vehicle/statistics/overview`; | |||||
| //vehiclePrimaryUrl = `${environment.api['fuelUrl']}hy/vehicle/weigh/scales/primary`; | |||||
| //vehicleSecondaryUrl = `${environment.api['fuelUrl']}hy/vehicle/weigh/scales/secondary`; | |||||
| trainOverViewUrl = `${environment.api['fuelUrl']}hy/automation/train/overview`; | |||||
| trainSamplingUrl = `${environment.api['fuelUrl']}hy/automation/train/sampling`; | |||||
| tipplersUrl = `${environment.api['fuelUrl']}hy/automation/tipplers`; | |||||
| samplersUrl = `${environment.api['fuelUrl']}hy/automation/samplers`; | |||||
| trainOutUrl = `${environment.api['fuelUrl']}hy/vehicle/statistics/onceWeighingCount`; | |||||
| autoSamplingUrl = `${environment.api['fuelUrl']}hy/automation/auto-sampling`; | |||||
| storageUrl = `${environment.api['fuelUrl']}hy/automation/storage`; | |||||
| assayUrl = `${environment.api['fuelUrl']}hy/automation/assay`; | |||||
| waterUrl = `${environment.api['fuelUrl']}hy/automation/water`; | |||||
| @HostListener('wheel', ['$event']) | |||||
| handleScroll(event: WheelEvent) { | |||||
| event.preventDefault(); | |||||
| } | |||||
| ngOnInit(): void { | |||||
| this.getVehicleOverView(); | |||||
| // this.getVehiclePrimary(); | |||||
| // this.getVehicleSecondary(); | |||||
| this.getTrainOverView(); | |||||
| this.getTrainSampling(); | |||||
| this.getTipplers(); | |||||
| this.getSamplers(); | |||||
| this.getTrainOut(); | |||||
| this.getAutoSampling(); | |||||
| this.getStorage(); | |||||
| this.getAssay(); | |||||
| this.getWater(); | |||||
| } | |||||
| backHome() { | |||||
| try { | |||||
| console.log('Navigating without parameters using router'); | |||||
| this.router.navigateByUrl('/datav/dashboard-v').then( | |||||
| success => console.log('Navigation success:', success), | |||||
| error => console.error('Navigation error:', error) | |||||
| ); | |||||
| } catch (error) { | |||||
| console.error('Navigation failed:', error); | |||||
| } | |||||
| } | |||||
| //汽车相关数据统计列表 | |||||
| vehicleOverView: any = {}; | |||||
| getVehicleOverView(){ | |||||
| this.http.get(this.vehicleOverViewUrl).subscribe((res: any) => { | |||||
| console.log('res1',res); | |||||
| if (res.code == 200) { | |||||
| this.vehicleOverView = { | |||||
| planBatch: (res.data||[]).find((item: any) => (item.typeName||'').indexOf('计划批次') > -1)?.quantity, | |||||
| planVehicle: (res.data||[]).find((item: any) => (item.typeName||'').indexOf('计划车数') > -1)?.quantity, | |||||
| enterCompleted: (res.data||[]).find((item: any) => (item.typeName||'').indexOf('已入厂') > -1)?.quantity, | |||||
| enterPending: (res.data||[]).find((item: any) => (item.typeName||'').indexOf('未入厂') > -1)?.quantity, | |||||
| enterNow: 0, | |||||
| samplingCompleted1: (res.data||[]).find((item: any) => (item.typeName||'').indexOf('采样完成') > -1)?.quantity, | |||||
| samplingPending1: (res.data||[]).find((item: any) => (item.typeName||'').indexOf('待采样') > -1)?.quantity, | |||||
| samplingNow1: 0, | |||||
| samplingCompleted2: 0, | |||||
| samplingPending2: 0, | |||||
| samplingNow2: 0, | |||||
| heavyCompleted1: (res.data||[]).find((item: any) => (item.typeName||'').indexOf('#1重磅') > -1)?.quantity, | |||||
| heavyPending1: !!(res.data||[]).find((item: any) => (item.typeName||'').indexOf('#1重磅') > -1)?.typeName ? (res.data||[]).find((item: any) => (item.typeName||'').indexOf('等待重磅') > -1)?.quantity : 0, | |||||
| heavyNow1: 0, | |||||
| heavyCompleted2: (res.data||[]).find((item: any) => (item.typeName||'').indexOf('#2重磅') > -1)?.quantity, | |||||
| heavyPending2: !!(res.data||[]).find((item: any) => (item.typeName||'').indexOf('#2重磅') > -1)?.typeName ? (res.data||[]).find((item: any) => (item.typeName||'').indexOf('等待重磅') > -1)?.quantity : 0, | |||||
| heavyNow2: 0, | |||||
| heavyCompleted3: (res.data||[]).find((item: any) => (item.typeName||'').indexOf('#3重磅') > -1)?.quantity, | |||||
| heavyPending3: !!(res.data||[]).find((item: any) => (item.typeName||'').indexOf('#3重磅') > -1)?.typeName ? (res.data||[]).find((item: any) => (item.typeName||'').indexOf('等待重磅') > -1)?.quantity : 0, | |||||
| heavyNow3: 0, | |||||
| coalCompleted: (res.data||[]).find((item: any) => (item.typeName||'').indexOf('卸煤完成') > -1)?.quantity, | |||||
| coalPending: (res.data||[]).find((item: any) => (item.typeName||'').indexOf('等待卸煤') > -1)?.quantity, | |||||
| coalNow: 0, | |||||
| lightCompleted1: (res.data||[]).find((item: any) => (item.typeName||'').indexOf('#1轻磅') > -1)?.quantity, | |||||
| lightPending1: !!(res.data||[]).find((item: any) => (item.typeName||'').indexOf('#1轻磅') > -1)?.typeName ? (res.data||[]).find((item: any) => (item.typeName||'').indexOf('等待轻磅') > -1)?.quantity : 0, | |||||
| lightNow1: 0, | |||||
| lightCompleted4: (res.data||[]).find((item: any) => (item.typeName||'').indexOf('#4轻磅') > -1)?.quantity, | |||||
| lightPending4: !!(res.data||[]).find((item: any) => (item.typeName||'').indexOf('#4轻磅') > -1)?.typeName ? (res.data||[]).find((item: any) => (item.typeName||'').indexOf('等待轻磅') > -1)?.quantity : 0, | |||||
| lightNow4: 0, | |||||
| outCompleted: (res.data||[]).find((item: any) => (item.typeName||'').indexOf('出厂数') > -1)?.quantity, | |||||
| outPending: (res.data||[]).find((item: any) => (item.typeName||'').indexOf('未出厂数') > -1)?.quantity, | |||||
| outNow: 0, | |||||
| } | |||||
| } else { | |||||
| //this.msg.error(res.msg); | |||||
| } | |||||
| }); | |||||
| } | |||||
| // getVehiclePrimary(){ | |||||
| // this.http.get(this.vehiclePrimaryUrl).subscribe((res: any) => { | |||||
| // console.log('res2',res); | |||||
| // if (res.code == 200) { | |||||
| // } else { | |||||
| // this.msg.error(res.msg); | |||||
| // } | |||||
| // }); | |||||
| // } | |||||
| // getVehicleSecondary(){ | |||||
| // this.http.get(this.vehicleSecondaryUrl).subscribe((res: any) => { | |||||
| // console.log('res3',res); | |||||
| // if (res.code == 200) { | |||||
| // } else { | |||||
| // this.msg.error(res.msg); | |||||
| // } | |||||
| // }); | |||||
| // } | |||||
| //火车计划与过衡数据 | |||||
| trainOverView: any = {}; | |||||
| getTrainOverView(){ | |||||
| this.http.get(this.trainOverViewUrl).subscribe((res: any) => { | |||||
| console.log('res4',res); | |||||
| if (res.code == 200) { | |||||
| this.trainOverView = res.data||{} | |||||
| this.trainOut['arrivedCount'] = res.data?.arrivedCount||0 | |||||
| } else { | |||||
| //this.msg.error(res.msg); | |||||
| } | |||||
| }); | |||||
| } | |||||
| //火车采样情况 | |||||
| sampling: any = {}; | |||||
| getTrainSampling(){ | |||||
| this.http.get(this.trainSamplingUrl).subscribe((res: any) => { | |||||
| console.log('res5',res); | |||||
| if (res.code == 200) { | |||||
| this.sampling = { | |||||
| completedCount1: (res.data||[]).find((item: any) => (item.name||'').indexOf('完成') > -1)?.count, | |||||
| pendingCount1: (res.data||[]).find((item: any) => (item.name||'').indexOf('等待') > -1)?.count, | |||||
| nowCount1: 0, | |||||
| completedCount2: 0, | |||||
| pendingCount2: 0, | |||||
| nowCount2: 0 | |||||
| } | |||||
| } else { | |||||
| //this.msg.error(res.msg); | |||||
| } | |||||
| }); | |||||
| } | |||||
| //翻车机 | |||||
| tipplers: any = {}; | |||||
| getTipplers(){ | |||||
| this.http.get(this.tipplersUrl).subscribe((res: any) => { | |||||
| console.log('res6',res); | |||||
| if (res.code == 200) { | |||||
| let tippler1 = (res.data||[]).find((item: any) => (item.tipplerIndex||0) == 0)||{} | |||||
| let tippler2 = (res.data||[]).find((item: any) => (item.tipplerIndex||0) == 1)||{} | |||||
| this.tipplers = { | |||||
| completedCount1: tippler1.completedCount, | |||||
| pendingCount1: tippler1.pendingCount, | |||||
| nowCount1: 0, | |||||
| completedCount2: tippler2.completedCount, | |||||
| pendingCount2: tippler2.pendingCount, | |||||
| nowCount2: 0, | |||||
| } | |||||
| } else { | |||||
| //this.msg.error(res.msg); | |||||
| } | |||||
| }); | |||||
| } | |||||
| //皮带采样机 | |||||
| samplers: any = {}; | |||||
| getSamplers(){ | |||||
| this.http.get(this.samplersUrl).subscribe((res: any) => { | |||||
| console.log('res7',res); | |||||
| if (res.code == 200) { | |||||
| let sampler1 = (res.data||[]).find((item: any) => (item.samplerIndex||0) == 0)||{} | |||||
| let sampler2 = (res.data||[]).find((item: any) => (item.samplerIndex||0) == 1)||{} | |||||
| this.samplers = { | |||||
| completedCount1: sampler1.completedCount, | |||||
| pendingCount1: sampler1.pendingCount, | |||||
| nowCount1: 0, | |||||
| completedCount2: sampler2.completedCount, | |||||
| pendingCount2: sampler2.pendingCount, | |||||
| nowCount2: 0, | |||||
| } | |||||
| } else { | |||||
| //this.msg.error(res.msg); | |||||
| } | |||||
| }); | |||||
| } | |||||
| //火车出厂数据 | |||||
| trainOut: any = { | |||||
| arrivedCount: 0, | |||||
| exitCount: 0, | |||||
| nowCount: 0 | |||||
| }; | |||||
| getTrainOut(){ | |||||
| this.http.get(this.trainOutUrl).subscribe((res: any) => { | |||||
| console.log('res4',res); | |||||
| if (res.code == 200) { | |||||
| this.trainOut['exitCount'] = res.data?.exitCount||0 | |||||
| } else { | |||||
| //this.msg.error(res.msg); | |||||
| } | |||||
| }); | |||||
| } | |||||
| //自动制样 | |||||
| autoSampling: any = {}; | |||||
| getAutoSampling(){ | |||||
| this.http.get(this.autoSamplingUrl).subscribe((res: any) => { | |||||
| console.log('res8',res); | |||||
| if (res.code == 200) { | |||||
| this.autoSampling = res.data||{} | |||||
| } else { | |||||
| //this.msg.error(res.msg); | |||||
| } | |||||
| }); | |||||
| } | |||||
| //自动存样 | |||||
| storage: any = {}; | |||||
| getStorage(){ | |||||
| this.http.get(this.storageUrl).subscribe((res: any) => { | |||||
| console.log('res9',res); | |||||
| if (res.code == 200) { | |||||
| this.storage = res.data||{} | |||||
| } else { | |||||
| //this.msg.error(res.msg); | |||||
| } | |||||
| }); | |||||
| } | |||||
| //自动化验 | |||||
| assay: any = {}; | |||||
| getAssay(){ | |||||
| this.http.get(this.assayUrl).subscribe((res: any) => { | |||||
| console.log('res10',res); | |||||
| if (res.code == 200) { | |||||
| this.assay = res.data||{} | |||||
| } else { | |||||
| //this.msg.error(res.msg); | |||||
| } | |||||
| }); | |||||
| } | |||||
| modalStyles = { | |||||
| top: 0 | |||||
| }; | |||||
| editVisible = false; | |||||
| src: SafeResourceUrl = ''; | |||||
| onClick(flag: any){ | |||||
| this.editVisible = true; | |||||
| let url = 'overflow' | |||||
| if(flag == 'assay'){ | |||||
| url = 'datav/automatic-assay' | |||||
| } | |||||
| if(flag == 'storage'){ | |||||
| url = 'datav/cunyanggui' | |||||
| } | |||||
| if(flag == 'autoSampling'){ | |||||
| url = 'datav/auto-sample' | |||||
| } | |||||
| if(flag == 'samplers'){ | |||||
| url = 'datav/sampling-docking' | |||||
| } | |||||
| if(flag == 'vehicleSampling'){ | |||||
| url = 'datav/car-sampling' | |||||
| } | |||||
| if(flag == 'vehicleHeavy'){ | |||||
| url = 'datav/truck-scale' | |||||
| } | |||||
| if(flag == 'vehicleLight'){ | |||||
| url = 'datav/truck-scale2' | |||||
| } | |||||
| this.src = this.sanitizer.bypassSecurityTrustResourceUrl( | |||||
| `${window.location.origin}/#/${url}` | |||||
| ); | |||||
| } | |||||
| close() { | |||||
| this.editVisible = false; | |||||
| } | |||||
| //在线全水 | |||||
| water: any = {}; | |||||
| getWater(){ | |||||
| this.http.get(this.waterUrl).subscribe((res: any) => { | |||||
| console.log('res11',res); | |||||
| if (res.code == 200) { | |||||
| this.water = res.data||{} | |||||
| } else { | |||||
| //this.msg.error(res.msg); | |||||
| } | |||||
| }); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,127 @@ | |||||
| @import '@delon/theme/index'; | |||||
| :host ::ng-deep { | |||||
| .langs { | |||||
| width: 100%; | |||||
| height: 40px; | |||||
| line-height: 44px; | |||||
| text-align: right; | |||||
| .anticon { | |||||
| cursor: pointer; | |||||
| margin-top: 24px; | |||||
| margin-right: 24px; | |||||
| font-size: 14px; | |||||
| vertical-align: top; | |||||
| } | |||||
| } | |||||
| .contain { | |||||
| display: flex; // 启用 flex 布局 | |||||
| flex-direction: column; | |||||
| // min-height: 100vh; | |||||
| background: #f0f2f5; | |||||
| overflow: auto; | |||||
| // height: 100%; | |||||
| // overflow: hidden; | |||||
| width: 100%; | |||||
| justify-content: center; // 先将内容垂直居中 | |||||
| position: relative; | |||||
| } | |||||
| // ... 已有代码 ... | |||||
| .wrap { | |||||
| // position: absolute; | |||||
| padding: 32px 0; | |||||
| z-index: 10; | |||||
| position: absolute; // 确保 .wrap 是相对定位 | |||||
| right:160px; | |||||
| } | |||||
| .video { | |||||
| // width: 100%; | |||||
| // height: 100%; | |||||
| // z-index: 1; | |||||
| object-fit: cover; | |||||
| // position: absolute; | |||||
| } | |||||
| .ant-form-item { | |||||
| display: flex; | |||||
| justify-content: space-between; | |||||
| margin-bottom: 24px; | |||||
| } | |||||
| .top { | |||||
| text-align: center; | |||||
| } | |||||
| .header { | |||||
| height: 44px; | |||||
| line-height: 44px; | |||||
| a { | |||||
| text-decoration: none; | |||||
| } | |||||
| } | |||||
| .logo { | |||||
| height: 44px; | |||||
| margin-right: 16px; | |||||
| } | |||||
| .title { | |||||
| position: relative; | |||||
| font-family: 'Myriad Pro', 'Helvetica Neue', Arial, Helvetica, sans-serif; | |||||
| font-size: 33px; | |||||
| font-weight: 600; | |||||
| color: @heading-color; | |||||
| vertical-align: middle; | |||||
| } | |||||
| .desc { | |||||
| margin-top: 12px; | |||||
| margin-bottom: 40px; | |||||
| font-size: @font-size-base; | |||||
| color: @text-color-secondary; | |||||
| } | |||||
| .container { | |||||
| position: relative; | |||||
| } | |||||
| } | |||||
| [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,75 @@ | |||||
| import { Component, OnInit, inject, AfterViewInit, ViewChild } from '@angular/core'; | |||||
| import { RouterOutlet, Router } from '@angular/router'; | |||||
| import { GlobalFooterModule } from '@delon/abc/global-footer'; | |||||
| import { DA_SERVICE_TOKEN } from '@delon/auth'; | |||||
| import { ThemeBtnComponent } from '@delon/theme/theme-btn'; | |||||
| import { NzIconModule } from 'ng-zorro-antd/icon'; | |||||
| @Component({ | |||||
| selector: 'layout-passport', | |||||
| template: ` | |||||
| <div class="contain"> | |||||
| <video class="video" autoplay muted loop id="bgvid"> | |||||
| <source src="../../assets/login/login.mp4" type="video/mp4" /> | |||||
| 您的浏览器不支持视频标签。 | |||||
| </video> | |||||
| <div class="wrap"> | |||||
| <div class="top"> | |||||
| <div class="head"> | |||||
| <!-- <img class="logo" src="./assets/logo-color.svg" /> --> | |||||
| <!-- <span class="title">全过程智能燃料管理系统</span> --> | |||||
| </div> | |||||
| <div class="desc"></div> | |||||
| </div> | |||||
| <router-outlet /> | |||||
| <!-- <global-footer [links]="links"> | |||||
| Copyright | |||||
| <i class="anticon anticon-copyright"></i> 2023 <a href="//github.com/cipchk" target="_blank">卡色</a>出品 | |||||
| </global-footer> --> | |||||
| </div> | |||||
| </div> | |||||
| `, | |||||
| styleUrls: ['./passport.component.less'], | |||||
| standalone: true, | |||||
| imports: [RouterOutlet, GlobalFooterModule, NzIconModule, ThemeBtnComponent] | |||||
| }) | |||||
| export class LayoutPassportComponent implements OnInit { | |||||
| private tokenService = inject(DA_SERVICE_TOKEN); | |||||
| private readonly router = inject(Router); | |||||
| @ViewChild('myVideo') myVideo: any; | |||||
| links = [ | |||||
| { | |||||
| title: '帮助', | |||||
| href: '' | |||||
| }, | |||||
| { | |||||
| title: '隐私', | |||||
| href: '' | |||||
| }, | |||||
| { | |||||
| title: '条款', | |||||
| href: '' | |||||
| } | |||||
| ]; | |||||
| ngOnInit(): void { | |||||
| // **🔹 判断是否为移动端** | |||||
| var ua = navigator.userAgent.toLowerCase(); | |||||
| var isMobile = /android|iphone|ipod|ipad|windows phone|blackberry|mobile/.test(ua); | |||||
| // **🔹 进行路由跳转** | |||||
| if (isMobile) { | |||||
| this.router.navigateByUrl('/mobile'); | |||||
| } | |||||
| setTimeout(() => { | |||||
| const width = document.querySelector('body')?.style.width; | |||||
| const height = document.querySelector('body')?.offsetHeight; | |||||
| const video: any = document.querySelector('.video'); | |||||
| video.style.width = width; | |||||
| video.style.height = `${height}px`; | |||||
| console.log(width, height, 'width'); | |||||
| console.log(height, 'height'); | |||||
| }, 0); | |||||
| this.tokenService.clear(); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,11 @@ | |||||
| <nz-tabset | |||||
| class="tabs tab-box" | |||||
| (nzSelectedIndexChange)="selectTab($event)" | |||||
| [(nzSelectedIndex)]="selectedIndex" | |||||
| (nzClose)="closeTab($event)" | |||||
| nzType="editable-card" | |||||
| > | |||||
| <nz-tab *ngFor="let tab of tabs; let i = index" nzClosable [nzTitle]="tab.title" /> | |||||
| </nz-tabset> | |||||
| <!-- <img width="16" class="zuo" src="../../../assets/common/zuo.svg" alt="" (click)="nav('jian')"> | |||||
| <img width="16" class="you" src="../../../assets/common/you.svg" alt="" (click)="nav('add')"> --> | |||||
| @@ -0,0 +1,70 @@ | |||||
| .tabs { | |||||
| width: 100%; | |||||
| right: 8px; | |||||
| } | |||||
| ::ng-deep .ant-tabs-nav-add { | |||||
| display: none; | |||||
| } | |||||
| ::ng-deep #nz-tabs-0-tab-0 { | |||||
| button { | |||||
| display: none; | |||||
| } | |||||
| } | |||||
| ::ng-deep .ant-tabs-card>.ant-tabs-nav .ant-tabs-tab-active, | |||||
| .ant-tabs-card>div>.ant-tabs-nav .ant-tabs-tab-active { | |||||
| background-color: rgb(24, 144, 255) !important; | |||||
| } | |||||
| ::ng-deep .ant-tabs-nav-list { | |||||
| .ant-tabs-tab { | |||||
| &:first-child { | |||||
| svg { | |||||
| display: none !important; | |||||
| } | |||||
| } | |||||
| } | |||||
| .ant-tabs-tab-remove { | |||||
| right: 11px !important; | |||||
| } | |||||
| } | |||||
| ::ng-deep .ant-tabs-tab { | |||||
| height: 32px; | |||||
| margin-top: 3px !important; | |||||
| margin-right: 6px !important; | |||||
| border-radius: 3px !important; | |||||
| } | |||||
| ::ng-deep .ant-tabs>.ant-tabs-nav .ant-tabs-nav-wrap, | |||||
| .ant-tabs>div>.ant-tabs-nav .ant-tabs-nav-wrap { | |||||
| height: 40px; | |||||
| } | |||||
| .zuo { | |||||
| position: absolute; | |||||
| top: 11px; | |||||
| left: 12px; | |||||
| z-index: 1111; | |||||
| cursor: pointer; | |||||
| } | |||||
| .you { | |||||
| position: absolute; | |||||
| top: 11px; | |||||
| right: 16px; | |||||
| z-index: 1111; | |||||
| cursor: pointer; | |||||
| } | |||||
| :host { | |||||
| ::ng-deep .ant-tabs-nav-wrap { | |||||
| padding-left: 8px !important; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,325 @@ | |||||
| import { Component, OnInit, inject, Input, ViewChild, AfterViewInit, ElementRef, HostListener } from '@angular/core'; | |||||
| import { Router, ActivatedRoute, NavigationEnd } from '@angular/router'; | |||||
| import { ALLOW_ANONYMOUS, DA_SERVICE_TOKEN } from '@delon/auth'; | |||||
| import { I18nPipe, MenuService, SettingsService, User } from '@delon/theme'; | |||||
| import { SharedModule } from '@shared'; | |||||
| import { NzIconModule } from 'ng-zorro-antd/icon'; | |||||
| import { NzTabSetComponent } from 'ng-zorro-antd/tabs'; | |||||
| import { forkJoin } from 'rxjs'; | |||||
| import { EventBusService } from '../../shared/event-bus.service'; | |||||
| const TABS_STORAGE_KEY = 'openedTabs'; | |||||
| const SELECTED_INDEX_STORAGE_KEY = 'selectedTabIndex'; | |||||
| @Component({ | |||||
| selector: 'tabs', | |||||
| templateUrl: './tabs.component.html', | |||||
| styleUrls: ['./tabs.component.less'], | |||||
| standalone: true, | |||||
| imports: [SharedModule, NzIconModule] | |||||
| }) | |||||
| export class TabsComponent implements OnInit, AfterViewInit { | |||||
| private menuService = inject(MenuService); | |||||
| private router = inject(Router); | |||||
| private route = inject(ActivatedRoute); | |||||
| private tokenService = inject(DA_SERVICE_TOKEN); | |||||
| tabs: any[] = []; | |||||
| selectedIndex: number = 0; // 默认选中第一个标签 | |||||
| @ViewChild('tabSet', { static: false }) tabSet!: NzTabSetComponent; | |||||
| @ViewChild('tabSet', { read: ElementRef, static: false }) tabSetElement!: ElementRef; | |||||
| ngAfterViewInit() { | |||||
| this.checkTabWidth(); | |||||
| } | |||||
| onResize() { | |||||
| this.checkTabWidth(); | |||||
| } | |||||
| private checkTabWidth() { | |||||
| if (this.tabSetElement) { | |||||
| const tabSetNativeElement = this.tabSetElement.nativeElement; | |||||
| const tabsContainer = tabSetNativeElement.querySelector('.ant-tabs-nav-scroll'); | |||||
| if (tabsContainer) { | |||||
| const scrollWidth = tabsContainer.scrollWidth; | |||||
| const clientWidth = tabsContainer.clientWidth; | |||||
| this.showArrows = scrollWidth > clientWidth; | |||||
| } | |||||
| } | |||||
| } | |||||
| showArrows = false; | |||||
| private readonly eventBusService = inject(EventBusService); | |||||
| ngOnInit() { | |||||
| this.loadTabsFromStorage(); | |||||
| this.initTabs(); | |||||
| this.setupRouteListener(); | |||||
| this.eventBusService.closeTab$.subscribe(url => { | |||||
| let newUrl = url.split('?')[0]; | |||||
| const index = this.tabs.findIndex(tab => { | |||||
| console.log(tab.path, '=====', url, 'zz'); | |||||
| return tab.path.indexOf(newUrl) !== -1; | |||||
| }); | |||||
| console.log(index, 'hanxiaosong'); | |||||
| if (index > -1) { | |||||
| this.closeTab({ index }); | |||||
| } | |||||
| }); | |||||
| } | |||||
| /** | |||||
| * 从 sessionStorage 加载页签信息 | |||||
| */ | |||||
| private loadTabsFromStorage() { | |||||
| const savedTabs = sessionStorage.getItem(TABS_STORAGE_KEY); | |||||
| const savedIndex = sessionStorage.getItem(SELECTED_INDEX_STORAGE_KEY); | |||||
| if (savedTabs) { | |||||
| this.tabs = JSON.parse(savedTabs); | |||||
| } else { | |||||
| this.tabs = [{ title: '首页', path: '/' }]; | |||||
| } | |||||
| if (savedIndex) { | |||||
| this.selectedIndex = parseInt(savedIndex, 10); | |||||
| } else { | |||||
| this.selectedIndex = 0; | |||||
| } | |||||
| } | |||||
| addTab(title: string, route: string, params?: any): void { | |||||
| const [basePath, existingQueryString] = route.split('?'); | |||||
| const newQueryParams = params ? new URLSearchParams(params).toString() : ''; | |||||
| const fullPath = existingQueryString | |||||
| ? `${basePath}?${existingQueryString}${newQueryParams ? `&${newQueryParams}` : ''}` | |||||
| : newQueryParams | |||||
| ? `${basePath}?${newQueryParams}` | |||||
| : basePath; | |||||
| const existingTab = this.tabs.find(tab => { | |||||
| const [tabBasePath, tabQueryString] = tab.path.split('?'); | |||||
| return tabBasePath === basePath && tabQueryString === newQueryParams; | |||||
| }); | |||||
| if (!existingTab) { | |||||
| // 直接使用原始标题,移除详情和审批逻辑 | |||||
| this.tabs.push({ title, path: fullPath }); | |||||
| this.navigateToTab(this.tabs.length - 1); | |||||
| } else { | |||||
| this.navigateToTab(this.tabs.indexOf(existingTab)); | |||||
| } | |||||
| this.saveTabsToStorage(); | |||||
| this.checkTabWidth(); // 检查页签宽度 | |||||
| } | |||||
| closeTab(obj: any): void { | |||||
| if (this.tabs.length > 1) { | |||||
| const removedIndex = obj.index; | |||||
| this.tabs.splice(removedIndex, 1); | |||||
| if (removedIndex <= this.selectedIndex) { | |||||
| this.selectedIndex = Math.max(0, this.selectedIndex - 1); | |||||
| } | |||||
| this.navigateToTab(this.selectedIndex); | |||||
| } else { | |||||
| this.router.navigate(['/home']); | |||||
| } | |||||
| this.saveTabsToStorage(); | |||||
| this.checkTabWidth(); // 检查页签宽度 | |||||
| } | |||||
| /** | |||||
| * 保存页签信息到 sessionStorage | |||||
| */ | |||||
| private saveTabsToStorage() { | |||||
| let token: any = this.tokenService.get(); | |||||
| if (Object.keys(token).length) { | |||||
| sessionStorage.setItem(TABS_STORAGE_KEY, JSON.stringify(this.tabs)); | |||||
| sessionStorage.setItem(SELECTED_INDEX_STORAGE_KEY, this.selectedIndex.toString()); | |||||
| } else { | |||||
| sessionStorage.removeItem(TABS_STORAGE_KEY); | |||||
| sessionStorage.removeItem(SELECTED_INDEX_STORAGE_KEY); | |||||
| } | |||||
| } | |||||
| /** | |||||
| * 初始化页签 | |||||
| */ | |||||
| private initTabs() { | |||||
| this.handleCurrentRoute(); | |||||
| this.hideHomeTabCloseButton(); | |||||
| } | |||||
| /** | |||||
| * 处理当前路由 | |||||
| */ | |||||
| private handleCurrentRoute() { | |||||
| if (this.router.url !== '/') { | |||||
| let token: any = this.tokenService.get(); | |||||
| if (Object.keys(token).length) { | |||||
| const menu = this.findNodeByPath(this.menuService['data'], this.router.url); | |||||
| if (menu) { | |||||
| this.addTabIfNotExists(menu); | |||||
| } else { | |||||
| console.log('未找到匹配的菜单项,跳转到登录页'); | |||||
| this.router.navigateByUrl('/passport/login'); | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| /** | |||||
| * 隐藏首页页签的关闭按钮 | |||||
| */ | |||||
| private hideHomeTabCloseButton() { | |||||
| if (this.router.url === '/') { | |||||
| const node: any = document.querySelector('.ant-tabs-tab-remove'); | |||||
| if (node) { | |||||
| node.style.display = 'none'; | |||||
| } | |||||
| } | |||||
| } | |||||
| /** | |||||
| * 设置路由监听器 | |||||
| */ | |||||
| private setupRouteListener() { | |||||
| this.router.events.subscribe(event => { | |||||
| if (event instanceof NavigationEnd) { | |||||
| const menu = this.findNodeByPath(this.menuService['data'], event.url); | |||||
| if (menu) { | |||||
| this.addTabIfNotExists(menu); | |||||
| } | |||||
| // this.saveTabsToStorage(); | |||||
| } | |||||
| }); | |||||
| } | |||||
| /** | |||||
| * 通过路径查找菜单项节点 | |||||
| * @param data 菜单数据数组 | |||||
| * @param path 要查找的路径,可能包含路由参数 | |||||
| * @returns 匹配的菜单项节点,如果未找到则返回 null | |||||
| */ | |||||
| findNodeByPath(data: any[] = [], path: string) { | |||||
| const [basePath, queryString] = path.split('?'); | |||||
| const queryParams = queryString ? new URLSearchParams(queryString) : null; | |||||
| for (const item of data) { | |||||
| const itemBasePath = item.link?.split('?')[0]; | |||||
| if (itemBasePath === basePath) { | |||||
| const matchedItem = { ...item }; | |||||
| if (queryParams) { | |||||
| matchedItem.queryParams = {}; | |||||
| for (const [key, value] of queryParams.entries()) { | |||||
| matchedItem.queryParams[key] = value; | |||||
| } | |||||
| } | |||||
| return matchedItem; | |||||
| } else if (item.children && item.children.length > 0) { | |||||
| const result: any = this.findNodeByPath(item.children, path); | |||||
| if (result) { | |||||
| return result; | |||||
| } | |||||
| } | |||||
| } | |||||
| return null; | |||||
| } | |||||
| /** | |||||
| * 如果页签不存在则添加 | |||||
| * @param menu 菜单项 | |||||
| */ | |||||
| private addTabIfNotExists(menu: any) { | |||||
| const fullPath = this.buildFullPath(menu); | |||||
| const [basePath, queryString] = fullPath.split('?'); | |||||
| const existingTab = this.tabs.find(tab => { | |||||
| const [tabBasePath, tabQueryString] = tab.path.split('?'); | |||||
| return tabBasePath === basePath && tabQueryString === queryString; | |||||
| }); | |||||
| if (!existingTab) { | |||||
| // 直接使用菜单名称,移除详情和审批逻辑 | |||||
| this.tabs.push({ title: menu.menuName, path: fullPath }); | |||||
| this.selectedIndex = this.tabs.length - 1; | |||||
| } else { | |||||
| this.selectedIndex = this.tabs.indexOf(existingTab); | |||||
| } | |||||
| } | |||||
| /** | |||||
| * 构建完整路径,包含查询参数 | |||||
| * @param menu 菜单项 | |||||
| * @returns 完整路径 | |||||
| */ | |||||
| private buildFullPath(menu: any) { | |||||
| let fullPath = menu.link; | |||||
| if (menu.queryParams) { | |||||
| const queryString = Object.entries(menu.queryParams) | |||||
| .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value as string)}`) | |||||
| .join('&'); | |||||
| fullPath += fullPath.includes('?') ? `&${queryString}` : `?${queryString}`; | |||||
| } | |||||
| return fullPath; | |||||
| } | |||||
| /** | |||||
| * 添加页签 | |||||
| * @param title 页签标题 | |||||
| * @param route 路由路径 | |||||
| * @param params 路由参数 | |||||
| */ | |||||
| /** | |||||
| * 导航到指定页签 | |||||
| * @param index 页签索引 | |||||
| */ | |||||
| navigateToTab(index: number): void { | |||||
| const tab = this.tabs[index]; | |||||
| console.log(tab, 'tab'); | |||||
| let path = tab.path; | |||||
| let queryParams = {}; | |||||
| if (tab.path.includes('?')) { | |||||
| const [basePath, queryString] = tab.path.split('?'); | |||||
| path = basePath; | |||||
| queryParams = Object.fromEntries(new URLSearchParams(queryString).entries()); | |||||
| } | |||||
| this.router.navigate([path], { queryParams }); | |||||
| } | |||||
| /** | |||||
| * 选择页签 | |||||
| * @param index 页签索引 | |||||
| */ | |||||
| selectTab(index: number): void { | |||||
| this.selectedIndex = index; | |||||
| this.navigateToTab(index); | |||||
| // this.saveTabsToStorage(); | |||||
| } | |||||
| /** | |||||
| * 导航到相邻页签 | |||||
| * @param value 'add' 表示下一个页签,其他值表示上一个页签 | |||||
| */ | |||||
| nav(value: string) { | |||||
| if (value === 'add' && this.selectedIndex < this.tabs.length - 1) { | |||||
| this.selectedIndex++; | |||||
| } else if (value !== 'add' && this.selectedIndex > 0) { | |||||
| this.selectedIndex--; | |||||
| } | |||||
| this.navigateToTab(this.selectedIndex); | |||||
| // this.saveTabsToStorage(); | |||||
| } | |||||
| // 新增方法:退出登录时清空 sessionStorage | |||||
| clearTabsOnLogout() { | |||||
| sessionStorage.removeItem(TABS_STORAGE_KEY); | |||||
| sessionStorage.removeItem(SELECTED_INDEX_STORAGE_KEY); | |||||
| this.tabs = [{ title: '首页', path: '/' }]; | |||||
| this.selectedIndex = 0; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,111 @@ | |||||
| <layout-default [options]="options" [nav]="null" [content]="contentTpl" [customError]="null"> | |||||
| <layout-default-header-item direction="left"> | |||||
| <span class="title">{{ title }}</span> | |||||
| </layout-default-header-item> | |||||
| <layout-default-header-item direction="right" hidden="mobile"> | |||||
| <header-fullscreen style="margin-right: 32px" /> | |||||
| </layout-default-header-item> | |||||
| <layout-default-header-item direction="right" style="vertical-align: top"> | |||||
| <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" /> | |||||
| <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">个人中心</li> | |||||
| <li nz-menu-item routerLink="/pro/account/settings">个人设置</li> | |||||
| </ul> | |||||
| </nz-dropdown-menu> | |||||
| </ng-template> | |||||
| <div class="tab-pane"> | |||||
| <tabs /> | |||||
| </div> | |||||
| <!-- 应用菜单 --> | |||||
| <div class="system"> | |||||
| <div class="logo"> | |||||
| <img style="width: 50px; height: 50px" src="../../../assets/logo.png" alt="" /> | |||||
| <!-- <img width="60" src="../../../assets/common/auseft.png" alt=""> --> | |||||
| </div> | |||||
| <div class="system-item"> | |||||
| <i class="oprateLeft material-icons menuCollapsed" (click)="collapsed()">menu</i> | |||||
| </div> | |||||
| <div class="primary-menu"> | |||||
| <div class="system-li" title="化验数据录核" [class.activeItem]="activeValue == 1" (click)="navigateToLaboratoryWithParams()"> | |||||
| <i class="material-icons">home</i> | |||||
| </div> | |||||
| <!-- <div class="system-li" title="燃料" (click)="toggleActive(2)" [class.activeItem]="activeValue==2"> | |||||
| <i class="material-icons">palette</i> | |||||
| </div> | |||||
| <div class="system-li" title="燃料" (click)="toggleActive(3)" [class.activeItem]="activeValue==3"> | |||||
| <i class="material-icons">account_balance_wallet</i> | |||||
| </div> | |||||
| <div class="system-li" title="燃料" (click)="toggleActive(4)" [class.activeItem]="activeValue==4"> | |||||
| <i class="material-icons">image</i> | |||||
| </div> --> | |||||
| <div class="set"> | |||||
| <div layout-default-header-item-trigger nz-dropdown [nzDropdownMenu]="settingsMenu" nzPlacement="bottomRight"> | |||||
| <i class="material-icons" style="font-size: 24px !important; color: rgb(166, 178, 178)">settings</i> | |||||
| </div> | |||||
| <nz-dropdown-menu #settingsMenu="nzDropdownMenu"> | |||||
| <div nz-menu style="width: 200px"> | |||||
| <div nz-menu-item> | |||||
| <header-fullscreen /> | |||||
| </div> | |||||
| <div nz-menu-item> | |||||
| <header-clear-storage /> | |||||
| </div> | |||||
| <div class="theme" nz-menu-item [class.active]="theme === 'dark'" (click)="switchTheme('dark')">黑色主题</div> | |||||
| <div class="theme" nz-menu-item [class.active]="theme === 'white'" (click)="switchTheme('white')">白色主题</div> | |||||
| </div> | |||||
| </nz-dropdown-menu> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| <layout-default-header-item class="head-right" direction="right"> | |||||
| <header-date /> | |||||
| </layout-default-header-item> | |||||
| <layout-default-header-item direction="right"> | |||||
| <div data-tauri-drag-region class="titlebar" id="titlebar" [hidden]="!isTauri"> | |||||
| <div class="titlebar-button" id="titlebar-minimize" (click)="minimize()"> | |||||
| <img src="../../../assets/window/minsize.svg" alt="minimize" /> | |||||
| </div> | |||||
| <div class="titlebar-button" id="titlebar-maximize" (click)="toggleMaximize()" [hidden]="!isMax"> | |||||
| <img src="../../../assets/window/window-window_line.svg" alt="maximize" /> | |||||
| </div> | |||||
| <div class="titlebar-button" id="titlebar-maximize" (click)="toggleMaximize()" [hidden]="isMax"> | |||||
| <img src="../../../assets/window/max-size.svg" alt="maximize" /> | |||||
| </div> | |||||
| <div class="titlebar-button" id="titlebar-close" (click)="close()"> | |||||
| <img src="../../../assets/window/Close.svg" alt="close" /> | |||||
| </div> | |||||
| </div> | |||||
| </layout-default-header-item> | |||||
| <layout-default-header-item direction="right"> | |||||
| <div data-tauri-drag-region class="titleDragger" id="titlebar" [hidden]="!isTauri"> </div> | |||||
| </layout-default-header-item> | |||||
| <ng-template #contentTpl> | |||||
| <div class="content-container"> | |||||
| <router-outlet /> | |||||
| </div> | |||||
| </ng-template> | |||||
| <!-- 固定面包屑导航 --> | |||||
| <nz-card class="breadcrumb fixed-breadcrumb"> | |||||
| <nz-breadcrumb [nzSeparator]="null"> | |||||
| <nz-breadcrumb-item *ngFor="let menu of breadcrumb"> | |||||
| <span *ngIf="menu.menuName === '智慧燃料'"> | |||||
| <i class="material-icons first-name">home</i><span>{{ menu.menuName }}</span></span | |||||
| > | |||||
| <span *ngIf="menu.menuName !== '智慧燃料'"> {{ menu.menuName }}</span> | |||||
| <nz-breadcrumb-separator>></nz-breadcrumb-separator> | |||||
| </nz-breadcrumb-item> | |||||
| <nz-breadcrumb-item *ngIf="activeRoute" class="activeMenu">{{ activeRoute.menuName }}</nz-breadcrumb-item> | |||||
| </nz-breadcrumb> | |||||
| </nz-card> | |||||
| </layout-default> | |||||
| @@ -0,0 +1,803 @@ | |||||
| // 系统名字样式 | |||||
| ::ng-deep .sys_title { | |||||
| color: #fff; | |||||
| font-size: 18px; | |||||
| line-height: 52px; | |||||
| height: 52px; | |||||
| position: absolute; | |||||
| top: 0px; | |||||
| left: 58px; | |||||
| padding-left: 16px; | |||||
| font-weight: bold; | |||||
| width: 220px; | |||||
| background-color: rgb(0, 33, 64) !important; | |||||
| visibility: hidden; | |||||
| } | |||||
| ::ng-deep .add { | |||||
| margin-top: -38px; | |||||
| } | |||||
| ::ng-deep .card-search { | |||||
| margin-bottom: 6px !important; | |||||
| } | |||||
| ::ng-deep .ant-modal-body { | |||||
| // padding: 24px 8px !important; | |||||
| padding: 0 8px!important; | |||||
| } | |||||
| ::ng-deep .ant-form-item-label>label.ant-form-item-required:not(.ant-form-item-required-mark-optional)::before { | |||||
| position: absolute; | |||||
| right: -7px; | |||||
| top: 11px; | |||||
| } | |||||
| ::ng-deep .anticon-fullscreen { | |||||
| cursor: pointer; | |||||
| svg { | |||||
| fill: #fff !important; | |||||
| } | |||||
| } | |||||
| ::ng-deep .anticon-fullscreen-exit { | |||||
| cursor: pointer; | |||||
| svg { | |||||
| fill: #fff !important; | |||||
| } | |||||
| } | |||||
| ::ng-deep a.sidebar-nav__item-link { | |||||
| color: #a5acb3; | |||||
| } | |||||
| :ng-deep .container-block .card-block { | |||||
| margin-bottom: 8px; | |||||
| } | |||||
| ::ng-deep .ant-breadcrumb .ant-breadcrumb-link { | |||||
| color: #666; | |||||
| } | |||||
| .oprateLeft { | |||||
| cursor: pointer; | |||||
| border-bottom: 1px solid rgba(255, 255, 255, 0.1); | |||||
| } | |||||
| .title { | |||||
| position: absolute; | |||||
| top: 10px; | |||||
| font-size: 20px; | |||||
| font-weight: bold; | |||||
| margin: auto; | |||||
| display: block; | |||||
| height: 50px; | |||||
| width: 30%; | |||||
| text-align: center; | |||||
| margin: auto; | |||||
| left: 0; | |||||
| right: 0; | |||||
| } | |||||
| // 设置 | |||||
| .set { | |||||
| position: absolute; | |||||
| bottom: 10px; | |||||
| left: 5px; | |||||
| } | |||||
| .active { | |||||
| color: rgb(19, 194, 194); | |||||
| } | |||||
| .theme { | |||||
| padding-left: 30px; | |||||
| } | |||||
| ::ng-deep .alain-default__header { | |||||
| box-shadow: none !important; | |||||
| height: 52px; | |||||
| } | |||||
| ::ng-deep .ant-card-bordered { | |||||
| border: 0px solid rgba(18, 189, 189, 1); | |||||
| } | |||||
| ::ng-deep .sidebar-nav__group-title { | |||||
| margin-left: -8px; | |||||
| background-color: rgba(100, 255, 255, 0.3); | |||||
| color: #fff; | |||||
| font-size: 16px; | |||||
| } | |||||
| ::ng-deep .alain-default__fixed .alain-default__aside { | |||||
| background-color: rgb(0, 33, 64) !important; | |||||
| transform: translate3d(0%, 0, 0) !important; | |||||
| border: 0px solid rgba(18, 189, 189, 1); | |||||
| height: calc(100% - 52px); | |||||
| margin-top: 52px !important; | |||||
| width: 218px; | |||||
| margin-left: 60px; | |||||
| .arrow { | |||||
| position: absolute; | |||||
| right: 0; | |||||
| /* 使它紧贴父级右边缘 */ | |||||
| top: 0; | |||||
| /* 从顶部开始 */ | |||||
| bottom: 0; | |||||
| /* 高度等于父级 */ | |||||
| width: 3px; | |||||
| /* 宽度设为1px */ | |||||
| // background-color: #215C6F; | |||||
| background-color: transparent !important; | |||||
| /* 将背景色保留为深蓝色 */ | |||||
| z-index: 111111; | |||||
| /* 保持较高的层级 */ | |||||
| cursor: ew-resize; | |||||
| /* 保持鼠标样式为左右拖动 */ | |||||
| height: 100%; | |||||
| } | |||||
| } | |||||
| // ::ng-deep .sidebar-nav__selected | |||||
| ::ng-deep .sidebar-nav__selected { | |||||
| border: none; | |||||
| background: none !important; | |||||
| } | |||||
| ::ng-deep .sidebar-nav__selected>.sidebar-nav__item-link { | |||||
| color: #fff !important; | |||||
| } | |||||
| ::ng-deep .dis-flex { | |||||
| height: calc(100% - 48px); | |||||
| overflow: auto; | |||||
| } | |||||
| ::ng-deep .def-cont-height { | |||||
| height: 100%; | |||||
| overflow: auto; | |||||
| } | |||||
| ::ng-deep .alain-default { | |||||
| margin-top: 0px; | |||||
| .page-header { | |||||
| margin-left: -16px; | |||||
| margin-right: 0; | |||||
| } | |||||
| } | |||||
| ::ng-deep .breadcrumb { | |||||
| .ant-card-body { | |||||
| padding: 6px 16px 6px 16px; | |||||
| } | |||||
| } | |||||
| ::ng-deep .ant-table-tbody>tr>td { | |||||
| background-color: rgb(0, 33, 64); | |||||
| padding: 4px 16px; | |||||
| } | |||||
| ::ng-deep .ant-table-thead { | |||||
| tr { | |||||
| th { | |||||
| padding: 4px 16px; | |||||
| } | |||||
| } | |||||
| } | |||||
| ::ng-deep .ant-card { | |||||
| background-color: rgb(0, 33, 64) !important; | |||||
| border-radius: 4px !important; | |||||
| } | |||||
| ::ng-deep .ant-btn { | |||||
| height: 28px !important; | |||||
| line-height: 19px !important; | |||||
| box-shadow: none !important; | |||||
| text-shadow: none !important; | |||||
| border-radius: 4px !important; | |||||
| } | |||||
| ::ng-deep { | |||||
| .card-search { | |||||
| .ant-select-single.ant-select-show-arrow .ant-select-selection-item, | |||||
| .ant-select-single.ant-select-show-arrow .ant-select-selection-placeholder { | |||||
| line-height: 27px; | |||||
| } | |||||
| .ant-select:not(.ant-select-customize-input) .ant-select-selector { | |||||
| height: 28px; | |||||
| } | |||||
| // 输入框 | |||||
| .ant-input { | |||||
| height: 28px; | |||||
| } | |||||
| } | |||||
| } | |||||
| // 状态样式 | |||||
| ::ng-deep { | |||||
| .ant-tag-green { | |||||
| color: #fff; | |||||
| border: none; | |||||
| background: none; | |||||
| font-size: 14px; | |||||
| &::before { | |||||
| vertical-align: middle; | |||||
| content: ''; | |||||
| display: inline-block; | |||||
| width: 6px; | |||||
| height: 6px; | |||||
| background-color: rgb(82, 196, 26); | |||||
| border-radius: 6px; | |||||
| } | |||||
| span { | |||||
| margin-left: 4px; | |||||
| } | |||||
| } | |||||
| .ant-tag-red { | |||||
| color: #fff; | |||||
| border: none; | |||||
| background: none; | |||||
| font-size: 14px; | |||||
| &::before { | |||||
| vertical-align: middle; | |||||
| content: ''; | |||||
| display: inline-block; | |||||
| width: 6px; | |||||
| height: 6px; | |||||
| background-color: red; | |||||
| border-radius: 6px; | |||||
| } | |||||
| span { | |||||
| margin-left: 4px; | |||||
| } | |||||
| } | |||||
| } | |||||
| // 状态样式 | |||||
| ::ng-deep { | |||||
| .textarea-input { | |||||
| .ant-form-item-control-input { | |||||
| height: auto !important; | |||||
| } | |||||
| } | |||||
| .ant-dropdown-trigger { | |||||
| border: none !important; | |||||
| } | |||||
| } | |||||
| .fixed-breadcrumb { | |||||
| background-color: rgb(0, 33, 64); | |||||
| position: absolute; | |||||
| left: 279px; | |||||
| // bottom: -5px; | |||||
| width: 100%; | |||||
| z-index: 9; | |||||
| box-shadow: 0 -1px 5px rgba(0, 0, 0, 0.1); | |||||
| } | |||||
| .content-container { | |||||
| z-index: 1999; | |||||
| height: 100%; | |||||
| } | |||||
| ::ng-deep .ant-tabs-nav { | |||||
| margin-bottom: 0px !important; | |||||
| } | |||||
| ::ng-deep .ant-form { | |||||
| padding-top: 6px; | |||||
| // width: 100% !important; | |||||
| } | |||||
| //穿透表单 | |||||
| ::ng-deep .ant-card-body { | |||||
| padding: 0px 0px; | |||||
| } | |||||
| //穿透搜索栏) | |||||
| ::ng-deep .sf__inline .ant-form-inline>.sf__item { | |||||
| margin: 0 10px; | |||||
| margin-bottom: 0; | |||||
| } | |||||
| /* 设置滚动条的宽度 */ | |||||
| ::ng-deep .ant-table-content::-webkit-scrollbar { | |||||
| width: 1.2em; | |||||
| } | |||||
| /* 设置滚动条滑块的样式 */ | |||||
| ::ng-deep .ant-table-content::-webkit-scrollbar-thumb { | |||||
| // margin-left: 0px; | |||||
| background-color: #1f7995; | |||||
| border: 2px solid #1f7995; | |||||
| border-radius: 10px; | |||||
| } | |||||
| /* 设置滚动条滑块悬浮时的样式 */ | |||||
| ::ng-deep .ant-table-content::-webkit-scrollbar-thumb:hover { | |||||
| // margin-left: 0px; | |||||
| background-color: #1f7995; | |||||
| } | |||||
| /* 设置滚动条滑块激活(即正在拖动滑块)时的样式 */ | |||||
| ::ng-deep .ant-table-content::-webkit-scrollbar-thumb:active { | |||||
| background-color: #1f7995; | |||||
| } | |||||
| //横向 | |||||
| ::ng-deep .ant-table-content { | |||||
| scrollbar-color: auto; | |||||
| } | |||||
| /* 设置滚动条的宽度 */ | |||||
| ::ng-deep .ant-table-content::-webkit-scrollbar { | |||||
| height: 1.2em; | |||||
| } | |||||
| /* 设置横向滚动条轨道的样式 */ | |||||
| ::ng-deep .ant-table-content::-webkit-scrollbar-track { | |||||
| background-color: transparent; | |||||
| border-color: #00eff8; | |||||
| } | |||||
| /* 设置横向滚动条滑块悬浮时的样式 */ | |||||
| ::ng-deep .ant-table-content::-webkit-scrollbar-thumb:hover { | |||||
| background-color: #1f7995; | |||||
| } | |||||
| /* 设置横向滚动条滑块激活时的样式 */ | |||||
| ::ng-deep .ant-table-content::-webkit-scrollbar-thumb:active { | |||||
| background-color: #1f7995; | |||||
| } | |||||
| /* 设置横向滚动条滑块的颜色 */ | |||||
| ::ng-deep .ant-table-content::-webkit-scrollbar-thumb { | |||||
| background-color: #1f7995; | |||||
| } | |||||
| ::ng-deep .ant-table-fixed-header{ | |||||
| .ant-table-body{ | |||||
| overflow-y:auto !important; | |||||
| } | |||||
| } | |||||
| ::ng-deep .alain-default__nav { | |||||
| position: static !important; | |||||
| li { | |||||
| &:first-child { | |||||
| margin-left: 140px; | |||||
| } | |||||
| } | |||||
| } | |||||
| //系统菜单 | |||||
| .system { | |||||
| width: 60px; | |||||
| height: 100%; | |||||
| position: absolute; | |||||
| background-color: rgb(0, 33, 64); | |||||
| z-index: 9; | |||||
| top: 0; | |||||
| left: 0; | |||||
| border-right: 1px solid rgba(255, 255, 255, 0.1); | |||||
| .logo { | |||||
| height: 52px; | |||||
| color: #fff; | |||||
| line-height: 50px; | |||||
| text-align: center; | |||||
| border-bottom: 1px solid rgba(255, 255, 255, 0.1); | |||||
| } | |||||
| .activeItem { | |||||
| background: linear-gradient(to right, #002140, #003a70); | |||||
| color: #1890ff !important; | |||||
| margin: 4px; | |||||
| width: 40px; | |||||
| height: 40px; | |||||
| border-radius: 2px; | |||||
| margin: auto; | |||||
| } | |||||
| .menuCollapsed:hover { | |||||
| background: linear-gradient(to right, #002140, #003a70); | |||||
| color: #1890ff !important; | |||||
| margin: 4px; | |||||
| width: 40px; | |||||
| height: 40px; | |||||
| border-radius: 2px; | |||||
| margin: auto; | |||||
| } | |||||
| .system-item { | |||||
| height: 38px; | |||||
| text-align: center; | |||||
| color: rgba(255, 255, 255, 0.65); | |||||
| cursor: pointer; | |||||
| border-bottom: 1px solid rgba(255, 255, 255, 0.1); | |||||
| } | |||||
| .activeItem::before { | |||||
| content: ''; | |||||
| position: absolute; | |||||
| left: 0; | |||||
| top: 0; | |||||
| width: 100%; | |||||
| height: 100%; | |||||
| background: rgba(24, 144, 255, 0.1); | |||||
| border-radius: 2px; | |||||
| } | |||||
| .material-icons { | |||||
| line-height: inherit; | |||||
| } | |||||
| .system-li { | |||||
| position: relative; | |||||
| height: 38px; | |||||
| text-align: center; | |||||
| color: rgba(255, 255, 255, 0.65); | |||||
| margin-top: 10px; | |||||
| cursor: pointer; | |||||
| } | |||||
| } | |||||
| .first-name { | |||||
| vertical-align: middle; | |||||
| line-height: inherit; | |||||
| } | |||||
| // 菜单页签选项卡下划线 | |||||
| ::ng-deep .ant-tabs-top>.ant-tabs-nav::before, | |||||
| .ant-tabs-bottom>.ant-tabs-nav::before, | |||||
| .ant-tabs-top>div>.ant-tabs-nav::before, | |||||
| .ant-tabs-bottom>div>.ant-tabs-nav::before { | |||||
| border-bottom: none !important; | |||||
| } | |||||
| /* 设置滚动条的宽度 */ | |||||
| .tab-pane::-webkit-scrollbar { | |||||
| height: 6px; | |||||
| } | |||||
| /* 设置横向滚动条轨道的样式 */ | |||||
| .tab-pane::-webkit-scrollbar-track { | |||||
| background-color: transparent; | |||||
| border-color: #00eff8; | |||||
| } | |||||
| /* 设置横向滚动条滑块悬浮时的样式 */ | |||||
| .tab-pane::-webkit-scrollbar-thumb:hover { | |||||
| background-color: #1f7995; | |||||
| } | |||||
| /* 设置横向滚动条滑块激活时的样式 */ | |||||
| .tab-pane::-webkit-scrollbar-thumb:active { | |||||
| background-color: #1f7995; | |||||
| } | |||||
| /* 设置横向滚动条滑块的颜色 */ | |||||
| .tab-pane::-webkit-scrollbar-thumb { | |||||
| background-color: #1f7995; | |||||
| } | |||||
| ::ng-deep .alain-default__nav-wrap { | |||||
| height: 54px !important; | |||||
| } | |||||
| .branch { | |||||
| img { | |||||
| width: 30px; | |||||
| } | |||||
| } | |||||
| nz-header { | |||||
| position: absolute; | |||||
| } | |||||
| ::ng-deep .alain-default { | |||||
| margin: 0 auto; | |||||
| position: relative; | |||||
| } | |||||
| ::ng-deep .sidebar-nav__item { | |||||
| border: none !important; | |||||
| } | |||||
| // 顶部区域 | |||||
| ::ng-deep .alain-default__fixed .alain-default__header { | |||||
| position: static; | |||||
| width: calc(100vw); | |||||
| border-bottom: 1px solid rgba(255, 255, 255, 0.1) !important; | |||||
| background-color: rgb(0, 33, 64) !important; | |||||
| } | |||||
| // 侧边栏菜单 | |||||
| ::ng-deep .alain-default__fixed .alain-default__aside { | |||||
| position: absolute !important; | |||||
| } | |||||
| ::ng-deep .alain-default__aside::after { | |||||
| display: none; | |||||
| } | |||||
| // 菜单箭头 | |||||
| ::ng-deep .sidebar-nav__item-icon { | |||||
| vertical-align: middle; | |||||
| } | |||||
| ::ng-deep .sidebar-nav__sub-arrow::before { | |||||
| content: ""; | |||||
| position: absolute; | |||||
| display: none; | |||||
| } | |||||
| ::ng-deep .sidebar-nav__open>.sidebar-nav__item-link .sidebar-nav__sub-arrow::before { | |||||
| transform: rotate(180deg); | |||||
| } | |||||
| ::ng-deep .sidebar-nav__sub-arrow::after { | |||||
| content: ""; | |||||
| position: absolute; | |||||
| display: none; | |||||
| } | |||||
| ::ng-deep .sidebar-nav__sub-arrow::before { | |||||
| transform: rotate(90deg); | |||||
| margin-top: -5px; | |||||
| } | |||||
| ::ng-deep .sidebar-nav__sub-arrow::before { | |||||
| display: block; | |||||
| // transform: none; | |||||
| width: 0; | |||||
| height: 0; | |||||
| border-left: 5px solid transparent; | |||||
| border-right: 5px solid transparent; | |||||
| border-bottom: 5px solid #a5acb3; | |||||
| background: none; | |||||
| transform-origin: center center; | |||||
| } | |||||
| ::ng-deep .alain-default__content { | |||||
| position: static !important; | |||||
| margin-left: 284px !important; | |||||
| margin-top: 46px !important; | |||||
| margin-right: 6px !important; | |||||
| height: calc(100% - 148px); | |||||
| overflow-y: auto; | |||||
| overflow-x: hidden; | |||||
| border-radius: 5px; | |||||
| // width: calc(100vw - 287px) !important; | |||||
| } | |||||
| ::ng-deep .alain-default__content::-webkit-scrollbar { | |||||
| width: 8px; | |||||
| /* 滚动条宽度 */ | |||||
| height: 8px; | |||||
| /* 如果是水平滚动条可设置宽度,垂直滚动条可设置高度,根据实际需求调整 */ | |||||
| } | |||||
| ::ng-deep .alain-default__content::-webkit-scrollbar-track { | |||||
| background-color: transparent; | |||||
| /* 滚动条轨道背景色 */ | |||||
| border-radius: 4px; | |||||
| /* 轨道边角圆滑处理 */ | |||||
| } | |||||
| ::ng-deep .alain-default__content::-webkit-scrollbar-thumb { | |||||
| background-color: #24c8db; | |||||
| /* 滚动条滑块的背景色 */ | |||||
| border-radius: 4px; | |||||
| /* 滑块边角圆滑 */ | |||||
| transition: background-color 0.3s ease; | |||||
| /* 添加过渡效果,鼠标悬停时变色更平滑 */ | |||||
| } | |||||
| ::ng-deep .alain-default__content::-webkit-scrollbar-thumb:hover { | |||||
| background-color: #24c8db; | |||||
| /* 鼠标悬停在滑块上时改变的背景色 */ | |||||
| } | |||||
| ::ng-deep .alain-default { | |||||
| height: 100% !important; | |||||
| } | |||||
| ::ng-deep .ant-table-tbody>tr.ant-table-row:hover>td, | |||||
| .ant-table-tbody>tr>td.ant-table-cell-row-hover { | |||||
| background-color: rgba(0, 0, 0, 0.8) !important; | |||||
| } | |||||
| // 页签 | |||||
| .tab-pane { | |||||
| z-index: 9; | |||||
| top: 52px; | |||||
| left: 278px; | |||||
| right: 0px; | |||||
| // width: calc(100% - 279px); | |||||
| // display: grid; | |||||
| align-content: space-between; | |||||
| // overflow-x: auto; | |||||
| // overflow-y: hidden; | |||||
| // overflow:hidden; | |||||
| } | |||||
| .tab-pane { | |||||
| position: absolute; | |||||
| // padding-left: 32px; | |||||
| // padding: 0 30px; | |||||
| background-color: rgb(0, 33, 64); | |||||
| ::ng-deep .ant-tabs-nav { | |||||
| width: 100%; | |||||
| .ant-tabs-nav-wrap { | |||||
| width: calc(100% - 565px); | |||||
| } | |||||
| } | |||||
| } | |||||
| ::ng-deep .ant-tabs-tab:hover { | |||||
| color: #fff !important; | |||||
| background-color: rgb(24, 144, 255) !important; | |||||
| } | |||||
| ::ng-deep .ant-tabs-card.ant-tabs-top>.ant-tabs-nav .ant-tabs-tab-active, | |||||
| .ant-tabs-card.ant-tabs-top>div>.ant-tabs-nav .ant-tabs-tab-active { | |||||
| border-bottom-color: #303030 !important; | |||||
| } | |||||
| ::ng-deep .ant-tabs-nav .ant-tabs-tab-active { | |||||
| color: #fff !important; | |||||
| padding-right: 12px !important; | |||||
| } | |||||
| ::ng-deep .tab-pane .ant-tabs-tab.ant-tabs-tab-active .ant-tabs-tab-btn { | |||||
| color: #fff; | |||||
| } | |||||
| ::ng-deep .sidebar-nav__item-link:hover { | |||||
| color: #fff; | |||||
| } | |||||
| ::ng-deep .sidebar-nav__open { | |||||
| >a.sidebar-nav__item-link { | |||||
| color: #fff; | |||||
| } | |||||
| } | |||||
| ::ng-deep li.sidebar-nav__open { | |||||
| background: #003a70; | |||||
| } | |||||
| ::ng-deep .sidebar-nav__open>.sidebar-nav__sub { | |||||
| background-color: #000; | |||||
| } | |||||
| ::ng-deep .sidebar-nav__open>a.sidebar-nav__item-link { | |||||
| color: #fff; | |||||
| } | |||||
| ::ng-deep .sidebar-nav__sub-arrow { | |||||
| transform: translateY(0px) !important; | |||||
| } | |||||
| @media (min-width: 768px) { | |||||
| ::ng-deep .alain-default__collapsed .sidebar-nav:not(.sidebar-nav__sub) .sidebar-nav__item-link { | |||||
| display: block !important; | |||||
| padding: 8px 32px 8px 16px !important; | |||||
| } | |||||
| ::ng-deep .alain-default__collapsed .sidebar-nav:not(.sidebar-nav__sub) .sidebar-nav__item-link .sidebar-nav__item-text { | |||||
| opacity: 1 !important; | |||||
| display: inline !important; | |||||
| color: rgb(165, 172, 179) !important; | |||||
| } | |||||
| ::ng-deep .alain-default__collapsed .sidebar-nav:not(.sidebar-nav__sub) .sidebar-nav__item-link .sidebar-nav__item-icon { | |||||
| margin-right: 8px !important; | |||||
| font-size: 14px !important; | |||||
| } | |||||
| ::ng-deep .alain-default__collapsed .alain-default__content { | |||||
| margin-left: 125px; | |||||
| } | |||||
| } | |||||
| .titlebar { | |||||
| height: 30px; | |||||
| // user-select: none; | |||||
| display: flex; | |||||
| justify-content: flex-end; | |||||
| padding-left: 15px; | |||||
| top: 0; | |||||
| left: 0; | |||||
| right: 0; | |||||
| z-index: 1000; | |||||
| } | |||||
| .titleDragger { | |||||
| height: 30px; | |||||
| // user-select: none; | |||||
| display: flex; | |||||
| justify-content: flex-end; | |||||
| position: fixed; | |||||
| top: 0; | |||||
| left: 0; | |||||
| right: 0; | |||||
| z-index: 1001; | |||||
| width: 200px; | |||||
| left: 50%; | |||||
| margin-left: -100px; | |||||
| } | |||||
| .titlebar-button { | |||||
| display: inline-flex; | |||||
| justify-content: center; | |||||
| align-items: center; | |||||
| width: 30px; | |||||
| height: 30px; | |||||
| user-select: none; | |||||
| -webkit-user-select: none; | |||||
| } | |||||
| .titlebar-button img { | |||||
| font-size: 14px; | |||||
| width: 18px; | |||||
| height: 18px; | |||||
| } | |||||
| @@ -0,0 +1,475 @@ | |||||
| import { Location } from '@angular/common'; | |||||
| import { Component, inject, HostListener, ChangeDetectorRef, OnInit } from '@angular/core'; | |||||
| import { Router, RouterLink, RouterOutlet, ActivatedRoute, NavigationEnd } from '@angular/router'; | |||||
| import { QuickMenuModule } from '@delon/abc/quick-menu'; | |||||
| import { I18nPipe, MenuService, SettingsService, User, _HttpClient } from '@delon/theme'; | |||||
| import { LayoutDefaultModule, LayoutDefaultOptions } from '@delon/theme/layout-default'; | |||||
| import { SettingDrawerModule } from '@delon/theme/setting-drawer'; | |||||
| import { ThemeBtnComponent } from '@delon/theme/theme-btn'; | |||||
| import { environment } from '@env/environment'; | |||||
| import { SharedModule } from '@shared'; | |||||
| import { isTauri } from '@tauri-apps/api/core'; | |||||
| import { Window } from '@tauri-apps/api/window'; | |||||
| import { NzAvatarModule } from 'ng-zorro-antd/avatar'; | |||||
| import { NzBreadCrumbComponent, NzBreadCrumbItemComponent, NzBreadCrumbSeparatorComponent } from 'ng-zorro-antd/breadcrumb'; | |||||
| import { NzCardComponent } from 'ng-zorro-antd/card'; | |||||
| import { NzDropDownModule } from 'ng-zorro-antd/dropdown'; | |||||
| import { NzIconModule } from 'ng-zorro-antd/icon'; | |||||
| import { NzLayoutModule } from 'ng-zorro-antd/layout'; | |||||
| import { NzMenuModule } from 'ng-zorro-antd/menu'; | |||||
| import { NzSelectModule } from 'ng-zorro-antd/select'; | |||||
| import { NzTableComponent } from 'ng-zorro-antd/table'; | |||||
| import { HeaderClearStorageComponent } from '../basic/widgets/clear-storage.component'; | |||||
| import { HeaderDateComponent } from '../basic/widgets/date.component'; | |||||
| import { HeaderFullScreenComponent } from '../basic/widgets/fullscreen.component'; | |||||
| import { HeaderUserComponent } from '../basic/widgets/user.component'; | |||||
| import { TabsComponent } from '../tabs/tabs.component'; | |||||
| @Component({ | |||||
| selector: 'layout-trad', | |||||
| templateUrl: './trad.component.html', | |||||
| styleUrls: ['./trad.component.less'], | |||||
| standalone: true, | |||||
| imports: [ | |||||
| RouterOutlet, | |||||
| RouterLink, | |||||
| I18nPipe, | |||||
| LayoutDefaultModule, | |||||
| SettingDrawerModule, | |||||
| NzSelectModule, | |||||
| ThemeBtnComponent, | |||||
| NzIconModule, | |||||
| NzMenuModule, | |||||
| NzDropDownModule, | |||||
| NzAvatarModule, | |||||
| HeaderUserComponent, | |||||
| HeaderClearStorageComponent, | |||||
| HeaderFullScreenComponent, | |||||
| NzLayoutModule, | |||||
| HeaderDateComponent, | |||||
| QuickMenuModule, | |||||
| NzBreadCrumbComponent, | |||||
| NzBreadCrumbItemComponent, | |||||
| NzBreadCrumbSeparatorComponent, | |||||
| NzCardComponent, | |||||
| TabsComponent, | |||||
| SharedModule | |||||
| ] | |||||
| }) | |||||
| export class LayoutTradComponent implements OnInit { | |||||
| private readonly cdr = inject(ChangeDetectorRef); | |||||
| private readonly settings = inject(SettingsService); | |||||
| private menuService = inject(MenuService); | |||||
| private readonly http = inject(_HttpClient); | |||||
| url = `${environment.api['fuelUrl']}system/config/configKey/pcSystemTitile`; | |||||
| options: LayoutDefaultOptions = { | |||||
| logoExpanded: `./assets/datav/trn.png`, | |||||
| logoCollapsed: `./assets/datav/trn.png`, | |||||
| showHeaderCollapse: false | |||||
| }; | |||||
| searchToggleStatus = false; | |||||
| currentOrgan: any; | |||||
| organs: any; | |||||
| showSettingDrawer = !environment.production; | |||||
| tabs: any[] = []; // 页签数据 | |||||
| theme: string = 'white'; | |||||
| breadcrumb: any[] = []; | |||||
| activeRoute: any; | |||||
| sidebarWidth: number = 200; | |||||
| appWindow = new Window('main'); | |||||
| isTauri = false; | |||||
| isMax = true; | |||||
| title = ''; | |||||
| private mouseDown = false; | |||||
| private mouseX = 0; | |||||
| private sidebarInitialWidth = 0; | |||||
| get user(): User { | |||||
| return this.settings.user; | |||||
| } | |||||
| switchTheme(theme: string) { | |||||
| this.theme = theme; | |||||
| document.body.className = theme; | |||||
| window.localStorage.setItem('theme', theme); | |||||
| } | |||||
| getMenu() { | |||||
| return this.menuService.menus; | |||||
| } | |||||
| add() { | |||||
| console.log(11); | |||||
| } | |||||
| activeValue = 1; | |||||
| toggleActive(value: number) { | |||||
| console.log('Toggle active called with:', value); | |||||
| this.activeValue = value; | |||||
| this.router.navigate(['/hy/laboratory-data-entry-1']); | |||||
| } | |||||
| onNavigateClick() { | |||||
| console.log('Navigation link clicked!'); | |||||
| this.activeValue = 1; | |||||
| } | |||||
| navigateToLaboratoryWithParams() { | |||||
| console.log('Navigating to laboratory with inputConfig parameters...'); | |||||
| this.activeValue = 1; | |||||
| // 直接拼接URL字符串,绕过Angular路由的查询参数处理 | |||||
| const url = '/hy/laboratory-data-entry-1?inputConfig=100:true,300:false,400:false,500:false,600:false,700:true'; | |||||
| window.location.hash = `#${url}`; | |||||
| } | |||||
| constructor( | |||||
| private route: ActivatedRoute, | |||||
| private router: Router, | |||||
| private location: Location, | |||||
| private menuS: MenuService, | |||||
| private settingsService: SettingsService | |||||
| ) { | |||||
| this.organs = this.settings.user['organs']; | |||||
| const c = this.settings.user['currentOrgan']; | |||||
| if (c) { | |||||
| const organ = this.organs.find((o: { id: any }) => o.id == c.id); | |||||
| this.currentOrgan = organ; | |||||
| } | |||||
| this.obtainIsTauri(); | |||||
| } | |||||
| // 获取侧边栏折叠状态 | |||||
| getSidebarCollapsed() { | |||||
| return this.settingsService.layout.collapsed; | |||||
| } | |||||
| obtainIsTauri() { | |||||
| try { | |||||
| this.isTauri = isTauri(); | |||||
| } catch { | |||||
| this.isTauri = false; | |||||
| } | |||||
| } | |||||
| findMenuDetails(menus: any, path: any) { | |||||
| let result = null; | |||||
| for (let menu of menus) { | |||||
| if (menu.link === path) { | |||||
| // 当前菜单就是我们要找的,返回结果 | |||||
| return { | |||||
| menu: menu, | |||||
| parent: null, | |||||
| ancestors: [] | |||||
| }; | |||||
| } | |||||
| if (menu.children && menu.children.length > 0) { | |||||
| // 递归查找子菜单 | |||||
| result = this.findMenuInChildren(menu, path); | |||||
| if (result) { | |||||
| // 子菜单中找到了匹配的路径,返回结果 | |||||
| result.parent = menu; | |||||
| result.ancestors.unshift(menu); | |||||
| return result; | |||||
| } | |||||
| } | |||||
| } | |||||
| // 没有找到匹配的菜单 | |||||
| return null; | |||||
| } | |||||
| findMenuInChildren(parentMenu: any, path: any) { | |||||
| for (let child of parentMenu.children) { | |||||
| if (child.link === path) { | |||||
| // 当前子菜单就是我们要找的,返回结果 | |||||
| return { | |||||
| menu: child, | |||||
| parent: null, | |||||
| ancestors: [] | |||||
| }; | |||||
| } | |||||
| if (child.children && child.children.length > 0) { | |||||
| // 递归查找孙子菜单 | |||||
| const result: any = this.findMenuInChildren(child, path); | |||||
| if (result) { | |||||
| // 子孙菜单中找到了匹配的路径,返回结果 | |||||
| result.parent = child; | |||||
| result.ancestors.unshift(child); | |||||
| return result; | |||||
| } | |||||
| } | |||||
| } | |||||
| // 没有找到匹配的菜单 | |||||
| return null; | |||||
| } | |||||
| minimize() { | |||||
| this.appWindow.minimize(); | |||||
| } | |||||
| toggleMaximize() { | |||||
| this.appWindow.isMaximized().then(p => { | |||||
| this.isMax = !p; | |||||
| }); | |||||
| this.appWindow.toggleMaximize(); | |||||
| } | |||||
| close() { | |||||
| this.appWindow.close(); | |||||
| } | |||||
| findNodeByPath(data: any, path: any) { | |||||
| for (let item of data) { | |||||
| if (item.link === path) { | |||||
| return item; | |||||
| } else if (item.children && item.children.length > 0) { | |||||
| let result: any = this.findNodeByPath(item.children, path); | |||||
| if (result) { | |||||
| return result; | |||||
| } | |||||
| } | |||||
| } | |||||
| return null; // 如果没有找到匹配的路径,返回null | |||||
| } | |||||
| addNode() { | |||||
| let parent: any = document.querySelector('.alain-default__aside'); | |||||
| // 创建一个新的子元素 | |||||
| const childElement = document.createElement('div'); | |||||
| // 添加class属性 | |||||
| childElement.className = 'arrow'; | |||||
| console.log(this.theme, 'this.theme'); | |||||
| // 添加文本内容 | |||||
| // childElement.textContent = this.theme === 'white' ? '' : '>'; | |||||
| childElement.textContent = ''; | |||||
| const section: any = document.querySelector('section'); | |||||
| // 将子元素添加到父元素中 | |||||
| parent.appendChild(childElement); | |||||
| let _this = this; | |||||
| if (!this.getSidebarCollapsed()) { | |||||
| const content: any = document.querySelector('.alain-default__content'); | |||||
| content.style = `width:calc(100vw - 287px) !important`; | |||||
| // calc(-287px + 100vw) !important | |||||
| // 定义鼠标移动事件处理函数 | |||||
| const mouseMoveHandler = (event: any) => { | |||||
| if (_this.mouseDown) { | |||||
| const delta = event.clientX - _this.mouseX; | |||||
| _this.sidebarWidth = _this.sidebarInitialWidth + delta; | |||||
| _this.sidebarWidth = Math.max(_this.sidebarWidth, 1); // 设置最小宽度 | |||||
| if (_this.sidebarWidth - 218 >= 1) { | |||||
| const node: any = document.querySelector('.alain-default__aside'); | |||||
| node.style.width = `${_this.sidebarWidth}px`; | |||||
| let tab: any = document.querySelector('.tab-pane'); | |||||
| setTimeout(() => { | |||||
| const node1: any = document.querySelector('.content-container'); | |||||
| node1.style.marginLeft = `${_this.sidebarWidth - 218}px`; | |||||
| tab.style.marginLeft = `${_this.sidebarWidth - 218}px`; | |||||
| const node2: any = document.querySelector('.breadcrumb'); | |||||
| node2.style.marginLeft = `${_this.sidebarWidth - 218}px`; | |||||
| }, 0); | |||||
| } else { | |||||
| console.log(this.sidebarWidth, 'this.sidebarWidth'); | |||||
| const node: any = document.querySelector('.alain-default__aside'); | |||||
| let tab: any = document.querySelector('.tab-pane'); | |||||
| const node2: any = document.querySelector('.breadcrumb'); | |||||
| node.style.width = `${_this.sidebarWidth}px`; | |||||
| if (_this.sidebarWidth < 70) { | |||||
| node.style.display = 'none'; | |||||
| content.style = `margin-left:65px !important`; | |||||
| tab.style = `margin-left:-220px!important`; | |||||
| node2.style = `margin-left:-219px!important`; | |||||
| _this.sidebarWidth = 218; | |||||
| _this.isCollapsed = true; | |||||
| // 移除鼠标移动事件监听 | |||||
| document.removeEventListener('mousemove', mouseMoveHandler); | |||||
| return; | |||||
| } | |||||
| setTimeout(() => { | |||||
| const left = `margin-left:-${218 - _this.sidebarWidth}px !important;`; | |||||
| // const container: any = document.querySelector('.content-container'); | |||||
| // container.style = left; | |||||
| content.style = `margin-left:${284 - (218 - _this.sidebarWidth)}px !important`; | |||||
| tab.style = left; | |||||
| node2.style = left; | |||||
| }, 0); | |||||
| } | |||||
| } | |||||
| }; | |||||
| childElement.addEventListener( | |||||
| 'mousedown', | |||||
| (event: any) => { | |||||
| console.log(_this.sidebarInitialWidth); | |||||
| const node: any = document.querySelector('.alain-default__aside'); | |||||
| _this.mouseDown = true; | |||||
| _this.mouseX = event.clientX; | |||||
| _this.sidebarInitialWidth = _this.sidebarWidth; | |||||
| document.body.style.userSelect = 'none'; | |||||
| // 添加鼠标移动事件监听 | |||||
| document.addEventListener('mousemove', mouseMoveHandler); | |||||
| }, | |||||
| false | |||||
| ); | |||||
| } | |||||
| } | |||||
| @HostListener('window:mouseup', ['$event']) | |||||
| onMouseUp(event: MouseEvent): void { | |||||
| this.mouseDown = false; | |||||
| document.body.style.userSelect = ''; | |||||
| } | |||||
| isCollapsed = false; | |||||
| private initialColWidths: number[] = []; // 用于存储初始列宽 | |||||
| // 记录初始列宽 | |||||
| private recordInitialColWidths() { | |||||
| const table: any = document.querySelector('table[nz-table-content][tablelayout="fixed"]'); | |||||
| const content: any = document.querySelector('.alain-default__content'); | |||||
| if (table && content) { | |||||
| const colElements = document.querySelectorAll('.ant-table-body table col'); | |||||
| colElements.forEach((col: any) => { | |||||
| const width = col.style.width ? parseInt(col.style.width) : col.clientWidth; | |||||
| this.initialColWidths.push(width); | |||||
| }); | |||||
| } | |||||
| } | |||||
| // 切换侧边栏折叠状态 | |||||
| collapsed() { | |||||
| this.isCollapsed = !this.isCollapsed; | |||||
| let node: any = document.querySelector('.alain-default__fixed .alain-default__aside'); | |||||
| let breadcrumb: any = document.querySelector('.fixed-breadcrumb'); | |||||
| let tabPane: any = document.querySelector('.tab-pane'); | |||||
| let content: any = document.querySelector('.alain-default__content'); | |||||
| let table: any = document.querySelector('table[nz-table-content][tablelayout="fixed"]'); | |||||
| if (!this.isCollapsed) { | |||||
| node.style = 'width:218px !important; opacity: 1;'; | |||||
| tabPane.style = 'left:280px !important;width:calc(100% - 279px) !important'; | |||||
| content.style = 'margin-left:284px !important;width:calc(100vw - 287px) !important'; | |||||
| breadcrumb.style = 'left:279px !important;'; | |||||
| } else { | |||||
| node.style = 'width:0 !important;pointer-events: none;'; | |||||
| tabPane.style = 'left:62px !important;width:calc(100% - 61px)'; | |||||
| breadcrumb.style = 'left:62px !important;'; | |||||
| content.style = 'margin-left:65px !important;width:calc(100vw - 65px) !important'; | |||||
| } | |||||
| // 侧边栏过渡动画结束事件处理 | |||||
| const handleTransitionEnd = () => { | |||||
| if (table && content) { | |||||
| setTimeout(() => { | |||||
| const colElements = document.querySelectorAll('.ant-table-body table col'); | |||||
| const colCount = colElements.length; | |||||
| if (colCount > 0) { | |||||
| if (this.isCollapsed) { | |||||
| // 折叠时使用初始列宽 | |||||
| colElements.forEach((col: any, index: number) => { | |||||
| const width = this.initialColWidths[index]; | |||||
| col.style.width = `${width}px`; | |||||
| col.style.minWidth = `${width}px`; | |||||
| }); | |||||
| } else { | |||||
| // 展开时重新计算列宽 | |||||
| const parentWidth = content.getBoundingClientRect().width; | |||||
| const availableWidth = parentWidth - (this.isCollapsed ? 65 : 284); | |||||
| const newColWidth = Math.floor(availableWidth / colCount); | |||||
| colElements.forEach((col: any) => { | |||||
| col.style.width = `${newColWidth}px`; | |||||
| col.style.minWidth = `${newColWidth}px`; | |||||
| }); | |||||
| } | |||||
| } | |||||
| this.cdr.detectChanges(); | |||||
| }, 100); | |||||
| } | |||||
| // node.removeEventListener('transitionend', handleTransitionEnd); | |||||
| }; | |||||
| // node.addEventListener('transitionend', handleTransitionEnd); | |||||
| } | |||||
| getSystemInfo() { | |||||
| this.http.get(this.url).subscribe((res: any) => { | |||||
| console.log(res, '222'); | |||||
| if (res.code == 200) { | |||||
| this.title = res.msg; | |||||
| } | |||||
| }); | |||||
| } | |||||
| ngOnInit() { | |||||
| // 初始化时记录表格列的初始宽度 | |||||
| this.recordInitialColWidths(); | |||||
| this.settingsService.setLayout('collapsed', false); | |||||
| if (this.getSidebarCollapsed()) { | |||||
| let content: any = document.querySelector('.alain-default__content'); | |||||
| content.style.marginLeft = '526px !important'; | |||||
| setTimeout(() => { | |||||
| let node: any = document.querySelector('.alain-default__fixed .alain-default__aside'); | |||||
| node.style.width = '47px'; | |||||
| let tabPane: any = document.querySelector('.tab-pane'); | |||||
| tabPane.style.left = '108px'; | |||||
| tabPane.style.width = 'calc(100% - 64px)'; | |||||
| }, 0); | |||||
| const arrow: any = document.querySelector('.arrow'); | |||||
| arrow.removeEventListener('mousedown', () => {}); | |||||
| document.addEventListener('mousemove', () => {}); | |||||
| } | |||||
| if (window.localStorage.getItem('theme')) { | |||||
| let theme: any = window.localStorage.getItem('theme'); | |||||
| document.body.className = theme; | |||||
| this.theme = theme; | |||||
| } else { | |||||
| document.body.className = this.theme; | |||||
| window.localStorage.setItem('theme', this.theme); | |||||
| } | |||||
| // 给侧边栏添加子元素 | |||||
| setTimeout(() => { | |||||
| this.addNode(); | |||||
| }, 0); | |||||
| // 根据设备分辨率动态设置容易宽度 | |||||
| const html: any = document.querySelector('html'); | |||||
| html.style.fontSize = '16px'; | |||||
| document.body.style.fontSize = '16px'; | |||||
| const width: number = window.screen.width; | |||||
| const data = this.findMenuDetails(this.menuService['data'], this.router.url); | |||||
| if (data && data.ancestors) { | |||||
| this.breadcrumb = data.ancestors; | |||||
| } | |||||
| this.activeRoute = this.findNodeByPath(this.menuService['data'], this.router.url); | |||||
| // 监听URL变化 | |||||
| this.router.events.subscribe(event => { | |||||
| if (event instanceof NavigationEnd) { | |||||
| const data = this.findMenuDetails(this.menuService['data'], this.router.url); | |||||
| if (data && data.ancestors) { | |||||
| this.breadcrumb = data.ancestors; | |||||
| } | |||||
| this.activeRoute = this.findNodeByPath(this.menuService['data'], this.router.url); | |||||
| } | |||||
| }); | |||||
| this.getSystemInfo(); | |||||
| } | |||||
| organSelectOptionChange(e: any) { | |||||
| const u = this.settings.user; | |||||
| u['currentOrgan'] = e; | |||||
| this.settings.setUser(u); | |||||
| } | |||||
| goBack(): void { | |||||
| this.location.back(); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,90 @@ | |||||
| <div class="leave-apply"> | |||||
| <form> | |||||
| <div class="form-item"> | |||||
| <label>申请人姓名</label> | |||||
| <nz-select | |||||
| [disabled]="true" | |||||
| [(ngModel)]="leaveForm.applicantId" | |||||
| name="applicantId" | |||||
| (ngModelChange)="onUserChange($event)" | |||||
| nzShowSearch | |||||
| [nzDropdownMatchSelectWidth]="false" | |||||
| nzPlaceHolder="请选择人员" | |||||
| style="width: 100%" | |||||
| > | |||||
| <nz-option *ngFor="let user of users" [nzLabel]="user.nickName" [nzValue]="user.userId" /> | |||||
| </nz-select> | |||||
| </div> | |||||
| <div class="form-item"> | |||||
| <label>所属部门</label> | |||||
| <input nz-input [(ngModel)]="leaveForm.department" name="department" placeholder="自动带出" readonly /> | |||||
| </div> | |||||
| <div class="form-item"> | |||||
| <label>请假类型</label> | |||||
| <nz-select [(ngModel)]="leaveForm.leaveType" name="leaveType" [nzDropdownMatchSelectWidth]="false"> | |||||
| <nz-option *ngFor="let type of leaveTypes" [nzLabel]="type.label" [nzValue]="type.value" /> | |||||
| </nz-select> | |||||
| </div> | |||||
| <div class="form-item"> | |||||
| <label>开始日期</label> | |||||
| <nz-date-picker | |||||
| nzShowTime | |||||
| nzFormat="yyyy-MM-dd HH:mm" | |||||
| [(ngModel)]="leaveForm.startTime" | |||||
| name="startTime" | |||||
| style="width: 100%" | |||||
| (ngModelChange)="autoCalcDays()" | |||||
| /> | |||||
| </div> | |||||
| <div class="form-item"> | |||||
| <label>结束日期</label> | |||||
| <nz-date-picker | |||||
| nzShowTime | |||||
| nzFormat="yyyy-MM-dd HH:mm" | |||||
| [nzDisabledDate]="disableBeforeStart" | |||||
| [nzDisabledTime]="getDisabledEndTime" | |||||
| [(ngModel)]="leaveForm.endTime" | |||||
| name="endTime" | |||||
| style="width: 100%" | |||||
| (ngModelChange)="autoCalcDays()" | |||||
| /> | |||||
| </div> | |||||
| <div class="form-item"> | |||||
| <label>请假天数</label> | |||||
| <input nz-input [value]="leaveForm.days" name="days" placeholder="请先选择开始和结束日期" readonly style="background: #f5f5f5" /> | |||||
| </div> | |||||
| <div class="form-item"> | |||||
| <label>请假原因</label> | |||||
| <textarea nz-input [(ngModel)]="leaveForm.reason" name="reason" placeholder="请输入请假原因"></textarea> | |||||
| </div> | |||||
| <!-- <div class="form-item"> | |||||
| <label>当前状态</label> | |||||
| <nz-select [(ngModel)]="leaveForm.status" name="status"> | |||||
| <nz-option *ngFor="let s of statusList" [nzLabel]="s" [nzValue]="s"></nz-option> | |||||
| </nz-select> | |||||
| </div> --> | |||||
| <!-- <div class="form-item"> | |||||
| <label>当前审批部门</label> | |||||
| <input nz-input [(ngModel)]="leaveForm.currentDepartment" name="currentDepartment" placeholder="请输入当前审批部门" /> | |||||
| </div> | |||||
| <div class="form-item"> | |||||
| <label>当前审批人姓名</label> | |||||
| <input nz-input [(ngModel)]="leaveForm.currentApproverName" name="currentApproverName" placeholder="请输入当前审批人姓名" /> | |||||
| </div> | |||||
| <div class="form-item"> | |||||
| <label>当前审批人ID</label> | |||||
| <input nz-input [(ngModel)]="leaveForm.currentApproverId" name="currentApproverId" placeholder="请输入当前审批人ID" /> | |||||
| </div> --> | |||||
| <!-- 关联流程实例ID,通常只读 --> | |||||
| <!-- <div class="form-item"> | |||||
| <label>流程实例ID</label> | |||||
| <input nz-input [(ngModel)]="leaveForm.processInstanceId" name="processInstanceId" placeholder="自动生成/只读" [readonly]="true" /> | |||||
| </div> --> | |||||
| <div class="form-actions"> | |||||
| <button nz-button nzType="primary" (click)="submitForm()">提交</button> | |||||
| </div> | |||||
| </form> | |||||
| </div> | |||||
| @@ -0,0 +1,51 @@ | |||||
| .leave-apply { | |||||
| padding: 24px; | |||||
| max-width: 800px; | |||||
| margin: 0 auto; | |||||
| background: #fff; | |||||
| border-radius: 8px; | |||||
| box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); | |||||
| h2 { | |||||
| text-align: center; | |||||
| margin-bottom: 24px; | |||||
| font-size: 24px; | |||||
| font-weight: 600; | |||||
| color: #333; | |||||
| } | |||||
| .form-item { | |||||
| margin-bottom: 20px; | |||||
| label { | |||||
| display: block; | |||||
| margin-bottom: 8px; | |||||
| font-weight: 500; | |||||
| color: #666; | |||||
| } | |||||
| nz-select, | |||||
| nz-date-picker, | |||||
| textarea { | |||||
| width: 100%; | |||||
| border-radius: 4px; | |||||
| } | |||||
| textarea { | |||||
| min-height: 100px; | |||||
| resize: vertical; | |||||
| } | |||||
| } | |||||
| .form-actions { | |||||
| text-align: right; | |||||
| margin-top: 32px; | |||||
| button { | |||||
| padding: 0 24px; | |||||
| height: 40px; | |||||
| font-size: 14px; | |||||
| border-radius: 4px; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,385 @@ | |||||
| import { CommonModule, DatePipe } from '@angular/common'; | |||||
| import { HttpClient } from '@angular/common/http'; | |||||
| import { Component, inject, OnInit, OnDestroy } from '@angular/core'; | |||||
| import { FormsModule } from '@angular/forms'; | |||||
| import { ActivatedRoute, Router } from '@angular/router'; | |||||
| import { environment } from '@env/environment'; | |||||
| import dayjs from 'dayjs'; | |||||
| import { NzButtonModule } from 'ng-zorro-antd/button'; | |||||
| import { NzDatePickerModule } from 'ng-zorro-antd/date-picker'; | |||||
| import { NzInputModule } from 'ng-zorro-antd/input'; | |||||
| import { NzMessageService } from 'ng-zorro-antd/message'; | |||||
| import { NzSelectModule } from 'ng-zorro-antd/select'; | |||||
| import { Subscription } from 'rxjs/internal/Subscription'; | |||||
| import { EventBusService } from 'src/app/shared/event-bus.service'; | |||||
| import { WorkflowService } from '../../wf/service/workflow.service'; | |||||
| import { TaskEventsService, TaskExecutedPayload } from '../../wf/task-framework/task-events.service'; | |||||
| @Component({ | |||||
| selector: 'app-leave-apply', | |||||
| standalone: true, | |||||
| imports: [CommonModule, FormsModule, NzInputModule, NzDatePickerModule, NzSelectModule, NzButtonModule], | |||||
| templateUrl: './leave-apply.component.html', | |||||
| styleUrls: ['./leave-apply.component.less'] | |||||
| }) | |||||
| export class LeaveApplyComponent implements OnInit, OnDestroy { | |||||
| private taskExecuted?: Subscription; | |||||
| private readonly workflowService = inject(WorkflowService); | |||||
| prodefId = '请假审批流程:4:6b853493-45be-11f0-a1fd-fe22573d199d'; | |||||
| userUrl = `${environment.api['fuelUrl']}system/user/list`; | |||||
| users: any[] = []; // 新增:人员下拉数据 | |||||
| // 新增注入 | |||||
| private readonly router = inject(Router); | |||||
| // 添加工作流相关属性 | |||||
| workFlowParams: any = {}; | |||||
| leaveForm: any = { | |||||
| id: null, | |||||
| applicantId: '', | |||||
| applicantName: '', | |||||
| department: '', | |||||
| leaveType: '病假', | |||||
| startTime: null, | |||||
| endTime: null, | |||||
| days: '', | |||||
| reason: '', | |||||
| status: '业务部门经理审批', | |||||
| currentDepartment: '业务部门', | |||||
| currentApproverId: '', | |||||
| currentApproverName: '', | |||||
| processInstanceId: '' | |||||
| }; | |||||
| leaveTypes = [ | |||||
| { label: '病假', value: '病假' }, | |||||
| { label: '事假', value: '事假' }, | |||||
| { label: '年假', value: '年假' } | |||||
| ]; | |||||
| statusList = ['申请中', '部门审批', '人事审批', '已通过', '已驳回']; | |||||
| private readonly route = inject(ActivatedRoute); | |||||
| private readonly http = inject(HttpClient); | |||||
| constructor( | |||||
| private message: NzMessageService, | |||||
| private taskEventsService: TaskEventsService, | |||||
| private eventBusService: EventBusService | |||||
| ) {} | |||||
| // 禁止选择今天之前的日期(精确到分钟) | |||||
| disableBeforeNow = (current: Date): boolean => { | |||||
| // 当前时间往后推30分钟 | |||||
| const limit = dayjs().add(30, 'minute').startOf('minute'); | |||||
| return current && dayjs(current).isBefore(limit); | |||||
| }; | |||||
| getUser() { | |||||
| // 获取用户列表 | |||||
| this.http.get<any>(this.userUrl, { params: { pageNum: 1, pageSize: 999 } }).subscribe(res => { | |||||
| // 你接口一般是 res.data.list,如果不是请调整 | |||||
| this.users = res?.rows || []; | |||||
| const userInfo = JSON.parse(window.sessionStorage.getItem('info') || '{}'); | |||||
| const userId = userInfo.user?.userId || ''; | |||||
| this.onUserChange(userId); | |||||
| }); | |||||
| } | |||||
| // 新增:选中人员后,自动填充部门等 | |||||
| onUserChange(userId: string | number) { | |||||
| const user = this.users.find(u => u.userId === userId); | |||||
| if (user) { | |||||
| this.leaveForm.applicantName = user.nickName; | |||||
| this.leaveForm.department = user.dept?.deptName || ''; | |||||
| this.leaveForm.applicantId = user.userId; | |||||
| } else { | |||||
| this.leaveForm.applicantName = ''; | |||||
| this.leaveForm.department = ''; | |||||
| } | |||||
| } | |||||
| // 自动计算请假天数 | |||||
| autoCalcDays() { | |||||
| const WORK_START = '08:30'; | |||||
| const WORK_END = '17:30'; | |||||
| const WORK_MINUTES = 9 * 60; | |||||
| if (!this.leaveForm.startTime || !this.leaveForm.endTime) { | |||||
| this.leaveForm.days = ''; | |||||
| return; | |||||
| } | |||||
| let start = dayjs(this.leaveForm.startTime); | |||||
| let end = dayjs(this.leaveForm.endTime); | |||||
| if (end.isBefore(start)) { | |||||
| this.leaveForm.days = ''; | |||||
| return; | |||||
| } | |||||
| let totalMinutes = 0; | |||||
| let cursor = start.startOf('day'); | |||||
| while (cursor.isBefore(end, 'day') || cursor.isSame(end, 'day')) { | |||||
| // 当天的工作时间 | |||||
| let dayStart = cursor.hour(8).minute(30).second(0); | |||||
| let dayEnd = cursor.hour(17).minute(30).second(0); | |||||
| // 请假区间与工作时间的交集 | |||||
| let segStart = dayStart.isAfter(start) ? dayStart : start; | |||||
| let segEnd = dayEnd.isBefore(end) ? dayEnd : end; | |||||
| // 计算在这一天的工作区间重叠部分 | |||||
| if (segEnd.isAfter(segStart)) { | |||||
| totalMinutes += segEnd.diff(segStart, 'minute'); | |||||
| } | |||||
| // 下一天 | |||||
| cursor = cursor.add(1, 'day'); | |||||
| // 把start和end移到下一个有效区间 | |||||
| if (cursor.isAfter(end, 'day')) break; | |||||
| } | |||||
| // 计算请假天数 | |||||
| this.leaveForm.days = (totalMinutes / WORK_MINUTES).toFixed(2); | |||||
| } | |||||
| getDisabledEndTime = (current: Date | Date[], partial?: 'start' | 'end') => { | |||||
| if (!this.leaveForm.startTime) { | |||||
| // 必须完整返回 | |||||
| return { | |||||
| nzDisabledHours: () => [], | |||||
| nzDisabledMinutes: () => [], | |||||
| nzDisabledSeconds: () => [] | |||||
| }; | |||||
| } | |||||
| const start = dayjs(this.leaveForm.startTime); | |||||
| const curr = Array.isArray(current) ? current[0] : current; | |||||
| // 仅处理选同一天 | |||||
| if (curr && dayjs(curr).isSame(start, 'day')) { | |||||
| const startHour = start.hour(); | |||||
| const startMinute = start.minute(); | |||||
| return { | |||||
| nzDisabledHours: () => Array.from({ length: 24 }, (_, i) => i).filter(h => h < 8 || h > 17), | |||||
| nzDisabledMinutes: (h: number) => { | |||||
| // 8点只能选30及以后 | |||||
| if (h === 8) return Array.from({ length: 60 }, (_, i) => (i < 30 ? i : -1)).filter(i => i !== -1); | |||||
| // 17点只能选30及以前 | |||||
| if (h === 17) return Array.from({ length: 60 }, (_, i) => (i > 30 ? i : -1)).filter(i => i !== -1); | |||||
| // 等于开始小时,只能选大于开始分钟 | |||||
| if (h === startHour) return Array.from({ length: 60 }, (_, i) => (i <= startMinute ? i : -1)).filter(i => i !== -1); | |||||
| return []; | |||||
| }, | |||||
| nzDisabledSeconds: () => Array.from({ length: 60 }, () => 0) // 全允许,返回[] | |||||
| }; | |||||
| } else { | |||||
| // 不同天,只允许8:30-17:30 | |||||
| return { | |||||
| nzDisabledHours: () => Array.from({ length: 24 }, (_, i) => i).filter(h => h < 8 || h > 17), | |||||
| nzDisabledMinutes: (h: number) => { | |||||
| if (h === 8) return Array.from({ length: 60 }, (_, i) => (i < 30 ? i : -1)).filter(i => i !== -1); | |||||
| if (h === 17) return Array.from({ length: 60 }, (_, i) => (i > 30 ? i : -1)).filter(i => i !== -1); | |||||
| return []; | |||||
| }, | |||||
| nzDisabledSeconds: () => [] | |||||
| }; | |||||
| } | |||||
| }; | |||||
| // 结束日期不能早于开始日期 | |||||
| // 禁止选择开始时间之前的日期 | |||||
| disableBeforeStart = (current: Date): boolean => { | |||||
| if (!this.leaveForm.startTime) { | |||||
| const limit = dayjs().add(30, 'minute').startOf('minute'); | |||||
| return current && dayjs(current).isBefore(limit); | |||||
| } | |||||
| // 不允许小于开始日期的 | |||||
| return current && dayjs(current).isBefore(dayjs(this.leaveForm.startTime).startOf('day')); | |||||
| }; | |||||
| changeTitle() { | |||||
| this.taskEventsService.emitButtonTitleChanged({ | |||||
| excuteTitle: '提交', | |||||
| excuteEnable: true, | |||||
| rejectTitle: '驳回', | |||||
| rejectEnable: false, | |||||
| rejectToStartTitle: '驳回到起点', | |||||
| rejectToStartEnable: false, | |||||
| deleteTitle: '删除', | |||||
| deleteEnable: false | |||||
| }); | |||||
| } | |||||
| ngOnInit(): void { | |||||
| // this.route.queryParams.subscribe(params => { | |||||
| // this.workFlowParams = params; | |||||
| // }); | |||||
| // 获取id,若有,查询详情 | |||||
| this.changeTitle(); | |||||
| this.getUser(); | |||||
| this.route.queryParamMap.subscribe(params => { | |||||
| if (params && params.get('businessId')) { | |||||
| this.leaveForm.id = params.get('businessId'); | |||||
| this.getLeaveDetail(Number(params.get('businessId') ?? 0)); | |||||
| } else { | |||||
| // 自动填充申请人信息 | |||||
| const userInfo = JSON.parse(window.sessionStorage.getItem('info') || '{}'); | |||||
| this.leaveForm.applicantId = userInfo.user?.userId || ''; | |||||
| this.leaveForm.applicantName = userInfo.user?.nickName || ''; | |||||
| this.leaveForm.department = userInfo.user?.deptName || ''; | |||||
| } | |||||
| }); | |||||
| this.taskExecuted = this.taskEventsService.taskExecuted$.subscribe((payload: TaskExecutedPayload) => { | |||||
| // payload 里有 taskId、formKey、prodefId 等 | |||||
| console.log('监听到待办执行:', payload); | |||||
| if (payload.action === 'approve') { | |||||
| this.submitForm(false); | |||||
| } else { | |||||
| } | |||||
| }); | |||||
| } | |||||
| ngOnDestroy() { | |||||
| if (this.taskExecuted) { | |||||
| this.taskExecuted.unsubscribe(); | |||||
| } | |||||
| } | |||||
| // 查询详情 | |||||
| getLeaveDetail(id: number) { | |||||
| this.http.get<any>(`/erp/request/${id}`).subscribe(res => { | |||||
| if (res.code === 200 && res.data) { | |||||
| this.leaveForm = { ...res.data }; | |||||
| } | |||||
| }); | |||||
| } | |||||
| submitForm(complete: boolean = true): void { | |||||
| // if ( | |||||
| // !this.leaveForm.applicantName || | |||||
| // !this.leaveForm.department || | |||||
| // !this.leaveForm.leaveType || | |||||
| // !this.leaveForm.startTime || | |||||
| // !this.leaveForm.endTime || | |||||
| // !this.leaveForm.reason | |||||
| // ) { | |||||
| // this.message.error('请填写完整信息'); | |||||
| // return; | |||||
| // } | |||||
| const info: any = window.sessionStorage.getItem('info'); | |||||
| const data: any = JSON.parse(info); | |||||
| let userId = data.user.userId; | |||||
| const pipe = new DatePipe('zh-CN'); | |||||
| // 提交前格式化 | |||||
| this.leaveForm.startTime = pipe.transform(this.leaveForm.startTime, 'yyyy-MM-dd HH:mm:ss'); | |||||
| this.leaveForm.endTime = pipe.transform(this.leaveForm.endTime, 'yyyy-MM-dd HH:mm:ss'); | |||||
| this.leaveForm.currentDepartment = '业务部门'; | |||||
| const now = pipe.transform(new Date(), 'yyyy-MM-dd HH:mm:ss'); // 当前时间格式化 | |||||
| // 新增 or 修改 | |||||
| if (!this.leaveForm.id) { | |||||
| this.http.post(`${environment.api['fuelUrl']}erp/request`, this.leaveForm).subscribe((res: any) => { | |||||
| if (!res || res.code === 500) { | |||||
| this.message.error('提交失败'); | |||||
| return; | |||||
| } | |||||
| this.leaveForm.id = res['data']; | |||||
| // 获取部门经理角色id | |||||
| let department = '10105'; | |||||
| // 1. 找到当前申请人(userId对应) | |||||
| const applicant = this.users.find(u => u.userId == this.leaveForm.applicantId); | |||||
| // 2. 判断deptId是否为15 | |||||
| if (applicant && applicant.deptId == 18) { | |||||
| department = '10104'; | |||||
| } | |||||
| if (applicant && applicant.deptId == 30) { | |||||
| department = '10105'; | |||||
| } | |||||
| if (applicant && applicant.deptId == 29) { | |||||
| department = '10105'; | |||||
| } | |||||
| if (applicant && applicant.deptId == 3) { | |||||
| department = '10105'; | |||||
| } | |||||
| if (applicant && applicant.deptId == 24) { | |||||
| department = '10105'; | |||||
| } | |||||
| const req = { | |||||
| processDefinitionId: this.prodefId, | |||||
| businessVariables: { | |||||
| applyUserId: userId, | |||||
| businessId: this.leaveForm.id, | |||||
| department: department, | |||||
| personnelManager: '10105', | |||||
| time: now, | |||||
| type: '请假', | |||||
| content: this.leaveForm.reason | |||||
| }, | |||||
| groupCode: { | |||||
| approverGroup: ['user2', 'user3'], // 示例候选人组 | |||||
| reviewerGroup: ['user4'] // 示例候选人组 | |||||
| } | |||||
| }; | |||||
| this.workflowService.startWorkflow(this.prodefId, req).subscribe(res => { | |||||
| if (res && res.code !== 500) { | |||||
| // 将流程实例id放入业务中 | |||||
| let instanceId = res.data; | |||||
| this.leaveForm.processInstanceId = instanceId; | |||||
| this.submitForm(true); | |||||
| //执行完成步骤 | |||||
| if (complete) { | |||||
| this.completeWorkflow(); | |||||
| } | |||||
| } else { | |||||
| } | |||||
| }); | |||||
| }); | |||||
| } else { | |||||
| this.http.put(`${environment.api['fuelUrl']}erp/request`, this.leaveForm).subscribe((res: any) => { | |||||
| if (!res || res.code === 500) { | |||||
| this.message.error('修改失败'); | |||||
| return; | |||||
| } | |||||
| if (complete) { | |||||
| this.taskEventsService.emitTaskFinished({ | |||||
| taskId: this.workFlowParams.taskId, | |||||
| action: 'approve', // 或 'reject' | |||||
| prodefId: this.workFlowParams.prodefId, | |||||
| params: {} // 可选,传递附加信息 | |||||
| }); | |||||
| } | |||||
| }); | |||||
| } | |||||
| } | |||||
| completeWorkflow() { | |||||
| const userInfo = JSON.parse(window.sessionStorage.getItem('info') || '{}'); | |||||
| const userId = userInfo.user?.userId || ''; | |||||
| // 为每个hyCode构建工作流参数 | |||||
| const workflowParams = { | |||||
| identifierValue: this.leaveForm.id, //申请id | |||||
| identifierKey: 'businessId', | |||||
| // formKey: (window.location.hash).split('?')[0].replace(/^#/, '') | |||||
| formKey: this.router.url | |||||
| }; | |||||
| this.workflowService.completeWorkflowV2(this.workFlowParams.prodefId, this.workFlowParams.taskId, workflowParams).subscribe({ | |||||
| next: () => { | |||||
| // this.message.success('流程完成成功'); | |||||
| this.message.success('提交成功'); | |||||
| this.goBack(); | |||||
| }, | |||||
| error: () => { | |||||
| this.message.error('提交失败'); | |||||
| } | |||||
| }); | |||||
| } | |||||
| goBack(): void { | |||||
| setTimeout(() => { | |||||
| const currentUrl = this.router.url; | |||||
| this.eventBusService.emitCloseTab(currentUrl); | |||||
| this.router.navigate(['/approve/own-leave-request-list'], { replaceUrl: true }); | |||||
| }, 1000); // 2000毫秒=2秒 | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,64 @@ | |||||
| <div class="manager-approve-detail"> | |||||
| <div class="detail-container"> | |||||
| <!-- <div class="detail-item"> | |||||
| <label>申请人ID:</label> | |||||
| <span>{{ leaveRequest.applicantId }}</span> | |||||
| </div> --> | |||||
| <div class="detail-item"> | |||||
| <label>申请人姓名:</label> | |||||
| <span>{{ leaveRequest.applicantName }}</span> | |||||
| </div> | |||||
| <div class="detail-item"> | |||||
| <label>所属部门:</label> | |||||
| <span>{{ leaveRequest.department }}</span> | |||||
| </div> | |||||
| <div class="detail-item"> | |||||
| <label>请假类型:</label> | |||||
| <span>{{ leaveRequest.leaveType }}</span> | |||||
| </div> | |||||
| <div class="detail-item"> | |||||
| <label>开始时间:</label> | |||||
| <span>{{ leaveRequest.startTime | date: 'yyyy-MM-dd HH:mm:ss' }}</span> | |||||
| </div> | |||||
| <div class="detail-item"> | |||||
| <label>结束时间:</label> | |||||
| <span>{{ leaveRequest.endTime | date: 'yyyy-MM-dd HH:mm:ss' }}</span> | |||||
| </div> | |||||
| <div class="detail-item"> | |||||
| <label>请假天数:</label> | |||||
| <span>{{ leaveRequest.days }}</span> | |||||
| </div> | |||||
| <div class="detail-item"> | |||||
| <label>请假原因:</label> | |||||
| <span>{{ leaveRequest.reason }}</span> | |||||
| </div> | |||||
| <div class="detail-item"> | |||||
| <label>当前状态:</label> | |||||
| <span>{{ leaveRequest.status }}</span> | |||||
| </div> | |||||
| <div class="detail-item"> | |||||
| <label>当前审批部门:</label> | |||||
| <span>{{ leaveRequest.currentDepartment }}</span> | |||||
| </div> | |||||
| <!-- <div class="detail-item"> | |||||
| <label>当前审批人姓名:</label> | |||||
| <span>{{ leaveRequest.currentApproverName }}</span> | |||||
| </div> --> | |||||
| <div class="detail-item"> | |||||
| <label>创建时间:</label> | |||||
| <span>{{ leaveRequest.createTime | date: 'yyyy-MM-dd HH:mm:ss' }}</span> | |||||
| </div> | |||||
| <div class="detail-item"> | |||||
| <label>更新时间:</label> | |||||
| <span>{{ leaveRequest.updateTime | date: 'yyyy-MM-dd HH:mm:ss' }}</span> | |||||
| </div> | |||||
| </div> | |||||
| <div class="actions"> | |||||
| <!-- <button nz-button nzType="primary" (click)="rejectToStart(leaveRequest)">撤销</button> | |||||
| <button nz-button nzType="default" (click)="delete(leaveRequest)">删除</button> --> | |||||
| <!-- <button nz-button nzType="default" (click)="goBack()">返回</button> --> | |||||
| </div> | |||||
| </div> | |||||
| @@ -0,0 +1,69 @@ | |||||
| .manager-approve { | |||||
| // padding: 16px; | |||||
| h2 { | |||||
| text-align: center; | |||||
| margin-bottom: 24px; | |||||
| } | |||||
| nz-table { | |||||
| margin-top: 16px; | |||||
| } | |||||
| button { | |||||
| margin-right: 8px; | |||||
| } | |||||
| } | |||||
| .manager-approve-detail { | |||||
| padding: 24px; | |||||
| max-width: 800px; | |||||
| // margin-top: 20px; | |||||
| padding-top: 20px; | |||||
| margin: 20px auto; | |||||
| background: #fff; | |||||
| border-radius: 8px; | |||||
| box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); | |||||
| h2 { | |||||
| text-align: center; | |||||
| margin-bottom: 24px; | |||||
| font-size: 24px; | |||||
| font-weight: 600; | |||||
| color: #333; | |||||
| } | |||||
| .detail-container { | |||||
| display: flex; | |||||
| flex-direction: column; | |||||
| gap: 16px; | |||||
| } | |||||
| .detail-item { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| padding: 8px 16px; | |||||
| background: #f9f9f9; | |||||
| border-radius: 4px; | |||||
| label { | |||||
| font-weight: 500; | |||||
| color: #333; | |||||
| min-width: 120px; | |||||
| } | |||||
| span { | |||||
| color: #666; | |||||
| } | |||||
| } | |||||
| .actions { | |||||
| margin-top: 24px; | |||||
| text-align: right; | |||||
| button { | |||||
| margin-left: 8px; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,190 @@ | |||||
| import { CommonModule } from '@angular/common'; | |||||
| import { HttpClient } from '@angular/common/http'; | |||||
| import { Component, inject, OnInit, ViewChild, AfterViewInit, OnDestroy } from '@angular/core'; | |||||
| import { FormBuilder, FormGroup, Validators, FormsModule } from '@angular/forms'; | |||||
| import { ActivatedRoute, Router } from '@angular/router'; | |||||
| import { DownFileModule } from '@delon/abc/down-file'; | |||||
| import { SGModule } from '@delon/abc/sg'; | |||||
| import { STColumn, STComponent, STChange } from '@delon/abc/st'; | |||||
| import { DelonFormModule, SFComponent, SFSchema, SFDateWidgetSchema } from '@delon/form'; | |||||
| import { _HttpClient, SettingsService } from '@delon/theme'; | |||||
| import { environment } from '@env/environment'; | |||||
| import { SHARED_IMPORTS, SharedModule } from '@shared'; | |||||
| import { invoke } from '@tauri-apps/api/core'; | |||||
| import { listen } from '@tauri-apps/api/event'; | |||||
| import { NzButtonModule } from 'ng-zorro-antd/button'; | |||||
| import { NzDatePickerModule } from 'ng-zorro-antd/date-picker'; | |||||
| import { NzDescriptionsModule } from 'ng-zorro-antd/descriptions'; | |||||
| import { NzMessageService } from 'ng-zorro-antd/message'; | |||||
| import { NzModalService } from 'ng-zorro-antd/modal'; | |||||
| import { NzQRCodeModule } from 'ng-zorro-antd/qr-code'; | |||||
| import { NzRadioModule } from 'ng-zorro-antd/radio'; | |||||
| import { NzSelectModule } from 'ng-zorro-antd/select'; | |||||
| import { NzSwitchModule } from 'ng-zorro-antd/switch'; | |||||
| import { NzTableModule } from 'ng-zorro-antd/table'; | |||||
| import { NzTreeSelectModule } from 'ng-zorro-antd/tree-select'; | |||||
| import { Subscription } from 'rxjs/internal/Subscription'; | |||||
| import { BaseService } from 'src/app/core/utils/base.service'; | |||||
| import { TaskEventsService, TaskExecutedPayload } from '../../wf/task-framework/task-events.service'; | |||||
| @Component({ | |||||
| selector: 'app-leave-request-detail', | |||||
| standalone: true, | |||||
| imports: [ | |||||
| CommonModule, | |||||
| NzTableModule, | |||||
| NzButtonModule, | |||||
| FormsModule, | |||||
| NzDatePickerModule, | |||||
| NzTreeSelectModule, | |||||
| SharedModule, | |||||
| SGModule, | |||||
| DownFileModule, | |||||
| ...SHARED_IMPORTS, | |||||
| NzRadioModule, | |||||
| NzSwitchModule, | |||||
| NzSelectModule, | |||||
| NzDescriptionsModule | |||||
| ], | |||||
| templateUrl: './leave-request-detail.component.html', | |||||
| styleUrls: ['./leave-request-detail.component.less'] | |||||
| }) | |||||
| export class LeaveRequestDetailComponent implements OnInit, OnDestroy { | |||||
| leaveRequest: any = {}; | |||||
| loading = false; | |||||
| businessId: string | null = null; | |||||
| routeParams: any; | |||||
| private readonly route = inject(ActivatedRoute); | |||||
| private readonly http = inject(HttpClient); | |||||
| private readonly router = inject(Router); | |||||
| private taskExecuted?: Subscription; | |||||
| constructor( | |||||
| private message: NzMessageService, | |||||
| private taskEventsService: TaskEventsService | |||||
| ) {} | |||||
| ngOnInit() { | |||||
| this.route.queryParamMap.subscribe(params => { | |||||
| this.routeParams = params; | |||||
| this.businessId = params.get('businessId'); | |||||
| if (this.businessId) { | |||||
| // 详情模式 | |||||
| this.getDetail(this.businessId); | |||||
| } else { | |||||
| // 列表模式 | |||||
| this.getList(); | |||||
| } | |||||
| }); | |||||
| this.taskExecuted = this.taskEventsService.taskExecuted$.subscribe((payload: TaskExecutedPayload) => { | |||||
| // payload 里有 taskId、formKey、prodefId 等 | |||||
| console.log('监听到待办执行:', payload); | |||||
| if (payload.action === 'rejectToStart') { | |||||
| this.rejectToStart(Number(payload.businessId ?? 0), false); | |||||
| } else if (payload.action === 'delete') { | |||||
| this.delete(Number(payload.businessId ?? 0), false); | |||||
| } | |||||
| }); | |||||
| } | |||||
| ngOnDestroy() { | |||||
| if (this.taskExecuted) { | |||||
| this.taskExecuted.unsubscribe(); | |||||
| } | |||||
| } | |||||
| // 获取详情 | |||||
| getDetail(id: string) { | |||||
| this.loading = true; | |||||
| this.http.get<any>(`${environment.api['fuelUrl']}erp/request/${id}`).subscribe({ | |||||
| next: res => { | |||||
| this.leaveRequest = res.data; | |||||
| this.loading = false; | |||||
| // 获取businessId参数 | |||||
| this.changeTitle(); | |||||
| }, | |||||
| error: () => { | |||||
| this.message.error('加载数据失败'); | |||||
| this.loading = false; | |||||
| } | |||||
| }); | |||||
| } | |||||
| // 获取列表 | |||||
| getList() { | |||||
| this.http | |||||
| .get<any>(`/erp/request/list`, { | |||||
| params: { | |||||
| currentDepartment: '业务部门' | |||||
| } | |||||
| }) | |||||
| .subscribe(res => { | |||||
| if (res.code === 200 && res.data) { | |||||
| this.leaveRequest = res.data; | |||||
| } else { | |||||
| this.leaveRequest = []; | |||||
| this.message.error('无待审批数据'); | |||||
| } | |||||
| }); | |||||
| } | |||||
| delete(id: number, complete: boolean = true): void { | |||||
| this.http.delete(`${environment.api['fuelUrl']}erp/request/${id}`).subscribe({ | |||||
| next: () => { | |||||
| this.message.success('已批准'); | |||||
| if (complete) { | |||||
| // 可根据实际业务,调用后端API变更审批状态 | |||||
| this.taskEventsService.emitTaskFinished({ | |||||
| taskId: this.routeParams['taskId'], | |||||
| action: 'delete', // 或 'reject' | |||||
| prodefId: this.routeParams['prodefId'], | |||||
| params: this.routeParams // 可选,传递附加信息 | |||||
| }); | |||||
| } | |||||
| this.goBack(); | |||||
| }, | |||||
| error: () => { | |||||
| this.message.error('删除失败'); | |||||
| } | |||||
| }); | |||||
| } | |||||
| rejectToStart(id: number, complete: boolean = true): void { | |||||
| this.http.put(`${environment.api['fuelUrl']}erp/request`, { id, status: '填写申请单' }).subscribe({ | |||||
| next: () => { | |||||
| this.message.success('已驳回'); | |||||
| if (complete) { | |||||
| // 可根据实际业务,调用后端API变更审批状态 | |||||
| this.taskEventsService.emitTaskFinished({ | |||||
| taskId: this.routeParams['taskId'], | |||||
| action: 'rejectToStart', // 或 'reject' | |||||
| prodefId: this.routeParams['prodefId'], | |||||
| params: this.routeParams // 可选,传递附加信息 | |||||
| }); | |||||
| } | |||||
| this.goBack(); | |||||
| }, | |||||
| error: () => { | |||||
| this.message.error('驳回失败'); | |||||
| } | |||||
| }); | |||||
| } | |||||
| changeTitle() { | |||||
| let deleteEnable = true; | |||||
| if (this.leaveRequest.status === '已批准') { | |||||
| deleteEnable = false; | |||||
| } | |||||
| this.taskEventsService.emitButtonTitleChanged({ | |||||
| excuteTitle: '批准', | |||||
| excuteEnable: false, | |||||
| rejectTitle: '驳回', | |||||
| rejectEnable: false, | |||||
| rejectToStartTitle: '撤销申请', | |||||
| rejectToStartEnable: true, | |||||
| deleteTitle: '删除', | |||||
| deleteEnable: deleteEnable | |||||
| }); | |||||
| } | |||||
| goBack(): void { | |||||
| this.router.navigate(['/approve/leave-request-list']); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,51 @@ | |||||
| <div class="container-block"> | |||||
| <nz-card class="card-block card-search"> | |||||
| <div> | |||||
| <sf | |||||
| #sf | |||||
| mode="search" | |||||
| size="large" | |||||
| (change)="onSearchChange($event)" | |||||
| [schema]="searchSchema" | |||||
| (formReset)="resetSearch()" | |||||
| (formSubmit)="search()" | |||||
| [button]="null" | |||||
| > | |||||
| <button nz-button nzType="primary" style="margin-right: 0.5rem" type="submit" class="searchBtn">搜索</button> | |||||
| <button class="resetBtn" (click)="sf.reset(); search()" type="button" nz-button>重置</button> | |||||
| </sf> | |||||
| </div> | |||||
| </nz-card> | |||||
| <nz-card class="table-card"> | |||||
| <st | |||||
| #st | |||||
| [data]="leaveRequests" | |||||
| [columns]="columns" | |||||
| [pi]="pageIndex" | |||||
| [ps]="pageSize" | |||||
| [page]="{ front: false }" | |||||
| [total]="total" | |||||
| (change)="stPageChange($event)" | |||||
| size="small" | |||||
| [rowClassName]="rowClassName" | |||||
| [scroll]="{ y: 'calc(100vh - 283px)' }" | |||||
| [page]="{ show: false }" | |||||
| /> | |||||
| <div class="common-pagination"> | |||||
| <nz-pagination | |||||
| [(nzPageIndex)]="pageIndex" | |||||
| [nzTotal]="total" | |||||
| nzShowSizeChanger | |||||
| [nzPageSize]="pageSize" | |||||
| [nzShowTotal]="totalTemplate" | |||||
| [nzShowQuickJumper]="true" | |||||
| (nzPageSizeChange)="pageSizeChang($event)" | |||||
| (nzPageIndexChange)="pageIndexChange($event)" | |||||
| /> | |||||
| <ng-template #totalTemplate let-total>共 {{ total }} 条</ng-template> | |||||
| </div> | |||||
| </nz-card> | |||||
| </div> | |||||
| <history-nz-modal *ngIf="hVisible" [id]="instantId" [visible]="hVisible" (modalCallBack)="modalCallBack($event)" /> | |||||
| @@ -0,0 +1,313 @@ | |||||
| .card-block { | |||||
| position: relative; | |||||
| } | |||||
| .add { | |||||
| float: right; | |||||
| margin-top: -38px; | |||||
| } | |||||
| ::ng-deep .row-color { | |||||
| background-color: rgb(214, 230, 245) !important; | |||||
| } | |||||
| ::ng-deep { | |||||
| .menu-tree { | |||||
| .ant-tree { | |||||
| width: 258px; | |||||
| height: 296px; | |||||
| overflow-x: hidden; | |||||
| overflow-y: auto; | |||||
| } | |||||
| .ant-form-item-label { | |||||
| width: 72px !important; | |||||
| display: none !important; | |||||
| } | |||||
| } | |||||
| } | |||||
| ::ng-deep { | |||||
| .anticon-down { | |||||
| display: none !important; | |||||
| } | |||||
| // .ant-table-body { | |||||
| // overflow-x: auto; | |||||
| // overflow-y: auto; | |||||
| // scrollbar-color: #fff #949494; | |||||
| // height: calc(100vh - 283px) !important; | |||||
| // } | |||||
| } | |||||
| .el-button-add { | |||||
| color: #1890ff !important; | |||||
| background: #e8f4ff !important; | |||||
| border-color: #a3d3ff !important | |||||
| } | |||||
| .el-button.is-round { | |||||
| border-radius: 20px !important; | |||||
| padding: 12px 23px !important; | |||||
| } | |||||
| .el-button.is-circle { | |||||
| border-radius: 50% !important; | |||||
| padding: 12px !important; | |||||
| } | |||||
| .el-button--primary { | |||||
| color: #fff !important; | |||||
| background-color: #1890ff !important; | |||||
| border-color: #1890ff !important; | |||||
| } | |||||
| .el-button--primary:focus, | |||||
| .el-button--primary:hover { | |||||
| background: #46a6ff !important; | |||||
| border-color: #46a6ff !important; | |||||
| color: #fff !important; | |||||
| } | |||||
| .el-button--primary:active { | |||||
| outline: none !important; | |||||
| } | |||||
| .el-button--primary.is-active, | |||||
| .el-button--primary:active { | |||||
| background: #1682e6 !important; | |||||
| border-color: #1682e6 !important; | |||||
| color: #fff !important; | |||||
| } | |||||
| .el-button--primary.is-disabled, | |||||
| .el-button--primary.is-disabled:active, | |||||
| .el-button--primary.is-disabled:focus, | |||||
| .el-button--primary.is-disabled:hover { | |||||
| color: #fff !important; | |||||
| background-color: #8cc8ff !important; | |||||
| border-color: #8cc8ff !important; | |||||
| } | |||||
| .el-button--primary.is-plain { | |||||
| color: #1890ff !important; | |||||
| background: #e8f4ff !important; | |||||
| border-color: #a3d3ff !important; | |||||
| } | |||||
| .el-button--primary.is-plain:focus, | |||||
| .el-button--primary.is-plain:hover { | |||||
| background: #1890ff !important; | |||||
| border-color: #1890ff !important; | |||||
| color: #fff !important; | |||||
| } | |||||
| .el-button--primary.is-plain:active { | |||||
| background: #1682e6 !important; | |||||
| border-color: #1682e6 !important; | |||||
| color: #fff !important; | |||||
| outline: none !important; | |||||
| } | |||||
| .el-button--primary.is-plain.is-disabled, | |||||
| .el-button--primary.is-plain.is-disabled:active, | |||||
| .el-button--primary.is-plain.is-disabled:focus, | |||||
| .el-button--primary.is-plain.is-disabled:hover { | |||||
| color: #74bcff !important; | |||||
| background-color: #e8f4ff !important; | |||||
| border-color: #d1e9ff !important; | |||||
| } | |||||
| .el-button--success { | |||||
| color: #fff !important; | |||||
| background-color: #13ce66 !important; | |||||
| border-color: #13ce66 !important; | |||||
| } | |||||
| .el-button--success:focus, | |||||
| .el-button--success:hover { | |||||
| background: #42d885 !important; | |||||
| border-color: #42d885 !important; | |||||
| color: #fff !important; | |||||
| } | |||||
| .el-button--success:active { | |||||
| outline: none !important; | |||||
| } | |||||
| .el-button--success.is-active, | |||||
| .el-button--success:active { | |||||
| background: #11b95c !important; | |||||
| border-color: #11b95c !important; | |||||
| color: #fff !important; | |||||
| } | |||||
| .el-button--success.is-disabled, | |||||
| .el-button--success.is-disabled:active, | |||||
| .el-button--success.is-disabled:focus, | |||||
| .el-button--success.is-disabled:hover { | |||||
| color: #fff !important; | |||||
| background-color: #89e7b3 !important; | |||||
| border-color: #89e7b3 !important; | |||||
| } | |||||
| .el-button--success.is-plain { | |||||
| color: #13ce66 !important; | |||||
| background: #e7faf0 !important; | |||||
| border-color: #a1ebc2 !important; | |||||
| } | |||||
| .el-button--success.is-plain:focus, | |||||
| .el-button--success.is-plain:hover { | |||||
| background: #13ce66 !important; | |||||
| border-color: #13ce66 !important; | |||||
| color: #fff !important; | |||||
| } | |||||
| .el-button--success.is-plain:active { | |||||
| background: #11b95c !important; | |||||
| border-color: #11b95c !important; | |||||
| color: #fff !important; | |||||
| outline: none !important; | |||||
| } | |||||
| .el-button--success.is-plain.is-disabled, | |||||
| .el-button--success.is-plain.is-disabled:active, | |||||
| .el-button--success.is-plain.is-disabled:focus, | |||||
| .el-button--success.is-plain.is-disabled:hover { | |||||
| color: #71e2a3 !important; | |||||
| background-color: #e7faf0 !important; | |||||
| border-color: #d0f5e0 !important; | |||||
| } | |||||
| .el-button--warning { | |||||
| color: #fff !important; | |||||
| background-color: #ffba00 !important; | |||||
| border-color: #ffba00 !important; | |||||
| } | |||||
| .el-button--warning:focus, | |||||
| .el-button--warning:hover { | |||||
| background: #ffc833 !important; | |||||
| border-color: #ffc833 !important; | |||||
| color: #fff !important; | |||||
| } | |||||
| .el-button--warning:active { | |||||
| outline: none !important; | |||||
| } | |||||
| .el-button--warning.is-active, | |||||
| .el-button--warning:active { | |||||
| background: #e6a700 !important; | |||||
| border-color: #e6a700 !important; | |||||
| color: #fff !important; | |||||
| } | |||||
| .el-button--warning.is-disabled, | |||||
| .el-button--warning.is-disabled:active, | |||||
| .el-button--warning.is-disabled:focus, | |||||
| .el-button--warning.is-disabled:hover { | |||||
| color: #fff !important; | |||||
| background-color: #ffdd80 !important; | |||||
| border-color: #ffdd80 !important; | |||||
| } | |||||
| .el-button--warning.is-plain { | |||||
| color: #ffba00 !important; | |||||
| background: #fff8e6 !important; | |||||
| border-color: #ffe399 !important; | |||||
| } | |||||
| .el-button--warning.is-plain:focus, | |||||
| .el-button--warning.is-plain:hover { | |||||
| background: #ffba00 !important; | |||||
| border-color: #ffba00 !important; | |||||
| color: #fff !important; | |||||
| } | |||||
| .el-button--warning.is-plain:active { | |||||
| background: #e6a700 !important; | |||||
| border-color: #e6a700 !important; | |||||
| color: #fff !important; | |||||
| outline: none !important; | |||||
| } | |||||
| .el-button--warning.is-plain.is-disabled, | |||||
| .el-button--warning.is-plain.is-disabled:active, | |||||
| .el-button--warning.is-plain.is-disabled:focus, | |||||
| .el-button--warning.is-plain.is-disabled:hover { | |||||
| color: #ffd666 !important; | |||||
| background-color: #fff8e6 !important; | |||||
| border-color: #fff1cc !important; | |||||
| } | |||||
| .el-button--danger { | |||||
| color: #fff !important; | |||||
| background-color: #ff4949 !important; | |||||
| border-color: #ff4949 !important; | |||||
| } | |||||
| .el-button--danger:focus, | |||||
| .el-button--danger:hover { | |||||
| background: #ff6d6d !important; | |||||
| border-color: #ff6d6d !important; | |||||
| color: #fff !important; | |||||
| } | |||||
| .el-button--danger:active { | |||||
| outline: none !important; | |||||
| } | |||||
| .el-button--danger.is-active, | |||||
| .el-button--danger:active { | |||||
| background: #e64242 !important; | |||||
| border-color: #e64242 !important; | |||||
| color: #fff !important; | |||||
| } | |||||
| .el-button--danger.is-disabled, | |||||
| .el-button--danger.is-disabled:active, | |||||
| .el-button--danger.is-disabled:focus, | |||||
| .el-button--danger.is-disabled:hover { | |||||
| color: #fff !important; | |||||
| background-color: #ffa4a4 !important; | |||||
| border-color: #ffa4a4 !important; | |||||
| } | |||||
| .el-button--danger.is-plain { | |||||
| color: #ff4949 !important; | |||||
| background: #ffeded !important; | |||||
| border-color: #ffb6b6 !important; | |||||
| } | |||||
| .el-button--danger.is-plain:focus, | |||||
| .el-button--danger.is-plain:hover { | |||||
| background: #ff4949 !important; | |||||
| border-color: #ff4949 !important; | |||||
| color: #fff !important; | |||||
| } | |||||
| .el-button--danger.is-plain:active { | |||||
| background: #e64242 !important; | |||||
| border-color: #e64242 !important; | |||||
| color: #fff !important; | |||||
| outline: none !important; | |||||
| } | |||||
| .el-button--danger.is-plain.is-disabled, | |||||
| .el-button--danger.is-plain.is-disabled:active, | |||||
| .el-button--danger.is-plain.is-disabled:focus, | |||||
| .el-button--danger.is-plain.is-disabled:hover { | |||||
| color: #ff9292 !important; | |||||
| background-color: #ffeded !important; | |||||
| border-color: #ffdbdb !important; | |||||
| } | |||||
| // ::ng-deep { | |||||
| // .ant-tag{ | |||||
| // padding-left:20px !important; | |||||
| // } | |||||
| // } | |||||
| @@ -0,0 +1,180 @@ | |||||
| import { CommonModule } from '@angular/common'; | |||||
| import { HttpClient } from '@angular/common/http'; | |||||
| import { Component, OnInit, ViewChild, AfterViewInit } from '@angular/core'; | |||||
| import { ActivatedRoute, Router } from '@angular/router'; | |||||
| import { STChange, STColumn, STComponent, STModule, STColumnTag, STClickRowClassNameType } from '@delon/abc/st'; | |||||
| import { SFComponent, SFSchema, SFSelectWidgetSchema } from '@delon/form'; | |||||
| import { ModalHelper, _HttpClient } from '@delon/theme'; | |||||
| import { environment } from '@env/environment'; | |||||
| import { SHARED_IMPORTS, SharedModule } from '@shared'; | |||||
| import { NzAlertModule } from 'ng-zorro-antd/alert'; | |||||
| import { NzButtonModule } from 'ng-zorro-antd/button'; | |||||
| import { NzFormatEmitEvent } from 'ng-zorro-antd/core/tree'; | |||||
| import { NzIconModule } from 'ng-zorro-antd/icon'; | |||||
| import { NzInputModule } from 'ng-zorro-antd/input'; | |||||
| import { NzMessageService, NzMessageModule } from 'ng-zorro-antd/message'; | |||||
| import { NzModalService } from 'ng-zorro-antd/modal'; | |||||
| import { NzPaginationModule } from 'ng-zorro-antd/pagination'; | |||||
| import { NzPopconfirmModule } from 'ng-zorro-antd/popconfirm'; | |||||
| import { NzRadioModule } from 'ng-zorro-antd/radio'; | |||||
| import { NzTableModule } from 'ng-zorro-antd/table'; | |||||
| import { NzTreeComponent } from 'ng-zorro-antd/tree'; | |||||
| import { HistoryNzModalComponent } from '../../wf/common/history-nz-modal/history-nz-modal.component'; | |||||
| @Component({ | |||||
| selector: 'app-leave-request-list', | |||||
| standalone: true, | |||||
| imports: [ | |||||
| CommonModule, | |||||
| STModule, | |||||
| NzTableModule, | |||||
| NzButtonModule, | |||||
| HistoryNzModalComponent, | |||||
| NzTableModule, | |||||
| NzPaginationModule, | |||||
| NzButtonModule, | |||||
| NzPaginationModule, | |||||
| NzInputModule, | |||||
| SharedModule, | |||||
| NzButtonModule, | |||||
| NzPopconfirmModule, | |||||
| NzMessageModule, | |||||
| NzAlertModule, | |||||
| ...SHARED_IMPORTS, | |||||
| NzRadioModule, | |||||
| NzIconModule | |||||
| ], | |||||
| templateUrl: './leave-request-list.component.html', | |||||
| styleUrls: ['./leave-request-list.component.less'] | |||||
| }) | |||||
| export class LeaveRequestListComponent implements OnInit, AfterViewInit { | |||||
| @ViewChild('sf', { static: false }) sf!: SFComponent; | |||||
| leaveRequests: any[] = []; | |||||
| loading = false; | |||||
| instantId = ''; | |||||
| total = 0; | |||||
| pageIndex = 1; | |||||
| pageSize = 10; | |||||
| constructor( | |||||
| private http: HttpClient, | |||||
| private message: NzMessageService, | |||||
| private router: Router | |||||
| ) {} | |||||
| searchParams: any = {}; | |||||
| searchSchema: any = { | |||||
| properties: { | |||||
| applicantName: { type: 'string', title: '申请人姓名' }, | |||||
| department: { type: 'string', title: '所属部门' }, | |||||
| leaveType: { | |||||
| type: 'string', | |||||
| title: '请假类型', | |||||
| enum: [ | |||||
| { label: '病假', value: '病假' }, | |||||
| { label: '事假', value: '事假' }, | |||||
| { label: '年假', value: '年假' } | |||||
| ] | |||||
| } | |||||
| } | |||||
| }; | |||||
| hVisible = false; | |||||
| columns: STColumn[] = [ | |||||
| { title: '序号', type: 'no', className: 'text-center', width: 80 }, | |||||
| { title: '申请人姓名', index: 'applicantName', width: 120, className: 'text-center' }, | |||||
| { title: '所属部门', index: 'department', width: 120, className: 'text-center' }, | |||||
| { title: '请假类型', index: 'leaveType', width: 100, className: 'text-center' }, | |||||
| { title: '开始时间', index: 'startTime', width: 150, type: 'date', dateFormat: 'yyyy-MM-dd HH:mm:ss', className: 'text-center' }, | |||||
| { title: '结束时间', index: 'endTime', width: 150, type: 'date', dateFormat: 'yyyy-MM-dd HH:mm:ss', className: 'text-center' }, | |||||
| { title: '请假天数', index: 'days', width: 80, className: 'text-center' }, | |||||
| { title: '请假原因', index: 'reason', width: 180, className: 'text-center' }, | |||||
| { title: '当前状态', index: 'status', width: 100, className: 'text-center' }, | |||||
| { | |||||
| title: '操作', | |||||
| width: 140, | |||||
| fixed: 'right', | |||||
| buttons: [ | |||||
| { | |||||
| text: '查看历史流程', | |||||
| click: (item: any) => this.history(item) | |||||
| } | |||||
| ] | |||||
| } | |||||
| ]; | |||||
| rowClassName = (record: any, index: any) => { | |||||
| if (index % 2 === 0) { | |||||
| return 'even-tr'; | |||||
| } else { | |||||
| return 'odd-tr'; | |||||
| } | |||||
| }; | |||||
| ngOnInit(): void {} | |||||
| ngAfterViewInit(): void { | |||||
| this.loadData(); | |||||
| } | |||||
| loadData(): void { | |||||
| this.searchParams = this.sf.value || {}; | |||||
| // 如果 this.sf 没有,默认用空对象 | |||||
| const searchValues = this.sf ? this.sf.value : {}; | |||||
| const params = { | |||||
| ...searchValues, | |||||
| pageNum: this.pageIndex, | |||||
| pageSize: this.pageSize | |||||
| }; | |||||
| console.log(params, 'params'); | |||||
| // this.loading = true; | |||||
| this.http.get<any[]>(`${environment.api['fuelUrl']}erp/request/list`, { params }).subscribe({ | |||||
| next: (res: any) => { | |||||
| this.leaveRequests = res.data; | |||||
| this.total = res.data?.total || this.leaveRequests.length; | |||||
| // this.loading = false; | |||||
| }, | |||||
| error: () => { | |||||
| this.message.error('加载数据失败'); | |||||
| // this.loading = false; | |||||
| } | |||||
| }); | |||||
| } | |||||
| viewHistory(processInstanceId: string): void { | |||||
| this.router.navigate(['/workflow/task-history'], { | |||||
| queryParams: { processInstanceId } | |||||
| }); | |||||
| } | |||||
| hClose() { | |||||
| this.hVisible = false; | |||||
| } | |||||
| history(item: any) { | |||||
| this.instantId = item.processInstanceId || ''; | |||||
| // this.id = item.id; | |||||
| this.hVisible = true; | |||||
| } | |||||
| modalCallBack(obj: any) { | |||||
| this.hVisible = obj.visible; | |||||
| } | |||||
| onSearchChange(e: any) { | |||||
| this.searchParams = e.value || {}; | |||||
| } | |||||
| search() { | |||||
| this.pageIndex = 1; | |||||
| this.loadData(); | |||||
| } | |||||
| resetSearch() { | |||||
| this.searchParams = {}; | |||||
| this.pageIndex = 1; | |||||
| this.loadData(); | |||||
| } | |||||
| stPageChange(e: any) { | |||||
| if (e.type === 'pi') this.pageIndex = e.pi; | |||||
| if (e.type === 'ps') this.pageSize = e.ps; | |||||
| } | |||||
| pageSizeChang(ps: number) { | |||||
| this.pageSize = ps; | |||||
| this.pageIndex = 1; | |||||
| } | |||||
| pageIndexChange(pi: number) { | |||||
| this.pageIndex = pi; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,64 @@ | |||||
| <div class="manager-approve-detail"> | |||||
| <div class="detail-container"> | |||||
| <!-- <div class="detail-item"> | |||||
| <label>申请人ID:</label> | |||||
| <span>{{ leaveRequest.applicantId }}</span> | |||||
| </div> --> | |||||
| <div class="detail-item"> | |||||
| <label>申请人姓名:</label> | |||||
| <span>{{ leaveRequest.applicantName }}</span> | |||||
| </div> | |||||
| <div class="detail-item"> | |||||
| <label>所属部门:</label> | |||||
| <span>{{ leaveRequest.department }}</span> | |||||
| </div> | |||||
| <div class="detail-item"> | |||||
| <label>请假类型:</label> | |||||
| <span>{{ leaveRequest.leaveType }}</span> | |||||
| </div> | |||||
| <div class="detail-item"> | |||||
| <label>开始时间:</label> | |||||
| <span>{{ leaveRequest.startTime | date: 'yyyy-MM-dd HH:mm:ss' }}</span> | |||||
| </div> | |||||
| <div class="detail-item"> | |||||
| <label>结束时间:</label> | |||||
| <span>{{ leaveRequest.endTime | date: 'yyyy-MM-dd HH:mm:ss' }}</span> | |||||
| </div> | |||||
| <div class="detail-item"> | |||||
| <label>请假天数:</label> | |||||
| <span>{{ leaveRequest.days }}</span> | |||||
| </div> | |||||
| <div class="detail-item"> | |||||
| <label>请假原因:</label> | |||||
| <span>{{ leaveRequest.reason }}</span> | |||||
| </div> | |||||
| <div class="detail-item"> | |||||
| <label>当前状态:</label> | |||||
| <span>{{ leaveRequest.status }}</span> | |||||
| </div> | |||||
| <div class="detail-item"> | |||||
| <label>当前审批部门:</label> | |||||
| <span>{{ leaveRequest.currentDepartment }}</span> | |||||
| </div> | |||||
| <!-- <div class="detail-item"> | |||||
| <label>当前审批人姓名:</label> | |||||
| <span>{{ leaveRequest.currentApproverName }}</span> | |||||
| </div> --> | |||||
| <div class="detail-item"> | |||||
| <label>创建时间:</label> | |||||
| <span>{{ leaveRequest.createTime | date: 'yyyy-MM-dd HH:mm:ss' }}</span> | |||||
| </div> | |||||
| <div class="detail-item"> | |||||
| <label>更新时间:</label> | |||||
| <span>{{ leaveRequest.updateTime | date: 'yyyy-MM-dd HH:mm:ss' }}</span> | |||||
| </div> | |||||
| </div> | |||||
| <!-- <div class="actions"> | |||||
| <button nz-button nzType="primary" (click)="approve(leaveRequest.id)">批准</button> | |||||
| <button nz-button nzType="default" (click)="reject(leaveRequest.id)">驳回</button> | |||||
| <button nz-button nzType="default" (click)="goBack()">返回</button> | |||||
| </div> --> | |||||
| </div> | |||||
| @@ -0,0 +1,69 @@ | |||||
| .manager-approve { | |||||
| // padding: 16px; | |||||
| h2 { | |||||
| text-align: center; | |||||
| margin-bottom: 24px; | |||||
| } | |||||
| nz-table { | |||||
| margin-top: 16px; | |||||
| } | |||||
| button { | |||||
| margin-right: 8px; | |||||
| } | |||||
| } | |||||
| .manager-approve-detail { | |||||
| padding: 24px; | |||||
| max-width: 800px; | |||||
| // margin-top: 20px; | |||||
| padding-top: 20px; | |||||
| margin: 20px auto; | |||||
| background: #fff; | |||||
| border-radius: 8px; | |||||
| box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); | |||||
| h2 { | |||||
| text-align: center; | |||||
| margin-bottom: 24px; | |||||
| font-size: 24px; | |||||
| font-weight: 600; | |||||
| color: #333; | |||||
| } | |||||
| .detail-container { | |||||
| display: flex; | |||||
| flex-direction: column; | |||||
| gap: 16px; | |||||
| } | |||||
| .detail-item { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| padding: 8px 16px; | |||||
| background: #f9f9f9; | |||||
| border-radius: 4px; | |||||
| label { | |||||
| font-weight: 500; | |||||
| color: #333; | |||||
| min-width: 120px; | |||||
| } | |||||
| span { | |||||
| color: #666; | |||||
| } | |||||
| } | |||||
| .actions { | |||||
| margin-top: 24px; | |||||
| text-align: right; | |||||
| button { | |||||
| margin-left: 8px; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,188 @@ | |||||
| import { CommonModule } from '@angular/common'; | |||||
| import { HttpClient } from '@angular/common/http'; | |||||
| import { Component, inject, OnInit, ViewChild, AfterViewInit, OnDestroy } from '@angular/core'; | |||||
| import { FormBuilder, FormGroup, Validators, FormsModule } from '@angular/forms'; | |||||
| import { ActivatedRoute, Router } from '@angular/router'; | |||||
| import { DownFileModule } from '@delon/abc/down-file'; | |||||
| import { SGModule } from '@delon/abc/sg'; | |||||
| import { STColumn, STComponent, STChange } from '@delon/abc/st'; | |||||
| import { DelonFormModule, SFComponent, SFSchema, SFDateWidgetSchema } from '@delon/form'; | |||||
| import { _HttpClient, SettingsService } from '@delon/theme'; | |||||
| import { environment } from '@env/environment'; | |||||
| import { SHARED_IMPORTS, SharedModule } from '@shared'; | |||||
| import { invoke } from '@tauri-apps/api/core'; | |||||
| import { listen } from '@tauri-apps/api/event'; | |||||
| import { NzButtonModule } from 'ng-zorro-antd/button'; | |||||
| import { NzDatePickerModule } from 'ng-zorro-antd/date-picker'; | |||||
| import { NzDescriptionsModule } from 'ng-zorro-antd/descriptions'; | |||||
| import { NzMessageService } from 'ng-zorro-antd/message'; | |||||
| import { NzModalService } from 'ng-zorro-antd/modal'; | |||||
| import { NzQRCodeModule } from 'ng-zorro-antd/qr-code'; | |||||
| import { NzRadioModule } from 'ng-zorro-antd/radio'; | |||||
| import { NzSelectModule } from 'ng-zorro-antd/select'; | |||||
| import { NzSwitchModule } from 'ng-zorro-antd/switch'; | |||||
| import { NzTableModule } from 'ng-zorro-antd/table'; | |||||
| import { NzTreeSelectModule } from 'ng-zorro-antd/tree-select'; | |||||
| import { Subscription } from 'rxjs/internal/Subscription'; | |||||
| import { BaseService } from 'src/app/core/utils/base.service'; | |||||
| import { TaskEventsService, TaskExecutedPayload } from '../../wf/task-framework/task-events.service'; | |||||
| @Component({ | |||||
| selector: 'app-manager-approve', | |||||
| standalone: true, | |||||
| imports: [ | |||||
| CommonModule, | |||||
| NzTableModule, | |||||
| NzButtonModule, | |||||
| FormsModule, | |||||
| NzDatePickerModule, | |||||
| NzTreeSelectModule, | |||||
| SharedModule, | |||||
| SGModule, | |||||
| DownFileModule, | |||||
| ...SHARED_IMPORTS, | |||||
| NzRadioModule, | |||||
| NzSwitchModule, | |||||
| NzSelectModule, | |||||
| NzDescriptionsModule | |||||
| ], | |||||
| templateUrl: './manager-approve.component.html', | |||||
| styleUrls: ['./manager-approve.component.less'] | |||||
| }) | |||||
| export class ManagerApproveComponent implements OnInit, OnDestroy { | |||||
| leaveRequest: any = {}; | |||||
| loading = false; | |||||
| businessId: string | null = null; | |||||
| routeParams: any; | |||||
| private readonly route = inject(ActivatedRoute); | |||||
| private readonly http = inject(HttpClient); | |||||
| private readonly router = inject(Router); | |||||
| private taskExecuted?: Subscription; | |||||
| constructor( | |||||
| private message: NzMessageService, | |||||
| private taskEventsService: TaskEventsService | |||||
| ) {} | |||||
| ngOnInit() { | |||||
| // 获取businessId参数 | |||||
| this.changeTitle(); | |||||
| this.route.queryParamMap.subscribe(params => { | |||||
| this.routeParams = params; | |||||
| this.businessId = params.get('businessId'); | |||||
| if (this.businessId) { | |||||
| // 详情模式 | |||||
| this.getDetail(this.businessId); | |||||
| } else { | |||||
| // 列表模式 | |||||
| this.getList(); | |||||
| } | |||||
| }); | |||||
| this.taskExecuted = this.taskEventsService.taskExecuted$.subscribe((payload: TaskExecutedPayload) => { | |||||
| // payload 里有 taskId、formKey、prodefId 等 | |||||
| console.log('监听到待办执行:', payload); | |||||
| if (payload.action === 'approve') { | |||||
| this.approve(Number(payload.businessId ?? 0), false); | |||||
| } else { | |||||
| this.reject(Number(payload.businessId ?? 0), false); | |||||
| } | |||||
| }); | |||||
| } | |||||
| ngOnDestroy() { | |||||
| if (this.taskExecuted) { | |||||
| this.taskExecuted.unsubscribe(); | |||||
| } | |||||
| } | |||||
| // 获取详情 | |||||
| getDetail(id: string) { | |||||
| this.loading = true; | |||||
| this.http.get<any>(`${environment.api['fuelUrl']}erp/request/${id}`).subscribe({ | |||||
| next: res => { | |||||
| this.leaveRequest = res.data; | |||||
| this.loading = false; | |||||
| }, | |||||
| error: () => { | |||||
| this.message.error('加载数据失败'); | |||||
| this.loading = false; | |||||
| } | |||||
| }); | |||||
| } | |||||
| // 获取列表 | |||||
| getList() { | |||||
| this.http | |||||
| .get<any>(`/erp/request/list`, { | |||||
| params: { | |||||
| currentDepartment: '业务部门' | |||||
| } | |||||
| }) | |||||
| .subscribe(res => { | |||||
| if (res.code === 200 && res.data) { | |||||
| this.leaveRequest = res.data; | |||||
| } else { | |||||
| this.leaveRequest = []; | |||||
| this.message.error('无待审批数据'); | |||||
| } | |||||
| }); | |||||
| } | |||||
| approve(id: number, complete: boolean = true): void { | |||||
| this.http.put(`${environment.api['fuelUrl']}erp/request`, { id, status: '业务部门领导已批准' }).subscribe({ | |||||
| next: () => { | |||||
| this.message.success('已批准'); | |||||
| if (complete) { | |||||
| // 可根据实际业务,调用后端API变更审批状态 | |||||
| this.taskEventsService.emitTaskFinished({ | |||||
| taskId: this.routeParams['taskId'], | |||||
| action: 'approve', // 或 'reject' | |||||
| prodefId: this.routeParams['prodefId'], | |||||
| params: this.routeParams // 可选,传递附加信息 | |||||
| }); | |||||
| } | |||||
| this.goBack(); | |||||
| }, | |||||
| error: () => { | |||||
| this.message.error('批准失败'); | |||||
| } | |||||
| }); | |||||
| } | |||||
| reject(id: number, complete: boolean = true): void { | |||||
| this.http.put(`${environment.api['fuelUrl']}erp/request`, { id, status: '业务部门经理已驳回' }).subscribe({ | |||||
| next: () => { | |||||
| this.message.success('已驳回'); | |||||
| if (complete) { | |||||
| // 可根据实际业务,调用后端API变更审批状态 | |||||
| this.taskEventsService.emitTaskFinished({ | |||||
| taskId: this.routeParams['taskId'], | |||||
| action: 'reject', // 或 'reject' | |||||
| prodefId: this.routeParams['prodefId'], | |||||
| params: this.routeParams // 可选,传递附加信息 | |||||
| }); | |||||
| } | |||||
| this.goBack(); | |||||
| }, | |||||
| error: () => { | |||||
| this.message.error('驳回失败'); | |||||
| } | |||||
| }); | |||||
| } | |||||
| changeTitle() { | |||||
| this.taskEventsService.emitButtonTitleChanged({ | |||||
| excuteTitle: '批准', | |||||
| excuteEnable: true, | |||||
| rejectTitle: '驳回', | |||||
| rejectEnable: true, | |||||
| rejectToStartTitle: '驳回到起点', | |||||
| rejectToStartEnable: false, | |||||
| deleteTitle: '删除', | |||||
| deleteEnable: false | |||||
| }); | |||||
| } | |||||
| goBack(): void { | |||||
| this.router.navigate(['/approve/leave-request-list']); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,51 @@ | |||||
| <div class="container-block"> | |||||
| <nz-card class="card-block card-search"> | |||||
| <div> | |||||
| <sf | |||||
| #sf | |||||
| mode="search" | |||||
| size="large" | |||||
| (change)="onSearchChange($event)" | |||||
| [schema]="searchSchema" | |||||
| (formReset)="resetSearch()" | |||||
| (formSubmit)="search()" | |||||
| [button]="null" | |||||
| > | |||||
| <button nz-button nzType="primary" style="margin-right: 0.5rem" type="submit" class="searchBtn">搜索</button> | |||||
| <button class="resetBtn" (click)="sf.reset(); search()" type="button" nz-button>重置</button> | |||||
| </sf> | |||||
| </div> | |||||
| </nz-card> | |||||
| <nz-card class="table-card"> | |||||
| <st | |||||
| #st | |||||
| [data]="leaveRequests" | |||||
| [columns]="columns" | |||||
| [pi]="pageIndex" | |||||
| [ps]="pageSize" | |||||
| [page]="{ front: false }" | |||||
| [total]="total" | |||||
| (change)="stPageChange($event)" | |||||
| size="small" | |||||
| [rowClassName]="rowClassName" | |||||
| [scroll]="{ y: 'calc(100vh - 283px)' }" | |||||
| [page]="{ show: false }" | |||||
| /> | |||||
| <div class="common-pagination"> | |||||
| <nz-pagination | |||||
| [(nzPageIndex)]="pageIndex" | |||||
| [nzTotal]="total" | |||||
| nzShowSizeChanger | |||||
| [nzPageSize]="pageSize" | |||||
| [nzShowTotal]="totalTemplate" | |||||
| [nzShowQuickJumper]="true" | |||||
| (nzPageSizeChange)="pageSizeChang($event)" | |||||
| (nzPageIndexChange)="pageIndexChange($event)" | |||||
| /> | |||||
| <ng-template #totalTemplate let-total>共 {{ total }} 条</ng-template> | |||||
| </div> | |||||
| </nz-card> | |||||
| </div> | |||||
| <history-nz-modal *ngIf="hVisible" [id]="instantId" [visible]="hVisible" (modalCallBack)="modalCallBack($event)" /> | |||||
| @@ -0,0 +1,305 @@ | |||||
| .card-block { | |||||
| position: relative; | |||||
| } | |||||
| .add { | |||||
| float: right; | |||||
| margin-top: -38px; | |||||
| } | |||||
| ::ng-deep .row-color { | |||||
| background-color: rgb(214, 230, 245) !important; | |||||
| } | |||||
| ::ng-deep { | |||||
| .menu-tree { | |||||
| .ant-tree { | |||||
| width: 258px; | |||||
| height: 296px; | |||||
| overflow-x: hidden; | |||||
| overflow-y: auto; | |||||
| } | |||||
| .ant-form-item-label { | |||||
| width: 72px !important; | |||||
| display: none !important; | |||||
| } | |||||
| } | |||||
| } | |||||
| ::ng-deep { | |||||
| .anticon-down { | |||||
| display: none !important; | |||||
| } | |||||
| } | |||||
| .el-button-add { | |||||
| color: #1890ff !important; | |||||
| background: #e8f4ff !important; | |||||
| border-color: #a3d3ff !important | |||||
| } | |||||
| .el-button.is-round { | |||||
| border-radius: 20px !important; | |||||
| padding: 12px 23px !important; | |||||
| } | |||||
| .el-button.is-circle { | |||||
| border-radius: 50% !important; | |||||
| padding: 12px !important; | |||||
| } | |||||
| .el-button--primary { | |||||
| color: #fff !important; | |||||
| background-color: #1890ff !important; | |||||
| border-color: #1890ff !important; | |||||
| } | |||||
| .el-button--primary:focus, | |||||
| .el-button--primary:hover { | |||||
| background: #46a6ff !important; | |||||
| border-color: #46a6ff !important; | |||||
| color: #fff !important; | |||||
| } | |||||
| .el-button--primary:active { | |||||
| outline: none !important; | |||||
| } | |||||
| .el-button--primary.is-active, | |||||
| .el-button--primary:active { | |||||
| background: #1682e6 !important; | |||||
| border-color: #1682e6 !important; | |||||
| color: #fff !important; | |||||
| } | |||||
| .el-button--primary.is-disabled, | |||||
| .el-button--primary.is-disabled:active, | |||||
| .el-button--primary.is-disabled:focus, | |||||
| .el-button--primary.is-disabled:hover { | |||||
| color: #fff !important; | |||||
| background-color: #8cc8ff !important; | |||||
| border-color: #8cc8ff !important; | |||||
| } | |||||
| .el-button--primary.is-plain { | |||||
| color: #1890ff !important; | |||||
| background: #e8f4ff !important; | |||||
| border-color: #a3d3ff !important; | |||||
| } | |||||
| .el-button--primary.is-plain:focus, | |||||
| .el-button--primary.is-plain:hover { | |||||
| background: #1890ff !important; | |||||
| border-color: #1890ff !important; | |||||
| color: #fff !important; | |||||
| } | |||||
| .el-button--primary.is-plain:active { | |||||
| background: #1682e6 !important; | |||||
| border-color: #1682e6 !important; | |||||
| color: #fff !important; | |||||
| outline: none !important; | |||||
| } | |||||
| .el-button--primary.is-plain.is-disabled, | |||||
| .el-button--primary.is-plain.is-disabled:active, | |||||
| .el-button--primary.is-plain.is-disabled:focus, | |||||
| .el-button--primary.is-plain.is-disabled:hover { | |||||
| color: #74bcff !important; | |||||
| background-color: #e8f4ff !important; | |||||
| border-color: #d1e9ff !important; | |||||
| } | |||||
| .el-button--success { | |||||
| color: #fff !important; | |||||
| background-color: #13ce66 !important; | |||||
| border-color: #13ce66 !important; | |||||
| } | |||||
| .el-button--success:focus, | |||||
| .el-button--success:hover { | |||||
| background: #42d885 !important; | |||||
| border-color: #42d885 !important; | |||||
| color: #fff !important; | |||||
| } | |||||
| .el-button--success:active { | |||||
| outline: none !important; | |||||
| } | |||||
| .el-button--success.is-active, | |||||
| .el-button--success:active { | |||||
| background: #11b95c !important; | |||||
| border-color: #11b95c !important; | |||||
| color: #fff !important; | |||||
| } | |||||
| .el-button--success.is-disabled, | |||||
| .el-button--success.is-disabled:active, | |||||
| .el-button--success.is-disabled:focus, | |||||
| .el-button--success.is-disabled:hover { | |||||
| color: #fff !important; | |||||
| background-color: #89e7b3 !important; | |||||
| border-color: #89e7b3 !important; | |||||
| } | |||||
| .el-button--success.is-plain { | |||||
| color: #13ce66 !important; | |||||
| background: #e7faf0 !important; | |||||
| border-color: #a1ebc2 !important; | |||||
| } | |||||
| .el-button--success.is-plain:focus, | |||||
| .el-button--success.is-plain:hover { | |||||
| background: #13ce66 !important; | |||||
| border-color: #13ce66 !important; | |||||
| color: #fff !important; | |||||
| } | |||||
| .el-button--success.is-plain:active { | |||||
| background: #11b95c !important; | |||||
| border-color: #11b95c !important; | |||||
| color: #fff !important; | |||||
| outline: none !important; | |||||
| } | |||||
| .el-button--success.is-plain.is-disabled, | |||||
| .el-button--success.is-plain.is-disabled:active, | |||||
| .el-button--success.is-plain.is-disabled:focus, | |||||
| .el-button--success.is-plain.is-disabled:hover { | |||||
| color: #71e2a3 !important; | |||||
| background-color: #e7faf0 !important; | |||||
| border-color: #d0f5e0 !important; | |||||
| } | |||||
| .el-button--warning { | |||||
| color: #fff !important; | |||||
| background-color: #ffba00 !important; | |||||
| border-color: #ffba00 !important; | |||||
| } | |||||
| .el-button--warning:focus, | |||||
| .el-button--warning:hover { | |||||
| background: #ffc833 !important; | |||||
| border-color: #ffc833 !important; | |||||
| color: #fff !important; | |||||
| } | |||||
| .el-button--warning:active { | |||||
| outline: none !important; | |||||
| } | |||||
| .el-button--warning.is-active, | |||||
| .el-button--warning:active { | |||||
| background: #e6a700 !important; | |||||
| border-color: #e6a700 !important; | |||||
| color: #fff !important; | |||||
| } | |||||
| .el-button--warning.is-disabled, | |||||
| .el-button--warning.is-disabled:active, | |||||
| .el-button--warning.is-disabled:focus, | |||||
| .el-button--warning.is-disabled:hover { | |||||
| color: #fff !important; | |||||
| background-color: #ffdd80 !important; | |||||
| border-color: #ffdd80 !important; | |||||
| } | |||||
| .el-button--warning.is-plain { | |||||
| color: #ffba00 !important; | |||||
| background: #fff8e6 !important; | |||||
| border-color: #ffe399 !important; | |||||
| } | |||||
| .el-button--warning.is-plain:focus, | |||||
| .el-button--warning.is-plain:hover { | |||||
| background: #ffba00 !important; | |||||
| border-color: #ffba00 !important; | |||||
| color: #fff !important; | |||||
| } | |||||
| .el-button--warning.is-plain:active { | |||||
| background: #e6a700 !important; | |||||
| border-color: #e6a700 !important; | |||||
| color: #fff !important; | |||||
| outline: none !important; | |||||
| } | |||||
| .el-button--warning.is-plain.is-disabled, | |||||
| .el-button--warning.is-plain.is-disabled:active, | |||||
| .el-button--warning.is-plain.is-disabled:focus, | |||||
| .el-button--warning.is-plain.is-disabled:hover { | |||||
| color: #ffd666 !important; | |||||
| background-color: #fff8e6 !important; | |||||
| border-color: #fff1cc !important; | |||||
| } | |||||
| .el-button--danger { | |||||
| color: #fff !important; | |||||
| background-color: #ff4949 !important; | |||||
| border-color: #ff4949 !important; | |||||
| } | |||||
| .el-button--danger:focus, | |||||
| .el-button--danger:hover { | |||||
| background: #ff6d6d !important; | |||||
| border-color: #ff6d6d !important; | |||||
| color: #fff !important; | |||||
| } | |||||
| .el-button--danger:active { | |||||
| outline: none !important; | |||||
| } | |||||
| .el-button--danger.is-active, | |||||
| .el-button--danger:active { | |||||
| background: #e64242 !important; | |||||
| border-color: #e64242 !important; | |||||
| color: #fff !important; | |||||
| } | |||||
| .el-button--danger.is-disabled, | |||||
| .el-button--danger.is-disabled:active, | |||||
| .el-button--danger.is-disabled:focus, | |||||
| .el-button--danger.is-disabled:hover { | |||||
| color: #fff !important; | |||||
| background-color: #ffa4a4 !important; | |||||
| border-color: #ffa4a4 !important; | |||||
| } | |||||
| .el-button--danger.is-plain { | |||||
| color: #ff4949 !important; | |||||
| background: #ffeded !important; | |||||
| border-color: #ffb6b6 !important; | |||||
| } | |||||
| .el-button--danger.is-plain:focus, | |||||
| .el-button--danger.is-plain:hover { | |||||
| background: #ff4949 !important; | |||||
| border-color: #ff4949 !important; | |||||
| color: #fff !important; | |||||
| } | |||||
| .el-button--danger.is-plain:active { | |||||
| background: #e64242 !important; | |||||
| border-color: #e64242 !important; | |||||
| color: #fff !important; | |||||
| outline: none !important; | |||||
| } | |||||
| .el-button--danger.is-plain.is-disabled, | |||||
| .el-button--danger.is-plain.is-disabled:active, | |||||
| .el-button--danger.is-plain.is-disabled:focus, | |||||
| .el-button--danger.is-plain.is-disabled:hover { | |||||
| color: #ff9292 !important; | |||||
| background-color: #ffeded !important; | |||||
| border-color: #ffdbdb !important; | |||||
| } | |||||
| // ::ng-deep { | |||||
| // .ant-tag{ | |||||
| // padding-left:20px !important; | |||||
| // } | |||||
| // } | |||||
| @@ -0,0 +1,213 @@ | |||||
| import { CommonModule } from '@angular/common'; | |||||
| import { HttpClient } from '@angular/common/http'; | |||||
| import { Component, OnInit, ViewChild, AfterViewInit } from '@angular/core'; | |||||
| import { ActivatedRoute, Router } from '@angular/router'; | |||||
| import { STChange, STColumn, STComponent, STModule, STColumnTag, STClickRowClassNameType } from '@delon/abc/st'; | |||||
| import { SFComponent, SFSchema, SFSelectWidgetSchema } from '@delon/form'; | |||||
| import { ModalHelper, _HttpClient } from '@delon/theme'; | |||||
| import { environment } from '@env/environment'; | |||||
| import { SHARED_IMPORTS, SharedModule } from '@shared'; | |||||
| import { NzAlertModule } from 'ng-zorro-antd/alert'; | |||||
| import { NzButtonModule } from 'ng-zorro-antd/button'; | |||||
| import { NzFormatEmitEvent } from 'ng-zorro-antd/core/tree'; | |||||
| import { NzIconModule } from 'ng-zorro-antd/icon'; | |||||
| import { NzInputModule } from 'ng-zorro-antd/input'; | |||||
| import { NzMessageService, NzMessageModule } from 'ng-zorro-antd/message'; | |||||
| import { NzModalService } from 'ng-zorro-antd/modal'; | |||||
| import { NzPaginationModule } from 'ng-zorro-antd/pagination'; | |||||
| import { NzPopconfirmModule } from 'ng-zorro-antd/popconfirm'; | |||||
| import { NzRadioModule } from 'ng-zorro-antd/radio'; | |||||
| import { NzTableModule } from 'ng-zorro-antd/table'; | |||||
| import { NzTreeComponent } from 'ng-zorro-antd/tree'; | |||||
| import { HistoryNzModalComponent } from '../../wf/common/history-nz-modal/history-nz-modal.component'; | |||||
| @Component({ | |||||
| selector: 'own-app-leave-request-list', | |||||
| standalone: true, | |||||
| imports: [ | |||||
| CommonModule, | |||||
| STModule, | |||||
| NzTableModule, | |||||
| NzButtonModule, | |||||
| HistoryNzModalComponent, | |||||
| NzTableModule, | |||||
| NzPaginationModule, | |||||
| NzButtonModule, | |||||
| NzPaginationModule, | |||||
| NzInputModule, | |||||
| SharedModule, | |||||
| NzButtonModule, | |||||
| NzPopconfirmModule, | |||||
| NzMessageModule, | |||||
| NzAlertModule, | |||||
| ...SHARED_IMPORTS, | |||||
| NzRadioModule, | |||||
| NzIconModule | |||||
| ], | |||||
| templateUrl: './own-leave-request-list.component.html', | |||||
| styleUrls: ['./own-leave-request-list.component.less'] | |||||
| }) | |||||
| export class OwnLeaveRequestListComponent implements OnInit, AfterViewInit { | |||||
| @ViewChild('sf', { static: false }) sf!: SFComponent; | |||||
| leaveRequests: any[] = []; | |||||
| loading = false; | |||||
| instantId = ''; | |||||
| total = 0; | |||||
| pageIndex = 1; | |||||
| pageSize = 10; | |||||
| constructor( | |||||
| private http: HttpClient, | |||||
| private message: NzMessageService, | |||||
| private router: Router | |||||
| ) {} | |||||
| searchParams: any = {}; | |||||
| searchSchema: any = { | |||||
| properties: { | |||||
| // applicantName: { type: 'string', title: '申请人姓名' }, | |||||
| // department: { type: 'string', title: '所属部门' }, | |||||
| leaveType: { | |||||
| type: 'string', | |||||
| title: '请假类型', | |||||
| enum: [ | |||||
| { label: '病假', value: '病假' }, | |||||
| { label: '事假', value: '事假' }, | |||||
| { label: '调休', value: '调休' }, | |||||
| { label: '年假', value: '年假' } | |||||
| ] | |||||
| } | |||||
| } | |||||
| }; | |||||
| hVisible = false; | |||||
| columns: STColumn[] = [ | |||||
| { title: '序号', type: 'no', className: 'text-center', width: 80 }, | |||||
| { title: '申请人姓名', index: 'applicantName', width: 120, className: 'text-center' }, | |||||
| { title: '所属部门', index: 'department', width: 120, className: 'text-center' }, | |||||
| { title: '请假类型', index: 'leaveType', width: 100, className: 'text-center' }, | |||||
| { title: '开始时间', index: 'startTime', width: 150, type: 'date', dateFormat: 'yyyy-MM-dd HH:mm:ss', className: 'text-center' }, | |||||
| { title: '结束时间', index: 'endTime', width: 150, type: 'date', dateFormat: 'yyyy-MM-dd HH:mm:ss', className: 'text-center' }, | |||||
| { title: '请假天数', index: 'days', width: 80, className: 'text-center' }, | |||||
| { title: '请假原因', index: 'reason', width: 180, className: 'text-center' }, | |||||
| { title: '当前状态', index: 'status', width: 100, className: 'text-center' }, | |||||
| { | |||||
| title: '操作', | |||||
| width: 140, | |||||
| fixed: 'right', | |||||
| buttons: [ | |||||
| { | |||||
| text: '详情', | |||||
| click: (item: any) => this.withdrawRequest(item) | |||||
| }, | |||||
| { | |||||
| text: '查看历史流程', | |||||
| click: (item: any) => this.history(item) | |||||
| } | |||||
| ] | |||||
| } | |||||
| ]; | |||||
| rowClassName = (record: any, index: any) => { | |||||
| if (index % 2 === 0) { | |||||
| return 'even-tr'; | |||||
| } else { | |||||
| return 'odd-tr'; | |||||
| } | |||||
| }; | |||||
| cancel(item: any): void {} | |||||
| ngOnInit(): void { | |||||
| this.loadData(); | |||||
| } | |||||
| ngAfterViewInit(): void { | |||||
| this.loadData(); | |||||
| } | |||||
| loadData(): void { | |||||
| this.searchParams = this.sf ? this.sf.value : {}; | |||||
| // 如果 this.sf 没有,默认用空对象 | |||||
| const searchValues = this.sf ? this.sf.value : {}; | |||||
| const userInfo = JSON.parse(window.sessionStorage.getItem('info') || '{}'); | |||||
| const userId = userInfo.user?.userId || ''; | |||||
| const params = { | |||||
| ...searchValues, | |||||
| pageNum: this.pageIndex, | |||||
| pageSize: this.pageSize, | |||||
| applicantName: userInfo.user?.nickName || '无' | |||||
| }; | |||||
| console.log(params, 'params'); | |||||
| this.loading = true; | |||||
| this.http.get<any[]>(`${environment.api['fuelUrl']}erp/request/list`, { params }).subscribe({ | |||||
| next: (res: any) => { | |||||
| this.leaveRequests = res.data; | |||||
| this.total = res.data?.total || this.leaveRequests.length; | |||||
| this.loading = false; | |||||
| }, | |||||
| error: () => { | |||||
| this.message.error('加载数据失败'); | |||||
| this.loading = false; | |||||
| } | |||||
| }); | |||||
| } | |||||
| withdrawRequest(item: any): void { | |||||
| // this.router.navigate(['/approve/leave-request-detail/'+id], { | |||||
| // queryParams: { | |||||
| // businessId: id, | |||||
| // } | |||||
| // }); | |||||
| this.router.navigate(['/workflow/task-framework'], { | |||||
| queryParams: { | |||||
| // taskId: task.id, | |||||
| prodefId: item.processDefinitionId, | |||||
| instantId: item.processInstanceId, | |||||
| formKey: '/approve/leave-request-detail', | |||||
| // hyCode: response?.variables.hyCode, | |||||
| businessId: item.id, | |||||
| title: '详情' | |||||
| } | |||||
| }); | |||||
| } | |||||
| viewHistory(processInstanceId: string): void { | |||||
| this.router.navigate(['/workflow/task-history'], { | |||||
| queryParams: { processInstanceId } | |||||
| }); | |||||
| } | |||||
| hClose() { | |||||
| this.hVisible = false; | |||||
| } | |||||
| history(item: any) { | |||||
| this.instantId = item.processInstanceId || ''; | |||||
| // this.id = item.id; | |||||
| this.hVisible = true; | |||||
| } | |||||
| modalCallBack(obj: any) { | |||||
| this.hVisible = obj.visible; | |||||
| } | |||||
| onSearchChange(e: any) { | |||||
| this.searchParams = e.value || {}; | |||||
| } | |||||
| search() { | |||||
| this.pageIndex = 1; | |||||
| this.loadData(); | |||||
| } | |||||
| resetSearch() { | |||||
| this.searchParams = {}; | |||||
| this.pageIndex = 1; | |||||
| this.loadData(); | |||||
| } | |||||
| stPageChange(e: any) { | |||||
| if (e.type === 'pi') this.pageIndex = e.pi; | |||||
| if (e.type === 'ps') this.pageSize = e.ps; | |||||
| } | |||||
| pageSizeChang(ps: number) { | |||||
| this.pageSize = ps; | |||||
| this.pageIndex = 1; | |||||
| } | |||||
| pageIndexChange(pi: number) { | |||||
| this.pageIndex = pi; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,64 @@ | |||||
| <div class="manager-approve-detail"> | |||||
| <div class="detail-container"> | |||||
| <!-- <div class="detail-item"> | |||||
| <label>申请人ID:</label> | |||||
| <span>{{ leaveRequest.applicantId }}</span> | |||||
| </div> --> | |||||
| <div class="detail-item"> | |||||
| <label>申请人姓名:</label> | |||||
| <span>{{ leaveRequest.applicantName }}</span> | |||||
| </div> | |||||
| <div class="detail-item"> | |||||
| <label>所属部门:</label> | |||||
| <span>{{ leaveRequest.department }}</span> | |||||
| </div> | |||||
| <div class="detail-item"> | |||||
| <label>请假类型:</label> | |||||
| <span>{{ leaveRequest.leaveType }}</span> | |||||
| </div> | |||||
| <div class="detail-item"> | |||||
| <label>开始时间:</label> | |||||
| <span>{{ leaveRequest.startTime | date: 'yyyy-MM-dd HH:mm:ss' }}</span> | |||||
| </div> | |||||
| <div class="detail-item"> | |||||
| <label>结束时间:</label> | |||||
| <span>{{ leaveRequest.endTime | date: 'yyyy-MM-dd HH:mm:ss' }}</span> | |||||
| </div> | |||||
| <div class="detail-item"> | |||||
| <label>请假天数:</label> | |||||
| <span>{{ leaveRequest.days }}</span> | |||||
| </div> | |||||
| <div class="detail-item"> | |||||
| <label>请假原因:</label> | |||||
| <span>{{ leaveRequest.reason }}</span> | |||||
| </div> | |||||
| <div class="detail-item"> | |||||
| <label>当前状态:</label> | |||||
| <span>{{ leaveRequest.status }}</span> | |||||
| </div> | |||||
| <div class="detail-item"> | |||||
| <label>当前审批部门:</label> | |||||
| <span>{{ leaveRequest.currentDepartment }}</span> | |||||
| </div> | |||||
| <!-- <div class="detail-item"> | |||||
| <label>当前审批人姓名:</label> | |||||
| <span>{{ leaveRequest.currentApproverName }}</span> | |||||
| </div> --> | |||||
| <div class="detail-item"> | |||||
| <label>创建时间:</label> | |||||
| <span>{{ leaveRequest.createTime | date: 'yyyy-MM-dd HH:mm:ss' }}</span> | |||||
| </div> | |||||
| <div class="detail-item"> | |||||
| <label>更新时间:</label> | |||||
| <span>{{ leaveRequest.updateTime | date: 'yyyy-MM-dd HH:mm:ss' }}</span> | |||||
| </div> | |||||
| </div> | |||||
| <!-- <div class="actions"> | |||||
| <button nz-button nzType="primary" (click)="approve(leaveRequest.id)">批准</button> | |||||
| <button nz-button nzType="default" (click)="reject(leaveRequest.id)">驳回</button> | |||||
| <button nz-button nzType="default" (click)="goBack()">返回</button> | |||||
| </div> --> | |||||
| </div> | |||||
| @@ -0,0 +1,69 @@ | |||||
| .manager-approve { | |||||
| // padding: 16px; | |||||
| h2 { | |||||
| text-align: center; | |||||
| margin-bottom: 24px; | |||||
| } | |||||
| nz-table { | |||||
| margin-top: 16px; | |||||
| } | |||||
| button { | |||||
| margin-right: 8px; | |||||
| } | |||||
| } | |||||
| .manager-approve-detail { | |||||
| padding: 24px; | |||||
| max-width: 800px; | |||||
| // margin-top: 20px; | |||||
| padding-top: 20px; | |||||
| margin: 20px auto; | |||||
| background: #fff; | |||||
| border-radius: 8px; | |||||
| box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); | |||||
| h2 { | |||||
| text-align: center; | |||||
| margin-bottom: 24px; | |||||
| font-size: 24px; | |||||
| font-weight: 600; | |||||
| color: #333; | |||||
| } | |||||
| .detail-container { | |||||
| display: flex; | |||||
| flex-direction: column; | |||||
| gap: 16px; | |||||
| } | |||||
| .detail-item { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| padding: 8px 16px; | |||||
| background: #f9f9f9; | |||||
| border-radius: 4px; | |||||
| label { | |||||
| font-weight: 500; | |||||
| color: #333; | |||||
| min-width: 120px; | |||||
| } | |||||
| span { | |||||
| color: #666; | |||||
| } | |||||
| } | |||||
| .actions { | |||||
| margin-top: 24px; | |||||
| text-align: right; | |||||
| button { | |||||
| margin-left: 8px; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,189 @@ | |||||
| import { CommonModule } from '@angular/common'; | |||||
| import { HttpClient } from '@angular/common/http'; | |||||
| import { Component, inject, OnInit, ViewChild, AfterViewInit, OnDestroy } from '@angular/core'; | |||||
| import { FormBuilder, FormGroup, Validators, FormsModule } from '@angular/forms'; | |||||
| import { ActivatedRoute, Router } from '@angular/router'; | |||||
| import { DownFileModule } from '@delon/abc/down-file'; | |||||
| import { SGModule } from '@delon/abc/sg'; | |||||
| import { STColumn, STComponent, STChange } from '@delon/abc/st'; | |||||
| import { DelonFormModule, SFComponent, SFSchema, SFDateWidgetSchema } from '@delon/form'; | |||||
| import { _HttpClient, SettingsService } from '@delon/theme'; | |||||
| import { environment } from '@env/environment'; | |||||
| import { SHARED_IMPORTS, SharedModule } from '@shared'; | |||||
| import { invoke } from '@tauri-apps/api/core'; | |||||
| import { listen } from '@tauri-apps/api/event'; | |||||
| import { NzButtonModule } from 'ng-zorro-antd/button'; | |||||
| import { NzDatePickerModule } from 'ng-zorro-antd/date-picker'; | |||||
| import { NzDescriptionsModule } from 'ng-zorro-antd/descriptions'; | |||||
| import { NzMessageService } from 'ng-zorro-antd/message'; | |||||
| import { NzModalService } from 'ng-zorro-antd/modal'; | |||||
| import { NzQRCodeModule } from 'ng-zorro-antd/qr-code'; | |||||
| import { NzRadioModule } from 'ng-zorro-antd/radio'; | |||||
| import { NzSelectModule } from 'ng-zorro-antd/select'; | |||||
| import { NzSwitchModule } from 'ng-zorro-antd/switch'; | |||||
| import { NzTableModule } from 'ng-zorro-antd/table'; | |||||
| import { NzTreeSelectModule } from 'ng-zorro-antd/tree-select'; | |||||
| import { Subscription } from 'rxjs/internal/Subscription'; | |||||
| import { BaseService } from 'src/app/core/utils/base.service'; | |||||
| import { TaskEventsService, TaskExecutedPayload } from '../../wf/task-framework/task-events.service'; | |||||
| @Component({ | |||||
| selector: 'app-manager-approve', | |||||
| standalone: true, | |||||
| imports: [ | |||||
| CommonModule, | |||||
| NzTableModule, | |||||
| NzButtonModule, | |||||
| FormsModule, | |||||
| NzDatePickerModule, | |||||
| NzTreeSelectModule, | |||||
| SharedModule, | |||||
| SGModule, | |||||
| DownFileModule, | |||||
| ...SHARED_IMPORTS, | |||||
| NzRadioModule, | |||||
| NzSwitchModule, | |||||
| NzSelectModule, | |||||
| NzDescriptionsModule | |||||
| ], | |||||
| templateUrl: './people-manager-approve.component.html', | |||||
| styleUrls: ['./people-manager-approve.component.less'] | |||||
| }) | |||||
| export class PeopleManagerApproveComponent implements OnInit, OnDestroy { | |||||
| leaveRequest: any = {}; | |||||
| loading = false; | |||||
| businessId: string | null = null; | |||||
| routeParams: any; | |||||
| private readonly route = inject(ActivatedRoute); | |||||
| private readonly http = inject(HttpClient); | |||||
| private readonly router = inject(Router); | |||||
| private taskExecuted?: Subscription; | |||||
| constructor( | |||||
| private message: NzMessageService, | |||||
| private taskEventsService: TaskEventsService | |||||
| ) {} | |||||
| ngOnInit() { | |||||
| this.changeTitle(); | |||||
| // 获取businessId参数 | |||||
| this.route.queryParamMap.subscribe(params => { | |||||
| this.routeParams = params; | |||||
| this.businessId = params.get('businessId'); | |||||
| if (this.businessId) { | |||||
| // 详情模式 | |||||
| this.getDetail(this.businessId); | |||||
| } else { | |||||
| // 列表模式 | |||||
| this.getList(); | |||||
| } | |||||
| }); | |||||
| this.taskExecuted = this.taskEventsService.taskExecuted$.subscribe((payload: TaskExecutedPayload) => { | |||||
| // payload 里有 taskId、formKey、prodefId 等 | |||||
| console.log('监听到待办执行:', payload); | |||||
| if (payload.action === 'approve') { | |||||
| this.approve(Number(payload.businessId ?? 0), false); | |||||
| } else { | |||||
| this.reject(Number(payload.businessId ?? 0), false); | |||||
| } | |||||
| }); | |||||
| } | |||||
| ngOnDestroy() { | |||||
| if (this.taskExecuted) { | |||||
| this.taskExecuted.unsubscribe(); | |||||
| } | |||||
| } | |||||
| // 获取详情 | |||||
| getDetail(id: string) { | |||||
| this.loading = true; | |||||
| this.http.get<any>(`${environment.api['fuelUrl']}erp/request/${id}`).subscribe({ | |||||
| next: res => { | |||||
| this.leaveRequest = res.data; | |||||
| this.loading = false; | |||||
| }, | |||||
| error: () => { | |||||
| this.message.error('加载数据失败'); | |||||
| this.loading = false; | |||||
| } | |||||
| }); | |||||
| } | |||||
| // 获取列表 | |||||
| getList() { | |||||
| this.http | |||||
| .get<any>(`/erp/request/list`, { | |||||
| params: { | |||||
| currentDepartment: '人事部门' | |||||
| } | |||||
| }) | |||||
| .subscribe(res => { | |||||
| if (res.code === 200 && res.data) { | |||||
| this.leaveRequest = res.data; | |||||
| } else { | |||||
| this.leaveRequest = []; | |||||
| this.message.error('无待审批数据'); | |||||
| } | |||||
| }); | |||||
| } | |||||
| approve(id: number, complete: boolean = true): void { | |||||
| this.http.put(`${environment.api['fuelUrl']}erp/request`, { id, status: '已批准' }).subscribe({ | |||||
| next: () => { | |||||
| this.message.success('已批准'); | |||||
| if (complete) { | |||||
| // 可根据实际业务,调用后端API变更审批状态 | |||||
| this.taskEventsService.emitTaskFinished({ | |||||
| taskId: this.routeParams['taskId'], | |||||
| action: 'approve', // 或 'reject' | |||||
| prodefId: this.routeParams['prodefId'], | |||||
| params: this.routeParams // 可选,传递附加信息 | |||||
| }); | |||||
| } | |||||
| this.goBack(); | |||||
| }, | |||||
| error: () => { | |||||
| this.message.error('批准失败'); | |||||
| } | |||||
| }); | |||||
| } | |||||
| reject(id: number, complete: boolean = true): void { | |||||
| this.http.put(`${environment.api['fuelUrl']}erp/request`, { id, status: '任务部门经理已驳回' }).subscribe({ | |||||
| next: () => { | |||||
| this.message.success('已驳回'); | |||||
| if (complete) { | |||||
| // 可根据实际业务,调用后端API变更审批状态 | |||||
| this.taskEventsService.emitTaskFinished({ | |||||
| taskId: this.routeParams['taskId'], | |||||
| action: 'reject', // 或 'reject' | |||||
| prodefId: this.routeParams['prodefId'], | |||||
| params: this.routeParams // 可选,传递附加信息 | |||||
| }); | |||||
| } | |||||
| this.goBack(); | |||||
| }, | |||||
| error: () => { | |||||
| this.message.error('驳回失败'); | |||||
| } | |||||
| }); | |||||
| } | |||||
| changeTitle() { | |||||
| this.taskEventsService.emitButtonTitleChanged({ | |||||
| excuteTitle: '批准', | |||||
| excuteEnable: true, | |||||
| rejectTitle: '驳回', | |||||
| rejectEnable: true, | |||||
| rejectToStartTitle: '驳回到起点', | |||||
| rejectToStartEnable: false, | |||||
| deleteTitle: '删除', | |||||
| deleteEnable: false | |||||
| }); | |||||
| } | |||||
| goBack(): void { | |||||
| this.router.navigate(['/approve/leave-request-list']); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,16 @@ | |||||
| import { Routes } from '@angular/router'; | |||||
| import { LeaveApplyComponent } from './leave-apply/leave-apply.component'; | |||||
| import { LeaveRequestDetailComponent } from './leave-request-detail/leave-request-detail.component'; | |||||
| import { LeaveRequestListComponent } from './leave-request-list/leave-request-list.component'; | |||||
| import { ManagerApproveComponent } from './manager-approve/manager-approve.component'; | |||||
| import { OwnLeaveRequestListComponent } from './own-leave-request-list/own-leave-request-list.component'; | |||||
| import { PeopleManagerApproveComponent } from './people-manager-approve/people-manager-approve.component'; | |||||
| export const routes: Routes = [ | |||||
| { path: 'leave-apply', component: LeaveApplyComponent }, | |||||
| { path: 'manager-approve', component: ManagerApproveComponent }, | |||||
| { path: 'people-manager-approve', component: PeopleManagerApproveComponent }, | |||||
| { path: 'leave-request-list', component: LeaveRequestListComponent }, | |||||
| { path: 'own-leave-request-list', component: OwnLeaveRequestListComponent }, | |||||
| { path: 'leave-request-detail', component: LeaveRequestDetailComponent } | |||||
| ]; | |||||
| @@ -0,0 +1,271 @@ | |||||
| <div> | |||||
| <!-- *ngIf="dataLoaded" --> | |||||
| <div class="container page-cont-height"> | |||||
| <div | |||||
| nz-col | |||||
| nzSpan="6" | |||||
| nzGutter="10" | |||||
| style="overflow: hidden; display: flex; height: 100%; flex-direction: column; justify-content: space-between" | |||||
| > | |||||
| <datav-border-box2 style="flex: 1"> | |||||
| <div class="card-default-title"> 系统状态 </div> | |||||
| <app-data-v-sys-stas | |||||
| [leftContentTitle]="'系统健康度'" | |||||
| [leftContentValue]="systemStatusInfo.systemHealthDegree" | |||||
| [rightContentTitle]="'设备投运率'" | |||||
| [rightContentValue]="systemStatusInfo.equipmentCommissioningRate" | |||||
| /> | |||||
| </datav-border-box2> | |||||
| <datav-border-box2 style="flex: 1"> | |||||
| <div class="card-default"> | |||||
| <div class="card-default-content"> | |||||
| <div style="margin-bottom: 1rem"> | |||||
| <datav-label-box4 [title]="'系 统 操 作 记 录'" [unit]="''" /> | |||||
| </div> | |||||
| <!-- <app-fuel-eqp-oper-info [topicFullPath]="'/api/main/event-msg-recv-rec?TopicFullPath=CS/PC/SysOperRec'" /> --> | |||||
| <!-- <sample-eqp-oper-info [topicFullPath]="'autosample/ReceptionRecords/sampleCabNum?sampleCabNum=1&TopicFullPath=CS/PC/scOperationRecordTopicTwo'" /> --> | |||||
| <sample-eqp-oper-info [topicFullPath]="'bm/sample/configInfo/scNum?scNum=2&TopicFullPath=CS/PC/scOperationRecordTopicTwo'" /> | |||||
| </div> | |||||
| </div> | |||||
| </datav-border-box2> | |||||
| <datav-border-box2 style="flex: 1"> | |||||
| <div class="card-default"> | |||||
| <div class="card-default-content"> | |||||
| <div style="margin-bottom: 1rem"> | |||||
| <datav-label-box4 [title]="'系 统 报 警 信 息'" [unit]="''" /> | |||||
| </div> | |||||
| <app-fuel-sys-alarm-info [topicFullPath]="'api/taskScheduling/eventMsgRec/list?TopicFullPath=ASS2/PLC2'" /> | |||||
| </div> | |||||
| </div> | |||||
| </datav-border-box2> | |||||
| <!-- <datav-border-box2> --> | |||||
| <!--仪表盘--> | |||||
| <!-- <div style="height: 10px;"></div> --> | |||||
| <!-- <app-echarts-gauge></app-echarts-gauge> --> | |||||
| <!-- </datav-border-box2> --> | |||||
| </div> | |||||
| <div | |||||
| nz-col | |||||
| nzSpan="14" | |||||
| nzGutter="10" | |||||
| style="overflow: hidden; display: flex; height: 100%; flex-direction: column; justify-content: space-between" | |||||
| > | |||||
| <div class="section section2" style="height: 84vh"> | |||||
| <datav-border-box2 style="height: 84vh"> | |||||
| <div class="inner-section top"> | |||||
| <!--存样瓶有无图片--> | |||||
| <div class="vertical-column vert1"> | |||||
| <div class="imageSource"> | |||||
| <img | |||||
| [src]="imageSource" | |||||
| alt="Dynamic Image" | |||||
| style=" | |||||
| width: calc(130px * var(--scaleX)); | |||||
| height: calc(400px * var(--scaleY)); | |||||
| margin-top: calc(160px * var(--scaleY)); | |||||
| margin-right: calc(20px * var(--scaleX)); | |||||
| " | |||||
| /> | |||||
| </div> | |||||
| <div class="circle-widget" style="margin-left: calc(10px * var(--scaleX)); margin-top: calc(-80px * var(--scaleY))"> | |||||
| <div class="item"> | |||||
| <div class="dot gray"></div> | |||||
| <p>无样空瓶</p> | |||||
| </div> | |||||
| <div class="item"> | |||||
| <div class="dot yellow"></div> | |||||
| <p>全水样</p> | |||||
| </div> | |||||
| <div class="item"> | |||||
| <div class="dot green"></div> | |||||
| <p>3mm样</p> | |||||
| </div> | |||||
| <div class="item"> | |||||
| <div class="dot blue"></div> | |||||
| <p>0.2mm样</p> | |||||
| </div> | |||||
| <div class="item"> | |||||
| <div class="dot red"></div> | |||||
| <p>停用</p> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| <!--<div class="vertical-column border">--> | |||||
| <div class="vertical-column vert2"> | |||||
| <!-- <div class="vert2top" style="height: 480px;"> --> | |||||
| <div class="vert2top" style="height: calc(480px * var(--scaleY))"> | |||||
| <!-- 上部内容 --> | |||||
| <!-- <canvas class="canvas" id="myChart" #chartCanvas></canvas> --> | |||||
| <!-- 给 ECharts 图表预留的 DOM 容器,大小可以自定义 --> | |||||
| <!-- <div #echartCanvas style="width: 1200px;height:450px;margin-top: 10px;" id="echartCanvas" class="echartCanvas"></div> --> | |||||
| <div | |||||
| #echartCanvas | |||||
| style=" | |||||
| width: calc(765px * var(--scaleX)); | |||||
| height: calc(380px * var(--scaleY)); | |||||
| margin-top: calc(0px * var(--scaleY)); | |||||
| margin-left: calc(38px * var(--scaleX)); | |||||
| " | |||||
| id="echartCanvas" | |||||
| class="echartCanvas" | |||||
| ></div> | |||||
| <div | |||||
| #trackChartCanvas | |||||
| style=" | |||||
| width: calc(900px * var(--scaleX)); | |||||
| height: calc(100px * var(--scaleY)); | |||||
| margin-top: calc(0px * var(--scaleY)); | |||||
| background-color: #034d6a; | |||||
| " | |||||
| id="trackChart" | |||||
| class="trackChart" | |||||
| ></div> | |||||
| </div> | |||||
| <div class="vert2bottom" style="height: calc(400px * var(--scaleY))"> | |||||
| <!-- 下部内容 --> | |||||
| <div | |||||
| #bottomCanvas | |||||
| style=" | |||||
| width: calc(765px * var(--scaleX)); | |||||
| height: calc(380px * var(--scaleY)); | |||||
| margin-top: calc(0px * var(--scaleY)); | |||||
| margin-left: calc(38px * var(--scaleX)); | |||||
| " | |||||
| id="bottomCanvas" | |||||
| class="bottomCanvas" | |||||
| ></div> | |||||
| </div> | |||||
| </div> | |||||
| <!--</div>--> | |||||
| <div class="vertical-column vert3"> | |||||
| <div | |||||
| #barCanvas | |||||
| style="width: calc(150px * var(--scaleX)); height: calc(400px * var(--scaleY))" | |||||
| id="barCanvas" | |||||
| class="barCanvas" | |||||
| ></div> | |||||
| <div style="height: calc(80px * var(--scaleY))"></div> | |||||
| <!-- <div #barbottomCanvas style="width:130px;height:450px;margin-top: 38px;" id="barbottomCanvas" class="barbottomCanvas"></div> --> | |||||
| <div | |||||
| #barbottomCanvas | |||||
| style="width: calc(150px * var(--scaleX)); height: calc(400px * var(--scaleY))" | |||||
| id="barbottomCanvas" | |||||
| class="barbottomCanvas" | |||||
| ></div> | |||||
| </div> | |||||
| </div> | |||||
| <!-- <div class="inner-section bottom"> | |||||
| </div> --> | |||||
| </datav-border-box2> | |||||
| </div> | |||||
| </div> | |||||
| <div | |||||
| nz-col | |||||
| nzSpan="4" | |||||
| nzGutter="10" | |||||
| style="overflow: hidden; display: flex; height: 100%; flex-direction: column; justify-content: space-between" | |||||
| > | |||||
| <datav-border-box2> | |||||
| <!--仪表盘--> | |||||
| <app-echarts-gauge /> | |||||
| </datav-border-box2> | |||||
| <div *ngIf="dataLoaded"> | |||||
| <datav-border-box2> | |||||
| <app-circle-icon | |||||
| [line1]="'总样本量'" | |||||
| [line2]="totalBottles.toString()" | |||||
| [iconPath]="'/assets/fuel/automatic-sample-storage/circleicon01.svg'" | |||||
| /> | |||||
| <!--分割线--> | |||||
| <div class="gradient-line"></div> | |||||
| <div class="icon-group"> | |||||
| <div class="left-icon"> | |||||
| <!-- 空瓶--> | |||||
| <app-circle-icon | |||||
| [line1]="'无样空瓶'" | |||||
| [line2]="totalNullBottles.toString()" | |||||
| [iconPath]="'/assets/fuel/automatic-sample-storage/circleicon09.svg'" | |||||
| /> | |||||
| </div> | |||||
| <div class="right-icon"> | |||||
| <!-- 全水样瓶--> | |||||
| <app-circle-icon | |||||
| [line1]="'全水样瓶'" | |||||
| [line2]="totalWaterBottles.toString()" | |||||
| [iconPath]="'/assets/fuel/automatic-sample-storage/circleicon06.svg'" | |||||
| /> | |||||
| </div> | |||||
| </div> | |||||
| <!--分割线--> | |||||
| <div class="gradient-line"></div> | |||||
| <div class="icon-group"> | |||||
| <div class="left-icon"> | |||||
| <!-- 3mm样瓶--> | |||||
| <app-circle-icon | |||||
| [line1]="'3mm样瓶'" | |||||
| [line2]="totalThreeBottles.toString()" | |||||
| [iconPath]="'/assets/fuel/automatic-sample-storage/circleicon07.svg'" | |||||
| /> | |||||
| </div> | |||||
| <div class="right-icon"> | |||||
| <!-- 0.2mm样瓶--> | |||||
| <app-circle-icon | |||||
| [line1]="'0.2mm样瓶'" | |||||
| [line2]="totalZeroTwoBottles.toString()" | |||||
| [iconPath]="'/assets/fuel/automatic-sample-storage/circleicon08.svg'" | |||||
| /> | |||||
| </div> | |||||
| </div> | |||||
| <!--分割线--> | |||||
| <div class="gradient-line"></div> | |||||
| </datav-border-box2> | |||||
| </div> | |||||
| <!-- <datav-border-box2> | |||||
| <div id="inputContainer"> --> | |||||
| <input | |||||
| type="text" | |||||
| id="pointIdInput" | |||||
| placeholder="请输入存样位置编号" | |||||
| class="id-input" | |||||
| (input)="onInput($event)" | |||||
| [(ngModel)]="pointId" | |||||
| [style.display]="'none'" | |||||
| /> | |||||
| <!-- <div class="right-container"> | |||||
| <app-device-control-button buttonText="存样" [pointId]="pointId"></app-device-control-button> | |||||
| <app-device-control-button buttonText="取样" [pointId]="pointId"></app-device-control-button> | |||||
| </div> --> | |||||
| <!-- </div> | |||||
| </datav-border-box2> --> | |||||
| <datav-border-box2> | |||||
| <app-switct-control-button (change)="onToggleChange($event)" /> | |||||
| </datav-border-box2> | |||||
| <datav-border-box2> | |||||
| <div class="section section3"> | |||||
| <div class="control-panel"> | |||||
| <!--方向盘组件--> | |||||
| <app-joystick /> | |||||
| <div class="button-group"> | |||||
| <!--急停--> | |||||
| <app-device-control-button buttonText="急停" [disabled]="toggleState" /> | |||||
| <!--取消急停--> | |||||
| <app-device-control-button buttonText="取消急停" [disabled]="toggleState" /> | |||||
| <!--反转--> | |||||
| <app-device-control-button buttonText="反转左" [disabled]="toggleState" /> | |||||
| <app-device-control-button buttonText="反转中" [disabled]="toggleState" /> | |||||
| <app-device-control-button buttonText="反转右" [disabled]="toggleState" /> | |||||
| <!--归位--> | |||||
| <app-device-control-button buttonText="复位" [disabled]="toggleState" /> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </datav-border-box2> | |||||
| </div> </div | |||||
| ></div> | |||||
| @@ -0,0 +1,265 @@ | |||||
| .container { | |||||
| display: flex; /* 使用Flexbox */ | |||||
| height: 84vh; /* 全屏高度 */ | |||||
| } | |||||
| .section { | |||||
| flex-grow: 1; /* 使所有部分平均分配容器空间 */ | |||||
| // height: 80vh; | |||||
| } | |||||
| .section1 { | |||||
| //background-color: lightblue; /* 第一块和第三块的背景色 */ | |||||
| //width: 14%; /* 占页面宽度的15% */ | |||||
| //display: flex; | |||||
| //background-image: url('/assets/fuel/automatic-sample-storage/bothsidesbackgrounds.svg'); | |||||
| opacity: 0.8; /* 50% 透明度 */ | |||||
| } | |||||
| .section3 { | |||||
| display: flex; | |||||
| flex-direction: column; | |||||
| justify-content: center; | |||||
| align-items: center; | |||||
| //height: 84vh; | |||||
| //background-color: lightblue; /* 第一块和第三块的背景色 */ | |||||
| //width: 14%; /* 占页面宽度的15% */ | |||||
| //display: flex; | |||||
| //background-image: url('/assets/fuel/automatic-sample-storage/bothsidesbackgrounds.svg'); | |||||
| opacity: 0.8; /* 50% 透明度 */ | |||||
| } | |||||
| .section2 { | |||||
| //background-color: lightgreen; /* 第二块的背景色 */ | |||||
| //width: 72%; /* 占页面宽度的70% */ | |||||
| display: flex; | |||||
| flex-direction: column; /* 在第二块内部使用垂直布局 */ | |||||
| } | |||||
| .section { | |||||
| &.section2 { | |||||
| // height: 78vh; | |||||
| .inner-section { | |||||
| &.top { | |||||
| display: flex; | |||||
| .vertical-column { | |||||
| display: inline-block; | |||||
| text-align: start; | |||||
| //padding: 5px; | |||||
| width: 100%; /* 设置竖直列占据整个宽度 */ | |||||
| // .top{ | |||||
| // height: 600px; | |||||
| // } | |||||
| // .bottom{ | |||||
| // height: 450px; | |||||
| // } | |||||
| &.vert1 { | |||||
| width: 11%; | |||||
| //height: 100%; | |||||
| display: flex; | |||||
| justify-content: center; /* 水平居中 */ | |||||
| align-items: center; /* 垂直居中 */ | |||||
| flex-direction: column; | |||||
| height: 100%; /* 容器的总高度,调整为需要的值 */ | |||||
| //overflow: hidden; /* 防止内容溢出 */ | |||||
| // img { | |||||
| // width: 100%; | |||||
| // height: 100%; | |||||
| // //max-width: 100%; | |||||
| // //height: auto; // 保持图片比例 | |||||
| // } | |||||
| } | |||||
| &.vert2 { | |||||
| width: 78.5%; | |||||
| height: 100%; | |||||
| border: 2px solid transparent; /* 设置为透明,因为我们将使用 border-image */ | |||||
| border-image: radial-gradient(circle, #42B2FE, transparent) 1 / 1 / 0 stretch; | |||||
| box-shadow: inset 0 0 20px #42B2FE; /* 添加发光效果 */ | |||||
| z-index: 9999; /* 确保边框效果显示在最上层 */ | |||||
| .vert2bottom { | |||||
| flex: 1; /* 下部占据剩余空间 */ | |||||
| } | |||||
| } | |||||
| &.vert3 { | |||||
| width: 10.5%; | |||||
| height: 100%; | |||||
| //background-color: #0000ff; /* 给竖3设置蓝色背景 */ | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| .canvas { | |||||
| width: 100%; | |||||
| height: 100%; | |||||
| // max-width: 1200px; | |||||
| // max-height: 400px; | |||||
| max-width: calc(1200px * var(--scaleX)); | |||||
| max-height: calc(400px * var(--scaleY)); | |||||
| display: block; // 确保 canvas 不受额外的空白符影响 | |||||
| } | |||||
| .top { | |||||
| //background-color: lightcoral; /* 第二块-上 的背景色 */ | |||||
| // background-image: url('/assets/fuel/automatic-sample-storage/maininterfacebackground.svg'); | |||||
| // opacity: 0.8; /* 50% 透明度 */ | |||||
| // background-repeat: no-repeat; /* 设置背景图片不重复 */ | |||||
| // background-size: cover; /* 让背景图片自适应并填满整个元素 */ | |||||
| flex-grow: 4; /* 因为我们想要占80%,所以这里用flex-grow的比例是4:1 */ | |||||
| } | |||||
| .bottom { | |||||
| background-image: url('/assets/fuel/automatic-sample-storage/bottombackground.svg'); | |||||
| opacity: 0.8; /* 50% 透明度 */ | |||||
| background-repeat: no-repeat; /* 设置背景图片不重复 */ | |||||
| background-size: cover; /* 让背景图片自适应并填满整个元素 */ | |||||
| //background-color: lightgrey; /* 第二块-下 的背景色 */ | |||||
| flex-grow: 1; /* 与上部分相比,这里使用1,总比例为4:1,即80%:20% */ | |||||
| } | |||||
| // .inner-section.bottom { | |||||
| // display: flex; | |||||
| // flex-direction: row; /* 水平排列子元素 */ | |||||
| // } | |||||
| .sub-section { | |||||
| //position: relative; | |||||
| flex-grow: 1; // 让每个子元素平均占据剩余空间 | |||||
| text-align: center; // 让子元素居中显示 | |||||
| // &.chart-section { | |||||
| // position: absolute; | |||||
| // top: 50%; | |||||
| // left: 50%; | |||||
| // transform: translate(-50%, -50%); | |||||
| // width: 60%; // 调整图表容器的宽度 | |||||
| // height: 60%; // 调整图表容器的高度 | |||||
| // } | |||||
| .centered { | |||||
| display: flex; | |||||
| justify-content: center; | |||||
| align-items: center; | |||||
| //height: 84vh; /* 设置高度占据整个视口 */ | |||||
| } | |||||
| } | |||||
| .imageSource { | |||||
| flex: 0 0 70%; /* 不允许imageSource部分伸缩,保持原有大小 */ | |||||
| width: calc(320px * var(--scaleX)); | |||||
| height: calc(300px * var(--scaleY)); | |||||
| display: flex; | |||||
| justify-content: center; /* 图片水平居中 */ | |||||
| align-items: center; /* 图片垂直居中 */ | |||||
| } | |||||
| .circle-widget { | |||||
| flex: 1; /* 剩余空间全部由circle-widget占据 */ | |||||
| display: flex; | |||||
| flex-direction: column; | |||||
| //justify-content: flex-end; /* 内容靠近底部 */ | |||||
| //align-items: flex-end; /* 内容靠近右侧 */ | |||||
| padding: 10px; /* 添加内边距 */ | |||||
| overflow: auto; /* 如果内容过多,允许滚动 */ | |||||
| } | |||||
| .dot { | |||||
| height: calc(18px * var(--scaleX)); /* 圆点的直径减小到14px */ | |||||
| width: calc(18px * var(--scaleX)); /* 圆点的直径减小到14px */ | |||||
| border-radius: 50%; /* 圆形 */ | |||||
| margin: 2px 0; /* 圆点上下的间距改为2px */ | |||||
| } | |||||
| .gray { background-color: #ccc; } | |||||
| .yellow { background-color: #ffcc00; } | |||||
| .green { background-color: #90ee90; } | |||||
| .blue { background-color: #6495ed; } | |||||
| .red{background-color: #ff0000;} | |||||
| .item { | |||||
| display: flex; | |||||
| flex-direction: column; | |||||
| align-items: center; /* 文字与圆点中心对齐 */ | |||||
| justify-content: center; /* 圆点垂直对齐 */ | |||||
| } | |||||
| #inputContainer { | |||||
| display: flex; | |||||
| flex-direction: column; /* 设置父容器为列方向排列 */ | |||||
| align-items: center; /* 水平居中对齐 */ | |||||
| width: 100%; /* 父容器的宽度 */ | |||||
| } | |||||
| .left-container { | |||||
| width: 80%; /* 让子容器占据父容器的80%宽度 */ | |||||
| margin: 10px 0; /* 上下间距 */ | |||||
| } | |||||
| .right-container { | |||||
| width: 80%; /* 让子容器占据父容器的80%宽度 */ | |||||
| display: flex; /* 使按钮容器中的按钮并排 */ | |||||
| justify-content: space-between; /* 两个按钮左右分布 */ | |||||
| //margin: 0px 0; /* 上下间距 */ | |||||
| } | |||||
| @darkBlueStart: #004e92; // 深蓝色开始 | |||||
| @darkBlueEnd: #000059; // 深蓝色结束 | |||||
| .id-input { | |||||
| color: #fff; /* 设置字体颜色为白色 */ | |||||
| background: linear-gradient(45deg, transparent 5%, #004e92 5%, #000059); /* 使用直接颜色值 */ | |||||
| border: 0; | |||||
| padding: calc(5px * var(--scaleY)); /* 调整内边距 */ | |||||
| font-size: calc(16px * var(--scaleX)); | |||||
| border-radius: calc(3px * var(--scaleX)); /* 与按钮的圆角保持一致 */ | |||||
| width: calc(220px * var(--scaleX)); /* 宽度与按钮一致 */ | |||||
| height: calc(50px * var(--scaleY)); /* 高度与按钮一致 */ | |||||
| box-shadow: 6px 0px 0px #00e6f6; /* 添加同样的阴影效果 */ | |||||
| display: flex; | |||||
| align-items: center; | |||||
| justify-content: center; | |||||
| letter-spacing: 3px; /* 字母间距 */ | |||||
| box-sizing: border-box; /* 包括内边距和边框 */ | |||||
| } | |||||
| .id-input:focus { | |||||
| border-color: #007bff; /* 设置聚焦时的边框颜色 */ | |||||
| outline: none; /* 去除默认的聚焦效果 */ | |||||
| } | |||||
| app-device-control-button { | |||||
| flex: 1; | |||||
| margin: 0 5px; /* 水平间距,确保按钮之间有一些空隙 */ | |||||
| text-align: center; /* 让按钮文本居中对齐 */ | |||||
| } | |||||
| .gradient-line { | |||||
| //position:absolute; | |||||
| //bottom: 20px; | |||||
| //margin-top: 10px; | |||||
| margin-top: calc(5px * var(--scaleY)); | |||||
| left: 0; | |||||
| width: 100%; | |||||
| height: calc(1.5px * var(--scaleY)); | |||||
| background: linear-gradient(to left, transparent, white 50%, white 50%, transparent); /* 两边向中间淡出的效果 */ | |||||
| } | |||||
| .icon-group { | |||||
| display: flex; /* 使用Flexbox布局 */ | |||||
| } | |||||
| .left-icon, | |||||
| .right-icon { | |||||
| flex: 1; /* 使两个子元素平分父容器的宽度 */ | |||||
| text-align: center; /* 可选:使内容居中对齐 */ | |||||
| } | |||||
| .control-panel { | |||||
| display: flex; /* 使用Flex布局 */ | |||||
| align-items: center; /* 垂直居中对齐 */ | |||||
| justify-content: space-between; /* 水平空间在元素之间平均分配 */ | |||||
| gap: 10px; /* 控制摇杆和按钮组之间的间隙 */ | |||||
| padding: 10px; /* 可以根据需求调整内边距 */ | |||||
| } | |||||
| .button-group { | |||||
| display: flex; /* 使用Flex布局 */ | |||||
| flex-direction: column; /* 垂直排列按钮 */ | |||||
| gap: 10px; /* 按钮之间的间隙 */ | |||||
| } | |||||
| /* 如果在不同组件中有不同指定的class名称,可以使用全局样式或特定的类名称来覆盖 */ | |||||
| app-joystick { | |||||
| flex: 1; /* 让摇杆占据尽可能多的空间 */ | |||||
| max-width: 400px; /* 根据需求设置最大宽度 */ | |||||
| } | |||||
| @@ -0,0 +1,24 @@ | |||||
| import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing'; | |||||
| import { FuelAutomaticSampleStorageComponent2 } from './automatic-sample-storage2.component'; | |||||
| describe('FuelAutomaticSampleStorageComponent', () => { | |||||
| let component: FuelAutomaticSampleStorageComponent2; | |||||
| let fixture: ComponentFixture<FuelAutomaticSampleStorageComponent2>; | |||||
| beforeEach(waitForAsync(() => { | |||||
| TestBed.configureTestingModule({ | |||||
| declarations: [FuelAutomaticSampleStorageComponent2] | |||||
| }).compileComponents(); | |||||
| })); | |||||
| beforeEach(() => { | |||||
| fixture = TestBed.createComponent(FuelAutomaticSampleStorageComponent2); | |||||
| component = fixture.componentInstance; | |||||
| fixture.detectChanges(); | |||||
| }); | |||||
| it('should create', () => { | |||||
| expect(component).toBeTruthy(); | |||||
| }); | |||||
| }); | |||||
| @@ -0,0 +1 @@ | |||||
| <div #chart id="chart" class="chart"></div> | |||||
| @@ -0,0 +1,5 @@ | |||||
| #chart { | |||||
| display: flex; | |||||
| width: 300px; /* 减少宽度 */ | |||||
| height: 150px; /* 减少高度 */ | |||||
| } | |||||
| @@ -0,0 +1,67 @@ | |||||
| import { Component, OnInit, OnDestroy, ElementRef, ViewChild, AfterViewInit } from '@angular/core'; | |||||
| import * as echarts from 'echarts'; | |||||
| @Component({ | |||||
| selector: 'app-bar-chart', | |||||
| templateUrl: './bar-chart.component.html', | |||||
| styleUrls: ['./bar-chart.component.less'], | |||||
| standalone: true | |||||
| }) | |||||
| export class BarChartComponent implements OnInit, OnDestroy, AfterViewInit { | |||||
| @ViewChild('chart') gaugeDiv!: ElementRef<HTMLDivElement>; | |||||
| myChart!: echarts.ECharts; | |||||
| ngOnInit(): void { | |||||
| // this.initChart(); | |||||
| } | |||||
| ngAfterViewInit(): void { | |||||
| this.initChart(); | |||||
| } | |||||
| ngOnDestroy(): void { | |||||
| if (this.myChart) { | |||||
| this.myChart.dispose(); | |||||
| } | |||||
| } | |||||
| private initChart(): void { | |||||
| const myChart = echarts.init(document.getElementById('chart')); | |||||
| const option = { | |||||
| tooltip: { | |||||
| trigger: 'axis', | |||||
| axisPointer: { | |||||
| type: 'shadow' | |||||
| } | |||||
| }, | |||||
| grid: { | |||||
| left: '3%', | |||||
| right: '4%', | |||||
| bottom: '3%', | |||||
| containLabel: true | |||||
| }, | |||||
| xAxis: { | |||||
| type: 'value', // 数值轴 | |||||
| min: 0, | |||||
| max: 100, | |||||
| interval: 20, // 设置间隔使刻度显示为20%的增量 | |||||
| axisLabel: { | |||||
| formatter: '{value}%' // 显示格式为带百分号 | |||||
| } | |||||
| }, | |||||
| yAxis: { | |||||
| type: 'category', | |||||
| data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'], | |||||
| axisTick: { | |||||
| alignWithLabel: true | |||||
| } | |||||
| }, | |||||
| series: [ | |||||
| { | |||||
| name: 'Direct', | |||||
| type: 'bar', | |||||
| barWidth: '60%', | |||||
| data: [10, 52, 52, 52, 52, 52, 52] // 修改对应的数据 | |||||
| } | |||||
| ] | |||||
| }; | |||||
| myChart.setOption(option); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,10 @@ | |||||
| <!-- circle-icon.component.html --> | |||||
| <div class="container" [style.color]="getTextColor()"> | |||||
| <div class="circle-icon"> | |||||
| <img class="icon" [src]="iconPath" alt="Icon" /> | |||||
| </div> | |||||
| <div class="texts"> | |||||
| <div class="text">{{ line1 }}</div> | |||||
| <div class="text">{{ line2 }}</div> | |||||
| </div> | |||||
| </div> | |||||