├── README.md
├── admin
├── static
│ └── .gitkeep
├── src
│ ├── store
│ │ ├── tag
│ │ │ ├── getters.js
│ │ │ ├── state.js
│ │ │ ├── mutation-types.js
│ │ │ ├── index.js
│ │ │ ├── actions.js
│ │ │ └── mutations.js
│ │ ├── user
│ │ │ ├── getters.js
│ │ │ ├── mutation-types.js
│ │ │ ├── state.js
│ │ │ ├── mutations.js
│ │ │ ├── index.js
│ │ │ └── actions.js
│ │ └── index.js
│ ├── pages
│ │ ├── wxk
│ │ │ └── index.vue
│ │ ├── manage
│ │ │ ├── index.vue
│ │ │ ├── navBar.vue
│ │ │ └── tabsView.vue
│ │ ├── usermanage
│ │ │ ├── userinfo
│ │ │ │ ├── index.js
│ │ │ │ └── index.vue
│ │ │ └── enchashment
│ │ │ │ ├── index.js
│ │ │ │ └── index.vue
│ │ ├── qualitymanage
│ │ │ └── review
│ │ │ │ ├── index.js
│ │ │ │ └── index.vue
│ │ └── login
│ │ │ └── index.vue
│ ├── assets
│ │ └── logo.png
│ ├── style
│ │ ├── mixin.scss
│ │ └── common.scss
│ ├── App.vue
│ ├── utils
│ │ ├── user.js
│ │ └── utils.js
│ ├── router
│ │ ├── permission.js
│ │ └── index.js
│ ├── service
│ │ ├── env.js
│ │ ├── api.js
│ │ └── fetch.js
│ ├── main.js
│ └── components
│ │ └── HelloWorld.vue
├── build
│ ├── logo.png
│ ├── vue-loader.conf.js
│ ├── build.js
│ ├── check-versions.js
│ ├── webpack.base.conf.js
│ ├── utils.js
│ ├── webpack.dev.conf.js
│ └── webpack.prod.conf.js
├── test
│ └── unit
│ │ ├── setup.js
│ │ ├── .eslintrc
│ │ ├── specs
│ │ └── HelloWorld.spec.js
│ │ └── jest.conf.js
├── config
│ ├── prod.env.js
│ ├── test.env.js
│ ├── dev.env.js
│ └── index.js
├── .editorconfig
├── .gitignore
├── .postcssrc.js
├── index.html
├── .babelrc
├── README.md
└── package.json
├── client
├── README.md
├── components
│ ├── README.md
│ ├── lock
│ │ ├── index.json
│ │ ├── index.wxml
│ │ ├── index.wxss
│ │ └── index.js
│ ├── nav
│ │ ├── index.json
│ │ ├── index.wxml
│ │ ├── index.js
│ │ └── index.wxss
│ ├── tip
│ │ ├── index.json
│ │ ├── index.wxml
│ │ ├── index.js
│ │ └── index.wxss
│ ├── booklist
│ │ ├── index.json
│ │ ├── index.wxml
│ │ ├── index.wxss
│ │ └── index.js
│ ├── contact
│ │ ├── index.json
│ │ ├── index.wxml
│ │ ├── index.js
│ │ └── index.wxss
│ ├── wallet
│ │ ├── index.json
│ │ ├── index.wxml
│ │ ├── index.js
│ │ └── index.wxss
│ └── booklistnav
│ │ ├── index.json
│ │ ├── index.wxml
│ │ ├── index.js
│ │ └── index.wxss
├── pages
│ ├── logs
│ │ ├── logs.json
│ │ ├── logs.wxss
│ │ ├── logs.wxml
│ │ └── logs.js
│ ├── login
│ │ ├── index.json
│ │ ├── index.wxml
│ │ ├── index.js
│ │ └── index.wxss
│ ├── editcourse
│ │ ├── index.json
│ │ ├── index.wxml
│ │ ├── index.wxss
│ │ └── index.js
│ ├── enchashment
│ │ ├── index.json
│ │ ├── index.wxss
│ │ ├── index.wxml
│ │ └── index.js
│ ├── setting
│ │ ├── index.json
│ │ ├── index.wxss
│ │ ├── index.wxml
│ │ └── index.js
│ ├── search
│ │ ├── index.json
│ │ ├── index.wxml
│ │ ├── index.wxss
│ │ └── index.js
│ ├── wallet
│ │ ├── index.json
│ │ ├── index.wxml
│ │ ├── index.wxss
│ │ └── index.js
│ ├── collection
│ │ ├── index.json
│ │ ├── index.wxss
│ │ ├── index.wxml
│ │ └── index.js
│ ├── myblist
│ │ ├── index.json
│ │ ├── index.wxml
│ │ ├── index.wxss
│ │ └── index.js
│ ├── purchase
│ │ ├── index.json
│ │ ├── index.wxml
│ │ ├── index.wxss
│ │ └── index.js
│ ├── index
│ │ ├── index.json
│ │ ├── index.wxss
│ │ ├── index.wxml
│ │ └── index.js
│ └── details
│ │ ├── index.json
│ │ ├── index.wxml
│ │ ├── index.wxss
│ │ └── index.js
├── images
│ ├── 1.png
│ ├── 2.png
│ ├── 3.png
│ ├── 4.png
│ ├── 5.png
│ ├── Back.png
│ ├── head.png
│ ├── lock.png
│ ├── logo.png
│ ├── pen.png
│ ├── pkg.png
│ ├── Shape.png
│ ├── github.png
│ ├── weibo.png
│ ├── weixin.png
│ ├── collect1.png
│ ├── collect2.png
│ └── blank-page.png
├── service
│ ├── api.js
│ └── user.js
├── app.wxss
├── app.json
├── project.config.json
├── app.js
└── utils
│ └── util.js
└── server
├── .eslintignore
├── .eslintrc
├── app.js
├── utils
├── util.js
└── token.js
├── .gitignore
├── app
├── router.js
├── controller
│ ├── home.js
│ ├── user.js
│ ├── base.js
│ ├── admin
│ │ ├── base.js
│ │ ├── user.js
│ │ ├── review.js
│ │ ├── admin.js
│ │ ├── enchashment.js
│ │ └── auth.js
│ ├── enchashment.js
│ ├── collect.js
│ ├── purchase.js
│ ├── course.js
│ └── auth.js
├── middleware
│ ├── interceptor.js
│ └── admin_interceptor.js
├── service
│ ├── enchashment.js
│ ├── admin
│ │ ├── admin.js
│ │ ├── review.js
│ │ └── enchashment.js
│ ├── collect.js
│ ├── user.js
│ ├── purchase.js
│ └── course.js
├── router
│ ├── admin.js
│ └── client.js
└── extend
│ └── helper.js
├── .travis.yml
├── config
├── plugin.js
└── config.default.js
├── appveyor.yml
├── .autod.conf.js
├── test
└── app
│ └── controller
│ └── home.test.js
├── README.md
├── README.zh-CN.md
└── package.json
/README.md:
--------------------------------------------------------------------------------
1 | # booklist
--------------------------------------------------------------------------------
/admin/static/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/client/README.md:
--------------------------------------------------------------------------------
1 | # booklist
--------------------------------------------------------------------------------
/server/.eslintignore:
--------------------------------------------------------------------------------
1 | coverage
2 |
--------------------------------------------------------------------------------
/client/components/README.md:
--------------------------------------------------------------------------------
1 | # booklist
--------------------------------------------------------------------------------
/admin/src/store/tag/getters.js:
--------------------------------------------------------------------------------
1 | export default{
2 |
3 | }
--------------------------------------------------------------------------------
/admin/src/store/user/getters.js:
--------------------------------------------------------------------------------
1 | export default{
2 |
3 | }
--------------------------------------------------------------------------------
/server/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "eslint-config-egg"
3 | }
4 |
--------------------------------------------------------------------------------
/client/pages/logs/logs.json:
--------------------------------------------------------------------------------
1 | {
2 | "navigationBarTitleText": "查看启动日志"
3 | }
--------------------------------------------------------------------------------
/client/pages/login/index.json:
--------------------------------------------------------------------------------
1 | {
2 | "navigationBarTitleText": "用户信息授权"
3 | }
--------------------------------------------------------------------------------
/admin/src/pages/wxk/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | 123
4 |
5 |
--------------------------------------------------------------------------------
/admin/build/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ftd-2018/booklist/HEAD/admin/build/logo.png
--------------------------------------------------------------------------------
/admin/test/unit/setup.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 |
3 | Vue.config.productionTip = false
4 |
--------------------------------------------------------------------------------
/client/components/lock/index.json:
--------------------------------------------------------------------------------
1 | {
2 | "component": true,
3 | "usingComponents": {}
4 | }
--------------------------------------------------------------------------------
/client/components/nav/index.json:
--------------------------------------------------------------------------------
1 | {
2 | "component": true,
3 | "usingComponents": {}
4 | }
--------------------------------------------------------------------------------
/client/components/tip/index.json:
--------------------------------------------------------------------------------
1 | {
2 | "component": true,
3 | "usingComponents": {}
4 | }
--------------------------------------------------------------------------------
/client/images/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ftd-2018/booklist/HEAD/client/images/1.png
--------------------------------------------------------------------------------
/client/images/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ftd-2018/booklist/HEAD/client/images/2.png
--------------------------------------------------------------------------------
/client/images/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ftd-2018/booklist/HEAD/client/images/3.png
--------------------------------------------------------------------------------
/client/images/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ftd-2018/booklist/HEAD/client/images/4.png
--------------------------------------------------------------------------------
/client/images/5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ftd-2018/booklist/HEAD/client/images/5.png
--------------------------------------------------------------------------------
/client/service/api.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | baseURL: "https://www.duolaimier.cn/"
3 | }
--------------------------------------------------------------------------------
/admin/src/store/tag/state.js:
--------------------------------------------------------------------------------
1 | export default {
2 | num: 0,
3 | visitedViews: []
4 | };
--------------------------------------------------------------------------------
/client/components/booklist/index.json:
--------------------------------------------------------------------------------
1 | {
2 | "component": true,
3 | "usingComponents": {}
4 | }
--------------------------------------------------------------------------------
/client/components/contact/index.json:
--------------------------------------------------------------------------------
1 | {
2 | "component": true,
3 | "usingComponents": {}
4 | }
--------------------------------------------------------------------------------
/client/components/wallet/index.json:
--------------------------------------------------------------------------------
1 | {
2 | "component": true,
3 | "usingComponents": {}
4 | }
--------------------------------------------------------------------------------
/client/images/Back.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ftd-2018/booklist/HEAD/client/images/Back.png
--------------------------------------------------------------------------------
/client/images/head.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ftd-2018/booklist/HEAD/client/images/head.png
--------------------------------------------------------------------------------
/client/images/lock.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ftd-2018/booklist/HEAD/client/images/lock.png
--------------------------------------------------------------------------------
/client/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ftd-2018/booklist/HEAD/client/images/logo.png
--------------------------------------------------------------------------------
/client/images/pen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ftd-2018/booklist/HEAD/client/images/pen.png
--------------------------------------------------------------------------------
/client/images/pkg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ftd-2018/booklist/HEAD/client/images/pkg.png
--------------------------------------------------------------------------------
/admin/config/prod.env.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | module.exports = {
3 | NODE_ENV: '"production"'
4 | }
5 |
--------------------------------------------------------------------------------
/admin/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ftd-2018/booklist/HEAD/admin/src/assets/logo.png
--------------------------------------------------------------------------------
/client/components/booklistnav/index.json:
--------------------------------------------------------------------------------
1 | {
2 | "component": true,
3 | "usingComponents": {}
4 | }
--------------------------------------------------------------------------------
/client/images/Shape.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ftd-2018/booklist/HEAD/client/images/Shape.png
--------------------------------------------------------------------------------
/client/images/github.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ftd-2018/booklist/HEAD/client/images/github.png
--------------------------------------------------------------------------------
/client/images/weibo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ftd-2018/booklist/HEAD/client/images/weibo.png
--------------------------------------------------------------------------------
/client/images/weixin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ftd-2018/booklist/HEAD/client/images/weixin.png
--------------------------------------------------------------------------------
/client/images/collect1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ftd-2018/booklist/HEAD/client/images/collect1.png
--------------------------------------------------------------------------------
/client/images/collect2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ftd-2018/booklist/HEAD/client/images/collect2.png
--------------------------------------------------------------------------------
/server/app.js:
--------------------------------------------------------------------------------
1 | // app.js
2 | module.exports = app => {
3 | // 使用 app 对象‘
4 | app.token = "";
5 | };
6 |
--------------------------------------------------------------------------------
/admin/test/unit/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "jest": true
4 | },
5 | "globals": {
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/client/images/blank-page.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ftd-2018/booklist/HEAD/client/images/blank-page.png
--------------------------------------------------------------------------------
/admin/src/store/user/mutation-types.js:
--------------------------------------------------------------------------------
1 | export const LOGOUT = 'LOGOUT';
2 | export const GET_USERINFO = 'GET_USERINFO';
--------------------------------------------------------------------------------
/admin/src/style/mixin.scss:
--------------------------------------------------------------------------------
1 | @mixin relative {
2 | position: relative;
3 | width: 100%;
4 | height: 100%;
5 | }
--------------------------------------------------------------------------------
/server/utils/util.js:
--------------------------------------------------------------------------------
1 | class Util {
2 | isEmpty(obj){
3 | if(obj) return false;
4 | return true;
5 | }
6 | }
7 |
8 | module.exports = Util;
--------------------------------------------------------------------------------
/client/components/contact/index.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 客服微信:fcsboy
4 |
5 |
--------------------------------------------------------------------------------
/client/pages/logs/logs.wxss:
--------------------------------------------------------------------------------
1 | .log-list {
2 | display: flex;
3 | flex-direction: column;
4 | padding: 40rpx;
5 | }
6 | .log-item {
7 | margin: 10rpx;
8 | }
9 |
--------------------------------------------------------------------------------
/admin/src/store/user/state.js:
--------------------------------------------------------------------------------
1 | export default {
2 | token: localStorage.getItem("token"),
3 | username: localStorage.getItem("userInfo") // 用户名
4 | };
5 |
6 |
--------------------------------------------------------------------------------
/admin/src/store/tag/mutation-types.js:
--------------------------------------------------------------------------------
1 | export const GET_NUMBER = 'GET_NUMBER';
2 | export const ADD_VISITED_VIEWS = 'ADD_VISITED_VIEWS';
3 | export const DEL_VISITED_VIEWS = 'DEL_VISITED_VIEWS';
--------------------------------------------------------------------------------
/admin/config/test.env.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const merge = require('webpack-merge')
3 | const devEnv = require('./dev.env')
4 |
5 | module.exports = merge(devEnv, {
6 | NODE_ENV: '"testing"'
7 | })
8 |
--------------------------------------------------------------------------------
/client/components/tip/index.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 客服微信「 fcsboy 」
4 |
5 |
--------------------------------------------------------------------------------
/client/pages/editcourse/index.json:
--------------------------------------------------------------------------------
1 | {
2 | "navigationBarTitleText": "写书单",
3 | "usingComponents": {
4 | "ftdNav": "/components/nav/index",
5 | "ftdTip": "/components/tip/index"
6 | }
7 | }
--------------------------------------------------------------------------------
/server/.gitignore:
--------------------------------------------------------------------------------
1 | logs/
2 | npm-debug.log
3 | yarn-error.log
4 | node_modules/
5 | package-lock.json
6 | yarn.lock
7 | coverage/
8 | .idea/
9 | run/
10 | .DS_Store
11 | *.sw*
12 | *.un~
13 |
--------------------------------------------------------------------------------
/admin/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 |
--------------------------------------------------------------------------------
/admin/.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 |
--------------------------------------------------------------------------------
/client/pages/enchashment/index.json:
--------------------------------------------------------------------------------
1 | {
2 | "usingComponents": {
3 | "ftdNav": "/components/nav/index",
4 | "ftdTip": "/components/tip/index"
5 | },
6 | "navigationBarTitleText": "提现"
7 | }
--------------------------------------------------------------------------------
/client/pages/setting/index.json:
--------------------------------------------------------------------------------
1 | {
2 | "navigationBarTitleText": "我的设置",
3 | "usingComponents": {
4 | "ftdNav": "/components/nav/index",
5 | "ftdTip": "/components/tip/index"
6 | }
7 | }
--------------------------------------------------------------------------------
/server/app/router.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * @param {Egg.Application} app - egg application
5 | */
6 | module.exports = app => {
7 | require('./router/client')(app);
8 | require('./router/admin')(app);
9 | };
--------------------------------------------------------------------------------
/client/pages/logs/logs.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{index + 1}}. {{log}}
5 |
6 |
7 |
--------------------------------------------------------------------------------
/server/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 | language: node_js
3 | node_js:
4 | - '8'
5 | install:
6 | - npm i npminstall && npminstall
7 | script:
8 | - npm run ci
9 | after_script:
10 | - npminstall codecov && codecov
11 |
--------------------------------------------------------------------------------
/client/pages/search/index.json:
--------------------------------------------------------------------------------
1 | {
2 | "navigationBarTitleText": "搜索",
3 | "usingComponents": {
4 | "ftdNav": "/components/nav/index",
5 | "ftdBL": "/components/booklist/index",
6 | "ftdTip": "/components/tip/index"
7 | }
8 | }
--------------------------------------------------------------------------------
/client/pages/wallet/index.json:
--------------------------------------------------------------------------------
1 | {
2 | "usingComponents": {
3 | "ftdNav": "/components/nav/index",
4 | "ftdWallet": "/components/wallet/index",
5 | "ftdTip": "/components/tip/index"
6 | },
7 | "navigationBarTitleText": "我的钱包"
8 | }
--------------------------------------------------------------------------------
/client/pages/collection/index.json:
--------------------------------------------------------------------------------
1 | {
2 | "navigationBarTitleText": "我的收藏",
3 | "usingComponents": {
4 | "ftdBLN": "/components/booklistnav/index",
5 | "ftdNav": "/components/nav/index",
6 | "ftdTip": "/components/tip/index"
7 | }
8 | }
--------------------------------------------------------------------------------
/client/pages/myblist/index.json:
--------------------------------------------------------------------------------
1 | {
2 | "usingComponents": {
3 | "ftdBLN": "/components/booklistnav/index",
4 | "ftdNav": "/components/nav/index",
5 | "ftdTip": "/components/tip/index"
6 | },
7 | "navigationBarTitleText": "我的书单"
8 | }
--------------------------------------------------------------------------------
/client/pages/purchase/index.json:
--------------------------------------------------------------------------------
1 | {
2 | "usingComponents": {
3 | "ftdBLN": "/components/booklistnav/index",
4 | "ftdNav": "/components/nav/index",
5 | "ftdTip": "/components/tip/index"
6 | },
7 | "navigationBarTitleText": "我的订阅"
8 | }
--------------------------------------------------------------------------------
/admin/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules/
3 | /dist/
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | /test/unit/coverage/
8 |
9 | # Editor directories and files
10 | .idea
11 | .vscode
12 | *.suo
13 | *.ntvs*
14 | *.njsproj
15 | *.sln
16 |
--------------------------------------------------------------------------------
/server/config/plugin.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // had enabled by egg
4 | // exports.static = true;
5 | exports.mysql = {
6 | enable: true,
7 | package: 'egg-mysql',
8 | };
9 |
10 |
11 | exports.cors = {
12 | enable: true,
13 | package: 'egg-cors',
14 | };
--------------------------------------------------------------------------------
/client/pages/index/index.json:
--------------------------------------------------------------------------------
1 | {
2 | "navigationBarTitleText": "书单分享",
3 | "usingComponents": {
4 | "ftdNav": "/components/nav/index",
5 | "ftdBL": "/components/booklist/index",
6 | "ftdTip": "/components/tip/index"
7 | },
8 | "enablePullDownRefresh":true
9 | }
--------------------------------------------------------------------------------
/admin/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
16 |
--------------------------------------------------------------------------------
/server/app/controller/home.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Controller = require('egg').Controller;
4 |
5 | class HomeController extends Controller {
6 | async index() {
7 | this.ctx.body = "{message:'请求成功', data:1}";
8 | }
9 | }
10 |
11 | module.exports = HomeController;
12 |
--------------------------------------------------------------------------------
/admin/src/store/user/mutations.js:
--------------------------------------------------------------------------------
1 | import * as types from './mutation-types'
2 |
3 | export default{
4 | [types.LOGOUT]: (state, token) => {
5 | state.token = token
6 | },
7 | [types.GET_USERINFO]: (state, getuserinfo) =>{
8 | state.username = getuserinfo.username;
9 | }
10 | }
--------------------------------------------------------------------------------
/client/pages/details/index.json:
--------------------------------------------------------------------------------
1 | {
2 | "usingComponents": {
3 | "ftdBLN": "/components/booklistnav/index",
4 | "ftdNav": "/components/nav/index",
5 | "ftdLock": "/components/lock/index",
6 | "ftdContact": "/components/contact/index",
7 | "ftdTip": "/components/tip/index"
8 | }
9 | }
--------------------------------------------------------------------------------
/admin/.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 |
--------------------------------------------------------------------------------
/admin/src/store/tag/index.js:
--------------------------------------------------------------------------------
1 | import state from './state';
2 | import mutations from './mutations';
3 | import getters from './getters';
4 | import actions from './actions';
5 |
6 | export default {
7 | namespaced: true,
8 | state,
9 | mutations,
10 | getters,
11 | actions
12 | };
--------------------------------------------------------------------------------
/admin/src/store/user/index.js:
--------------------------------------------------------------------------------
1 | import state from './state';
2 | import mutations from './mutations';
3 | import getters from './getters';
4 | import actions from './actions';
5 |
6 | export default {
7 | namespaced: true,
8 | state,
9 | mutations,
10 | getters,
11 | actions
12 | };
--------------------------------------------------------------------------------
/client/components/contact/index.js:
--------------------------------------------------------------------------------
1 | // components/contact/index.js
2 | Component({
3 | /**
4 | * 组件的属性列表
5 | */
6 | properties: {
7 |
8 | },
9 |
10 | /**
11 | * 组件的初始数据
12 | */
13 | data: {
14 |
15 | },
16 |
17 | /**
18 | * 组件的方法列表
19 | */
20 | methods: {
21 |
22 | }
23 | })
24 |
--------------------------------------------------------------------------------
/server/appveyor.yml:
--------------------------------------------------------------------------------
1 | environment:
2 | matrix:
3 | - nodejs_version: '8'
4 |
5 | install:
6 | - ps: Install-Product node $env:nodejs_version
7 | - npm i npminstall && node_modules\.bin\npminstall
8 |
9 | test_script:
10 | - node --version
11 | - npm --version
12 | - npm run test
13 |
14 | build: off
15 |
--------------------------------------------------------------------------------
/admin/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | admin
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/client/components/contact/index.wxss:
--------------------------------------------------------------------------------
1 | /* components/contact/index.wxss */
2 | .ftd-contact{
3 | position: fixed;
4 | font-size: 12px;
5 | color: #fff;
6 | width: 750rpx;
7 | bottom: 0;
8 | height: 80rpx;
9 | line-height: 80rpx;
10 | left: 0;
11 | text-align: center;
12 | background: rgba(0, 0, 0, .3);
13 | }
--------------------------------------------------------------------------------
/client/pages/logs/logs.js:
--------------------------------------------------------------------------------
1 | //logs.js
2 | const util = require('../../utils/util.js')
3 |
4 | Page({
5 | data: {
6 | logs: []
7 | },
8 | onLoad: function () {
9 | this.setData({
10 | logs: (wx.getStorageSync('logs') || []).map(log => {
11 | return util.formatTime(new Date(log))
12 | })
13 | })
14 | }
15 | })
16 |
--------------------------------------------------------------------------------
/client/app.wxss:
--------------------------------------------------------------------------------
1 | /**app.wxss**/
2 | view,image,text,navigator{
3 | list-style: none;
4 | padding:0;
5 | margin:0;
6 |
7 | }
8 | .container {
9 | font-family: PingFang-SC-Heavy;
10 | height: 100%;
11 | display: flex;
12 | flex-direction: column;
13 | align-items: center;
14 | justify-content: space-between;
15 | padding: 118rpx 0 0 0;
16 | box-sizing: border-box;
17 | }
18 |
--------------------------------------------------------------------------------
/admin/src/store/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import Vuex from 'vuex';
3 | import createPersistedState from "vuex-persistedstate";
4 | Vue.use(Vuex);
5 |
6 | import user from './user/';
7 | import tag from './tag/';
8 |
9 | export default new Vuex.Store({
10 | modules: {
11 | user,
12 | tag
13 | },
14 | plugins: [createPersistedState({ storage: window.sessionStorage })]
15 | });
16 |
--------------------------------------------------------------------------------
/admin/src/utils/user.js:
--------------------------------------------------------------------------------
1 | const userInfo = 'userInfo'
2 |
3 | function getUserInfo(){
4 | // return localStorage.getItem(JSON.parse(userInfo));
5 | }
6 |
7 | function setUserInfo(token){
8 | return localStorage.setItem(userInfo, JSON.stringify(token));
9 | }
10 |
11 | function removeUserInfo(){
12 | return localStorage.removeItem(userInfo);
13 | }
14 |
15 | export {
16 | getUserInfo,
17 | setUserInfo,
18 | removeUserInfo
19 | }
--------------------------------------------------------------------------------
/admin/test/unit/specs/HelloWorld.spec.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import HelloWorld from '@/components/HelloWorld'
3 |
4 | describe('HelloWorld.vue', () => {
5 | it('should render correct contents', () => {
6 | const Constructor = Vue.extend(HelloWorld)
7 | const vm = new Constructor().$mount()
8 | expect(vm.$el.querySelector('.hello h1').textContent)
9 | .toEqual('Welcome to Your Vue.js App')
10 | })
11 | })
12 |
--------------------------------------------------------------------------------
/client/pages/index/index.wxss:
--------------------------------------------------------------------------------
1 | /**index.wxss**/
2 | .serach{
3 | width: 640rpx;
4 | height: 92rpx;
5 | margin: 0 auto;
6 | background: #F5F5F5;
7 | border-radius: 16rpx;
8 | font-size: 30rpx;
9 | color: rgba(36, 37, 61, .2);
10 | line-height: 92rpx;
11 | padding-left: 30rpx;
12 | margin-bottom: 36rpx;
13 | }
14 | .bottomTips{
15 | font-size: 28rpx;
16 | text-align: center;
17 | padding: 50rpx;
18 | color: #ccc;
19 | }
--------------------------------------------------------------------------------
/admin/src/store/tag/actions.js:
--------------------------------------------------------------------------------
1 | import {
2 | ADD_VISITED_VIEWS,
3 | DEL_VISITED_VIEWS
4 | } from './mutation-types';
5 |
6 | export default {
7 | addVisitedViews({ commit }, view){
8 | commit(ADD_VISITED_VIEWS, view)
9 | },
10 | delVisitedViews({ commit, state }, view){
11 | return new Promise((resolve) => {
12 | commit(DEL_VISITED_VIEWS, view);
13 | resolve([...state.visitedViews])
14 | });
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/client/components/lock/index.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | 剩余50%的内容订阅后可查看
9 | 直接购买阅读全文
10 | {{price}}积分
11 |
--------------------------------------------------------------------------------
/server/app/controller/user.js:
--------------------------------------------------------------------------------
1 | const Base = require('./base');
2 |
3 | class UserController extends Base {
4 | async setInfo(){
5 | const {ctx, app} = this;
6 | const param = ctx.request.body;
7 | param.id = app.userId;
8 | let result = await ctx.service.user.update(param);
9 | if(result.affectedRows === 1){
10 | return this.success("更新成功");
11 | }else{
12 | return this.fail("更新失败");
13 | }
14 | }
15 | }
16 |
17 | module.exports = UserController;
--------------------------------------------------------------------------------
/server/app/middleware/interceptor.js:
--------------------------------------------------------------------------------
1 | module.exports = options => {
2 | return async function interceptor(ctx, next) {
3 | if(ctx.path == '/' || ctx.path == '/auth/loginByWeixinAction'){
4 |
5 | await next();
6 | return;
7 | }
8 | // 根据token值获取用户id
9 | ctx.app.token = ctx.header['x-booklist-token'] || '';
10 | let token =new ctx.helper.Token(ctx);
11 | ctx.app.userId = await token.getUserId();
12 | await next();
13 | };
14 | };
15 |
--------------------------------------------------------------------------------
/admin/src/store/user/actions.js:
--------------------------------------------------------------------------------
1 | import * as types from './mutation-types';
2 |
3 | export default {
4 | logOut({ commit }) {
5 | return new Promise(async (resolve, reject) => {
6 | // let result = await logout();
7 | commit('LOGOUT', '')
8 | localStorage.removeItem("userInfo")
9 | resolve()
10 | }).catch(error => {
11 | reject(error)
12 | })
13 | },
14 | getUserInfo({commit}, data) {
15 | commit('GET_USERINFO', data);
16 | }
17 | };
--------------------------------------------------------------------------------
/client/components/wallet/index.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ type == 1? '被订阅' : '订阅' }}
5 | 已支付
6 |
7 |
8 | {{type == 1? '+' : '-'}}{{price}}
9 | {{time}}
10 |
11 |
12 |
--------------------------------------------------------------------------------
/admin/.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 | "env": {
13 | "test": {
14 | "presets": ["env", "stage-2"],
15 | "plugins": ["transform-vue-jsx", "transform-es2015-modules-commonjs", "dynamic-import-node"]
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/server/app/middleware/admin_interceptor.js:
--------------------------------------------------------------------------------
1 | module.exports = options => {
2 | return async function adminInterceptor(ctx, next) {
3 | // if(ctx.path == '/' || ctx.path == '/auth/loginByWeixinAction'){
4 | // await next();
5 | // return;
6 | // }
7 | // // 根据token值获取用户id
8 | // ctx.app.token = ctx.header['x-bladmin-token'] || '';
9 | // let token =new ctx.helper.Token(ctx);
10 | // ctx.app.userId = await token.getUserId();
11 | await next();
12 | };
13 | };
--------------------------------------------------------------------------------
/server/app/service/enchashment.js:
--------------------------------------------------------------------------------
1 | const Service = require('egg').Service;
2 |
3 | class EnchashmentService extends Service {
4 | async newAndSave(credit, wechat){
5 | const {app, ctx} = this;
6 | const result = await app.mysql.insert("enchashment", {
7 | user_id: app.userId,
8 | enchashment_credit: credit,
9 | verify: 0,
10 | wechat: wechat,
11 | verify_time: ctx.helper.timest()
12 | });
13 | return result;
14 | }
15 | }
16 |
17 | module.exports = EnchashmentService;
--------------------------------------------------------------------------------
/client/pages/login/index.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 书单分享
7 | 大学专业课书单是一个专门提供各个大学学生的专业课书单的社区,在这里,您可以看到各个学校的各个专业的最全面的专业课书单。
8 |
9 |
10 | 请升级微信版本
11 |
12 |
13 |
--------------------------------------------------------------------------------
/server/.autod.conf.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | write: true,
5 | prefix: '^',
6 | plugin: 'autod-egg',
7 | test: [
8 | 'test',
9 | 'benchmark',
10 | ],
11 | dep: [
12 | 'egg',
13 | 'egg-scripts',
14 | ],
15 | devdep: [
16 | 'egg-ci',
17 | 'egg-bin',
18 | 'egg-mock',
19 | 'autod',
20 | 'autod-egg',
21 | 'eslint',
22 | 'eslint-config-egg',
23 | 'webstorm-disable-index',
24 | ],
25 | exclude: [
26 | './test/fixtures',
27 | './dist',
28 | ],
29 | };
30 |
31 |
--------------------------------------------------------------------------------
/server/app/service/admin/admin.js:
--------------------------------------------------------------------------------
1 | const Service = require('egg').Service;
2 |
3 | class AdminService extends Service{
4 | async insert(data){
5 | const result = await this.app.mysql.insert("admin", data);
6 | return result;
7 | }
8 |
9 | async getByUName(username){
10 | const result = await this.app.mysql.get("admin", {
11 | username: username
12 | });
13 |
14 | return result;
15 | }
16 |
17 | async update(data){
18 | await this.app.mysql.update("admin", data);
19 | }
20 | }
21 |
22 | module.exports = AdminService;
23 |
24 |
--------------------------------------------------------------------------------
/admin/src/router/permission.js:
--------------------------------------------------------------------------------
1 | import router from './index.js'
2 | import store from '../store'
3 |
4 | const whiteList = ['/login']// 不重定向白名单
5 |
6 | router.beforeEach((to, from, next) => {
7 | if(localStorage.getItem('token')){ // 判断是否有token
8 | if(to.path === '/login'){
9 | next({
10 | path: '/'
11 | })
12 | }else{
13 | next();
14 | }
15 | }else{
16 | if (whiteList.indexOf(to.path) !== -1) { // 在免登录白名单,直接进入
17 | next();
18 | }else{
19 | next({
20 | path: '/login'
21 | })
22 | }
23 |
24 | }
25 | });
26 |
27 |
--------------------------------------------------------------------------------
/client/components/booklist/index.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{title}}
5 |
6 |
7 | {{master}}
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/client/components/booklistnav/index.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{item.title}}
5 | 审核中
6 |
7 |
8 |
9 | 加载中...
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/admin/src/service/env.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 配置编译环境和线上环境之间的切换
3 | *
4 | * baseUrl: 域名地址
5 | * routerMode: 路由模式
6 | * imgBaseUrl: 图片所在域名地址
7 | *
8 | */
9 |
10 | let baseUrl = '';
11 | let routerMode = 'history';
12 | let imgBaseUrl = '';
13 |
14 |
15 | baseUrl = 'https://www.duolaimier.cn/';
16 |
17 | // baseUrl = 'http://192.168.5.105:8088/manage';
18 | // if (process.env.NODE_ENV == 'development') {
19 |
20 | // }else if(process.env.NODE_ENV == 'production'){
21 | // // baseUrl = 'http://cangdu.org:8001';
22 | // }
23 |
24 | export {
25 | baseUrl,
26 | routerMode,
27 | imgBaseUrl,
28 | }
--------------------------------------------------------------------------------
/server/test/app/controller/home.test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const { app, assert } = require('egg-mock/bootstrap');
4 |
5 | describe('test/app/controller/home.test.js', () => {
6 |
7 | it('should assert', function* () {
8 | const pkg = require('../../../package.json');
9 | assert(app.config.keys.startsWith(pkg.name));
10 |
11 | // const ctx = app.mockContext({});
12 | // yield ctx.service.xx();
13 | });
14 |
15 | it('should GET /', () => {
16 | return app.httpRequest()
17 | .get('/')
18 | .expect('hi, egg')
19 | .expect(200);
20 | });
21 | });
22 |
--------------------------------------------------------------------------------
/client/pages/collection/index.wxss:
--------------------------------------------------------------------------------
1 | /**index.wxss**/
2 | .course-blank,.course-data {
3 | display: flex;
4 | flex-direction: column;
5 | align-items: center;
6 | }
7 |
8 | .course-blank .blank-img{
9 | width:290rpx;
10 | height:240rpx;
11 | margin:240rpx auto 0;
12 | }
13 |
14 | .course__title{
15 | font-size: 36rpx;
16 | color: #24253D;
17 | margin-bottom: 30rpx;
18 | width: 670rpx;
19 | }
20 |
21 | .course-blank .blank-text{
22 | font-size:28rpx;
23 | line-height: 40rpx;
24 | margin-top:20rpx;
25 | }
26 |
27 | .blank-text .write-course{
28 | color:orange;
29 | }
30 |
31 |
--------------------------------------------------------------------------------
/server/app/controller/base.js:
--------------------------------------------------------------------------------
1 | const { Controller } = require('egg');
2 |
3 | class BaseController extends Controller{
4 | constructor(props) {
5 | super(props);
6 | }
7 | success(data, status) {
8 | let result = {
9 | error: "操作成功",
10 | msg: "操作成功",
11 | status: status || 0,
12 | result: data
13 | };
14 | this.ctx.body = result;
15 | }
16 | fail(data, status) {
17 | let result = {
18 | error: "操作失败",
19 | msg: "操作失败",
20 | status: status || -1,
21 | result: data
22 | };
23 | this.ctx.body = result;
24 | }
25 | }
26 |
27 |
28 | module.exports = BaseController;
--------------------------------------------------------------------------------
/server/app/controller/admin/base.js:
--------------------------------------------------------------------------------
1 | const { Controller } = require('egg');
2 |
3 | class BaseController extends Controller{
4 | constructor(props) {
5 | super(props);
6 | }
7 | success(data, status) {
8 | let result = {
9 | error: "操作成功",
10 | msg: "操作成功",
11 | status: status || 0,
12 | result: data
13 | };
14 | this.ctx.body = result;
15 | }
16 | fail(data, status) {
17 | let result = {
18 | error: "操作失败",
19 | msg: "操作失败",
20 | status: status || -1,
21 | result: data
22 | };
23 | this.ctx.body = result;
24 | }
25 | }
26 |
27 |
28 | module.exports = BaseController;
--------------------------------------------------------------------------------
/client/components/booklistnav/index.js:
--------------------------------------------------------------------------------
1 | // components/booklistnav/index.js
2 | Component({
3 | /**
4 | * 组件的属性列表
5 | */
6 | properties: {
7 | collections:{
8 | type:Array,
9 | value:[]
10 | },
11 | hasMore:{
12 | type:Boolean,
13 | value: false
14 | },
15 | loading:{
16 | type: Boolean,
17 | value: false
18 | }
19 | },
20 | ready(){
21 | console.log(this.properties.collections);
22 | },
23 | /**
24 | * 组件的初始数据
25 | */
26 | data: {
27 |
28 | },
29 |
30 | /**
31 | * 组件的方法列表
32 | */
33 | methods: {
34 |
35 | }
36 | })
37 |
--------------------------------------------------------------------------------
/admin/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 './router/permission.js'
7 | import ElementUI from 'element-ui';
8 | import 'element-ui/lib/theme-chalk/index.css';
9 | import store from './store/'
10 |
11 | Vue.use(ElementUI);
12 | Vue.config.productionTip = false
13 |
14 | /* eslint-disable no-new */
15 | new Vue({
16 | el: '#app',
17 | router,
18 | store,
19 | components: { App },
20 | template: ''
21 | })
22 |
--------------------------------------------------------------------------------
/server/utils/token.js:
--------------------------------------------------------------------------------
1 | const jwt = require('jsonwebtoken');
2 | const secret = 'FTD#@HBJRR@@gf';
3 | class Token {
4 | async create(userInfo){
5 | const token = jwt.sign(userInfo, secret);
6 | return token;
7 | }
8 |
9 | async parse() {
10 | if (app.token) {
11 | try {
12 | return jwt.verify(app.token, secret);
13 | } catch (err) {
14 | return null;
15 | }
16 | }
17 | return null;
18 | }
19 |
20 | async verify() {
21 | const result = await this.parse();
22 | if (think.isEmpty(result)) {
23 | return false;
24 | }
25 |
26 | return true;
27 | }
28 | }
29 | module.export = Token;
--------------------------------------------------------------------------------
/server/README.md:
--------------------------------------------------------------------------------
1 | # server
2 |
3 | booklist server
4 |
5 | ## QuickStart
6 |
7 |
8 |
9 | see [egg docs][egg] for more detail.
10 |
11 | ### Development
12 |
13 | ```bash
14 | $ npm i
15 | $ npm run dev
16 | $ open http://localhost:7001/
17 | ```
18 |
19 | ### Deploy
20 |
21 | ```bash
22 | $ npm start
23 | $ npm stop
24 | ```
25 |
26 | ### npm scripts
27 |
28 | - Use `npm run lint` to check code style.
29 | - Use `npm test` to run unit test.
30 | - Use `npm run autod` to auto detect dependencies upgrade, see [autod](https://www.npmjs.com/package/autod) for more detail.
31 |
32 |
33 | [egg]: https://eggjs.org
--------------------------------------------------------------------------------
/admin/README.md:
--------------------------------------------------------------------------------
1 | # admin
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 | # run unit tests
21 | npm run unit
22 |
23 | # run all tests
24 | npm test
25 | ```
26 |
27 | For a detailed explanation on how things work, check out the [guide](http://vuejs-templates.github.io/webpack/) and [docs for vue-loader](http://vuejs.github.io/vue-loader).
28 |
--------------------------------------------------------------------------------
/client/pages/login/index.js:
--------------------------------------------------------------------------------
1 | const user = require('../../service/user.js');
2 | const app = getApp();
3 | // pages/login/index.js
4 | Page({
5 |
6 | /**
7 | * 页面的初始数据
8 | */
9 | data: {
10 | canIUse: wx.canIUse('button.open-type.getUserInfo')
11 | },
12 | bindGetUserInfo: function (e) {
13 | user.loginByWeixin().then(res => {
14 | app.globalData.userInfo = res.result.userInfo;
15 | app.globalData.token = res.result.token;
16 | wx.redirectTo({
17 | url: '/pages/index/index'
18 | })
19 | }).catch((err) => {
20 | console.log(err)
21 | });
22 | },
23 |
24 | })
--------------------------------------------------------------------------------
/admin/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 |
--------------------------------------------------------------------------------
/client/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "pages": [
3 | "pages/index/index",
4 | "pages/details/index",
5 | "pages/myblist/index",
6 | "pages/collection/index",
7 | "pages/setting/index",
8 | "pages/editcourse/index",
9 | "pages/logs/logs",
10 | "pages/search/index",
11 | "pages/login/index",
12 | "pages/purchase/index",
13 | "pages/enchashment/index",
14 | "pages/wallet/index"
15 | ],
16 | "window": {
17 | "backgroundTextStyle": "light",
18 | "navigationBarBackgroundColor": "#fff",
19 | "navigationBarTitleText": "书单分享",
20 | "navigationBarTextStyle": "black"
21 | }
22 | }
--------------------------------------------------------------------------------
/client/pages/collection/index.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 我的收藏
6 |
7 |
8 |
9 | 还没有收藏课程哦~
10 |
11 |
12 |
13 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/admin/src/store/tag/mutations.js:
--------------------------------------------------------------------------------
1 | import {
2 | GET_NUMBER,
3 | ADD_VISITED_VIEWS,
4 | DEL_VISITED_VIEWS
5 | } from './mutation-types.js'
6 |
7 | export default{
8 | [GET_NUMBER](state, num){
9 | state.num = num;
10 | },
11 | [ADD_VISITED_VIEWS](state, view){
12 | if (state.visitedViews.some(v => v.path === view.path)) return;
13 | state.visitedViews.push({ name: view.name, path: view.path });
14 | },
15 | [DEL_VISITED_VIEWS](state, view){
16 | let index;
17 | for (const [i, v] of state.visitedViews.entries()) {
18 | if (v.path === view.path) {
19 | index = i
20 | break;
21 | }
22 | }
23 | state.visitedViews.splice(index, 1);
24 | }
25 | }
--------------------------------------------------------------------------------
/server/app/controller/admin/user.js:
--------------------------------------------------------------------------------
1 | const Base = require('./base');
2 |
3 | class UserController extends Base {
4 | async getUserList(){
5 | const {ctx} = this;
6 | const param = ctx.request.body;
7 | const result = await ctx.service.user.searchByTelWX(param);
8 | return this.success(result);
9 | }
10 |
11 | async updateCredit(){
12 | const {ctx} = this;
13 | const credit = ctx.request.body.credit;
14 | const id = ctx.request.body.id;
15 | const result = await ctx.service.user.updateCredit(id, credit);
16 | if(result.affectedRows === 1){
17 | return this.success("更新成功");
18 | }else{
19 | return this.fail("更新失败");
20 | }
21 | }
22 | }
23 |
24 | module.exports = UserController;
--------------------------------------------------------------------------------
/client/pages/login/index.wxss:
--------------------------------------------------------------------------------
1 | /* pages/login/index.wxss */
2 | .logo {
3 | padding-top: 25rpx;
4 | padding-bottom: 16rpx;
5 | }
6 | .logo image{
7 | width: 102rpx;
8 | height: 153rpx;
9 | }
10 | .title{
11 | color: #333;
12 | font-size: 36rpx;
13 | font-weight: bold;
14 | margin-bottom: 32rpx;
15 | }
16 | .intr{
17 | width: 670rpx;
18 | color: #333;
19 | font-size: 24rpx;
20 | letter-spacing: 1.61px;
21 | margin-bottom: 100rpx;
22 | }
23 | .btn{
24 | width: 686rpx;
25 | height: 92rpx;
26 | }
27 | .btn button{
28 | background-color: #00B92E;
29 | color: #FFF;
30 | }
31 | .btn .update{
32 | text-align: center;
33 | font-size: 30rpx;
34 | }
--------------------------------------------------------------------------------
/client/pages/myblist/index.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 我的书单
6 |
7 |
8 | 还没有添加书单哦,现在去写书单
9 |
10 |
11 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/client/project.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "项目配置文件。",
3 | "packOptions": {
4 | "ignore": []
5 | },
6 | "setting": {
7 | "urlCheck": false,
8 | "es6": true,
9 | "postcss": true,
10 | "minified": true,
11 | "newFeature": true
12 | },
13 | "compileType": "miniprogram",
14 | "libVersion": "2.0.9",
15 | "appid": "",
16 | "projectname": "booklist",
17 | "isGameTourist": false,
18 | "condition": {
19 | "search": {
20 | "current": -1,
21 | "list": []
22 | },
23 | "conversation": {
24 | "current": -1,
25 | "list": []
26 | },
27 | "game": {
28 | "currentL": -1,
29 | "list": []
30 | },
31 | "miniprogram": {
32 | "current": -1,
33 | "list": []
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/client/pages/purchase/index.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 我的订阅
6 |
7 |
8 | 还没有订阅专业书单哦,现在去订阅
9 |
10 |
11 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/client/pages/index/index.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 请输入专业名称
6 |
7 |
8 |
17 |
18 |
19 |
20 | 加载中...
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/server/README.zh-CN.md:
--------------------------------------------------------------------------------
1 | # server
2 |
3 | booklist server
4 |
5 | ## 快速入门
6 |
7 |
8 |
9 | 如需进一步了解,参见 [egg 文档][egg]。
10 |
11 | ### 本地开发
12 |
13 | ```bash
14 | $ npm i
15 | $ npm run dev
16 | $ open http://localhost:7001/
17 | ```
18 |
19 | ### 部署
20 |
21 | ```bash
22 | $ npm start
23 | $ npm stop
24 | ```
25 |
26 | ### 单元测试
27 |
28 | - [egg-bin] 内置了 [mocha], [thunk-mocha], [power-assert], [istanbul] 等框架,让你可以专注于写单元测试,无需理会配套工具。
29 | - 断言库非常推荐使用 [power-assert]。
30 | - 具体参见 [egg 文档 - 单元测试](https://eggjs.org/zh-cn/core/unittest)。
31 |
32 | ### 内置指令
33 |
34 | - 使用 `npm run lint` 来做代码风格检查。
35 | - 使用 `npm test` 来执行单元测试。
36 | - 使用 `npm run autod` 来自动检测依赖更新,详细参见 [autod](https://www.npmjs.com/package/autod) 。
37 |
38 |
39 | [egg]: https://eggjs.org
40 |
--------------------------------------------------------------------------------
/client/components/wallet/index.js:
--------------------------------------------------------------------------------
1 | const util = require('../../utils/util.js');
2 | // components/purchase/index.js
3 | Component({
4 | /**
5 | * 组件的属性列表
6 | */
7 | properties: {
8 | type: {
9 | type: Number,
10 | value: 1
11 | },
12 | price:{
13 | type: Number,
14 | value: 0
15 | },
16 | addTime:{
17 | type:Number,
18 | value: 0
19 | }
20 | },
21 |
22 | /**
23 | * 组件的初始数据
24 | */
25 | data: {
26 | time: ''
27 | },
28 | ready:function(){
29 | console.log(123,this.properties.addTime);
30 | this.setData({
31 | time: util.formatTime(new Date(this.properties.addTime*1000))
32 | });
33 | },
34 | /**
35 | * 组件的方法列表
36 | */
37 | methods: {
38 |
39 | }
40 | })
41 |
--------------------------------------------------------------------------------
/admin/test/unit/jest.conf.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 |
3 | module.exports = {
4 | rootDir: path.resolve(__dirname, '../../'),
5 | moduleFileExtensions: [
6 | 'js',
7 | 'json',
8 | 'vue'
9 | ],
10 | moduleNameMapper: {
11 | '^@/(.*)$': '/src/$1'
12 | },
13 | transform: {
14 | '^.+\\.js$': '/node_modules/babel-jest',
15 | '.*\\.(vue)$': '/node_modules/vue-jest'
16 | },
17 | snapshotSerializers: ['/node_modules/jest-serializer-vue'],
18 | setupFiles: ['/test/unit/setup'],
19 | mapCoverage: true,
20 | coverageDirectory: '/test/unit/coverage',
21 | collectCoverageFrom: [
22 | 'src/**/*.{js,vue}',
23 | '!src/main.js',
24 | '!src/router/index.js',
25 | '!**/node_modules/**'
26 | ]
27 | }
28 |
--------------------------------------------------------------------------------
/client/components/nav/index.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 写书单
6 |
7 |
8 |
9 |
10 |
13 |
14 | {{item.text}}
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/client/components/booklistnav/index.wxss:
--------------------------------------------------------------------------------
1 | /* components/booklistnav/index.wxss */
2 | .ftd-booklistnav_all{
3 | position: relative;
4 | width: 630rpx;
5 | padding: 17rpx 22rpx 17rpx 18rpx;
6 | font-size: 34rpx;
7 | color: #24253D;
8 | background: #F5F5F5;
9 | border-radius: 16rpx;
10 | margin-bottom: 20rpx;
11 | }
12 | .ftd-booklistnav_all navigator{
13 | width: 500rpx;
14 | overflow: hidden;
15 | text-overflow: ellipsis;
16 | white-space: nowrap;
17 | }
18 | .ftd-booklistnav_verify{
19 | position: absolute;
20 | right: 25rpx;
21 | top: 20rpx;
22 | font-size: 20rpx;
23 | border-radius: 2px;
24 | border: 1px solid #D73316;
25 | padding: 5rpx 10rpx;
26 | color: #D73316;
27 | }
28 |
29 | .bottomTips{
30 | font-size: 28rpx;
31 | text-align: center;
32 | padding: 50rpx;
33 | color: #ccc;
34 | }
--------------------------------------------------------------------------------
/client/pages/myblist/index.wxss:
--------------------------------------------------------------------------------
1 | /**index.wxss**/
2 | .course-blank,.course-data {
3 | display: flex;
4 | flex-direction: column;
5 | align-items: center;
6 | }
7 | .course__title{
8 | font-size: 36rpx;
9 | color: #24253D;
10 | margin-bottom: 30rpx;
11 | width: 670rpx;
12 | }
13 | .course-blank .blank-img{
14 | width:290rpx;
15 | height:240rpx;
16 | margin:240rpx auto 0;
17 | }
18 |
19 | .course-blank .blank-text{
20 | font-size:28rpx;
21 | line-height: 40rpx;
22 | margin-top:20rpx;
23 | }
24 |
25 | .blank-text .write-course{
26 | color:orange;
27 | }
28 |
29 | .course-data .course-list{
30 | width:652rpx;
31 | height: 82rpx;
32 | line-height: 82rpx;
33 | font-size: 34rpx;
34 | background-color:#F5F5F5;
35 | margin:30rpx auto 0;
36 | padding-left: 18rpx;
37 | border-radius: 16rpx;
38 | color: #24253D;
39 | }
--------------------------------------------------------------------------------
/client/pages/purchase/index.wxss:
--------------------------------------------------------------------------------
1 | /* pages/purchased/index.wxss */
2 | .course-blank,.course-data {
3 | display: flex;
4 | flex-direction: column;
5 | align-items: center;
6 | }
7 | .course__title{
8 | font-size: 36rpx;
9 | color: #24253D;
10 | margin-bottom: 30rpx;
11 | width: 670rpx;
12 | }
13 | .course-blank .blank-img{
14 | width:290rpx;
15 | height:240rpx;
16 | margin:240rpx auto 0;
17 | }
18 |
19 | .course-blank .blank-text{
20 | font-size:28rpx;
21 | line-height: 40rpx;
22 | margin-top:20rpx;
23 | }
24 |
25 | .blank-text .write-course{
26 | color:orange;
27 | }
28 |
29 | .course-data .course-list{
30 | width:652rpx;
31 | height: 82rpx;
32 | line-height: 82rpx;
33 | font-size: 34rpx;
34 | background-color:#F5F5F5;
35 | margin:30rpx auto 0;
36 | padding-left: 18rpx;
37 | border-radius: 16rpx;
38 | color: #24253D;
39 | }
--------------------------------------------------------------------------------
/client/pages/details/index.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | {{userInfo.username}}
8 | {{userInfo.undergraduate}}
9 |
10 |
11 | 编辑专业
12 |
13 |
14 |
15 | {{item.text}}
16 |
17 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/client/components/tip/index.js:
--------------------------------------------------------------------------------
1 | // components/tip/index.js
2 | Component({
3 | /**
4 | * 组件的属性列表
5 | */
6 | properties: {
7 |
8 | },
9 |
10 | /**
11 | * 组件的初始数据
12 | */
13 | data: {
14 | isShow: '',
15 | isGo: false,
16 | y:300+"px"
17 | },
18 |
19 | /**
20 | * 组件的方法列表
21 | */
22 | methods: {
23 | showTip(){
24 | const that = this;
25 | if(this.data.isGo == false){
26 | this.setData({
27 | isGo: true,
28 | isShow: 'animated bounceInRight'
29 | });
30 |
31 | setTimeout(function(){
32 | that.setData({
33 | isGo: false,
34 | isShow: 'animated fadeInLeft'
35 | });
36 | },4000);
37 | }else{
38 |
39 | }
40 | },
41 | dragTip(e){
42 | this.setData({
43 | y: e.touches[0].clientY+"px"
44 | });
45 | }
46 | }
47 | })
48 |
--------------------------------------------------------------------------------
/client/pages/setting/index.wxss:
--------------------------------------------------------------------------------
1 | .my-setting{
2 | display: flex;
3 | flex-direction: column;
4 | padding: 118rpx 0 0 0;
5 | }
6 | .setting-inp{
7 | width:684rpx;
8 | height: 100rpx;
9 | padding:0 32rpx;
10 | font-size:28rpx;
11 | line-height: 100rpx;
12 | color:#333333;
13 | background-color:#FFFFFF;
14 | }
15 | .setting-inp .label{
16 | float:left;
17 | /* width:490rpx; */
18 | }
19 | .setting-inp .inp-text{
20 | float:right;
21 | /* width:190rpx; */
22 | height: 100rpx;
23 | text-align: right;
24 | }
25 | .inp-text input{
26 | height: 100rpx;
27 | }
28 |
29 | .setting-login .login-btn{
30 | width:686rpx;
31 | height:93rpx;
32 | margin:80rpx auto 0;
33 | font-size:28rpx;
34 | line-height:93rpx;
35 | color:#FFFFFF;
36 | background-color:#D73316;
37 | border-radius: 8rpx;
38 | }
39 |
40 | .search-placeholder{
41 | font-size: 30rpx;
42 | color: rgba(36, 37, 61, .3);
43 | }
--------------------------------------------------------------------------------
/server/app/controller/enchashment.js:
--------------------------------------------------------------------------------
1 | const Base = require('./base');
2 |
3 | class EnchashmentController extends Base {
4 | async add(){
5 | const {ctx, app} = this;
6 | const credit = ctx.request.body.credit;
7 | const wechat = ctx.request.body.wechat;
8 |
9 | // 保证 credit 为数字
10 | if(!/^[0-9]*$/.test(credit))
11 | return this.fail("credit类型错误");
12 |
13 | const uInfo = await ctx.service.user.find({
14 | id: app.userId
15 | });
16 | const result = await ctx.service.user.update({
17 | id: app.userId,
18 | credit: uInfo.credit - credit
19 | });
20 |
21 | if(result.affectedRows === 1){
22 | const resultSave = await ctx.service.enchashment.newAndSave(credit, wechat);
23 | if(resultSave.affectedRows === 1)
24 | return this.success("提审成功");
25 | else
26 | return this.fail("已扣除积分,但提审失败");
27 | }else{
28 | return this.fail("提审失败")
29 | }
30 | }
31 | }
32 |
33 | module.exports = EnchashmentController;
--------------------------------------------------------------------------------
/server/app/controller/admin/review.js:
--------------------------------------------------------------------------------
1 | const Base = require('./base');
2 |
3 | class ReviewController extends Base {
4 | async getList(){
5 | const {ctx} = this;
6 | const result = await ctx.service.admin.review.search(ctx.request.body);
7 | return this.success(result);
8 | }
9 |
10 | async publish(){
11 | const {ctx} = this;
12 | const courseID = ctx.request.body.courseID;
13 | const result = await ctx.service.admin.review.isPublish(courseID, 1);
14 | if(result.affectedRows === 1){
15 | return this.success("发布成功");
16 | }else{
17 | return this.fail("发布失败");
18 | }
19 | }
20 |
21 | async revoke(){
22 | const {ctx} = this;
23 | const courseID = ctx.request.body.courseID;
24 | const result = await ctx.service.admin.review.isPublish(courseID, 0);
25 | if(result.affectedRows === 1){
26 | return this.success("撤销成功");
27 | }else{
28 | return this.fail("撤销失败");
29 | }
30 | }
31 | }
32 |
33 | module.exports = ReviewController;
--------------------------------------------------------------------------------
/server/app/controller/admin/admin.js:
--------------------------------------------------------------------------------
1 | const Base = require('./base');
2 | const bcrypt = require('bcrypt');
3 |
4 | class AdminController extends Base {
5 | async add(){
6 | const {ctx} = this;
7 | const username = "admin";
8 | const password = "888888";
9 | const addTime = ctx.helper.timest();
10 |
11 | // 密码加盐操作
12 | const saltRounds = 10;
13 | const salt = bcrypt.genSaltSync(saltRounds);
14 | const hash = bcrypt.hashSync(password, salt);
15 |
16 | if(!username || !password || !salt || !hash){
17 | return this.fail("参数无效");
18 | }
19 |
20 | // 把 salt 和 hash 存入数据库
21 | const result = await ctx.service.admin.admin.insert({
22 | username: username,
23 | password_hash: hash,
24 | password_salt: salt,
25 | add_time: addTime
26 | });
27 |
28 | if(result.affectedRows === 1){
29 | return this.success("插入成功");
30 | }else{
31 | return this.fail("插入失败");
32 | }
33 | }
34 | }
35 |
36 | module.exports = AdminController;
--------------------------------------------------------------------------------
/client/app.js:
--------------------------------------------------------------------------------
1 | var user = require('./service/user.js');
2 | //app.js
3 | App({
4 | onLaunch: function () {
5 | const that = this;
6 | // 查看是否授权
7 | user.checkLogin().then(res => {
8 | that.globalData.userInfo = wx.getStorageSync('userInfo');
9 | that.globalData.token = wx.getStorageSync('token');
10 | // if (that.employIdCallback) {
11 | // that.employIdCallback(wx.getStorageSync('userInfo'));
12 | // }
13 | }).catch(() => {
14 | user.loginByWeixin().then(res => {
15 | that.globalData.userInfo = res.result.userInfo;
16 | that.globalData.token = res.result.token;
17 | }).catch((err) => {
18 | console.log(err);
19 | });
20 | });
21 | },
22 | globalData: {
23 | userInfo: {
24 | username: '',
25 | avatar: '',
26 | undergraduate: '',
27 | master_school: '',
28 | wechat_id: '',
29 | tel_id:''
30 | },
31 | token: '',
32 | }
33 | })
--------------------------------------------------------------------------------
/client/pages/search/index.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 搜索
7 |
8 |
9 |
18 |
19 | 哎呀,结果溜走了,换个词试试
20 |
21 |
22 | 加载中...
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/client/pages/search/index.wxss:
--------------------------------------------------------------------------------
1 | /* pages/search/index.wxss */
2 | .search{
3 | display: flex;
4 | align-items: center;
5 | margin-bottom: 36rpx;
6 | }
7 | .search__input{
8 | height: 92rpx;
9 | width: 500rpx;
10 | background: #F5F5F5;
11 | border-radius: 16rpx;
12 | padding:0 30rpx;
13 | margin-right: 30rpx;
14 | }
15 | .search__btn{
16 | width: 100rpx;
17 | height: 72rpx;
18 | background: #D73316;
19 | color: #fff;
20 | font-size: 12px;
21 | line-height: 72rpx;
22 | text-align: center;
23 | border-radius: 16rpx;
24 | }
25 | .booklist{
26 | /* display: flex;
27 | justify-content: space-between;
28 | width: 670rpx;
29 | flex-wrap: wrap; */
30 | }
31 | .search__placeholder{
32 | font-size: 30rpx;
33 | color: rgba(36, 37, 61, .2);
34 | }
35 | .toptip{
36 | font-size: 28rpx;
37 | color: #ccc;
38 | padding-top: 100rpx;
39 | }
40 |
41 | .bottomTips{
42 | font-size: 28rpx;
43 | text-align: center;
44 | padding: 50rpx;
45 | color: #ccc;
46 | }
--------------------------------------------------------------------------------
/server/app/service/admin/review.js:
--------------------------------------------------------------------------------
1 | const Service = require('egg').Service;
2 |
3 | class ReviewService extends Service{
4 | async search(param){
5 | const {app} = this;
6 | const wechat_id = param.wechat_id || '';
7 | const tel_id = param.tel_id || '';
8 | const title = param.title || '';
9 | const publish = param.publish || 0;
10 | let str = `select u.username,u.wechat_id,u.tel_id,u.credit,c.title,c.my_course,c.publish,c.id from user as u left join course as c on u.id=c.user_id where u.wechat_id LIKE '%${wechat_id}%' AND u.tel_id LIKE '%${tel_id}%' AND c.title LIKE '%${title}%' AND c.publish LIKE '%${publish}%'`;
11 | const result = await app.mysql.query(str);
12 | return result;
13 | }
14 |
15 | async isPublish(courseID, type){
16 | const {app} = this;
17 | console.log(courseID, type);
18 | const result = await app.mysql.update('course', {
19 | id: courseID,
20 | publish: type
21 | });
22 | return result;
23 | }
24 | }
25 |
26 | module.exports = ReviewService;
--------------------------------------------------------------------------------
/server/app/service/admin/enchashment.js:
--------------------------------------------------------------------------------
1 | const Service = require('egg').Service;
2 |
3 | class EnchashmentService extends Service{
4 | async search(param){
5 | const {app} = this;
6 | const wechat_id = param.wechat_id || '';
7 | const tel_id = param.tel_id || '';
8 | const username = param.username || '';
9 | const verify = param.verify || 0;
10 | let str = `select u.username,u.wechat_id,u.tel_id,u.credit,e.enchashment_credit,e.verify,e.id from user as u left join enchashment as e on u.id=e.user_id where u.wechat_id LIKE '%${wechat_id}%' AND u.tel_id LIKE '%${tel_id}%' AND u.username LIKE '%${username}%' AND e.verify LIKE '%${verify}%'`;
11 | const result = await app.mysql.query(str);
12 | return result;
13 | }
14 |
15 | async isEnchashment(enchasmentID, type){
16 | const {app} = this;
17 | const result = await app.mysql.update('enchashment', {
18 | id: enchasmentID,
19 | verify: type
20 | });
21 | return result;
22 | }
23 | }
24 |
25 | module.exports = EnchashmentService;
--------------------------------------------------------------------------------
/client/pages/enchashment/index.wxss:
--------------------------------------------------------------------------------
1 | /* pages/enchashment/index.wxss */
2 | .form-box{
3 | width:100%;
4 | background-color: #fff;
5 | margin-top: 20rpx;
6 | }
7 | .form_row{
8 | width: 720rpx;
9 | height: 88rpx;
10 | line-height: 88rpx;
11 | margin-left: 30rpx;
12 | border-bottom: 1rpx solid #eee;
13 | display: flex;
14 | font-size: 28rpx;
15 | /*justify-content: space-between;*/
16 | }
17 | .form_label{
18 | width: 160rpx;
19 | color: #000
20 | }
21 | .form_right{
22 | flex: 1;
23 | height: 88rpx;
24 | line-height: 88rpx;
25 | }
26 | .form_input{
27 | height: 100%;
28 | font-size: 28rpx;
29 | padding-right: 30rpx;
30 | }
31 |
32 | .form_money{
33 | width: 720rpx;
34 | font-size: 14px;
35 | color: #b2b2b2;
36 | margin-top: 30rpx;
37 | margin-left: 30rpx;
38 | }
39 |
40 | .form-btn{
41 | width: 690rpx;
42 | height: 80rpx;
43 | margin-top:30rpx;
44 | font-size: 30rpx;
45 | border-radius: 100rpx;
46 | }
--------------------------------------------------------------------------------
/client/components/wallet/index.wxss:
--------------------------------------------------------------------------------
1 | /* components/purchase/index.wxss */
2 | .ftd-wallet{
3 | display: flex;
4 | height: 132rpx;
5 | margin-left: 30rpx;
6 | margin-right: 30rpx;
7 | -webkit-box-align: center;
8 | -moz-box-align: center;
9 | -ms-flex-align: center;
10 | align-items: center;
11 | border-bottom: 1px solid #e5e5e5;
12 | }
13 |
14 | .ftd-walletall_left{
15 | display: flex;
16 | flex-direction: column;
17 | }
18 |
19 | .ftd-walletall_title{
20 | font-size: 32rpx;
21 | color: #333333;
22 | }
23 |
24 | .ftd-wallet_status{
25 | font-size: 24rpx;
26 | color: #999999;
27 | }
28 |
29 | .ftd-wallet_right{
30 | margin-left: auto;
31 | display: flex;
32 | flex-direction: column;
33 | }
34 |
35 | .ftd-wallet_amount{
36 | color: #D73316;
37 | font-size: 32rpx;
38 | text-align: right;
39 | }
40 |
41 | .ftd-wallet_time{
42 | font-size: 24rpx;
43 | color: #999999;
44 | }
45 |
46 | .bottomTips{
47 | font-size: 28rpx;
48 | text-align: center;
49 | padding: 50rpx;
50 | color: #ccc;
51 | }
--------------------------------------------------------------------------------
/server/app/controller/admin/enchashment.js:
--------------------------------------------------------------------------------
1 | const Base = require('./base');
2 |
3 | class EnchashmentController extends Base {
4 | async getList(){
5 | const {ctx} = this;
6 | const result = await ctx.service.admin.enchashment.search(ctx.request.body);
7 | return this.success(result);
8 | }
9 |
10 | async enchashment(){
11 | const {ctx} = this;
12 | const enchashmentID = ctx.request.body.enchashmentID;
13 | const result = await ctx.service.admin.enchashment.isEnchashment(enchashmentID, 1);
14 | if(result.affectedRows === 1){
15 | return this.success("提现成功");
16 | }else{
17 | return this.fail("提现失败");
18 | }
19 | }
20 |
21 | async revoke(){
22 | const {ctx} = this;
23 | const enchashmentID = ctx.request.body.enchashmentID;
24 | const result = await ctx.service.admin.enchashment.isEnchashment(enchashmentID, 0);
25 | if(result.affectedRows === 1){
26 | return this.success("撤销成功");
27 | }else{
28 | return this.fail("撤销失败");
29 | }
30 | }
31 | }
32 |
33 | module.exports = EnchashmentController;
--------------------------------------------------------------------------------
/client/pages/details/index.wxss:
--------------------------------------------------------------------------------
1 | /**index.wxss**/
2 | .userinfo {
3 | display: flex;
4 | width: 670rpx;
5 | margin-bottom: 30rpx;
6 | }
7 |
8 | .userinfo__avatar {
9 | width: 100rpx;
10 | height: 100rpx;
11 | border-radius: 50%;
12 | margin-right: 26rpx;
13 | }
14 |
15 | .userinfo__nickname {
16 | color: #24253D ;
17 | font-size: 34rpx;
18 | margin-bottom: 10rpx;
19 | padding-top: 10rpx;
20 | }
21 |
22 | .userinfo__des{
23 | font-size: 24rpx;
24 | color: rgba(36,37,61,0.50);
25 | }
26 |
27 | .userinfo__edit{
28 | flex: 1;
29 | }
30 |
31 | .userinfo__btn{
32 | float: right;
33 | padding: 8rpx 15rpx;
34 | border: 1px solid #C7C7C7;
35 | border-radius: 50rpx;
36 | font-size: 26rpx;
37 | color: #C7C7C7;
38 | margin-top: 15rpx;
39 | }
40 |
41 | .usermotto {
42 | margin-top: 200px;
43 | }
44 |
45 | .booklist{
46 | width: 630rpx;
47 | padding: 17rpx 22rpx 17rpx 18rpx;
48 | font-size: 34rpx;
49 | color: #24253D;
50 | background: #F5F5F5;
51 | border-radius: 16rpx;
52 | margin-bottom: 20rpx;
53 | }
--------------------------------------------------------------------------------
/admin/src/service/api.js:
--------------------------------------------------------------------------------
1 | import fetch from './fetch'
2 |
3 | /**
4 | * 登录
5 | */
6 | export const login = data => fetch('auth/login', data);
7 |
8 | /**
9 | * 获取文章审查机制列表
10 | */
11 | export const getReviewList = data => fetch('admin/review/getList', data);
12 |
13 | /**
14 | * 发布
15 | */
16 | export const publish = data => fetch('admin/review/publish', data);
17 |
18 | /**
19 | * 撤销
20 | */
21 | export const revoke = data => fetch('admin/review/revoke', data);
22 |
23 | /**
24 | * 获取用户列表
25 | */
26 | export const getUserList = data => fetch('admin/user/getUserList', data);
27 |
28 | /**
29 | * 修改用户积分
30 | */
31 | export const updateCredit = data => fetch('admin/user/updateCredit', data);
32 |
33 | /**
34 | * 获取提现列表
35 | */
36 | export const getEnchaList = data => fetch('admin/enchashment/getList', data);
37 |
38 | /**
39 | * 提现
40 | */
41 | export const enchashment = data => fetch('admin/enchashment/enchashment', data);
42 |
43 | /**
44 | * 提现撤销
45 | */
46 | export const enchaRevoke = data => fetch('admin/enchashment/revoke', data);
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/server/app/router/admin.js:
--------------------------------------------------------------------------------
1 | module.exports = app => {
2 | const { router, controller } = app;
3 | const adminInterceptor = app.middleware.adminInterceptor();
4 | router.post('/admin/addAdmin', adminInterceptor, controller.admin.admin.add);
5 | router.post('/auth/login', adminInterceptor, controller.admin.auth.login);
6 | router.post('/admin/review/getList', adminInterceptor, controller.admin.review.getList);
7 | router.post('/admin/review/publish', adminInterceptor, controller.admin.review.publish);
8 | router.post('/admin/review/revoke', adminInterceptor, controller.admin.review.revoke);
9 | router.post('/admin/user/getUserList', adminInterceptor, controller.admin.user.getUserList);
10 | router.post('/admin/user/updateCredit', adminInterceptor, controller.admin.user.updateCredit);
11 | router.post('/admin/enchashment/getList', adminInterceptor, controller.admin.enchashment.getList);
12 | router.post('/admin/enchashment/enchashment', adminInterceptor, controller.admin.enchashment.enchashment);
13 | router.post('/admin/enchashment/revoke', adminInterceptor, controller.admin.enchashment.revoke);
14 | }
--------------------------------------------------------------------------------
/server/config/config.default.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = appInfo => {
4 | const config = exports = {};
5 |
6 | // use for cookie sign key, should change to your own and keep security
7 | config.keys = appInfo.name + '_1528019285960_9766';
8 |
9 | // add your config here
10 | config.middleware = [];
11 |
12 | // 小程序只能存storage,关闭csrf
13 | config.security = {
14 | csrf: {
15 | enable: false
16 | },
17 | };
18 |
19 | config.wx = {
20 | secret: '',
21 | appid: ''
22 | }
23 |
24 | config.mysql = {
25 | client: {
26 | host: 'cd-cdb-3c9c8yyp.sql.tencentcdb.com',
27 | // 端口号
28 | port: '63885',
29 | // 用户名
30 | user: 'root',
31 | // 密码
32 | password: '',
33 | // 数据库名
34 | database: '',
35 | },
36 | // 所有数据库配置的默认值
37 | default:{},
38 |
39 | // 是否加载到 app 上,默认开启
40 | app: true,
41 | // 是否加载到 agent 上,默认关闭
42 | agent: false
43 | }
44 |
45 | config.cors = {
46 | origin: '*',
47 | allowMethods: 'GET,HEAD,PUT,POST,DELETE,PATCH'
48 | };
49 |
50 | return config;
51 | };
52 |
--------------------------------------------------------------------------------
/client/pages/wallet/index.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | 账号剩余(积分)
9 | {{credit}}
10 |
11 | 充值
12 | 提现到微信
13 |
14 |
15 |
16 | 交易记录
17 |
18 |
24 |
25 |
26 |
27 | 加载中...
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/client/pages/collection/index.js:
--------------------------------------------------------------------------------
1 | const util = require('../../utils/util.js');
2 | const app=getApp()
3 | Page({
4 | data: {
5 | myCollection: [],
6 | page: 1,
7 | size: 14,
8 | hasMore: false,
9 | loading: false
10 | },
11 | loadMore:function(){
12 | const that = this;
13 | if (!this.data.hasMore) return;
14 | util.request('collect/listMyCollectRange', { page: this.data.page++, size: this.data.size}).then(res => {
15 | if (res.status === 0) {
16 | if (res.result.length) {
17 | that.setData({
18 | myCollection: that.data.myCollection.concat(res.result),
19 | loading: false
20 | });
21 | }else{
22 | that.setData({ hasMore: false })
23 | }
24 | }
25 | });
26 | },
27 | onLoad: function(){
28 | this.setData({ hasMore: true, loading: true })
29 | this.loadMore();
30 | },
31 | onReachBottom: function () {
32 | this.setData({
33 | loading: true
34 | })
35 | this.loadMore();
36 | },
37 | goToWrite: function () {
38 | wx.navigateTo({
39 | url: '../editcourse/index'
40 | });
41 | }
42 | })
--------------------------------------------------------------------------------
/client/components/lock/index.wxss:
--------------------------------------------------------------------------------
1 | /* components/lock/index.wxss */
2 | .ftd-lock{
3 | width: 750rpx;
4 | display: flex;
5 | flex-direction: column;
6 | align-items: center;
7 | box-shadow: 0 -70rpx 20px #fff
8 | }
9 | .content{
10 | width: 100%;
11 | height: 30rpx;
12 | }
13 | .ftd-lock .lock{
14 | position: relative;
15 | width: 670rpx;
16 | box-sizing: border-box;
17 | }
18 | .lock .lock_img{
19 | width: 48rpx;
20 | height: 48rpx;
21 | padding: 10rpx;
22 | background: #fff;
23 | position: absolute;
24 | left: 50%;
25 | top: -32rpx;
26 | margin-left: -32rpx;
27 | }
28 | .lock .lock_line{
29 | border-bottom: 1px solid #EDEDED;
30 | }
31 |
32 | .ftd-lock .intr{
33 | margin-top: 30rpx;
34 | font-size: 14px;
35 | color: #D73316;
36 | }
37 |
38 | .ftd-lock .tip{
39 | margin-top: 30rpx;
40 | font-size: 14px;
41 | color: #818181;
42 | }
43 |
44 | .ftd-lock .payBtn{
45 | margin-top: 25rpx;
46 | padding: 10rpx 48rpx;
47 | background: #FFFAFA;
48 | border: 1px solid #FF7055;
49 | border-radius: 100rpx;
50 | color: #D73316;
51 | font-size: 14px;
52 | }
--------------------------------------------------------------------------------
/client/components/booklist/index.wxss:
--------------------------------------------------------------------------------
1 | /* components/booklist/index.wxss */
2 | .ftd-booklist{
3 | position: relative;
4 | background: #F5F5F5;
5 | border-radius: 16rpx;
6 | width: 690rpx;
7 | height: 144rpx;
8 | font-size: 28rpx;
9 | color: #24253D;
10 | margin:0 auto;
11 | margin-bottom: 20rpx;
12 | }
13 | .ftd-booklist navigator{
14 | overflow: hidden;
15 | text-overflow: ellipsis;
16 | white-space: nowrap;
17 | width: 614rpx;
18 | height: 120rpx;
19 | padding-left: 30rpx;
20 | padding-top: 20rpx;
21 | font-size: 28rpx;
22 | color: #24253D;
23 | }
24 | .ftd-booklist__collectBackImage{
25 | position: absolute;
26 | width: 37rpx;
27 | height: 34rpx;
28 | top: 18rpx;
29 | right: 30rpx;
30 | }
31 | .ftd-booklist .ftd-booklist__head{
32 | position: absolute;
33 | width: 50rpx;
34 | height: 50rpx;
35 | border-radius: 25rpx;
36 | top: 74rpx;
37 | left: 30rpx;
38 | }
39 | .ftd-booklist__master{
40 | position: absolute;
41 | left: 98rpx;
42 | top: 83rpx;
43 | font-size: 24rpx;
44 | color: rgba(36,37,61,0.50);
45 | }
46 | .payCount{
47 | position: absolute;
48 | color: #949494;
49 | font-size: 22rpx;
50 | right: 30rpx;
51 | bottom: 28rpx;
52 | }
--------------------------------------------------------------------------------
/client/pages/purchase/index.js:
--------------------------------------------------------------------------------
1 | const app = getApp()
2 | const util = require('../../utils/util.js');
3 |
4 | Page({
5 | data: {
6 | myPurchase: [],
7 | page: 1,
8 | size: 30,
9 | hasMore: false,
10 | loading: false
11 | },
12 | loadMore:function(){
13 | const that = this;
14 | if (!this.data.hasMore) return;
15 | util.request('purchase/listMyPurchase', { page: this.data.page++, size: this.data.size }).then(res => {
16 | if (res.status === 0) {
17 | let result = res.result;
18 | if (result.length){
19 | result = util.changeArr(result, 'id', 'course_id');
20 | that.setData({
21 | myPurchase: that.data.myPurchase.concat(result),
22 | loading: false
23 | });
24 | }else{
25 | that.setData({ hasMore: false })
26 | }
27 | }
28 | });
29 | },
30 | onLoad: function () {
31 | this.setData({ hasMore: true, loading: true })
32 | this.loadMore();
33 | },
34 | onReachBottom: function () {
35 | this.setData({
36 | loading: true
37 | })
38 | this.loadMore();
39 | },
40 | goToWrite: function () {
41 | wx.reLaunch({
42 | url: '../index/index'
43 | });
44 | }
45 | })
--------------------------------------------------------------------------------
/client/components/lock/index.js:
--------------------------------------------------------------------------------
1 | const util = require('../../utils/util.js');
2 | // components/lock/index.js
3 | Component({
4 | /**
5 | * 组件的属性列表
6 | */
7 | properties: {
8 | price: {
9 | type: String,
10 | value: ""
11 | },
12 | courseID:{
13 | type: String,
14 | value: ""
15 | }
16 | },
17 |
18 | /**
19 | * 组件的初始数据
20 | */
21 | data: {
22 |
23 | },
24 |
25 | /**
26 | * 组件的方法列表
27 | */
28 | methods: {
29 | pay:function(){
30 | const that = this;
31 | util.request('purchase/addPurchase', { courseID: this.properties.courseID }).then(res => {
32 | if(res.status == 0){
33 | wx.showToast({
34 | title: res.result,
35 | icon: 'success',
36 | duration: 2000,
37 | complete: function () {
38 | that.triggerEvent("evenPay");
39 | }
40 | })
41 | }else{
42 | wx.showModal({
43 | title: "订阅失败",
44 | content: res.result,
45 | showCancel: false
46 | })
47 | }
48 | });
49 | }
50 | }
51 | })
52 |
--------------------------------------------------------------------------------
/client/pages/enchashment/index.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/client/pages/myblist/index.js:
--------------------------------------------------------------------------------
1 | const app=getApp()
2 | const util = require('../../utils/util.js');
3 |
4 | Page({
5 | data:{
6 | booklist:[],
7 | page: 1,
8 | size: 30,
9 | hasMore: false,
10 | loading: false
11 | },
12 | loadMore:function(){
13 | const that = this;
14 | if (!this.data.hasMore) return;
15 | util.request('course/listMyCourse',{ page: this.data.page++, size: this.data.size }).then(res => {
16 | if (res.status === 0) {
17 | let result = res.result;
18 | if (result.length){
19 | result = util.changeArr(result, 'id', 'course_id');
20 | that.setData({
21 | booklist: that.data.booklist.concat(result),
22 | loading: false
23 | });
24 | }else{
25 | that.setData({ hasMore: false })
26 | }
27 | }
28 | });
29 | },
30 | onLoad: function(){
31 | this.setData({ hasMore: true, loading: true })
32 | this.loadMore();
33 | },
34 | onReachBottom: function () {
35 | this.setData({
36 | loading: true
37 | })
38 | this.loadMore();
39 | },
40 | goToWrite: function(){
41 | wx.navigateTo({
42 | url: '../setting/index',
43 | url: '../editcourse/index'
44 | });
45 | }
46 | })
--------------------------------------------------------------------------------
/server/app/controller/collect.js:
--------------------------------------------------------------------------------
1 | const Base = require('./base');
2 |
3 | class CollectController extends Base {
4 | async listMyCollect(){
5 | const {ctx, app} = this;
6 | const result = await ctx.service.collect.listMyCollect();
7 | return this.success(result);
8 | }
9 |
10 | async listMyCollectRange(){
11 | const {ctx} = this;
12 | const page = ctx.request.body.page;
13 | const size = ctx.request.body.size;
14 | const result = await ctx.service.collect.listMyCollectRange(page, size);
15 | return this.success(result);
16 | }
17 |
18 | async addOrDeleteCollect(){
19 | const {ctx, app} = this;
20 | const courseID = ctx.request.body.courseID;
21 | const collect = await ctx.service.collect.getByUID(courseID);
22 | let collectRes = null;
23 | let handleType = 1;
24 | if(ctx.helper.isEmpty(collect)){
25 | // add
26 | collectRes = await ctx.service.collect.insert({
27 | user_id: app.userId,
28 | course_id: courseID
29 | });
30 | }else{
31 | // delete
32 | collectRes = await ctx.service.collect.delete(collect.id);
33 | handleType = 0;
34 | }
35 |
36 | if(collectRes){
37 | return this.success(handleType);
38 | }
39 |
40 | return this.fail("操作失败");
41 | }
42 | }
43 |
44 | module.exports = CollectController;
--------------------------------------------------------------------------------
/admin/src/utils/utils.js:
--------------------------------------------------------------------------------
1 | export function typeOf(obj) {
2 | const toString = Object.prototype.toString;
3 | const map = {
4 | '[object Boolean]': 'boolean',
5 | '[object Number]': 'number',
6 | '[object String]': 'string',
7 | '[object Function]': 'function',
8 | '[object Array]': 'array',
9 | '[object Date]': 'date',
10 | '[object RegExp]': 'regExp',
11 | '[object Undefined]': 'undefined',
12 | '[object Null]': 'null',
13 | '[object Object]': 'object'
14 | };
15 | return map[toString.call(obj)];
16 | }
17 |
18 | /**
19 | * 深拷贝
20 | */
21 | export const deepCopy = (obj) => {
22 | const t = typeOf(obj);
23 | let o;
24 |
25 | if (t === 'array') {
26 | o = [];
27 | } else if (t === 'object') {
28 | o = {};
29 | } else {
30 | return obj;
31 | }
32 |
33 | if (t === 'array') {
34 | for (let i = 0; i < obj.length; i++) {
35 | o.push(deepCopy(obj[i]));
36 | }
37 | } else if (t === 'object') {
38 | for (let i in obj) {
39 | o[i] = deepCopy(obj[i]);
40 | }
41 | }
42 | return o;
43 | }
44 |
45 | export const removeNull = function(obj){
46 | let o = deepCopy(obj);
47 | let param = {};
48 | for(let k in o){
49 | if(o[k] != null && o[k] != undefined && o[k] != '' ){
50 | param[k] = o[k];
51 | }
52 | }
53 | return param;
54 | }
--------------------------------------------------------------------------------
/admin/src/pages/manage/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 首页
7 |
8 | 内容管理
9 | 文章审核机制
10 |
11 |
12 | 用户管理
13 | 用户信息
14 | 用户提现记录
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
39 |
--------------------------------------------------------------------------------
/server/app/controller/admin/auth.js:
--------------------------------------------------------------------------------
1 | const Base = require('./base');
2 | const bcrypt = require('bcrypt');
3 |
4 | class AuthController extends Base {
5 | constructor(props) {
6 | super(props);
7 | this.token =new this.ctx.helper.Token(this.ctx);
8 | }
9 | async login(){
10 | const {ctx} = this;
11 | const username = ctx.request.body.userName;
12 | const password = ctx.request.body.password;
13 | const admin = await ctx.service.admin.admin.getByUName(username);
14 | if (ctx.helper.isEmpty(admin)) {
15 | return this.fail('不存在该用户');
16 | }
17 | // 解盐
18 | if (!bcrypt.compareSync(password, admin.password_hash)) {
19 | return this.fail('密码不正确');
20 | }
21 |
22 | // 更新登录信息
23 | await ctx.service.admin.admin.update({
24 | id: admin.id,
25 | last_login_time: ctx.helper.timest(),
26 | last_login_ip: ctx.ip
27 | });
28 |
29 | const sessionKey = await this.token.create({
30 | user_id: admin.id
31 | });
32 |
33 | if (ctx.helper.isEmpty(sessionKey)) {
34 | return this.fail('登录失败');
35 | }
36 |
37 | const userInfo = {
38 | id: admin.id,
39 | username: admin.username
40 | };
41 |
42 | return this.success({ token: sessionKey, userInfo: userInfo });
43 | }
44 | }
45 |
46 | module.exports = AuthController;
--------------------------------------------------------------------------------
/server/app/controller/purchase.js:
--------------------------------------------------------------------------------
1 | const Base = require('./base');
2 |
3 | class PurchaseController extends Base {
4 | async addPurchase(){
5 | const {ctx, app} = this;
6 | const courseID = ctx.request.body.courseID;
7 | const status = await ctx.service.purchase.addPurchase(courseID);
8 | if(status == 1){
9 | this.success("订阅成功");
10 | }else{
11 | this.fail("账户积分不足,请加客服微信fcsboy,进行充值");
12 | }
13 | }
14 |
15 | async listMyPurchase(){
16 | const {ctx, app} = this;
17 | const page = ctx.request.body.page;
18 | const size = ctx.request.body.size;
19 | const result = await ctx.service.purchase.selectMyPurchase(page, size);
20 | return this.success(result);
21 | }
22 |
23 | async listMyPurchaseAboutMe(){
24 | const {ctx, app} = this;
25 | const page = ctx.request.body.page;
26 | const size = ctx.request.body.size;
27 | const result = await ctx.service.purchase.selectPurchaseAboutMe(page, size);
28 | const userInfo = await ctx.service.user.find({
29 | id: app.userId
30 | });
31 | let param = {
32 | list: result,
33 | credit: userInfo.credit
34 | }
35 |
36 | return this.success(param);
37 | }
38 |
39 | async getPayCount(){
40 | const {ctx, app} = this;
41 | const result = await ctx.service.purchase.getPayCount();
42 | return result;
43 | }
44 | }
45 |
46 | module.exports = PurchaseController;
--------------------------------------------------------------------------------
/server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "server",
3 | "version": "1.0.0",
4 | "description": "booklist server",
5 | "private": true,
6 | "dependencies": {
7 | "bcrypt": "^2.0.1",
8 | "egg": "^2.2.1",
9 | "egg-cors": "^2.0.0",
10 | "egg-mysql": "^3.0.0",
11 | "egg-scripts": "^2.5.0",
12 | "jsonwebtoken": "^8.3.0",
13 | "log4js": "^3.0.5"
14 | },
15 | "devDependencies": {
16 | "autod": "^3.0.1",
17 | "autod-egg": "^1.0.0",
18 | "egg-bin": "^4.3.5",
19 | "egg-ci": "^1.8.0",
20 | "egg-mock": "^3.14.0",
21 | "eslint": "^4.11.0",
22 | "eslint-config-egg": "^6.0.0",
23 | "webstorm-disable-index": "^1.2.0"
24 | },
25 | "engines": {
26 | "node": ">=8.9.0"
27 | },
28 | "scripts": {
29 | "start": "egg-scripts start --sticky",
30 | "stop": "egg-scripts stop --title=egg-server-server",
31 | "dev": "egg-bin dev --sticky",
32 | "debug": "egg-bin debug",
33 | "test": "npm run lint -- --fix && npm run test-local",
34 | "test-local": "egg-bin test",
35 | "cov": "egg-bin cov",
36 | "lint": "eslint .",
37 | "ci": "npm run lint && npm run cov",
38 | "autod": "autod"
39 | },
40 | "ci": {
41 | "version": "8"
42 | },
43 | "repository": {
44 | "type": "git",
45 | "url": ""
46 | },
47 | "author": "xiaoxiaoxiao",
48 | "license": "MIT"
49 | }
50 |
--------------------------------------------------------------------------------
/server/app/service/collect.js:
--------------------------------------------------------------------------------
1 | const Service = require('egg').Service;
2 |
3 | class CollectService extends Service{
4 | async getByUID(courseID){
5 | const {app} = this;
6 | const result = await app.mysql.get('collect', {user_id: app.userId, course_id: courseID})
7 | return result;
8 | }
9 |
10 | async selectByUID(){
11 | const result = await this.app.mysql.select('collect', {
12 | where: {user_id: this.app.userId}
13 | });
14 | return result;
15 | }
16 |
17 | async insert(data){
18 | const result = await this.app.mysql.insert('collect', data);
19 | return result;
20 | }
21 |
22 | async delete(id){
23 | const result = await this.app.mysql.delete('collect', {
24 | id: id,
25 | });
26 | return result;
27 | }
28 |
29 | async listMyCollect(){
30 | const {app} = this;
31 | const result = await app.mysql.query('select * from course left join collect on course.id=collect.course_id where collect.user_id='+app.userId+';');
32 | return result;
33 | }
34 |
35 | async listMyCollectRange(page, size){
36 | const {app} = this;
37 | const result = await app.mysql.query('select collect.course_id,course.title,course.publish from course left join collect on course.id=collect.course_id where collect.user_id='+app.userId+' LIMIT '+(page-1)*size+','+size);
38 | return result;
39 | }
40 | }
41 |
42 | module.exports = CollectService;
--------------------------------------------------------------------------------
/admin/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 |
--------------------------------------------------------------------------------
/client/pages/wallet/index.wxss:
--------------------------------------------------------------------------------
1 | /* pages/wallet/index.wxss */
2 | .container{
3 | background: #f7f7f7;
4 | }
5 | .main{
6 | position: relative;
7 | background-color: white;
8 | width: 750rpx;
9 | height: 455rpx;
10 | text-align: center;
11 | }
12 | .main_img{
13 | margin-top: 50rpx;
14 | font-size:0;
15 | }
16 | .main_img image{
17 | width: 82rpx;
18 | height: 82rpx;
19 | }
20 | .main_intr{
21 | font-size: 24rpx;
22 | color: #999999;
23 | margin-top: 30rpx;
24 | }
25 | .main_credit{
26 | font-size: 44rpx;
27 | color: #24253D;
28 | }
29 | .main_btn{
30 | display: flex;
31 | padding: 50rpx 68rpx 0;
32 | justify-content: space-between;
33 | }
34 | .main_recharge{
35 | width: 288rpx;
36 | height: 88rpx;
37 | line-height: 88rpx;
38 | background: #D73316;
39 | border-radius: 8rpx;
40 | font-size: 28rpx;
41 | color: #FFFFFF;
42 | }
43 |
44 | .main_gowx{
45 | font-size: 28rpx;
46 | width: 288rpx;
47 | height: 88rpx;
48 | line-height: 88rpx;
49 | border: 1px solid #D73316;
50 | border-radius: 8rpx;
51 | color: #D73316;
52 | }
53 | .list{
54 | width: 750rpx;
55 | }
56 | .list_title{
57 | font-size: 24rpx;
58 | color: #999999;
59 | padding: 35rpx 0 35rpx 35rpx;
60 | }
61 |
62 | .list_content{
63 | background-color: white;
64 | }
65 |
66 | .bottomTips{
67 | font-size: 28rpx;
68 | text-align: center;
69 | padding: 50rpx;
70 | color: #ccc;
71 | }
--------------------------------------------------------------------------------
/client/service/user.js:
--------------------------------------------------------------------------------
1 | const api = require('./api.js');
2 | const util = require('../utils/util.js');
3 |
4 | /**
5 | * 调用微信登录
6 | */
7 | const loginByWeixin = ()=> {
8 | let code = null;
9 | return new Promise(function (resolve, reject) {
10 | return util.login().then((res) => {
11 | code = res.code;
12 | return util.getUserInfo();
13 | }).then((userInfo) => {
14 | //登录远程服务器
15 | util.request("auth/loginByWeixinAction", { code: code, userInfo: userInfo }, 'POST').then(res => {
16 | if (res.status === 0) {
17 | //存储用户信息
18 | wx.setStorageSync('userInfo', res.result.userInfo);
19 | wx.setStorageSync('token', res.result.token);
20 | resolve(res);
21 | } else {
22 | reject(res);
23 | }
24 | }).catch((err) => {
25 | reject(err);
26 | });
27 | }).catch((err) => {
28 | reject(err);
29 | })
30 | });
31 | }
32 |
33 |
34 | /**
35 | * 判断用户是否登录
36 | */
37 | const checkLogin = ()=> {
38 | return new Promise(function (resolve, reject) {
39 | if (wx.getStorageSync('userInfo') && wx.getStorageSync('token')) {
40 | util.checkSession().then(() => {
41 | resolve(true);
42 | }).catch(() => {
43 | reject(false);
44 | });
45 |
46 | } else {
47 | reject(false);
48 | }
49 | });
50 | }
51 |
52 | module.exports = {
53 | loginByWeixin,
54 | checkLogin,
55 | };
--------------------------------------------------------------------------------
/client/pages/editcourse/index.wxml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | 专业
7 |
9 |
10 |
11 |
12 |
13 | x
14 | {{item.name}}
15 |
16 |
17 |
18 | 书名
19 |
21 |
22 |
23 | 作者
24 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/admin/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 |
--------------------------------------------------------------------------------
/admin/src/pages/usermanage/userinfo/index.js:
--------------------------------------------------------------------------------
1 | import {getUserList, updateCredit} from "@/service/api";
2 | import {removeNull} from "@/utils/utils"
3 | export default {
4 | data(){
5 | return{
6 | listQuery:{
7 | wechat_id: undefined,
8 | tel_id: undefined,
9 | username: undefined
10 | },
11 | list:[]
12 | }
13 | },
14 | mounted(){
15 | this.handleCtr();
16 | },
17 | methods:{
18 | async handleCtr(){
19 | const result = await this.getListMode();
20 | if(result.status == 0)
21 | this.list = result.result;
22 |
23 | },
24 | async getListMode(){
25 | const param = removeNull(this.listQuery);
26 | let result = await getUserList(param);
27 | if(result.status != 0){}
28 | result.result = result.result.map(v => {
29 | this.$set(v, 'edit', false) // https://vuejs.org/v2/guide/reactivity.html
30 | v.originalTitle = v.credit // will be used when user click the cancel botton
31 | return v
32 | })
33 | return result;
34 | },
35 | cancelEdit(row) {
36 | row.credit = row.originalTitle
37 | row.edit = false
38 | this.$message({
39 | message: '取消修改',
40 | type: 'warning'
41 | })
42 | },
43 | async confirmEdit(row) {
44 | console.log(row.id, row.credit);
45 | const result = await updateCredit({
46 | id:row.id,
47 | credit:row.credit
48 | });
49 | if(result.status == 0){
50 | row.edit = false
51 | row.originalTitle = row.credit
52 | this.$message({
53 | message: result.result,
54 | type: 'success'
55 | })
56 | }
57 | }
58 | }
59 | }
--------------------------------------------------------------------------------
/client/pages/wallet/index.js:
--------------------------------------------------------------------------------
1 | const util = require('../../utils/util.js');
2 | // pages/wallet/index.js
3 | Page({
4 |
5 | /**
6 | * 页面的初始数据
7 | */
8 | data: {
9 | listPurchase: [],
10 | credit: '',
11 | page: 1,
12 | size: 30,
13 | hasMore: false,
14 | loading: false
15 | },
16 | loadMore:function(){
17 | const that = this;
18 | if (!this.data.hasMore) return;
19 | util.request('purchase/listMyPurchaseAboutMe', { page: this.data.page++, size: this.data.size }).then(res => {
20 | if (res.status === 0) {
21 | let result = res.result;
22 | if (result.list.length) {
23 | that.setData({
24 | listPurchase: that.data.listPurchase.concat(result.list),
25 | loading: false
26 | });
27 | } else {
28 | that.setData({ hasMore: false })
29 | }
30 | that.setData({
31 | credit: result.credit
32 | });
33 | }
34 | });
35 | },
36 | /**
37 | * 生命周期函数--监听页面加载
38 | */
39 | onLoad: function (options) {
40 | this.setData({ hasMore: true, loading: true })
41 | this.loadMore();
42 | },
43 | onReachBottom: function () {
44 | this.setData({
45 | loading: true
46 | })
47 | this.loadMore();
48 | },
49 | showCue:function(){
50 | wx.showModal({
51 | content: "加客服微信「 fcsboy 」,再进行充值",
52 | confirmText: "确定",
53 | showCancel: false
54 | });
55 | }
56 | })
--------------------------------------------------------------------------------
/server/app/router/client.js:
--------------------------------------------------------------------------------
1 | module.exports = app => {
2 | const { router, controller } = app;
3 | const interceptor = app.middleware.interceptor();
4 | router.post('/auth/loginByWeixinAction', interceptor, controller.auth.loginByWeixinAction);
5 | router.post('/course/addCourse', interceptor, controller.course.addCourse);
6 | router.post('/course/listMyCourse', interceptor, controller.course.listMyCourse);
7 | router.post('/course/listCourseDetail', interceptor, controller.course.listCourseDetail);
8 | router.post('/course/listAllCourse', interceptor, controller.course.listAllCourse);
9 | router.post('/collect/addOrDeleteCollect', interceptor, controller.collect.addOrDeleteCollect);
10 | router.post('/collect/listMyCollect', interceptor, controller.collect.listMyCollect);
11 | router.post('/collect/listMyCollectRange', interceptor, controller.collect.listMyCollectRange);
12 | router.post('/course/listSearchCourse', interceptor, controller.course.listSearchCourse);
13 | router.post('/user/setInfo', interceptor, controller.user.setInfo);
14 | router.post('/purchase/addPurchase', interceptor, controller.purchase.addPurchase);
15 | router.post('/purchase/listMyPurchase', interceptor, controller.purchase.listMyPurchase);
16 | router.post('/purchase/listMyPurchaseAboutMe', interceptor, controller.purchase.listMyPurchaseAboutMe);
17 | router.post('/enchashment/add', interceptor, controller.enchashment.add);
18 | router.post('/course/update', interceptor, controller.course.update);
19 | router.post('/purchase/getPayCount', interceptor, controller.purchase.getPayCount);
20 |
21 | }
--------------------------------------------------------------------------------
/admin/src/pages/qualitymanage/review/index.js:
--------------------------------------------------------------------------------
1 | import {getReviewList, publish, revoke} from "@/service/api"
2 | import {removeNull} from "@/utils/utils"
3 | export default {
4 | data(){
5 | return{
6 | listQuery:{
7 | tel_id: null,
8 | publish: '0',
9 | wechat_id: null,
10 | title: null,
11 | },
12 | publishOptions:[{
13 | key: '0',
14 | label: '未审核'
15 | },{
16 | key: '1',
17 | label: '已发布'
18 | }],
19 | list: []
20 | }
21 | },
22 | mounted(){
23 | this.handleCtr();
24 | },
25 | methods:{
26 | async handleCtr(){
27 | const result = await this.getListMode();
28 | if(result.status == 0)
29 | this.list = result.result;
30 | },
31 | async getListMode(){
32 | const param = removeNull(this.listQuery);
33 | let result = await getReviewList(param);
34 | return result;
35 | },
36 | async publish(index, row){
37 | const result = await publish({
38 | courseID: row.id
39 | });
40 | if(result.status == 0){
41 | this.$message({
42 | message: result.result,
43 | type: 'success'
44 | });
45 | this.list.splice(index, 1);
46 | }
47 | },
48 | async revoke(index, row){
49 | const result = await revoke({
50 | courseID: row.id
51 | });
52 | if(result.status == 0){
53 | this.$message({
54 | message: result.result,
55 | type: 'success'
56 | });
57 | this.list.splice(index, 1);
58 | }
59 | }
60 | },
61 | filters:{
62 | statusFilter(status){
63 | let str = "";
64 | if(status == 0){
65 | str = "审核中"
66 | }else{
67 | str = "已发布";
68 | }
69 | return str;
70 | }
71 | }
72 | }
--------------------------------------------------------------------------------
/admin/src/pages/manage/navBar.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 首页
5 | {{item}}
6 |
7 |
8 |
9 |
10 |
11 | {{userName}}
12 |
13 |
14 | 退出登录
15 |
16 |
17 |
18 |
19 |
20 |
43 |
65 |
--------------------------------------------------------------------------------
/client/pages/search/index.js:
--------------------------------------------------------------------------------
1 | const util = require('../../utils/util.js');
2 | // pages/search/index.js
3 | Page({
4 |
5 | /**
6 | * 页面的初始数据
7 | */
8 | data: {
9 | inputValue: '',
10 | courseList: [],
11 | showTip: false,
12 | page: 1,
13 | size: 30,
14 | hasMore: false,
15 | loading:false
16 | },
17 |
18 | loadMore:function(){
19 | const that = this;
20 | if (!this.data.hasMore) return;
21 | util.request("course/listSearchCourse", { title: that.data.inputValue, page: this.data.page++, size: this.data.size}).then(res => {
22 | if (res.status === 0) {
23 | if (res.result.length){
24 | that.setData({
25 | courseList: that.data.courseList.concat(res.result),
26 | loading:false
27 | });
28 | }else{
29 | that.setData({ hasMore: false })
30 | }
31 | if (that.data.courseList.length == 0){
32 | that.setData({
33 | showTip: true
34 | });
35 | }
36 | }
37 | });
38 | },
39 | /**
40 | * 页面上拉触底事件的处理函数
41 | */
42 | onReachBottom: function () {
43 | this.setData({
44 | loading: true
45 | })
46 | this.loadMore();
47 | },
48 | /**
49 | * 用户点击右上角分享
50 | */
51 | onShareAppMessage: function () {
52 |
53 | },
54 | bindKeyInput: function(e){
55 | this.setData({
56 | inputValue: e.detail.value
57 | });
58 | },
59 | search: function(){
60 | this.setData({
61 | courseList: [],
62 | page: 1
63 | });
64 | this.setData({
65 | hasMore: true,
66 | loading:true,
67 | showTip: false
68 | });
69 | this.loadMore();
70 | }
71 | })
--------------------------------------------------------------------------------
/client/pages/index/index.js:
--------------------------------------------------------------------------------
1 | const util = require('../../utils/util.js');
2 | //获取应用实例
3 | const app = getApp()
4 |
5 | Page({
6 | data: {
7 | page: 1,
8 | size: 30,
9 | courseList:[],
10 | hasMore: false,
11 | loading: false
12 | },
13 | //事件处理函数
14 | bindViewTap: function() {
15 | wx.navigateTo({
16 | url: '../myblist/index'
17 | })
18 | },
19 | loadMore: function () {
20 | const that = this;
21 | if (!this.data.hasMore) return;
22 | util.request('course/listAllCourse', { page:this.data.page++, size: this.data.size}).then(res => {
23 | if (res.status === 0) {
24 | wx.stopPullDownRefresh();
25 | if (res.result.length) {
26 | that.setData({
27 | courseList: that.data.courseList.concat(res.result),
28 | loading: false
29 | });
30 | }else{
31 | that.setData({ hasMore: false })
32 | }
33 |
34 | }
35 | });
36 | },
37 | onLoad: function () {
38 | this.setData({ hasMore: true, loading:true})
39 | this.loadMore();
40 | },
41 | getUserInfo: function(e) {
42 | app.globalData.userInfo = e.detail.userInfo
43 | this.setData({
44 | userInfo: e.detail.userInfo,
45 | hasUserInfo: true
46 | });
47 | },
48 | onReachBottom() {
49 | this.setData({
50 | loading: true
51 | })
52 | this.loadMore()
53 | },
54 | /**
55 | * 页面相关事件处理函数--监听用户下拉动作
56 | */
57 | onPullDownRefresh() {
58 | this.setData({
59 | courseList:[],
60 | page: 1
61 | });
62 | this.setData({
63 | hasMore:true,
64 | loading: true
65 | });
66 | this.loadMore();
67 | }
68 | })
69 |
--------------------------------------------------------------------------------
/admin/src/pages/usermanage/enchashment/index.js:
--------------------------------------------------------------------------------
1 | import {getEnchaList, enchashment, enchaRevoke} from "@/service/api"
2 | import {removeNull} from "@/utils/utils"
3 | export default {
4 | data(){
5 | return{
6 | listQuery:{
7 | tel_id: null,
8 | verify: '0',
9 | wechat_id: null,
10 | username: null,
11 | },
12 | enchashmentOptions:[{
13 | key: '0',
14 | label: '未提现'
15 | },{
16 | key: '1',
17 | label: '已提现'
18 | }],
19 | list: []
20 | }
21 | },
22 | mounted(){
23 | this.handleCtr();
24 | },
25 | methods:{
26 | async handleCtr(){
27 | const result = await this.getListMode();
28 | if(result.status == 0)
29 | this.list = result.result;
30 | },
31 | async getListMode(){
32 | const param = removeNull(this.listQuery);
33 | let result = await getEnchaList(param);
34 | return result;
35 | },
36 | async enchashment(index, row){
37 | const result = await enchashment({
38 | enchashmentID: row.id
39 | });
40 | if(result.status == 0){
41 | this.$message({
42 | message: result.result,
43 | type: 'success'
44 | });
45 | this.list.splice(index, 1);
46 | }
47 | },
48 | async revoke(index, row){
49 | const result = await enchaRevoke({
50 | enchashmentID: row.id
51 | });
52 | if(result.status == 0){
53 | this.$message({
54 | message: result.result,
55 | type: 'success'
56 | });
57 | this.list.splice(index, 1);
58 | }
59 | }
60 | },
61 | filters:{
62 | statusFilter(status){
63 | let str = "";
64 | if(status == 0){
65 | str = "审核中"
66 | }else{
67 | str = "已提现";
68 | }
69 | return str;
70 | },
71 | calculatCash(val){
72 | return (val/2*0.7).toFixed(2);
73 | }
74 | }
75 | }
--------------------------------------------------------------------------------
/admin/src/pages/manage/tabsView.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{tag.name}}
6 |
7 |
8 |
9 |
10 |
11 |
59 |
60 |
--------------------------------------------------------------------------------
/admin/src/router/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Router from 'vue-router'
3 | Vue.use(Router)
4 |
5 | // v1.0.0
6 | const manage = r => require.ensure([], () => r(require('@/pages/manage')), 'manage');
7 | const wxk = r => require.ensure([], () => r(require('@/pages/wxk/')), 'wxk');
8 | const login = r => require.ensure([], () => r(require('@/pages/login/')), 'login'); //登录
9 | const review = r => require.ensure([], () => r(require('@/pages/qualitymanage/review/index.vue')), 'review'); // 文章审核机制
10 | const enchashment = r => require.ensure([], () => r(require('@/pages/usermanage/enchashment/index.vue')), 'enchashment'); // 提现
11 | const userInfo = r => require.ensure([], () => r(require('@/pages/usermanage/userinfo/index.vue')), 'userinfo'); // 提现
12 |
13 | export default new Router({
14 | mode: 'history',
15 | routes: [
16 | {
17 | path: '/',
18 | redirect: '/manage'
19 | },
20 | {
21 | path: '*',
22 | redirect: '/'
23 | },
24 | {
25 | path: '/login',
26 | name: '登录',
27 | component: login
28 | },
29 | {
30 | path: '/manage',
31 | component: manage,
32 | children: [{
33 | path: '/review',
34 | component: review,
35 | name: '文章审核机制',
36 | meta: {nav: ['内容管理', '文章审核机制']}
37 | },{
38 | path: '/enchashment',
39 | component: enchashment,
40 | name: '用户提现记录',
41 | meta: {nav: ['用户管理', '用户提现记录']}
42 | },{
43 | path: '/userinfo',
44 | component: userInfo,
45 | name: '用户信息',
46 | meta: {nav: ['用户管理', '用户信息']}
47 | }]
48 | }
49 | ]
50 | })
51 |
--------------------------------------------------------------------------------
/server/app/service/user.js:
--------------------------------------------------------------------------------
1 | const Service = require('egg').Service;
2 | class UserService extends Service {
3 | async find(data){
4 | const result = await this.app.mysql.get('user', data);
5 | let obj;
6 | if(result){
7 | obj = {
8 | id: result.id,
9 | username: result.username,
10 | avatar: result.avatar,
11 | undergraduate: result.undergraduate,
12 | master_school: result.master_school,
13 | wechat_id: result.wechat_id,
14 | tel_id: result.tel_id,
15 | credit: result.credit,
16 | introduce: result.introduce
17 | };
18 | }else{
19 | obj = null;
20 | }
21 | return obj ;
22 | }
23 |
24 | async insert(data) {
25 | const result = await this.app.mysql.insert('user', data);
26 | return result;
27 | }
28 |
29 | async update(data){
30 | const result = await this.app.mysql.update('user', data);
31 | return result;
32 | }
33 |
34 | async select(data){
35 | const result = await this.app.mysql.select('user', data);
36 | return result;
37 | }
38 |
39 | async searchByTelWX(param){
40 | const {app} = this;
41 | const wechat_id = param.wechat_id || '';
42 | const tel_id = param.tel_id || '';
43 | const username = param.username || '';
44 | let str = `select id,wechat_id,username,tel_id,credit from user where wechat_id LIKE '%${wechat_id}%' AND tel_id LIKE '%${tel_id}%' AND username LIKE '%${username}%'`;
45 | const result = await app.mysql.query(str);
46 | return result;
47 | }
48 |
49 | async updateCredit(id, credit){
50 | const {app} = this;
51 | const result = await app.mysql.update('user',{
52 | id: id,
53 | credit : credit
54 | });
55 | return result;
56 | }
57 |
58 | }
59 | module.exports = UserService;
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/client/components/booklist/index.js:
--------------------------------------------------------------------------------
1 | const util = require('../../utils/util.js');
2 | // components/booklist/index.js
3 | Component({
4 | /**
5 | * 组件的属性列表
6 | */
7 | properties: {
8 |
9 | title: {
10 | type: String,
11 | value: '',
12 | },
13 | isCollect: {
14 | type: String,
15 | value: '0'
16 | },
17 | courseID:{
18 | type: String,
19 | value: ''
20 | },
21 | payCount:{
22 | type: String,
23 | value: "0"
24 | },
25 | headImg:{
26 | type: String,
27 | value: "/images/head.png"
28 | },
29 | master:{
30 | type: String,
31 | value: "123"
32 | }
33 | },
34 | /**
35 | * 组件的初始数据
36 | */
37 | data: {
38 | noCollectImage: "/images/collect2.png",
39 | hasCollectImage: "/images/collect1.png",
40 | collectBackImage: "/images/collect2.png",
41 | userHasCollect: 1
42 | },
43 | ready(){
44 | if(this.properties.isCollect == 1){
45 | this.setData({
46 | "collectBackImage": this.data.hasCollectImage,
47 | "userHasCollect": 0
48 | });
49 | }else{
50 | this.setData({
51 | "collectBackImage": this.data.noCollectImage,
52 | "userHasCollect": 1
53 | });
54 | }
55 | },
56 | /**
57 | * 组件的方法列表
58 | */
59 | methods: {
60 | collectBookList(){
61 | util.request('collect/addOrDeleteCollect', { courseID: this.properties.courseID }).then(res => {
62 | if (res.status === 0) {
63 | if(res.result == 0){
64 | // 取消
65 | this.setData({
66 | 'collectBackImage': this.data.noCollectImage
67 | });
68 | }else{
69 | // 收藏
70 | this.setData({
71 | 'collectBackImage': this.data.hasCollectImage
72 | });
73 | }
74 | }
75 | });
76 | }
77 | }
78 | })
79 |
--------------------------------------------------------------------------------
/client/pages/editcourse/index.wxss:
--------------------------------------------------------------------------------
1 | .edit-course{
2 | display: flex;
3 | flex-direction: column;
4 | padding: 118rpx 0 0 0;
5 | }
6 | .edit-course .course-inp{
7 | width:750rpx;
8 | height: 100rpx;
9 | padding:0 32rpx;
10 | font-size:28rpx;
11 | line-height: 100rpx;
12 | color:#333333;
13 | background-color:#FFFFFF;
14 | }
15 | .search-placeholder{
16 | color: rgba(36, 37, 61, .3);
17 | }
18 | .course-inp .label{
19 | float:left;
20 | width:390rpx;
21 | }
22 | .course-inp .inp-text{
23 | float:left;
24 | width:290rpx;
25 | height: 100rpx;
26 | }
27 | .inp-text input{
28 | height: 100rpx;
29 | }
30 |
31 | .course-inp-two{
32 | width:750rpx;
33 | height: 20rpx;
34 | background-color:#F5F5F5;
35 | }
36 | .course-add{
37 | flex-direction: column;
38 | }
39 | .add-text{
40 | position: relative;
41 | width:574rpx;
42 | padding: 17rpx 0;
43 | font-size:34rpx;
44 | background-color:#F5F5F5;
45 | margin:30rpx auto 0;
46 | padding-left: 36rpx;
47 | padding-right: 60rpx;
48 | border-radius: 16rpx;
49 | color: #24253D;
50 | }
51 |
52 | .add-text .text{
53 | word-break: break-all;
54 | }
55 |
56 | .course-login .login-btn{
57 | width:686rpx;
58 | height:93rpx;
59 | margin:80rpx auto 0;
60 | font-size:28rpx;
61 | line-height:93rpx;
62 | color:#FFFFFF;
63 | background-color:#D73316;
64 | border-radius: 8rpx;
65 | }
66 |
67 | .login-submit{
68 | width:686rpx;
69 | height:93rpx;
70 | line-height:93rpx;
71 | background: #fff;
72 | border: 1px solid #D73316;
73 | margin-top: 24rpx;
74 | font-size: 28rpx;
75 | color: #D73316;
76 | }
77 | .login-submit.noMessage{
78 | border: 1px solid rgba(36, 37, 61, .2);
79 | color: rgba(36, 37, 61, .2);
80 | }
81 |
82 | .close{
83 | position: absolute;
84 | width: 40rpx;
85 | height: 40rpx;
86 | border-radius: 20rpx;
87 | background: rgba(0, 0, 0, .6);
88 | right: 10rpx;
89 | top: 10rpx;
90 | color: #fff;
91 | line-height: 36rpx;
92 | text-align: center;
93 | }
--------------------------------------------------------------------------------
/client/pages/enchashment/index.js:
--------------------------------------------------------------------------------
1 | const util = require('../../utils/util.js');
2 | // pages/enchashment/index.js
3 | Page({
4 |
5 | /**
6 | * 页面的初始数据
7 | */
8 | data: {
9 | credit: 0
10 | },
11 |
12 | /**
13 | * 生命周期函数--监听页面加载
14 | */
15 | onLoad: function (options) {
16 | this.setData({
17 | credit: options.credit
18 | });
19 | },
20 | bindSave:function(e){
21 | const wechat = e.detail.value.wechat || "";
22 | const credit = e.detail.value.credit || "";
23 | if(wechat == ""){
24 | wx.showToast({
25 | title: '微信号不能为空',
26 | icon: 'none',
27 | duration: 2000
28 | });
29 | return;
30 | }
31 |
32 | if(credit == ""){
33 | wx.showToast({
34 | title: '积分不能为空',
35 | icon: 'none',
36 | duration: 2000
37 | });
38 | return;
39 | }
40 |
41 | if (!/^[0-9]*$/.test(credit)) {
42 | wx.showToast({
43 | title: '积分为数字类型',
44 | icon: 'none',
45 | duration: 2000
46 | });
47 | return;
48 | }
49 |
50 | if(credit < 100){
51 | wx.showToast({
52 | title: '积分不能少于100',
53 | icon: 'none',
54 | duration: 2000
55 | });
56 | return;
57 | }
58 |
59 | if (credit > parseInt(this.data.credit)){
60 | wx.showToast({
61 | title: '超过了可提现积分',
62 | icon: 'none',
63 | duration: 2000
64 | });
65 | return;
66 | }
67 |
68 | const that = this;
69 | const param = {
70 | credit: credit,
71 | wechat: wechat
72 | }
73 | util.request('enchashment/add', param).then(res => {
74 | if (res.status === 0) {
75 | wx.showToast({
76 | title: res.result,
77 | icon: 'success',
78 | duration: 5000,
79 | complete:function(){
80 | wx.navigateTo({
81 | url: '../wallet/index',
82 | })
83 | }
84 | });
85 | }
86 | });
87 | }
88 | })
--------------------------------------------------------------------------------
/client/components/nav/index.js:
--------------------------------------------------------------------------------
1 | var appInstance = getApp();
2 | var util = require("../../utils/util.js");
3 | var api = require("../../service/api.js");
4 | // components/nav/index.js
5 | Component({
6 | /**
7 | * 组件的属性列表
8 | */
9 | properties: {
10 | type:{
11 | type:Boolean,
12 | value: true
13 | }
14 | },
15 |
16 | /**
17 | * 组件的初始数据
18 | */
19 | data: {
20 | title: "写书单",
21 | src : "/images/head.png",
22 | penimg: "/images/pen.png",
23 | logo: "/images/logo.png",
24 | navItems:[{
25 | url: "/pages/collection/index",
26 | text: "我的收藏",
27 | imgUrl: "/images/1.png"
28 | },{
29 | url: "/pages/myblist/index",
30 | text: "我的书单",
31 | imgUrl: "/images/2.png"
32 | },
33 | // {
34 | // url: "/pages/purchase/index",
35 | // text: "我的订阅",
36 | // imgUrl: "/images/3.png"
37 | // },
38 | // {
39 | // url: "/pages/wallet/index",
40 | // text: "我的钱包",
41 | // imgUrl: "/images/4.png"
42 | // },
43 | {
44 | url: "/pages/setting/index",
45 | text: "我的设置",
46 | imgUrl: "/images/5.png"
47 | }
48 | ],
49 | isShowNav: false
50 | },
51 | ready:function(){
52 | let userInfo = wx.getStorageSync('userInfo');
53 | let token = wx.getStorageSync('token');
54 |
55 | // 页面显示
56 | if (userInfo && token) {
57 | appInstance.globalData.userInfo = userInfo;
58 | appInstance.globalData.token = token;
59 | }
60 |
61 | if (appInstance.globalData.userInfo.avatar){
62 | this.setData({
63 | src: appInstance.globalData.userInfo.avatar,
64 | });
65 | }
66 | },
67 | moved:function(){
68 | console.log(123123);
69 | },
70 | /**
71 | * 组件的方法列表
72 | */
73 | methods: {
74 | showNav:function(){
75 | if(this.data.isShowNav){
76 | this.setData({
77 | isShowNav: false
78 | })
79 | }else{
80 | this.setData({
81 | isShowNav: true
82 | })
83 | }
84 | }
85 | }
86 | })
87 |
--------------------------------------------------------------------------------
/server/app/controller/course.js:
--------------------------------------------------------------------------------
1 | const Base = require('./base');
2 |
3 | class CourseController extends Base {
4 | async addCourse(){
5 | const ctx = this.ctx;
6 | const title = ctx.request.body.title;
7 | const myCourse = ctx.request.body.myCourse;
8 | let addResult = await ctx.service.course.insert({
9 | title: title,
10 | my_course: myCourse,
11 | user_id: ctx.app.userId
12 | });
13 |
14 | if(addResult.affectedRows === 1){
15 | return this.success("插入成功");
16 | }else{
17 | return this.fail("插入失败");
18 | }
19 | }
20 |
21 | async update(){
22 | const {ctx} = this;
23 | const title = ctx.request.body.title;
24 | const myCourse = ctx.request.body.myCourse;
25 | const courseID = ctx.request.body.courseID;
26 | const result = await ctx.service.course.update(courseID, myCourse, title);
27 | if(result.affectedRows === 1){
28 | return this.success("更新成功");
29 | }else{
30 | return this.fail("更新失败");
31 | }
32 | }
33 |
34 | async listMyCourse(){
35 | const {ctx} = this;
36 | const page = ctx.request.body.page;
37 | const size = ctx.request.body.size;
38 | let result = await ctx.service.course.selectByUID(page, size);
39 | return this.success(result);
40 | }
41 |
42 | async listCourseDetail(){
43 | const ctx = this.ctx;
44 | const id = ctx.request.body.id;
45 | let result = await ctx.service.course.getByID(id);
46 | return this.success(result);
47 | }
48 |
49 | async listAllCourse(){
50 | const {ctx} = this;
51 | const page = ctx.request.body.page;
52 | const size = ctx.request.body.size;
53 | console.log(123123);
54 | const getCollectCourse = await ctx.service.course.selectCourseWithCollect(page, size);
55 | return this.success(getCollectCourse);
56 | }
57 |
58 | async listSearchCourse(){
59 | const {ctx} = this;
60 | const title = ctx.request.body.title;
61 | const page = ctx.request.body.page;
62 | const size = ctx.request.body.size;
63 | const getSearchCourse = await ctx.service.course.selectCourseWithTitle(title, page, size);
64 | return this.success(getSearchCourse);
65 | }
66 | }
67 |
68 | module.exports = CourseController;
--------------------------------------------------------------------------------
/client/pages/setting/index.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 用户名
5 |
7 |
8 |
13 |
14 | 本科院校及专业
15 |
17 |
18 |
19 | 研究生院校及专业(选填)
20 |
22 |
23 |
24 | 职业经历
25 |
27 |
28 |
29 | 微信号
30 |
32 |
33 |
34 | 手机号
35 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/client/pages/details/index.js:
--------------------------------------------------------------------------------
1 | var api = require('../../service/api.js');
2 | const util = require('../../utils/util.js');
3 | //index.js
4 | //获取应用实例
5 | const app = getApp()
6 |
7 | Page({
8 | data: {
9 | motto: 'Hello World',
10 | userInfo: {},
11 | hasUserInfo: false,
12 | canIUse: wx.canIUse('button.open-type.getUserInfo'),
13 | booklist:[],
14 | isPay: true,
15 | price: '',
16 | courseID: '',
17 | showEdit: false,
18 | myCourse: '',
19 | title:''
20 | },
21 | //事件处理函数
22 | bindViewTap: function() {
23 | },
24 | onLoad: function (options) {
25 | this.setData({
26 | courseID: options.courseID,
27 | title: options.title
28 | });
29 | wx.setNavigationBarTitle({
30 | title: options.title,
31 | });
32 | this.getMode();
33 | },
34 | getMode:function(){
35 | const that = this;
36 | util.request('course/listCourseDetail', { id: this.data.courseID }).then(res => {
37 | if (res.status === 0) {
38 | let arr = [];
39 | let result = res.result;
40 | let resultList = util.splitStr(result.my_course);
41 | for (let index in resultList) {
42 | arr.push({
43 | text: resultList[index]
44 | })
45 | }
46 | that.setData({
47 | booklist: arr,
48 | userInfo: {
49 | username: result.username,
50 | undergraduate: result.undergraduate,
51 | avatar: result.avatar
52 | },
53 | isPay: result.isPay,
54 | price: result.price,
55 | showEdit: result.showEdit,
56 | myCourse: result.my_course
57 | });
58 | }
59 | });
60 | },
61 | getUserInfo: function(e) {
62 | app.globalData.userInfo = e.detail.userInfo
63 | this.setData({
64 | userInfo: e.detail.userInfo,
65 | hasUserInfo: true
66 | })
67 | },
68 | hasPay:function(){
69 | this.getMode();
70 | },
71 | editCourse:function(){
72 | wx.redirectTo({
73 | url: '../editcourse/index?myCourse=' + this.data.myCourse + '&title='+this.data.title + '&courseID='+this.data.courseID
74 | })
75 | }
76 | })
77 |
--------------------------------------------------------------------------------
/server/app/service/purchase.js:
--------------------------------------------------------------------------------
1 | const Service = require('egg').Service;
2 |
3 | class PurchaseService extends Service {
4 | async addPurchase(courseID){
5 | const {app,ctx} = this;
6 | const uInfo = await ctx.service.user.find({
7 | id: app.userId
8 | });
9 | const courseInfo = await ctx.service.course.selectByID(courseID);
10 | let status = 0;
11 | if(uInfo.credit >= courseInfo.price){
12 |
13 | const result = await app.mysql.update("user", {
14 | id: app.userId,
15 | credit: uInfo.credit - courseInfo.price
16 | });
17 | if(result.affectedRows === 1){
18 | const result = await app.mysql.insert("purchase",{
19 | course_id: courseID,
20 | user_id: app.userId,
21 | add_time: ctx.helper.timest()
22 | });
23 | if(result.affectedRows === 1){
24 | status = 1;
25 | }
26 | }
27 | }
28 | return status;
29 | }
30 |
31 | async selectMyPurchase(page, size){
32 | const {app, ctx} = this;
33 | const result = await app.mysql.query('select c.id,c.title,c.publish from course as c left join purchase as p on c.id=p.course_id where p.user_id='+app.userId+' LIMIT '+(page-1)*size+','+size);
34 | return result;
35 | }
36 |
37 | async selectPurchaseAboutMe(page, size){
38 | const {app, ctx} = this;
39 | const result = await app.mysql.query('select c.price,p.user_id,p.add_time from purchase as p left join course as c on c.id=p.course_id where p.course_id IN (select course_id from purchase where course_id IN (select id from course where user_id='+app.userId+')) OR p.user_id='+app.userId+' ORDER BY p.add_time LIMIT '+(page-1)*size+','+size);
40 |
41 | // 修改result字段,user_id 属性去除,user_id 为自身则是购买类型 0,不是自身则被订购类型 1
42 | if(result.length > 0){
43 | for(let i in result){
44 | if(result[i].user_id == app.userId)
45 | result[i]['type'] = 0;
46 | else
47 | result[i]['type'] = 1;
48 | delete result[i]['user_id'];
49 | }
50 | }
51 | return result;
52 | }
53 |
54 | async getPayCount(courseID){
55 | const {app, ctx} = this;
56 | const result = await app.mysql.select('purchase', {
57 | where: {
58 | course_id: courseID
59 | }
60 | });
61 | return result.length;
62 | }
63 | }
64 |
65 | module.exports = PurchaseService;
--------------------------------------------------------------------------------
/server/app/extend/helper.js:
--------------------------------------------------------------------------------
1 | const jwt = require('jsonwebtoken');
2 | const secret = 'FTD#@HBJRR@@gf';
3 |
4 |
5 | // exports.token = {
6 | // async create(userInfo){
7 | // const token = jwt.sign(userInfo, secret);
8 | // return token;
9 | // },
10 |
11 | // async parse(ctx) {
12 | // if (ctx.app.token) {
13 | // try {
14 | // return jwt.verify(ctx.app.token, secret);
15 | // } catch (err) {
16 | // return null;
17 | // }
18 | // }
19 | // return null;
20 | // },
21 |
22 | // async getUserId(ctx){
23 | // const token = ctx.app.token;
24 | // console.log(123123, app);
25 | // if (!token) {
26 | // return 0;
27 | // }
28 |
29 | // // const result = await this.parse(ctx);
30 | // // console.log('result', result);
31 | // // if (isEmpty(result) || result.user_id <= 0) {
32 | // // return 0;
33 | // // }
34 |
35 | // // return result.user_id;
36 | // }
37 | // }
38 |
39 | const isEmpty = function(obj){
40 | if(obj) return false;
41 | return true;
42 | }
43 |
44 | exports.isEmpty = isEmpty;
45 |
46 | class Token{
47 | constructor(ctx){
48 | this.ctx = ctx;
49 | }
50 | async create(userInfo){
51 | const token = jwt.sign(userInfo, secret);
52 | return token;
53 | }
54 |
55 | async parse() {
56 | if (this.ctx.app.token) {
57 | try {
58 | return jwt.verify(this.ctx.app.token, secret);
59 | } catch (err) {
60 | return null;
61 | }
62 | }
63 | return null;
64 | }
65 |
66 | async getUserId(){
67 | const token = this.ctx.app.token;
68 | if (!token) {
69 | return 0;
70 | }
71 | const result = await this.parse();
72 | if (isEmpty(result) || result.data.user_id <= 0) {
73 | return 0;
74 | }
75 | return result.data.user_id;
76 | }
77 | }
78 |
79 | exports.Token = Token;
80 |
81 | //从1970年开始的毫秒数然后截取10位变成 从1970年开始的秒数
82 | const timest = function() {
83 | var tmp = Date.parse( new Date() ).toString();
84 | tmp = tmp.substr(0,10);
85 | return tmp;
86 | }
87 |
88 | exports.timest = timest;
89 |
90 | const halfArr = function(str){
91 | let arr = str.split(",");
92 | arr = arr.slice(Math.ceil(arr.length/2)).join(',');
93 | return arr;
94 | }
95 |
96 | exports.halfArr = halfArr;
--------------------------------------------------------------------------------
/admin/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 |
15 | // Various Dev Server settings
16 | host: 'localhost', // can be overwritten by process.env.HOST
17 | port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined
18 | autoOpenBrowser: false,
19 | errorOverlay: true,
20 | notifyOnErrors: true,
21 | poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions-
22 |
23 |
24 | /**
25 | * Source Maps
26 | */
27 |
28 | // https://webpack.js.org/configuration/devtool/#development
29 | devtool: 'cheap-module-eval-source-map',
30 |
31 | // If you have problems debugging vue-files in devtools,
32 | // set this to false - it *may* help
33 | // https://vue-loader.vuejs.org/en/options.html#cachebusting
34 | cacheBusting: true,
35 |
36 | cssSourceMap: true
37 | },
38 |
39 | build: {
40 | // Template for index.html
41 | index: path.resolve(__dirname, '../dist/index.html'),
42 |
43 | // Paths
44 | assetsRoot: path.resolve(__dirname, '../dist'),
45 | assetsSubDirectory: 'static',
46 | assetsPublicPath: '/',
47 |
48 | /**
49 | * Source Maps
50 | */
51 |
52 | productionSourceMap: true,
53 | // https://webpack.js.org/configuration/devtool/#production
54 | devtool: '#source-map',
55 |
56 | // Gzip off by default as many popular static hosts such as
57 | // Surge or Netlify already gzip all static assets for you.
58 | // Before setting to `true`, make sure to:
59 | // npm install --save-dev compression-webpack-plugin
60 | productionGzip: false,
61 | productionGzipExtensions: ['js', 'css'],
62 |
63 | // Run the build command with an extra argument to
64 | // View the bundle analyzer report after build finishes:
65 | // `npm run build --report`
66 | // Set to `true` or `false` to always turn it on or off
67 | bundleAnalyzerReport: process.env.npm_config_report
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/admin/src/style/common.scss:
--------------------------------------------------------------------------------
1 | body, div, span, header, footer, nav, section, aside, article, ul, dl, dt, dd, li, a, p, h1, h2, h3, h4,h5, h6, i, b, textarea, button, input, select, figure, figcaption {
2 | padding: 0;
3 | margin: 0;
4 | list-style: none;
5 | font-style: normal;
6 | text-decoration: none;
7 | border: none;
8 | font-family: "Microsoft Yahei",sans-serif;
9 | -webkit-tap-highlight-color:transparent;
10 | -webkit-font-smoothing: antialiased;
11 | &:focus {
12 | outline: none;
13 | }
14 | }
15 |
16 | /*定义滚动条高宽及背景 高宽分别对应横竖滚动条的尺寸*/
17 | ::-webkit-scrollbar
18 | {
19 | width: 0px;
20 | height: 0px;
21 | background-color: #F5F5F5;
22 | }
23 |
24 | /*定义滚动条轨道 内阴影+圆角*/
25 | ::-webkit-scrollbar-track
26 | {
27 | -webkit-box-shadow: inset 0 0 1px rgba(0,0,0,0);
28 | border-radius: 10px;
29 | background-color: #F5F5F5;
30 | }
31 |
32 | /*定义滑块 内阴影+圆角*/
33 | ::-webkit-scrollbar-thumb
34 | {
35 | border-radius: 10px;
36 | -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.3);
37 | background-color: #555;
38 | }
39 |
40 | input[type="button"], input[type="submit"], input[type="search"], input[type="reset"] {
41 | -webkit-appearance: none;
42 | }
43 |
44 | textarea { -webkit-appearance: none;}
45 |
46 | html,body{
47 | height: 100%;
48 | width: 100%;
49 | // background-color: #F5F5F5;
50 | }
51 |
52 | .fillcontain{
53 | height: 100%;
54 | width: 100%;
55 | }
56 | .clear:after{
57 | content: '';
58 | display: block;
59 | clear: both;
60 | }
61 |
62 | .clear{
63 | zoom:1;
64 | }
65 |
66 | .back_img{
67 | background-repeat: no-repeat;
68 | background-size: 100% 100%;
69 | }
70 |
71 | .margin{
72 | margin: 0 auto;
73 | }
74 |
75 | .left{
76 | float: left;
77 | }
78 |
79 | .right{
80 | float: right;
81 | }
82 |
83 | .hide{
84 | display: none;
85 | }
86 |
87 | .show{
88 | display: block;
89 | }
90 |
91 | .ellipsis{
92 | overflow: hidden;
93 | text-overflow: ellipsis;
94 | white-space: nowrap;
95 | }
96 |
97 | .width1200{
98 | width: 1200px;
99 | }
100 |
101 | .app-container {
102 | padding: 20px;
103 | }
--------------------------------------------------------------------------------
/client/components/nav/index.wxss:
--------------------------------------------------------------------------------
1 | /* components/nav/index.wxss */
2 | .ftd-nav{
3 | width: 750rpx;
4 | height: 90rpx;
5 | background: #FFFFFF;
6 | box-shadow: 0 4px 4px 0 rgba(227,227,227,0.50);
7 | position: absolute;
8 | top: 0;
9 | left: 0;
10 | }
11 | .ftd-nav__logo{
12 | position: absolute;
13 | top: 12rpx;
14 | left: 45rpx;
15 | width: 47rpx;
16 | height: 58rpx;
17 | }
18 | .ftd-nav__img{
19 | width: 32rpx;
20 | height: 32rpx;
21 | margin-right: 12rpx;
22 | }
23 | .ftd-nav__write{
24 | position: absolute;
25 | width: 136rpx;
26 | padding-right: 24rpx;
27 | height: 50rpx;
28 | top: 20rpx;
29 | right: 114rpx;
30 | font-size: 28rpx;
31 | text-align: right;
32 | color:#FFFFFF;
33 | line-height: 50rpx;
34 | background: #D73316;
35 | background-size: 23rpx 22rpx;
36 | border-radius: 70rpx;
37 | }
38 | .ftd-nav__sub{
39 | position: absolute;
40 | width: 105rpx;
41 | height: 40rpx;
42 | top: 24rpx;
43 | right: 110rpx;
44 | font-size: 10px;
45 | text-align: center;
46 | color:#FFFFFF;
47 | line-height: 40rpx;
48 | padding-right: 6rpx;
49 | background: #D73316;
50 | background-size: 23rpx 22rpx;
51 | }
52 | .ftd-nav__write image{
53 | position: absolute;
54 | left: 18rpx;
55 | top: 12rpx;
56 | width: 28rpx;
57 | height: 26rpx;
58 | }
59 | .ftd-nav__icon{
60 | position: absolute;
61 | top: 15rpx;
62 | right: 30rpx;
63 | width: 60rpx;
64 | height: 60rpx;
65 | border-radius: 30rpx;
66 | }
67 | .ftd-nav__panel{
68 | position: absolute;
69 | top: 82rpx;
70 | right: 13rpx;
71 | padding: 15rpx 42rpx;
72 | background: #FFFFFF;
73 | border: 1px solid #F0F0F0;
74 | box-shadow: 0 4px 8px 0 rgba(221,221,221,0.50);
75 | z-index: 99999;
76 | }
77 | /* .ftd-nav__panel::after{
78 | content: '';
79 | position: absolute;
80 | transform: rotate(45deg) translate(6px, 6px);
81 | display: block;
82 | width: 14rpx;
83 | height: 14rpx;
84 | background: #fff;
85 | top: -23rpx;
86 | right: 40rpx;
87 | border-top: 1px solid #F0F0F0;
88 | border-left: 1px solid #F0F0F0;
89 | z-index: -1;
90 | } */
91 | .ftd-nav__content{
92 | display: flex;
93 | align-items:center;
94 | padding:15rpx 0;
95 | }
96 | .ftd-nav__navigator{
97 | font-size: 28rpx;
98 | color: #24253D;
99 | text-align: center;
100 | }
--------------------------------------------------------------------------------
/admin/build/webpack.base.conf.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const path = require('path')
3 | const utils = require('./utils')
4 | const config = require('../config')
5 | const vueLoaderConfig = require('./vue-loader.conf')
6 |
7 | function resolve (dir) {
8 | return path.join(__dirname, '..', dir)
9 | }
10 |
11 |
12 |
13 | module.exports = {
14 | context: path.resolve(__dirname, '../'),
15 | entry: {
16 | app: './src/main.js'
17 | },
18 | output: {
19 | path: config.build.assetsRoot,
20 | filename: '[name].js',
21 | publicPath: process.env.NODE_ENV === 'production'
22 | ? config.build.assetsPublicPath
23 | : config.dev.assetsPublicPath
24 | },
25 | resolve: {
26 | extensions: ['.js', '.vue', '.json'],
27 | alias: {
28 | 'vue$': 'vue/dist/vue.esm.js',
29 | '@': resolve('src'),
30 | }
31 | },
32 | module: {
33 | rules: [
34 | {
35 | test: /\.vue$/,
36 | loader: 'vue-loader',
37 | options: vueLoaderConfig
38 | },
39 | {
40 | test: /\.js$/,
41 | loader: 'babel-loader',
42 | include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')]
43 | },
44 | {
45 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
46 | loader: 'url-loader',
47 | options: {
48 | limit: 10000,
49 | name: utils.assetsPath('img/[name].[hash:7].[ext]')
50 | }
51 | },
52 | {
53 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
54 | loader: 'url-loader',
55 | options: {
56 | limit: 10000,
57 | name: utils.assetsPath('media/[name].[hash:7].[ext]')
58 | }
59 | },
60 | {
61 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
62 | loader: 'url-loader',
63 | options: {
64 | limit: 10000,
65 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
66 | }
67 | }
68 | ]
69 | },
70 | node: {
71 | // prevent webpack from injecting useless setImmediate polyfill because Vue
72 | // source contains it (although only uses it if it's native).
73 | setImmediate: false,
74 | // prevent webpack from injecting mocks to Node native modules
75 | // that does not make sense for the client
76 | dgram: 'empty',
77 | fs: 'empty',
78 | net: 'empty',
79 | tls: 'empty',
80 | child_process: 'empty'
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/admin/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "admin",
3 | "version": "1.0.0",
4 | "description": "A Vue.js project",
5 | "author": "hbj <301085344@qq.com>",
6 | "private": true,
7 | "scripts": {
8 | "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
9 | "start": "npm run dev",
10 | "unit": "jest --config test/unit/jest.conf.js --coverage",
11 | "test": "npm run unit",
12 | "build": "node build/build.js"
13 | },
14 | "dependencies": {
15 | "element-ui": "^2.4.1",
16 | "vue": "^2.5.2",
17 | "vue-router": "^3.0.1",
18 | "vuex": "^3.0.1",
19 | "vuex-persistedstate": "^2.5.4"
20 | },
21 | "devDependencies": {
22 | "autoprefixer": "^7.1.2",
23 | "babel-core": "^6.22.1",
24 | "babel-helper-vue-jsx-merge-props": "^2.0.3",
25 | "babel-jest": "^21.0.2",
26 | "babel-loader": "^7.1.1",
27 | "babel-plugin-dynamic-import-node": "^1.2.0",
28 | "babel-plugin-syntax-jsx": "^6.18.0",
29 | "babel-plugin-transform-es2015-modules-commonjs": "^6.26.0",
30 | "babel-plugin-transform-runtime": "^6.22.0",
31 | "babel-plugin-transform-vue-jsx": "^3.5.0",
32 | "babel-preset-env": "^1.3.2",
33 | "babel-preset-stage-2": "^6.22.0",
34 | "chalk": "^2.0.1",
35 | "copy-webpack-plugin": "^4.0.1",
36 | "css-loader": "^0.28.0",
37 | "extract-text-webpack-plugin": "^3.0.0",
38 | "file-loader": "^1.1.4",
39 | "friendly-errors-webpack-plugin": "^1.6.1",
40 | "html-webpack-plugin": "^2.30.1",
41 | "jest": "^22.0.4",
42 | "jest-serializer-vue": "^0.3.0",
43 | "node-notifier": "^5.1.2",
44 | "node-sass": "^4.9.0",
45 | "optimize-css-assets-webpack-plugin": "^3.2.0",
46 | "ora": "^1.2.0",
47 | "portfinder": "^1.0.13",
48 | "postcss-import": "^11.0.0",
49 | "postcss-loader": "^2.0.8",
50 | "postcss-url": "^7.2.1",
51 | "rimraf": "^2.6.0",
52 | "sass-loader": "^7.0.3",
53 | "semver": "^5.3.0",
54 | "shelljs": "^0.7.6",
55 | "uglifyjs-webpack-plugin": "^1.1.1",
56 | "url-loader": "^0.5.8",
57 | "vue-jest": "^1.0.2",
58 | "vue-loader": "^13.3.0",
59 | "vue-style-loader": "^3.0.1",
60 | "vue-template-compiler": "^2.5.2",
61 | "webpack": "^3.6.0",
62 | "webpack-bundle-analyzer": "^2.9.0",
63 | "webpack-dev-server": "^2.9.1",
64 | "webpack-merge": "^4.1.0"
65 | },
66 | "engines": {
67 | "node": ">= 6.0.0",
68 | "npm": ">= 3.0.0"
69 | },
70 | "browserslist": [
71 | "> 1%",
72 | "last 2 versions",
73 | "not ie <= 8"
74 | ]
75 | }
76 |
--------------------------------------------------------------------------------
/admin/src/components/HelloWorld.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ msg }}
4 |
Essential Links
5 |
48 |
Ecosystem
49 |
83 |
84 |
85 |
86 |
96 |
97 |
98 |
114 |
--------------------------------------------------------------------------------
/admin/src/service/fetch.js:
--------------------------------------------------------------------------------
1 | import {
2 | baseUrl
3 | } from './env'
4 | import Vue from 'vue'
5 | import router from '@/router/index';
6 |
7 | export default async(url = '', data = {}, type = 'POST', token = 'token', method = 'promise') => {
8 | type = type.toUpperCase();
9 | url = baseUrl + url;
10 |
11 |
12 | let dataStr = ''; //数据拼接字符串,参数要: a=1&b=2。不能{a:1,b:2}的方式,所以要对data做处理
13 | Object.keys(data).forEach(key => {
14 | dataStr += key + '=' + data[key] + '&';
15 | })
16 | dataStr = dataStr.substr(0, dataStr.lastIndexOf('&'));
17 |
18 | if (type == 'GET') {
19 | if (dataStr !== '') {
20 | url = url + '?' + dataStr;
21 | }
22 | }
23 |
24 | if (window.fetch && method == 'fetch') {
25 |
26 |
27 | let requestConfig = {
28 | credentials: true,
29 | method: type,
30 | headers: {
31 | 'Accept': 'application/json',
32 | 'Content-type': 'application/x-www-form-urlencoded'
33 | },
34 | mode: "cors",
35 | cache: "force-cache"
36 | }
37 |
38 | if (type == 'POST') {
39 | Object.defineProperty(requestConfig, 'body', {
40 | value: dataStr
41 | })
42 | }
43 |
44 | try {
45 | const response = await fetch(url, requestConfig);
46 | const responseJson = await response.json();
47 | return responseJson
48 | } catch (error) {
49 | throw new Error(error)
50 | }
51 | } else {
52 | return new Promise((resolve, reject) => {
53 | let requestObj;
54 | if (window.XMLHttpRequest) {
55 | requestObj = new XMLHttpRequest();
56 | } else {
57 | requestObj = new ActiveXObject;
58 | }
59 |
60 | let sendData = '';
61 | if (type == 'POST') {
62 | sendData = dataStr;
63 | }
64 |
65 | requestObj.open(type, url, true);
66 | requestObj.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
67 | if(token == 'token'){
68 | requestObj.setRequestHeader("token", localStorage.getItem('token') || '');
69 | }
70 |
71 | requestObj.send(sendData);
72 |
73 | requestObj.onreadystatechange = () => {
74 | if (requestObj.readyState == 4) {
75 | if (requestObj.status == 200) {
76 | let obj = requestObj.response
77 | if(obj.status == -3){
78 | router.push({path: "/login"});
79 | return;
80 | }
81 | if (typeof obj !== 'object') {
82 | try{
83 | obj = JSON.parse(obj);
84 | }catch(e){
85 | console.log(e);
86 | }
87 |
88 | }
89 | resolve(obj)
90 | } else {
91 | reject(requestObj)
92 | }
93 | }
94 | }
95 | })
96 | }
97 | }
--------------------------------------------------------------------------------
/server/app/controller/auth.js:
--------------------------------------------------------------------------------
1 | const Base = require('./base');
2 | const Util = require('../../utils/util');
3 | const util = new Util();
4 |
5 | class AuthController extends Base {
6 | constructor(props) {
7 | super(props);
8 | this.token =new this.ctx.helper.Token(this.ctx);
9 | }
10 | async loginByWeixinAction() {
11 | let token = new this.ctx.helper.Token(this.ctx);
12 | const ctx = this.ctx;
13 | const code = ctx.request.body.code;
14 | const fullUserInfo = ctx.request.body.userInfo;
15 | const userInfo = fullUserInfo.userInfo;
16 |
17 | // 获取openid
18 | const options = {
19 | method: 'GET',
20 | contentType: 'json',
21 | data: {
22 | grant_type: 'authorization_code',
23 | js_code: code,
24 | secret: this.config.wx.secret,
25 | appid: this.config.wx.appid
26 | },
27 | dataType: 'json'
28 | };
29 |
30 | const sessionData = await ctx.curl('https://api.weixin.qq.com/sns/jscode2session',options);
31 | if (!sessionData.data.openid) {
32 | return this.fail('登录失败1');
33 | }
34 | // 验证用户信息完整性(数字签名验证)
35 | const crypto = require('crypto');
36 | const sha1 = crypto.createHash('sha1').update(fullUserInfo.rawData + sessionData.data.session_key).digest('hex');
37 | if (fullUserInfo.signature !== sha1) {
38 | return this.fail('登录失败2');
39 | }
40 | //根据openid查找用户是否已经注册
41 | let userMsg = await ctx.service.user.find({openid: sessionData.data.openid});
42 | if(ctx.helper.isEmpty(userMsg)){
43 | let result = await ctx.service.user.insert({
44 | username: userInfo.nickName,
45 | register_time: parseInt(new Date().getTime() / 1000),
46 | last_login_time: parseInt(new Date().getTime() / 1000),
47 | openid: sessionData.data.openid,
48 | avatar: userInfo.avatarUrl || '',
49 | gender: userInfo.gender || 1, // 性别 0:未知、1:男、2:女
50 | });
51 |
52 | if(result.affectedRows === 1){
53 | console.log("插入成功");
54 | }
55 | userMsg = {};
56 | userMsg.id = result.insertId;
57 | }
58 |
59 | sessionData.data.user_id = userMsg.id;
60 | // 查询用户信息
61 | const resultNewUser = await ctx.service.user.find({
62 | id: userMsg.id
63 | });
64 |
65 | const newUserInfo = {
66 | avatar: resultNewUser.avatar,
67 | username: resultNewUser.username,
68 | undergraduate: resultNewUser.undergraduate,
69 | master_school: resultNewUser.master_school,
70 | wechat_id: resultNewUser.wechat_id,
71 | tel_id: resultNewUser.tel_id,
72 | introduce: resultNewUser.introduce
73 | }
74 |
75 | // 更新登录信息
76 | const resultUpdata = await ctx.service.user.update({
77 | id: userMsg.id,
78 | last_login_time: parseInt(new Date().getTime() / 1000)
79 | });
80 |
81 | if(resultUpdata.affectedRows === 1){
82 | console.log("更新成功");
83 | }
84 |
85 | // 生成 Token 返回 客户端
86 | const sessionKey = await this.token.create(sessionData);
87 |
88 | return this.success({ token:sessionKey, userInfo: newUserInfo });
89 |
90 | }
91 | }
92 |
93 | module.exports = AuthController;
--------------------------------------------------------------------------------
/admin/src/pages/usermanage/userinfo/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | 查询
19 |
20 |
21 |
22 |
24 |
25 |
26 |
27 |
28 | {{scope.row.username}}
29 |
30 |
31 |
32 |
33 | {{scope.row.wechat_id}}
34 |
35 |
36 |
37 |
38 | {{scope.row.tel_id}}
39 |
40 |
41 |
42 |
43 |
44 |
45 | cancel
46 |
47 | {{ scope.row.credit }}
48 |
49 |
50 |
51 |
52 | Ok
53 | Edit
54 |
55 |
56 |
57 |
58 |
59 |
63 |
--------------------------------------------------------------------------------
/admin/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 |
--------------------------------------------------------------------------------
/admin/src/pages/usermanage/enchashment/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | 查询
25 |
26 |
27 |
28 |
30 |
31 |
32 |
33 |
34 | {{scope.row.username}}
35 |
36 |
37 |
38 |
39 | {{scope.row.wechat_id}}
40 |
41 |
42 |
43 |
44 | {{scope.row.tel_id}}
45 |
46 |
47 |
48 |
49 | {{scope.row.enchashment_credit}}
50 |
51 |
52 |
53 |
54 | {{scope.row.enchashment_credit | calculatCash}}
55 |
56 |
57 |
58 |
59 | {{scope.row.verify | statusFilter}}
60 |
61 |
62 |
63 |
64 | 待提现
65 | 撤销
66 |
67 |
68 |
69 |
70 |
71 |
75 |
--------------------------------------------------------------------------------
/admin/src/pages/qualitymanage/review/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | 查询
25 |
26 |
27 |
28 |
30 |
31 |
32 |
33 |
34 | {{scope.row.title}}
35 |
36 |
37 |
38 |
39 | {{scope.row.my_course}}
40 |
41 |
42 |
43 |
44 | {{scope.row.username}}
45 |
46 |
47 |
48 |
49 | {{scope.row.wechat_id}}
50 |
51 |
52 |
53 |
54 | {{scope.row.tel_id}}
55 |
56 |
57 |
58 |
59 | {{scope.row.publish | statusFilter}}
60 |
61 |
62 |
63 |
64 | 发布
65 | 撤销
66 |
67 |
68 |
69 |
70 |
71 |
75 |
--------------------------------------------------------------------------------
/admin/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 |
--------------------------------------------------------------------------------
/client/pages/setting/index.js:
--------------------------------------------------------------------------------
1 | const util = require('../../utils/util.js');
2 | const app=getApp()
3 |
4 | Page({
5 | data:{
6 | userInp:'',
7 | collegesInp:'',
8 | graduateInp:'',
9 | weiChatInp:'',
10 | mobileInp:'',
11 | introInp:''
12 | },
13 | onLoad:function(){
14 | this.setData({
15 | userInp: app.globalData.userInfo.username,
16 | collegesInp: app.globalData.userInfo.undergraduate,
17 | graduateInp: app.globalData.userInfo.master_school,
18 | introInp: app.globalData.userInfo.introduce,
19 | weiChatInp: app.globalData.userInfo.wechat_id,
20 | mobileInp: app.globalData.userInfo.tel_id
21 | });
22 | },
23 | userInp(e){
24 | this.setData({
25 | userInp:e.detail.value
26 | })
27 | },
28 | myAvatar(e){
29 | this.setData({
30 | myAvatar:e.detail.value
31 | })
32 | },
33 | collegesInp(e){
34 | this.setData({
35 | collegesInp:e.detail.value
36 | })
37 | },
38 | graduateInp(e){
39 | this.setData({
40 | graduateInp:e.detail.value
41 | })
42 | },
43 | introInp(e) {
44 | this.setData({
45 | introInp: e.detail.value
46 | })
47 | },
48 | weiChatInp(e){
49 | this.setData({
50 | weiChatInp:e.detail.value
51 | })
52 | },
53 | mobileInp(e){
54 | this.setData({
55 | mobileInp:e.detail.value
56 | })
57 | },
58 | loginBtnClick(){
59 | const that = this;
60 | let param = {};
61 | if (this.data.userInp == ''){
62 | wx.showToast({
63 | title: '用户名不能为空',
64 | icon: 'none'
65 | });
66 | return;
67 | }
68 | if (this.data.collegesInp == ''){
69 | wx.showToast({
70 | title: '本科院校不能为空',
71 | icon: 'none'
72 | });
73 | return;
74 | }
75 | if (this.data.introInp == '') {
76 | wx.showToast({
77 | title: '个人简介不能为空',
78 | icon: 'none'
79 | });
80 | return;
81 | }
82 | if (this.data.weiChatInp == ''){
83 | wx.showToast({
84 | title: '微信号不能为空',
85 | icon: 'none'
86 | });
87 | return;
88 | }
89 |
90 | if (this.data.mobileInp == ''){
91 | wx.showToast({
92 | title: '手机号不能为空',
93 | icon: 'none'
94 | });
95 | return;
96 | }
97 | param.username = this.data.userInp;
98 | param.undergraduate = this.data.collegesInp;
99 | param.master_school = this.data.graduateInp;
100 | param.introduce = this.data.introInp;
101 | param.wechat_id = this.data.weiChatInp;
102 | param.tel_id = this.data.mobileInp;
103 | util.request('user/setInfo', param).then(res => {
104 | if (res.status === 0) {
105 | wx.showToast({
106 | title: res.result,
107 | icon: 'none',
108 | duration: 2000,
109 | complete: function(){
110 | setTimeout(function(){
111 | wx.navigateBack();
112 | },2000);
113 | }
114 | });
115 | Object.assign(app.globalData.userInfo, param);
116 | wx.setStorageSync("userInfo", app.globalData.userInfo);
117 | }
118 | });
119 | },
120 | })
--------------------------------------------------------------------------------
/client/components/tip/index.wxss:
--------------------------------------------------------------------------------
1 | /* components/tip/index.wxss */
2 | .ftd-tip{
3 | position: fixed;
4 | right: 20rpx;
5 | transform: translateX(100%);
6 | background: #D73316;
7 | opacity: .3;
8 | border-bottom-left-radius: 8rpx;
9 | border-top-left-radius: 8rpx;
10 | padding: 15rpx 20rpx 15rpx 20rpx;
11 | color: #fff;
12 | font-size: 24rpx;
13 | z-index: 9999;
14 | }
15 |
16 | .animated {
17 | -webkit-animation-duration: 1s;
18 | animation-duration: 1s;
19 | -webkit-animation-fill-mode: both;
20 | animation-fill-mode: both;
21 | }
22 |
23 | .animated.infinite {
24 | -webkit-animation-iteration-count: infinite;
25 | animation-iteration-count: infinite;
26 | }
27 |
28 | .animated.flip {
29 | -webkit-backface-visibility: visible;
30 | backface-visibility: visible;
31 | -webkit-animation-name: flip;
32 | animation-name: flip;
33 | }
34 |
35 | @-webkit-keyframes bounceInRight {
36 | from,
37 | 60%,
38 | 75%,
39 | 90%,
40 | to {
41 | -webkit-animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
42 | animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
43 | }
44 |
45 | from {
46 | opacity: 0;
47 | -webkit-transform: translate3d(3000px, 0, 0);
48 | transform: translate3d(3000px, 0, 0);
49 | }
50 |
51 | 60% {
52 | opacity: .8;
53 | -webkit-transform: translate3d(-25px, 0, 0);
54 | transform: translate3d(-25px, 0, 0);
55 | }
56 |
57 | 75% {
58 | -webkit-transform: translate3d(10px, 0, 0);
59 | transform: translate3d(10px, 0, 0);
60 | }
61 |
62 | 90% {
63 | -webkit-transform: translate3d(-5px, 0, 0);
64 | transform: translate3d(-5px, 0, 0);
65 | }
66 |
67 | to {
68 | opacity: 1;
69 | right: -5rpx;
70 | -webkit-transform: translate3d(0, 0, 0);
71 | transform: translate3d(0, 0, 0);
72 | }
73 | }
74 |
75 | @keyframes bounceInRight {
76 | from,
77 | 60%,
78 | 75%,
79 | 90%,
80 | to {
81 | -webkit-animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
82 | animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
83 | }
84 |
85 | from {
86 | opacity: 0;
87 | -webkit-transform: translate3d(3000px, 0, 0);
88 | transform: translate3d(3000px, 0, 0);
89 | }
90 |
91 | 60% {
92 | opacity: .8;
93 | -webkit-transform: translate3d(-25px, 0, 0);
94 | transform: translate3d(-25px, 0, 0);
95 | }
96 |
97 | 75% {
98 | -webkit-transform: translate3d(10px, 0, 0);
99 | transform: translate3d(10px, 0, 0);
100 | }
101 |
102 | 90% {
103 | -webkit-transform: translate3d(-5px, 0, 0);
104 | transform: translate3d(-5px, 0, 0);
105 | }
106 |
107 | to {
108 | opacity: 1;
109 | right: -5rpx;
110 | -webkit-transform: translate3d(0, 0, 0);
111 | transform: translate3d(0, 0, 0);
112 | }
113 | }
114 |
115 | .bounceInRight {
116 | -webkit-animation-name: bounceInRight;
117 | animation-name: bounceInRight;
118 | }
119 |
120 | @-webkit-keyframes fadeInLeft {
121 | from {
122 | opacity: 1;
123 | -webkit-transform: translate3d(0, 0, 0);
124 | transform: translate3d(0, 0, 0);
125 | }
126 |
127 | to {
128 | opacity: .3;
129 | -webkit-transform: translate3d(100%, 0, 0);
130 | transform: translate3d(100%, 0, 0);
131 | }
132 | }
133 |
134 | @keyframes fadeInLeft {
135 | from {
136 | opacity: 1;
137 | right: -10rpx;
138 | -webkit-transform: translate3d(0, 0, 0);
139 | transform: translate3d(0, 0, 0);
140 | }
141 |
142 | to {
143 | opacity: .3;
144 | -webkit-transform: translate3d(100%, 0, 0);
145 | transform: translate3d(100%, 0, 0);
146 | }
147 | }
148 |
149 | .fadeInLeft {
150 | -webkit-animation-name: fadeInLeft;
151 | animation-name: fadeInLeft;
152 | }
--------------------------------------------------------------------------------
/server/app/service/course.js:
--------------------------------------------------------------------------------
1 | const Service = require('egg').Service;
2 |
3 | class CourseService extends Service {
4 | async insert(data){
5 | const result = await this.app.mysql.insert('course', data);
6 | return result;
7 | }
8 |
9 | async update(courseID, myCourse, title){
10 | const {ctx, app} = this;
11 | const result = await app.mysql.update('course', {
12 | id: courseID,
13 | my_course: myCourse,
14 | title: title
15 | });
16 | return result;
17 | }
18 |
19 | async selectByUID(page, size){
20 | const result = await this.app.mysql.select('course', {
21 | where:{user_id: this.app.userId},
22 | columns: ['id', 'title', 'publish'],
23 | limit: size,
24 | offset: (page-1)*size
25 | });
26 | return result;
27 | }
28 |
29 | async selectByID(id){
30 | const {ctx,app} = this;
31 | const result = await app.mysql.get('course', {
32 | id: id
33 | });
34 | return result;
35 | }
36 |
37 | async getByID(id){
38 | const {ctx,app} = this;
39 | const result = await app.mysql.query('select c.user_id,c.title,c.my_course,c.price,u.username,u.avatar,u.undergraduate,u.master_school from course c left join user u on c.user_id=u.id where c.id='+ id);
40 | let tmp = {};
41 | if(result.length >= 1){
42 | tmp = result[0];
43 | }
44 |
45 | // 自己写的课程和价格为0的课程直接公开
46 | if(tmp && tmp.user_id && tmp.user_id == app.userId || tmp.price == 0){
47 | tmp.isPay = true;
48 | // 自己写的文章展示编辑
49 | if(tmp.user_id == app.userId){
50 | tmp.showEdit = true;
51 | }else{
52 | tmp.showEdit = false;
53 | }
54 | return tmp;
55 | }
56 | const resultPurchase = await app.mysql.get('purchase',{
57 | user_id: app.userId,
58 | course_id: id
59 | });
60 | tmp.showEdit = false;
61 | if(resultPurchase){
62 | tmp.isPay = true;
63 | }else{
64 | // 未购买课程只公开一半
65 | tmp.my_course = ctx.helper.halfArr(tmp.my_course);
66 | tmp.isPay = false;
67 | }
68 | return tmp;
69 | }
70 |
71 | async selectCourseWithCollect(page, size){
72 | const {ctx, app} = this;
73 | const collect = await ctx.service.collect.listMyCollect();
74 | let allCourse = await app.mysql.select('course',{
75 | where:{publish: 1},
76 | columns: ['id', 'title', 'user_id'],
77 | limit: size,
78 | offset: (page - 1)*size
79 | });
80 | for(let i = 0; i < allCourse.length; i++){
81 | allCourse[i].isCollect = 0; // 未收藏
82 | let length = await ctx.service.purchase.getPayCount(allCourse[i].id);
83 | allCourse[i].payCount = length;
84 | for(let j = 0; j < collect.length; j++){
85 | if(collect[j].course_id == allCourse[i].id){
86 | allCourse[i].isCollect = 1; // 已收藏
87 | }
88 | }
89 | let result = await ctx.service.user.find({id:allCourse[i].user_id});
90 | allCourse[i].headImg = result.avatar;
91 | allCourse[i].undergraduate = result.username+"/"+result.undergraduate;
92 | }
93 | return allCourse;
94 | }
95 |
96 | async selectCourseWithTitle(title, page, size){
97 | const {ctx, app} = this;
98 | const collect = await ctx.service.collect.listMyCollect();
99 | let allCourse = await app.mysql.query('select id,title,user_id from course where title like "%'+title+'%" AND publish=1 LIMIT '+(page-1)*size+','+size);
100 | for(let i = 0; i < allCourse.length; i++){
101 | allCourse[i].isCollect = 0; // 未收藏
102 | let length = await ctx.service.purchase.getPayCount(allCourse[i].id);
103 | allCourse[i].payCount = length;
104 | for(let j = 0; j < collect.length; j++){
105 | if(collect[j].course_id == allCourse[i].id){
106 | allCourse[i].isCollect = 1; // 已收藏
107 | }
108 | }
109 | let result = await ctx.service.user.find({id:allCourse[i].user_id});
110 | allCourse[i].headImg = result.avatar;
111 | allCourse[i].undergraduate = result.username+"/"+result.undergraduate;
112 | }
113 | return allCourse;
114 | }
115 | }
116 |
117 | module.exports = CourseService;
--------------------------------------------------------------------------------
/client/utils/util.js:
--------------------------------------------------------------------------------
1 | var api = require('../service/api.js');
2 |
3 | const formatTime = date => {
4 | const year = date.getFullYear()
5 | const month = date.getMonth() + 1
6 | const day = date.getDate()
7 | const hour = date.getHours()
8 | const minute = date.getMinutes()
9 | const second = date.getSeconds()
10 |
11 | return [year, month, day].map(formatNumber).join('-') + ' ' + [hour, minute, second].map(formatNumber).join(':')
12 | }
13 |
14 | const formatNumber = n => {
15 | n = n.toString()
16 | return n[1] ? n : '0' + n
17 | }
18 |
19 | /**
20 | * 调用微信登录
21 | */
22 | const login = ()=> {
23 | return new Promise(function (resolve, reject) {
24 | wx.login({
25 | success: function (res) {
26 | if (res.code) {
27 | //登录远程服务器
28 | console.log(res)
29 | resolve(res);
30 | } else {
31 | reject(res);
32 | }
33 | },
34 | fail: function (err) {
35 | reject(err);
36 | }
37 | });
38 | });
39 | }
40 |
41 | const getUserInfo = ()=>{
42 | return new Promise(function (resolve, reject) {
43 | wx.getSetting({
44 | success: function (res) {
45 | if (res.authSetting['scope.userInfo']) {
46 | // 已经授权,可以直接调用 getUserInfo 获取头像昵称
47 | wx.getUserInfo({
48 | withCredentials: true,
49 | success: function (res) {
50 | console.log(res)
51 | resolve(res);
52 | },
53 | fail: function (err) {
54 | reject(err);
55 | }
56 | })
57 | }else{
58 | wx.redirectTo({
59 | url: '/pages/login/index',
60 | })
61 | }
62 | }
63 | });
64 |
65 | });
66 | }
67 |
68 | /**
69 | * 检查微信会话是否过期
70 | */
71 | const checkSession = ()=> {
72 | return new Promise(function (resolve, reject) {
73 | wx.checkSession({
74 | success: function () {
75 | resolve(true);
76 | },
77 | fail: function () {
78 | reject(false);
79 | }
80 | })
81 | });
82 | }
83 |
84 |
85 | /**
86 | * 封封微信的的request
87 | */
88 | const request = (url, data = {}, method = "POST")=> {
89 | return new Promise(function (resolve, reject) {
90 | wx.request({
91 | url: api.baseURL+url,
92 | data: data,
93 | method: method,
94 | header: {
95 | 'Content-Type': 'application/json',
96 | 'X-Booklist-Token': wx.getStorageSync('token')
97 | },
98 | success: function (res) {
99 | if (res.statusCode == 200) {
100 | if (res.data.status == -3) {
101 | //需要登录后才可以操作
102 | let code = null;
103 | return login().then((res) => {
104 | code = res.code;
105 | return getUserInfo();
106 | }).then((userInfo) => {
107 | //登录远程服务器
108 | request(api.AuthLoginByWeixin, { code: code, userInfo: userInfo }, 'POST').then(res => {
109 | if (res.errno === 0) {
110 | //存储用户信息
111 | wx.setStorageSync('userInfo', res.data.userInfo);
112 | wx.setStorageSync('token', res.data.token);
113 |
114 | resolve(res);
115 | } else {
116 | reject(res);
117 | }
118 | }).catch((err) => {
119 | reject(err);
120 | });
121 | }).catch((err) => {
122 | reject(err);
123 | })
124 | } else {
125 | resolve(res.data);
126 | }
127 | } else {
128 | reject(res.errMsg);
129 | }
130 | },
131 | fail: function (err) {
132 | reject(err)
133 | console.log("failed")
134 | }
135 | })
136 | });
137 | }
138 |
139 | const leaveLastStr = (str) => {
140 | return str.slice(0, str.length - 1);
141 | }
142 |
143 | const splitStr = (str) =>{
144 | return str.split(",");
145 | }
146 |
147 | const changeArr = (arr, source, target) => {
148 | const reg = new RegExp(source, "g");
149 | let n = JSON.parse(JSON.stringify(arr).replace(reg, target));
150 | return n;
151 | }
152 |
153 | module.exports = {
154 | login,
155 | getUserInfo,
156 | checkSession,
157 | request,
158 | leaveLastStr,
159 | splitStr,
160 | formatTime,
161 | changeArr
162 | }
163 |
--------------------------------------------------------------------------------
/client/pages/editcourse/index.js:
--------------------------------------------------------------------------------
1 | const util = require("../../utils/util.js");
2 | const app=getApp()
3 |
4 | Page({
5 | data:{
6 | titleInp:'',
7 | nameInp:'',
8 | authorInp:'',
9 | courseList:[],
10 | author:'',
11 | allCourse: '',
12 | isAdd: true,
13 | courseID: '',
14 | type: false
15 | },
16 | onLoad(options){
17 | if(options.myCourse && options.title){
18 | let resultList = util.splitStr(options.myCourse);
19 | let arr = [];
20 | for (let index in resultList) {
21 | arr.push({
22 | name: resultList[index]
23 | })
24 | }
25 | this.setData({
26 | courseList: arr,
27 | titleInp: options.title,
28 | isAdd: false,
29 | courseID: options.courseID
30 | });
31 |
32 | this.setData({
33 | allCourse: this.regroup()
34 | });
35 | }
36 |
37 | this.goSetting();
38 | },
39 | goSetting: function () {
40 | if (!app.globalData.userInfo.undergraduate || !app.globalData.userInfo.username || !app.globalData.userInfo.wechat_id || !app.globalData.userInfo.tel_id) {
41 | wx.showToast({
42 | title: '先完善个人信息',
43 | icon: 'none',
44 | duration: 2000,
45 | complete:function(){
46 | setTimeout(function(){
47 | wx.redirectTo({
48 | url: '/pages/setting/index',
49 | });
50 | },2000);
51 | }
52 | })
53 |
54 | }
55 | },
56 | titleInp(e){
57 | this.setData({
58 | titleInp:e.detail.value
59 | })
60 | },
61 | nameInp(e){
62 | this.setData({
63 | nameInp:e.detail.value
64 | })
65 | },
66 | authorInp(e){
67 | this.setData({
68 | authorInp:e.detail.value
69 | })
70 | },
71 | addBtnClick(){
72 | var list=this.data.courseList;
73 | if (this.data.nameInp == '' || this.data.authorInp == ''){
74 | wx.showToast({
75 | title: '书单信息不能为空',
76 | icon: 'none'
77 | });
78 | return;
79 | }
80 | list.push({name: "《"+this.data.nameInp+"》"+' '+this.data.authorInp+"著"});
81 | this.setData({
82 | courseList:list
83 | });
84 | this.setData({
85 | allCourse: this.regroup(),
86 | nameInp: '',
87 | authorInp:''
88 | })
89 | },
90 | regroup(){
91 | var list = this.data.courseList;
92 | var concatStr = '';
93 | for (var i = 0; i < list.length; i++) {
94 | concatStr += list[i].name + ',';
95 | }
96 | return util.leaveLastStr(concatStr);
97 | },
98 | deleteCourse(e){
99 | this.data.courseList.splice(e.target.dataset.itemIndex, 1);
100 | this.setData({
101 | courseList: this.data.courseList,
102 | allCourse: this.regroup()
103 | });
104 | },
105 | submitHandle(){
106 | const that = this;
107 | if (this.data.courseList.length <= 0)
108 | return;
109 | if (this.data.titleInp == "") {
110 | wx.showToast({
111 | title: '专业名不能为空',
112 | icon: 'none'
113 | })
114 | return;
115 | }
116 | if (this.data.allCourse == "") {
117 | wx.showToast({
118 | title: '书单信息不能为空',
119 | icon: 'none'
120 | })
121 | return;
122 | }
123 |
124 | if (this.data.isAdd) {
125 | // 添加
126 | util.request("course/addCourse",
127 | {
128 | title: this.data.titleInp,
129 | myCourse: this.data.allCourse
130 | }, 'POST').then(function (res) {
131 | if (res.status === 0) {
132 | wx.showToast({
133 | title: '添加成功',
134 | icon: 'none',
135 | duration: 2000,
136 | complete: function () {
137 | setTimeout(function(){
138 | wx.redirectTo({
139 | url: '../../pages/myblist/index',
140 | });
141 | },2000);
142 | }
143 | })
144 | }
145 | });
146 | } else {
147 | // 更新
148 | util.request("course/update", {
149 | courseID: this.data.courseID,
150 | title: this.data.titleInp,
151 | myCourse: this.data.allCourse
152 | }).then(res => {
153 | wx.showToast({
154 | title: res.result,
155 | icon: 'none',
156 | duration: 2000,
157 | complete: function () {
158 | setTimeout(function () {
159 | wx.redirectTo({
160 | url: '../../pages/details/index?courseID=' + that.data.courseID + '&title=' + that.data.titleInp,
161 | });
162 | }, 2000);
163 |
164 | }
165 | });
166 | });
167 | }
168 | }
169 | })
--------------------------------------------------------------------------------
/admin/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 = process.env.NODE_ENV === 'testing'
15 | ? require('../config/test.env')
16 | : require('../config/prod.env')
17 |
18 | const webpackConfig = merge(baseWebpackConfig, {
19 | module: {
20 | rules: utils.styleLoaders({
21 | sourceMap: config.build.productionSourceMap,
22 | extract: true,
23 | usePostCSS: true
24 | })
25 | },
26 | devtool: config.build.productionSourceMap ? config.build.devtool : false,
27 | output: {
28 | path: config.build.assetsRoot,
29 | filename: utils.assetsPath('js/[name].[chunkhash].js'),
30 | chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
31 | },
32 | plugins: [
33 | // http://vuejs.github.io/vue-loader/en/workflow/production.html
34 | new webpack.DefinePlugin({
35 | 'process.env': env
36 | }),
37 | new UglifyJsPlugin({
38 | uglifyOptions: {
39 | compress: {
40 | warnings: false
41 | }
42 | },
43 | sourceMap: config.build.productionSourceMap,
44 | parallel: true
45 | }),
46 | // extract css into its own file
47 | new ExtractTextPlugin({
48 | filename: utils.assetsPath('css/[name].[contenthash].css'),
49 | // Setting the following option to `false` will not extract CSS from codesplit chunks.
50 | // Their CSS will instead be inserted dynamically with style-loader when the codesplit chunk has been loaded by webpack.
51 | // It's currently set to `true` because we are seeing that sourcemaps are included in the codesplit bundle as well when it's `false`,
52 | // increasing file size: https://github.com/vuejs-templates/webpack/issues/1110
53 | allChunks: true,
54 | }),
55 | // Compress extracted CSS. We are using this plugin so that possible
56 | // duplicated CSS from different components can be deduped.
57 | new OptimizeCSSPlugin({
58 | cssProcessorOptions: config.build.productionSourceMap
59 | ? { safe: true, map: { inline: false } }
60 | : { safe: true }
61 | }),
62 | // generate dist index.html with correct asset hash for caching.
63 | // you can customize output by editing /index.html
64 | // see https://github.com/ampedandwired/html-webpack-plugin
65 | new HtmlWebpackPlugin({
66 | filename: process.env.NODE_ENV === 'testing'
67 | ? 'index.html'
68 | : config.build.index,
69 | template: 'index.html',
70 | inject: true,
71 | minify: {
72 | removeComments: true,
73 | collapseWhitespace: true,
74 | removeAttributeQuotes: true
75 | // more options:
76 | // https://github.com/kangax/html-minifier#options-quick-reference
77 | },
78 | // necessary to consistently work with multiple chunks via CommonsChunkPlugin
79 | chunksSortMode: 'dependency'
80 | }),
81 | // keep module.id stable when vendor modules does not change
82 | new webpack.HashedModuleIdsPlugin(),
83 | // enable scope hoisting
84 | new webpack.optimize.ModuleConcatenationPlugin(),
85 | // split vendor js into its own file
86 | new webpack.optimize.CommonsChunkPlugin({
87 | name: 'vendor',
88 | minChunks (module) {
89 | // any required modules inside node_modules are extracted to vendor
90 | return (
91 | module.resource &&
92 | /\.js$/.test(module.resource) &&
93 | module.resource.indexOf(
94 | path.join(__dirname, '../node_modules')
95 | ) === 0
96 | )
97 | }
98 | }),
99 | // extract webpack runtime and module manifest to its own file in order to
100 | // prevent vendor hash from being updated whenever app bundle is updated
101 | new webpack.optimize.CommonsChunkPlugin({
102 | name: 'manifest',
103 | minChunks: Infinity
104 | }),
105 | // This instance extracts shared chunks from code splitted chunks and bundles them
106 | // in a separate chunk, similar to the vendor chunk
107 | // see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk
108 | new webpack.optimize.CommonsChunkPlugin({
109 | name: 'app',
110 | async: 'vendor-async',
111 | children: true,
112 | minChunks: 3
113 | }),
114 |
115 | // copy custom static assets
116 | new CopyWebpackPlugin([
117 | {
118 | from: path.resolve(__dirname, '../static'),
119 | to: config.build.assetsSubDirectory,
120 | ignore: ['.*']
121 | }
122 | ])
123 | ]
124 | })
125 |
126 | if (config.build.productionGzip) {
127 | const CompressionWebpackPlugin = require('compression-webpack-plugin')
128 |
129 | webpackConfig.plugins.push(
130 | new CompressionWebpackPlugin({
131 | asset: '[path].gz[query]',
132 | algorithm: 'gzip',
133 | test: new RegExp(
134 | '\\.(' +
135 | config.build.productionGzipExtensions.join('|') +
136 | ')$'
137 | ),
138 | threshold: 10240,
139 | minRatio: 0.8
140 | })
141 | )
142 | }
143 |
144 | if (config.build.bundleAnalyzerReport) {
145 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
146 | webpackConfig.plugins.push(new BundleAnalyzerPlugin())
147 | }
148 |
149 | module.exports = webpackConfig
150 |
--------------------------------------------------------------------------------
/admin/src/pages/login/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 系统登录
5 |
6 |
7 |
8 |
9 |
11 |
12 |
13 |
21 | 登录
22 |
23 |
24 |
25 |
26 |
117 |
118 |
--------------------------------------------------------------------------------