├── .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 | 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 | 15 | 51 | 79 | -------------------------------------------------------------------------------- /src/components/slider.vue: -------------------------------------------------------------------------------- 1 | 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 | 132 | 276 | 279 | -------------------------------------------------------------------------------- /src/views/login.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 75 | 76 | 104 | -------------------------------------------------------------------------------- /src/views/records.vue: -------------------------------------------------------------------------------- 1 | 67 | 68 | 125 | 128 | -------------------------------------------------------------------------------- /src/views/users.vue: -------------------------------------------------------------------------------- 1 | 65 | 66 | 163 | 164 | 167 | -------------------------------------------------------------------------------- /src/views/wishes.vue: -------------------------------------------------------------------------------- 1 | 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 | 3 | 4 | 5 | 6 | 7 | 8 | {{bookDetail.title}} 9 | 10 | 11 | 作者: 12 | {{bookDetail.author}} 13 | 14 | 15 | 出版社: 16 | {{bookDetail.publisher}} 17 | 18 | 19 | 页数: 20 | {{bookDetail.pages}} 21 | 22 | 23 | 豆瓣评分: 24 | {{bookDetail.rating}} 25 | 26 | 27 | 当前状态: 28 | 在架 29 | 借出 30 | 31 | 32 | 33 | 34 | 内容简介 35 | 36 |

{{bookDetail.summary}}

37 |
38 |
39 |
40 | 41 | 42 | -------------------------------------------------------------------------------- /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 | 13 | 14 | 15 | 16 | 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 | 3 | 4 | 5 | {{userInfo.nickName}} 6 | 7 | 11 | 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 | --------------------------------------------------------------------------------