├── .babelrc
├── .editorconfig
├── .eslintignore
├── .eslintrc.js
├── .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-lock.json
├── package.json
├── server
├── app.js
├── db.js
├── routes
│ ├── books.js
│ ├── records.js
│ ├── users.js
│ └── wishes.js
└── schema
│ ├── book.js
│ ├── record.js
│ ├── user.js
│ └── wish.js
├── src
├── App.vue
├── assets
│ ├── css
│ │ ├── common.scss
│ │ ├── global.scss
│ │ ├── index.scss
│ │ ├── mixin.scss
│ │ ├── reset.scss
│ │ └── variable.scss
│ ├── image
│ │ └── bg.jpg
│ └── js
│ │ ├── mixins.js
│ │ ├── reg.js
│ │ └── utils.js
├── components
│ ├── navbar.vue
│ └── slider.vue
├── config.js
├── main.js
├── router.js
└── views
│ ├── books.vue
│ ├── login.vue
│ ├── records.vue
│ ├── users.vue
│ └── wishes.vue
├── static
└── .gitkeep
└── weixin
├── app.js
├── app.json
├── app.wxss
├── images
├── home.png
├── home_selected.png
├── login_bg.png
├── search.png
├── search_selected.png
├── user.png
└── user_selected.png
├── pages
├── bookDetail
│ ├── bookDetail.js
│ ├── bookDetail.json
│ ├── bookDetail.wxml
│ └── bookDetail.wxss
├── bookList
│ ├── bookList.js
│ ├── bookList.json
│ ├── bookList.wxml
│ └── bookList.wxss
├── index
│ ├── index.js
│ ├── index.json
│ ├── index.wxml
│ └── index.wxss
├── login
│ ├── login.js
│ ├── login.json
│ ├── login.wxml
│ └── login.wxss
└── mine
│ ├── mine.js
│ ├── mine.json
│ ├── mine.wxml
│ └── mine.wxss
├── project.config.json
└── utils
└── util.js
/.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 = 4
7 | end_of_line = lf
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | /build/
2 | /config/
3 | /dist/
4 | /*.js
5 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | // https://eslint.org/docs/user-guide/configuring
2 |
3 | module.exports = {
4 | root: true,
5 | parserOptions: {
6 | parser: 'babel-eslint'
7 | },
8 | env: {
9 | browser: true,
10 | },
11 | extends: [
12 | // https://github.com/vuejs/eslint-plugin-vue#priority-a-essential-error-prevention
13 | // consider switching to `plugin:vue/strongly-recommended` or `plugin:vue/recommended` for stricter rules.
14 | 'plugin:vue/essential',
15 | // https://github.com/standard/standard/blob/master/docs/RULES-en.md
16 | 'standard'
17 | ],
18 | // required to lint *.vue files
19 | plugins: [
20 | 'vue'
21 | ],
22 | // add your custom rules here
23 | rules: {
24 | "indent": 0,
25 | "space-before-function-paren": 0,
26 | // allow async-await
27 | 'generator-star-spacing': 'off',
28 | // allow debugger during development
29 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/.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 | # cloud-library
2 | 涵盖前端和后台的图书馆借阅管理系统。
3 |
4 | ## 功能包括:
5 | - 管理端
6 | 1. 图书管理(图书上下架、分类搜索)
7 | 2. 用户管理(添加、删除用户)
8 | 3. 借阅记录(查询与搜索图书的借阅记录)
9 | 4. 愿望清单(用户的愿望清单列表)
10 | 5. 待完善...
11 |
12 | - 用户端
13 | 1. 所有书籍列表
14 | 2. 搜索图书
15 | 3. 将图书加入/移除心愿单
16 | 4. 查看图书详情
17 | 5. 图书的借阅、归还
18 | 6. 借阅排行列表
19 | 7. 待完善...
20 |
21 | ## 涉及技术:
22 | - 后台:Node.js + Express
23 | - 包管理:NPM
24 | - 管理平台:Vue.js + ElementUI
25 | - 用户平台:微信小程序
26 | - 数据库:Mongodb
27 | - CSS库:Sass
28 | - JavaScript版本: ES6
29 |
30 | ## 说明:
31 |
32 | 1. 所有书籍信息均来源于豆瓣读书开放api接口;
33 | 2. 此项目是作者用于学习小程序与后台技术的实验性产品,望各位多多批评指正。
34 |
--------------------------------------------------------------------------------
/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/Pengjiaxun/cloud-library/f1596d93a624273b0decce6c87194e9016229789/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 | })
52 | } else {
53 | return ['vue-style-loader'].concat(loaders)
54 | }
55 | }
56 |
57 | // https://vue-loader.vuejs.org/en/configurations/extract-css.html
58 | return {
59 | css: generateLoaders(),
60 | postcss: generateLoaders(),
61 | less: generateLoaders('less'),
62 | sass: generateLoaders('sass', { indentedSyntax: true }),
63 | scss: generateLoaders('sass'),
64 | stylus: generateLoaders('stylus'),
65 | styl: generateLoaders('stylus')
66 | }
67 | }
68 |
69 | // Generate loaders for standalone style files (outside of .vue)
70 | exports.styleLoaders = function (options) {
71 | const output = []
72 | const loaders = exports.cssLoaders(options)
73 |
74 | for (const extension in loaders) {
75 | const loader = loaders[extension]
76 | output.push({
77 | test: new RegExp('\\.' + extension + '$'),
78 | use: loader
79 | })
80 | }
81 |
82 | return output
83 | }
84 |
85 | exports.createNotifierCallback = () => {
86 | const notifier = require('node-notifier')
87 |
88 | return (severity, errors) => {
89 | if (severity !== 'error') return
90 |
91 | const error = errors[0]
92 | const filename = error.file && error.file.split('!').pop()
93 |
94 | notifier.notify({
95 | title: packageConfig.name,
96 | message: severity + ': ' + error.name,
97 | subtitle: filename || '',
98 | icon: path.join(__dirname, 'logo.png')
99 | })
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/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 | const createLintingRule = () => ({
12 | test: /\.(js|vue)$/,
13 | loader: 'eslint-loader',
14 | enforce: 'pre',
15 | include: [resolve('src'), resolve('test')],
16 | options: {
17 | formatter: require('eslint-friendly-formatter'),
18 | emitWarning: !config.dev.showEslintErrorsInOverlay
19 | }
20 | })
21 |
22 | module.exports = {
23 | context: path.resolve(__dirname, '../'),
24 | entry: {
25 | app: './src/main.js'
26 | },
27 | output: {
28 | path: config.build.assetsRoot,
29 | filename: '[name].js',
30 | publicPath: process.env.NODE_ENV === 'production'
31 | ? config.build.assetsPublicPath
32 | : config.dev.assetsPublicPath
33 | },
34 | resolve: {
35 | extensions: ['.js', '.vue', '.json'],
36 | alias: {
37 | 'vue$': 'vue/dist/vue.esm.js',
38 | '@': resolve('src'),
39 | }
40 | },
41 | module: {
42 | rules: [
43 | ...(config.dev.useEslint ? [createLintingRule()] : []),
44 | {
45 | test: /\.vue$/,
46 | loader: 'vue-loader',
47 | options: vueLoaderConfig
48 | },
49 | {
50 | test: /\.js$/,
51 | loader: 'babel-loader',
52 | include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')]
53 | },
54 | {
55 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
56 | loader: 'url-loader',
57 | options: {
58 | limit: 10000,
59 | name: utils.assetsPath('img/[name].[hash:7].[ext]')
60 | }
61 | },
62 | {
63 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
64 | loader: 'url-loader',
65 | options: {
66 | limit: 10000,
67 | name: utils.assetsPath('media/[name].[hash:7].[ext]')
68 | }
69 | },
70 | {
71 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
72 | loader: 'url-loader',
73 | options: {
74 | limit: 10000,
75 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
76 | }
77 | }
78 | ]
79 | },
80 | node: {
81 | // prevent webpack from injecting useless setImmediate polyfill because Vue
82 | // source contains it (although only uses it if it's native).
83 | setImmediate: false,
84 | // prevent webpack from injecting mocks to Node native modules
85 | // that does not make sense for the client
86 | dgram: 'empty',
87 | fs: 'empty',
88 | net: 'empty',
89 | tls: 'empty',
90 | child_process: 'empty'
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/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 |
10 | // Paths
11 | assetsSubDirectory: 'static',
12 | assetsPublicPath: '/',
13 | proxyTable: {
14 | '/api': {
15 | target: 'https://api.douban.com',
16 | changeOrigin: true,
17 | pathRewrite: {
18 | '^/api': ''
19 | }
20 | }
21 | },
22 |
23 | // Various Dev Server settings
24 | host: 'localhost', // can be overwritten by process.env.HOST
25 | port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined
26 | autoOpenBrowser: false,
27 | errorOverlay: true,
28 | notifyOnErrors: true,
29 | poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions-
30 |
31 | // Use Eslint Loader?
32 | // If true, your code will be linted during bundling and
33 | // linting errors and warnings will be shown in the console.
34 | useEslint: true,
35 | // If true, eslint errors and warnings will also be shown in the error overlay
36 | // in the browser.
37 | showEslintErrorsInOverlay: false,
38 |
39 | /**
40 | * Source Maps
41 | */
42 |
43 | // https://webpack.js.org/configuration/devtool/#development
44 | devtool: 'cheap-module-eval-source-map',
45 |
46 | // If you have problems debugging vue-files in devtools,
47 | // set this to false - it *may* help
48 | // https://vue-loader.vuejs.org/en/options.html#cachebusting
49 | cacheBusting: true,
50 |
51 | cssSourceMap: true
52 | },
53 |
54 | build: {
55 | // Template for index.html
56 | index: path.resolve(__dirname, '../dist/index.html'),
57 |
58 | // Paths
59 | assetsRoot: path.resolve(__dirname, '../dist'),
60 | assetsSubDirectory: 'static',
61 | assetsPublicPath: '/',
62 |
63 | /**
64 | * Source Maps
65 | */
66 |
67 | productionSourceMap: true,
68 | // https://webpack.js.org/configuration/devtool/#production
69 | devtool: '#source-map',
70 |
71 | // Gzip off by default as many popular static hosts such as
72 | // Surge or Netlify already gzip all static assets for you.
73 | // Before setting to `true`, make sure to:
74 | // npm install --save-dev compression-webpack-plugin
75 | productionGzip: false,
76 | productionGzipExtensions: ['js', 'css'],
77 |
78 | // Run the build command with an extra argument to
79 | // View the bundle analyzer report after build finishes:
80 | // `npm run build --report`
81 | // Set to `true` or `false` to always turn it on or off
82 | bundleAnalyzerReport: process.env.npm_config_report
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "book",
3 | "version": "1.0.0",
4 | "description": "A Vue.js project",
5 | "author": "彭佳勋 ",
6 | "private": true,
7 | "scripts": {
8 | "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
9 | "start": "npm run dev",
10 | "lint": "eslint --ext .js,.vue src",
11 | "build": "node build/build.js"
12 | },
13 | "dependencies": {
14 | "axios": "^0.17.1",
15 | "body-parser": "^1.18.2",
16 | "element-ui": "^2.1.0",
17 | "express": "^4.16.2",
18 | "mongoose": "^5.0.3",
19 | "node-sass": "^4.7.2",
20 | "normalize.css": "^7.0.0",
21 | "sass": "^1.0.0-beta.5.2",
22 | "sass-loader": "^6.0.6",
23 | "store2": "^2.6.0",
24 | "vue": "^2.5.2",
25 | "vue-router": "^3.0.1"
26 | },
27 | "devDependencies": {
28 | "autoprefixer": "^7.1.2",
29 | "babel-core": "^6.22.1",
30 | "babel-eslint": "^8.2.1",
31 | "babel-helper-vue-jsx-merge-props": "^2.0.3",
32 | "babel-loader": "^7.1.1",
33 | "babel-plugin-syntax-jsx": "^6.18.0",
34 | "babel-plugin-transform-runtime": "^6.22.0",
35 | "babel-plugin-transform-vue-jsx": "^3.5.0",
36 | "babel-preset-env": "^1.3.2",
37 | "babel-preset-stage-2": "^6.22.0",
38 | "chalk": "^2.0.1",
39 | "copy-webpack-plugin": "^4.0.1",
40 | "css-loader": "^0.28.0",
41 | "eslint": "^4.15.0",
42 | "eslint-config-standard": "^10.2.1",
43 | "eslint-friendly-formatter": "^3.0.0",
44 | "eslint-loader": "^1.7.1",
45 | "eslint-plugin-import": "^2.7.0",
46 | "eslint-plugin-node": "^5.2.0",
47 | "eslint-plugin-promise": "^3.4.0",
48 | "eslint-plugin-standard": "^3.0.1",
49 | "eslint-plugin-vue": "^4.0.0",
50 | "extract-text-webpack-plugin": "^3.0.0",
51 | "file-loader": "^1.1.4",
52 | "friendly-errors-webpack-plugin": "^1.6.1",
53 | "html-webpack-plugin": "^2.30.1",
54 | "node-notifier": "^5.1.2",
55 | "optimize-css-assets-webpack-plugin": "^3.2.0",
56 | "ora": "^1.2.0",
57 | "portfinder": "^1.0.13",
58 | "postcss-import": "^11.0.0",
59 | "postcss-loader": "^2.0.8",
60 | "postcss-url": "^7.2.1",
61 | "rimraf": "^2.6.0",
62 | "semver": "^5.3.0",
63 | "shelljs": "^0.7.6",
64 | "uglifyjs-webpack-plugin": "^1.1.1",
65 | "url-loader": "^0.5.8",
66 | "vue-loader": "^13.3.0",
67 | "vue-style-loader": "^3.0.1",
68 | "vue-template-compiler": "^2.5.2",
69 | "webpack": "^3.6.0",
70 | "webpack-bundle-analyzer": "^2.9.0",
71 | "webpack-dev-server": "^2.9.1",
72 | "webpack-merge": "^4.1.0"
73 | },
74 | "engines": {
75 | "node": ">= 6.0.0",
76 | "npm": ">= 3.0.0"
77 | },
78 | "browserslist": [
79 | "> 1%",
80 | "last 2 versions",
81 | "not ie <= 8"
82 | ]
83 | }
84 |
--------------------------------------------------------------------------------
/server/app.js:
--------------------------------------------------------------------------------
1 | const express = require('express')
2 | const app = express()
3 | const bodyParser = require('body-parser')
4 | const mongoose = require('mongoose')
5 | const DB_URL = 'mongodb://localhost:27017/book'
6 | mongoose.connect(DB_URL)
7 |
8 | // 解析表单数据
9 | app.use(bodyParser.json())
10 | app.use(bodyParser.urlencoded({ extended: true }))
11 |
12 | // 引入 路由模块
13 | var booksRouter = require('./routes/books')
14 | var usersRouter = require('./routes/users')
15 | var recordRouter = require('./routes/records')
16 | var wishRouter = require('./routes/wishes')
17 |
18 | app.use('/book', booksRouter)
19 | app.use('/user', usersRouter)
20 | app.use('/record', recordRouter)
21 | app.use('/wish', wishRouter)
22 |
23 | app.listen(8888, function () {
24 | console.log('server connect, listening at http://localhost:8888')
25 | })
26 |
--------------------------------------------------------------------------------
/server/db.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose')
2 |
3 | // 数据库地址
4 | const DB_URL = 'mongodb://localhost:27017/book'
5 |
6 | mongoose.connect(DB_URL)
7 | console.log('db connect success')
8 |
9 | mongoose.connection.on('disconnected', function () {
10 | console.log('db connect wrong')
11 | })
12 |
13 | module.exports = mongoose
14 |
--------------------------------------------------------------------------------
/server/routes/books.js:
--------------------------------------------------------------------------------
1 | const express = require('express')
2 | const router = express.Router()
3 | const BookSchema = require('../schema/book')
4 |
5 | // 设置跨域
6 | router.all('*', (req, res, next) => {
7 | res.header('Access-Control-Allow-Origin', '*')
8 | res.header('Access-Control-Allow-Headers', 'Content-Type, Content-Length, Authorization, Accept, X-Requested-With , yourHeaderFeild')
9 | res.header('Access-Control-Allow-Methods', 'PUT, POST, GET, DELETE, OPTIONS')
10 |
11 | if (req.method === 'OPTIONS') {
12 | res.sendStatus(200) // 让options请求快速返回
13 | } else {
14 | next()
15 | }
16 | })
17 |
18 | // 录入图书信息
19 | router.post('/record', (req, res) => {
20 | const { title } = req.body
21 | BookSchema.find({ title }, (err, data) => {
22 | if (err) {
23 | res.json({
24 | result: false,
25 | msg: err
26 | })
27 | } else {
28 | // 录入的图书不存在,则录入
29 | if (data.length === 0) {
30 | insert(req.body).then(r => {
31 | if (r.result) {
32 | res.json({
33 | result: true,
34 | msg: '录入成功'
35 | })
36 | } else {
37 | res.json({
38 | result: false,
39 | msg: r.msg
40 | })
41 | }
42 | }).catch(err => {
43 | res.json({
44 | result: false,
45 | msg: err
46 | })
47 | })
48 | } else {
49 | res.json({
50 | result: false,
51 | msg: '该图书已经在书架上啦'
52 | })
53 | }
54 | }
55 | })
56 | })
57 | function insert({ title, image, rating, author, author_intro, pubdate, pages, summary = '无', publisher }) {
58 | return new Promise((resolve, reject) => {
59 | let book = new BookSchema({
60 | title,
61 | image,
62 | rating: rating.average || '无',
63 | author: author[0] || '未知',
64 | author_intro,
65 | pages,
66 | summary,
67 | pubdate,
68 | publisher,
69 | record_date: new Date().getTime(),
70 | borrowCount: 0,
71 | status: 1
72 | })
73 | book.save((err, data) => {
74 | if (err) {
75 | reject({ result: false, msg: err })
76 | } else {
77 | resolve({ result: true, msg: '', data: data })
78 | }
79 | })
80 | })
81 | }
82 |
83 | // 图书下架
84 | router.post('/offshelf', (req, res) => {
85 | const { title } = req.body
86 | BookSchema.remove({ title }, (err, data) => {
87 | if (err) {
88 | res.json({
89 | result: false,
90 | msg: err
91 | })
92 | } else {
93 | res.json({
94 | result: true,
95 | msg: '下架成功'
96 | })
97 | }
98 | })
99 | })
100 |
101 | // 获取图书信息
102 | router.get('/list', (req, res) => {
103 | const { status, title, order } = req.query
104 | const filter = {}
105 | if (status && status !== '0') {
106 | filter.status = Number(status)
107 | }
108 | if (title) {
109 | filter.title = title
110 | }
111 | if (order) {
112 | BookSchema
113 | .find()
114 | .where(filter)
115 | .sort({ borrowCount: -1 })
116 | .exec((err, data) => {
117 | if (err) {
118 | res.json({
119 | result: false,
120 | msg: err
121 | })
122 | } else {
123 | res.json({
124 | result: true,
125 | data: data
126 | })
127 | }
128 | })
129 | } else {
130 | BookSchema
131 | .find()
132 | .where(filter)
133 | .exec((err, data) => {
134 | if (err) {
135 | res.json({
136 | result: false,
137 | msg: err
138 | })
139 | } else {
140 | res.json({
141 | result: true,
142 | data: data
143 | })
144 | }
145 | })
146 | }
147 | })
148 |
149 | module.exports = router
150 |
--------------------------------------------------------------------------------
/server/routes/records.js:
--------------------------------------------------------------------------------
1 | const express = require('express')
2 | const router = express.Router()
3 | const BookSchema = require('../schema/book')
4 | const RecordSchema = require('../schema/record')
5 |
6 | // 设置跨域
7 | router.all('*', function (req, res, next) {
8 | res.header('Access-Control-Allow-Origin', '*')
9 | res.header('Access-Control-Allow-Headers', 'Content-Type, Content-Length, Authorization, Accept, X-Requested-With , yourHeaderFeild')
10 | res.header('Access-Control-Allow-Methods', 'PUT, POST, GET, DELETE, OPTIONS')
11 |
12 | if (req.method === 'OPTIONS') {
13 | res.sendStatus(200) // 让options请求快速返回
14 | } else {
15 | next()
16 | }
17 | })
18 |
19 | // 增加借阅记录
20 | router.post('/add', (req, res) => {
21 | insertRecord(req.body).then(res1 => {
22 | if (res1.result) {
23 | updateBookStatus(req.body).then(res2 => {
24 | if (res2.result) {
25 | res.json({
26 | result: true,
27 | msg: '借阅成功'
28 | })
29 | } else {
30 | res.json({
31 | result: false,
32 | msg: res2.msg
33 | })
34 | }
35 | }).catch(err => {
36 | res.json({
37 | result: false,
38 | msg: err
39 | })
40 | })
41 | } else {
42 | res.json({
43 | result: false,
44 | msg: res1.msg
45 | })
46 | }
47 | }).catch(err => {
48 | res.json({
49 | result: false,
50 | msg: err
51 | })
52 | })
53 | })
54 | function insertRecord(body) {
55 | const { title, user, status, image } = body
56 | return new Promise((resolve, reject) => {
57 | RecordSchema.find({ title, user }, (err, data) => {
58 | if (err) {
59 | reject({ result: false, msg: err })
60 | } else {
61 | if (data.length === 0) {
62 | const record = new RecordSchema({
63 | title,
64 | user,
65 | image,
66 | status,
67 | date: new Date().getTime(),
68 | returnDate: ''
69 | })
70 | record.save((err, data) => {
71 | if (err) {
72 | reject({ result: false, msg: err })
73 | } else {
74 | resolve({ result: true, msg: '' })
75 | }
76 | })
77 | } else {
78 | if (status === 1) {
79 | RecordSchema
80 | .where({ title, user })
81 | .update({ status, date: new Date().getTime(), returnDate: '' }, (err) => {
82 | if (err) {
83 | reject({ result: false, msg: err })
84 | } else {
85 | resolve({ result: true, msg: '' })
86 | }
87 | })
88 | } else {
89 | RecordSchema
90 | .where({ title, user })
91 | .update({ status, returnDate: new Date().getTime() }, (err) => {
92 | if (err) {
93 | reject({ result: false, msg: err })
94 | } else {
95 | resolve({ result: true, msg: '' })
96 | }
97 | })
98 | }
99 | }
100 | }
101 | })
102 | })
103 | }
104 | function updateBookStatus(body) {
105 | const { title, status } = body
106 | let options = {}
107 | // 借书时,书籍的借阅次数才增加 1
108 | if (status === 1) {
109 | options = {
110 | status: 2,
111 | $inc: { borrowCount: 1 }
112 | }
113 | } else {
114 | options = {
115 | status: 1
116 | }
117 | }
118 | return new Promise((resolve, reject) => {
119 | BookSchema
120 | .where({ title })
121 | .update(options, (err, data) => {
122 | if (err) {
123 | reject({ result: false, msg: err })
124 | } else {
125 | resolve({ result: true, msg: '' })
126 | }
127 | })
128 | })
129 | }
130 |
131 | // 借阅记录列表
132 | router.get('/list', (req, res) => {
133 | const { title, status } = req.query
134 | const filter = {}
135 | if (title) {
136 | filter.title = title
137 | }
138 | if (status && status !== '0') {
139 | filter.status = Number(status)
140 | }
141 | RecordSchema
142 | .find(filter)
143 | .sort({ date: 'asc' })
144 | .exec((err, data) => {
145 | if (err) {
146 | res.json({
147 | result: false,
148 | msg: err
149 | })
150 | } else {
151 | res.json({
152 | result: true,
153 | data: data
154 | })
155 | }
156 | })
157 | })
158 |
159 | module.exports = router
160 |
--------------------------------------------------------------------------------
/server/routes/users.js:
--------------------------------------------------------------------------------
1 | const express = require('express')
2 | const router = express.Router()
3 | const UserSchema = require('../schema/user')
4 |
5 | // 设置跨域
6 | router.all('*', function (req, res, next) {
7 | res.header('Access-Control-Allow-Origin', '*')
8 | res.header('Access-Control-Allow-Headers', 'Content-Type, Content-Length, Authorization, Accept, X-Requested-With , yourHeaderFeild')
9 | res.header('Access-Control-Allow-Methods', 'PUT, POST, GET, DELETE, OPTIONS')
10 |
11 | if (req.method === 'OPTIONS') {
12 | res.sendStatus(200) // 让options请求快速返回
13 | } else {
14 | next()
15 | }
16 | })
17 |
18 | // 登录
19 | router.post('/login', (req, res) => {
20 | const { name, pwd } = req.body
21 | UserSchema.find({ name, pwd }, (err, data) => {
22 | if (err) {
23 | res.json({
24 | result: false,
25 | msg: err
26 | })
27 | } else {
28 | if (data.length !== 0) {
29 | res.json({
30 | result: true,
31 | msg: '登录成功'
32 | })
33 | } else {
34 | res.json({
35 | result: false,
36 | msg: '用户不存在或密码错误'
37 | })
38 | }
39 | }
40 | })
41 | })
42 |
43 | // 用户列表
44 | router.get('/list', (req, res) => {
45 | UserSchema.find({}, (err, data) => {
46 | if (err) {
47 | res.json({
48 | result: false,
49 | msg: err
50 | })
51 | } else {
52 | res.json({
53 | result: true,
54 | msg: '查询成功',
55 | data: data
56 | })
57 | }
58 | })
59 | })
60 |
61 | // 新增用户
62 | router.post('/add', (req, res) => {
63 | const { name, pwd } = req.body
64 | if (name !== '' && pwd !== '') {
65 | const user = new UserSchema({
66 | name,
67 | pwd
68 | })
69 | UserSchema.find({ name }, (err, data) => {
70 | if (err) {
71 | res.json({
72 | result: false,
73 | msg: err
74 | })
75 | } else {
76 | if (data.length !== 0) {
77 | res.json({
78 | result: false,
79 | msg: '该用户已存在'
80 | })
81 | } else {
82 | user.save((err, data) => {
83 | if (err) {
84 | res.json({
85 | result: false,
86 | msg: err
87 | })
88 | } else {
89 | res.json({
90 | result: true,
91 | msg: '新增成功'
92 | })
93 | }
94 | })
95 | }
96 | }
97 | })
98 | } else {
99 | res.json({
100 | result: false,
101 | msg: '用户名或密码不能为空'
102 | })
103 | }
104 | })
105 |
106 | // 删除用户
107 | router.post('/del', (req, res) => {
108 | const { name } = req.body
109 | UserSchema.remove({ name }, (err, data) => {
110 | if (err) {
111 | res.json({
112 | result: false,
113 | msg: err
114 | })
115 | } else {
116 | res.json({
117 | result: true,
118 | msg: '删除成功'
119 | })
120 | }
121 | })
122 | })
123 |
124 | module.exports = router
125 |
--------------------------------------------------------------------------------
/server/routes/wishes.js:
--------------------------------------------------------------------------------
1 | const express = require('express')
2 | const router = express.Router()
3 | const WishSchema = require('../schema/wish')
4 |
5 | // 设置跨域
6 | router.all('*', function (req, res, next) {
7 | res.header('Access-Control-Allow-Origin', '*')
8 | res.header('Access-Control-Allow-Headers', 'Content-Type, Content-Length, Authorization, Accept, X-Requested-With , yourHeaderFeild')
9 | res.header('Access-Control-Allow-Methods', 'PUT, POST, GET, DELETE, OPTIONS')
10 |
11 | if (req.method === 'OPTIONS') {
12 | res.sendStatus(200) // 让options请求快速返回
13 | } else {
14 | next()
15 | }
16 | })
17 |
18 | // 后台愿望列表
19 | router.get('/list', (req, res) => {
20 | WishSchema
21 | .aggregate([{
22 | $group: {
23 | _id: '$title',
24 | account: { $sum: 1 }
25 | }
26 | }])
27 | .exec((err, data) => {
28 | console.log(err, data)
29 | if (err) {
30 | res.json({
31 | result: false,
32 | msg: err
33 | })
34 | } else {
35 | res.json({
36 | result: true,
37 | msg: '查询成功',
38 | data: data
39 | })
40 | }
41 | })
42 | })
43 |
44 | // 用户愿望列表
45 | router.get('/mine', (req, res) => {
46 | const { user } = req.query
47 | WishSchema.find({ user }, (err, data) => {
48 | if (err) {
49 | res.json({
50 | result: false,
51 | msg: err
52 | })
53 | } else {
54 | res.json({
55 | result: true,
56 | msg: '查询成功',
57 | data: data
58 | })
59 | }
60 | })
61 | })
62 |
63 | // 加入愿望清单
64 | router.post('/add', (req, res) => {
65 | const { title, user } = req.body
66 | if (title !== '' && user !== '') {
67 | const wish = new WishSchema({
68 | title,
69 | user
70 | })
71 | WishSchema.find({ title, user }, (err, data) => {
72 | if (err) {
73 | res.json({
74 | result: false,
75 | msg: err
76 | })
77 | } else {
78 | if (data.length !== 0) {
79 | res.json({
80 | result: false,
81 | msg: '已加入愿望清单'
82 | })
83 | } else {
84 | wish.save((err, data) => {
85 | if (err) {
86 | res.json({
87 | result: false,
88 | msg: err
89 | })
90 | } else {
91 | res.json({
92 | result: true,
93 | msg: '已加入愿望清单'
94 | })
95 | }
96 | })
97 | }
98 | }
99 | })
100 | } else {
101 | res.json({
102 | result: false,
103 | msg: '用户名或书名不能为空'
104 | })
105 | }
106 | })
107 |
108 | // 移除愿望清单
109 | router.post('/del', (req, res) => {
110 | console.log(req.body)
111 | const { title, user } = req.body
112 | WishSchema.remove({ title, user }, (err, data) => {
113 | if (err) {
114 | res.json({
115 | result: false,
116 | msg: err
117 | })
118 | } else {
119 | res.json({
120 | result: true,
121 | msg: '删除成功'
122 | })
123 | }
124 | })
125 | })
126 |
127 | module.exports = router
128 |
--------------------------------------------------------------------------------
/server/schema/book.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('../db')
2 | const Schema = mongoose.Schema
3 |
4 | const BookSchema = new Schema({
5 | title: String, // 图书名称
6 | image: String, // 封面
7 | rating: Number, // 评分
8 | author: String, // 作者
9 | author_intro: String, // 作者简介
10 | pages: String, // 页数
11 | summary: String, // 图书简介
12 | pubdate: String, // 出版时间
13 | publisher: String, // 出版社
14 | record_date: String, // 上架时间
15 | borrowCount: Number, // 被借阅次数
16 | status: Number // 0 = 所有,1 = 在架,2 = 借出
17 | })
18 |
19 | module.exports = mongoose.model('Book', BookSchema)
20 |
--------------------------------------------------------------------------------
/server/schema/record.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('../db')
2 | const Schema = mongoose.Schema
3 |
4 | const RecordSchema = new Schema({
5 | title: String,
6 | user: String,
7 | image: String,
8 | date: String,
9 | returnDate: String,
10 | status: Number // 1 = 借出, 2 = 已归还
11 | })
12 |
13 | module.exports = mongoose.model('Record', RecordSchema)
14 |
--------------------------------------------------------------------------------
/server/schema/user.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('../db')
2 | const Schema = mongoose.Schema
3 |
4 | const UserSchema = new Schema({
5 | name: String,
6 | pwd: String
7 | })
8 |
9 | module.exports = mongoose.model('User', UserSchema)
10 |
--------------------------------------------------------------------------------
/server/schema/wish.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('../db')
2 | const Schema = mongoose.Schema
3 |
4 | const WishSchema = new Schema({
5 | title: String,
6 | user: String
7 | })
8 |
9 | module.exports = mongoose.model('Wish', WishSchema)
10 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
41 |
71 |
--------------------------------------------------------------------------------
/src/assets/css/common.scss:
--------------------------------------------------------------------------------
1 | // 清楚浮动
2 | .clearfix {
3 | display: inline-block;
4 | &:after {
5 | display: block;
6 | content: '.';
7 | height: 0;
8 | line-height: 0;
9 | clear: both;
10 | visibility: hidden;
11 | }
12 | }
13 |
14 | // 单行文本超出长度显示省略号
15 | .ellipsis {
16 | text-overflow: ellipsis;
17 | overflow: hidden;
18 | white-space: nowrap;
19 | }
20 |
21 | // 定位
22 | .pos-abs {
23 | position: absolute;
24 | }
25 |
26 | .pos-rel {
27 | position: relative;
28 | }
29 |
30 | .pos-fix {
31 | position: fixed;
32 | }
33 |
34 | // display
35 | .inline-block {
36 | display: inline-block;
37 | }
38 |
39 | .none {
40 | display: none;
41 | }
42 |
43 | // 文字排版
44 | .tl {
45 | text-align: left;
46 | }
47 |
48 | .tr {
49 | text-align: right;
50 | }
51 |
52 | .tc {
53 | text-align: center;
54 | }
55 |
56 | // 块级元素水平居中
57 | .mc {
58 | margin: 0 auto;
59 | }
60 |
61 | // 浮动
62 | .fl {
63 | float: left;
64 | }
65 |
66 | .fr {
67 | float: right;
68 | }
69 |
70 | // 字体大小
71 | .fs-14 {
72 | font-size: 14px;
73 | }
74 |
75 | .fs-16 {
76 | font-size: 16px;
77 | }
78 |
79 | .fs-18 {
80 | font-size: 18px;
81 | }
82 |
83 | .fs-20 {
84 | font-size: 20px;
85 | }
86 |
87 | .fs-22 {
88 | font-size: 22px;
89 | }
90 |
91 | .fs-24 {
92 | font-size: 24px;
93 | }
94 |
95 | .fs-26 {
96 | font-size: 26px;
97 | }
98 |
99 | .fs-28 {
100 | font-size: 28px;
101 | }
102 |
103 | // 字体颜色
104 | .white {
105 | color: #fff;
106 | }
--------------------------------------------------------------------------------
/src/assets/css/global.scss:
--------------------------------------------------------------------------------
1 | .container {
2 | .el-breadcrumb {
3 | position: absolute;
4 | top: 60px;
5 | left: 200px;
6 | height: 48px;
7 | line-height: 48px;
8 | background-color: #fff;
9 | width: 100%;
10 | padding-left: 20px;
11 | border-bottom: 1px solid $border-color;
12 | .el-breadcrumb__inner {
13 | font-weight: bolder!important;
14 | }
15 | }
16 | .header {
17 | margin-bottom: 20px;
18 | .el-select {
19 | width: 120px;
20 | .el-input {
21 | width: 100%;
22 | }
23 | }
24 | .el-input {
25 | width: 300px;
26 | }
27 | }
28 | }
--------------------------------------------------------------------------------
/src/assets/css/index.scss:
--------------------------------------------------------------------------------
1 | @import './reset.scss';
2 | @import './variable.scss';
3 | @import './mixin.scss';
4 | @import './common.scss';
5 | @import './global.scss';
--------------------------------------------------------------------------------
/src/assets/css/mixin.scss:
--------------------------------------------------------------------------------
1 | // 移动端1px
2 | @mixin border-1px($color) {
3 | position: relative;
4 | &:after {
5 | display: block;
6 | position: absolute;
7 | left: 0;
8 | bottom: 0;
9 | width: 100%;
10 | border-bottom: 1px solid $color;
11 | content: ' ';
12 | }
13 | }
14 |
15 | @media (-webkit-min-device-pixel-ratio:1.5),
16 | (min-device-pixel-ratio:1.5) {
17 | .border-1px {
18 | &::after {
19 | -webkit-transform: scaleY(0.7);
20 | transform: scaleY(0.7);
21 | }
22 | }
23 | }
24 |
25 | @media (-webkit-min-device-pixel-ratio:2),
26 | (min-device-pixel-ratio:2) {
27 | .border-1px {
28 | &::after {
29 | -webkit-transform: scaleY(0.5);
30 | transform: scaleY(0.5);
31 | }
32 | }
33 | }
34 |
35 | // 多行文本超出长度显示省略号
36 | @mixin multi-ellipsis($line: 2) {
37 | display: -webkit-box;
38 | -webkit-box-orient: vertical;
39 | -webkit-line-clamp: $line;
40 | overflow: hidden;
41 | }
42 |
43 | // 扩展点击区域
44 | @mixin extend-click {
45 | position: relative;
46 | &:before {
47 | content: '';
48 | position: absolute;
49 | top: -10px;
50 | left: -10px;
51 | right: -10px;
52 | bottom: -10px;
53 | }
54 | }
55 |
56 | // 不换行
57 | @mixin no-wrap {
58 | text-overflow: ellipsis;
59 | overflow: hidden;
60 | white-space: nowrap;
61 | }
--------------------------------------------------------------------------------
/src/assets/css/reset.scss:
--------------------------------------------------------------------------------
1 | /*! normalize.css v5.0.0 | MIT License | github.com/necolas/normalize.css */
2 |
3 | /**
4 | * 1. Change the default font family in all browsers (opinionated).
5 | * 2. Correct the line height in all browsers.
6 | * 3. Prevent adjustments of font size after orientation changes in
7 | * IE on Windows Phone and in iOS.
8 | */
9 |
10 | /* Document
11 | ========================================================================== */
12 |
13 | html {
14 | font-family: sans-serif;
15 | /* 1 */
16 | line-height: 1.15;
17 | /* 2 */
18 | -ms-text-size-adjust: 100%;
19 | /* 3 */
20 | -webkit-text-size-adjust: 100%;
21 | /* 3 */
22 | }
23 |
24 | /* Sections
25 | ========================================================================== */
26 |
27 | /**
28 | * Remove the margin in all browsers (opinionated).
29 | */
30 |
31 | body {
32 | margin: 0;
33 | }
34 |
35 | /**
36 | * Add the correct display in IE 9-.
37 | */
38 |
39 | article,
40 | aside,
41 | footer,
42 | header,
43 | nav,
44 | section {
45 | display: block;
46 | }
47 |
48 | /**
49 | * Correct the font size and margin on `h1` elements within `section` and
50 | * `article` contexts in Chrome, Firefox, and Safari.
51 | */
52 |
53 | h1 {
54 | font-size: 2em;
55 | margin: .67em 0;
56 | }
57 |
58 | /* Grouping content
59 | ========================================================================== */
60 |
61 | /**
62 | * Add the correct display in IE 9-.
63 | * 1. Add the correct display in IE.
64 | */
65 |
66 | figcaption,
67 | figure,
68 | main {
69 | /* 1 */
70 | display: block;
71 | }
72 |
73 | /**
74 | * Add the correct margin in IE 8.
75 | */
76 |
77 | figure {
78 | margin: 1em 40px;
79 | }
80 |
81 | /**
82 | * 1. Add the correct box sizing in Firefox.
83 | * 2. Show the overflow in Edge and IE.
84 | */
85 |
86 | hr {
87 | box-sizing: content-box;
88 | /* 1 */
89 | height: 0;
90 | /* 1 */
91 | overflow: visible;
92 | /* 2 */
93 | }
94 |
95 | /**
96 | * 1. Correct the inheritance and scaling of font size in all browsers.
97 | * 2. Correct the odd `em` font sizing in all browsers.
98 | */
99 |
100 | pre {
101 | font-family: monospace, monospace;
102 | /* 1 */
103 | font-size: 1em;
104 | /* 2 */
105 | }
106 |
107 | /* Text-level semantics
108 | ========================================================================== */
109 |
110 | /**
111 | * 1. Remove the gray background on active links in IE 10.
112 | * 2. Remove gaps in links underline in iOS 8+ and Safari 8+.
113 | */
114 |
115 | a {
116 | background-color: transparent;
117 | /* 1 */
118 | -webkit-text-decoration-skip: objects;
119 | /* 2 */
120 | }
121 |
122 | /**
123 | * Remove the outline on focused links when they are also active or hovered
124 | * in all browsers (opinionated).
125 | */
126 |
127 | a:active,
128 | a:hover {
129 | outline-width: 0;
130 | }
131 |
132 | /**
133 | * 1. Remove the bottom border in Firefox 39-.
134 | * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
135 | */
136 |
137 | abbr[title] {
138 | border-bottom: none;
139 | /* 1 */
140 | text-decoration: underline;
141 | /* 2 */
142 | }
143 |
144 | /**
145 | * Prevent the duplicate application of `bolder` by the next rule in Safari 6.
146 | */
147 |
148 | b,
149 | strong {
150 | font-weight: inherit;
151 | }
152 |
153 | /**
154 | * Add the correct font weight in Chrome, Edge, and Safari.
155 | */
156 |
157 | b,
158 | strong {
159 | font-weight: bolder;
160 | }
161 |
162 | /**
163 | * 1. Correct the inheritance and scaling of font size in all browsers.
164 | * 2. Correct the odd `em` font sizing in all browsers.
165 | */
166 |
167 | code,
168 | kbd,
169 | samp {
170 | font-family: monospace, monospace;
171 | /* 1 */
172 | font-size: 1em;
173 | /* 2 */
174 | }
175 |
176 | /**
177 | * Add the correct font style in Android 4.3-.
178 | */
179 |
180 | dfn {
181 | font-style: italic;
182 | }
183 |
184 | /**
185 | * Add the correct background and color in IE 9-.
186 | */
187 |
188 | mark {
189 | background-color: #ff0;
190 | color: #000;
191 | }
192 |
193 | /**
194 | * Add the correct font size in all browsers.
195 | */
196 |
197 | small {
198 | font-size: 80%;
199 | }
200 |
201 | /**
202 | * Prevent `sub` and `sup` elements from affecting the line height in
203 | * all browsers.
204 | */
205 |
206 | sub,
207 | sup {
208 | font-size: 75%;
209 | line-height: 0;
210 | position: relative;
211 | vertical-align: baseline;
212 | }
213 |
214 | sub {
215 | bottom: -.25em;
216 | }
217 |
218 | sup {
219 | top: -.5em;
220 | }
221 |
222 | /* Embedded content
223 | ========================================================================== */
224 |
225 | /**
226 | * Add the correct display in IE 9-.
227 | */
228 |
229 | audio,
230 | video {
231 | display: inline-block;
232 | }
233 |
234 | /**
235 | * Add the correct display in iOS 4-7.
236 | */
237 |
238 | audio:not([controls]) {
239 | display: none;
240 | height: 0;
241 | }
242 |
243 | /**
244 | * Remove the border on images inside links in IE 10-.
245 | */
246 |
247 | img {
248 | border-style: none;
249 | }
250 |
251 | /**
252 | * Hide the overflow in IE.
253 | */
254 |
255 | svg:not(:root) {
256 | overflow: hidden;
257 | }
258 |
259 | /* Forms
260 | ========================================================================== */
261 |
262 | /**
263 | * 1. Change the font styles in all browsers (opinionated).
264 | * 2. Remove the margin in Firefox and Safari.
265 | */
266 |
267 | button,
268 | input,
269 | optgroup,
270 | select,
271 | textarea {
272 | font-family: sans-serif;
273 | /* 1 */
274 | font-size: 100%;
275 | /* 1 */
276 | line-height: 1.15;
277 | /* 1 */
278 | margin: 0;
279 | /* 2 */
280 | }
281 |
282 | /**
283 | * Show the overflow in IE.
284 | * 1. Show the overflow in Edge.
285 | */
286 |
287 | button,
288 | input {
289 | /* 1 */
290 | overflow: visible;
291 | }
292 |
293 | /**
294 | * Remove the inheritance of text transform in Edge, Firefox, and IE.
295 | * 1. Remove the inheritance of text transform in Firefox.
296 | */
297 |
298 | button,
299 | select {
300 | /* 1 */
301 | text-transform: none;
302 | }
303 |
304 | /**
305 | * 1. Prevent a WebKit bug where (2) destroys native `audio` and `video`
306 | * controls in Android 4.
307 | * 2. Correct the inability to style clickable types in iOS and Safari.
308 | */
309 |
310 | button,
311 | html [type="button"],
312 | /* 1 */
313 |
314 | [type="reset"],
315 | [type="submit"] {
316 | -webkit-appearance: button;
317 | /* 2 */
318 | }
319 |
320 | /**
321 | * Remove the inner border and padding in Firefox.
322 | */
323 |
324 | button::-moz-focus-inner,
325 | [type="button"]::-moz-focus-inner,
326 | [type="reset"]::-moz-focus-inner,
327 | [type="submit"]::-moz-focus-inner {
328 | border-style: none;
329 | padding: 0;
330 | }
331 |
332 | /**
333 | * Restore the focus styles unset by the previous rule.
334 | */
335 |
336 | button:-moz-focusring,
337 | [type="button"]:-moz-focusring,
338 | [type="reset"]:-moz-focusring,
339 | [type="submit"]:-moz-focusring {
340 | outline: 1px dotted #666;
341 | }
342 |
343 | /**
344 | * Change the border, margin, and padding in all browsers (opinionated).
345 | */
346 |
347 | fieldset {
348 | border: 1px solid #c0c0c0;
349 | margin: 0 2px;
350 | padding: .35em .625em .75em;
351 | }
352 |
353 | /**
354 | * 1. Correct the text wrapping in Edge and IE.
355 | * 2. Correct the color inheritance from `fieldset` elements in IE.
356 | * 3. Remove the padding so developers are not caught out when they zero out
357 | * `fieldset` elements in all browsers.
358 | */
359 |
360 | legend {
361 | box-sizing: border-box;
362 | /* 1 */
363 | color: inherit;
364 | /* 2 */
365 | display: table;
366 | /* 1 */
367 | max-width: 100%;
368 | /* 1 */
369 | padding: 0;
370 | /* 3 */
371 | white-space: normal;
372 | /* 1 */
373 | }
374 |
375 | /**
376 | * 1. Add the correct display in IE 9-.
377 | * 2. Add the correct vertical alignment in Chrome, Firefox, and Opera.
378 | */
379 |
380 | progress {
381 | display: inline-block;
382 | /* 1 */
383 | vertical-align: baseline;
384 | /* 2 */
385 | }
386 |
387 | /**
388 | * Remove the default vertical scrollbar in IE.
389 | */
390 |
391 | textarea {
392 | overflow: auto;
393 | }
394 |
395 | /**
396 | * 1. Add the correct box sizing in IE 10-.
397 | * 2. Remove the padding in IE 10-.
398 | */
399 |
400 | [type="checkbox"],
401 | [type="radio"] {
402 | box-sizing: border-box;
403 | /* 1 */
404 | padding: 0;
405 | /* 2 */
406 | }
407 |
408 | /**
409 | * Correct the cursor style of increment and decrement buttons in Chrome.
410 | */
411 |
412 | [type="number"]::-webkit-inner-spin-button,
413 | [type="number"]::-webkit-outer-spin-button {
414 | height: auto;
415 | }
416 |
417 | /**
418 | * 1. Correct the odd appearance in Chrome and Safari.
419 | * 2. Correct the outline style in Safari.
420 | */
421 |
422 | [type="search"] {
423 | -webkit-appearance: textfield;
424 | /* 1 */
425 | outline-offset: -2px;
426 | /* 2 */
427 | }
428 |
429 | /**
430 | * Remove the inner padding and cancel buttons in Chrome and Safari on macOS.
431 | */
432 |
433 | [type="search"]::-webkit-search-cancel-button,
434 | [type="search"]::-webkit-search-decoration {
435 | -webkit-appearance: none;
436 | }
437 |
438 | /**
439 | * 1. Correct the inability to style clickable types in iOS and Safari.
440 | * 2. Change font properties to `inherit` in Safari.
441 | */
442 |
443 | ::-webkit-file-upload-button {
444 | -webkit-appearance: button;
445 | /* 1 */
446 | font: inherit;
447 | /* 2 */
448 | }
449 |
450 | /* Interactive
451 | ========================================================================== */
452 |
453 | /*
454 | * Add the correct display in IE 9-.
455 | * 1. Add the correct display in Edge, IE, and Firefox.
456 | */
457 |
458 | details,
459 | /* 1 */
460 |
461 | menu {
462 | display: block;
463 | }
464 |
465 | /*
466 | * Add the correct display in all browsers.
467 | */
468 |
469 | summary {
470 | display: list-item;
471 | }
472 |
473 | /* Scripting
474 | ========================================================================== */
475 |
476 | /**
477 | * Add the correct display in IE 9-.
478 | */
479 |
480 | canvas {
481 | display: inline-block;
482 | }
483 |
484 | /**
485 | * Add the correct display in IE.
486 | */
487 |
488 | template {
489 | display: none;
490 | }
491 |
492 | /* Hidden
493 | ========================================================================== */
494 |
495 | /**
496 | * Add the correct display in IE 10-.
497 | */
498 |
499 | [hidden] {
500 | display: none;
501 | }
502 |
503 | // custom
504 | ul > li {
505 | list-style: none;
506 | }
507 |
508 | i {
509 | display: inline-block;
510 | }
--------------------------------------------------------------------------------
/src/assets/css/variable.scss:
--------------------------------------------------------------------------------
1 | $border-color: #e6e6e6;
2 | $main-color: #4c99fe;
--------------------------------------------------------------------------------
/src/assets/image/bg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Pengjiaxun/cloud-library/f1596d93a624273b0decce6c87194e9016229789/src/assets/image/bg.jpg
--------------------------------------------------------------------------------
/src/assets/js/mixins.js:
--------------------------------------------------------------------------------
1 | export const Loading = {
2 | data() {
3 | return {
4 | loading: false
5 | }
6 | },
7 | methods: {
8 | showLoading() {
9 | this.loading = true
10 | },
11 | hideLoading() {
12 | this.loading = false
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/assets/js/reg.js:
--------------------------------------------------------------------------------
1 | export default {
2 | // 电话
3 | tel: /^(0|86|17951)?(13[0-9]|14[57]|15[0-9]|17[0-9]|18[0-9])[0-9]{8}$/,
4 | // 大陆身份证
5 | idCard: /(^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx*]$)|(^[1-9]\d{5}\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}$)/,
6 | // 日期(2017-01-01)
7 | date: /^((((1[6-9]|[2-9]\d)\d{2})-(1[02]|0?[13578])-([12]\d|3[01]|0?[1-9]))|(((1[6-9]|[2-9]\d)\d{2})-(1[012]|0?[13456789])-([12]\d|30|0?[1-9]))|(((1[6-9]|[2-9]\d)\d{2})-0?2-(1\d|2[0-8]|0?[1-9]))|(((1[6-9]|[2-9]\d)(0[48]|[2468][048]|[13579][26])|((16|[2468][048]|[3579][26])00))-0?2-29-))$/,
8 | // 正整数
9 | posInteger: /^\+?[1-9][0-9]*$/,
10 | // 包含大小写字母和数字,不能使用特殊字符,长度8-10
11 | enhanceStr: /^(?=.*\\d)(?=.*[a-z])(?=.*[A-Z]).{8,10}$/,
12 | // 只能是中文
13 | chinese: /^[\\u4e00-\\u9fa5]{0,}$/,
14 | // 数字,26个英文字母或下划线
15 | wStr: /^\\w+$/,
16 | // 邮箱地址
17 | email: /[\\w!#$%&'*+/=?^_`{|}~-]+(?:\\.[\\w!#$%&'*+/=?^_`{|}~-]+)*@(?:[\\w](?:[\\w-]*[\\w])?\\.)+[\\w](?:[\\w-]*[\\w])?/,
18 | // 金额,精确到2位小数
19 | money: /^[0-9]+(.[0-9]{2})?$/,
20 | // 判断银行卡
21 | bankcard: /^\d{16}|\d{19}$/
22 | }
--------------------------------------------------------------------------------
/src/assets/js/utils.js:
--------------------------------------------------------------------------------
1 | /** 时间戳格式化方法
2 | * @param {any} timestamp 时间戳数字
3 | * @param {string} [type='unix'] 分为unix时间和js时间
4 | * @param {string} [format='yyyy-mm-dd hh:mm:ss'] 格式化形式
5 | * @returns 2017-03-03 15:00:00
6 | */
7 | export const formatDate = function (timestamp, format = 'yyyy-mm-dd', type = 'js') {
8 | function fixZero(num, length) {
9 | let str = '' + num
10 | let len = str.length
11 | let s = ''
12 | for (let i = length; i-- > len;) {
13 | s += '0'
14 | }
15 | return s + str
16 | }
17 |
18 | let date
19 | type === 'unix' ? date = new Date(timestamp * 1000) : date = new Date(timestamp)
20 | let dateInfo = {
21 | fullYear: date.getFullYear(),
22 | month: fixZero(date.getMonth() + 1, 2),
23 | date: fixZero(date.getDate(), 2),
24 | hours: fixZero(date.getHours(), 2),
25 | minutes: fixZero(date.getMinutes(), 2),
26 | seconds: fixZero(date.getSeconds(), 2)
27 | }
28 | if (format === 'yyyy-mm-dd hh:mm:ss') {
29 | return dateInfo.fullYear + '-' + dateInfo.month + '-' + dateInfo.date + ' ' + dateInfo.hours + ':' + dateInfo.minutes + ':' + dateInfo.seconds
30 | } else if (format === 'mm-dd hh:mm') {
31 | return dateInfo.month + '-' + dateInfo.date + ' ' + dateInfo.hours + ':' + dateInfo.minutes
32 | } else if (format === 'mm.dd(cDay)') {
33 | return dateInfo.month + '.' + dateInfo.date + '(' + dateInfo.cDay + ')'
34 | } else if (format === 'yyyy-mm-dd') {
35 | return dateInfo.fullYear + '-' + dateInfo.month + '-' + dateInfo.date
36 | } else if (format === 'yyyy/mm/dd') {
37 | return dateInfo.fullYear + '/' + dateInfo.month + '/' + dateInfo.date
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/components/navbar.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | - 云空间图书管理系统
7 | -
8 | 您好: {{username}}
9 | - 退出
11 |
12 |
13 |
14 |
15 |
51 |
79 |
--------------------------------------------------------------------------------
/src/components/slider.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
17 |
18 |
55 |
62 |
69 |
--------------------------------------------------------------------------------
/src/config.js:
--------------------------------------------------------------------------------
1 | export const api = {
2 | loginApi: 'http://localhost:8888/user/login',
3 | bookApi: 'http://localhost:8888/book',
4 | userApi: 'http://localhost:8888/user',
5 | recordApi: 'http://localhost:8888/record',
6 | wishApi: 'http://localhost:8888/wish'
7 | }
8 |
--------------------------------------------------------------------------------
/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 from './App'
5 | import router from './router'
6 | import 'normalize.css'
7 | import './assets/css/index.scss'
8 | import ElementUI from 'element-ui'
9 | import 'element-ui/lib/theme-chalk/index.css'
10 | import http from 'axios'
11 |
12 | Vue.prototype.http = http
13 | Vue.use(ElementUI)
14 | Vue.config.productionTip = false
15 |
16 | /* eslint-disable no-new */
17 | new Vue({
18 | el: '#app',
19 | router,
20 | components: { App },
21 | template: ''
22 | })
23 |
--------------------------------------------------------------------------------
/src/router.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Router from 'vue-router'
3 | import Login from '@/views/login'
4 | import Books from '@/views/books'
5 | import Users from '@/views/users'
6 | import Records from '@/views/records'
7 | import Wishes from '@/views/wishes'
8 |
9 | Vue.use(Router)
10 |
11 | export default new Router({
12 | routes: [
13 | {
14 | path: '/',
15 | redirect: 'books'
16 | },
17 | {
18 | path: '/login',
19 | name: 'Login',
20 | component: Login
21 | },
22 | {
23 | path: '/books',
24 | name: 'Books',
25 | component: Books
26 | },
27 | {
28 | path: '/users',
29 | name: 'Users',
30 | component: Users
31 | },
32 | {
33 | path: '/records',
34 | name: 'Records',
35 | component: Records
36 | },
37 | {
38 | path: '/wishes',
39 | name: 'Wishes',
40 | component: Wishes
41 | }
42 | ]
43 | })
44 |
--------------------------------------------------------------------------------
/src/views/books.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 图书管理
5 |
6 |
8 |
10 |
29 |
30 |
38 |
40 |
41 |
44 |
45 |
46 |
48 |
49 |
51 |
52 |
54 |
55 |
58 |
59 |
62 |
63 | {{scope.row.status === 1 ? '在架' : '借出'}}
64 |
65 |
66 |
68 |
69 | 下架
72 |
73 |
74 |
75 |
76 |
77 |
79 |
89 |
90 |
97 |
99 |
100 |
103 |
104 |
105 |
107 |
108 |
110 |
111 |
113 |
114 |
117 |
118 |
120 |
121 | 录入
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
276 |
279 |
--------------------------------------------------------------------------------
/src/views/login.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
云空间图书馆
4 |
5 |
8 |
9 |
11 |
12 |
13 |
16 |
17 |
18 | 登录
22 |
23 |
24 |
25 |
26 |
27 |
28 |
75 |
76 |
104 |
--------------------------------------------------------------------------------
/src/views/records.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 借阅记录
5 |
6 |
25 |
26 |
33 |
35 |
36 | {{scope.$index + 1}}
37 |
38 |
39 |
41 |
42 |
44 |
45 |
47 |
48 | {{format(scope.row.date)}}
49 |
50 |
51 |
53 |
54 | {{scope.row.returnDate ? format(scope.row.returnDate) : ''}}
55 |
56 |
57 |
59 |
60 | {{scope.row.status === 1 ? '借阅中' : '已归还'}}
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
125 |
128 |
--------------------------------------------------------------------------------
/src/views/users.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 用户管理
5 |
6 |
11 |
12 |
19 |
21 |
22 | {{scope.$index + 1}}
23 |
24 |
25 |
27 |
28 |
30 |
31 |
33 |
34 | 删除
36 |
37 |
38 |
39 |
40 |
43 |
46 |
47 |
49 |
50 |
51 |
54 |
55 |
56 |
62 |
63 |
64 |
65 |
66 |
163 |
164 |
167 |
--------------------------------------------------------------------------------
/src/views/wishes.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 愿望清单
5 |
6 |
7 |
14 |
16 |
17 | {{scope.$index + 1}}
18 |
19 |
20 |
22 |
23 |
24 |
25 | {{scope.row.account}} 次
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
67 |
68 |
71 |
--------------------------------------------------------------------------------
/static/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Pengjiaxun/cloud-library/f1596d93a624273b0decce6c87194e9016229789/static/.gitkeep
--------------------------------------------------------------------------------
/weixin/app.js:
--------------------------------------------------------------------------------
1 | //app.js
2 | App({
3 | onLaunch: function () {
4 | // 展示本地存储能力
5 | var logs = wx.getStorageSync('logs') || []
6 | logs.unshift(Date.now())
7 | wx.setStorageSync('logs', logs)
8 |
9 | // 登录
10 | wx.login({
11 | success: res => {
12 | // 发送 res.code 到后台换取 openId, sessionKey, unionId
13 | }
14 | })
15 | // 获取用户信息
16 | wx.getSetting({
17 | success: res => {
18 | if (res.authSetting['scope.userInfo']) {
19 | // 已经授权,可以直接调用 getUserInfo 获取头像昵称,不会弹框
20 | wx.getUserInfo({
21 | success: res => {
22 | // 可以将 res 发送给后台解码出 unionId
23 | this.globalData.userInfo = res.userInfo
24 | console.log(res.userInfo, 'userInfo')
25 | // 由于 getUserInfo 是网络请求,可能会在 Page.onLoad 之后才返回
26 | // 所以此处加入 callback 以防止这种情况
27 | if (this.userInfoReadyCallback) {
28 | this.userInfoReadyCallback(res)
29 | }
30 | }
31 | })
32 | }
33 | }
34 | })
35 |
36 | // 验证是否登录
37 | wx.getStorage({
38 | key: 'isLogin',
39 | success: res => {
40 | if (res && res.data !== '1') {
41 | wx.navigateTo({
42 | url: './pages/login/login',
43 | success(e) {
44 | console.log(e)
45 | }
46 | })
47 | }
48 | },
49 | fail(res) {
50 | wx.navigateTo({
51 | url: './pages/login/login',
52 | success(e) {
53 | console.log(e)
54 | }
55 | })
56 | }
57 | })
58 |
59 | // 获取登录账号
60 | wx.getStorage({
61 | key: 'user',
62 | success: res => {
63 | if (res && res.data) {
64 | this.globalData.user = res.data
65 | }
66 | },
67 | fail(res) {
68 | console.log(res)
69 | }
70 | })
71 | },
72 | globalData: {
73 | userInfo: null,
74 | user: null
75 | }
76 | })
--------------------------------------------------------------------------------
/weixin/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "pages": [
3 | "pages/mine/mine",
4 | "pages/bookList/bookList",
5 | "pages/login/login",
6 | "pages/bookDetail/bookDetail",
7 | "pages/index/index"
8 | ],
9 | "window": {
10 | "backgroundTextStyle": "light",
11 | "navigationBarBackgroundColor": "#62c0e2",
12 | "navigationBarTitleText": "WeChat",
13 | "navigationBarTextStyle": "black"
14 | },
15 | "tabBar": {
16 | "backgroundColor": "#62c0e2",
17 | "color": "#333",
18 | "selectedColor": "#fff",
19 | "iconPath": "",
20 | "list": [
21 | {
22 | "text": "首页",
23 | "pagePath": "pages/bookList/bookList",
24 | "iconPath": "images/home_selected.png",
25 | "selectedIconPath": "images/home.png"
26 | },
27 | {
28 | "text": "我的",
29 | "pagePath": "pages/mine/mine",
30 | "iconPath": "images/user_selected.png",
31 | "selectedIconPath": "images/user.png"
32 | }
33 | ]
34 | }
35 | }
--------------------------------------------------------------------------------
/weixin/app.wxss:
--------------------------------------------------------------------------------
1 | /**app.wxss**/
2 |
3 | page {
4 | height: 100%;
5 | }
6 |
7 | .book-item {
8 | display: flex;
9 | justify-content: space-between;
10 | align-items: center;
11 | height: 160rpx;
12 | margin-bottom: 20rpx;
13 | padding-bottom: 10rpx;
14 | border-bottom: 2rpx solid #e7e7e7;
15 | }
16 |
17 | .book-item:last-child {
18 | border: none;
19 | }
20 |
21 | .book-item .book-img {
22 | position: relative;
23 | height: 100%;
24 | margin-right: 20rpx;
25 | }
26 |
27 | .book-item .book-img image {
28 | width: 150rpx;
29 | height: 100%;
30 | }
31 |
32 | .book-item .book-img .index {
33 | position: absolute;
34 | top: -20rpx;
35 | left: -10rpx;
36 | width: 30rpx;
37 | height: 30rpx;
38 | color: #62c0e2;
39 | text-align: center;
40 | font-weight: bolder;
41 | font-style: italic;
42 | }
43 | .book-item .book-img .index.first3 {
44 | color: red;
45 | }
46 |
47 | .book-item .book-info {
48 | min-width: 375rpx;
49 | height: 100%;
50 | text-align: left;
51 | }
52 |
53 | .book-info .title {
54 | font-weight: bold;
55 | margin-bottom: 15rpx;
56 | text-overflow: ellipsis;
57 | overflow: hidden;
58 | white-space: nowrap;
59 | }
60 |
61 | .book-item .return {
62 | width: 220rpx;
63 | }
64 |
65 | .book-info .label {
66 | color: #666;
67 | margin-right: 10rpx;
68 | }
69 |
70 | .book-info .author, .book-info .issued {
71 | margin-bottom: 15rpx;
72 | font-size: 28rpx;
73 | color: gray;
74 | }
75 |
--------------------------------------------------------------------------------
/weixin/images/home.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Pengjiaxun/cloud-library/f1596d93a624273b0decce6c87194e9016229789/weixin/images/home.png
--------------------------------------------------------------------------------
/weixin/images/home_selected.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Pengjiaxun/cloud-library/f1596d93a624273b0decce6c87194e9016229789/weixin/images/home_selected.png
--------------------------------------------------------------------------------
/weixin/images/login_bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Pengjiaxun/cloud-library/f1596d93a624273b0decce6c87194e9016229789/weixin/images/login_bg.png
--------------------------------------------------------------------------------
/weixin/images/search.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Pengjiaxun/cloud-library/f1596d93a624273b0decce6c87194e9016229789/weixin/images/search.png
--------------------------------------------------------------------------------
/weixin/images/search_selected.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Pengjiaxun/cloud-library/f1596d93a624273b0decce6c87194e9016229789/weixin/images/search_selected.png
--------------------------------------------------------------------------------
/weixin/images/user.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Pengjiaxun/cloud-library/f1596d93a624273b0decce6c87194e9016229789/weixin/images/user.png
--------------------------------------------------------------------------------
/weixin/images/user_selected.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Pengjiaxun/cloud-library/f1596d93a624273b0decce6c87194e9016229789/weixin/images/user_selected.png
--------------------------------------------------------------------------------
/weixin/pages/bookDetail/bookDetail.js:
--------------------------------------------------------------------------------
1 | //index.js
2 | //获取应用实例
3 | const app = getApp()
4 |
5 | Page({
6 | data: {
7 | title: '',
8 | bookDetail: {}
9 | },
10 | onLoad(option) {
11 | this.setData({
12 | title: option.title
13 | })
14 | this.getBookDetail(option.title)
15 | },
16 | getBookDetail(title) {
17 | if (title) {
18 | const _this = this
19 | wx.request({
20 | url: 'http://localhost:8888/book/list',
21 | data: {
22 | title,
23 | status: 0
24 | },
25 | success(res) {
26 | if (res.data.result) {
27 | _this.setData({
28 | bookDetail: res.data.data[0]
29 | })
30 | }
31 | }
32 | })
33 | }
34 | },
35 | borrow() {
36 | const { title, image } = this.data.bookDetail
37 | const _this = this
38 | wx.request({
39 | url: 'http://localhost:8888/record/add',
40 | method: 'POST',
41 | data: {
42 | title,
43 | image,
44 | user: app.globalData.user,
45 | status: 1
46 | },
47 | success(res) {
48 | if (res.data.result) {
49 | wx.showToast({
50 | title: '借阅成功',
51 | })
52 | _this.getBookDetail(_this.data.title)
53 | } else {
54 | wx.showToast({
55 | title: `借阅失败:${res.data.msg}`,
56 | })
57 | }
58 |
59 | }
60 | })
61 | }
62 | })
--------------------------------------------------------------------------------
/weixin/pages/bookDetail/bookDetail.json:
--------------------------------------------------------------------------------
1 | {
2 | "navigationBarTitleText": "书籍详情"
3 | }
--------------------------------------------------------------------------------
/weixin/pages/bookDetail/bookDetail.wxml:
--------------------------------------------------------------------------------
1 |
2 |
33 |
34 | 内容简介
35 |
36 | {{bookDetail.summary}}
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/weixin/pages/bookDetail/bookDetail.wxss:
--------------------------------------------------------------------------------
1 | .book-detail {
2 | padding: 20rpx 40rpx 140rpx 40rpx;
3 | }
4 |
5 | .book-detail .header {
6 | display: flex;
7 | min-height: 300rpx;
8 | padding-bottom: 20rpx;
9 | border-bottom: 1rpx solid #d7d7d7;
10 | }
11 |
12 | .header image {
13 | width: 200rpx;
14 | height: 100%;
15 | }
16 |
17 | .info {
18 | font-size: 30rpx;
19 | margin-left: 20rpx;
20 | }
21 |
22 | .info .title {
23 | font-weight: bold;
24 | font-size: 40rpx;
25 | margin-bottom: 20rpx;
26 | }
27 |
28 | .info .label {
29 | color: #666;
30 | margin-right: 10rpx;
31 | }
32 |
33 | .info .status .on {
34 | color: #62c0e2;
35 | }
36 |
37 | .info .status .out {
38 | color: red;
39 | }
40 |
41 | .desc .title {
42 | margin: 20rpx 0;
43 | }
44 |
45 | .desc .content {
46 | font-size: 28rpx;
47 | color: #111;
48 | text-indent: 20rpx;
49 | }
50 |
51 | .footer {
52 | position: fixed;
53 | bottom: 0;
54 | width: 100%;
55 | box-sizing: border-box;
56 | padding: 20rpx 40rpx;
57 | background-color: #fff;
58 | }
59 |
60 | .footer button {
61 | width: 100%;
62 | }
63 |
--------------------------------------------------------------------------------
/weixin/pages/bookList/bookList.js:
--------------------------------------------------------------------------------
1 | // pages/bookList/bookList.js
2 | const date = require('../../utils/util.js')
3 |
4 | Page({
5 | data: {
6 | activeIndex: 1,
7 | searchKey: '',
8 | bookList: [],
9 | orderList: []
10 | },
11 | onLoad() {
12 | // console.log('123')
13 | wx.setEnableDebug({
14 | enableDebug: true
15 | })
16 | this.getBookList()
17 | this.getOrderList()
18 | },
19 | onShow() {
20 | this.setData({
21 | searchKey: ''
22 | })
23 | this.getBookList()
24 | this.getOrderList()
25 | },
26 | switchTab(event) {
27 | this.setData({ activeIndex: +event.target.dataset.index })
28 | },
29 | bindKeyInput(e) {
30 | this.setData({
31 | searchKey: e.detail.value
32 | })
33 | },
34 | search() {
35 | if (!this.data.searchKey) {
36 | return
37 | }
38 | const _this = this
39 | wx.showLoading({
40 | title: 'loading',
41 | })
42 | wx.request({
43 | url: 'http://localhost:8888/book/list',
44 | data: {
45 | status: 0,
46 | title: _this.data.searchKey
47 | },
48 | success(res) {
49 | if (res.data.result) {
50 | const data = res.data.data
51 | data.forEach(item => {
52 | item.record_date = date.formatTime(item.record_date).substring(0, 10)
53 | })
54 | _this.setData({
55 | bookList: data
56 | })
57 | }
58 | },
59 | complete() {
60 | wx.hideLoading()
61 | }
62 | })
63 | console.log(this.data.searchKey)
64 | },
65 | resetList() {
66 | this.setData({
67 | searchKey: ''
68 | })
69 | this.getBookList()
70 | },
71 | toBookDetail(item) {
72 | const id = item.currentTarget.dataset.id
73 | const title = item.currentTarget.dataset.title
74 | wx.navigateTo({
75 | url: `/pages/bookDetail/bookDetail?id=${id}&title=${title}`
76 | })
77 | },
78 | getBookList() {
79 | wx.showLoading({
80 | title: 'loading',
81 | })
82 | const _this = this
83 | wx.request({
84 | url: 'http://localhost:8888/book/list',
85 | data: {
86 | status: 0
87 | },
88 | success(res) {
89 | if (res.data.result) {
90 | const data = res.data.data
91 | data.forEach(item => {
92 | item.record_date = date.formatTime(item.record_date).substring(0, 10)
93 | })
94 | _this.setData({
95 | bookList: data
96 | })
97 | }
98 | },
99 | complete() {
100 | wx.hideLoading()
101 | }
102 | })
103 | },
104 | getOrderList() {
105 | const _this = this
106 | wx.request({
107 | url: 'http://localhost:8888/book/list',
108 | data: {
109 | status: 0,
110 | order: true
111 | },
112 | success(res) {
113 | if (res.data.result) {
114 | _this.setData({
115 | orderList: res.data.data
116 | })
117 | }
118 | }
119 | })
120 | },
121 | addWish() {
122 | const _this = this
123 | const user = wx.getStorageSync('user')
124 | wx.request({
125 | url: 'http://localhost:8888/wish/add',
126 | method: 'POST',
127 | data: {
128 | title: _this.data.searchKey,
129 | user: user
130 | },
131 | success(res) {
132 | wx.showToast({
133 | title: res.data.msg,
134 | icon: 'success',
135 | duration: 2000
136 | })
137 | }
138 | })
139 | }
140 | })
--------------------------------------------------------------------------------
/weixin/pages/bookList/bookList.json:
--------------------------------------------------------------------------------
1 | {
2 | "navigationBarTitleText": "首页"
3 | }
--------------------------------------------------------------------------------
/weixin/pages/bookList/bookList.wxml:
--------------------------------------------------------------------------------
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 | {{item.title}}
34 |
35 |
36 | 作者: {{item.author}}
37 |
38 |
39 | 上架时间: {{item.record_date}}
40 |
41 |
42 | >
43 |
44 |
45 |
46 |
47 |
48 | 图书馆内没有找到该书!
49 | 加入心愿单
50 | ,管理员会尽快采购哦!
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 | {{index + 1}}
59 | {{index + 1}}
60 |
61 |
62 |
63 |
64 | {{item.title}}
65 |
66 |
67 | 作者:{{item.author}}
68 |
69 |
70 | 借阅次数: {{item.borrowCount}} 次
71 |
72 |
73 | >
74 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/weixin/pages/bookList/bookList.wxss:
--------------------------------------------------------------------------------
1 | /* pages/bookList/bookList.wxss */
2 |
3 | .tab {
4 | display: flex;
5 | padding: 0 40rpx;
6 | margin-bottom: 20rpx;
7 | }
8 |
9 | .tab-item {
10 | flex: 1;
11 | width: 20rpx;
12 | font-size: 28rpx;
13 | text-align: center;
14 | padding: 10rpx 0;
15 | }
16 |
17 | .active {
18 | font-weight: bold;
19 | border-bottom: 6rpx solid #62c0e2;
20 | }
21 |
22 | .main {
23 | height: 100%;
24 | padding: 0 40rpx;
25 | font-size: 30rpx;
26 | }
27 |
28 | .main .empty-container {
29 | padding: 100rpx;
30 | text-align: center;
31 | }
32 |
33 | .main .empty-container .add-wish {
34 | color: red;
35 | }
36 |
37 | .list .search {
38 | display: flex;
39 | margin-bottom: 20rpx;
40 | padding: 8rpx 20rpx;
41 | font-size: 28rpx;
42 | border-radius: 20rpx;
43 | border: 4rpx solid #62c0e2;
44 | }
45 |
46 | .list .search input {
47 | flex: 1;
48 | font-size: 26rpx;
49 | }
50 |
51 | .list .search icon {
52 | padding: 10rpx;
53 | }
54 |
55 | .book-item .book-info {
56 | width: 100%;
57 | }
58 |
59 | .book-item .more-btn {
60 | }
61 |
--------------------------------------------------------------------------------
/weixin/pages/index/index.js:
--------------------------------------------------------------------------------
1 | //index.js
2 | //获取应用实例
3 | const app = getApp()
4 | Page({
5 | data: {
6 | imgUrls: [
7 | 'http://img02.tooopen.com/images/20150928/tooopen_sy_143912755726.jpg',
8 | 'http://img06.tooopen.com/images/20160818/tooopen_sy_175866434296.jpg',
9 | 'http://img06.tooopen.com/images/20160818/tooopen_sy_175833047715.jpg'
10 | ],
11 | indicatorDots: true,
12 | autoplay: true,
13 | interval: 3000,
14 | duration: 1000
15 | }
16 | })
--------------------------------------------------------------------------------
/weixin/pages/index/index.json:
--------------------------------------------------------------------------------
1 | {
2 | "navigationBarTitleText": "首页"
3 | }
--------------------------------------------------------------------------------
/weixin/pages/index/index.wxml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/weixin/pages/index/index.wxss:
--------------------------------------------------------------------------------
1 | /**index.wxss**/
2 | .slide-image {
3 | width: 100%;
4 | }
--------------------------------------------------------------------------------
/weixin/pages/login/login.js:
--------------------------------------------------------------------------------
1 | // pages/login/login.js
2 | Page({
3 |
4 | /**
5 | * 页面的初始数据
6 | */
7 | data: {
8 | account: 'pjx',
9 | pwd: '123'
10 | },
11 | bindAccountInput: function (e) {
12 | this.setData({
13 | account: e.detail.value
14 | })
15 | },
16 | bindPwdInput: function (e) {
17 | this.setData({
18 | pwd: e.detail.value
19 | })
20 | },
21 | verify() {
22 | if (!this.data.account) {
23 | wx.showToast({
24 | title: '账号不能为空',
25 | icon: 'loading',
26 | duration: 1800
27 | })
28 | return false
29 | } else if (!this.data.pwd) {
30 | wx.showToast({
31 | title: '密码不能为空',
32 | icon: 'loading',
33 | duration: 1800
34 | })
35 | return false
36 | }
37 | return true
38 | },
39 | login() {
40 | console.log(this.data.account, this.data.pwd)
41 | const _this = this
42 | if (this.verify()) {
43 | wx.setStorage({
44 | key: 'isLogin',
45 | data: '1',
46 | })
47 | wx.setStorage({
48 | key: 'user',
49 | data: _this.data.account,
50 | })
51 | wx.switchTab({
52 | url: '../mine/mine',
53 | complete: function (e) {
54 | console.log(e)
55 | }
56 | })
57 | }
58 | },
59 | /**
60 | * 生命周期函数--监听页面加载
61 | */
62 | onLoad: function (options) {
63 | wx.getStorage({
64 | key: 'isLogin',
65 | success: function (res) {
66 | if (res && res.data) {
67 | console.log(res, 'res')
68 | if (res.data == '1') {
69 | wx.switchTab({
70 | // url: '../mine/mine',
71 | url: '../bookList/bookList',
72 | })
73 | }
74 | }
75 | }
76 | })
77 | },
78 |
79 | /**
80 | * 生命周期函数--监听页面初次渲染完成
81 | */
82 | onReady: function () {
83 |
84 | },
85 |
86 | /**
87 | * 生命周期函数--监听页面显示
88 | */
89 | onShow: function () {
90 |
91 | },
92 |
93 | /**
94 | * 生命周期函数--监听页面隐藏
95 | */
96 | onHide: function () {
97 |
98 | },
99 |
100 | /**
101 | * 生命周期函数--监听页面卸载
102 | */
103 | onUnload: function () {
104 |
105 | },
106 |
107 | /**
108 | * 页面相关事件处理函数--监听用户下拉动作
109 | */
110 | onPullDownRefresh: function () {
111 |
112 | },
113 |
114 | /**
115 | * 页面上拉触底事件的处理函数
116 | */
117 | onReachBottom: function () {
118 |
119 | },
120 |
121 | /**
122 | * 用户点击右上角分享
123 | */
124 | onShareAppMessage: function () {
125 |
126 | }
127 | })
--------------------------------------------------------------------------------
/weixin/pages/login/login.json:
--------------------------------------------------------------------------------
1 | {}
--------------------------------------------------------------------------------
/weixin/pages/login/login.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | 云空间·书架
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/weixin/pages/login/login.wxss:
--------------------------------------------------------------------------------
1 | /* pages/login/login.wxss */
2 | .container {
3 | min-height: 100%;
4 | display: flex;
5 | flex-direction: column;
6 | align-items: center;
7 | padding: 280rpx 0;
8 | box-sizing: border-box;
9 | }
10 | .bg-img {
11 | position: absolute;
12 | top: 0;
13 | left: 0;
14 | width: 100%;
15 | height: 100%;
16 | z-index: -1;
17 | opacity: 0.9;
18 | }
19 | .bg-img image {
20 | width: 100%;
21 | height: 100%;
22 | }
23 | .main {
24 | text-align: center;
25 | }
26 | .title {
27 | font-size: 40rpx;
28 | color: #fff;
29 | margin-bottom: 40rpx;
30 | font-weight: bolder;
31 | }
32 | .account-input, .pwd-input {
33 | margin-bottom: 30rpx;
34 | border: 6rpx solid #fff;
35 | border-radius: 10rpx;
36 | padding: 10rpx;
37 | color: #fff;
38 | font-size: 30rpx;
39 | }
40 | .login-btn button {
41 | width: 100%;
42 | }
--------------------------------------------------------------------------------
/weixin/pages/mine/mine.js:
--------------------------------------------------------------------------------
1 | // pages/mine/mine.js
2 | const date = require('../../utils/util.js')
3 | var app = getApp()
4 |
5 | Page({
6 | data: {
7 | user: '',
8 | userInfo: {},
9 | hasUserInfo: false,
10 | showHistory: false,
11 | showWishes: false,
12 | records: [],
13 | wishes: []
14 | },
15 | onLoad() {
16 | const user =
17 | this.setData({
18 | user: wx.getStorageSync('user')
19 | })
20 | },
21 | onShow() {
22 | this.getUserInfo()
23 | this.getRecords()
24 | this.getWishes()
25 | },
26 | getUserInfo() {
27 | if (app.globalData.userInfo) {
28 | this.setData({
29 | userInfo: app.globalData.userInfo,
30 | })
31 | } else {
32 | wx.getUserInfo({
33 | success: res => {
34 | app.globalData.userInfo = res.userInfo
35 | this.setData({
36 | userInfo: res.userInfo,
37 | hasUserInfo: true
38 | })
39 | }
40 | })
41 | }
42 | },
43 | getRecords() {
44 | const _this = this
45 | wx.request({
46 | url: 'http://localhost:8888/record/list',
47 | data: {
48 | user: _this.data.user
49 | },
50 | success(res) {
51 | if (res.data.result) {
52 | const data = res.data.data
53 | data.forEach(item => {
54 | item.date = date.formatTime(item.date).substring(0, 10)
55 | item.day = Math.ceil((Date.now() - new Date(item.date).getTime()) / 1000 / 60 / 60 / 24)
56 | })
57 | _this.setData({
58 | records: data
59 | })
60 | }
61 | }
62 | })
63 | },
64 | getWishes() {
65 | const _this = this
66 | wx.request({
67 | url: 'http://localhost:8888/wish/mine',
68 | data: {
69 | user: _this.data.user
70 | },
71 | success(res) {
72 | if (res.data.result) {
73 | _this.setData({
74 | wishes: res.data.data
75 | })
76 | }
77 | }
78 | })
79 | },
80 | returnBack(option) {
81 | const { title, user, image } = option.currentTarget.dataset.detail
82 | const _this = this
83 | wx.request({
84 | url: 'http://localhost:8888/record/add',
85 | method: 'POST',
86 | data: {
87 | title,
88 | image,
89 | user: app.globalData.user,
90 | status: 2
91 | },
92 | success(res) {
93 | if (res.data.result) {
94 | wx.showToast({
95 | title: '还书成功',
96 | })
97 | _this.getRecords()
98 | } else {
99 | wx.showToast({
100 | title: `还书失败:${res.data.msg}`,
101 | })
102 | }
103 | }
104 | })
105 | },
106 | toggleShowHistory() {
107 | this.setData({
108 | showHistory: !this.data.showHistory
109 | })
110 | },
111 | toggleShowWishes() {
112 | this.setData({
113 | showWishes: !this.data.showWishes
114 | })
115 | },
116 | cancelWish(option) {
117 | const { title, user } = option.currentTarget.dataset.detail
118 | const _this = this
119 | wx.request({
120 | url: 'http://localhost:8888/wish/del',
121 | method: 'POST',
122 | data: {
123 | title,
124 | user
125 | },
126 | success(res) {
127 | if (res.data.result) {
128 | wx.showToast({
129 | title: '移除成功',
130 | })
131 | _this.getWishes(_this.data.user)
132 | } else {
133 | wx.showToast({
134 | title: `移除失败:${res.data.msg}`,
135 | })
136 | }
137 | }
138 | })
139 | }
140 | })
141 |
--------------------------------------------------------------------------------
/weixin/pages/mine/mine.json:
--------------------------------------------------------------------------------
1 | {
2 | "navigationBarTitleText": "我的"
3 | }
--------------------------------------------------------------------------------
/weixin/pages/mine/mine.wxml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
14 | 借阅历史
15 |
16 | ^
17 | ^
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | {{item.title}}
28 |
29 |
30 | 借阅日期:
31 | {{item.date}}
32 |
33 |
34 | 状态:
35 | {{item.status === 1 ? '在借 ' + item.day + ' 天' : '已还'}}
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | 心愿单
46 |
47 | ^
48 | ^
49 |
50 |
51 |
52 |
53 |
54 | {{item.title}}
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
--------------------------------------------------------------------------------
/weixin/pages/mine/mine.wxss:
--------------------------------------------------------------------------------
1 | /* pages/mine/mine.wxss */
2 |
3 | .header {
4 | background-color: #62c0e2;
5 | text-align: center;
6 | padding: 20rpx 0;
7 | color: #fff;
8 | border-bottom: 30rpx solid #e7e7e7;
9 | }
10 |
11 | .header image {
12 | border-radius: 50%;
13 | width: 160rpx;
14 | height: 160rpx;
15 | border: 6rpx solid #fff;
16 | }
17 |
18 | .header .user-name {
19 | margin-bottom: 20rpx;
20 | }
21 |
22 | .header .user-info {
23 | font-size: 28rpx;
24 | color: #fff;
25 | }
26 |
27 | .main {
28 | font-size: 30rpx;
29 | }
30 |
31 | .main {
32 | padding: 15rpx 40rpx;
33 | }
34 |
35 | .main .section {
36 | font-weight: bolder;
37 | padding-bottom: 15rpx;
38 | border-bottom: 1rpx solid #e7e7e7;
39 | margin-bottom: 20rpx;
40 | }
41 |
42 | .main .section .show-btn, .main .section .hide-btn {
43 | float: right;
44 | display: inline-block;
45 | }
46 |
47 | .main .section .show-btn {
48 | transform: rotate(180deg);
49 | }
50 |
51 | .main .wishes .book-info {
52 | display: flex;
53 | align-items: center;
54 | }
55 |
56 | .main .wishes .book-item {
57 | height: auto;
58 | padding-left: 20rpx;
59 | }
60 |
--------------------------------------------------------------------------------
/weixin/project.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "项目配置文件。",
3 | "setting": {
4 | "urlCheck": false,
5 | "es6": true,
6 | "postcss": true,
7 | "minified": true,
8 | "newFeature": true
9 | },
10 | "compileType": "miniprogram",
11 | "libVersion": "1.7.2",
12 | "appid": "wxc57fc76d1232384e",
13 | "projectname": "weixin",
14 | "condition": {
15 | "search": {
16 | "current": -1,
17 | "list": []
18 | },
19 | "conversation": {
20 | "current": -1,
21 | "list": []
22 | },
23 | "miniprogram": {
24 | "current": -1,
25 | "list": []
26 | }
27 | }
28 | }
--------------------------------------------------------------------------------
/weixin/utils/util.js:
--------------------------------------------------------------------------------
1 | const formatTime = date => {
2 | const d = new Date(+date)
3 | const year = d.getFullYear()
4 | const month = d.getMonth() + 1
5 | const day = d.getDate()
6 | const hour = d.getHours()
7 | const minute = d.getMinutes()
8 | const second = d.getSeconds()
9 |
10 | return [year, month, day].map(formatNumber).join('-') + ' ' + [hour, minute, second].map(formatNumber).join(':')
11 | }
12 |
13 | const formatNumber = n => {
14 | n = n.toString()
15 | return n[1] ? n : '0' + n
16 | }
17 |
18 | module.exports.formatTime = formatTime
19 |
--------------------------------------------------------------------------------