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