| @@ -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 | |||||
| @@ -0,0 +1,5 @@ | |||||
| # just a flag | |||||
| ENV = 'development' | |||||
| # base api | |||||
| VUE_APP_BASE_API = 'https://admin.meta-autotv.com/homeA/' | |||||
| @@ -0,0 +1,6 @@ | |||||
| NODE_ENV = 'production' | |||||
| OUTPUT_DIR = 'int' | |||||
| VUE_APP_ENV = 'int' | |||||
| # base api | |||||
| VUE_APP_BASE_API = '/adcop_cloud/' | |||||
| @@ -0,0 +1,7 @@ | |||||
| NODE_ENV = 'production' | |||||
| OUTPUT_DIR = 'pro' | |||||
| VUE_APP_ENV = 'production' | |||||
| # base api | |||||
| VUE_APP_BASE_API = '/' | |||||
| @@ -0,0 +1,4 @@ | |||||
| build/*.js | |||||
| src/assets | |||||
| public | |||||
| dist | |||||
| @@ -0,0 +1,200 @@ | |||||
| 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/html-closing-bracket-spacing': 'off', | |||||
| 'vue/no-multi-spaces': 'off', | |||||
| 'no-useless-catch': 'off', | |||||
| 'vue/require-default-prop': 'off', | |||||
| 'vue/require-prop-types': 'off', | |||||
| 'vue/html-self-closing': "off", | |||||
| "vue/max-attributes-per-line": "off", | |||||
| "vue/attributes-order": "off", | |||||
| "vue/html-indent": "off", | |||||
| "vue/html-quotes": "off", | |||||
| "vue/html-closing-bracket-newline": "off", | |||||
| "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 | |||||
| }], */ | |||||
| 'indent': "off", | |||||
| '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': 'off', | |||||
| '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': 0, | |||||
| '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': [2, 'never'], | |||||
| 'space-in-parens': [0, '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': 0, | |||||
| 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0, | |||||
| 'object-curly-spacing': [2, 'always', { | |||||
| objectsInObjects: false | |||||
| }], | |||||
| 'array-bracket-spacing': [2, 'never'] | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,21 @@ | |||||
| .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 | |||||
| /int | |||||
| /stg | |||||
| /pro | |||||
| /uat | |||||
| @@ -0,0 +1,36 @@ | |||||
| # 后台管理系统 | |||||
| #### 前端模板 | |||||
| 初始模板基于: [https://github.com/PanJiaChen/vue-element-admin](https://github.com/PanJiaChen/vue-element-admin) | |||||
| 模板文档: [https://panjiachen.github.io/vue-element-admin-site/zh/guide/](https://panjiachen.github.io/vue-element-admin-site/zh/guide/) | |||||
| #### Build Setup | |||||
| ``` bash | |||||
| # 安装依赖 | |||||
| npm install | |||||
| # 启动服务 | |||||
| npm run dev | |||||
| # 构建INT、Stage、生产环境 | |||||
| npm run int | |||||
| npm run stg | |||||
| npm run pro | |||||
| ``` | |||||
| #### 常见问题 | |||||
| 1、linux 系统在安装依赖的时候会出现 node-sass 无法安装的问题 | |||||
| 解决方案: | |||||
| ``` | |||||
| 1. 单独安装:npm install --unsafe-perm node-sass | |||||
| 2. 直接使用:npm install --unsafe-perm | |||||
| ``` | |||||
| 2、加速node-sass安装 | |||||
| https://www.ydyno.com/archives/1219.html | |||||
| @@ -0,0 +1,19 @@ | |||||
| const prodPlugins = [] | |||||
| if (process.env.VUE_APP_ENV === 'production') { | |||||
| prodPlugins.push('transform-remove-console') | |||||
| } | |||||
| module.exports = { | |||||
| plugins: [...prodPlugins], | |||||
| 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'] | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -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}`) | |||||
| } | |||||
| @@ -0,0 +1,65 @@ | |||||
| { | |||||
| "name": "admin-manage", | |||||
| "version": "1.0.0", | |||||
| "description": "后台管理系统", | |||||
| "author": "", | |||||
| "scripts": { | |||||
| "dev": "vue-cli-service serve", | |||||
| "pro": "vue-cli-service build", | |||||
| "int": "vue-cli-service build --mode int", | |||||
| "lint": "eslint --ext .js,.vue src" | |||||
| }, | |||||
| "dependencies": { | |||||
| "axios": "0.18.1", | |||||
| "clipboard": "^2.0.8", | |||||
| "core-js": "3.6.5", | |||||
| "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", | |||||
| "vue-ueditor-wrap": "^2.5.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.0.1", | |||||
| "babel-jest": "23.6.0", | |||||
| "babel-plugin-dynamic-import-node": "2.3.3", | |||||
| "babel-plugin-transform-remove-console": "^6.9.4", | |||||
| "chalk": "2.4.2", | |||||
| "connect": "3.6.6", | |||||
| "eslint": "^6.7.2", | |||||
| "eslint-plugin-vue": "6.2.2", | |||||
| "html-webpack-plugin": "3.2.0", | |||||
| "husky": "^4.3.8", | |||||
| "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", | |||||
| "vue-template-compiler": "2.6.10" | |||||
| }, | |||||
| "browserslist": [ | |||||
| "> 1%", | |||||
| "last 2 versions" | |||||
| ], | |||||
| "engines": { | |||||
| "node": ">=8.9", | |||||
| "npm": ">= 3.0.0" | |||||
| }, | |||||
| "husky": { | |||||
| "hooks": { | |||||
| "pre-commit": "npm run lint" | |||||
| } | |||||
| }, | |||||
| "license": "MIT" | |||||
| } | |||||
| @@ -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': {} | |||||
| } | |||||
| } | |||||
| @@ -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> | |||||
| @@ -0,0 +1,16 @@ | |||||
| <template> | |||||
| <div id="app"> | |||||
| <router-view /> | |||||
| </div> | |||||
| </template> | |||||
| <script> | |||||
| export default { | |||||
| name: 'App', | |||||
| methods: { | |||||
| test() { | |||||
| const a = 0 | |||||
| } | |||||
| } | |||||
| } | |||||
| </script> | |||||
| @@ -0,0 +1,75 @@ | |||||
| /* | |||||
| * @Date: 2021-08-24 14:51:18 | |||||
| * @LastEditTime: 2022-07-27 10:47:43 | |||||
| * @Description: 汽车资讯 | |||||
| */ | |||||
| import request from '@/utils/request' | |||||
| /** 轮播 */ | |||||
| export function saveLunbo(data) { | |||||
| return request({ | |||||
| url: '/car/saveLunbo', | |||||
| method: 'post', | |||||
| data | |||||
| }) | |||||
| } | |||||
| export function delLunbo(id) { | |||||
| return request({ | |||||
| url: '/index/delLunbo', | |||||
| method: 'post', | |||||
| data: { id } | |||||
| }) | |||||
| } | |||||
| /** 热点新闻配置(是否显示) */ | |||||
| export function hotNewsConfig() { | |||||
| return request({ | |||||
| url: '/car/hotNewsConfig', | |||||
| method: 'get' | |||||
| }) | |||||
| } | |||||
| export function saveHotNewsConfig(data) { | |||||
| return request({ | |||||
| url: '/index/saveHotNewsConfig', | |||||
| method: 'post', | |||||
| data | |||||
| }) | |||||
| } | |||||
| /** 广告位1 */ | |||||
| export function guanggao1() { | |||||
| return request({ | |||||
| url: '/car/guanggao1', | |||||
| method: 'get' | |||||
| }) | |||||
| } | |||||
| export function saveGuangGao(data) { | |||||
| return request({ | |||||
| url: '/index/saveGuangGao', | |||||
| method: 'post', | |||||
| data | |||||
| }) | |||||
| } | |||||
| /** 视频轮播 */ | |||||
| export function videoSave(data) { | |||||
| return request({ | |||||
| url: '/car/videoSave', | |||||
| method: 'post', | |||||
| data | |||||
| }) | |||||
| } | |||||
| /** 广告位2 */ | |||||
| export function guanggao2() { | |||||
| return request({ | |||||
| url: '/car/guanggao2', | |||||
| method: 'get' | |||||
| }) | |||||
| } | |||||
| @@ -0,0 +1,17 @@ | |||||
| import request from '@/utils/request' | |||||
| export function upload(data) { | |||||
| var formData = new FormData() | |||||
| formData.append('file', data.file) | |||||
| console.log('upload:', data) | |||||
| return request({ | |||||
| url: '/upload/awsImgUpload', | |||||
| method: 'post', | |||||
| data: formData, | |||||
| timeout: 1000 * 60 * 2, // 超时时间两分钟 | |||||
| params: { | |||||
| width: 200, | |||||
| hight: 200 | |||||
| } | |||||
| }) | |||||
| } | |||||
| @@ -0,0 +1,18 @@ | |||||
| import request from '@/utils/request' | |||||
| import qs from 'qs' | |||||
| export function initData(url, params) { | |||||
| // console.log(qs.stringify(params, { indices: false })) | |||||
| return request({ | |||||
| url: url + '?' + qs.stringify(params, { indices: false }), | |||||
| method: 'get' | |||||
| }) | |||||
| } | |||||
| export function download(url, params) { | |||||
| return request({ | |||||
| url: url + '?' + qs.stringify(params, { indices: false }), | |||||
| method: 'get', | |||||
| responseType: 'blob' | |||||
| }) | |||||
| } | |||||
| @@ -0,0 +1,71 @@ | |||||
| /* | |||||
| * @Date: 2021-08-24 14:51:18 | |||||
| * @LastEditTime: 2022-07-27 10:47:43 | |||||
| * @Description: 汽车资讯 | |||||
| */ | |||||
| import request from '@/utils/request' | |||||
| /** 轮播 */ | |||||
| export function saveLunbo(data) { | |||||
| return request({ | |||||
| url: '/index/saveLunbo', | |||||
| method: 'post', | |||||
| data | |||||
| }) | |||||
| } | |||||
| export function delLunbo(id) { | |||||
| return request({ | |||||
| url: '/index/delLunbo', | |||||
| method: 'post', | |||||
| data: { id } | |||||
| }) | |||||
| } | |||||
| /** 热点新闻配置(是否显示) */ | |||||
| export function carNewsHotSave(newsIdList) { | |||||
| return request({ | |||||
| url: ' /index/carNewsHotSave', | |||||
| method: 'post', | |||||
| data: { newsIdList } | |||||
| }) | |||||
| } | |||||
| export function hotNewsConfig() { | |||||
| return request({ | |||||
| url: '/index/hotNewsConfig', | |||||
| method: 'get' | |||||
| }) | |||||
| } | |||||
| export function saveHotNewsConfig(data) { | |||||
| return request({ | |||||
| url: '/index/saveHotNewsConfig', | |||||
| method: 'post', | |||||
| data | |||||
| }) | |||||
| } | |||||
| /** 广告位1 */ | |||||
| export function guanggao1() { | |||||
| return request({ | |||||
| url: '/index/guanggao1', | |||||
| method: 'get' | |||||
| }) | |||||
| } | |||||
| export function saveGuangGao(data) { | |||||
| return request({ | |||||
| url: '/index/saveGuangGao', | |||||
| method: 'post', | |||||
| data | |||||
| }) | |||||
| } | |||||
| /** 广告位2 */ | |||||
| export function guanggao2() { | |||||
| return request({ | |||||
| url: '/index/guanggao2', | |||||
| method: 'get' | |||||
| }) | |||||
| } | |||||
| @@ -0,0 +1,164 @@ | |||||
| import request from '@/utils/request.js' | |||||
| const baseURL = process.env.VUE_APP_BASE_API | |||||
| /** | |||||
| * @description 登录 | |||||
| * @params email,password,code | |||||
| * @returns data | |||||
| */ | |||||
| export function loginApi(data) { | |||||
| return request({ | |||||
| url: `/doLogin`, | |||||
| method: 'post', | |||||
| data | |||||
| }) | |||||
| } | |||||
| /** | |||||
| * @description 退出登录 | |||||
| * @params email,password,code | |||||
| * @returns data | |||||
| */ | |||||
| export function logoutApi(data) { | |||||
| return request({ | |||||
| // url: `/api/user/loginByEmail`, | |||||
| url: `/logout`, | |||||
| method: 'get' | |||||
| }) | |||||
| } | |||||
| /** | |||||
| /** | |||||
| * @description:重新发送邮件 | |||||
| * @param {string} data | |||||
| * @return: data | |||||
| */ | |||||
| export function sendRegisterEmail(email) { | |||||
| return request({ | |||||
| url: `/api/user/sendRegisterEmail`, | |||||
| method: 'post', | |||||
| data: { | |||||
| } | |||||
| }) | |||||
| } | |||||
| /** | |||||
| /** | |||||
| * @description:忘记密码发送邮件 | |||||
| * @param {string} data | |||||
| * @return: data | |||||
| */ | |||||
| // export function sendUpdPwdEmailApi(email) { | |||||
| // return request({ | |||||
| // url: `/api/user/sendUpdPwdEmail`, | |||||
| // method: 'post', | |||||
| // data: { | |||||
| // } | |||||
| // }) | |||||
| // } | |||||
| export function sendUpdPwdEmailApi(data) { | |||||
| return request({ | |||||
| url: `/api/user/sendPhoneCode?phone=${data.phone}&code=${data.code}`, | |||||
| method: 'get' | |||||
| }) | |||||
| } | |||||
| /** `/api/user/sendLoginPhoneCode?phone=${phone}` | |||||
| * @description 忘记密码第二步,短信验证码和新密码 | |||||
| * @param {object} loginParams (phone,code,pwd) | |||||
| * @returns data | |||||
| */ | |||||
| export function doUpdPassApi(loginParams) { | |||||
| return request({ | |||||
| url: `/api/user/updPass`, | |||||
| method: 'post', | |||||
| data: loginParams | |||||
| }) | |||||
| } | |||||
| /** | |||||
| * @description 获取手机验证码 | |||||
| * @param {number} phone | |||||
| * @returns data | |||||
| */ | |||||
| export function getSMSToLoginApi(phone) { | |||||
| return request({ | |||||
| url: `/api/user/sendLoginPhoneCode?phone=${phone}`, | |||||
| method: 'get' | |||||
| }) | |||||
| } | |||||
| /** | |||||
| * @description 短信验证码登录 | |||||
| * @param {object} loginParams (phone,code) | |||||
| * @returns data | |||||
| */ | |||||
| export function loginByMSMApi(loginParams) { | |||||
| return request({ | |||||
| url: `/api/user/doLoginByPhone`, | |||||
| method: 'post', | |||||
| data: loginParams | |||||
| }) | |||||
| } | |||||
| /** | |||||
| * @description 查询用户邀请码状态 | |||||
| * @returns data | |||||
| */ | |||||
| export function doFindInviteCode() { | |||||
| return request({ | |||||
| url: `/api/inviteCode/find`, | |||||
| method: 'get' | |||||
| }) | |||||
| } | |||||
| /** | |||||
| * @description 更新邀请码 | |||||
| * @param {object} inviteCodeData | |||||
| * @returns data | |||||
| */ | |||||
| export function doUpdateInviteCode(inviteCodeData) { | |||||
| return request({ | |||||
| url: `/api/inviteCode/update`, | |||||
| method: 'post', | |||||
| data: inviteCodeData | |||||
| }) | |||||
| } | |||||
| /** | |||||
| /** | |||||
| * @description:邮件重置密码, | |||||
| * @param pwd, ticket | |||||
| * @return: data | |||||
| */ | |||||
| export function resetPwdEmailApi(data) { | |||||
| return request({ | |||||
| url: `/api/user/doUpdPass`, | |||||
| method: 'post', | |||||
| data | |||||
| }) | |||||
| } | |||||
| /** | |||||
| /** | |||||
| * @description:邮件重置密码, | |||||
| * @param oldpwd, newpwd | |||||
| * @return: data | |||||
| */ | |||||
| export function resetPwdFormApi(data) { | |||||
| return request({ | |||||
| url: `/api/user/oldUpdPass`, | |||||
| method: 'post', | |||||
| data | |||||
| }) | |||||
| } | |||||
| /** | |||||
| * @description:获取图形验证码 | |||||
| * @return: 验证码地址 | |||||
| */ | |||||
| export const getCodeImgUrl = baseURL + '/captcha.jpg' | |||||
| @@ -0,0 +1,42 @@ | |||||
| /* | |||||
| * @Date: 2021-08-24 14:51:18 | |||||
| * @LastEditTime: 2022-07-27 10:47:43 | |||||
| * @Description: 列表demo API | |||||
| */ | |||||
| import request from '@/utils/request' | |||||
| export function del(data) { | |||||
| return request({ | |||||
| url: '', | |||||
| method: 'post', | |||||
| data: { data } | |||||
| }) | |||||
| } | |||||
| /* 导出 */ | |||||
| export function exportFile(data) { | |||||
| return request({ | |||||
| url: '', | |||||
| method: 'get', | |||||
| params: data, | |||||
| responseType: 'blob' | |||||
| }) | |||||
| } | |||||
| export function getData(id) { | |||||
| return request({ | |||||
| url: '', | |||||
| method: 'post', | |||||
| data: { id } | |||||
| }) | |||||
| } | |||||
| export function save(data) { | |||||
| return request({ | |||||
| url: '', | |||||
| method: 'post', | |||||
| data: { data } | |||||
| }) | |||||
| } | |||||
| export default { del, getData, save, exportFile } | |||||
| @@ -0,0 +1,24 @@ | |||||
| import request from '@/utils/request' | |||||
| export function login(data) { | |||||
| return request({ | |||||
| url: '/vue-admin-template/user/login', | |||||
| method: 'post', | |||||
| data | |||||
| }) | |||||
| } | |||||
| export function getInfo(token) { | |||||
| return request({ | |||||
| url: '/vue-admin-template/user/info', | |||||
| method: 'get', | |||||
| params: { token } | |||||
| }) | |||||
| } | |||||
| export function logout() { | |||||
| return request({ | |||||
| url: '/vue-admin-template/user/logout', | |||||
| method: 'post' | |||||
| }) | |||||
| } | |||||
| @@ -0,0 +1,63 @@ | |||||
| @font-face { | |||||
| font-family: "iconfont"; /* Project id 2589074 */ | |||||
| src: url('./iconfont.woff2?t=1622700365894') format('woff2'), | |||||
| url('./iconfont.woff?t=1622700365894') format('woff'), | |||||
| url('./iconfont.ttf?t=1622700365894') format('truetype'); | |||||
| } | |||||
| .icon { | |||||
| font-family: "iconfont" !important; | |||||
| font-size: 16px; | |||||
| font-style: normal; | |||||
| -webkit-font-smoothing: antialiased; | |||||
| -moz-osx-font-smoothing: grayscale; | |||||
| } | |||||
| .icon-table:before { | |||||
| content: "\e652"; | |||||
| } | |||||
| .icon-form:before { | |||||
| content: "\e653"; | |||||
| } | |||||
| .icon-eye-open:before { | |||||
| content: "\e654"; | |||||
| } | |||||
| .icon-dashboard:before { | |||||
| content: "\e655"; | |||||
| } | |||||
| .icon-nested:before { | |||||
| content: "\e64b"; | |||||
| } | |||||
| .icon-example:before { | |||||
| content: "\e64c"; | |||||
| } | |||||
| .icon-link:before { | |||||
| content: "\e64d"; | |||||
| } | |||||
| .icon-eye:before { | |||||
| content: "\e67d"; | |||||
| } | |||||
| .icon-hamburger:before { | |||||
| content: "\e615"; | |||||
| } | |||||
| .icon-tree:before { | |||||
| content: "\e64f"; | |||||
| } | |||||
| .icon-user:before { | |||||
| content: "\e650"; | |||||
| } | |||||
| .icon-password:before { | |||||
| content: "\e651"; | |||||
| } | |||||
| @@ -0,0 +1,35 @@ | |||||
| // base color | |||||
| $blue:#324157; | |||||
| $light-blue:#3A71A8; | |||||
| $red:#C03639; | |||||
| $pink: #E65D6E; | |||||
| $green: #30B08F; | |||||
| $tiffany: #4AB7BD; | |||||
| $yellow:#FEC171; | |||||
| $panGreen: #30B08F; | |||||
| // sidebar | |||||
| $menuText:#bfcbd9; | |||||
| $menuActiveText:#409EFF; | |||||
| $subMenuActiveText:#f4f4f5; // https://github.com/ElemeFE/element/issues/12951 | |||||
| $menuBg:#304156; | |||||
| $menuHover:#263445; | |||||
| $subMenuBg:#1f2d3d; | |||||
| $subMenuHover:#001528; | |||||
| $sideBarWidth: 205px; | |||||
| // 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; | |||||
| } | |||||
| @@ -0,0 +1,121 @@ | |||||
| .head-container { | |||||
| padding-bottom: 10px; | |||||
| .filter-item { | |||||
| width: 150px; | |||||
| display: inline-block; | |||||
| vertical-align: middle; | |||||
| margin: 0 3px 10px 0; | |||||
| input { | |||||
| height: 30.5px; | |||||
| line-height: 30.5px; | |||||
| } | |||||
| } | |||||
| .el-form-item-label { | |||||
| margin: 0 3px 9px 0; | |||||
| display: inline-block; | |||||
| text-align: right; | |||||
| vertical-align: middle; | |||||
| font-size: 14px; | |||||
| color: #606266; | |||||
| line-height: 30.5px; | |||||
| padding: 0 7px 0 7px; | |||||
| } | |||||
| .el-button{ | |||||
| width: auto; | |||||
| } | |||||
| .el-button+.el-button { | |||||
| margin-left: 0 !important; | |||||
| } | |||||
| .el-select__caret.el-input__icon.el-icon-arrow-up{ | |||||
| line-height: 30.5px; | |||||
| } | |||||
| .date-item { | |||||
| display: inline-block; | |||||
| vertical-align: middle; | |||||
| margin: 0 3px 10px 0; | |||||
| height: 30.5px !important; | |||||
| width: 230px !important; | |||||
| } | |||||
| } | |||||
| .el-avatar { | |||||
| display: inline-block; | |||||
| text-align: center; | |||||
| background: #ccc; | |||||
| color: #fff; | |||||
| white-space: nowrap; | |||||
| position: relative; | |||||
| overflow: hidden; | |||||
| vertical-align: middle; | |||||
| width: 32px; | |||||
| height: 32px; | |||||
| line-height: 32px; | |||||
| border-radius: 16px; | |||||
| } | |||||
| .logo-con{ | |||||
| height: 60px; | |||||
| padding: 13px 0 0; | |||||
| img{ | |||||
| height: 32px; | |||||
| width: 135px; | |||||
| display: block; | |||||
| //margin: 0 auto; | |||||
| } | |||||
| } | |||||
| #el-login-footer { | |||||
| height: 40px; | |||||
| line-height: 40px; | |||||
| position: fixed; | |||||
| bottom: 0; | |||||
| width: 100%; | |||||
| text-align: center; | |||||
| color: #fff; | |||||
| font-family: Arial, serif; | |||||
| font-size: 12px; | |||||
| letter-spacing: 1px; | |||||
| } | |||||
| #el-main-footer { | |||||
| background: none repeat scroll 0 0 white; | |||||
| border-top: 1px solid #e7eaec; | |||||
| overflow: hidden; | |||||
| padding: 10px 6px 0 6px; | |||||
| height: 33px; | |||||
| font-size: 0.7rem !important; | |||||
| color: #7a8b9a; | |||||
| letter-spacing: 0.8px; | |||||
| font-family: Arial, sans-serif !important; | |||||
| position: fixed; | |||||
| bottom: 0; | |||||
| z-index: 99; | |||||
| width: 100%; | |||||
| } | |||||
| .eladmin-upload { | |||||
| border: 1px dashed #c0ccda; | |||||
| border-radius: 5px; | |||||
| height: 45px; | |||||
| line-height: 45px; | |||||
| width: 368px; | |||||
| } | |||||
| .my-blockquote{ | |||||
| margin: 0 0 10px; | |||||
| padding: 15px; | |||||
| line-height: 22px; | |||||
| border-left: 5px solid #00437B; | |||||
| border-radius: 0 2px 2px 0; | |||||
| background-color: #f2f2f2; | |||||
| } | |||||
| .my-code{ | |||||
| position: relative; | |||||
| padding: 15px; | |||||
| line-height: 20px; | |||||
| border-left: 5px solid #ddd; | |||||
| color: #333; | |||||
| font-family: Courier New, serif; | |||||
| font-size: 12px | |||||
| } | |||||
| .el-tabs{ | |||||
| margin-bottom: 25px; | |||||
| } | |||||
| @@ -0,0 +1,70 @@ | |||||
| // 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; | |||||
| } | |||||
| // fix date-picker ui bug in filter-item | |||||
| .el-range-editor.el-input__inner { | |||||
| display: inline-flex !important; | |||||
| } | |||||
| //全局覆盖样式 | |||||
| //用于解决select标签无法独占一行的情况 | |||||
| .resetform { | |||||
| width: 100%; | |||||
| height: auto; | |||||
| .el-select { | |||||
| width: 100%; | |||||
| } | |||||
| .el-input__inner{ | |||||
| width: 100%; | |||||
| } | |||||
| .el-cascader{ | |||||
| width: 100%; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,31 @@ | |||||
| /** | |||||
| * I think element-ui's default theme color is too light for long-term use. | |||||
| * So I modified the default color and you can modify it to your liking. | |||||
| **/ | |||||
| /* theme color */ | |||||
| $--color-primary: #1890ff; | |||||
| $--color-success: #13ce66; | |||||
| $--color-warning: #FFBA00; | |||||
| $--color-danger: #ff4949; | |||||
| // $--color-info: #1E1E1E; | |||||
| $--button-font-weight: 400; | |||||
| // $--color-text-regular: #1f2d3d; | |||||
| $--border-color-light: #dfe4ed; | |||||
| $--border-color-lighter: #e6ebf5; | |||||
| $--table-border:1px solid#dfe6ec; | |||||
| /* icon font path, required */ | |||||
| $--font-path: '~element-ui/lib/theme-chalk/fonts'; | |||||
| @import "../../../node_modules/element-ui/packages/theme-chalk/src/index"; | |||||
| // the :export directive is the magic sauce for webpack | |||||
| // https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass | |||||
| :export { | |||||
| theme: $--color-primary; | |||||
| } | |||||
| @@ -0,0 +1,81 @@ | |||||
| @import './variables.scss'; | |||||
| @import './mixin.scss'; | |||||
| @import './transition.scss'; | |||||
| @import './element-ui.scss'; | |||||
| @import './sidebar.scss'; | |||||
| @import './eladmin.scss'; | |||||
| @import './btn.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 20px 70px 20px; | |||||
| height: calc(100vh - 50px); | |||||
| overflow: auto; | |||||
| } | |||||
| //竖向自适应布局 start | |||||
| .flex_fixed { | |||||
| flex: none; | |||||
| } | |||||
| .flex_auto { | |||||
| flex: 1; | |||||
| } | |||||
| .flex_parent { | |||||
| display: flex; | |||||
| flex-direction: column; | |||||
| } | |||||
| //竖向自适应布局 end | |||||
| @@ -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%; | |||||
| } | |||||
| @@ -0,0 +1,226 @@ | |||||
| #app { | |||||
| overflow: hidden; | |||||
| .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, .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, .icon { | |||||
| margin-left: 19px; | |||||
| } | |||||
| } | |||||
| } | |||||
| .el-submenu { | |||||
| overflow: hidden; | |||||
| &>.el-submenu__title { | |||||
| padding: 0 !important; | |||||
| .svg-icon { | |||||
| margin-left: 20px; | |||||
| } | |||||
| .sub-el-icon, .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, .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; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -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; | |||||
| } | |||||
| @@ -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; | |||||
| } | |||||
| @@ -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 | |||||
| const matched = this.$route.matched.filter(item => item.meta && item.meta.title) | |||||
| /* const first = matched[0] | |||||
| if (!this.isDashboard(first)) { | |||||
| matched = [{ path: '/home', 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() === 'Home'.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> | |||||
| @@ -0,0 +1,268 @@ | |||||
| <template> | |||||
| <div class="crud-opts"> | |||||
| <span class="crud-opts-left"> | |||||
| <!--左侧插槽--> | |||||
| <slot name="left" /> | |||||
| <el-button | |||||
| v-if="crud.optShow.add" | |||||
| v-permission="permission.add" | |||||
| class="filter-item" | |||||
| size="mini" | |||||
| type="primary" | |||||
| icon="el-icon-plus" | |||||
| @click="crud.toAdd" | |||||
| > | |||||
| 新增 | |||||
| </el-button> | |||||
| <el-button | |||||
| v-if="crud.optShow.edit" | |||||
| v-permission="permission.edit" | |||||
| class="filter-item" | |||||
| size="mini" | |||||
| type="success" | |||||
| icon="el-icon-edit" | |||||
| :disabled="crud.selections.length !== 1" | |||||
| @click="crud.toEdit(crud.selections[0])" | |||||
| > | |||||
| 修改 | |||||
| </el-button> | |||||
| <el-button | |||||
| v-if="crud.optShow.del" | |||||
| slot="reference" | |||||
| v-permission="permission.del" | |||||
| class="filter-item" | |||||
| type="danger" | |||||
| icon="el-icon-delete" | |||||
| size="mini" | |||||
| :loading="crud.delAllLoading" | |||||
| :disabled="crud.selections.length === 0" | |||||
| @click="toDelete(crud.selections)" | |||||
| > | |||||
| 删除 | |||||
| </el-button> | |||||
| <el-button | |||||
| v-if="crud.optShow.download" | |||||
| :loading="crud.downloadLoading" | |||||
| :disabled="!crud.data.length" | |||||
| class="filter-item" | |||||
| size="mini" | |||||
| type="warning" | |||||
| icon="el-icon-download" | |||||
| @click="crud.doExport" | |||||
| >导出</el-button> | |||||
| <!--右侧--> | |||||
| <slot name="right" /> | |||||
| </span> | |||||
| <el-button-group class="crud-opts-right"> | |||||
| <el-button | |||||
| size="mini" | |||||
| plain | |||||
| type="info" | |||||
| icon="el-icon-search" | |||||
| @click="toggleSearch()" | |||||
| /> | |||||
| <el-button | |||||
| size="mini" | |||||
| icon="el-icon-refresh" | |||||
| @click="crud.refresh()" | |||||
| /> | |||||
| <el-popover | |||||
| placement="bottom-end" | |||||
| width="150" | |||||
| trigger="click" | |||||
| > | |||||
| <el-button | |||||
| slot="reference" | |||||
| size="mini" | |||||
| icon="el-icon-s-grid" | |||||
| > | |||||
| <i | |||||
| class="fa fa-caret-down" | |||||
| aria-hidden="true" | |||||
| /> | |||||
| </el-button> | |||||
| <el-checkbox | |||||
| v-model="allColumnsSelected" | |||||
| :indeterminate="allColumnsSelectedIndeterminate" | |||||
| @change="handleCheckAllChange" | |||||
| > | |||||
| 全选 | |||||
| </el-checkbox> | |||||
| <el-checkbox | |||||
| v-for="item in tableColumns" | |||||
| :key="item.property" | |||||
| v-model="item.visible" | |||||
| @change="handleCheckedTableColumnsChange(item)" | |||||
| > | |||||
| {{ item.label }} | |||||
| </el-checkbox> | |||||
| </el-popover> | |||||
| </el-button-group> | |||||
| </div> | |||||
| </template> | |||||
| <script> | |||||
| import CRUD, { crud } from '@crud/crud' | |||||
| function sortWithRef(src, ref) { | |||||
| const result = Object.assign([], ref) | |||||
| let cursor = -1 | |||||
| src.forEach(e => { | |||||
| const idx = result.indexOf(e) | |||||
| if (idx === -1) { | |||||
| cursor += 1 | |||||
| result.splice(cursor, 0, e) | |||||
| } else { | |||||
| cursor = idx | |||||
| } | |||||
| }) | |||||
| return result | |||||
| } | |||||
| export default { | |||||
| mixins: [crud()], | |||||
| props: { | |||||
| permission: { | |||||
| type: Object, | |||||
| default: () => { return {} } | |||||
| }, | |||||
| hiddenColumns: { | |||||
| type: Array, | |||||
| default: () => { return [] } | |||||
| }, | |||||
| ignoreColumns: { | |||||
| type: Array, | |||||
| default: () => { return [] } | |||||
| } | |||||
| }, | |||||
| data() { | |||||
| return { | |||||
| tableColumns: [], | |||||
| allColumnsSelected: true, | |||||
| allColumnsSelectedIndeterminate: false, | |||||
| tableUnwatcher: null, | |||||
| // 忽略下次表格列变动 | |||||
| ignoreNextTableColumnsChange: false | |||||
| } | |||||
| }, | |||||
| watch: { | |||||
| 'crud.props.table'() { | |||||
| this.updateTableColumns() | |||||
| this.tableColumns.forEach(column => { | |||||
| if (this.hiddenColumns.indexOf(column.property) !== -1) { | |||||
| column.visible = false | |||||
| this.updateColumnVisible(column) | |||||
| } | |||||
| }) | |||||
| }, | |||||
| 'crud.props.table.store.states.columns'() { | |||||
| this.updateTableColumns() | |||||
| } | |||||
| }, | |||||
| created() { | |||||
| this.crud.updateProp('searchToggle', true) | |||||
| }, | |||||
| methods: { | |||||
| updateTableColumns() { | |||||
| const table = this.crud.getTable() | |||||
| if (!table) { | |||||
| this.tableColumns = [] | |||||
| return | |||||
| } | |||||
| let cols = null | |||||
| const columnFilter = e => e && e.type === 'default' && e.property && this.ignoreColumns.indexOf(e.property) === -1 | |||||
| const refCols = table.columns.filter(columnFilter) | |||||
| if (this.ignoreNextTableColumnsChange) { | |||||
| this.ignoreNextTableColumnsChange = false | |||||
| return | |||||
| } | |||||
| this.ignoreNextTableColumnsChange = false | |||||
| const columns = [] | |||||
| const fullTableColumns = table.$children.map(e => e.columnConfig).filter(columnFilter) | |||||
| cols = sortWithRef(fullTableColumns, refCols) | |||||
| cols.forEach(config => { | |||||
| const column = { | |||||
| property: config.property, | |||||
| label: config.label, | |||||
| visible: refCols.indexOf(config) !== -1 | |||||
| } | |||||
| columns.push(column) | |||||
| }) | |||||
| this.tableColumns = columns | |||||
| }, | |||||
| toDelete(datas) { | |||||
| this.$confirm(`确认删除选中的${datas.length}条数据?`, '提示', { | |||||
| confirmButtonText: '确定', | |||||
| cancelButtonText: '取消', | |||||
| type: 'warning' | |||||
| }).then(() => { | |||||
| this.crud.delAllLoading = true | |||||
| this.crud.doDelete(datas) | |||||
| }).catch(() => { | |||||
| }) | |||||
| }, | |||||
| handleCheckAllChange(val) { | |||||
| if (val === false) { | |||||
| this.allColumnsSelected = true | |||||
| return | |||||
| } | |||||
| this.tableColumns.forEach(column => { | |||||
| if (!column.visible) { | |||||
| column.visible = true | |||||
| this.updateColumnVisible(column) | |||||
| } | |||||
| }) | |||||
| this.allColumnsSelected = val | |||||
| this.allColumnsSelectedIndeterminate = false | |||||
| }, | |||||
| handleCheckedTableColumnsChange(item) { | |||||
| let totalCount = 0 | |||||
| let selectedCount = 0 | |||||
| this.tableColumns.forEach(column => { | |||||
| ++totalCount | |||||
| selectedCount += column.visible ? 1 : 0 | |||||
| }) | |||||
| if (selectedCount === 0) { | |||||
| this.crud.notify('请至少选择一列', CRUD.NOTIFICATION_TYPE.WARNING) | |||||
| this.$nextTick(function() { | |||||
| item.visible = true | |||||
| }) | |||||
| return | |||||
| } | |||||
| this.allColumnsSelected = selectedCount === totalCount | |||||
| this.allColumnsSelectedIndeterminate = selectedCount !== totalCount && selectedCount !== 0 | |||||
| this.updateColumnVisible(item) | |||||
| }, | |||||
| updateColumnVisible(item) { | |||||
| const table = this.crud.props.table | |||||
| const vm = table.$children.find(e => e.prop === item.property) | |||||
| const columnConfig = vm.columnConfig | |||||
| if (item.visible) { | |||||
| // 找出合适的插入点 | |||||
| const columnIndex = this.tableColumns.indexOf(item) | |||||
| vm.owner.store.commit('insertColumn', columnConfig, columnIndex + 1, null) | |||||
| } else { | |||||
| vm.owner.store.commit('removeColumn', columnConfig, null) | |||||
| } | |||||
| this.ignoreNextTableColumnsChange = true | |||||
| }, | |||||
| toggleSearch() { | |||||
| this.crud.props.searchToggle = !this.crud.props.searchToggle | |||||
| } | |||||
| } | |||||
| } | |||||
| </script> | |||||
| <style> | |||||
| .crud-opts { | |||||
| padding: 4px 0; | |||||
| display: -webkit-flex; | |||||
| display: flex; | |||||
| align-items: center; | |||||
| } | |||||
| .crud-opts .crud-opts-right { | |||||
| margin-left: auto; | |||||
| } | |||||
| .crud-opts .crud-opts-right span { | |||||
| float: left; | |||||
| } | |||||
| </style> | |||||
| @@ -0,0 +1,18 @@ | |||||
| <!--分页--> | |||||
| <template> | |||||
| <el-pagination | |||||
| :page-size.sync="page.size" | |||||
| :total="page.total" | |||||
| :current-page.sync="page.page" | |||||
| style="margin-top: 8px;" | |||||
| layout="total, prev, pager, next, sizes" | |||||
| @size-change="crud.sizeChangeHandler($event)" | |||||
| @current-change="crud.pageChangeHandler" | |||||
| /> | |||||
| </template> | |||||
| <script> | |||||
| import { pagination } from '@crud/crud' | |||||
| export default { | |||||
| mixins: [pagination()] | |||||
| } | |||||
| </script> | |||||
| @@ -0,0 +1,20 @@ | |||||
| <!--搜索与重置--> | |||||
| <template> | |||||
| <span> | |||||
| <el-button class="filter-item" size="mini" type="success" icon="el-icon-search" @click="crud.toQuery">搜索</el-button> | |||||
| <el-button v-if="crud.optShow.reset" class="filter-item" size="mini" type="warning" icon="el-icon-refresh-left" @click="crud.resetQuery()">重置</el-button> | |||||
| </span> | |||||
| </template> | |||||
| <script> | |||||
| import { crud } from '@crud/crud' | |||||
| export default { | |||||
| mixins: [crud()], | |||||
| props: { | |||||
| itemClass: { | |||||
| type: String, | |||||
| required: false, | |||||
| default: '' | |||||
| } | |||||
| } | |||||
| } | |||||
| </script> | |||||
| @@ -0,0 +1,71 @@ | |||||
| <template> | |||||
| <div> | |||||
| <el-button v-permission="permission.edit" :loading="crud.status.cu === 2" :disabled="disabledEdit" size="mini" type="primary" icon="el-icon-edit" @click="crud.toEdit(data)" /> | |||||
| <el-popover v-model="pop" v-permission="permission.del" placement="top" width="180" trigger="manual" @show="onPopoverShow" @hide="onPopoverHide"> | |||||
| <p>{{ msg }}</p> | |||||
| <div style="text-align: right; margin: 0"> | |||||
| <el-button size="mini" type="text" @click="doCancel">取消</el-button> | |||||
| <el-button :loading="crud.dataStatus[crud.getDataId(data)].delete === 2" type="primary" size="mini" @click="crud.doDelete(data)">确定</el-button> | |||||
| </div> | |||||
| <el-button slot="reference" :disabled="disabledDle" type="danger" icon="el-icon-delete" size="mini" @click="toDelete" /> | |||||
| </el-popover> | |||||
| </div> | |||||
| </template> | |||||
| <script> | |||||
| import CRUD, { crud } from '@crud/crud' | |||||
| export default { | |||||
| mixins: [crud()], | |||||
| props: { | |||||
| data: { | |||||
| type: Object, | |||||
| required: true | |||||
| }, | |||||
| permission: { | |||||
| type: Object, | |||||
| required: true | |||||
| }, | |||||
| disabledEdit: { | |||||
| type: Boolean, | |||||
| default: false | |||||
| }, | |||||
| disabledDle: { | |||||
| type: Boolean, | |||||
| default: false | |||||
| }, | |||||
| msg: { | |||||
| type: String, | |||||
| default: '确定删除本条数据吗?' | |||||
| } | |||||
| }, | |||||
| data() { | |||||
| return { | |||||
| pop: false | |||||
| } | |||||
| }, | |||||
| methods: { | |||||
| doCancel() { | |||||
| this.pop = false | |||||
| this.crud.cancelDelete(this.data) | |||||
| }, | |||||
| toDelete() { | |||||
| this.pop = true | |||||
| }, | |||||
| [CRUD.HOOK.afterDelete](crud, data) { | |||||
| if (data === this.data) { | |||||
| this.pop = false | |||||
| } | |||||
| }, | |||||
| onPopoverShow() { | |||||
| setTimeout(() => { | |||||
| document.addEventListener('click', this.handleDocumentClick) | |||||
| }, 0) | |||||
| }, | |||||
| onPopoverHide() { | |||||
| document.removeEventListener('click', this.handleDocumentClick) | |||||
| }, | |||||
| handleDocumentClick(event) { | |||||
| this.pop = false | |||||
| } | |||||
| } | |||||
| } | |||||
| </script> | |||||
| @@ -0,0 +1,864 @@ | |||||
| import { initData, download } from '@/api/data' | |||||
| import { parseTime, downloadFile } from '@/utils/index' | |||||
| import Vue from 'vue' | |||||
| /** | |||||
| * CRUD配置 | |||||
| * @author moxun | |||||
| * @param {*} options <br> | |||||
| * @return crud instance. | |||||
| * @example | |||||
| * 要使用多crud时,请在关联crud的组件处使用crud-tag进行标记,如:<jobForm :job-status="dict.job_status" crud-tag="job" /> | |||||
| */ | |||||
| function CRUD(options) { | |||||
| const defaultOptions = { | |||||
| tag: 'default', | |||||
| // id字段名 | |||||
| idField: 'id', | |||||
| // 标题 | |||||
| title: '', | |||||
| // 请求数据的url | |||||
| url: '', | |||||
| // 表格数据 | |||||
| data: [], | |||||
| // 选择项 | |||||
| selections: [], | |||||
| // 待查询的对象 | |||||
| query: {}, | |||||
| // 查询数据的参数 | |||||
| params: {}, | |||||
| // Form 表单 | |||||
| form: {}, | |||||
| // 重置表单 | |||||
| defaultForm: () => {}, | |||||
| // 排序规则,默认 id 降序, 支持多字段排序 ['id,desc', 'createTime,asc'] | |||||
| sort: ['id,desc'], | |||||
| // 等待时间 | |||||
| time: 50, | |||||
| // CRUD Method | |||||
| crudMethod: { | |||||
| add: (form) => {}, | |||||
| del: (id) => {}, | |||||
| edit: (form) => {}, | |||||
| get: (id) => {} | |||||
| }, | |||||
| // 主页操作栏显示哪些按钮 | |||||
| optShow: { | |||||
| add: true, | |||||
| edit: true, | |||||
| del: true, | |||||
| download: true, | |||||
| reset: true | |||||
| }, | |||||
| // 自定义一些扩展属性 | |||||
| props: {}, | |||||
| // 在主页准备 | |||||
| queryOnPresenterCreated: true, | |||||
| // 调试开关 | |||||
| debug: false | |||||
| } | |||||
| options = mergeOptions(defaultOptions, options) | |||||
| const data = { | |||||
| ...options, | |||||
| // 记录数据状态 | |||||
| dataStatus: {}, | |||||
| status: { | |||||
| add: CRUD.STATUS.NORMAL, | |||||
| edit: CRUD.STATUS.NORMAL, | |||||
| // 添加或编辑状态 | |||||
| get cu() { | |||||
| if (this.add === CRUD.STATUS.NORMAL && this.edit === CRUD.STATUS.NORMAL) { | |||||
| return CRUD.STATUS.NORMAL | |||||
| } else if (this.add === CRUD.STATUS.PREPARED || this.edit === CRUD.STATUS.PREPARED) { | |||||
| return CRUD.STATUS.PREPARED | |||||
| } else if (this.add === CRUD.STATUS.PROCESSING || this.edit === CRUD.STATUS.PROCESSING) { | |||||
| return CRUD.STATUS.PROCESSING | |||||
| } | |||||
| throw new Error('wrong crud\'s cu status') | |||||
| }, | |||||
| // 标题 | |||||
| get title() { | |||||
| return this.add > CRUD.STATUS.NORMAL ? `新增${crud.title}` : this.edit > CRUD.STATUS.NORMAL ? `编辑${crud.title}` : crud.title | |||||
| } | |||||
| }, | |||||
| msg: { | |||||
| submit: '提交成功', | |||||
| add: '新增成功', | |||||
| edit: '编辑成功', | |||||
| del: '删除成功' | |||||
| }, | |||||
| page: { | |||||
| // 页码 | |||||
| page: 0, | |||||
| // 每页数据条数 | |||||
| size: 10, | |||||
| // 总数据条数 | |||||
| total: 0 | |||||
| }, | |||||
| // 整体loading | |||||
| loading: false, | |||||
| // 导出的 Loading | |||||
| downloadLoading: false, | |||||
| // 删除的 Loading | |||||
| delAllLoading: false | |||||
| } | |||||
| const methods = { | |||||
| /** | |||||
| * 通用的提示 | |||||
| */ | |||||
| submitSuccessNotify() { | |||||
| crud.notify(crud.msg.submit, CRUD.NOTIFICATION_TYPE.SUCCESS) | |||||
| }, | |||||
| addSuccessNotify() { | |||||
| crud.notify(crud.msg.add, CRUD.NOTIFICATION_TYPE.SUCCESS) | |||||
| }, | |||||
| editSuccessNotify() { | |||||
| crud.notify(crud.msg.edit, CRUD.NOTIFICATION_TYPE.SUCCESS) | |||||
| }, | |||||
| delSuccessNotify() { | |||||
| crud.notify(crud.msg.del, CRUD.NOTIFICATION_TYPE.SUCCESS) | |||||
| }, | |||||
| // 搜索 | |||||
| toQuery() { | |||||
| crud.page.page = 1 | |||||
| crud.refresh() | |||||
| }, | |||||
| // 刷新 | |||||
| refresh() { | |||||
| if (!callVmHook(crud, CRUD.HOOK.beforeRefresh)) { | |||||
| return | |||||
| } | |||||
| return new Promise((resolve, reject) => { | |||||
| crud.loading = true | |||||
| // 请求数据 | |||||
| initData(crud.url, crud.getQueryParams()).then(data => { | |||||
| const table = crud.getTable() | |||||
| if (table && table.lazy) { // 懒加载子节点数据,清掉已加载的数据 | |||||
| table.store.states.treeData = {} | |||||
| table.store.states.lazyTreeNodeMap = {} | |||||
| } | |||||
| crud.page.total = Number(data.total) | |||||
| crud.data = data.list | |||||
| crud.resetDataStatus() | |||||
| // time 毫秒后显示表格 | |||||
| setTimeout(() => { | |||||
| crud.loading = false | |||||
| callVmHook(crud, CRUD.HOOK.afterRefresh) | |||||
| }, crud.time) | |||||
| resolve(data) | |||||
| }).catch(err => { | |||||
| crud.loading = false | |||||
| reject(err) | |||||
| }) | |||||
| }) | |||||
| }, | |||||
| /** | |||||
| * 启动添加 | |||||
| */ | |||||
| toAdd() { | |||||
| crud.resetForm() | |||||
| if (!(callVmHook(crud, CRUD.HOOK.beforeToAdd, crud.form) && callVmHook(crud, CRUD.HOOK.beforeToCU, crud.form))) { | |||||
| return | |||||
| } | |||||
| crud.status.add = CRUD.STATUS.PREPARED | |||||
| callVmHook(crud, CRUD.HOOK.afterToAdd, crud.form) | |||||
| callVmHook(crud, CRUD.HOOK.afterToCU, crud.form) | |||||
| }, | |||||
| /** | |||||
| * 启动编辑 | |||||
| * @param {*} data 数据项 | |||||
| */ | |||||
| toEdit(data) { | |||||
| crud.resetForm(JSON.parse(JSON.stringify(data))) | |||||
| if (!(callVmHook(crud, CRUD.HOOK.beforeToEdit, crud.form) && callVmHook(crud, CRUD.HOOK.beforeToCU, crud.form))) { | |||||
| return | |||||
| } | |||||
| crud.status.edit = CRUD.STATUS.PREPARED | |||||
| crud.getDataStatus(crud.getDataId(data)).edit = CRUD.STATUS.PREPARED | |||||
| callVmHook(crud, CRUD.HOOK.afterToEdit, crud.form) | |||||
| callVmHook(crud, CRUD.HOOK.afterToCU, crud.form) | |||||
| }, | |||||
| /** | |||||
| * 启动删除 | |||||
| * @param {*} data 数据项 | |||||
| */ | |||||
| toDelete(data) { | |||||
| crud.getDataStatus(crud.getDataId(data)).delete = CRUD.STATUS.PREPARED | |||||
| }, | |||||
| /** | |||||
| * 取消删除 | |||||
| * @param {*} data 数据项 | |||||
| */ | |||||
| cancelDelete(data) { | |||||
| if (!callVmHook(crud, CRUD.HOOK.beforeDeleteCancel, data)) { | |||||
| return | |||||
| } | |||||
| crud.getDataStatus(crud.getDataId(data)).delete = CRUD.STATUS.NORMAL | |||||
| callVmHook(crud, CRUD.HOOK.afterDeleteCancel, data) | |||||
| }, | |||||
| /** | |||||
| * 取消新增/编辑 | |||||
| */ | |||||
| cancelCU() { | |||||
| const addStatus = crud.status.add | |||||
| const editStatus = crud.status.edit | |||||
| if (addStatus === CRUD.STATUS.PREPARED) { | |||||
| if (!callVmHook(crud, CRUD.HOOK.beforeAddCancel, crud.form)) { | |||||
| return | |||||
| } | |||||
| crud.status.add = CRUD.STATUS.NORMAL | |||||
| } | |||||
| if (editStatus === CRUD.STATUS.PREPARED) { | |||||
| if (!callVmHook(crud, CRUD.HOOK.beforeEditCancel, crud.form)) { | |||||
| return | |||||
| } | |||||
| crud.status.edit = CRUD.STATUS.NORMAL | |||||
| crud.getDataStatus(crud.getDataId(crud.form)).edit = CRUD.STATUS.NORMAL | |||||
| } | |||||
| crud.resetForm() | |||||
| if (addStatus === CRUD.STATUS.PREPARED) { | |||||
| callVmHook(crud, CRUD.HOOK.afterAddCancel, crud.form) | |||||
| } | |||||
| if (editStatus === CRUD.STATUS.PREPARED) { | |||||
| callVmHook(crud, CRUD.HOOK.afterEditCancel, crud.form) | |||||
| } | |||||
| // 清除表单验证 | |||||
| if (crud.findVM('form').$refs['form']) { | |||||
| crud.findVM('form').$refs['form'].clearValidate() | |||||
| } | |||||
| }, | |||||
| /** | |||||
| * 提交新增/编辑 | |||||
| */ | |||||
| submitCU() { | |||||
| if (!callVmHook(crud, CRUD.HOOK.beforeValidateCU)) { | |||||
| return | |||||
| } | |||||
| crud.findVM('form').$refs['form'].validate(valid => { | |||||
| if (!valid) { | |||||
| return | |||||
| } | |||||
| if (!callVmHook(crud, CRUD.HOOK.afterValidateCU)) { | |||||
| return | |||||
| } | |||||
| if (crud.status.add === CRUD.STATUS.PREPARED) { | |||||
| crud.doAdd() | |||||
| } else if (crud.status.edit === CRUD.STATUS.PREPARED) { | |||||
| crud.doEdit() | |||||
| } | |||||
| }) | |||||
| }, | |||||
| /** | |||||
| * 执行添加 | |||||
| */ | |||||
| doAdd() { | |||||
| if (!callVmHook(crud, CRUD.HOOK.beforeSubmit)) { | |||||
| return | |||||
| } | |||||
| crud.status.add = CRUD.STATUS.PROCESSING | |||||
| crud.crudMethod.add(crud.form).then(() => { | |||||
| crud.status.add = CRUD.STATUS.NORMAL | |||||
| crud.resetForm() | |||||
| crud.addSuccessNotify() | |||||
| callVmHook(crud, CRUD.HOOK.afterSubmit) | |||||
| crud.toQuery() | |||||
| }).catch(() => { | |||||
| crud.status.add = CRUD.STATUS.PREPARED | |||||
| callVmHook(crud, CRUD.HOOK.afterAddError) | |||||
| }) | |||||
| }, | |||||
| /** | |||||
| * 执行编辑 | |||||
| */ | |||||
| doEdit() { | |||||
| if (!callVmHook(crud, CRUD.HOOK.beforeSubmit)) { | |||||
| return | |||||
| } | |||||
| crud.status.edit = CRUD.STATUS.PROCESSING | |||||
| crud.crudMethod.edit(crud.form).then(() => { | |||||
| crud.status.edit = CRUD.STATUS.NORMAL | |||||
| crud.getDataStatus(crud.getDataId(crud.form)).edit = CRUD.STATUS.NORMAL | |||||
| crud.editSuccessNotify() | |||||
| crud.resetForm() | |||||
| callVmHook(crud, CRUD.HOOK.afterSubmit) | |||||
| crud.refresh() | |||||
| }).catch(() => { | |||||
| crud.status.edit = CRUD.STATUS.PREPARED | |||||
| callVmHook(crud, CRUD.HOOK.afterEditError) | |||||
| }) | |||||
| }, | |||||
| /** | |||||
| * 执行删除 | |||||
| * @param {*} data 数据项 | |||||
| */ | |||||
| doDelete(data) { | |||||
| let delAll = false | |||||
| let dataStatus | |||||
| const ids = [] | |||||
| if (data instanceof Array) { | |||||
| delAll = true | |||||
| data.forEach(val => { | |||||
| ids.push(this.getDataId(val)) | |||||
| }) | |||||
| } else { | |||||
| ids.push(this.getDataId(data)) | |||||
| dataStatus = crud.getDataStatus(this.getDataId(data)) | |||||
| } | |||||
| if (!callVmHook(crud, CRUD.HOOK.beforeDelete, data)) { | |||||
| return | |||||
| } | |||||
| if (!delAll) { | |||||
| dataStatus.delete = CRUD.STATUS.PROCESSING | |||||
| } | |||||
| return crud.crudMethod.del(ids).then(() => { | |||||
| if (delAll) { | |||||
| crud.delAllLoading = false | |||||
| } else dataStatus.delete = CRUD.STATUS.PREPARED | |||||
| crud.dleChangePage(1) | |||||
| crud.delSuccessNotify() | |||||
| callVmHook(crud, CRUD.HOOK.afterDelete, data) | |||||
| crud.refresh() | |||||
| }).catch(() => { | |||||
| if (delAll) { | |||||
| crud.delAllLoading = false | |||||
| } else dataStatus.delete = CRUD.STATUS.PREPARED | |||||
| }) | |||||
| }, | |||||
| /** | |||||
| * 通用导出 | |||||
| */ | |||||
| doExport() { | |||||
| crud.downloadLoading = true | |||||
| download(crud.url + '/download', crud.getQueryParams()).then(result => { | |||||
| downloadFile(result, crud.title + '数据', 'xlsx') | |||||
| crud.downloadLoading = false | |||||
| }).catch(() => { | |||||
| crud.downloadLoading = false | |||||
| }) | |||||
| }, | |||||
| /** | |||||
| * 获取查询参数 | |||||
| */ | |||||
| getQueryParams: function() { | |||||
| // 清除参数无值的情况 | |||||
| Object.keys(crud.query).length !== 0 && Object.keys(crud.query).forEach(item => { | |||||
| if (crud.query[item] === null || crud.query[item] === '') crud.query[item] = undefined | |||||
| }) | |||||
| Object.keys(crud.params).length !== 0 && Object.keys(crud.params).forEach(item => { | |||||
| if (crud.params[item] === null || crud.params[item] === '') crud.params[item] = undefined | |||||
| }) | |||||
| return { | |||||
| pageNum: crud.page.page, | |||||
| pageSize: crud.page.size, | |||||
| ...crud.query, | |||||
| ...crud.params | |||||
| } | |||||
| }, | |||||
| // 当前页改变 | |||||
| pageChangeHandler(e) { | |||||
| crud.page.page = e | |||||
| crud.refresh() | |||||
| }, | |||||
| // 每页条数改变 | |||||
| sizeChangeHandler(e) { | |||||
| crud.page.size = e | |||||
| crud.page.page = 1 | |||||
| crud.refresh() | |||||
| }, | |||||
| // 预防删除第二页最后一条数据时,或者多选删除第二页的数据时,页码错误导致请求无数据 | |||||
| dleChangePage(size) { | |||||
| if (crud.data.length === size && crud.page.page !== 1) { | |||||
| crud.page.page -= 1 | |||||
| } | |||||
| }, | |||||
| // 选择改变 | |||||
| selectionChangeHandler(val) { | |||||
| crud.selections = val | |||||
| }, | |||||
| /** | |||||
| * 重置查询参数 | |||||
| * @param {Boolean} toQuery 重置后进行查询操作 | |||||
| */ | |||||
| resetQuery(toQuery = true) { | |||||
| const defaultQuery = JSON.parse(JSON.stringify(crud.defaultQuery)) | |||||
| const query = crud.query | |||||
| Object.keys(query).forEach(key => { | |||||
| query[key] = defaultQuery[key] | |||||
| }) | |||||
| // 重置参数 | |||||
| this.params = {} | |||||
| if (toQuery) { | |||||
| crud.toQuery() | |||||
| } | |||||
| }, | |||||
| /** | |||||
| * 重置表单 | |||||
| * @param {Array} data 数据 | |||||
| */ | |||||
| resetForm(data) { | |||||
| const form = data || (typeof crud.defaultForm === 'object' ? JSON.parse(JSON.stringify(crud.defaultForm)) : crud.defaultForm.apply(crud.findVM('form'))) | |||||
| const crudFrom = crud.form | |||||
| for (const key in form) { | |||||
| // eslint-disable-next-line no-prototype-builtins | |||||
| if (crudFrom.hasOwnProperty(key)) { | |||||
| crudFrom[key] = form[key] | |||||
| } else { | |||||
| Vue.set(crudFrom, key, form[key]) | |||||
| } | |||||
| } | |||||
| // add by ghl 2020-10-04 页面重复添加信息时,下拉框的校验会存在,需要找工取消 | |||||
| if (crud.findVM('form').$refs['form']) { | |||||
| crud.findVM('form').$refs['form'].clearValidate() | |||||
| } | |||||
| }, | |||||
| /** | |||||
| * 重置数据状态 | |||||
| */ | |||||
| resetDataStatus() { | |||||
| const dataStatus = {} | |||||
| function resetStatus(datas) { | |||||
| datas.forEach(e => { | |||||
| dataStatus[crud.getDataId(e)] = { | |||||
| delete: 0, | |||||
| edit: 0 | |||||
| } | |||||
| if (e.children) { | |||||
| resetStatus(e.children) | |||||
| } | |||||
| }) | |||||
| } | |||||
| resetStatus(crud.data) | |||||
| crud.dataStatus = dataStatus | |||||
| }, | |||||
| /** | |||||
| * 获取数据状态 | |||||
| * @param {Number | String} id 数据项id | |||||
| */ | |||||
| getDataStatus(id) { | |||||
| return crud.dataStatus[id] | |||||
| }, | |||||
| /** | |||||
| * 用于树形表格多选, 选中所有 | |||||
| * @param selection | |||||
| */ | |||||
| selectAllChange(selection) { | |||||
| // 如果选中的数目与请求到的数目相同就选中子节点,否则就清空选中 | |||||
| if (selection && selection.length === crud.data.length) { | |||||
| selection.forEach(val => { | |||||
| crud.selectChange(selection, val) | |||||
| }) | |||||
| } else { | |||||
| crud.getTable().clearSelection() | |||||
| } | |||||
| }, | |||||
| /** | |||||
| * 用于树形表格多选,单选的封装 | |||||
| * @param selection | |||||
| * @param row | |||||
| */ | |||||
| selectChange(selection, row) { | |||||
| // 如果selection中存在row代表是选中,否则是取消选中 | |||||
| if (selection.find(val => { return crud.getDataId(val) === crud.getDataId(row) })) { | |||||
| if (row.children) { | |||||
| row.children.forEach(val => { | |||||
| crud.getTable().toggleRowSelection(val, true) | |||||
| selection.push(val) | |||||
| if (val.children) { | |||||
| crud.selectChange(selection, val) | |||||
| } | |||||
| }) | |||||
| } | |||||
| } else { | |||||
| crud.toggleRowSelection(selection, row) | |||||
| } | |||||
| }, | |||||
| /** | |||||
| * 切换选中状态 | |||||
| * @param selection | |||||
| * @param data | |||||
| */ | |||||
| toggleRowSelection(selection, data) { | |||||
| if (data.children) { | |||||
| data.children.forEach(val => { | |||||
| crud.getTable().toggleRowSelection(val, false) | |||||
| if (val.children) { | |||||
| crud.toggleRowSelection(selection, val) | |||||
| } | |||||
| }) | |||||
| } | |||||
| }, | |||||
| findVM(type) { | |||||
| return crud.vms.find(vm => vm && vm.type === type).vm | |||||
| }, | |||||
| notify(title, type = CRUD.NOTIFICATION_TYPE.INFO) { | |||||
| crud.vms[0].vm.$notify({ | |||||
| title, | |||||
| type, | |||||
| duration: 2500 | |||||
| }) | |||||
| }, | |||||
| updateProp(name, value) { | |||||
| Vue.set(crud.props, name, value) | |||||
| }, | |||||
| getDataId(data) { | |||||
| return data[this.idField] | |||||
| }, | |||||
| getTable() { | |||||
| return this.findVM('presenter').$refs.table | |||||
| }, | |||||
| attchTable() { | |||||
| const table = this.getTable() | |||||
| this.updateProp('table', table) | |||||
| const that = this | |||||
| table.$on('expand-change', (row, expanded) => { | |||||
| if (!expanded) { | |||||
| return | |||||
| } | |||||
| const lazyTreeNodeMap = table.store.states.lazyTreeNodeMap | |||||
| row.children = lazyTreeNodeMap[crud.getDataId(row)] | |||||
| if (row.children) { | |||||
| row.children.forEach(ele => { | |||||
| const id = crud.getDataId(ele) | |||||
| if (that.dataStatus[id] === undefined) { | |||||
| that.dataStatus[id] = { | |||||
| delete: 0, | |||||
| edit: 0 | |||||
| } | |||||
| } | |||||
| }) | |||||
| } | |||||
| }) | |||||
| } | |||||
| } | |||||
| const crud = Object.assign({}, data) | |||||
| // 可观测化 | |||||
| Vue.observable(crud) | |||||
| // 附加方法 | |||||
| Object.assign(crud, methods) | |||||
| // 记录初始默认的查询参数,后续重置查询时使用 | |||||
| Object.assign(crud, { | |||||
| defaultQuery: JSON.parse(JSON.stringify(data.query)), | |||||
| // 预留4位存储:组件 主页、头部、分页、表单,调试查看也方便找 | |||||
| vms: Array(4), | |||||
| /** | |||||
| * 注册组件实例 | |||||
| * @param {String} type 类型 | |||||
| * @param {*} vm 组件实例 | |||||
| * @param {Number} index 该参数内部使用 | |||||
| */ | |||||
| registerVM(type, vm, index = -1) { | |||||
| const vmObj = { | |||||
| type, | |||||
| vm: vm | |||||
| } | |||||
| if (index < 0) { | |||||
| this.vms.push(vmObj) | |||||
| return | |||||
| } | |||||
| if (index < 4) { // 内置预留vm数 | |||||
| this.vms[index] = vmObj | |||||
| return | |||||
| } | |||||
| this.vms.length = Math.max(this.vms.length, index) | |||||
| this.vms.splice(index, 1, vmObj) | |||||
| }, | |||||
| /** | |||||
| * 取消注册组件实例 | |||||
| * @param {*} vm 组件实例 | |||||
| */ | |||||
| unregisterVM(type, vm) { | |||||
| for (let i = this.vms.length - 1; i >= 0; i--) { | |||||
| if (this.vms[i] === undefined) { | |||||
| continue | |||||
| } | |||||
| if (this.vms[i].type === type && this.vms[i].vm === vm) { | |||||
| if (i < 4) { // 内置预留vm数 | |||||
| this.vms[i] = undefined | |||||
| } else { | |||||
| this.vms.splice(i, 1) | |||||
| } | |||||
| break | |||||
| } | |||||
| } | |||||
| } | |||||
| }) | |||||
| // 冻结处理,需要扩展数据的话,使用crud.updateProp(name, value),以crud.props.name形式访问,这个是响应式的,可以做数据绑定 | |||||
| Object.freeze(crud) | |||||
| return crud | |||||
| } | |||||
| // hook VM | |||||
| function callVmHook(crud, hook) { | |||||
| if (crud.debug) { | |||||
| console.log('callVmHook: ' + hook) | |||||
| } | |||||
| const tagHook = crud.tag ? hook + '$' + crud.tag : null | |||||
| let ret = true | |||||
| const nargs = [crud] | |||||
| for (let i = 2; i < arguments.length; ++i) { | |||||
| nargs.push(arguments[i]) | |||||
| } | |||||
| // 有些组件扮演了多个角色,调用钩子时,需要去重 | |||||
| const vmSet = new Set() | |||||
| crud.vms.forEach(vm => vm && vmSet.add(vm.vm)) | |||||
| vmSet.forEach(vm => { | |||||
| if (vm[hook]) { | |||||
| ret = vm[hook].apply(vm, nargs) !== false && ret | |||||
| } | |||||
| if (tagHook && vm[tagHook]) { | |||||
| ret = vm[tagHook].apply(vm, nargs) !== false && ret | |||||
| } | |||||
| }) | |||||
| return ret | |||||
| } | |||||
| function mergeOptions(src, opts) { | |||||
| const optsRet = { | |||||
| ...src | |||||
| } | |||||
| for (const key in src) { | |||||
| // eslint-disable-next-line no-prototype-builtins | |||||
| if (opts.hasOwnProperty(key)) { | |||||
| optsRet[key] = opts[key] | |||||
| } | |||||
| } | |||||
| return optsRet | |||||
| } | |||||
| /** | |||||
| * 查找crud | |||||
| * @param {*} vm | |||||
| * @param {string} tag | |||||
| */ | |||||
| function lookupCrud(vm, tag) { | |||||
| tag = tag || vm.$attrs['crud-tag'] || 'default' | |||||
| // function lookupCrud(vm, tag) { | |||||
| if (vm.$crud) { | |||||
| const ret = vm.$crud[tag] | |||||
| if (ret) { | |||||
| return ret | |||||
| } | |||||
| } | |||||
| return vm.$parent ? lookupCrud(vm.$parent, tag) : undefined | |||||
| } | |||||
| /** | |||||
| * crud主页 | |||||
| */ | |||||
| function presenter(crud) { | |||||
| if (crud) { | |||||
| console.warn('[CRUD warn]: ' + 'please use $options.cruds() { return CRUD(...) or [CRUD(...), ...] }') | |||||
| } | |||||
| return { | |||||
| data() { | |||||
| // 在data中返回crud,是为了将crud与当前实例关联,组件观测crud相关属性变化 | |||||
| return { | |||||
| crud: this.crud | |||||
| } | |||||
| }, | |||||
| beforeCreate() { | |||||
| this.$crud = this.$crud || {} | |||||
| let cruds = this.$options.cruds instanceof Function ? this.$options.cruds() : crud | |||||
| if (!(cruds instanceof Array)) { | |||||
| cruds = [cruds] | |||||
| } | |||||
| cruds.forEach(ele => { | |||||
| if (this.$crud[ele.tag]) { | |||||
| console.error('[CRUD error]: ' + 'crud with tag [' + ele.tag + ' is already exist') | |||||
| } | |||||
| this.$crud[ele.tag] = ele | |||||
| ele.registerVM('presenter', this, 0) | |||||
| }) | |||||
| this.crud = this.$crud['defalut'] || cruds[0] | |||||
| }, | |||||
| methods: { | |||||
| parseTime | |||||
| }, | |||||
| created() { | |||||
| for (const k in this.$crud) { | |||||
| if (this.$crud[k].queryOnPresenterCreated) { | |||||
| this.$crud[k].toQuery() | |||||
| } | |||||
| } | |||||
| }, | |||||
| destroyed() { | |||||
| for (const k in this.$crud) { | |||||
| this.$crud[k].unregisterVM('presenter', this) | |||||
| } | |||||
| }, | |||||
| mounted() { | |||||
| // 如果table未实例化(例如使用了v-if),请稍后在适当时机crud.attchTable刷新table信息 | |||||
| if (this.$refs.table !== undefined) { | |||||
| this.crud.attchTable() | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| /** | |||||
| * 头部 | |||||
| */ | |||||
| function header() { | |||||
| return { | |||||
| data() { | |||||
| return { | |||||
| crud: this.crud, | |||||
| query: this.crud.query | |||||
| } | |||||
| }, | |||||
| beforeCreate() { | |||||
| this.crud = lookupCrud(this) | |||||
| this.crud.registerVM('header', this, 1) | |||||
| }, | |||||
| destroyed() { | |||||
| this.crud.unregisterVM('header', this) | |||||
| } | |||||
| } | |||||
| } | |||||
| /** | |||||
| * 分页 | |||||
| */ | |||||
| function pagination() { | |||||
| return { | |||||
| data() { | |||||
| return { | |||||
| crud: this.crud, | |||||
| page: this.crud.page | |||||
| } | |||||
| }, | |||||
| beforeCreate() { | |||||
| this.crud = lookupCrud(this) | |||||
| this.crud.registerVM('pagination', this, 2) | |||||
| }, | |||||
| destroyed() { | |||||
| this.crud.unregisterVM('pagination', this) | |||||
| } | |||||
| } | |||||
| } | |||||
| /** | |||||
| * 表单 | |||||
| */ | |||||
| function form(defaultForm) { | |||||
| return { | |||||
| data() { | |||||
| return { | |||||
| crud: this.crud, | |||||
| form: this.crud.form | |||||
| } | |||||
| }, | |||||
| beforeCreate() { | |||||
| this.crud = lookupCrud(this) | |||||
| this.crud.registerVM('form', this, 3) | |||||
| }, | |||||
| created() { | |||||
| this.crud.defaultForm = defaultForm | |||||
| this.crud.resetForm() | |||||
| }, | |||||
| destroyed() { | |||||
| this.crud.unregisterVM('form', this) | |||||
| } | |||||
| } | |||||
| } | |||||
| /** | |||||
| * crud | |||||
| */ | |||||
| function crud(options = {}) { | |||||
| const defaultOptions = { | |||||
| type: undefined | |||||
| } | |||||
| options = mergeOptions(defaultOptions, options) | |||||
| return { | |||||
| data() { | |||||
| return { | |||||
| crud: this.crud | |||||
| } | |||||
| }, | |||||
| beforeCreate() { | |||||
| this.crud = lookupCrud(this) | |||||
| this.crud.registerVM(options.type, this) | |||||
| }, | |||||
| destroyed() { | |||||
| this.crud.unregisterVM(options.type, this) | |||||
| } | |||||
| } | |||||
| } | |||||
| /** | |||||
| * CRUD钩子 | |||||
| */ | |||||
| CRUD.HOOK = { | |||||
| /** 刷新 - 之前 */ | |||||
| beforeRefresh: 'beforeCrudRefresh', | |||||
| /** 刷新 - 之后 */ | |||||
| afterRefresh: 'afterCrudRefresh', | |||||
| /** 删除 - 之前 */ | |||||
| beforeDelete: 'beforeCrudDelete', | |||||
| /** 删除 - 之后 */ | |||||
| afterDelete: 'afterCrudDelete', | |||||
| /** 删除取消 - 之前 */ | |||||
| beforeDeleteCancel: 'beforeCrudDeleteCancel', | |||||
| /** 删除取消 - 之后 */ | |||||
| afterDeleteCancel: 'afterCrudDeleteCancel', | |||||
| /** 新建 - 之前 */ | |||||
| beforeToAdd: 'beforeCrudToAdd', | |||||
| /** 新建 - 之后 */ | |||||
| afterToAdd: 'afterCrudToAdd', | |||||
| /** 编辑 - 之前 */ | |||||
| beforeToEdit: 'beforeCrudToEdit', | |||||
| /** 编辑 - 之后 */ | |||||
| afterToEdit: 'afterCrudToEdit', | |||||
| /** 开始 "新建/编辑" - 之前 */ | |||||
| beforeToCU: 'beforeCrudToCU', | |||||
| /** 开始 "新建/编辑" - 之后 */ | |||||
| afterToCU: 'afterCrudToCU', | |||||
| /** "新建/编辑" 验证 - 之前 */ | |||||
| beforeValidateCU: 'beforeCrudValidateCU', | |||||
| /** "新建/编辑" 验证 - 之后 */ | |||||
| afterValidateCU: 'afterCrudValidateCU', | |||||
| /** 添加取消 - 之前 */ | |||||
| beforeAddCancel: 'beforeCrudAddCancel', | |||||
| /** 添加取消 - 之后 */ | |||||
| afterAddCancel: 'afterCrudAddCancel', | |||||
| /** 编辑取消 - 之前 */ | |||||
| beforeEditCancel: 'beforeCrudEditCancel', | |||||
| /** 编辑取消 - 之后 */ | |||||
| afterEditCancel: 'afterCrudEditCancel', | |||||
| /** 提交 - 之前 */ | |||||
| beforeSubmit: 'beforeCrudSubmitCU', | |||||
| /** 提交 - 之后 */ | |||||
| afterSubmit: 'afterCrudSubmitCU', | |||||
| afterAddError: 'afterCrudAddError', | |||||
| afterEditError: 'afterCrudEditError' | |||||
| } | |||||
| /** | |||||
| * CRUD状态 | |||||
| */ | |||||
| CRUD.STATUS = { | |||||
| NORMAL: 0, | |||||
| PREPARED: 1, | |||||
| PROCESSING: 2 | |||||
| } | |||||
| /** | |||||
| * CRUD通知类型 | |||||
| */ | |||||
| CRUD.NOTIFICATION_TYPE = { | |||||
| SUCCESS: 'success', | |||||
| WARNING: 'warning', | |||||
| INFO: 'info', | |||||
| ERROR: 'error' | |||||
| } | |||||
| export default CRUD | |||||
| export { | |||||
| presenter, | |||||
| header, | |||||
| form, | |||||
| pagination, | |||||
| crud | |||||
| } | |||||
| @@ -0,0 +1,35 @@ | |||||
| <template> | |||||
| <div style="padding: 0 15px;" @click="toggleClick"> | |||||
| <i class="icon icon-hamburger hamburger" :class="{'is-active': !isActive}" /> | |||||
| </div> | |||||
| </template> | |||||
| <script> | |||||
| export default { | |||||
| name: 'Hamburger', | |||||
| props: { | |||||
| isActive: { | |||||
| type: Boolean, | |||||
| default: false | |||||
| } | |||||
| }, | |||||
| methods: { | |||||
| toggleClick() { | |||||
| this.$emit('toggleClick') | |||||
| } | |||||
| } | |||||
| } | |||||
| </script> | |||||
| <style scoped> | |||||
| .hamburger { | |||||
| vertical-align: middle; | |||||
| font-size: 20px; | |||||
| display:inline-block; | |||||
| transform: rotate(180deg); | |||||
| } | |||||
| .hamburger.is-active { | |||||
| transform: rotate(0deg); | |||||
| } | |||||
| </style> | |||||
| @@ -0,0 +1,13 @@ | |||||
| import permission from './permission' | |||||
| const install = function(Vue) { | |||||
| Vue.directive('permission', permission) | |||||
| } | |||||
| if (window.Vue) { | |||||
| window['permission'] = permission | |||||
| Vue.use(install); // eslint-disable-line | |||||
| } | |||||
| permission.install = install | |||||
| export default permission | |||||
| @@ -0,0 +1,21 @@ | |||||
| import store from '@/store' | |||||
| export default { | |||||
| inserted(el, binding) { | |||||
| const { value } = binding | |||||
| const roles = store.getters && store.getters.roles | |||||
| if (value && value instanceof Array) { | |||||
| if (value.length > 0) { | |||||
| const permissionRoles = value | |||||
| const hasPermission = roles.some(role => { | |||||
| return permissionRoles.includes(role) | |||||
| }) | |||||
| if (!hasPermission) { | |||||
| el.parentNode && el.parentNode.removeChild(el) | |||||
| } | |||||
| } | |||||
| } else { | |||||
| throw new Error(`使用方式: v-permission="['admin','editor']"`) | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,208 @@ | |||||
| <template> | |||||
| <div> | |||||
| <!-- @beforeInit="addCustomButtom" --> | |||||
| <VueUeditorWrap | |||||
| class="ueditor" | |||||
| :id="setSessionid()" | |||||
| @ready="readyCustom" | |||||
| v-model="content" | |||||
| :config="config" | |||||
| ></VueUeditorWrap> | |||||
| </div> | |||||
| </template> | |||||
| <script> | |||||
| import VueUeditorWrap from 'vue-ueditor-wrap' | |||||
| export default { | |||||
| components: { | |||||
| VueUeditorWrap | |||||
| }, | |||||
| props: { | |||||
| btnShow: { | |||||
| type: Boolean, | |||||
| default: false | |||||
| }, | |||||
| comeContent: { | |||||
| type: String, | |||||
| default: '' | |||||
| }, | |||||
| zIndex: { | |||||
| type: Number, | |||||
| default: 999 | |||||
| }, | |||||
| isDisabled: { | |||||
| type: Number, | |||||
| default: 0 | |||||
| } | |||||
| }, | |||||
| data() { | |||||
| return { | |||||
| content: '', | |||||
| editor: null, | |||||
| config: { | |||||
| toolbars: [ | |||||
| [ | |||||
| 'source', // 源代码 | |||||
| 'undo', // 撤销 | |||||
| 'redo', // 重做 | |||||
| 'bold', // 加粗 | |||||
| 'indent', // 首行缩进 | |||||
| // 'snapscreen', //截 图 | |||||
| 'italic', // 斜体 | |||||
| 'underline', // 下划线 | |||||
| 'strikethrough', // 删除线 | |||||
| 'subscript', // 下标 | |||||
| 'fontborder', // 字符边框 | |||||
| 'superscript', // 上标 | |||||
| 'formatmatch', // 格式刷 | |||||
| 'blockquote', // 引用 | |||||
| // 'pasteplain', // 纯文本粘贴模式 | |||||
| 'selectall', // 全选 | |||||
| // 'preview', // 预览 | |||||
| 'horizontal', // 分隔线 | |||||
| 'removeformat', // 清除格式 | |||||
| // 'time', // 时间 | |||||
| // 'date', // 日期 | |||||
| 'unlink', // 取消链接 | |||||
| /* 'insertrow', // 前插入行 | |||||
| 'insertcol', // 前插入列 | |||||
| 'mergeright', // 右合并单元格 | |||||
| 'mergedown', // 下合并单元格 | |||||
| 'deleterow', // 删除行 | |||||
| 'deletecol', // 删除列 | |||||
| 'splittorows', // 拆分成行 | |||||
| 'splittocols', // 拆分成列 | |||||
| 'splittocells', // 完全拆分单元格 | |||||
| 'deletecaption', // 删除表格标题 | |||||
| 'inserttitle', // 插入标题 | |||||
| 'mergecells', // 合并多个单元格 | |||||
| 'deletetable', // 删除表格 | |||||
| 'cleardoc', // 清空文档 | |||||
| 'insertparagraphbeforetable', //" 表格前插入行" | |||||
| 'insertcode', // 代码语言 */ | |||||
| 'fontfamily', // 字体 | |||||
| 'fontsize', // 字号 | |||||
| 'paragraph', // 段落格式 | |||||
| // 'edittable', // 表格属性 | |||||
| // 'edittd', // 单元格属性 | |||||
| 'link', // 超链接 | |||||
| // 'emotion', // 表情 | |||||
| 'spechars', // 特殊字符 | |||||
| // 'searchreplace', // 查询替换 | |||||
| 'justifyleft', // 居左对齐 | |||||
| 'justifyright', // 居右对齐 | |||||
| 'justifycenter', // 居中对齐 | |||||
| 'justifyjustify', // 两端对齐 | |||||
| 'forecolor', // 字体颜色 | |||||
| 'backcolor', // 背景色 | |||||
| 'lineheight' // 行间距 | |||||
| /* 'insertorderedlist', // 有序列表 | |||||
| 'insertunorderedlist', // 无序列表 | |||||
| 'directionalityltr', // 从左向右输入 | |||||
| 'directionalityrtl', // 从右向左输入 | |||||
| 'rowspacingtop', // 段前距 | |||||
| 'rowspacingbottom', // 段后距 | |||||
| 'pagebreak', // 分页 | |||||
| 'imagenone', // 默认 | |||||
| 'imageleft', // 左浮动 | |||||
| 'imageright', // 右浮动 | |||||
| 'imagecenter', // 居中 | |||||
| 'wordimage', // 图片转存 | |||||
| 'edittip ', // 编辑提示 | |||||
| 'customstyle', // 自定义标题 | |||||
| 'touppercase', // 字母大写 | |||||
| 'tolowercase', // 字母小写 | |||||
| 'inserttable' // 插入表格 */ | |||||
| ] | |||||
| ], | |||||
| zIndex: this.zIndex, | |||||
| autoHeightEnabled: false, // 编译器不自动被内容撑高 | |||||
| autoFloatEnabled: false, | |||||
| initialFrameHeight: 300, // 初始容器高度 | |||||
| initialFrameWith: '100%', | |||||
| postParams: this.postParams, | |||||
| // serverUrl: process.env.baseUrl + '/product/uploadFiles', // 上传文件地址 | |||||
| UEDITOR_HOME_URL: process.env.VUE_APP_BASE_API + '/editor/UEditor/' // UEditor资源文件的存放路径 | |||||
| } | |||||
| } | |||||
| }, | |||||
| watch: { | |||||
| content(val) { | |||||
| this.$emit('editorChange', val) | |||||
| }, | |||||
| comeContent: { | |||||
| handler(newVal, oldVal) { | |||||
| this.content = newVal | |||||
| }, | |||||
| immediate: true, | |||||
| deep: true | |||||
| } | |||||
| }, | |||||
| mounted() { | |||||
| }, | |||||
| methods: { | |||||
| readyCustom(editor) { | |||||
| this.editor = editor | |||||
| console.log(this.isDisabled) | |||||
| if (this.isDisabled) this.editor.options.readonly = true | |||||
| }, | |||||
| addCustomButtom(editorId) { | |||||
| this.editorId = editorId | |||||
| this.divID = 'key' + editorId | |||||
| var _ = this | |||||
| window.UE.registerUI( | |||||
| 'test-button', | |||||
| function(editor, uiName) { | |||||
| // 注册按钮执行时的 command 命令,使用命令默认就会带有回退操作 | |||||
| editor.registerCommand(uiName, { | |||||
| execCommand: function() { | |||||
| _.editorHandler = editor | |||||
| } | |||||
| }) | |||||
| // 创建一个 button | |||||
| var btn = new window.UE.ui.Button({ | |||||
| // 按钮的名字 | |||||
| name: uiName, | |||||
| // 提示 | |||||
| title: '上传图片', | |||||
| // 需要添加的额外样式,可指定 icon 图标,图标路径参考常见问题 2 | |||||
| // cssRules: `background-image: url("${UploadImg}") !important;background-size: cover;`, | |||||
| // 点击时执行的命令 | |||||
| onclick: function(res) { | |||||
| // 点击新图标时,触发到点击input按钮的事件 | |||||
| editor.execCommand(uiName) | |||||
| document.getElementById('key' + editorId).click() | |||||
| } | |||||
| }) | |||||
| // 当点到编辑内容上时,按钮要做的状态反射 | |||||
| editor.addListener('selectionchange', function() { }) | |||||
| // 因为你是添加 button,所以需要返回这个 button | |||||
| return btn | |||||
| } | |||||
| // 0 /* 指定添加到工具栏上的哪个位置,默认时追加到最后 */ | |||||
| // editorId /* 指定这个 UI 是哪个编辑器实例上的,默认是页面上所有的编辑器都会添加这个按钮 */ | |||||
| ) | |||||
| }, | |||||
| setSessionid(num) { | |||||
| var len = num || 32 | |||||
| var chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678' | |||||
| var maxPos = chars.length | |||||
| var pwd = '' | |||||
| for (var i = 0; i < len; i++) { | |||||
| pwd += chars.charAt(Math.floor(Math.random() * maxPos)) | |||||
| } | |||||
| return pwd | |||||
| } | |||||
| } | |||||
| } | |||||
| </script> | |||||
| <style lang="scss" scoped> | |||||
| ::v-deep.ueditor{ | |||||
| .edui-editor{ | |||||
| width: 100% !important; | |||||
| } | |||||
| } | |||||
| </style> | |||||
| @@ -0,0 +1,96 @@ | |||||
| <!-- | |||||
| * @Autor: Chuting | |||||
| * @Date: 2022-11-02 10:03:47 | |||||
| * @LastEditors: Chuting | |||||
| * @LastEditTime: 2022-11-02 13:56:17 | |||||
| * @Description: 图片上传 | |||||
| --> | |||||
| <template> | |||||
| <el-upload | |||||
| class="avatar-uploader" | |||||
| action="#" | |||||
| :show-file-list="false" | |||||
| :before-upload="beforeAvatarUpload" | |||||
| :http-request="uploadHttpRequest"> | |||||
| <template v-if="url"> | |||||
| <video v-if="type === 'video'" :src="url" class="avatar" controls="controls"> | |||||
| 您的浏览器不支持视频播放 | |||||
| </video> | |||||
| <img v-else :src="url" class="avatar"> | |||||
| </template> | |||||
| <i v-else class="el-icon-plus avatar-uploader-icon"></i> | |||||
| </el-upload> | |||||
| </template> | |||||
| <script> | |||||
| import { upload } from '@/api/common.js' | |||||
| export default { | |||||
| props: { | |||||
| url: { | |||||
| type: String, | |||||
| default: '' | |||||
| }, | |||||
| type: { | |||||
| type: String, | |||||
| default: '' | |||||
| } | |||||
| }, | |||||
| methods: { | |||||
| beforeAvatarUpload(file) { | |||||
| const isType = this.type === 'video' ? (file.type === ('video/mp4' || 'video/avi' || 'video/wmv' || 'video/mov')) : (file.type === 'image/jpeg' || file.type === 'image/png' || file.type === 'image/jpg' || file.type === 'image/gif') | |||||
| const isLt2M = this.type === 'video' ? file.size / 1024 / 1024 < 50 : file.size / 1024 < 500 | |||||
| if (!isType) { | |||||
| let txt = this.type === 'video' ? '请上传正确的视频格式!' : '上传头像图片只能是 JPG/JPEG/PNG/GIF 格式!' | |||||
| this.$message.error(txt) | |||||
| return false | |||||
| } | |||||
| if (!isLt2M) { | |||||
| let txt = this.type === 'video' ? '上传视频大小不能超过 50MB!!' : '上传图片大小不能超过 500KB!' | |||||
| this.$message.error(txt) | |||||
| return false | |||||
| } | |||||
| }, | |||||
| uploadHttpRequest(file) { | |||||
| console.log('pid:', this.$route.params.pid) | |||||
| let obj = { | |||||
| 'page': 'OPERATE', | |||||
| 'template': 'FORMWORK' | |||||
| } | |||||
| let _data = { | |||||
| file: file.file | |||||
| } | |||||
| // OPERATE FORMWORK | |||||
| upload(_data).then(res => { | |||||
| if (res) { | |||||
| this.$emit('update:url', res.url) | |||||
| } | |||||
| }) | |||||
| } | |||||
| } | |||||
| } | |||||
| </script> | |||||
| <style lang="scss" scoped> | |||||
| ::v-deep.avatar-uploader .el-upload { | |||||
| border: 1px dashed #d9d9d9; | |||||
| border-radius: 6px; | |||||
| cursor: pointer; | |||||
| position: relative; | |||||
| overflow: hidden; | |||||
| } | |||||
| ::v-deep.avatar-uploader .el-upload:hover { | |||||
| border-color: #409EFF; | |||||
| } | |||||
| ::v-deep.avatar-uploader-icon { | |||||
| font-size: 28px; | |||||
| color: #8c939d; | |||||
| width: 150px; | |||||
| height: 150px; | |||||
| line-height: 150px; | |||||
| text-align: center; | |||||
| } | |||||
| ::v-deep.avatar { | |||||
| width: 150px; | |||||
| height: 150px; | |||||
| display: block; | |||||
| } | |||||
| </style> | |||||
| @@ -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: auto; | |||||
| } | |||||
| .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> | |||||
| @@ -0,0 +1,139 @@ | |||||
| <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"> | |||||
| <router-link to="/"> | |||||
| <el-dropdown-item> | |||||
| Home | |||||
| </el-dropdown-item> | |||||
| </router-link> | |||||
| <!-- <a target="_blank" href="https://github.com/PanJiaChen/vue-admin-template/"> | |||||
| <el-dropdown-item>Github</el-dropdown-item> | |||||
| </a> | |||||
| <a target="_blank" href="https://panjiachen.github.io/vue-element-admin-site/#/"> | |||||
| <el-dropdown-item>Docs</el-dropdown-item> | |||||
| </a> --> | |||||
| <el-dropdown-item divided @click.native="logout"> | |||||
| <span style="display:block;">Log Out</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> | |||||
| @@ -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) | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -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(<i class={'icon icon-' + 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> | |||||
| @@ -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> | |||||
| @@ -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: '后台管理系统', | |||||
| 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> | |||||
| @@ -0,0 +1,95 @@ | |||||
| <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 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 {} | |||||
| }, | |||||
| methods: { | |||||
| hasOneShowingChild(children = [], parent) { | |||||
| const showingChildren = children.filter(item => { | |||||
| if (item.hidden) { | |||||
| 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> | |||||
| @@ -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 '@/assets/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> | |||||
| @@ -0,0 +1,3 @@ | |||||
| export { default as Navbar } from './Navbar' | |||||
| export { default as Sidebar } from './Sidebar' | |||||
| export { default as AppMain } from './AppMain' | |||||
| @@ -0,0 +1,97 @@ | |||||
| <template> | |||||
| <div :class="classObj" class="app-wrapper"> | |||||
| <div v-if="device==='mobile'&& sidebar.opened && isChild" class="drawer-bg" @click="handleClickOutside" /> | |||||
| <sidebar v-if="isChild" class="sidebar-container" /> | |||||
| <div class="main-container" :style="{marginLeft: isChild ? null : 0 }"> | |||||
| <div v-if="isChild" :class="{'fixed-header':fixedHeader}"> | |||||
| <navbar /> | |||||
| </div> | |||||
| <app-main /> | |||||
| </div> | |||||
| </div> | |||||
| </template> | |||||
| <script> | |||||
| import { Navbar, Sidebar, AppMain } from './components' | |||||
| import ResizeMixin from './mixin/ResizeHandler' | |||||
| import { mapGetters } from 'vuex' | |||||
| 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' | |||||
| } | |||||
| }, | |||||
| ...mapGetters([ | |||||
| 'isChild' | |||||
| ]) | |||||
| }, | |||||
| methods: { | |||||
| handleClickOutside() { | |||||
| this.$store.dispatch('app/closeSideBar', { withoutAnimation: false }) | |||||
| } | |||||
| } | |||||
| } | |||||
| </script> | |||||
| <style lang="scss" scoped> | |||||
| @import "~@/assets/styles/mixin.scss"; | |||||
| @import "~@/assets/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> | |||||
| @@ -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 }) | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,85 @@ | |||||
| /* | |||||
| * @Date: 2021-07-01 15:30:29 | |||||
| * @LastEditTime: 2022-07-27 10:47:36 | |||||
| * @Description: 增加微应用配置 | |||||
| */ | |||||
| import './public-path' | |||||
| import Vue from 'vue' | |||||
| import 'normalize.css/normalize.css' // A modern alternative to CSS resets | |||||
| import ElementUI from 'element-ui' | |||||
| import './assets/styles/element-variables.scss' | |||||
| import '@/assets/styles/index.scss' // global css | |||||
| import App from './App' | |||||
| import store from './store' | |||||
| import router from './router' | |||||
| import '@/assets/icon/icons.css' | |||||
| // 权限控制 | |||||
| import checkPer from '@/utils/permission' | |||||
| import permission from './components/Permission' | |||||
| import { setToken } from '@/utils/auth' | |||||
| Vue.use(checkPer) | |||||
| Vue.use(permission) | |||||
| Vue.use(ElementUI, { | |||||
| size: 'small' | |||||
| }) | |||||
| Vue.config.productionTip = false | |||||
| let instance = null | |||||
| function render({ container } = {}) { | |||||
| instance = new Vue({ | |||||
| router, | |||||
| store, | |||||
| render: h => h(App) | |||||
| }).$mount(container ? container.querySelector('#app') : '#app') | |||||
| // index.html 里面的 id 需要改成 appVueHash,否则子项目无法独立运行 | |||||
| } | |||||
| if (!window.__POWERED_BY_QIANKUN__) { | |||||
| // 全局变量来判断环境 | |||||
| render() | |||||
| } | |||||
| /** | |||||
| * bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。 | |||||
| * 通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等。 | |||||
| */ | |||||
| export async function bootstrap() { | |||||
| console.log('现在进入子应用content-cloud的bootstraped阶段') | |||||
| } | |||||
| /** | |||||
| * 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法 | |||||
| */ | |||||
| export async function mount(props) { | |||||
| console.log('现在进入子应用content-cloud的mount周期', props) | |||||
| setToken(props.token) | |||||
| store.dispatch('user/setChild', false ) | |||||
| store.dispatch('user/setRoles', props.roles ) | |||||
| store.dispatch('user/setToken', props.token ) | |||||
| render(props) | |||||
| } | |||||
| /** | |||||
| * 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例 | |||||
| */ | |||||
| export async function unmount() { | |||||
| console.log('现在进入子应用content-cloud的unmount阶段') | |||||
| instance.$destroy() | |||||
| instance.$el.innerHTML = '' | |||||
| instance = null | |||||
| // router = null | |||||
| } | |||||
| /** | |||||
| * 可选生命周期钩子,仅使用 loadMicroApp 方式加载微应用时生效 | |||||
| */ | |||||
| export async function update(props) { | |||||
| console.log('update props', props) | |||||
| } | |||||
| @@ -0,0 +1,335 @@ | |||||
| import { | |||||
| initData, | |||||
| download | |||||
| } from '@/api/data' | |||||
| import { | |||||
| parseTime, | |||||
| downloadFile | |||||
| } from '@/utils/index' | |||||
| export default { | |||||
| data() { | |||||
| return { | |||||
| // 表格数据 | |||||
| data: [], | |||||
| // 排序规则,默认 id 降序, 支持多字段排序 ['id,desc', 'createTime,asc'] | |||||
| sort: ['id,desc'], | |||||
| // 页码 | |||||
| page: 0, | |||||
| // 每页数据条数 | |||||
| size: 10, | |||||
| // 总数据条数 | |||||
| total: 0, | |||||
| // 请求数据的url | |||||
| url: '', | |||||
| // 查询数据的参数 | |||||
| params: {}, | |||||
| // 待查询的对象 | |||||
| query: {}, | |||||
| // 等待时间 | |||||
| time: 50, | |||||
| // 是否为新增类型的表单 | |||||
| isAdd: false, | |||||
| // 导出的 Loading | |||||
| downloadLoading: false, | |||||
| // 表格 Loading 属性 | |||||
| loading: true, | |||||
| // 删除 Loading 属性 | |||||
| delLoading: false, | |||||
| delAllLoading: false, | |||||
| // 弹窗属性 | |||||
| dialog: false, | |||||
| // Form 表单 | |||||
| form: {}, | |||||
| // 重置表单 | |||||
| resetForm: {}, | |||||
| // 标题 | |||||
| title: '' | |||||
| } | |||||
| }, | |||||
| methods: { | |||||
| parseTime, | |||||
| downloadFile, | |||||
| async init() { | |||||
| if (!await this.beforeInit()) { | |||||
| return | |||||
| } | |||||
| return new Promise((resolve, reject) => { | |||||
| this.loading = true | |||||
| // 请求数据 | |||||
| initData(this.url, this.getQueryParame()).then(data => { | |||||
| this.total = data.totalElements | |||||
| this.data = data.content | |||||
| // time 毫秒后显示表格 | |||||
| setTimeout(() => { | |||||
| this.loading = false | |||||
| }, this.time) | |||||
| resolve(data) | |||||
| }).catch(err => { | |||||
| this.loading = false | |||||
| reject(err) | |||||
| }) | |||||
| }) | |||||
| }, | |||||
| beforeInit() { | |||||
| return true | |||||
| }, | |||||
| getQueryParame: function() { | |||||
| return { | |||||
| pageNum: this.page, | |||||
| pageSize: this.size, | |||||
| sort: this.sort, | |||||
| ...this.query, | |||||
| ...this.params | |||||
| } | |||||
| }, | |||||
| // 改变页码 | |||||
| pageChange(e) { | |||||
| this.page = e - 1 | |||||
| this.init() | |||||
| }, | |||||
| // 改变每页显示数 | |||||
| sizeChange(e) { | |||||
| this.page = 0 | |||||
| this.size = e | |||||
| this.init() | |||||
| }, | |||||
| // 预防删除第二页最后一条数据时,或者多选删除第二页的数据时,页码错误导致请求无数据 | |||||
| dleChangePage(size) { | |||||
| if (size === undefined) { | |||||
| size = 1 | |||||
| } | |||||
| if (this.data.length === size && this.page !== 0) { | |||||
| this.page = this.page - 1 | |||||
| } | |||||
| }, | |||||
| // 查询方法 | |||||
| toQuery() { | |||||
| this.page = 0 | |||||
| this.init() | |||||
| }, | |||||
| /** | |||||
| * 通用的提示封装 | |||||
| */ | |||||
| submitSuccessNotify() { | |||||
| this.$notify({ | |||||
| title: '提交成功', | |||||
| type: 'success', | |||||
| duration: 2500 | |||||
| }) | |||||
| }, | |||||
| addSuccessNotify() { | |||||
| this.$notify({ | |||||
| title: '新增成功', | |||||
| type: 'success', | |||||
| duration: 2500 | |||||
| }) | |||||
| }, | |||||
| editSuccessNotify() { | |||||
| this.$notify({ | |||||
| title: '编辑成功', | |||||
| type: 'success', | |||||
| duration: 2500 | |||||
| }) | |||||
| }, | |||||
| delSuccessNotify() { | |||||
| this.$notify({ | |||||
| title: '删除成功', | |||||
| type: 'success', | |||||
| duration: 2500 | |||||
| }) | |||||
| }, | |||||
| notify(title, type) { | |||||
| this.$notify({ | |||||
| title: title, | |||||
| type: type, | |||||
| duration: 2500 | |||||
| }) | |||||
| }, | |||||
| /** | |||||
| * 删除前可以调用 beforeDelMethod 做一些操作 | |||||
| */ | |||||
| beforeDelMethod() { | |||||
| return true | |||||
| }, | |||||
| /** | |||||
| * 通用的删除 | |||||
| */ | |||||
| delMethod(id) { | |||||
| if (!this.beforeDelMethod()) { | |||||
| return | |||||
| } | |||||
| this.delLoading = true | |||||
| this.crudMethod.del(id).then(() => { | |||||
| this.delLoading = false | |||||
| this.$refs[id].doClose() | |||||
| this.dleChangePage() | |||||
| this.delSuccessNotify() | |||||
| this.afterDelMethod() | |||||
| this.init() | |||||
| }).catch(() => { | |||||
| this.delLoading = false | |||||
| this.$refs[id].doClose() | |||||
| }) | |||||
| }, | |||||
| afterDelMethod() {}, | |||||
| /** | |||||
| * 多选删除提示 | |||||
| */ | |||||
| beforeDelAllMethod() { | |||||
| this.$confirm('你确定删除选中的数据吗?', '提示', { | |||||
| confirmButtonText: '确定', | |||||
| cancelButtonText: '取消', | |||||
| type: 'warning' | |||||
| }).then(() => { | |||||
| this.delAllMethod() | |||||
| }) | |||||
| }, | |||||
| /** | |||||
| * 多选删除 | |||||
| */ | |||||
| delAllMethod() { | |||||
| this.delAllLoading = true | |||||
| const data = this.$refs.table.selection | |||||
| const ids = [] | |||||
| for (let i = 0; i < data.length; i++) { | |||||
| ids.push(data[i].id) | |||||
| } | |||||
| this.crudMethod.delAll(ids).then(() => { | |||||
| this.delAllLoading = false | |||||
| this.dleChangePage(ids.length) | |||||
| this.init() | |||||
| this.$notify({ | |||||
| title: '删除成功', | |||||
| type: 'success', | |||||
| duration: 2500 | |||||
| }) | |||||
| }).catch(() => { | |||||
| this.delAllLoading = false | |||||
| }) | |||||
| }, | |||||
| /** | |||||
| * 显示新增弹窗前可以调用该方法 | |||||
| */ | |||||
| beforeShowAddForm() {}, | |||||
| /** | |||||
| * 显示新增弹窗 | |||||
| */ | |||||
| showAddFormDialog() { | |||||
| this.isAdd = true | |||||
| this.resetForm = JSON.parse(JSON.stringify(this.form)) | |||||
| this.beforeShowAddForm() | |||||
| this.dialog = true | |||||
| }, | |||||
| /** | |||||
| * 显示编辑弹窗前可以调用该方法 | |||||
| */ | |||||
| beforeShowEditForm(data) {}, | |||||
| /** | |||||
| * 显示编辑弹窗 | |||||
| */ | |||||
| showEditFormDialog(data = '') { | |||||
| this.isAdd = false | |||||
| if (data) { | |||||
| this.resetForm = JSON.parse(JSON.stringify(this.form)) | |||||
| this.form = JSON.parse(JSON.stringify(data)) | |||||
| } | |||||
| this.beforeShowEditForm(data) | |||||
| this.dialog = true | |||||
| }, | |||||
| /** | |||||
| * 新增方法 | |||||
| */ | |||||
| addMethod() { | |||||
| this.crudMethod.add(this.form).then(() => { | |||||
| this.addSuccessNotify() | |||||
| this.loading = false | |||||
| this.afterAddMethod() | |||||
| this.cancel() | |||||
| this.init() | |||||
| }).catch(() => { | |||||
| this.loading = false | |||||
| this.afterAddErrorMethod() | |||||
| }) | |||||
| }, | |||||
| /** | |||||
| * 新增后可以调用该方法 | |||||
| */ | |||||
| afterAddMethod() {}, | |||||
| /** | |||||
| * 新增失败后调用该方法 | |||||
| */ | |||||
| afterAddErrorMethod() {}, | |||||
| /** | |||||
| * 通用的编辑方法 | |||||
| */ | |||||
| editMethod() { | |||||
| this.crudMethod.edit(this.form).then(() => { | |||||
| this.editSuccessNotify() | |||||
| this.loading = false | |||||
| this.afterEditMethod() | |||||
| this.cancel() | |||||
| this.init() | |||||
| }).catch(() => { | |||||
| this.loading = false | |||||
| }) | |||||
| }, | |||||
| /** | |||||
| * 编辑后可以调用该方法 | |||||
| */ | |||||
| afterEditMethod() {}, | |||||
| /** | |||||
| * 提交前可以调用该方法 | |||||
| */ | |||||
| beforeSubmitMethod() { | |||||
| return true | |||||
| }, | |||||
| /** | |||||
| * 提交 | |||||
| */ | |||||
| submitMethod() { | |||||
| if (!this.beforeSubmitMethod()) { | |||||
| return | |||||
| } | |||||
| if (this.$refs['form']) { | |||||
| this.$refs['form'].validate((valid) => { | |||||
| if (valid) { | |||||
| this.loading = true | |||||
| if (this.isAdd) { | |||||
| this.addMethod() | |||||
| } else this.editMethod() | |||||
| } | |||||
| }) | |||||
| } | |||||
| }, | |||||
| /** | |||||
| * 隐藏弹窗 | |||||
| */ | |||||
| cancel() { | |||||
| this.dialog = false | |||||
| if (this.$refs['form']) { | |||||
| this.$refs['form'].clearValidate() | |||||
| this.form = this.resetForm | |||||
| } | |||||
| }, | |||||
| /** | |||||
| * 获取弹窗的标题 | |||||
| */ | |||||
| getFormTitle() { | |||||
| return this.isAdd ? `新增${this.title}` : `编辑${this.title}` | |||||
| }, | |||||
| /** | |||||
| * 通用导出 | |||||
| */ | |||||
| downloadMethod() { | |||||
| this.beforeInit() | |||||
| this.downloadLoading = true | |||||
| download(this.url + '/download', this.params).then(result => { | |||||
| this.downloadFile(result, this.title + '数据', 'xlsx') | |||||
| this.downloadLoading = false | |||||
| }).catch(() => { | |||||
| this.downloadLoading = false | |||||
| }) | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,7 @@ | |||||
| /* eslint-disable camelcase */ | |||||
| if (window.__POWERED_BY_QIANKUN__) { | |||||
| // eslint-disable-next-line no-undef | |||||
| __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__ | |||||
| } | |||||
| @@ -0,0 +1,114 @@ | |||||
| 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 | |||||
| }, | |||||
| { | |||||
| path: '/404', | |||||
| component: () => import('@/views/404'), | |||||
| hidden: true | |||||
| }, | |||||
| { | |||||
| path: '/', | |||||
| component: Layout, | |||||
| redirect: '/home', | |||||
| children: [{ | |||||
| path: 'home', | |||||
| name: 'home', | |||||
| component: () => import('@/views/home/index'), | |||||
| meta: { title: 'Home', icon: 'dashboard' } | |||||
| }] | |||||
| }, | |||||
| { | |||||
| path: '/homePage', | |||||
| component: Layout, | |||||
| alwaysShow: true, | |||||
| redirect: '/homePage/carousel', | |||||
| name: 'homePage', | |||||
| meta: { | |||||
| title: '首页', | |||||
| icon: 'nested' | |||||
| }, | |||||
| children: [ | |||||
| { | |||||
| path: 'carousel', | |||||
| component: () => import('@/views/homePage/carousel'), | |||||
| meta: { title: '轮播' } | |||||
| }, | |||||
| { | |||||
| path: 'carHot', | |||||
| component: () => import('@/views/homePage/carHot'), | |||||
| meta: { title: '汽车资讯热门' } | |||||
| }, | |||||
| { | |||||
| path: 'banner1', | |||||
| component: () => import('@/views/homePage/banner1'), | |||||
| meta: { title: '广告位1' } | |||||
| }, | |||||
| { | |||||
| path: 'yyzHot', | |||||
| component: () => import('@/views/homePage/yyzHot'), | |||||
| meta: { title: '元宇宙日爆热门' } | |||||
| }, | |||||
| { | |||||
| path: 'banner2', | |||||
| component: () => import('@/views/homePage/banner2'), | |||||
| meta: { title: '广告位2' } | |||||
| } | |||||
| ] | |||||
| } | |||||
| // 404 page must be placed at the end !!! | |||||
| // { path: '*', redirect: '/404', hidden: true } | |||||
| ] | |||||
| const createRouter = () => new Router({ | |||||
| base: window.__POWERED_BY_QIANKUN__ ? '/vdcop_auth/admin' : '/admin', | |||||
| 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 | |||||
| } | |||||
| export default router | |||||
| @@ -0,0 +1,16 @@ | |||||
| module.exports = { | |||||
| title: '后台管理系统', | |||||
| /** | |||||
| * @type {boolean} true | false | |||||
| * @description Whether fix the header | |||||
| */ | |||||
| fixedHeader: false, | |||||
| /** | |||||
| * @type {boolean} true | false | |||||
| * @description Whether show the logo in sidebar | |||||
| */ | |||||
| sidebarLogo: false | |||||
| } | |||||
| @@ -0,0 +1,15 @@ | |||||
| /* | |||||
| * @Date: 2021-07-16 17:52:04 | |||||
| * @LastEditTime: 2022-07-27 10:48:11 | |||||
| * @Description: | |||||
| */ | |||||
| 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, | |||||
| isChild: state => state.user.isChild, | |||||
| roles: state => state.user.roles | |||||
| } | |||||
| export default getters | |||||
| @@ -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 | |||||
| @@ -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 | |||||
| } | |||||
| @@ -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 | |||||
| } | |||||
| @@ -0,0 +1,114 @@ | |||||
| import { login, logout, getInfo } from '@/api/user' | |||||
| import { getToken, removeToken } from '@/utils/auth' | |||||
| import { resetRouter } from '@/router' | |||||
| const getDefaultState = () => { | |||||
| return { | |||||
| token: getToken(), | |||||
| name: '', | |||||
| avatar: '', | |||||
| isChild: true, | |||||
| roles: [] | |||||
| } | |||||
| } | |||||
| const state = getDefaultState() | |||||
| const mutations = { | |||||
| RESET_STATE: (state) => { | |||||
| Object.assign(state, getDefaultState()) | |||||
| }, | |||||
| SET_TOKEN: (state, token) => { | |||||
| state.token = token | |||||
| }, | |||||
| SET_NAME: (state, name) => { | |||||
| state.name = name | |||||
| }, | |||||
| SET_AVATAR: (state, avatar) => { | |||||
| state.avatar = avatar | |||||
| }, | |||||
| SET_ISCHILD: (state, isChild) => { | |||||
| state.isChild = isChild | |||||
| }, | |||||
| SET_ROLES: (state, roles) => { | |||||
| state.roles = roles | |||||
| } | |||||
| } | |||||
| const actions = { | |||||
| setChild({ commit }, isChild) { | |||||
| commit('SET_ISCHILD', isChild) | |||||
| }, | |||||
| setRoles({ commit }, roles) { | |||||
| commit('SET_ROLES', roles) | |||||
| }, | |||||
| setToken({ commit }, token) { | |||||
| commit('SET_TOKEN', token) | |||||
| }, | |||||
| // 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) | |||||
| setToken(data.token) | |||||
| resolve() | |||||
| }).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, avatar } = data | |||||
| commit('SET_NAME', name) | |||||
| 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 | |||||
| } | |||||
| @@ -0,0 +1,15 @@ | |||||
| import Cookies from 'js-cookie' | |||||
| const TokenKey = 'token' | |||||
| export function getToken() { | |||||
| return Cookies.get(TokenKey) | |||||
| } | |||||
| export function setToken(token) { | |||||
| return Cookies.set(TokenKey, token) | |||||
| } | |||||
| export function removeToken() { | |||||
| return Cookies.remove(TokenKey) | |||||
| } | |||||
| @@ -0,0 +1,36 @@ | |||||
| import Vue from 'vue' | |||||
| import Clipboard from 'clipboard' | |||||
| function clipboardSuccess() { | |||||
| Vue.prototype.$message({ | |||||
| message: 'Copy successfully', | |||||
| type: 'success', | |||||
| duration: 1500 | |||||
| }) | |||||
| } | |||||
| function clipboardError() { | |||||
| Vue.prototype.$message({ | |||||
| message: 'Copy failed', | |||||
| type: 'error' | |||||
| }) | |||||
| } | |||||
| export default function handleClipboard(text, event) { | |||||
| const clipboard = new Clipboard(event.target, { | |||||
| text: () => text | |||||
| }) | |||||
| clipboard.on('success', () => { | |||||
| clipboardSuccess() | |||||
| clipboard.off('error') | |||||
| clipboard.off('success') | |||||
| clipboard.destroy() | |||||
| }) | |||||
| clipboard.on('error', () => { | |||||
| clipboardError() | |||||
| clipboard.off('error') | |||||
| clipboard.off('success') | |||||
| clipboard.destroy() | |||||
| }) | |||||
| clipboard.onClick(event) | |||||
| } | |||||
| @@ -0,0 +1,17 @@ | |||||
| /* | |||||
| * @Autor: Chuting | |||||
| * @Date: 2021-09-07 17:13:12 | |||||
| * @LastEditors: Chuting | |||||
| * @LastEditTime: 2022-07-27 10:48:18 | |||||
| * @Description: | |||||
| */ | |||||
| import defaultSettings from '@/settings' | |||||
| const title = defaultSettings.title || '后台管理系统' | |||||
| export default function getPageTitle(pageTitle) { | |||||
| if (pageTitle) { | |||||
| return `${pageTitle} - ${title}` | |||||
| } | |||||
| return `${title}` | |||||
| } | |||||
| @@ -0,0 +1,171 @@ | |||||
| /** | |||||
| * 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 | |||||
| } | |||||
| /** | |||||
| * @param {Function} func | |||||
| * @param {number} wait | |||||
| * @param {boolean} immediate | |||||
| * @return {*} | |||||
| */ | |||||
| export function debounce(func, wait, immediate) { | |||||
| let timeout, args, context, timestamp, result | |||||
| const later = function() { | |||||
| // 据上一次触发时间间隔 | |||||
| const last = +new Date() - timestamp | |||||
| // 上次被包装函数被调用时间间隔 last 小于设定时间间隔 wait | |||||
| if (last < wait && last > 0) { | |||||
| timeout = setTimeout(later, wait - last) | |||||
| } else { | |||||
| timeout = null | |||||
| // 如果设定为immediate===true,因为开始边界已经调用过了此处无需调用 | |||||
| if (!immediate) { | |||||
| result = func.apply(context, args) | |||||
| if (!timeout) context = args = null | |||||
| } | |||||
| } | |||||
| } | |||||
| return function(...args) { | |||||
| context = this | |||||
| timestamp = +new Date() | |||||
| const callNow = immediate && !timeout | |||||
| // 如果延时不存在,重新设定延时 | |||||
| if (!timeout) timeout = setTimeout(later, wait) | |||||
| if (callNow) { | |||||
| result = func.apply(context, args) | |||||
| context = args = null | |||||
| } | |||||
| return result | |||||
| } | |||||
| } | |||||
| // 下载文件 | |||||
| export function downloadFile(obj, name, suffix) { | |||||
| const url = window.URL.createObjectURL(new Blob([obj])) | |||||
| const link = document.createElement('a') | |||||
| link.style.display = 'none' | |||||
| link.href = url | |||||
| const fileName = parseTime(new Date()) + '-' + name + '.' + suffix | |||||
| link.setAttribute('download', fileName) | |||||
| document.body.appendChild(link) | |||||
| link.click() | |||||
| document.body.removeChild(link) | |||||
| } | |||||
| @@ -0,0 +1,23 @@ | |||||
| import store from '@/store' | |||||
| /** | |||||
| * @param {Array} value | |||||
| * @returns {Boolean} | |||||
| * @example see @/views/permission/directive.vue | |||||
| */ | |||||
| export default { | |||||
| install(Vue) { | |||||
| Vue.prototype.checkPer = (value) => { | |||||
| if (value && value instanceof Array && value.length > 0) { | |||||
| const roles = store.getters && store.getters.roles | |||||
| const permissionRoles = value | |||||
| return roles.some(role => { | |||||
| return permissionRoles.includes(role) | |||||
| }) | |||||
| } else { | |||||
| console.error(`need roles! Like v-permission="['admin','editor']"`) | |||||
| return false | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,71 @@ | |||||
| /* | |||||
| * @Date: 2021-07-01 15:30:29 | |||||
| * @LastEditTime: 2022-07-27 10:48:39 | |||||
| * @Description: 请求封装 | |||||
| */ | |||||
| import axios from 'axios' | |||||
| import { Notification } 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, // api 的 base_url | |||||
| // withCredentials: true, // send cookies when cross-domain requests | |||||
| timeout: 1000 * 30 // request timeout | |||||
| }) | |||||
| // request interceptor | |||||
| service.interceptors.request.use( | |||||
| config => { | |||||
| // do something before request is sent | |||||
| // const AccessToken = localStorage.getItem("AccessToken") | |||||
| // if (AccessToken) { | |||||
| // config.headers.token = AccessToken | |||||
| // } | |||||
| 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 | |||||
| // 兼容blob下载 | |||||
| if (res instanceof Blob && res.type.toLowerCase().indexOf('json') === -1) { | |||||
| return res | |||||
| } | |||||
| if (res.code === 200) { | |||||
| return res.data | |||||
| } | |||||
| Notification.error({ | |||||
| title: res.message, | |||||
| duration: 5000 | |||||
| }) | |||||
| return Promise.reject(res.message || 'Error') | |||||
| }, | |||||
| error => { | |||||
| Notification.error({ | |||||
| title: '操作失败!', | |||||
| duration: 5000 | |||||
| }) | |||||
| return Promise.reject(error) | |||||
| } | |||||
| ) | |||||
| export default service | |||||
| @@ -0,0 +1,36 @@ | |||||
| /** | |||||
| * 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) { | |||||
| const valid_map = ['admin', 'editor'] | |||||
| return valid_map.indexOf(str.trim()) >= 0 | |||||
| } | |||||
| /** | |||||
| * 邮箱 | |||||
| * @param {*} s | |||||
| */ | |||||
| export function isEmail(s) { | |||||
| return /^([a-zA-Z0-9_-])+@([a-zA-Z0-9_-])+((.[a-zA-Z0-9_-]{2,3}){1,2})$/.test(s) | |||||
| } | |||||
| /** | |||||
| * 手机号码 | |||||
| * @param {*} s | |||||
| */ | |||||
| export function isMobile(s) { | |||||
| return /^1[0-9]{10}$/.test(s) | |||||
| } | |||||
| @@ -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> | |||||
| @@ -0,0 +1,115 @@ | |||||
| <!-- | |||||
| * @Date: 2021-07-08 11:02:40 | |||||
| * @LastEditTime: 2022-07-27 10:48:43 | |||||
| * @Description: 文章列表 | |||||
| --> | |||||
| <template> | |||||
| <div class="app-container"> | |||||
| <!--工具栏--> | |||||
| <div class="head-container"> | |||||
| <div> | |||||
| <!-- 搜索 --> | |||||
| <el-input v-model="query.name" prefix-icon="el-icon-search" clearable placeholder="名称" class="filter-item" @keyup.enter.native="crud.toQuery" /> | |||||
| <el-input v-model="query.releaseName" clearable placeholder="活动名称" class="filter-item"/> | |||||
| <el-select v-model="query.list" clearable placeholder="列表" class="filter-item"> | |||||
| <el-option v-for="item in releaseSourceList" :key="item.id" :label="item.name" :value="item.id"></el-option> | |||||
| </el-select> | |||||
| <el-date-picker v-model="query.times" value-format="yyyy-MM-dd HH:mm:ss" type="datetimerange" range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" class="date-item" style="width:300px !important; margin: 0 3px 10px 0;"></el-date-picker> | |||||
| <rrOperation /> | |||||
| </div> | |||||
| <div> | |||||
| <el-button class="filter-item" size="mini" type="primary" icon="el-icon-plus" @click="openSave()">新增</el-button> | |||||
| <el-button class="filter-item" size="mini" type="warning" icon="el-icon-download" @click="exportFile">导出</el-button> | |||||
| </div> | |||||
| </div> | |||||
| <!--表格渲染--> | |||||
| <el-table ref="table" v-loading="crud.loading" :data="crud.data" style="width: 100%;"> | |||||
| <el-table-column :show-overflow-tooltip="true" prop="id" label="活动编号" /> | |||||
| <el-table-column :show-overflow-tooltip="true" prop="releaseName" label="活动名称" /> | |||||
| <el-table-column :show-overflow-tooltip="true" prop="contentTitle" label="文章名称" /> | |||||
| <el-table-column :show-overflow-tooltip="true" prop="releaseTime" label="发布时间" /> | |||||
| <el-table-column :show-overflow-tooltip="true" prop="publisher" label="发布人" /> | |||||
| <el-table-column label="操作" width="230" align="center" fixed="right"> | |||||
| <template slot-scope="scope" class="btn"> | |||||
| <el-button type="text" size="mini" @click="openSave(scope.row.id)">编辑</el-button> | |||||
| <el-popover placement="top" width="200" :ref="`del-${scope.$index}`"> | |||||
| <p>是否确认删除该数据?</p> | |||||
| <div style="text-align: right; margin: 0"> | |||||
| <el-button size="mini" @click="scope._self.$refs[`del-${scope.$index}`].doClose()">取消</el-button> | |||||
| <el-button type="primary" size="mini" @click="del(scope)">确定</el-button> | |||||
| </div> | |||||
| <el-button slot="reference" type="text" size="mini" style="margin-left:10px">删除</el-button> | |||||
| </el-popover> | |||||
| </template> | |||||
| </el-table-column> | |||||
| </el-table> | |||||
| <!--分页组件--> | |||||
| <pagination /> | |||||
| <!-- 新增修改弹窗 --> | |||||
| <el-dialog v-if="dialogVisible" title="标题" :visible.sync="dialogVisible" append-to-body width="80%"> | |||||
| <save-page :id='id' @close="close"></save-page> | |||||
| </el-dialog> | |||||
| </div> | |||||
| </template> | |||||
| <script> | |||||
| import demoService from '@/api/nested/demo' | |||||
| import CRUD, { presenter, header, crud } from '@crud/crud' | |||||
| import rrOperation from '@crud/RR.operation' | |||||
| import pagination from '@crud/Pagination' | |||||
| import { downloadFile } from '@/utils/index' | |||||
| import savePage from './save' | |||||
| export default { | |||||
| name: 'Demo', | |||||
| components: { rrOperation, pagination, savePage }, | |||||
| mixins: [presenter(), header(), crud()], | |||||
| data() { | |||||
| return { | |||||
| releaseSourceList: [], | |||||
| dialogVisible: false, | |||||
| id: '' | |||||
| } | |||||
| }, | |||||
| cruds() { | |||||
| return CRUD({ title: '列表', url: 'xxx', query: {}}) | |||||
| }, | |||||
| methods: { | |||||
| /* 刷新前操作 */ | |||||
| [CRUD.HOOK.beforeRefresh]() { | |||||
| this.query.id = 'xxx' | |||||
| }, | |||||
| openSave(id) { | |||||
| this.id = id || '' | |||||
| this.dialogVisible = true | |||||
| }, | |||||
| close(tag) { | |||||
| if (tag) this.crud.toQuery() | |||||
| this.dialogVisible = false | |||||
| }, | |||||
| /* 删除 */ | |||||
| del(scope) { | |||||
| demoService.del(scope.row.id).then(res => { | |||||
| this.$message({ | |||||
| message: '删除成功!', | |||||
| type: 'success' | |||||
| }) | |||||
| this.crud.toQuery() | |||||
| scope._self.$refs[`del-${scope.$index}`].doClose() | |||||
| }) | |||||
| }, | |||||
| /* 导出 */ | |||||
| exportFile() { | |||||
| demoService.exportFile(this.crud.query).then(res => { | |||||
| downloadFile(res, '文章列表', 'xlsx') | |||||
| }) | |||||
| } | |||||
| } | |||||
| } | |||||
| </script> | |||||
| @@ -0,0 +1,112 @@ | |||||
| <!-- | |||||
| * @Date: 2021-10-11 14:12:26 | |||||
| * @LastEditTime: 2022-07-27 10:48:47 | |||||
| * @Description: 新增/编辑页面 | |||||
| --> | |||||
| <template> | |||||
| <el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-width="100px" class="resetform"> | |||||
| <el-form-item label="文章标题" prop="name"> | |||||
| <el-input v-model="ruleForm.name"></el-input> | |||||
| </el-form-item> | |||||
| <el-form-item label="活动区域" prop="region"> | |||||
| <el-select v-model="ruleForm.region" placeholder="请选择活动区域"> | |||||
| <el-option label="区域一" value="shanghai"></el-option> | |||||
| <el-option label="区域二" value="beijing"></el-option> | |||||
| </el-select> | |||||
| </el-form-item> | |||||
| <el-form-item label="活动性质" prop="type"> | |||||
| <el-checkbox-group v-model="ruleForm.type"> | |||||
| <el-checkbox label="美食/餐厅线上活动" name="type"></el-checkbox> | |||||
| <el-checkbox label="地推活动" name="type"></el-checkbox> | |||||
| <el-checkbox label="线下主题活动" name="type"></el-checkbox> | |||||
| <el-checkbox label="单纯品牌曝光" name="type"></el-checkbox> | |||||
| </el-checkbox-group> | |||||
| </el-form-item> | |||||
| <el-form-item label="特殊资源" prop="resource"> | |||||
| <el-radio-group v-model="ruleForm.resource"> | |||||
| <el-radio label="线上品牌商赞助"></el-radio> | |||||
| <el-radio label="线下场地免费"></el-radio> | |||||
| </el-radio-group> | |||||
| </el-form-item> | |||||
| <el-form-item label="活动形式" prop="desc"> | |||||
| <el-input type="textarea" v-model="ruleForm.desc"></el-input> | |||||
| </el-form-item> | |||||
| <el-form-item> | |||||
| <el-button type="primary" :loading="loadingBtn" @click="submitForm('ruleForm')">立即创建</el-button> | |||||
| <el-button @click="resetForm('ruleForm')">重置</el-button> | |||||
| </el-form-item> | |||||
| </el-form> | |||||
| </template> | |||||
| <script> | |||||
| import demoService from '@/api/nested/demo' | |||||
| export default { | |||||
| props: ['id'], | |||||
| data() { | |||||
| return { | |||||
| ruleForm: { | |||||
| name: '', | |||||
| region: '', | |||||
| type: [], | |||||
| resource: '', | |||||
| desc: '' | |||||
| }, | |||||
| rules: { | |||||
| name: [ | |||||
| { required: true, message: '请输入活动名称', trigger: 'blur' }, | |||||
| { min: 3, max: 5, message: '长度在 3 到 5 个字符', trigger: 'blur' } | |||||
| ], | |||||
| region: [ | |||||
| { required: true, message: '请选择活动区域', trigger: 'change' } | |||||
| ], | |||||
| type: [ | |||||
| { type: 'array', required: true, message: '请至少选择一个活动性质', trigger: 'change' } | |||||
| ], | |||||
| resource: [ | |||||
| { required: true, message: '请选择活动资源', trigger: 'change' } | |||||
| ], | |||||
| desc: [ | |||||
| { required: true, message: '请填写活动形式', trigger: 'blur' } | |||||
| ] | |||||
| }, | |||||
| loadingBtn: false | |||||
| } | |||||
| }, | |||||
| created() { | |||||
| if (this.id) { | |||||
| this.getData() | |||||
| } | |||||
| }, | |||||
| methods: { | |||||
| getData() { | |||||
| demoService.getData().then(res => { | |||||
| this.ruleForm = res | |||||
| }) | |||||
| }, | |||||
| submitForm(formName) { | |||||
| this.$refs[formName].validate((valid) => { | |||||
| if (valid) { | |||||
| this.loadingBtn = true | |||||
| demoService.save(this.ruleForm).then(res => { | |||||
| this.$message({ | |||||
| message: '操作成功!', | |||||
| type: 'success' | |||||
| }) | |||||
| this.loadingBtn = false | |||||
| this.$emit('close', 'save') | |||||
| }).catch(() => { | |||||
| this.loadingBtn = true | |||||
| }) | |||||
| } else { | |||||
| console.log('error submit!!') | |||||
| return false | |||||
| } | |||||
| }) | |||||
| }, | |||||
| resetForm(formName) { | |||||
| this.$refs[formName].resetFields() | |||||
| } | |||||
| } | |||||
| } | |||||
| </script> | |||||
| @@ -0,0 +1,30 @@ | |||||
| <template> | |||||
| <div class="home"> | |||||
| <img :src="imgUrl" alt=""> | |||||
| </div> | |||||
| </template> | |||||
| <script> | |||||
| import imgUrl from '@/assets/img/home.png' | |||||
| export default { | |||||
| name: 'Home', | |||||
| data() { | |||||
| return { | |||||
| imgUrl | |||||
| } | |||||
| } | |||||
| } | |||||
| </script> | |||||
| <style lang="scss" scoped> | |||||
| .home { | |||||
| height: calc(100vh - 50px); | |||||
| display: flex; | |||||
| align-items: center; | |||||
| justify-content: center; | |||||
| img { | |||||
| width: 50%; | |||||
| } | |||||
| } | |||||
| </style> | |||||
| @@ -0,0 +1,115 @@ | |||||
| <!-- | |||||
| * @Date: 2021-07-08 11:02:40 | |||||
| * @LastEditTime: 2022-07-27 10:48:43 | |||||
| * @Description: 广告位 | |||||
| --> | |||||
| <template> | |||||
| <div class="app-container"> | |||||
| <!--表格渲染--> | |||||
| <el-table :data="data" style="width: 100%;"> | |||||
| <el-table-column :show-overflow-tooltip="true" align="center" prop="link" label="链接" /> | |||||
| <el-table-column :show-overflow-tooltip="true" align="center" prop="image" label="图片"> | |||||
| <template slot-scope="scope"> | |||||
| <img :src="scope.row.image" width="50px" /> | |||||
| </template> | |||||
| </el-table-column> | |||||
| <el-table-column :show-overflow-tooltip="true" align="center" prop="newOpen" label="是否新开页面打开"> | |||||
| <template slot-scope="scope"> | |||||
| {{ scope.row.newOpen === 1 ? '是' : '否' }} | |||||
| </template> | |||||
| </el-table-column> | |||||
| <el-table-column label="操作" width="230" align="center" fixed="right"> | |||||
| <template slot-scope="scope" class="btn"> | |||||
| <el-button type="text" size="mini" @click="openSave(scope.row)">编辑</el-button> | |||||
| </template> | |||||
| </el-table-column> | |||||
| </el-table> | |||||
| <!-- 新增修改弹窗 --> | |||||
| <el-dialog v-if="dialogVisible" title="轮播" :visible.sync="dialogVisible" append-to-body width="600px"> | |||||
| <el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-width="130px" class="resetform"> | |||||
| <el-form-item label="图片" prop="image"> | |||||
| <uploadFile :url.sync="ruleForm.image" type="image" /> | |||||
| </el-form-item> | |||||
| <el-form-item label="链接" prop="link"> | |||||
| <el-input v-model="ruleForm.link"></el-input> | |||||
| </el-form-item> | |||||
| <el-form-item label="是否新开页面打开"> | |||||
| <el-radio-group v-model="ruleForm.newOpen"> | |||||
| <el-radio :label="1">是</el-radio> | |||||
| <el-radio :label="0">否</el-radio> | |||||
| </el-radio-group> | |||||
| </el-form-item> | |||||
| <el-form-item> | |||||
| <el-button type="primary" @click="submitForm('ruleForm')">立即创建</el-button> | |||||
| <el-button @click="dialogVisible = false">取消</el-button> | |||||
| </el-form-item> | |||||
| </el-form> | |||||
| </el-dialog> | |||||
| </div> | |||||
| </template> | |||||
| <script> | |||||
| import { guanggao1, saveGuangGao } from '@/api/homePage' | |||||
| import uploadFile from '@/components/uploadFile' | |||||
| export default { | |||||
| components: { | |||||
| uploadFile | |||||
| }, | |||||
| data() { | |||||
| return { | |||||
| dialogVisible: false, | |||||
| id: '', | |||||
| data: [], | |||||
| ruleForm: { | |||||
| id: '', | |||||
| image: '', | |||||
| link: '', | |||||
| newOpen: '1' | |||||
| }, | |||||
| rules: { | |||||
| image: [ | |||||
| { required: true, message: '请上传图片', trigger: 'blur' } | |||||
| ], | |||||
| link: [ | |||||
| { required: true, message: '请输入链接', trigger: 'blur' } | |||||
| ] | |||||
| } | |||||
| } | |||||
| }, | |||||
| created() { | |||||
| this.getData() | |||||
| }, | |||||
| methods: { | |||||
| getData() { | |||||
| guanggao1().then(res => { | |||||
| this.data = [res] | |||||
| }) | |||||
| }, | |||||
| openSave(item) { | |||||
| this.ruleForm.id = item.id | |||||
| this.ruleForm.image = item.image | |||||
| this.ruleForm.link = item.link | |||||
| this.ruleForm.newOpen = item.newOpen | |||||
| this.dialogVisible = true | |||||
| }, | |||||
| submitForm(formName) { | |||||
| this.$refs[formName].validate((valid) => { | |||||
| if (valid) { | |||||
| saveGuangGao(this.ruleForm).then(res => { | |||||
| this.$message({ | |||||
| message: '操作成功!', | |||||
| type: 'success' | |||||
| }) | |||||
| this.getData() | |||||
| this.dialogVisible = false | |||||
| }) | |||||
| } else { | |||||
| console.log('error submit!!') | |||||
| return false | |||||
| } | |||||
| }) | |||||
| } | |||||
| } | |||||
| } | |||||
| </script> | |||||
| @@ -0,0 +1,115 @@ | |||||
| <!-- | |||||
| * @Date: 2021-07-08 11:02:40 | |||||
| * @LastEditTime: 2022-07-27 10:48:43 | |||||
| * @Description: 广告位 | |||||
| --> | |||||
| <template> | |||||
| <div class="app-container"> | |||||
| <!--表格渲染--> | |||||
| <el-table :data="data" style="width: 100%;"> | |||||
| <el-table-column :show-overflow-tooltip="true" align="center" prop="link" label="链接" /> | |||||
| <el-table-column :show-overflow-tooltip="true" align="center" prop="image" label="图片"> | |||||
| <template slot-scope="scope"> | |||||
| <img :src="scope.row.image" width="50px" /> | |||||
| </template> | |||||
| </el-table-column> | |||||
| <el-table-column :show-overflow-tooltip="true" align="center" prop="newOpen" label="是否新开页面打开"> | |||||
| <template slot-scope="scope"> | |||||
| {{ scope.row.newOpen === 1 ? '是' : '否' }} | |||||
| </template> | |||||
| </el-table-column> | |||||
| <el-table-column label="操作" width="230" align="center" fixed="right"> | |||||
| <template slot-scope="scope" class="btn"> | |||||
| <el-button type="text" size="mini" @click="openSave(scope.row)">编辑</el-button> | |||||
| </template> | |||||
| </el-table-column> | |||||
| </el-table> | |||||
| <!-- 新增修改弹窗 --> | |||||
| <el-dialog v-if="dialogVisible" title="轮播" :visible.sync="dialogVisible" append-to-body width="600px"> | |||||
| <el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-width="130px" class="resetform"> | |||||
| <el-form-item label="图片" prop="image"> | |||||
| <uploadFile :url.sync="ruleForm.image" type="image" /> | |||||
| </el-form-item> | |||||
| <el-form-item label="链接" prop="link"> | |||||
| <el-input v-model="ruleForm.link"></el-input> | |||||
| </el-form-item> | |||||
| <el-form-item label="是否新开页面打开"> | |||||
| <el-radio-group v-model="ruleForm.newOpen"> | |||||
| <el-radio :label="1">是</el-radio> | |||||
| <el-radio :label="0">否</el-radio> | |||||
| </el-radio-group> | |||||
| </el-form-item> | |||||
| <el-form-item> | |||||
| <el-button type="primary" @click="submitForm('ruleForm')">立即创建</el-button> | |||||
| <el-button @click="dialogVisible = false">取消</el-button> | |||||
| </el-form-item> | |||||
| </el-form> | |||||
| </el-dialog> | |||||
| </div> | |||||
| </template> | |||||
| <script> | |||||
| import { guanggao2, saveGuangGao } from '@/api/homePage' | |||||
| import uploadFile from '@/components/uploadFile' | |||||
| export default { | |||||
| components: { | |||||
| uploadFile | |||||
| }, | |||||
| data() { | |||||
| return { | |||||
| dialogVisible: false, | |||||
| id: '', | |||||
| data: [], | |||||
| ruleForm: { | |||||
| id: '', | |||||
| image: '', | |||||
| link: '', | |||||
| newOpen: '1' | |||||
| }, | |||||
| rules: { | |||||
| image: [ | |||||
| { required: true, message: '请上传图片', trigger: 'blur' } | |||||
| ], | |||||
| link: [ | |||||
| { required: true, message: '请输入链接', trigger: 'blur' } | |||||
| ] | |||||
| } | |||||
| } | |||||
| }, | |||||
| created() { | |||||
| this.getData() | |||||
| }, | |||||
| methods: { | |||||
| getData() { | |||||
| guanggao2().then(res => { | |||||
| this.data = [res] | |||||
| }) | |||||
| }, | |||||
| openSave(item) { | |||||
| this.ruleForm.id = item.id | |||||
| this.ruleForm.image = item.image | |||||
| this.ruleForm.link = item.link | |||||
| this.ruleForm.newOpen = item.newOpen | |||||
| this.dialogVisible = true | |||||
| }, | |||||
| submitForm(formName) { | |||||
| this.$refs[formName].validate((valid) => { | |||||
| if (valid) { | |||||
| saveGuangGao(this.ruleForm).then(res => { | |||||
| this.$message({ | |||||
| message: '操作成功!', | |||||
| type: 'success' | |||||
| }) | |||||
| this.getData() | |||||
| this.dialogVisible = false | |||||
| }) | |||||
| } else { | |||||
| console.log('error submit!!') | |||||
| return false | |||||
| } | |||||
| }) | |||||
| } | |||||
| } | |||||
| } | |||||
| </script> | |||||
| @@ -0,0 +1,54 @@ | |||||
| <!-- | |||||
| * @Date: 2021-07-08 11:02:40 | |||||
| * @LastEditTime: 2022-07-27 10:48:43 | |||||
| * @Description: 列表查询demo | |||||
| --> | |||||
| <template> | |||||
| <div class="app-container"> | |||||
| <!--工具栏--> | |||||
| <div class="head-container"> | |||||
| <div> | |||||
| <el-button class="filter-item" size="mini" type="primary" icon="el-icon-plus" | |||||
| @click="openSave()">新增</el-button> | |||||
| </div> | |||||
| </div> | |||||
| <!--表格渲染--> | |||||
| <el-table ref="table" v-loading="crud.loading" :data="crud.data" style="width: 100%;" @selection-change="handleSelectionChange" | |||||
| > | |||||
| <el-table-column type="selection" width="55" /> | |||||
| <el-table-column :show-overflow-tooltip="true" prop="newsId" label="序号" /> | |||||
| <el-table-column :show-overflow-tooltip="true" prop="标题" label="newsTitle" /> | |||||
| <el-table-column :show-overflow-tooltip="true" prop="createDate" label="创建时间" /> | |||||
| </el-table> | |||||
| <!--分页组件--> | |||||
| <pagination /> | |||||
| </div> | |||||
| </template> | |||||
| <script> | |||||
| import carHotService from '@/api/index/carHot' | |||||
| import CRUD, { presenter, header, crud } from '@crud/crud' | |||||
| import pagination from '@crud/Pagination' | |||||
| export default { | |||||
| name: 'Demo', | |||||
| components: { pagination }, | |||||
| mixins: [presenter(), header(), crud()], | |||||
| data() { | |||||
| return { | |||||
| releaseSourceList: [], | |||||
| dialogVisible: false, | |||||
| id: '', | |||||
| multipleSelection: [] | |||||
| } | |||||
| }, | |||||
| cruds() { | |||||
| return CRUD({ title: '列表', url: '/index/carNewsHotList', query: {}}) | |||||
| }, | |||||
| methods: { | |||||
| handleSelectionChange(val) { | |||||
| this.multipleSelection = val | |||||
| } | |||||
| } | |||||
| } | |||||
| </script> | |||||
| @@ -0,0 +1,171 @@ | |||||
| <!-- | |||||
| * @Date: 2021-07-08 11:02:40 | |||||
| * @LastEditTime: 2022-07-27 10:48:43 | |||||
| * @Description: 列表查询demo | |||||
| --> | |||||
| <template> | |||||
| <div class="app-container"> | |||||
| <!--工具栏--> | |||||
| <div class="head-container"> | |||||
| <!-- <div> | |||||
| 搜索 | |||||
| <el-input v-model="query.name" prefix-icon="el-icon-search" clearable placeholder="名称" class="filter-item" | |||||
| @keyup.enter.native="crud.toQuery" /> | |||||
| <rrOperation /> | |||||
| </div> --> | |||||
| <div> | |||||
| <el-button class="filter-item" size="mini" type="primary" icon="el-icon-plus" | |||||
| @click="openSave()">新增</el-button> | |||||
| </div> | |||||
| </div> | |||||
| <!--表格渲染--> | |||||
| <el-table ref="table" v-loading="crud.loading" :data="crud.data" style="width: 100%;"> | |||||
| <el-table-column :show-overflow-tooltip="true" prop="serialNumber" label="序号" /> | |||||
| <el-table-column :show-overflow-tooltip="true" prop="image" label="图片"> | |||||
| <template slot-scope="scope"> | |||||
| <img :src="scope.row.image" width="50px" /> | |||||
| </template> | |||||
| </el-table-column> | |||||
| <el-table-column :show-overflow-tooltip="true" prop="link" label="链接" /> | |||||
| <el-table-column :show-overflow-tooltip="true" prop="createDate" label="创建时间" /> | |||||
| <el-table-column label="操作" width="230" align="center" fixed="right"> | |||||
| <template slot-scope="scope" class="btn"> | |||||
| <el-button type="text" size="mini" @click="openSave(scope.row)">编辑</el-button> | |||||
| <el-popover placement="top" width="200" :ref="`del-${scope.$index}`"> | |||||
| <p>是否确认删除该数据?</p> | |||||
| <div style="text-align: right; margin: 0"> | |||||
| <el-button size="mini" | |||||
| @click="scope._self.$refs[`del-${scope.$index}`].doClose()">取消</el-button> | |||||
| <el-button type="primary" size="mini" @click="del(scope)">确定</el-button> | |||||
| </div> | |||||
| <el-button slot="reference" type="text" size="mini" style="margin-left:10px">删除</el-button> | |||||
| </el-popover> | |||||
| </template> | |||||
| </el-table-column> | |||||
| </el-table> | |||||
| <!--分页组件--> | |||||
| <pagination /> | |||||
| <!-- 新增修改弹窗 --> | |||||
| <el-dialog v-if="dialogVisible" title="轮播" :visible.sync="dialogVisible" append-to-body width="600px"> | |||||
| <el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-width="130px" class="resetform"> | |||||
| <el-form-item label="活动名称" prop="image"> | |||||
| <uploadFile :url.sync="ruleForm.image" type="image" /> | |||||
| </el-form-item> | |||||
| <el-form-item label="序号" prop="serialNumber"> | |||||
| <el-input v-model="ruleForm.serialNumber"></el-input> | |||||
| </el-form-item> | |||||
| <el-form-item label="链接" prop="link"> | |||||
| <el-input v-model="ruleForm.link"></el-input> | |||||
| </el-form-item> | |||||
| <el-form-item label="是否新开页面打开"> | |||||
| <el-radio-group v-model="ruleForm.newOpen"> | |||||
| <el-radio label="1">是</el-radio> | |||||
| <el-radio label="0">否</el-radio> | |||||
| </el-radio-group> | |||||
| </el-form-item> | |||||
| <el-form-item> | |||||
| <el-button type="primary" :loading="loadingBtn" @click="submitForm('ruleForm')">立即创建</el-button> | |||||
| <el-button @click="resetForm('ruleForm')">重置</el-button> | |||||
| </el-form-item> | |||||
| </el-form> | |||||
| </el-dialog> | |||||
| </div> | |||||
| </template> | |||||
| <script> | |||||
| import { delLunbo, saveLunbo } from '@/api/homePage' | |||||
| import CRUD, { presenter, header, crud } from '@crud/crud' | |||||
| import pagination from '@crud/Pagination' | |||||
| import uploadFile from '@/components/uploadFile' | |||||
| export default { | |||||
| components: { pagination, uploadFile }, | |||||
| mixins: [presenter(), header(), crud()], | |||||
| data() { | |||||
| return { | |||||
| releaseSourceList: [], | |||||
| dialogVisible: false, | |||||
| id: '', | |||||
| ruleForm: { | |||||
| image: '', | |||||
| serialNumber: '', | |||||
| link: [], | |||||
| newOpen: '1' | |||||
| }, | |||||
| rules: { | |||||
| name: [ | |||||
| { required: true, message: '请上传图片', trigger: 'blur' } | |||||
| ], | |||||
| serialNumber: [ | |||||
| { required: true, message: '请输入序号', trigger: 'blur' } | |||||
| ], | |||||
| link: [ | |||||
| { required: true, message: '请输入链接', trigger: 'blur' } | |||||
| ], | |||||
| desc: [ | |||||
| { required: true, message: '请填写活动形式', trigger: 'blur' } | |||||
| ] | |||||
| }, | |||||
| loadingBtn: false | |||||
| } | |||||
| }, | |||||
| cruds() { | |||||
| return CRUD({ title: '列表', url: '/index/lunboList', query: {}}) | |||||
| }, | |||||
| methods: { | |||||
| openSave(item) { | |||||
| if (item) { | |||||
| this.ruleForm.id = item.id | |||||
| this.ruleForm.image = item.image | |||||
| this.ruleForm.serialNumber = item.serialNumber | |||||
| this.ruleForm.link = item.link | |||||
| this.ruleForm.newOpen = item.newOpen || '1' | |||||
| } | |||||
| this.dialogVisible = true | |||||
| }, | |||||
| close(tag) { | |||||
| if (tag) this.crud.toQuery() | |||||
| this.dialogVisible = false | |||||
| }, | |||||
| /* 删除 */ | |||||
| del(scope) { | |||||
| delLunbo(scope.row.id).then(res => { | |||||
| this.$message({ | |||||
| message: '删除成功!', | |||||
| type: 'success' | |||||
| }) | |||||
| this.crud.toQuery() | |||||
| scope._self.$refs[`del-${scope.$index}`].doClose() | |||||
| }) | |||||
| }, | |||||
| submitForm(formName) { | |||||
| this.$refs[formName].validate((valid) => { | |||||
| if (valid) { | |||||
| this.loadingBtn = true | |||||
| saveLunbo(this.ruleForm).then(res => { | |||||
| this.$message({ | |||||
| message: '操作成功!', | |||||
| type: 'success' | |||||
| }) | |||||
| this.loadingBtn = false | |||||
| this.dialogVisible = false | |||||
| this.$refs[formName].resetFields() | |||||
| this.crud.toQuery() | |||||
| }).catch(() => { | |||||
| this.loadingBtn = false | |||||
| }) | |||||
| } else { | |||||
| console.log('error submit!!') | |||||
| return false | |||||
| } | |||||
| }) | |||||
| }, | |||||
| resetForm(formName) { | |||||
| this.$refs[formName].resetFields() | |||||
| } | |||||
| } | |||||
| } | |||||
| </script> | |||||
| @@ -0,0 +1,118 @@ | |||||
| <!-- | |||||
| * @Date: 2021-10-11 14:12:26 | |||||
| * @LastEditTime: 2022-07-27 10:48:47 | |||||
| * @Description: 新增/编辑页面 | |||||
| --> | |||||
| <template> | |||||
| <div class="app-container"> | |||||
| <el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-width="100px" class="resetform"> | |||||
| <el-form-item label="是否显示"> | |||||
| <el-radio-group v-model="ruleForm.isShow"> | |||||
| <el-radio label="1">是</el-radio> | |||||
| <el-radio label="0">否</el-radio> | |||||
| </el-radio-group> | |||||
| </el-form-item> | |||||
| <el-card style="margin-top:20px" class="suIbtemCard" v-for="(item, index) in imgList" :key="index"> | |||||
| <el-form-item label="上传视频"> | |||||
| <uploadFile :url.sync="item.imgSrc" type="image"/> | |||||
| <el-input v-model="item.imgSrc"></el-input> | |||||
| </el-form-item> | |||||
| <el-form-item label="标题"> | |||||
| <el-input v-model="item.title"></el-input> | |||||
| </el-form-item> | |||||
| <el-form-item label="描述"> | |||||
| <el-input v-model="item.desc"></el-input> | |||||
| </el-form-item> | |||||
| <el-form-item label="跳转链接"> | |||||
| <el-input v-model="item.link"></el-input> | |||||
| </el-form-item> | |||||
| <el-form-item> | |||||
| <el-button type="success" icon="el-icon-plus" circle v-if="imgList.length - 1 === index" @click="addItem"></el-button> | |||||
| <el-button type="danger" icon="el-icon-delete" circle @click="imgList.length > 1 && delItem(item, index)"></el-button> | |||||
| </el-form-item> | |||||
| </el-card> | |||||
| <el-form-item> | |||||
| <el-button type="primary" :loading="loadingBtn" @click="submitForm('ruleForm')">保存</el-button> | |||||
| </el-form-item> | |||||
| </el-form> | |||||
| </div> | |||||
| </template> | |||||
| <script> | |||||
| import demoService from '@/api/nested/demo' | |||||
| import uploadFile from '@/components/uploadFile' | |||||
| export default { | |||||
| components: { | |||||
| uploadFile | |||||
| }, | |||||
| data() { | |||||
| return { | |||||
| imgList: [ | |||||
| { imgSrc: '', link: '', title: '', desc: '' } | |||||
| ], | |||||
| ruleForm: { | |||||
| name: '', | |||||
| region: '', | |||||
| type: [], | |||||
| resource: '', | |||||
| desc: '' | |||||
| }, | |||||
| rules: { | |||||
| name: [ | |||||
| { required: true, message: '请输入活动名称', trigger: 'blur' }, | |||||
| { min: 3, max: 5, message: '长度在 3 到 5 个字符', trigger: 'blur' } | |||||
| ], | |||||
| region: [ | |||||
| { required: true, message: '请选择活动区域', trigger: 'change' } | |||||
| ], | |||||
| type: [ | |||||
| { type: 'array', required: true, message: '请至少选择一个活动性质', trigger: 'change' } | |||||
| ], | |||||
| resource: [ | |||||
| { required: true, message: '请选择活动资源', trigger: 'change' } | |||||
| ], | |||||
| desc: [ | |||||
| { required: true, message: '请填写活动形式', trigger: 'blur' } | |||||
| ] | |||||
| }, | |||||
| loadingBtn: false | |||||
| } | |||||
| }, | |||||
| created() { | |||||
| if (this.id) { | |||||
| this.getData() | |||||
| } | |||||
| }, | |||||
| methods: { | |||||
| addItem() { | |||||
| this.imgList.push({ imgSrc: '', link: '', title: '', desc: '' }) | |||||
| }, | |||||
| delItem(item, index) { | |||||
| this.imgList.splice(index, 1) | |||||
| }, | |||||
| submitForm(formName) { | |||||
| this.$refs[formName].validate((valid) => { | |||||
| if (valid) { | |||||
| this.loadingBtn = true | |||||
| demoService.save(this.ruleForm).then(res => { | |||||
| this.$message({ | |||||
| message: '操作成功!', | |||||
| type: 'success' | |||||
| }) | |||||
| this.loadingBtn = false | |||||
| this.$emit('close', 'save') | |||||
| }).catch(() => { | |||||
| this.loadingBtn = true | |||||
| }) | |||||
| } else { | |||||
| console.log('error submit!!') | |||||
| return false | |||||
| } | |||||
| }) | |||||
| }, | |||||
| resetForm(formName) { | |||||
| this.$refs[formName].resetFields() | |||||
| } | |||||
| } | |||||
| } | |||||
| </script> | |||||
| @@ -0,0 +1,289 @@ | |||||
| <template> | |||||
| <div class='login-container'> | |||||
| <div class='Version'>Version 1.0.8 test</div> | |||||
| <div class='videoBox'> | |||||
| <video src='https://video.metavatar.cc/sv/4aaaaa5e-18832dbb5e6/4aaaaa5e-18832dbb5e6.mp4' muted autoplay | |||||
| loop></video> | |||||
| </div> | |||||
| <el-form ref='loginFormRef' :model='loginData' class='login-form'> | |||||
| <el-form-item prop='username'> | |||||
| <div class='text-white'> | |||||
| <i class="el-icon-user"></i> | |||||
| </div> | |||||
| <el-input class='flex-1' ref='email' size='large' v-model='loginData.username' placeholder='用户名' | |||||
| name='email' /> | |||||
| </el-form-item> | |||||
| <el-form-item prop='password'> | |||||
| <span class='text-white'> | |||||
| <i class="el-icon-lock"></i> | |||||
| </span> | |||||
| <el-input class='flex-1' v-model='loginData.password' placeholder='密码' | |||||
| :type="passwordVisible === false ? 'password' : 'input'" size='large' name='password' | |||||
| @keyup.enter='handleLogin' /> | |||||
| <span class='mr-2' @click='passwordVisible = !passwordVisible'> | |||||
| </span> | |||||
| </el-form-item> | |||||
| <!-- 验证码 --> | |||||
| <el-form-item prop='captcha'> | |||||
| <span class='text-white'> | |||||
| <i class="el-icon-key"></i> | |||||
| </span> | |||||
| <el-input v-model='loginData.captcha' auto-complete='off' placeholder='验证码' class='flex-1' | |||||
| @keyup.enter='handleLogin' /> | |||||
| <span class='captcha'> | |||||
| <img v-if='showCodeImg' :src='verifyCodeUrl' @click='getCode' /> | |||||
| </span> | |||||
| </el-form-item> | |||||
| <el-button size='default' :loading='loading' type='primary' class='loginBtn' @click.prevent='handleLogin'> | |||||
| 登录 | |||||
| </el-button> | |||||
| </el-form> | |||||
| </div> | |||||
| </template> | |||||
| <script> | |||||
| import { | |||||
| loginApi | |||||
| } from '@/api/login' | |||||
| export default { | |||||
| data() { | |||||
| return { | |||||
| isLogin: 1, | |||||
| passwordVisible: false, | |||||
| loading: false, | |||||
| loginData: { | |||||
| username: '', | |||||
| password: '', | |||||
| captcha: '' | |||||
| }, | |||||
| showCodeImg: true, | |||||
| isLogining: true, | |||||
| verifyCodeUrl: process.env.VUE_APP_BASE_API + '/captcha.jpg' | |||||
| } | |||||
| }, | |||||
| methods: { | |||||
| handleLogin() { | |||||
| loginApi(this.loginData).then(res => { | |||||
| this.$router.push({ | |||||
| path: '/homePage/carousel' | |||||
| }) | |||||
| }) | |||||
| }, | |||||
| getCode() { | |||||
| this.verifyCodeUrl = process.env.VUE_APP_BASE_API + '/captcha.jpg?time=' + Math.random() | |||||
| } | |||||
| } | |||||
| } | |||||
| </script> | |||||
| <style lang='scss' scoped> | |||||
| .login-container { | |||||
| position: relative; | |||||
| width: 100%; | |||||
| // width: 1920px; | |||||
| // height: 1080px; | |||||
| min-height: 100%; | |||||
| overflow: hidden; | |||||
| // background-color: #2d3a4b; | |||||
| .Version { | |||||
| position: absolute; | |||||
| bottom: 20px; | |||||
| right: 20px; | |||||
| color: #ffffff; | |||||
| } | |||||
| .login-form { | |||||
| position: absolute; | |||||
| top: 46%; | |||||
| left: 10%; | |||||
| width: 520px; | |||||
| max-width: 100%; | |||||
| padding: 20px; | |||||
| overflow: hidden; | |||||
| backdrop-filter: blur(3px); | |||||
| background: linear-gradient(123.64deg, | |||||
| rgba(188, 193, 196, 0.1) -22.71%, | |||||
| rgba(255, 255, 255, 0.039) 70.04%); | |||||
| border-radius: 20px; | |||||
| transform: translateY(-50%); | |||||
| border: 1px solid #cacaca; | |||||
| transition: all 0.15s; | |||||
| .captcha { | |||||
| position: absolute; | |||||
| top: 0; | |||||
| right: 0; | |||||
| img { | |||||
| width: 120px; | |||||
| height: 48px; | |||||
| cursor: pointer; | |||||
| } | |||||
| .sendSMSBtn { | |||||
| width: 120px; | |||||
| height: 48px; | |||||
| line-height: 48px; | |||||
| color: #fff; | |||||
| text-align: center; | |||||
| background: linear-gradient(270deg, #4b61dc, #15d1b8); | |||||
| } | |||||
| } | |||||
| &:hover { | |||||
| backdrop-filter: blur(7px); | |||||
| } | |||||
| .items-center { | |||||
| padding: 20px; | |||||
| font-size: 30px; | |||||
| } | |||||
| .el-form-item { | |||||
| .text-white { | |||||
| padding: 8px; | |||||
| i{ | |||||
| color:#fff; | |||||
| font-size: 20px; | |||||
| } | |||||
| } | |||||
| ::v-deep .el-form-item__content{ | |||||
| display: flex; | |||||
| } | |||||
| ::v-deep .el-input .el-input__inner { | |||||
| color: #fff; | |||||
| background: transparent; | |||||
| border: 0; | |||||
| border-radius: 0; | |||||
| caret-color: #fff; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| .loginBtn { | |||||
| width: 100%; | |||||
| height: 40px; | |||||
| background: linear-gradient(270deg, #4b61dc, #15d1b8); | |||||
| transition: all 0.3s; | |||||
| &:hover { | |||||
| transform: scale(1.02); | |||||
| } | |||||
| cursor: pointer; | |||||
| } | |||||
| .bottomBtnCN { | |||||
| display: flex; | |||||
| justify-content: space-between; | |||||
| align-items: center; | |||||
| padding: 0 10px; | |||||
| color: #fff; | |||||
| text-align: center; | |||||
| margin-top: 20px; | |||||
| .join { | |||||
| cursor: pointer; | |||||
| border-bottom: 2px solid #ffffff00; | |||||
| padding: 0 3px; | |||||
| padding-bottom: 2px; | |||||
| transition: all 0.3s; | |||||
| &:hover { | |||||
| border-bottom: 2px solid #ffffff; | |||||
| } | |||||
| } | |||||
| } | |||||
| .bottomBtnEN { | |||||
| padding: 0 10px; | |||||
| color: #fff; | |||||
| text-align: center; | |||||
| margin-top: 20px; | |||||
| .join { | |||||
| cursor: pointer; | |||||
| border-bottom: 2px solid #ffffff00; | |||||
| padding: 0 3px; | |||||
| padding-bottom: 2px; | |||||
| transition: all 0.3s; | |||||
| &:hover { | |||||
| border-bottom: 2px solid #ffffff; | |||||
| } | |||||
| } | |||||
| } | |||||
| .videoBox { | |||||
| position: absolute; | |||||
| top: 0; | |||||
| left: 0; | |||||
| width: 100%; | |||||
| height: 100%; | |||||
| z-index: -1; | |||||
| video { | |||||
| width: 100%; | |||||
| height: 100%; | |||||
| object-fit: fill; | |||||
| } | |||||
| } | |||||
| .el-form-item { | |||||
| background: rgb(0 0 0 / 10%); | |||||
| border: 1px solid #ffffff2d; | |||||
| border-radius: 5px; | |||||
| } | |||||
| .el-input { | |||||
| background: transparent; | |||||
| // 子组件 scoped 无效,使用 :deep | |||||
| :deep(.el-input__wrapper) { | |||||
| padding: 0; | |||||
| background: transparent; | |||||
| box-shadow: none; | |||||
| .el-input__inner { | |||||
| color: #fff; | |||||
| background: transparent; | |||||
| border: 0; | |||||
| border-radius: 0; | |||||
| caret-color: #fff; | |||||
| &:-webkit-autofill { | |||||
| box-shadow: 0 0 0 1000px transparent inset !important; | |||||
| -webkit-text-fill-color: #fff !important; | |||||
| } | |||||
| // 设置输入框自动填充的延迟属性 | |||||
| &:-webkit-autofill, | |||||
| &:-webkit-autofill:hover, | |||||
| &:-webkit-autofill:focus, | |||||
| &:-webkit-autofill:active { | |||||
| transition: color 99999s ease-out, background-color 99999s ease-out; | |||||
| transition-delay: 99999s; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| @keyframes rotate { | |||||
| from { | |||||
| transform: rotate(0deg); | |||||
| } | |||||
| to { | |||||
| transform: rotate(360deg); | |||||
| } | |||||
| } | |||||
| </style> | |||||
| @@ -0,0 +1,115 @@ | |||||
| <!-- | |||||
| * @Date: 2021-07-08 11:02:40 | |||||
| * @LastEditTime: 2022-07-27 10:48:43 | |||||
| * @Description: 列表查询demo | |||||
| --> | |||||
| <template> | |||||
| <div class="app-container"> | |||||
| <!--工具栏--> | |||||
| <div class="head-container"> | |||||
| <div> | |||||
| <!-- 搜索 --> | |||||
| <el-input v-model="query.name" prefix-icon="el-icon-search" clearable placeholder="名称" class="filter-item" @keyup.enter.native="crud.toQuery" /> | |||||
| <el-input v-model="query.releaseName" clearable placeholder="活动名称" class="filter-item"/> | |||||
| <el-select v-model="query.list" clearable placeholder="列表" class="filter-item"> | |||||
| <el-option v-for="item in releaseSourceList" :key="item.id" :label="item.name" :value="item.id"></el-option> | |||||
| </el-select> | |||||
| <el-date-picker v-model="query.times" value-format="yyyy-MM-dd HH:mm:ss" type="datetimerange" range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" class="date-item" style="width:300px !important; margin: 0 3px 10px 0;"></el-date-picker> | |||||
| <rrOperation /> | |||||
| </div> | |||||
| <div> | |||||
| <el-button class="filter-item" size="mini" type="primary" icon="el-icon-plus" @click="openSave()">新增</el-button> | |||||
| <el-button class="filter-item" size="mini" type="warning" icon="el-icon-download" @click="exportFile">导出</el-button> | |||||
| </div> | |||||
| </div> | |||||
| <!--表格渲染--> | |||||
| <el-table ref="table" v-loading="crud.loading" :data="crud.data" style="width: 100%;"> | |||||
| <el-table-column :show-overflow-tooltip="true" prop="id" label="活动编号" /> | |||||
| <el-table-column :show-overflow-tooltip="true" prop="releaseName" label="活动名称" /> | |||||
| <el-table-column :show-overflow-tooltip="true" prop="contentTitle" label="文章名称" /> | |||||
| <el-table-column :show-overflow-tooltip="true" prop="releaseTime" label="发布时间" /> | |||||
| <el-table-column :show-overflow-tooltip="true" prop="publisher" label="发布人" /> | |||||
| <el-table-column label="操作" width="230" align="center" fixed="right"> | |||||
| <template slot-scope="scope" class="btn"> | |||||
| <el-button type="text" size="mini" @click="openSave(scope.row.id)">编辑</el-button> | |||||
| <el-popover placement="top" width="200" :ref="`del-${scope.$index}`"> | |||||
| <p>是否确认删除该数据?</p> | |||||
| <div style="text-align: right; margin: 0"> | |||||
| <el-button size="mini" @click="scope._self.$refs[`del-${scope.$index}`].doClose()">取消</el-button> | |||||
| <el-button type="primary" size="mini" @click="del(scope)">确定</el-button> | |||||
| </div> | |||||
| <el-button slot="reference" type="text" size="mini" style="margin-left:10px">删除</el-button> | |||||
| </el-popover> | |||||
| </template> | |||||
| </el-table-column> | |||||
| </el-table> | |||||
| <!--分页组件--> | |||||
| <pagination /> | |||||
| <!-- 新增修改弹窗 --> | |||||
| <el-dialog v-if="dialogVisible" title="标题" :visible.sync="dialogVisible" append-to-body width="500px"> | |||||
| <save-page :id='id' @close="close"></save-page> | |||||
| </el-dialog> | |||||
| </div> | |||||
| </template> | |||||
| <script> | |||||
| import demoService from '@/api/nested/demo' | |||||
| import CRUD, { presenter, header, crud } from '@crud/crud' | |||||
| import rrOperation from '@crud/RR.operation' | |||||
| import pagination from '@crud/Pagination' | |||||
| import { downloadFile } from '@/utils/index' | |||||
| import savePage from './save' | |||||
| export default { | |||||
| name: 'Demo', | |||||
| components: { rrOperation, pagination, savePage }, | |||||
| mixins: [presenter(), header(), crud()], | |||||
| data() { | |||||
| return { | |||||
| releaseSourceList: [], | |||||
| dialogVisible: false, | |||||
| id: '' | |||||
| } | |||||
| }, | |||||
| cruds() { | |||||
| return CRUD({ title: '列表', url: 'xxx', query: {}}) | |||||
| }, | |||||
| methods: { | |||||
| /* 刷新前操作 */ | |||||
| [CRUD.HOOK.beforeRefresh]() { | |||||
| this.query.id = 'xxx' | |||||
| }, | |||||
| openSave(id) { | |||||
| this.id = id || '' | |||||
| this.dialogVisible = true | |||||
| }, | |||||
| close(tag) { | |||||
| if (tag) this.crud.toQuery() | |||||
| this.dialogVisible = false | |||||
| }, | |||||
| /* 删除 */ | |||||
| del(scope) { | |||||
| demoService.del(scope.row.id).then(res => { | |||||
| this.$message({ | |||||
| message: '删除成功!', | |||||
| type: 'success' | |||||
| }) | |||||
| this.crud.toQuery() | |||||
| scope._self.$refs[`del-${scope.$index}`].doClose() | |||||
| }) | |||||
| }, | |||||
| /* 导出 */ | |||||
| exportFile() { | |||||
| demoService.exportFile(this.crud.query).then(res => { | |||||
| downloadFile(res, '文章列表', 'xlsx') | |||||
| }) | |||||
| } | |||||
| } | |||||
| } | |||||
| </script> | |||||
| @@ -0,0 +1,112 @@ | |||||
| <!-- | |||||
| * @Date: 2021-10-11 14:12:26 | |||||
| * @LastEditTime: 2022-07-27 10:48:47 | |||||
| * @Description: 新增/编辑页面 | |||||
| --> | |||||
| <template> | |||||
| <el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-width="100px" class="resetform"> | |||||
| <el-form-item label="活动名称" prop="name"> | |||||
| <el-input v-model="ruleForm.name"></el-input> | |||||
| </el-form-item> | |||||
| <el-form-item label="活动区域" prop="region"> | |||||
| <el-select v-model="ruleForm.region" placeholder="请选择活动区域"> | |||||
| <el-option label="区域一" value="shanghai"></el-option> | |||||
| <el-option label="区域二" value="beijing"></el-option> | |||||
| </el-select> | |||||
| </el-form-item> | |||||
| <el-form-item label="活动性质" prop="type"> | |||||
| <el-checkbox-group v-model="ruleForm.type"> | |||||
| <el-checkbox label="美食/餐厅线上活动" name="type"></el-checkbox> | |||||
| <el-checkbox label="地推活动" name="type"></el-checkbox> | |||||
| <el-checkbox label="线下主题活动" name="type"></el-checkbox> | |||||
| <el-checkbox label="单纯品牌曝光" name="type"></el-checkbox> | |||||
| </el-checkbox-group> | |||||
| </el-form-item> | |||||
| <el-form-item label="特殊资源" prop="resource"> | |||||
| <el-radio-group v-model="ruleForm.resource"> | |||||
| <el-radio label="线上品牌商赞助"></el-radio> | |||||
| <el-radio label="线下场地免费"></el-radio> | |||||
| </el-radio-group> | |||||
| </el-form-item> | |||||
| <el-form-item label="活动形式" prop="desc"> | |||||
| <el-input type="textarea" v-model="ruleForm.desc"></el-input> | |||||
| </el-form-item> | |||||
| <el-form-item> | |||||
| <el-button type="primary" :loading="loadingBtn" @click="submitForm('ruleForm')">立即创建</el-button> | |||||
| <el-button @click="resetForm('ruleForm')">重置</el-button> | |||||
| </el-form-item> | |||||
| </el-form> | |||||
| </template> | |||||
| <script> | |||||
| import demoService from '@/api/nested/demo' | |||||
| export default { | |||||
| props: ['id'], | |||||
| data() { | |||||
| return { | |||||
| ruleForm: { | |||||
| name: '', | |||||
| region: '', | |||||
| type: [], | |||||
| resource: '', | |||||
| desc: '' | |||||
| }, | |||||
| rules: { | |||||
| name: [ | |||||
| { required: true, message: '请输入活动名称', trigger: 'blur' }, | |||||
| { min: 3, max: 5, message: '长度在 3 到 5 个字符', trigger: 'blur' } | |||||
| ], | |||||
| region: [ | |||||
| { required: true, message: '请选择活动区域', trigger: 'change' } | |||||
| ], | |||||
| type: [ | |||||
| { type: 'array', required: true, message: '请至少选择一个活动性质', trigger: 'change' } | |||||
| ], | |||||
| resource: [ | |||||
| { required: true, message: '请选择活动资源', trigger: 'change' } | |||||
| ], | |||||
| desc: [ | |||||
| { required: true, message: '请填写活动形式', trigger: 'blur' } | |||||
| ] | |||||
| }, | |||||
| loadingBtn: false | |||||
| } | |||||
| }, | |||||
| created() { | |||||
| if (this.id) { | |||||
| this.getData() | |||||
| } | |||||
| }, | |||||
| methods: { | |||||
| getData() { | |||||
| demoService.getData().then(res => { | |||||
| this.ruleForm = res | |||||
| }) | |||||
| }, | |||||
| submitForm(formName) { | |||||
| this.$refs[formName].validate((valid) => { | |||||
| if (valid) { | |||||
| this.loadingBtn = true | |||||
| demoService.save(this.ruleForm).then(res => { | |||||
| this.$message({ | |||||
| message: '操作成功!', | |||||
| type: 'success' | |||||
| }) | |||||
| this.loadingBtn = false | |||||
| this.$emit('close', 'save') | |||||
| }).catch(() => { | |||||
| this.loadingBtn = true | |||||
| }) | |||||
| } else { | |||||
| console.log('error submit!!') | |||||
| return false | |||||
| } | |||||
| }) | |||||
| }, | |||||
| resetForm(formName) { | |||||
| this.$refs[formName].resetFields() | |||||
| } | |||||
| } | |||||
| } | |||||
| </script> | |||||
| @@ -0,0 +1,7 @@ | |||||
| <template> | |||||
| <div style="padding:30px;"> | |||||
| <el-alert :closable="false" title="menu 1"> | |||||
| <router-view /> | |||||
| </el-alert> | |||||
| </div> | |||||
| </template> | |||||
| @@ -0,0 +1,7 @@ | |||||
| <template> | |||||
| <div style="padding:30px;"> | |||||
| <el-alert :closable="false" title="menu 1-1" type="success"> | |||||
| <router-view /> | |||||
| </el-alert> | |||||
| </div> | |||||
| </template> | |||||
| @@ -0,0 +1,7 @@ | |||||
| <template> | |||||
| <div style="padding:30px;"> | |||||
| <el-alert :closable="false" title="menu 1-2" type="success"> | |||||
| <router-view /> | |||||
| </el-alert> | |||||
| </div> | |||||
| </template> | |||||
| @@ -0,0 +1,5 @@ | |||||
| <template functional> | |||||
| <div style="padding:30px;"> | |||||
| <el-alert :closable="false" title="menu 1-2-1" type="warning" /> | |||||
| </div> | |||||
| </template> | |||||
| @@ -0,0 +1,5 @@ | |||||
| <template functional> | |||||
| <div style="padding:30px;"> | |||||
| <el-alert :closable="false" title="menu 1-2-2" type="warning" /> | |||||
| </div> | |||||
| </template> | |||||
| @@ -0,0 +1,5 @@ | |||||
| <template functional> | |||||
| <div style="padding:30px;"> | |||||
| <el-alert :closable="false" title="menu 1-3" type="success" /> | |||||
| </div> | |||||
| </template> | |||||
| @@ -0,0 +1,126 @@ | |||||
| 'use strict' | |||||
| const path = require('path') | |||||
| const defaultSettings = require('./src/settings.js') | |||||
| function resolve(dir) { | |||||
| return path.join(__dirname, dir) | |||||
| } | |||||
| const title = defaultSettings.title || '后台管理系统' // page title | |||||
| // If your port is set to 80, | |||||
| // use administrator privileges to execute the command line. | |||||
| // For example, Mac: sudo npm run | |||||
| // You can change the port by the following methods: | |||||
| // port = 9528 npm run dev OR npm run dev --port = 9528 | |||||
| const port = 8011 // dev port | |||||
| const { name } = require('./package') | |||||
| // All configuration item explanations can be find in https://cli.vuejs.org/config/ | |||||
| module.exports = { | |||||
| /** | |||||
| * You will need to set publicPath if you plan to deploy your site under a sub path, | |||||
| * for example GitHub Pages. If you plan to deploy your site to https://foo.github.io/bar/, | |||||
| * then publicPath should be set to "/bar/". | |||||
| * In most cases please use '/' !!! | |||||
| * Detail: https://cli.vuejs.org/config/#publicpath | |||||
| */ | |||||
| publicPath: process.env.NODE_ENV === 'development' ? '/' : '/admin', | |||||
| outputDir: process.env.OUTPUT_DIR, | |||||
| assetsDir: 'static', | |||||
| lintOnSave: process.env.NODE_ENV === 'development', | |||||
| productionSourceMap: false, | |||||
| devServer: { | |||||
| port: port, | |||||
| headers: { | |||||
| 'Access-Control-Allow-Origin': '*' | |||||
| }, | |||||
| open: true, | |||||
| overlay: { | |||||
| warnings: false, | |||||
| errors: true | |||||
| }, | |||||
| proxy: { | |||||
| '/api': { | |||||
| target: process.env.VUE_APP_BASE_API, | |||||
| changeOrigin: true, | |||||
| pathRewrite: { | |||||
| '^/api': 'api' | |||||
| } | |||||
| } | |||||
| } | |||||
| }, | |||||
| configureWebpack: { | |||||
| devtool: 'source map', | |||||
| // provide the app's title in webpack's name field, so that | |||||
| // it can be accessed in index.html to inject the correct title. | |||||
| name: title, | |||||
| resolve: { | |||||
| alias: { | |||||
| '@': resolve('src'), | |||||
| '@crud': resolve('src/components/Crud') | |||||
| } | |||||
| }, | |||||
| output: { | |||||
| // 把子应用打包成 umd 库格式 | |||||
| library: `${name}-[name]`, | |||||
| libraryTarget: 'umd', | |||||
| jsonpFunction: `webpackJsonp_${name}` | |||||
| } | |||||
| }, | |||||
| chainWebpack(config) { | |||||
| // it can improve the speed of the first screen, it is recommended to turn on preload | |||||
| config.plugin('preload').tap(() => [ | |||||
| { | |||||
| rel: 'preload', | |||||
| // to ignore runtime.js | |||||
| // https://github.com/vuejs/vue-cli/blob/dev/packages/@vue/cli-service/lib/config/app.js#L171 | |||||
| fileBlacklist: [/\.map$/, /hot-update\.js$/, /runtime\..*\.js$/], | |||||
| include: 'initial' | |||||
| } | |||||
| ]) | |||||
| // when there are many pages, it will cause too many meaningless requests | |||||
| config.plugins.delete('prefetch') | |||||
| config | |||||
| .when(process.env.NODE_ENV !== 'development', | |||||
| config => { | |||||
| config | |||||
| .plugin('ScriptExtHtmlWebpackPlugin') | |||||
| .after('html') | |||||
| .use('script-ext-html-webpack-plugin', [{ | |||||
| // `runtime` must same as runtimeChunk name. default is `runtime` | |||||
| inline: /runtime\..*\.js$/ | |||||
| }]) | |||||
| .end() | |||||
| config | |||||
| .optimization.splitChunks({ | |||||
| chunks: 'all', | |||||
| cacheGroups: { | |||||
| libs: { | |||||
| name: 'chunk-libs', | |||||
| test: /[\\/]node_modules[\\/]/, | |||||
| priority: 10, | |||||
| chunks: 'initial' // only package third parties that are initially dependent | |||||
| }, | |||||
| elementUI: { | |||||
| name: 'chunk-elementUI', // split elementUI into a single package | |||||
| priority: 20, // the weight needs to be larger than libs and app or it will be packaged into libs or app | |||||
| test: /[\\/]node_modules[\\/]_?element-ui(.*)/ // in order to adapt to cnpm | |||||
| }, | |||||
| commons: { | |||||
| name: 'chunk-commons', | |||||
| test: resolve('src/components'), // can customize your rules | |||||
| minChunks: 3, // minimum common number | |||||
| priority: 5, | |||||
| reuseExistingChunk: true | |||||
| } | |||||
| } | |||||
| }) | |||||
| // https:// webpack.js.org/configuration/optimization/#optimizationruntimechunk | |||||
| config.optimization.runtimeChunk('single') | |||||
| } | |||||
| ) | |||||
| } | |||||
| } | |||||