@@ -0,0 +1,14 @@ | |||||
# http://editorconfig.org | |||||
root = true | |||||
[*] | |||||
charset = utf-8 | |||||
indent_style = space | |||||
indent_size = 2 | |||||
end_of_line = lf | |||||
insert_final_newline = true | |||||
trim_trailing_whitespace = true | |||||
[*.md] | |||||
insert_final_newline = false | |||||
trim_trailing_whitespace = false |
@@ -0,0 +1,5 @@ | |||||
# just a flag | |||||
ENV = 'development' | |||||
# base api | |||||
VUE_APP_BASE_API = 'https://admin.meta-autotv.com/homeA/' |
@@ -0,0 +1,6 @@ | |||||
NODE_ENV = 'production' | |||||
OUTPUT_DIR = 'int' | |||||
VUE_APP_ENV = 'int' | |||||
# base api | |||||
VUE_APP_BASE_API = '/adcop_cloud/' |
@@ -0,0 +1,7 @@ | |||||
NODE_ENV = 'production' | |||||
OUTPUT_DIR = 'pro' | |||||
VUE_APP_ENV = 'production' | |||||
# base api | |||||
VUE_APP_BASE_API = '/' | |||||
@@ -0,0 +1,4 @@ | |||||
build/*.js | |||||
src/assets | |||||
public | |||||
dist |
@@ -0,0 +1,200 @@ | |||||
module.exports = { | |||||
root: true, | |||||
parserOptions: { | |||||
parser: 'babel-eslint', | |||||
sourceType: 'module' | |||||
}, | |||||
env: { | |||||
browser: true, | |||||
node: true, | |||||
es6: true, | |||||
}, | |||||
extends: ['plugin:vue/recommended', 'eslint:recommended'], | |||||
// add your custom rules here | |||||
//it is base on https://github.com/vuejs/eslint-config-vue | |||||
rules: { | |||||
'vue/html-closing-bracket-spacing': 'off', | |||||
'vue/no-multi-spaces': 'off', | |||||
'no-useless-catch': 'off', | |||||
'vue/require-default-prop': 'off', | |||||
'vue/require-prop-types': 'off', | |||||
'vue/html-self-closing': "off", | |||||
"vue/max-attributes-per-line": "off", | |||||
"vue/attributes-order": "off", | |||||
"vue/html-indent": "off", | |||||
"vue/html-quotes": "off", | |||||
"vue/html-closing-bracket-newline": "off", | |||||
"vue/singleline-html-element-content-newline": "off", | |||||
"vue/multiline-html-element-content-newline":"off", | |||||
"vue/name-property-casing": ["error", "PascalCase"], | |||||
"vue/no-v-html": "off", | |||||
'accessor-pairs': 2, | |||||
'arrow-spacing': [2, { | |||||
'before': true, | |||||
'after': true | |||||
}], | |||||
'block-spacing': [2, 'always'], | |||||
'brace-style': [2, '1tbs', { | |||||
'allowSingleLine': true | |||||
}], | |||||
'camelcase': [0, { | |||||
'properties': 'always' | |||||
}], | |||||
'comma-dangle': [2, 'never'], | |||||
'comma-spacing': [2, { | |||||
'before': false, | |||||
'after': true | |||||
}], | |||||
'comma-style': [2, 'last'], | |||||
'constructor-super': 2, | |||||
'curly': [2, 'multi-line'], | |||||
'dot-location': [2, 'property'], | |||||
'eol-last': 2, | |||||
'eqeqeq': ["error", "always", {"null": "ignore"}], | |||||
'generator-star-spacing': [2, { | |||||
'before': true, | |||||
'after': true | |||||
}], | |||||
'handle-callback-err': [2, '^(err|error)$'], | |||||
/* 'indent': [2, 2, { | |||||
'SwitchCase': 1 | |||||
}], */ | |||||
'indent': "off", | |||||
'jsx-quotes': [2, 'prefer-single'], | |||||
'key-spacing': [2, { | |||||
'beforeColon': false, | |||||
'afterColon': true | |||||
}], | |||||
'keyword-spacing': [2, { | |||||
'before': true, | |||||
'after': true | |||||
}], | |||||
'new-cap': [2, { | |||||
'newIsCap': true, | |||||
'capIsNew': false | |||||
}], | |||||
'new-parens': 2, | |||||
'no-array-constructor': 2, | |||||
'no-caller': 2, | |||||
'no-console': 'off', | |||||
'no-class-assign': 2, | |||||
'no-cond-assign': 2, | |||||
'no-const-assign': 2, | |||||
'no-control-regex': 0, | |||||
'no-delete-var': 2, | |||||
'no-dupe-args': 2, | |||||
'no-dupe-class-members': 2, | |||||
'no-dupe-keys': 2, | |||||
'no-duplicate-case': 2, | |||||
'no-empty-character-class': 2, | |||||
'no-empty-pattern': 2, | |||||
'no-eval': 2, | |||||
'no-ex-assign': 2, | |||||
'no-extend-native': 2, | |||||
'no-extra-bind': 2, | |||||
'no-extra-boolean-cast': 2, | |||||
'no-extra-parens': [2, 'functions'], | |||||
'no-fallthrough': 2, | |||||
'no-floating-decimal': 2, | |||||
'no-func-assign': 2, | |||||
'no-implied-eval': 2, | |||||
'no-inner-declarations': [2, 'functions'], | |||||
'no-invalid-regexp': 2, | |||||
'no-irregular-whitespace': 2, | |||||
'no-iterator': 2, | |||||
'no-label-var': 2, | |||||
'no-labels': [2, { | |||||
'allowLoop': false, | |||||
'allowSwitch': false | |||||
}], | |||||
'no-lone-blocks': 2, | |||||
'no-mixed-spaces-and-tabs': 2, | |||||
'no-multi-spaces': 2, | |||||
'no-multi-str': 2, | |||||
'no-multiple-empty-lines': [2, { | |||||
'max': 1 | |||||
}], | |||||
'no-native-reassign': 2, | |||||
'no-negated-in-lhs': 2, | |||||
'no-new-object': 2, | |||||
'no-new-require': 2, | |||||
'no-new-symbol': 2, | |||||
'no-new-wrappers': 2, | |||||
'no-obj-calls': 2, | |||||
'no-octal': 2, | |||||
'no-octal-escape': 2, | |||||
'no-path-concat': 2, | |||||
'no-proto': 2, | |||||
'no-redeclare': 2, | |||||
'no-regex-spaces': 2, | |||||
'no-return-assign': [2, 'except-parens'], | |||||
'no-self-assign': 2, | |||||
'no-self-compare': 2, | |||||
'no-sequences': 2, | |||||
'no-shadow-restricted-names': 2, | |||||
'no-spaced-func': 2, | |||||
'no-sparse-arrays': 2, | |||||
'no-this-before-super': 2, | |||||
'no-throw-literal': 2, | |||||
'no-trailing-spaces': 'off', | |||||
'no-undef': 2, | |||||
'no-undef-init': 2, | |||||
'no-unexpected-multiline': 2, | |||||
'no-unmodified-loop-condition': 2, | |||||
'no-unneeded-ternary': [2, { | |||||
'defaultAssignment': false | |||||
}], | |||||
'no-unreachable': 2, | |||||
'no-unsafe-finally': 2, | |||||
'no-unused-vars': 0, | |||||
'no-useless-call': 2, | |||||
'no-useless-computed-key': 2, | |||||
'no-useless-constructor': 2, | |||||
'no-useless-escape': 0, | |||||
'no-whitespace-before-property': 2, | |||||
'no-with': 2, | |||||
'one-var': [2, { | |||||
'initialized': 'never' | |||||
}], | |||||
'operator-linebreak': [2, 'after', { | |||||
'overrides': { | |||||
'?': 'before', | |||||
':': 'before' | |||||
} | |||||
}], | |||||
'padded-blocks': [2, 'never'], | |||||
'quotes': [2, 'single', { | |||||
'avoidEscape': true, | |||||
'allowTemplateLiterals': true | |||||
}], | |||||
'semi': [2, 'never'], | |||||
'semi-spacing': [2, { | |||||
'before': false, | |||||
'after': true | |||||
}], | |||||
'space-before-blocks': [2, 'always'], | |||||
'space-before-function-paren': [2, 'never'], | |||||
'space-in-parens': [0, 'never'], | |||||
'space-infix-ops': 2, | |||||
'space-unary-ops': [2, { | |||||
'words': true, | |||||
'nonwords': false | |||||
}], | |||||
'spaced-comment': [2, 'always', { | |||||
'markers': ['global', 'globals', 'eslint', 'eslint-disable', '*package', '!', ','] | |||||
}], | |||||
'template-curly-spacing': [2, 'never'], | |||||
'use-isnan': 2, | |||||
'valid-typeof': 2, | |||||
'wrap-iife': [2, 'any'], | |||||
'yield-star-spacing': [2, 'both'], | |||||
'yoda': [2, 'never'], | |||||
'prefer-const': 0, | |||||
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0, | |||||
'object-curly-spacing': [2, 'always', { | |||||
objectsInObjects: false | |||||
}], | |||||
'array-bracket-spacing': [2, 'never'] | |||||
} | |||||
} |
@@ -0,0 +1,21 @@ | |||||
.DS_Store | |||||
node_modules/ | |||||
dist/ | |||||
npm-debug.log* | |||||
yarn-debug.log* | |||||
yarn-error.log* | |||||
package-lock.json | |||||
tests/**/coverage/ | |||||
# Editor directories and files | |||||
.idea | |||||
.vscode | |||||
*.suo | |||||
*.ntvs* | |||||
*.njsproj | |||||
*.sln | |||||
/int | |||||
/stg | |||||
/pro | |||||
/uat |
@@ -0,0 +1,36 @@ | |||||
# 后台管理系统 | |||||
#### 前端模板 | |||||
初始模板基于: [https://github.com/PanJiaChen/vue-element-admin](https://github.com/PanJiaChen/vue-element-admin) | |||||
模板文档: [https://panjiachen.github.io/vue-element-admin-site/zh/guide/](https://panjiachen.github.io/vue-element-admin-site/zh/guide/) | |||||
#### Build Setup | |||||
``` bash | |||||
# 安装依赖 | |||||
npm install | |||||
# 启动服务 | |||||
npm run dev | |||||
# 构建INT、Stage、生产环境 | |||||
npm run int | |||||
npm run stg | |||||
npm run pro | |||||
``` | |||||
#### 常见问题 | |||||
1、linux 系统在安装依赖的时候会出现 node-sass 无法安装的问题 | |||||
解决方案: | |||||
``` | |||||
1. 单独安装:npm install --unsafe-perm node-sass | |||||
2. 直接使用:npm install --unsafe-perm | |||||
``` | |||||
2、加速node-sass安装 | |||||
https://www.ydyno.com/archives/1219.html |
@@ -0,0 +1,19 @@ | |||||
const prodPlugins = [] | |||||
if (process.env.VUE_APP_ENV === 'production') { | |||||
prodPlugins.push('transform-remove-console') | |||||
} | |||||
module.exports = { | |||||
plugins: [...prodPlugins], | |||||
presets: [ | |||||
// https://github.com/vuejs/vue-cli/tree/master/packages/@vue/babel-preset-app | |||||
'@vue/cli-plugin-babel/preset' | |||||
], | |||||
'env': { | |||||
'development': { | |||||
// babel-plugin-dynamic-import-node plugin only does one thing by converting all import() to require(). | |||||
// This plugin can significantly increase the speed of hot updates, when you have a large number of pages. | |||||
// https://panjiachen.github.io/vue-element-admin-site/guide/advanced/lazy-loading.html | |||||
'plugins': ['dynamic-import-node'] | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,35 @@ | |||||
const { run } = require('runjs') | |||||
const chalk = require('chalk') | |||||
const config = require('../vue.config.js') | |||||
const rawArgv = process.argv.slice(2) | |||||
const args = rawArgv.join(' ') | |||||
if (process.env.npm_config_preview || rawArgv.includes('--preview')) { | |||||
const report = rawArgv.includes('--report') | |||||
run(`vue-cli-service build ${args}`) | |||||
const port = 9526 | |||||
const publicPath = config.publicPath | |||||
var connect = require('connect') | |||||
var serveStatic = require('serve-static') | |||||
const app = connect() | |||||
app.use( | |||||
publicPath, | |||||
serveStatic('./dist', { | |||||
index: ['index.html', '/'] | |||||
}) | |||||
) | |||||
app.listen(port, function () { | |||||
console.log(chalk.green(`> Preview at http://localhost:${port}${publicPath}`)) | |||||
if (report) { | |||||
console.log(chalk.green(`> Report at http://localhost:${port}${publicPath}report.html`)) | |||||
} | |||||
}) | |||||
} else { | |||||
run(`vue-cli-service build ${args}`) | |||||
} |
@@ -0,0 +1,65 @@ | |||||
{ | |||||
"name": "admin-manage", | |||||
"version": "1.0.0", | |||||
"description": "后台管理系统", | |||||
"author": "", | |||||
"scripts": { | |||||
"dev": "vue-cli-service serve", | |||||
"pro": "vue-cli-service build", | |||||
"int": "vue-cli-service build --mode int", | |||||
"lint": "eslint --ext .js,.vue src" | |||||
}, | |||||
"dependencies": { | |||||
"axios": "0.18.1", | |||||
"clipboard": "^2.0.8", | |||||
"core-js": "3.6.5", | |||||
"element-ui": "2.13.2", | |||||
"js-cookie": "2.2.0", | |||||
"moment": "^2.29.1", | |||||
"normalize.css": "7.0.0", | |||||
"nprogress": "0.2.0", | |||||
"path-to-regexp": "2.4.0", | |||||
"vue": "2.6.10", | |||||
"vue-router": "3.0.6", | |||||
"vue-ueditor-wrap": "^2.5.6", | |||||
"vuex": "3.1.0" | |||||
}, | |||||
"devDependencies": { | |||||
"@vue/cli-plugin-babel": "4.4.4", | |||||
"@vue/cli-plugin-eslint": "4.4.4", | |||||
"@vue/cli-plugin-unit-jest": "4.4.4", | |||||
"@vue/cli-service": "4.4.4", | |||||
"@vue/test-utils": "1.0.0-beta.29", | |||||
"autoprefixer": "9.5.1", | |||||
"babel-eslint": "10.0.1", | |||||
"babel-jest": "23.6.0", | |||||
"babel-plugin-dynamic-import-node": "2.3.3", | |||||
"babel-plugin-transform-remove-console": "^6.9.4", | |||||
"chalk": "2.4.2", | |||||
"connect": "3.6.6", | |||||
"eslint": "^6.7.2", | |||||
"eslint-plugin-vue": "6.2.2", | |||||
"html-webpack-plugin": "3.2.0", | |||||
"husky": "^4.3.8", | |||||
"runjs": "4.3.2", | |||||
"sass": "1.26.8", | |||||
"sass-loader": "8.0.2", | |||||
"script-ext-html-webpack-plugin": "2.1.3", | |||||
"serve-static": "1.13.2", | |||||
"vue-template-compiler": "2.6.10" | |||||
}, | |||||
"browserslist": [ | |||||
"> 1%", | |||||
"last 2 versions" | |||||
], | |||||
"engines": { | |||||
"node": ">=8.9", | |||||
"npm": ">= 3.0.0" | |||||
}, | |||||
"husky": { | |||||
"hooks": { | |||||
"pre-commit": "npm run lint" | |||||
} | |||||
}, | |||||
"license": "MIT" | |||||
} |
@@ -0,0 +1,8 @@ | |||||
// https://github.com/michael-ciniawsky/postcss-load-config | |||||
module.exports = { | |||||
'plugins': { | |||||
// to edit target browsers: use "browserslist" field in package.json | |||||
'autoprefixer': {} | |||||
} | |||||
} |
@@ -0,0 +1,17 @@ | |||||
<!DOCTYPE html> | |||||
<html> | |||||
<head> | |||||
<meta charset="utf-8"> | |||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> | |||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"> | |||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico"> | |||||
<title><%= webpackConfig.name %></title> | |||||
</head> | |||||
<body> | |||||
<noscript> | |||||
<strong>We're sorry but <%= webpackConfig.name %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong> | |||||
</noscript> | |||||
<div id="app"></div> | |||||
<!-- built files will be auto injected --> | |||||
</body> | |||||
</html> |
@@ -0,0 +1,16 @@ | |||||
<template> | |||||
<div id="app"> | |||||
<router-view /> | |||||
</div> | |||||
</template> | |||||
<script> | |||||
export default { | |||||
name: 'App', | |||||
methods: { | |||||
test() { | |||||
const a = 0 | |||||
} | |||||
} | |||||
} | |||||
</script> |
@@ -0,0 +1,75 @@ | |||||
/* | |||||
* @Date: 2021-08-24 14:51:18 | |||||
* @LastEditTime: 2022-07-27 10:47:43 | |||||
* @Description: 汽车资讯 | |||||
*/ | |||||
import request from '@/utils/request' | |||||
/** 轮播 */ | |||||
export function saveLunbo(data) { | |||||
return request({ | |||||
url: '/car/saveLunbo', | |||||
method: 'post', | |||||
data | |||||
}) | |||||
} | |||||
export function delLunbo(id) { | |||||
return request({ | |||||
url: '/index/delLunbo', | |||||
method: 'post', | |||||
data: { id } | |||||
}) | |||||
} | |||||
/** 热点新闻配置(是否显示) */ | |||||
export function hotNewsConfig() { | |||||
return request({ | |||||
url: '/car/hotNewsConfig', | |||||
method: 'get' | |||||
}) | |||||
} | |||||
export function saveHotNewsConfig(data) { | |||||
return request({ | |||||
url: '/index/saveHotNewsConfig', | |||||
method: 'post', | |||||
data | |||||
}) | |||||
} | |||||
/** 广告位1 */ | |||||
export function guanggao1() { | |||||
return request({ | |||||
url: '/car/guanggao1', | |||||
method: 'get' | |||||
}) | |||||
} | |||||
export function saveGuangGao(data) { | |||||
return request({ | |||||
url: '/index/saveGuangGao', | |||||
method: 'post', | |||||
data | |||||
}) | |||||
} | |||||
/** 视频轮播 */ | |||||
export function videoSave(data) { | |||||
return request({ | |||||
url: '/car/videoSave', | |||||
method: 'post', | |||||
data | |||||
}) | |||||
} | |||||
/** 广告位2 */ | |||||
export function guanggao2() { | |||||
return request({ | |||||
url: '/car/guanggao2', | |||||
method: 'get' | |||||
}) | |||||
} |
@@ -0,0 +1,17 @@ | |||||
import request from '@/utils/request' | |||||
export function upload(data) { | |||||
var formData = new FormData() | |||||
formData.append('file', data.file) | |||||
console.log('upload:', data) | |||||
return request({ | |||||
url: '/upload/awsImgUpload', | |||||
method: 'post', | |||||
data: formData, | |||||
timeout: 1000 * 60 * 2, // 超时时间两分钟 | |||||
params: { | |||||
width: 200, | |||||
hight: 200 | |||||
} | |||||
}) | |||||
} |
@@ -0,0 +1,18 @@ | |||||
import request from '@/utils/request' | |||||
import qs from 'qs' | |||||
export function initData(url, params) { | |||||
// console.log(qs.stringify(params, { indices: false })) | |||||
return request({ | |||||
url: url + '?' + qs.stringify(params, { indices: false }), | |||||
method: 'get' | |||||
}) | |||||
} | |||||
export function download(url, params) { | |||||
return request({ | |||||
url: url + '?' + qs.stringify(params, { indices: false }), | |||||
method: 'get', | |||||
responseType: 'blob' | |||||
}) | |||||
} |
@@ -0,0 +1,71 @@ | |||||
/* | |||||
* @Date: 2021-08-24 14:51:18 | |||||
* @LastEditTime: 2022-07-27 10:47:43 | |||||
* @Description: 汽车资讯 | |||||
*/ | |||||
import request from '@/utils/request' | |||||
/** 轮播 */ | |||||
export function saveLunbo(data) { | |||||
return request({ | |||||
url: '/index/saveLunbo', | |||||
method: 'post', | |||||
data | |||||
}) | |||||
} | |||||
export function delLunbo(id) { | |||||
return request({ | |||||
url: '/index/delLunbo', | |||||
method: 'post', | |||||
data: { id } | |||||
}) | |||||
} | |||||
/** 热点新闻配置(是否显示) */ | |||||
export function carNewsHotSave(newsIdList) { | |||||
return request({ | |||||
url: ' /index/carNewsHotSave', | |||||
method: 'post', | |||||
data: { newsIdList } | |||||
}) | |||||
} | |||||
export function hotNewsConfig() { | |||||
return request({ | |||||
url: '/index/hotNewsConfig', | |||||
method: 'get' | |||||
}) | |||||
} | |||||
export function saveHotNewsConfig(data) { | |||||
return request({ | |||||
url: '/index/saveHotNewsConfig', | |||||
method: 'post', | |||||
data | |||||
}) | |||||
} | |||||
/** 广告位1 */ | |||||
export function guanggao1() { | |||||
return request({ | |||||
url: '/index/guanggao1', | |||||
method: 'get' | |||||
}) | |||||
} | |||||
export function saveGuangGao(data) { | |||||
return request({ | |||||
url: '/index/saveGuangGao', | |||||
method: 'post', | |||||
data | |||||
}) | |||||
} | |||||
/** 广告位2 */ | |||||
export function guanggao2() { | |||||
return request({ | |||||
url: '/index/guanggao2', | |||||
method: 'get' | |||||
}) | |||||
} |
@@ -0,0 +1,164 @@ | |||||
import request from '@/utils/request.js' | |||||
const baseURL = process.env.VUE_APP_BASE_API | |||||
/** | |||||
* @description 登录 | |||||
* @params email,password,code | |||||
* @returns data | |||||
*/ | |||||
export function loginApi(data) { | |||||
return request({ | |||||
url: `/doLogin`, | |||||
method: 'post', | |||||
data | |||||
}) | |||||
} | |||||
/** | |||||
* @description 退出登录 | |||||
* @params email,password,code | |||||
* @returns data | |||||
*/ | |||||
export function logoutApi(data) { | |||||
return request({ | |||||
// url: `/api/user/loginByEmail`, | |||||
url: `/logout`, | |||||
method: 'get' | |||||
}) | |||||
} | |||||
/** | |||||
/** | |||||
* @description:重新发送邮件 | |||||
* @param {string} data | |||||
* @return: data | |||||
*/ | |||||
export function sendRegisterEmail(email) { | |||||
return request({ | |||||
url: `/api/user/sendRegisterEmail`, | |||||
method: 'post', | |||||
data: { | |||||
} | |||||
}) | |||||
} | |||||
/** | |||||
/** | |||||
* @description:忘记密码发送邮件 | |||||
* @param {string} data | |||||
* @return: data | |||||
*/ | |||||
// export function sendUpdPwdEmailApi(email) { | |||||
// return request({ | |||||
// url: `/api/user/sendUpdPwdEmail`, | |||||
// method: 'post', | |||||
// data: { | |||||
// } | |||||
// }) | |||||
// } | |||||
export function sendUpdPwdEmailApi(data) { | |||||
return request({ | |||||
url: `/api/user/sendPhoneCode?phone=${data.phone}&code=${data.code}`, | |||||
method: 'get' | |||||
}) | |||||
} | |||||
/** `/api/user/sendLoginPhoneCode?phone=${phone}` | |||||
* @description 忘记密码第二步,短信验证码和新密码 | |||||
* @param {object} loginParams (phone,code,pwd) | |||||
* @returns data | |||||
*/ | |||||
export function doUpdPassApi(loginParams) { | |||||
return request({ | |||||
url: `/api/user/updPass`, | |||||
method: 'post', | |||||
data: loginParams | |||||
}) | |||||
} | |||||
/** | |||||
* @description 获取手机验证码 | |||||
* @param {number} phone | |||||
* @returns data | |||||
*/ | |||||
export function getSMSToLoginApi(phone) { | |||||
return request({ | |||||
url: `/api/user/sendLoginPhoneCode?phone=${phone}`, | |||||
method: 'get' | |||||
}) | |||||
} | |||||
/** | |||||
* @description 短信验证码登录 | |||||
* @param {object} loginParams (phone,code) | |||||
* @returns data | |||||
*/ | |||||
export function loginByMSMApi(loginParams) { | |||||
return request({ | |||||
url: `/api/user/doLoginByPhone`, | |||||
method: 'post', | |||||
data: loginParams | |||||
}) | |||||
} | |||||
/** | |||||
* @description 查询用户邀请码状态 | |||||
* @returns data | |||||
*/ | |||||
export function doFindInviteCode() { | |||||
return request({ | |||||
url: `/api/inviteCode/find`, | |||||
method: 'get' | |||||
}) | |||||
} | |||||
/** | |||||
* @description 更新邀请码 | |||||
* @param {object} inviteCodeData | |||||
* @returns data | |||||
*/ | |||||
export function doUpdateInviteCode(inviteCodeData) { | |||||
return request({ | |||||
url: `/api/inviteCode/update`, | |||||
method: 'post', | |||||
data: inviteCodeData | |||||
}) | |||||
} | |||||
/** | |||||
/** | |||||
* @description:邮件重置密码, | |||||
* @param pwd, ticket | |||||
* @return: data | |||||
*/ | |||||
export function resetPwdEmailApi(data) { | |||||
return request({ | |||||
url: `/api/user/doUpdPass`, | |||||
method: 'post', | |||||
data | |||||
}) | |||||
} | |||||
/** | |||||
/** | |||||
* @description:邮件重置密码, | |||||
* @param oldpwd, newpwd | |||||
* @return: data | |||||
*/ | |||||
export function resetPwdFormApi(data) { | |||||
return request({ | |||||
url: `/api/user/oldUpdPass`, | |||||
method: 'post', | |||||
data | |||||
}) | |||||
} | |||||
/** | |||||
* @description:获取图形验证码 | |||||
* @return: 验证码地址 | |||||
*/ | |||||
export const getCodeImgUrl = baseURL + '/captcha.jpg' | |||||
@@ -0,0 +1,42 @@ | |||||
/* | |||||
* @Date: 2021-08-24 14:51:18 | |||||
* @LastEditTime: 2022-07-27 10:47:43 | |||||
* @Description: 列表demo API | |||||
*/ | |||||
import request from '@/utils/request' | |||||
export function del(data) { | |||||
return request({ | |||||
url: '', | |||||
method: 'post', | |||||
data: { data } | |||||
}) | |||||
} | |||||
/* 导出 */ | |||||
export function exportFile(data) { | |||||
return request({ | |||||
url: '', | |||||
method: 'get', | |||||
params: data, | |||||
responseType: 'blob' | |||||
}) | |||||
} | |||||
export function getData(id) { | |||||
return request({ | |||||
url: '', | |||||
method: 'post', | |||||
data: { id } | |||||
}) | |||||
} | |||||
export function save(data) { | |||||
return request({ | |||||
url: '', | |||||
method: 'post', | |||||
data: { data } | |||||
}) | |||||
} | |||||
export default { del, getData, save, exportFile } |
@@ -0,0 +1,24 @@ | |||||
import request from '@/utils/request' | |||||
export function login(data) { | |||||
return request({ | |||||
url: '/vue-admin-template/user/login', | |||||
method: 'post', | |||||
data | |||||
}) | |||||
} | |||||
export function getInfo(token) { | |||||
return request({ | |||||
url: '/vue-admin-template/user/info', | |||||
method: 'get', | |||||
params: { token } | |||||
}) | |||||
} | |||||
export function logout() { | |||||
return request({ | |||||
url: '/vue-admin-template/user/logout', | |||||
method: 'post' | |||||
}) | |||||
} |
@@ -0,0 +1,63 @@ | |||||
@font-face { | |||||
font-family: "iconfont"; /* Project id 2589074 */ | |||||
src: url('./iconfont.woff2?t=1622700365894') format('woff2'), | |||||
url('./iconfont.woff?t=1622700365894') format('woff'), | |||||
url('./iconfont.ttf?t=1622700365894') format('truetype'); | |||||
} | |||||
.icon { | |||||
font-family: "iconfont" !important; | |||||
font-size: 16px; | |||||
font-style: normal; | |||||
-webkit-font-smoothing: antialiased; | |||||
-moz-osx-font-smoothing: grayscale; | |||||
} | |||||
.icon-table:before { | |||||
content: "\e652"; | |||||
} | |||||
.icon-form:before { | |||||
content: "\e653"; | |||||
} | |||||
.icon-eye-open:before { | |||||
content: "\e654"; | |||||
} | |||||
.icon-dashboard:before { | |||||
content: "\e655"; | |||||
} | |||||
.icon-nested:before { | |||||
content: "\e64b"; | |||||
} | |||||
.icon-example:before { | |||||
content: "\e64c"; | |||||
} | |||||
.icon-link:before { | |||||
content: "\e64d"; | |||||
} | |||||
.icon-eye:before { | |||||
content: "\e67d"; | |||||
} | |||||
.icon-hamburger:before { | |||||
content: "\e615"; | |||||
} | |||||
.icon-tree:before { | |||||
content: "\e64f"; | |||||
} | |||||
.icon-user:before { | |||||
content: "\e650"; | |||||
} | |||||
.icon-password:before { | |||||
content: "\e651"; | |||||
} | |||||
@@ -0,0 +1,35 @@ | |||||
// base color | |||||
$blue:#324157; | |||||
$light-blue:#3A71A8; | |||||
$red:#C03639; | |||||
$pink: #E65D6E; | |||||
$green: #30B08F; | |||||
$tiffany: #4AB7BD; | |||||
$yellow:#FEC171; | |||||
$panGreen: #30B08F; | |||||
// sidebar | |||||
$menuText:#bfcbd9; | |||||
$menuActiveText:#409EFF; | |||||
$subMenuActiveText:#f4f4f5; // https://github.com/ElemeFE/element/issues/12951 | |||||
$menuBg:#304156; | |||||
$menuHover:#263445; | |||||
$subMenuBg:#1f2d3d; | |||||
$subMenuHover:#001528; | |||||
$sideBarWidth: 205px; | |||||
// the :export directive is the magic sauce for webpack | |||||
// https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass | |||||
:export { | |||||
menuText: $menuText; | |||||
menuActiveText: $menuActiveText; | |||||
subMenuActiveText: $subMenuActiveText; | |||||
menuBg: $menuBg; | |||||
menuHover: $menuHover; | |||||
subMenuBg: $subMenuBg; | |||||
subMenuHover: $subMenuHover; | |||||
sideBarWidth: $sideBarWidth; | |||||
} |
@@ -0,0 +1,121 @@ | |||||
.head-container { | |||||
padding-bottom: 10px; | |||||
.filter-item { | |||||
width: 150px; | |||||
display: inline-block; | |||||
vertical-align: middle; | |||||
margin: 0 3px 10px 0; | |||||
input { | |||||
height: 30.5px; | |||||
line-height: 30.5px; | |||||
} | |||||
} | |||||
.el-form-item-label { | |||||
margin: 0 3px 9px 0; | |||||
display: inline-block; | |||||
text-align: right; | |||||
vertical-align: middle; | |||||
font-size: 14px; | |||||
color: #606266; | |||||
line-height: 30.5px; | |||||
padding: 0 7px 0 7px; | |||||
} | |||||
.el-button{ | |||||
width: auto; | |||||
} | |||||
.el-button+.el-button { | |||||
margin-left: 0 !important; | |||||
} | |||||
.el-select__caret.el-input__icon.el-icon-arrow-up{ | |||||
line-height: 30.5px; | |||||
} | |||||
.date-item { | |||||
display: inline-block; | |||||
vertical-align: middle; | |||||
margin: 0 3px 10px 0; | |||||
height: 30.5px !important; | |||||
width: 230px !important; | |||||
} | |||||
} | |||||
.el-avatar { | |||||
display: inline-block; | |||||
text-align: center; | |||||
background: #ccc; | |||||
color: #fff; | |||||
white-space: nowrap; | |||||
position: relative; | |||||
overflow: hidden; | |||||
vertical-align: middle; | |||||
width: 32px; | |||||
height: 32px; | |||||
line-height: 32px; | |||||
border-radius: 16px; | |||||
} | |||||
.logo-con{ | |||||
height: 60px; | |||||
padding: 13px 0 0; | |||||
img{ | |||||
height: 32px; | |||||
width: 135px; | |||||
display: block; | |||||
//margin: 0 auto; | |||||
} | |||||
} | |||||
#el-login-footer { | |||||
height: 40px; | |||||
line-height: 40px; | |||||
position: fixed; | |||||
bottom: 0; | |||||
width: 100%; | |||||
text-align: center; | |||||
color: #fff; | |||||
font-family: Arial, serif; | |||||
font-size: 12px; | |||||
letter-spacing: 1px; | |||||
} | |||||
#el-main-footer { | |||||
background: none repeat scroll 0 0 white; | |||||
border-top: 1px solid #e7eaec; | |||||
overflow: hidden; | |||||
padding: 10px 6px 0 6px; | |||||
height: 33px; | |||||
font-size: 0.7rem !important; | |||||
color: #7a8b9a; | |||||
letter-spacing: 0.8px; | |||||
font-family: Arial, sans-serif !important; | |||||
position: fixed; | |||||
bottom: 0; | |||||
z-index: 99; | |||||
width: 100%; | |||||
} | |||||
.eladmin-upload { | |||||
border: 1px dashed #c0ccda; | |||||
border-radius: 5px; | |||||
height: 45px; | |||||
line-height: 45px; | |||||
width: 368px; | |||||
} | |||||
.my-blockquote{ | |||||
margin: 0 0 10px; | |||||
padding: 15px; | |||||
line-height: 22px; | |||||
border-left: 5px solid #00437B; | |||||
border-radius: 0 2px 2px 0; | |||||
background-color: #f2f2f2; | |||||
} | |||||
.my-code{ | |||||
position: relative; | |||||
padding: 15px; | |||||
line-height: 20px; | |||||
border-left: 5px solid #ddd; | |||||
color: #333; | |||||
font-family: Courier New, serif; | |||||
font-size: 12px | |||||
} | |||||
.el-tabs{ | |||||
margin-bottom: 25px; | |||||
} |
@@ -0,0 +1,70 @@ | |||||
// cover some element-ui styles | |||||
.el-breadcrumb__inner, | |||||
.el-breadcrumb__inner a { | |||||
font-weight: 400 !important; | |||||
} | |||||
.el-upload { | |||||
input[type="file"] { | |||||
display: none !important; | |||||
} | |||||
} | |||||
.el-upload__input { | |||||
display: none; | |||||
} | |||||
// to fixed https://github.com/ElemeFE/element/issues/2461 | |||||
.el-dialog { | |||||
transform: none; | |||||
left: 0; | |||||
position: relative; | |||||
margin: 0 auto; | |||||
} | |||||
// refine element ui upload | |||||
.upload-container { | |||||
.el-upload { | |||||
width: 100%; | |||||
.el-upload-dragger { | |||||
width: 100%; | |||||
height: 200px; | |||||
} | |||||
} | |||||
} | |||||
// dropdown | |||||
.el-dropdown-menu { | |||||
a { | |||||
display: block | |||||
} | |||||
} | |||||
// to fix el-date-picker css style | |||||
.el-range-separator { | |||||
box-sizing: content-box; | |||||
} | |||||
// fix date-picker ui bug in filter-item | |||||
.el-range-editor.el-input__inner { | |||||
display: inline-flex !important; | |||||
} | |||||
//全局覆盖样式 | |||||
//用于解决select标签无法独占一行的情况 | |||||
.resetform { | |||||
width: 100%; | |||||
height: auto; | |||||
.el-select { | |||||
width: 100%; | |||||
} | |||||
.el-input__inner{ | |||||
width: 100%; | |||||
} | |||||
.el-cascader{ | |||||
width: 100%; | |||||
} | |||||
} |
@@ -0,0 +1,31 @@ | |||||
/** | |||||
* I think element-ui's default theme color is too light for long-term use. | |||||
* So I modified the default color and you can modify it to your liking. | |||||
**/ | |||||
/* theme color */ | |||||
$--color-primary: #1890ff; | |||||
$--color-success: #13ce66; | |||||
$--color-warning: #FFBA00; | |||||
$--color-danger: #ff4949; | |||||
// $--color-info: #1E1E1E; | |||||
$--button-font-weight: 400; | |||||
// $--color-text-regular: #1f2d3d; | |||||
$--border-color-light: #dfe4ed; | |||||
$--border-color-lighter: #e6ebf5; | |||||
$--table-border:1px solid#dfe6ec; | |||||
/* icon font path, required */ | |||||
$--font-path: '~element-ui/lib/theme-chalk/fonts'; | |||||
@import "../../../node_modules/element-ui/packages/theme-chalk/src/index"; | |||||
// the :export directive is the magic sauce for webpack | |||||
// https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass | |||||
:export { | |||||
theme: $--color-primary; | |||||
} |
@@ -0,0 +1,81 @@ | |||||
@import './variables.scss'; | |||||
@import './mixin.scss'; | |||||
@import './transition.scss'; | |||||
@import './element-ui.scss'; | |||||
@import './sidebar.scss'; | |||||
@import './eladmin.scss'; | |||||
@import './btn.scss'; | |||||
body { | |||||
height: 100%; | |||||
-moz-osx-font-smoothing: grayscale; | |||||
-webkit-font-smoothing: antialiased; | |||||
text-rendering: optimizeLegibility; | |||||
font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif; | |||||
} | |||||
label { | |||||
font-weight: 700; | |||||
} | |||||
html { | |||||
height: 100%; | |||||
box-sizing: border-box; | |||||
} | |||||
#app { | |||||
height: 100%; | |||||
} | |||||
*, | |||||
*:before, | |||||
*:after { | |||||
box-sizing: inherit; | |||||
} | |||||
a:focus, | |||||
a:active { | |||||
outline: none; | |||||
} | |||||
a, | |||||
a:focus, | |||||
a:hover { | |||||
cursor: pointer; | |||||
color: inherit; | |||||
text-decoration: none; | |||||
} | |||||
div:focus { | |||||
outline: none; | |||||
} | |||||
.clearfix { | |||||
&:after { | |||||
visibility: hidden; | |||||
display: block; | |||||
font-size: 0; | |||||
content: " "; | |||||
clear: both; | |||||
height: 0; | |||||
} | |||||
} | |||||
// main-container global css | |||||
.app-container { | |||||
padding: 20px 20px 70px 20px; | |||||
height: calc(100vh - 50px); | |||||
overflow: auto; | |||||
} | |||||
//竖向自适应布局 start | |||||
.flex_fixed { | |||||
flex: none; | |||||
} | |||||
.flex_auto { | |||||
flex: 1; | |||||
} | |||||
.flex_parent { | |||||
display: flex; | |||||
flex-direction: column; | |||||
} | |||||
//竖向自适应布局 end |
@@ -0,0 +1,28 @@ | |||||
@mixin clearfix { | |||||
&:after { | |||||
content: ""; | |||||
display: table; | |||||
clear: both; | |||||
} | |||||
} | |||||
@mixin scrollBar { | |||||
&::-webkit-scrollbar-track-piece { | |||||
background: #d3dce6; | |||||
} | |||||
&::-webkit-scrollbar { | |||||
width: 6px; | |||||
} | |||||
&::-webkit-scrollbar-thumb { | |||||
background: #99a9bf; | |||||
border-radius: 20px; | |||||
} | |||||
} | |||||
@mixin relative { | |||||
position: relative; | |||||
width: 100%; | |||||
height: 100%; | |||||
} |
@@ -0,0 +1,226 @@ | |||||
#app { | |||||
overflow: hidden; | |||||
.main-container { | |||||
min-height: 100%; | |||||
transition: margin-left .28s; | |||||
margin-left: $sideBarWidth; | |||||
position: relative; | |||||
} | |||||
.sidebar-container { | |||||
transition: width 0.28s; | |||||
width: $sideBarWidth !important; | |||||
background-color: $menuBg; | |||||
height: 100%; | |||||
position: fixed; | |||||
font-size: 0px; | |||||
top: 0; | |||||
bottom: 0; | |||||
left: 0; | |||||
z-index: 1001; | |||||
overflow: hidden; | |||||
// reset element-ui css | |||||
.horizontal-collapse-transition { | |||||
transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out; | |||||
} | |||||
.scrollbar-wrapper { | |||||
overflow-x: hidden !important; | |||||
} | |||||
.el-scrollbar__bar.is-vertical { | |||||
right: 0px; | |||||
} | |||||
.el-scrollbar { | |||||
height: 100%; | |||||
} | |||||
&.has-logo { | |||||
.el-scrollbar { | |||||
height: calc(100% - 50px); | |||||
} | |||||
} | |||||
.is-horizontal { | |||||
display: none; | |||||
} | |||||
a { | |||||
display: inline-block; | |||||
width: 100%; | |||||
overflow: hidden; | |||||
} | |||||
.svg-icon { | |||||
margin-right: 16px; | |||||
} | |||||
.sub-el-icon, .icon { | |||||
margin-right: 12px; | |||||
margin-left: -2px; | |||||
} | |||||
.el-menu { | |||||
border: none; | |||||
height: 100%; | |||||
width: 100% !important; | |||||
} | |||||
// menu hover | |||||
.submenu-title-noDropdown, | |||||
.el-submenu__title { | |||||
&:hover { | |||||
background-color: $menuHover !important; | |||||
} | |||||
} | |||||
.is-active>.el-submenu__title { | |||||
color: $subMenuActiveText !important; | |||||
} | |||||
& .nest-menu .el-submenu>.el-submenu__title, | |||||
& .el-submenu .el-menu-item { | |||||
min-width: $sideBarWidth !important; | |||||
background-color: $subMenuBg !important; | |||||
&:hover { | |||||
background-color: $subMenuHover !important; | |||||
} | |||||
} | |||||
} | |||||
.hideSidebar { | |||||
.sidebar-container { | |||||
width: 54px !important; | |||||
} | |||||
.main-container { | |||||
margin-left: 54px; | |||||
} | |||||
.submenu-title-noDropdown { | |||||
padding: 0 !important; | |||||
position: relative; | |||||
.el-tooltip { | |||||
padding: 0 !important; | |||||
.svg-icon { | |||||
margin-left: 20px; | |||||
} | |||||
.sub-el-icon, .icon { | |||||
margin-left: 19px; | |||||
} | |||||
} | |||||
} | |||||
.el-submenu { | |||||
overflow: hidden; | |||||
&>.el-submenu__title { | |||||
padding: 0 !important; | |||||
.svg-icon { | |||||
margin-left: 20px; | |||||
} | |||||
.sub-el-icon, .icon { | |||||
margin-left: 19px; | |||||
} | |||||
.el-submenu__icon-arrow { | |||||
display: none; | |||||
} | |||||
} | |||||
} | |||||
.el-menu--collapse { | |||||
.el-submenu { | |||||
&>.el-submenu__title { | |||||
&>span { | |||||
height: 0; | |||||
width: 0; | |||||
overflow: hidden; | |||||
visibility: hidden; | |||||
display: inline-block; | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
.el-menu--collapse .el-menu .el-submenu { | |||||
min-width: $sideBarWidth !important; | |||||
} | |||||
// mobile responsive | |||||
.mobile { | |||||
.main-container { | |||||
margin-left: 0px; | |||||
} | |||||
.sidebar-container { | |||||
transition: transform .28s; | |||||
width: $sideBarWidth !important; | |||||
} | |||||
&.hideSidebar { | |||||
.sidebar-container { | |||||
pointer-events: none; | |||||
transition-duration: 0.3s; | |||||
transform: translate3d(-$sideBarWidth, 0, 0); | |||||
} | |||||
} | |||||
} | |||||
.withoutAnimation { | |||||
.main-container, | |||||
.sidebar-container { | |||||
transition: none; | |||||
} | |||||
} | |||||
} | |||||
// when menu collapsed | |||||
.el-menu--vertical { | |||||
&>.el-menu { | |||||
.svg-icon { | |||||
margin-right: 16px; | |||||
} | |||||
.sub-el-icon, .icon { | |||||
margin-right: 12px; | |||||
margin-left: -2px; | |||||
} | |||||
} | |||||
.nest-menu .el-submenu>.el-submenu__title, | |||||
.el-menu-item { | |||||
&:hover { | |||||
// you can use $subMenuHover | |||||
background-color: $menuHover !important; | |||||
} | |||||
} | |||||
// the scroll bar appears when the subMenu is too long | |||||
>.el-menu--popup { | |||||
max-height: 100vh; | |||||
overflow-y: auto; | |||||
&::-webkit-scrollbar-track-piece { | |||||
background: #d3dce6; | |||||
} | |||||
&::-webkit-scrollbar { | |||||
width: 6px; | |||||
} | |||||
&::-webkit-scrollbar-thumb { | |||||
background: #99a9bf; | |||||
border-radius: 20px; | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,48 @@ | |||||
// global transition css | |||||
/* fade */ | |||||
.fade-enter-active, | |||||
.fade-leave-active { | |||||
transition: opacity 0.28s; | |||||
} | |||||
.fade-enter, | |||||
.fade-leave-active { | |||||
opacity: 0; | |||||
} | |||||
/* fade-transform */ | |||||
.fade-transform-leave-active, | |||||
.fade-transform-enter-active { | |||||
transition: all .5s; | |||||
} | |||||
.fade-transform-enter { | |||||
opacity: 0; | |||||
transform: translateX(-30px); | |||||
} | |||||
.fade-transform-leave-to { | |||||
opacity: 0; | |||||
transform: translateX(30px); | |||||
} | |||||
/* breadcrumb transition */ | |||||
.breadcrumb-enter-active, | |||||
.breadcrumb-leave-active { | |||||
transition: all .5s; | |||||
} | |||||
.breadcrumb-enter, | |||||
.breadcrumb-leave-active { | |||||
opacity: 0; | |||||
transform: translateX(20px); | |||||
} | |||||
.breadcrumb-move { | |||||
transition: all .5s; | |||||
} | |||||
.breadcrumb-leave-active { | |||||
position: absolute; | |||||
} |
@@ -0,0 +1,25 @@ | |||||
// sidebar | |||||
$menuText:#bfcbd9; | |||||
$menuActiveText:#409EFF; | |||||
$subMenuActiveText:#f4f4f5; //https://github.com/ElemeFE/element/issues/12951 | |||||
$menuBg:#304156; | |||||
$menuHover:#263445; | |||||
$subMenuBg:#1f2d3d; | |||||
$subMenuHover:#001528; | |||||
$sideBarWidth: 210px; | |||||
// the :export directive is the magic sauce for webpack | |||||
// https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass | |||||
:export { | |||||
menuText: $menuText; | |||||
menuActiveText: $menuActiveText; | |||||
subMenuActiveText: $subMenuActiveText; | |||||
menuBg: $menuBg; | |||||
menuHover: $menuHover; | |||||
subMenuBg: $subMenuBg; | |||||
subMenuHover: $subMenuHover; | |||||
sideBarWidth: $sideBarWidth; | |||||
} |
@@ -0,0 +1,78 @@ | |||||
<template> | |||||
<el-breadcrumb class="app-breadcrumb" separator="/"> | |||||
<transition-group name="breadcrumb"> | |||||
<el-breadcrumb-item v-for="(item,index) in levelList" :key="item.path"> | |||||
<span v-if="item.redirect==='noRedirect'||index==levelList.length-1" class="no-redirect">{{ item.meta.title }}</span> | |||||
<a v-else @click.prevent="handleLink(item)">{{ item.meta.title }}</a> | |||||
</el-breadcrumb-item> | |||||
</transition-group> | |||||
</el-breadcrumb> | |||||
</template> | |||||
<script> | |||||
import pathToRegexp from 'path-to-regexp' | |||||
export default { | |||||
data() { | |||||
return { | |||||
levelList: null | |||||
} | |||||
}, | |||||
watch: { | |||||
$route() { | |||||
this.getBreadcrumb() | |||||
} | |||||
}, | |||||
created() { | |||||
this.getBreadcrumb() | |||||
}, | |||||
methods: { | |||||
getBreadcrumb() { | |||||
// only show routes with meta.title | |||||
const matched = this.$route.matched.filter(item => item.meta && item.meta.title) | |||||
/* const first = matched[0] | |||||
if (!this.isDashboard(first)) { | |||||
matched = [{ path: '/home', meta: { title: '后台管理' }}].concat(matched) | |||||
} */ | |||||
this.levelList = matched.filter(item => item.meta && item.meta.title && item.meta.breadcrumb !== false) | |||||
}, | |||||
isDashboard(route) { | |||||
const name = route && route.name | |||||
if (!name) { | |||||
return false | |||||
} | |||||
return name.trim().toLocaleLowerCase() === 'Home'.toLocaleLowerCase() | |||||
}, | |||||
pathCompile(path) { | |||||
// To solve this problem https://github.com/PanJiaChen/vue-element-admin/issues/561 | |||||
const { params } = this.$route | |||||
var toPath = pathToRegexp.compile(path) | |||||
return toPath(params) | |||||
}, | |||||
handleLink(item) { | |||||
const { redirect, path } = item | |||||
if (redirect) { | |||||
this.$router.push(redirect) | |||||
return | |||||
} | |||||
this.$router.push(this.pathCompile(path)) | |||||
} | |||||
} | |||||
} | |||||
</script> | |||||
<style lang="scss" scoped> | |||||
.app-breadcrumb.el-breadcrumb { | |||||
display: inline-block; | |||||
font-size: 14px; | |||||
line-height: 50px; | |||||
margin-left: 8px; | |||||
.no-redirect { | |||||
color: #97a8be; | |||||
cursor: text; | |||||
} | |||||
} | |||||
</style> |
@@ -0,0 +1,268 @@ | |||||
<template> | |||||
<div class="crud-opts"> | |||||
<span class="crud-opts-left"> | |||||
<!--左侧插槽--> | |||||
<slot name="left" /> | |||||
<el-button | |||||
v-if="crud.optShow.add" | |||||
v-permission="permission.add" | |||||
class="filter-item" | |||||
size="mini" | |||||
type="primary" | |||||
icon="el-icon-plus" | |||||
@click="crud.toAdd" | |||||
> | |||||
新增 | |||||
</el-button> | |||||
<el-button | |||||
v-if="crud.optShow.edit" | |||||
v-permission="permission.edit" | |||||
class="filter-item" | |||||
size="mini" | |||||
type="success" | |||||
icon="el-icon-edit" | |||||
:disabled="crud.selections.length !== 1" | |||||
@click="crud.toEdit(crud.selections[0])" | |||||
> | |||||
修改 | |||||
</el-button> | |||||
<el-button | |||||
v-if="crud.optShow.del" | |||||
slot="reference" | |||||
v-permission="permission.del" | |||||
class="filter-item" | |||||
type="danger" | |||||
icon="el-icon-delete" | |||||
size="mini" | |||||
:loading="crud.delAllLoading" | |||||
:disabled="crud.selections.length === 0" | |||||
@click="toDelete(crud.selections)" | |||||
> | |||||
删除 | |||||
</el-button> | |||||
<el-button | |||||
v-if="crud.optShow.download" | |||||
:loading="crud.downloadLoading" | |||||
:disabled="!crud.data.length" | |||||
class="filter-item" | |||||
size="mini" | |||||
type="warning" | |||||
icon="el-icon-download" | |||||
@click="crud.doExport" | |||||
>导出</el-button> | |||||
<!--右侧--> | |||||
<slot name="right" /> | |||||
</span> | |||||
<el-button-group class="crud-opts-right"> | |||||
<el-button | |||||
size="mini" | |||||
plain | |||||
type="info" | |||||
icon="el-icon-search" | |||||
@click="toggleSearch()" | |||||
/> | |||||
<el-button | |||||
size="mini" | |||||
icon="el-icon-refresh" | |||||
@click="crud.refresh()" | |||||
/> | |||||
<el-popover | |||||
placement="bottom-end" | |||||
width="150" | |||||
trigger="click" | |||||
> | |||||
<el-button | |||||
slot="reference" | |||||
size="mini" | |||||
icon="el-icon-s-grid" | |||||
> | |||||
<i | |||||
class="fa fa-caret-down" | |||||
aria-hidden="true" | |||||
/> | |||||
</el-button> | |||||
<el-checkbox | |||||
v-model="allColumnsSelected" | |||||
:indeterminate="allColumnsSelectedIndeterminate" | |||||
@change="handleCheckAllChange" | |||||
> | |||||
全选 | |||||
</el-checkbox> | |||||
<el-checkbox | |||||
v-for="item in tableColumns" | |||||
:key="item.property" | |||||
v-model="item.visible" | |||||
@change="handleCheckedTableColumnsChange(item)" | |||||
> | |||||
{{ item.label }} | |||||
</el-checkbox> | |||||
</el-popover> | |||||
</el-button-group> | |||||
</div> | |||||
</template> | |||||
<script> | |||||
import CRUD, { crud } from '@crud/crud' | |||||
function sortWithRef(src, ref) { | |||||
const result = Object.assign([], ref) | |||||
let cursor = -1 | |||||
src.forEach(e => { | |||||
const idx = result.indexOf(e) | |||||
if (idx === -1) { | |||||
cursor += 1 | |||||
result.splice(cursor, 0, e) | |||||
} else { | |||||
cursor = idx | |||||
} | |||||
}) | |||||
return result | |||||
} | |||||
export default { | |||||
mixins: [crud()], | |||||
props: { | |||||
permission: { | |||||
type: Object, | |||||
default: () => { return {} } | |||||
}, | |||||
hiddenColumns: { | |||||
type: Array, | |||||
default: () => { return [] } | |||||
}, | |||||
ignoreColumns: { | |||||
type: Array, | |||||
default: () => { return [] } | |||||
} | |||||
}, | |||||
data() { | |||||
return { | |||||
tableColumns: [], | |||||
allColumnsSelected: true, | |||||
allColumnsSelectedIndeterminate: false, | |||||
tableUnwatcher: null, | |||||
// 忽略下次表格列变动 | |||||
ignoreNextTableColumnsChange: false | |||||
} | |||||
}, | |||||
watch: { | |||||
'crud.props.table'() { | |||||
this.updateTableColumns() | |||||
this.tableColumns.forEach(column => { | |||||
if (this.hiddenColumns.indexOf(column.property) !== -1) { | |||||
column.visible = false | |||||
this.updateColumnVisible(column) | |||||
} | |||||
}) | |||||
}, | |||||
'crud.props.table.store.states.columns'() { | |||||
this.updateTableColumns() | |||||
} | |||||
}, | |||||
created() { | |||||
this.crud.updateProp('searchToggle', true) | |||||
}, | |||||
methods: { | |||||
updateTableColumns() { | |||||
const table = this.crud.getTable() | |||||
if (!table) { | |||||
this.tableColumns = [] | |||||
return | |||||
} | |||||
let cols = null | |||||
const columnFilter = e => e && e.type === 'default' && e.property && this.ignoreColumns.indexOf(e.property) === -1 | |||||
const refCols = table.columns.filter(columnFilter) | |||||
if (this.ignoreNextTableColumnsChange) { | |||||
this.ignoreNextTableColumnsChange = false | |||||
return | |||||
} | |||||
this.ignoreNextTableColumnsChange = false | |||||
const columns = [] | |||||
const fullTableColumns = table.$children.map(e => e.columnConfig).filter(columnFilter) | |||||
cols = sortWithRef(fullTableColumns, refCols) | |||||
cols.forEach(config => { | |||||
const column = { | |||||
property: config.property, | |||||
label: config.label, | |||||
visible: refCols.indexOf(config) !== -1 | |||||
} | |||||
columns.push(column) | |||||
}) | |||||
this.tableColumns = columns | |||||
}, | |||||
toDelete(datas) { | |||||
this.$confirm(`确认删除选中的${datas.length}条数据?`, '提示', { | |||||
confirmButtonText: '确定', | |||||
cancelButtonText: '取消', | |||||
type: 'warning' | |||||
}).then(() => { | |||||
this.crud.delAllLoading = true | |||||
this.crud.doDelete(datas) | |||||
}).catch(() => { | |||||
}) | |||||
}, | |||||
handleCheckAllChange(val) { | |||||
if (val === false) { | |||||
this.allColumnsSelected = true | |||||
return | |||||
} | |||||
this.tableColumns.forEach(column => { | |||||
if (!column.visible) { | |||||
column.visible = true | |||||
this.updateColumnVisible(column) | |||||
} | |||||
}) | |||||
this.allColumnsSelected = val | |||||
this.allColumnsSelectedIndeterminate = false | |||||
}, | |||||
handleCheckedTableColumnsChange(item) { | |||||
let totalCount = 0 | |||||
let selectedCount = 0 | |||||
this.tableColumns.forEach(column => { | |||||
++totalCount | |||||
selectedCount += column.visible ? 1 : 0 | |||||
}) | |||||
if (selectedCount === 0) { | |||||
this.crud.notify('请至少选择一列', CRUD.NOTIFICATION_TYPE.WARNING) | |||||
this.$nextTick(function() { | |||||
item.visible = true | |||||
}) | |||||
return | |||||
} | |||||
this.allColumnsSelected = selectedCount === totalCount | |||||
this.allColumnsSelectedIndeterminate = selectedCount !== totalCount && selectedCount !== 0 | |||||
this.updateColumnVisible(item) | |||||
}, | |||||
updateColumnVisible(item) { | |||||
const table = this.crud.props.table | |||||
const vm = table.$children.find(e => e.prop === item.property) | |||||
const columnConfig = vm.columnConfig | |||||
if (item.visible) { | |||||
// 找出合适的插入点 | |||||
const columnIndex = this.tableColumns.indexOf(item) | |||||
vm.owner.store.commit('insertColumn', columnConfig, columnIndex + 1, null) | |||||
} else { | |||||
vm.owner.store.commit('removeColumn', columnConfig, null) | |||||
} | |||||
this.ignoreNextTableColumnsChange = true | |||||
}, | |||||
toggleSearch() { | |||||
this.crud.props.searchToggle = !this.crud.props.searchToggle | |||||
} | |||||
} | |||||
} | |||||
</script> | |||||
<style> | |||||
.crud-opts { | |||||
padding: 4px 0; | |||||
display: -webkit-flex; | |||||
display: flex; | |||||
align-items: center; | |||||
} | |||||
.crud-opts .crud-opts-right { | |||||
margin-left: auto; | |||||
} | |||||
.crud-opts .crud-opts-right span { | |||||
float: left; | |||||
} | |||||
</style> |
@@ -0,0 +1,18 @@ | |||||
<!--分页--> | |||||
<template> | |||||
<el-pagination | |||||
:page-size.sync="page.size" | |||||
:total="page.total" | |||||
:current-page.sync="page.page" | |||||
style="margin-top: 8px;" | |||||
layout="total, prev, pager, next, sizes" | |||||
@size-change="crud.sizeChangeHandler($event)" | |||||
@current-change="crud.pageChangeHandler" | |||||
/> | |||||
</template> | |||||
<script> | |||||
import { pagination } from '@crud/crud' | |||||
export default { | |||||
mixins: [pagination()] | |||||
} | |||||
</script> |
@@ -0,0 +1,20 @@ | |||||
<!--搜索与重置--> | |||||
<template> | |||||
<span> | |||||
<el-button class="filter-item" size="mini" type="success" icon="el-icon-search" @click="crud.toQuery">搜索</el-button> | |||||
<el-button v-if="crud.optShow.reset" class="filter-item" size="mini" type="warning" icon="el-icon-refresh-left" @click="crud.resetQuery()">重置</el-button> | |||||
</span> | |||||
</template> | |||||
<script> | |||||
import { crud } from '@crud/crud' | |||||
export default { | |||||
mixins: [crud()], | |||||
props: { | |||||
itemClass: { | |||||
type: String, | |||||
required: false, | |||||
default: '' | |||||
} | |||||
} | |||||
} | |||||
</script> |
@@ -0,0 +1,71 @@ | |||||
<template> | |||||
<div> | |||||
<el-button v-permission="permission.edit" :loading="crud.status.cu === 2" :disabled="disabledEdit" size="mini" type="primary" icon="el-icon-edit" @click="crud.toEdit(data)" /> | |||||
<el-popover v-model="pop" v-permission="permission.del" placement="top" width="180" trigger="manual" @show="onPopoverShow" @hide="onPopoverHide"> | |||||
<p>{{ msg }}</p> | |||||
<div style="text-align: right; margin: 0"> | |||||
<el-button size="mini" type="text" @click="doCancel">取消</el-button> | |||||
<el-button :loading="crud.dataStatus[crud.getDataId(data)].delete === 2" type="primary" size="mini" @click="crud.doDelete(data)">确定</el-button> | |||||
</div> | |||||
<el-button slot="reference" :disabled="disabledDle" type="danger" icon="el-icon-delete" size="mini" @click="toDelete" /> | |||||
</el-popover> | |||||
</div> | |||||
</template> | |||||
<script> | |||||
import CRUD, { crud } from '@crud/crud' | |||||
export default { | |||||
mixins: [crud()], | |||||
props: { | |||||
data: { | |||||
type: Object, | |||||
required: true | |||||
}, | |||||
permission: { | |||||
type: Object, | |||||
required: true | |||||
}, | |||||
disabledEdit: { | |||||
type: Boolean, | |||||
default: false | |||||
}, | |||||
disabledDle: { | |||||
type: Boolean, | |||||
default: false | |||||
}, | |||||
msg: { | |||||
type: String, | |||||
default: '确定删除本条数据吗?' | |||||
} | |||||
}, | |||||
data() { | |||||
return { | |||||
pop: false | |||||
} | |||||
}, | |||||
methods: { | |||||
doCancel() { | |||||
this.pop = false | |||||
this.crud.cancelDelete(this.data) | |||||
}, | |||||
toDelete() { | |||||
this.pop = true | |||||
}, | |||||
[CRUD.HOOK.afterDelete](crud, data) { | |||||
if (data === this.data) { | |||||
this.pop = false | |||||
} | |||||
}, | |||||
onPopoverShow() { | |||||
setTimeout(() => { | |||||
document.addEventListener('click', this.handleDocumentClick) | |||||
}, 0) | |||||
}, | |||||
onPopoverHide() { | |||||
document.removeEventListener('click', this.handleDocumentClick) | |||||
}, | |||||
handleDocumentClick(event) { | |||||
this.pop = false | |||||
} | |||||
} | |||||
} | |||||
</script> |
@@ -0,0 +1,864 @@ | |||||
import { initData, download } from '@/api/data' | |||||
import { parseTime, downloadFile } from '@/utils/index' | |||||
import Vue from 'vue' | |||||
/** | |||||
* CRUD配置 | |||||
* @author moxun | |||||
* @param {*} options <br> | |||||
* @return crud instance. | |||||
* @example | |||||
* 要使用多crud时,请在关联crud的组件处使用crud-tag进行标记,如:<jobForm :job-status="dict.job_status" crud-tag="job" /> | |||||
*/ | |||||
function CRUD(options) { | |||||
const defaultOptions = { | |||||
tag: 'default', | |||||
// id字段名 | |||||
idField: 'id', | |||||
// 标题 | |||||
title: '', | |||||
// 请求数据的url | |||||
url: '', | |||||
// 表格数据 | |||||
data: [], | |||||
// 选择项 | |||||
selections: [], | |||||
// 待查询的对象 | |||||
query: {}, | |||||
// 查询数据的参数 | |||||
params: {}, | |||||
// Form 表单 | |||||
form: {}, | |||||
// 重置表单 | |||||
defaultForm: () => {}, | |||||
// 排序规则,默认 id 降序, 支持多字段排序 ['id,desc', 'createTime,asc'] | |||||
sort: ['id,desc'], | |||||
// 等待时间 | |||||
time: 50, | |||||
// CRUD Method | |||||
crudMethod: { | |||||
add: (form) => {}, | |||||
del: (id) => {}, | |||||
edit: (form) => {}, | |||||
get: (id) => {} | |||||
}, | |||||
// 主页操作栏显示哪些按钮 | |||||
optShow: { | |||||
add: true, | |||||
edit: true, | |||||
del: true, | |||||
download: true, | |||||
reset: true | |||||
}, | |||||
// 自定义一些扩展属性 | |||||
props: {}, | |||||
// 在主页准备 | |||||
queryOnPresenterCreated: true, | |||||
// 调试开关 | |||||
debug: false | |||||
} | |||||
options = mergeOptions(defaultOptions, options) | |||||
const data = { | |||||
...options, | |||||
// 记录数据状态 | |||||
dataStatus: {}, | |||||
status: { | |||||
add: CRUD.STATUS.NORMAL, | |||||
edit: CRUD.STATUS.NORMAL, | |||||
// 添加或编辑状态 | |||||
get cu() { | |||||
if (this.add === CRUD.STATUS.NORMAL && this.edit === CRUD.STATUS.NORMAL) { | |||||
return CRUD.STATUS.NORMAL | |||||
} else if (this.add === CRUD.STATUS.PREPARED || this.edit === CRUD.STATUS.PREPARED) { | |||||
return CRUD.STATUS.PREPARED | |||||
} else if (this.add === CRUD.STATUS.PROCESSING || this.edit === CRUD.STATUS.PROCESSING) { | |||||
return CRUD.STATUS.PROCESSING | |||||
} | |||||
throw new Error('wrong crud\'s cu status') | |||||
}, | |||||
// 标题 | |||||
get title() { | |||||
return this.add > CRUD.STATUS.NORMAL ? `新增${crud.title}` : this.edit > CRUD.STATUS.NORMAL ? `编辑${crud.title}` : crud.title | |||||
} | |||||
}, | |||||
msg: { | |||||
submit: '提交成功', | |||||
add: '新增成功', | |||||
edit: '编辑成功', | |||||
del: '删除成功' | |||||
}, | |||||
page: { | |||||
// 页码 | |||||
page: 0, | |||||
// 每页数据条数 | |||||
size: 10, | |||||
// 总数据条数 | |||||
total: 0 | |||||
}, | |||||
// 整体loading | |||||
loading: false, | |||||
// 导出的 Loading | |||||
downloadLoading: false, | |||||
// 删除的 Loading | |||||
delAllLoading: false | |||||
} | |||||
const methods = { | |||||
/** | |||||
* 通用的提示 | |||||
*/ | |||||
submitSuccessNotify() { | |||||
crud.notify(crud.msg.submit, CRUD.NOTIFICATION_TYPE.SUCCESS) | |||||
}, | |||||
addSuccessNotify() { | |||||
crud.notify(crud.msg.add, CRUD.NOTIFICATION_TYPE.SUCCESS) | |||||
}, | |||||
editSuccessNotify() { | |||||
crud.notify(crud.msg.edit, CRUD.NOTIFICATION_TYPE.SUCCESS) | |||||
}, | |||||
delSuccessNotify() { | |||||
crud.notify(crud.msg.del, CRUD.NOTIFICATION_TYPE.SUCCESS) | |||||
}, | |||||
// 搜索 | |||||
toQuery() { | |||||
crud.page.page = 1 | |||||
crud.refresh() | |||||
}, | |||||
// 刷新 | |||||
refresh() { | |||||
if (!callVmHook(crud, CRUD.HOOK.beforeRefresh)) { | |||||
return | |||||
} | |||||
return new Promise((resolve, reject) => { | |||||
crud.loading = true | |||||
// 请求数据 | |||||
initData(crud.url, crud.getQueryParams()).then(data => { | |||||
const table = crud.getTable() | |||||
if (table && table.lazy) { // 懒加载子节点数据,清掉已加载的数据 | |||||
table.store.states.treeData = {} | |||||
table.store.states.lazyTreeNodeMap = {} | |||||
} | |||||
crud.page.total = Number(data.total) | |||||
crud.data = data.list | |||||
crud.resetDataStatus() | |||||
// time 毫秒后显示表格 | |||||
setTimeout(() => { | |||||
crud.loading = false | |||||
callVmHook(crud, CRUD.HOOK.afterRefresh) | |||||
}, crud.time) | |||||
resolve(data) | |||||
}).catch(err => { | |||||
crud.loading = false | |||||
reject(err) | |||||
}) | |||||
}) | |||||
}, | |||||
/** | |||||
* 启动添加 | |||||
*/ | |||||
toAdd() { | |||||
crud.resetForm() | |||||
if (!(callVmHook(crud, CRUD.HOOK.beforeToAdd, crud.form) && callVmHook(crud, CRUD.HOOK.beforeToCU, crud.form))) { | |||||
return | |||||
} | |||||
crud.status.add = CRUD.STATUS.PREPARED | |||||
callVmHook(crud, CRUD.HOOK.afterToAdd, crud.form) | |||||
callVmHook(crud, CRUD.HOOK.afterToCU, crud.form) | |||||
}, | |||||
/** | |||||
* 启动编辑 | |||||
* @param {*} data 数据项 | |||||
*/ | |||||
toEdit(data) { | |||||
crud.resetForm(JSON.parse(JSON.stringify(data))) | |||||
if (!(callVmHook(crud, CRUD.HOOK.beforeToEdit, crud.form) && callVmHook(crud, CRUD.HOOK.beforeToCU, crud.form))) { | |||||
return | |||||
} | |||||
crud.status.edit = CRUD.STATUS.PREPARED | |||||
crud.getDataStatus(crud.getDataId(data)).edit = CRUD.STATUS.PREPARED | |||||
callVmHook(crud, CRUD.HOOK.afterToEdit, crud.form) | |||||
callVmHook(crud, CRUD.HOOK.afterToCU, crud.form) | |||||
}, | |||||
/** | |||||
* 启动删除 | |||||
* @param {*} data 数据项 | |||||
*/ | |||||
toDelete(data) { | |||||
crud.getDataStatus(crud.getDataId(data)).delete = CRUD.STATUS.PREPARED | |||||
}, | |||||
/** | |||||
* 取消删除 | |||||
* @param {*} data 数据项 | |||||
*/ | |||||
cancelDelete(data) { | |||||
if (!callVmHook(crud, CRUD.HOOK.beforeDeleteCancel, data)) { | |||||
return | |||||
} | |||||
crud.getDataStatus(crud.getDataId(data)).delete = CRUD.STATUS.NORMAL | |||||
callVmHook(crud, CRUD.HOOK.afterDeleteCancel, data) | |||||
}, | |||||
/** | |||||
* 取消新增/编辑 | |||||
*/ | |||||
cancelCU() { | |||||
const addStatus = crud.status.add | |||||
const editStatus = crud.status.edit | |||||
if (addStatus === CRUD.STATUS.PREPARED) { | |||||
if (!callVmHook(crud, CRUD.HOOK.beforeAddCancel, crud.form)) { | |||||
return | |||||
} | |||||
crud.status.add = CRUD.STATUS.NORMAL | |||||
} | |||||
if (editStatus === CRUD.STATUS.PREPARED) { | |||||
if (!callVmHook(crud, CRUD.HOOK.beforeEditCancel, crud.form)) { | |||||
return | |||||
} | |||||
crud.status.edit = CRUD.STATUS.NORMAL | |||||
crud.getDataStatus(crud.getDataId(crud.form)).edit = CRUD.STATUS.NORMAL | |||||
} | |||||
crud.resetForm() | |||||
if (addStatus === CRUD.STATUS.PREPARED) { | |||||
callVmHook(crud, CRUD.HOOK.afterAddCancel, crud.form) | |||||
} | |||||
if (editStatus === CRUD.STATUS.PREPARED) { | |||||
callVmHook(crud, CRUD.HOOK.afterEditCancel, crud.form) | |||||
} | |||||
// 清除表单验证 | |||||
if (crud.findVM('form').$refs['form']) { | |||||
crud.findVM('form').$refs['form'].clearValidate() | |||||
} | |||||
}, | |||||
/** | |||||
* 提交新增/编辑 | |||||
*/ | |||||
submitCU() { | |||||
if (!callVmHook(crud, CRUD.HOOK.beforeValidateCU)) { | |||||
return | |||||
} | |||||
crud.findVM('form').$refs['form'].validate(valid => { | |||||
if (!valid) { | |||||
return | |||||
} | |||||
if (!callVmHook(crud, CRUD.HOOK.afterValidateCU)) { | |||||
return | |||||
} | |||||
if (crud.status.add === CRUD.STATUS.PREPARED) { | |||||
crud.doAdd() | |||||
} else if (crud.status.edit === CRUD.STATUS.PREPARED) { | |||||
crud.doEdit() | |||||
} | |||||
}) | |||||
}, | |||||
/** | |||||
* 执行添加 | |||||
*/ | |||||
doAdd() { | |||||
if (!callVmHook(crud, CRUD.HOOK.beforeSubmit)) { | |||||
return | |||||
} | |||||
crud.status.add = CRUD.STATUS.PROCESSING | |||||
crud.crudMethod.add(crud.form).then(() => { | |||||
crud.status.add = CRUD.STATUS.NORMAL | |||||
crud.resetForm() | |||||
crud.addSuccessNotify() | |||||
callVmHook(crud, CRUD.HOOK.afterSubmit) | |||||
crud.toQuery() | |||||
}).catch(() => { | |||||
crud.status.add = CRUD.STATUS.PREPARED | |||||
callVmHook(crud, CRUD.HOOK.afterAddError) | |||||
}) | |||||
}, | |||||
/** | |||||
* 执行编辑 | |||||
*/ | |||||
doEdit() { | |||||
if (!callVmHook(crud, CRUD.HOOK.beforeSubmit)) { | |||||
return | |||||
} | |||||
crud.status.edit = CRUD.STATUS.PROCESSING | |||||
crud.crudMethod.edit(crud.form).then(() => { | |||||
crud.status.edit = CRUD.STATUS.NORMAL | |||||
crud.getDataStatus(crud.getDataId(crud.form)).edit = CRUD.STATUS.NORMAL | |||||
crud.editSuccessNotify() | |||||
crud.resetForm() | |||||
callVmHook(crud, CRUD.HOOK.afterSubmit) | |||||
crud.refresh() | |||||
}).catch(() => { | |||||
crud.status.edit = CRUD.STATUS.PREPARED | |||||
callVmHook(crud, CRUD.HOOK.afterEditError) | |||||
}) | |||||
}, | |||||
/** | |||||
* 执行删除 | |||||
* @param {*} data 数据项 | |||||
*/ | |||||
doDelete(data) { | |||||
let delAll = false | |||||
let dataStatus | |||||
const ids = [] | |||||
if (data instanceof Array) { | |||||
delAll = true | |||||
data.forEach(val => { | |||||
ids.push(this.getDataId(val)) | |||||
}) | |||||
} else { | |||||
ids.push(this.getDataId(data)) | |||||
dataStatus = crud.getDataStatus(this.getDataId(data)) | |||||
} | |||||
if (!callVmHook(crud, CRUD.HOOK.beforeDelete, data)) { | |||||
return | |||||
} | |||||
if (!delAll) { | |||||
dataStatus.delete = CRUD.STATUS.PROCESSING | |||||
} | |||||
return crud.crudMethod.del(ids).then(() => { | |||||
if (delAll) { | |||||
crud.delAllLoading = false | |||||
} else dataStatus.delete = CRUD.STATUS.PREPARED | |||||
crud.dleChangePage(1) | |||||
crud.delSuccessNotify() | |||||
callVmHook(crud, CRUD.HOOK.afterDelete, data) | |||||
crud.refresh() | |||||
}).catch(() => { | |||||
if (delAll) { | |||||
crud.delAllLoading = false | |||||
} else dataStatus.delete = CRUD.STATUS.PREPARED | |||||
}) | |||||
}, | |||||
/** | |||||
* 通用导出 | |||||
*/ | |||||
doExport() { | |||||
crud.downloadLoading = true | |||||
download(crud.url + '/download', crud.getQueryParams()).then(result => { | |||||
downloadFile(result, crud.title + '数据', 'xlsx') | |||||
crud.downloadLoading = false | |||||
}).catch(() => { | |||||
crud.downloadLoading = false | |||||
}) | |||||
}, | |||||
/** | |||||
* 获取查询参数 | |||||
*/ | |||||
getQueryParams: function() { | |||||
// 清除参数无值的情况 | |||||
Object.keys(crud.query).length !== 0 && Object.keys(crud.query).forEach(item => { | |||||
if (crud.query[item] === null || crud.query[item] === '') crud.query[item] = undefined | |||||
}) | |||||
Object.keys(crud.params).length !== 0 && Object.keys(crud.params).forEach(item => { | |||||
if (crud.params[item] === null || crud.params[item] === '') crud.params[item] = undefined | |||||
}) | |||||
return { | |||||
pageNum: crud.page.page, | |||||
pageSize: crud.page.size, | |||||
...crud.query, | |||||
...crud.params | |||||
} | |||||
}, | |||||
// 当前页改变 | |||||
pageChangeHandler(e) { | |||||
crud.page.page = e | |||||
crud.refresh() | |||||
}, | |||||
// 每页条数改变 | |||||
sizeChangeHandler(e) { | |||||
crud.page.size = e | |||||
crud.page.page = 1 | |||||
crud.refresh() | |||||
}, | |||||
// 预防删除第二页最后一条数据时,或者多选删除第二页的数据时,页码错误导致请求无数据 | |||||
dleChangePage(size) { | |||||
if (crud.data.length === size && crud.page.page !== 1) { | |||||
crud.page.page -= 1 | |||||
} | |||||
}, | |||||
// 选择改变 | |||||
selectionChangeHandler(val) { | |||||
crud.selections = val | |||||
}, | |||||
/** | |||||
* 重置查询参数 | |||||
* @param {Boolean} toQuery 重置后进行查询操作 | |||||
*/ | |||||
resetQuery(toQuery = true) { | |||||
const defaultQuery = JSON.parse(JSON.stringify(crud.defaultQuery)) | |||||
const query = crud.query | |||||
Object.keys(query).forEach(key => { | |||||
query[key] = defaultQuery[key] | |||||
}) | |||||
// 重置参数 | |||||
this.params = {} | |||||
if (toQuery) { | |||||
crud.toQuery() | |||||
} | |||||
}, | |||||
/** | |||||
* 重置表单 | |||||
* @param {Array} data 数据 | |||||
*/ | |||||
resetForm(data) { | |||||
const form = data || (typeof crud.defaultForm === 'object' ? JSON.parse(JSON.stringify(crud.defaultForm)) : crud.defaultForm.apply(crud.findVM('form'))) | |||||
const crudFrom = crud.form | |||||
for (const key in form) { | |||||
// eslint-disable-next-line no-prototype-builtins | |||||
if (crudFrom.hasOwnProperty(key)) { | |||||
crudFrom[key] = form[key] | |||||
} else { | |||||
Vue.set(crudFrom, key, form[key]) | |||||
} | |||||
} | |||||
// add by ghl 2020-10-04 页面重复添加信息时,下拉框的校验会存在,需要找工取消 | |||||
if (crud.findVM('form').$refs['form']) { | |||||
crud.findVM('form').$refs['form'].clearValidate() | |||||
} | |||||
}, | |||||
/** | |||||
* 重置数据状态 | |||||
*/ | |||||
resetDataStatus() { | |||||
const dataStatus = {} | |||||
function resetStatus(datas) { | |||||
datas.forEach(e => { | |||||
dataStatus[crud.getDataId(e)] = { | |||||
delete: 0, | |||||
edit: 0 | |||||
} | |||||
if (e.children) { | |||||
resetStatus(e.children) | |||||
} | |||||
}) | |||||
} | |||||
resetStatus(crud.data) | |||||
crud.dataStatus = dataStatus | |||||
}, | |||||
/** | |||||
* 获取数据状态 | |||||
* @param {Number | String} id 数据项id | |||||
*/ | |||||
getDataStatus(id) { | |||||
return crud.dataStatus[id] | |||||
}, | |||||
/** | |||||
* 用于树形表格多选, 选中所有 | |||||
* @param selection | |||||
*/ | |||||
selectAllChange(selection) { | |||||
// 如果选中的数目与请求到的数目相同就选中子节点,否则就清空选中 | |||||
if (selection && selection.length === crud.data.length) { | |||||
selection.forEach(val => { | |||||
crud.selectChange(selection, val) | |||||
}) | |||||
} else { | |||||
crud.getTable().clearSelection() | |||||
} | |||||
}, | |||||
/** | |||||
* 用于树形表格多选,单选的封装 | |||||
* @param selection | |||||
* @param row | |||||
*/ | |||||
selectChange(selection, row) { | |||||
// 如果selection中存在row代表是选中,否则是取消选中 | |||||
if (selection.find(val => { return crud.getDataId(val) === crud.getDataId(row) })) { | |||||
if (row.children) { | |||||
row.children.forEach(val => { | |||||
crud.getTable().toggleRowSelection(val, true) | |||||
selection.push(val) | |||||
if (val.children) { | |||||
crud.selectChange(selection, val) | |||||
} | |||||
}) | |||||
} | |||||
} else { | |||||
crud.toggleRowSelection(selection, row) | |||||
} | |||||
}, | |||||
/** | |||||
* 切换选中状态 | |||||
* @param selection | |||||
* @param data | |||||
*/ | |||||
toggleRowSelection(selection, data) { | |||||
if (data.children) { | |||||
data.children.forEach(val => { | |||||
crud.getTable().toggleRowSelection(val, false) | |||||
if (val.children) { | |||||
crud.toggleRowSelection(selection, val) | |||||
} | |||||
}) | |||||
} | |||||
}, | |||||
findVM(type) { | |||||
return crud.vms.find(vm => vm && vm.type === type).vm | |||||
}, | |||||
notify(title, type = CRUD.NOTIFICATION_TYPE.INFO) { | |||||
crud.vms[0].vm.$notify({ | |||||
title, | |||||
type, | |||||
duration: 2500 | |||||
}) | |||||
}, | |||||
updateProp(name, value) { | |||||
Vue.set(crud.props, name, value) | |||||
}, | |||||
getDataId(data) { | |||||
return data[this.idField] | |||||
}, | |||||
getTable() { | |||||
return this.findVM('presenter').$refs.table | |||||
}, | |||||
attchTable() { | |||||
const table = this.getTable() | |||||
this.updateProp('table', table) | |||||
const that = this | |||||
table.$on('expand-change', (row, expanded) => { | |||||
if (!expanded) { | |||||
return | |||||
} | |||||
const lazyTreeNodeMap = table.store.states.lazyTreeNodeMap | |||||
row.children = lazyTreeNodeMap[crud.getDataId(row)] | |||||
if (row.children) { | |||||
row.children.forEach(ele => { | |||||
const id = crud.getDataId(ele) | |||||
if (that.dataStatus[id] === undefined) { | |||||
that.dataStatus[id] = { | |||||
delete: 0, | |||||
edit: 0 | |||||
} | |||||
} | |||||
}) | |||||
} | |||||
}) | |||||
} | |||||
} | |||||
const crud = Object.assign({}, data) | |||||
// 可观测化 | |||||
Vue.observable(crud) | |||||
// 附加方法 | |||||
Object.assign(crud, methods) | |||||
// 记录初始默认的查询参数,后续重置查询时使用 | |||||
Object.assign(crud, { | |||||
defaultQuery: JSON.parse(JSON.stringify(data.query)), | |||||
// 预留4位存储:组件 主页、头部、分页、表单,调试查看也方便找 | |||||
vms: Array(4), | |||||
/** | |||||
* 注册组件实例 | |||||
* @param {String} type 类型 | |||||
* @param {*} vm 组件实例 | |||||
* @param {Number} index 该参数内部使用 | |||||
*/ | |||||
registerVM(type, vm, index = -1) { | |||||
const vmObj = { | |||||
type, | |||||
vm: vm | |||||
} | |||||
if (index < 0) { | |||||
this.vms.push(vmObj) | |||||
return | |||||
} | |||||
if (index < 4) { // 内置预留vm数 | |||||
this.vms[index] = vmObj | |||||
return | |||||
} | |||||
this.vms.length = Math.max(this.vms.length, index) | |||||
this.vms.splice(index, 1, vmObj) | |||||
}, | |||||
/** | |||||
* 取消注册组件实例 | |||||
* @param {*} vm 组件实例 | |||||
*/ | |||||
unregisterVM(type, vm) { | |||||
for (let i = this.vms.length - 1; i >= 0; i--) { | |||||
if (this.vms[i] === undefined) { | |||||
continue | |||||
} | |||||
if (this.vms[i].type === type && this.vms[i].vm === vm) { | |||||
if (i < 4) { // 内置预留vm数 | |||||
this.vms[i] = undefined | |||||
} else { | |||||
this.vms.splice(i, 1) | |||||
} | |||||
break | |||||
} | |||||
} | |||||
} | |||||
}) | |||||
// 冻结处理,需要扩展数据的话,使用crud.updateProp(name, value),以crud.props.name形式访问,这个是响应式的,可以做数据绑定 | |||||
Object.freeze(crud) | |||||
return crud | |||||
} | |||||
// hook VM | |||||
function callVmHook(crud, hook) { | |||||
if (crud.debug) { | |||||
console.log('callVmHook: ' + hook) | |||||
} | |||||
const tagHook = crud.tag ? hook + '$' + crud.tag : null | |||||
let ret = true | |||||
const nargs = [crud] | |||||
for (let i = 2; i < arguments.length; ++i) { | |||||
nargs.push(arguments[i]) | |||||
} | |||||
// 有些组件扮演了多个角色,调用钩子时,需要去重 | |||||
const vmSet = new Set() | |||||
crud.vms.forEach(vm => vm && vmSet.add(vm.vm)) | |||||
vmSet.forEach(vm => { | |||||
if (vm[hook]) { | |||||
ret = vm[hook].apply(vm, nargs) !== false && ret | |||||
} | |||||
if (tagHook && vm[tagHook]) { | |||||
ret = vm[tagHook].apply(vm, nargs) !== false && ret | |||||
} | |||||
}) | |||||
return ret | |||||
} | |||||
function mergeOptions(src, opts) { | |||||
const optsRet = { | |||||
...src | |||||
} | |||||
for (const key in src) { | |||||
// eslint-disable-next-line no-prototype-builtins | |||||
if (opts.hasOwnProperty(key)) { | |||||
optsRet[key] = opts[key] | |||||
} | |||||
} | |||||
return optsRet | |||||
} | |||||
/** | |||||
* 查找crud | |||||
* @param {*} vm | |||||
* @param {string} tag | |||||
*/ | |||||
function lookupCrud(vm, tag) { | |||||
tag = tag || vm.$attrs['crud-tag'] || 'default' | |||||
// function lookupCrud(vm, tag) { | |||||
if (vm.$crud) { | |||||
const ret = vm.$crud[tag] | |||||
if (ret) { | |||||
return ret | |||||
} | |||||
} | |||||
return vm.$parent ? lookupCrud(vm.$parent, tag) : undefined | |||||
} | |||||
/** | |||||
* crud主页 | |||||
*/ | |||||
function presenter(crud) { | |||||
if (crud) { | |||||
console.warn('[CRUD warn]: ' + 'please use $options.cruds() { return CRUD(...) or [CRUD(...), ...] }') | |||||
} | |||||
return { | |||||
data() { | |||||
// 在data中返回crud,是为了将crud与当前实例关联,组件观测crud相关属性变化 | |||||
return { | |||||
crud: this.crud | |||||
} | |||||
}, | |||||
beforeCreate() { | |||||
this.$crud = this.$crud || {} | |||||
let cruds = this.$options.cruds instanceof Function ? this.$options.cruds() : crud | |||||
if (!(cruds instanceof Array)) { | |||||
cruds = [cruds] | |||||
} | |||||
cruds.forEach(ele => { | |||||
if (this.$crud[ele.tag]) { | |||||
console.error('[CRUD error]: ' + 'crud with tag [' + ele.tag + ' is already exist') | |||||
} | |||||
this.$crud[ele.tag] = ele | |||||
ele.registerVM('presenter', this, 0) | |||||
}) | |||||
this.crud = this.$crud['defalut'] || cruds[0] | |||||
}, | |||||
methods: { | |||||
parseTime | |||||
}, | |||||
created() { | |||||
for (const k in this.$crud) { | |||||
if (this.$crud[k].queryOnPresenterCreated) { | |||||
this.$crud[k].toQuery() | |||||
} | |||||
} | |||||
}, | |||||
destroyed() { | |||||
for (const k in this.$crud) { | |||||
this.$crud[k].unregisterVM('presenter', this) | |||||
} | |||||
}, | |||||
mounted() { | |||||
// 如果table未实例化(例如使用了v-if),请稍后在适当时机crud.attchTable刷新table信息 | |||||
if (this.$refs.table !== undefined) { | |||||
this.crud.attchTable() | |||||
} | |||||
} | |||||
} | |||||
} | |||||
/** | |||||
* 头部 | |||||
*/ | |||||
function header() { | |||||
return { | |||||
data() { | |||||
return { | |||||
crud: this.crud, | |||||
query: this.crud.query | |||||
} | |||||
}, | |||||
beforeCreate() { | |||||
this.crud = lookupCrud(this) | |||||
this.crud.registerVM('header', this, 1) | |||||
}, | |||||
destroyed() { | |||||
this.crud.unregisterVM('header', this) | |||||
} | |||||
} | |||||
} | |||||
/** | |||||
* 分页 | |||||
*/ | |||||
function pagination() { | |||||
return { | |||||
data() { | |||||
return { | |||||
crud: this.crud, | |||||
page: this.crud.page | |||||
} | |||||
}, | |||||
beforeCreate() { | |||||
this.crud = lookupCrud(this) | |||||
this.crud.registerVM('pagination', this, 2) | |||||
}, | |||||
destroyed() { | |||||
this.crud.unregisterVM('pagination', this) | |||||
} | |||||
} | |||||
} | |||||
/** | |||||
* 表单 | |||||
*/ | |||||
function form(defaultForm) { | |||||
return { | |||||
data() { | |||||
return { | |||||
crud: this.crud, | |||||
form: this.crud.form | |||||
} | |||||
}, | |||||
beforeCreate() { | |||||
this.crud = lookupCrud(this) | |||||
this.crud.registerVM('form', this, 3) | |||||
}, | |||||
created() { | |||||
this.crud.defaultForm = defaultForm | |||||
this.crud.resetForm() | |||||
}, | |||||
destroyed() { | |||||
this.crud.unregisterVM('form', this) | |||||
} | |||||
} | |||||
} | |||||
/** | |||||
* crud | |||||
*/ | |||||
function crud(options = {}) { | |||||
const defaultOptions = { | |||||
type: undefined | |||||
} | |||||
options = mergeOptions(defaultOptions, options) | |||||
return { | |||||
data() { | |||||
return { | |||||
crud: this.crud | |||||
} | |||||
}, | |||||
beforeCreate() { | |||||
this.crud = lookupCrud(this) | |||||
this.crud.registerVM(options.type, this) | |||||
}, | |||||
destroyed() { | |||||
this.crud.unregisterVM(options.type, this) | |||||
} | |||||
} | |||||
} | |||||
/** | |||||
* CRUD钩子 | |||||
*/ | |||||
CRUD.HOOK = { | |||||
/** 刷新 - 之前 */ | |||||
beforeRefresh: 'beforeCrudRefresh', | |||||
/** 刷新 - 之后 */ | |||||
afterRefresh: 'afterCrudRefresh', | |||||
/** 删除 - 之前 */ | |||||
beforeDelete: 'beforeCrudDelete', | |||||
/** 删除 - 之后 */ | |||||
afterDelete: 'afterCrudDelete', | |||||
/** 删除取消 - 之前 */ | |||||
beforeDeleteCancel: 'beforeCrudDeleteCancel', | |||||
/** 删除取消 - 之后 */ | |||||
afterDeleteCancel: 'afterCrudDeleteCancel', | |||||
/** 新建 - 之前 */ | |||||
beforeToAdd: 'beforeCrudToAdd', | |||||
/** 新建 - 之后 */ | |||||
afterToAdd: 'afterCrudToAdd', | |||||
/** 编辑 - 之前 */ | |||||
beforeToEdit: 'beforeCrudToEdit', | |||||
/** 编辑 - 之后 */ | |||||
afterToEdit: 'afterCrudToEdit', | |||||
/** 开始 "新建/编辑" - 之前 */ | |||||
beforeToCU: 'beforeCrudToCU', | |||||
/** 开始 "新建/编辑" - 之后 */ | |||||
afterToCU: 'afterCrudToCU', | |||||
/** "新建/编辑" 验证 - 之前 */ | |||||
beforeValidateCU: 'beforeCrudValidateCU', | |||||
/** "新建/编辑" 验证 - 之后 */ | |||||
afterValidateCU: 'afterCrudValidateCU', | |||||
/** 添加取消 - 之前 */ | |||||
beforeAddCancel: 'beforeCrudAddCancel', | |||||
/** 添加取消 - 之后 */ | |||||
afterAddCancel: 'afterCrudAddCancel', | |||||
/** 编辑取消 - 之前 */ | |||||
beforeEditCancel: 'beforeCrudEditCancel', | |||||
/** 编辑取消 - 之后 */ | |||||
afterEditCancel: 'afterCrudEditCancel', | |||||
/** 提交 - 之前 */ | |||||
beforeSubmit: 'beforeCrudSubmitCU', | |||||
/** 提交 - 之后 */ | |||||
afterSubmit: 'afterCrudSubmitCU', | |||||
afterAddError: 'afterCrudAddError', | |||||
afterEditError: 'afterCrudEditError' | |||||
} | |||||
/** | |||||
* CRUD状态 | |||||
*/ | |||||
CRUD.STATUS = { | |||||
NORMAL: 0, | |||||
PREPARED: 1, | |||||
PROCESSING: 2 | |||||
} | |||||
/** | |||||
* CRUD通知类型 | |||||
*/ | |||||
CRUD.NOTIFICATION_TYPE = { | |||||
SUCCESS: 'success', | |||||
WARNING: 'warning', | |||||
INFO: 'info', | |||||
ERROR: 'error' | |||||
} | |||||
export default CRUD | |||||
export { | |||||
presenter, | |||||
header, | |||||
form, | |||||
pagination, | |||||
crud | |||||
} |
@@ -0,0 +1,35 @@ | |||||
<template> | |||||
<div style="padding: 0 15px;" @click="toggleClick"> | |||||
<i class="icon icon-hamburger hamburger" :class="{'is-active': !isActive}" /> | |||||
</div> | |||||
</template> | |||||
<script> | |||||
export default { | |||||
name: 'Hamburger', | |||||
props: { | |||||
isActive: { | |||||
type: Boolean, | |||||
default: false | |||||
} | |||||
}, | |||||
methods: { | |||||
toggleClick() { | |||||
this.$emit('toggleClick') | |||||
} | |||||
} | |||||
} | |||||
</script> | |||||
<style scoped> | |||||
.hamburger { | |||||
vertical-align: middle; | |||||
font-size: 20px; | |||||
display:inline-block; | |||||
transform: rotate(180deg); | |||||
} | |||||
.hamburger.is-active { | |||||
transform: rotate(0deg); | |||||
} | |||||
</style> |
@@ -0,0 +1,13 @@ | |||||
import permission from './permission' | |||||
const install = function(Vue) { | |||||
Vue.directive('permission', permission) | |||||
} | |||||
if (window.Vue) { | |||||
window['permission'] = permission | |||||
Vue.use(install); // eslint-disable-line | |||||
} | |||||
permission.install = install | |||||
export default permission |
@@ -0,0 +1,21 @@ | |||||
import store from '@/store' | |||||
export default { | |||||
inserted(el, binding) { | |||||
const { value } = binding | |||||
const roles = store.getters && store.getters.roles | |||||
if (value && value instanceof Array) { | |||||
if (value.length > 0) { | |||||
const permissionRoles = value | |||||
const hasPermission = roles.some(role => { | |||||
return permissionRoles.includes(role) | |||||
}) | |||||
if (!hasPermission) { | |||||
el.parentNode && el.parentNode.removeChild(el) | |||||
} | |||||
} | |||||
} else { | |||||
throw new Error(`使用方式: v-permission="['admin','editor']"`) | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,208 @@ | |||||
<template> | |||||
<div> | |||||
<!-- @beforeInit="addCustomButtom" --> | |||||
<VueUeditorWrap | |||||
class="ueditor" | |||||
:id="setSessionid()" | |||||
@ready="readyCustom" | |||||
v-model="content" | |||||
:config="config" | |||||
></VueUeditorWrap> | |||||
</div> | |||||
</template> | |||||
<script> | |||||
import VueUeditorWrap from 'vue-ueditor-wrap' | |||||
export default { | |||||
components: { | |||||
VueUeditorWrap | |||||
}, | |||||
props: { | |||||
btnShow: { | |||||
type: Boolean, | |||||
default: false | |||||
}, | |||||
comeContent: { | |||||
type: String, | |||||
default: '' | |||||
}, | |||||
zIndex: { | |||||
type: Number, | |||||
default: 999 | |||||
}, | |||||
isDisabled: { | |||||
type: Number, | |||||
default: 0 | |||||
} | |||||
}, | |||||
data() { | |||||
return { | |||||
content: '', | |||||
editor: null, | |||||
config: { | |||||
toolbars: [ | |||||
[ | |||||
'source', // 源代码 | |||||
'undo', // 撤销 | |||||
'redo', // 重做 | |||||
'bold', // 加粗 | |||||
'indent', // 首行缩进 | |||||
// 'snapscreen', //截 图 | |||||
'italic', // 斜体 | |||||
'underline', // 下划线 | |||||
'strikethrough', // 删除线 | |||||
'subscript', // 下标 | |||||
'fontborder', // 字符边框 | |||||
'superscript', // 上标 | |||||
'formatmatch', // 格式刷 | |||||
'blockquote', // 引用 | |||||
// 'pasteplain', // 纯文本粘贴模式 | |||||
'selectall', // 全选 | |||||
// 'preview', // 预览 | |||||
'horizontal', // 分隔线 | |||||
'removeformat', // 清除格式 | |||||
// 'time', // 时间 | |||||
// 'date', // 日期 | |||||
'unlink', // 取消链接 | |||||
/* 'insertrow', // 前插入行 | |||||
'insertcol', // 前插入列 | |||||
'mergeright', // 右合并单元格 | |||||
'mergedown', // 下合并单元格 | |||||
'deleterow', // 删除行 | |||||
'deletecol', // 删除列 | |||||
'splittorows', // 拆分成行 | |||||
'splittocols', // 拆分成列 | |||||
'splittocells', // 完全拆分单元格 | |||||
'deletecaption', // 删除表格标题 | |||||
'inserttitle', // 插入标题 | |||||
'mergecells', // 合并多个单元格 | |||||
'deletetable', // 删除表格 | |||||
'cleardoc', // 清空文档 | |||||
'insertparagraphbeforetable', //" 表格前插入行" | |||||
'insertcode', // 代码语言 */ | |||||
'fontfamily', // 字体 | |||||
'fontsize', // 字号 | |||||
'paragraph', // 段落格式 | |||||
// 'edittable', // 表格属性 | |||||
// 'edittd', // 单元格属性 | |||||
'link', // 超链接 | |||||
// 'emotion', // 表情 | |||||
'spechars', // 特殊字符 | |||||
// 'searchreplace', // 查询替换 | |||||
'justifyleft', // 居左对齐 | |||||
'justifyright', // 居右对齐 | |||||
'justifycenter', // 居中对齐 | |||||
'justifyjustify', // 两端对齐 | |||||
'forecolor', // 字体颜色 | |||||
'backcolor', // 背景色 | |||||
'lineheight' // 行间距 | |||||
/* 'insertorderedlist', // 有序列表 | |||||
'insertunorderedlist', // 无序列表 | |||||
'directionalityltr', // 从左向右输入 | |||||
'directionalityrtl', // 从右向左输入 | |||||
'rowspacingtop', // 段前距 | |||||
'rowspacingbottom', // 段后距 | |||||
'pagebreak', // 分页 | |||||
'imagenone', // 默认 | |||||
'imageleft', // 左浮动 | |||||
'imageright', // 右浮动 | |||||
'imagecenter', // 居中 | |||||
'wordimage', // 图片转存 | |||||
'edittip ', // 编辑提示 | |||||
'customstyle', // 自定义标题 | |||||
'touppercase', // 字母大写 | |||||
'tolowercase', // 字母小写 | |||||
'inserttable' // 插入表格 */ | |||||
] | |||||
], | |||||
zIndex: this.zIndex, | |||||
autoHeightEnabled: false, // 编译器不自动被内容撑高 | |||||
autoFloatEnabled: false, | |||||
initialFrameHeight: 300, // 初始容器高度 | |||||
initialFrameWith: '100%', | |||||
postParams: this.postParams, | |||||
// serverUrl: process.env.baseUrl + '/product/uploadFiles', // 上传文件地址 | |||||
UEDITOR_HOME_URL: process.env.VUE_APP_BASE_API + '/editor/UEditor/' // UEditor资源文件的存放路径 | |||||
} | |||||
} | |||||
}, | |||||
watch: { | |||||
content(val) { | |||||
this.$emit('editorChange', val) | |||||
}, | |||||
comeContent: { | |||||
handler(newVal, oldVal) { | |||||
this.content = newVal | |||||
}, | |||||
immediate: true, | |||||
deep: true | |||||
} | |||||
}, | |||||
mounted() { | |||||
}, | |||||
methods: { | |||||
readyCustom(editor) { | |||||
this.editor = editor | |||||
console.log(this.isDisabled) | |||||
if (this.isDisabled) this.editor.options.readonly = true | |||||
}, | |||||
addCustomButtom(editorId) { | |||||
this.editorId = editorId | |||||
this.divID = 'key' + editorId | |||||
var _ = this | |||||
window.UE.registerUI( | |||||
'test-button', | |||||
function(editor, uiName) { | |||||
// 注册按钮执行时的 command 命令,使用命令默认就会带有回退操作 | |||||
editor.registerCommand(uiName, { | |||||
execCommand: function() { | |||||
_.editorHandler = editor | |||||
} | |||||
}) | |||||
// 创建一个 button | |||||
var btn = new window.UE.ui.Button({ | |||||
// 按钮的名字 | |||||
name: uiName, | |||||
// 提示 | |||||
title: '上传图片', | |||||
// 需要添加的额外样式,可指定 icon 图标,图标路径参考常见问题 2 | |||||
// cssRules: `background-image: url("${UploadImg}") !important;background-size: cover;`, | |||||
// 点击时执行的命令 | |||||
onclick: function(res) { | |||||
// 点击新图标时,触发到点击input按钮的事件 | |||||
editor.execCommand(uiName) | |||||
document.getElementById('key' + editorId).click() | |||||
} | |||||
}) | |||||
// 当点到编辑内容上时,按钮要做的状态反射 | |||||
editor.addListener('selectionchange', function() { }) | |||||
// 因为你是添加 button,所以需要返回这个 button | |||||
return btn | |||||
} | |||||
// 0 /* 指定添加到工具栏上的哪个位置,默认时追加到最后 */ | |||||
// editorId /* 指定这个 UI 是哪个编辑器实例上的,默认是页面上所有的编辑器都会添加这个按钮 */ | |||||
) | |||||
}, | |||||
setSessionid(num) { | |||||
var len = num || 32 | |||||
var chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678' | |||||
var maxPos = chars.length | |||||
var pwd = '' | |||||
for (var i = 0; i < len; i++) { | |||||
pwd += chars.charAt(Math.floor(Math.random() * maxPos)) | |||||
} | |||||
return pwd | |||||
} | |||||
} | |||||
} | |||||
</script> | |||||
<style lang="scss" scoped> | |||||
::v-deep.ueditor{ | |||||
.edui-editor{ | |||||
width: 100% !important; | |||||
} | |||||
} | |||||
</style> |
@@ -0,0 +1,96 @@ | |||||
<!-- | |||||
* @Autor: Chuting | |||||
* @Date: 2022-11-02 10:03:47 | |||||
* @LastEditors: Chuting | |||||
* @LastEditTime: 2022-11-02 13:56:17 | |||||
* @Description: 图片上传 | |||||
--> | |||||
<template> | |||||
<el-upload | |||||
class="avatar-uploader" | |||||
action="#" | |||||
:show-file-list="false" | |||||
:before-upload="beforeAvatarUpload" | |||||
:http-request="uploadHttpRequest"> | |||||
<template v-if="url"> | |||||
<video v-if="type === 'video'" :src="url" class="avatar" controls="controls"> | |||||
您的浏览器不支持视频播放 | |||||
</video> | |||||
<img v-else :src="url" class="avatar"> | |||||
</template> | |||||
<i v-else class="el-icon-plus avatar-uploader-icon"></i> | |||||
</el-upload> | |||||
</template> | |||||
<script> | |||||
import { upload } from '@/api/common.js' | |||||
export default { | |||||
props: { | |||||
url: { | |||||
type: String, | |||||
default: '' | |||||
}, | |||||
type: { | |||||
type: String, | |||||
default: '' | |||||
} | |||||
}, | |||||
methods: { | |||||
beforeAvatarUpload(file) { | |||||
const isType = this.type === 'video' ? (file.type === ('video/mp4' || 'video/avi' || 'video/wmv' || 'video/mov')) : (file.type === 'image/jpeg' || file.type === 'image/png' || file.type === 'image/jpg' || file.type === 'image/gif') | |||||
const isLt2M = this.type === 'video' ? file.size / 1024 / 1024 < 50 : file.size / 1024 < 500 | |||||
if (!isType) { | |||||
let txt = this.type === 'video' ? '请上传正确的视频格式!' : '上传头像图片只能是 JPG/JPEG/PNG/GIF 格式!' | |||||
this.$message.error(txt) | |||||
return false | |||||
} | |||||
if (!isLt2M) { | |||||
let txt = this.type === 'video' ? '上传视频大小不能超过 50MB!!' : '上传图片大小不能超过 500KB!' | |||||
this.$message.error(txt) | |||||
return false | |||||
} | |||||
}, | |||||
uploadHttpRequest(file) { | |||||
console.log('pid:', this.$route.params.pid) | |||||
let obj = { | |||||
'page': 'OPERATE', | |||||
'template': 'FORMWORK' | |||||
} | |||||
let _data = { | |||||
file: file.file | |||||
} | |||||
// OPERATE FORMWORK | |||||
upload(_data).then(res => { | |||||
if (res) { | |||||
this.$emit('update:url', res.url) | |||||
} | |||||
}) | |||||
} | |||||
} | |||||
} | |||||
</script> | |||||
<style lang="scss" scoped> | |||||
::v-deep.avatar-uploader .el-upload { | |||||
border: 1px dashed #d9d9d9; | |||||
border-radius: 6px; | |||||
cursor: pointer; | |||||
position: relative; | |||||
overflow: hidden; | |||||
} | |||||
::v-deep.avatar-uploader .el-upload:hover { | |||||
border-color: #409EFF; | |||||
} | |||||
::v-deep.avatar-uploader-icon { | |||||
font-size: 28px; | |||||
color: #8c939d; | |||||
width: 150px; | |||||
height: 150px; | |||||
line-height: 150px; | |||||
text-align: center; | |||||
} | |||||
::v-deep.avatar { | |||||
width: 150px; | |||||
height: 150px; | |||||
display: block; | |||||
} | |||||
</style> |
@@ -0,0 +1,40 @@ | |||||
<template> | |||||
<section class="app-main"> | |||||
<transition name="fade-transform" mode="out-in"> | |||||
<router-view :key="key" /> | |||||
</transition> | |||||
</section> | |||||
</template> | |||||
<script> | |||||
export default { | |||||
name: 'AppMain', | |||||
computed: { | |||||
key() { | |||||
return this.$route.path | |||||
} | |||||
} | |||||
} | |||||
</script> | |||||
<style scoped> | |||||
.app-main { | |||||
/*50 = navbar */ | |||||
min-height: calc(100vh - 50px); | |||||
width: 100%; | |||||
position: relative; | |||||
overflow: auto; | |||||
} | |||||
.fixed-header+.app-main { | |||||
padding-top: 50px; | |||||
} | |||||
</style> | |||||
<style lang="scss"> | |||||
// fix css style bug in open el-dialog | |||||
.el-popup-parent--hidden { | |||||
.fixed-header { | |||||
padding-right: 15px; | |||||
} | |||||
} | |||||
</style> |
@@ -0,0 +1,139 @@ | |||||
<template> | |||||
<div class="navbar"> | |||||
<hamburger :is-active="sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" /> | |||||
<breadcrumb class="breadcrumb-container" /> | |||||
<div class="right-menu"> | |||||
<el-dropdown class="avatar-container" trigger="click"> | |||||
<div class="avatar-wrapper"> | |||||
<img :src="avatar+'?imageView2/1/w/80/h/80'" class="user-avatar"> | |||||
<i class="el-icon-caret-bottom" /> | |||||
</div> | |||||
<el-dropdown-menu slot="dropdown" class="user-dropdown"> | |||||
<router-link to="/"> | |||||
<el-dropdown-item> | |||||
Home | |||||
</el-dropdown-item> | |||||
</router-link> | |||||
<!-- <a target="_blank" href="https://github.com/PanJiaChen/vue-admin-template/"> | |||||
<el-dropdown-item>Github</el-dropdown-item> | |||||
</a> | |||||
<a target="_blank" href="https://panjiachen.github.io/vue-element-admin-site/#/"> | |||||
<el-dropdown-item>Docs</el-dropdown-item> | |||||
</a> --> | |||||
<el-dropdown-item divided @click.native="logout"> | |||||
<span style="display:block;">Log Out</span> | |||||
</el-dropdown-item> | |||||
</el-dropdown-menu> | |||||
</el-dropdown> | |||||
</div> | |||||
</div> | |||||
</template> | |||||
<script> | |||||
import { mapGetters } from 'vuex' | |||||
import Breadcrumb from '@/components/Breadcrumb' | |||||
import Hamburger from '@/components/Hamburger' | |||||
export default { | |||||
components: { | |||||
Breadcrumb, | |||||
Hamburger | |||||
}, | |||||
computed: { | |||||
...mapGetters([ | |||||
'sidebar', | |||||
'avatar' | |||||
]) | |||||
}, | |||||
methods: { | |||||
toggleSideBar() { | |||||
this.$store.dispatch('app/toggleSideBar') | |||||
}, | |||||
async logout() { | |||||
// await this.$store.dispatch('user/logout') | |||||
this.$router.push(`/login?redirect=${this.$route.fullPath}`) | |||||
} | |||||
} | |||||
} | |||||
</script> | |||||
<style lang="scss" scoped> | |||||
.navbar { | |||||
height: 50px; | |||||
overflow: hidden; | |||||
position: relative; | |||||
background: #fff; | |||||
box-shadow: 0 1px 4px rgba(0,21,41,.08); | |||||
.hamburger-container { | |||||
line-height: 46px; | |||||
height: 100%; | |||||
float: left; | |||||
cursor: pointer; | |||||
transition: background .3s; | |||||
-webkit-tap-highlight-color:transparent; | |||||
&:hover { | |||||
background: rgba(0, 0, 0, .025) | |||||
} | |||||
} | |||||
.breadcrumb-container { | |||||
float: left; | |||||
} | |||||
.right-menu { | |||||
float: right; | |||||
height: 100%; | |||||
line-height: 50px; | |||||
&:focus { | |||||
outline: none; | |||||
} | |||||
.right-menu-item { | |||||
display: inline-block; | |||||
padding: 0 8px; | |||||
height: 100%; | |||||
font-size: 18px; | |||||
color: #5a5e66; | |||||
vertical-align: text-bottom; | |||||
&.hover-effect { | |||||
cursor: pointer; | |||||
transition: background .3s; | |||||
&:hover { | |||||
background: rgba(0, 0, 0, .025) | |||||
} | |||||
} | |||||
} | |||||
.avatar-container { | |||||
margin-right: 30px; | |||||
.avatar-wrapper { | |||||
margin-top: 5px; | |||||
position: relative; | |||||
.user-avatar { | |||||
cursor: pointer; | |||||
width: 40px; | |||||
height: 40px; | |||||
border-radius: 10px; | |||||
} | |||||
.el-icon-caret-bottom { | |||||
cursor: pointer; | |||||
position: absolute; | |||||
right: -20px; | |||||
top: 25px; | |||||
font-size: 12px; | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
</style> |
@@ -0,0 +1,26 @@ | |||||
export default { | |||||
computed: { | |||||
device() { | |||||
return this.$store.state.app.device | |||||
} | |||||
}, | |||||
mounted() { | |||||
// In order to fix the click on menu on the ios device will trigger the mouseleave bug | |||||
// https://github.com/PanJiaChen/vue-element-admin/issues/1135 | |||||
this.fixBugIniOS() | |||||
}, | |||||
methods: { | |||||
fixBugIniOS() { | |||||
const $subMenu = this.$refs.subMenu | |||||
if ($subMenu) { | |||||
const handleMouseleave = $subMenu.handleMouseleave | |||||
$subMenu.handleMouseleave = (e) => { | |||||
if (this.device === 'mobile') { | |||||
return | |||||
} | |||||
handleMouseleave(e) | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,41 @@ | |||||
<script> | |||||
export default { | |||||
name: 'MenuItem', | |||||
functional: true, | |||||
props: { | |||||
icon: { | |||||
type: String, | |||||
default: '' | |||||
}, | |||||
title: { | |||||
type: String, | |||||
default: '' | |||||
} | |||||
}, | |||||
render(h, context) { | |||||
const { icon, title } = context.props | |||||
const vnodes = [] | |||||
if (icon) { | |||||
if (icon.includes('el-icon')) { | |||||
vnodes.push(<i class={[icon, 'sub-el-icon']} />) | |||||
} else { | |||||
vnodes.push(<i class={'icon icon-' + icon} />) | |||||
} | |||||
} | |||||
if (title) { | |||||
vnodes.push(<span slot='title'>{(title)}</span>) | |||||
} | |||||
return vnodes | |||||
} | |||||
} | |||||
</script> | |||||
<style scoped> | |||||
.sub-el-icon { | |||||
color: currentColor; | |||||
width: 1em; | |||||
height: 1em; | |||||
} | |||||
</style> |
@@ -0,0 +1,43 @@ | |||||
<template> | |||||
<component :is="type" v-bind="linkProps(to)"> | |||||
<slot /> | |||||
</component> | |||||
</template> | |||||
<script> | |||||
import { isExternal } from '@/utils/validate' | |||||
export default { | |||||
props: { | |||||
to: { | |||||
type: String, | |||||
required: true | |||||
} | |||||
}, | |||||
computed: { | |||||
isExternal() { | |||||
return isExternal(this.to) | |||||
}, | |||||
type() { | |||||
if (this.isExternal) { | |||||
return 'a' | |||||
} | |||||
return 'router-link' | |||||
} | |||||
}, | |||||
methods: { | |||||
linkProps(to) { | |||||
if (this.isExternal) { | |||||
return { | |||||
href: to, | |||||
target: '_blank', | |||||
rel: 'noopener' | |||||
} | |||||
} | |||||
return { | |||||
to: to | |||||
} | |||||
} | |||||
} | |||||
} | |||||
</script> |
@@ -0,0 +1,82 @@ | |||||
<template> | |||||
<div class="sidebar-logo-container" :class="{'collapse':collapse}"> | |||||
<transition name="sidebarLogoFade"> | |||||
<router-link v-if="collapse" key="collapse" class="sidebar-logo-link" to="/"> | |||||
<img v-if="logo" :src="logo" class="sidebar-logo"> | |||||
<h1 v-else class="sidebar-title">{{ title }} </h1> | |||||
</router-link> | |||||
<router-link v-else key="expand" class="sidebar-logo-link" to="/"> | |||||
<img v-if="logo" :src="logo" class="sidebar-logo"> | |||||
<h1 class="sidebar-title">{{ title }} </h1> | |||||
</router-link> | |||||
</transition> | |||||
</div> | |||||
</template> | |||||
<script> | |||||
export default { | |||||
name: 'SidebarLogo', | |||||
props: { | |||||
collapse: { | |||||
type: Boolean, | |||||
required: true | |||||
} | |||||
}, | |||||
data() { | |||||
return { | |||||
title: '后台管理系统', | |||||
logo: 'https://wpimg.wallstcn.com/69a1c46c-eb1c-4b46-8bd4-e9e686ef5251.png' | |||||
} | |||||
} | |||||
} | |||||
</script> | |||||
<style lang="scss" scoped> | |||||
.sidebarLogoFade-enter-active { | |||||
transition: opacity 1.5s; | |||||
} | |||||
.sidebarLogoFade-enter, | |||||
.sidebarLogoFade-leave-to { | |||||
opacity: 0; | |||||
} | |||||
.sidebar-logo-container { | |||||
position: relative; | |||||
width: 100%; | |||||
height: 50px; | |||||
line-height: 50px; | |||||
background: #2b2f3a; | |||||
text-align: center; | |||||
overflow: hidden; | |||||
& .sidebar-logo-link { | |||||
height: 100%; | |||||
width: 100%; | |||||
& .sidebar-logo { | |||||
width: 32px; | |||||
height: 32px; | |||||
vertical-align: middle; | |||||
margin-right: 12px; | |||||
} | |||||
& .sidebar-title { | |||||
display: inline-block; | |||||
margin: 0; | |||||
color: #fff; | |||||
font-weight: 600; | |||||
line-height: 50px; | |||||
font-size: 14px; | |||||
font-family: Avenir, Helvetica Neue, Arial, Helvetica, sans-serif; | |||||
vertical-align: middle; | |||||
} | |||||
} | |||||
&.collapse { | |||||
.sidebar-logo { | |||||
margin-right: 0px; | |||||
} | |||||
} | |||||
} | |||||
</style> |
@@ -0,0 +1,95 @@ | |||||
<template> | |||||
<div v-if="!item.hidden"> | |||||
<template v-if="hasOneShowingChild(item.children,item) && (!onlyOneChild.children||onlyOneChild.noShowingChildren)&&!item.alwaysShow"> | |||||
<app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path)"> | |||||
<el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{'submenu-title-noDropdown':!isNest}"> | |||||
<item :icon="onlyOneChild.meta.icon||(item.meta&&item.meta.icon)" :title="onlyOneChild.meta.title" /> | |||||
</el-menu-item> | |||||
</app-link> | |||||
</template> | |||||
<el-submenu v-else ref="subMenu" :index="resolvePath(item.path)" popper-append-to-body> | |||||
<template slot="title"> | |||||
<item v-if="item.meta" :icon="item.meta && item.meta.icon" :title="item.meta.title" /> | |||||
</template> | |||||
<sidebar-item | |||||
v-for="child in item.children" | |||||
:key="child.path" | |||||
:is-nest="true" | |||||
:item="child" | |||||
:base-path="resolvePath(child.path)" | |||||
class="nest-menu" | |||||
/> | |||||
</el-submenu> | |||||
</div> | |||||
</template> | |||||
<script> | |||||
import path from 'path' | |||||
import { isExternal } from '@/utils/validate' | |||||
import Item from './Item' | |||||
import AppLink from './Link' | |||||
import FixiOSBug from './FixiOSBug' | |||||
export default { | |||||
name: 'SidebarItem', | |||||
components: { Item, AppLink }, | |||||
mixins: [FixiOSBug], | |||||
props: { | |||||
// route object | |||||
item: { | |||||
type: Object, | |||||
required: true | |||||
}, | |||||
isNest: { | |||||
type: Boolean, | |||||
default: false | |||||
}, | |||||
basePath: { | |||||
type: String, | |||||
default: '' | |||||
} | |||||
}, | |||||
data() { | |||||
// To fix https://github.com/PanJiaChen/vue-admin-template/issues/237 | |||||
// TODO: refactor with render function | |||||
this.onlyOneChild = null | |||||
return {} | |||||
}, | |||||
methods: { | |||||
hasOneShowingChild(children = [], parent) { | |||||
const showingChildren = children.filter(item => { | |||||
if (item.hidden) { | |||||
return false | |||||
} else { | |||||
// Temp set(will be used if only has one showing child) | |||||
this.onlyOneChild = item | |||||
return true | |||||
} | |||||
}) | |||||
// When there is only one child router, the child router is displayed by default | |||||
if (showingChildren.length === 1) { | |||||
return true | |||||
} | |||||
// Show parent if there are no child router to display | |||||
if (showingChildren.length === 0) { | |||||
this.onlyOneChild = { ... parent, path: '', noShowingChildren: true } | |||||
return true | |||||
} | |||||
return false | |||||
}, | |||||
resolvePath(routePath) { | |||||
if (isExternal(routePath)) { | |||||
return routePath | |||||
} | |||||
if (isExternal(this.basePath)) { | |||||
return this.basePath | |||||
} | |||||
return path.resolve(this.basePath, routePath) | |||||
} | |||||
} | |||||
} | |||||
</script> |
@@ -0,0 +1,56 @@ | |||||
<template> | |||||
<div :class="{'has-logo':showLogo}"> | |||||
<logo v-if="showLogo" :collapse="isCollapse" /> | |||||
<el-scrollbar wrap-class="scrollbar-wrapper"> | |||||
<el-menu | |||||
:default-active="activeMenu" | |||||
:collapse="isCollapse" | |||||
:background-color="variables.menuBg" | |||||
:text-color="variables.menuText" | |||||
:unique-opened="false" | |||||
:active-text-color="variables.menuActiveText" | |||||
:collapse-transition="false" | |||||
mode="vertical" | |||||
> | |||||
<sidebar-item v-for="route in routes" :key="route.path" :item="route" :base-path="route.path" /> | |||||
</el-menu> | |||||
</el-scrollbar> | |||||
</div> | |||||
</template> | |||||
<script> | |||||
import { mapGetters } from 'vuex' | |||||
import Logo from './Logo' | |||||
import SidebarItem from './SidebarItem' | |||||
import variables from '@/assets/styles/variables.scss' | |||||
export default { | |||||
components: { SidebarItem, Logo }, | |||||
computed: { | |||||
...mapGetters([ | |||||
'sidebar' | |||||
]), | |||||
routes() { | |||||
return this.$router.options.routes | |||||
}, | |||||
activeMenu() { | |||||
const route = this.$route | |||||
const { meta, path } = route | |||||
// if set path, the sidebar will highlight the path you set | |||||
if (meta.activeMenu) { | |||||
return meta.activeMenu | |||||
} | |||||
return path | |||||
}, | |||||
showLogo() { | |||||
return this.$store.state.settings.sidebarLogo | |||||
}, | |||||
variables() { | |||||
return variables | |||||
}, | |||||
isCollapse() { | |||||
return !this.sidebar.opened | |||||
} | |||||
} | |||||
} | |||||
</script> |
@@ -0,0 +1,3 @@ | |||||
export { default as Navbar } from './Navbar' | |||||
export { default as Sidebar } from './Sidebar' | |||||
export { default as AppMain } from './AppMain' |
@@ -0,0 +1,97 @@ | |||||
<template> | |||||
<div :class="classObj" class="app-wrapper"> | |||||
<div v-if="device==='mobile'&& sidebar.opened && isChild" class="drawer-bg" @click="handleClickOutside" /> | |||||
<sidebar v-if="isChild" class="sidebar-container" /> | |||||
<div class="main-container" :style="{marginLeft: isChild ? null : 0 }"> | |||||
<div v-if="isChild" :class="{'fixed-header':fixedHeader}"> | |||||
<navbar /> | |||||
</div> | |||||
<app-main /> | |||||
</div> | |||||
</div> | |||||
</template> | |||||
<script> | |||||
import { Navbar, Sidebar, AppMain } from './components' | |||||
import ResizeMixin from './mixin/ResizeHandler' | |||||
import { mapGetters } from 'vuex' | |||||
export default { | |||||
name: 'Layout', | |||||
components: { | |||||
Navbar, | |||||
Sidebar, | |||||
AppMain | |||||
}, | |||||
mixins: [ResizeMixin], | |||||
computed: { | |||||
sidebar() { | |||||
return this.$store.state.app.sidebar | |||||
}, | |||||
device() { | |||||
return this.$store.state.app.device | |||||
}, | |||||
fixedHeader() { | |||||
return this.$store.state.settings.fixedHeader | |||||
}, | |||||
classObj() { | |||||
return { | |||||
hideSidebar: !this.sidebar.opened, | |||||
openSidebar: this.sidebar.opened, | |||||
withoutAnimation: this.sidebar.withoutAnimation, | |||||
mobile: this.device === 'mobile' | |||||
} | |||||
}, | |||||
...mapGetters([ | |||||
'isChild' | |||||
]) | |||||
}, | |||||
methods: { | |||||
handleClickOutside() { | |||||
this.$store.dispatch('app/closeSideBar', { withoutAnimation: false }) | |||||
} | |||||
} | |||||
} | |||||
</script> | |||||
<style lang="scss" scoped> | |||||
@import "~@/assets/styles/mixin.scss"; | |||||
@import "~@/assets/styles/variables.scss"; | |||||
.app-wrapper { | |||||
@include clearfix; | |||||
position: relative; | |||||
height: 100%; | |||||
width: 100%; | |||||
&.mobile.openSidebar{ | |||||
position: fixed; | |||||
top: 0; | |||||
} | |||||
} | |||||
.drawer-bg { | |||||
background: #000; | |||||
opacity: 0.3; | |||||
width: 100%; | |||||
top: 0; | |||||
height: 100%; | |||||
position: absolute; | |||||
z-index: 999; | |||||
} | |||||
.fixed-header { | |||||
position: fixed; | |||||
top: 0; | |||||
right: 0; | |||||
z-index: 9; | |||||
width: calc(100% - #{$sideBarWidth}); | |||||
transition: width 0.28s; | |||||
} | |||||
.hideSidebar .fixed-header { | |||||
width: calc(100% - 54px) | |||||
} | |||||
.mobile .fixed-header { | |||||
width: 100%; | |||||
} | |||||
</style> |
@@ -0,0 +1,45 @@ | |||||
import store from '@/store' | |||||
const { body } = document | |||||
const WIDTH = 992 // refer to Bootstrap's responsive design | |||||
export default { | |||||
watch: { | |||||
$route(route) { | |||||
if (this.device === 'mobile' && this.sidebar.opened) { | |||||
store.dispatch('app/closeSideBar', { withoutAnimation: false }) | |||||
} | |||||
} | |||||
}, | |||||
beforeMount() { | |||||
window.addEventListener('resize', this.$_resizeHandler) | |||||
}, | |||||
beforeDestroy() { | |||||
window.removeEventListener('resize', this.$_resizeHandler) | |||||
}, | |||||
mounted() { | |||||
const isMobile = this.$_isMobile() | |||||
if (isMobile) { | |||||
store.dispatch('app/toggleDevice', 'mobile') | |||||
store.dispatch('app/closeSideBar', { withoutAnimation: true }) | |||||
} | |||||
}, | |||||
methods: { | |||||
// use $_ for mixins properties | |||||
// https://vuejs.org/v2/style-guide/index.html#Private-property-names-essential | |||||
$_isMobile() { | |||||
const rect = body.getBoundingClientRect() | |||||
return rect.width - 1 < WIDTH | |||||
}, | |||||
$_resizeHandler() { | |||||
if (!document.hidden) { | |||||
const isMobile = this.$_isMobile() | |||||
store.dispatch('app/toggleDevice', isMobile ? 'mobile' : 'desktop') | |||||
if (isMobile) { | |||||
store.dispatch('app/closeSideBar', { withoutAnimation: true }) | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,85 @@ | |||||
/* | |||||
* @Date: 2021-07-01 15:30:29 | |||||
* @LastEditTime: 2022-07-27 10:47:36 | |||||
* @Description: 增加微应用配置 | |||||
*/ | |||||
import './public-path' | |||||
import Vue from 'vue' | |||||
import 'normalize.css/normalize.css' // A modern alternative to CSS resets | |||||
import ElementUI from 'element-ui' | |||||
import './assets/styles/element-variables.scss' | |||||
import '@/assets/styles/index.scss' // global css | |||||
import App from './App' | |||||
import store from './store' | |||||
import router from './router' | |||||
import '@/assets/icon/icons.css' | |||||
// 权限控制 | |||||
import checkPer from '@/utils/permission' | |||||
import permission from './components/Permission' | |||||
import { setToken } from '@/utils/auth' | |||||
Vue.use(checkPer) | |||||
Vue.use(permission) | |||||
Vue.use(ElementUI, { | |||||
size: 'small' | |||||
}) | |||||
Vue.config.productionTip = false | |||||
let instance = null | |||||
function render({ container } = {}) { | |||||
instance = new Vue({ | |||||
router, | |||||
store, | |||||
render: h => h(App) | |||||
}).$mount(container ? container.querySelector('#app') : '#app') | |||||
// index.html 里面的 id 需要改成 appVueHash,否则子项目无法独立运行 | |||||
} | |||||
if (!window.__POWERED_BY_QIANKUN__) { | |||||
// 全局变量来判断环境 | |||||
render() | |||||
} | |||||
/** | |||||
* bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。 | |||||
* 通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等。 | |||||
*/ | |||||
export async function bootstrap() { | |||||
console.log('现在进入子应用content-cloud的bootstraped阶段') | |||||
} | |||||
/** | |||||
* 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法 | |||||
*/ | |||||
export async function mount(props) { | |||||
console.log('现在进入子应用content-cloud的mount周期', props) | |||||
setToken(props.token) | |||||
store.dispatch('user/setChild', false ) | |||||
store.dispatch('user/setRoles', props.roles ) | |||||
store.dispatch('user/setToken', props.token ) | |||||
render(props) | |||||
} | |||||
/** | |||||
* 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例 | |||||
*/ | |||||
export async function unmount() { | |||||
console.log('现在进入子应用content-cloud的unmount阶段') | |||||
instance.$destroy() | |||||
instance.$el.innerHTML = '' | |||||
instance = null | |||||
// router = null | |||||
} | |||||
/** | |||||
* 可选生命周期钩子,仅使用 loadMicroApp 方式加载微应用时生效 | |||||
*/ | |||||
export async function update(props) { | |||||
console.log('update props', props) | |||||
} |
@@ -0,0 +1,335 @@ | |||||
import { | |||||
initData, | |||||
download | |||||
} from '@/api/data' | |||||
import { | |||||
parseTime, | |||||
downloadFile | |||||
} from '@/utils/index' | |||||
export default { | |||||
data() { | |||||
return { | |||||
// 表格数据 | |||||
data: [], | |||||
// 排序规则,默认 id 降序, 支持多字段排序 ['id,desc', 'createTime,asc'] | |||||
sort: ['id,desc'], | |||||
// 页码 | |||||
page: 0, | |||||
// 每页数据条数 | |||||
size: 10, | |||||
// 总数据条数 | |||||
total: 0, | |||||
// 请求数据的url | |||||
url: '', | |||||
// 查询数据的参数 | |||||
params: {}, | |||||
// 待查询的对象 | |||||
query: {}, | |||||
// 等待时间 | |||||
time: 50, | |||||
// 是否为新增类型的表单 | |||||
isAdd: false, | |||||
// 导出的 Loading | |||||
downloadLoading: false, | |||||
// 表格 Loading 属性 | |||||
loading: true, | |||||
// 删除 Loading 属性 | |||||
delLoading: false, | |||||
delAllLoading: false, | |||||
// 弹窗属性 | |||||
dialog: false, | |||||
// Form 表单 | |||||
form: {}, | |||||
// 重置表单 | |||||
resetForm: {}, | |||||
// 标题 | |||||
title: '' | |||||
} | |||||
}, | |||||
methods: { | |||||
parseTime, | |||||
downloadFile, | |||||
async init() { | |||||
if (!await this.beforeInit()) { | |||||
return | |||||
} | |||||
return new Promise((resolve, reject) => { | |||||
this.loading = true | |||||
// 请求数据 | |||||
initData(this.url, this.getQueryParame()).then(data => { | |||||
this.total = data.totalElements | |||||
this.data = data.content | |||||
// time 毫秒后显示表格 | |||||
setTimeout(() => { | |||||
this.loading = false | |||||
}, this.time) | |||||
resolve(data) | |||||
}).catch(err => { | |||||
this.loading = false | |||||
reject(err) | |||||
}) | |||||
}) | |||||
}, | |||||
beforeInit() { | |||||
return true | |||||
}, | |||||
getQueryParame: function() { | |||||
return { | |||||
pageNum: this.page, | |||||
pageSize: this.size, | |||||
sort: this.sort, | |||||
...this.query, | |||||
...this.params | |||||
} | |||||
}, | |||||
// 改变页码 | |||||
pageChange(e) { | |||||
this.page = e - 1 | |||||
this.init() | |||||
}, | |||||
// 改变每页显示数 | |||||
sizeChange(e) { | |||||
this.page = 0 | |||||
this.size = e | |||||
this.init() | |||||
}, | |||||
// 预防删除第二页最后一条数据时,或者多选删除第二页的数据时,页码错误导致请求无数据 | |||||
dleChangePage(size) { | |||||
if (size === undefined) { | |||||
size = 1 | |||||
} | |||||
if (this.data.length === size && this.page !== 0) { | |||||
this.page = this.page - 1 | |||||
} | |||||
}, | |||||
// 查询方法 | |||||
toQuery() { | |||||
this.page = 0 | |||||
this.init() | |||||
}, | |||||
/** | |||||
* 通用的提示封装 | |||||
*/ | |||||
submitSuccessNotify() { | |||||
this.$notify({ | |||||
title: '提交成功', | |||||
type: 'success', | |||||
duration: 2500 | |||||
}) | |||||
}, | |||||
addSuccessNotify() { | |||||
this.$notify({ | |||||
title: '新增成功', | |||||
type: 'success', | |||||
duration: 2500 | |||||
}) | |||||
}, | |||||
editSuccessNotify() { | |||||
this.$notify({ | |||||
title: '编辑成功', | |||||
type: 'success', | |||||
duration: 2500 | |||||
}) | |||||
}, | |||||
delSuccessNotify() { | |||||
this.$notify({ | |||||
title: '删除成功', | |||||
type: 'success', | |||||
duration: 2500 | |||||
}) | |||||
}, | |||||
notify(title, type) { | |||||
this.$notify({ | |||||
title: title, | |||||
type: type, | |||||
duration: 2500 | |||||
}) | |||||
}, | |||||
/** | |||||
* 删除前可以调用 beforeDelMethod 做一些操作 | |||||
*/ | |||||
beforeDelMethod() { | |||||
return true | |||||
}, | |||||
/** | |||||
* 通用的删除 | |||||
*/ | |||||
delMethod(id) { | |||||
if (!this.beforeDelMethod()) { | |||||
return | |||||
} | |||||
this.delLoading = true | |||||
this.crudMethod.del(id).then(() => { | |||||
this.delLoading = false | |||||
this.$refs[id].doClose() | |||||
this.dleChangePage() | |||||
this.delSuccessNotify() | |||||
this.afterDelMethod() | |||||
this.init() | |||||
}).catch(() => { | |||||
this.delLoading = false | |||||
this.$refs[id].doClose() | |||||
}) | |||||
}, | |||||
afterDelMethod() {}, | |||||
/** | |||||
* 多选删除提示 | |||||
*/ | |||||
beforeDelAllMethod() { | |||||
this.$confirm('你确定删除选中的数据吗?', '提示', { | |||||
confirmButtonText: '确定', | |||||
cancelButtonText: '取消', | |||||
type: 'warning' | |||||
}).then(() => { | |||||
this.delAllMethod() | |||||
}) | |||||
}, | |||||
/** | |||||
* 多选删除 | |||||
*/ | |||||
delAllMethod() { | |||||
this.delAllLoading = true | |||||
const data = this.$refs.table.selection | |||||
const ids = [] | |||||
for (let i = 0; i < data.length; i++) { | |||||
ids.push(data[i].id) | |||||
} | |||||
this.crudMethod.delAll(ids).then(() => { | |||||
this.delAllLoading = false | |||||
this.dleChangePage(ids.length) | |||||
this.init() | |||||
this.$notify({ | |||||
title: '删除成功', | |||||
type: 'success', | |||||
duration: 2500 | |||||
}) | |||||
}).catch(() => { | |||||
this.delAllLoading = false | |||||
}) | |||||
}, | |||||
/** | |||||
* 显示新增弹窗前可以调用该方法 | |||||
*/ | |||||
beforeShowAddForm() {}, | |||||
/** | |||||
* 显示新增弹窗 | |||||
*/ | |||||
showAddFormDialog() { | |||||
this.isAdd = true | |||||
this.resetForm = JSON.parse(JSON.stringify(this.form)) | |||||
this.beforeShowAddForm() | |||||
this.dialog = true | |||||
}, | |||||
/** | |||||
* 显示编辑弹窗前可以调用该方法 | |||||
*/ | |||||
beforeShowEditForm(data) {}, | |||||
/** | |||||
* 显示编辑弹窗 | |||||
*/ | |||||
showEditFormDialog(data = '') { | |||||
this.isAdd = false | |||||
if (data) { | |||||
this.resetForm = JSON.parse(JSON.stringify(this.form)) | |||||
this.form = JSON.parse(JSON.stringify(data)) | |||||
} | |||||
this.beforeShowEditForm(data) | |||||
this.dialog = true | |||||
}, | |||||
/** | |||||
* 新增方法 | |||||
*/ | |||||
addMethod() { | |||||
this.crudMethod.add(this.form).then(() => { | |||||
this.addSuccessNotify() | |||||
this.loading = false | |||||
this.afterAddMethod() | |||||
this.cancel() | |||||
this.init() | |||||
}).catch(() => { | |||||
this.loading = false | |||||
this.afterAddErrorMethod() | |||||
}) | |||||
}, | |||||
/** | |||||
* 新增后可以调用该方法 | |||||
*/ | |||||
afterAddMethod() {}, | |||||
/** | |||||
* 新增失败后调用该方法 | |||||
*/ | |||||
afterAddErrorMethod() {}, | |||||
/** | |||||
* 通用的编辑方法 | |||||
*/ | |||||
editMethod() { | |||||
this.crudMethod.edit(this.form).then(() => { | |||||
this.editSuccessNotify() | |||||
this.loading = false | |||||
this.afterEditMethod() | |||||
this.cancel() | |||||
this.init() | |||||
}).catch(() => { | |||||
this.loading = false | |||||
}) | |||||
}, | |||||
/** | |||||
* 编辑后可以调用该方法 | |||||
*/ | |||||
afterEditMethod() {}, | |||||
/** | |||||
* 提交前可以调用该方法 | |||||
*/ | |||||
beforeSubmitMethod() { | |||||
return true | |||||
}, | |||||
/** | |||||
* 提交 | |||||
*/ | |||||
submitMethod() { | |||||
if (!this.beforeSubmitMethod()) { | |||||
return | |||||
} | |||||
if (this.$refs['form']) { | |||||
this.$refs['form'].validate((valid) => { | |||||
if (valid) { | |||||
this.loading = true | |||||
if (this.isAdd) { | |||||
this.addMethod() | |||||
} else this.editMethod() | |||||
} | |||||
}) | |||||
} | |||||
}, | |||||
/** | |||||
* 隐藏弹窗 | |||||
*/ | |||||
cancel() { | |||||
this.dialog = false | |||||
if (this.$refs['form']) { | |||||
this.$refs['form'].clearValidate() | |||||
this.form = this.resetForm | |||||
} | |||||
}, | |||||
/** | |||||
* 获取弹窗的标题 | |||||
*/ | |||||
getFormTitle() { | |||||
return this.isAdd ? `新增${this.title}` : `编辑${this.title}` | |||||
}, | |||||
/** | |||||
* 通用导出 | |||||
*/ | |||||
downloadMethod() { | |||||
this.beforeInit() | |||||
this.downloadLoading = true | |||||
download(this.url + '/download', this.params).then(result => { | |||||
this.downloadFile(result, this.title + '数据', 'xlsx') | |||||
this.downloadLoading = false | |||||
}).catch(() => { | |||||
this.downloadLoading = false | |||||
}) | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,7 @@ | |||||
/* eslint-disable camelcase */ | |||||
if (window.__POWERED_BY_QIANKUN__) { | |||||
// eslint-disable-next-line no-undef | |||||
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__ | |||||
} |
@@ -0,0 +1,114 @@ | |||||
import Vue from 'vue' | |||||
import Router from 'vue-router' | |||||
Vue.use(Router) | |||||
/* Layout */ | |||||
import Layout from '@/layout' | |||||
/** | |||||
* Note: sub-menu only appear when route children.length >= 1 | |||||
* Detail see: https://panjiachen.github.io/vue-element-admin-site/guide/essentials/router-and-nav.html | |||||
* | |||||
* hidden: true if set true, item will not show in the sidebar(default is false) | |||||
* alwaysShow: true if set true, will always show the root menu | |||||
* if not set alwaysShow, when item has more than one children route, | |||||
* it will becomes nested mode, otherwise not show the root menu | |||||
* redirect: noRedirect if set noRedirect will no redirect in the breadcrumb | |||||
* name:'router-name' the name is used by <keep-alive> (must set!!!) | |||||
* meta : { | |||||
roles: ['admin','editor'] control the page roles (you can set multiple roles) | |||||
title: 'title' the name show in sidebar and breadcrumb (recommend set) | |||||
icon: 'svg-name'/'el-icon-x' the icon show in the sidebar | |||||
breadcrumb: false if set false, the item will hidden in breadcrumb(default is true) | |||||
activeMenu: '/example/list' if set path, the sidebar will highlight the path you set | |||||
} | |||||
*/ | |||||
/** | |||||
* constantRoutes | |||||
* a base page that does not have permission requirements | |||||
* all roles can be accessed | |||||
*/ | |||||
export const constantRoutes = [ | |||||
{ | |||||
path: '/login', | |||||
component: () => import('@/views/login/index'), | |||||
hidden: true | |||||
}, | |||||
{ | |||||
path: '/404', | |||||
component: () => import('@/views/404'), | |||||
hidden: true | |||||
}, | |||||
{ | |||||
path: '/', | |||||
component: Layout, | |||||
redirect: '/home', | |||||
children: [{ | |||||
path: 'home', | |||||
name: 'home', | |||||
component: () => import('@/views/home/index'), | |||||
meta: { title: 'Home', icon: 'dashboard' } | |||||
}] | |||||
}, | |||||
{ | |||||
path: '/homePage', | |||||
component: Layout, | |||||
alwaysShow: true, | |||||
redirect: '/homePage/carousel', | |||||
name: 'homePage', | |||||
meta: { | |||||
title: '首页', | |||||
icon: 'nested' | |||||
}, | |||||
children: [ | |||||
{ | |||||
path: 'carousel', | |||||
component: () => import('@/views/homePage/carousel'), | |||||
meta: { title: '轮播' } | |||||
}, | |||||
{ | |||||
path: 'carHot', | |||||
component: () => import('@/views/homePage/carHot'), | |||||
meta: { title: '汽车资讯热门' } | |||||
}, | |||||
{ | |||||
path: 'banner1', | |||||
component: () => import('@/views/homePage/banner1'), | |||||
meta: { title: '广告位1' } | |||||
}, | |||||
{ | |||||
path: 'yyzHot', | |||||
component: () => import('@/views/homePage/yyzHot'), | |||||
meta: { title: '元宇宙日爆热门' } | |||||
}, | |||||
{ | |||||
path: 'banner2', | |||||
component: () => import('@/views/homePage/banner2'), | |||||
meta: { title: '广告位2' } | |||||
} | |||||
] | |||||
} | |||||
// 404 page must be placed at the end !!! | |||||
// { path: '*', redirect: '/404', hidden: true } | |||||
] | |||||
const createRouter = () => new Router({ | |||||
base: window.__POWERED_BY_QIANKUN__ ? '/vdcop_auth/admin' : '/admin', | |||||
mode: 'history', // require service support | |||||
scrollBehavior: () => ({ y: 0 }), | |||||
routes: constantRoutes | |||||
}) | |||||
const router = createRouter() | |||||
// Detail see: https://github.com/vuejs/vue-router/issues/1234#issuecomment-357941465 | |||||
export function resetRouter() { | |||||
const newRouter = createRouter() | |||||
router.matcher = newRouter.matcher // reset router | |||||
} | |||||
export default router |
@@ -0,0 +1,16 @@ | |||||
module.exports = { | |||||
title: '后台管理系统', | |||||
/** | |||||
* @type {boolean} true | false | |||||
* @description Whether fix the header | |||||
*/ | |||||
fixedHeader: false, | |||||
/** | |||||
* @type {boolean} true | false | |||||
* @description Whether show the logo in sidebar | |||||
*/ | |||||
sidebarLogo: false | |||||
} |
@@ -0,0 +1,15 @@ | |||||
/* | |||||
* @Date: 2021-07-16 17:52:04 | |||||
* @LastEditTime: 2022-07-27 10:48:11 | |||||
* @Description: | |||||
*/ | |||||
const getters = { | |||||
sidebar: state => state.app.sidebar, | |||||
device: state => state.app.device, | |||||
token: state => state.user.token, | |||||
avatar: state => state.user.avatar, | |||||
name: state => state.user.name, | |||||
isChild: state => state.user.isChild, | |||||
roles: state => state.user.roles | |||||
} | |||||
export default getters |
@@ -0,0 +1,19 @@ | |||||
import Vue from 'vue' | |||||
import Vuex from 'vuex' | |||||
import getters from './getters' | |||||
import app from './modules/app' | |||||
import settings from './modules/settings' | |||||
import user from './modules/user' | |||||
Vue.use(Vuex) | |||||
const store = new Vuex.Store({ | |||||
modules: { | |||||
app, | |||||
settings, | |||||
user | |||||
}, | |||||
getters | |||||
}) | |||||
export default store |
@@ -0,0 +1,48 @@ | |||||
import Cookies from 'js-cookie' | |||||
const state = { | |||||
sidebar: { | |||||
opened: Cookies.get('sidebarStatus') ? !!+Cookies.get('sidebarStatus') : true, | |||||
withoutAnimation: false | |||||
}, | |||||
device: 'desktop' | |||||
} | |||||
const mutations = { | |||||
TOGGLE_SIDEBAR: state => { | |||||
state.sidebar.opened = !state.sidebar.opened | |||||
state.sidebar.withoutAnimation = false | |||||
if (state.sidebar.opened) { | |||||
Cookies.set('sidebarStatus', 1) | |||||
} else { | |||||
Cookies.set('sidebarStatus', 0) | |||||
} | |||||
}, | |||||
CLOSE_SIDEBAR: (state, withoutAnimation) => { | |||||
Cookies.set('sidebarStatus', 0) | |||||
state.sidebar.opened = false | |||||
state.sidebar.withoutAnimation = withoutAnimation | |||||
}, | |||||
TOGGLE_DEVICE: (state, device) => { | |||||
state.device = device | |||||
} | |||||
} | |||||
const actions = { | |||||
toggleSideBar({ commit }) { | |||||
commit('TOGGLE_SIDEBAR') | |||||
}, | |||||
closeSideBar({ commit }, { withoutAnimation }) { | |||||
commit('CLOSE_SIDEBAR', withoutAnimation) | |||||
}, | |||||
toggleDevice({ commit }, device) { | |||||
commit('TOGGLE_DEVICE', device) | |||||
} | |||||
} | |||||
export default { | |||||
namespaced: true, | |||||
state, | |||||
mutations, | |||||
actions | |||||
} |
@@ -0,0 +1,32 @@ | |||||
import defaultSettings from '@/settings' | |||||
const { showSettings, fixedHeader, sidebarLogo } = defaultSettings | |||||
const state = { | |||||
showSettings: showSettings, | |||||
fixedHeader: fixedHeader, | |||||
sidebarLogo: sidebarLogo | |||||
} | |||||
const mutations = { | |||||
CHANGE_SETTING: (state, { key, value }) => { | |||||
// eslint-disable-next-line no-prototype-builtins | |||||
if (state.hasOwnProperty(key)) { | |||||
state[key] = value | |||||
} | |||||
} | |||||
} | |||||
const actions = { | |||||
changeSetting({ commit }, data) { | |||||
commit('CHANGE_SETTING', data) | |||||
} | |||||
} | |||||
export default { | |||||
namespaced: true, | |||||
state, | |||||
mutations, | |||||
actions | |||||
} | |||||
@@ -0,0 +1,114 @@ | |||||
import { login, logout, getInfo } from '@/api/user' | |||||
import { getToken, removeToken } from '@/utils/auth' | |||||
import { resetRouter } from '@/router' | |||||
const getDefaultState = () => { | |||||
return { | |||||
token: getToken(), | |||||
name: '', | |||||
avatar: '', | |||||
isChild: true, | |||||
roles: [] | |||||
} | |||||
} | |||||
const state = getDefaultState() | |||||
const mutations = { | |||||
RESET_STATE: (state) => { | |||||
Object.assign(state, getDefaultState()) | |||||
}, | |||||
SET_TOKEN: (state, token) => { | |||||
state.token = token | |||||
}, | |||||
SET_NAME: (state, name) => { | |||||
state.name = name | |||||
}, | |||||
SET_AVATAR: (state, avatar) => { | |||||
state.avatar = avatar | |||||
}, | |||||
SET_ISCHILD: (state, isChild) => { | |||||
state.isChild = isChild | |||||
}, | |||||
SET_ROLES: (state, roles) => { | |||||
state.roles = roles | |||||
} | |||||
} | |||||
const actions = { | |||||
setChild({ commit }, isChild) { | |||||
commit('SET_ISCHILD', isChild) | |||||
}, | |||||
setRoles({ commit }, roles) { | |||||
commit('SET_ROLES', roles) | |||||
}, | |||||
setToken({ commit }, token) { | |||||
commit('SET_TOKEN', token) | |||||
}, | |||||
// user login | |||||
/* login({ commit }, userInfo) { | |||||
const { username, password } = userInfo | |||||
return new Promise((resolve, reject) => { | |||||
login({ username: username.trim(), password: password }).then(response => { | |||||
const { data } = response | |||||
commit('SET_TOKEN', data.token) | |||||
setToken(data.token) | |||||
resolve() | |||||
}).catch(error => { | |||||
reject(error) | |||||
}) | |||||
}) | |||||
}, */ | |||||
// get user info | |||||
getInfo({ commit, state }) { | |||||
return new Promise((resolve, reject) => { | |||||
getInfo(state.token).then(response => { | |||||
const { data } = response | |||||
if (!data) { | |||||
return reject('Verification failed, please Login again.') | |||||
} | |||||
const { name, avatar } = data | |||||
commit('SET_NAME', name) | |||||
commit('SET_AVATAR', avatar) | |||||
resolve(data) | |||||
}).catch(error => { | |||||
reject(error) | |||||
}) | |||||
}) | |||||
}, | |||||
// user logout | |||||
logout({ commit, state }) { | |||||
return new Promise((resolve, reject) => { | |||||
logout(state.token).then(() => { | |||||
removeToken() // must remove token first | |||||
resetRouter() | |||||
commit('RESET_STATE') | |||||
resolve() | |||||
}).catch(error => { | |||||
reject(error) | |||||
}) | |||||
}) | |||||
}, | |||||
// remove token | |||||
resetToken({ commit }) { | |||||
return new Promise(resolve => { | |||||
removeToken() // must remove token first | |||||
commit('RESET_STATE') | |||||
resolve() | |||||
}) | |||||
} | |||||
} | |||||
export default { | |||||
namespaced: true, | |||||
state, | |||||
mutations, | |||||
actions | |||||
} | |||||
@@ -0,0 +1,15 @@ | |||||
import Cookies from 'js-cookie' | |||||
const TokenKey = 'token' | |||||
export function getToken() { | |||||
return Cookies.get(TokenKey) | |||||
} | |||||
export function setToken(token) { | |||||
return Cookies.set(TokenKey, token) | |||||
} | |||||
export function removeToken() { | |||||
return Cookies.remove(TokenKey) | |||||
} |
@@ -0,0 +1,36 @@ | |||||
import Vue from 'vue' | |||||
import Clipboard from 'clipboard' | |||||
function clipboardSuccess() { | |||||
Vue.prototype.$message({ | |||||
message: 'Copy successfully', | |||||
type: 'success', | |||||
duration: 1500 | |||||
}) | |||||
} | |||||
function clipboardError() { | |||||
Vue.prototype.$message({ | |||||
message: 'Copy failed', | |||||
type: 'error' | |||||
}) | |||||
} | |||||
export default function handleClipboard(text, event) { | |||||
const clipboard = new Clipboard(event.target, { | |||||
text: () => text | |||||
}) | |||||
clipboard.on('success', () => { | |||||
clipboardSuccess() | |||||
clipboard.off('error') | |||||
clipboard.off('success') | |||||
clipboard.destroy() | |||||
}) | |||||
clipboard.on('error', () => { | |||||
clipboardError() | |||||
clipboard.off('error') | |||||
clipboard.off('success') | |||||
clipboard.destroy() | |||||
}) | |||||
clipboard.onClick(event) | |||||
} |
@@ -0,0 +1,17 @@ | |||||
/* | |||||
* @Autor: Chuting | |||||
* @Date: 2021-09-07 17:13:12 | |||||
* @LastEditors: Chuting | |||||
* @LastEditTime: 2022-07-27 10:48:18 | |||||
* @Description: | |||||
*/ | |||||
import defaultSettings from '@/settings' | |||||
const title = defaultSettings.title || '后台管理系统' | |||||
export default function getPageTitle(pageTitle) { | |||||
if (pageTitle) { | |||||
return `${pageTitle} - ${title}` | |||||
} | |||||
return `${title}` | |||||
} |
@@ -0,0 +1,171 @@ | |||||
/** | |||||
* Created by PanJiaChen on 16/11/18. | |||||
*/ | |||||
/** | |||||
* Parse the time to string | |||||
* @param {(Object|string|number)} time | |||||
* @param {string} cFormat | |||||
* @returns {string | null} | |||||
*/ | |||||
export function parseTime(time, cFormat) { | |||||
if (arguments.length === 0 || !time) { | |||||
return null | |||||
} | |||||
const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}' | |||||
let date | |||||
if (typeof time === 'object') { | |||||
date = time | |||||
} else { | |||||
if ((typeof time === 'string')) { | |||||
if ((/^[0-9]+$/.test(time))) { | |||||
// support "1548221490638" | |||||
time = parseInt(time) | |||||
} else { | |||||
// support safari | |||||
// https://stackoverflow.com/questions/4310953/invalid-date-in-safari | |||||
time = time.replace(new RegExp(/-/gm), '/') | |||||
} | |||||
} | |||||
if ((typeof time === 'number') && (time.toString().length === 10)) { | |||||
time = time * 1000 | |||||
} | |||||
date = new Date(time) | |||||
} | |||||
const formatObj = { | |||||
y: date.getFullYear(), | |||||
m: date.getMonth() + 1, | |||||
d: date.getDate(), | |||||
h: date.getHours(), | |||||
i: date.getMinutes(), | |||||
s: date.getSeconds(), | |||||
a: date.getDay() | |||||
} | |||||
const time_str = format.replace(/{([ymdhisa])+}/g, (result, key) => { | |||||
const value = formatObj[key] | |||||
// Note: getDay() returns 0 on Sunday | |||||
if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value ] } | |||||
return value.toString().padStart(2, '0') | |||||
}) | |||||
return time_str | |||||
} | |||||
/** | |||||
* @param {number} time | |||||
* @param {string} option | |||||
* @returns {string} | |||||
*/ | |||||
export function formatTime(time, option) { | |||||
if (('' + time).length === 10) { | |||||
time = parseInt(time) * 1000 | |||||
} else { | |||||
time = +time | |||||
} | |||||
const d = new Date(time) | |||||
const now = Date.now() | |||||
const diff = (now - d) / 1000 | |||||
if (diff < 30) { | |||||
return '刚刚' | |||||
} else if (diff < 3600) { | |||||
// less 1 hour | |||||
return Math.ceil(diff / 60) + '分钟前' | |||||
} else if (diff < 3600 * 24) { | |||||
return Math.ceil(diff / 3600) + '小时前' | |||||
} else if (diff < 3600 * 24 * 2) { | |||||
return '1天前' | |||||
} | |||||
if (option) { | |||||
return parseTime(time, option) | |||||
} else { | |||||
return ( | |||||
d.getMonth() + | |||||
1 + | |||||
'月' + | |||||
d.getDate() + | |||||
'日' + | |||||
d.getHours() + | |||||
'时' + | |||||
d.getMinutes() + | |||||
'分' | |||||
) | |||||
} | |||||
} | |||||
/** | |||||
* @param {string} url | |||||
* @returns {Object} | |||||
*/ | |||||
export function param2Obj(url) { | |||||
const search = decodeURIComponent(url.split('?')[1]).replace(/\+/g, ' ') | |||||
if (!search) { | |||||
return {} | |||||
} | |||||
const obj = {} | |||||
const searchArr = search.split('&') | |||||
searchArr.forEach(v => { | |||||
const index = v.indexOf('=') | |||||
if (index !== -1) { | |||||
const name = v.substring(0, index) | |||||
const val = v.substring(index + 1, v.length) | |||||
obj[name] = val | |||||
} | |||||
}) | |||||
return obj | |||||
} | |||||
/** | |||||
* @param {Function} func | |||||
* @param {number} wait | |||||
* @param {boolean} immediate | |||||
* @return {*} | |||||
*/ | |||||
export function debounce(func, wait, immediate) { | |||||
let timeout, args, context, timestamp, result | |||||
const later = function() { | |||||
// 据上一次触发时间间隔 | |||||
const last = +new Date() - timestamp | |||||
// 上次被包装函数被调用时间间隔 last 小于设定时间间隔 wait | |||||
if (last < wait && last > 0) { | |||||
timeout = setTimeout(later, wait - last) | |||||
} else { | |||||
timeout = null | |||||
// 如果设定为immediate===true,因为开始边界已经调用过了此处无需调用 | |||||
if (!immediate) { | |||||
result = func.apply(context, args) | |||||
if (!timeout) context = args = null | |||||
} | |||||
} | |||||
} | |||||
return function(...args) { | |||||
context = this | |||||
timestamp = +new Date() | |||||
const callNow = immediate && !timeout | |||||
// 如果延时不存在,重新设定延时 | |||||
if (!timeout) timeout = setTimeout(later, wait) | |||||
if (callNow) { | |||||
result = func.apply(context, args) | |||||
context = args = null | |||||
} | |||||
return result | |||||
} | |||||
} | |||||
// 下载文件 | |||||
export function downloadFile(obj, name, suffix) { | |||||
const url = window.URL.createObjectURL(new Blob([obj])) | |||||
const link = document.createElement('a') | |||||
link.style.display = 'none' | |||||
link.href = url | |||||
const fileName = parseTime(new Date()) + '-' + name + '.' + suffix | |||||
link.setAttribute('download', fileName) | |||||
document.body.appendChild(link) | |||||
link.click() | |||||
document.body.removeChild(link) | |||||
} |
@@ -0,0 +1,23 @@ | |||||
import store from '@/store' | |||||
/** | |||||
* @param {Array} value | |||||
* @returns {Boolean} | |||||
* @example see @/views/permission/directive.vue | |||||
*/ | |||||
export default { | |||||
install(Vue) { | |||||
Vue.prototype.checkPer = (value) => { | |||||
if (value && value instanceof Array && value.length > 0) { | |||||
const roles = store.getters && store.getters.roles | |||||
const permissionRoles = value | |||||
return roles.some(role => { | |||||
return permissionRoles.includes(role) | |||||
}) | |||||
} else { | |||||
console.error(`need roles! Like v-permission="['admin','editor']"`) | |||||
return false | |||||
} | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,71 @@ | |||||
/* | |||||
* @Date: 2021-07-01 15:30:29 | |||||
* @LastEditTime: 2022-07-27 10:48:39 | |||||
* @Description: 请求封装 | |||||
*/ | |||||
import axios from 'axios' | |||||
import { Notification } from 'element-ui' | |||||
import store from '@/store' | |||||
import { getToken } from '@/utils/auth' | |||||
// create an axios instance | |||||
const service = axios.create({ | |||||
baseURL: process.env.VUE_APP_BASE_API, // api 的 base_url | |||||
// withCredentials: true, // send cookies when cross-domain requests | |||||
timeout: 1000 * 30 // request timeout | |||||
}) | |||||
// request interceptor | |||||
service.interceptors.request.use( | |||||
config => { | |||||
// do something before request is sent | |||||
// const AccessToken = localStorage.getItem("AccessToken") | |||||
// if (AccessToken) { | |||||
// config.headers.token = AccessToken | |||||
// } | |||||
return config | |||||
}, | |||||
error => { | |||||
// do something with request error | |||||
console.log(error) // for debug | |||||
return Promise.reject(error) | |||||
} | |||||
) | |||||
// response interceptor | |||||
service.interceptors.response.use( | |||||
/** | |||||
* If you want to get http information such as headers or status | |||||
* Please return response => response | |||||
*/ | |||||
/** | |||||
* Determine the request status by custom code | |||||
* Here is just an example | |||||
* You can also judge the status by HTTP Status Code | |||||
*/ | |||||
response => { | |||||
const res = response.data | |||||
// 兼容blob下载 | |||||
if (res instanceof Blob && res.type.toLowerCase().indexOf('json') === -1) { | |||||
return res | |||||
} | |||||
if (res.code === 200) { | |||||
return res.data | |||||
} | |||||
Notification.error({ | |||||
title: res.message, | |||||
duration: 5000 | |||||
}) | |||||
return Promise.reject(res.message || 'Error') | |||||
}, | |||||
error => { | |||||
Notification.error({ | |||||
title: '操作失败!', | |||||
duration: 5000 | |||||
}) | |||||
return Promise.reject(error) | |||||
} | |||||
) | |||||
export default service |
@@ -0,0 +1,36 @@ | |||||
/** | |||||
* Created by PanJiaChen on 16/11/18. | |||||
*/ | |||||
/** | |||||
* @param {string} path | |||||
* @returns {Boolean} | |||||
*/ | |||||
export function isExternal(path) { | |||||
return /^(https?:|mailto:|tel:)/.test(path) | |||||
} | |||||
/** | |||||
* @param {string} str | |||||
* @returns {Boolean} | |||||
*/ | |||||
export function validUsername(str) { | |||||
const valid_map = ['admin', 'editor'] | |||||
return valid_map.indexOf(str.trim()) >= 0 | |||||
} | |||||
/** | |||||
* 邮箱 | |||||
* @param {*} s | |||||
*/ | |||||
export function isEmail(s) { | |||||
return /^([a-zA-Z0-9_-])+@([a-zA-Z0-9_-])+((.[a-zA-Z0-9_-]{2,3}){1,2})$/.test(s) | |||||
} | |||||
/** | |||||
* 手机号码 | |||||
* @param {*} s | |||||
*/ | |||||
export function isMobile(s) { | |||||
return /^1[0-9]{10}$/.test(s) | |||||
} |
@@ -0,0 +1,228 @@ | |||||
<template> | |||||
<div class="wscn-http404-container"> | |||||
<div class="wscn-http404"> | |||||
<div class="pic-404"> | |||||
<img class="pic-404__parent" src="@/assets/404_images/404.png" alt="404"> | |||||
<img class="pic-404__child left" src="@/assets/404_images/404_cloud.png" alt="404"> | |||||
<img class="pic-404__child mid" src="@/assets/404_images/404_cloud.png" alt="404"> | |||||
<img class="pic-404__child right" src="@/assets/404_images/404_cloud.png" alt="404"> | |||||
</div> | |||||
<div class="bullshit"> | |||||
<div class="bullshit__oops">OOPS!</div> | |||||
<div class="bullshit__info">All rights reserved | |||||
<a style="color:#20a0ff" href="https://wallstreetcn.com" target="_blank">wallstreetcn</a> | |||||
</div> | |||||
<div class="bullshit__headline">{{ message }}</div> | |||||
<div class="bullshit__info">Please check that the URL you entered is correct, or click the button below to return to the homepage.</div> | |||||
<a href="" class="bullshit__return-home">Back to home</a> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
</template> | |||||
<script> | |||||
export default { | |||||
name: 'Page404', | |||||
computed: { | |||||
message() { | |||||
return 'The webmaster said that you can not enter this page...' | |||||
} | |||||
} | |||||
} | |||||
</script> | |||||
<style lang="scss" scoped> | |||||
.wscn-http404-container{ | |||||
transform: translate(-50%,-50%); | |||||
position: absolute; | |||||
top: 40%; | |||||
left: 50%; | |||||
} | |||||
.wscn-http404 { | |||||
position: relative; | |||||
width: 1200px; | |||||
padding: 0 50px; | |||||
overflow: hidden; | |||||
.pic-404 { | |||||
position: relative; | |||||
float: left; | |||||
width: 600px; | |||||
overflow: hidden; | |||||
&__parent { | |||||
width: 100%; | |||||
} | |||||
&__child { | |||||
position: absolute; | |||||
&.left { | |||||
width: 80px; | |||||
top: 17px; | |||||
left: 220px; | |||||
opacity: 0; | |||||
animation-name: cloudLeft; | |||||
animation-duration: 2s; | |||||
animation-timing-function: linear; | |||||
animation-fill-mode: forwards; | |||||
animation-delay: 1s; | |||||
} | |||||
&.mid { | |||||
width: 46px; | |||||
top: 10px; | |||||
left: 420px; | |||||
opacity: 0; | |||||
animation-name: cloudMid; | |||||
animation-duration: 2s; | |||||
animation-timing-function: linear; | |||||
animation-fill-mode: forwards; | |||||
animation-delay: 1.2s; | |||||
} | |||||
&.right { | |||||
width: 62px; | |||||
top: 100px; | |||||
left: 500px; | |||||
opacity: 0; | |||||
animation-name: cloudRight; | |||||
animation-duration: 2s; | |||||
animation-timing-function: linear; | |||||
animation-fill-mode: forwards; | |||||
animation-delay: 1s; | |||||
} | |||||
@keyframes cloudLeft { | |||||
0% { | |||||
top: 17px; | |||||
left: 220px; | |||||
opacity: 0; | |||||
} | |||||
20% { | |||||
top: 33px; | |||||
left: 188px; | |||||
opacity: 1; | |||||
} | |||||
80% { | |||||
top: 81px; | |||||
left: 92px; | |||||
opacity: 1; | |||||
} | |||||
100% { | |||||
top: 97px; | |||||
left: 60px; | |||||
opacity: 0; | |||||
} | |||||
} | |||||
@keyframes cloudMid { | |||||
0% { | |||||
top: 10px; | |||||
left: 420px; | |||||
opacity: 0; | |||||
} | |||||
20% { | |||||
top: 40px; | |||||
left: 360px; | |||||
opacity: 1; | |||||
} | |||||
70% { | |||||
top: 130px; | |||||
left: 180px; | |||||
opacity: 1; | |||||
} | |||||
100% { | |||||
top: 160px; | |||||
left: 120px; | |||||
opacity: 0; | |||||
} | |||||
} | |||||
@keyframes cloudRight { | |||||
0% { | |||||
top: 100px; | |||||
left: 500px; | |||||
opacity: 0; | |||||
} | |||||
20% { | |||||
top: 120px; | |||||
left: 460px; | |||||
opacity: 1; | |||||
} | |||||
80% { | |||||
top: 180px; | |||||
left: 340px; | |||||
opacity: 1; | |||||
} | |||||
100% { | |||||
top: 200px; | |||||
left: 300px; | |||||
opacity: 0; | |||||
} | |||||
} | |||||
} | |||||
} | |||||
.bullshit { | |||||
position: relative; | |||||
float: left; | |||||
width: 300px; | |||||
padding: 30px 0; | |||||
overflow: hidden; | |||||
&__oops { | |||||
font-size: 32px; | |||||
font-weight: bold; | |||||
line-height: 40px; | |||||
color: #1482f0; | |||||
opacity: 0; | |||||
margin-bottom: 20px; | |||||
animation-name: slideUp; | |||||
animation-duration: 0.5s; | |||||
animation-fill-mode: forwards; | |||||
} | |||||
&__headline { | |||||
font-size: 20px; | |||||
line-height: 24px; | |||||
color: #222; | |||||
font-weight: bold; | |||||
opacity: 0; | |||||
margin-bottom: 10px; | |||||
animation-name: slideUp; | |||||
animation-duration: 0.5s; | |||||
animation-delay: 0.1s; | |||||
animation-fill-mode: forwards; | |||||
} | |||||
&__info { | |||||
font-size: 13px; | |||||
line-height: 21px; | |||||
color: grey; | |||||
opacity: 0; | |||||
margin-bottom: 30px; | |||||
animation-name: slideUp; | |||||
animation-duration: 0.5s; | |||||
animation-delay: 0.2s; | |||||
animation-fill-mode: forwards; | |||||
} | |||||
&__return-home { | |||||
display: block; | |||||
float: left; | |||||
width: 110px; | |||||
height: 36px; | |||||
background: #1482f0; | |||||
border-radius: 100px; | |||||
text-align: center; | |||||
color: #ffffff; | |||||
opacity: 0; | |||||
font-size: 14px; | |||||
line-height: 36px; | |||||
cursor: pointer; | |||||
animation-name: slideUp; | |||||
animation-duration: 0.5s; | |||||
animation-delay: 0.3s; | |||||
animation-fill-mode: forwards; | |||||
} | |||||
@keyframes slideUp { | |||||
0% { | |||||
transform: translateY(60px); | |||||
opacity: 0; | |||||
} | |||||
100% { | |||||
transform: translateY(0); | |||||
opacity: 1; | |||||
} | |||||
} | |||||
} | |||||
} | |||||
</style> |
@@ -0,0 +1,115 @@ | |||||
<!-- | |||||
* @Date: 2021-07-08 11:02:40 | |||||
* @LastEditTime: 2022-07-27 10:48:43 | |||||
* @Description: 文章列表 | |||||
--> | |||||
<template> | |||||
<div class="app-container"> | |||||
<!--工具栏--> | |||||
<div class="head-container"> | |||||
<div> | |||||
<!-- 搜索 --> | |||||
<el-input v-model="query.name" prefix-icon="el-icon-search" clearable placeholder="名称" class="filter-item" @keyup.enter.native="crud.toQuery" /> | |||||
<el-input v-model="query.releaseName" clearable placeholder="活动名称" class="filter-item"/> | |||||
<el-select v-model="query.list" clearable placeholder="列表" class="filter-item"> | |||||
<el-option v-for="item in releaseSourceList" :key="item.id" :label="item.name" :value="item.id"></el-option> | |||||
</el-select> | |||||
<el-date-picker v-model="query.times" value-format="yyyy-MM-dd HH:mm:ss" type="datetimerange" range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" class="date-item" style="width:300px !important; margin: 0 3px 10px 0;"></el-date-picker> | |||||
<rrOperation /> | |||||
</div> | |||||
<div> | |||||
<el-button class="filter-item" size="mini" type="primary" icon="el-icon-plus" @click="openSave()">新增</el-button> | |||||
<el-button class="filter-item" size="mini" type="warning" icon="el-icon-download" @click="exportFile">导出</el-button> | |||||
</div> | |||||
</div> | |||||
<!--表格渲染--> | |||||
<el-table ref="table" v-loading="crud.loading" :data="crud.data" style="width: 100%;"> | |||||
<el-table-column :show-overflow-tooltip="true" prop="id" label="活动编号" /> | |||||
<el-table-column :show-overflow-tooltip="true" prop="releaseName" label="活动名称" /> | |||||
<el-table-column :show-overflow-tooltip="true" prop="contentTitle" label="文章名称" /> | |||||
<el-table-column :show-overflow-tooltip="true" prop="releaseTime" label="发布时间" /> | |||||
<el-table-column :show-overflow-tooltip="true" prop="publisher" label="发布人" /> | |||||
<el-table-column label="操作" width="230" align="center" fixed="right"> | |||||
<template slot-scope="scope" class="btn"> | |||||
<el-button type="text" size="mini" @click="openSave(scope.row.id)">编辑</el-button> | |||||
<el-popover placement="top" width="200" :ref="`del-${scope.$index}`"> | |||||
<p>是否确认删除该数据?</p> | |||||
<div style="text-align: right; margin: 0"> | |||||
<el-button size="mini" @click="scope._self.$refs[`del-${scope.$index}`].doClose()">取消</el-button> | |||||
<el-button type="primary" size="mini" @click="del(scope)">确定</el-button> | |||||
</div> | |||||
<el-button slot="reference" type="text" size="mini" style="margin-left:10px">删除</el-button> | |||||
</el-popover> | |||||
</template> | |||||
</el-table-column> | |||||
</el-table> | |||||
<!--分页组件--> | |||||
<pagination /> | |||||
<!-- 新增修改弹窗 --> | |||||
<el-dialog v-if="dialogVisible" title="标题" :visible.sync="dialogVisible" append-to-body width="80%"> | |||||
<save-page :id='id' @close="close"></save-page> | |||||
</el-dialog> | |||||
</div> | |||||
</template> | |||||
<script> | |||||
import demoService from '@/api/nested/demo' | |||||
import CRUD, { presenter, header, crud } from '@crud/crud' | |||||
import rrOperation from '@crud/RR.operation' | |||||
import pagination from '@crud/Pagination' | |||||
import { downloadFile } from '@/utils/index' | |||||
import savePage from './save' | |||||
export default { | |||||
name: 'Demo', | |||||
components: { rrOperation, pagination, savePage }, | |||||
mixins: [presenter(), header(), crud()], | |||||
data() { | |||||
return { | |||||
releaseSourceList: [], | |||||
dialogVisible: false, | |||||
id: '' | |||||
} | |||||
}, | |||||
cruds() { | |||||
return CRUD({ title: '列表', url: 'xxx', query: {}}) | |||||
}, | |||||
methods: { | |||||
/* 刷新前操作 */ | |||||
[CRUD.HOOK.beforeRefresh]() { | |||||
this.query.id = 'xxx' | |||||
}, | |||||
openSave(id) { | |||||
this.id = id || '' | |||||
this.dialogVisible = true | |||||
}, | |||||
close(tag) { | |||||
if (tag) this.crud.toQuery() | |||||
this.dialogVisible = false | |||||
}, | |||||
/* 删除 */ | |||||
del(scope) { | |||||
demoService.del(scope.row.id).then(res => { | |||||
this.$message({ | |||||
message: '删除成功!', | |||||
type: 'success' | |||||
}) | |||||
this.crud.toQuery() | |||||
scope._self.$refs[`del-${scope.$index}`].doClose() | |||||
}) | |||||
}, | |||||
/* 导出 */ | |||||
exportFile() { | |||||
demoService.exportFile(this.crud.query).then(res => { | |||||
downloadFile(res, '文章列表', 'xlsx') | |||||
}) | |||||
} | |||||
} | |||||
} | |||||
</script> |
@@ -0,0 +1,112 @@ | |||||
<!-- | |||||
* @Date: 2021-10-11 14:12:26 | |||||
* @LastEditTime: 2022-07-27 10:48:47 | |||||
* @Description: 新增/编辑页面 | |||||
--> | |||||
<template> | |||||
<el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-width="100px" class="resetform"> | |||||
<el-form-item label="文章标题" prop="name"> | |||||
<el-input v-model="ruleForm.name"></el-input> | |||||
</el-form-item> | |||||
<el-form-item label="活动区域" prop="region"> | |||||
<el-select v-model="ruleForm.region" placeholder="请选择活动区域"> | |||||
<el-option label="区域一" value="shanghai"></el-option> | |||||
<el-option label="区域二" value="beijing"></el-option> | |||||
</el-select> | |||||
</el-form-item> | |||||
<el-form-item label="活动性质" prop="type"> | |||||
<el-checkbox-group v-model="ruleForm.type"> | |||||
<el-checkbox label="美食/餐厅线上活动" name="type"></el-checkbox> | |||||
<el-checkbox label="地推活动" name="type"></el-checkbox> | |||||
<el-checkbox label="线下主题活动" name="type"></el-checkbox> | |||||
<el-checkbox label="单纯品牌曝光" name="type"></el-checkbox> | |||||
</el-checkbox-group> | |||||
</el-form-item> | |||||
<el-form-item label="特殊资源" prop="resource"> | |||||
<el-radio-group v-model="ruleForm.resource"> | |||||
<el-radio label="线上品牌商赞助"></el-radio> | |||||
<el-radio label="线下场地免费"></el-radio> | |||||
</el-radio-group> | |||||
</el-form-item> | |||||
<el-form-item label="活动形式" prop="desc"> | |||||
<el-input type="textarea" v-model="ruleForm.desc"></el-input> | |||||
</el-form-item> | |||||
<el-form-item> | |||||
<el-button type="primary" :loading="loadingBtn" @click="submitForm('ruleForm')">立即创建</el-button> | |||||
<el-button @click="resetForm('ruleForm')">重置</el-button> | |||||
</el-form-item> | |||||
</el-form> | |||||
</template> | |||||
<script> | |||||
import demoService from '@/api/nested/demo' | |||||
export default { | |||||
props: ['id'], | |||||
data() { | |||||
return { | |||||
ruleForm: { | |||||
name: '', | |||||
region: '', | |||||
type: [], | |||||
resource: '', | |||||
desc: '' | |||||
}, | |||||
rules: { | |||||
name: [ | |||||
{ required: true, message: '请输入活动名称', trigger: 'blur' }, | |||||
{ min: 3, max: 5, message: '长度在 3 到 5 个字符', trigger: 'blur' } | |||||
], | |||||
region: [ | |||||
{ required: true, message: '请选择活动区域', trigger: 'change' } | |||||
], | |||||
type: [ | |||||
{ type: 'array', required: true, message: '请至少选择一个活动性质', trigger: 'change' } | |||||
], | |||||
resource: [ | |||||
{ required: true, message: '请选择活动资源', trigger: 'change' } | |||||
], | |||||
desc: [ | |||||
{ required: true, message: '请填写活动形式', trigger: 'blur' } | |||||
] | |||||
}, | |||||
loadingBtn: false | |||||
} | |||||
}, | |||||
created() { | |||||
if (this.id) { | |||||
this.getData() | |||||
} | |||||
}, | |||||
methods: { | |||||
getData() { | |||||
demoService.getData().then(res => { | |||||
this.ruleForm = res | |||||
}) | |||||
}, | |||||
submitForm(formName) { | |||||
this.$refs[formName].validate((valid) => { | |||||
if (valid) { | |||||
this.loadingBtn = true | |||||
demoService.save(this.ruleForm).then(res => { | |||||
this.$message({ | |||||
message: '操作成功!', | |||||
type: 'success' | |||||
}) | |||||
this.loadingBtn = false | |||||
this.$emit('close', 'save') | |||||
}).catch(() => { | |||||
this.loadingBtn = true | |||||
}) | |||||
} else { | |||||
console.log('error submit!!') | |||||
return false | |||||
} | |||||
}) | |||||
}, | |||||
resetForm(formName) { | |||||
this.$refs[formName].resetFields() | |||||
} | |||||
} | |||||
} | |||||
</script> |
@@ -0,0 +1,30 @@ | |||||
<template> | |||||
<div class="home"> | |||||
<img :src="imgUrl" alt=""> | |||||
</div> | |||||
</template> | |||||
<script> | |||||
import imgUrl from '@/assets/img/home.png' | |||||
export default { | |||||
name: 'Home', | |||||
data() { | |||||
return { | |||||
imgUrl | |||||
} | |||||
} | |||||
} | |||||
</script> | |||||
<style lang="scss" scoped> | |||||
.home { | |||||
height: calc(100vh - 50px); | |||||
display: flex; | |||||
align-items: center; | |||||
justify-content: center; | |||||
img { | |||||
width: 50%; | |||||
} | |||||
} | |||||
</style> |
@@ -0,0 +1,115 @@ | |||||
<!-- | |||||
* @Date: 2021-07-08 11:02:40 | |||||
* @LastEditTime: 2022-07-27 10:48:43 | |||||
* @Description: 广告位 | |||||
--> | |||||
<template> | |||||
<div class="app-container"> | |||||
<!--表格渲染--> | |||||
<el-table :data="data" style="width: 100%;"> | |||||
<el-table-column :show-overflow-tooltip="true" align="center" prop="link" label="链接" /> | |||||
<el-table-column :show-overflow-tooltip="true" align="center" prop="image" label="图片"> | |||||
<template slot-scope="scope"> | |||||
<img :src="scope.row.image" width="50px" /> | |||||
</template> | |||||
</el-table-column> | |||||
<el-table-column :show-overflow-tooltip="true" align="center" prop="newOpen" label="是否新开页面打开"> | |||||
<template slot-scope="scope"> | |||||
{{ scope.row.newOpen === 1 ? '是' : '否' }} | |||||
</template> | |||||
</el-table-column> | |||||
<el-table-column label="操作" width="230" align="center" fixed="right"> | |||||
<template slot-scope="scope" class="btn"> | |||||
<el-button type="text" size="mini" @click="openSave(scope.row)">编辑</el-button> | |||||
</template> | |||||
</el-table-column> | |||||
</el-table> | |||||
<!-- 新增修改弹窗 --> | |||||
<el-dialog v-if="dialogVisible" title="轮播" :visible.sync="dialogVisible" append-to-body width="600px"> | |||||
<el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-width="130px" class="resetform"> | |||||
<el-form-item label="图片" prop="image"> | |||||
<uploadFile :url.sync="ruleForm.image" type="image" /> | |||||
</el-form-item> | |||||
<el-form-item label="链接" prop="link"> | |||||
<el-input v-model="ruleForm.link"></el-input> | |||||
</el-form-item> | |||||
<el-form-item label="是否新开页面打开"> | |||||
<el-radio-group v-model="ruleForm.newOpen"> | |||||
<el-radio :label="1">是</el-radio> | |||||
<el-radio :label="0">否</el-radio> | |||||
</el-radio-group> | |||||
</el-form-item> | |||||
<el-form-item> | |||||
<el-button type="primary" @click="submitForm('ruleForm')">立即创建</el-button> | |||||
<el-button @click="dialogVisible = false">取消</el-button> | |||||
</el-form-item> | |||||
</el-form> | |||||
</el-dialog> | |||||
</div> | |||||
</template> | |||||
<script> | |||||
import { guanggao1, saveGuangGao } from '@/api/homePage' | |||||
import uploadFile from '@/components/uploadFile' | |||||
export default { | |||||
components: { | |||||
uploadFile | |||||
}, | |||||
data() { | |||||
return { | |||||
dialogVisible: false, | |||||
id: '', | |||||
data: [], | |||||
ruleForm: { | |||||
id: '', | |||||
image: '', | |||||
link: '', | |||||
newOpen: '1' | |||||
}, | |||||
rules: { | |||||
image: [ | |||||
{ required: true, message: '请上传图片', trigger: 'blur' } | |||||
], | |||||
link: [ | |||||
{ required: true, message: '请输入链接', trigger: 'blur' } | |||||
] | |||||
} | |||||
} | |||||
}, | |||||
created() { | |||||
this.getData() | |||||
}, | |||||
methods: { | |||||
getData() { | |||||
guanggao1().then(res => { | |||||
this.data = [res] | |||||
}) | |||||
}, | |||||
openSave(item) { | |||||
this.ruleForm.id = item.id | |||||
this.ruleForm.image = item.image | |||||
this.ruleForm.link = item.link | |||||
this.ruleForm.newOpen = item.newOpen | |||||
this.dialogVisible = true | |||||
}, | |||||
submitForm(formName) { | |||||
this.$refs[formName].validate((valid) => { | |||||
if (valid) { | |||||
saveGuangGao(this.ruleForm).then(res => { | |||||
this.$message({ | |||||
message: '操作成功!', | |||||
type: 'success' | |||||
}) | |||||
this.getData() | |||||
this.dialogVisible = false | |||||
}) | |||||
} else { | |||||
console.log('error submit!!') | |||||
return false | |||||
} | |||||
}) | |||||
} | |||||
} | |||||
} | |||||
</script> |
@@ -0,0 +1,115 @@ | |||||
<!-- | |||||
* @Date: 2021-07-08 11:02:40 | |||||
* @LastEditTime: 2022-07-27 10:48:43 | |||||
* @Description: 广告位 | |||||
--> | |||||
<template> | |||||
<div class="app-container"> | |||||
<!--表格渲染--> | |||||
<el-table :data="data" style="width: 100%;"> | |||||
<el-table-column :show-overflow-tooltip="true" align="center" prop="link" label="链接" /> | |||||
<el-table-column :show-overflow-tooltip="true" align="center" prop="image" label="图片"> | |||||
<template slot-scope="scope"> | |||||
<img :src="scope.row.image" width="50px" /> | |||||
</template> | |||||
</el-table-column> | |||||
<el-table-column :show-overflow-tooltip="true" align="center" prop="newOpen" label="是否新开页面打开"> | |||||
<template slot-scope="scope"> | |||||
{{ scope.row.newOpen === 1 ? '是' : '否' }} | |||||
</template> | |||||
</el-table-column> | |||||
<el-table-column label="操作" width="230" align="center" fixed="right"> | |||||
<template slot-scope="scope" class="btn"> | |||||
<el-button type="text" size="mini" @click="openSave(scope.row)">编辑</el-button> | |||||
</template> | |||||
</el-table-column> | |||||
</el-table> | |||||
<!-- 新增修改弹窗 --> | |||||
<el-dialog v-if="dialogVisible" title="轮播" :visible.sync="dialogVisible" append-to-body width="600px"> | |||||
<el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-width="130px" class="resetform"> | |||||
<el-form-item label="图片" prop="image"> | |||||
<uploadFile :url.sync="ruleForm.image" type="image" /> | |||||
</el-form-item> | |||||
<el-form-item label="链接" prop="link"> | |||||
<el-input v-model="ruleForm.link"></el-input> | |||||
</el-form-item> | |||||
<el-form-item label="是否新开页面打开"> | |||||
<el-radio-group v-model="ruleForm.newOpen"> | |||||
<el-radio :label="1">是</el-radio> | |||||
<el-radio :label="0">否</el-radio> | |||||
</el-radio-group> | |||||
</el-form-item> | |||||
<el-form-item> | |||||
<el-button type="primary" @click="submitForm('ruleForm')">立即创建</el-button> | |||||
<el-button @click="dialogVisible = false">取消</el-button> | |||||
</el-form-item> | |||||
</el-form> | |||||
</el-dialog> | |||||
</div> | |||||
</template> | |||||
<script> | |||||
import { guanggao2, saveGuangGao } from '@/api/homePage' | |||||
import uploadFile from '@/components/uploadFile' | |||||
export default { | |||||
components: { | |||||
uploadFile | |||||
}, | |||||
data() { | |||||
return { | |||||
dialogVisible: false, | |||||
id: '', | |||||
data: [], | |||||
ruleForm: { | |||||
id: '', | |||||
image: '', | |||||
link: '', | |||||
newOpen: '1' | |||||
}, | |||||
rules: { | |||||
image: [ | |||||
{ required: true, message: '请上传图片', trigger: 'blur' } | |||||
], | |||||
link: [ | |||||
{ required: true, message: '请输入链接', trigger: 'blur' } | |||||
] | |||||
} | |||||
} | |||||
}, | |||||
created() { | |||||
this.getData() | |||||
}, | |||||
methods: { | |||||
getData() { | |||||
guanggao2().then(res => { | |||||
this.data = [res] | |||||
}) | |||||
}, | |||||
openSave(item) { | |||||
this.ruleForm.id = item.id | |||||
this.ruleForm.image = item.image | |||||
this.ruleForm.link = item.link | |||||
this.ruleForm.newOpen = item.newOpen | |||||
this.dialogVisible = true | |||||
}, | |||||
submitForm(formName) { | |||||
this.$refs[formName].validate((valid) => { | |||||
if (valid) { | |||||
saveGuangGao(this.ruleForm).then(res => { | |||||
this.$message({ | |||||
message: '操作成功!', | |||||
type: 'success' | |||||
}) | |||||
this.getData() | |||||
this.dialogVisible = false | |||||
}) | |||||
} else { | |||||
console.log('error submit!!') | |||||
return false | |||||
} | |||||
}) | |||||
} | |||||
} | |||||
} | |||||
</script> |
@@ -0,0 +1,54 @@ | |||||
<!-- | |||||
* @Date: 2021-07-08 11:02:40 | |||||
* @LastEditTime: 2022-07-27 10:48:43 | |||||
* @Description: 列表查询demo | |||||
--> | |||||
<template> | |||||
<div class="app-container"> | |||||
<!--工具栏--> | |||||
<div class="head-container"> | |||||
<div> | |||||
<el-button class="filter-item" size="mini" type="primary" icon="el-icon-plus" | |||||
@click="openSave()">新增</el-button> | |||||
</div> | |||||
</div> | |||||
<!--表格渲染--> | |||||
<el-table ref="table" v-loading="crud.loading" :data="crud.data" style="width: 100%;" @selection-change="handleSelectionChange" | |||||
> | |||||
<el-table-column type="selection" width="55" /> | |||||
<el-table-column :show-overflow-tooltip="true" prop="newsId" label="序号" /> | |||||
<el-table-column :show-overflow-tooltip="true" prop="标题" label="newsTitle" /> | |||||
<el-table-column :show-overflow-tooltip="true" prop="createDate" label="创建时间" /> | |||||
</el-table> | |||||
<!--分页组件--> | |||||
<pagination /> | |||||
</div> | |||||
</template> | |||||
<script> | |||||
import carHotService from '@/api/index/carHot' | |||||
import CRUD, { presenter, header, crud } from '@crud/crud' | |||||
import pagination from '@crud/Pagination' | |||||
export default { | |||||
name: 'Demo', | |||||
components: { pagination }, | |||||
mixins: [presenter(), header(), crud()], | |||||
data() { | |||||
return { | |||||
releaseSourceList: [], | |||||
dialogVisible: false, | |||||
id: '', | |||||
multipleSelection: [] | |||||
} | |||||
}, | |||||
cruds() { | |||||
return CRUD({ title: '列表', url: '/index/carNewsHotList', query: {}}) | |||||
}, | |||||
methods: { | |||||
handleSelectionChange(val) { | |||||
this.multipleSelection = val | |||||
} | |||||
} | |||||
} | |||||
</script> |
@@ -0,0 +1,171 @@ | |||||
<!-- | |||||
* @Date: 2021-07-08 11:02:40 | |||||
* @LastEditTime: 2022-07-27 10:48:43 | |||||
* @Description: 列表查询demo | |||||
--> | |||||
<template> | |||||
<div class="app-container"> | |||||
<!--工具栏--> | |||||
<div class="head-container"> | |||||
<!-- <div> | |||||
搜索 | |||||
<el-input v-model="query.name" prefix-icon="el-icon-search" clearable placeholder="名称" class="filter-item" | |||||
@keyup.enter.native="crud.toQuery" /> | |||||
<rrOperation /> | |||||
</div> --> | |||||
<div> | |||||
<el-button class="filter-item" size="mini" type="primary" icon="el-icon-plus" | |||||
@click="openSave()">新增</el-button> | |||||
</div> | |||||
</div> | |||||
<!--表格渲染--> | |||||
<el-table ref="table" v-loading="crud.loading" :data="crud.data" style="width: 100%;"> | |||||
<el-table-column :show-overflow-tooltip="true" prop="serialNumber" label="序号" /> | |||||
<el-table-column :show-overflow-tooltip="true" prop="image" label="图片"> | |||||
<template slot-scope="scope"> | |||||
<img :src="scope.row.image" width="50px" /> | |||||
</template> | |||||
</el-table-column> | |||||
<el-table-column :show-overflow-tooltip="true" prop="link" label="链接" /> | |||||
<el-table-column :show-overflow-tooltip="true" prop="createDate" label="创建时间" /> | |||||
<el-table-column label="操作" width="230" align="center" fixed="right"> | |||||
<template slot-scope="scope" class="btn"> | |||||
<el-button type="text" size="mini" @click="openSave(scope.row)">编辑</el-button> | |||||
<el-popover placement="top" width="200" :ref="`del-${scope.$index}`"> | |||||
<p>是否确认删除该数据?</p> | |||||
<div style="text-align: right; margin: 0"> | |||||
<el-button size="mini" | |||||
@click="scope._self.$refs[`del-${scope.$index}`].doClose()">取消</el-button> | |||||
<el-button type="primary" size="mini" @click="del(scope)">确定</el-button> | |||||
</div> | |||||
<el-button slot="reference" type="text" size="mini" style="margin-left:10px">删除</el-button> | |||||
</el-popover> | |||||
</template> | |||||
</el-table-column> | |||||
</el-table> | |||||
<!--分页组件--> | |||||
<pagination /> | |||||
<!-- 新增修改弹窗 --> | |||||
<el-dialog v-if="dialogVisible" title="轮播" :visible.sync="dialogVisible" append-to-body width="600px"> | |||||
<el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-width="130px" class="resetform"> | |||||
<el-form-item label="活动名称" prop="image"> | |||||
<uploadFile :url.sync="ruleForm.image" type="image" /> | |||||
</el-form-item> | |||||
<el-form-item label="序号" prop="serialNumber"> | |||||
<el-input v-model="ruleForm.serialNumber"></el-input> | |||||
</el-form-item> | |||||
<el-form-item label="链接" prop="link"> | |||||
<el-input v-model="ruleForm.link"></el-input> | |||||
</el-form-item> | |||||
<el-form-item label="是否新开页面打开"> | |||||
<el-radio-group v-model="ruleForm.newOpen"> | |||||
<el-radio label="1">是</el-radio> | |||||
<el-radio label="0">否</el-radio> | |||||
</el-radio-group> | |||||
</el-form-item> | |||||
<el-form-item> | |||||
<el-button type="primary" :loading="loadingBtn" @click="submitForm('ruleForm')">立即创建</el-button> | |||||
<el-button @click="resetForm('ruleForm')">重置</el-button> | |||||
</el-form-item> | |||||
</el-form> | |||||
</el-dialog> | |||||
</div> | |||||
</template> | |||||
<script> | |||||
import { delLunbo, saveLunbo } from '@/api/homePage' | |||||
import CRUD, { presenter, header, crud } from '@crud/crud' | |||||
import pagination from '@crud/Pagination' | |||||
import uploadFile from '@/components/uploadFile' | |||||
export default { | |||||
components: { pagination, uploadFile }, | |||||
mixins: [presenter(), header(), crud()], | |||||
data() { | |||||
return { | |||||
releaseSourceList: [], | |||||
dialogVisible: false, | |||||
id: '', | |||||
ruleForm: { | |||||
image: '', | |||||
serialNumber: '', | |||||
link: [], | |||||
newOpen: '1' | |||||
}, | |||||
rules: { | |||||
name: [ | |||||
{ required: true, message: '请上传图片', trigger: 'blur' } | |||||
], | |||||
serialNumber: [ | |||||
{ required: true, message: '请输入序号', trigger: 'blur' } | |||||
], | |||||
link: [ | |||||
{ required: true, message: '请输入链接', trigger: 'blur' } | |||||
], | |||||
desc: [ | |||||
{ required: true, message: '请填写活动形式', trigger: 'blur' } | |||||
] | |||||
}, | |||||
loadingBtn: false | |||||
} | |||||
}, | |||||
cruds() { | |||||
return CRUD({ title: '列表', url: '/index/lunboList', query: {}}) | |||||
}, | |||||
methods: { | |||||
openSave(item) { | |||||
if (item) { | |||||
this.ruleForm.id = item.id | |||||
this.ruleForm.image = item.image | |||||
this.ruleForm.serialNumber = item.serialNumber | |||||
this.ruleForm.link = item.link | |||||
this.ruleForm.newOpen = item.newOpen || '1' | |||||
} | |||||
this.dialogVisible = true | |||||
}, | |||||
close(tag) { | |||||
if (tag) this.crud.toQuery() | |||||
this.dialogVisible = false | |||||
}, | |||||
/* 删除 */ | |||||
del(scope) { | |||||
delLunbo(scope.row.id).then(res => { | |||||
this.$message({ | |||||
message: '删除成功!', | |||||
type: 'success' | |||||
}) | |||||
this.crud.toQuery() | |||||
scope._self.$refs[`del-${scope.$index}`].doClose() | |||||
}) | |||||
}, | |||||
submitForm(formName) { | |||||
this.$refs[formName].validate((valid) => { | |||||
if (valid) { | |||||
this.loadingBtn = true | |||||
saveLunbo(this.ruleForm).then(res => { | |||||
this.$message({ | |||||
message: '操作成功!', | |||||
type: 'success' | |||||
}) | |||||
this.loadingBtn = false | |||||
this.dialogVisible = false | |||||
this.$refs[formName].resetFields() | |||||
this.crud.toQuery() | |||||
}).catch(() => { | |||||
this.loadingBtn = false | |||||
}) | |||||
} else { | |||||
console.log('error submit!!') | |||||
return false | |||||
} | |||||
}) | |||||
}, | |||||
resetForm(formName) { | |||||
this.$refs[formName].resetFields() | |||||
} | |||||
} | |||||
} | |||||
</script> |
@@ -0,0 +1,118 @@ | |||||
<!-- | |||||
* @Date: 2021-10-11 14:12:26 | |||||
* @LastEditTime: 2022-07-27 10:48:47 | |||||
* @Description: 新增/编辑页面 | |||||
--> | |||||
<template> | |||||
<div class="app-container"> | |||||
<el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-width="100px" class="resetform"> | |||||
<el-form-item label="是否显示"> | |||||
<el-radio-group v-model="ruleForm.isShow"> | |||||
<el-radio label="1">是</el-radio> | |||||
<el-radio label="0">否</el-radio> | |||||
</el-radio-group> | |||||
</el-form-item> | |||||
<el-card style="margin-top:20px" class="suIbtemCard" v-for="(item, index) in imgList" :key="index"> | |||||
<el-form-item label="上传视频"> | |||||
<uploadFile :url.sync="item.imgSrc" type="image"/> | |||||
<el-input v-model="item.imgSrc"></el-input> | |||||
</el-form-item> | |||||
<el-form-item label="标题"> | |||||
<el-input v-model="item.title"></el-input> | |||||
</el-form-item> | |||||
<el-form-item label="描述"> | |||||
<el-input v-model="item.desc"></el-input> | |||||
</el-form-item> | |||||
<el-form-item label="跳转链接"> | |||||
<el-input v-model="item.link"></el-input> | |||||
</el-form-item> | |||||
<el-form-item> | |||||
<el-button type="success" icon="el-icon-plus" circle v-if="imgList.length - 1 === index" @click="addItem"></el-button> | |||||
<el-button type="danger" icon="el-icon-delete" circle @click="imgList.length > 1 && delItem(item, index)"></el-button> | |||||
</el-form-item> | |||||
</el-card> | |||||
<el-form-item> | |||||
<el-button type="primary" :loading="loadingBtn" @click="submitForm('ruleForm')">保存</el-button> | |||||
</el-form-item> | |||||
</el-form> | |||||
</div> | |||||
</template> | |||||
<script> | |||||
import demoService from '@/api/nested/demo' | |||||
import uploadFile from '@/components/uploadFile' | |||||
export default { | |||||
components: { | |||||
uploadFile | |||||
}, | |||||
data() { | |||||
return { | |||||
imgList: [ | |||||
{ imgSrc: '', link: '', title: '', desc: '' } | |||||
], | |||||
ruleForm: { | |||||
name: '', | |||||
region: '', | |||||
type: [], | |||||
resource: '', | |||||
desc: '' | |||||
}, | |||||
rules: { | |||||
name: [ | |||||
{ required: true, message: '请输入活动名称', trigger: 'blur' }, | |||||
{ min: 3, max: 5, message: '长度在 3 到 5 个字符', trigger: 'blur' } | |||||
], | |||||
region: [ | |||||
{ required: true, message: '请选择活动区域', trigger: 'change' } | |||||
], | |||||
type: [ | |||||
{ type: 'array', required: true, message: '请至少选择一个活动性质', trigger: 'change' } | |||||
], | |||||
resource: [ | |||||
{ required: true, message: '请选择活动资源', trigger: 'change' } | |||||
], | |||||
desc: [ | |||||
{ required: true, message: '请填写活动形式', trigger: 'blur' } | |||||
] | |||||
}, | |||||
loadingBtn: false | |||||
} | |||||
}, | |||||
created() { | |||||
if (this.id) { | |||||
this.getData() | |||||
} | |||||
}, | |||||
methods: { | |||||
addItem() { | |||||
this.imgList.push({ imgSrc: '', link: '', title: '', desc: '' }) | |||||
}, | |||||
delItem(item, index) { | |||||
this.imgList.splice(index, 1) | |||||
}, | |||||
submitForm(formName) { | |||||
this.$refs[formName].validate((valid) => { | |||||
if (valid) { | |||||
this.loadingBtn = true | |||||
demoService.save(this.ruleForm).then(res => { | |||||
this.$message({ | |||||
message: '操作成功!', | |||||
type: 'success' | |||||
}) | |||||
this.loadingBtn = false | |||||
this.$emit('close', 'save') | |||||
}).catch(() => { | |||||
this.loadingBtn = true | |||||
}) | |||||
} else { | |||||
console.log('error submit!!') | |||||
return false | |||||
} | |||||
}) | |||||
}, | |||||
resetForm(formName) { | |||||
this.$refs[formName].resetFields() | |||||
} | |||||
} | |||||
} | |||||
</script> |
@@ -0,0 +1,289 @@ | |||||
<template> | |||||
<div class='login-container'> | |||||
<div class='Version'>Version 1.0.8 test</div> | |||||
<div class='videoBox'> | |||||
<video src='https://video.metavatar.cc/sv/4aaaaa5e-18832dbb5e6/4aaaaa5e-18832dbb5e6.mp4' muted autoplay | |||||
loop></video> | |||||
</div> | |||||
<el-form ref='loginFormRef' :model='loginData' class='login-form'> | |||||
<el-form-item prop='username'> | |||||
<div class='text-white'> | |||||
<i class="el-icon-user"></i> | |||||
</div> | |||||
<el-input class='flex-1' ref='email' size='large' v-model='loginData.username' placeholder='用户名' | |||||
name='email' /> | |||||
</el-form-item> | |||||
<el-form-item prop='password'> | |||||
<span class='text-white'> | |||||
<i class="el-icon-lock"></i> | |||||
</span> | |||||
<el-input class='flex-1' v-model='loginData.password' placeholder='密码' | |||||
:type="passwordVisible === false ? 'password' : 'input'" size='large' name='password' | |||||
@keyup.enter='handleLogin' /> | |||||
<span class='mr-2' @click='passwordVisible = !passwordVisible'> | |||||
</span> | |||||
</el-form-item> | |||||
<!-- 验证码 --> | |||||
<el-form-item prop='captcha'> | |||||
<span class='text-white'> | |||||
<i class="el-icon-key"></i> | |||||
</span> | |||||
<el-input v-model='loginData.captcha' auto-complete='off' placeholder='验证码' class='flex-1' | |||||
@keyup.enter='handleLogin' /> | |||||
<span class='captcha'> | |||||
<img v-if='showCodeImg' :src='verifyCodeUrl' @click='getCode' /> | |||||
</span> | |||||
</el-form-item> | |||||
<el-button size='default' :loading='loading' type='primary' class='loginBtn' @click.prevent='handleLogin'> | |||||
登录 | |||||
</el-button> | |||||
</el-form> | |||||
</div> | |||||
</template> | |||||
<script> | |||||
import { | |||||
loginApi | |||||
} from '@/api/login' | |||||
export default { | |||||
data() { | |||||
return { | |||||
isLogin: 1, | |||||
passwordVisible: false, | |||||
loading: false, | |||||
loginData: { | |||||
username: '', | |||||
password: '', | |||||
captcha: '' | |||||
}, | |||||
showCodeImg: true, | |||||
isLogining: true, | |||||
verifyCodeUrl: process.env.VUE_APP_BASE_API + '/captcha.jpg' | |||||
} | |||||
}, | |||||
methods: { | |||||
handleLogin() { | |||||
loginApi(this.loginData).then(res => { | |||||
this.$router.push({ | |||||
path: '/homePage/carousel' | |||||
}) | |||||
}) | |||||
}, | |||||
getCode() { | |||||
this.verifyCodeUrl = process.env.VUE_APP_BASE_API + '/captcha.jpg?time=' + Math.random() | |||||
} | |||||
} | |||||
} | |||||
</script> | |||||
<style lang='scss' scoped> | |||||
.login-container { | |||||
position: relative; | |||||
width: 100%; | |||||
// width: 1920px; | |||||
// height: 1080px; | |||||
min-height: 100%; | |||||
overflow: hidden; | |||||
// background-color: #2d3a4b; | |||||
.Version { | |||||
position: absolute; | |||||
bottom: 20px; | |||||
right: 20px; | |||||
color: #ffffff; | |||||
} | |||||
.login-form { | |||||
position: absolute; | |||||
top: 46%; | |||||
left: 10%; | |||||
width: 520px; | |||||
max-width: 100%; | |||||
padding: 20px; | |||||
overflow: hidden; | |||||
backdrop-filter: blur(3px); | |||||
background: linear-gradient(123.64deg, | |||||
rgba(188, 193, 196, 0.1) -22.71%, | |||||
rgba(255, 255, 255, 0.039) 70.04%); | |||||
border-radius: 20px; | |||||
transform: translateY(-50%); | |||||
border: 1px solid #cacaca; | |||||
transition: all 0.15s; | |||||
.captcha { | |||||
position: absolute; | |||||
top: 0; | |||||
right: 0; | |||||
img { | |||||
width: 120px; | |||||
height: 48px; | |||||
cursor: pointer; | |||||
} | |||||
.sendSMSBtn { | |||||
width: 120px; | |||||
height: 48px; | |||||
line-height: 48px; | |||||
color: #fff; | |||||
text-align: center; | |||||
background: linear-gradient(270deg, #4b61dc, #15d1b8); | |||||
} | |||||
} | |||||
&:hover { | |||||
backdrop-filter: blur(7px); | |||||
} | |||||
.items-center { | |||||
padding: 20px; | |||||
font-size: 30px; | |||||
} | |||||
.el-form-item { | |||||
.text-white { | |||||
padding: 8px; | |||||
i{ | |||||
color:#fff; | |||||
font-size: 20px; | |||||
} | |||||
} | |||||
::v-deep .el-form-item__content{ | |||||
display: flex; | |||||
} | |||||
::v-deep .el-input .el-input__inner { | |||||
color: #fff; | |||||
background: transparent; | |||||
border: 0; | |||||
border-radius: 0; | |||||
caret-color: #fff; | |||||
} | |||||
} | |||||
} | |||||
} | |||||
.loginBtn { | |||||
width: 100%; | |||||
height: 40px; | |||||
background: linear-gradient(270deg, #4b61dc, #15d1b8); | |||||
transition: all 0.3s; | |||||
&:hover { | |||||
transform: scale(1.02); | |||||
} | |||||
cursor: pointer; | |||||
} | |||||
.bottomBtnCN { | |||||
display: flex; | |||||
justify-content: space-between; | |||||
align-items: center; | |||||
padding: 0 10px; | |||||
color: #fff; | |||||
text-align: center; | |||||
margin-top: 20px; | |||||
.join { | |||||
cursor: pointer; | |||||
border-bottom: 2px solid #ffffff00; | |||||
padding: 0 3px; | |||||
padding-bottom: 2px; | |||||
transition: all 0.3s; | |||||
&:hover { | |||||
border-bottom: 2px solid #ffffff; | |||||
} | |||||
} | |||||
} | |||||
.bottomBtnEN { | |||||
padding: 0 10px; | |||||
color: #fff; | |||||
text-align: center; | |||||
margin-top: 20px; | |||||
.join { | |||||
cursor: pointer; | |||||
border-bottom: 2px solid #ffffff00; | |||||
padding: 0 3px; | |||||
padding-bottom: 2px; | |||||
transition: all 0.3s; | |||||
&:hover { | |||||
border-bottom: 2px solid #ffffff; | |||||
} | |||||
} | |||||
} | |||||
.videoBox { | |||||
position: absolute; | |||||
top: 0; | |||||
left: 0; | |||||
width: 100%; | |||||
height: 100%; | |||||
z-index: -1; | |||||
video { | |||||
width: 100%; | |||||
height: 100%; | |||||
object-fit: fill; | |||||
} | |||||
} | |||||
.el-form-item { | |||||
background: rgb(0 0 0 / 10%); | |||||
border: 1px solid #ffffff2d; | |||||
border-radius: 5px; | |||||
} | |||||
.el-input { | |||||
background: transparent; | |||||
// 子组件 scoped 无效,使用 :deep | |||||
:deep(.el-input__wrapper) { | |||||
padding: 0; | |||||
background: transparent; | |||||
box-shadow: none; | |||||
.el-input__inner { | |||||
color: #fff; | |||||
background: transparent; | |||||
border: 0; | |||||
border-radius: 0; | |||||
caret-color: #fff; | |||||
&:-webkit-autofill { | |||||
box-shadow: 0 0 0 1000px transparent inset !important; | |||||
-webkit-text-fill-color: #fff !important; | |||||
} | |||||
// 设置输入框自动填充的延迟属性 | |||||
&:-webkit-autofill, | |||||
&:-webkit-autofill:hover, | |||||
&:-webkit-autofill:focus, | |||||
&:-webkit-autofill:active { | |||||
transition: color 99999s ease-out, background-color 99999s ease-out; | |||||
transition-delay: 99999s; | |||||
} | |||||
} | |||||
} | |||||
} | |||||
@keyframes rotate { | |||||
from { | |||||
transform: rotate(0deg); | |||||
} | |||||
to { | |||||
transform: rotate(360deg); | |||||
} | |||||
} | |||||
</style> |
@@ -0,0 +1,115 @@ | |||||
<!-- | |||||
* @Date: 2021-07-08 11:02:40 | |||||
* @LastEditTime: 2022-07-27 10:48:43 | |||||
* @Description: 列表查询demo | |||||
--> | |||||
<template> | |||||
<div class="app-container"> | |||||
<!--工具栏--> | |||||
<div class="head-container"> | |||||
<div> | |||||
<!-- 搜索 --> | |||||
<el-input v-model="query.name" prefix-icon="el-icon-search" clearable placeholder="名称" class="filter-item" @keyup.enter.native="crud.toQuery" /> | |||||
<el-input v-model="query.releaseName" clearable placeholder="活动名称" class="filter-item"/> | |||||
<el-select v-model="query.list" clearable placeholder="列表" class="filter-item"> | |||||
<el-option v-for="item in releaseSourceList" :key="item.id" :label="item.name" :value="item.id"></el-option> | |||||
</el-select> | |||||
<el-date-picker v-model="query.times" value-format="yyyy-MM-dd HH:mm:ss" type="datetimerange" range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" class="date-item" style="width:300px !important; margin: 0 3px 10px 0;"></el-date-picker> | |||||
<rrOperation /> | |||||
</div> | |||||
<div> | |||||
<el-button class="filter-item" size="mini" type="primary" icon="el-icon-plus" @click="openSave()">新增</el-button> | |||||
<el-button class="filter-item" size="mini" type="warning" icon="el-icon-download" @click="exportFile">导出</el-button> | |||||
</div> | |||||
</div> | |||||
<!--表格渲染--> | |||||
<el-table ref="table" v-loading="crud.loading" :data="crud.data" style="width: 100%;"> | |||||
<el-table-column :show-overflow-tooltip="true" prop="id" label="活动编号" /> | |||||
<el-table-column :show-overflow-tooltip="true" prop="releaseName" label="活动名称" /> | |||||
<el-table-column :show-overflow-tooltip="true" prop="contentTitle" label="文章名称" /> | |||||
<el-table-column :show-overflow-tooltip="true" prop="releaseTime" label="发布时间" /> | |||||
<el-table-column :show-overflow-tooltip="true" prop="publisher" label="发布人" /> | |||||
<el-table-column label="操作" width="230" align="center" fixed="right"> | |||||
<template slot-scope="scope" class="btn"> | |||||
<el-button type="text" size="mini" @click="openSave(scope.row.id)">编辑</el-button> | |||||
<el-popover placement="top" width="200" :ref="`del-${scope.$index}`"> | |||||
<p>是否确认删除该数据?</p> | |||||
<div style="text-align: right; margin: 0"> | |||||
<el-button size="mini" @click="scope._self.$refs[`del-${scope.$index}`].doClose()">取消</el-button> | |||||
<el-button type="primary" size="mini" @click="del(scope)">确定</el-button> | |||||
</div> | |||||
<el-button slot="reference" type="text" size="mini" style="margin-left:10px">删除</el-button> | |||||
</el-popover> | |||||
</template> | |||||
</el-table-column> | |||||
</el-table> | |||||
<!--分页组件--> | |||||
<pagination /> | |||||
<!-- 新增修改弹窗 --> | |||||
<el-dialog v-if="dialogVisible" title="标题" :visible.sync="dialogVisible" append-to-body width="500px"> | |||||
<save-page :id='id' @close="close"></save-page> | |||||
</el-dialog> | |||||
</div> | |||||
</template> | |||||
<script> | |||||
import demoService from '@/api/nested/demo' | |||||
import CRUD, { presenter, header, crud } from '@crud/crud' | |||||
import rrOperation from '@crud/RR.operation' | |||||
import pagination from '@crud/Pagination' | |||||
import { downloadFile } from '@/utils/index' | |||||
import savePage from './save' | |||||
export default { | |||||
name: 'Demo', | |||||
components: { rrOperation, pagination, savePage }, | |||||
mixins: [presenter(), header(), crud()], | |||||
data() { | |||||
return { | |||||
releaseSourceList: [], | |||||
dialogVisible: false, | |||||
id: '' | |||||
} | |||||
}, | |||||
cruds() { | |||||
return CRUD({ title: '列表', url: 'xxx', query: {}}) | |||||
}, | |||||
methods: { | |||||
/* 刷新前操作 */ | |||||
[CRUD.HOOK.beforeRefresh]() { | |||||
this.query.id = 'xxx' | |||||
}, | |||||
openSave(id) { | |||||
this.id = id || '' | |||||
this.dialogVisible = true | |||||
}, | |||||
close(tag) { | |||||
if (tag) this.crud.toQuery() | |||||
this.dialogVisible = false | |||||
}, | |||||
/* 删除 */ | |||||
del(scope) { | |||||
demoService.del(scope.row.id).then(res => { | |||||
this.$message({ | |||||
message: '删除成功!', | |||||
type: 'success' | |||||
}) | |||||
this.crud.toQuery() | |||||
scope._self.$refs[`del-${scope.$index}`].doClose() | |||||
}) | |||||
}, | |||||
/* 导出 */ | |||||
exportFile() { | |||||
demoService.exportFile(this.crud.query).then(res => { | |||||
downloadFile(res, '文章列表', 'xlsx') | |||||
}) | |||||
} | |||||
} | |||||
} | |||||
</script> |
@@ -0,0 +1,112 @@ | |||||
<!-- | |||||
* @Date: 2021-10-11 14:12:26 | |||||
* @LastEditTime: 2022-07-27 10:48:47 | |||||
* @Description: 新增/编辑页面 | |||||
--> | |||||
<template> | |||||
<el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-width="100px" class="resetform"> | |||||
<el-form-item label="活动名称" prop="name"> | |||||
<el-input v-model="ruleForm.name"></el-input> | |||||
</el-form-item> | |||||
<el-form-item label="活动区域" prop="region"> | |||||
<el-select v-model="ruleForm.region" placeholder="请选择活动区域"> | |||||
<el-option label="区域一" value="shanghai"></el-option> | |||||
<el-option label="区域二" value="beijing"></el-option> | |||||
</el-select> | |||||
</el-form-item> | |||||
<el-form-item label="活动性质" prop="type"> | |||||
<el-checkbox-group v-model="ruleForm.type"> | |||||
<el-checkbox label="美食/餐厅线上活动" name="type"></el-checkbox> | |||||
<el-checkbox label="地推活动" name="type"></el-checkbox> | |||||
<el-checkbox label="线下主题活动" name="type"></el-checkbox> | |||||
<el-checkbox label="单纯品牌曝光" name="type"></el-checkbox> | |||||
</el-checkbox-group> | |||||
</el-form-item> | |||||
<el-form-item label="特殊资源" prop="resource"> | |||||
<el-radio-group v-model="ruleForm.resource"> | |||||
<el-radio label="线上品牌商赞助"></el-radio> | |||||
<el-radio label="线下场地免费"></el-radio> | |||||
</el-radio-group> | |||||
</el-form-item> | |||||
<el-form-item label="活动形式" prop="desc"> | |||||
<el-input type="textarea" v-model="ruleForm.desc"></el-input> | |||||
</el-form-item> | |||||
<el-form-item> | |||||
<el-button type="primary" :loading="loadingBtn" @click="submitForm('ruleForm')">立即创建</el-button> | |||||
<el-button @click="resetForm('ruleForm')">重置</el-button> | |||||
</el-form-item> | |||||
</el-form> | |||||
</template> | |||||
<script> | |||||
import demoService from '@/api/nested/demo' | |||||
export default { | |||||
props: ['id'], | |||||
data() { | |||||
return { | |||||
ruleForm: { | |||||
name: '', | |||||
region: '', | |||||
type: [], | |||||
resource: '', | |||||
desc: '' | |||||
}, | |||||
rules: { | |||||
name: [ | |||||
{ required: true, message: '请输入活动名称', trigger: 'blur' }, | |||||
{ min: 3, max: 5, message: '长度在 3 到 5 个字符', trigger: 'blur' } | |||||
], | |||||
region: [ | |||||
{ required: true, message: '请选择活动区域', trigger: 'change' } | |||||
], | |||||
type: [ | |||||
{ type: 'array', required: true, message: '请至少选择一个活动性质', trigger: 'change' } | |||||
], | |||||
resource: [ | |||||
{ required: true, message: '请选择活动资源', trigger: 'change' } | |||||
], | |||||
desc: [ | |||||
{ required: true, message: '请填写活动形式', trigger: 'blur' } | |||||
] | |||||
}, | |||||
loadingBtn: false | |||||
} | |||||
}, | |||||
created() { | |||||
if (this.id) { | |||||
this.getData() | |||||
} | |||||
}, | |||||
methods: { | |||||
getData() { | |||||
demoService.getData().then(res => { | |||||
this.ruleForm = res | |||||
}) | |||||
}, | |||||
submitForm(formName) { | |||||
this.$refs[formName].validate((valid) => { | |||||
if (valid) { | |||||
this.loadingBtn = true | |||||
demoService.save(this.ruleForm).then(res => { | |||||
this.$message({ | |||||
message: '操作成功!', | |||||
type: 'success' | |||||
}) | |||||
this.loadingBtn = false | |||||
this.$emit('close', 'save') | |||||
}).catch(() => { | |||||
this.loadingBtn = true | |||||
}) | |||||
} else { | |||||
console.log('error submit!!') | |||||
return false | |||||
} | |||||
}) | |||||
}, | |||||
resetForm(formName) { | |||||
this.$refs[formName].resetFields() | |||||
} | |||||
} | |||||
} | |||||
</script> |
@@ -0,0 +1,7 @@ | |||||
<template> | |||||
<div style="padding:30px;"> | |||||
<el-alert :closable="false" title="menu 1"> | |||||
<router-view /> | |||||
</el-alert> | |||||
</div> | |||||
</template> |
@@ -0,0 +1,7 @@ | |||||
<template> | |||||
<div style="padding:30px;"> | |||||
<el-alert :closable="false" title="menu 1-1" type="success"> | |||||
<router-view /> | |||||
</el-alert> | |||||
</div> | |||||
</template> |
@@ -0,0 +1,7 @@ | |||||
<template> | |||||
<div style="padding:30px;"> | |||||
<el-alert :closable="false" title="menu 1-2" type="success"> | |||||
<router-view /> | |||||
</el-alert> | |||||
</div> | |||||
</template> |
@@ -0,0 +1,5 @@ | |||||
<template functional> | |||||
<div style="padding:30px;"> | |||||
<el-alert :closable="false" title="menu 1-2-1" type="warning" /> | |||||
</div> | |||||
</template> |
@@ -0,0 +1,5 @@ | |||||
<template functional> | |||||
<div style="padding:30px;"> | |||||
<el-alert :closable="false" title="menu 1-2-2" type="warning" /> | |||||
</div> | |||||
</template> |
@@ -0,0 +1,5 @@ | |||||
<template functional> | |||||
<div style="padding:30px;"> | |||||
<el-alert :closable="false" title="menu 1-3" type="success" /> | |||||
</div> | |||||
</template> |
@@ -0,0 +1,126 @@ | |||||
'use strict' | |||||
const path = require('path') | |||||
const defaultSettings = require('./src/settings.js') | |||||
function resolve(dir) { | |||||
return path.join(__dirname, dir) | |||||
} | |||||
const title = defaultSettings.title || '后台管理系统' // page title | |||||
// If your port is set to 80, | |||||
// use administrator privileges to execute the command line. | |||||
// For example, Mac: sudo npm run | |||||
// You can change the port by the following methods: | |||||
// port = 9528 npm run dev OR npm run dev --port = 9528 | |||||
const port = 8011 // dev port | |||||
const { name } = require('./package') | |||||
// All configuration item explanations can be find in https://cli.vuejs.org/config/ | |||||
module.exports = { | |||||
/** | |||||
* You will need to set publicPath if you plan to deploy your site under a sub path, | |||||
* for example GitHub Pages. If you plan to deploy your site to https://foo.github.io/bar/, | |||||
* then publicPath should be set to "/bar/". | |||||
* In most cases please use '/' !!! | |||||
* Detail: https://cli.vuejs.org/config/#publicpath | |||||
*/ | |||||
publicPath: process.env.NODE_ENV === 'development' ? '/' : '/admin', | |||||
outputDir: process.env.OUTPUT_DIR, | |||||
assetsDir: 'static', | |||||
lintOnSave: process.env.NODE_ENV === 'development', | |||||
productionSourceMap: false, | |||||
devServer: { | |||||
port: port, | |||||
headers: { | |||||
'Access-Control-Allow-Origin': '*' | |||||
}, | |||||
open: true, | |||||
overlay: { | |||||
warnings: false, | |||||
errors: true | |||||
}, | |||||
proxy: { | |||||
'/api': { | |||||
target: process.env.VUE_APP_BASE_API, | |||||
changeOrigin: true, | |||||
pathRewrite: { | |||||
'^/api': 'api' | |||||
} | |||||
} | |||||
} | |||||
}, | |||||
configureWebpack: { | |||||
devtool: 'source map', | |||||
// provide the app's title in webpack's name field, so that | |||||
// it can be accessed in index.html to inject the correct title. | |||||
name: title, | |||||
resolve: { | |||||
alias: { | |||||
'@': resolve('src'), | |||||
'@crud': resolve('src/components/Crud') | |||||
} | |||||
}, | |||||
output: { | |||||
// 把子应用打包成 umd 库格式 | |||||
library: `${name}-[name]`, | |||||
libraryTarget: 'umd', | |||||
jsonpFunction: `webpackJsonp_${name}` | |||||
} | |||||
}, | |||||
chainWebpack(config) { | |||||
// it can improve the speed of the first screen, it is recommended to turn on preload | |||||
config.plugin('preload').tap(() => [ | |||||
{ | |||||
rel: 'preload', | |||||
// to ignore runtime.js | |||||
// https://github.com/vuejs/vue-cli/blob/dev/packages/@vue/cli-service/lib/config/app.js#L171 | |||||
fileBlacklist: [/\.map$/, /hot-update\.js$/, /runtime\..*\.js$/], | |||||
include: 'initial' | |||||
} | |||||
]) | |||||
// when there are many pages, it will cause too many meaningless requests | |||||
config.plugins.delete('prefetch') | |||||
config | |||||
.when(process.env.NODE_ENV !== 'development', | |||||
config => { | |||||
config | |||||
.plugin('ScriptExtHtmlWebpackPlugin') | |||||
.after('html') | |||||
.use('script-ext-html-webpack-plugin', [{ | |||||
// `runtime` must same as runtimeChunk name. default is `runtime` | |||||
inline: /runtime\..*\.js$/ | |||||
}]) | |||||
.end() | |||||
config | |||||
.optimization.splitChunks({ | |||||
chunks: 'all', | |||||
cacheGroups: { | |||||
libs: { | |||||
name: 'chunk-libs', | |||||
test: /[\\/]node_modules[\\/]/, | |||||
priority: 10, | |||||
chunks: 'initial' // only package third parties that are initially dependent | |||||
}, | |||||
elementUI: { | |||||
name: 'chunk-elementUI', // split elementUI into a single package | |||||
priority: 20, // the weight needs to be larger than libs and app or it will be packaged into libs or app | |||||
test: /[\\/]node_modules[\\/]_?element-ui(.*)/ // in order to adapt to cnpm | |||||
}, | |||||
commons: { | |||||
name: 'chunk-commons', | |||||
test: resolve('src/components'), // can customize your rules | |||||
minChunks: 3, // minimum common number | |||||
priority: 5, | |||||
reuseExistingChunk: true | |||||
} | |||||
} | |||||
}) | |||||
// https:// webpack.js.org/configuration/optimization/#optimizationruntimechunk | |||||
config.optimization.runtimeChunk('single') | |||||
} | |||||
) | |||||
} | |||||
} |