├── .browserslistrc ├── .editorconfig ├── .eslintrc.js ├── .gitignore ├── LICENSE ├── README.md ├── babel.config.js ├── jest.config.js ├── package.json ├── public ├── color.html ├── favicon.ico ├── index.html └── mock │ ├── common │ ├── footer.json │ ├── header.json │ └── sidebar.json │ ├── notice │ └── list.json │ └── user │ ├── address.json │ ├── binding.json │ ├── info.json │ ├── log.json │ ├── login.json │ └── register.json ├── src ├── App.vue ├── api │ ├── common.ts │ ├── config.ts │ ├── notice.ts │ └── user.ts ├── assets │ ├── images │ │ ├── common │ │ │ ├── app-download.png │ │ │ └── brand.png │ │ └── login │ │ │ └── signlogo.png │ ├── logo.png │ └── styles │ │ ├── iconfont.scss │ │ ├── index.scss │ │ ├── mixin.scss │ │ ├── responsive.scss │ │ ├── transition.scss │ │ └── variables.scss ├── components │ ├── footer │ │ └── index.vue │ ├── header │ │ ├── index.vue │ │ └── modules │ │ │ ├── appDownload.vue │ │ │ ├── loginArea.vue │ │ │ ├── navigation.vue │ │ │ ├── search.vue │ │ │ └── userCard.vue │ └── sidebar │ │ └── index.vue ├── hooks │ ├── core │ │ ├── useAppConfig.ts │ │ └── useMessage.ts │ ├── event │ │ └── useScroll.ts │ ├── userLogin.ts │ └── utils │ │ └── useThrottle.ts ├── layouts │ ├── footer │ │ └── index.vue │ ├── header │ │ └── index.vue │ ├── index.vue │ └── main │ │ └── index.vue ├── main.ts ├── router │ ├── index.ts │ └── scrollBehavior.ts ├── setup │ └── element-plus │ │ └── index.ts ├── shims-vue.d.ts ├── store │ ├── getters.ts │ ├── index.ts │ ├── modules │ │ └── user.ts │ └── mutation-types.ts ├── types │ ├── axios.ts │ ├── event.d.ts │ ├── index.d.ts │ ├── mock.ts │ ├── router.ts │ └── store.ts ├── utils │ ├── axios.ts │ └── cache.ts └── views │ ├── cart │ └── index.vue │ ├── home │ └── index.vue │ ├── login │ ├── index.vue │ └── modules │ │ └── login.vue │ ├── notice │ └── index.vue │ ├── order │ └── index.vue │ └── user │ ├── address │ └── index.vue │ ├── binding │ └── index.vue │ ├── index.vue │ ├── log │ └── index.vue │ └── profile │ └── index.vue ├── tests └── unit │ └── example.spec.ts ├── tsconfig.json ├── vue.config.js └── yarn.lock /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not dead 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.{js,jsx,ts,tsx,vue}] 2 | indent_style = space 3 | indent_size = 2 4 | trim_trailing_whitespace = true 5 | insert_final_newline = true 6 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true 5 | }, 6 | extends: [ 7 | 'plugin:vue/vue3-essential', 8 | '@vue/standard', 9 | '@vue/typescript/recommended' 10 | ], 11 | parserOptions: { 12 | ecmaVersion: 2020 13 | }, 14 | rules: { 15 | 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 16 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 17 | '@typescript-eslint/no-explicit-any': 'off', 18 | '@typescript-eslint/ban-ts-ignore': 'off' 19 | }, 20 | overrides: [ 21 | { 22 | files: [ 23 | '**/__tests__/*.{j,t}s?(x)', 24 | '**/tests/unit/**/*.spec.{j,t}s?(x)' 25 | ], 26 | env: { 27 | jest: true 28 | } 29 | } 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | 6 | # local env files 7 | .env.local 8 | .env.*.local 9 | 10 | # Log files 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | pnpm-debug.log* 15 | 16 | # Editor directories and files 17 | .idea 18 | .vscode 19 | *.suo 20 | *.ntvs* 21 | *.njsproj 22 | *.sln 23 | *.sw? 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright <2020> 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, 4 | provided that the above copyright notice and this permission notice appear in all copies. 5 | 6 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING 7 | ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, 8 | DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, 9 | WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, 10 | ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-mooc-next 2 | 3 | ## Project setup 4 | ``` 5 | npm install 6 | ``` 7 | 8 | ### Compiles and hot-reloads for development 9 | ``` 10 | npm run serve 11 | ``` 12 | 13 | ### Compiles and minifies for production 14 | ``` 15 | npm run build 16 | ``` 17 | 18 | ### Run your unit tests 19 | ``` 20 | npm run test:unit 21 | ``` 22 | 23 | ### Lints and fixes files 24 | ``` 25 | npm run lint 26 | ``` 27 | 28 | ### Customize configuration 29 | See [Configuration Reference](https://cli.vuejs.org/config/). 30 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ], 5 | plugins: [ 6 | [ 7 | 'component', 8 | { 9 | libraryName: 'element-plus', 10 | styleLibraryName: 'theme-chalk' 11 | } 12 | ] 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: '@vue/cli-plugin-unit-jest/presets/typescript-and-babel', 3 | transform: { 4 | '^.+\\.vue$': 'vue-jest' 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-mooc-next", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "test:unit": "vue-cli-service test:unit", 9 | "lint": "vue-cli-service lint" 10 | }, 11 | "dependencies": { 12 | "axios": "^0.21.1", 13 | "core-js": "^3.6.5", 14 | "element-plus": "^1.0.1-beta.14", 15 | "good-storage": "^1.1.1", 16 | "normalize.css": "^8.0.1", 17 | "throttle-debounce": "^3.0.1", 18 | "vue": "^3.0.0", 19 | "vue-router": "^4.0.0-0", 20 | "vuex": "^4.0.0-0" 21 | }, 22 | "devDependencies": { 23 | "@types/axios": "^0.14.0", 24 | "@types/good-storage": "^1.1.0", 25 | "@types/jest": "^24.0.19", 26 | "@types/throttle-debounce": "^2.1.0", 27 | "@typescript-eslint/eslint-plugin": "^2.33.0", 28 | "@typescript-eslint/parser": "^2.33.0", 29 | "@vue/cli-plugin-babel": "~4.5.0", 30 | "@vue/cli-plugin-eslint": "~4.5.0", 31 | "@vue/cli-plugin-router": "~4.5.0", 32 | "@vue/cli-plugin-typescript": "~4.5.0", 33 | "@vue/cli-plugin-unit-jest": "~4.5.0", 34 | "@vue/cli-plugin-vuex": "~4.5.0", 35 | "@vue/cli-service": "~4.5.0", 36 | "@vue/compiler-sfc": "^3.0.0", 37 | "@vue/eslint-config-standard": "^5.1.2", 38 | "@vue/eslint-config-typescript": "^5.0.2", 39 | "@vue/test-utils": "^2.0.0-0", 40 | "babel-plugin-component": "^1.1.1", 41 | "eslint": "^6.7.2", 42 | "eslint-plugin-import": "^2.20.2", 43 | "eslint-plugin-node": "^11.1.0", 44 | "eslint-plugin-promise": "^4.2.1", 45 | "eslint-plugin-standard": "^4.0.0", 46 | "eslint-plugin-vue": "^7.0.0-0", 47 | "lint-staged": "^9.5.0", 48 | "sass": "^1.26.5", 49 | "sass-loader": "^8.0.2", 50 | "typescript": "4.3.5", 51 | "vue-jest": "^5.0.0-0" 52 | }, 53 | "gitHooks": { 54 | "pre-commit": "lint-staged" 55 | }, 56 | "lint-staged": { 57 | "*.{js,jsx,vue,ts,tsx}": [ 58 | "vue-cli-service lint", 59 | "git add" 60 | ] 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /public/color.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Color 7 | 32 | 33 | 34 |
35 |
36 | 字体颜色规范: 37 |
$dark-text:#000
38 |
$primary-text:#333
39 |
$regular-text:#666
40 |
$secondary-text:#999
41 |
$placeholder-text:#c0c0c0
42 |
43 |
44 | 主题色彩规范: 45 |
$theme-blue:#2688E8
46 |
$theme-green:#09BB07
47 |
$theme-orange:#FAAD15
48 |
$theme-red:#E22D2D
49 |
$theme-info:#8896B3
50 |
51 |
52 | 圆角大小规范: 53 |
$border-radius-mini: 2px
54 |
$border-radius-small: 4px
55 |
$border-radius-normal: 8px
56 |
$border-radius-large: 16px
57 |
$border-radius-largex: 20px
58 |
59 |
60 | 字体大小规范: 61 |
$font-small:12px
62 |
$font-normal:14px
63 |
$font-medium:16px
64 |
$font-large:18px
65 |
$font-largex:20px
66 |
67 |
68 | 圆角投影规范: 69 |
$box-shadow-small
70 |
$box-shadow-normal
71 |
$box-shadow-large
72 |
73 |
74 | 75 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangtunan/vue-mooc/66e2aaa9276b55a4321c49e8b607827cc2cbaede/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 13 |
14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /public/mock/common/footer.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 0, 3 | "msg": "请求成功", 4 | "data": [ 5 | { "title": "企业服务", "url": "/", "target": "_blank" }, 6 | { "title": "关于我们", "url": "/", "target": "_blank" }, 7 | { "title": "联系我们", "url": "/", "target": "_blank" }, 8 | { "title": "讲师招募", "url": "/", "target": "_blank" }, 9 | { "title": "帮助中心", "url": "/", "target": "_blank" }, 10 | { "title": "意见反馈", "url": "/", "target": "_blank" }, 11 | { "title": "慕课大学", "url": "/", "target": "_blank" }, 12 | { "title": "代码托管", "url": "/", "target": "_blank" }, 13 | { "title": "友情链接", "url": "/", "target": "_blank" } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /public/mock/common/header.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 0, 3 | "msg": "请求成功", 4 | "data": [ 5 | { "title": "免费课程", "url": "/" }, 6 | { "title": "实战课程", "url": "/" }, 7 | { "title": "金职位", "url": "/" }, 8 | { "title": "慕课教程", "url": "/" }, 9 | { "title": "专栏", "url": "/" }, 10 | { "title": "手记", "url": "/" } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /public/mock/common/sidebar.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 0, 3 | "msg": "请求成功", 4 | "data": [ 5 | { "title": "意见反馈", "icon": "feedback" }, 6 | { "title": "帮助中心", "icon": "help" }, 7 | { "title": "APP下载", "icon": "app" }, 8 | { "title": "官方微信", "icon": "weixin" } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /public/mock/notice/list.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 0, 3 | "msg": "获取成功", 4 | "data": { 5 | "list": [ 6 | { 7 | "code": 1, 8 | "isRead": false, 9 | "title": "多知多懂的程序猿,慕丝2069084诚恳的向你抛来了一个问题,不要吝惜你的才华,帮帮他吧!", 10 | "time": "2019-08-13 16:32:01" 11 | }, 12 | { 13 | "code": 1, 14 | "isRead": false, 15 | "title": "你购买的“前端要学的测试课 从Jest入门到 TDD/BDD双实战”课程又更新了,一如既往的坚持学习吧", 16 | "time": "2019-08-12 13:36:45" 17 | }, 18 | { 19 | "code": 1, 20 | "isRead": true, 21 | "title": "多知多懂的程序猿,qq_慕尼黑1028606诚恳的向你抛来了一个问题,,不要吝惜你的才华,帮帮他吧!", 22 | "time": "2019-08-13 16:32:01" 23 | }, 24 | { 25 | "code": 1, 26 | "isRead": true, 27 | "title": "你购买的“前端要学的测试课 从Jest入门到 TDD/BDD双实战”课程又更新了,一如既往的坚持学习吧!", 28 | "time": "2019-08-13 16:32:01" 29 | }, 30 | { 31 | "code": 2, 32 | "isRead": false, 33 | "title": "您于07月18日14时27分收到支付宝充值余额1000元", 34 | "time": "2019-08-13 16:32:01" 35 | }, 36 | { 37 | "code": 2, 38 | "isRead": false, 39 | "title": "尊敬的用户,您有8张优惠券即将过期,机不可失,来选一门心仪的课程吧!", 40 | "time": "2019-08-13 16:32:01" 41 | }, 42 | { 43 | "code": 2, 44 | "isRead": true, 45 | "title": "尊敬的用户,您有8张优惠券即将过期,机不可失,来选一门心仪的课程吧!", 46 | "time": "2019-08-13 16:32:01" 47 | }, 48 | { 49 | "code": 1, 50 | "isRead": true, 51 | "title": "你购买的“前端下一代开发语言TypeScript 从基础到axios实战”课程又更新了,一如既往的坚持学习吧", 52 | "time": "2019-08-13 16:32:01" 53 | }, 54 | { 55 | "code": 1, 56 | "isRead": false, 57 | "title": "多知多懂的程序猿,慕丝2069084诚恳的向你抛来了一个问题,不要吝惜你的才华,帮帮他吧!", 58 | "time": "2019-08-13 16:32:01" 59 | }, 60 | { 61 | "code": 1, 62 | "isRead": false, 63 | "title": "你购买的“前端要学的测试课 从Jest入门到 TDD/BDD双实战”课程又更新了,一如既往的坚持学习吧", 64 | "time": "2019-08-12 13:36:45" 65 | }, 66 | { 67 | "code": 1, 68 | "isRead": true, 69 | "title": "多知多懂的程序猿,qq_慕尼黑1028606诚恳的向你抛来了一个问题,,不要吝惜你的才华,帮帮他吧!", 70 | "time": "2019-08-13 16:32:01" 71 | }, 72 | { 73 | "code": 1, 74 | "isRead": true, 75 | "title": "你购买的“前端要学的测试课 从Jest入门到 TDD/BDD双实战”课程又更新了,一如既往的坚持学习吧!", 76 | "time": "2019-08-13 16:32:01" 77 | }, 78 | { 79 | "code": 2, 80 | "isRead": false, 81 | "title": "您于07月18日14时27分收到支付宝充值余额1000元", 82 | "time": "2019-08-13 16:32:01" 83 | }, 84 | { 85 | "code": 2, 86 | "isRead": false, 87 | "title": "尊敬的用户,您有8张优惠券即将过期,机不可失,来选一门心仪的课程吧!", 88 | "time": "2019-08-13 16:32:01" 89 | }, 90 | { 91 | "code": 2, 92 | "isRead": true, 93 | "title": "尊敬的用户,您有8张优惠券即将过期,机不可失,来选一门心仪的课程吧!", 94 | "time": "2019-08-13 16:32:01" 95 | }, 96 | { 97 | "code": 1, 98 | "isRead": true, 99 | "title": "你购买的“前端下一代开发语言TypeScript 从基础到axios实战”课程又更新了,一如既往的坚持学习吧", 100 | "time": "2019-08-13 16:32:01" 101 | }, 102 | { 103 | "code": 1, 104 | "isRead": false, 105 | "title": "多知多懂的程序猿,慕丝2069084诚恳的向你抛来了一个问题,不要吝惜你的才华,帮帮他吧!", 106 | "time": "2019-08-13 16:32:01" 107 | }, 108 | { 109 | "code": 1, 110 | "isRead": false, 111 | "title": "你购买的“前端要学的测试课 从Jest入门到 TDD/BDD双实战”课程又更新了,一如既往的坚持学习吧", 112 | "time": "2019-08-12 13:36:45" 113 | }, 114 | { 115 | "code": 1, 116 | "isRead": true, 117 | "title": "多知多懂的程序猿,qq_慕尼黑1028606诚恳的向你抛来了一个问题,,不要吝惜你的才华,帮帮他吧!", 118 | "time": "2019-08-13 16:32:01" 119 | }, 120 | { 121 | "code": 1, 122 | "isRead": true, 123 | "title": "你购买的“前端要学的测试课 从Jest入门到 TDD/BDD双实战”课程又更新了,一如既往的坚持学习吧!", 124 | "time": "2019-08-13 16:32:01" 125 | }, 126 | { 127 | "code": 2, 128 | "isRead": false, 129 | "title": "您于07月18日14时27分收到支付宝充值余额1000元", 130 | "time": "2019-08-13 16:32:01" 131 | }, 132 | { 133 | "code": 2, 134 | "isRead": false, 135 | "title": "尊敬的用户,您有8张优惠券即将过期,机不可失,来选一门心仪的课程吧!", 136 | "time": "2019-08-13 16:32:01" 137 | }, 138 | { 139 | "code": 2, 140 | "isRead": true, 141 | "title": "尊敬的用户,您有8张优惠券即将过期,机不可失,来选一门心仪的课程吧!", 142 | "time": "2019-08-13 16:32:01" 143 | }, 144 | { 145 | "code": 1, 146 | "isRead": true, 147 | "title": "你购买的“前端下一代开发语言TypeScript 从基础到axios实战”课程又更新了,一如既往的坚持学习吧", 148 | "time": "2019-08-13 16:32:01" 149 | } 150 | ], 151 | "total": 25 152 | } 153 | } -------------------------------------------------------------------------------- /public/mock/user/address.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 0, 3 | "msg": "获取成功", 4 | "data": [ 5 | { 6 | "id": "123456", 7 | "name": "张三", 8 | "phone": "13877778888", 9 | "address": "广东省广州市天河区", 10 | "postcode": "000000", 11 | "isDefault": true 12 | }, 13 | { 14 | "id": "456789", 15 | "name": "李四", 16 | "phone": "13877778888", 17 | "address": "湖北省武汉市武昌区", 18 | "postcode": "000000", 19 | "isDefault": false 20 | }, 21 | { 22 | "id": "987654", 23 | "name": "王五", 24 | "phone": "13877778888", 25 | "address": "广东省深圳市南山区", 26 | "postcode": "000000", 27 | "isDefault": false 28 | } 29 | ] 30 | } -------------------------------------------------------------------------------- /public/mock/user/binding.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 0, 3 | "msg": "获取成功", 4 | "data": { 5 | "email": "why583440138@gmail.com", 6 | "phone": "182******33", 7 | "password": true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /public/mock/user/info.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 0, 3 | "msg": "请求成功", 4 | "data": { 5 | "uid": "22334455", 6 | "avatar": "https://img1.sycdn.imooc.com/5882f5f70001525e01000100.jpg", 7 | "nickname": "汪汪汪", 8 | "job": "Web前端工程师", 9 | "integral": 124, 10 | "exp": 22787, 11 | "city": "广东省广州市", 12 | "signature": "故九万里,则风斯在下矣,而后乃今培风;背负青天,而莫之夭阏者,而后乃今将图南", 13 | "sex": "男", 14 | "notice": 11, 15 | "latest": { 16 | "name": "Vue3 从入门到实战 进阶式掌握完整知识体系", 17 | "section": "学习至10-1 首页附近店铺数据动态化-详情页准备" 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /public/mock/user/log.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 0, 3 | "msg": "获取成功", 4 | "data": { 5 | "list": [ 6 | { 7 | "type": "账号登陆", 8 | "time": "2019-07-11 08:08:08", 9 | "city": "广东省广州市天河区", 10 | "ip": "192.168.0.1", 11 | "device": "web", 12 | "system": "未知操作系统" 13 | }, 14 | { 15 | "type": "账号登陆", 16 | "time": "2019-07-11 08:08:08", 17 | "city": "广东省广州市天河区", 18 | "ip": "192.168.0.1", 19 | "device": "web", 20 | "system": "Mac" 21 | }, 22 | { 23 | "type": "短信验证码登陆", 24 | "time": "2019-07-11 08:08:08", 25 | "city": "广东省广州市天河区", 26 | "ip": "192.168.0.1", 27 | "device": "ipad", 28 | "system": "Mac" 29 | }, 30 | { 31 | "type": "短信验证码登陆", 32 | "time": "2019-07-11 08:08:08", 33 | "city": "广东省广州市天河区", 34 | "ip": "192.168.0.1", 35 | "device": "ipad", 36 | "system": "Mac" 37 | }, 38 | { 39 | "type": "二维码登陆", 40 | "time": "2019-07-11 08:08:08", 41 | "city": "广东省广州市天河区", 42 | "ip": "192.168.0.1", 43 | "device": "ipad", 44 | "system": "Mac" 45 | }, 46 | { 47 | "type": "二维码登陆", 48 | "time": "2019-07-11 08:08:08", 49 | "city": "广东省广州市天河区", 50 | "ip": "192.168.0.1", 51 | "device": "APP", 52 | "system": "HUAWEI" 53 | }, 54 | { 55 | "type": "账号登陆", 56 | "time": "2019-07-11 08:08:08", 57 | "city": "广东省广州市天河区", 58 | "ip": "192.168.0.1", 59 | "device": "APP", 60 | "system": "HUAWEI" 61 | }, 62 | { 63 | "type": "账号登陆", 64 | "time": "2019-07-11 08:08:08", 65 | "city": "广东省广州市天河区", 66 | "ip": "192.168.0.1", 67 | "device": "APP", 68 | "system": "HUAWEI" 69 | }, 70 | { 71 | "type": "短信验证码登陆", 72 | "time": "2019-07-11 08:08:08", 73 | "city": "广东省广州市天河区", 74 | "ip": "192.168.0.1", 75 | "device": "APP", 76 | "system": "HUAWEI" 77 | }, 78 | { 79 | "type": "短信验证码登陆", 80 | "time": "2019-07-11 08:08:08", 81 | "city": "广东省广州市天河区", 82 | "ip": "192.168.0.1", 83 | "device": "APP", 84 | "system": "HUAWEI" 85 | } 86 | ], 87 | "total": 10 88 | } 89 | } -------------------------------------------------------------------------------- /public/mock/user/login.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 0, 3 | "msg": "登录成功", 4 | "data": "1609168311953" 5 | } 6 | -------------------------------------------------------------------------------- /public/mock/user/register.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 0, 3 | "msg": "注册成功", 4 | "data": "1609168322953" 5 | } 6 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 4 | 14 | -------------------------------------------------------------------------------- /src/api/common.ts: -------------------------------------------------------------------------------- 1 | import axios from '@/utils/axios' 2 | import { FooterLinkConfig, NavConfig, MoocResponseConfig, SidebarItemConfig } from '@/types' 3 | 4 | // header navigation list 5 | export function getHeaderNav (): Promise> { 6 | return axios.get('/api/mock/common/header.json') 7 | } 8 | 9 | // footer link list 10 | export function getFooterLink (): Promise> { 11 | return axios.get('/api/mock/common/footer.json') 12 | } 13 | 14 | // sidebar list 15 | export function getSidebar (): Promise> { 16 | return axios.get('/api/mock/common/sidebar.json') 17 | } 18 | -------------------------------------------------------------------------------- /src/api/config.ts: -------------------------------------------------------------------------------- 1 | export const ERR_OK = 0 2 | export const baseURL = '' 3 | -------------------------------------------------------------------------------- /src/api/notice.ts: -------------------------------------------------------------------------------- 1 | import { MoocResponseConfig, MoocListResponseConfig } from '@/types' 2 | import axios from '@/utils/axios' 3 | 4 | // notice list 5 | export function getNoticeList (): Promise>> { 6 | return axios.get('/auth/mock/notice/list.json') 7 | } 8 | -------------------------------------------------------------------------------- /src/api/user.ts: -------------------------------------------------------------------------------- 1 | import { BindingConfig, LoginParams, MoocListResponseConfig, MoocResponseConfig, UserInfo } from '@/types' 2 | import axios from '@/utils/axios' 3 | 4 | // user login 用户登录 5 | export function userLogin (params: LoginParams): Promise> { 6 | const { account, password } = params 7 | if (account && password) { 8 | return axios.get('/api/mock/user/login.json') 9 | } else { 10 | const result = { code: -1, msg: '用户名或密码错误', data: '' } 11 | return Promise.resolve(result) 12 | } 13 | } 14 | 15 | // user register 用户注册 16 | export function userRegister (params: LoginParams): Promise> { 17 | const { account, password } = params 18 | if (account && password) { 19 | return axios.get('/api/mock/user/register.json') 20 | } else { 21 | const result = { code: -1, msg: '用户名或密码不能为空', data: '' } 22 | return Promise.resolve(result) 23 | } 24 | } 25 | 26 | // get user info 获取用户详情 27 | export function getUserInfo (token: string): Promise> { 28 | return axios.get(`/auth/mock/user/info.json?token=${token}`) 29 | } 30 | 31 | // get user binding 获取用户绑定信息 32 | export function getUserBinding (): Promise> { 33 | return axios.get('/auth/mock/user/binding.json') 34 | } 35 | 36 | // get user logs 获取用户登陆日志 37 | export function getUserLogs (): Promise>> { 38 | return axios.get('/auth/mock/user/log.json') 39 | } 40 | 41 | // get user address 获取用户地址 42 | export function getUserAddresses (): Promise> { 43 | return axios.get('/auth/mock/user/address.json') 44 | } 45 | -------------------------------------------------------------------------------- /src/assets/images/common/app-download.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangtunan/vue-mooc/66e2aaa9276b55a4321c49e8b607827cc2cbaede/src/assets/images/common/app-download.png -------------------------------------------------------------------------------- /src/assets/images/common/brand.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangtunan/vue-mooc/66e2aaa9276b55a4321c49e8b607827cc2cbaede/src/assets/images/common/brand.png -------------------------------------------------------------------------------- /src/assets/images/login/signlogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangtunan/vue-mooc/66e2aaa9276b55a4321c49e8b607827cc2cbaede/src/assets/images/login/signlogo.png -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangtunan/vue-mooc/66e2aaa9276b55a4321c49e8b607827cc2cbaede/src/assets/logo.png -------------------------------------------------------------------------------- /src/assets/styles/iconfont.scss: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'iconfont'; 3 | src: url('iconfont.eot'); 4 | src: url('iconfont.eot?#iefix') format('embedded-opentype'), 5 | url('iconfont.woff') format('woff'), 6 | url('iconfont.ttf') format('truetype'), 7 | url('iconfont.svg#iconfont') format('svg'); 8 | } 9 | .iconfont { 10 | font-family:"iconfont" !important; 11 | font-size:16px;font-style:normal; 12 | -webkit-font-smoothing: antialiased; 13 | -webkit-text-stroke-width: 0.2px; 14 | -moz-osx-font-smoothing: grayscale; 15 | } -------------------------------------------------------------------------------- /src/assets/styles/index.scss: -------------------------------------------------------------------------------- 1 | // @import './iconfont.scss'; 2 | @import '//at.alicdn.com/t/font_2293706_hjq2682ws7.css'; 3 | @import './transition.scss'; 4 | html, body { 5 | width: 100%; 6 | height: 100%; 7 | } 8 | #app { 9 | height: 100%; 10 | } 11 | h1, h2, h3, h4, h5, h6, p, dl, dt, dd { 12 | margin: 0; 13 | padding: 0; 14 | line-height: 1; 15 | } 16 | ul, ol { 17 | margin: 0; 18 | padding: 0; 19 | list-style: none; 20 | } 21 | a { 22 | text-decoration: none; 23 | color: inherit; 24 | } 25 | -------------------------------------------------------------------------------- /src/assets/styles/mixin.scss: -------------------------------------------------------------------------------- 1 | // 单行折叠省略 2 | @mixin ellipsis { 3 | text-overflow: ellipsis; 4 | white-space: nowrap; 5 | overflow: hidden; 6 | } 7 | 8 | // 多行折叠省略 9 | @mixin multline-ellipsis($line) { 10 | display: -webkit-box; 11 | -webkit-box-orient: vertical; 12 | -webkit-line-clamp: $line; 13 | text-overflow: ellipsis; 14 | overflow: hidden; 15 | } 16 | 17 | // 取消滚动条 18 | @mixin no-scrollbar { 19 | -ms-overflow-style: none; 20 | scrollbar-width: none; 21 | &::-webkit-scrollbar { 22 | width: 0!important; 23 | } 24 | } 25 | 26 | // 扩展可点击区域 27 | @mixin extend-click($extend: -5px) { 28 | &::after { 29 | content: ''; 30 | position: absolute; 31 | left: $extend; 32 | right: $extend; 33 | top: $extend; 34 | bottom: $extend; 35 | } 36 | } 37 | 38 | // 版心 39 | @mixin mooc-center($width: 1152px) { 40 | margin: 0 auto; 41 | width: $width; 42 | } 43 | -------------------------------------------------------------------------------- /src/assets/styles/responsive.scss: -------------------------------------------------------------------------------- 1 | $screen-md-size: 1600px; 2 | $screen-md-max: ($screen-md-size - 1px); 3 | @mixin respond-to() { 4 | @media only screen and (max-width: $screen-md-max) { 5 | @content 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/assets/styles/transition.scss: -------------------------------------------------------------------------------- 1 | .slide-up-enter-active, .slide-up-leave-active { 2 | transition: all 0.3s linear; 3 | transform-origin: center top; 4 | transform: scaleY(1); 5 | opacity: 1; 6 | } 7 | .slide-up-enter, .slide-up-leave-to { 8 | opacity: 0; 9 | transform: scaleY(0) 10 | } -------------------------------------------------------------------------------- /src/assets/styles/variables.scss: -------------------------------------------------------------------------------- 1 | // 字体颜色规范 2 | $dark-text: #000; 3 | $primary-text: #333; 4 | $regular-text: #666; 5 | $secondary-text: #999; 6 | $placeholder-text: #c0c0c0; 7 | 8 | // 主体色彩规范 9 | $theme-dark: #333333; 10 | $theme-blue: #2688E8; 11 | $theme-green: #09BB07; 12 | $theme-orange: #FAAD15; 13 | $theme-red: #E22D2D; 14 | $theme-info: #8896B3; 15 | 16 | // 圆角大小规范 17 | $border-radius-mini: 2px; 18 | $border-radius-small: 4px; 19 | $border-radius-normal: 8px; 20 | $border-radius-large: 16px; 21 | $border-radius-largex: 20px; 22 | 23 | // 字体大小规范 24 | $font-small: 12px; 25 | $font-normal: 14px; 26 | $font-medium: 16px; 27 | $font-large: 18px; 28 | $font-largex: 20px; 29 | 30 | // 圆角投影规范 31 | $box-shadow-color: rgba(17,17,27,0.1); 32 | $box-shadow-small: 0 2px 4px 0 $box-shadow-color; 33 | $box-shadow-normal: 0 4px 8px 0 $box-shadow-color; 34 | $box-shadow-large: 0 12px 20px 0 $box-shadow-color; 35 | 36 | :export { 37 | darkText: $dark-text; 38 | primaryText: $primary-text; 39 | regularText: $regular-text; 40 | secondaryText: $regular-text; 41 | placeHolderText: $placeholder-text; 42 | themeDark: $theme-dark; 43 | themeBlue: $theme-blue; 44 | themeGreen: $theme-green; 45 | themeOrange: $theme-orange; 46 | themeRed: $theme-red; 47 | themeInfo: $theme-info; 48 | borderRadiusMini: $border-radius-mini; 49 | borderRadiusSmall: $border-radius-small; 50 | borderRadiusNormal: $border-radius-normal; 51 | borderRadiusLarge: $border-radius-large; 52 | borderRadiusLargex: $border-radius-largex; 53 | fontSmall: $font-small; 54 | fontNormal: $font-normal; 55 | fontMedium: $font-medium; 56 | fontLarge: $font-large; 57 | fontLargex: $font-largex; 58 | } -------------------------------------------------------------------------------- /src/components/footer/index.vue: -------------------------------------------------------------------------------- 1 | 39 | 58 | 120 | -------------------------------------------------------------------------------- /src/components/header/index.vue: -------------------------------------------------------------------------------- 1 | 31 | 45 | 58 | -------------------------------------------------------------------------------- /src/components/header/modules/appDownload.vue: -------------------------------------------------------------------------------- 1 | 22 | 86 | -------------------------------------------------------------------------------- /src/components/header/modules/loginArea.vue: -------------------------------------------------------------------------------- 1 | 40 | 65 | 162 | -------------------------------------------------------------------------------- /src/components/header/modules/navigation.vue: -------------------------------------------------------------------------------- 1 | 13 | 32 | 59 | -------------------------------------------------------------------------------- /src/components/header/modules/search.vue: -------------------------------------------------------------------------------- 1 | 26 | 37 | 105 | -------------------------------------------------------------------------------- /src/components/header/modules/userCard.vue: -------------------------------------------------------------------------------- 1 | 42 | 63 | 200 | -------------------------------------------------------------------------------- /src/components/sidebar/index.vue: -------------------------------------------------------------------------------- 1 | 17 | 46 | 95 | -------------------------------------------------------------------------------- /src/hooks/core/useAppConfig.ts: -------------------------------------------------------------------------------- 1 | import { ComponentInternalInstance, getCurrentInstance } from 'vue' 2 | 3 | export function useAppConfig () { 4 | const { appContext } = getCurrentInstance() as ComponentInternalInstance 5 | return appContext.config.globalProperties 6 | } 7 | -------------------------------------------------------------------------------- /src/hooks/core/useMessage.ts: -------------------------------------------------------------------------------- 1 | import { IMessage } from 'element-plus/lib/el-message/src/types' 2 | import { useAppConfig } from './useAppConfig' 3 | 4 | export function useMessage () { 5 | const Message = useAppConfig().$message as IMessage 6 | return Message 7 | } 8 | -------------------------------------------------------------------------------- /src/hooks/event/useScroll.ts: -------------------------------------------------------------------------------- 1 | import { useThrottle } from '../utils/useThrottle' 2 | import { ref, watch, onMounted, onUnmounted } from 'vue' 3 | import { ScrollHandler } from '@/types' 4 | 5 | export function useScroll ( 6 | element: HTMLElement | Window, 7 | handler: ScrollHandler, 8 | delay?: number 9 | ) { 10 | const scrollTop = ref(0) 11 | const throttleFunc = useThrottle(() => { 12 | scrollTop.value = document.body.scrollTop || document.documentElement.scrollTop 13 | }, delay) 14 | const unwatch = watch(scrollTop, () => { 15 | handler(scrollTop.value) 16 | }) 17 | const scrollTo = () => { 18 | element.scrollTo({ left: 0, top: 0, behavior: 'auto' }) 19 | } 20 | onMounted(() => { 21 | element.addEventListener('scroll', throttleFunc) 22 | }) 23 | onUnmounted(() => { 24 | element.removeEventListener('scroll', throttleFunc) 25 | unwatch() 26 | }) 27 | return { scrollTo } 28 | } 29 | -------------------------------------------------------------------------------- /src/hooks/userLogin.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangtunan/vue-mooc/66e2aaa9276b55a4321c49e8b607827cc2cbaede/src/hooks/userLogin.ts -------------------------------------------------------------------------------- /src/hooks/utils/useThrottle.ts: -------------------------------------------------------------------------------- 1 | import { throttle } from 'throttle-debounce' 2 | 3 | export function useThrottle (handler: () => void, delay = 300) { 4 | return throttle(delay, handler) 5 | } 6 | -------------------------------------------------------------------------------- /src/layouts/footer/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 12 | -------------------------------------------------------------------------------- /src/layouts/header/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 12 | -------------------------------------------------------------------------------- /src/layouts/index.vue: -------------------------------------------------------------------------------- 1 | 16 | 41 | 68 | -------------------------------------------------------------------------------- /src/layouts/main/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 12 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import App from './App.vue' 3 | import router, { setupRouter } from './router' 4 | import { setupElementPlus } from '@/setup/element-plus/index' 5 | import { setupStore } from '@/store/index' 6 | import 'normalize.css' 7 | import '@/assets/styles/index.scss' 8 | 9 | const app = createApp(App) 10 | 11 | // setup element-plus 12 | setupElementPlus(app) 13 | 14 | // setup vuex store 15 | setupStore(app) 16 | 17 | // setup router 18 | setupRouter(app) 19 | 20 | // mount app when router is ready 21 | router.isReady().then(() => { 22 | app.mount('#app') 23 | }) 24 | -------------------------------------------------------------------------------- /src/router/index.ts: -------------------------------------------------------------------------------- 1 | import { App } from 'vue' 2 | import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router' 3 | import { scrollBehavior } from './scrollBehavior' 4 | import store from '@/store/index' 5 | import { AppRouteRecordRaw, AppRouteMetaConfig } from '@/types' 6 | import { getToken, getUserInfo } from '@/utils/cache' 7 | 8 | // basic routes 基础路由 9 | const basicRoutes: AppRouteRecordRaw[] = [ 10 | { 11 | path: '/', 12 | redirect: '/home', 13 | component: () => import('@/views/home/index.vue') 14 | }, 15 | { 16 | path: '/home', 17 | name: 'Home', 18 | component: () => import('@/views/home/index.vue') 19 | }, 20 | { 21 | path: '/login', 22 | name: 'Login', 23 | component: () => import('@/views/login/index.vue'), 24 | meta: { 25 | title: '登陆', 26 | hideFooter: true, 27 | hideHeader: true, 28 | hideSidebar: true 29 | } 30 | } 31 | ] 32 | 33 | // auth routes 权限路由 34 | const authRoutes: AppRouteRecordRaw[] = [ 35 | { 36 | path: '/notice', 37 | component: () => import('@/views/notice/index.vue'), 38 | meta: { 39 | title: '我的通知', 40 | auth: true 41 | } 42 | }, 43 | { 44 | path: '/user', 45 | redirect: '/user/binding', 46 | component: () => import('@/views/user/index.vue'), 47 | children: [ 48 | { 49 | path: 'binding', 50 | component: () => import('@/views/user/binding/index.vue'), 51 | meta: { 52 | title: '账号绑定', 53 | auth: true 54 | } 55 | }, 56 | { 57 | path: 'profile', 58 | component: () => import('@/views/user/profile/index.vue'), 59 | meta: { 60 | title: '个人资料', 61 | auth: true 62 | } 63 | }, 64 | { 65 | path: 'log', 66 | component: () => import('@/views/user/log/index.vue'), 67 | meta: { 68 | title: '操作记录', 69 | auth: true 70 | } 71 | }, 72 | { 73 | path: 'address', 74 | component: () => import('@/views/user/address/index.vue'), 75 | meta: { 76 | title: '收件地址', 77 | auth: true 78 | } 79 | } 80 | ] 81 | } 82 | ] 83 | 84 | // full routes 全部路由 85 | const routes = [ 86 | ...basicRoutes, 87 | ...authRoutes 88 | ] as RouteRecordRaw[] 89 | 90 | const router = createRouter({ 91 | history: createWebHashHistory(), 92 | scrollBehavior: scrollBehavior, 93 | routes: routes 94 | }) 95 | 96 | router.beforeEach((to, from, next) => { 97 | const meta = to.meta as AppRouteMetaConfig 98 | document.title = meta.title ? `慕课网-${meta.title}` : '慕课网-程序员的梦工厂' 99 | const token = getToken() 100 | if (token) { 101 | const userInfo = getUserInfo() 102 | if (!userInfo.uid) { 103 | store.dispatch('user/getInfo', token) 104 | } 105 | } 106 | if (meta && meta.auth) { 107 | token ? next() : next('/login') 108 | } else { 109 | next() 110 | } 111 | }) 112 | 113 | export function setupRouter (app: App) { 114 | app.use(router) 115 | } 116 | 117 | export default router 118 | -------------------------------------------------------------------------------- /src/router/scrollBehavior.ts: -------------------------------------------------------------------------------- 1 | import { RouteLocation } from 'vue-router' 2 | 3 | export function scrollBehavior (to: RouteLocation, from: RouteLocation, savedPosition: any) { 4 | if (savedPosition) { 5 | return savedPosition 6 | } 7 | return { left: 0, top: 0 } 8 | } 9 | -------------------------------------------------------------------------------- /src/setup/element-plus/index.ts: -------------------------------------------------------------------------------- 1 | import { App } from 'vue' 2 | import { 3 | ElForm, 4 | ElFormItem, 5 | ElInput, 6 | ElCheckbox, 7 | ElButton, 8 | ElBadge, 9 | ElMessage, 10 | ElTable, 11 | ElTableColumn 12 | } from 'element-plus' 13 | 14 | const components = [ 15 | ElForm, 16 | ElFormItem, 17 | ElInput, 18 | ElCheckbox, 19 | ElButton, 20 | ElBadge, 21 | ElTable, 22 | ElTableColumn 23 | ] 24 | 25 | const plugins = [ElMessage] 26 | 27 | export function setupElementPlus (app: App) { 28 | components.forEach(component => { 29 | app.use(component as any) 30 | }) 31 | plugins.forEach(plugin => { 32 | // @ts-ignore 33 | app.use(plugin) 34 | }) 35 | } 36 | -------------------------------------------------------------------------------- /src/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import type { DefineComponent } from 'vue' 3 | const component: DefineComponent<{}, {}, any> 4 | export default component 5 | } 6 | -------------------------------------------------------------------------------- /src/store/getters.ts: -------------------------------------------------------------------------------- 1 | import { UserState } from '@/types/index' 2 | 3 | export const token = (state: any) => (state.user as UserState).token 4 | 5 | export const userInfo = (state: any) => (state.user as UserState).userInfo 6 | -------------------------------------------------------------------------------- /src/store/index.ts: -------------------------------------------------------------------------------- 1 | import { App } from 'vue' 2 | import { createStore } from 'vuex' 3 | import user from './modules/user' 4 | import * as getters from './getters' 5 | 6 | export const router = createStore({ 7 | getters, 8 | modules: { 9 | user 10 | } 11 | }) 12 | 13 | export function setupStore (app: App) { 14 | app.use(router) 15 | } 16 | 17 | export default router 18 | -------------------------------------------------------------------------------- /src/store/modules/user.ts: -------------------------------------------------------------------------------- 1 | import router from '@/router/index' 2 | import * as types from '../mutation-types' 3 | import { LoginParams, UserState } from '@/types/index' 4 | import { ActionContext } from 'vuex' 5 | import { userLogin, userRegister, getUserInfo } from '@/api/user' 6 | import { ERR_OK } from '@/api/config' 7 | import { getToken, setToken, removeToken, removeUserInfo, setUserInfo, getUserInfo as getUserCacheInfo } from '@/utils/cache' 8 | 9 | const state = { 10 | token: getToken(), 11 | userInfo: getUserCacheInfo() 12 | } 13 | 14 | const mutations = { 15 | [types.SET_TOKEN] (state: UserState, token: string) { 16 | state.token = token 17 | }, 18 | [types.SET_USER_INFO] (state: UserState, userInfo: object) { 19 | state.userInfo = userInfo 20 | } 21 | } 22 | 23 | const actions = { 24 | async login ({ commit }: ActionContext, params: LoginParams) { 25 | try { 26 | const { code, data, msg } = await userLogin(params) 27 | if (code === ERR_OK) { 28 | commit(`${types.SET_TOKEN}`, setToken(data)) 29 | return Promise.resolve(true) 30 | } else { 31 | throw new Error(msg) 32 | } 33 | } catch (e) { 34 | return Promise.reject(e) 35 | } 36 | }, 37 | async register ({ commit }: ActionContext, params: LoginParams) { 38 | try { 39 | const { code, data, msg } = await userRegister(params) 40 | if (code === ERR_OK) { 41 | commit(`${types.SET_TOKEN}`, setToken(data)) 42 | return Promise.resolve(true) 43 | } else { 44 | throw new Error(msg) 45 | } 46 | } catch (e) { 47 | return Promise.reject(e) 48 | } 49 | }, 50 | async getInfo ({ commit }: ActionContext, token: string) { 51 | try { 52 | const { code, data, msg } = await getUserInfo(token) 53 | if (code === ERR_OK) { 54 | commit(`${types.SET_USER_INFO}`, setUserInfo(data)) 55 | } else { 56 | throw new Error(msg) 57 | } 58 | } catch (e) { 59 | return Promise.reject(e) 60 | } 61 | }, 62 | logout ({ commit }: ActionContext) { 63 | removeToken() 64 | removeUserInfo() 65 | commit(`${types.SET_TOKEN}`, '') 66 | commit(`${types.SET_USER_INFO}`, {}) 67 | router.push('/login') 68 | } 69 | } 70 | 71 | export default { 72 | namespaced: true, 73 | state, 74 | mutations, 75 | actions 76 | } 77 | -------------------------------------------------------------------------------- /src/store/mutation-types.ts: -------------------------------------------------------------------------------- 1 | export const SET_TOKEN = 'SET_TOKEN' 2 | 3 | export const SET_USER_INFO = 'SET_USER_INFO' 4 | -------------------------------------------------------------------------------- /src/types/axios.ts: -------------------------------------------------------------------------------- 1 | export interface MoocResponseConfig { 2 | code: number; 3 | msg?: string; 4 | data: T; 5 | } 6 | 7 | export interface MoocListResponseConfig { 8 | list: T[]; 9 | total: number; 10 | } 11 | -------------------------------------------------------------------------------- /src/types/event.d.ts: -------------------------------------------------------------------------------- 1 | // scroll事件处理函数 2 | export interface ScrollHandler { 3 | (scrollTop: number): void; 4 | } 5 | -------------------------------------------------------------------------------- /src/types/index.d.ts: -------------------------------------------------------------------------------- 1 | export * from './event' 2 | export * from './router' 3 | export * from './axios' 4 | export * from './mock' 5 | export * from './store' 6 | -------------------------------------------------------------------------------- /src/types/mock.ts: -------------------------------------------------------------------------------- 1 | // header导航数组类型 2 | export interface NavConfig { 3 | title: string; 4 | url?: string; 5 | } 6 | 7 | // footer链接数组类型 8 | export interface FooterLinkConfig extends NavConfig { 9 | target?: string; 10 | } 11 | 12 | // sidebar数组类型 13 | export interface SidebarItemConfig extends NavConfig { 14 | icon: string; 15 | } 16 | 17 | // login or register参数 18 | export interface LoginParams { 19 | account: string; 20 | password: string; 21 | } 22 | 23 | // userInfo类型 24 | export interface UserInfo { 25 | uid: string | number; 26 | avatar?: string; 27 | nickname?: string; 28 | job?: string; 29 | integral?: number | string; 30 | exp?: number | string; 31 | city?: string; 32 | signature?: string; 33 | notice?: number | string; 34 | latest?: any; 35 | } 36 | 37 | // notice通知类型 38 | export interface NoticeConfig { 39 | isRead: boolean; 40 | code: number; 41 | title: string; 42 | time: string; 43 | } 44 | 45 | // binding 类型 46 | export interface BindingConfig { 47 | email?: string; 48 | phone?: string; 49 | password?: boolean; 50 | } 51 | 52 | // log 类型 53 | export interface LogConfig { 54 | type: string; 55 | time: string; 56 | city: string; 57 | ip: string; 58 | device: string; 59 | system: string; 60 | } 61 | 62 | // address 类型 63 | export interface AddressConfig { 64 | id: string; 65 | name: string; 66 | phone: string; 67 | address: string; 68 | postcode: string; 69 | isDefault: boolean; 70 | } 71 | -------------------------------------------------------------------------------- /src/types/router.ts: -------------------------------------------------------------------------------- 1 | import { RouteRecordRaw } from 'vue-router' 2 | 3 | export interface AppRouteMetaConfig { 4 | title?: string; 5 | auth?: boolean; 6 | hideHeader?: boolean; 7 | hideFooter?: boolean; 8 | hideSidebar?: boolean; 9 | } 10 | 11 | export interface AppRouteRecordRaw extends Omit { 12 | meta?: AppRouteMetaConfig; 13 | } 14 | -------------------------------------------------------------------------------- /src/types/store.ts: -------------------------------------------------------------------------------- 1 | export interface UserState { 2 | token: string; 3 | userInfo: object; 4 | } 5 | -------------------------------------------------------------------------------- /src/utils/axios.ts: -------------------------------------------------------------------------------- 1 | import axios, { AxiosError, AxiosRequestConfig, AxiosResponse } from 'axios' 2 | import Message from 'element-plus/lib/el-message' 3 | import { getToken } from './cache' 4 | import { baseURL } from '@/api/config' 5 | const instance = axios.create({ 6 | timeout: 10000, 7 | baseURL: baseURL 8 | }) 9 | 10 | // 请求拦截 11 | instance.interceptors.request.use((config: AxiosRequestConfig) => { 12 | // add token TDD 13 | const { url } = config 14 | if (url?.indexOf('/auth') !== -1) { 15 | config.headers.token = getToken() 16 | } 17 | return config 18 | }) 19 | 20 | // 响应拦截 21 | instance.interceptors.response.use( 22 | (response: AxiosResponse) => { 23 | const { status, data } = response 24 | if (status !== 200) { 25 | return Promise.reject(data) 26 | } else { 27 | return Promise.resolve(data) 28 | } 29 | }, 30 | (err: AxiosError) => { 31 | Message.error(err.message || '接口请求异常') 32 | } 33 | ) 34 | 35 | export default instance 36 | -------------------------------------------------------------------------------- /src/utils/cache.ts: -------------------------------------------------------------------------------- 1 | import { UserInfo } from '@/types' 2 | import storage from 'good-storage' 3 | const tokenPrefix = 'mooc_token' 4 | const userInfoPrefix = 'mooc_user_info' 5 | 6 | // token cache 7 | export function setToken (token: string): string { 8 | storage.set(tokenPrefix, token) 9 | return token 10 | } 11 | export function getToken (): string { 12 | return storage.get(tokenPrefix, '') 13 | } 14 | export function removeToken () { 15 | storage.remove(tokenPrefix) 16 | } 17 | 18 | // userInfo cacje 19 | export function setUserInfo (userInfo: UserInfo): UserInfo { 20 | storage.set(userInfoPrefix, userInfo) 21 | return userInfo 22 | } 23 | export function getUserInfo (): UserInfo { 24 | return storage.get(userInfoPrefix, {}) 25 | } 26 | export function removeUserInfo () { 27 | storage.remove(userInfoPrefix) 28 | } 29 | -------------------------------------------------------------------------------- /src/views/cart/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 12 | -------------------------------------------------------------------------------- /src/views/home/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 12 | -------------------------------------------------------------------------------- /src/views/login/index.vue: -------------------------------------------------------------------------------- 1 | 28 | 47 | 100 | -------------------------------------------------------------------------------- /src/views/login/modules/login.vue: -------------------------------------------------------------------------------- 1 | 42 | 123 | 181 | -------------------------------------------------------------------------------- /src/views/notice/index.vue: -------------------------------------------------------------------------------- 1 | 41 | 92 | 180 | -------------------------------------------------------------------------------- /src/views/order/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 12 | -------------------------------------------------------------------------------- /src/views/user/address/index.vue: -------------------------------------------------------------------------------- 1 | 37 | 56 | 166 | -------------------------------------------------------------------------------- /src/views/user/binding/index.vue: -------------------------------------------------------------------------------- 1 | 39 | 63 | 130 | -------------------------------------------------------------------------------- /src/views/user/index.vue: -------------------------------------------------------------------------------- 1 | 33 | 72 | 150 | -------------------------------------------------------------------------------- /src/views/user/log/index.vue: -------------------------------------------------------------------------------- 1 | 19 | 38 | 64 | -------------------------------------------------------------------------------- /src/views/user/profile/index.vue: -------------------------------------------------------------------------------- 1 | 28 | 43 | 83 | -------------------------------------------------------------------------------- /tests/unit/example.spec.ts: -------------------------------------------------------------------------------- 1 | // import { shallowMount } from '@vue/test-utils' 2 | 3 | describe('HelloWorld.vue', () => { 4 | it('renders props.msg when passed', () => { 5 | expect(true).toBe(true) 6 | }) 7 | }) 8 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "strict": true, 6 | "jsx": "preserve", 7 | "importHelpers": true, 8 | "moduleResolution": "node", 9 | "skipLibCheck": true, 10 | "esModuleInterop": true, 11 | "allowSyntheticDefaultImports": true, 12 | "sourceMap": true, 13 | "baseUrl": ".", 14 | "types": [ 15 | "webpack-env", 16 | "jest" 17 | ], 18 | "paths": { 19 | "@/*": [ 20 | "src/*" 21 | ] 22 | }, 23 | "lib": [ 24 | "esnext", 25 | "dom", 26 | "dom.iterable", 27 | "scripthost" 28 | ] 29 | }, 30 | "include": [ 31 | "src/**/*.ts", 32 | "src/**/*.tsx", 33 | "src/**/*.vue", 34 | "tests/**/*.ts", 35 | "tests/**/*.tsx" 36 | ], 37 | "exclude": [ 38 | "node_modules" 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | devServer: { 3 | port: 4300, 4 | proxy: { 5 | '/api': { 6 | target: 'http://localhost:4300', 7 | pathRewrite: { 8 | '/api': '' 9 | } 10 | }, 11 | '/auth': { 12 | target: 'http://localhost:4300', 13 | pathRewrite: { 14 | '/auth': '' 15 | } 16 | } 17 | } 18 | } 19 | } 20 | --------------------------------------------------------------------------------