├── .eslintrc.js ├── .gitignore ├── readMe.md ├── segment ├── .babelrc ├── .editorconfig ├── .postcssrc.js ├── README.md ├── build │ ├── build.js │ ├── check-versions.js │ ├── dev-client.js │ ├── dev-server.js │ ├── utils.js │ ├── vue-loader.conf.js │ ├── webpack.base.conf.js │ ├── webpack.dev.conf.js │ └── webpack.prod.conf.js ├── config │ ├── dev.env.js │ ├── index.js │ └── prod.env.js ├── index.html ├── package.json ├── src │ ├── App.vue │ ├── api │ │ ├── api.js │ │ └── http.js │ ├── assets │ │ ├── css │ │ │ ├── common.css │ │ │ ├── iconfont │ │ │ │ ├── icon-msg │ │ │ │ │ ├── iconfont.eot │ │ │ │ │ ├── iconfont.svg │ │ │ │ │ ├── iconfont.ttf │ │ │ │ │ └── iconfont.woff │ │ │ │ ├── icon-search │ │ │ │ │ ├── iconfont.eot │ │ │ │ │ ├── iconfont.svg │ │ │ │ │ ├── iconfont.ttf │ │ │ │ │ └── iconfont.woff │ │ │ │ ├── iconfont.css │ │ │ │ └── iconfont2.css │ │ │ ├── mixin.less │ │ │ └── reset.css │ │ └── images │ │ │ └── user.png │ ├── main.js │ ├── pages │ │ ├── Layout.vue │ │ ├── header │ │ │ ├── HasLogin.vue │ │ │ ├── Header.vue │ │ │ └── Login.vue │ │ ├── main │ │ │ ├── Ask.vue │ │ │ ├── QuestionDetail.vue │ │ │ └── QuestionList.vue │ │ └── user │ │ │ ├── UserHeader.vue │ │ │ └── UserIndex.vue │ ├── router │ │ └── router.js │ └── vuex │ │ └── store.js └── static │ ├── .gitkeep │ ├── ask.png │ ├── body.png │ ├── green-dark.png │ ├── page.png │ ├── question_detail.png │ ├── segment_未登录.png │ ├── segment_登录.png │ ├── tag_selection.png │ ├── userInfo-header.png │ ├── userinfo.png │ └── 登录注册.png └── server ├── app.js ├── bin └── www ├── db └── config.js ├── models ├── QuestionModel.js └── UsersModel.js ├── package.json ├── routes ├── index.js ├── question.js └── users.js └── session └── CheckSession.js /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "browser": true, 4 | "commonjs": true, 5 | "es6": true, 6 | "node": true 7 | }, 8 | "extends": "eslint:recommended", 9 | "parserOptions": { 10 | "sourceType": "module" 11 | }, 12 | "rules": { 13 | "indent": [ 14 | "error", 15 | 4, 16 | { "SwitchCase": 1 } 17 | ], 18 | "linebreak-style": [ 19 | "error", 20 | "windows" 21 | ], 22 | "quotes": [ 23 | "error", 24 | "single" 25 | ], 26 | "semi": [ 27 | "error", 28 | "always" 29 | ], 30 | "no-console":0 31 | } 32 | }; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | segment/node_modules/ 3 | server/node_modules/ 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Editor directories and files 9 | .vscode/ 10 | .idea 11 | *.suo 12 | *.ntvs* 13 | *.njsproj 14 | *.sln 15 | -------------------------------------------------------------------------------- /readMe.md: -------------------------------------------------------------------------------- 1 | ## express+mysql+vue 前后端分离 仿segment论坛 2 | 3 | ### segment(前端) 4 | - vue + vue-router + vuex + axios + less 5 | ```bash 6 | cd segment 7 | npm i 8 | npm run dev 9 | 10 | ``` 11 | 12 | 13 | ### server(服务端) 14 | - express + mysql 15 | ```bash 16 | cd server 17 | npm i 18 | npm start 19 | 20 | ``` 21 | - mysql config 22 | ```js 23 | mysql: { 24 | 'host': 'localhost', 25 | 'user': 'root', 26 | 'password': '', 27 | 'database': 'segment2', 28 | 'port': '3306' 29 | } 30 | ``` 31 | - API error code 32 | - 200 success 33 | - 400 error 34 | - 401 连接错误 35 | - 402 登录过期 36 | 37 | 38 | 39 | ### mysql tables 40 | ```mysql 41 | 42 | // 用户表 43 | CREATE TABLE `user` ( 44 | `uid` int(11) NOT NULL AUTO_INCREMENT COMMENT '用户id', 45 | `mobile` char(15) DEFAULT NULL, 46 | `email` varchar(60) DEFAULT NULL, 47 | `pwd` varchar(60) NOT NULL, 48 | `username` varchar(120) NOT NULL, 49 | `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间', 50 | `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', 51 | PRIMARY KEY (`uid`), 52 | UNIQUE KEY `username_unique` (`username`), 53 | UNIQUE KEY `email_unique` (`email`), 54 | UNIQUE KEY `mobile_unique` (`mobile`) USING BTREE 55 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 56 | 57 | // 问题表 58 | create table questions( 59 | q_id smallint unsigned primary key auto_increment, 60 | q_title varchar(220) not null, 61 | q_tag varchar(50) not null, 62 | q_content longtext not null, 63 | votes int not null default 0, 64 | views int unsigned not null default 0, 65 | answer int unsigned not null default 0, 66 | last_res_id int unsigned comment '最近回复的用户id', 67 | create_time timestamp default current_timestamp, 68 | user_id int not null, 69 | last_res_time timestamp 70 | )engine=innodb default charset=utf8; 71 | 72 | ``` -------------------------------------------------------------------------------- /segment/.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-runtime"], 12 | "env": { 13 | "test": { 14 | "presets": ["env", "stage-2"], 15 | "plugins": ["istanbul"] 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /segment/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /segment/.postcssrc.js: -------------------------------------------------------------------------------- 1 | // https://github.com/michael-ciniawsky/postcss-load-config 2 | 3 | module.exports = { 4 | "plugins": { 5 | // to edit target browsers: use "browserslist" field in package.json 6 | "autoprefixer": {} 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /segment/README.md: -------------------------------------------------------------------------------- 1 | # segment 2 | 3 | > A Vue.js project 4 | 5 | ## Build Setup 6 | 7 | ``` bash 8 | # install dependencies 9 | npm install 10 | 11 | # serve with hot reload at localhost:8080 12 | npm run dev 13 | 14 | # build for production with minification 15 | npm run build 16 | 17 | # build for production and view the bundle analyzer report 18 | npm run build --report 19 | ``` 20 | 21 | For detailed explanation on how things work, checkout the [guide](http://vuejs-templates.github.io/webpack/) and [docs for vue-loader](http://vuejs.github.io/vue-loader). 22 | -------------------------------------------------------------------------------- /segment/build/build.js: -------------------------------------------------------------------------------- 1 | require('./check-versions')() 2 | 3 | process.env.NODE_ENV = 'production' 4 | 5 | var ora = require('ora') 6 | var rm = require('rimraf') 7 | var path = require('path') 8 | var chalk = require('chalk') 9 | var webpack = require('webpack') 10 | var config = require('../config') 11 | var webpackConfig = require('./webpack.prod.conf') 12 | 13 | var spinner = ora('building for production...') 14 | spinner.start() 15 | 16 | rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => { 17 | if (err) throw err 18 | webpack(webpackConfig, function (err, stats) { 19 | spinner.stop() 20 | if (err) throw err 21 | process.stdout.write(stats.toString({ 22 | colors: true, 23 | modules: false, 24 | children: false, 25 | chunks: false, 26 | chunkModules: false 27 | }) + '\n\n') 28 | 29 | if (stats.hasErrors()) { 30 | console.log(chalk.red(' Build failed with errors.\n')) 31 | process.exit(1) 32 | } 33 | 34 | console.log(chalk.cyan(' Build complete.\n')) 35 | console.log(chalk.yellow( 36 | ' Tip: built files are meant to be served over an HTTP server.\n' + 37 | ' Opening index.html over file:// won\'t work.\n' 38 | )) 39 | }) 40 | }) 41 | -------------------------------------------------------------------------------- /segment/build/check-versions.js: -------------------------------------------------------------------------------- 1 | var chalk = require('chalk') 2 | var semver = require('semver') 3 | var packageConfig = require('../package.json') 4 | var shell = require('shelljs') 5 | function exec (cmd) { 6 | return require('child_process').execSync(cmd).toString().trim() 7 | } 8 | 9 | var versionRequirements = [ 10 | { 11 | name: 'node', 12 | currentVersion: semver.clean(process.version), 13 | versionRequirement: packageConfig.engines.node 14 | } 15 | ] 16 | 17 | if (shell.which('npm')) { 18 | versionRequirements.push({ 19 | name: 'npm', 20 | currentVersion: exec('npm --version'), 21 | versionRequirement: packageConfig.engines.npm 22 | }) 23 | } 24 | 25 | module.exports = function () { 26 | var warnings = [] 27 | for (var i = 0; i < versionRequirements.length; i++) { 28 | var mod = versionRequirements[i] 29 | if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) { 30 | warnings.push(mod.name + ': ' + 31 | chalk.red(mod.currentVersion) + ' should be ' + 32 | chalk.green(mod.versionRequirement) 33 | ) 34 | } 35 | } 36 | 37 | if (warnings.length) { 38 | console.log('') 39 | console.log(chalk.yellow('To use this template, you must update following to modules:')) 40 | console.log() 41 | for (var i = 0; i < warnings.length; i++) { 42 | var warning = warnings[i] 43 | console.log(' ' + warning) 44 | } 45 | console.log() 46 | process.exit(1) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /segment/build/dev-client.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | require('eventsource-polyfill') 3 | var hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true') 4 | 5 | hotClient.subscribe(function (event) { 6 | if (event.action === 'reload') { 7 | window.location.reload() 8 | } 9 | }) 10 | -------------------------------------------------------------------------------- /segment/build/dev-server.js: -------------------------------------------------------------------------------- 1 | require('./check-versions')() 2 | 3 | var config = require('../config') 4 | if (!process.env.NODE_ENV) { 5 | process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV) 6 | } 7 | 8 | var opn = require('opn') 9 | var path = require('path') 10 | var express = require('express') 11 | var webpack = require('webpack') 12 | var proxyMiddleware = require('http-proxy-middleware') 13 | var webpackConfig = require('./webpack.dev.conf') 14 | 15 | // default port where dev server listens for incoming traffic 16 | var port = process.env.PORT || config.dev.port 17 | // automatically open browser, if not set will be false 18 | var autoOpenBrowser = !!config.dev.autoOpenBrowser 19 | // Define HTTP proxies to your custom API backend 20 | // https://github.com/chimurai/http-proxy-middleware 21 | var proxyTable = config.dev.proxyTable 22 | 23 | var app = express() 24 | var compiler = webpack(webpackConfig) 25 | 26 | var devMiddleware = require('webpack-dev-middleware')(compiler, { 27 | publicPath: webpackConfig.output.publicPath, 28 | quiet: true 29 | }) 30 | 31 | var hotMiddleware = require('webpack-hot-middleware')(compiler, { 32 | log: false, 33 | heartbeat: 2000 34 | }) 35 | // force page reload when html-webpack-plugin template changes 36 | compiler.plugin('compilation', function (compilation) { 37 | compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) { 38 | hotMiddleware.publish({ action: 'reload' }) 39 | cb() 40 | }) 41 | }) 42 | 43 | // proxy api requests 44 | Object.keys(proxyTable).forEach(function (context) { 45 | var options = proxyTable[context] 46 | if (typeof options === 'string') { 47 | options = { target: options } 48 | } 49 | app.use(proxyMiddleware(options.filter || context, options)) 50 | }) 51 | 52 | // handle fallback for HTML5 history API 53 | app.use(require('connect-history-api-fallback')()) 54 | 55 | // serve webpack bundle output 56 | app.use(devMiddleware) 57 | 58 | // enable hot-reload and state-preserving 59 | // compilation error display 60 | app.use(hotMiddleware) 61 | 62 | // serve pure static assets 63 | var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory) 64 | app.use(staticPath, express.static('./static')) 65 | 66 | var uri = 'http://localhost:' + port 67 | 68 | var _resolve 69 | var readyPromise = new Promise(resolve => { 70 | _resolve = resolve 71 | }) 72 | 73 | console.log('> Starting dev server...') 74 | devMiddleware.waitUntilValid(() => { 75 | console.log('> Listening at ' + uri + '\n') 76 | // when env is testing, don't need open it 77 | if (autoOpenBrowser && process.env.NODE_ENV !== 'testing') { 78 | opn(uri) 79 | } 80 | _resolve() 81 | }) 82 | 83 | var server = app.listen(port) 84 | 85 | module.exports = { 86 | ready: readyPromise, 87 | close: () => { 88 | server.close() 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /segment/build/utils.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var config = require('../config') 3 | var ExtractTextPlugin = require('extract-text-webpack-plugin') 4 | 5 | exports.assetsPath = function (_path) { 6 | var assetsSubDirectory = process.env.NODE_ENV === 'production' 7 | ? config.build.assetsSubDirectory 8 | : config.dev.assetsSubDirectory 9 | return path.posix.join(assetsSubDirectory, _path) 10 | } 11 | 12 | exports.cssLoaders = function (options) { 13 | options = options || {} 14 | 15 | var cssLoader = { 16 | loader: 'css-loader', 17 | options: { 18 | minimize: process.env.NODE_ENV === 'production', 19 | sourceMap: options.sourceMap 20 | } 21 | } 22 | 23 | // generate loader string to be used with extract text plugin 24 | function generateLoaders (loader, loaderOptions) { 25 | var loaders = [cssLoader] 26 | if (loader) { 27 | loaders.push({ 28 | loader: loader + '-loader', 29 | options: Object.assign({}, loaderOptions, { 30 | sourceMap: options.sourceMap 31 | }) 32 | }) 33 | } 34 | 35 | // Extract CSS when that option is specified 36 | // (which is the case during production build) 37 | if (options.extract) { 38 | return ExtractTextPlugin.extract({ 39 | use: loaders, 40 | fallback: 'vue-style-loader' 41 | }) 42 | } else { 43 | return ['vue-style-loader'].concat(loaders) 44 | } 45 | } 46 | 47 | // https://vue-loader.vuejs.org/en/configurations/extract-css.html 48 | return { 49 | css: generateLoaders(), 50 | postcss: generateLoaders(), 51 | less: generateLoaders('less'), 52 | sass: generateLoaders('sass', { indentedSyntax: true }), 53 | scss: generateLoaders('sass'), 54 | stylus: generateLoaders('stylus'), 55 | styl: generateLoaders('stylus') 56 | } 57 | } 58 | 59 | // Generate loaders for standalone style files (outside of .vue) 60 | exports.styleLoaders = function (options) { 61 | var output = [] 62 | var loaders = exports.cssLoaders(options) 63 | for (var extension in loaders) { 64 | var loader = loaders[extension] 65 | output.push({ 66 | test: new RegExp('\\.' + extension + '$'), 67 | use: loader 68 | }) 69 | } 70 | return output 71 | } 72 | -------------------------------------------------------------------------------- /segment/build/vue-loader.conf.js: -------------------------------------------------------------------------------- 1 | var utils = require('./utils') 2 | var config = require('../config') 3 | var isProduction = process.env.NODE_ENV === 'production' 4 | 5 | module.exports = { 6 | loaders: utils.cssLoaders({ 7 | sourceMap: isProduction 8 | ? config.build.productionSourceMap 9 | : config.dev.cssSourceMap, 10 | extract: isProduction 11 | }), 12 | transformToRequire: { 13 | video: 'src', 14 | source: 'src', 15 | img: 'src', 16 | image: 'xlink:href' 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /segment/build/webpack.base.conf.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var fs = require('fs') 3 | var utils = require('./utils') 4 | var config = require('../config') 5 | var vueLoaderConfig = require('./vue-loader.conf') 6 | 7 | function resolve (dir) { 8 | return path.join(__dirname, '..', dir) 9 | } 10 | 11 | module.exports = { 12 | entry: { 13 | app: './src/main.js' 14 | }, 15 | output: { 16 | path: config.build.assetsRoot, 17 | filename: '[name].js', 18 | publicPath: process.env.NODE_ENV === 'production' 19 | ? config.build.assetsPublicPath 20 | : config.dev.assetsPublicPath 21 | }, 22 | resolve: { 23 | extensions: ['.js', '.vue', '.json'], 24 | alias: { 25 | 'vue$': 'vue/dist/vue.esm.js', 26 | '@': resolve('src'), 27 | }, 28 | symlinks: false 29 | }, 30 | module: { 31 | rules: [ 32 | { 33 | test: /\.vue$/, 34 | loader: 'vue-loader', 35 | options: vueLoaderConfig 36 | }, 37 | { 38 | test: /\.js$/, 39 | loader: 'babel-loader', 40 | include: [resolve('src'), resolve('test')] 41 | }, 42 | { 43 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 44 | loader: 'url-loader', 45 | options: { 46 | limit: 10000, 47 | name: utils.assetsPath('img/[name].[hash:7].[ext]') 48 | } 49 | }, 50 | { 51 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, 52 | loader: 'url-loader', 53 | options: { 54 | limit: 10000, 55 | name: utils.assetsPath('media/[name].[hash:7].[ext]') 56 | } 57 | }, 58 | { 59 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 60 | loader: 'url-loader', 61 | options: { 62 | limit: 10000, 63 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]') 64 | } 65 | } 66 | ] 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /segment/build/webpack.dev.conf.js: -------------------------------------------------------------------------------- 1 | var utils = require('./utils') 2 | var webpack = require('webpack') 3 | var config = require('../config') 4 | var merge = require('webpack-merge') 5 | var baseWebpackConfig = require('./webpack.base.conf') 6 | var HtmlWebpackPlugin = require('html-webpack-plugin') 7 | var FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin') 8 | 9 | // add hot-reload related code to entry chunks 10 | Object.keys(baseWebpackConfig.entry).forEach(function (name) { 11 | baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name]) 12 | }) 13 | 14 | module.exports = merge(baseWebpackConfig, { 15 | module: { 16 | rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap }) 17 | }, 18 | // cheap-module-eval-source-map is faster for development 19 | devtool: '#cheap-module-eval-source-map', 20 | plugins: [ 21 | new webpack.DefinePlugin({ 22 | 'process.env': config.dev.env 23 | }), 24 | // https://github.com/glenjamin/webpack-hot-middleware#installation--usage 25 | new webpack.HotModuleReplacementPlugin(), 26 | new webpack.NoEmitOnErrorsPlugin(), 27 | // https://github.com/ampedandwired/html-webpack-plugin 28 | new HtmlWebpackPlugin({ 29 | filename: 'index.html', 30 | template: 'index.html', 31 | inject: true 32 | }), 33 | new FriendlyErrorsPlugin() 34 | ] 35 | }) 36 | -------------------------------------------------------------------------------- /segment/build/webpack.prod.conf.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var utils = require('./utils') 3 | var webpack = require('webpack') 4 | var config = require('../config') 5 | var merge = require('webpack-merge') 6 | var baseWebpackConfig = require('./webpack.base.conf') 7 | var CopyWebpackPlugin = require('copy-webpack-plugin') 8 | var HtmlWebpackPlugin = require('html-webpack-plugin') 9 | var ExtractTextPlugin = require('extract-text-webpack-plugin') 10 | var OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin') 11 | 12 | var env = config.build.env 13 | 14 | var webpackConfig = merge(baseWebpackConfig, { 15 | module: { 16 | rules: utils.styleLoaders({ 17 | sourceMap: config.build.productionSourceMap, 18 | extract: true 19 | }) 20 | }, 21 | devtool: config.build.productionSourceMap ? '#source-map' : false, 22 | output: { 23 | path: config.build.assetsRoot, 24 | filename: utils.assetsPath('js/[name].[chunkhash].js'), 25 | chunkFilename: utils.assetsPath('js/[id].[chunkhash].js') 26 | }, 27 | plugins: [ 28 | // http://vuejs.github.io/vue-loader/en/workflow/production.html 29 | new webpack.DefinePlugin({ 30 | 'process.env': env 31 | }), 32 | new webpack.optimize.UglifyJsPlugin({ 33 | compress: { 34 | warnings: false 35 | }, 36 | sourceMap: true 37 | }), 38 | // extract css into its own file 39 | new ExtractTextPlugin({ 40 | filename: utils.assetsPath('css/[name].[contenthash].css') 41 | }), 42 | // Compress extracted CSS. We are using this plugin so that possible 43 | // duplicated CSS from different components can be deduped. 44 | new OptimizeCSSPlugin({ 45 | cssProcessorOptions: { 46 | safe: true 47 | } 48 | }), 49 | // generate dist index.html with correct asset hash for caching. 50 | // you can customize output by editing /index.html 51 | // see https://github.com/ampedandwired/html-webpack-plugin 52 | new HtmlWebpackPlugin({ 53 | filename: config.build.index, 54 | template: 'index.html', 55 | inject: true, 56 | minify: { 57 | removeComments: true, 58 | collapseWhitespace: true, 59 | removeAttributeQuotes: true 60 | // more options: 61 | // https://github.com/kangax/html-minifier#options-quick-reference 62 | }, 63 | // necessary to consistently work with multiple chunks via CommonsChunkPlugin 64 | chunksSortMode: 'dependency' 65 | }), 66 | // keep module.id stable when vender modules does not change 67 | new webpack.HashedModuleIdsPlugin(), 68 | // split vendor js into its own file 69 | new webpack.optimize.CommonsChunkPlugin({ 70 | name: 'vendor', 71 | minChunks: function (module, count) { 72 | // any required modules inside node_modules are extracted to vendor 73 | return ( 74 | module.resource && 75 | /\.js$/.test(module.resource) && 76 | module.resource.indexOf( 77 | path.join(__dirname, '../node_modules') 78 | ) === 0 79 | ) 80 | } 81 | }), 82 | // extract webpack runtime and module manifest to its own file in order to 83 | // prevent vendor hash from being updated whenever app bundle is updated 84 | new webpack.optimize.CommonsChunkPlugin({ 85 | name: 'manifest', 86 | chunks: ['vendor'] 87 | }), 88 | // copy custom static assets 89 | new CopyWebpackPlugin([ 90 | { 91 | from: path.resolve(__dirname, '../static'), 92 | to: config.build.assetsSubDirectory, 93 | ignore: ['.*'] 94 | } 95 | ]) 96 | ] 97 | }) 98 | 99 | if (config.build.productionGzip) { 100 | var CompressionWebpackPlugin = require('compression-webpack-plugin') 101 | 102 | webpackConfig.plugins.push( 103 | new CompressionWebpackPlugin({ 104 | asset: '[path].gz[query]', 105 | algorithm: 'gzip', 106 | test: new RegExp( 107 | '\\.(' + 108 | config.build.productionGzipExtensions.join('|') + 109 | ')$' 110 | ), 111 | threshold: 10240, 112 | minRatio: 0.8 113 | }) 114 | ) 115 | } 116 | 117 | if (config.build.bundleAnalyzerReport) { 118 | var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin 119 | webpackConfig.plugins.push(new BundleAnalyzerPlugin()) 120 | } 121 | 122 | module.exports = webpackConfig 123 | -------------------------------------------------------------------------------- /segment/config/dev.env.js: -------------------------------------------------------------------------------- 1 | var merge = require('webpack-merge') 2 | var prodEnv = require('./prod.env') 3 | 4 | module.exports = merge(prodEnv, { 5 | NODE_ENV: '"development"' 6 | }) 7 | -------------------------------------------------------------------------------- /segment/config/index.js: -------------------------------------------------------------------------------- 1 | // see http://vuejs-templates.github.io/webpack for documentation. 2 | var path = require('path') 3 | 4 | module.exports = { 5 | build: { 6 | env: require('./prod.env'), 7 | index: path.resolve(__dirname, '../dist/index.html'), 8 | assetsRoot: path.resolve(__dirname, '../dist'), 9 | assetsSubDirectory: 'static', 10 | assetsPublicPath: '/', 11 | productionSourceMap: true, 12 | // Gzip off by default as many popular static hosts such as 13 | // Surge or Netlify already gzip all static assets for you. 14 | // Before setting to `true`, make sure to: 15 | // npm install --save-dev compression-webpack-plugin 16 | productionGzip: false, 17 | productionGzipExtensions: ['js', 'css'], 18 | // Run the build command with an extra argument to 19 | // View the bundle analyzer report after build finishes: 20 | // `npm run build --report` 21 | // Set to `true` or `false` to always turn it on or off 22 | bundleAnalyzerReport: process.env.npm_config_report 23 | }, 24 | dev: { 25 | env: require('./dev.env'), 26 | port: 9000, 27 | autoOpenBrowser: true, 28 | assetsSubDirectory: 'static', 29 | assetsPublicPath: '/', 30 | proxyTable: { 31 | '/node':{ 32 | target:'http://localhost:3000', 33 | changeOrigin: true, 34 | pathRewrite: { 35 | '^/node' : '/', // rewrite path (重定向路径) 36 | } 37 | }, 38 | }, 39 | // CSS Sourcemaps off by default because relative paths are "buggy" 40 | // with this option, according to the CSS-Loader README 41 | // (https://github.com/webpack/css-loader#sourcemaps) 42 | // In our experience, they generally work as expected, 43 | // just be aware of this issue when enabling this option. 44 | cssSourceMap: false 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /segment/config/prod.env.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | NODE_ENV: '"production"' 3 | } 4 | -------------------------------------------------------------------------------- /segment/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | segment 6 | 7 | 8 |
9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /segment/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "segment", 3 | "version": "1.0.0", 4 | "description": "A Vue.js project", 5 | "author": "weenta", 6 | "private": true, 7 | "scripts": { 8 | "dev": "node build/dev-server.js", 9 | "start": "node build/dev-server.js", 10 | "build": "node build/build.js" 11 | }, 12 | "dependencies": { 13 | "axios": "^0.16.2", 14 | "vue": "^2.4.2", 15 | "vue-quill-editor": "^2.3.2", 16 | "vue-router": "^2.7.0", 17 | "vuex": "^2.4.0" 18 | }, 19 | "devDependencies": { 20 | "autoprefixer": "^7.1.2", 21 | "babel-core": "^6.22.1", 22 | "babel-loader": "^7.1.1", 23 | "babel-plugin-transform-runtime": "^6.22.0", 24 | "babel-preset-env": "^1.3.2", 25 | "babel-preset-stage-2": "^6.22.0", 26 | "babel-register": "^6.22.0", 27 | "chalk": "^2.0.1", 28 | "connect-history-api-fallback": "^1.3.0", 29 | "copy-webpack-plugin": "^4.0.1", 30 | "css-loader": "^0.28.0", 31 | "cssnano": "^3.10.0", 32 | "eventsource-polyfill": "^0.9.6", 33 | "express": "^4.14.1", 34 | "extract-text-webpack-plugin": "^2.0.0", 35 | "file-loader": "^0.11.1", 36 | "friendly-errors-webpack-plugin": "^1.1.3", 37 | "html-webpack-plugin": "^2.28.0", 38 | "http-proxy-middleware": "^0.17.3", 39 | "less": "^2.7.2", 40 | "less-loader": "^4.0.5", 41 | "opn": "^5.1.0", 42 | "optimize-css-assets-webpack-plugin": "^2.0.0", 43 | "ora": "^1.2.0", 44 | "rimraf": "^2.6.0", 45 | "semver": "^5.3.0", 46 | "shelljs": "^0.7.6", 47 | "url-loader": "^0.5.8", 48 | "vue-loader": "^13.0.4", 49 | "vue-style-loader": "^3.0.1", 50 | "vue-template-compiler": "^2.4.2", 51 | "webpack": "^2.6.1", 52 | "webpack-bundle-analyzer": "^2.2.1", 53 | "webpack-dev-middleware": "^1.10.0", 54 | "webpack-hot-middleware": "^2.18.0", 55 | "webpack-merge": "^4.1.0" 56 | }, 57 | "engines": { 58 | "node": ">= 4.0.0", 59 | "npm": ">= 3.0.0" 60 | }, 61 | "browserslist": [ 62 | "> 1%", 63 | "last 2 versions", 64 | "not ie <= 8" 65 | ] 66 | } 67 | -------------------------------------------------------------------------------- /segment/src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 21 | -------------------------------------------------------------------------------- /segment/src/api/api.js: -------------------------------------------------------------------------------- 1 | import axios from './http'; 2 | import qs from 'qs'; 3 | 4 | let base = '/node'; 5 | 6 | // 注册 7 | export const REGISTER = params => { return axios.post(`${base}/users/register`, qs.stringify(params)); }; 8 | 9 | // 登录 10 | export const LOGIN = params =>{ return axios.post(`${base}/users/login`,qs.stringify(params));}; 11 | 12 | // 退出登录 13 | export const LOGOUT = params =>{ return axios.get(`${base}/users/logout`, {params:params});}; 14 | 15 | // 提交提问 16 | export const SUBMIT_QUESTION = params => { return axios.post(`${base}/question/ask_question`,qs.stringify(params));}; 17 | 18 | // 问题列表 19 | export const QUESTION_LIST = params => {return axios.get(`${base}/question/question_list`, {params:params});}; 20 | 21 | // 获取问题详情 22 | export const QUESTION_DETAIL = params => { return axios.get(`${base}/question/question_detail`,{params:params});}; 23 | 24 | // 获取用户信息 25 | export const GET_USER_INFO = params => { return axios.get(`${base}/users/userinfo`,{params:params});}; 26 | 27 | 28 | 29 | 30 | 31 | //=========================================================================== 32 | 33 | // 获取用户列表 34 | export const GET_USER_LIST = params=>{ return axios.get(`${base}/users/getlist`,{params:params});}; 35 | -------------------------------------------------------------------------------- /segment/src/api/http.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import store from '../vuex/store'; 3 | 4 | 5 | // http response 拦截器 6 | axios.interceptors.response.use( 7 | (response) => { 8 | if(response.data.code == 402){ 9 | let payload = { 10 | hasLogin: true, 11 | isOverdue: true, 12 | showLogin: true 13 | }; 14 | store.commit('reLogin',payload); 15 | return; 16 | } 17 | return response; 18 | } 19 | ); 20 | export default axios; -------------------------------------------------------------------------------- /segment/src/assets/css/common.css: -------------------------------------------------------------------------------- 1 | /* 清除浮动 */ 2 | .clearfix:before, 3 | .clearfix:after { 4 | content: " "; 5 | display: table; 6 | } 7 | .clearfix:after{ 8 | clear: both; 9 | } 10 | 11 | .pointer { 12 | cursor: pointer; 13 | } 14 | 15 | .mask { 16 | position: fixed; 17 | left: 0; 18 | top:0; 19 | z-index: 9; 20 | width: 100%; 21 | height: 100%; 22 | background-color: rgba(0,0,0,0.5); 23 | } 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /segment/src/assets/css/iconfont/icon-msg/iconfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhy0313/nodejs_segment2/589e6c546d30680e2ee6730a876852286bb62030/segment/src/assets/css/iconfont/icon-msg/iconfont.eot -------------------------------------------------------------------------------- /segment/src/assets/css/iconfont/icon-msg/iconfont.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | Created by iconfont 9 | 10 | 11 | 12 | 13 | 21 | 22 | 23 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /segment/src/assets/css/iconfont/icon-msg/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhy0313/nodejs_segment2/589e6c546d30680e2ee6730a876852286bb62030/segment/src/assets/css/iconfont/icon-msg/iconfont.ttf -------------------------------------------------------------------------------- /segment/src/assets/css/iconfont/icon-msg/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhy0313/nodejs_segment2/589e6c546d30680e2ee6730a876852286bb62030/segment/src/assets/css/iconfont/icon-msg/iconfont.woff -------------------------------------------------------------------------------- /segment/src/assets/css/iconfont/icon-search/iconfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhy0313/nodejs_segment2/589e6c546d30680e2ee6730a876852286bb62030/segment/src/assets/css/iconfont/icon-search/iconfont.eot -------------------------------------------------------------------------------- /segment/src/assets/css/iconfont/icon-search/iconfont.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | Created by iconfont 9 | 10 | 11 | 12 | 13 | 21 | 22 | 23 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /segment/src/assets/css/iconfont/icon-search/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhy0313/nodejs_segment2/589e6c546d30680e2ee6730a876852286bb62030/segment/src/assets/css/iconfont/icon-search/iconfont.ttf -------------------------------------------------------------------------------- /segment/src/assets/css/iconfont/icon-search/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhy0313/nodejs_segment2/589e6c546d30680e2ee6730a876852286bb62030/segment/src/assets/css/iconfont/icon-search/iconfont.woff -------------------------------------------------------------------------------- /segment/src/assets/css/iconfont/iconfont.css: -------------------------------------------------------------------------------- 1 | 2 | @font-face {font-family: "iconfont"; 3 | src: url('./icon-search/iconfont.eot?t=1504430104908'); /* IE9*/ 4 | src: url('./icon-search/iconfont.eot?t=1504430104908#iefix') format('embedded-opentype'), /* IE6-IE8 */ 5 | url('data:application/x-font-woff;charset=utf-8;base64,d09GRgABAAAAAAUoAAsAAAAAB4AAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABHU1VCAAABCAAAADMAAABCsP6z7U9TLzIAAAE8AAAARAAAAFZW7kf9Y21hcAAAAYAAAABeAAABhpmEBr5nbHlmAAAB4AAAAVsAAAFsocwQnmhlYWQAAAM8AAAALwAAADYOwxYtaGhlYQAAA2wAAAAcAAAAJAfeA4RobXR4AAADiAAAAAwAAAAMC+kAAGxvY2EAAAOUAAAACAAAAAgAdgC2bWF4cAAAA5wAAAAfAAAAIAESAF1uYW1lAAADvAAAAUUAAAJtPlT+fXBvc3QAAAUEAAAAIwAAADTX4Nt+eJxjYGRgYOBikGPQYWB0cfMJYeBgYGGAAJAMY05meiJQDMoDyrGAaQ4gZoOIAgCKIwNPAHicY2Bk/sE4gYGVgYOpk+kMAwNDP4RmfM1gxMjBwMDEwMrMgBUEpLmmMDgwVDwTZm7438AQw9zA0AAUZgTJAQAkxQx/eJzFkMENgDAMAy9t6QPx6xI8GIgXc3TirlFMKA8mqCXHimMpUYAFiOIhJrAL48Ep19yPrO4nz2SpEait9K7+U0U0y65BJTMNNm/1H5vXfXT6CnVQJ7byknADfgcL2wAAeJw1jz1Lw1AYRu977036YZvafCc1bdqYXqW1xZi2FDFFEERxEJwcnbq0rl0cuggODmYXAiKIW7sXxN/Q/gHR3+BmNSI+PNPhLAdxCH2/kRnRkYQ20DY6QKcIAV+DioAtKDO/gWuglDlFkwXCHFZOOJUG2QOtwsuq1/arGp/gcyBAEXbKXps1MIOWH+Bd8FQLwCiYZ6K7JpI7SOuseL08xg+glJy1XLC1PKr3ZM+WkqOMKBqieJvkOS6JMc0JMNTUFJdK88tHLmcqs9ImLkHGYObJedYuiBc3/qXlaimA8Rikgi089fJmPv6VqUqikVjNJnUz66zLMPpY0aWMVX1H8XDc+kJeyT4ykIuQy6osgFYDmACJImheu6OW4DfKjVkAnZgJQJ4nHLVsuoiiOaXzqNuvEzrVe82vz0FISDgYhhiHhxNK6v3unxItqG1ROtWavXscDv+1H0HYQ+8AeJxjYGRgYADiZCsJoXh+m68M3CwMIHD14ksJBP1/PwsDsyOQy8HABBIFAAlECdYAeJxjYGRgYG7438AQw8IAAkCSkQEVMAMARwkCbAQAAAAD6QAABAAAAAAAAAAAdgC2eJxjYGRgYGBmCGRgZQABJiDmAkIGhv9gPgMAEPcBcAB4nGWPTU7DMBCFX/oHpBKqqGCH5AViASj9EatuWFRq911036ZOmyqJI8et1ANwHo7ACTgC3IA78EgnmzaWx9+8eWNPANzgBx6O3y33kT1cMjtyDRe4F65TfxBukF+Em2jjVbhF/U3YxzOmwm10YXmD17hi9oR3YQ8dfAjXcI1P4Tr1L+EG+Vu4iTv8CrfQ8erCPuZeV7iNRy/2x1YvnF6p5UHFockikzm/gple75KFrdLqnGtbxCZTg6BfSVOdaVvdU+zXQ+ciFVmTqgmrOkmMyq3Z6tAFG+fyUa8XiR6EJuVYY/62xgKOcQWFJQ6MMUIYZIjK6Og7VWb0r7FDwl57Vj3N53RbFNT/c4UBAvTPXFO6stJ5Ok+BPV8bUnV0K27LnpQ0kV7NSRKyQl7WtlRC6gE2ZVeOEXpc0Yk/KGdI/wAJWm7IAAAAeJxjYGKAAC4G7ICZkYmRmZGFgbGCrTg1sSg5g4EBABXEAxEA') format('woff'), 6 | url('./icon-search/iconfont.ttf?t=1504430104908') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/ 7 | url('./icon-search/iconfont.svg?t=1504430104908#iconfont') format('svg'); /* iOS 4.1- */ 8 | } 9 | 10 | .iconfont { 11 | font-family:"iconfont" !important; 12 | font-size:16px; 13 | font-style:normal; 14 | -webkit-font-smoothing: antialiased; 15 | -moz-osx-font-smoothing: grayscale; 16 | } 17 | 18 | .icon-search:before { content: "\e613"; } 19 | 20 | 21 | -------------------------------------------------------------------------------- /segment/src/assets/css/iconfont/iconfont2.css: -------------------------------------------------------------------------------- 1 | 2 | @font-face {font-family: "iconfont"; 3 | src: url('./icon-msg/iconfont.eot?t=1504912473236'); /* IE9*/ 4 | src: url('./icon-msg/iconfont.eot?t=1504912473236#iefix') format('embedded-opentype'), /* IE6-IE8 */ 5 | url('data:application/x-font-woff;charset=utf-8;base64,d09GRgABAAAAAAXQAAsAAAAACFgAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABHU1VCAAABCAAAADMAAABCsP6z7U9TLzIAAAE8AAAARAAAAFZW7kh2Y21hcAAAAYAAAABnAAABnM+fbEtnbHlmAAAB6AAAAegAAAIc7+HyaGhlYWQAAAPQAAAALwAAADYO0c6oaGhlYQAABAAAAAAcAAAAJAfeA4VobXR4AAAEHAAAABAAAAAQD+kAAGxvY2EAAAQsAAAACgAAAAoBhADYbWF4cAAABDgAAAAfAAAAIAETAF1uYW1lAAAEWAAAAUUAAAJtPlT+fXBvc3QAAAWgAAAALgAAAD+/UKmYeJxjYGRgYOBikGPQYWB0cfMJYeBgYGGAAJAMY05meiJQDMoDyrGAaQ4gZoOIAgCKIwNPAHicY2Bk/sU4gYGVgYOpk+kMAwNDP4RmfM1gxMjBwMDEwMrMgBUEpLmmMDgwVDzrYm7438AQw9zA0AAUZgTJAQAuQAz4eJzFkMENwCAIRT9iG9N0lB6brmPSU0dwBgdlDQvoxQn85gn8EDEA2ACwcikRoA8E06suuc843I94tE56gsYiWWprU2Yi70ieBXuZdiwTrRs96/T7HpXtuwz0i5I7tlepHfAPeUsTFwB4nB1RPW/TUBR999nvOXlJbJ4/Yyd2YhvbhYIFL04iWpFk6JIKECISggoGho4FiQ5dGLyAGDp0zogQTEwwRRXiryD4B8xJsHp17lnOGc65FxGEtr+lS6mNDLSD7qID9BghoLsQqdiHMCtyvAtWSCzHVKUszkIljnLpPjgRNW0xKlKHKlQDFQIYhGKU5TiDYTHB+yBsH8DteAs96erSBbB2FrzfHOJPYPXirja5vZnfmpqib9TOmrru6vp5jRJSw1jWVDhx7DqpM7r5TDTPuuzdwD1oupn34Hmr39FffSxe+4lTByhLMDp99euUe7zCO882dFe51qq1vVZ83YSzv4220fTTP6gaqer6T3oitZCL7qEpQkkY5VBMYTiB8aQK7ATgWI5t0iwJsxyyYbU5jIdFDtS644NJFRUUy6ZK5RSjsTRYL1mDkT04XlBxIOjiGPYIo+ufGKVAAJ7OWUvmAW9oB0cYMPkA4pGAkxrj7PT7s4sg2LkZBecvfpwSbsD+QyLLvw7fcsIZZRo0VSANE6w385Usy98yIbKrDhWVuEQa0lG3+lUYUYubtlNdf8yLVCGJxIkIYDROuDRbl34c+7h0U4hfbtEWzWBAWJ0RSHEZ+5srwYfKNVsuNyUcrWiVc7X5gtB/fv9V8HicY2BkYGAA4sY9JfPj+W2+MnCzMIDA1ZuukQj6/0MWBmYJIJeDgQkkCgA7ugqiAHicY2BkYGBu+N/AEMPCAAJAkpEBFbAAAEcKAm0EAAAAA+kAAAQAAAAEAAAAAAAAAAB2ANgBDgAAeJxjYGRgYGBhCGRgZQABJiDmAkIGhv9gPgMAERIBcQB4nGWPTU7DMBCFX/oHpBKqqGCH5AViASj9EatuWFRq911036ZOmyqJI8et1ANwHo7ACTgC3IA78EgnmzaWx9+8eWNPANzgBx6O3y33kT1cMjtyDRe4F65TfxBukF+Em2jjVbhF/U3YxzOmwm10YXmD17hi9oR3YQ8dfAjXcI1P4Tr1L+EG+Vu4iTv8CrfQ8erCPuZeV7iNRy/2x1YvnF6p5UHFockikzm/gple75KFrdLqnGtbxCZTg6BfSVOdaVvdU+zXQ+ciFVmTqgmrOkmMyq3Z6tAFG+fyUa8XiR6EJuVYY/62xgKOcQWFJQ6MMUIYZIjK6Og7VWb0r7FDwl57Vj3N53RbFNT/c4UBAvTPXFO6stJ5Ok+BPV8bUnV0K27LnpQ0kV7NSRKyQl7WtlRC6gE2ZVeOEXpc0Yk/KGdI/wAJWm7IAAAAeJxjYGKAAC4G7ICFkYmRmZGFkZWBsYKtKDU3My+FIy01NSUpMTmbgQEAR38GTgAA') format('woff'), 6 | url('./icon-msg/iconfont.ttf?t=1504912473236') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/ 7 | url('./icon-msg/iconfont.svg?t=1504912473236#iconfont') format('svg'); /* iOS 4.1- */ 8 | } 9 | 10 | .iconfont { 11 | font-family:"iconfont" !important; 12 | font-size:16px; 13 | font-style:normal; 14 | -webkit-font-smoothing: antialiased; 15 | -moz-osx-font-smoothing: grayscale; 16 | } 17 | 18 | .icon-remind:before { content: "\e65f"; } 19 | 20 | .icon-feedback:before { content: "\e68a"; } 21 | 22 | -------------------------------------------------------------------------------- /segment/src/assets/css/mixin.less: -------------------------------------------------------------------------------- 1 | /* 颜色变量 */ 2 | // 绿色 3 | @green:#009a61; 4 | 5 | // 文字颜色-浅灰 6 | @gray-l: #757575; 7 | -------------------------------------------------------------------------------- /segment/src/assets/css/reset.css: -------------------------------------------------------------------------------- 1 | /* http://meyerweb.com/eric/tools/css/reset/ 2 | v2.0 | 20110126 3 | License: none (public domain) 4 | */ 5 | 6 | html, body, div, span, applet, object, iframe, 7 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 8 | a, abbr, acronym, address, big, cite, code, 9 | del, dfn, em, img, ins, kbd, q, s, samp, 10 | small, strike, strong, sub, sup, tt, var, 11 | b, u, i, center, 12 | dl, dt, dd, ol, ul, li, 13 | fieldset, form, label, legend, 14 | table, caption, tbody, tfoot, thead, tr, th, td, 15 | article, aside, canvas, details, embed, 16 | figure, figcaption, footer, header, hgroup, 17 | menu, nav, output, ruby, section, summary, 18 | time, mark, audio, video { 19 | margin: 0; 20 | padding: 0; 21 | border: 0; 22 | font-size: 100%; 23 | font: inherit; 24 | vertical-align: baseline; 25 | } 26 | /* HTML5 display-role reset for older browsers */ 27 | article, aside, details, figcaption, figure, 28 | footer, header, hgroup, menu, nav, section { 29 | display: block; 30 | } 31 | body { 32 | line-height: 1; 33 | } 34 | ol, ul { 35 | list-style: none; 36 | } 37 | blockquote, q { 38 | quotes: none; 39 | } 40 | blockquote:before, blockquote:after, 41 | q:before, q:after { 42 | content: ''; 43 | content: none; 44 | } 45 | table { 46 | border-collapse: collapse; 47 | border-spacing: 0; 48 | } 49 | a { 50 | text-decoration: none; 51 | } -------------------------------------------------------------------------------- /segment/src/assets/images/user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhy0313/nodejs_segment2/589e6c546d30680e2ee6730a876852286bb62030/segment/src/assets/images/user.png -------------------------------------------------------------------------------- /segment/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/router'; 6 | import store from './vuex/store'; 7 | 8 | 9 | // 富文本框 10 | import VueQuillEditor from 'vue-quill-editor'; 11 | Vue.use(VueQuillEditor); 12 | 13 | 14 | // 表单验证 15 | import Vuelidate from 'vuelidate'; 16 | Vue.use(Vuelidate); 17 | 18 | // 格式化css 19 | import './assets/css/reset.css'; 20 | import './assets/css/common.css'; 21 | 22 | // iconfont 23 | import './assets/css/iconfont/iconfont.css'; 24 | import './assets/css/iconfont/iconfont2.css'; 25 | 26 | Vue.config.productionTip = false; 27 | 28 | 29 | /* eslint-disable no-new */ 30 | new Vue({ 31 | el: '#app', 32 | router, 33 | store, 34 | template: '', 35 | components: { App } 36 | }); 37 | -------------------------------------------------------------------------------- /segment/src/pages/Layout.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 41 | 42 | 57 | 58 | -------------------------------------------------------------------------------- /segment/src/pages/header/HasLogin.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 63 | 64 | 191 | -------------------------------------------------------------------------------- /segment/src/pages/header/Header.vue: -------------------------------------------------------------------------------- 1 | 53 | 192 | 193 | 363 | 364 | -------------------------------------------------------------------------------- /segment/src/pages/header/Login.vue: -------------------------------------------------------------------------------- 1 | 65 | 66 | 296 | 297 | -------------------------------------------------------------------------------- /segment/src/pages/main/Ask.vue: -------------------------------------------------------------------------------- 1 | 35 | 36 | 187 | 331 | 332 | -------------------------------------------------------------------------------- /segment/src/pages/main/QuestionDetail.vue: -------------------------------------------------------------------------------- 1 | 17 | 96 | 169 | 170 | 171 | -------------------------------------------------------------------------------- /segment/src/pages/main/QuestionList.vue: -------------------------------------------------------------------------------- 1 | 51 | 52 | 160 | 161 | 383 | 384 | -------------------------------------------------------------------------------- /segment/src/pages/user/UserHeader.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 69 | 70 | 165 | 166 | 167 | -------------------------------------------------------------------------------- /segment/src/pages/user/UserIndex.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 20 | 21 | -------------------------------------------------------------------------------- /segment/src/router/router.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import store from '../vuex/store'; 3 | import Router from 'vue-router'; 4 | import Layout from '@/pages/Layout'; 5 | import UserIndex from '@/pages/user/UserIndex'; 6 | import QuestionList from '@/pages/main/QuestionList'; 7 | import Ask from '@/pages/main/Ask'; 8 | import QuestionDetail from '@/pages/main/QuestionDetail'; 9 | 10 | 11 | Vue.use(Router); 12 | 13 | const routes = [ 14 | { 15 | path: '/', 16 | name: 'Layout', 17 | component: Layout, 18 | redirect:'/question', // 重定向到问答页 19 | children:[ 20 | { path:'/myindex', component:UserIndex, name:'我的主页' }, 21 | { path:'/question', component:QuestionList, name:'问答' }, 22 | { path:'/ask', component:Ask, name:'提问' }, 23 | { path:'/q/:q_id', component:QuestionDetail, name:'问题详情' }, 24 | ] 25 | } 26 | ]; 27 | 28 | const router = new Router({routes}); 29 | 30 | // 导航钩子 31 | router.beforeEach((to, from, next) => { 32 | // 更新一级导航状态 33 | if(to.path == '/question' || to.path == '/myindex'){ 34 | store.commit('updateWriteTypeCode',-1); 35 | } 36 | next(); 37 | }); 38 | 39 | export default router; 40 | -------------------------------------------------------------------------------- /segment/src/vuex/store.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Vuex from 'vuex'; 3 | 4 | Vue.use(Vuex); 5 | 6 | export default new Vuex.Store({ 7 | state:{ 8 | hasLogin:false, //登录状态 9 | showLogin: false, // 显示/隐藏 登录/注册框 10 | showLoginBtn: true, // 显示/隐藏 header登录/注册按钮 11 | isOverdue: false, // 是否登录过期 12 | writeTypeCode: -1 //一级导航 13 | }, 14 | 15 | mutations:{ 16 | // 刷新页面后还原state 17 | resetState(state,payload){ 18 | state.writeTypeCode = payload.writeTypeCode; 19 | state.hasLogin = payload.hasLogin; 20 | state.showLogin = payload.showLogin; 21 | state.showLoginBtn = payload.showLoginBtn; 22 | }, 23 | 24 | // 保存vuex状态 25 | saveState(state){ 26 | let data = JSON.stringify(state); 27 | sessionStorage.setItem('segment_state',data); 28 | }, 29 | 30 | // 更新一级导航 31 | updateWriteTypeCode(state,payload){ 32 | state.writeTypeCode = payload; 33 | }, 34 | 35 | // 重新登录 36 | reLogin(state,payload){ 37 | state.hasLogin = payload.hasLogin; 38 | state.isOverdue = payload.isOverdue; 39 | state.showLogin = payload.showLogin; 40 | }, 41 | 42 | // 更新登录状态 43 | loginStatus(state,payload){ 44 | state.hasLogin = payload; 45 | }, 46 | 47 | // 显示隐藏 登录/注册框 48 | showLogin(state,payload){ 49 | state.showLogin = payload; 50 | }, 51 | 52 | // 显示隐藏 header-注册/登录按钮 53 | toggleLoginBtn(state,payload){ 54 | state.showLoginBtn = payload; 55 | } 56 | } 57 | }); 58 | 59 | 60 | -------------------------------------------------------------------------------- /segment/static/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhy0313/nodejs_segment2/589e6c546d30680e2ee6730a876852286bb62030/segment/static/.gitkeep -------------------------------------------------------------------------------- /segment/static/ask.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhy0313/nodejs_segment2/589e6c546d30680e2ee6730a876852286bb62030/segment/static/ask.png -------------------------------------------------------------------------------- /segment/static/body.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhy0313/nodejs_segment2/589e6c546d30680e2ee6730a876852286bb62030/segment/static/body.png -------------------------------------------------------------------------------- /segment/static/green-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhy0313/nodejs_segment2/589e6c546d30680e2ee6730a876852286bb62030/segment/static/green-dark.png -------------------------------------------------------------------------------- /segment/static/page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhy0313/nodejs_segment2/589e6c546d30680e2ee6730a876852286bb62030/segment/static/page.png -------------------------------------------------------------------------------- /segment/static/question_detail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhy0313/nodejs_segment2/589e6c546d30680e2ee6730a876852286bb62030/segment/static/question_detail.png -------------------------------------------------------------------------------- /segment/static/segment_未登录.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhy0313/nodejs_segment2/589e6c546d30680e2ee6730a876852286bb62030/segment/static/segment_未登录.png -------------------------------------------------------------------------------- /segment/static/segment_登录.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhy0313/nodejs_segment2/589e6c546d30680e2ee6730a876852286bb62030/segment/static/segment_登录.png -------------------------------------------------------------------------------- /segment/static/tag_selection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhy0313/nodejs_segment2/589e6c546d30680e2ee6730a876852286bb62030/segment/static/tag_selection.png -------------------------------------------------------------------------------- /segment/static/userInfo-header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhy0313/nodejs_segment2/589e6c546d30680e2ee6730a876852286bb62030/segment/static/userInfo-header.png -------------------------------------------------------------------------------- /segment/static/userinfo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhy0313/nodejs_segment2/589e6c546d30680e2ee6730a876852286bb62030/segment/static/userinfo.png -------------------------------------------------------------------------------- /segment/static/登录注册.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhy0313/nodejs_segment2/589e6c546d30680e2ee6730a876852286bb62030/segment/static/登录注册.png -------------------------------------------------------------------------------- /server/app.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var path = require('path'); 3 | var favicon = require('serve-favicon'); 4 | var logger = require('morgan'); 5 | var cookieParser = require('cookie-parser'); 6 | var bodyParser = require('body-parser'); 7 | var session = require('express-session'); 8 | 9 | var index = require('./routes/index'); 10 | var users = require('./routes/users'); 11 | var question = require('./routes/question'); 12 | 13 | var app = express(); 14 | 15 | // 设置session 16 | app.use(session({ 17 | secret: 'keyboard cat', 18 | resave: true, 19 | saveUninitialized: true, 20 | cookie: { maxAge: 3 * 60 * 1000 } 21 | })); 22 | 23 | // view engine setup 24 | // app.set('views', path.join(__dirname, 'views')); 25 | // app.set('view engine', 'jade'); 26 | 27 | // uncomment after placing your favicon in /public 28 | //app.use(favicon(path.join(__dirname, 'public', 'favicon.ico'))); 29 | app.use(logger('dev')); 30 | app.use(bodyParser.json()); 31 | app.use(bodyParser.urlencoded({ extended: false })); 32 | // app.use(cookieParser()); 33 | app.use(express.static(path.join(__dirname, 'public'))); 34 | 35 | app.use('/', index); 36 | app.use('/users', users); 37 | app.use('/question', question); 38 | 39 | // catch 404 and forward to error handler 40 | app.use(function (req, res, next) { 41 | var err = new Error('Not Found'); 42 | err.status = 404; 43 | next(err); 44 | }); 45 | 46 | // error handler 47 | app.use(function (err, req, res, next) { 48 | // set locals, only providing error in development 49 | res.locals.message = err.message; 50 | res.locals.error = req.app.get('env') === 'development' ? err : {}; 51 | 52 | // render the error page 53 | res.status(err.status || 500); 54 | res.render('error'); 55 | }); 56 | 57 | module.exports = app; 58 | -------------------------------------------------------------------------------- /server/bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var app = require('../app'); 8 | var debug = require('debug')('server:server'); 9 | var http = require('http'); 10 | 11 | /** 12 | * Get port from environment and store in Express. 13 | */ 14 | 15 | var port = normalizePort(process.env.PORT || '3000'); 16 | app.set('port', port); 17 | 18 | /** 19 | * Create HTTP server. 20 | */ 21 | 22 | var server = http.createServer(app); 23 | 24 | /** 25 | * Listen on provided port, on all network interfaces. 26 | */ 27 | 28 | server.listen(port); 29 | server.on('error', onError); 30 | server.on('listening', onListening); 31 | 32 | /** 33 | * Normalize a port into a number, string, or false. 34 | */ 35 | 36 | function normalizePort(val) { 37 | var port = parseInt(val, 10); 38 | 39 | if (isNaN(port)) { 40 | // named pipe 41 | return val; 42 | } 43 | 44 | if (port >= 0) { 45 | // port number 46 | return port; 47 | } 48 | 49 | return false; 50 | } 51 | 52 | /** 53 | * Event listener for HTTP server "error" event. 54 | */ 55 | 56 | function onError(error) { 57 | if (error.syscall !== 'listen') { 58 | throw error; 59 | } 60 | 61 | var bind = typeof port === 'string' 62 | ? 'Pipe ' + port 63 | : 'Port ' + port; 64 | 65 | // handle specific listen errors with friendly messages 66 | switch (error.code) { 67 | case 'EACCES': 68 | console.error(bind + ' requires elevated privileges'); 69 | process.exit(1); 70 | break; 71 | case 'EADDRINUSE': 72 | console.error(bind + ' is already in use'); 73 | process.exit(1); 74 | break; 75 | default: 76 | throw error; 77 | } 78 | } 79 | 80 | /** 81 | * Event listener for HTTP server "listening" event. 82 | */ 83 | 84 | function onListening() { 85 | var addr = server.address(); 86 | var bind = typeof addr === 'string' 87 | ? 'pipe ' + addr 88 | : 'port ' + addr.port; 89 | debug('Listening on ' + bind); 90 | } 91 | -------------------------------------------------------------------------------- /server/db/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | mysql: { 3 | 'host': 'localhost', 4 | 'user': 'root', 5 | 'password': '', 6 | 'database': 'segment2', 7 | 'port': '3306' 8 | } 9 | }; -------------------------------------------------------------------------------- /server/models/QuestionModel.js: -------------------------------------------------------------------------------- 1 | var mysql = require('mysql'); 2 | var async = require('async'); 3 | var config = require('../db/config'); 4 | 5 | var pool = mysql.createPool(config.mysql); 6 | pool.on('connection', function (connection) { 7 | connection.query('SET SESSION auto_increment_increment=1'); 8 | }); 9 | 10 | module.exports = { 11 | /** 12 | * 提问 13 | * title 标题 14 | * tag 标签 15 | * content 内容 16 | */ 17 | askQuestion(req,res){ 18 | let data = { 19 | code:200, 20 | msg:'success' 21 | }; 22 | 23 | let body = req.body; 24 | let title = body.title; 25 | let tag = body.tag; 26 | let content = body.content; 27 | let user_id = req.session.sessionID; 28 | // 验证 29 | if(title == '' || tag == '' || content==''){ 30 | data.code = 400; 31 | data.msg = '请填写完整问题信息'; 32 | res.send(data); 33 | return; 34 | } 35 | 36 | let askSql = 'insert questions(q_id,q_title,q_tag,q_content,user_id) values(null,?,?,?,?)'; 37 | let param = [title,tag,content,user_id]; 38 | 39 | pool.getConnection((err,conn)=>{ 40 | if(err){ 41 | data.code = 401; 42 | data.msg = '连接错误'; 43 | res.send(data); 44 | return; 45 | } 46 | 47 | conn.query(askSql,param,(err)=>{ 48 | if(err){ 49 | data.code = 400; 50 | data.msg = err.message; 51 | res.send(data); 52 | return; 53 | } 54 | res.send(data); 55 | }); 56 | conn.release(); 57 | }); 58 | }, 59 | 60 | /** 61 | * 获取问题列表 62 | * page 当前页 63 | * pageSize 分页大小 64 | */ 65 | questionList(req,res){ 66 | let data = { 67 | code:200, 68 | msg: 'success', 69 | data:'' 70 | }; 71 | 72 | let page = req.query.page ; 73 | let pageSize = req.query.pageSize; 74 | 75 | let params = [(page-1)*pageSize, parseInt(pageSize)]; // 注意字符串转成数字,否则sql无法报错 76 | // 查询当前页 77 | let queSql = 'SELECT q_id,q_title questionTitle,q_tag tagName,votes,answer,views,u.username AS lastRespondent FROM questions AS q INNER JOIN user AS u ON q.user_id = u.uid ORDER BY q.create_time DESC limit ?,?'; 78 | 79 | // 查询问题总条数 80 | let countSql = 'select count(*) as total from questions'; 81 | 82 | pool.getConnection((err,conn)=>{ 83 | if(err){ 84 | data.code = 401; 85 | data.msg = '连接错误,请稍候重试'; 86 | res.send(data); 87 | return; 88 | } 89 | 90 | async.series([ 91 | // 查询问题总条数 92 | function(callback){ 93 | conn.query(countSql,[],(err,rs)=>{ 94 | let total = rs[0].total; 95 | callback(null,total); 96 | }); 97 | }, 98 | 99 | // 查询当前页 100 | function(callback){ 101 | conn.query(queSql,params,(err,rs)=>{ 102 | // 转换tagName 103 | if(rs != undefined && rs.length>0){ 104 | rs.forEach((e)=>{ 105 | e.tagName = e.tagName.split(','); 106 | }); 107 | callback(null,rs); 108 | } 109 | }); 110 | } 111 | ], 112 | function(err,result){ 113 | if(err){ 114 | data.code = 400; 115 | data.msg = err.message; 116 | res.send(data); 117 | return; 118 | } 119 | 120 | data.total = result[0]; 121 | data.data = result[1]; 122 | res.send(data); 123 | }); 124 | 125 | conn.release(); 126 | }); 127 | }, 128 | 129 | /** 130 | * 获取问题详情 131 | */ 132 | questionDetail(req,res){ 133 | let data = { 134 | code:200, 135 | message: 'sucess', 136 | data:'' 137 | }; 138 | 139 | // 查看问题详情 140 | let queSql = 'select q.q_title as title, q.q_content as content,u.username, q.q_tag as tags, q.create_time, q.last_res_time,q.last_res_id from questions as q inner join user as u on q.user_id = u.uid where q_id=?'; 141 | let param = req.query.q_id; 142 | 143 | // views+1 144 | let uptSql = ' update questions set views=views+1 where q_id = ?'; 145 | 146 | pool.getConnection((err,conn)=>{ 147 | if(err){ 148 | data.code = 401; 149 | data.msg = '连接错误'; 150 | res.send(data); 151 | return; 152 | } 153 | async.series([ 154 | // 查看问题详情 155 | function(callback){ 156 | conn.query(queSql,param,(err,rs)=>{ 157 | let result = rs[0]; 158 | result.content = result.content.toString(); 159 | data.data = result; 160 | // res.send(data); 161 | callback(null,data); 162 | }); 163 | }, 164 | // views+1 165 | function(callback){ 166 | conn.query(uptSql,param,(err,rs)=>{ 167 | 168 | callback(null,rs); 169 | }); 170 | 171 | } 172 | ], 173 | function(err,rs){ 174 | if(err){ 175 | data.code = 400; 176 | data.msg = err.message; 177 | res.send(data); 178 | return; 179 | } 180 | res.send(rs[0]); 181 | }); 182 | conn.release(); 183 | }); 184 | } 185 | 186 | }; -------------------------------------------------------------------------------- /server/models/UsersModel.js: -------------------------------------------------------------------------------- 1 | var mysql = require('mysql'); 2 | var async = require('async'); 3 | var config = require('../db/config'); 4 | 5 | var pool = mysql.createPool(config.mysql); 6 | pool.on('connection', function (connection) { 7 | connection.query('SET SESSION auto_increment_increment=1'); 8 | }); 9 | 10 | module.exports = { 11 | // 注册 12 | register(req,res){ 13 | // 注册 14 | let addUserSql = 'insert into user (username,email,mobile,pwd) values(?,?,?,?)'; 15 | 16 | // 读取新用户uid 17 | let querySql = 'select uid from user where username = ?'; 18 | 19 | let body = req.body; 20 | let email = body.email == '' ? null : body.email; // 为空则存储null 21 | let mobile = body.mobile == '' ? null : body.mobile; // 为空则存储null 22 | let params = [body.name,email,mobile,body.pwd]; 23 | let queryParams = [body.name]; 24 | 25 | pool.getConnection((err,conn)=>{ 26 | let data = { 27 | code: 200, 28 | msg: '', 29 | data:'' 30 | }; 31 | if(err) { 32 | data.code = 400; 33 | data.msg = '连接错误,请稍后再试'; 34 | res.send(data); 35 | return; 36 | } 37 | 38 | async.series([ 39 | // 注册 40 | function(callback){ 41 | conn.query(addUserSql,params,(err)=>{ 42 | if(err){ 43 | console.log(`写入异常:${err}`); 44 | data.code = 400; 45 | let msg = err.message; 46 | if(msg.indexOf('email_unique') > -1){ 47 | data.msg = '邮箱已被注册'; 48 | data.errType = 'email'; 49 | }else if(msg.indexOf('username_unique') > -1){ 50 | data.msg = '用户名已被注册'; 51 | data.errType = 'name'; 52 | }else if(msg.indexOf('mobile_unique')){ 53 | data.msg = '手机已被注册'; 54 | data.errType = 'mobile'; 55 | } else { 56 | data.msg = msg; 57 | } 58 | res.send(data); 59 | return; 60 | } 61 | callback(null,'register'); 62 | }); 63 | }, 64 | 65 | // 读取新用户的uid 66 | function(callback){ 67 | conn.query(querySql,queryParams,(err,rs)=>{ 68 | if(err){ 69 | data.code = 400; 70 | data.msg = err.message; 71 | res.send(data); 72 | return; 73 | } 74 | callback(null,rs[0]); 75 | }); 76 | } 77 | ], 78 | function(err,result){ 79 | //拿到user_id 放入session 80 | req.session.sessionID = result[1].uid; 81 | data.msg = 'success'; 82 | res.send(data); 83 | }); 84 | conn.release(); 85 | }); 86 | }, 87 | 88 | // 登录 89 | login(req,res){ 90 | 91 | let loginSql = 'select uid,username from user where (email=? or mobile=?) and pwd=?'; 92 | let body = req.body; 93 | let params = [body.user,body.user,body.pwd]; 94 | 95 | pool.getConnection((err,conn)=>{ 96 | let data = { 97 | code: 200, 98 | msg:'success' 99 | }; 100 | 101 | if(err){ 102 | data.code = 401; 103 | data.msg = err.message; 104 | res.send(data); 105 | return; 106 | } 107 | 108 | conn.query(loginSql,params,(err,rs)=>{ 109 | if(err){ 110 | data.code = 401, 111 | data.msg = err.message, 112 | res.send(data); 113 | return; 114 | } 115 | 116 | // 查询结果结果 117 | if(rs.length > 0 ){ 118 | data.data = rs[0]; 119 | 120 | // 设置session 121 | let sessionID = rs[0].uid; 122 | req.session.sessionID = sessionID; 123 | 124 | }else { 125 | data.code = 400; 126 | data.msg = '用户名或密码错误'; 127 | } 128 | res.send(data); 129 | }); 130 | conn.release(); 131 | }); 132 | }, 133 | 134 | // 查询用户信息 135 | getUserInfo(req,res){ 136 | let userSql = 'select mobile, email, username, create_time from user where uid = ? '; 137 | let params = [req.session.sessionID]; 138 | 139 | pool.getConnection((err,conn)=>{ 140 | let data = { 141 | code: 200, 142 | msg: 'success', 143 | data: '' 144 | }; 145 | if(err){ 146 | data.code = 401; 147 | data.msg = err.message; 148 | res.send(data); 149 | return; 150 | } 151 | 152 | conn.query(userSql,params,(err,rs)=>{ 153 | if(err){ 154 | data.code = 401; 155 | data.msg = err.message; 156 | res.send(data); 157 | return; 158 | } 159 | if(rs.length>0){ 160 | data.data = rs[0]; 161 | }else{ 162 | data.msg = '无此用户'; 163 | } 164 | res.send(data); 165 | }); 166 | }); 167 | }, 168 | 169 | 170 | //========================= 171 | // 查询用户列表 172 | getUserList(req,res){ 173 | let getUserSql = ' select uid,username from user'; 174 | let params = []; 175 | 176 | pool.getConnection((err,conn)=>{ 177 | if(err) throw err; 178 | 179 | conn.query(getUserSql,params,(err,rs)=>{ 180 | if(err) throw err; 181 | 182 | let arr = []; 183 | if(rs.length){ 184 | rs.forEach((e)=>{ 185 | let obj = { 186 | username: e.username, 187 | email: e.email 188 | }; 189 | arr.push(obj); 190 | }); 191 | 192 | let data = { 193 | code:200, 194 | data:arr 195 | }; 196 | res.send(data); 197 | return; 198 | } 199 | res.send('无用户'); 200 | }); 201 | conn.release(); 202 | }); 203 | } 204 | }; -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "server", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "node ./bin/www", 7 | "nodemon": "nodemon ./bin/www" 8 | }, 9 | "dependencies": { 10 | "body-parser": "~1.17.1", 11 | "cookie-parser": "~1.4.3", 12 | "debug": "~2.6.3", 13 | "express": "~4.15.2", 14 | "jsonwebtoken": "^8.0.0", 15 | "morgan": "~1.8.1", 16 | "mysql": "^2.14.1", 17 | "serve-favicon": "~2.4.2" 18 | }, 19 | "devDependencies": { 20 | "async": "^2.5.0", 21 | "express-session": "^1.15.5" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /server/routes/index.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | 4 | 5 | /* GET home page. */ 6 | router.get('/', function (req, res, next) { 7 | res.send('welcome to segment'); 8 | }); 9 | 10 | module.exports = router; 11 | -------------------------------------------------------------------------------- /server/routes/question.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | var QuestionModel = require('../models/QuestionModel'); 4 | var checkSession = require('../session/CheckSession'); 5 | 6 | // 提问 7 | router.post('/ask_question',(req,res)=>{ 8 | //验证session 9 | let result = checkSession.check(req); 10 | if (result.code == 402) { 11 | res.send(result); 12 | return; 13 | } 14 | QuestionModel.askQuestion(req,res); 15 | }); 16 | 17 | // 获取问题列表 18 | router.get('/question_list',(req,res)=>{ 19 | QuestionModel.questionList(req,res); 20 | }); 21 | 22 | // 查看问题详情 23 | router.get('/question_detail',(req,res)=>{ 24 | QuestionModel.questionDetail(req,res); 25 | }); 26 | 27 | module.exports = router; -------------------------------------------------------------------------------- /server/routes/users.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | var UsersModel = require('../models/UsersModel'); 4 | var checkSession = require('../session/CheckSession'); 5 | 6 | // 注册 7 | router.post('/register',(req,res)=>{ 8 | UsersModel.register(req,res); 9 | }); 10 | 11 | // 登录 12 | router.post('/login',(req,res)=>{ 13 | UsersModel.login(req,res); 14 | }); 15 | 16 | // 退出登录 17 | router.get('/logout',(req,res)=>{ 18 | //注销session 19 | req.session.destroy(()=>{ 20 | let data = { 21 | code:200, 22 | msg:'sucess' 23 | }; 24 | res.send(data); 25 | }); 26 | }); 27 | 28 | // 获取用户信息 29 | router.get('/userinfo',(req,res)=>{ 30 | //验证session 31 | let result = checkSession.check(req); 32 | if (result.code == 402) { 33 | res.send(result); 34 | return; 35 | } 36 | UsersModel.getUserInfo(req,res); 37 | }); 38 | 39 | 40 | //=========================== 41 | // 查询用户列表 42 | router.get('/getlist', function (req, res) { 43 | //验证session 44 | let result = checkSession.check(req, res); 45 | if (result.code == 402) { 46 | res.send(result); 47 | return; 48 | } 49 | UsersModel.getUserList(req,res); 50 | }); 51 | module.exports = router; 52 | -------------------------------------------------------------------------------- /server/session/CheckSession.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | check(req){ 3 | let id = req.session.sessionID; 4 | let data = { 5 | code: 200, 6 | msg: 'success' 7 | }; 8 | if(!id){ 9 | data.code = 402, 10 | data.msg = '登录过期,请重新登录'; 11 | } 12 | return data; 13 | } 14 | }; --------------------------------------------------------------------------------