| @@ -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> | |||