commit af5c55ab53fb78461efce77402b881497dcc604c Author: chutingting Date: Wed Jan 3 22:36:47 2024 +0800 1 diff --git a/.editorconfig b/.editorconfig new file mode 100755 index 0000000..ea6e20f --- /dev/null +++ b/.editorconfig @@ -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 diff --git a/.env.development b/.env.development new file mode 100755 index 0000000..3b09cfe --- /dev/null +++ b/.env.development @@ -0,0 +1,5 @@ +# just a flag +ENV = 'development' + +# base api +VUE_APP_BASE_API = 'https://admin.meta-autotv.com/homeA/' diff --git a/.env.int b/.env.int new file mode 100755 index 0000000..19107fa --- /dev/null +++ b/.env.int @@ -0,0 +1,6 @@ +NODE_ENV = 'production' +OUTPUT_DIR = 'int' +VUE_APP_ENV = 'int' + +# base api +VUE_APP_BASE_API = '/adcop_cloud/' \ No newline at end of file diff --git a/.env.production b/.env.production new file mode 100755 index 0000000..4e0ebc7 --- /dev/null +++ b/.env.production @@ -0,0 +1,7 @@ +NODE_ENV = 'production' +OUTPUT_DIR = 'pro' +VUE_APP_ENV = 'production' + +# base api +VUE_APP_BASE_API = '/' + diff --git a/.eslintignore b/.eslintignore new file mode 100755 index 0000000..e6529fc --- /dev/null +++ b/.eslintignore @@ -0,0 +1,4 @@ +build/*.js +src/assets +public +dist diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100755 index 0000000..27cc6f4 --- /dev/null +++ b/.eslintrc.js @@ -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'] + } +} diff --git a/.gitignore b/.gitignore new file mode 100755 index 0000000..ab98a32 --- /dev/null +++ b/.gitignore @@ -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 \ No newline at end of file diff --git a/README.md b/README.md new file mode 100755 index 0000000..6a1ca19 --- /dev/null +++ b/README.md @@ -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 diff --git a/babel.config.js b/babel.config.js new file mode 100755 index 0000000..be5fe7a --- /dev/null +++ b/babel.config.js @@ -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'] + } + } +} diff --git a/build/index.js b/build/index.js new file mode 100755 index 0000000..0c57de2 --- /dev/null +++ b/build/index.js @@ -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}`) +} diff --git a/package.json b/package.json new file mode 100755 index 0000000..2259315 --- /dev/null +++ b/package.json @@ -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" +} diff --git a/postcss.config.js b/postcss.config.js new file mode 100755 index 0000000..10473ef --- /dev/null +++ b/postcss.config.js @@ -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': {} + } +} diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100755 index 0000000..34b63ac Binary files /dev/null and b/public/favicon.ico differ diff --git a/public/index.html b/public/index.html new file mode 100755 index 0000000..fa2be91 --- /dev/null +++ b/public/index.html @@ -0,0 +1,17 @@ + + + + + + + + <%= webpackConfig.name %> + + + +
+ + + diff --git a/src/App.vue b/src/App.vue new file mode 100755 index 0000000..45924c8 --- /dev/null +++ b/src/App.vue @@ -0,0 +1,16 @@ + + + diff --git a/src/api/car.js b/src/api/car.js new file mode 100644 index 0000000..ab3724d --- /dev/null +++ b/src/api/car.js @@ -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' + }) +} diff --git a/src/api/common.js b/src/api/common.js new file mode 100644 index 0000000..5e4fdd5 --- /dev/null +++ b/src/api/common.js @@ -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 + } + }) +} diff --git a/src/api/data.js b/src/api/data.js new file mode 100755 index 0000000..bfcbe59 --- /dev/null +++ b/src/api/data.js @@ -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' + }) +} diff --git a/src/api/homePage.js b/src/api/homePage.js new file mode 100644 index 0000000..4c4adb4 --- /dev/null +++ b/src/api/homePage.js @@ -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' + }) +} diff --git a/src/api/login.js b/src/api/login.js new file mode 100644 index 0000000..32a5a61 --- /dev/null +++ b/src/api/login.js @@ -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: { + email + } + }) +} + +/** +/** + * @description:忘记密码发送邮件 + * @param {string} data + * @return: data + */ +// export function sendUpdPwdEmailApi(email) { +// return request({ +// url: `/api/user/sendUpdPwdEmail`, +// method: 'post', +// data: { +// email +// } +// }) +// } +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' + diff --git a/src/api/nested/demo.js b/src/api/nested/demo.js new file mode 100755 index 0000000..8877965 --- /dev/null +++ b/src/api/nested/demo.js @@ -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 } diff --git a/src/api/user.js b/src/api/user.js new file mode 100755 index 0000000..8ff4389 --- /dev/null +++ b/src/api/user.js @@ -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' + }) +} diff --git a/src/assets/404_images/404.png b/src/assets/404_images/404.png new file mode 100755 index 0000000..3d8e230 Binary files /dev/null and b/src/assets/404_images/404.png differ diff --git a/src/assets/404_images/404_cloud.png b/src/assets/404_images/404_cloud.png new file mode 100755 index 0000000..c6281d0 Binary files /dev/null and b/src/assets/404_images/404_cloud.png differ diff --git a/src/assets/icon/iconfont.ttf b/src/assets/icon/iconfont.ttf new file mode 100755 index 0000000..5dde1c4 Binary files /dev/null and b/src/assets/icon/iconfont.ttf differ diff --git a/src/assets/icon/iconfont.woff b/src/assets/icon/iconfont.woff new file mode 100755 index 0000000..293b9a5 Binary files /dev/null and b/src/assets/icon/iconfont.woff differ diff --git a/src/assets/icon/iconfont.woff2 b/src/assets/icon/iconfont.woff2 new file mode 100755 index 0000000..b69f719 Binary files /dev/null and b/src/assets/icon/iconfont.woff2 differ diff --git a/src/assets/icon/icons.css b/src/assets/icon/icons.css new file mode 100755 index 0000000..b43c13e --- /dev/null +++ b/src/assets/icon/icons.css @@ -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"; +} + diff --git a/src/assets/img/home.png b/src/assets/img/home.png new file mode 100755 index 0000000..af92f4d Binary files /dev/null and b/src/assets/img/home.png differ diff --git a/src/assets/styles/btn.scss b/src/assets/styles/btn.scss new file mode 100755 index 0000000..41de79c --- /dev/null +++ b/src/assets/styles/btn.scss @@ -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; +} diff --git a/src/assets/styles/eladmin.scss b/src/assets/styles/eladmin.scss new file mode 100755 index 0000000..7bd6b86 --- /dev/null +++ b/src/assets/styles/eladmin.scss @@ -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; +} diff --git a/src/assets/styles/element-ui.scss b/src/assets/styles/element-ui.scss new file mode 100755 index 0000000..e465906 --- /dev/null +++ b/src/assets/styles/element-ui.scss @@ -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%; + } +} \ No newline at end of file diff --git a/src/assets/styles/element-variables.scss b/src/assets/styles/element-variables.scss new file mode 100755 index 0000000..a4f8c4a --- /dev/null +++ b/src/assets/styles/element-variables.scss @@ -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; +} diff --git a/src/assets/styles/index.scss b/src/assets/styles/index.scss new file mode 100755 index 0000000..0dde07e --- /dev/null +++ b/src/assets/styles/index.scss @@ -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 \ No newline at end of file diff --git a/src/assets/styles/mixin.scss b/src/assets/styles/mixin.scss new file mode 100755 index 0000000..36b74bb --- /dev/null +++ b/src/assets/styles/mixin.scss @@ -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%; +} diff --git a/src/assets/styles/sidebar.scss b/src/assets/styles/sidebar.scss new file mode 100755 index 0000000..12b93d3 --- /dev/null +++ b/src/assets/styles/sidebar.scss @@ -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; + } + } +} diff --git a/src/assets/styles/transition.scss b/src/assets/styles/transition.scss new file mode 100755 index 0000000..4cb27cc --- /dev/null +++ b/src/assets/styles/transition.scss @@ -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; +} diff --git a/src/assets/styles/variables.scss b/src/assets/styles/variables.scss new file mode 100755 index 0000000..be55772 --- /dev/null +++ b/src/assets/styles/variables.scss @@ -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; +} diff --git a/src/components/Breadcrumb/index.vue b/src/components/Breadcrumb/index.vue new file mode 100755 index 0000000..ba3c8a6 --- /dev/null +++ b/src/components/Breadcrumb/index.vue @@ -0,0 +1,78 @@ + + + + + diff --git a/src/components/Crud/CRUD.operation.vue b/src/components/Crud/CRUD.operation.vue new file mode 100755 index 0000000..33d2077 --- /dev/null +++ b/src/components/Crud/CRUD.operation.vue @@ -0,0 +1,268 @@ + + + + diff --git a/src/components/Crud/Pagination.vue b/src/components/Crud/Pagination.vue new file mode 100755 index 0000000..d4482fb --- /dev/null +++ b/src/components/Crud/Pagination.vue @@ -0,0 +1,18 @@ + + + diff --git a/src/components/Crud/RR.operation.vue b/src/components/Crud/RR.operation.vue new file mode 100755 index 0000000..df2c138 --- /dev/null +++ b/src/components/Crud/RR.operation.vue @@ -0,0 +1,20 @@ + + + diff --git a/src/components/Crud/UD.operation.vue b/src/components/Crud/UD.operation.vue new file mode 100755 index 0000000..c60abd7 --- /dev/null +++ b/src/components/Crud/UD.operation.vue @@ -0,0 +1,71 @@ + + diff --git a/src/components/Crud/crud.js b/src/components/Crud/crud.js new file mode 100755 index 0000000..1c08dd6 --- /dev/null +++ b/src/components/Crud/crud.js @@ -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
+ * @return crud instance. + * @example + * 要使用多crud时,请在关联crud的组件处使用crud-tag进行标记,如: + */ +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 +} diff --git a/src/components/Hamburger/index.vue b/src/components/Hamburger/index.vue new file mode 100755 index 0000000..b630a7d --- /dev/null +++ b/src/components/Hamburger/index.vue @@ -0,0 +1,35 @@ + + + + + diff --git a/src/components/Permission/index.js b/src/components/Permission/index.js new file mode 100755 index 0000000..e5dadd3 --- /dev/null +++ b/src/components/Permission/index.js @@ -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 diff --git a/src/components/Permission/permission.js b/src/components/Permission/permission.js new file mode 100755 index 0000000..9a6ba28 --- /dev/null +++ b/src/components/Permission/permission.js @@ -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']"`) + } + } +} diff --git a/src/components/ueditor/index.vue b/src/components/ueditor/index.vue new file mode 100755 index 0000000..ea13bd9 --- /dev/null +++ b/src/components/ueditor/index.vue @@ -0,0 +1,208 @@ + + + + + diff --git a/src/components/uploadFile/index.vue b/src/components/uploadFile/index.vue new file mode 100755 index 0000000..5abe2b5 --- /dev/null +++ b/src/components/uploadFile/index.vue @@ -0,0 +1,96 @@ + + + + diff --git a/src/layout/components/AppMain.vue b/src/layout/components/AppMain.vue new file mode 100755 index 0000000..1bb5c2b --- /dev/null +++ b/src/layout/components/AppMain.vue @@ -0,0 +1,40 @@ + + + + + + + diff --git a/src/layout/components/Navbar.vue b/src/layout/components/Navbar.vue new file mode 100755 index 0000000..85cdb62 --- /dev/null +++ b/src/layout/components/Navbar.vue @@ -0,0 +1,139 @@ + + + + + diff --git a/src/layout/components/Sidebar/FixiOSBug.js b/src/layout/components/Sidebar/FixiOSBug.js new file mode 100755 index 0000000..bc14856 --- /dev/null +++ b/src/layout/components/Sidebar/FixiOSBug.js @@ -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) + } + } + } + } +} diff --git a/src/layout/components/Sidebar/Item.vue b/src/layout/components/Sidebar/Item.vue new file mode 100755 index 0000000..8ff2108 --- /dev/null +++ b/src/layout/components/Sidebar/Item.vue @@ -0,0 +1,41 @@ + + + diff --git a/src/layout/components/Sidebar/Link.vue b/src/layout/components/Sidebar/Link.vue new file mode 100755 index 0000000..530b3d5 --- /dev/null +++ b/src/layout/components/Sidebar/Link.vue @@ -0,0 +1,43 @@ + + + diff --git a/src/layout/components/Sidebar/Logo.vue b/src/layout/components/Sidebar/Logo.vue new file mode 100755 index 0000000..579dcd5 --- /dev/null +++ b/src/layout/components/Sidebar/Logo.vue @@ -0,0 +1,82 @@ + + + + + diff --git a/src/layout/components/Sidebar/SidebarItem.vue b/src/layout/components/Sidebar/SidebarItem.vue new file mode 100755 index 0000000..a418c3d --- /dev/null +++ b/src/layout/components/Sidebar/SidebarItem.vue @@ -0,0 +1,95 @@ + + + diff --git a/src/layout/components/Sidebar/index.vue b/src/layout/components/Sidebar/index.vue new file mode 100755 index 0000000..a74b380 --- /dev/null +++ b/src/layout/components/Sidebar/index.vue @@ -0,0 +1,56 @@ + + + diff --git a/src/layout/components/index.js b/src/layout/components/index.js new file mode 100755 index 0000000..97ee3cd --- /dev/null +++ b/src/layout/components/index.js @@ -0,0 +1,3 @@ +export { default as Navbar } from './Navbar' +export { default as Sidebar } from './Sidebar' +export { default as AppMain } from './AppMain' diff --git a/src/layout/index.vue b/src/layout/index.vue new file mode 100755 index 0000000..61fb0d4 --- /dev/null +++ b/src/layout/index.vue @@ -0,0 +1,97 @@ + + + + + diff --git a/src/layout/mixin/ResizeHandler.js b/src/layout/mixin/ResizeHandler.js new file mode 100755 index 0000000..e8d0df8 --- /dev/null +++ b/src/layout/mixin/ResizeHandler.js @@ -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 }) + } + } + } + } +} diff --git a/src/main.js b/src/main.js new file mode 100755 index 0000000..67a6390 --- /dev/null +++ b/src/main.js @@ -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) +} diff --git a/src/mixins/crud.js b/src/mixins/crud.js new file mode 100755 index 0000000..e578cb1 --- /dev/null +++ b/src/mixins/crud.js @@ -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 + }) + } + } +} diff --git a/src/public-path.js b/src/public-path.js new file mode 100755 index 0000000..3cfba85 --- /dev/null +++ b/src/public-path.js @@ -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__ +} diff --git a/src/router/index.js b/src/router/index.js new file mode 100755 index 0000000..e84e2da --- /dev/null +++ b/src/router/index.js @@ -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 (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 diff --git a/src/settings.js b/src/settings.js new file mode 100755 index 0000000..347482f --- /dev/null +++ b/src/settings.js @@ -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 +} diff --git a/src/store/getters.js b/src/store/getters.js new file mode 100755 index 0000000..f316aa5 --- /dev/null +++ b/src/store/getters.js @@ -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 diff --git a/src/store/index.js b/src/store/index.js new file mode 100755 index 0000000..6be466a --- /dev/null +++ b/src/store/index.js @@ -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 diff --git a/src/store/modules/app.js b/src/store/modules/app.js new file mode 100755 index 0000000..7ea7e33 --- /dev/null +++ b/src/store/modules/app.js @@ -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 +} diff --git a/src/store/modules/settings.js b/src/store/modules/settings.js new file mode 100755 index 0000000..b3f33f8 --- /dev/null +++ b/src/store/modules/settings.js @@ -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 +} + diff --git a/src/store/modules/user.js b/src/store/modules/user.js new file mode 100755 index 0000000..f703322 --- /dev/null +++ b/src/store/modules/user.js @@ -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 +} + diff --git a/src/utils/auth.js b/src/utils/auth.js new file mode 100755 index 0000000..d715c23 --- /dev/null +++ b/src/utils/auth.js @@ -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) +} diff --git a/src/utils/clipboard.js b/src/utils/clipboard.js new file mode 100755 index 0000000..e916a44 --- /dev/null +++ b/src/utils/clipboard.js @@ -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) +} diff --git a/src/utils/get-page-title.js b/src/utils/get-page-title.js new file mode 100755 index 0000000..539c6ee --- /dev/null +++ b/src/utils/get-page-title.js @@ -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}` +} diff --git a/src/utils/index.js b/src/utils/index.js new file mode 100755 index 0000000..3af2232 --- /dev/null +++ b/src/utils/index.js @@ -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) +} diff --git a/src/utils/permission.js b/src/utils/permission.js new file mode 100755 index 0000000..b0c7523 --- /dev/null +++ b/src/utils/permission.js @@ -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 + } + } + } +} diff --git a/src/utils/request.js b/src/utils/request.js new file mode 100755 index 0000000..09ca5bb --- /dev/null +++ b/src/utils/request.js @@ -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 diff --git a/src/utils/validate.js b/src/utils/validate.js new file mode 100755 index 0000000..521cf0e --- /dev/null +++ b/src/utils/validate.js @@ -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) +} diff --git a/src/views/404.vue b/src/views/404.vue new file mode 100755 index 0000000..1791f55 --- /dev/null +++ b/src/views/404.vue @@ -0,0 +1,228 @@ + + + + + diff --git a/src/views/articleManage/index.vue b/src/views/articleManage/index.vue new file mode 100755 index 0000000..3f1ecfc --- /dev/null +++ b/src/views/articleManage/index.vue @@ -0,0 +1,115 @@ + + + diff --git a/src/views/articleManage/save.vue b/src/views/articleManage/save.vue new file mode 100755 index 0000000..15db2ed --- /dev/null +++ b/src/views/articleManage/save.vue @@ -0,0 +1,112 @@ + + + + diff --git a/src/views/home/index.vue b/src/views/home/index.vue new file mode 100755 index 0000000..212c17d --- /dev/null +++ b/src/views/home/index.vue @@ -0,0 +1,30 @@ + + + + + diff --git a/src/views/homePage/banner1.vue b/src/views/homePage/banner1.vue new file mode 100644 index 0000000..83b6da6 --- /dev/null +++ b/src/views/homePage/banner1.vue @@ -0,0 +1,115 @@ + + + diff --git a/src/views/homePage/banner2.vue b/src/views/homePage/banner2.vue new file mode 100644 index 0000000..962eed5 --- /dev/null +++ b/src/views/homePage/banner2.vue @@ -0,0 +1,115 @@ + + + diff --git a/src/views/homePage/carHot.vue b/src/views/homePage/carHot.vue new file mode 100644 index 0000000..6a20675 --- /dev/null +++ b/src/views/homePage/carHot.vue @@ -0,0 +1,54 @@ + + + diff --git a/src/views/homePage/carousel.vue b/src/views/homePage/carousel.vue new file mode 100644 index 0000000..cd2f3d0 --- /dev/null +++ b/src/views/homePage/carousel.vue @@ -0,0 +1,171 @@ + + + diff --git a/src/views/homePage/link.vue b/src/views/homePage/link.vue new file mode 100644 index 0000000..e69de29 diff --git a/src/views/homePage/yyzHot.vue b/src/views/homePage/yyzHot.vue new file mode 100644 index 0000000..1236ba0 --- /dev/null +++ b/src/views/homePage/yyzHot.vue @@ -0,0 +1,118 @@ + + + + diff --git a/src/views/login/index.vue b/src/views/login/index.vue new file mode 100755 index 0000000..8f88605 --- /dev/null +++ b/src/views/login/index.vue @@ -0,0 +1,289 @@ + + + + + diff --git a/src/views/nested/demo/index.vue b/src/views/nested/demo/index.vue new file mode 100755 index 0000000..25622b6 --- /dev/null +++ b/src/views/nested/demo/index.vue @@ -0,0 +1,115 @@ + + + diff --git a/src/views/nested/demo/save.vue b/src/views/nested/demo/save.vue new file mode 100755 index 0000000..d910a40 --- /dev/null +++ b/src/views/nested/demo/save.vue @@ -0,0 +1,112 @@ + + + + diff --git a/src/views/nested/menu1/index.vue b/src/views/nested/menu1/index.vue new file mode 100755 index 0000000..30cb670 --- /dev/null +++ b/src/views/nested/menu1/index.vue @@ -0,0 +1,7 @@ + diff --git a/src/views/nested/menu1/menu1-1/index.vue b/src/views/nested/menu1/menu1-1/index.vue new file mode 100755 index 0000000..27e173a --- /dev/null +++ b/src/views/nested/menu1/menu1-1/index.vue @@ -0,0 +1,7 @@ + diff --git a/src/views/nested/menu1/menu1-2/index.vue b/src/views/nested/menu1/menu1-2/index.vue new file mode 100755 index 0000000..0c86276 --- /dev/null +++ b/src/views/nested/menu1/menu1-2/index.vue @@ -0,0 +1,7 @@ + diff --git a/src/views/nested/menu1/menu1-2/menu1-2-1/index.vue b/src/views/nested/menu1/menu1-2/menu1-2-1/index.vue new file mode 100755 index 0000000..f87d88f --- /dev/null +++ b/src/views/nested/menu1/menu1-2/menu1-2-1/index.vue @@ -0,0 +1,5 @@ + diff --git a/src/views/nested/menu1/menu1-2/menu1-2-2/index.vue b/src/views/nested/menu1/menu1-2/menu1-2-2/index.vue new file mode 100755 index 0000000..d88789f --- /dev/null +++ b/src/views/nested/menu1/menu1-2/menu1-2-2/index.vue @@ -0,0 +1,5 @@ + diff --git a/src/views/nested/menu1/menu1-3/index.vue b/src/views/nested/menu1/menu1-3/index.vue new file mode 100755 index 0000000..f7cd073 --- /dev/null +++ b/src/views/nested/menu1/menu1-3/index.vue @@ -0,0 +1,5 @@ + diff --git a/vue.config.js b/vue.config.js new file mode 100755 index 0000000..fd84714 --- /dev/null +++ b/vue.config.js @@ -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') + } + ) + } +}