| @@ -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') | |||
| } | |||
| ) | |||
| } | |||
| } | |||