初始化
25
.dockerignore
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
**/.classpath
|
||||||
|
**/.dockerignore
|
||||||
|
**/.env
|
||||||
|
**/.git
|
||||||
|
**/.gitignore
|
||||||
|
**/.project
|
||||||
|
**/.settings
|
||||||
|
**/.toolstarget
|
||||||
|
**/.vs
|
||||||
|
**/.vscode
|
||||||
|
**/*.*proj.user
|
||||||
|
**/*.dbmdl
|
||||||
|
**/*.jfm
|
||||||
|
**/azds.yaml
|
||||||
|
**/bin
|
||||||
|
**/charts
|
||||||
|
**/docker-compose*
|
||||||
|
**/Dockerfile*
|
||||||
|
**/node_modules
|
||||||
|
**/npm-debug.log
|
||||||
|
**/obj
|
||||||
|
**/secrets.dev.yaml
|
||||||
|
**/values.dev.yaml
|
||||||
|
LICENSE
|
||||||
|
README.md
|
36
.github/workflows/dockerservice.yml
vendored
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
name: docker_service
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ master ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ master ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
-
|
||||||
|
name: Check out the repo
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
-
|
||||||
|
name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v1
|
||||||
|
-
|
||||||
|
name: Login to DockerHub
|
||||||
|
uses: docker/login-action@v1
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
-
|
||||||
|
name: Docker Build & Push to Docker Hub For Service
|
||||||
|
uses: docker/build-push-action@v2
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: ./Inotify/Dockerfile
|
||||||
|
platforms: linux/amd64
|
||||||
|
push: true
|
||||||
|
tags: ${{ secrets.DOCKERHUB_TAG }}:service
|
||||||
|
|
||||||
|
- name: 'Report Suecss'
|
||||||
|
run: curl ${{ secrets.INOTIFY }}/Inotify_service/dockerBuildComplated!
|
56
.github/workflows/dockervue.yml
vendored
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
name: docker_vue
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [master]
|
||||||
|
pull_request:
|
||||||
|
branches: [master]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Check out the repo
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Cache node modules NPM
|
||||||
|
uses: actions/cache@v2
|
||||||
|
env:
|
||||||
|
cache-name: cache-node-modules-NPM
|
||||||
|
with:
|
||||||
|
path: ~/.npm
|
||||||
|
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('./Inotify.Vue/package.json') }}
|
||||||
|
restore-keys: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('./Inotify.Vue/package.json') }}
|
||||||
|
|
||||||
|
- name: Cache node modules
|
||||||
|
uses: actions/cache@v2
|
||||||
|
env:
|
||||||
|
cache-name: cache-node-modules
|
||||||
|
with:
|
||||||
|
path: ./Inotify.Vue/node_modules
|
||||||
|
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('./Inotify.Vue/package.json') }}
|
||||||
|
restore-keys: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('./Inotify.Vue/package.json') }}
|
||||||
|
|
||||||
|
- name: InstallNode and BuildVue
|
||||||
|
run: |
|
||||||
|
cd ./Inotify.Vue
|
||||||
|
npm install
|
||||||
|
npm run build:prod
|
||||||
|
|
||||||
|
- name: Login to DockerHub
|
||||||
|
uses: docker/login-action@v1
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Docker Build & Push to Docker Hub For Inotify.Vue
|
||||||
|
uses: docker/build-push-action@v2
|
||||||
|
with:
|
||||||
|
context: ./Inotify.Vue
|
||||||
|
file: ./Inotify.Vue/Dockerfile
|
||||||
|
platforms: linux/amd64
|
||||||
|
push: true
|
||||||
|
tags: ${{ secrets.DOCKERHUB_TAG }}:vue
|
||||||
|
|
||||||
|
- name: 'Report Suecss'
|
||||||
|
run: curl ${{ secrets.INOTIFY }}/InotifyVue/dockerBuildComplated!
|
104
.gitignore
vendored
@ -1,104 +0,0 @@
|
|||||||
# Logs
|
|
||||||
logs
|
|
||||||
*.log
|
|
||||||
npm-debug.log*
|
|
||||||
yarn-debug.log*
|
|
||||||
yarn-error.log*
|
|
||||||
lerna-debug.log*
|
|
||||||
|
|
||||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
|
||||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
|
||||||
|
|
||||||
# Runtime data
|
|
||||||
pids
|
|
||||||
*.pid
|
|
||||||
*.seed
|
|
||||||
*.pid.lock
|
|
||||||
|
|
||||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
|
||||||
lib-cov
|
|
||||||
|
|
||||||
# Coverage directory used by tools like istanbul
|
|
||||||
coverage
|
|
||||||
*.lcov
|
|
||||||
|
|
||||||
# nyc test coverage
|
|
||||||
.nyc_output
|
|
||||||
|
|
||||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
|
||||||
.grunt
|
|
||||||
|
|
||||||
# Bower dependency directory (https://bower.io/)
|
|
||||||
bower_components
|
|
||||||
|
|
||||||
# node-waf configuration
|
|
||||||
.lock-wscript
|
|
||||||
|
|
||||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
|
||||||
build/Release
|
|
||||||
|
|
||||||
# Dependency directories
|
|
||||||
node_modules/
|
|
||||||
jspm_packages/
|
|
||||||
|
|
||||||
# TypeScript v1 declaration files
|
|
||||||
typings/
|
|
||||||
|
|
||||||
# TypeScript cache
|
|
||||||
*.tsbuildinfo
|
|
||||||
|
|
||||||
# Optional npm cache directory
|
|
||||||
.npm
|
|
||||||
|
|
||||||
# Optional eslint cache
|
|
||||||
.eslintcache
|
|
||||||
|
|
||||||
# Microbundle cache
|
|
||||||
.rpt2_cache/
|
|
||||||
.rts2_cache_cjs/
|
|
||||||
.rts2_cache_es/
|
|
||||||
.rts2_cache_umd/
|
|
||||||
|
|
||||||
# Optional REPL history
|
|
||||||
.node_repl_history
|
|
||||||
|
|
||||||
# Output of 'npm pack'
|
|
||||||
*.tgz
|
|
||||||
|
|
||||||
# Yarn Integrity file
|
|
||||||
.yarn-integrity
|
|
||||||
|
|
||||||
# dotenv environment variables file
|
|
||||||
.env
|
|
||||||
.env.test
|
|
||||||
|
|
||||||
# parcel-bundler cache (https://parceljs.org/)
|
|
||||||
.cache
|
|
||||||
|
|
||||||
# Next.js build output
|
|
||||||
.next
|
|
||||||
|
|
||||||
# Nuxt.js build / generate output
|
|
||||||
.nuxt
|
|
||||||
dist
|
|
||||||
|
|
||||||
# Gatsby files
|
|
||||||
.cache/
|
|
||||||
# Comment in the public line in if your project uses Gatsby and *not* Next.js
|
|
||||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
|
||||||
# public
|
|
||||||
|
|
||||||
# vuepress build output
|
|
||||||
.vuepress/dist
|
|
||||||
|
|
||||||
# Serverless directories
|
|
||||||
.serverless/
|
|
||||||
|
|
||||||
# FuseBox cache
|
|
||||||
.fusebox/
|
|
||||||
|
|
||||||
# DynamoDB Local files
|
|
||||||
.dynamodb/
|
|
||||||
|
|
||||||
# TernJS port file
|
|
||||||
.tern-port
|
|
14
Inotify.Vue/.editorconfig
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
# http://editorconfig.org
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
insert_final_newline = false
|
||||||
|
trim_trailing_whitespace = false
|
5
Inotify.Vue/.env.development
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# just a flag
|
||||||
|
ENV = 'development'
|
||||||
|
|
||||||
|
# base api
|
||||||
|
VUE_APP_BASE_API = '/api'
|
6
Inotify.Vue/.env.production
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
# just a flag
|
||||||
|
ENV = 'production'
|
||||||
|
|
||||||
|
# base api
|
||||||
|
VUE_APP_BASE_API = '/api'
|
||||||
|
|
8
Inotify.Vue/.env.staging
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
NODE_ENV = production
|
||||||
|
|
||||||
|
# just a flag
|
||||||
|
ENV = 'staging'
|
||||||
|
|
||||||
|
# base api
|
||||||
|
VUE_APP_BASE_API = '/api'
|
||||||
|
|
5
Inotify.Vue/.eslintignore
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
build/*.js
|
||||||
|
src/assets
|
||||||
|
public
|
||||||
|
dist
|
||||||
|
*.vue
|
198
Inotify.Vue/.eslintrc.js
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
parserOptions: {
|
||||||
|
parser: 'babel-eslint',
|
||||||
|
sourceType: 'module'
|
||||||
|
},
|
||||||
|
env: {
|
||||||
|
browser: true,
|
||||||
|
node: true,
|
||||||
|
es6: true,
|
||||||
|
},
|
||||||
|
extends: ['plugin:vue/recommended', 'eslint:recommended'],
|
||||||
|
|
||||||
|
// add your custom rules here
|
||||||
|
//it is base on https://github.com/vuejs/eslint-config-vue
|
||||||
|
rules: {
|
||||||
|
"vue/max-attributes-per-line": [2, {
|
||||||
|
"singleline": 10,
|
||||||
|
"multiline": {
|
||||||
|
"max": 1,
|
||||||
|
"allowFirstLine": false
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
"vue/singleline-html-element-content-newline": "off",
|
||||||
|
"vue/multiline-html-element-content-newline":"off",
|
||||||
|
"vue/name-property-casing": ["error", "PascalCase"],
|
||||||
|
"vue/no-v-html": "off",
|
||||||
|
'accessor-pairs': 2,
|
||||||
|
'arrow-spacing': [2, {
|
||||||
|
'before': true,
|
||||||
|
'after': true
|
||||||
|
}],
|
||||||
|
'block-spacing': [2, 'always'],
|
||||||
|
'brace-style': [2, '1tbs', {
|
||||||
|
'allowSingleLine': true
|
||||||
|
}],
|
||||||
|
'camelcase': [0, {
|
||||||
|
'properties': 'always'
|
||||||
|
}],
|
||||||
|
'comma-dangle': [2, 'never'],
|
||||||
|
'comma-spacing': [2, {
|
||||||
|
'before': false,
|
||||||
|
'after': true
|
||||||
|
}],
|
||||||
|
'comma-style': [2, 'last'],
|
||||||
|
'constructor-super': 2,
|
||||||
|
'curly': [2, 'multi-line'],
|
||||||
|
'dot-location': [2, 'property'],
|
||||||
|
'eol-last': 2,
|
||||||
|
'eqeqeq': ["error", "always", {"null": "ignore"}],
|
||||||
|
'generator-star-spacing': [2, {
|
||||||
|
'before': true,
|
||||||
|
'after': true
|
||||||
|
}],
|
||||||
|
'handle-callback-err': [2, '^(err|error)$'],
|
||||||
|
'indent': [2, 2, {
|
||||||
|
'SwitchCase': 1
|
||||||
|
}],
|
||||||
|
'jsx-quotes': [2, 'prefer-single'],
|
||||||
|
'key-spacing': [2, {
|
||||||
|
'beforeColon': false,
|
||||||
|
'afterColon': true
|
||||||
|
}],
|
||||||
|
'keyword-spacing': [2, {
|
||||||
|
'before': true,
|
||||||
|
'after': true
|
||||||
|
}],
|
||||||
|
'new-cap': [2, {
|
||||||
|
'newIsCap': true,
|
||||||
|
'capIsNew': false
|
||||||
|
}],
|
||||||
|
'new-parens': 2,
|
||||||
|
'no-array-constructor': 2,
|
||||||
|
'no-caller': 2,
|
||||||
|
'no-console': 'off',
|
||||||
|
'no-class-assign': 2,
|
||||||
|
'no-cond-assign': 2,
|
||||||
|
'no-const-assign': 2,
|
||||||
|
'no-control-regex': 0,
|
||||||
|
'no-delete-var': 2,
|
||||||
|
'no-dupe-args': 2,
|
||||||
|
'no-dupe-class-members': 2,
|
||||||
|
'no-dupe-keys': 2,
|
||||||
|
'no-duplicate-case': 2,
|
||||||
|
'no-empty-character-class': 2,
|
||||||
|
'no-empty-pattern': 2,
|
||||||
|
'no-eval': 2,
|
||||||
|
'no-ex-assign': 2,
|
||||||
|
'no-extend-native': 2,
|
||||||
|
'no-extra-bind': 2,
|
||||||
|
'no-extra-boolean-cast': 2,
|
||||||
|
'no-extra-parens': [2, 'functions'],
|
||||||
|
'no-fallthrough': 2,
|
||||||
|
'no-floating-decimal': 2,
|
||||||
|
'no-func-assign': 2,
|
||||||
|
'no-implied-eval': 2,
|
||||||
|
'no-inner-declarations': [2, 'functions'],
|
||||||
|
'no-invalid-regexp': 2,
|
||||||
|
'no-irregular-whitespace': 2,
|
||||||
|
'no-iterator': 2,
|
||||||
|
'no-label-var': 2,
|
||||||
|
'no-labels': [2, {
|
||||||
|
'allowLoop': false,
|
||||||
|
'allowSwitch': false
|
||||||
|
}],
|
||||||
|
'no-lone-blocks': 2,
|
||||||
|
'no-mixed-spaces-and-tabs': 2,
|
||||||
|
'no-multi-spaces': 2,
|
||||||
|
'no-multi-str': 2,
|
||||||
|
'no-multiple-empty-lines': [2, {
|
||||||
|
'max': 1
|
||||||
|
}],
|
||||||
|
'no-native-reassign': 2,
|
||||||
|
'no-negated-in-lhs': 2,
|
||||||
|
'no-new-object': 2,
|
||||||
|
'no-new-require': 2,
|
||||||
|
'no-new-symbol': 2,
|
||||||
|
'no-new-wrappers': 2,
|
||||||
|
'no-obj-calls': 2,
|
||||||
|
'no-octal': 2,
|
||||||
|
'no-octal-escape': 2,
|
||||||
|
'no-path-concat': 2,
|
||||||
|
'no-proto': 2,
|
||||||
|
'no-redeclare': 2,
|
||||||
|
'no-regex-spaces': 2,
|
||||||
|
'no-return-assign': [2, 'except-parens'],
|
||||||
|
'no-self-assign': 2,
|
||||||
|
'no-self-compare': 2,
|
||||||
|
'no-sequences': 2,
|
||||||
|
'no-shadow-restricted-names': 2,
|
||||||
|
'no-spaced-func': 2,
|
||||||
|
'no-sparse-arrays': 2,
|
||||||
|
'no-this-before-super': 2,
|
||||||
|
'no-throw-literal': 2,
|
||||||
|
'no-trailing-spaces': 2,
|
||||||
|
'no-undef': 2,
|
||||||
|
'no-undef-init': 2,
|
||||||
|
'no-unexpected-multiline': 2,
|
||||||
|
'no-unmodified-loop-condition': 2,
|
||||||
|
'no-unneeded-ternary': [2, {
|
||||||
|
'defaultAssignment': false
|
||||||
|
}],
|
||||||
|
'no-unreachable': 2,
|
||||||
|
'no-unsafe-finally': 2,
|
||||||
|
'no-unused-vars': [2, {
|
||||||
|
'vars': 'all',
|
||||||
|
'args': 'none'
|
||||||
|
}],
|
||||||
|
'no-useless-call': 2,
|
||||||
|
'no-useless-computed-key': 2,
|
||||||
|
'no-useless-constructor': 2,
|
||||||
|
'no-useless-escape': 0,
|
||||||
|
'no-whitespace-before-property': 2,
|
||||||
|
'no-with': 2,
|
||||||
|
'one-var': [2, {
|
||||||
|
'initialized': 'never'
|
||||||
|
}],
|
||||||
|
'operator-linebreak': [2, 'after', {
|
||||||
|
'overrides': {
|
||||||
|
'?': 'before',
|
||||||
|
':': 'before'
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
'padded-blocks': [2, 'never'],
|
||||||
|
'quotes': [2, 'single', {
|
||||||
|
'avoidEscape': true,
|
||||||
|
'allowTemplateLiterals': true
|
||||||
|
}],
|
||||||
|
'semi': [2, 'never'],
|
||||||
|
'semi-spacing': [2, {
|
||||||
|
'before': false,
|
||||||
|
'after': true
|
||||||
|
}],
|
||||||
|
'space-before-blocks': [2, 'always'],
|
||||||
|
'space-before-function-paren': 0,
|
||||||
|
'space-in-parens': [2, 'never'],
|
||||||
|
'space-infix-ops': 2,
|
||||||
|
'space-unary-ops': [2, {
|
||||||
|
'words': true,
|
||||||
|
'nonwords': false
|
||||||
|
}],
|
||||||
|
'spaced-comment': [2, 'always', {
|
||||||
|
'markers': ['global', 'globals', 'eslint', 'eslint-disable', '*package', '!', ',']
|
||||||
|
}],
|
||||||
|
'template-curly-spacing': [2, 'never'],
|
||||||
|
'use-isnan': 2,
|
||||||
|
'valid-typeof': 2,
|
||||||
|
'wrap-iife': [2, 'any'],
|
||||||
|
'yield-star-spacing': [2, 'both'],
|
||||||
|
'yoda': [2, 'never'],
|
||||||
|
'prefer-const': 2,
|
||||||
|
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
|
||||||
|
'object-curly-spacing': [2, 'always', {
|
||||||
|
objectsInObjects: false
|
||||||
|
}],
|
||||||
|
'array-bracket-spacing': [2, 'never']
|
||||||
|
}
|
||||||
|
}
|
16
Inotify.Vue/.gitignore
vendored
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
.DS_Store
|
||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
package-lock.json
|
||||||
|
tests/**/coverage/
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.idea
|
||||||
|
.vscode
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
6
Inotify.Vue/.prettierrc
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"printWidth": 300,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"singleQuote": true,
|
||||||
|
"semi": false
|
||||||
|
}
|
5
Inotify.Vue/.travis.yml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
language: node_js
|
||||||
|
node_js: 10
|
||||||
|
script: npm run test
|
||||||
|
notifications:
|
||||||
|
email: false
|
6
Inotify.Vue/Dockerfile
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
FROM nginx
|
||||||
|
RUN mkdir /usr/share/nginx/dist
|
||||||
|
RUN rm -rf /etc/nginx/nginx.conf
|
||||||
|
COPY ./nginx.conf /etc/nginx/nginx.conf
|
||||||
|
COPY ./dist /usr/share/nginx/dist
|
||||||
|
EXPOSE 9099
|
21
Inotify.Vue/LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2017-present PanJiaChen
|
||||||
|
|
||||||
|
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.
|
16
Inotify.Vue/babel.config.js
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
module.exports = {
|
||||||
|
presets: [
|
||||||
|
// https://github.com/vuejs/vue-cli/tree/master/packages/@vue/babel-preset-app
|
||||||
|
'@vue/cli-plugin-babel/preset'
|
||||||
|
],
|
||||||
|
'env': {
|
||||||
|
'development': {
|
||||||
|
// babel-plugin-dynamic-import-node plugin only does one thing by converting all import() to require().
|
||||||
|
// This plugin can significantly increase the speed of hot updates, when you have a large number of pages.
|
||||||
|
// https://panjiachen.github.io/vue-element-admin-site/guide/advanced/lazy-loading.html
|
||||||
|
'plugins': ['dynamic-import-node'],
|
||||||
|
'sourceMaps':true,
|
||||||
|
'retainLines':true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
35
Inotify.Vue/build/index.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
const { run } = require('runjs')
|
||||||
|
const chalk = require('chalk')
|
||||||
|
const config = require('../vue.config.js')
|
||||||
|
const rawArgv = process.argv.slice(2)
|
||||||
|
const args = rawArgv.join(' ')
|
||||||
|
|
||||||
|
if (process.env.npm_config_preview || rawArgv.includes('--preview')) {
|
||||||
|
const report = rawArgv.includes('--report')
|
||||||
|
|
||||||
|
run(`vue-cli-service build ${args}`)
|
||||||
|
|
||||||
|
const port = 9526
|
||||||
|
const publicPath = config.publicPath
|
||||||
|
|
||||||
|
var connect = require('connect')
|
||||||
|
var serveStatic = require('serve-static')
|
||||||
|
const app = connect()
|
||||||
|
|
||||||
|
app.use(
|
||||||
|
publicPath,
|
||||||
|
serveStatic('./dist', {
|
||||||
|
index: ['index.html', '/']
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
app.listen(port, function () {
|
||||||
|
console.log(chalk.green(`> Preview at http://localhost:${port}${publicPath}`))
|
||||||
|
if (report) {
|
||||||
|
console.log(chalk.green(`> Report at http://localhost:${port}${publicPath}report.html`))
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
run(`vue-cli-service build ${args}`)
|
||||||
|
}
|
24
Inotify.Vue/jest.config.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
module.exports = {
|
||||||
|
moduleFileExtensions: ['js', 'jsx', 'json', 'vue'],
|
||||||
|
transform: {
|
||||||
|
'^.+\\.vue$': 'vue-jest',
|
||||||
|
'.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$':
|
||||||
|
'jest-transform-stub',
|
||||||
|
'^.+\\.jsx?$': 'babel-jest'
|
||||||
|
},
|
||||||
|
moduleNameMapper: {
|
||||||
|
'^@/(.*)$': '<rootDir>/src/$1'
|
||||||
|
},
|
||||||
|
snapshotSerializers: ['jest-serializer-vue'],
|
||||||
|
testMatch: [
|
||||||
|
'**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)'
|
||||||
|
],
|
||||||
|
collectCoverageFrom: ['src/utils/**/*.{js,vue}', '!src/utils/auth.js', '!src/utils/request.js', 'src/components/**/*.{js,vue}'],
|
||||||
|
coverageDirectory: '<rootDir>/tests/unit/coverage',
|
||||||
|
// 'collectCoverage': true,
|
||||||
|
'coverageReporters': [
|
||||||
|
'lcov',
|
||||||
|
'text-summary'
|
||||||
|
],
|
||||||
|
testURL: 'http://localhost/'
|
||||||
|
}
|
9
Inotify.Vue/jsconfig.json
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": "./",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["src/*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"exclude": ["node_modules", "dist"]
|
||||||
|
}
|
59
Inotify.Vue/mock/index.js
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
const Mock = require('mockjs')
|
||||||
|
const { param2Obj } = require('./utils')
|
||||||
|
|
||||||
|
const user = require('./user')
|
||||||
|
const table = require('./table')
|
||||||
|
const setting = require('./setting')
|
||||||
|
|
||||||
|
const mocks = [
|
||||||
|
...user,
|
||||||
|
...table,
|
||||||
|
...setting
|
||||||
|
]
|
||||||
|
|
||||||
|
// for front mock
|
||||||
|
// please use it cautiously, it will redefine XMLHttpRequest,
|
||||||
|
// which will cause many of your third-party libraries to be invalidated(like progress event).
|
||||||
|
function mockXHR() {
|
||||||
|
// mock patch
|
||||||
|
// https://github.com/nuysoft/Mock/issues/300
|
||||||
|
Mock.XHR.prototype.proxy_send = Mock.XHR.prototype.send
|
||||||
|
Mock.XHR.prototype.send = function() {
|
||||||
|
if (this.custom.xhr) {
|
||||||
|
this.custom.xhr.withCredentials = this.withCredentials || false
|
||||||
|
|
||||||
|
if (this.responseType) {
|
||||||
|
this.custom.xhr.responseType = this.responseType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.proxy_send(...arguments)
|
||||||
|
}
|
||||||
|
|
||||||
|
function XHR2ExpressReqWrap(respond) {
|
||||||
|
return function(options) {
|
||||||
|
let result = null
|
||||||
|
if (respond instanceof Function) {
|
||||||
|
const { body, type, url } = options
|
||||||
|
// https://expressjs.com/en/4x/api.html#req
|
||||||
|
result = respond({
|
||||||
|
method: type,
|
||||||
|
body: JSON.parse(body),
|
||||||
|
query: param2Obj(url)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
result = respond
|
||||||
|
}
|
||||||
|
return Mock.mock(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const i of mocks) {
|
||||||
|
Mock.mock(new RegExp(i.url), i.type || 'get', XHR2ExpressReqWrap(i.response))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
mocks,
|
||||||
|
mockXHR
|
||||||
|
}
|
||||||
|
|
81
Inotify.Vue/mock/mock-server.js
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
const chokidar = require('chokidar')
|
||||||
|
const bodyParser = require('body-parser')
|
||||||
|
const chalk = require('chalk')
|
||||||
|
const path = require('path')
|
||||||
|
const Mock = require('mockjs')
|
||||||
|
|
||||||
|
const mockDir = path.join(process.cwd(), 'mock')
|
||||||
|
|
||||||
|
function registerRoutes(app) {
|
||||||
|
let mockLastIndex
|
||||||
|
const { mocks } = require('./index.js')
|
||||||
|
const mocksForServer = mocks.map(route => {
|
||||||
|
return responseFake(route.url, route.type, route.response)
|
||||||
|
})
|
||||||
|
for (const mock of mocksForServer) {
|
||||||
|
app[mock.type](mock.url, mock.response)
|
||||||
|
mockLastIndex = app._router.stack.length
|
||||||
|
}
|
||||||
|
const mockRoutesLength = Object.keys(mocksForServer).length
|
||||||
|
return {
|
||||||
|
mockRoutesLength: mockRoutesLength,
|
||||||
|
mockStartIndex: mockLastIndex - mockRoutesLength
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function unregisterRoutes() {
|
||||||
|
Object.keys(require.cache).forEach(i => {
|
||||||
|
if (i.includes(mockDir)) {
|
||||||
|
delete require.cache[require.resolve(i)]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// for mock server
|
||||||
|
const responseFake = (url, type, respond) => {
|
||||||
|
return {
|
||||||
|
url: new RegExp(`${process.env.VUE_APP_BASE_API}${url}`),
|
||||||
|
type: type || 'get',
|
||||||
|
response(req, res) {
|
||||||
|
console.log('request invoke:' + req.path)
|
||||||
|
res.json(Mock.mock(respond instanceof Function ? respond(req, res) : respond))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = app => {
|
||||||
|
// parse app.body
|
||||||
|
// https://expressjs.com/en/4x/api.html#req.body
|
||||||
|
app.use(bodyParser.json())
|
||||||
|
app.use(bodyParser.urlencoded({
|
||||||
|
extended: true
|
||||||
|
}))
|
||||||
|
|
||||||
|
const mockRoutes = registerRoutes(app)
|
||||||
|
var mockRoutesLength = mockRoutes.mockRoutesLength
|
||||||
|
var mockStartIndex = mockRoutes.mockStartIndex
|
||||||
|
|
||||||
|
// watch files, hot reload mock server
|
||||||
|
chokidar.watch(mockDir, {
|
||||||
|
ignored: /mock-server/,
|
||||||
|
ignoreInitial: true
|
||||||
|
}).on('all', (event, path) => {
|
||||||
|
if (event === 'change' || event === 'add') {
|
||||||
|
try {
|
||||||
|
// remove mock routes stack
|
||||||
|
app._router.stack.splice(mockStartIndex, mockRoutesLength)
|
||||||
|
|
||||||
|
// clear routes cache
|
||||||
|
unregisterRoutes()
|
||||||
|
|
||||||
|
const mockRoutes = registerRoutes(app)
|
||||||
|
mockRoutesLength = mockRoutes.mockRoutesLength
|
||||||
|
mockStartIndex = mockRoutes.mockStartIndex
|
||||||
|
|
||||||
|
console.log(chalk.magentaBright(`\n > Mock Server hot reload success! changed ${path}`))
|
||||||
|
} catch (error) {
|
||||||
|
console.log(chalk.redBright(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
189
Inotify.Vue/mock/setting.js
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
const Mock = require('mockjs')
|
||||||
|
|
||||||
|
const data = Mock.mock({
|
||||||
|
'items|30': [
|
||||||
|
{
|
||||||
|
id: '@id',
|
||||||
|
title: '@sentence(10, 20)',
|
||||||
|
'status|1': ['published', 'draft', 'deleted'],
|
||||||
|
author: 'name',
|
||||||
|
display_time: '@datetime',
|
||||||
|
pageviews: '@integer(300, 5000)'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
const sendTemeplateData = [
|
||||||
|
{
|
||||||
|
name: '邮件推送',
|
||||||
|
key: 'EA2B43F7-956C-4C01-B583-0C943ABB36C3',
|
||||||
|
values: [
|
||||||
|
{
|
||||||
|
name: 'FromAddress',
|
||||||
|
description: '\u53D1\u4EF6\u5730\u5740',
|
||||||
|
default: 'abc@qq.com',
|
||||||
|
type: 0,
|
||||||
|
order: 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'FromPasssWord',
|
||||||
|
description: '\u53D1\u4EF6\u5BC6\u7801',
|
||||||
|
default: '123456789',
|
||||||
|
type: 0,
|
||||||
|
order: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'FromServer',
|
||||||
|
description: '\u53D1\u4EF6SMTP',
|
||||||
|
default: 'stmp.qq.com',
|
||||||
|
type: 0,
|
||||||
|
order: 2
|
||||||
|
},
|
||||||
|
{ name: 'EnableSSL', description: 'SSL', default: 'true|false', type: 1, order: 3 },
|
||||||
|
{
|
||||||
|
name: 'ToAddress',
|
||||||
|
description: '\u6536\u4EF6\u7BB1',
|
||||||
|
default: 'abcd@qq.com',
|
||||||
|
type: 0,
|
||||||
|
order: 4
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '409A30D5-ABE8-4A28-BADD-D04B9908D763',
|
||||||
|
name: '企业微信',
|
||||||
|
values: [
|
||||||
|
{
|
||||||
|
name: 'Corpid',
|
||||||
|
description: '\u4F01\u4E1AID',
|
||||||
|
default: 'Corpid',
|
||||||
|
type: 0,
|
||||||
|
order: 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Corpsecret',
|
||||||
|
description: '\u5BC6\u94A5',
|
||||||
|
default: 'Corpsecret',
|
||||||
|
type: 0,
|
||||||
|
order: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'AgentID',
|
||||||
|
description: '\u5E94\u7528ID',
|
||||||
|
default: 'AgentID',
|
||||||
|
type: 0,
|
||||||
|
order: 2
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
const sendAuths = {
|
||||||
|
code: 200,
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
key: '409A30D5-ABE8-4A28-BADD-D04B9908D763',
|
||||||
|
type: '微信推送',
|
||||||
|
name: '测试',
|
||||||
|
isActive: true,
|
||||||
|
auth: '{\u0022Corpid\u0022:\u0022ww4199b1ecd7dcecba\u0022,\u0022Corpsecret\u0022:\u0022kZUQf52AMYAMsxPGXEiQsHISLwjHhHAnyPXYKLPdoo4\u0022,\u0022AgentID\u0022:\u00221000002\u0022}',
|
||||||
|
values: [
|
||||||
|
{ name: 'Corpid', description: '企业ID', default: 'Corpid', type: 1, order: 0, value: 'ww4199b1ecd7dcecba' },
|
||||||
|
{ name: 'Corpsecret', description: '密钥', default: 'Corpsecret', type: 1, order: 1, value: 'kZUQf52AMYAMsxPGXEiQsHISLwjHhHAnyPXYKLPdoo4' },
|
||||||
|
{ name: 'AgentID', description: '应用ID', default: 'AgentID', type: 1, order: 2, value: '1000002' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
const sendKeyData = '3015679CB0DC462C89F2E37779540894'
|
||||||
|
|
||||||
|
module.exports = [
|
||||||
|
// user login
|
||||||
|
{
|
||||||
|
url: '/setting/getSendTemplates',
|
||||||
|
type: 'get',
|
||||||
|
response: config => {
|
||||||
|
const items = sendTemeplateData
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
data: sendTemeplateData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: '/setting/getSendAuths',
|
||||||
|
type: 'get',
|
||||||
|
response: config => {
|
||||||
|
return sendAuths
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: '/setting/getSendKey',
|
||||||
|
type: 'get',
|
||||||
|
response: config => {
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
data: sendKeyData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: '/setting/reSendKey',
|
||||||
|
type: 'get',
|
||||||
|
response: config => {
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
data: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: '/send',
|
||||||
|
type: 'get',
|
||||||
|
response: config => {
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
data: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: '/setting/deleteSendAuth',
|
||||||
|
type: 'get',
|
||||||
|
response: config => {
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
data: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: '/setting/ActiveSendAuth',
|
||||||
|
type: 'get',
|
||||||
|
response: config => {
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
data: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: '/setting/addSendAuth',
|
||||||
|
type: 'post',
|
||||||
|
response: config => {
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
data: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: '/setting/modifySendAuth',
|
||||||
|
type: 'post',
|
||||||
|
response: config => {
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
data: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
29
Inotify.Vue/mock/table.js
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
const Mock = require('mockjs')
|
||||||
|
|
||||||
|
const data = Mock.mock({
|
||||||
|
'items|30': [{
|
||||||
|
id: '@id',
|
||||||
|
title: '@sentence(10, 20)',
|
||||||
|
'status|1': ['published', 'draft', 'deleted'],
|
||||||
|
author: 'name',
|
||||||
|
display_time: '@datetime',
|
||||||
|
pageviews: '@integer(300, 5000)'
|
||||||
|
}]
|
||||||
|
})
|
||||||
|
|
||||||
|
module.exports = [
|
||||||
|
{
|
||||||
|
url: '/vue-admin-template/table/list',
|
||||||
|
type: 'get',
|
||||||
|
response: config => {
|
||||||
|
const items = data.items
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
data: {
|
||||||
|
total: items.length,
|
||||||
|
items: items
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
104
Inotify.Vue/mock/user.js
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
|
||||||
|
const tokens = {
|
||||||
|
admin: {
|
||||||
|
token: 'admin-token'
|
||||||
|
},
|
||||||
|
editor: {
|
||||||
|
token: 'editor-token'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const users = {
|
||||||
|
'admin-token': {
|
||||||
|
roles: ['admin'],
|
||||||
|
introduction: 'I am a super administrator',
|
||||||
|
avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
|
||||||
|
name: 'Super Admin'
|
||||||
|
},
|
||||||
|
'editor-token': {
|
||||||
|
roles: ['editor'],
|
||||||
|
introduction: 'I am an editor',
|
||||||
|
avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
|
||||||
|
name: 'Normal Editor'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = [
|
||||||
|
// user login
|
||||||
|
{
|
||||||
|
url: '/oauth/login',
|
||||||
|
type: 'post',
|
||||||
|
response: config => {
|
||||||
|
const { username } = config.body
|
||||||
|
const token = tokens[username]
|
||||||
|
|
||||||
|
// mock error
|
||||||
|
if (!token) {
|
||||||
|
return {
|
||||||
|
code: 60204,
|
||||||
|
message: 'Account and password are incorrect.'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
data: token
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: '/oauth/githublogin',
|
||||||
|
type: 'post',
|
||||||
|
response: config => {
|
||||||
|
const { username } = config.body
|
||||||
|
const token = tokens[username]
|
||||||
|
|
||||||
|
// mock error
|
||||||
|
if (!token) {
|
||||||
|
return {
|
||||||
|
code: 60204,
|
||||||
|
message: '登陆失败.'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
data: token
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// get user info
|
||||||
|
{
|
||||||
|
url: '/oauth/info',
|
||||||
|
type: 'get',
|
||||||
|
response: config => {
|
||||||
|
const { token } = config.query
|
||||||
|
const info = users[token]
|
||||||
|
|
||||||
|
// mock error
|
||||||
|
if (!info) {
|
||||||
|
return {
|
||||||
|
code: 50008,
|
||||||
|
message: 'Login failed, unable to get user details.'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
data: info
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// user logout
|
||||||
|
{
|
||||||
|
url: '/oauth/logout',
|
||||||
|
type: 'post',
|
||||||
|
response: _ => {
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
data: 'success'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
25
Inotify.Vue/mock/utils.js
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
/**
|
||||||
|
* @param {string} url
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
function param2Obj(url) {
|
||||||
|
const search = decodeURIComponent(url.split('?')[1]).replace(/\+/g, ' ')
|
||||||
|
if (!search) {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
const obj = {}
|
||||||
|
const searchArr = search.split('&')
|
||||||
|
searchArr.forEach(v => {
|
||||||
|
const index = v.indexOf('=')
|
||||||
|
if (index !== -1) {
|
||||||
|
const name = v.substring(0, index)
|
||||||
|
const val = v.substring(index + 1, v.length)
|
||||||
|
obj[name] = val
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
param2Obj
|
||||||
|
}
|
20
Inotify.Vue/nginx.conf
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
# fangt add for web server
|
||||||
|
# 2018-10-27
|
||||||
|
worker_processes 1;
|
||||||
|
|
||||||
|
events {
|
||||||
|
worker_connections 1024;
|
||||||
|
}
|
||||||
|
http {
|
||||||
|
include mime.types;
|
||||||
|
sendfile on;
|
||||||
|
keepalive_timeout 65;
|
||||||
|
default_type application/octet-stream;
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name localhost;
|
||||||
|
location / {
|
||||||
|
root /usr/share/nginx/dist;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
65
Inotify.Vue/package.json
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
{
|
||||||
|
"name": "Inotify-vue",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"description": "WebControl for Inotify",
|
||||||
|
"author": "<mrdemonson@gmail.com>",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vue-cli-service serve",
|
||||||
|
"build:prod": "vue-cli-service build",
|
||||||
|
"build:stage": "vue-cli-service build --mode staging",
|
||||||
|
"preview": "node build/index.js --preview",
|
||||||
|
"svgo": "svgo -f src/icons/svg --config=src/icons/svgo.yml",
|
||||||
|
"lint": "eslint --ext .js,.vue src",
|
||||||
|
"test:unit": "jest --clearCache && vue-cli-service test:unit",
|
||||||
|
"test:ci": "npm run lint && npm run test:unit"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"axios": "0.18.1",
|
||||||
|
"core-js": "3.6.5",
|
||||||
|
"echarts": "^4.9.0",
|
||||||
|
"element-plus": "^1.0.2-beta.32",
|
||||||
|
"element-ui": "2.13.2",
|
||||||
|
"js-cookie": "2.2.0",
|
||||||
|
"moment": "^2.29.1",
|
||||||
|
"normalize.css": "7.0.0",
|
||||||
|
"nprogress": "0.2.0",
|
||||||
|
"path-to-regexp": "2.4.0",
|
||||||
|
"vue": "2.6.10",
|
||||||
|
"vue-router": "3.0.6",
|
||||||
|
"vuex": "3.1.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@vue/cli-plugin-babel": "4.4.4",
|
||||||
|
"@vue/cli-plugin-eslint": "4.4.4",
|
||||||
|
"@vue/cli-plugin-unit-jest": "4.4.4",
|
||||||
|
"@vue/cli-service": "4.4.4",
|
||||||
|
"@vue/test-utils": "1.0.0-beta.29",
|
||||||
|
"autoprefixer": "9.5.1",
|
||||||
|
"babel-eslint": "10.1.0",
|
||||||
|
"babel-jest": "23.6.0",
|
||||||
|
"babel-plugin-dynamic-import-node": "2.3.3",
|
||||||
|
"chalk": "2.4.2",
|
||||||
|
"connect": "3.6.6",
|
||||||
|
"eslint": "6.7.2",
|
||||||
|
"eslint-plugin-vue": "6.2.2",
|
||||||
|
"html-webpack-plugin": "3.2.0",
|
||||||
|
"mockjs": "1.0.1-beta3",
|
||||||
|
"runjs": "4.3.2",
|
||||||
|
"sass": "1.26.8",
|
||||||
|
"sass-loader": "8.0.2",
|
||||||
|
"script-ext-html-webpack-plugin": "2.1.3",
|
||||||
|
"serve-static": "1.13.2",
|
||||||
|
"svg-sprite-loader": "4.1.3",
|
||||||
|
"svgo": "1.2.2",
|
||||||
|
"vue-template-compiler": "2.6.10"
|
||||||
|
},
|
||||||
|
"browserslist": [
|
||||||
|
"> 1%",
|
||||||
|
"last 2 versions"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.9",
|
||||||
|
"npm": ">= 3.0.0"
|
||||||
|
},
|
||||||
|
"license": "MIT"
|
||||||
|
}
|
8
Inotify.Vue/postcss.config.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
// https://github.com/michael-ciniawsky/postcss-load-config
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
'plugins': {
|
||||||
|
// to edit target browsers: use "browserslist" field in package.json
|
||||||
|
'autoprefixer': {}
|
||||||
|
}
|
||||||
|
}
|
BIN
Inotify.Vue/public/favicon.ico
Normal file
After Width: | Height: | Size: 66 KiB |
17
Inotify.Vue/public/index.html
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
||||||
|
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||||
|
<title><%= webpackConfig.name %></title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<noscript>
|
||||||
|
<strong>We're sorry but <%= webpackConfig.name %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||||
|
</noscript>
|
||||||
|
<div id="app"></div>
|
||||||
|
<!-- built files will be auto injected -->
|
||||||
|
</body>
|
||||||
|
</html>
|
11
Inotify.Vue/src/App.vue
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<template>
|
||||||
|
<div id="app">
|
||||||
|
<router-view />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'App'
|
||||||
|
}
|
||||||
|
</script>
|
96
Inotify.Vue/src/api/setting.js
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
import request from '@/utils/request'
|
||||||
|
|
||||||
|
export function getSendTemplates(data) {
|
||||||
|
return request({
|
||||||
|
url: '/setting/getSendTemplates',
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getSendAuths(authInfo) {
|
||||||
|
return request({
|
||||||
|
url: '/setting/getSendAuths',
|
||||||
|
method: 'get',
|
||||||
|
params: { authInfo }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getSendKey(authInfo) {
|
||||||
|
return request({
|
||||||
|
url: '/setting/getSendKey',
|
||||||
|
method: 'get',
|
||||||
|
params: { authInfo }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function reSendKey(authInfo) {
|
||||||
|
return request({
|
||||||
|
url: '/setting/reSendKey',
|
||||||
|
method: 'get',
|
||||||
|
params: { authInfo }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sendMessage(message) {
|
||||||
|
return request({
|
||||||
|
url: '/send',
|
||||||
|
method: 'get',
|
||||||
|
params: message
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteAuthInfo(sendAuthId) {
|
||||||
|
return request({
|
||||||
|
url: '/setting/deleteSendAuth',
|
||||||
|
method: 'post',
|
||||||
|
params: { sendAuthId: sendAuthId }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function activeAuthInfo(sendAuthId, state) {
|
||||||
|
return request({
|
||||||
|
url: '/setting/activeSendAuth',
|
||||||
|
method: 'post',
|
||||||
|
params: { sendAuthId: sendAuthId, state: state }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function addAuthInfo(template) {
|
||||||
|
return request({
|
||||||
|
url: '/setting/addSendAuth',
|
||||||
|
method: 'post',
|
||||||
|
data: template
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function modifySendAuth(template) {
|
||||||
|
return request({
|
||||||
|
url: '/setting/modifySendAuth',
|
||||||
|
method: 'post',
|
||||||
|
data: template
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deepClone(data) {
|
||||||
|
let d
|
||||||
|
if (typeof data === 'object') {
|
||||||
|
if (data == null) {
|
||||||
|
d = null
|
||||||
|
} else {
|
||||||
|
if (data.constructor === Array) {
|
||||||
|
d = []
|
||||||
|
for (const i in data) {
|
||||||
|
d.push(deepClone(data[i]))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
d = {}
|
||||||
|
for (const i in data) {
|
||||||
|
d[i] = deepClone(data[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
d = data
|
||||||
|
}
|
||||||
|
return d
|
||||||
|
}
|
79
Inotify.Vue/src/api/systemsetting.js
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
import request from '@/utils/request'
|
||||||
|
|
||||||
|
export function getGlobal() {
|
||||||
|
return request({
|
||||||
|
url: '/settingsys/GetGlobal',
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setGlobal(data) {
|
||||||
|
return request({
|
||||||
|
url: '/settingsys/setGlobal',
|
||||||
|
method: 'post',
|
||||||
|
params: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getJWT() {
|
||||||
|
return request({
|
||||||
|
url: '/settingsys/getJWT',
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setJWT(data) {
|
||||||
|
return request({
|
||||||
|
url: '/settingsys/setJWT',
|
||||||
|
method: 'post',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getGithubEnable() {
|
||||||
|
return request({
|
||||||
|
url: '/settingsys/getGithubEnable',
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getUsers(query, page, pagesize) {
|
||||||
|
return request({
|
||||||
|
url: '/settingsys/getUsers',
|
||||||
|
method: 'get',
|
||||||
|
params: { query: query, page: page, pagesize: pagesize }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function activeUser(userName, active) {
|
||||||
|
return request({
|
||||||
|
url: '/settingsys/activeUser',
|
||||||
|
method: 'post',
|
||||||
|
params: { userName: userName, active: active }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteUser(userName) {
|
||||||
|
return request({
|
||||||
|
url: '/settingsys/deleteUser',
|
||||||
|
method: 'post',
|
||||||
|
params: { userName: userName }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getSendTypeInfos(start, end) {
|
||||||
|
return request({
|
||||||
|
url: '/settingsys/GetSendTypeInfos',
|
||||||
|
method: 'get',
|
||||||
|
params: { start: start, end: end }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getSendInfos(start, end) {
|
||||||
|
return request({
|
||||||
|
url: '/settingsys/GetSendInfos',
|
||||||
|
method: 'get',
|
||||||
|
params: { start: start, end: end }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
9
Inotify.Vue/src/api/table.js
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import request from '@/utils/request'
|
||||||
|
|
||||||
|
export function getList(params) {
|
||||||
|
return request({
|
||||||
|
url: '/vue-admin-template/table/list',
|
||||||
|
method: 'get',
|
||||||
|
params
|
||||||
|
})
|
||||||
|
}
|
51
Inotify.Vue/src/api/user.js
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import request from '@/utils/request'
|
||||||
|
|
||||||
|
export function login(data) {
|
||||||
|
return request({
|
||||||
|
url: '/oauth/login',
|
||||||
|
method: 'post',
|
||||||
|
params: {
|
||||||
|
username: data.username,
|
||||||
|
password: data.password
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function githubLogin() {
|
||||||
|
return request({
|
||||||
|
url: '/oauth/githublogin',
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function githubEnable() {
|
||||||
|
return request({
|
||||||
|
url: '/oauth/GithubEnable',
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resetPassword(password) {
|
||||||
|
return request({
|
||||||
|
url: '/oauth/resetPassword',
|
||||||
|
method: 'post',
|
||||||
|
params: {
|
||||||
|
password: password
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getInfo(token) {
|
||||||
|
return request({
|
||||||
|
url: '/oauth/info',
|
||||||
|
method: 'get',
|
||||||
|
params: { token }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function logout() {
|
||||||
|
return request({
|
||||||
|
url: '/oauth/logout',
|
||||||
|
method: 'post'
|
||||||
|
})
|
||||||
|
}
|
BIN
Inotify.Vue/src/assets/404_images/404.png
Normal file
After Width: | Height: | Size: 96 KiB |
BIN
Inotify.Vue/src/assets/404_images/404_cloud.png
Normal file
After Width: | Height: | Size: 4.7 KiB |
78
Inotify.Vue/src/components/Breadcrumb/index.vue
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
<template>
|
||||||
|
<el-breadcrumb class="app-breadcrumb" separator="/">
|
||||||
|
<transition-group name="breadcrumb">
|
||||||
|
<el-breadcrumb-item v-for="(item,index) in levelList" :key="item.path">
|
||||||
|
<span v-if="item.redirect==='noRedirect'||index==levelList.length-1" class="no-redirect">{{ item.meta.title }}</span>
|
||||||
|
<a v-else @click.prevent="handleLink(item)">{{ item.meta.title }}</a>
|
||||||
|
</el-breadcrumb-item>
|
||||||
|
</transition-group>
|
||||||
|
</el-breadcrumb>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import pathToRegexp from 'path-to-regexp'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
levelList: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
$route() {
|
||||||
|
this.getBreadcrumb()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.getBreadcrumb()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getBreadcrumb() {
|
||||||
|
// only show routes with meta.title
|
||||||
|
let matched = this.$route.matched.filter(item => item.meta && item.meta.title)
|
||||||
|
const first = matched[0]
|
||||||
|
|
||||||
|
if (!this.isDashboard(first)) {
|
||||||
|
matched = [{ path: '/manager', meta: { title: '系统管理' }}].concat(matched)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.levelList = matched.filter(item => item.meta && item.meta.title && item.meta.breadcrumb !== false)
|
||||||
|
},
|
||||||
|
isDashboard(route) {
|
||||||
|
const name = route && route.name
|
||||||
|
if (!name) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return name.trim().toLocaleLowerCase() === 'Manager'.toLocaleLowerCase()
|
||||||
|
},
|
||||||
|
pathCompile(path) {
|
||||||
|
// To solve this problem https://github.com/PanJiaChen/vue-element-admin/issues/561
|
||||||
|
const { params } = this.$route
|
||||||
|
var toPath = pathToRegexp.compile(path)
|
||||||
|
return toPath(params)
|
||||||
|
},
|
||||||
|
handleLink(item) {
|
||||||
|
const { redirect, path } = item
|
||||||
|
if (redirect) {
|
||||||
|
this.$router.push(redirect)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.$router.push(this.pathCompile(path))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.app-breadcrumb.el-breadcrumb {
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 50px;
|
||||||
|
margin-left: 8px;
|
||||||
|
|
||||||
|
.no-redirect {
|
||||||
|
color: #97a8be;
|
||||||
|
cursor: text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
44
Inotify.Vue/src/components/Hamburger/index.vue
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
<template>
|
||||||
|
<div style="padding: 0 15px;" @click="toggleClick">
|
||||||
|
<svg
|
||||||
|
:class="{'is-active':isActive}"
|
||||||
|
class="hamburger"
|
||||||
|
viewBox="0 0 1024 1024"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="64"
|
||||||
|
height="64"
|
||||||
|
>
|
||||||
|
<path d="M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM142.4 642.1L298.7 519a8.84 8.84 0 0 0 0-13.9L142.4 381.9c-5.8-4.6-14.4-.5-14.4 6.9v246.3a8.9 8.9 0 0 0 14.4 7z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'Hamburger',
|
||||||
|
props: {
|
||||||
|
isActive: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
toggleClick() {
|
||||||
|
this.$emit('toggleClick')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.hamburger {
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hamburger.is-active {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
</style>
|
62
Inotify.Vue/src/components/SvgIcon/index.vue
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
<template>
|
||||||
|
<div v-if="isExternal" :style="styleExternalIcon" class="svg-external-icon svg-icon" v-on="$listeners" />
|
||||||
|
<svg v-else :class="svgClass" aria-hidden="true" v-on="$listeners">
|
||||||
|
<use :xlink:href="iconName" />
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// doc: https://panjiachen.github.io/vue-element-admin-site/feature/component/svg-icon.html#usage
|
||||||
|
import { isExternal } from '@/utils/validate'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'SvgIcon',
|
||||||
|
props: {
|
||||||
|
iconClass: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
className: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
isExternal() {
|
||||||
|
return isExternal(this.iconClass)
|
||||||
|
},
|
||||||
|
iconName() {
|
||||||
|
return `#icon-${this.iconClass}`
|
||||||
|
},
|
||||||
|
svgClass() {
|
||||||
|
if (this.className) {
|
||||||
|
return 'svg-icon ' + this.className
|
||||||
|
} else {
|
||||||
|
return 'svg-icon'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
styleExternalIcon() {
|
||||||
|
return {
|
||||||
|
mask: `url(${this.iconClass}) no-repeat 50% 50%`,
|
||||||
|
'-webkit-mask': `url(${this.iconClass}) no-repeat 50% 50%`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.svg-icon {
|
||||||
|
width: 1em;
|
||||||
|
height: 1em;
|
||||||
|
vertical-align: -0.15em;
|
||||||
|
fill: currentColor;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.svg-external-icon {
|
||||||
|
background-color: currentColor;
|
||||||
|
mask-size: cover!important;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
</style>
|
9
Inotify.Vue/src/icons/index.js
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import Vue from 'vue'
|
||||||
|
import SvgIcon from '@/components/SvgIcon'// svg component
|
||||||
|
|
||||||
|
// register globally
|
||||||
|
Vue.component('svg-icon', SvgIcon)
|
||||||
|
|
||||||
|
const req = require.context('./svg', false, /\.svg$/)
|
||||||
|
const requireAll = requireContext => requireContext.keys().map(requireContext)
|
||||||
|
requireAll(req)
|
1
Inotify.Vue/src/icons/svg/dashboard.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg width="128" height="100" xmlns="http://www.w3.org/2000/svg"><path d="M27.429 63.638c0-2.508-.893-4.65-2.679-6.424-1.786-1.775-3.94-2.662-6.464-2.662-2.524 0-4.679.887-6.465 2.662-1.785 1.774-2.678 3.916-2.678 6.424 0 2.508.893 4.65 2.678 6.424 1.786 1.775 3.94 2.662 6.465 2.662 2.524 0 4.678-.887 6.464-2.662 1.786-1.775 2.679-3.916 2.679-6.424zm13.714-31.801c0-2.508-.893-4.65-2.679-6.424-1.785-1.775-3.94-2.662-6.464-2.662-2.524 0-4.679.887-6.464 2.662-1.786 1.774-2.679 3.916-2.679 6.424 0 2.508.893 4.65 2.679 6.424 1.785 1.774 3.94 2.662 6.464 2.662 2.524 0 4.679-.888 6.464-2.662 1.786-1.775 2.679-3.916 2.679-6.424zM71.714 65.98l7.215-27.116c.285-1.23.107-2.378-.536-3.443-.643-1.064-1.56-1.762-2.75-2.094-1.19-.33-2.333-.177-3.429.462-1.095.639-1.81 1.573-2.143 2.804l-7.214 27.116c-2.857.237-5.405 1.266-7.643 3.088-2.238 1.822-3.738 4.152-4.5 6.992-.952 3.644-.476 7.098 1.429 10.364 1.905 3.265 4.69 5.37 8.357 6.317 3.667.947 7.143.474 10.429-1.42 3.285-1.892 5.404-4.66 6.357-8.305.762-2.84.619-5.607-.429-8.305-1.047-2.697-2.762-4.85-5.143-6.46zm47.143-2.342c0-2.508-.893-4.65-2.678-6.424-1.786-1.775-3.94-2.662-6.465-2.662-2.524 0-4.678.887-6.464 2.662-1.786 1.774-2.679 3.916-2.679 6.424 0 2.508.893 4.65 2.679 6.424 1.786 1.775 3.94 2.662 6.464 2.662 2.524 0 4.679-.887 6.465-2.662 1.785-1.775 2.678-3.916 2.678-6.424zm-45.714-45.43c0-2.509-.893-4.65-2.679-6.425C68.68 10.01 66.524 9.122 64 9.122c-2.524 0-4.679.887-6.464 2.661-1.786 1.775-2.679 3.916-2.679 6.425 0 2.508.893 4.65 2.679 6.424 1.785 1.774 3.94 2.662 6.464 2.662 2.524 0 4.679-.888 6.464-2.662 1.786-1.775 2.679-3.916 2.679-6.424zm32 13.629c0-2.508-.893-4.65-2.679-6.424-1.785-1.775-3.94-2.662-6.464-2.662-2.524 0-4.679.887-6.464 2.662-1.786 1.774-2.679 3.916-2.679 6.424 0 2.508.893 4.65 2.679 6.424 1.785 1.774 3.94 2.662 6.464 2.662 2.524 0 4.679-.888 6.464-2.662 1.786-1.775 2.679-3.916 2.679-6.424zM128 63.638c0 12.351-3.357 23.78-10.071 34.286-.905 1.372-2.19 2.058-3.858 2.058H13.93c-1.667 0-2.953-.686-3.858-2.058C3.357 87.465 0 76.037 0 63.638c0-8.613 1.69-16.847 5.071-24.703C8.452 31.08 13 24.312 18.714 18.634c5.715-5.68 12.524-10.199 20.429-13.559C47.048 1.715 55.333.035 64 .035c8.667 0 16.952 1.68 24.857 5.04 7.905 3.36 14.714 7.88 20.429 13.559 5.714 5.678 10.262 12.446 13.643 20.301 3.38 7.856 5.071 16.09 5.071 24.703z"/></svg>
|
After Width: | Height: | Size: 2.3 KiB |
1
Inotify.Vue/src/icons/svg/example.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M96.258 57.462h31.421C124.794 27.323 100.426 2.956 70.287.07v31.422a32.856 32.856 0 0 1 25.971 25.97zm-38.796-25.97V.07C27.323 2.956 2.956 27.323.07 57.462h31.422a32.856 32.856 0 0 1 25.97-25.97zm12.825 64.766v31.421c30.46-2.885 54.507-27.253 57.713-57.712H96.579c-2.886 13.466-13.146 23.726-26.292 26.291zM31.492 70.287H.07c2.886 30.46 27.253 54.507 57.713 57.713V96.579c-13.466-2.886-23.726-13.146-26.291-26.292z"/></svg>
|
After Width: | Height: | Size: 497 B |
1
Inotify.Vue/src/icons/svg/eye-open.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg class="icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="128" height="128"><defs><style/></defs><path d="M512 128q69.675 0 135.51 21.163t115.498 54.997 93.483 74.837 73.685 82.006 51.67 74.837 32.17 54.827L1024 512q-2.347 4.992-6.315 13.483T998.87 560.17t-31.658 51.669-44.331 59.99-56.832 64.34-69.504 60.16-82.347 51.5-94.848 34.687T512 896q-69.675 0-135.51-21.163t-115.498-54.826-93.483-74.326-73.685-81.493-51.67-74.496-32.17-54.997L0 513.707q2.347-4.992 6.315-13.483t18.816-34.816 31.658-51.84 44.331-60.33 56.832-64.683 69.504-60.331 82.347-51.84 94.848-34.816T512 128.085zm0 85.333q-46.677 0-91.648 12.331t-81.152 31.83-70.656 47.146-59.648 54.485-48.853 57.686-37.675 52.821-26.325 43.99q12.33 21.674 26.325 43.52t37.675 52.351 48.853 57.003 59.648 53.845T339.2 767.02t81.152 31.488T512 810.667t91.648-12.331 81.152-31.659 70.656-46.848 59.648-54.186 48.853-57.344 37.675-52.651T927.957 512q-12.33-21.675-26.325-43.648t-37.675-52.65-48.853-57.345-59.648-54.186-70.656-46.848-81.152-31.659T512 213.334zm0 128q70.656 0 120.661 50.006T682.667 512 632.66 632.661 512 682.667 391.339 632.66 341.333 512t50.006-120.661T512 341.333zm0 85.334q-35.328 0-60.33 25.002T426.666 512t25.002 60.33T512 597.334t60.33-25.002T597.334 512t-25.002-60.33T512 426.666z"/></svg>
|
After Width: | Height: | Size: 1.3 KiB |
1
Inotify.Vue/src/icons/svg/eye.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg width="128" height="64" xmlns="http://www.w3.org/2000/svg"><path d="M127.072 7.994c1.37-2.208.914-5.152-.914-6.87-2.056-1.717-4.797-1.226-6.396.982-.229.245-25.586 32.382-55.74 32.382-29.24 0-55.74-32.382-55.968-32.627-1.6-1.963-4.57-2.208-6.397-.49C-.17 3.086-.399 6.275 1.2 8.238c.457.736 5.94 7.36 14.62 14.72L4.17 35.96c-1.828 1.963-1.6 5.152.228 6.87.457.98 1.6 1.471 2.742 1.471s2.284-.49 3.198-1.472l12.564-13.983c5.94 4.416 13.021 8.587 20.788 11.53l-4.797 17.418c-.685 2.699.686 5.397 3.198 6.133h1.37c2.057 0 3.884-1.472 4.341-3.68L52.6 42.83c3.655.736 7.538 1.227 11.422 1.227 3.883 0 7.767-.49 11.422-1.227l4.797 17.173c.457 2.208 2.513 3.68 4.34 3.68.457 0 .914 0 1.143-.246 2.513-.736 3.883-3.434 3.198-6.133l-4.797-17.172c7.767-2.944 14.848-7.114 20.788-11.53l12.336 13.738c.913.981 2.056 1.472 3.198 1.472s2.284-.49 3.198-1.472c1.828-1.963 1.828-4.906.228-6.87l-11.65-13.001c9.366-7.36 14.849-14.474 14.849-14.474z"/></svg>
|
After Width: | Height: | Size: 944 B |
1
Inotify.Vue/src/icons/svg/form.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M84.068 23.784c-1.02 0-1.877-.32-2.572-.96a8.588 8.588 0 0 1-1.738-2.237 11.524 11.524 0 0 1-1.042-2.621c-.232-.895-.348-1.641-.348-2.238V0h.278c.834 0 1.622.085 2.363.256.742.17 1.645.575 2.711 1.214 1.066.64 2.363 1.535 3.892 2.686 1.53 1.15 3.453 2.664 5.77 4.54 2.502 2.045 4.494 3.771 5.977 5.178 1.483 1.406 2.618 2.6 3.406 3.58.787.98 1.274 1.812 1.46 2.494.185.682.277 1.278.277 1.79v2.046H84.068zM127.3 84.01c.278.682.464 1.535.556 2.558.093 1.023-.37 2.003-1.39 2.94-.463.427-.88.832-1.25 1.215-.372.384-.696.704-.974.96a6.69 6.69 0 0 1-.973.767l-11.816-10.741a44.331 44.331 0 0 0 1.877-1.535 31.028 31.028 0 0 1 1.737-1.406c1.112-.938 2.317-1.343 3.615-1.215 1.297.128 2.363.405 3.197.83.927.427 1.923 1.173 2.989 2.239 1.065 1.065 1.876 2.195 2.432 3.388zM78.23 95.902c2.038 0 3.752-.511 5.143-1.534l-26.969 25.83H18.037c-1.761 0-3.684-.47-5.77-1.407a24.549 24.549 0 0 1-5.838-3.709 21.373 21.373 0 0 1-4.518-5.306c-1.204-2.003-1.807-4.07-1.807-6.202V16.495c0-1.79.44-3.665 1.32-5.626A18.41 18.41 0 0 1 5.04 5.562a21.798 21.798 0 0 1 5.213-3.964C12.198.533 14.237 0 16.37 0h53.24v15.984c0 1.62.278 3.367.834 5.242a16.704 16.704 0 0 0 2.572 5.179c1.159 1.577 2.665 2.898 4.518 3.964 1.853 1.066 4.078 1.598 6.673 1.598h20.295v42.325L85.458 92.45c1.02-1.364 1.529-2.856 1.529-4.476 0-2.216-.857-4.113-2.572-5.69-1.714-1.577-3.776-2.366-6.186-2.366H26.1c-2.409 0-4.448.789-6.116 2.366-1.668 1.577-2.502 3.474-2.502 5.69 0 2.217.834 4.092 2.502 5.626 1.668 1.535 3.707 2.302 6.117 2.302h52.13zM26.1 47.951c-2.41 0-4.449.789-6.117 2.366-1.668 1.577-2.502 3.473-2.502 5.69 0 2.216.834 4.092 2.502 5.626 1.668 1.534 3.707 2.302 6.117 2.302h52.13c2.409 0 4.47-.768 6.185-2.302 1.715-1.534 2.572-3.41 2.572-5.626 0-2.217-.857-4.113-2.572-5.69-1.714-1.577-3.776-2.366-6.186-2.366H26.1zm52.407 64.063l1.807-1.663 3.476-3.196a479.75 479.75 0 0 0 4.587-4.284 500.757 500.757 0 0 1 5.004-4.667c3.985-3.666 8.48-7.758 13.485-12.276l11.677 10.741-13.485 12.404-5.004 4.603-4.587 4.22a179.46 179.46 0 0 0-3.267 3.068c-.88.853-1.367 1.322-1.46 1.407-.463.341-.973.703-1.529 1.087-.556.383-1.112.703-1.668.959-.556.256-1.413.575-2.572.959a83.5 83.5 0 0 1-3.545 1.087 72.2 72.2 0 0 1-3.475.895c-1.112.256-1.946.426-2.502.511-1.112.17-1.854.043-2.224-.383-.371-.426-.464-1.151-.278-2.174.092-.511.278-1.279.556-2.302.278-1.023.602-2.067.973-3.132l1.042-3.005c.325-.938.58-1.577.765-1.918a10.157 10.157 0 0 1 2.224-2.941z"/></svg>
|
After Width: | Height: | Size: 2.4 KiB |
1
Inotify.Vue/src/icons/svg/github.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg height="32" viewBox="0 0 16 16" version="1.1" width="32" aria-hidden="true"><path fill-rule="evenodd" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"></path></svg>
|
After Width: | Height: | Size: 694 B |
1
Inotify.Vue/src/icons/svg/link.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M115.625 127.937H.063V12.375h57.781v12.374H12.438v90.813h90.813V70.156h12.374z"/><path d="M116.426 2.821l8.753 8.753-56.734 56.734-8.753-8.745z"/><path d="M127.893 37.982h-12.375V12.375H88.706V0h39.187z"/></svg>
|
After Width: | Height: | Size: 285 B |
1
Inotify.Vue/src/icons/svg/nested.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M.002 9.2c0 5.044 3.58 9.133 7.998 9.133 4.417 0 7.997-4.089 7.997-9.133 0-5.043-3.58-9.132-7.997-9.132S.002 4.157.002 9.2zM31.997.066h95.981V18.33H31.997V.066zm0 45.669c0 5.044 3.58 9.132 7.998 9.132 4.417 0 7.997-4.088 7.997-9.132 0-3.263-1.524-6.278-3.998-7.91-2.475-1.63-5.524-1.63-7.998 0-2.475 1.632-4 4.647-4 7.91zM63.992 36.6h63.986v18.265H63.992V36.6zm-31.995 82.2c0 5.043 3.58 9.132 7.998 9.132 4.417 0 7.997-4.089 7.997-9.132 0-5.044-3.58-9.133-7.997-9.133s-7.998 4.089-7.998 9.133zm31.995-9.131h63.986v18.265H63.992V109.67zm0-27.404c0 5.044 3.58 9.133 7.998 9.133 4.417 0 7.997-4.089 7.997-9.133 0-3.263-1.524-6.277-3.998-7.909-2.475-1.631-5.524-1.631-7.998 0-2.475 1.632-4 4.646-4 7.91zm31.995-9.13h31.991V91.4H95.987V73.135z"/></svg>
|
After Width: | Height: | Size: 821 B |
1
Inotify.Vue/src/icons/svg/password.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M108.8 44.322H89.6v-5.36c0-9.04-3.308-24.163-25.6-24.163-23.145 0-25.6 16.881-25.6 24.162v5.361H19.2v-5.36C19.2 15.281 36.798 0 64 0c27.202 0 44.8 15.281 44.8 38.961v5.361zm-32 39.356c0-5.44-5.763-9.832-12.8-9.832-7.037 0-12.8 4.392-12.8 9.832 0 3.682 2.567 6.808 6.407 8.477v11.205c0 2.718 2.875 4.962 6.4 4.962 3.524 0 6.4-2.244 6.4-4.962V92.155c3.833-1.669 6.393-4.795 6.393-8.477zM128 64v49.201c0 8.158-8.645 14.799-19.2 14.799H19.2C8.651 128 0 121.359 0 113.201V64c0-8.153 8.645-14.799 19.2-14.799h89.6c10.555 0 19.2 6.646 19.2 14.799z"/></svg>
|
After Width: | Height: | Size: 623 B |
1
Inotify.Vue/src/icons/svg/table.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M.006.064h127.988v31.104H.006V.064zm0 38.016h38.396v41.472H.006V38.08zm0 48.384h38.396v41.472H.006V86.464zM44.802 38.08h38.396v41.472H44.802V38.08zm0 48.384h38.396v41.472H44.802V86.464zM89.598 38.08h38.396v41.472H89.598zm0 48.384h38.396v41.472H89.598z"/><path d="M.006.064h127.988v31.104H.006V.064zm0 38.016h38.396v41.472H.006V38.08zm0 48.384h38.396v41.472H.006V86.464zM44.802 38.08h38.396v41.472H44.802V38.08zm0 48.384h38.396v41.472H44.802V86.464zM89.598 38.08h38.396v41.472H89.598zm0 48.384h38.396v41.472H89.598z"/></svg>
|
After Width: | Height: | Size: 597 B |
1
Inotify.Vue/src/icons/svg/tree.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M126.713 90.023c.858.985 1.287 2.134 1.287 3.447v29.553c0 1.423-.429 2.6-1.287 3.53-.858.93-1.907 1.395-3.146 1.395H97.824c-1.145 0-2.146-.465-3.004-1.395-.858-.93-1.287-2.107-1.287-3.53V93.47c0-.875.19-1.696.572-2.462.382-.766.906-1.368 1.573-1.806a3.84 3.84 0 0 1 2.146-.657h9.725V69.007a3.84 3.84 0 0 0-.43-1.806 3.569 3.569 0 0 0-1.143-1.313 2.714 2.714 0 0 0-1.573-.492h-36.47v23.149h9.725c1.144 0 2.145.492 3.004 1.478.858.985 1.287 2.134 1.287 3.447v29.553c0 .876-.191 1.696-.573 2.463-.38.766-.905 1.368-1.573 1.806a3.84 3.84 0 0 1-2.145.656H51.915a3.84 3.84 0 0 1-2.145-.656c-.668-.438-1.216-1.04-1.645-1.806a4.96 4.96 0 0 1-.644-2.463V93.47c0-1.313.43-2.462 1.288-3.447.858-.986 1.907-1.478 3.146-1.478h9.582v-23.15h-37.9c-.953 0-1.74.356-2.359 1.068-.62.711-.93 1.56-.93 2.544v19.538h9.726c1.239 0 2.264.492 3.074 1.478.81.985 1.216 2.134 1.216 3.447v29.553c0 1.423-.405 2.6-1.216 3.53-.81.93-1.835 1.395-3.074 1.395H4.29c-.476 0-.93-.082-1.358-.246a4.1 4.1 0 0 1-1.144-.657 4.658 4.658 0 0 1-.93-1.067 5.186 5.186 0 0 1-.643-1.395 5.566 5.566 0 0 1-.215-1.56V93.47c0-.437.048-.875.143-1.313a3.95 3.95 0 0 1 .429-1.15c.19-.328.429-.656.715-.984.286-.329.572-.602.858-.821.286-.22.62-.383 1.001-.493.382-.11.763-.164 1.144-.164h9.726V61.619c0-.985.31-1.833.93-2.544.619-.712 1.358-1.068 2.216-1.068h44.335V39.62h-9.582c-1.24 0-2.288-.492-3.146-1.477a5.09 5.09 0 0 1-1.287-3.448V5.14c0-1.423.429-2.627 1.287-3.612.858-.985 1.907-1.477 3.146-1.477h25.743c.763 0 1.478.246 2.145.739a5.17 5.17 0 0 1 1.573 1.888c.382.766.573 1.587.573 2.462v29.553c0 1.313-.43 2.463-1.287 3.448-.859.985-1.86 1.477-3.004 1.477h-9.725v18.389h42.762c.954 0 1.74.355 2.36 1.067.62.711.93 1.56.93 2.545v26.925h9.582c1.239 0 2.288.492 3.146 1.478z"/></svg>
|
After Width: | Height: | Size: 1.8 KiB |
1
Inotify.Vue/src/icons/svg/user.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg width="130" height="130" xmlns="http://www.w3.org/2000/svg"><path d="M63.444 64.996c20.633 0 37.359-14.308 37.359-31.953 0-17.649-16.726-31.952-37.359-31.952-20.631 0-37.36 14.303-37.358 31.952 0 17.645 16.727 31.953 37.359 31.953zM80.57 75.65H49.434c-26.652 0-48.26 18.477-48.26 41.27v2.664c0 9.316 21.608 9.325 48.26 9.325H80.57c26.649 0 48.256-.344 48.256-9.325v-2.663c0-22.794-21.605-41.271-48.256-41.271z" stroke="#979797"/></svg>
|
After Width: | Height: | Size: 440 B |
22
Inotify.Vue/src/icons/svgo.yml
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
# replace default config
|
||||||
|
|
||||||
|
# multipass: true
|
||||||
|
# full: true
|
||||||
|
|
||||||
|
plugins:
|
||||||
|
|
||||||
|
# - name
|
||||||
|
#
|
||||||
|
# or:
|
||||||
|
# - name: false
|
||||||
|
# - name: true
|
||||||
|
#
|
||||||
|
# or:
|
||||||
|
# - name:
|
||||||
|
# param1: 1
|
||||||
|
# param2: 2
|
||||||
|
|
||||||
|
- removeAttrs:
|
||||||
|
attrs:
|
||||||
|
- 'fill'
|
||||||
|
- 'fill-rule'
|
40
Inotify.Vue/src/layout/components/AppMain.vue
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
<template>
|
||||||
|
<section class="app-main">
|
||||||
|
<transition name="fade-transform" mode="out-in">
|
||||||
|
<router-view :key="key" />
|
||||||
|
</transition>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'AppMain',
|
||||||
|
computed: {
|
||||||
|
key() {
|
||||||
|
return this.$route.path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.app-main {
|
||||||
|
/*50 = navbar */
|
||||||
|
min-height: calc(100vh - 50px);
|
||||||
|
width: 100%;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.fixed-header+.app-main {
|
||||||
|
padding-top: 50px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
// fix css style bug in open el-dialog
|
||||||
|
.el-popup-parent--hidden {
|
||||||
|
.fixed-header {
|
||||||
|
padding-right: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
131
Inotify.Vue/src/layout/components/Navbar.vue
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
<template>
|
||||||
|
<div class="navbar">
|
||||||
|
<hamburger :is-active="sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" />
|
||||||
|
|
||||||
|
<breadcrumb class="breadcrumb-container" />
|
||||||
|
|
||||||
|
<div class="right-menu">
|
||||||
|
<el-dropdown class="avatar-container" trigger="click">
|
||||||
|
<div class="avatar-wrapper">
|
||||||
|
<img :src="avatar+'?imageView2/1/w/80/h/80'" class="user-avatar">
|
||||||
|
<i class="el-icon-caret-bottom" />
|
||||||
|
</div>
|
||||||
|
<el-dropdown-menu slot="dropdown" class="user-dropdown">
|
||||||
|
<!-- <a target="_blank" href="https://github.com/PanJiaChen/vue-admin-template/">
|
||||||
|
<el-dropdown-item>Github</el-dropdown-item>
|
||||||
|
</a> -->
|
||||||
|
<el-dropdown-item divided @click.native="logout">
|
||||||
|
<span style="display:block;">退出</span>
|
||||||
|
</el-dropdown-item>
|
||||||
|
</el-dropdown-menu>
|
||||||
|
</el-dropdown>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapGetters } from 'vuex'
|
||||||
|
import Breadcrumb from '@/components/Breadcrumb'
|
||||||
|
import Hamburger from '@/components/Hamburger'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
Breadcrumb,
|
||||||
|
Hamburger
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapGetters([
|
||||||
|
'sidebar',
|
||||||
|
'avatar'
|
||||||
|
])
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
toggleSideBar() {
|
||||||
|
this.$store.dispatch('app/toggleSideBar')
|
||||||
|
},
|
||||||
|
async logout() {
|
||||||
|
await this.$store.dispatch('user/logout')
|
||||||
|
this.$router.push(`/login?redirect=${this.$route.fullPath}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.navbar {
|
||||||
|
height: 50px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: 0 1px 4px rgba(0,21,41,.08);
|
||||||
|
|
||||||
|
.hamburger-container {
|
||||||
|
line-height: 46px;
|
||||||
|
height: 100%;
|
||||||
|
float: left;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background .3s;
|
||||||
|
-webkit-tap-highlight-color:transparent;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: rgba(0, 0, 0, .025)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.breadcrumb-container {
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-menu {
|
||||||
|
float: right;
|
||||||
|
height: 100%;
|
||||||
|
line-height: 50px;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-menu-item {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 8px;
|
||||||
|
height: 100%;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #5a5e66;
|
||||||
|
vertical-align: text-bottom;
|
||||||
|
|
||||||
|
&.hover-effect {
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background .3s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: rgba(0, 0, 0, .025)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-container {
|
||||||
|
margin-right: 30px;
|
||||||
|
|
||||||
|
.avatar-wrapper {
|
||||||
|
margin-top: 5px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.user-avatar {
|
||||||
|
cursor: pointer;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-icon-caret-bottom {
|
||||||
|
cursor: pointer;
|
||||||
|
position: absolute;
|
||||||
|
right: -20px;
|
||||||
|
top: 25px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
26
Inotify.Vue/src/layout/components/Sidebar/FixiOSBug.js
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
export default {
|
||||||
|
computed: {
|
||||||
|
device() {
|
||||||
|
return this.$store.state.app.device
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
// In order to fix the click on menu on the ios device will trigger the mouseleave bug
|
||||||
|
// https://github.com/PanJiaChen/vue-element-admin/issues/1135
|
||||||
|
this.fixBugIniOS()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
fixBugIniOS() {
|
||||||
|
const $subMenu = this.$refs.subMenu
|
||||||
|
if ($subMenu) {
|
||||||
|
const handleMouseleave = $subMenu.handleMouseleave
|
||||||
|
$subMenu.handleMouseleave = (e) => {
|
||||||
|
if (this.device === 'mobile') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
handleMouseleave(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
41
Inotify.Vue/src/layout/components/Sidebar/Item.vue
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'MenuItem',
|
||||||
|
functional: true,
|
||||||
|
props: {
|
||||||
|
icon: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
render(h, context) {
|
||||||
|
const { icon, title } = context.props
|
||||||
|
const vnodes = []
|
||||||
|
|
||||||
|
if (icon) {
|
||||||
|
if (icon.includes('el-icon')) {
|
||||||
|
vnodes.push(<i class={[icon, 'sub-el-icon']} />)
|
||||||
|
} else {
|
||||||
|
vnodes.push(<svg-icon icon-class={icon}/>)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (title) {
|
||||||
|
vnodes.push(<span slot='title'>{(title)}</span>)
|
||||||
|
}
|
||||||
|
return vnodes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.sub-el-icon {
|
||||||
|
color: currentColor;
|
||||||
|
width: 1em;
|
||||||
|
height: 1em;
|
||||||
|
}
|
||||||
|
</style>
|
43
Inotify.Vue/src/layout/components/Sidebar/Link.vue
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
<template>
|
||||||
|
<component :is="type" v-bind="linkProps(to)">
|
||||||
|
<slot />
|
||||||
|
</component>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { isExternal } from '@/utils/validate'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
to: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
isExternal() {
|
||||||
|
return isExternal(this.to)
|
||||||
|
},
|
||||||
|
type() {
|
||||||
|
if (this.isExternal) {
|
||||||
|
return 'a'
|
||||||
|
}
|
||||||
|
return 'router-link'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
linkProps(to) {
|
||||||
|
if (this.isExternal) {
|
||||||
|
return {
|
||||||
|
href: to,
|
||||||
|
target: '_blank',
|
||||||
|
rel: 'noopener'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
to: to
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
82
Inotify.Vue/src/layout/components/Sidebar/Logo.vue
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
<template>
|
||||||
|
<div class="sidebar-logo-container" :class="{'collapse':collapse}">
|
||||||
|
<transition name="sidebarLogoFade">
|
||||||
|
<router-link v-if="collapse" key="collapse" class="sidebar-logo-link" to="/">
|
||||||
|
<img v-if="logo" :src="logo" class="sidebar-logo">
|
||||||
|
<h1 v-else class="sidebar-title">{{ title }} </h1>
|
||||||
|
</router-link>
|
||||||
|
<router-link v-else key="expand" class="sidebar-logo-link" to="/">
|
||||||
|
<img v-if="logo" :src="logo" class="sidebar-logo">
|
||||||
|
<h1 class="sidebar-title">{{ title }} </h1>
|
||||||
|
</router-link>
|
||||||
|
</transition>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'SidebarLogo',
|
||||||
|
props: {
|
||||||
|
collapse: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
title: 'Vue Admin Template',
|
||||||
|
logo: 'https://wpimg.wallstcn.com/69a1c46c-eb1c-4b46-8bd4-e9e686ef5251.png'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.sidebarLogoFade-enter-active {
|
||||||
|
transition: opacity 1.5s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebarLogoFade-enter,
|
||||||
|
.sidebarLogoFade-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-logo-container {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 50px;
|
||||||
|
line-height: 50px;
|
||||||
|
background: #2b2f3a;
|
||||||
|
text-align: center;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
& .sidebar-logo-link {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
& .sidebar-logo {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
vertical-align: middle;
|
||||||
|
margin-right: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
& .sidebar-title {
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0;
|
||||||
|
color: #fff;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 50px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-family: Avenir, Helvetica Neue, Arial, Helvetica, sans-serif;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.collapse {
|
||||||
|
.sidebar-logo {
|
||||||
|
margin-right: 0px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
107
Inotify.Vue/src/layout/components/Sidebar/SidebarItem.vue
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
<template>
|
||||||
|
<div v-if="!item.hidden">
|
||||||
|
<template v-if="hasOneShowingChild(item.children, item) && (!onlyOneChild.children || onlyOneChild.noShowingChildren) && !item.alwaysShow">
|
||||||
|
<app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path)">
|
||||||
|
<el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{ 'submenu-title-noDropdown': !isNest }">
|
||||||
|
<item :icon="onlyOneChild.meta.icon || (item.meta && item.meta.icon)" :title="onlyOneChild.meta.title" />
|
||||||
|
</el-menu-item>
|
||||||
|
</app-link>
|
||||||
|
</template>
|
||||||
|
<el-submenu v-else ref="subMenu" :index="resolvePath(item.path)" popper-append-to-body>
|
||||||
|
<template slot="title">
|
||||||
|
<item v-if="item.meta" :icon="item.meta && item.meta.icon" :title="item.meta.title" />
|
||||||
|
</template>
|
||||||
|
<sidebar-item v-for="child in item.children" :key="child.path" :is-nest="true" :item="child" :base-path="resolvePath(child.path)" class="nest-menu" />
|
||||||
|
</el-submenu>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import store from '@/store'
|
||||||
|
import path from 'path'
|
||||||
|
import {
|
||||||
|
isExternal
|
||||||
|
} from '@/utils/validate'
|
||||||
|
import Item from './Item'
|
||||||
|
import AppLink from './Link'
|
||||||
|
import FixiOSBug from './FixiOSBug'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'SidebarItem',
|
||||||
|
components: {
|
||||||
|
Item,
|
||||||
|
AppLink,
|
||||||
|
},
|
||||||
|
mixins: [FixiOSBug],
|
||||||
|
props: {
|
||||||
|
// route object
|
||||||
|
item: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
isNest: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
basePath: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
// To fix https://github.com/PanJiaChen/vue-admin-template/issues/237
|
||||||
|
// TODO: refactor with render function
|
||||||
|
|
||||||
|
this.onlyOneChild = null
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
const role = store.getters.role
|
||||||
|
if (this.item && this.item.meta && this.item.meta.roles && !this.item.meta.roles.includes(role)) {
|
||||||
|
this.item.hidden = true
|
||||||
|
} else {
|
||||||
|
this.item.hidden = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
hasOneShowingChild(children = [], parent) {
|
||||||
|
const showingChildren = children.filter((item) => {
|
||||||
|
const role = store.getters.role
|
||||||
|
if (item.hidden || (item.meta.roles && !item.meta.roles.includes(role))) {
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
// Temp set(will be used if only has one showing child)
|
||||||
|
this.onlyOneChild = item
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// When there is only one child router, the child router is displayed by default
|
||||||
|
if (showingChildren.length === 1) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show parent if there are no child router to display
|
||||||
|
if (showingChildren.length === 0) {
|
||||||
|
this.onlyOneChild = {
|
||||||
|
...parent,
|
||||||
|
path: '',
|
||||||
|
noShowingChildren: true,
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
resolvePath(routePath) {
|
||||||
|
if (isExternal(routePath)) {
|
||||||
|
return routePath
|
||||||
|
}
|
||||||
|
if (isExternal(this.basePath)) {
|
||||||
|
return this.basePath
|
||||||
|
}
|
||||||
|
return path.resolve(this.basePath, routePath)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
56
Inotify.Vue/src/layout/components/Sidebar/index.vue
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
<template>
|
||||||
|
<div :class="{'has-logo':showLogo}">
|
||||||
|
<logo v-if="showLogo" :collapse="isCollapse" />
|
||||||
|
<el-scrollbar wrap-class="scrollbar-wrapper">
|
||||||
|
<el-menu
|
||||||
|
:default-active="activeMenu"
|
||||||
|
:collapse="isCollapse"
|
||||||
|
:background-color="variables.menuBg"
|
||||||
|
:text-color="variables.menuText"
|
||||||
|
:unique-opened="false"
|
||||||
|
:active-text-color="variables.menuActiveText"
|
||||||
|
:collapse-transition="false"
|
||||||
|
mode="vertical"
|
||||||
|
>
|
||||||
|
<sidebar-item v-for="route in routes" :key="route.path" :item="route" :base-path="route.path" />
|
||||||
|
</el-menu>
|
||||||
|
</el-scrollbar>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapGetters } from 'vuex'
|
||||||
|
import Logo from './Logo'
|
||||||
|
import SidebarItem from './SidebarItem'
|
||||||
|
import variables from '@/styles/variables.scss'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: { SidebarItem, Logo },
|
||||||
|
computed: {
|
||||||
|
...mapGetters([
|
||||||
|
'sidebar'
|
||||||
|
]),
|
||||||
|
routes() {
|
||||||
|
return this.$router.options.routes
|
||||||
|
},
|
||||||
|
activeMenu() {
|
||||||
|
const route = this.$route
|
||||||
|
const { meta, path } = route
|
||||||
|
// if set path, the sidebar will highlight the path you set
|
||||||
|
if (meta.activeMenu) {
|
||||||
|
return meta.activeMenu
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
},
|
||||||
|
showLogo() {
|
||||||
|
return this.$store.state.settings.sidebarLogo
|
||||||
|
},
|
||||||
|
variables() {
|
||||||
|
return variables
|
||||||
|
},
|
||||||
|
isCollapse() {
|
||||||
|
return !this.sidebar.opened
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
3
Inotify.Vue/src/layout/components/index.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export { default as Navbar } from './Navbar'
|
||||||
|
export { default as Sidebar } from './Sidebar'
|
||||||
|
export { default as AppMain } from './AppMain'
|
93
Inotify.Vue/src/layout/index.vue
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
<template>
|
||||||
|
<div :class="classObj" class="app-wrapper">
|
||||||
|
<div v-if="device==='mobile'&&sidebar.opened" class="drawer-bg" @click="handleClickOutside" />
|
||||||
|
<sidebar class="sidebar-container" />
|
||||||
|
<div class="main-container">
|
||||||
|
<div :class="{'fixed-header':fixedHeader}">
|
||||||
|
<navbar />
|
||||||
|
</div>
|
||||||
|
<app-main />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { Navbar, Sidebar, AppMain } from './components'
|
||||||
|
import ResizeMixin from './mixin/ResizeHandler'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'Layout',
|
||||||
|
components: {
|
||||||
|
Navbar,
|
||||||
|
Sidebar,
|
||||||
|
AppMain
|
||||||
|
},
|
||||||
|
mixins: [ResizeMixin],
|
||||||
|
computed: {
|
||||||
|
sidebar() {
|
||||||
|
return this.$store.state.app.sidebar
|
||||||
|
},
|
||||||
|
device() {
|
||||||
|
return this.$store.state.app.device
|
||||||
|
},
|
||||||
|
fixedHeader() {
|
||||||
|
return this.$store.state.settings.fixedHeader
|
||||||
|
},
|
||||||
|
classObj() {
|
||||||
|
return {
|
||||||
|
hideSidebar: !this.sidebar.opened,
|
||||||
|
openSidebar: this.sidebar.opened,
|
||||||
|
withoutAnimation: this.sidebar.withoutAnimation,
|
||||||
|
mobile: this.device === 'mobile'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleClickOutside() {
|
||||||
|
this.$store.dispatch('app/closeSideBar', { withoutAnimation: false })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import "~@/styles/mixin.scss";
|
||||||
|
@import "~@/styles/variables.scss";
|
||||||
|
|
||||||
|
.app-wrapper {
|
||||||
|
@include clearfix;
|
||||||
|
position: relative;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
&.mobile.openSidebar{
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.drawer-bg {
|
||||||
|
background: #000;
|
||||||
|
opacity: 0.3;
|
||||||
|
width: 100%;
|
||||||
|
top: 0;
|
||||||
|
height: 100%;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fixed-header {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 9;
|
||||||
|
width: calc(100% - #{$sideBarWidth});
|
||||||
|
transition: width 0.28s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hideSidebar .fixed-header {
|
||||||
|
width: calc(100% - 54px)
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile .fixed-header {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
45
Inotify.Vue/src/layout/mixin/ResizeHandler.js
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import store from '@/store'
|
||||||
|
|
||||||
|
const { body } = document
|
||||||
|
const WIDTH = 992 // refer to Bootstrap's responsive design
|
||||||
|
|
||||||
|
export default {
|
||||||
|
watch: {
|
||||||
|
$route(route) {
|
||||||
|
if (this.device === 'mobile' && this.sidebar.opened) {
|
||||||
|
store.dispatch('app/closeSideBar', { withoutAnimation: false })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
beforeMount() {
|
||||||
|
window.addEventListener('resize', this.$_resizeHandler)
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
window.removeEventListener('resize', this.$_resizeHandler)
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
const isMobile = this.$_isMobile()
|
||||||
|
if (isMobile) {
|
||||||
|
store.dispatch('app/toggleDevice', 'mobile')
|
||||||
|
store.dispatch('app/closeSideBar', { withoutAnimation: true })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
// use $_ for mixins properties
|
||||||
|
// https://vuejs.org/v2/style-guide/index.html#Private-property-names-essential
|
||||||
|
$_isMobile() {
|
||||||
|
const rect = body.getBoundingClientRect()
|
||||||
|
return rect.width - 1 < WIDTH
|
||||||
|
},
|
||||||
|
$_resizeHandler() {
|
||||||
|
if (!document.hidden) {
|
||||||
|
const isMobile = this.$_isMobile()
|
||||||
|
store.dispatch('app/toggleDevice', isMobile ? 'mobile' : 'desktop')
|
||||||
|
|
||||||
|
if (isMobile) {
|
||||||
|
store.dispatch('app/closeSideBar', { withoutAnimation: true })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
42
Inotify.Vue/src/main.js
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import Vue from 'vue'
|
||||||
|
import 'normalize.css/normalize.css' // A modern alternative to CSS resets
|
||||||
|
import ElementUI from 'element-ui'
|
||||||
|
|
||||||
|
import 'element-ui/lib/theme-chalk/index.css'
|
||||||
|
import locale from 'element-ui/lib/locale/lang/zh-CN' // lang i18n
|
||||||
|
|
||||||
|
import '@/styles/index.scss' // global css
|
||||||
|
|
||||||
|
import App from './App'
|
||||||
|
import store from './store'
|
||||||
|
import router from './router'
|
||||||
|
|
||||||
|
import '@/icons' // icon
|
||||||
|
import '@/permission' // permission control
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If you don't want to use mock-server
|
||||||
|
* you want to use MockJs for mock api
|
||||||
|
* you can execute: mockXHR()
|
||||||
|
*
|
||||||
|
* Currently MockJs will be used in the production environment,
|
||||||
|
* please remove it before going online ! ! !
|
||||||
|
*/
|
||||||
|
// if (process.env.NODE_ENV === 'production') {
|
||||||
|
// const { mockXHR } = require('../mock')
|
||||||
|
// mockXHR()
|
||||||
|
// }
|
||||||
|
|
||||||
|
// set ElementUI lang to EN
|
||||||
|
Vue.use(ElementUI, { locale, size: 'small' })
|
||||||
|
// 如果想要中文版 element-ui,按如下方式声明
|
||||||
|
// Vue.use(ElementUI)
|
||||||
|
|
||||||
|
Vue.config.productionTip = false
|
||||||
|
|
||||||
|
new Vue({
|
||||||
|
el: '#app',
|
||||||
|
router,
|
||||||
|
store,
|
||||||
|
render: h => h(App)
|
||||||
|
})
|
69
Inotify.Vue/src/permission.js
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
import router from './router'
|
||||||
|
import store from './store'
|
||||||
|
import { Message } from 'element-ui'
|
||||||
|
import NProgress from 'nprogress' // progress bar
|
||||||
|
import 'nprogress/nprogress.css' // progress bar style
|
||||||
|
import { getToken } from '@/utils/auth' // get token from cookie
|
||||||
|
import getPageTitle from '@/utils/get-page-title'
|
||||||
|
|
||||||
|
NProgress.configure({ showSpinner: false })
|
||||||
|
// NProgress Configuration
|
||||||
|
const whiteList = ['/login']
|
||||||
|
|
||||||
|
// no redirect whitelist
|
||||||
|
router.beforeEach(async (to, from, next) => {
|
||||||
|
// start progress bar
|
||||||
|
NProgress.start()
|
||||||
|
|
||||||
|
// set page title
|
||||||
|
document.title = getPageTitle(to.meta.title)
|
||||||
|
|
||||||
|
// determine whether the user has logged in
|
||||||
|
const hasToken = getToken()
|
||||||
|
if (hasToken) {
|
||||||
|
if (to.path === '/login') {
|
||||||
|
// if is logged in, redirect to the home page
|
||||||
|
next({ path: '/' })
|
||||||
|
NProgress.done()
|
||||||
|
} else {
|
||||||
|
const hasGetUserInfo = store.getters.name
|
||||||
|
const role = store.getters.role
|
||||||
|
if (hasGetUserInfo) {
|
||||||
|
if (!to.meta.roles || to.meta.roles.includes(role)) {
|
||||||
|
next()
|
||||||
|
} else {
|
||||||
|
Message.error('未授权访问')
|
||||||
|
NProgress.done()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
// get user info
|
||||||
|
await store.dispatch('user/getInfo')
|
||||||
|
next()
|
||||||
|
} catch (error) {
|
||||||
|
// remove token and go to login page to re-login
|
||||||
|
await store.dispatch('user/resetToken')
|
||||||
|
Message.error(error || 'Has Error')
|
||||||
|
next(`/login?redirect=${to.path}`)
|
||||||
|
NProgress.done()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/* has no token*/
|
||||||
|
|
||||||
|
if (whiteList.indexOf(to.path) !== -1) {
|
||||||
|
// in the free login whitelist, go directly
|
||||||
|
next()
|
||||||
|
} else {
|
||||||
|
// other pages that do not have permission to access are redirected to the login page.
|
||||||
|
next(`/login?redirect=${to.path}`)
|
||||||
|
NProgress.done()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
router.afterEach(() => {
|
||||||
|
// finish progress bar
|
||||||
|
NProgress.done()
|
||||||
|
})
|
154
Inotify.Vue/src/router/index.js
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
import Vue from 'vue'
|
||||||
|
import Router from 'vue-router'
|
||||||
|
|
||||||
|
Vue.use(Router)
|
||||||
|
|
||||||
|
/* Layout */
|
||||||
|
import Layout from '@/layout'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Note: sub-menu only appear when route children.length >= 1
|
||||||
|
* Detail see: https://panjiachen.github.io/vue-element-admin-site/guide/essentials/router-and-nav.html
|
||||||
|
*
|
||||||
|
* hidden: true if set true, item will not show in the sidebar(default is false)
|
||||||
|
* alwaysShow: true if set true, will always show the root menu
|
||||||
|
* if not set alwaysShow, when item has more than one children route,
|
||||||
|
* it will becomes nested mode, otherwise not show the root menu
|
||||||
|
* redirect: noRedirect if set noRedirect will no redirect in the breadcrumb
|
||||||
|
* name:'router-name' the name is used by <keep-alive> (must set!!!)
|
||||||
|
* meta : {
|
||||||
|
roles: ['admin','editor'] control the page roles (you can set multiple roles)
|
||||||
|
title: 'title' the name show in sidebar and breadcrumb (recommend set)
|
||||||
|
icon: 'svg-name'/'el-icon-x' the icon show in the sidebar
|
||||||
|
breadcrumb: false if set false, the item will hidden in breadcrumb(default is true)
|
||||||
|
activeMenu: '/example/list' if set path, the sidebar will highlight the path you set
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* constantRoutes
|
||||||
|
* a base page that does not have permission requirements
|
||||||
|
* all roles can be accessed
|
||||||
|
*/
|
||||||
|
export const constantRoutes = [
|
||||||
|
{
|
||||||
|
path: '/login',
|
||||||
|
component: () => import('@/views/login/index'),
|
||||||
|
hidden: true,
|
||||||
|
props: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/404',
|
||||||
|
component: () => import('@/views/404'),
|
||||||
|
hidden: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
component: Layout,
|
||||||
|
redirect: '/manager',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: 'manager',
|
||||||
|
name: 'Manager',
|
||||||
|
component: () => import('@/views/manager/settingpro/sendkey/index'),
|
||||||
|
meta: { roles: ['user', 'system'], title: '后台管理', icon: 'el-icon-s-home' }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/settingpro',
|
||||||
|
component: Layout,
|
||||||
|
redirect: '/settingpro/sendmethods',
|
||||||
|
name: 'settingpro',
|
||||||
|
meta: { roles: ['user', 'system'], title: '个人设置', icon: 'el-icon-user' },
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: 'sendkey',
|
||||||
|
name: 'sendkey',
|
||||||
|
component: () => import('@/views/manager/settingpro/sendkey/index'),
|
||||||
|
meta: { roles: ['user', 'system'], title: '消息验证', icon: 'el-icon-s-comment' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'sendmethods',
|
||||||
|
name: 'sendmethods',
|
||||||
|
component: () => import('@/views/manager/settingpro/sendauths/index'),
|
||||||
|
meta: { roles: ['user', 'system'], title: '消息通道', icon: 'el-icon-s-promotion' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'oauthsetting',
|
||||||
|
name: 'oauthsetting',
|
||||||
|
component: () => import('@/views/manager/settingpro/oauthsetting/index'),
|
||||||
|
meta: { roles: ['user', 'system'], title: '重置密码', icon: 'el-icon-s-custom' }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/settingsys',
|
||||||
|
component: Layout,
|
||||||
|
redirect: '/settingsys/systeminfo',
|
||||||
|
name: 'settingsys',
|
||||||
|
meta: { roles: ['system'], title: '系统设置', icon: 'el-icon-setting' },
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: 'systeminfo',
|
||||||
|
name: 'systeminfo',
|
||||||
|
component: () => import('@/views/manager/settingsys/systeminfo/index'),
|
||||||
|
meta: { roles: ['system'], title: '系统状态', icon: 'el-icon-s-data' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'systemusers',
|
||||||
|
name: 'systemusers',
|
||||||
|
component: () => import('@/views/manager/settingsys/usermanager/index'),
|
||||||
|
meta: { roles: ['system'], title: '用户管理', icon: 'el-icon-s-check' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'systemjwt',
|
||||||
|
name: 'systemjwt',
|
||||||
|
component: () => import('@/views/manager/settingsys/jwt/index'),
|
||||||
|
meta: { roles: ['system'], title: 'JWT验证', icon: 'el-icon-s-platform' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'systemglobal',
|
||||||
|
name: 'systemglobal',
|
||||||
|
component: () => import('@/views/manager/settingsys/systemglobal/index'),
|
||||||
|
meta: { roles: ['system'], title: '全局参数', icon: 'el-icon-s-tools' }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'external-link',
|
||||||
|
component: Layout,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: 'https://github.com/xpnas/Inotify',
|
||||||
|
meta: { title: '源码', icon: 'el-icon-share' }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
// 404 page must be placed at the end !!!
|
||||||
|
{ path: '*', redirect: '/404', hidden: true }
|
||||||
|
]
|
||||||
|
|
||||||
|
const createRouter = () =>
|
||||||
|
new Router({
|
||||||
|
// mode: 'history', // require service support
|
||||||
|
scrollBehavior: () => ({ y: 0 }),
|
||||||
|
routes: constantRoutes
|
||||||
|
})
|
||||||
|
|
||||||
|
const router = createRouter()
|
||||||
|
|
||||||
|
// Detail see: https://github.com/vuejs/vue-router/issues/1234#issuecomment-357941465
|
||||||
|
export function resetRouter() {
|
||||||
|
const newRouter = createRouter()
|
||||||
|
router.matcher = newRouter.matcher // reset router
|
||||||
|
router.push({
|
||||||
|
name: '/login',
|
||||||
|
params: {
|
||||||
|
token: ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export default router
|
17
Inotify.Vue/src/settings.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
module.exports = {
|
||||||
|
|
||||||
|
title: 'Inotify',
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {boolean} true | false
|
||||||
|
* @description Whether fix the header
|
||||||
|
*/
|
||||||
|
fixedHeader: false,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {boolean} true | false
|
||||||
|
* @description Whether show the logo in sidebar
|
||||||
|
*/
|
||||||
|
sidebarLogo: false
|
||||||
|
}
|
||||||
|
|
9
Inotify.Vue/src/store/getters.js
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
const getters = {
|
||||||
|
sidebar: state => state.app.sidebar,
|
||||||
|
device: state => state.app.device,
|
||||||
|
token: state => state.user.token,
|
||||||
|
avatar: state => state.user.avatar,
|
||||||
|
name: state => state.user.name,
|
||||||
|
role: state => state.user.role
|
||||||
|
}
|
||||||
|
export default getters
|
19
Inotify.Vue/src/store/index.js
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import Vue from 'vue'
|
||||||
|
import Vuex from 'vuex'
|
||||||
|
import getters from './getters'
|
||||||
|
import app from './modules/app'
|
||||||
|
import settings from './modules/settings'
|
||||||
|
import user from './modules/user'
|
||||||
|
|
||||||
|
Vue.use(Vuex)
|
||||||
|
|
||||||
|
const store = new Vuex.Store({
|
||||||
|
modules: {
|
||||||
|
app,
|
||||||
|
settings,
|
||||||
|
user
|
||||||
|
},
|
||||||
|
getters
|
||||||
|
})
|
||||||
|
|
||||||
|
export default store
|
48
Inotify.Vue/src/store/modules/app.js
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import Cookies from 'js-cookie'
|
||||||
|
|
||||||
|
const state = {
|
||||||
|
sidebar: {
|
||||||
|
opened: Cookies.get('sidebarStatus') ? !!+Cookies.get('sidebarStatus') : true,
|
||||||
|
withoutAnimation: false
|
||||||
|
},
|
||||||
|
device: 'desktop'
|
||||||
|
}
|
||||||
|
|
||||||
|
const mutations = {
|
||||||
|
TOGGLE_SIDEBAR: state => {
|
||||||
|
state.sidebar.opened = !state.sidebar.opened
|
||||||
|
state.sidebar.withoutAnimation = false
|
||||||
|
if (state.sidebar.opened) {
|
||||||
|
Cookies.set('sidebarStatus', 1)
|
||||||
|
} else {
|
||||||
|
Cookies.set('sidebarStatus', 0)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
CLOSE_SIDEBAR: (state, withoutAnimation) => {
|
||||||
|
Cookies.set('sidebarStatus', 0)
|
||||||
|
state.sidebar.opened = false
|
||||||
|
state.sidebar.withoutAnimation = withoutAnimation
|
||||||
|
},
|
||||||
|
TOGGLE_DEVICE: (state, device) => {
|
||||||
|
state.device = device
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const actions = {
|
||||||
|
toggleSideBar({ commit }) {
|
||||||
|
commit('TOGGLE_SIDEBAR')
|
||||||
|
},
|
||||||
|
closeSideBar({ commit }, { withoutAnimation }) {
|
||||||
|
commit('CLOSE_SIDEBAR', withoutAnimation)
|
||||||
|
},
|
||||||
|
toggleDevice({ commit }, device) {
|
||||||
|
commit('TOGGLE_DEVICE', device)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
namespaced: true,
|
||||||
|
state,
|
||||||
|
mutations,
|
||||||
|
actions
|
||||||
|
}
|
32
Inotify.Vue/src/store/modules/settings.js
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import defaultSettings from '@/settings'
|
||||||
|
|
||||||
|
const { showSettings, fixedHeader, sidebarLogo } = defaultSettings
|
||||||
|
|
||||||
|
const state = {
|
||||||
|
showSettings: showSettings,
|
||||||
|
fixedHeader: fixedHeader,
|
||||||
|
sidebarLogo: sidebarLogo
|
||||||
|
}
|
||||||
|
|
||||||
|
const mutations = {
|
||||||
|
CHANGE_SETTING: (state, { key, value }) => {
|
||||||
|
// eslint-disable-next-line no-prototype-builtins
|
||||||
|
if (state.hasOwnProperty(key)) {
|
||||||
|
state[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const actions = {
|
||||||
|
changeSetting({ commit }, data) {
|
||||||
|
commit('CHANGE_SETTING', data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
namespaced: true,
|
||||||
|
state,
|
||||||
|
mutations,
|
||||||
|
actions
|
||||||
|
}
|
||||||
|
|
125
Inotify.Vue/src/store/modules/user.js
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
import { login, logout, getInfo } from '@/api/user'
|
||||||
|
import { getToken, setToken, removeToken } from '@/utils/auth'
|
||||||
|
import { resetRouter } from '@/router'
|
||||||
|
|
||||||
|
const getDefaultState = () => {
|
||||||
|
return {
|
||||||
|
token: getToken(),
|
||||||
|
name: '',
|
||||||
|
role: '',
|
||||||
|
avatar: ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const state = getDefaultState()
|
||||||
|
|
||||||
|
const mutations = {
|
||||||
|
RESET_STATE: state => {
|
||||||
|
Object.assign(state, getDefaultState())
|
||||||
|
},
|
||||||
|
SET_TOKEN: (state, token) => {
|
||||||
|
state.token = token
|
||||||
|
},
|
||||||
|
SET_ROLE: (state, role) => {
|
||||||
|
state.role = role
|
||||||
|
},
|
||||||
|
SET_NAME: (state, name) => {
|
||||||
|
state.name = name
|
||||||
|
},
|
||||||
|
SET_AVATAR: (state, avatar) => {
|
||||||
|
state.avatar = avatar
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const actions = {
|
||||||
|
// user login
|
||||||
|
login({ commit }, userInfo) {
|
||||||
|
const { username, password } = userInfo
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
login({ username: username.trim(), password: password })
|
||||||
|
.then(response => {
|
||||||
|
const { data } = response
|
||||||
|
commit('SET_TOKEN', data.token)
|
||||||
|
commit('SET_ROLE', data.role)
|
||||||
|
setToken(data.token)
|
||||||
|
resolve()
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
reject(error)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
thirdlogin({ commit }, token) {
|
||||||
|
commit('SET_TOKEN', token)
|
||||||
|
setToken(token)
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
getInfo(token)
|
||||||
|
.then(response => {
|
||||||
|
const { data } = response
|
||||||
|
if (!data) {
|
||||||
|
return reject('Verification failed, please Login again.')
|
||||||
|
}
|
||||||
|
const { name, role, avatar } = data
|
||||||
|
commit('SET_NAME', name)
|
||||||
|
commit('SET_ROLE', role)
|
||||||
|
commit('SET_AVATAR', avatar)
|
||||||
|
resolve(data)
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
reject(error)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
// get user info
|
||||||
|
getInfo({ commit, state }) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
getInfo(state.token)
|
||||||
|
.then(response => {
|
||||||
|
const { data } = response
|
||||||
|
if (!data) {
|
||||||
|
return reject('Verification failed, please Login again.')
|
||||||
|
}
|
||||||
|
const { name, role, avatar } = data
|
||||||
|
commit('SET_NAME', name)
|
||||||
|
commit('SET_ROLE', role)
|
||||||
|
commit('SET_AVATAR', avatar)
|
||||||
|
resolve(data)
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
reject(error)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
// user logout
|
||||||
|
logout({ commit, state }) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
logout(state.token)
|
||||||
|
.then(() => {
|
||||||
|
removeToken() // must remove token first
|
||||||
|
resetRouter()
|
||||||
|
commit('RESET_STATE')
|
||||||
|
resolve()
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
reject(error)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
// remove token
|
||||||
|
resetToken({ commit }) {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
removeToken() // must remove token first
|
||||||
|
commit('RESET_STATE')
|
||||||
|
resolve()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
namespaced: true,
|
||||||
|
state,
|
||||||
|
mutations,
|
||||||
|
actions
|
||||||
|
}
|
49
Inotify.Vue/src/styles/element-ui.scss
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
// cover some element-ui styles
|
||||||
|
|
||||||
|
.el-breadcrumb__inner,
|
||||||
|
.el-breadcrumb__inner a {
|
||||||
|
font-weight: 400 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-upload {
|
||||||
|
input[type="file"] {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-upload__input {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// to fixed https://github.com/ElemeFE/element/issues/2461
|
||||||
|
.el-dialog {
|
||||||
|
transform: none;
|
||||||
|
left: 0;
|
||||||
|
position: relative;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
// refine element ui upload
|
||||||
|
.upload-container {
|
||||||
|
.el-upload {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.el-upload-dragger {
|
||||||
|
width: 100%;
|
||||||
|
height: 200px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// dropdown
|
||||||
|
.el-dropdown-menu {
|
||||||
|
a {
|
||||||
|
display: block
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// to fix el-date-picker css style
|
||||||
|
.el-range-separator {
|
||||||
|
box-sizing: content-box;
|
||||||
|
}
|
65
Inotify.Vue/src/styles/index.scss
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
@import './variables.scss';
|
||||||
|
@import './mixin.scss';
|
||||||
|
@import './transition.scss';
|
||||||
|
@import './element-ui.scss';
|
||||||
|
@import './sidebar.scss';
|
||||||
|
|
||||||
|
body {
|
||||||
|
height: 100%;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
|
font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
height: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
#app {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
*,
|
||||||
|
*:before,
|
||||||
|
*:after {
|
||||||
|
box-sizing: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:focus,
|
||||||
|
a:active {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
a,
|
||||||
|
a:focus,
|
||||||
|
a:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
div:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clearfix {
|
||||||
|
&:after {
|
||||||
|
visibility: hidden;
|
||||||
|
display: block;
|
||||||
|
font-size: 0;
|
||||||
|
content: " ";
|
||||||
|
clear: both;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// main-container global css
|
||||||
|
.app-container {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
28
Inotify.Vue/src/styles/mixin.scss
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
@mixin clearfix {
|
||||||
|
&:after {
|
||||||
|
content: "";
|
||||||
|
display: table;
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin scrollBar {
|
||||||
|
&::-webkit-scrollbar-track-piece {
|
||||||
|
background: #d3dce6;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-thumb {
|
||||||
|
background: #99a9bf;
|
||||||
|
border-radius: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin relative {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
226
Inotify.Vue/src/styles/sidebar.scss
Normal file
@ -0,0 +1,226 @@
|
|||||||
|
#app {
|
||||||
|
|
||||||
|
.main-container {
|
||||||
|
min-height: 100%;
|
||||||
|
transition: margin-left .28s;
|
||||||
|
margin-left: $sideBarWidth;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-container {
|
||||||
|
transition: width 0.28s;
|
||||||
|
width: $sideBarWidth !important;
|
||||||
|
background-color: $menuBg;
|
||||||
|
height: 100%;
|
||||||
|
position: fixed;
|
||||||
|
font-size: 0px;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 1001;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
// reset element-ui css
|
||||||
|
.horizontal-collapse-transition {
|
||||||
|
transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scrollbar-wrapper {
|
||||||
|
overflow-x: hidden !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-scrollbar__bar.is-vertical {
|
||||||
|
right: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-scrollbar {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.has-logo {
|
||||||
|
.el-scrollbar {
|
||||||
|
height: calc(100% - 50px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.is-horizontal {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
display: inline-block;
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.svg-icon {
|
||||||
|
margin-right: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sub-el-icon {
|
||||||
|
margin-right: 12px;
|
||||||
|
margin-left: -2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-menu {
|
||||||
|
border: none;
|
||||||
|
height: 100%;
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
// menu hover
|
||||||
|
.submenu-title-noDropdown,
|
||||||
|
.el-submenu__title {
|
||||||
|
&:hover {
|
||||||
|
background-color: $menuHover !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.is-active>.el-submenu__title {
|
||||||
|
color: $subMenuActiveText !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
& .nest-menu .el-submenu>.el-submenu__title,
|
||||||
|
& .el-submenu .el-menu-item {
|
||||||
|
min-width: $sideBarWidth !important;
|
||||||
|
background-color: $subMenuBg !important;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: $subMenuHover !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hideSidebar {
|
||||||
|
.sidebar-container {
|
||||||
|
width: 54px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-container {
|
||||||
|
margin-left: 54px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submenu-title-noDropdown {
|
||||||
|
padding: 0 !important;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.el-tooltip {
|
||||||
|
padding: 0 !important;
|
||||||
|
|
||||||
|
.svg-icon {
|
||||||
|
margin-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sub-el-icon {
|
||||||
|
margin-left: 19px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-submenu {
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
&>.el-submenu__title {
|
||||||
|
padding: 0 !important;
|
||||||
|
|
||||||
|
.svg-icon {
|
||||||
|
margin-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sub-el-icon {
|
||||||
|
margin-left: 19px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-submenu__icon-arrow {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-menu--collapse {
|
||||||
|
.el-submenu {
|
||||||
|
&>.el-submenu__title {
|
||||||
|
&>span {
|
||||||
|
height: 0;
|
||||||
|
width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
visibility: hidden;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-menu--collapse .el-menu .el-submenu {
|
||||||
|
min-width: $sideBarWidth !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
// mobile responsive
|
||||||
|
.mobile {
|
||||||
|
.main-container {
|
||||||
|
margin-left: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-container {
|
||||||
|
transition: transform .28s;
|
||||||
|
width: $sideBarWidth !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.hideSidebar {
|
||||||
|
.sidebar-container {
|
||||||
|
pointer-events: none;
|
||||||
|
transition-duration: 0.3s;
|
||||||
|
transform: translate3d(-$sideBarWidth, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.withoutAnimation {
|
||||||
|
|
||||||
|
.main-container,
|
||||||
|
.sidebar-container {
|
||||||
|
transition: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// when menu collapsed
|
||||||
|
.el-menu--vertical {
|
||||||
|
&>.el-menu {
|
||||||
|
.svg-icon {
|
||||||
|
margin-right: 16px;
|
||||||
|
}
|
||||||
|
.sub-el-icon {
|
||||||
|
margin-right: 12px;
|
||||||
|
margin-left: -2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.nest-menu .el-submenu>.el-submenu__title,
|
||||||
|
.el-menu-item {
|
||||||
|
&:hover {
|
||||||
|
// you can use $subMenuHover
|
||||||
|
background-color: $menuHover !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// the scroll bar appears when the subMenu is too long
|
||||||
|
>.el-menu--popup {
|
||||||
|
max-height: 100vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-track-piece {
|
||||||
|
background: #d3dce6;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-thumb {
|
||||||
|
background: #99a9bf;
|
||||||
|
border-radius: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
48
Inotify.Vue/src/styles/transition.scss
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
// global transition css
|
||||||
|
|
||||||
|
/* fade */
|
||||||
|
.fade-enter-active,
|
||||||
|
.fade-leave-active {
|
||||||
|
transition: opacity 0.28s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-enter,
|
||||||
|
.fade-leave-active {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* fade-transform */
|
||||||
|
.fade-transform-leave-active,
|
||||||
|
.fade-transform-enter-active {
|
||||||
|
transition: all .5s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-transform-enter {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(-30px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-transform-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(30px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* breadcrumb transition */
|
||||||
|
.breadcrumb-enter-active,
|
||||||
|
.breadcrumb-leave-active {
|
||||||
|
transition: all .5s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breadcrumb-enter,
|
||||||
|
.breadcrumb-leave-active {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(20px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.breadcrumb-move {
|
||||||
|
transition: all .5s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breadcrumb-leave-active {
|
||||||
|
position: absolute;
|
||||||
|
}
|
25
Inotify.Vue/src/styles/variables.scss
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
// sidebar
|
||||||
|
$menuText:#bfcbd9;
|
||||||
|
$menuActiveText:#409EFF;
|
||||||
|
$subMenuActiveText:#f4f4f5; //https://github.com/ElemeFE/element/issues/12951
|
||||||
|
|
||||||
|
$menuBg:#304156;
|
||||||
|
$menuHover:#263445;
|
||||||
|
|
||||||
|
$subMenuBg:#1f2d3d;
|
||||||
|
$subMenuHover:#001528;
|
||||||
|
|
||||||
|
$sideBarWidth: 210px;
|
||||||
|
|
||||||
|
// the :export directive is the magic sauce for webpack
|
||||||
|
// https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass
|
||||||
|
:export {
|
||||||
|
menuText: $menuText;
|
||||||
|
menuActiveText: $menuActiveText;
|
||||||
|
subMenuActiveText: $subMenuActiveText;
|
||||||
|
menuBg: $menuBg;
|
||||||
|
menuHover: $menuHover;
|
||||||
|
subMenuBg: $subMenuBg;
|
||||||
|
subMenuHover: $subMenuHover;
|
||||||
|
sideBarWidth: $sideBarWidth;
|
||||||
|
}
|
15
Inotify.Vue/src/utils/auth.js
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import Cookies from 'js-cookie'
|
||||||
|
|
||||||
|
const TokenKey = 'JWTToken'
|
||||||
|
|
||||||
|
export function getToken() {
|
||||||
|
return Cookies.get(TokenKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setToken(token) {
|
||||||
|
return Cookies.set(TokenKey, token)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeToken() {
|
||||||
|
return Cookies.remove(TokenKey)
|
||||||
|
}
|
10
Inotify.Vue/src/utils/get-page-title.js
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import defaultSettings from '@/settings'
|
||||||
|
|
||||||
|
const title = defaultSettings.title || 'Vue Admin Template'
|
||||||
|
|
||||||
|
export default function getPageTitle(pageTitle) {
|
||||||
|
if (pageTitle) {
|
||||||
|
return `${pageTitle} - ${title}`
|
||||||
|
}
|
||||||
|
return `${title}`
|
||||||
|
}
|
117
Inotify.Vue/src/utils/index.js
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
/**
|
||||||
|
* Created by PanJiaChen on 16/11/18.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse the time to string
|
||||||
|
* @param {(Object|string|number)} time
|
||||||
|
* @param {string} cFormat
|
||||||
|
* @returns {string | null}
|
||||||
|
*/
|
||||||
|
export function parseTime(time, cFormat) {
|
||||||
|
if (arguments.length === 0 || !time) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}'
|
||||||
|
let date
|
||||||
|
if (typeof time === 'object') {
|
||||||
|
date = time
|
||||||
|
} else {
|
||||||
|
if ((typeof time === 'string')) {
|
||||||
|
if ((/^[0-9]+$/.test(time))) {
|
||||||
|
// support "1548221490638"
|
||||||
|
time = parseInt(time)
|
||||||
|
} else {
|
||||||
|
// support safari
|
||||||
|
// https://stackoverflow.com/questions/4310953/invalid-date-in-safari
|
||||||
|
time = time.replace(new RegExp(/-/gm), '/')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((typeof time === 'number') && (time.toString().length === 10)) {
|
||||||
|
time = time * 1000
|
||||||
|
}
|
||||||
|
date = new Date(time)
|
||||||
|
}
|
||||||
|
const formatObj = {
|
||||||
|
y: date.getFullYear(),
|
||||||
|
m: date.getMonth() + 1,
|
||||||
|
d: date.getDate(),
|
||||||
|
h: date.getHours(),
|
||||||
|
i: date.getMinutes(),
|
||||||
|
s: date.getSeconds(),
|
||||||
|
a: date.getDay()
|
||||||
|
}
|
||||||
|
const time_str = format.replace(/{([ymdhisa])+}/g, (result, key) => {
|
||||||
|
const value = formatObj[key]
|
||||||
|
// Note: getDay() returns 0 on Sunday
|
||||||
|
if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value ] }
|
||||||
|
return value.toString().padStart(2, '0')
|
||||||
|
})
|
||||||
|
return time_str
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number} time
|
||||||
|
* @param {string} option
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
export function formatTime(time, option) {
|
||||||
|
if (('' + time).length === 10) {
|
||||||
|
time = parseInt(time) * 1000
|
||||||
|
} else {
|
||||||
|
time = +time
|
||||||
|
}
|
||||||
|
const d = new Date(time)
|
||||||
|
const now = Date.now()
|
||||||
|
|
||||||
|
const diff = (now - d) / 1000
|
||||||
|
|
||||||
|
if (diff < 30) {
|
||||||
|
return '刚刚'
|
||||||
|
} else if (diff < 3600) {
|
||||||
|
// less 1 hour
|
||||||
|
return Math.ceil(diff / 60) + '分钟前'
|
||||||
|
} else if (diff < 3600 * 24) {
|
||||||
|
return Math.ceil(diff / 3600) + '小时前'
|
||||||
|
} else if (diff < 3600 * 24 * 2) {
|
||||||
|
return '1天前'
|
||||||
|
}
|
||||||
|
if (option) {
|
||||||
|
return parseTime(time, option)
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
d.getMonth() +
|
||||||
|
1 +
|
||||||
|
'月' +
|
||||||
|
d.getDate() +
|
||||||
|
'日' +
|
||||||
|
d.getHours() +
|
||||||
|
'时' +
|
||||||
|
d.getMinutes() +
|
||||||
|
'分'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} url
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
export function param2Obj(url) {
|
||||||
|
const search = decodeURIComponent(url.split('?')[1]).replace(/\+/g, ' ')
|
||||||
|
if (!search) {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
const obj = {}
|
||||||
|
const searchArr = search.split('&')
|
||||||
|
searchArr.forEach(v => {
|
||||||
|
const index = v.indexOf('=')
|
||||||
|
if (index !== -1) {
|
||||||
|
const name = v.substring(0, index)
|
||||||
|
const val = v.substring(index + 1, v.length)
|
||||||
|
obj[name] = val
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return obj
|
||||||
|
}
|
85
Inotify.Vue/src/utils/request.js
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
import axios from 'axios'
|
||||||
|
import { MessageBox, Message } from 'element-ui'
|
||||||
|
import store from '@/store'
|
||||||
|
import { getToken } from '@/utils/auth'
|
||||||
|
|
||||||
|
// create an axios instance
|
||||||
|
const service = axios.create({
|
||||||
|
baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
|
||||||
|
// withCredentials: true, // send cookies when cross-domain requests
|
||||||
|
timeout: 5000 // request timeout
|
||||||
|
})
|
||||||
|
|
||||||
|
// request interceptor
|
||||||
|
service.interceptors.request.use(
|
||||||
|
config => {
|
||||||
|
// do something before request is sent
|
||||||
|
if (store.getters.token) {
|
||||||
|
// let each request carry token
|
||||||
|
// ['X-Token'] is a custom headers key
|
||||||
|
// please modify it according to the actual situation
|
||||||
|
config.headers.Authorization = `bearer ${getToken()}`
|
||||||
|
}
|
||||||
|
return config
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
// do something with request error
|
||||||
|
console.log(error) // for debug
|
||||||
|
return Promise.reject(error)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// response interceptor
|
||||||
|
service.interceptors.response.use(
|
||||||
|
/**
|
||||||
|
* If you want to get http information such as headers or status
|
||||||
|
* Please return response => response
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine the request status by custom code
|
||||||
|
* Here is just an example
|
||||||
|
* You can also judge the status by HTTP Status Code
|
||||||
|
*/
|
||||||
|
response => {
|
||||||
|
const res = response.data
|
||||||
|
|
||||||
|
if (res.code === 403) {
|
||||||
|
MessageBox.confirm('您已登出, 需要重新登陆', '确认登出', {
|
||||||
|
confirmButtonText: '重新登陆',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
}).then(() => {
|
||||||
|
store.dispatch('user/resetToken').then(() => {
|
||||||
|
location.reload()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
} else if (res.code === 405) {
|
||||||
|
Message({
|
||||||
|
message: res.message || '无权访问',
|
||||||
|
type: 'error',
|
||||||
|
duration: 5 * 1000
|
||||||
|
})
|
||||||
|
} else if (res.code !== 200) {
|
||||||
|
Message({
|
||||||
|
message: res.message || '系统错误,请联系管理员',
|
||||||
|
type: 'error',
|
||||||
|
duration: 5 * 1000
|
||||||
|
})
|
||||||
|
return Promise.reject(new Error(res.message || 'Error'))
|
||||||
|
} else {
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
console.log('err' + error) // for debug
|
||||||
|
Message({
|
||||||
|
message: error.message,
|
||||||
|
type: 'error',
|
||||||
|
duration: 5 * 1000
|
||||||
|
})
|
||||||
|
return Promise.reject(error)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
export default service
|
19
Inotify.Vue/src/utils/validate.js
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
/**
|
||||||
|
* Created by PanJiaChen on 16/11/18.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} path
|
||||||
|
* @returns {Boolean}
|
||||||
|
*/
|
||||||
|
export function isExternal(path) {
|
||||||
|
return /^(https?:|mailto:|tel:)/.test(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} str
|
||||||
|
* @returns {Boolean}
|
||||||
|
*/
|
||||||
|
export function validUsername(str) {
|
||||||
|
return str.trim().length >= 0
|
||||||
|
}
|
228
Inotify.Vue/src/views/404.vue
Normal file
@ -0,0 +1,228 @@
|
|||||||
|
<template>
|
||||||
|
<div class="wscn-http404-container">
|
||||||
|
<div class="wscn-http404">
|
||||||
|
<div class="pic-404">
|
||||||
|
<img class="pic-404__parent" src="@/assets/404_images/404.png" alt="404">
|
||||||
|
<img class="pic-404__child left" src="@/assets/404_images/404_cloud.png" alt="404">
|
||||||
|
<img class="pic-404__child mid" src="@/assets/404_images/404_cloud.png" alt="404">
|
||||||
|
<img class="pic-404__child right" src="@/assets/404_images/404_cloud.png" alt="404">
|
||||||
|
</div>
|
||||||
|
<div class="bullshit">
|
||||||
|
<div class="bullshit__oops">OOPS!</div>
|
||||||
|
<div class="bullshit__info">All rights reserved
|
||||||
|
<a style="color:#20a0ff" href="https://wallstreetcn.com" target="_blank">wallstreetcn</a>
|
||||||
|
</div>
|
||||||
|
<div class="bullshit__headline">{{ message }}</div>
|
||||||
|
<div class="bullshit__info">Please check that the URL you entered is correct, or click the button below to return to the homepage.</div>
|
||||||
|
<a href="" class="bullshit__return-home">Back to home</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'Page404',
|
||||||
|
computed: {
|
||||||
|
message() {
|
||||||
|
return 'The webmaster said that you can not enter this page...'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.wscn-http404-container{
|
||||||
|
transform: translate(-50%,-50%);
|
||||||
|
position: absolute;
|
||||||
|
top: 40%;
|
||||||
|
left: 50%;
|
||||||
|
}
|
||||||
|
.wscn-http404 {
|
||||||
|
position: relative;
|
||||||
|
width: 1200px;
|
||||||
|
padding: 0 50px;
|
||||||
|
overflow: hidden;
|
||||||
|
.pic-404 {
|
||||||
|
position: relative;
|
||||||
|
float: left;
|
||||||
|
width: 600px;
|
||||||
|
overflow: hidden;
|
||||||
|
&__parent {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
&__child {
|
||||||
|
position: absolute;
|
||||||
|
&.left {
|
||||||
|
width: 80px;
|
||||||
|
top: 17px;
|
||||||
|
left: 220px;
|
||||||
|
opacity: 0;
|
||||||
|
animation-name: cloudLeft;
|
||||||
|
animation-duration: 2s;
|
||||||
|
animation-timing-function: linear;
|
||||||
|
animation-fill-mode: forwards;
|
||||||
|
animation-delay: 1s;
|
||||||
|
}
|
||||||
|
&.mid {
|
||||||
|
width: 46px;
|
||||||
|
top: 10px;
|
||||||
|
left: 420px;
|
||||||
|
opacity: 0;
|
||||||
|
animation-name: cloudMid;
|
||||||
|
animation-duration: 2s;
|
||||||
|
animation-timing-function: linear;
|
||||||
|
animation-fill-mode: forwards;
|
||||||
|
animation-delay: 1.2s;
|
||||||
|
}
|
||||||
|
&.right {
|
||||||
|
width: 62px;
|
||||||
|
top: 100px;
|
||||||
|
left: 500px;
|
||||||
|
opacity: 0;
|
||||||
|
animation-name: cloudRight;
|
||||||
|
animation-duration: 2s;
|
||||||
|
animation-timing-function: linear;
|
||||||
|
animation-fill-mode: forwards;
|
||||||
|
animation-delay: 1s;
|
||||||
|
}
|
||||||
|
@keyframes cloudLeft {
|
||||||
|
0% {
|
||||||
|
top: 17px;
|
||||||
|
left: 220px;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
20% {
|
||||||
|
top: 33px;
|
||||||
|
left: 188px;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
80% {
|
||||||
|
top: 81px;
|
||||||
|
left: 92px;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
top: 97px;
|
||||||
|
left: 60px;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes cloudMid {
|
||||||
|
0% {
|
||||||
|
top: 10px;
|
||||||
|
left: 420px;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
20% {
|
||||||
|
top: 40px;
|
||||||
|
left: 360px;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
70% {
|
||||||
|
top: 130px;
|
||||||
|
left: 180px;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
top: 160px;
|
||||||
|
left: 120px;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes cloudRight {
|
||||||
|
0% {
|
||||||
|
top: 100px;
|
||||||
|
left: 500px;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
20% {
|
||||||
|
top: 120px;
|
||||||
|
left: 460px;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
80% {
|
||||||
|
top: 180px;
|
||||||
|
left: 340px;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
top: 200px;
|
||||||
|
left: 300px;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.bullshit {
|
||||||
|
position: relative;
|
||||||
|
float: left;
|
||||||
|
width: 300px;
|
||||||
|
padding: 30px 0;
|
||||||
|
overflow: hidden;
|
||||||
|
&__oops {
|
||||||
|
font-size: 32px;
|
||||||
|
font-weight: bold;
|
||||||
|
line-height: 40px;
|
||||||
|
color: #1482f0;
|
||||||
|
opacity: 0;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
animation-name: slideUp;
|
||||||
|
animation-duration: 0.5s;
|
||||||
|
animation-fill-mode: forwards;
|
||||||
|
}
|
||||||
|
&__headline {
|
||||||
|
font-size: 20px;
|
||||||
|
line-height: 24px;
|
||||||
|
color: #222;
|
||||||
|
font-weight: bold;
|
||||||
|
opacity: 0;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
animation-name: slideUp;
|
||||||
|
animation-duration: 0.5s;
|
||||||
|
animation-delay: 0.1s;
|
||||||
|
animation-fill-mode: forwards;
|
||||||
|
}
|
||||||
|
&__info {
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 21px;
|
||||||
|
color: grey;
|
||||||
|
opacity: 0;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
animation-name: slideUp;
|
||||||
|
animation-duration: 0.5s;
|
||||||
|
animation-delay: 0.2s;
|
||||||
|
animation-fill-mode: forwards;
|
||||||
|
}
|
||||||
|
&__return-home {
|
||||||
|
display: block;
|
||||||
|
float: left;
|
||||||
|
width: 110px;
|
||||||
|
height: 36px;
|
||||||
|
background: #1482f0;
|
||||||
|
border-radius: 100px;
|
||||||
|
text-align: center;
|
||||||
|
color: #ffffff;
|
||||||
|
opacity: 0;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 36px;
|
||||||
|
cursor: pointer;
|
||||||
|
animation-name: slideUp;
|
||||||
|
animation-duration: 0.5s;
|
||||||
|
animation-delay: 0.3s;
|
||||||
|
animation-fill-mode: forwards;
|
||||||
|
}
|
||||||
|
@keyframes slideUp {
|
||||||
|
0% {
|
||||||
|
transform: translateY(60px);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translateY(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
256
Inotify.Vue/src/views/login/index.vue
Normal file
@ -0,0 +1,256 @@
|
|||||||
|
<template>
|
||||||
|
<div class="login-container">
|
||||||
|
<el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form" auto-complete="on" label-position="left">
|
||||||
|
<div class="loginGitHub">
|
||||||
|
<svg-icon class="loginGitHubIcon" icon-class="github" @click.native.prevent="github" />
|
||||||
|
</div>
|
||||||
|
<el-form-item prop="username">
|
||||||
|
<span class="svg-container">
|
||||||
|
<svg-icon icon-class="user" />
|
||||||
|
</span>
|
||||||
|
<el-input ref="username" v-model="loginForm.username" placeholder="用户名" name="username" type="text" tabindex="1" auto-complete="on" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item prop="password">
|
||||||
|
<span class="svg-container">
|
||||||
|
<svg-icon icon-class="password" />
|
||||||
|
</span>
|
||||||
|
<el-input :key="passwordType" ref="password" v-model="loginForm.password" :type="passwordType" placeholder="密码" name="password" tabindex="2" auto-complete="on" @keyup.enter.native="handleLogin" />
|
||||||
|
<span class="show-pwd" @click="showPwd">
|
||||||
|
<svg-icon :icon-class="passwordType === 'password' ? 'eye' : 'eye-open'" />
|
||||||
|
</span>
|
||||||
|
</el-form-item>
|
||||||
|
<el-button :loading="loading" type="primary" style="width:100%;height:45px" @click.native.prevent="handleLogin">登陆</el-button>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {
|
||||||
|
githubLogin
|
||||||
|
} from '@/api/user'
|
||||||
|
import {
|
||||||
|
validUsername
|
||||||
|
} from '@/utils/validate'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'Login',
|
||||||
|
data() {
|
||||||
|
const validateUsername = (rule, value, callback) => {
|
||||||
|
if (!validUsername(value)) {
|
||||||
|
callback(new Error('请出入用户名'))
|
||||||
|
} else {
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const validatePassword = (rule, value, callback) => {
|
||||||
|
if (value.length < 6) {
|
||||||
|
callback(new Error('密码长度必须大于6'))
|
||||||
|
} else {
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
githubEnable: true,
|
||||||
|
loginForm: {
|
||||||
|
username: '',
|
||||||
|
password: ''
|
||||||
|
},
|
||||||
|
loginRules: {
|
||||||
|
username: [{
|
||||||
|
required: true,
|
||||||
|
trigger: 'blur',
|
||||||
|
validator: validateUsername
|
||||||
|
}],
|
||||||
|
password: [{
|
||||||
|
required: true,
|
||||||
|
trigger: 'blur',
|
||||||
|
validator: validatePassword
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
loading: false,
|
||||||
|
passwordType: 'password',
|
||||||
|
redirect: undefined
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
$route: {
|
||||||
|
handler: function (route) {
|
||||||
|
this.redirect = route.query && route.query.redirect
|
||||||
|
},
|
||||||
|
immediate: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
beforeCreate() {
|
||||||
|
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
if (this.$route.query.token) {
|
||||||
|
let token = this.$route.query.token
|
||||||
|
this.$store.dispatch('user/thirdlogin', token).then(() => {
|
||||||
|
this.$router.push({
|
||||||
|
path: this.redirect || '/'
|
||||||
|
})
|
||||||
|
this.loading = false
|
||||||
|
}).catch(() => {
|
||||||
|
this.loading = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
showPwd() {
|
||||||
|
if (this.passwordType === 'password') {
|
||||||
|
this.passwordType = ''
|
||||||
|
} else {
|
||||||
|
this.passwordType = 'password'
|
||||||
|
}
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs.password.focus()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
github() {
|
||||||
|
this.loading = true
|
||||||
|
githubLogin().then((response) => {
|
||||||
|
window.location.href = response.data
|
||||||
|
})
|
||||||
|
},
|
||||||
|
handleLogin() {
|
||||||
|
this.$refs['loginForm'].validate(valid => {
|
||||||
|
if (valid) {
|
||||||
|
this.loading = true
|
||||||
|
this.$store.dispatch('user/login', this.loginForm).then(() => {
|
||||||
|
this.$router.push({
|
||||||
|
path: this.redirect || '/'
|
||||||
|
})
|
||||||
|
this.loading = false
|
||||||
|
}).catch(() => {
|
||||||
|
this.loading = false
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
console.log('error submit!!')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
$bg:#283443;
|
||||||
|
$light_gray:#fff;
|
||||||
|
$cursor: #fff;
|
||||||
|
|
||||||
|
@supports (-webkit-mask: none) and (not (cater-color: $cursor)) {
|
||||||
|
.login-container .el-input input {
|
||||||
|
color: $cursor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* reset element-ui css */
|
||||||
|
.login-container {
|
||||||
|
.el-input {
|
||||||
|
display: inline-block;
|
||||||
|
height: 47px;
|
||||||
|
width: 85%;
|
||||||
|
|
||||||
|
input {
|
||||||
|
background: transparent;
|
||||||
|
border: 0px;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
border-radius: 0px;
|
||||||
|
padding: 12px 5px 12px 15px;
|
||||||
|
color: $light_gray;
|
||||||
|
height: 47px;
|
||||||
|
caret-color: $cursor;
|
||||||
|
|
||||||
|
&:-webkit-autofill {
|
||||||
|
box-shadow: 0 0 0px 1000px $bg inset !important;
|
||||||
|
-webkit-text-fill-color: $cursor !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-form-item {
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
background: rgba(0, 0, 0, 0.1);
|
||||||
|
border-radius: 5px;
|
||||||
|
color: #454545;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style><style lang="scss" scoped>
|
||||||
|
$bg:#2d3a4b;
|
||||||
|
$dark_gray:#889aa4;
|
||||||
|
$light_gray:#eee;
|
||||||
|
|
||||||
|
.login-container {
|
||||||
|
min-height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
background-color: $bg;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.loginGitHub {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loginGitHubIcon {
|
||||||
|
margin: 20px;
|
||||||
|
height: 85px;
|
||||||
|
text-align: center;
|
||||||
|
width: 100px;
|
||||||
|
font-size: 30px;
|
||||||
|
cursor: pointer;
|
||||||
|
fill: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-form {
|
||||||
|
position: relative;
|
||||||
|
width: 520px;
|
||||||
|
max-width: 100%;
|
||||||
|
padding: 160px 35px 0;
|
||||||
|
margin: 0 auto;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tips {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #fff;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
|
||||||
|
span {
|
||||||
|
&:first-of-type {
|
||||||
|
margin-right: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.svg-container {
|
||||||
|
padding: 6px 5px 6px 15px;
|
||||||
|
color: $dark_gray;
|
||||||
|
vertical-align: middle;
|
||||||
|
width: 30px;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-container {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 26px;
|
||||||
|
color: $light_gray;
|
||||||
|
margin: 0px auto 40px auto;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.show-pwd {
|
||||||
|
position: absolute;
|
||||||
|
right: 10px;
|
||||||
|
top: 7px;
|
||||||
|
font-size: 16px;
|
||||||
|
color: $dark_gray;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
33
Inotify.Vue/src/views/manager/index.vue
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<template>
|
||||||
|
<div class="dashboard-container">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {
|
||||||
|
mapGetters
|
||||||
|
} from 'vuex'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'Dashboard',
|
||||||
|
computed: {
|
||||||
|
...mapGetters([
|
||||||
|
'name'
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.dashboard {
|
||||||
|
&-container {
|
||||||
|
margin: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-text {
|
||||||
|
font-size: 30px;
|
||||||
|
line-height: 46px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
100
Inotify.Vue/src/views/manager/settingpro/oauthsetting/index.vue
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
<template>
|
||||||
|
<div class='app-container'>
|
||||||
|
<el-card>
|
||||||
|
<template #header>
|
||||||
|
<div class="card-header">
|
||||||
|
<span>重置密码</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<el-form ref="oauthForm" :model="oauthForm" label-width="20%" label-position='right' :rules="oauthRules">
|
||||||
|
<el-form-item label="用户名">
|
||||||
|
<el-input v-model="oauthForm.username" :disabled="true"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item prop="newpassword" label='新密码'>
|
||||||
|
<el-input ref="password" name="password" v-model='oauthForm.newpassword' placeholder="新密码"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item prop="newpassword2" label='确认密码'>
|
||||||
|
<el-input ref="password2" name="password2" v-model="oauthForm.newpassword2" placeholder="确认密码"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" @click="submitForm('oauthForm')">重置</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {
|
||||||
|
resetPassword
|
||||||
|
} from '@/api/user'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
var validatePassword = (rule, value, callback) => {
|
||||||
|
if (value.length < 6) {
|
||||||
|
callback(new Error('密码长度必须大于6'))
|
||||||
|
} else {
|
||||||
|
|
||||||
|
callback()
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var validatePassword2 = (rule, value, callback) => {
|
||||||
|
if (value.length < 6) {
|
||||||
|
callback(new Error('密码长度必须大于6'))
|
||||||
|
} else {
|
||||||
|
if (this.oauthForm.newpassword !== this.oauthForm.newpassword2) {
|
||||||
|
callback(new Error('密码不一致'))
|
||||||
|
} else {
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
oauthForm: {
|
||||||
|
username: '',
|
||||||
|
newpassword: '',
|
||||||
|
newpassword2: ''
|
||||||
|
},
|
||||||
|
oauthRules: {
|
||||||
|
newpassword: [{
|
||||||
|
required: true,
|
||||||
|
trigger: 'blur',
|
||||||
|
validator: validatePassword
|
||||||
|
}],
|
||||||
|
newpassword2: [{
|
||||||
|
required: true,
|
||||||
|
trigger: 'blur',
|
||||||
|
validator: validatePassword2
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.fetchData()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
fetchData() {
|
||||||
|
|
||||||
|
this.oauthForm.username = this.$store.getters.name;
|
||||||
|
},
|
||||||
|
submitForm(formName) {
|
||||||
|
this.$refs[formName].validate((valid) => {
|
||||||
|
|
||||||
|
resetPassword(this.oauthForm.newpassword2).then((response) => {
|
||||||
|
if (response.code == 200) {
|
||||||
|
this.$message({
|
||||||
|
message: '重置成功',
|
||||||
|
type: 'success'
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.$message.error('重置失败');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
236
Inotify.Vue/src/views/manager/settingpro/sendauths/index.vue
Normal file
@ -0,0 +1,236 @@
|
|||||||
|
<template>
|
||||||
|
<div class='app-container'>
|
||||||
|
<el-dialog :title='title' :visible.sync='dialogVisible' :append-to-body='true'>
|
||||||
|
<el-form ref="authform" :model="authform" label-width="100px" label-position='right' :rules="authformrules">
|
||||||
|
<el-form-item label='通道类型'>
|
||||||
|
<el-select :disabled='isModify' value-key="key" v-model='selectTemplate' placeholder='请选择' @change="selectTemplateChange">
|
||||||
|
<el-option v-for="item in sendTemplates" :key="item.key" :label="item.name" :value="item">
|
||||||
|
</el-option>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="名称">
|
||||||
|
<el-input v-model="selectTemplate.name"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item v-for="(item) in selectTemplate.values" :label="item.description" :key="item.name" required>
|
||||||
|
<el-input v-model="item.value" :placeholder="item.default"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<span class='dialog-footer'>
|
||||||
|
<el-button @click='dialogVisible = false'>取 消</el-button>
|
||||||
|
<el-button type='primary' @click="submitForm('authform')">确 定</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
<el-button type='primary' icon='el-icon-edit' @click='addAuth'>新 增</el-button>
|
||||||
|
<el-table v-loading='listLoading' :data='sendAuthinfos' element-loading-text='加载中' border fit highlight-current-row>
|
||||||
|
<el-table-column align='center' label='序号' width='95'>
|
||||||
|
<template slot-scope='scope'>
|
||||||
|
{{ scope.$index+1 }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label='类型' width='110' align='center'>
|
||||||
|
<template slot-scope='scope'>
|
||||||
|
<span>{{ scope.row.type }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label='名称' width='110' align='center'>
|
||||||
|
<template slot-scope='scope'>
|
||||||
|
<span>{{ scope.row.name }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label='配置信息' align='center'>>
|
||||||
|
<template slot-scope='scope'>
|
||||||
|
{{ scope.row.authData }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column align='center' prop='created_at' label='编辑' width='200'>
|
||||||
|
<template slot-scope='scope'>
|
||||||
|
<el-button-group>
|
||||||
|
<el-button type='primary' icon='el-icon-edit' @click='modifyAuth(scope.$index, scope.row)'></el-button>
|
||||||
|
<el-button type='danger' icon='el-icon-delete' @click='deleteAuth(scope.$index, scope.row)'></el-button>
|
||||||
|
</el-button-group>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column class-name='status-col' label='激活' width='110' align='center'>
|
||||||
|
<template slot-scope='scope'>
|
||||||
|
<el-switch active-color='#13ce66' v-model='scope.row.isActive' @change='activeAuth(scope.$index,scope.row,scope.row.isActive)' inactive-color='#ff4949'></el-switch>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {
|
||||||
|
getSendAuths,
|
||||||
|
deleteAuthInfo,
|
||||||
|
activeAuthInfo,
|
||||||
|
modifySendAuth,
|
||||||
|
getSendTemplates,
|
||||||
|
addAuthInfo,
|
||||||
|
deepClone,
|
||||||
|
|
||||||
|
} from '@/api/setting'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
authformrules: {
|
||||||
|
name: [{
|
||||||
|
required: true,
|
||||||
|
message: '必填'
|
||||||
|
}],
|
||||||
|
value: [{
|
||||||
|
required: true,
|
||||||
|
message: '必填'
|
||||||
|
}]
|
||||||
|
|
||||||
|
},
|
||||||
|
sendAuthinfos: [],
|
||||||
|
sendTemplates: [],
|
||||||
|
selectTemplate: {
|
||||||
|
key: 0,
|
||||||
|
name: '',
|
||||||
|
Values: []
|
||||||
|
},
|
||||||
|
selectTemplateKey: '',
|
||||||
|
formLabelWidth: '120px',
|
||||||
|
listLoading: true,
|
||||||
|
dialogVisible: false,
|
||||||
|
isModify: false,
|
||||||
|
authform: {},
|
||||||
|
title: "设置",
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.fetchData()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
fetchData() {
|
||||||
|
this.listLoading = true
|
||||||
|
getSendAuths().then((response) => {
|
||||||
|
this.sendAuthinfos = response.data
|
||||||
|
this.listLoading = false
|
||||||
|
})
|
||||||
|
getSendTemplates().then((response) => {
|
||||||
|
if (response.code == 200) {
|
||||||
|
|
||||||
|
this.sendTemplates = response.data;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
selectTemplateChange(selectTemplate) {
|
||||||
|
|
||||||
|
this.selectTemplate = deepClone(selectTemplate);
|
||||||
|
},
|
||||||
|
submitForm(formName) {
|
||||||
|
this.$refs[formName].validate((valid) => {
|
||||||
|
if (valid) {
|
||||||
|
let allReady = this.selectTemplate.values.every(u => u.value);
|
||||||
|
if (allReady) {
|
||||||
|
if (this.isModify) {
|
||||||
|
modifySendAuth(this.selectTemplate).then((response) => {
|
||||||
|
if (response.code == 200) {
|
||||||
|
this.$message({
|
||||||
|
message: '修改成功',
|
||||||
|
type: 'success'
|
||||||
|
})
|
||||||
|
this.dialogVisible = false;
|
||||||
|
this.fetchData()
|
||||||
|
} else {
|
||||||
|
this.$message.error('修改失败');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
addAuthInfo(this.selectTemplate).then((response) => {
|
||||||
|
if (response.code == 200) {
|
||||||
|
this.$message({
|
||||||
|
message: '新增成功',
|
||||||
|
type: 'success'
|
||||||
|
})
|
||||||
|
this.dialogVisible = false;
|
||||||
|
this.fetchData()
|
||||||
|
} else {
|
||||||
|
this.$message.error('新增失败');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
this.$message.error('请填写完整!');
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
addAuth() {
|
||||||
|
this.title = '新增设置'
|
||||||
|
this.dialogVisible = true
|
||||||
|
this.isModify = false
|
||||||
|
this.selectTemplate = deepClone(this.sendTemplates[0])
|
||||||
|
},
|
||||||
|
|
||||||
|
modifyAuth(index, row) {
|
||||||
|
this.title = '修改设置'
|
||||||
|
this.isModify = true;
|
||||||
|
this.selectTemplate = deepClone(row);
|
||||||
|
this.dialogVisible = true;
|
||||||
|
|
||||||
|
},
|
||||||
|
deleteAuth(index, row) {
|
||||||
|
deleteAuthInfo(row.sendAuthId).then((response) => {
|
||||||
|
if (response.code == 200) {
|
||||||
|
this.$message({
|
||||||
|
message: '删除成功',
|
||||||
|
type: 'success'
|
||||||
|
})
|
||||||
|
this.fetchData()
|
||||||
|
} else {
|
||||||
|
this.$message.error('删除失败');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
activeAuth(index, row, state) {
|
||||||
|
activeAuthInfo(row.sendAuthId, state).then((response) => {
|
||||||
|
if (response.code == 200) {
|
||||||
|
this.$message({
|
||||||
|
message: state ? '激活成功' : '注销成功',
|
||||||
|
type: 'success'
|
||||||
|
})
|
||||||
|
this.fetchData()
|
||||||
|
} else {
|
||||||
|
this.$message.error(state ? '激活失败' : '注销失败');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style lang="scss">
|
||||||
|
|
||||||
|
@media screen and (min-width:800px) {
|
||||||
|
.el-dialog__wrapper .el-dialog {
|
||||||
|
width: 800px !important;
|
||||||
|
}
|
||||||
|
.el-dialog__wrapper .el-dialog .el-dialog__body {
|
||||||
|
overflow: auto
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 800px) {
|
||||||
|
.el-dialog__wrapper .el-dialog {
|
||||||
|
width: 99% !important;
|
||||||
|
}
|
||||||
|
.el-dialog__wrapper .el-dialog .el-dialog__body {
|
||||||
|
overflow: auto
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
155
Inotify.Vue/src/views/manager/settingpro/sendkey/index.vue
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
<template>
|
||||||
|
<div class="app-container">
|
||||||
|
<el-card>
|
||||||
|
<template #header>
|
||||||
|
<div class="card-header">
|
||||||
|
<span>消息验证</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<el-form ref="sendform" :model="messageForm" label-width="20%" :rules="messageForm.rules">
|
||||||
|
<el-form-item label="消息标题" prop='title'>
|
||||||
|
<el-input type="text" v-model="messageForm.title" placeholder="必填"> </el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="消息内容" prop='data'>
|
||||||
|
<el-input type="textarea" v-model="messageForm.data" placeholder="选填"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" @click="onMessage('sendform')">发送</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
|
||||||
|
<el-divider content-position="left">重置授权</el-divider>
|
||||||
|
<el-form ref="resetform" :model="keyForm" label-width="20%">
|
||||||
|
<el-form-item label="当前SendKey">
|
||||||
|
<el-input v-model="keyForm.sendKey" :readonly="true"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="快捷地址(标题)">
|
||||||
|
<el-input type="textarea" v-model="keyForm.sendUrlTitle" :readonly="true"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="快捷地址(完整)">
|
||||||
|
<el-input type="textarea" v-model="keyForm.sendUrl" :readonly="true"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" @click="onReSendKey('resetform')">重置SendKey</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {
|
||||||
|
getSendKey,
|
||||||
|
reSendKey,
|
||||||
|
sendMessage
|
||||||
|
} from "@/api/setting";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
filters: {
|
||||||
|
statusFilter(status) {
|
||||||
|
const statusMap = {
|
||||||
|
published: "success",
|
||||||
|
draft: "gray",
|
||||||
|
deleted: "danger",
|
||||||
|
};
|
||||||
|
return statusMap[status];
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
listLoading: true,
|
||||||
|
messageForm: {
|
||||||
|
title: '',
|
||||||
|
data: '',
|
||||||
|
sendKey: '',
|
||||||
|
rules: {
|
||||||
|
|
||||||
|
title: [{
|
||||||
|
required: true,
|
||||||
|
message: '请输入标题',
|
||||||
|
trigger: 'blur'
|
||||||
|
}],
|
||||||
|
data: [{
|
||||||
|
required: false,
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
keyForm: {
|
||||||
|
sendKey: '',
|
||||||
|
sendUrl: '',
|
||||||
|
},
|
||||||
|
sendTemplates: []
|
||||||
|
};
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.fetchData();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
fetchData() {
|
||||||
|
this.listLoading = true;
|
||||||
|
getSendKey().then((response) => {
|
||||||
|
this.keyForm.sendKey = response.data;
|
||||||
|
this.messageForm.sendKey = response.data;
|
||||||
|
let wPath = window.document.location.href;
|
||||||
|
let pathName = this.$route.path;
|
||||||
|
let pos = wPath.indexOf(pathName);
|
||||||
|
let localhostPath = wPath.substring(0, pos);
|
||||||
|
localhostPath = localhostPath.replace("#", "");
|
||||||
|
this.keyForm.sendUrlTitle = localhostPath + 'api/'+ this.keyForm.sendKey+'.send' + "/{title}"
|
||||||
|
this.keyForm.sendUrl = localhostPath + 'api/'+ this.keyForm.sendKey+'.send' + "/{title}/{data}"
|
||||||
|
// this.keyForm.sendUrl = localhostPath + 'api/send?token=' + this.keyForm.sendKey + "&title={title}&data={data}"
|
||||||
|
this.listLoading = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onMessage(fromname) {
|
||||||
|
this.$refs[fromname].validate(valid => {
|
||||||
|
|
||||||
|
if (valid) {
|
||||||
|
let url = this.keyForm.sendUrl.replace("{{title}}}", this.messageForm.title);
|
||||||
|
url.replace("{{data}}}", this.messageForm.data);
|
||||||
|
sendMessage({
|
||||||
|
token: this.keyForm.sendKey,
|
||||||
|
title: this.messageForm.title,
|
||||||
|
data: this.messageForm.data
|
||||||
|
}).then((response) => {
|
||||||
|
if (response.code == 200) {
|
||||||
|
this.$message({
|
||||||
|
message: '发送成功',
|
||||||
|
type: 'success'
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.$message.error('发送失败');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onReSendKey() {
|
||||||
|
reSendKey().then((response) => {
|
||||||
|
if (response.code == 200) {
|
||||||
|
this.$message({
|
||||||
|
message: '重置成功',
|
||||||
|
type: 'success'
|
||||||
|
});
|
||||||
|
this.fetchData();
|
||||||
|
} else {
|
||||||
|
this.$message.error('重置失败');
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.sendkey {
|
||||||
|
width: 300px;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.box-card {
|
||||||
|
margin: 5px;
|
||||||
|
}
|
||||||
|
</style>
|
113
Inotify.Vue/src/views/manager/settingsys/jwt/index.vue
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
<template>
|
||||||
|
<div class="app-container">
|
||||||
|
<el-card>
|
||||||
|
<template #header>
|
||||||
|
<div class="card-header">
|
||||||
|
<span>JWT验证</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<el-form ref="settingForm" :model="jwt" label-width="20%" label-position="right" :rules="jwtRules">
|
||||||
|
<el-form-item prop="clockSkew" label="ClockSkew">
|
||||||
|
<el-input v-model="jwt.clockSkew" placeholder="ClockSkew">></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item prop="audience" label="Audience">
|
||||||
|
<el-input v-model="jwt.audience" placeholder="Audience"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item prop="issuer" label="Issuer">
|
||||||
|
<el-input v-model="jwt.issuer" placeholder="Issuer"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item prop="issuerSigningKey" label="IssuerSigningKey">
|
||||||
|
<el-input type="textarea" v-model="jwt.issuerSigningKey" placeholder="IssuerSigningKey"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item prop="expiration" label="Expiration">
|
||||||
|
<el-input v-model="jwt.expiration" placeholder="Expiration"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" @click="submitForm('settingForm')">确认修改</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
</el-form>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {
|
||||||
|
getJWT,
|
||||||
|
setJWT
|
||||||
|
} from '@/api/systemsetting'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
jwtRules: {
|
||||||
|
clockSkew: [{
|
||||||
|
required: true,
|
||||||
|
message: '请输入clockSkew',
|
||||||
|
trigger: 'blur'
|
||||||
|
}],
|
||||||
|
audience: [{
|
||||||
|
required: true,
|
||||||
|
message: '请输入audience',
|
||||||
|
trigger: 'blur'
|
||||||
|
}],
|
||||||
|
issuer: [{
|
||||||
|
required: true,
|
||||||
|
message: '请输入issuer',
|
||||||
|
trigger: 'blur'
|
||||||
|
}],
|
||||||
|
issuerSigningKey: [{
|
||||||
|
required: true,
|
||||||
|
message: '请输入issuerSigningKey',
|
||||||
|
trigger: 'blur'
|
||||||
|
}],
|
||||||
|
expiration: [{
|
||||||
|
required: true,
|
||||||
|
message: '请输入expiration',
|
||||||
|
trigger: 'blur'
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
|
||||||
|
jwt: {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.fetchData()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
fetchData() {
|
||||||
|
getJWT().then((resposne) => {
|
||||||
|
this.jwt = resposne.data
|
||||||
|
});
|
||||||
|
},
|
||||||
|
submitForm(formName) {
|
||||||
|
this.$refs[formName].validate((valid) => {
|
||||||
|
this.$confirm('您修改了JWT授权,这将影响所有已登录用户导致需要重新登陆,确认修改吗', '警告', {
|
||||||
|
distinguishCancelAndClose: true,
|
||||||
|
confirmButtonText: '确定修改',
|
||||||
|
cancelButtonText: '放弃修改',
|
||||||
|
type: 'warning',
|
||||||
|
center: true,
|
||||||
|
callback: action => {
|
||||||
|
if (action == 'confirm') {
|
||||||
|
setJWT(this.jwt).then((response) => {
|
||||||
|
if (response.code == 200) {
|
||||||
|
this.$message({
|
||||||
|
message: '设置成功',
|
||||||
|
type: 'success'
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
this.$message.error('设置失败');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
109
Inotify.Vue/src/views/manager/settingsys/systemglobal/index.vue
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
<template>
|
||||||
|
<div class="app-container">
|
||||||
|
<el-card>
|
||||||
|
<template #header>
|
||||||
|
<div class="card-header">
|
||||||
|
<span>参数设置</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<el-form ref="settingForm" :model="settingForm" label-width="20%" label-position="right">
|
||||||
|
<el-form-item prop="sendthread" label="发送线程">
|
||||||
|
<el-input ref="sendthread" name="thread" v-model="settingForm.sendthread" placeholder="2">></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-divider content-position="left">管理权限</el-divider>
|
||||||
|
<el-form-item label="用户名">
|
||||||
|
<el-input v-model="settingForm.administrators" placeholder="admin|demon">></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-divider content-position="left">代理设置</el-divider>
|
||||||
|
<el-form-item prop="proxy" label="代理地址">
|
||||||
|
<el-input ref="proxy" name="proxy" v-model="settingForm.proxy" placeholder="http://127.0.0.1:1080"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item prop="proxy" label="启用代理">
|
||||||
|
<template>
|
||||||
|
<el-switch active-color='#13ce66' v-model='settingForm.proxyenable' inactive-color='#ff4949'></el-switch>
|
||||||
|
</template>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-divider content-position="left">Github登陆</el-divider>
|
||||||
|
<el-form-item label="应用ID">
|
||||||
|
<el-input v-model="settingForm.githubClientID" placeholder="Client ID">></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="应用密钥">
|
||||||
|
<el-input v-model="settingForm.githubClientSecret" placeholder="Client secrets"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="启用登陆">
|
||||||
|
<template>
|
||||||
|
<el-switch active-color='#13ce66' v-model='settingForm.githubEnable' inactive-color='#ff4949'></el-switch>
|
||||||
|
</template>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" @click="submitForm('settingForm')">确认修改</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {
|
||||||
|
getGlobal,
|
||||||
|
setGlobal
|
||||||
|
} from '@/api/systemsetting'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
settingForm: {
|
||||||
|
sendthread: '',
|
||||||
|
administrators: '',
|
||||||
|
proxy: '',
|
||||||
|
proxyenable: false,
|
||||||
|
githubClientID: '',
|
||||||
|
githubClientSecret: '',
|
||||||
|
githubEnable: false
|
||||||
|
},
|
||||||
|
jwt: {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.fetchData()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
fetchData() {
|
||||||
|
|
||||||
|
getGlobal().then((resposne) => {
|
||||||
|
this.settingForm = resposne.data
|
||||||
|
});
|
||||||
|
},
|
||||||
|
submitForm(formName) {
|
||||||
|
|
||||||
|
this.$confirm('您修改了全局参数,这将变更管理员及三方验证,确认修改吗', '警告', {
|
||||||
|
distinguishCancelAndClose: true,
|
||||||
|
confirmButtonText: '确定修改',
|
||||||
|
cancelButtonText: '放弃修改',
|
||||||
|
type: 'warning',
|
||||||
|
center: true,
|
||||||
|
callback: action => {
|
||||||
|
if (action == 'confirm') {
|
||||||
|
this.$refs[formName].validate((valid) => {
|
||||||
|
setGlobal(this.settingForm).then((response) => {
|
||||||
|
if (response.code == 200) {
|
||||||
|
this.$message({
|
||||||
|
message: '设置成功',
|
||||||
|
type: 'success'
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.$message.error('设置失败');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
@ -0,0 +1,98 @@
|
|||||||
|
<template>
|
||||||
|
<div class="app-container">
|
||||||
|
<div class="block" align="center">
|
||||||
|
<el-date-picker :default-time="['00:00:00', '23:59:59']" @change="change" v-model="dateValue" type="daterange" range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期"> </el-date-picker>
|
||||||
|
</div>
|
||||||
|
<div align="center">
|
||||||
|
<div id="SendTypeInfo" style="width: 80%; height: 350px"></div>
|
||||||
|
<div id="SendInfo" style="width: 80%; height: 400px" align="center"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import moment from 'moment'
|
||||||
|
import echarts from 'echarts'
|
||||||
|
import {
|
||||||
|
getSendTypeInfos,
|
||||||
|
getSendInfos
|
||||||
|
} from '@/api/systemsetting'
|
||||||
|
export default {
|
||||||
|
filters: {
|
||||||
|
statusFilter(status) {
|
||||||
|
const statusMap = {
|
||||||
|
published: 'success',
|
||||||
|
draft: 'gray',
|
||||||
|
deleted: 'danger',
|
||||||
|
}
|
||||||
|
return statusMap[status]
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
SendTypeInfoChart: '',
|
||||||
|
SendTypeInfoChartData: [{
|
||||||
|
value: 1,
|
||||||
|
name: '全部',
|
||||||
|
}, ],
|
||||||
|
SendInfoChart: '',
|
||||||
|
listLoading: false,
|
||||||
|
dateValue: '',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.fetchData()
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
const start = new Date()
|
||||||
|
const end = new Date()
|
||||||
|
start.setTime(end.getTime() - 3600 * 1000 * 24 * 30)
|
||||||
|
this.dateValue = [start, end]
|
||||||
|
this.change()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
fetchData() {},
|
||||||
|
change() {
|
||||||
|
const startDate = new moment(this.dateValue[0]).format('YYYYMMDD')
|
||||||
|
const endData = new moment(this.dateValue[1]).format('YYYYMMDD')
|
||||||
|
this.drawSendInfo(startDate, endData)
|
||||||
|
},
|
||||||
|
drawSendInfo(start, end) {
|
||||||
|
this.listLoading = true;
|
||||||
|
this.SendInfoChart = echarts.init(document.getElementById('SendInfo'))
|
||||||
|
this.SendTypeInfoChart = echarts.init(document.getElementById('SendTypeInfo'))
|
||||||
|
getSendInfos(start, end).then((response) => {
|
||||||
|
this.SendTypeInfoChart.setOption({
|
||||||
|
roseType: 'angle',
|
||||||
|
tooltip: {},
|
||||||
|
series: [{
|
||||||
|
name: '消息类型',
|
||||||
|
type: 'pie',
|
||||||
|
radius: '55%',
|
||||||
|
data: response.data.items,
|
||||||
|
}, ],
|
||||||
|
})
|
||||||
|
|
||||||
|
this.SendInfoChart.setOption({
|
||||||
|
title: {
|
||||||
|
text: '消息统计',
|
||||||
|
},
|
||||||
|
tooltip: {},
|
||||||
|
xAxis: {
|
||||||
|
type: 'category',
|
||||||
|
data: response.data.dataX,
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
type: 'value',
|
||||||
|
},
|
||||||
|
series: [{
|
||||||
|
data: response.data.dataY,
|
||||||
|
type: 'line',
|
||||||
|
}, ]
|
||||||
|
})
|
||||||
|
this.listLoading = false;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
135
Inotify.Vue/src/views/manager/settingsys/usermanager/index.vue
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
<template>
|
||||||
|
<div class="app-container">
|
||||||
|
<div style="margin-top:10px;margin-bottom:10px;width:400px;">
|
||||||
|
<el-input v-model='query' @change='search' width='250' placeholder="请输入内容" >
|
||||||
|
<el-button slot="append" icon="el-icon-search"></el-button>
|
||||||
|
</el-input>
|
||||||
|
</div>
|
||||||
|
<el-table style="height:100%" v-loading='listLoading' :data='data.items' element-loading-text='加载中' border fit highlight-current-row>
|
||||||
|
<el-table-column align='center' label='序号' width='95'>
|
||||||
|
<template slot-scope='scope'>
|
||||||
|
{{ scope.$index+1 }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label='用户名' width='110' align='center'>
|
||||||
|
<template slot-scope='scope'>
|
||||||
|
<span>{{ scope.row.userName }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label='邮箱' align='center'>
|
||||||
|
<template slot-scope='scope'>
|
||||||
|
<span>{{ scope.row.email }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label='创建时间' width='160' align='center'>
|
||||||
|
<template slot-scope='scope'>
|
||||||
|
<el-date-picker type="date" v-model='scope.row.createTime' value-format="yyyy-MM-dd" style="width: 100%;" readonly></el-date-picker>
|
||||||
|
<span hidden>{{ scope.row.email }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column align='center' prop='created_at' label='编辑' width='100'>
|
||||||
|
<template slot-scope='scope'>
|
||||||
|
<el-button-group>
|
||||||
|
<!-- <el-button type='primary' icon='el-icon-edit' @click='modifyuser(scope.$index, scope.row)'></el-button> -->
|
||||||
|
<el-button type='danger' icon='el-icon-delete' @click='deleteuser(scope.$index, scope.row)'></el-button>
|
||||||
|
</el-button-group>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column class-name='status-col' label='激活' width='110' align='center'>
|
||||||
|
<template slot-scope='scope'>
|
||||||
|
<el-switch active-color='#13ce66' v-model='scope.row.active' @change='activeuser(scope.row)' inactive-color='#ff4949'></el-switch>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
<div class="block" style="text-align: right;">
|
||||||
|
<el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange" layout="total, sizes, prev, pager, next, jumper" :current-page="currentPage" :page-sizes="[10, 20, 50, 100]" :page-size="pageSize" :total="data.totalItems">
|
||||||
|
</el-pagination>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {
|
||||||
|
getUsers,
|
||||||
|
deleteUser,
|
||||||
|
activeUser
|
||||||
|
} from '@/api/systemsetting'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
query: '',
|
||||||
|
listLoading: false,
|
||||||
|
currentPage: 0,
|
||||||
|
pageSize: 10,
|
||||||
|
data: {
|
||||||
|
currentPage: 0,
|
||||||
|
totalPages: 0,
|
||||||
|
totalItems: 0,
|
||||||
|
itemsPerPage: 10,
|
||||||
|
items: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.fetchData()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
|
||||||
|
fetchData() {
|
||||||
|
getUsers(this.query, this.currentPage, this.pageSize).then((response) => {
|
||||||
|
this.data = response.data;
|
||||||
|
})
|
||||||
|
},
|
||||||
|
handleCurrentChange(val) {
|
||||||
|
this.currentPage = val;
|
||||||
|
this.fetchData()
|
||||||
|
},
|
||||||
|
handleSizeChange(val) {
|
||||||
|
this.pageSize = val
|
||||||
|
this.fetchData()
|
||||||
|
},
|
||||||
|
search() {
|
||||||
|
this.fetchData()
|
||||||
|
},
|
||||||
|
|
||||||
|
modifyuser(index, row) {
|
||||||
|
|
||||||
|
},
|
||||||
|
deleteuser(index, row) {
|
||||||
|
|
||||||
|
this.$confirm('确认删除用户:' + row.userName, '警告', {
|
||||||
|
distinguishCancelAndClose: true,
|
||||||
|
confirmButtonText: '确定删除',
|
||||||
|
cancelButtonText: '放弃删除',
|
||||||
|
type: 'warning',
|
||||||
|
center: true,
|
||||||
|
callback: action => {
|
||||||
|
if (action == 'confirm') {
|
||||||
|
deleteUser(row.userName).then((response) => {
|
||||||
|
if (response.code == 200) {
|
||||||
|
this.$message.success(row.userName + '删除成功')
|
||||||
|
this.fetchData()
|
||||||
|
} else {
|
||||||
|
this.$message.error(row.userName + '删除失败')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
activeuser(row) {
|
||||||
|
var active = row.active;
|
||||||
|
activeUser(row.userName, active).then((response) => {
|
||||||
|
if (response.code == 200) {
|
||||||
|
this.$message.success(row.userName + (active ? '激活成功' : '禁用成功'))
|
||||||
|
this.fetchData()
|
||||||
|
} else {
|
||||||
|
this.$message.error(row.userName + (active ? '激活失败' : '禁用成功'))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
5
Inotify.Vue/tests/unit/.eslintrc.js
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
module.exports = {
|
||||||
|
env: {
|
||||||
|
jest: true
|
||||||
|
}
|
||||||
|
}
|
98
Inotify.Vue/tests/unit/components/Breadcrumb.spec.js
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
import { mount, createLocalVue } from '@vue/test-utils'
|
||||||
|
import VueRouter from 'vue-router'
|
||||||
|
import ElementUI from 'element-ui'
|
||||||
|
import Breadcrumb from '@/components/Breadcrumb/index.vue'
|
||||||
|
|
||||||
|
const localVue = createLocalVue()
|
||||||
|
localVue.use(VueRouter)
|
||||||
|
localVue.use(ElementUI)
|
||||||
|
|
||||||
|
const routes = [
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
name: 'home',
|
||||||
|
children: [{
|
||||||
|
path: 'dashboard',
|
||||||
|
name: 'dashboard'
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/menu',
|
||||||
|
name: 'menu',
|
||||||
|
children: [{
|
||||||
|
path: 'menu1',
|
||||||
|
name: 'menu1',
|
||||||
|
meta: { title: 'menu1' },
|
||||||
|
children: [{
|
||||||
|
path: 'menu1-1',
|
||||||
|
name: 'menu1-1',
|
||||||
|
meta: { title: 'menu1-1' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'menu1-2',
|
||||||
|
name: 'menu1-2',
|
||||||
|
redirect: 'noredirect',
|
||||||
|
meta: { title: 'menu1-2' },
|
||||||
|
children: [{
|
||||||
|
path: 'menu1-2-1',
|
||||||
|
name: 'menu1-2-1',
|
||||||
|
meta: { title: 'menu1-2-1' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'menu1-2-2',
|
||||||
|
name: 'menu1-2-2'
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
|
||||||
|
const router = new VueRouter({
|
||||||
|
routes
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Breadcrumb.vue', () => {
|
||||||
|
const wrapper = mount(Breadcrumb, {
|
||||||
|
localVue,
|
||||||
|
router
|
||||||
|
})
|
||||||
|
it('dashboard', () => {
|
||||||
|
router.push('/dashboard')
|
||||||
|
const len = wrapper.findAll('.el-breadcrumb__inner').length
|
||||||
|
expect(len).toBe(1)
|
||||||
|
})
|
||||||
|
it('normal route', () => {
|
||||||
|
router.push('/menu/menu1')
|
||||||
|
const len = wrapper.findAll('.el-breadcrumb__inner').length
|
||||||
|
expect(len).toBe(2)
|
||||||
|
})
|
||||||
|
it('nested route', () => {
|
||||||
|
router.push('/menu/menu1/menu1-2/menu1-2-1')
|
||||||
|
const len = wrapper.findAll('.el-breadcrumb__inner').length
|
||||||
|
expect(len).toBe(4)
|
||||||
|
})
|
||||||
|
it('no meta.title', () => {
|
||||||
|
router.push('/menu/menu1/menu1-2/menu1-2-2')
|
||||||
|
const len = wrapper.findAll('.el-breadcrumb__inner').length
|
||||||
|
expect(len).toBe(3)
|
||||||
|
})
|
||||||
|
// it('click link', () => {
|
||||||
|
// router.push('/menu/menu1/menu1-2/menu1-2-2')
|
||||||
|
// const breadcrumbArray = wrapper.findAll('.el-breadcrumb__inner')
|
||||||
|
// const second = breadcrumbArray.at(1)
|
||||||
|
// console.log(breadcrumbArray)
|
||||||
|
// const href = second.find('a').attributes().href
|
||||||
|
// expect(href).toBe('#/menu/menu1')
|
||||||
|
// })
|
||||||
|
// it('noRedirect', () => {
|
||||||
|
// router.push('/menu/menu1/menu1-2/menu1-2-1')
|
||||||
|
// const breadcrumbArray = wrapper.findAll('.el-breadcrumb__inner')
|
||||||
|
// const redirectBreadcrumb = breadcrumbArray.at(2)
|
||||||
|
// expect(redirectBreadcrumb.contains('a')).toBe(false)
|
||||||
|
// })
|
||||||
|
it('last breadcrumb', () => {
|
||||||
|
router.push('/menu/menu1/menu1-2/menu1-2-1')
|
||||||
|
const breadcrumbArray = wrapper.findAll('.el-breadcrumb__inner')
|
||||||
|
const redirectBreadcrumb = breadcrumbArray.at(3)
|
||||||
|
expect(redirectBreadcrumb.contains('a')).toBe(false)
|
||||||
|
})
|
||||||
|
})
|
18
Inotify.Vue/tests/unit/components/Hamburger.spec.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { shallowMount } from '@vue/test-utils'
|
||||||
|
import Hamburger from '@/components/Hamburger/index.vue'
|
||||||
|
describe('Hamburger.vue', () => {
|
||||||
|
it('toggle click', () => {
|
||||||
|
const wrapper = shallowMount(Hamburger)
|
||||||
|
const mockFn = jest.fn()
|
||||||
|
wrapper.vm.$on('toggleClick', mockFn)
|
||||||
|
wrapper.find('.hamburger').trigger('click')
|
||||||
|
expect(mockFn).toBeCalled()
|
||||||
|
})
|
||||||
|
it('prop isActive', () => {
|
||||||
|
const wrapper = shallowMount(Hamburger)
|
||||||
|
wrapper.setProps({ isActive: true })
|
||||||
|
expect(wrapper.contains('.is-active')).toBe(true)
|
||||||
|
wrapper.setProps({ isActive: false })
|
||||||
|
expect(wrapper.contains('.is-active')).toBe(false)
|
||||||
|
})
|
||||||
|
})
|
22
Inotify.Vue/tests/unit/components/SvgIcon.spec.js
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { shallowMount } from '@vue/test-utils'
|
||||||
|
import SvgIcon from '@/components/SvgIcon/index.vue'
|
||||||
|
describe('SvgIcon.vue', () => {
|
||||||
|
it('iconClass', () => {
|
||||||
|
const wrapper = shallowMount(SvgIcon, {
|
||||||
|
propsData: {
|
||||||
|
iconClass: 'test'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
expect(wrapper.find('use').attributes().href).toBe('#icon-test')
|
||||||
|
})
|
||||||
|
it('className', () => {
|
||||||
|
const wrapper = shallowMount(SvgIcon, {
|
||||||
|
propsData: {
|
||||||
|
iconClass: 'test'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
expect(wrapper.classes().length).toBe(1)
|
||||||
|
wrapper.setProps({ className: 'test' })
|
||||||
|
expect(wrapper.classes().includes('test')).toBe(true)
|
||||||
|
})
|
||||||
|
})
|
30
Inotify.Vue/tests/unit/utils/formatTime.spec.js
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { formatTime } from '@/utils/index.js'
|
||||||
|
|
||||||
|
describe('Utils:formatTime', () => {
|
||||||
|
const d = new Date('2018-07-13 17:54:01') // "2018-07-13 17:54:01"
|
||||||
|
const retrofit = 5 * 1000
|
||||||
|
|
||||||
|
it('ten digits timestamp', () => {
|
||||||
|
expect(formatTime((d / 1000).toFixed(0))).toBe('7月13日17时54分')
|
||||||
|
})
|
||||||
|
it('test now', () => {
|
||||||
|
expect(formatTime(+new Date() - 1)).toBe('刚刚')
|
||||||
|
})
|
||||||
|
it('less two minute', () => {
|
||||||
|
expect(formatTime(+new Date() - 60 * 2 * 1000 + retrofit)).toBe('2分钟前')
|
||||||
|
})
|
||||||
|
it('less two hour', () => {
|
||||||
|
expect(formatTime(+new Date() - 60 * 60 * 2 * 1000 + retrofit)).toBe('2小时前')
|
||||||
|
})
|
||||||
|
it('less one day', () => {
|
||||||
|
expect(formatTime(+new Date() - 60 * 60 * 24 * 1 * 1000)).toBe('1天前')
|
||||||
|
})
|
||||||
|
it('more than one day', () => {
|
||||||
|
expect(formatTime(d)).toBe('7月13日17时54分')
|
||||||
|
})
|
||||||
|
it('format', () => {
|
||||||
|
expect(formatTime(d, '{y}-{m}-{d} {h}:{i}')).toBe('2018-07-13 17:54')
|
||||||
|
expect(formatTime(d, '{y}-{m}-{d}')).toBe('2018-07-13')
|
||||||
|
expect(formatTime(d, '{y}/{m}/{d} {h}-{i}')).toBe('2018/07/13 17-54')
|
||||||
|
})
|
||||||
|
})
|