├── .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 | 
87 | 登录
88 | 
89 | 图书简介
90 | 
91 | 图书借还
92 | 
93 | 图书管理
94 | 
95 | 读者管理
96 | 
97 | 借阅记录
98 | 
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 |
2 |
3 |
4 |
5 |
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 |
2 |
3 | 0
4 |
5 |
6 |
86 |
87 |
92 |
--------------------------------------------------------------------------------
/src/components/Editor.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
81 |
82 |
87 |
--------------------------------------------------------------------------------
/src/components/Layout/Block.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
16 |
17 |
22 |
--------------------------------------------------------------------------------
/src/components/Layout/Layout.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
15 |
16 |
25 |
--------------------------------------------------------------------------------
/src/components/Layout/SearchBox.vue:
--------------------------------------------------------------------------------
1 |
2 |
24 |
25 |
26 |
62 |
63 |
84 |
--------------------------------------------------------------------------------
/src/components/Upload/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
![]()
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
168 |
--------------------------------------------------------------------------------
/src/components/fullScreen.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
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 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
26 |
27 |
28 |
29 |
30 |
33 |
34 |
35 |
36 |
37 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
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 |
2 |
3 |
4 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
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 |
2 |
3 |
4 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
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 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
26 |
27 |
28 |
29 |
30 |
33 |
34 |
35 |
36 |
37 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
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 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
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 |
2 |
3 |
4 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
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 |
2 |
3 |
4 |
5 |
6 |
7 |
{{status}}
8 |
{{errors[status]}}
9 |
10 |
11 |
12 |
13 |
14 |
27 |
28 |
73 |
--------------------------------------------------------------------------------
/src/pages/index/Index.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
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 |
2 |
32 |
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 |
2 |
65 |
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 |
2 |
3 |
4 |
5 |
9 |
10 | 关闭所有
11 | 关闭其他
12 |
13 |
14 |
15 |
16 |
17 |
18 | {{page.title}}
19 |
20 |
21 |
22 |
23 |
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 |
2 |
32 |
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 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
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 |
2 |
3 |
4 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
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 |
2 |
3 |
4 |
5 |
6 | 图书借阅量统计图
7 |
8 |
9 |
10 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | 图书借阅量统计
20 |
21 |
23 |
24 |
25 |
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 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
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 |
2 |
3 |
4 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
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 |
2 |
3 |
4 |
5 |
6 |
7 |
13 |
14 |
15 |
16 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
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 |
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
--------------------------------------------------------------------------------