├── .babelrc ├── .editorconfig ├── .gitignore ├── .postcssrc.js ├── README.md ├── build ├── build.js ├── check-versions.js ├── logo.png ├── utils.js ├── vue-loader.conf.js ├── webpack.base.conf.js ├── webpack.dev.conf.js └── webpack.prod.conf.js ├── config ├── dev.env.js ├── index.js └── prod.env.js ├── index.html ├── package.json ├── src ├── App.vue ├── app.js ├── assets │ └── logo.png ├── components │ ├── AnimateNumber.vue │ ├── Editor.vue │ ├── Layout │ │ ├── Block.vue │ │ ├── Layout.vue │ │ └── SearchBox.vue │ ├── Upload │ │ └── index.vue │ └── fullScreen.vue ├── config │ ├── config.default.js │ └── menus.js ├── main.js ├── mixins │ ├── base.js │ └── dict.js ├── pages │ ├── book │ │ ├── list │ │ │ ├── columns.js │ │ │ ├── index.vue │ │ │ └── main.js │ │ └── newRow │ │ │ ├── index.vue │ │ │ └── main.js │ ├── borrow │ │ ├── borrowBook │ │ │ ├── index.vue │ │ │ └── main.js │ │ └── list │ │ │ ├── columns.js │ │ │ ├── index.vue │ │ │ └── main.js │ ├── category │ │ ├── list │ │ │ ├── columns.js │ │ │ ├── index.vue │ │ │ └── main.js │ │ └── newRow │ │ │ ├── index.vue │ │ │ └── main.js │ ├── errors │ │ └── index.vue │ ├── index │ │ ├── Index.vue │ │ └── style.less │ ├── login │ │ ├── index.vue │ │ ├── main.js │ │ └── style.less │ ├── main_com │ │ ├── header │ │ │ ├── index.less │ │ │ └── index.vue │ │ ├── routeBox │ │ │ ├── index.less │ │ │ └── index.vue │ │ └── sidebarMenu │ │ │ ├── sidebarMenu.less │ │ │ └── sidebarMenu.vue │ ├── press │ │ ├── list │ │ │ ├── columns.js │ │ │ ├── index.vue │ │ │ └── main.js │ │ └── newRow │ │ │ ├── index.vue │ │ │ └── mian.js │ ├── ranking │ │ ├── charts │ │ │ └── readingInit.js │ │ ├── columns.js │ │ └── index.vue │ └── user │ │ ├── list │ │ ├── columns.js │ │ ├── index.vue │ │ └── main.js │ │ ├── newRow │ │ ├── index.vue │ │ └── main.js │ │ └── record │ │ ├── columns.js │ │ ├── index.vue │ │ └── main.js ├── render.js ├── router │ ├── event │ │ ├── filterMiddle.js │ │ ├── index.js │ │ └── isLoginMiddle.js │ └── index.js ├── service │ └── index.js ├── store │ ├── index.js │ └── modules │ │ ├── cache.js │ │ ├── defaults.js │ │ ├── menu.js │ │ └── users.js ├── styles │ ├── common.less │ ├── custom.less │ ├── font │ │ ├── iconfont.eot │ │ ├── iconfont.js │ │ ├── iconfont.svg │ │ ├── iconfont.ttf │ │ └── iconfont.woff │ ├── iconfont.css │ ├── index.less │ ├── mixins.less │ ├── reset.less │ └── theme │ │ ├── index.less │ │ ├── theme-black.less │ │ └── theme-white.less └── utils │ ├── filters.js │ ├── interface.js │ ├── request.js │ ├── storage.js │ └── utils.js └── static ├── .gitkeep ├── config.json └── imgs ├── error.svg ├── img-bg.svg └── login-bg.jpg /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "modules": false, 5 | "targets": { 6 | "browsers": ["> 1%", "last 2 versions", "not ie <= 8"] 7 | } 8 | }], 9 | "stage-2" 10 | ], 11 | "plugins": ["transform-vue-jsx", "transform-runtime"] 12 | } 13 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | /dist/ 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Editor directories and files 9 | .idea 10 | .vscode 11 | *.suo 12 | *.ntvs* 13 | *.njsproj 14 | *.sln 15 | -------------------------------------------------------------------------------- /.postcssrc.js: -------------------------------------------------------------------------------- 1 | // https://github.com/michael-ciniawsky/postcss-load-config 2 | 3 | module.exports = { 4 | "plugins": { 5 | "postcss-import": {}, 6 | "postcss-url": {}, 7 | // to edit target browsers: use "browserslist" field in package.json 8 | "autoprefixer": {} 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **前端** 2 | 3 | [前端地址](https://github.com/fengyaogit123/bookqd.git) 4 | [后端地址](https://github.com/fengyaogit123/bookAdmin.git) 5 | 6 | 觉得不错给个star,谢谢~ 7 | 8 | # 介绍 9 | 由于最近需要做一个简单的图书管理系统,于是就用了vue前后端分离的开发模式 10 | 模块 11 | 1.分类管理 12 | 2.出版管理 13 | 3.读者管理 14 | 4.图书管理 15 | 5.借阅管理 16 | 6.借阅记录 17 | 7.图书概览 18 | 另外还有主题功能,有多个主题可供选择(主题代码被我注释了,在store/modules/menu.js下) 19 | 可以全屏放大缩小 20 | ## 技术栈 21 | ```js 22 | //前端 https://github.com/fengyaogit123/bookqd.git 23 | 1.vue 24 | 2.vue-router 25 | 3.vuex 26 | 4.iview 27 | 5.axios 28 | 29 | //后端 https://github.com/fengyaogit123/bookAdmin.git 30 | 1.Node 31 | 2.Eggjs 32 | 3.Mongodb + Mongoose 33 | 34 | ``` 35 | 36 | ## 安装 37 | ```js 38 | //使用前端之前 需要后端的项目已经运行 详情请看 39 | //https://github.com/fengyaogit123/bookAdmin.git 40 | $ git clone https://github.com/fengyaogit123/bookqd.git 41 | $ cd bookqd 42 | $ npm i 43 | $ npm run dev 44 | 45 | //打开地址 http://127.0.0.1:9898 查看 46 | ``` 47 | 48 | **后端** 49 | # 介绍 50 | 由于最近需要做一个简单的图书管理系统,于是就用了vue前后端分离的开发模式 51 | 52 | ## 技术栈 53 | ```js 54 | //前端 https://github.com/fengyaogit123/bookqd.git 55 | 1.vue 56 | 2.vue-router 57 | 3.vuex 58 | 4.iview 59 | 5.axios 60 | 61 | //后端 https://github.com/fengyaogit123/bookAdmin.git 62 | 1.Node 63 | 2.Eggjs 64 | 3.Mongodb + Mongoose 65 | 66 | ``` 67 | 68 | ## 安装 69 | ```js 70 | //确认已经安装mongodb 这里默认配置是 71 | config.mongoose = { 72 | url: 'mongodb://127.0.0.1:27017/book', 73 | options: {}, 74 | }; 75 | //没有设置账号密码 ,确保mongo服务已经打开 76 | 77 | //前端项目地址 https://github.com/fengyaogit123/bookqd.git 78 | $ git clone https://github.com/fengyaogit123/bookAdmin.git 79 | $ cd bookAdmin 80 | $ npm i 81 | $ npm run dev 82 | 83 | //打开地址 http://127.0.0.1:7001/public/web/index.html 查看 84 | ``` 85 | ## 预览 86 | ![1.jpg](http://ooaa8syjw.bkt.clouddn.com/FkmDqxC6VB7b_wUmv464shTpeX1f) 87 | 登录 88 | ![2.jpg](http://ooaa8syjw.bkt.clouddn.com/FhIHskYQivj5H56uqaaG1YsKeGmg) 89 | 图书简介 90 | ![3.jpg](http://ooaa8syjw.bkt.clouddn.com/Fny0qZU463t12CQelfTTw4IKhMGp) 91 | 图书借还 92 | ![4.jpg](http://ooaa8syjw.bkt.clouddn.com/Fl2aCFTZuHHDcnIOvYdIMYJKU79S) 93 | 图书管理 94 | ![6.jpg](http://ooaa8syjw.bkt.clouddn.com/Fg_yAJ1vj3MJ_1CErWnYnAeuskwe) 95 | 读者管理 96 | ![7.jpg](http://ooaa8syjw.bkt.clouddn.com/Fm-scuetubCUorxdifCBSL1073ZT) 97 | 借阅记录 98 | ![image.png](http://ooaa8syjw.bkt.clouddn.com/FuzYQxIVnh0tx_zLt_s70KK_K5e2) 99 | 出版管理以及分类管理 100 | 101 | ## 参考 102 | iView GitHub:https://github.com/iview/iview 103 | iView Admin GitHub:https://github.com/iview/iview-admin 104 | -------------------------------------------------------------------------------- /build/build.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | require('./check-versions')() 3 | 4 | process.env.NODE_ENV = 'production' 5 | 6 | const ora = require('ora') 7 | const rm = require('rimraf') 8 | const path = require('path') 9 | const chalk = require('chalk') 10 | const webpack = require('webpack') 11 | const config = require('../config') 12 | const webpackConfig = require('./webpack.prod.conf') 13 | 14 | const spinner = ora('building for production...') 15 | spinner.start() 16 | 17 | rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => { 18 | if (err) throw err 19 | webpack(webpackConfig, (err, stats) => { 20 | spinner.stop() 21 | if (err) throw err 22 | process.stdout.write(stats.toString({ 23 | colors: true, 24 | modules: false, 25 | children: false, // If you are using ts-loader, setting this to true will make TypeScript errors show up during build. 26 | chunks: false, 27 | chunkModules: false 28 | }) + '\n\n') 29 | 30 | if (stats.hasErrors()) { 31 | console.log(chalk.red(' Build failed with errors.\n')) 32 | process.exit(1) 33 | } 34 | 35 | console.log(chalk.cyan(' Build complete.\n')) 36 | console.log(chalk.yellow( 37 | ' Tip: built files are meant to be served over an HTTP server.\n' + 38 | ' Opening index.html over file:// won\'t work.\n' 39 | )) 40 | }) 41 | }) 42 | -------------------------------------------------------------------------------- /build/check-versions.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const chalk = require('chalk') 3 | const semver = require('semver') 4 | const packageConfig = require('../package.json') 5 | const shell = require('shelljs') 6 | 7 | function exec (cmd) { 8 | return require('child_process').execSync(cmd).toString().trim() 9 | } 10 | 11 | const versionRequirements = [ 12 | { 13 | name: 'node', 14 | currentVersion: semver.clean(process.version), 15 | versionRequirement: packageConfig.engines.node 16 | } 17 | ] 18 | 19 | if (shell.which('npm')) { 20 | versionRequirements.push({ 21 | name: 'npm', 22 | currentVersion: exec('npm --version'), 23 | versionRequirement: packageConfig.engines.npm 24 | }) 25 | } 26 | 27 | module.exports = function () { 28 | const warnings = [] 29 | 30 | for (let i = 0; i < versionRequirements.length; i++) { 31 | const mod = versionRequirements[i] 32 | 33 | if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) { 34 | warnings.push(mod.name + ': ' + 35 | chalk.red(mod.currentVersion) + ' should be ' + 36 | chalk.green(mod.versionRequirement) 37 | ) 38 | } 39 | } 40 | 41 | if (warnings.length) { 42 | console.log('') 43 | console.log(chalk.yellow('To use this template, you must update following to modules:')) 44 | console.log() 45 | 46 | for (let i = 0; i < warnings.length; i++) { 47 | const warning = warnings[i] 48 | console.log(' ' + warning) 49 | } 50 | 51 | console.log() 52 | process.exit(1) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /build/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fengyaogit123/bookqd/44f7db8eb23e18edc88207eb6494b2df241b0a3f/build/logo.png -------------------------------------------------------------------------------- /build/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const config = require('../config') 4 | const ExtractTextPlugin = require('extract-text-webpack-plugin') 5 | const packageConfig = require('../package.json') 6 | 7 | exports.assetsPath = function (_path) { 8 | const assetsSubDirectory = process.env.NODE_ENV === 'production' 9 | ? config.build.assetsSubDirectory 10 | : config.dev.assetsSubDirectory 11 | 12 | return path.posix.join(assetsSubDirectory, _path) 13 | } 14 | 15 | exports.cssLoaders = function (options) { 16 | options = options || {} 17 | 18 | const cssLoader = { 19 | loader: 'css-loader', 20 | options: { 21 | sourceMap: options.sourceMap 22 | } 23 | } 24 | 25 | const postcssLoader = { 26 | loader: 'postcss-loader', 27 | options: { 28 | sourceMap: options.sourceMap 29 | } 30 | } 31 | 32 | // generate loader string to be used with extract text plugin 33 | function generateLoaders (loader, loaderOptions) { 34 | const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader] 35 | 36 | if (loader) { 37 | loaders.push({ 38 | loader: loader + '-loader', 39 | options: Object.assign({}, loaderOptions, { 40 | sourceMap: options.sourceMap 41 | }) 42 | }) 43 | } 44 | 45 | // Extract CSS when that option is specified 46 | // (which is the case during production build) 47 | if (options.extract) { 48 | return ExtractTextPlugin.extract({ 49 | use: loaders, 50 | fallback: 'vue-style-loader', 51 | publicPath:'../../' 52 | }) 53 | } else { 54 | return ['vue-style-loader'].concat(loaders) 55 | } 56 | } 57 | 58 | // https://vue-loader.vuejs.org/en/configurations/extract-css.html 59 | return { 60 | css: generateLoaders(), 61 | postcss: generateLoaders(), 62 | less: generateLoaders('less'), 63 | sass: generateLoaders('sass', { indentedSyntax: true }), 64 | scss: generateLoaders('sass'), 65 | stylus: generateLoaders('stylus'), 66 | styl: generateLoaders('stylus') 67 | } 68 | } 69 | 70 | // Generate loaders for standalone style files (outside of .vue) 71 | exports.styleLoaders = function (options) { 72 | const output = [] 73 | const loaders = exports.cssLoaders(options) 74 | 75 | for (const extension in loaders) { 76 | const loader = loaders[extension] 77 | output.push({ 78 | test: new RegExp('\\.' + extension + '$'), 79 | use: loader 80 | }) 81 | } 82 | 83 | return output 84 | } 85 | 86 | exports.createNotifierCallback = () => { 87 | const notifier = require('node-notifier') 88 | 89 | return (severity, errors) => { 90 | if (severity !== 'error') return 91 | 92 | const error = errors[0] 93 | const filename = error.file && error.file.split('!').pop() 94 | 95 | notifier.notify({ 96 | title: packageConfig.name, 97 | message: severity + ': ' + error.name, 98 | subtitle: filename || '', 99 | icon: path.join(__dirname, 'logo.png') 100 | }) 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /build/vue-loader.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const utils = require('./utils') 3 | const config = require('../config') 4 | const isProduction = process.env.NODE_ENV === 'production' 5 | const sourceMapEnabled = isProduction 6 | ? config.build.productionSourceMap 7 | : config.dev.cssSourceMap 8 | 9 | module.exports = { 10 | loaders: utils.cssLoaders({ 11 | sourceMap: sourceMapEnabled, 12 | extract: isProduction 13 | }), 14 | cssSourceMap: sourceMapEnabled, 15 | cacheBusting: config.dev.cacheBusting, 16 | transformToRequire: { 17 | video: ['src', 'poster'], 18 | source: 'src', 19 | img: 'src', 20 | image: 'xlink:href' 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /build/webpack.base.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const utils = require('./utils') 4 | const config = require('../config') 5 | const vueLoaderConfig = require('./vue-loader.conf') 6 | 7 | function resolve (dir) { 8 | return path.join(__dirname, '..', dir) 9 | } 10 | 11 | 12 | 13 | module.exports = { 14 | context: path.resolve(__dirname, '../'), 15 | entry: { 16 | app: './src/main.js' 17 | }, 18 | output: { 19 | path: config.build.assetsRoot, 20 | filename: '[name].js', 21 | publicPath: process.env.NODE_ENV === 'production' 22 | ? config.build.assetsPublicPath 23 | : config.dev.assetsPublicPath 24 | }, 25 | resolve: { 26 | extensions: ['.js', '.vue', '.json'], 27 | alias: { 28 | 'vue$': 'vue/dist/vue.esm.js', 29 | '@': resolve('src'), 30 | } 31 | }, 32 | externals:{ 33 | "vue":"Vue", 34 | "vuex":"Vuex", 35 | "vue-router":"VueRouter", 36 | "velocity-animate":"Velocity", 37 | "axios":"axios" 38 | }, 39 | module: { 40 | rules: [ 41 | { 42 | test: /\.vue$/, 43 | loader: 'vue-loader', 44 | options: vueLoaderConfig 45 | }, 46 | { 47 | test: /\.js$/, 48 | loader: 'babel-loader', 49 | include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')] 50 | }, 51 | { 52 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 53 | loader: 'url-loader', 54 | options: { 55 | limit: 10000, 56 | name: utils.assetsPath('img/[name].[hash:7].[ext]') 57 | } 58 | }, 59 | { 60 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, 61 | loader: 'url-loader', 62 | options: { 63 | limit: 10000, 64 | name: utils.assetsPath('media/[name].[hash:7].[ext]') 65 | } 66 | }, 67 | { 68 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 69 | loader: 'url-loader', 70 | options: { 71 | limit: 10000, 72 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]') 73 | } 74 | } 75 | ] 76 | }, 77 | node: { 78 | // prevent webpack from injecting useless setImmediate polyfill because Vue 79 | // source contains it (although only uses it if it's native). 80 | setImmediate: false, 81 | // prevent webpack from injecting mocks to Node native modules 82 | // that does not make sense for the client 83 | dgram: 'empty', 84 | fs: 'empty', 85 | net: 'empty', 86 | tls: 'empty', 87 | child_process: 'empty' 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /build/webpack.dev.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const utils = require('./utils') 3 | const webpack = require('webpack') 4 | const config = require('../config') 5 | const merge = require('webpack-merge') 6 | const path = require('path') 7 | const baseWebpackConfig = require('./webpack.base.conf') 8 | const CopyWebpackPlugin = require('copy-webpack-plugin') 9 | const HtmlWebpackPlugin = require('html-webpack-plugin') 10 | const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin') 11 | const portfinder = require('portfinder') 12 | 13 | const HOST = process.env.HOST 14 | const PORT = process.env.PORT && Number(process.env.PORT) 15 | 16 | const devWebpackConfig = merge(baseWebpackConfig, { 17 | module: { 18 | rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true }) 19 | }, 20 | // cheap-module-eval-source-map is faster for development 21 | devtool: config.dev.devtool, 22 | 23 | // these devServer options should be customized in /config/index.js 24 | devServer: { 25 | clientLogLevel: 'warning', 26 | historyApiFallback: { 27 | rewrites: [ 28 | { from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') }, 29 | ], 30 | }, 31 | hot: true, 32 | contentBase: false, // since we use CopyWebpackPlugin. 33 | compress: true, 34 | host: HOST || config.dev.host, 35 | port: PORT || config.dev.port, 36 | open: config.dev.autoOpenBrowser, 37 | overlay: config.dev.errorOverlay 38 | ? { warnings: false, errors: true } 39 | : false, 40 | publicPath: config.dev.assetsPublicPath, 41 | proxy: config.dev.proxyTable, 42 | quiet: true, // necessary for FriendlyErrorsPlugin 43 | watchOptions: { 44 | poll: config.dev.poll, 45 | } 46 | }, 47 | plugins: [ 48 | new webpack.DefinePlugin({ 49 | 'process.env': require('../config/dev.env') 50 | }), 51 | new webpack.HotModuleReplacementPlugin(), 52 | new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update. 53 | new webpack.NoEmitOnErrorsPlugin(), 54 | // https://github.com/ampedandwired/html-webpack-plugin 55 | new HtmlWebpackPlugin({ 56 | filename: 'index.html', 57 | template: 'index.html', 58 | inject: true 59 | }), 60 | // copy custom static assets 61 | new CopyWebpackPlugin([ 62 | { 63 | from: path.resolve(__dirname, '../static'), 64 | to: config.dev.assetsSubDirectory, 65 | ignore: ['.*'] 66 | } 67 | ]) 68 | ] 69 | }) 70 | 71 | module.exports = new Promise((resolve, reject) => { 72 | portfinder.basePort = process.env.PORT || config.dev.port 73 | portfinder.getPort((err, port) => { 74 | if (err) { 75 | reject(err) 76 | } else { 77 | // publish the new Port, necessary for e2e tests 78 | process.env.PORT = port 79 | // add port to devServer config 80 | devWebpackConfig.devServer.port = port 81 | 82 | // Add FriendlyErrorsPlugin 83 | devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({ 84 | compilationSuccessInfo: { 85 | messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`], 86 | }, 87 | onErrors: config.dev.notifyOnErrors 88 | ? utils.createNotifierCallback() 89 | : undefined 90 | })) 91 | 92 | resolve(devWebpackConfig) 93 | } 94 | }) 95 | }) 96 | -------------------------------------------------------------------------------- /build/webpack.prod.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const utils = require('./utils') 4 | const webpack = require('webpack') 5 | const config = require('../config') 6 | const merge = require('webpack-merge') 7 | const baseWebpackConfig = require('./webpack.base.conf') 8 | const CopyWebpackPlugin = require('copy-webpack-plugin') 9 | const HtmlWebpackPlugin = require('html-webpack-plugin') 10 | const ExtractTextPlugin = require('extract-text-webpack-plugin') 11 | const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin') 12 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin') 13 | 14 | const env = require('../config/prod.env') 15 | 16 | const webpackConfig = merge(baseWebpackConfig, { 17 | module: { 18 | rules: utils.styleLoaders({ 19 | sourceMap: config.build.productionSourceMap, 20 | extract: true, 21 | usePostCSS: true 22 | }) 23 | }, 24 | devtool: config.build.productionSourceMap ? config.build.devtool : false, 25 | output: { 26 | path: config.build.assetsRoot, 27 | filename: utils.assetsPath('js/[name].[chunkhash].js'), 28 | chunkFilename: utils.assetsPath('js/[id].[chunkhash].js') 29 | }, 30 | plugins: [ 31 | // http://vuejs.github.io/vue-loader/en/workflow/production.html 32 | new webpack.DefinePlugin({ 33 | 'process.env': env 34 | }), 35 | new UglifyJsPlugin({ 36 | uglifyOptions: { 37 | compress: { 38 | warnings: false 39 | } 40 | }, 41 | sourceMap: config.build.productionSourceMap, 42 | parallel: true 43 | }), 44 | // extract css into its own file 45 | new ExtractTextPlugin({ 46 | filename: utils.assetsPath('css/[name].[contenthash].css'), 47 | // Setting the following option to `false` will not extract CSS from codesplit chunks. 48 | // Their CSS will instead be inserted dynamically with style-loader when the codesplit chunk has been loaded by webpack. 49 | // It's currently set to `true` because we are seeing that sourcemaps are included in the codesplit bundle as well when it's `false`, 50 | // increasing file size: https://github.com/vuejs-templates/webpack/issues/1110 51 | allChunks: true, 52 | }), 53 | // Compress extracted CSS. We are using this plugin so that possible 54 | // duplicated CSS from different components can be deduped. 55 | new OptimizeCSSPlugin({ 56 | cssProcessorOptions: config.build.productionSourceMap 57 | ? { safe: true, map: { inline: false } } 58 | : { safe: true } 59 | }), 60 | // generate dist index.html with correct asset hash for caching. 61 | // you can customize output by editing /index.html 62 | // see https://github.com/ampedandwired/html-webpack-plugin 63 | new HtmlWebpackPlugin({ 64 | filename: config.build.index, 65 | template: 'index.html', 66 | inject: true, 67 | minify: { 68 | removeComments: true, 69 | collapseWhitespace: true, 70 | removeAttributeQuotes: true 71 | // more options: 72 | // https://github.com/kangax/html-minifier#options-quick-reference 73 | }, 74 | // necessary to consistently work with multiple chunks via CommonsChunkPlugin 75 | chunksSortMode: 'dependency' 76 | }), 77 | // keep module.id stable when vendor modules does not change 78 | new webpack.HashedModuleIdsPlugin(), 79 | // enable scope hoisting 80 | new webpack.optimize.ModuleConcatenationPlugin(), 81 | // split vendor js into its own file 82 | new webpack.optimize.CommonsChunkPlugin({ 83 | name: 'vendor', 84 | minChunks (module) { 85 | // any required modules inside node_modules are extracted to vendor 86 | return ( 87 | module.resource && 88 | /\.js$/.test(module.resource) && 89 | module.resource.indexOf( 90 | path.join(__dirname, '../node_modules') 91 | ) === 0 92 | ) 93 | } 94 | }), 95 | // extract webpack runtime and module manifest to its own file in order to 96 | // prevent vendor hash from being updated whenever app bundle is updated 97 | new webpack.optimize.CommonsChunkPlugin({ 98 | name: 'manifest', 99 | minChunks: Infinity 100 | }), 101 | // This instance extracts shared chunks from code splitted chunks and bundles them 102 | // in a separate chunk, similar to the vendor chunk 103 | // see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk 104 | new webpack.optimize.CommonsChunkPlugin({ 105 | name: 'app', 106 | async: 'vendor-async', 107 | children: true, 108 | minChunks: 3 109 | }), 110 | 111 | // copy custom static assets 112 | new CopyWebpackPlugin([ 113 | { 114 | from: path.resolve(__dirname, '../static'), 115 | to: config.build.assetsSubDirectory, 116 | ignore: ['.*'] 117 | } 118 | ]) 119 | ] 120 | }) 121 | 122 | if (config.build.productionGzip) { 123 | const CompressionWebpackPlugin = require('compression-webpack-plugin') 124 | 125 | webpackConfig.plugins.push( 126 | new CompressionWebpackPlugin({ 127 | asset: '[path].gz[query]', 128 | algorithm: 'gzip', 129 | test: new RegExp( 130 | '\\.(' + 131 | config.build.productionGzipExtensions.join('|') + 132 | ')$' 133 | ), 134 | threshold: 10240, 135 | minRatio: 0.8 136 | }) 137 | ) 138 | } 139 | 140 | if (config.build.bundleAnalyzerReport) { 141 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin 142 | webpackConfig.plugins.push(new BundleAnalyzerPlugin()) 143 | } 144 | 145 | module.exports = webpackConfig 146 | -------------------------------------------------------------------------------- /config/dev.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const merge = require('webpack-merge') 3 | const prodEnv = require('./prod.env') 4 | 5 | module.exports = merge(prodEnv, { 6 | NODE_ENV: '"development"' 7 | }) 8 | -------------------------------------------------------------------------------- /config/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // Template version: 1.3.1 3 | // see http://vuejs-templates.github.io/webpack for documentation. 4 | 5 | const path = require('path') 6 | 7 | module.exports = { 8 | dev: { 9 | // Paths 10 | assetsSubDirectory: 'static', 11 | assetsPublicPath: '/', 12 | proxyTable: { 13 | '/api': { 14 | target: 'http://127.0.0.1:7001', 15 | changeOrigin: true, 16 | pathRewrite: { 17 | '^/api': 'api' 18 | } 19 | }, 20 | }, 21 | 22 | // Various Dev Server settings 23 | host: '127.0.0.1', // can be overwritten by process.env.HOST 24 | port: 9898, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined 25 | autoOpenBrowser: true, 26 | errorOverlay: true, 27 | notifyOnErrors: true, 28 | poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions- 29 | 30 | 31 | /** 32 | * Source Maps 33 | */ 34 | 35 | // https://webpack.js.org/configuration/devtool/#development 36 | devtool: 'cheap-module-eval-source-map', 37 | 38 | // If you have problems debugging vue-files in devtools, 39 | // set this to false - it *may* help 40 | // https://vue-loader.vuejs.org/en/options.html#cachebusting 41 | cacheBusting: true, 42 | 43 | cssSourceMap: false 44 | }, 45 | 46 | build: { 47 | // Template for index.html 48 | index: path.resolve(__dirname, '../../bookAdmin/app/public/web/index.html'), 49 | 50 | // Paths 51 | assetsRoot: path.resolve(__dirname, '../../bookAdmin/app/public/web'), 52 | assetsSubDirectory: 'static', 53 | assetsPublicPath: '', 54 | 55 | /** 56 | * Source Maps 57 | */ 58 | 59 | productionSourceMap: false, 60 | // https://webpack.js.org/configuration/devtool/#production 61 | devtool: '#source-map', 62 | 63 | // Gzip off by default as many popular static hosts such as 64 | // Surge or Netlify already gzip all static assets for you. 65 | // Before setting to `true`, make sure to: 66 | // npm install --save-dev compression-webpack-plugin 67 | productionGzip: false, 68 | productionGzipExtensions: ['js', 'css'], 69 | 70 | // Run the build command with an extra argument to 71 | // View the bundle analyzer report after build finishes: 72 | // `npm run build --report` 73 | // Set to `true` or `false` to always turn it on or off 74 | bundleAnalyzerReport: process.env.npm_config_report 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /config/prod.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = { 3 | NODE_ENV: '"production"' 4 | } 5 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "blogadmin", 3 | "version": "1.0.0", 4 | "description": "A Vue.js project", 5 | "author": "fengyao123 <1724238345@qq.com>", 6 | "private": true, 7 | "scripts": { 8 | "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js", 9 | "start": "npm run dev", 10 | "build": "node build/build.js" 11 | }, 12 | "dependencies": { 13 | "axios": "^0.16.2", 14 | "babel-polyfill": "^6.26.0", 15 | "cookies-js": "^1.2.3", 16 | "iview": "^2.11.0", 17 | "js-sha256": "^0.9.0", 18 | "querystring": "^0.2.0", 19 | "velocity-animate": "^1.5.0", 20 | "vue": "^2.5.2", 21 | "vue-router": "^3.0.1", 22 | "vuex": "^3.0.1", 23 | "xss": "^0.3.7" 24 | }, 25 | "devDependencies": { 26 | "autoprefixer": "^7.1.2", 27 | "babel-core": "^6.22.1", 28 | "babel-helper-vue-jsx-merge-props": "^2.0.3", 29 | "babel-loader": "^7.1.1", 30 | "babel-plugin-syntax-jsx": "^6.18.0", 31 | "babel-plugin-transform-runtime": "^6.22.0", 32 | "babel-plugin-transform-vue-jsx": "^3.5.0", 33 | "babel-preset-env": "^1.3.2", 34 | "babel-preset-stage-2": "^6.22.0", 35 | "chalk": "^2.0.1", 36 | "copy-webpack-plugin": "^4.0.1", 37 | "css-loader": "^0.28.0", 38 | "extract-text-webpack-plugin": "^3.0.0", 39 | "file-loader": "^1.1.4", 40 | "friendly-errors-webpack-plugin": "^1.6.1", 41 | "html-webpack-plugin": "^2.30.1", 42 | "less": "^2.7.3", 43 | "less-loader": "^4.0.5", 44 | "node-notifier": "^5.1.2", 45 | "optimize-css-assets-webpack-plugin": "^3.2.0", 46 | "ora": "^1.2.0", 47 | "portfinder": "^1.0.13", 48 | "postcss-import": "^11.0.0", 49 | "postcss-loader": "^2.0.8", 50 | "postcss-url": "^7.2.1", 51 | "rimraf": "^2.6.0", 52 | "semver": "^5.3.0", 53 | "shelljs": "^0.7.6", 54 | "uglifyjs-webpack-plugin": "^1.1.1", 55 | "url-loader": "^0.5.8", 56 | "vue-loader": "^13.3.0", 57 | "vue-style-loader": "^3.0.1", 58 | "vue-template-compiler": "^2.5.2", 59 | "webpack": "^3.6.0", 60 | "webpack-bundle-analyzer": "^2.9.0", 61 | "webpack-dev-server": "^2.9.1", 62 | "webpack-merge": "^4.1.0" 63 | }, 64 | "engines": { 65 | "node": ">= 6.0.0", 66 | "npm": ">= 3.0.0" 67 | }, 68 | "browserslist": [ 69 | "> 1%", 70 | "last 2 versions", 71 | "not ie <= 8" 72 | ] 73 | } 74 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 21 | 28 | -------------------------------------------------------------------------------- /src/app.js: -------------------------------------------------------------------------------- 1 | /**按需引用 */ 2 | import Vue from 'vue' 3 | import iView from 'iview'; 4 | import 'iview/dist/styles/iview.css'; 5 | import Layout from '@/components/Layout/Layout' 6 | import Block from '@/components/Layout/Block' 7 | import AnimateNumber from './components/AnimateNumber.vue' 8 | import FullScreen from '@/components/fullScreen' 9 | import Upload from '@/components/Upload' 10 | import SearchBox from '@/components/Layout/SearchBox' 11 | Vue.use(iView); 12 | /**自定义组件 */ 13 | Vue.component('FyLayout', Layout) 14 | Vue.component('Block', Block) 15 | Vue.component('AnimateNumber', AnimateNumber) 16 | Vue.component('FullScreen', FullScreen) 17 | Vue.component("SearchBox",SearchBox) -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fengyaogit123/bookqd/44f7db8eb23e18edc88207eb6494b2df241b0a3f/src/assets/logo.png -------------------------------------------------------------------------------- /src/components/AnimateNumber.vue: -------------------------------------------------------------------------------- 1 | 6 | 86 | 87 | 92 | -------------------------------------------------------------------------------- /src/components/Editor.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 81 | 82 | 87 | -------------------------------------------------------------------------------- /src/components/Layout/Block.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 16 | 17 | 22 | -------------------------------------------------------------------------------- /src/components/Layout/Layout.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 15 | 16 | 25 | -------------------------------------------------------------------------------- /src/components/Layout/SearchBox.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 62 | 63 | 84 | -------------------------------------------------------------------------------- /src/components/Upload/index.vue: -------------------------------------------------------------------------------- 1 | 35 | 168 | -------------------------------------------------------------------------------- /src/components/fullScreen.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 75 | -------------------------------------------------------------------------------- /src/config/config.default.js: -------------------------------------------------------------------------------- 1 | /**匹配 */ 2 | export default { 3 | match:"",//匹配内容 4 | key:'', 5 | value:"" 6 | } 7 | 8 | export const Homes = 'Ranking' 9 | export const title ='图书简介' -------------------------------------------------------------------------------- /src/config/menus.js: -------------------------------------------------------------------------------- 1 | export default [{ 2 | name: '分类管理', 3 | icon: 'android-share-alt', 4 | routerName: "Category" 5 | }, { 6 | name: '出版管理', 7 | icon: 'share', 8 | routerName: "Press" 9 | }, { 10 | name: '读者管理', 11 | icon: 'android-contact', 12 | routerName: "User" 13 | }, { 14 | name: '图书管理', 15 | icon: 'android-map', 16 | routerName: "Book" 17 | }, { 18 | name: '图书借还', 19 | icon: 'android-exit', 20 | routerName: "Borrow" 21 | }, { 22 | name: '图书简介', 23 | icon: 'ios-book-outline', 24 | routerName: "Ranking" 25 | }].concat([{ 26 | name: '借阅记录', 27 | hide: true, 28 | routerName: "Record" 29 | }]) -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | // The Vue build version to load with the `import` command 2 | // (runtime-only or standalone) has been set in webpack.base.conf with an alias. 3 | import Vue from 'vue' 4 | import './app.js' 5 | import router from './router' 6 | import App from './App.vue' 7 | import axios from '@/utils/request' 8 | import store from '@/store/index' 9 | import utils from '@/utils/utils' 10 | import '@/utils/filters' 11 | import './render' 12 | 13 | Vue.config.silent = false 14 | Vue.config.productionTip = false 15 | Vue.prototype.$Message.config({ 16 | duration: 1 17 | }); 18 | /* eslint-disable no-new */ 19 | new Vue({ 20 | el: '#app', 21 | router, 22 | store, 23 | template: '', 24 | created() { 25 | this.$store.commit('cache/initCache') 26 | }, 27 | components: { 28 | App 29 | } 30 | }) 31 | -------------------------------------------------------------------------------- /src/mixins/base.js: -------------------------------------------------------------------------------- 1 | //增删改查基础混合 2 | export default { 3 | methods: { 4 | //分页change 5 | async pageChange(pageNo) { 6 | this.params.pageNo = pageNo; 7 | this.showList(); 8 | }, 9 | //重置search 10 | async reset(form) { 11 | form && form.resetFields(); 12 | }, 13 | //查询 14 | async search() { 15 | this.params.pageNo = 1; 16 | this.showList() 17 | }, 18 | //多选 19 | async selectChange(rows) { 20 | this.rows = rows; 21 | }, 22 | //删除多个 23 | async deletes(ids) { 24 | if ((!ids || ids.length == 0) && (!this.rows || this.rows.length == 0)) { 25 | return this.$Message.info("请选择删除的列表!") 26 | } 27 | this.$Modal.confirm({ 28 | title: "提示", 29 | content: "是否确认删除?", 30 | onOk: async () => { 31 | this.confirmDel(ids); 32 | }, 33 | onCancel: () => { 34 | this.cancelDel && this.cancelDel(); 35 | } 36 | }); 37 | }, 38 | } 39 | } -------------------------------------------------------------------------------- /src/mixins/dict.js: -------------------------------------------------------------------------------- 1 | import { Book, Category, Press } from "@/service"; 2 | export default { 3 | methods: { 4 | async getCategoryAll() { 5 | let { data } = await Category.getAll();; 6 | this.categoryList = data.rows; 7 | }, 8 | async getPressAll() { 9 | let { data } = await Press.getAll();; 10 | this.pressList = data.rows; 11 | }, 12 | } 13 | } -------------------------------------------------------------------------------- /src/pages/book/list/columns.js: -------------------------------------------------------------------------------- 1 | import router from "@/router"; 2 | export default function () { 3 | return [ 4 | { 5 | type: "selection", 6 | width: 60, 7 | align: "center" 8 | }, 9 | { 10 | title: "书名", 11 | key: "name", 12 | align: "center", 13 | }, 14 | { 15 | title: "借阅状态", 16 | key: "borrowStatus", 17 | align: "center", 18 | width:140, 19 | render: (h, { row }) => { 20 | 21 | return row.borrowStatus ? 22 | 已借出 23 | : 未借出 24 | } 25 | }, 26 | { 27 | title: "类别", 28 | key: "category", 29 | align: "center", 30 | render: (h, { row }) => { 31 | return row.category && row.category.name || "" 32 | } 33 | }, 34 | { 35 | title: "出版社", 36 | key: "press", 37 | align: "center", 38 | render: (h, { row }) => { 39 | return row.press && row.press.name || "" 40 | } 41 | }, 42 | { 43 | title: "作者", 44 | key: "author", 45 | align: "center", 46 | }, 47 | { 48 | title: "简介", 49 | key: "desc", 50 | align: "center", 51 | render: (h, { row }) => { 52 | let text = row.desc ? row.desc.substring(0, 5) + '...' : "" 53 | return {text} 54 | } 55 | }, 56 | { 57 | title: "借阅次数", 58 | key: "borrowTotal", 59 | align: "center", 60 | render: (h, { row }) => { 61 | return row.borrowTotal || 0; 62 | } 63 | }, 64 | { 65 | title: "上架时间", 66 | key: "createdAt", 67 | align: "center", 68 | render: (h, { row }) => { 69 | return new Date(row.createdAt).Format('yyyy-MM-dd') 70 | } 71 | }, 72 | { 73 | title: "操作", 74 | align: "center", 75 | width: 150, 76 | render: (h, { row }) => { 77 | const editClick = () => { 78 | this.modal.show = true; 79 | this.modal.row = row; 80 | } 81 | return ( 82 |
83 | 84 | 85 |
86 | ) 87 | } 88 | } 89 | ] 90 | } -------------------------------------------------------------------------------- /src/pages/book/list/index.vue: -------------------------------------------------------------------------------- 1 | 60 | 61 | 63 | 64 | -------------------------------------------------------------------------------- /src/pages/book/list/main.js: -------------------------------------------------------------------------------- 1 | import { Book } from "@/service"; 2 | import columns from "./columns"; 3 | import { stringify } from "querystring"; 4 | import base from '@/mixins/base' 5 | import dict from '@/mixins/dict' 6 | export default { 7 | name: "Book", 8 | mixins: [base, dict], 9 | components: { 10 | NewRow: () => import('../newRow/index.vue') 11 | }, 12 | data() { 13 | return { 14 | loading: false, 15 | data: [], 16 | count: 0, 17 | params: { 18 | pageNo: 1, //当前页 19 | size: 20, 20 | category: "", 21 | name: "", 22 | press: "", 23 | author: "", 24 | createdAt: "", 25 | borrowStatus:"" 26 | }, 27 | rows: [], 28 | columns: columns.call(this), 29 | categoryList: [], 30 | pressList: [], 31 | modal: { 32 | show: false, 33 | row: null,//编辑的对象 34 | }, 35 | }; 36 | }, 37 | created() { 38 | this.showList(); 39 | this.getCategoryAll(); 40 | this.getPressAll(); 41 | }, 42 | methods: { 43 | async addrow() { 44 | this.modal.show = true; 45 | }, 46 | async showList() { 47 | let { createdAt } = this.params; 48 | createdAt = createdAt ? new Date(this.params.createdAt).Format('yyyy-MM-dd') : "" 49 | const { data } = await Book.get({ 50 | ...this.params, 51 | createdAt 52 | }); 53 | let { rows = [], count } = data; 54 | this.data = rows; 55 | this.count = count; 56 | this.$Message.success("加载完成"); 57 | }, 58 | async confirmDel(ids) { 59 | let { data } = await Book.delete({ 60 | ids: ids || this.rows.map(({ _id }) => _id) 61 | }); 62 | this.$Message.success("删除成功!"); 63 | this.showList(); 64 | }, 65 | 66 | } 67 | }; -------------------------------------------------------------------------------- /src/pages/book/newRow/index.vue: -------------------------------------------------------------------------------- 1 | 42 | 43 | 44 | 49 | 50 | -------------------------------------------------------------------------------- /src/pages/book/newRow/main.js: -------------------------------------------------------------------------------- 1 | import { Book, Category, Press } from "@/service"; 2 | import dict from '@/mixins/dict' 3 | export default { 4 | mixins: [dict], 5 | props: { 6 | modal: Object 7 | }, 8 | watch: { 9 | "modal.show": { 10 | handler: function () { 11 | if (this.modal.show && this.modal.row) { 12 | this.modal.row.isAdmin = this.modal.row.isAdmin + ""; 13 | let { category, press } = this.modal.row 14 | return (this.params = { 15 | ...this.params, 16 | ...this.modal.row, 17 | category: category ? category._id : '', 18 | press: press ? press._id : '' 19 | }); 20 | } 21 | this.modal.row = null; 22 | }, 23 | immediate: true 24 | } 25 | }, 26 | data() { 27 | return { 28 | params: this.initData(), 29 | categoryList: [], 30 | pressList: [], 31 | rules: { 32 | name: { type: "string", required: true, message: "必填项" }, 33 | category: { type: "string", required: true, message: "必填项" }, 34 | press: { type: "string", required: true, message: "必填项" }, 35 | author: { type: "string", required: true, message: "必填项" }, 36 | stock: { type: "number", required: true, message: "必填项" } 37 | } 38 | }; 39 | }, 40 | created() { 41 | this.getCategoryAll(); 42 | this.getPressAll(); 43 | }, 44 | methods: { 45 | initData() { 46 | return { 47 | _id: undefined, 48 | name: "", 49 | category: "", 50 | press: "", 51 | author: "", 52 | stock: "", 53 | desc: "", 54 | borrowStatus: "0" 55 | }; 56 | }, 57 | async modalOk() { 58 | let validate = await this.$refs.form.validate(); 59 | if (!validate) return; 60 | if (!this.params._id) { 61 | await this.create(); 62 | } else { 63 | await this.update(); 64 | } 65 | this.$Message.success("提交成功!"); 66 | this.initModal("ok"); 67 | }, 68 | async create() { 69 | let { data } = await Book.create(this.$QS.stringify(this.params)); 70 | }, 71 | async update() { 72 | let { data } = await Book.update(this.$QS.stringify(this.params)); 73 | }, 74 | 75 | cancel() { 76 | this.initModal("cancel"); 77 | }, 78 | initModal(type) { 79 | this.params = this.initData(); 80 | this.modal.show = false; 81 | this.$refs.form.resetFields(); 82 | this.$emit(type); 83 | } 84 | } 85 | }; -------------------------------------------------------------------------------- /src/pages/borrow/borrowBook/index.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 39 | 57 | 58 | -------------------------------------------------------------------------------- /src/pages/borrow/borrowBook/main.js: -------------------------------------------------------------------------------- 1 | import { Book, Category, Press, User } from "@/service"; 2 | import dict from '@/mixins/dict' 3 | export default { 4 | name: "BorrowBook", 5 | mixins: [dict], 6 | props: { 7 | modal: Object 8 | }, 9 | watch: { 10 | "modal.show": { 11 | handler: function () { 12 | if (this.modal.show && this.modal.row) { 13 | this.modal.row.isAdmin = this.modal.row.isAdmin + ""; 14 | let { category, press } = this.modal.row 15 | return (this.params = { 16 | ...this.params, 17 | ...this.modal.row, 18 | category: category ? category._id : '', 19 | press: press ? press._id : '' 20 | }); 21 | } 22 | this.modal.row = null; 23 | }, 24 | immediate: true 25 | } 26 | }, 27 | data() { 28 | return { 29 | params: this.initData(), 30 | categoryList: [], 31 | pressList: [], 32 | loading: false, 33 | users: [], 34 | userId: "", 35 | rules: { 36 | userId: { type: "string", required: true, message: "必填项" }, 37 | _id: { type: "string", required: true, message: "必填项" }, 38 | borrowStatus: { type: "string", required: true, message: "必填项" }, 39 | } 40 | }; 41 | }, 42 | created() { 43 | this.getCategoryAll(); 44 | this.getPressAll(); 45 | }, 46 | methods: { 47 | initData() { 48 | return { 49 | _id: undefined, 50 | name: "", 51 | category: "", 52 | press: "", 53 | author: "", 54 | stock: "", 55 | desc: "", 56 | borrowStatus: "0", 57 | userId: "", 58 | }; 59 | }, 60 | async remoteMethod(query) { 61 | if (query !== '') { 62 | this.loading = true; 63 | try { 64 | let { data } = await User.get({ 65 | name: query, 66 | pageNo: 1, 67 | size: 20 68 | }) 69 | this.loading = false; 70 | this.users = data.rows || []; 71 | } catch (e) { 72 | this.loading = false; 73 | } 74 | } 75 | }, 76 | async modalOk() { 77 | let validate = await this.$refs.form.validate(); 78 | if (!validate) return; 79 | if (this.params.borrowStatus != 1) { 80 | await this.borrow(); 81 | } else { 82 | await this.book(); 83 | } 84 | this.$Message.success("提交成功!"); 85 | this.initModal("ok"); 86 | }, 87 | //借书 88 | async borrow() { 89 | let { data } = await Book.borrow(this.$QS.stringify({ 90 | _id: this.params._id, 91 | borrowStatus: 1, 92 | userId:this.params.userId 93 | })); 94 | }, 95 | //还书 96 | async book() { 97 | let { data } = await Book.borrow(this.$QS.stringify({ 98 | _id: this.params._id, 99 | borrowStatus: 0, 100 | userId:this.params.userId 101 | })); 102 | }, 103 | 104 | cancel() { 105 | this.initModal("cancel"); 106 | }, 107 | initModal(type) { 108 | this.params = this.initData(); 109 | this.modal.show = false; 110 | this.$refs.form.resetFields(); 111 | this.$emit(type); 112 | } 113 | } 114 | }; -------------------------------------------------------------------------------- /src/pages/borrow/list/columns.js: -------------------------------------------------------------------------------- 1 | import router from "@/router"; 2 | export default function () { 3 | return [ 4 | { 5 | type: "selection", 6 | width: 60, 7 | align: "center" 8 | }, 9 | { 10 | title: "书名", 11 | key: "name", 12 | align: "center", 13 | }, 14 | { 15 | title: "借阅状态", 16 | key: "borrowStatus", 17 | align: "center", 18 | width: 140, 19 | render: (h, { row }) => { 20 | 21 | return row.borrowStatus ? 22 | 已借出 23 | : 未借出 24 | } 25 | }, 26 | { 27 | title: "类别", 28 | key: "category", 29 | align: "center", 30 | render: (h, { row }) => { 31 | return row.category && row.category.name || "" 32 | } 33 | }, 34 | { 35 | title: "出版社", 36 | key: "press", 37 | align: "center", 38 | render: (h, { row }) => { 39 | return row.press && row.press.name || "" 40 | } 41 | }, 42 | { 43 | title: "作者", 44 | key: "author", 45 | align: "center", 46 | }, 47 | { 48 | title: "上架时间", 49 | key: "createdAt", 50 | align: "center", 51 | render: (h, { row }) => { 52 | return new Date(row.createdAt).Format('yyyy-MM-dd') 53 | } 54 | }, 55 | { 56 | title: "操作", 57 | align: "center", 58 | width: 100, 59 | render: (h, { row }) => { 60 | const editClick = () => { 61 | this.modal.show = true; 62 | this.modal.row = row; 63 | } 64 | const status = row.borrowStatus; 65 | return ( 66 |
67 | {status ? : null} 68 | {!status ? : null} 69 |
70 | ) 71 | } 72 | } 73 | ] 74 | } -------------------------------------------------------------------------------- /src/pages/borrow/list/index.vue: -------------------------------------------------------------------------------- 1 | 48 | 49 | 51 | 52 | -------------------------------------------------------------------------------- /src/pages/borrow/list/main.js: -------------------------------------------------------------------------------- 1 | import { Book } from "@/service"; 2 | import columns from "./columns"; 3 | import { stringify } from "querystring"; 4 | import base from '@/mixins/base' 5 | import dict from '@/mixins/dict' 6 | export default { 7 | name: "Borrow", 8 | mixins: [base, dict], 9 | components: { 10 | BorrowBook: () => import('../borrowBook') 11 | }, 12 | data() { 13 | return { 14 | loading: false, 15 | data: [], 16 | count: 0, 17 | params: { 18 | pageNo: 1, //当前页 19 | size: 20, 20 | category: "", 21 | name: "", 22 | press: "", 23 | author: "", 24 | createdAt: "" 25 | }, 26 | rows: [], 27 | columns: columns.call(this), 28 | categoryList: [], 29 | pressList: [], 30 | modal: { 31 | show: false, 32 | row: null,//编辑的对象 33 | }, 34 | }; 35 | }, 36 | created() { 37 | this.showList(); 38 | this.getCategoryAll(); 39 | this.getPressAll(); 40 | }, 41 | methods: { 42 | async addrow() { 43 | this.modal.show = true; 44 | }, 45 | async showList() { 46 | let { createdAt } = this.params; 47 | createdAt = createdAt ? new Date(this.params.createdAt).Format('yyyy-MM-dd') : "" 48 | const { data } = await Book.get({ 49 | ...this.params, 50 | createdAt 51 | }); 52 | let { rows = [], count } = data; 53 | this.data = rows; 54 | this.count = count; 55 | this.$Message.success("加载完成"); 56 | }, 57 | async confirmDel(ids) { 58 | let { data } = await Book.delete({ 59 | ids: ids || this.rows.map(({ _id }) => _id) 60 | }); 61 | this.$Message.success("删除成功!"); 62 | this.showList(); 63 | }, 64 | 65 | } 66 | }; -------------------------------------------------------------------------------- /src/pages/category/list/columns.js: -------------------------------------------------------------------------------- 1 | import router from "@/router"; 2 | export default function () { 3 | return [ 4 | { 5 | type: "selection", 6 | width: 60, 7 | align: "center" 8 | }, 9 | { 10 | title: "_id", 11 | width: 200, 12 | key: "_id", 13 | align: "center" 14 | }, 15 | { 16 | title: "名称", 17 | key: "name", 18 | align: "center", 19 | }, 20 | { 21 | title: "操作", 22 | align: "center", 23 | width: 150, 24 | render: (h, { row }) => { 25 | const editClick = () => { 26 | this.modal.show = true; 27 | this.modal.row = row; 28 | } 29 | return ( 30 |
31 | 32 | 33 |
34 | ) 35 | } 36 | } 37 | ] 38 | } -------------------------------------------------------------------------------- /src/pages/category/list/index.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 31 | 32 | -------------------------------------------------------------------------------- /src/pages/category/list/main.js: -------------------------------------------------------------------------------- 1 | import { Category } from "@/service"; 2 | import columns from "./columns"; 3 | import base from '@/mixins/base' 4 | export default { 5 | name: "Category", 6 | mixins: [base], 7 | components: { 8 | NewRow: () => import('../newRow/index.vue') 9 | }, 10 | data() { 11 | return { 12 | loading: false, 13 | data: [], 14 | count: 0, 15 | params: { 16 | pageNo: 1, //当前页 17 | size: 20, 18 | name:"" 19 | }, 20 | rows: [], 21 | columns: columns.call(this), 22 | modal: { 23 | show: false, 24 | row:null,//编辑的对象 25 | } 26 | }; 27 | }, 28 | created() { 29 | this.showList(); 30 | }, 31 | methods: { 32 | async addrow() { 33 | this.modal.show = true; 34 | }, 35 | async showList() { 36 | const { data } = await Category.get({ 37 | ...this.params, 38 | }); 39 | let { rows = [], count } = data; 40 | this.data = rows; 41 | this.count = count; 42 | this.$Message.success("加载完成"); 43 | }, 44 | async confirmDel(ids) { 45 | let { data } = await Category.delete({ 46 | ids: ids || this.rows.map(({ _id }) => _id) 47 | }); 48 | this.$Message.success("删除成功!"); 49 | this.showList(); 50 | }, 51 | 52 | } 53 | }; -------------------------------------------------------------------------------- /src/pages/category/newRow/index.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/pages/category/newRow/main.js: -------------------------------------------------------------------------------- 1 | import { Category } from "@/service"; 2 | export default { 3 | props: { 4 | modal: Object 5 | }, 6 | watch: { 7 | "modal.show": { 8 | handler: function () { 9 | if (this.modal.show && this.modal.row) { 10 | return (this.params = { ...this.params, ...this.modal.row }); 11 | } 12 | this.modal.row = null; 13 | }, 14 | immediate: true 15 | } 16 | }, 17 | data() { 18 | return { 19 | params: this.initData(), 20 | rules: { 21 | name: { type: "string", required: true, message: "必填项" } 22 | } 23 | }; 24 | }, 25 | methods: { 26 | initData() { 27 | return { 28 | _id: undefined, 29 | name: "", 30 | }; 31 | }, 32 | async modalOk() { 33 | let validate = await this.$refs.form.validate(); 34 | if (!validate) return; 35 | if (!this.params._id) { 36 | await this.create(); 37 | } else { 38 | await this.update(); 39 | } 40 | this.$Message.success("提交成功!"); 41 | this.initModal("ok"); 42 | }, 43 | async create() { 44 | let { data } = await Category.create(this.$QS.stringify(this.params)); 45 | }, 46 | async update() { 47 | let { data } = await Category.update(this.$QS.stringify(this.params)); 48 | }, 49 | 50 | cancel() { 51 | this.initModal("cancel"); 52 | }, 53 | initModal(type) { 54 | this.params = this.initData(); 55 | this.modal.show = false; 56 | this.$refs.form.resetFields(); 57 | this.$emit(type); 58 | } 59 | } 60 | }; -------------------------------------------------------------------------------- /src/pages/errors/index.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 27 | 28 | 73 | -------------------------------------------------------------------------------- /src/pages/index/Index.vue: -------------------------------------------------------------------------------- 1 | 4 | 21 | 46 | 47 | -------------------------------------------------------------------------------- /src/pages/index/style.less: -------------------------------------------------------------------------------- 1 | .main{ 2 | position: absolute; 3 | width: 100%; 4 | height: 100%; 5 | .single-page-con{ 6 | position: absolute; 7 | top: 64px; 8 | left: 200px; 9 | right: 0; 10 | bottom: 0; 11 | background-color: #f0f2f5; 12 | z-index: 1; 13 | transition: left .4s; 14 | .single-page{ 15 | padding: 25px; 16 | overflow-y: auto; 17 | height: 100%; 18 | padding-bottom: 50px; 19 | } 20 | } 21 | .single-page-sk{ 22 | left: 60px; 23 | } 24 | } -------------------------------------------------------------------------------- /src/pages/login/index.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 35 | 38 | -------------------------------------------------------------------------------- /src/pages/login/main.js: -------------------------------------------------------------------------------- 1 | import { User } from "@/service"; 2 | import utils from "@/utils/utils"; 3 | const { storage } = utils; 4 | export default { 5 | name: "Login", 6 | data() { 7 | return { 8 | loading: false, 9 | remember: true, 10 | code: "/api/login/checkcode", 11 | forms: { 12 | userName: "", 13 | password: "", 14 | img: "" 15 | }, 16 | rules: { 17 | userName: { 18 | required: true, 19 | message: "The userName cannot be empty", 20 | trigger: "blur" 21 | }, 22 | password: { 23 | required: true, 24 | message: "The password cannot be empty", 25 | trigger: "blur" 26 | }, 27 | img: { 28 | required: true, 29 | message: "The checkcode cannot be empty", 30 | trigger: "blur" 31 | } 32 | } 33 | }; 34 | }, 35 | created() { 36 | this.initPwdLocal(); 37 | }, 38 | methods: { 39 | resetCode() { 40 | this.code = `${this.code}?t=${+new Date()}`; 41 | this.forms.img = ""; 42 | }, 43 | async submit() { 44 | let result = await this.$refs.forms.validate(); 45 | if (!result) return; 46 | this.loading = true; 47 | try { 48 | let { data } = await User.login( 49 | this.$QS.stringify(this.forms) 50 | ); 51 | this.loading = false; 52 | this.$Message.success("登录成功"); 53 | this.pwdlocal(); 54 | this.$store.commit("users/users", data); 55 | this.$router.replace({ name: "User" }); 56 | } catch (e) { 57 | this.loading = false; 58 | this.resetCode(); 59 | } 60 | }, 61 | //记住密码 62 | pwdlocal() { 63 | if (this.remember) { 64 | storage.setLocal("password", this.forms.password); 65 | storage.setLocal("userName", this.forms.userName); 66 | } else { 67 | storage.setLocal("password", ""); 68 | storage.setLocal("userName", ""); 69 | } 70 | }, 71 | //获取密码 72 | initPwdLocal() { 73 | if (this.remember) { 74 | this.forms.password = storage.getLocal("password")||""; 75 | this.forms.userName = storage.getLocal("userName")||""; 76 | } 77 | } 78 | } 79 | }; -------------------------------------------------------------------------------- /src/pages/login/style.less: -------------------------------------------------------------------------------- 1 | #login { 2 | height: 100%; 3 | background: url(../../../static/imgs/login-bg.jpg) center center; 4 | background-size: cover; 5 | .login-mask { 6 | height: 100%; 7 | background-color: rgba(0, 0, 0, 0.35); 8 | } 9 | .form-sub { 10 | .icon { 11 | vertical-align: middle; 12 | margin-left: 16px; 13 | font-size: 30px; 14 | color: rgba(0, 0, 0, 0.2); 15 | transition: all 0.5s; 16 | cursor: pointer; 17 | } 18 | i.icon-weibo-copy { 19 | .icon; 20 | &:hover { 21 | color: rgba(230, 22, 45, 0.7); 22 | } 23 | } 24 | } 25 | .form-wrap { 26 | h1 { 27 | text-align: center; 28 | padding-bottom: 15px; 29 | font-size: 33px; 30 | } 31 | p { 32 | text-align: center; 33 | padding: 15px 0; 34 | span { 35 | padding: 0 10px; 36 | padding-bottom: 10px; 37 | font-size: 18px; 38 | display: inline-block; 39 | } 40 | } 41 | width: 380px; 42 | background-color: #fff; 43 | padding: 15px 30px; 44 | border-radius: 4px; 45 | position: absolute; 46 | left: 50%; 47 | top: 50%; 48 | transform: translate(-50%, -50%); 49 | box-shadow: 1px 1px 8px rgba(0, 0, 0, 0.4); 50 | } 51 | .form-margin { 52 | margin-bottom: 15px; 53 | } 54 | .form-code { 55 | padding-right: 105px; 56 | position: relative; 57 | } 58 | .checkcode { 59 | position: absolute; 60 | right: 0; 61 | top: 0; 62 | height: 36px; 63 | border: 1px solid #dddee1; 64 | } 65 | @media screen and(max-width:678px) { 66 | .form-wrap { 67 | width: 90%; 68 | margin-left: 0; 69 | } 70 | } 71 | } -------------------------------------------------------------------------------- /src/pages/main_com/header/index.less: -------------------------------------------------------------------------------- 1 | @import '../../../styles/custom.less'; 2 | .header{ 3 | position: fixed; 4 | top: 0; 5 | left: 0; 6 | display: flex; 7 | align-items: center; 8 | padding: 0 16px; 9 | width: 100%; 10 | height: 64px; 11 | box-shadow: 0 1px 10px rgba(0,0,0,.2); 12 | z-index: 22; 13 | background-color: @white-cyan; 14 | transition: all 1s; 15 | .logo{ 16 | margin-left: -16px; 17 | width: 200px; 18 | transition: width .2s cubic-bezier(.25,0,.15,1); 19 | a{ 20 | display: block; 21 | text-align: center; 22 | } 23 | p{ 24 | vertical-align: middle; 25 | max-width: 100%; 26 | animation: fadeIn 1s; 27 | color: #fff; 28 | font-size: 16px; 29 | } 30 | } 31 | } 32 | .top-nav-wrap { 33 | flex: 1; 34 | display: flex; 35 | align-items: center; 36 | .left i{ 37 | color: #fff; 38 | font-size: 32px; 39 | transition: transform .4s; 40 | }.company{ 41 | font-size: 14px; 42 | margin-right: 8px; 43 | color: #fff; 44 | } 45 | .bread{ 46 | padding-left:10px; 47 | flex:1; 48 | a,span{ 49 | color: #fff; 50 | font-size: 14px; 51 | } 52 | } 53 | .right{ 54 | .liChild{ 55 | padding: 4px 10px; 56 | cursor: pointer; 57 | } 58 | .theme{ 59 | padding:6px 0 6px 8px; 60 | } 61 | .last{ 62 | padding-right: 0; 63 | } 64 | width: 210px; 65 | display: flex; 66 | justify-content: space-between; 67 | align-items: center; 68 | flex-direction: row; 69 | .down{ 70 | display: inline-block; 71 | padding-right: 10px; 72 | } 73 | .down a{ 74 | color: #fff; 75 | 76 | } 77 | .radius{ 78 | display: inline-block; 79 | background-color: #ccc; 80 | width: 24px; 81 | height: 24px; 82 | line-height: 24px; 83 | text-align: center; 84 | border-radius: 50%; 85 | } 86 | .arrow-expand,.person{ 87 | font-size: 23px; 88 | color: #fff; 89 | } 90 | .person{ 91 | font-size: 14px; 92 | } 93 | .color-paint{ 94 | padding: 0; 95 | } 96 | } 97 | } 98 | .hover:hover{ 99 | background-color: hsla(0,0%,100%,.2)!important; 100 | } -------------------------------------------------------------------------------- /src/pages/main_com/header/index.vue: -------------------------------------------------------------------------------- 1 | 66 | 103 | 106 | -------------------------------------------------------------------------------- /src/pages/main_com/routeBox/index.less: -------------------------------------------------------------------------------- 1 | .route-box { 2 | width: 100%; 3 | height: 42px; 4 | background: rgba(255, 255, 255, 0.979)!important; 5 | padding: 3px 4px 4px 4px; 6 | border-bottom: 1px solid #efe3e5; 7 | box-shadow: 0 1px 5px #eee; 8 | position: relative; 9 | z-index: 1; 10 | overflow: hidden; 11 | .route-inner-scroll{ 12 | position: absolute; 13 | padding: 2px 10px; 14 | overflow: visible; 15 | white-space: nowrap; 16 | transition: left .3s ease; 17 | left: 0; 18 | top: 0; 19 | height: 100%; 20 | } 21 | .route-close { 22 | position: absolute; 23 | right: 0; 24 | top: 0; 25 | box-sizing: border-box; 26 | padding-top: 8px; 27 | text-align: center; 28 | width: 110px; 29 | height: 100%; 30 | background: #fff; 31 | box-shadow: -3px 0 15px 3px rgba(0,0,0,.1); 32 | z-index: 10; 33 | } 34 | } 35 | .taglist-moving-animation-move{ 36 | transition: transform .3s; 37 | } -------------------------------------------------------------------------------- /src/pages/main_com/routeBox/index.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 142 | 143 | 146 | -------------------------------------------------------------------------------- /src/pages/main_com/sidebarMenu/sidebarMenu.less: -------------------------------------------------------------------------------- 1 | @import '../../../styles/custom.less'; 2 | .sidebar-menu-con::-webkit-scrollbar-track { 3 | background-color: transparent; 4 | } 5 | .sidebar-menu-con::-webkit-scrollbar-track { 6 | background-color: transparent; 7 | } 8 | .sidebar-menu-con::-webkit-scrollbar-thumb { 9 | background-color: transparent; 10 | } 11 | .sidebar-menu-con::-webkit-scrollbar { 12 | width: 0; 13 | height:0; 14 | background-color: transparent; 15 | } 16 | .sidebar-menu-con { 17 | width: 200px; 18 | overflow: auto; 19 | background: #fff; 20 | height: 100%; 21 | position: fixed; 22 | top: 0; 23 | left: 0; 24 | z-index: 21; 25 | transition: width .4s; 26 | overflow-x: hidden; 27 | border-right: 1px solid #efe3e5; 28 | 29 | .menu-po { 30 | width: 200px!important; 31 | } 32 | .logo-con { 33 | text-align: center; 34 | padding: 20px; 35 | height: 65px; 36 | img { 37 | width: 100%; 38 | } 39 | } 40 | } 41 | .menu-icon{ 42 | color: rgba(0,0,0,0.50); 43 | transition:font-size .15s cubic-bezier(.215,.61,.355,1),margin .3s cubic-bezier(.645,.045,.355,1); 44 | font-size: 16px; 45 | } 46 | .font-center{ 47 | .font-center-btn{ 48 | width: 70px; 49 | margin-left: -5px; 50 | padding:10px 0; 51 | } 52 | } 53 | /** 收缩状态**/ 54 | .sidebar-menu-sk { 55 | width: 60px; 56 | .logo-con{ 57 | padding: 10px; 58 | } 59 | .menu-icon { 60 | transition:font-size .15s cubic-bezier(.215,.61,.355,1),margin .3s cubic-bezier(.645,.045,.355,1); 61 | font-size: 24px; 62 | } 63 | .font-center:hover .menu-icon{ 64 | color: @white-cyan; 65 | } 66 | .ivu-dropdown-item{ 67 | padding: 9px 13px; 68 | text-align: left; 69 | } 70 | .ivu-dropdown-menu{ 71 | max-height: 200px; 72 | overflow-y: auto; 73 | overflow-x: hidden; 74 | } 75 | } 76 | .layout-text{ 77 | display: inline-block; 78 | white-space: nowrap; 79 | font-size: 14px; 80 | opacity: .85; 81 | } 82 | .ivu-menu-vertical .ivu-menu-item, .ivu-menu-vertical .ivu-menu-submenu-title{ 83 | white-space: normal; 84 | padding: 8px 16px; 85 | } 86 | .ivu-menu-submenu-title>.ivu-icon{ 87 | margin-right: 0; 88 | } 89 | // active样式 90 | .ivu-menu-light.ivu-menu-vertical .ivu-menu-item-active:not(.ivu-menu-submenu){ 91 | color: @white-cyan; 92 | border: 0; 93 | } 94 | .ivu-menu-submenu{ 95 | border-left: 3px solid transparent; 96 | 97 | } 98 | .ivu-menu-opened{ 99 | border-color: @white-cyan; 100 | background-color: #fcfcfc; 101 | .ivu-menu-submenu-title,.menu-icon{ 102 | color: @white-cyan; 103 | } 104 | } 105 | .ivu-menu-vertical .ivu-menu-item:hover, .ivu-menu-vertical .ivu-menu-submenu-title:hover{ 106 | color: @white-cyan; 107 | background-color: transparent; 108 | i{ 109 | color: @white-cyan; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/pages/main_com/sidebarMenu/sidebarMenu.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 72 | 73 | -------------------------------------------------------------------------------- /src/pages/press/list/columns.js: -------------------------------------------------------------------------------- 1 | import router from "@/router"; 2 | export default function () { 3 | return [ 4 | { 5 | type: "selection", 6 | width: 60, 7 | align: "center" 8 | }, 9 | { 10 | title: "_id", 11 | width: 200, 12 | key: "_id", 13 | align: "center" 14 | }, 15 | { 16 | title: "名称", 17 | key: "name", 18 | align: "center", 19 | }, 20 | { 21 | title: "操作", 22 | align: "center", 23 | width: 150, 24 | render: (h, { row }) => { 25 | const editClick = () => { 26 | this.modal.show = true; 27 | this.modal.row = row; 28 | } 29 | return ( 30 |
31 | 32 | 33 |
34 | ) 35 | } 36 | } 37 | ] 38 | } -------------------------------------------------------------------------------- /src/pages/press/list/index.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 31 | 32 | -------------------------------------------------------------------------------- /src/pages/press/list/main.js: -------------------------------------------------------------------------------- 1 | import { Press } from "@/service"; 2 | import columns from "./columns"; 3 | import base from '@/mixins/base' 4 | export default { 5 | name: "Press", 6 | mixins: [base], 7 | components: { 8 | NewRow: () => import('../newRow/index.vue') 9 | }, 10 | data() { 11 | return { 12 | loading: false, 13 | data: [], 14 | count: 0, 15 | params: { 16 | pageNo: 1, //当前页 17 | size: 20 18 | }, 19 | rows: [], 20 | columns: columns.call(this), 21 | modal: { 22 | show: false, 23 | row:null,//编辑的对象 24 | } 25 | }; 26 | }, 27 | created() { 28 | this.showList(); 29 | }, 30 | methods: { 31 | async addrow() { 32 | this.modal.show = true; 33 | }, 34 | async showList() { 35 | const { data } = await Press.get({ 36 | ...this.params, 37 | }); 38 | let { rows = [], count } = data; 39 | this.data = rows; 40 | this.count = count; 41 | this.$Message.success("加载完成"); 42 | }, 43 | async confirmDel(ids) { 44 | let { data } = await Press.delete({ 45 | ids: ids || this.rows.map(({ _id }) => _id) 46 | }); 47 | this.$Message.success("删除成功!"); 48 | this.showList(); 49 | }, 50 | 51 | } 52 | }; -------------------------------------------------------------------------------- /src/pages/press/newRow/index.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/pages/press/newRow/mian.js: -------------------------------------------------------------------------------- 1 | import { Press } from "@/service"; 2 | export default { 3 | props: { 4 | modal: Object 5 | }, 6 | watch: { 7 | "modal.show": { 8 | handler: function () { 9 | if (this.modal.show && this.modal.row) { 10 | return (this.params = { ...this.params, ...this.modal.row }); 11 | } 12 | this.modal.row = null; 13 | }, 14 | immediate: true 15 | } 16 | }, 17 | data() { 18 | return { 19 | params: this.initData(), 20 | rules: { 21 | name: { type: "string", required: true, message: "必填项" } 22 | } 23 | }; 24 | }, 25 | methods: { 26 | initData() { 27 | return { 28 | _id: undefined, 29 | name: "", 30 | }; 31 | }, 32 | async modalOk() { 33 | let validate = await this.$refs.form.validate(); 34 | if (!validate) return; 35 | if (!this.params._id) { 36 | await this.create(); 37 | } else { 38 | await this.update(); 39 | } 40 | this.$Message.success("提交成功!"); 41 | this.initModal("ok"); 42 | }, 43 | async create() { 44 | let { data } = await Press.create(this.$QS.stringify(this.params)); 45 | }, 46 | async update() { 47 | let { data } = await Press.update(this.$QS.stringify(this.params)); 48 | }, 49 | 50 | cancel() { 51 | this.initModal("cancel"); 52 | }, 53 | initModal(type) { 54 | this.params = this.initData(); 55 | this.modal.show = false; 56 | this.$refs.form.resetFields(); 57 | this.$emit(type); 58 | } 59 | } 60 | }; -------------------------------------------------------------------------------- /src/pages/ranking/charts/readingInit.js: -------------------------------------------------------------------------------- 1 | // 图书借阅量统计图 2 | export function readingInit({ data, dataset },ctx) { 3 | let source = dataset.createView('reading').source(data); 4 | let chart = new G2.Chart({ 5 | container: "chart1", 6 | forceFit: true, 7 | height: 250 8 | }); 9 | chart.source(source); 10 | Bar(chart) 11 | return chart 12 | } 13 | export function Bar(chart, clear) { 14 | clear && chart.clear(); 15 | chart.interval().position('name*borrowTotal').color('name') 16 | chart.render(); 17 | } -------------------------------------------------------------------------------- /src/pages/ranking/columns.js: -------------------------------------------------------------------------------- 1 | import router from "@/router"; 2 | export default function () { 3 | return [ 4 | { 5 | type: "index", 6 | width: 60, 7 | align: "center" 8 | }, 9 | { 10 | title: "书名", 11 | key: "name", 12 | align: "center", 13 | }, 14 | { 15 | title: "借阅状态", 16 | key: "borrowStatus", 17 | align: "center", 18 | width:140, 19 | render: (h, { row }) => { 20 | 21 | return row.borrowStatus ? 22 | 已借出 23 | : 未借出 24 | } 25 | }, 26 | { 27 | title: "类别", 28 | key: "category", 29 | align: "center", 30 | render: (h, { row }) => { 31 | return row.category && row.category.name || "" 32 | } 33 | }, 34 | { 35 | title: "出版社", 36 | key: "press", 37 | align: "center", 38 | render: (h, { row }) => { 39 | return row.press && row.press.name || "" 40 | } 41 | }, 42 | { 43 | title: "作者", 44 | key: "author", 45 | align: "center", 46 | }, 47 | { 48 | title: "简介", 49 | key: "desc", 50 | align: "center", 51 | render: (h, { row }) => { 52 | let text = row.desc ? row.desc.substring(0, 5) + '...' : "" 53 | return {text} 54 | } 55 | }, 56 | { 57 | title: "借阅次数", 58 | key: "borrowTotal", 59 | align: "center", 60 | render: (h, { row }) => { 61 | return row.borrowTotal || 0; 62 | } 63 | }, 64 | { 65 | title: "上架时间", 66 | key: "createdAt", 67 | align: "center", 68 | render: (h, { row }) => { 69 | return new Date(row.createdAt).Format('yyyy-MM-dd') 70 | } 71 | }, 72 | ] 73 | } -------------------------------------------------------------------------------- /src/pages/ranking/index.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 57 | 58 | 76 | -------------------------------------------------------------------------------- /src/pages/user/list/columns.js: -------------------------------------------------------------------------------- 1 | import router from "@/router"; 2 | export default function () { 3 | return [ 4 | { 5 | type: "selection", 6 | width: 60, 7 | align: "center" 8 | }, 9 | { 10 | title: "_id", 11 | width: 200, 12 | key: "_id", 13 | align: "center" 14 | }, 15 | { 16 | title: "账号", 17 | key: "userName", 18 | align: "center", 19 | }, 20 | { 21 | title: "姓名", 22 | key: "name", 23 | align: "center", 24 | }, 25 | { 26 | title: "性别", 27 | key: "sex", 28 | align: "center", 29 | }, 30 | { 31 | title: "权限", 32 | key: "isAdmin", 33 | align: "center", 34 | render: (h, { row }) => { 35 | return row.isAdmin ? 管理员 : "读者" 36 | } 37 | }, 38 | { 39 | title: "操作", 40 | align: "center", 41 | width: 200, 42 | render: (h, { row }) => { 43 | const editClick = () => { 44 | this.modal.show = true; 45 | this.modal.row = row; 46 | } 47 | const recordClick = () => { 48 | this.$router.push({ 49 | name: 'Record', 50 | query: { userId: row._id } 51 | }) 52 | } 53 | return ( 54 |
55 | 56 | 57 | 58 |
59 | ) 60 | } 61 | } 62 | ] 63 | } -------------------------------------------------------------------------------- /src/pages/user/list/index.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 31 | 32 | -------------------------------------------------------------------------------- /src/pages/user/list/main.js: -------------------------------------------------------------------------------- 1 | import { User } from "@/service"; 2 | import columns from "./columns"; 3 | import { stringify } from "querystring"; 4 | import base from '@/mixins/base' 5 | export default { 6 | name: "Resou", 7 | mixins: [base], 8 | components: { 9 | NewRow: () => import('../newRow/index.vue') 10 | }, 11 | data() { 12 | return { 13 | loading: false, 14 | data: [], 15 | count: 0, 16 | params: { 17 | pageNo: 1, //当前页 18 | size: 20 19 | }, 20 | rows: [], 21 | columns: columns.call(this), 22 | modal: { 23 | show: false, 24 | row:null,//编辑的对象 25 | } 26 | }; 27 | }, 28 | created() { 29 | this.showList(); 30 | }, 31 | methods: { 32 | async addrow() { 33 | this.modal.show = true; 34 | }, 35 | async showList() { 36 | const { data } = await User.get({ 37 | ...this.params, 38 | }); 39 | let { rows = [], count } = data; 40 | this.data = rows; 41 | this.count = count; 42 | this.$Message.success("加载完成"); 43 | }, 44 | async confirmDel(ids) { 45 | let { data } = await User.delete({ 46 | ids: ids || this.rows.map(({ _id }) => _id) 47 | }); 48 | this.$Message.success("删除成功!"); 49 | this.showList(); 50 | }, 51 | 52 | } 53 | }; -------------------------------------------------------------------------------- /src/pages/user/newRow/index.vue: -------------------------------------------------------------------------------- 1 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/pages/user/newRow/main.js: -------------------------------------------------------------------------------- 1 | import { User } from "@/service"; 2 | export default { 3 | props: { 4 | modal: Object 5 | }, 6 | watch: { 7 | "modal.show": { 8 | handler: function () { 9 | if (this.modal.show && this.modal.row) { 10 | this.modal.row.isAdmin = this.modal.row.isAdmin + ""; 11 | return (this.params = { ...this.params, ...this.modal.row }); 12 | } 13 | this.modal.row = null; 14 | }, 15 | immediate: true 16 | } 17 | }, 18 | data() { 19 | return { 20 | params: this.initData(), 21 | rules: { 22 | name: { type: "string", required: true, message: "必填项" } 23 | } 24 | }; 25 | }, 26 | methods: { 27 | initData() { 28 | return { 29 | _id: undefined, 30 | userName: "", 31 | password: "", 32 | sex: "男", 33 | name: "", 34 | isAdmin: "false" 35 | }; 36 | }, 37 | async modalOk() { 38 | let validate = await this.$refs.form.validate(); 39 | if (!validate) return; 40 | if (!this.params._id) { 41 | await this.create(); 42 | } else { 43 | await this.update(); 44 | } 45 | this.$Message.success("提交成功!"); 46 | this.initModal("ok"); 47 | }, 48 | async create() { 49 | let { data } = await User.create(this.$QS.stringify(this.params)); 50 | }, 51 | async update() { 52 | let { data } = await User.update(this.$QS.stringify(this.params)); 53 | }, 54 | 55 | cancel() { 56 | this.initModal("cancel"); 57 | }, 58 | initModal(type) { 59 | this.params = this.initData(); 60 | this.modal.show = false; 61 | this.$refs.form.resetFields(); 62 | this.$emit(type); 63 | } 64 | } 65 | }; -------------------------------------------------------------------------------- /src/pages/user/record/columns.js: -------------------------------------------------------------------------------- 1 | import router from "@/router"; 2 | export default function () { 3 | return [ 4 | { 5 | title: "状态", 6 | align: "center", 7 | render: (h, { row }) => { 8 | let text = row.borrowStatus == 1 ? '借阅中' : '已还书' 9 | let type = row.borrowStatus == 1 ? 'blue' : 'green' 10 | return ( 11 | {text} 12 | ) 13 | } 14 | }, 15 | { 16 | title: "书籍名称", 17 | align: "center", 18 | render: (h, { row }) => { 19 | return row.book.name 20 | } 21 | }, 22 | { 23 | title: "作者", 24 | align: "center", 25 | render: (h, { row }) => { 26 | return row.book.author 27 | } 28 | }, 29 | { 30 | title: "时间", 31 | align: "center", 32 | render: (h, { row }) => { 33 | return new Date(row.createdAt).Format('yyyy-MM-dd hh:mm:ss') 34 | } 35 | }, 36 | { 37 | title: "用户姓名", 38 | align: "center", 39 | render: (h, { row }) => { 40 | return {row.user.name} 41 | } 42 | }, 43 | { 44 | title: "性别", 45 | align: "center", 46 | render: (h, { row }) => { 47 | return row.user.sex 48 | } 49 | } 50 | ] 51 | } -------------------------------------------------------------------------------- /src/pages/user/record/index.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 30 | 31 | -------------------------------------------------------------------------------- /src/pages/user/record/main.js: -------------------------------------------------------------------------------- 1 | import { Borrow } from "@/service"; 2 | import columns from "./columns"; 3 | import { stringify } from "querystring"; 4 | import base from '@/mixins/base' 5 | export default { 6 | name: "Resou", 7 | mixins: [base], 8 | data() { 9 | return { 10 | loading: false, 11 | data: [], 12 | count: 0, 13 | params: { 14 | pageNo: 1, //当前页 15 | size: 20, 16 | userId: "", 17 | borrowStatus:"1" 18 | }, 19 | rows: [], 20 | columns: columns.call(this), 21 | modal: { 22 | show: false, 23 | row: null,//编辑的对象 24 | } 25 | }; 26 | }, 27 | created() { 28 | this.params.userId = this.$route.query.userId; 29 | this.showList(); 30 | }, 31 | methods: { 32 | async addrow() { 33 | this.modal.show = true; 34 | }, 35 | async showList() { 36 | const { data } = await Borrow.get({ 37 | ...this.params, 38 | }); 39 | let { rows = [], count } = data; 40 | this.data = rows; 41 | this.count = count; 42 | this.$Message.success("加载完成"); 43 | }, 44 | } 45 | }; -------------------------------------------------------------------------------- /src/render.js: -------------------------------------------------------------------------------- 1 | [ 2 | 'Button', 3 | 'Dropdown', 4 | 'DropdownMenu', 5 | 'Icon', 6 | 'DropdownItem', 7 | "Tag", 8 | "Form", 9 | "FormItem", 10 | "Input", 11 | "Row", 12 | "Col" 13 | ].map((item) => window[item] = item) -------------------------------------------------------------------------------- /src/router/event/filterMiddle.js: -------------------------------------------------------------------------------- 1 | import $store from '@/store/index' 2 | import utils from '@/utils/utils' 3 | export function filterMiddle(to, from, next) { 4 | let filter = ['Login'] 5 | if (filter.indexOf(to.name) !== -1) { 6 | next(true) 7 | return false 8 | } 9 | return true 10 | } -------------------------------------------------------------------------------- /src/router/event/index.js: -------------------------------------------------------------------------------- 1 | import $store from '@/store/index' 2 | import utils from '@/utils/utils' 3 | 4 | import { filterMiddle } from './filterMiddle' 5 | import { isLoginMiddle } from './isLoginMiddle' 6 | const fns = [filterMiddle, isLoginMiddle]; 7 | export const beforeEach = (to, from, next) => { 8 | $store.commit('menu/currPageName', to.name) 9 | let isNext = null; 10 | let i = 0; 11 | while (i < fns.length) { 12 | isNext = fns[i](to, from, next); 13 | if (!isNext) break; 14 | i++; 15 | } 16 | if (isNext) { next() } 17 | } 18 | 19 | export const afterEach = ({ path, name, params, query }) => { 20 | utils.openNewPage({ path, name, params, query }, $store) 21 | } 22 | -------------------------------------------------------------------------------- /src/router/event/isLoginMiddle.js: -------------------------------------------------------------------------------- 1 | import utils from '@/utils/utils' 2 | // 判断是否登录 3 | export function isLoginMiddle(to, from, next) { 4 | let users = utils.storage.getLocal('users') 5 | // 已登录 继续执行 6 | if (users) { 7 | return true 8 | } 9 | utils.$Message.warning('请登录') 10 | next({ name:'Login'}); 11 | return false ; 12 | } -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueRouter from 'vue-router' 3 | import { beforeEach, afterEach } from '@/router/event' 4 | /**子路由 */ 5 | const ErrorCom = { 6 | path: 'Errors', 7 | name: 'Errors', 8 | component: () => import('@/pages/errors') 9 | } 10 | const Others = { 11 | path: '*', 12 | redirect: '/home/Errors' 13 | } 14 | const router = new VueRouter({ 15 | routes: [{ 16 | path: "/", 17 | redirect: "/Login" 18 | }, { 19 | path: '/home', 20 | name: 'home', 21 | component: () => import('@/pages/index/Index.vue'), 22 | children: [ 23 | ErrorCom, 24 | { 25 | path: 'User', 26 | name: 'User', 27 | component: () => import('@/pages/user/list') 28 | }, { 29 | path: 'Category', 30 | name: 'Category', 31 | component: () => import('@/pages/category/list') 32 | }, { 33 | path: 'Press', 34 | name: 'Press', 35 | component: () => import('@/pages/press/list') 36 | }, { 37 | path: 'Book', 38 | name: 'Book', 39 | component: () => import('@/pages/book/list') 40 | }, { 41 | path: 'Borrow', 42 | name: 'Borrow', 43 | component: () => import('@/pages/borrow/list') 44 | },{ 45 | path: 'Record', 46 | name: 'Record', 47 | component: () => import('@/pages/user/record') 48 | },{ 49 | path:"Ranking", 50 | name:"Ranking", 51 | component: () => import('@/pages/ranking') 52 | } 53 | ] 54 | }, { 55 | path: '/login', 56 | name: 'Login', 57 | component: () => import('@/pages/login'), 58 | }, Others] 59 | }) 60 | router.beforeEach(beforeEach) 61 | router.afterEach(afterEach); 62 | 63 | export default router 64 | -------------------------------------------------------------------------------- /src/service/index.js: -------------------------------------------------------------------------------- 1 | import http from '@/utils/request.js' 2 | 3 | export const Category = { 4 | async get(params) { 5 | return await http({ 6 | url: '/api/category/list', 7 | params 8 | }); 9 | }, 10 | async getAll(params) { 11 | return await http({ 12 | url: '/api/category/allList', 13 | params 14 | }); 15 | }, 16 | async create(data) { 17 | return await http({ 18 | url: '/api/category/create', 19 | method: "post", 20 | data 21 | }); 22 | }, 23 | async update(data) { 24 | return await http({ 25 | url: '/api/category/update', 26 | method: "post", 27 | data 28 | }); 29 | }, 30 | async delete(data) { 31 | return await http({ 32 | url: '/api/category/remove', 33 | method: 'delete', 34 | data 35 | }); 36 | } 37 | } 38 | 39 | export const Press = { 40 | async get(params) { 41 | return await http({ 42 | url: '/api/press/list', 43 | params 44 | }); 45 | }, 46 | async getAll(params) { 47 | return await http({ 48 | url: '/api/press/allList', 49 | params 50 | }); 51 | }, 52 | async create(data) { 53 | return await http({ 54 | url: '/api/press/create', 55 | method: "post", 56 | data 57 | }); 58 | }, 59 | async update(data) { 60 | return await http({ 61 | url: '/api/press/update', 62 | method: "post", 63 | data 64 | }); 65 | }, 66 | async delete(data) { 67 | return await http({ 68 | url: '/api/press/remove', 69 | method: 'delete', 70 | data 71 | }); 72 | } 73 | } 74 | 75 | export const Book = { 76 | async get(params) { 77 | return await http({ 78 | url: '/api/book/list', 79 | params 80 | }); 81 | }, 82 | async create(data) { 83 | return await http({ 84 | url: '/api/book/create', 85 | method: "post", 86 | data 87 | }); 88 | }, 89 | async update(data) { 90 | return await http({ 91 | url: '/api/book/update', 92 | method: "post", 93 | data 94 | }); 95 | }, 96 | async delete(data) { 97 | return await http({ 98 | url: '/api/book/remove', 99 | method: 'delete', 100 | data 101 | }); 102 | }, 103 | async borrow(data) { 104 | return await http({ 105 | url: '/api/book/borrow', 106 | method: "post", 107 | data 108 | }); 109 | }, 110 | async readingAmount(){ 111 | return await http({ 112 | url: '/api/book/readingAmount', 113 | }); 114 | } 115 | } 116 | 117 | 118 | export const User = { 119 | async login(data) { 120 | return await http({ 121 | url: '/api/login', 122 | method: 'post', 123 | data 124 | }); 125 | }, 126 | async loginOut() { 127 | return await http({ 128 | url: '/api/loginOut', 129 | method: 'post' 130 | }) 131 | }, 132 | async get(params) { 133 | return await http({ 134 | url: '/api/users/list', 135 | params 136 | }); 137 | }, 138 | async create(data) { 139 | return await http({ 140 | url: '/api/users/create', 141 | method: "post", 142 | data 143 | }); 144 | }, 145 | async update(data) { 146 | return await http({ 147 | url: '/api/users/update', 148 | method: "post", 149 | data 150 | }); 151 | }, 152 | async delete(data) { 153 | return await http({ 154 | url: '/api/users/remove', 155 | method: 'delete', 156 | data 157 | }); 158 | } 159 | } 160 | 161 | 162 | export const Borrow = { 163 | async get(params) { 164 | return await http({ 165 | url: '/api/book/record', 166 | params 167 | }); 168 | } 169 | } -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | //全局模块 4 | import cache from '@/store/modules/cache'//缓存 5 | import menu from '@/store/modules/menu' 6 | import defaults from '@/store/modules/defaults' 7 | import users from '@/store/modules/users' 8 | //组件模块 9 | const state = { 10 | modules: { 11 | cache, 12 | users, 13 | menu, 14 | defaults 15 | } 16 | }; 17 | const store = new Vuex.Store(state); 18 | export default store; -------------------------------------------------------------------------------- /src/store/modules/cache.js: -------------------------------------------------------------------------------- 1 | import $router from '@/router/index' 2 | import utils from '@/utils/utils' 3 | let { is } = utils; 4 | export default { 5 | namespaced: true, 6 | state: { 7 | cacheList: ['$root'] 8 | }, 9 | mutations: { 10 | /*添加一个页面到缓存中 */ 11 | addCache(state, value) { 12 | if (state.cacheList.indexOf(value) == -1) { 13 | state.cacheList.push(value); 14 | } 15 | }, 16 | /**删除指定缓存 */ 17 | removeCache(state, value) { 18 | if (state.cacheList.indexOf(value) !== -1) { 19 | let index = state.cacheList.indexOf(value); 20 | state.cacheList.splice(index, 1); 21 | } 22 | }, 23 | /**刷新指定缓存 */ 24 | refresh(state, value) { 25 | if (!value) { 26 | state.cacheList = ['$root']; 27 | setTimeout(() => { 28 | state.cacheList = ['$root'].concat(initCache()); 29 | }); 30 | return; 31 | } 32 | let index = state.cacheList.indexOf(value); 33 | if (index !== -1) { 34 | state.cacheList.splice(index, 1); 35 | setTimeout(() => { 36 | state.cacheList.push(value); 37 | }); 38 | } 39 | }, 40 | /**不刷新指定缓存 */ 41 | notRefresh(state, value){ 42 | state.cacheList = ['$root',value]; 43 | setTimeout(() => { 44 | state.cacheList = ['$root'].concat(initCache()); 45 | }); 46 | }, 47 | /**删除页面缓存 */ 48 | clearCache(state, value) { 49 | state.cacheList = ['$root']; 50 | }, 51 | /**初始化缓存 */ 52 | initCache(state, value) { 53 | state.cacheList = ['$root'].concat(initCache()); 54 | } 55 | } 56 | } 57 | function initCache() { 58 | let { routes } = $router.options; 59 | let names = []; 60 | getCache(routes, names) 61 | return names; 62 | } 63 | function getCache(routes, result) { 64 | if (!is().Array(routes)) { 65 | return; 66 | } 67 | if (routes.length == 0) { 68 | return; 69 | } 70 | for (let i = 0; i < routes.length; i++) { 71 | let route = routes[i]; 72 | if (route.meta && route.meta.keepAlive) { 73 | result.push(route.name); 74 | } 75 | getCache(route.children, result) 76 | } 77 | } 78 | 79 | -------------------------------------------------------------------------------- /src/store/modules/defaults.js: -------------------------------------------------------------------------------- 1 | /** 全局配置 */ 2 | export default { 3 | namespaced: true, 4 | state: { 5 | 6 | }, 7 | mutations: { 8 | 9 | } 10 | } -------------------------------------------------------------------------------- /src/store/modules/menu.js: -------------------------------------------------------------------------------- 1 | import axios from '@/utils/request' 2 | import { LOGOUT, MY_MENU } from '@/utils/interface' 3 | import QS from 'querystring' 4 | import $router from '@/router/index' 5 | import utils from '@/utils/utils' 6 | import $store from '@/store/index' 7 | import menuArr from '@/config/menus' 8 | import { Homes, title } from '@/config/config.default.js' 9 | import { User } from "@/service"; 10 | 11 | let { storage, is, attrData } = utils 12 | //初始化缓存 13 | let root = attrData('root', 'Object', { 14 | title: title, 15 | routerName: Homes, 16 | name: Homes 17 | }); 18 | export default { 19 | namespaced: true, 20 | state: { 21 | menus: menuArr,//导航菜单 22 | currentPath: attrData('currentPath', 'Array', [root]), 23 | openedArr: attrData('openedArr', 'Array', []), // 要展开的菜单数组 24 | pageList: attrData('pageList', 'Array', [root]), 25 | currPageName: attrData('currPageName'),//当前路由name 26 | theme: attrData('theme'),//当前主题 27 | themeList: getThemeList(),//主题 28 | hideMenuText: attrData('hideMenuText', 'Boolean', false), 29 | root 30 | }, 31 | mutations: { 32 | //设置主题 33 | theme(state, theme) { 34 | let { themeList } = state; 35 | state.theme = theme; 36 | storage.setLocal('theme', theme); 37 | }, 38 | root(state, root) { 39 | state.root = root; 40 | state.pageList.splice(0, 1, root); 41 | storage.setLocal('root', root); 42 | }, 43 | menus(state, menus) { 44 | if (!is().Array(menus)) { 45 | menus = []; 46 | } 47 | menus = setMenu(menus); 48 | state.menus = menus; 49 | storage.setLocal('menus', menus); 50 | function setMenu(menus) { 51 | if (!is().Array(menus)) { return; } 52 | if (menus.length == 0) { return; } 53 | menus = menus.filter(({ routerName, children }, index) => { 54 | menus[index].children = setMenu(children) 55 | return true; 56 | }); 57 | return menus; 58 | } 59 | }, 60 | hideMenuText(state, value) { 61 | state.hideMenuText = value !== undefined ? value : (!state.hideMenuText); 62 | storage.setLocal('hideMenuText', state.hideMenuText); 63 | }, 64 | currentPath(state, { parent, children }) { 65 | state.currentPath = [state.root, parent, children]; 66 | storage.setLocal('currentPath', state.currentPath); 67 | }, 68 | currPageName(state, currPageName) { 69 | state.currPageName = currPageName; 70 | storage.setLocal('currPageName', currPageName); 71 | }, 72 | openedArr(state, openedArr) { 73 | state.openedArr = openedArr; 74 | storage.setLocal('openedArr', openedArr); 75 | }, 76 | addPageList(state, page) { 77 | state.pageList.push(page); 78 | storage.setLocal('pageList', state.pageList); 79 | }, 80 | //切换当前路由 可从缓存中获取参数,若要重新传参,可以直接使用 router.push 81 | toPage(state, name) { 82 | let { pageList } = state; 83 | let index = pageList.map(({ name }) => name).indexOf(name); 84 | if (index !== -1) { 85 | let { name, params, query } = pageList[index]; 86 | $router.push({ 87 | name, 88 | params, 89 | query 90 | }); 91 | } 92 | }, 93 | deletePageList(state, name) { 94 | let { pageList } = state; 95 | for (let i = 0; i < pageList.length; i++) { 96 | let page = pageList[i]; 97 | if (page.name == name) { 98 | $store.commit('cache/refresh', name); 99 | pageList.splice(i, 1); 100 | storage.setLocal('pageList', pageList); 101 | page = pageList[pageList.length - 1]; 102 | $router.push({ 103 | name: page.name, 104 | params: page.params, 105 | query: page.query 106 | }); 107 | return; 108 | } 109 | } 110 | }, 111 | updatePageParam(state, { name, params = {}, query = {} }) { 112 | let { pageList } = state; 113 | let index = pageList.map(({ name }) => name).indexOf(name); 114 | if (index > 0) { 115 | let page = pageList[index]; 116 | page = { name, params, query }; 117 | pageList.splice(index, 1, page); 118 | } 119 | }, 120 | pageColoseAll(state) { 121 | $router.push({ name: state.root.name }); 122 | state.pageList = [state.root]; 123 | $store.commit('cache/refresh'); 124 | storage.setLocal('pageList', state.pageList); 125 | }, 126 | pageClearOthers(state) { 127 | let { pageList, currPageName } = state; 128 | let newPageList = [state.root]; 129 | for (let i = 0; i < pageList.length; i++) { 130 | let page = pageList[i]; 131 | if (page.name == currPageName && page.name !== state.root.name) { 132 | newPageList.push(page); 133 | break; 134 | } 135 | } 136 | $store.commit('cache/notRefresh', currPageName); 137 | state.pageList = newPageList; 138 | storage.setLocal('pageList', state.pageList); 139 | }, 140 | //根据权限跳转到对应首页 141 | toHome(state) { 142 | let { menus } = state; 143 | let root = {}; 144 | let i = 0; 145 | while (i < menus.length) { 146 | let page = menus[i]; 147 | if (page.routerName == Homes) { 148 | root.title = page.name; 149 | root.routerName = page.routerName; 150 | root.name = page.routerName; 151 | break; 152 | } 153 | if (is().Array(page.children) && page.children.length > 0) { 154 | root.title = page.children[0].name; 155 | root.routerName = page.children[0].routerName; 156 | root.name = page.children[0].routerName; 157 | break; 158 | } 159 | i++; 160 | } 161 | $store.commit('menu/root', root); 162 | $router.replace({ name: root.name }); 163 | }, 164 | //清除路由状态 165 | clearPage(state) { 166 | state.pageList = []; 167 | state.openedArr = []; 168 | state.currentPath = []; 169 | state.currPageName = ''; 170 | state.root = {}; 171 | storage.setLocal('pageList', ''); 172 | storage.setLocal('openedArr', ''); 173 | storage.setLocal('currentPath', ''); 174 | storage.setLocal('currPageName', ''); 175 | storage.setLocal('root', ''); 176 | }, 177 | }, 178 | actions: { 179 | //退出登录 180 | async logOutBtn({ commit }) { 181 | // await User.loginOut(); 182 | $router.replace({ name: 'Login' }); 183 | $store.commit('users/clearUser'); 184 | commit('clearPage'); 185 | } 186 | } 187 | } 188 | /** 189 | * @description 获取主题配置 190 | */ 191 | function getThemeList() { 192 | return [ 193 | // { 194 | // name: "white-cyan", 195 | // element: "rgb(0,162,174)" 196 | // }, 197 | { 198 | name: "white-blue", 199 | element: "rgb(16,142,233)" 200 | }, 201 | // { 202 | // name: "white-green", 203 | // element: "rgb(0,168,84)" 204 | // }, 205 | // { 206 | // name: "white-red", 207 | // element: "rgb(240,65,52)" 208 | // }, 209 | { 210 | name: "white-black", 211 | element: "rgb(55,61,65)" 212 | }, 213 | // // 214 | // { 215 | // name: 'black-cyan', 216 | // element: 'rgb(0, 162, 174)' 217 | // }, 218 | // { 219 | // name: 'black-blue', 220 | // element: 'rgb(16, 142, 233)' 221 | // }, 222 | // { 223 | // name: 'black-green', 224 | // element: 'rgb(0, 168, 84)' 225 | // }, 226 | // { 227 | // name: 'black-red', 228 | // element: 'rgb(240, 65, 52)' 229 | // }, 230 | // { 231 | // name: 'black-black', 232 | // element: 'rgb(55, 61, 65)' 233 | // } 234 | ] 235 | } 236 | -------------------------------------------------------------------------------- /src/store/modules/users.js: -------------------------------------------------------------------------------- 1 | /** 用户 */ 2 | import utils from '@/utils/utils'; 3 | let { storage, is, attrData } = utils; 4 | export default { 5 | namespaced: true, 6 | state: initState(), 7 | mutations: { 8 | users(state, users) { 9 | state.users = users; 10 | storage.setLocal('users', users); 11 | }, 12 | clearUser(state) { 13 | initState(state) 14 | } 15 | } 16 | } 17 | /** 18 | * @description 初始化state state存在则清空缓存,不存在则从缓存中初始化state 19 | * @param {Object} state 20 | */ 21 | function initState(state) { 22 | state && storage.remove('users');//清空所有缓存 23 | state = state || {}; 24 | state.users = attrData('users', 'Object', {}); 25 | state.isLogin = false; 26 | return state; 27 | } -------------------------------------------------------------------------------- /src/styles/common.less: -------------------------------------------------------------------------------- 1 | 2 | //通用样式 定义 3 | .pull-left{ 4 | float: left; 5 | } 6 | .pull-right{ 7 | float:right; 8 | } 9 | .font-center{ 10 | text-align: center; 11 | } 12 | .font-left{ 13 | text-align: left; 14 | } 15 | .font-right{ 16 | text-align: right; 17 | } 18 | 19 | .font-warp{ 20 | overflow: hidden; 21 | text-overflow:ellipsis; 22 | white-space: nowrap; 23 | } 24 | 25 | .clearfix:after { 26 | content: ' '; 27 | display: block; 28 | clear: both; 29 | visibility: hidden; 30 | line-height: 0; 31 | height: 0; 32 | } 33 | .no-select{ 34 | moz-user-select: -moz-none; 35 | -moz-user-select: none; 36 | -o-user-select:none; 37 | -khtml-user-select:none; 38 | -webkit-user-select:none; 39 | -ms-user-select:none; 40 | user-select:none; 41 | } 42 | .font-14{ 43 | font-size: 14px; 44 | } 45 | .link{ 46 | color: @primary-color; 47 | cursor: pointer; 48 | } 49 | .error{ 50 | color: @error-color; 51 | cursor: pointer; 52 | } 53 | [v-cloak]{ 54 | display: none; 55 | } 56 | .modal-form{ 57 | padding-right: 20px; 58 | } 59 | //宽度 60 | .w175{ 61 | width: 175px; 62 | } 63 | .w200{ 64 | width: 200px; 65 | } 66 | .w465{ 67 | width: 465px; 68 | } 69 | .margin-top-8{ 70 | margin-top: 8px; 71 | } 72 | .margin-top-10{ 73 | margin-top: 10px; 74 | } 75 | .margin-top-20{ 76 | margin-top: 20px; 77 | } 78 | .margin-left-10{ 79 | margin-left: 10px; 80 | } 81 | .margin-bottom-10{ 82 | margin-bottom: 10px; 83 | } 84 | .margin-bottom-20{ 85 | margin-bottom: 20px; 86 | } 87 | .margin-bottom-30{ 88 | margin-bottom: 30px; 89 | } 90 | .margin-bottom-100{ 91 | margin-bottom: 100px; 92 | } 93 | .margin-right-10{ 94 | margin-right: 10px; 95 | } 96 | .padding-left-6{ 97 | padding-left: 6px; 98 | } 99 | .padding-left-8{ 100 | padding-left: 5px; 101 | } 102 | .padding-left-10{ 103 | padding-left: 10px; 104 | } 105 | .padding-left-20{ 106 | padding-left: 20px; 107 | } 108 | .height-100{ 109 | height: 100%; 110 | } 111 | .height-120px{ 112 | height: 100px; 113 | } 114 | .height-200px{ 115 | height: 200px; 116 | } 117 | .height-492px{ 118 | height: 492px; 119 | } 120 | .height-460px{ 121 | height: 460px; 122 | } 123 | 124 | //padding 125 | .p-0-20{ 126 | padding: 0 20px; 127 | } 128 | 129 | .margin-top-10{ 130 | margin-top:10px; 131 | } 132 | -------------------------------------------------------------------------------- /src/styles/custom.less: -------------------------------------------------------------------------------- 1 | // Prefix 变量定义 2 | @css-prefix : fy-; 3 | //table 4 | @theadColor :#495060; //表头字体颜色 5 | @theadBgColor :#eee;//表头背景 6 | @theadBorderColor :#e9eaec;//表格边框颜色 7 | @tableFontColor :#444;//表格内容字体颜色 8 | @tableHoverRowColor :#ecf6fd;//表格hover背景色 9 | @tableRowPadding :2px 0; 10 | @tableActiveColor :#ddd; 11 | 12 | //主题 13 | @theme-list :white-cyan,white-blue,white-green,white-red,white-black,black-cyan,black-blue,black-green,black-red,black-black; 14 | @theme-black-list :black-cyan,black-blue,black-green,black-red,black-black; 15 | @white-cyan :rgb(0,162,174);//默认主题 16 | @white-blue :rgb(16,142,233); 17 | @white-green :rgb(0,168,84); 18 | @white-red :rgb(240,65,52); 19 | @white-black :rgb(55,61,65); 20 | 21 | @theme-black :#404040; 22 | @theme-opened :#333; 23 | @theme-black-default :rgba(255,255,255,.65); 24 | @black-cyan :rgb(0,162,174);//默认主题 25 | @black-blue :rgb(16,142,233); 26 | @black-green :rgb(0,168,84); 27 | @black-red :rgb(240,65,52); 28 | @black-black :rgb(55,61,65); 29 | // Color 30 | @primary-color : #2d8cf0; 31 | @info-color : #2db7f5; 32 | @success-color : #19be6b; 33 | @warning-color : #ff9900; 34 | @error-color : #f5222d; 35 | @link-color : #2D8cF0; 36 | @link-hover-color : tint(@link-color, 20%); 37 | @link-active-color : shade(@link-color, 5%); 38 | @selected-color : fade(@primary-color, 90%); 39 | @tooltip-color : #fff; 40 | @subsidiary-color : #80848f; 41 | @rate-star-color : #f5a623; 42 | 43 | // Base 44 | @body-background : #fff; 45 | @font-family : "Helvetica Neue",Helvetica,"PingFang SC","Hiragino Sans GB","Microsoft YaHei","微软雅黑",Arial,sans-serif; 46 | @code-family : Consolas,Menlo,Courier,monospace; 47 | @title-color : #1c2438; 48 | @text-color : #495060; 49 | @font-size-base : 14px; 50 | @font-size-small : 12px; 51 | @line-height-base : 1.5; 52 | @line-height-computed : floor((@font-size-base * @line-height-base)); 53 | @border-radius-base : 6px; 54 | @border-radius-small : 4px; 55 | @cursor-disabled : not-allowed; 56 | 57 | // Border color 58 | @border-color-base : #dddee1; // outside 59 | @border-color-split : #e9eaec; // inside 60 | 61 | // Background color 62 | @background-color-base : #f7f7f7; // base 63 | @tooltip-bg : rgba(70, 76, 91, .9); 64 | @head-bg : #f9fafc; 65 | @table-thead-bg : #f8f8f9; 66 | @table-td-stripe-bg : #f8f8f9; 67 | @table-td-hover-bg : #ebf7ff; 68 | @table-td-highlight-bg : #ebf7ff; 69 | @menu-dark-title : #495060; 70 | @menu-dark-active-bg : #363e4f; 71 | @menu-dark-subsidiary-color : rgba(255,255,255,.7); 72 | @menu-dark-group-title-color : rgba(255,255,255,.36); 73 | @date-picker-cell-hover-bg : #e1f0fe; 74 | 75 | // Shadow 76 | @shadow-color : rgba(0, 0, 0, .2); 77 | @shadow-base : @shadow-down; 78 | @shadow-card : 0 1px 1px 0 rgba(0,0,0,.1); 79 | @shadow-up : 0 -1px 6px @shadow-color; 80 | @shadow-down : 0 1px 6px @shadow-color; 81 | @shadow-left : -1px 0 6px @shadow-color; 82 | @shadow-right : 1px 0 6px @shadow-color; 83 | 84 | // Button 85 | @btn-font-weight : normal; 86 | @btn-padding-base : 6px 15px; 87 | @btn-padding-large : 6px 15px 7px 15px; 88 | @btn-padding-small : 2px 7px; 89 | @btn-font-size : 12px; 90 | @btn-font-size-large : 14px; 91 | @btn-border-radius : 4px; 92 | @btn-border-radius-small: 3px; 93 | @btn-group-border : shade(@primary-color, 5%); 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /src/styles/font/iconfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fengyaogit123/bookqd/44f7db8eb23e18edc88207eb6494b2df241b0a3f/src/styles/font/iconfont.eot -------------------------------------------------------------------------------- /src/styles/font/iconfont.js: -------------------------------------------------------------------------------- 1 | (function(window){var svgSprite='';var script=function(){var scripts=document.getElementsByTagName("script");return scripts[scripts.length-1]}();var shouldInjectCss=script.getAttribute("data-injectcss");var ready=function(fn){if(document.addEventListener){if(~["complete","loaded","interactive"].indexOf(document.readyState)){setTimeout(fn,0)}else{var loadFn=function(){document.removeEventListener("DOMContentLoaded",loadFn,false);fn()};document.addEventListener("DOMContentLoaded",loadFn,false)}}else if(document.attachEvent){IEContentLoaded(window,fn)}function IEContentLoaded(w,fn){var d=w.document,done=false,init=function(){if(!done){done=true;fn()}};var polling=function(){try{d.documentElement.doScroll("left")}catch(e){setTimeout(polling,50);return}init()};polling();d.onreadystatechange=function(){if(d.readyState=="complete"){d.onreadystatechange=null;init()}}}};var before=function(el,target){target.parentNode.insertBefore(el,target)};var prepend=function(el,target){if(target.firstChild){before(el,target.firstChild)}else{target.appendChild(el)}};function appendSvg(){var div,svg;div=document.createElement("div");div.innerHTML=svgSprite;svgSprite=null;svg=div.getElementsByTagName("svg")[0];if(svg){svg.setAttribute("aria-hidden","true");svg.style.position="absolute";svg.style.width=0;svg.style.height=0;svg.style.overflow="hidden";prepend(svg,document.body)}}if(shouldInjectCss&&!window.__iconfont__svg__cssinject__){window.__iconfont__svg__cssinject__=true;try{document.write("")}catch(e){console&&console.log(e)}}ready(appendSvg)})(window) -------------------------------------------------------------------------------- /src/styles/font/iconfont.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | Created by iconfont 9 | 10 | 11 | 12 | 13 | 21 | 22 | 23 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/styles/font/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fengyaogit123/bookqd/44f7db8eb23e18edc88207eb6494b2df241b0a3f/src/styles/font/iconfont.ttf -------------------------------------------------------------------------------- /src/styles/font/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fengyaogit123/bookqd/44f7db8eb23e18edc88207eb6494b2df241b0a3f/src/styles/font/iconfont.woff -------------------------------------------------------------------------------- /src/styles/iconfont.css: -------------------------------------------------------------------------------- 1 | 2 | @font-face {font-family: "iconfont"; 3 | src: url('./font/iconfont.eot?t=1517391280534'); /* IE9*/ 4 | src: url('./font/iconfont.eot?t=1517391280534#iefix') format('embedded-opentype'), /* IE6-IE8 */ 5 | url('data:application/x-font-woff;charset=utf-8;base64,d09GRgABAAAAAAYgAAsAAAAACNAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABHU1VCAAABCAAAADMAAABCsP6z7U9TLzIAAAE8AAAARAAAAFZW7kg9Y21hcAAAAYAAAABeAAABhpqEBr5nbHlmAAAB4AAAAk4AAAK4pqlL4GhlYWQAAAQwAAAALwAAADYQTqFdaGhlYQAABGAAAAAcAAAAJAfeA4RobXR4AAAEfAAAAAwAAAAMC+kAAGxvY2EAAASIAAAACAAAAAgAdgFcbWF4cAAABJAAAAAfAAAAIAEVAKJuYW1lAAAEsAAAAUUAAAJtPlT+fXBvc3QAAAX4AAAAJwAAADhYH0bheJxjYGRgYOBikGPQYWB0cfMJYeBgYGGAAJAMY05meiJQDMoDyrGAaQ4gZoOIAgCKIwNPAHicY2Bk/sE4gYGVgYOpk+kMAwNDP4RmfM1gxMjBwMDEwMrMgBUEpLmmMDgwVDwLZm7438AQw9zA0AAUZgTJAQAphQy/eJzFkMENgDAMAy9t6QMxCA+26YcXc3TirlFMKA8mqCXHimMpUYAFiOIhJrAL48Ep19yPrO4nz2SpEait9K7+U0U0y65BJTMNNm/1H5vXfXT6CnVQJ7byknADJBYM2wAAeJw1T01v00AQ3dnN2l7HX/HXJk7sJHZih6a1FMe11VZNEUIgCqgIuCAuoF64gFRxqISK6AWJAwcuHLkgJP5EoRJXfgWCv0FgU8Rqd/bNm7cz+xBF6M8PckbayEETNENX0R2EQJpCbOAQhlmZ4yl4Q+px1yBZkg3lJM7JLvBYcv2iKlMuyZIJBkQwHxZVluMMNssF3oHCDwE63eCePe7Z5B2o7Sx6vdzHH8HrJz1zsbG8sb7nFgNHOdZsu2PbbxWJUgXjhmnAU+4zylRp+YmagXfWv4T7oHWy4NYDfdC1H78pn4VjzgBOT8HpDozPe62gJfZJ4Dt2R7Z0pR3oyciF41/NtqOF6U8kliK8fiXfyBXURB5aQ9voGnqCXqH3wrEBnuvPi2qzTGvhqA9VLVCapbIBsuTzCOpivoC6uiBiUagXeEUTkUbARSkHZ6U1IYcsF8p6Ab4Jrlevnvn/mmQ5Kav5rFph0Si5EHIDprACIpPjXIwWdR8+mLylW741YhIQRW3IJ4ZVba3f7o/YWng51rXsejRVjsxZOJpRTJRsZ+61cO98Seny/CK2o/tFmye7h9Mq7O2+5GUI2HUnbhKqVDaKzh7rWZrGpMJTVAk3ZcoYps9xUyFMBW0UHzSYBk0mH27MYAyuoZmrYzQUaKiqjJUDJnsSiQbp3ci1oBEEk4dHfgfGA12PxqbGnTne/v8XEX9/WZ9pEqbgd+NHWy92VKkbcFUltAk4jQ294dz8rsqgtcRQzpJAYYANxrcUVVz7k6GgbQ2wNa3RX7LWYTcAAHicY2BkYGAA4uCny9fF89t8ZeBmYQCBa9PXbUDQ//exMDA7AbkcDEwgUQBcVwvkAHicY2BkYGBu+N/AEMPCAAJAkpEBFTADAEcJAmwEAAAAA+kAAAQAAAAAAAAAAHYBXHicY2BkYGBgZpjGwMEAAkxAzAWEDAz/wXwGABf5AbgAeJxlj01OwzAQhV/6B6QSqqhgh+QFYgEo/RGrblhUavdddN+mTpsqiSPHrdQDcB6OwAk4AtyAO/BIJ5s2lsffvHljTwDc4Acejt8t95E9XDI7cg0XuBeuU38QbpBfhJto41W4Rf1N2MczpsJtdGF5g9e4YvaEd2EPHXwI13CNT+E69S/hBvlbuIk7/Aq30PHqwj7mXle4jUcv9sdWL5xeqeVBxaHJIpM5v4KZXu+Sha3S6pxrW8QmU4OgX0lTnWlb3VPs10PnIhVZk6oJqzpJjMqt2erQBRvn8lGvF4kehCblWGP+tsYCjnEFhSUOjDFCGGSIyujoO1Vm9K+xQ8Jee1Y9zed0WxTU/3OFAQL0z1xTurLSeTpPgT1fG1J1dCtuy56UNJFezUkSskJe1rZUQuoBNmVXjhF6XNGJPyhnSP8ACVpuyAAAAHicY2BigAAuBuyAmZGJkZmRhYGxgqs8NTMpXzc5v6CSgQEAKJcEnQA=') format('woff'), 6 | url('./font/iconfont.ttf?t=1517391280534') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/ 7 | url('./font/iconfont.svg?t=1517391280534#iconfont') format('svg'); /* iOS 4.1- */ 8 | } 9 | 10 | .iconfont { 11 | font-family:"iconfont" !important; 12 | font-size:16px; 13 | font-style:normal; 14 | -webkit-font-smoothing: antialiased; 15 | -moz-osx-font-smoothing: grayscale; 16 | } 17 | 18 | .icon-weibo-copy:before { content: "\e653"; } 19 | 20 | -------------------------------------------------------------------------------- /src/styles/index.less: -------------------------------------------------------------------------------- 1 | //所有less入口 2 | @import './custom.less'; 3 | @import './mixins.less'; 4 | @import './reset.less'; 5 | @import './common.less'; 6 | @import './theme/index.less'; 7 | @import './iconfont.css'; -------------------------------------------------------------------------------- /src/styles/mixins.less: -------------------------------------------------------------------------------- 1 | //定义 mixins -------------------------------------------------------------------------------- /src/styles/reset.less: -------------------------------------------------------------------------------- 1 | //css 初始化 修改框架的样式less 2 | ul{ 3 | list-style: none; 4 | } 5 | html, 6 | body { 7 | width: 100%; 8 | height: 100%; 9 | overflow: hidden; 10 | } 11 | //滚动条 12 | ::-webkit-scrollbar-track { 13 | background-color: #F5F5F5; 14 | } 15 | ::-webkit-scrollbar-thumb { 16 | background-color: #ccc; 17 | } 18 | ::-webkit-scrollbar { 19 | width: 6px; 20 | background-color: #F5F5F5; 21 | } 22 | // 23 | /** iview**/ 24 | .ivu-message{ 25 | z-index: 99999; 26 | } 27 | .ivu-btn-small{ 28 | padding: 4px 9px; 29 | } 30 | .ivu-poptip-popper{ 31 | width: auto!important; 32 | } 33 | .expand-box .ivu-form-item{ 34 | margin-bottom: 0; 35 | } -------------------------------------------------------------------------------- /src/styles/theme/index.less: -------------------------------------------------------------------------------- 1 | @import './theme-white.less'; 2 | @import './theme-black.less'; 3 | .theme-create(length(@theme-list)); 4 | .theme-create-black(length(@theme-black-list)); -------------------------------------------------------------------------------- /src/styles/theme/theme-black.less: -------------------------------------------------------------------------------- 1 | 2 | .theme-create-black(@n, @i: 1) when (@i =< @n) { 3 | @theme:extract(@theme-black-list,@i); 4 | .@{theme}{ 5 | .ivu-menu-light, 6 | .sidebar-menu-con{ 7 | background-color: @theme-black; 8 | transition: all 0.4s; 9 | } 10 | .ivu-menu-opened{ 11 | background-color: @theme-opened; 12 | transition: all 0.4s; 13 | } 14 | 15 | .menu-icon, 16 | .ivu-menu{ 17 | color: @theme-black-default; 18 | transition: all 0.4s; 19 | } 20 | } 21 | .theme-create-black(@n, (@i + 1)); 22 | } -------------------------------------------------------------------------------- /src/styles/theme/theme-white.less: -------------------------------------------------------------------------------- 1 | //顶部变色 左侧白色 2 | .theme-create(@n, @i: 1) when (@i =< @n) { 3 | @theme:extract(@theme-list,@i); 4 | .@{theme}{ 5 | .header{ 6 | background-color: @@theme; 7 | } 8 | .sidebar-menu-con .font-center:hover .menu-icon{ 9 | color: @@theme; 10 | } 11 | .ivu-menu-light.ivu-menu-vertical .ivu-menu-item-active:not(.ivu-menu-submenu){ 12 | color: @@theme; 13 | } 14 | .ivu-menu-item-active .menu-icon{ 15 | color: @@theme; 16 | } 17 | .ivu-menu-opened{ 18 | border-color: @@theme; 19 | .ivu-menu-submenu-title,.menu-icon{ 20 | color: @@theme; 21 | } 22 | } 23 | .ivu-menu-vertical .ivu-menu-item:hover, .ivu-menu-vertical .ivu-menu-submenu-title:hover{ 24 | color:@@theme; 25 | i{ 26 | color:@@theme; 27 | } 28 | } 29 | } 30 | .theme-create(@n, (@i + 1)); 31 | } 32 | //黑色背景特殊处理 33 | @black-color:rgba(255,255,255,.8)!important; 34 | .black-black{ 35 | .sidebar-menu-con .font-center:hover .menu-icon{ 36 | color:@black-color; 37 | } 38 | .ivu-menu-light.ivu-menu-vertical .ivu-menu-item-active:not(.ivu-menu-submenu){ 39 | color: @black-color; 40 | } 41 | .ivu-menu-opened{ 42 | border-color:@black-color; 43 | .ivu-menu-submenu-title,.menu-icon{ 44 | color: @black-color; 45 | } 46 | } 47 | .ivu-menu-vertical .ivu-menu-item:hover, .ivu-menu-vertical .ivu-menu-submenu-title:hover{ 48 | color:@black-color; 49 | i{ 50 | color:@black-color; 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/utils/filters.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | function toText(str) { 3 | if (typeof str !== "string") return ""; 4 | return str.replace(/<[^>]+>/g, ""); //去掉所有的html标记 5 | } 6 | function toDate(value) { 7 | if (!value) return ""; 8 | const TODAY = +new Date(); 9 | const MIN = 60 * 1000; //分钟 10 | const HOUR = 60 * MIN; //小时 11 | const DAY = 24 * HOUR; //一天 12 | const WEEK = 7 * DAY; //一周 13 | const MONTH = 30 * DAY; //一月 14 | let time = +new Date(value); //时间 15 | //小时 16 | if (TODAY - time < MIN) { 17 | return "刚刚"; 18 | } 19 | if (TODAY - time < HOUR) { 20 | return parseInt((TODAY - time) / MIN) + " 分钟前"; 21 | } 22 | if (TODAY - time < DAY) { 23 | return parseInt((TODAY - time) / HOUR) + " 小时前"; 24 | } 25 | if (TODAY - time < WEEK) { 26 | return parseInt((TODAY - time) / DAY) + " 天前"; 27 | } 28 | if (TODAY - time < MONTH) { 29 | return parseInt((TODAY - time) / WEEK) + " 周前"; 30 | } 31 | if (TODAY - time >= MONTH) { 32 | return new Date(value).Format("yyyy-MM-dd"); 33 | } 34 | } 35 | 36 | Vue.filter('toText',toText) 37 | Vue.filter('toDate',toDate) -------------------------------------------------------------------------------- /src/utils/interface.js: -------------------------------------------------------------------------------- 1 | 2 | import Vue from 'vue'; 3 | const root = '/api' 4 | 5 | const User = { 6 | LOGIN: `${root}/v1/login`, 7 | CREATE: `${root}/v1/users`, 8 | UPLOAD: `${root}/v1/upload/image` 9 | } 10 | const Article = { 11 | ART_ADD: `${root}/v1/article`,//根据add 12 | ART_BY_USER: (userId) => `${root}/v1/article/byuser/${userId}`,//根据id查询列表 13 | ART_PUT: artId => `${root}/v1/article/${artId}` 14 | } 15 | Vue.prototype.$Inter = { 16 | ...User, 17 | ...Article 18 | } -------------------------------------------------------------------------------- /src/utils/request.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import axios from 'axios'; 3 | import router from '@/router'; 4 | import utils from '@/utils/utils'; 5 | import $store from '@/store/index'; 6 | axios.defaults.timeout = 20 * 1000; 7 | axios.defaults.withCredentials = true; 8 | 9 | axios.interceptors.request.use(function (config) { 10 | return config; 11 | }, function (error) { 12 | return Promise.reject(error); 13 | }); 14 | 15 | axios.interceptors.response.use(function (res) { 16 | return res; 17 | }, function (error) { 18 | if (!error.response) { 19 | utils.$Message.error('请求超时,请重试!'); 20 | return Promise.reject(error); 21 | } 22 | let { status, data } = error.response; 23 | if (data.message) { 24 | utils.$Message.error(`${data.code}:${data.message}`); 25 | } 26 | if (status == 403) { 27 | $store.dispatch("menu/logOutBtn"); 28 | } 29 | 30 | return Promise.reject(status); 31 | }); 32 | Vue.prototype.$http = axios; 33 | export default axios; 34 | -------------------------------------------------------------------------------- /src/utils/storage.js: -------------------------------------------------------------------------------- 1 | /**本地存储 */ 2 | export default { 3 | session: window.localStorage, 4 | local: window.localStorage, 5 | set(type, key, value) { 6 | if (this.isString(value)) { 7 | return this[type].setItem(key, value); 8 | } 9 | if (this.isObject(value)) { 10 | try { 11 | value = JSON.stringify(value); 12 | } catch (error) { 13 | } 14 | return this[type].setItem(key, value); 15 | } 16 | return this[type].setItem(key, value); 17 | }, 18 | get(type, key) { 19 | let value = this[type].getItem(key); 20 | if (this.isParse(value)) { 21 | try { 22 | value = JSON.parse(value); 23 | } catch (error) { 24 | value = this[type].getItem(key); 25 | } 26 | } 27 | return value; 28 | }, 29 | setSession(key, value) { 30 | this.set('session', key, value); 31 | }, 32 | getSession(key) { 33 | return this.get('session', key); 34 | }, 35 | setLocal(key, value) { 36 | this.set('local', key, value); 37 | }, 38 | getLocal(key) { 39 | return this.get('local', key); 40 | }, 41 | isString(value) { 42 | return typeof value === 'string'; 43 | }, 44 | isObject(value) { 45 | return typeof value === 'object'; 46 | }, 47 | remove(key) { 48 | this.session.removeItem(key); 49 | this.local.removeItem(key); 50 | }, 51 | clear() { 52 | this.session.clear(); 53 | this.local.clear(); 54 | }, 55 | isParse(value) { 56 | if (!value) { 57 | return false; 58 | } 59 | return value.indexOf('{') !== -1 || value.indexOf('[') !== -1 || value.indexOf('(') ? true : false; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/utils/utils.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import storage from '@/utils/storage.js' 3 | import $router from '@/router/index' 4 | import QS from 'querystring' 5 | let Message = Vue.prototype.$Message 6 | 7 | Date.prototype.Format = function (fmt) { //author: meizz 8 | var o = { 9 | "M+": this.getMonth() + 1, //月份 10 | "d+": this.getDate(), //日 11 | "H+": this.getHours(), //小时 12 | "h+": this.getHours(), //小时 13 | "m+": this.getMinutes(), //分 14 | "s+": this.getSeconds(), //秒 15 | "q+": Math.floor((this.getMonth() + 3) / 3), //季度 16 | "S": this.getMilliseconds() //毫秒 17 | }; 18 | if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length)); 19 | for (var k in o) 20 | if (new RegExp("(" + k + ")").test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length))); 21 | return fmt; 22 | }; 23 | /** 24 | * url 格式化 25 | * @param {*} url 26 | */ 27 | function URLObject(url) { 28 | url = url || window.location.search 29 | let theRequest = {} 30 | let strs = '' 31 | if (url.indexOf('?') != -1) { 32 | let str = url.substr(1) 33 | strs = str.split('&') 34 | for (let i = 0; i < strs.length; i++) { 35 | theRequest[strs[i].split('=')[0]] = decodeURIComponent(strs[i].split('=')[1]) 36 | } 37 | } 38 | return theRequest 39 | } 40 | /** 41 | * 判断字符串是否为空 42 | * @param {*} value 43 | */ 44 | function isNull(value) { 45 | if (!value) return true 46 | if (value == 'null' && value == 'undefined') return true 47 | return false 48 | } 49 | /** 50 | * 判断是否为数组 51 | * @param {arr} 52 | */ 53 | function isArray(arr) { 54 | return arr && (arr instanceof Array) 55 | } 56 | /** 57 | * @description 初始化属性值 58 | * @param {String} name 属性名 59 | * @param {String} type 类型 60 | * @param {*} default 默认值 61 | * */ 62 | function attrData(name = '', type = 'String', defaults = '') { 63 | let value = storage.getLocal(name); 64 | try { 65 | value = is()[type](value) ? value : defaults; 66 | } catch (e) { 67 | value = defaults; 68 | } 69 | return value; 70 | } 71 | function is() { 72 | let is = { 73 | types: ["Array","Function", "Boolean", "Date", "Number", "Object", "RegExp", "String", "Window", "HTMLDocument"] 74 | }; 75 | for (let i = 0, c; c = is.types[i++];) { 76 | is[c] = (function (type) { 77 | return function (obj) { 78 | if(type=='Number'&&isNaN(obj)){ 79 | return false; 80 | } 81 | return Object.prototype.toString.call(obj) == "[object " + type + "]"; 82 | } 83 | })(c); 84 | } 85 | return is; 86 | } 87 | /** 88 | * 对象深拷贝 89 | */ 90 | function OParse(obj) { 91 | return JSON.parse(JSON.stringify(obj)) 92 | } 93 | /** 94 | * 新增页面 95 | */ 96 | function openNewPage({ path, name, params, query }, $store) { 97 | let { pageList } = $store.state.menu; // 当前page 98 | let { title, parent, children } = findTitle($store, name); 99 | $store.commit('menu/currentPath', { parent, children }); 100 | let index = pageList.map(({ name }) => name).indexOf(name); 101 | if (index !== -1) { 102 | let page = pageList[index]; 103 | page.query = query; 104 | page.params = params; 105 | pageList.splice(index, 1, page); 106 | return; 107 | } 108 | if (title) { 109 | $store.commit('menu/addPageList', { path, name, params, query, title }) 110 | } 111 | } 112 | function findTitle($store, name) { 113 | let { menus } = $store.state.menu 114 | let parent = {}, children = {} 115 | for (let i = 0; i < menus.length; i++) { 116 | let menu = menus[i] 117 | if (menu.routerName == name) { 118 | if (menu.routerName !== 'Workbench') { 119 | parent.title = menu.name 120 | parent.routerName = menu.routerName 121 | } else { 122 | parent = null 123 | } 124 | children = null 125 | return { 126 | title: menu.name, 127 | parent: parent, 128 | children: children 129 | } 130 | } 131 | if (menu.hasChildren) { 132 | for (let k = 0; k < menu.children.length; k++) { 133 | let cMenu = menu.children[k] 134 | if (cMenu.routerName == name) { 135 | parent.title = menu.name 136 | parent.routerName = menu.routerName 137 | children.title = cMenu.name 138 | children.routerName = cMenu.routerName 139 | return { 140 | title: cMenu.name, 141 | parent: parent, 142 | children: children 143 | } 144 | } 145 | } 146 | } 147 | } 148 | return {} 149 | } 150 | /** 151 | * @description 去抖 152 | * @param {Function} fn 153 | * @param {Number} delay 154 | * @return {Function} 155 | */ 156 | function debounce(fn, delay) { 157 | let t = null; 158 | return function (...args) { 159 | const func = () => { 160 | fn.apply(this, args) 161 | } 162 | clearTimeout(t) 163 | t = setTimeout(func, delay) 164 | } 165 | } 166 | /** 167 | * @description 去抖 168 | * @param {Function} fn 169 | * @param {Number} delay 170 | * @param {String} msg 171 | * @return {Function} 172 | */ 173 | function throttle(fn, delay) { 174 | let start = 0; 175 | return function (...args) { 176 | let curr = +new Date(); 177 | if (curr >= (start + delay)) { 178 | fn.apply(this, args) 179 | start = curr; 180 | return; 181 | } 182 | } 183 | } 184 | Vue.prototype.$URLObject = URLObject 185 | Vue.prototype.$storage = storage; // 本地存储 186 | Vue.prototype.$OParse = OParse; 187 | Vue.prototype.$is = is; 188 | Vue.prototype.$QS = QS 189 | export default { 190 | URLObject, 191 | storage, 192 | $Message: Message, 193 | isNull, 194 | isArray, 195 | OParse, 196 | openNewPage, 197 | debounce, 198 | throttle, 199 | is, 200 | attrData 201 | } 202 | -------------------------------------------------------------------------------- /static/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fengyaogit123/bookqd/44f7db8eb23e18edc88207eb6494b2df241b0a3f/static/.gitkeep -------------------------------------------------------------------------------- /static/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "title":"公告", 3 | "text":"公告内容", 4 | "logoText":"LOGO TEXT", 5 | "logoTextMin":"LOGO" 6 | } -------------------------------------------------------------------------------- /static/imgs/img-bg.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/imgs/login-bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fengyaogit123/bookqd/44f7db8eb23e18edc88207eb6494b2df241b0a3f/static/imgs/login-bg.jpg --------------------------------------------------------------------------------