├── .browserslistrc
├── .eslintignore
├── src
├── assets
│ ├── css
│ │ └── index.scss
│ └── logo.png
├── config
│ ├── index.js
│ ├── env.staging.js
│ ├── env.development.js
│ └── env.production.js
├── views
│ └── home
│ │ ├── error.vue
│ │ ├── index.vue
│ │ └── about.vue
├── filters
│ ├── index.js
│ └── filter.js
├── store
│ ├── modules
│ │ ├── app.js
│ │ └── user.js
│ ├── getters.js
│ └── index.js
├── utils
│ ├── get-page-title.js
│ ├── validate.js
│ ├── directives.js
│ ├── cache.js
│ ├── request.js
│ └── index.js
├── plugins
│ ├── vant.js
│ └── wechatAuth.js
├── App.vue
├── main.js
├── api
│ └── user.js
├── router
│ └── index.js
├── components
│ └── TabBar.vue
└── permission.js
├── public
├── favicon.ico
└── index.html
├── static
└── gognzhonghao.jpg
├── .travis.yml
├── .env.staging
├── .env.production
├── .gitignore
├── .env.development
├── .editorconfig
├── babel.config.js
├── .postcssrc.js
├── README.en.md
├── .eslintrc.js
├── .prettierrc
├── README.md
├── LICENSE
├── package.json
├── auth.html
└── vue.config.js
/.browserslistrc:
--------------------------------------------------------------------------------
1 | > 1%
2 | last 2 versions
3 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | build/*.js
2 | src/assets
3 | public
4 | dist
5 |
--------------------------------------------------------------------------------
/src/assets/css/index.scss:
--------------------------------------------------------------------------------
1 | .app-container{
2 | padding-bottom:50px
3 | }
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sunniejs/vue-wechat-auth/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sunniejs/vue-wechat-auth/HEAD/src/assets/logo.png
--------------------------------------------------------------------------------
/static/gognzhonghao.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sunniejs/vue-wechat-auth/HEAD/static/gognzhonghao.jpg
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js: 10
3 | script: npm run test
4 | notifications:
5 | email: false
6 |
--------------------------------------------------------------------------------
/src/config/index.js:
--------------------------------------------------------------------------------
1 | // 根据环境引入不同配置 process.env.NODE_ENV
2 | const config = require('./env.' + process.env.VUE_APP_ENV)
3 | module.exports = config
4 |
--------------------------------------------------------------------------------
/src/views/home/error.vue:
--------------------------------------------------------------------------------
1 |
2 | 404
3 |
4 |
5 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/.env.staging:
--------------------------------------------------------------------------------
1 | NODE_ENV='production'
2 | # must start with VUE_APP_
3 | VUE_APP_ENV = 'staging'
4 | #appid
5 | VUE_APP_WECHAT_APPID='12345678'
6 | #authUrl
7 | VUE_APP_WECHAT_AUTH_URL='https://www.abc.com/auth.html'
8 |
--------------------------------------------------------------------------------
/src/filters/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import * as filter from './filter'
3 |
4 | Object.keys(filter).forEach(k => Vue.filter(k, filter[k]))
5 |
6 | Vue.prototype.$formatDate = Vue.filter('formatDate')
7 |
--------------------------------------------------------------------------------
/src/store/modules/app.js:
--------------------------------------------------------------------------------
1 | const state = {}
2 |
3 | const mutations = {}
4 |
5 | const actions = {}
6 |
7 | export default {
8 | namespaced: true,
9 | state,
10 | mutations,
11 | actions
12 | }
13 |
--------------------------------------------------------------------------------
/src/utils/get-page-title.js:
--------------------------------------------------------------------------------
1 | const title = '微信网页授权demo'
2 | export default function getPageTitle(pageTitle) {
3 | if (pageTitle) {
4 | return `${pageTitle} - ${title}`
5 | }
6 | return `${title}`
7 | }
8 |
--------------------------------------------------------------------------------
/src/plugins/vant.js:
--------------------------------------------------------------------------------
1 | // 按需全局引入 vant组件
2 | import Vue from 'vue'
3 | import {Button, List, Cell, Tabbar, TabbarItem} from 'vant'
4 | Vue.use(Button)
5 | Vue.use(Cell)
6 | Vue.use(List)
7 | Vue.use(Tabbar).use(TabbarItem)
8 |
--------------------------------------------------------------------------------
/src/store/getters.js:
--------------------------------------------------------------------------------
1 | const getters = {
2 | // user
3 | token: state => state.user.token,
4 | userInfo: state => state.user.userInfo,
5 | loginStatus: state => state.user.loginStatus
6 | }
7 | export default getters
8 |
--------------------------------------------------------------------------------
/.env.production:
--------------------------------------------------------------------------------
1 | NODE_ENV='production'
2 | # must start with VUE_APP_
3 | VUE_APP_ENV = 'production'
4 | #appid
5 | VUE_APP_WECHAT_APPID='1234567890'
6 | #authUrl
7 | VUE_APP_WECHAT_AUTH_URL='https://www.abc.com/auth.html'
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules/
3 | dist/
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | package-lock.json
8 | tests/**/coverage/
9 |
10 | # Editor directories and files
11 | .idea
12 | .vscode
13 | *.suo
14 | *.ntvs*
15 | *.njsproj
16 | *.sln
17 |
--------------------------------------------------------------------------------
/.env.development:
--------------------------------------------------------------------------------
1 | NODE_ENV='development'
2 | # must start with VUE_APP_
3 | VUE_APP_ENV = 'development'
4 | #appid
5 | VUE_APP_WECHAT_APPID='1234567'
6 | #authUrl
7 | VUE_APP_WECHAT_AUTH_URL='https://www.abc.com/auth.html'
8 |
9 | VUE_CLI_BABEL_TRANSPILE_MODULES = true
10 |
--------------------------------------------------------------------------------
/src/config/env.staging.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | title: 'vue-wechat-auth',
3 | baseUrl: 'https://test.xxx.com', // 测试项目地址
4 | baseApi: 'https://test.xxx.com', // 测试api请求地址
5 | api: {
6 | base_api: 'https://xxx.xxx.com/admin',
7 | common_api: 'https://xxx.xxx.com/common'
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | end_of_line = lf
9 | insert_final_newline = true
10 | trim_trailing_whitespace = true
11 |
12 | [*.md]
13 | insert_final_newline = false
14 | trim_trailing_whitespace = false
15 |
--------------------------------------------------------------------------------
/src/config/env.development.js:
--------------------------------------------------------------------------------
1 | // 本地
2 | module.exports = {
3 | title: 'vue-wechat-auth',
4 | baseUrl: 'https://test.xxx.com', // 项目地址
5 | baseApi: 'https://test.xxx.com', // 本地api请求地址
6 | api: {
7 | base_api: 'https://xxx.xxx.com/admin',
8 | common_api: 'https://xxx.xxx.com/common'
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [['@vue/cli-plugin-babel/preset', {useBuiltIns: 'entry'}]],
3 | plugins: [
4 | [
5 | 'import',
6 | {
7 | libraryName: 'vant',
8 | libraryDirectory: 'es',
9 | style: true
10 | },
11 | 'vant'
12 | ]
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/src/config/env.production.js:
--------------------------------------------------------------------------------
1 | // 正式
2 | module.exports = {
3 | title: 'vue-wechat-auth',
4 | baseUrl: 'https://www.xxx.com/', // 正式项目地址
5 | baseApi: 'https://test.xxx.com', // 正式api请求地址
6 | api: {
7 | base_api: 'https://xxx.xxx.com/admin',
8 | common_api: 'https://xxx.xxx.com/common'
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/store/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Vuex from 'vuex'
3 | import getters from './getters'
4 | import app from './modules/app'
5 | import user from './modules/user'
6 |
7 | Vue.use(Vuex)
8 |
9 | const store = new Vuex.Store({
10 | modules: {
11 | app,
12 | user
13 | },
14 | getters
15 | })
16 |
17 | export default store
18 |
--------------------------------------------------------------------------------
/.postcssrc.js:
--------------------------------------------------------------------------------
1 | // https://github.com/michael-ciniawsky/postcss-load-config
2 | module.exports = {
3 | plugins: {
4 | autoprefixer: {
5 | overrideBrowserslist: ['Android 4.1', 'iOS 7.1', 'Chrome > 31', 'ff > 31', 'ie >= 8']
6 | },
7 | 'postcss-pxtorem': {
8 | rootValue: 37.5,
9 | propList: ['*']
10 | }
11 | }
12 | }
--------------------------------------------------------------------------------
/README.en.md:
--------------------------------------------------------------------------------
1 | # vue-h5-auth
2 |
3 | vue 微信网页授权,基于 vue-cli3.0+webpack 4+vant ui + sass+ rem 适配方案+axios,开发的微信授权方案。
4 | 项目地址:[项目介绍](https://juejin.im/post/5d0361e66fb9a07f0052d7e7)
5 |
6 | # 关于我
7 |
8 | 您可以扫描添加下方的微信并备注 Soul 加交流群,给我提意见,交流学习。
9 |
10 |
11 |
12 |
13 |
14 | 如果有错误希望能够被指正,一起成长。如果对你有帮助送我一颗小星星(づ ̄3 ̄)づ╭❤~
15 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: {
4 | node: true
5 | },
6 | extends: ["plugin:vue/essential", "eslint:recommended", "@vue/prettier"],
7 | parserOptions: {
8 | parser: "babel-eslint"
9 | },
10 | rules: {
11 | "no-console": process.env.NODE_ENV === "production" ? "error" : "off",
12 | "no-debugger": process.env.NODE_ENV === "production" ? "error" : "off",
13 |
14 | }
15 | };
16 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
20 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import App from './App.vue'
3 | import router from './router'
4 | import store from './store'
5 | // 引入全局样式
6 | import '@/assets/css/index.scss'
7 | // 全局引入按需引入UI库 vant
8 | import '@/plugins/vant'
9 | // IE 兼容
10 | import '@babel/polyfill'
11 | // 移动端适配
12 | import 'lib-flexible/flexible.js'
13 | // 授权
14 | import '@/permission'
15 |
16 | Vue.config.productionTip = false
17 |
18 | new Vue({
19 | el: '#app',
20 | router,
21 | store,
22 | render: h => h(App)
23 | })
24 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 120,
3 | "tabWidth": 2,
4 | "singleQuote": true,
5 | "trailingComma": "none",
6 | "semi": false,
7 | "wrap_line_length": 120,
8 | "wrap_attributes": "auto",
9 | "proseWrap": "always",
10 | "arrowParens": "avoid",
11 | "bracketSpacing": false,
12 | "jsxBracketSameLine": true,
13 | "useTabs": false,
14 | "overrides": [{
15 | "files": ".prettierrc",
16 | "options": {
17 | "parser": "json"
18 | }
19 | }]
20 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # vue-wechat-auth
2 |
3 | 2020-05-06 更新 修复vue-router hash 生产环境拿不到code
4 |
5 | 查看文档:[vue微信授权解决方案(第三次优化)](https://juejin.im/post/5e50f4cdf265da57444ab7a1)
6 |
7 | # 运行项目
8 |
9 | ```bash
10 | // 克隆项目
11 | git clone https://github.com/sunniejs/vue-wechat-auth.git
12 | //安装依赖
13 | npm install
14 | ```
15 |
16 | # 关于我
17 |
18 | 获取更多技术相关文章,关注公众号”前端女塾“。
19 |
20 | 回复加群,即可加入”前端仙女群“
21 |
22 |
23 |
24 |
25 |
26 | 扫描添加下方的微信并备注 Sol 加交流群,交流学习,及时获取代码最新动态。
27 |
28 |
29 |
30 |
31 |
32 | 如果对你有帮助送我一颗小星星(づ ̄3 ̄)づ╭❤~
33 |
--------------------------------------------------------------------------------
/src/utils/validate.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by PanJiaChen on 16/11/18.
3 | */
4 |
5 | /**
6 | * @param {string} path
7 | * @returns {Boolean}
8 | */
9 | export function isExternal(path) {
10 | return /^(https?:|mailto:|tel:)/.test(path)
11 | }
12 |
13 | /**
14 | * @param {string} str
15 | * @returns {Boolean}
16 | */
17 | export function validUsername(str) {
18 | const valid_map = ['admin', 'editor']
19 | return valid_map.indexOf(str.trim()) >= 0
20 | }
21 |
22 | /* 手机号*/
23 | export function mobile(str) {
24 | const reg = /^[1][3,4,5,6,7,8,9][0-9]{9}$/
25 | return reg.test(str)
26 | }
27 | /* 数字 */
28 | export function number(str) {
29 | const reg = /^\d{4}$/
30 | return reg.test(str)
31 | }
32 |
--------------------------------------------------------------------------------
/src/utils/directives.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 |
3 | Vue.directive('focus', {
4 | inserted: function(el) {
5 | // 获取焦点
6 | el.focus()
7 | }
8 | })
9 |
10 | Vue.directive('numberOnly', {
11 | inserted: function(el) {
12 | // 获取焦点
13 | el.handler = function() {
14 | el.value = el.value.replace(/[^\d]/g, '')
15 | }
16 | el.addEventListener('input', el.handler)
17 | }
18 | })
19 | Vue.directive('resetPage', {
20 | inserted: function(el) {
21 | // 监听键盘收起事件
22 | document.body.addEventListener('focusout', () => {
23 | if (/(iPhone|iPad|iPod|iOS)/i.test(navigator.userAgent)) {
24 | // 软键盘收起的事件处理
25 | setTimeout(() => {
26 | const scrollHeight =
27 | document.documentElement.scrollTop || document.body.scrollTop || 0
28 | window.scrollTo(0, Math.max(scrollHeight - 1, 0))
29 | }, 100)
30 | }
31 | })
32 | }
33 | })
34 |
--------------------------------------------------------------------------------
/src/api/user.js:
--------------------------------------------------------------------------------
1 | import qs from 'qs'
2 | import request from '@/utils/request'
3 |
4 | /**
5 | * 登录接口请求token与userinfo
6 | * @param params {code: code}
7 | */
8 | export function loginByCode(params) {
9 | return request({
10 | url: '/wechat/auth2',
11 | method: 'post',
12 | data: qs.stringify(params)
13 | })
14 | }
15 | /**
16 | * 获取登录用户信息
17 | * @param params
18 | */
19 | export function getUserInfo(params) {
20 | return request({
21 | url: '/user/get_user',
22 | method: 'post',
23 | data: qs.stringify(params)
24 | })
25 | }
26 |
27 | /**
28 | * 默认请求url import { api } from '@/config' 的 base_api + /wechat/auth2
29 | * 请求common_api打头的参照如下示例:
30 | * import { api } from '@/config'
31 | * export function loginByCode(params) {
32 | * return request({
33 | * url:api.common_api+ '/wechat/auth2',
34 | * method: 'post',
35 | * data: qs.stringify(params)
36 | * })
37 | * }
38 | */
39 |
--------------------------------------------------------------------------------
/src/router/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Router from 'vue-router'
3 |
4 | Vue.use(Router)
5 | export const router = [
6 | {
7 | path: '/',
8 | name: 'index',
9 | component: () => import('@/views/home/index'),
10 | meta: {
11 | keepAlive: false
12 | }
13 | },
14 | {
15 | path: '/about',
16 | name: 'about',
17 | component: () => import('@/views/home/about'),
18 | meta: {
19 | title: '关于我',
20 | keepAlive: false
21 | }
22 | },
23 | {
24 | path: '/404',
25 | name: 'error',
26 | component: () => import('@/views/home/error'),
27 | meta: {
28 | keepAlive: false
29 | }
30 | }
31 | ]
32 |
33 | const createRouter = () =>
34 | new Router({
35 | // mode: 'history', // 如果你是 history模式 需要配置vue.config.js publicPath
36 | // base: '/app/',
37 | scrollBehavior: () => ({y: 0}),
38 | routes: router
39 | })
40 |
41 | export default createRouter()
42 |
--------------------------------------------------------------------------------
/src/filters/filter.js:
--------------------------------------------------------------------------------
1 | /**
2 | *格式化时间
3 | *yyyy-MM-dd hh:mm:ss
4 | */
5 | export function formatDate(time, fmt) {
6 | if (time === undefined || '') {
7 | return
8 | }
9 | const date = new Date(time)
10 | if (/(y+)/.test(fmt)) {
11 | fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length))
12 | }
13 | const o = {
14 | 'M+': date.getMonth() + 1,
15 | 'd+': date.getDate(),
16 | 'h+': date.getHours(),
17 | 'm+': date.getMinutes(),
18 | 's+': date.getSeconds()
19 | }
20 | for (const k in o) {
21 | if (new RegExp(`(${k})`).test(fmt)) {
22 | const str = o[k] + ''
23 | fmt = fmt.replace(RegExp.$1, RegExp.$1.length === 1 ? str : padLeftZero(str))
24 | }
25 | }
26 | return fmt
27 | }
28 |
29 | function padLeftZero(str) {
30 | return ('00' + str).substr(str.length)
31 | }
32 | /*
33 | * 隐藏用户手机号中间四位
34 | */
35 | export function hidePhone(phone) {
36 | return phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2')
37 | }
38 |
--------------------------------------------------------------------------------
/src/components/TabBar.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 首页
6 |
7 |
8 | 关于我
9 |
10 |
11 |
15 |
16 |
17 |
18 |
32 |
33 |
34 |
50 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017-present PanJiaChen
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/utils/cache.js:
--------------------------------------------------------------------------------
1 | import cookies from 'js-cookie'
2 | import storage from 'good-storage'
3 | const LoginStatusKey = 'Login-Status' // 登录态 0未授权未登录 1授权未登录 2 登陆成功
4 | const TokenKey = 'Access-Token' // token
5 | const UserInfoKey = 'User-Info' // 用户信息 {} {...}
6 | // 获取登录状态
7 | export function loadLoginStatus() {
8 | return cookies.get(LoginStatusKey) || 0
9 | }
10 | // 保持登录状态
11 | export function saveLoginStatus(status) {
12 | cookies.set(LoginStatusKey, status, {expires: 7})
13 | return status
14 | }
15 | // 删除登录状态
16 | export function removeLoginStatus() {
17 | cookies.remove(LoginStatusKey)
18 | return ''
19 | }
20 | // 获取token
21 | export function loadToken() {
22 | return storage.get(TokenKey, '')
23 | }
24 | // 保存token
25 | export function saveToken(token) {
26 | storage.set(TokenKey, token)
27 | return token
28 | }
29 | // 删除token
30 | export function removeToken() {
31 | storage.remove(TokenKey)
32 | return ''
33 | }
34 | // 获取用户信息
35 | export function loadUserInfo() {
36 | return storage.get(UserInfoKey, {})
37 | }
38 | // 保存用户信息
39 | export function saveUserInfo(userInfo) {
40 | storage.set(UserInfoKey, userInfo)
41 | return userInfo
42 | }
43 | // 删除用户信息
44 | export function removeUserInfo() {
45 | storage.remove(UserInfoKey)
46 | return {}
47 | }
48 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue-wechat-auth",
3 | "version": "2.0.0",
4 | "description": "vue wechat auth",
5 | "author": "Sunnie ",
6 | "private": true,
7 | "scripts": {
8 | "serve": "vue-cli-service serve --open",
9 | "build:prod": "vue-cli-service build",
10 | "build:stage": "vue-cli-service build --mode staging",
11 | "lint": "vue-cli-service lint"
12 | },
13 | "dependencies": {
14 | "axios": "^0.19.2",
15 | "core-js": "^3.6.4",
16 | "good-storage": "^1.1.0",
17 | "js-cookie": "^2.2.1",
18 | "lib-flexible": "^0.3.2",
19 | "vant": "^2.4.7",
20 | "vue": "^2.6.11",
21 | "vue-router": "^3.1.5",
22 | "vuex": "^3.1.2"
23 | },
24 | "devDependencies": {
25 | "@babel/polyfill": "^7.8.3",
26 | "@vue/cli-plugin-babel": "~4.2.0",
27 | "@vue/cli-plugin-eslint": "~4.2.0",
28 | "@vue/cli-service": "~4.2.0",
29 | "@vue/eslint-config-prettier": "^6.0.0",
30 | "babel-eslint": "^10.0.3",
31 | "babel-plugin-import": "^1.13.0",
32 | "babel-plugin-transform-remove-console": "^6.9.4",
33 | "eslint": "^6.7.2",
34 | "eslint-plugin-prettier": "^3.1.1",
35 | "eslint-plugin-vue": "^6.1.2",
36 | "node-sass": "^4.13.1",
37 | "postcss-pxtorem": "^4.0.1",
38 | "prettier": "^1.19.1",
39 | "sass-loader": "^8.0.2",
40 | "script-ext-html-webpack-plugin": "^2.1.4",
41 | "vue-template-compiler": "^2.6.11"
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | <% for (var i in
12 | htmlWebpackPlugin.options.cdn&&htmlWebpackPlugin.options.cdn.css) { %>
13 |
14 |
15 | <% } %>
16 | <%= webpackConfig.name %>
17 |
18 |
19 |
22 |
23 |
24 | <% for (var i in
25 | htmlWebpackPlugin.options.cdn&&htmlWebpackPlugin.options.cdn.js) { %>
26 |
27 | <% } %>
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/src/utils/request.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 | import store from '@/store'
3 | import { Toast } from 'vant'
4 | import { api } from '@/config'
5 | // create an axios instance
6 | const service = axios.create({
7 | baseURL: api.base_api, // url = base url + request url
8 | withCredentials: true, // send cookies when cross-domain requests
9 | timeout: 5000 // request timeout
10 | })
11 |
12 | // request拦截器 request interceptor
13 | service.interceptors.request.use(
14 | config => {
15 | // 不传递默认开启loading
16 | if (!config.hideloading) {
17 | // loading
18 | Toast.loading({
19 | forbidClick: true
20 | })
21 | }
22 | if (store.getters.token) {
23 | config.headers['token'] = store.getters.token
24 | }
25 | return config
26 | },
27 | error => {
28 | // do something with request error
29 | console.log(error) // for debug
30 | return Promise.reject(error)
31 | }
32 | )
33 | // respone拦截器
34 | service.interceptors.response.use(
35 | response => {
36 | Toast.clear()
37 | const res = response.data
38 | // 这里注意修改成你访问的服务端接口规则
39 | if (res.status && res.status !== 200) {
40 | Toast({
41 | message: res.info
42 | })
43 | // 登录超时,重新登录
44 | if (res.status === 401) {
45 | store.dispatch('user/fedLogOut').then(() => {
46 | location.reload()
47 | })
48 | }
49 | return Promise.reject(res || 'error')
50 | } else {
51 | return Promise.resolve(res)
52 | }
53 | },
54 | error => {
55 | Toast.clear()
56 | console.log('err' + error) // for debug
57 | return Promise.reject(error)
58 | }
59 | )
60 |
61 | export default service
62 |
--------------------------------------------------------------------------------
/src/views/home/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
VUE WECHAT AUTH
6 |
7 | vue 微信授权解决方案
8 |
9 |
10 |
11 |
12 | 首页
13 | github
14 |
15 |
16 |
17 |
18 |
40 |
68 |
--------------------------------------------------------------------------------
/src/store/modules/user.js:
--------------------------------------------------------------------------------
1 | import {loginByCode} from '@/api/user'
2 | import {
3 | saveToken,
4 | saveLoginStatus,
5 | saveUserInfo,
6 | removeToken,
7 | removeUserInfo,
8 | removeLoginStatus,
9 | loadLoginStatus,
10 | loadToken,
11 | loadUserInfo
12 | } from '@/utils/cache'
13 | const state = {
14 | loginStatus: loadLoginStatus(), // 登录状态
15 | token: loadToken(), // token
16 | userInfo: loadUserInfo() // 用户登录信息
17 | }
18 |
19 | const mutations = {
20 | SET_USERINFO: (state, userInfo) => {
21 | state.userInfo = userInfo
22 | },
23 | SET_LOGIN_STATUS: (state, loginStatus) => {
24 | state.loginStatus = loginStatus
25 | },
26 | SET_TOKEN: (state, token) => {
27 | state.token = token
28 | }
29 | }
30 |
31 | const actions = {
32 | // 登录相关,通过code获取token和用户信息,用户根据自己的需求对接后台
33 | loginWechatAuth({commit}, code) {
34 | const data = {
35 | code: code
36 | }
37 | return new Promise((resolve, reject) => {
38 | loginByCode(data)
39 | .then(res => {
40 | // 存用户信息,token
41 | commit('SET_USERINFO', saveUserInfo(res.data.user))
42 | commit('SET_TOKEN', saveToken(res.data.token))
43 | resolve(res)
44 | })
45 | .catch(error => {
46 | reject(error)
47 | })
48 | })
49 | },
50 | // 设置状态
51 | setLoginStatus({commit}, query) {
52 | if (query === 0 || query === 1) {
53 | // 上线打开注释,本地调试注释掉,保持信息最新
54 | removeToken()
55 | removeUserInfo()
56 | }
57 | // 设置不同的登录状态
58 | commit('SET_LOGIN_STATUS', saveLoginStatus(query))
59 | },
60 | // 登出
61 | fedLogOut() {
62 | // 删除token,用户信息,登陆状态
63 | removeToken()
64 | removeUserInfo()
65 | removeLoginStatus()
66 | }
67 | }
68 |
69 | export default {
70 | namespaced: true,
71 | state,
72 | mutations,
73 | actions
74 | }
75 |
--------------------------------------------------------------------------------
/src/views/home/about.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
VUE 微信授权解决方案
7 |
8 |
项目作者: sunnie
9 |
10 |
11 |

12 |
13 |
关注公众号:回复“加群”即可加 前端仙女群
14 |
15 |
16 |
17 |
18 |
19 |
36 |
83 |
--------------------------------------------------------------------------------
/src/permission.js:
--------------------------------------------------------------------------------
1 | import qs from 'qs'
2 | import router from '@/router'
3 | import store from '@/store'
4 | import wechatAuth from '@/plugins/wechatAuth'
5 | // 设置APPID
6 | wechatAuth.setAppId(process.env.VUE_APP_WECHAT_APPID)
7 | const whiteList = ['/404']
8 | router.beforeEach(async (to, from, next) => {
9 | // 在白名单,直接进入
10 | if (whiteList.indexOf(to.path) !== -1) {
11 | return next()
12 | }
13 | const {loginStatus} = store.getters
14 | switch (Number(loginStatus)) {
15 | case 0:
16 | // 获取跳转地址
17 | wechatAuth.redirect_uri = processUrl()
18 | await store.dispatch('user/setLoginStatus', 1)
19 | window.location.href = wechatAuth.authUrl
20 | break
21 | case 1:
22 | try {
23 | wechatAuth.returnFromWechat(window.location.href)
24 | const code = wechatAuth.code
25 | console.log(code)
26 | // 通过code换取token
27 | // await store.dispatch('user/loginWechatAuth', code)
28 | await store.dispatch('user/setLoginStatus', 2)
29 | // hash
30 | if (process.env.NODE_ENV !== 'development' && router.mode === 'hash') {
31 | window.location.href = window.location.origin + window.location.pathname + window.location.hash
32 | } else {
33 | next()
34 | }
35 | } catch (err) {
36 | await store.dispatch('user/setLoginStatus', 0)
37 | next('/404')
38 | }
39 | break
40 | case 2:
41 | next()
42 | break
43 | default:
44 | break
45 | }
46 | })
47 |
48 | /**
49 | * 处理url链接
50 | * @returns {string}
51 | */
52 | function processUrl() {
53 | // 本地环境换通过auth.html拿code
54 | if (process.env.NODE_ENV === 'development') {
55 | // 中间授权页地址
56 | return `${process.env.VUE_APP_WECHAT_AUTH_URL}?backUrl=${window.location.href}`
57 | }
58 | const url = window.location.href
59 | // 解决多次登录url添加重复的code与state问题
60 | const urlParams = qs.parse(url.split('?')[1])
61 | let redirectUrl = url
62 | if (urlParams.code && urlParams.state) {
63 | delete urlParams.code
64 | delete urlParams.state
65 | const query = qs.stringify(urlParams)
66 | if (query.length) {
67 | redirectUrl = `${url.split('?')[0]}?${query}`
68 | } else {
69 | redirectUrl = `${url.split('?')[0]}`
70 | }
71 | }
72 | return redirectUrl
73 | }
74 |
--------------------------------------------------------------------------------
/src/plugins/wechatAuth.js:
--------------------------------------------------------------------------------
1 | const qs = require('qs')
2 | // 应用授权作用域,snsapi_base (不弹出授权页面,直接跳转,只能获取用户openid),snsapi_userinfo (弹出授权页面,可通过openid拿到昵称、性别、所在地。并且,即使在未关注的情况下,只要用户授权,也能获取其信息)
3 | const SCOPES = ['snsapi_base', 'snsapi_userinfo']
4 |
5 | class VueWechatAuthPlugin {
6 | constructor() {
7 | this.appid = null
8 | this.redirect_uri = null
9 | this.scope = SCOPES[1]
10 | this._code = null
11 | this._redirect_uri = null
12 | }
13 |
14 | static makeState() {
15 | return (
16 | Math.random()
17 | .toString(36)
18 | .substring(2, 15) +
19 | Math.random()
20 | .toString(36)
21 | .substring(2, 15)
22 | )
23 | }
24 | setAppId(appid) {
25 | this.appid = appid
26 | }
27 | set redirect_uri(redirect_uri) {
28 | this._redirect_uri = encodeURIComponent(redirect_uri)
29 | }
30 | get redirect_uri() {
31 | return this._redirect_uri
32 | }
33 | get state() {
34 | return localStorage.getItem('wechat_auth:state')
35 | }
36 | set state(state) {
37 | localStorage.setItem('wechat_auth:state', state)
38 | }
39 | get authUrl() {
40 | if (this.appid === null) {
41 | throw new Error('appid must not be null')
42 | }
43 | if (this.redirect_uri === null) {
44 | throw new Error('redirect uri must not be null')
45 | }
46 | this.state = VueWechatAuthPlugin.makeState()
47 | return `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${this.appid}&redirect_uri=${this.redirect_uri}&response_type=code&scope=${this.scope}&state=${this.state}#wechat_redirect`
48 | }
49 | returnFromWechat(redirect_uri) {
50 | let baseWithSearch = redirect_uri.split('#')[0]
51 | let parsedUrl = ''
52 | // 本地环境
53 | if (process.env.NODE_ENV === 'development') {
54 | parsedUrl = qs.parse(redirect_uri.split('?')[1])
55 | this.state = null
56 | this._code = parsedUrl.code
57 | } else {
58 | parsedUrl = qs.parse(baseWithSearch.split('?')[1])
59 | if (this.state === null) {
60 | throw new Error("You did't set state")
61 | }
62 | if (parsedUrl.state === this.state) {
63 | this.state = null
64 | this._code = parsedUrl.code
65 | } else {
66 | this.state = null
67 | throw new Error(`Wrong state: ${parsedUrl.state}`)
68 | }
69 | }
70 | }
71 | get code() {
72 | if (this._code === null) {
73 | throw new Error('Not get the code from wechat server!')
74 | }
75 | const code = this._code
76 | this._code = null
77 | // console.log('code: ' + code)
78 | return code
79 | }
80 | }
81 | const vueWechatAuthPlugin = new VueWechatAuthPlugin()
82 |
83 | export default vueWechatAuthPlugin
84 |
--------------------------------------------------------------------------------
/src/utils/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by PanJiaChen on 16/11/18.
3 | */
4 |
5 | /**
6 | * Parse the time to string
7 | * @param {(Object|string|number)} time
8 | * @param {string} cFormat
9 | * @returns {string}
10 | */
11 | export function parseTime(time, cFormat) {
12 | if (arguments.length === 0) {
13 | return null
14 | }
15 | const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}'
16 | let date
17 | if (typeof time === 'object') {
18 | date = time
19 | } else {
20 | if (typeof time === 'string' && /^[0-9]+$/.test(time)) {
21 | time = parseInt(time)
22 | }
23 | if (typeof time === 'number' && time.toString().length === 10) {
24 | time = time * 1000
25 | }
26 | date = new Date(time)
27 | }
28 | const formatObj = {
29 | y: date.getFullYear(),
30 | m: date.getMonth() + 1,
31 | d: date.getDate(),
32 | h: date.getHours(),
33 | i: date.getMinutes(),
34 | s: date.getSeconds(),
35 | a: date.getDay()
36 | }
37 | const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
38 | let value = formatObj[key]
39 | // Note: getDay() returns 0 on Sunday
40 | if (key === 'a') {
41 | return ['日', '一', '二', '三', '四', '五', '六'][value]
42 | }
43 | if (result.length > 0 && value < 10) {
44 | value = '0' + value
45 | }
46 | return value || 0
47 | })
48 | return time_str
49 | }
50 |
51 | /**
52 | * @param {number} time
53 | * @param {string} option
54 | * @returns {string}
55 | */
56 | export function formatTime(time, option) {
57 | if (('' + time).length === 10) {
58 | time = parseInt(time) * 1000
59 | } else {
60 | time = +time
61 | }
62 | const d = new Date(time)
63 | const now = Date.now()
64 |
65 | const diff = (now - d) / 1000
66 |
67 | if (diff < 30) {
68 | return '刚刚'
69 | } else if (diff < 3600) {
70 | // less 1 hour
71 | return Math.ceil(diff / 60) + '分钟前'
72 | } else if (diff < 3600 * 24) {
73 | return Math.ceil(diff / 3600) + '小时前'
74 | } else if (diff < 3600 * 24 * 2) {
75 | return '1天前'
76 | }
77 | if (option) {
78 | return parseTime(time, option)
79 | } else {
80 | return (
81 | d.getMonth() +
82 | 1 +
83 | '月' +
84 | d.getDate() +
85 | '日' +
86 | d.getHours() +
87 | '时' +
88 | d.getMinutes() +
89 | '分'
90 | )
91 | }
92 | }
93 |
94 | /**
95 | * @param {string} url
96 | * @returns {Object}
97 | */
98 | export function param2Obj(url) {
99 | const search = url.split('?')[1]
100 | if (!search) {
101 | return {}
102 | }
103 | return JSON.parse(
104 | '{"' +
105 | decodeURIComponent(search)
106 | .replace(/"/g, '\\"')
107 | .replace(/&/g, '","')
108 | .replace(/=/g, '":"')
109 | .replace(/\+/g, ' ') +
110 | '"}'
111 | )
112 | }
113 | // 获取 localStorage
114 | export function getStorage(key) {
115 | return window.localStorage.getItem(key)
116 | }
117 | // 设置 localStorage
118 | export function setStorage(key, value) {
119 | return window.localStorage.setItem(key, value)
120 | }
121 | // 删除 localStorage
122 | export function removeStorage(key) {
123 | return Cookies.remove(key)
124 | }
125 |
--------------------------------------------------------------------------------
/auth.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 微信登录
6 |
7 |
8 |
9 |
89 |
90 |
91 |
--------------------------------------------------------------------------------
/vue.config.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const path = require('path')
3 | const defaultSettings = require('./src/config/index.js')
4 | function resolve(dir) {
5 | return path.join(__dirname, dir)
6 | }
7 | const name = defaultSettings.title || 'vue mobile template' // page title
8 | const port = 9018 // dev port
9 | const externals = {
10 | vue: 'Vue',
11 | 'vue-router': 'VueRouter',
12 | vuex: 'Vuex',
13 | vant: 'vant',
14 | axios: 'axios'
15 | }
16 | // cdn
17 | const cdn = {
18 | // 开发环境
19 | dev: {
20 | css: [],
21 | js: []
22 | },
23 | // 生产环境
24 | build: {
25 | css: ['https://cdn.jsdelivr.net/npm/vant@beta/lib/index.css'],
26 | js: [
27 | 'https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.min.js',
28 | 'https://cdnjs.cloudflare.com/ajax/libs/vue-router/3.0.6/vue-router.min.js',
29 | 'https://cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.min.js',
30 | 'https://cdnjs.cloudflare.com/ajax/libs/vuex/3.1.1/vuex.min.js',
31 | 'https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.9-1/crypto-js.min.js',
32 | 'https://cdn.jsdelivr.net/npm/vant@beta/lib/vant.min.js'
33 | ]
34 | }
35 | }
36 | module.exports = {
37 | publicPath: './', // router hash 模式使用
38 | // publicPath: process.env.NODE_ENV === 'development' ? '/' : '/app/', //router history模式使用 需要区分生产环境和开发环境,不然build会报错
39 | outputDir: 'dist',
40 | assetsDir: 'static',
41 | lintOnSave: process.env.NODE_ENV === 'development',
42 | productionSourceMap: false,
43 | devServer: {
44 | port: port,
45 | open: false,
46 | overlay: {
47 | warnings: false,
48 | errors: true
49 | }
50 | },
51 |
52 | configureWebpack: config => {
53 | // 为生产环境修改配置...
54 | if (process.env.NODE_ENV === 'production') {
55 | // externals里的模块不打包
56 | Object.assign(config, {
57 | name: name,
58 | externals: externals
59 | })
60 | }
61 | // 为开发环境修改配置...
62 | // if (process.env.NODE_ENV === 'development') {
63 | // }
64 | },
65 | chainWebpack(config) {
66 | config.plugins.delete('preload') // TODO: need test
67 | config.plugins.delete('prefetch') // TODO: need test
68 | // alias
69 | config.resolve.alias
70 | .set('@', resolve('src'))
71 | .set('assets', resolve('src/assets'))
72 | .set('api', resolve('src/api'))
73 | .set('views', resolve('src/views'))
74 | .set('components', resolve('src/components'))
75 |
76 | /**
77 | * 添加CDN参数到htmlWebpackPlugin配置中, 详见public/index.html 修改
78 | */
79 | config.plugin('html').tap(args => {
80 | if (process.env.NODE_ENV === 'production') {
81 | args[0].cdn = cdn.build
82 | }
83 | if (process.env.NODE_ENV === 'development') {
84 | args[0].cdn = cdn.dev
85 | }
86 | return args
87 | })
88 |
89 | // set preserveWhitespace
90 | config.module
91 | .rule('vue')
92 | .use('vue-loader')
93 | .loader('vue-loader')
94 | .tap(options => {
95 | options.compilerOptions.preserveWhitespace = true
96 | return options
97 | })
98 | .end()
99 |
100 | config
101 | // https://webpack.js.org/configuration/devtool/#development
102 | .when(process.env.NODE_ENV === 'development', config => config.devtool('cheap-source-map'))
103 |
104 | config.when(process.env.NODE_ENV !== 'development', config => {
105 | config
106 | .plugin('ScriptExtHtmlWebpackPlugin')
107 | .after('html')
108 | .use('script-ext-html-webpack-plugin', [
109 | {
110 | // `runtime` must same as runtimeChunk name. default is `runtime`
111 | inline: /runtime\..*\.js$/
112 | }
113 | ])
114 | .end()
115 | config.optimization.splitChunks({
116 | chunks: 'all',
117 | cacheGroups: {
118 | commons: {
119 | name: 'chunk-commons',
120 | test: resolve('src/components'), // can customize your rules
121 | minChunks: 3, // minimum common number
122 | priority: 5,
123 | reuseExistingChunk: true
124 | },
125 | libs: {
126 | name: 'chunk-libs',
127 | chunks: 'initial', // only package third parties that are initially dependent
128 | test: /[\\/]node_modules[\\/]/,
129 | priority: 10
130 | }
131 | }
132 | })
133 | config.optimization.runtimeChunk('single')
134 | })
135 | }
136 | }
137 |
--------------------------------------------------------------------------------