feat:init version 2.0.0

master
ewall 5 years ago
parent 5626983cd9
commit 630b816c14

@ -0,0 +1,2 @@
> 1%
last 2 versions

@ -0,0 +1,5 @@
[*.{js,jsx,ts,tsx,vue}]
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
insert_final_newline = true

@ -0,0 +1,14 @@
# just a flag
ENV = 'development'
# base api
VUE_APP_BASE_API = '/dev-api'
# vue-cli uses the VUE_CLI_BABEL_TRANSPILE_MODULES environment variable,
# to control whether the babel-plugin-dynamic-import-node plugin is enabled.
# It only does one thing by converting all import() to require().
# This configuration can significantly increase the speed of hot updates,
# when you have a large number of pages.
# Detail: https://github.com/vuejs/vue-cli/blob/dev/packages/@vue/babel-preset-app/index.js
VUE_CLI_BABEL_TRANSPILE_MODULES = true

@ -0,0 +1,6 @@
# just a flag
ENV = 'production'
# base api
VUE_APP_BASE_API = '/prod-api'

@ -0,0 +1,4 @@
build/*.js
src/assets
public
dist

@ -0,0 +1,18 @@
module.exports = {
root: true,
env: {
node: true
},
extends: [
'plugin:vue/essential',
'@vue/standard'
],
parserOptions: {
parser: 'babel-eslint'
},
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
"space-before-function-paren": 0,
}
}

23
.gitignore vendored

@ -0,0 +1,23 @@
.DS_Store
node_modules
/dist
package-lock.json
yarn.lock
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 Ewall&熊猫
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

@ -0,0 +1,15 @@
module.exports = {
presets: ['@vue/cli-plugin-babel/preset'],
// vant引入https://youzan.github.io/vant/#/zh-CN/quickstart#yin-ru-zu-jian
plugins: [
[
'import',
{
libraryName: 'vant',
libraryDirectory: 'es',
style: name => `${name}/style/less`
},
'vant'
]
]
}

@ -0,0 +1,57 @@
{
"name": "panda-vue-template",
"version": "0.1.0",
"author": "Ewall&熊猫",
"license": "MIT",
"keywords": [
"vue",
"vue-template",
"panda-vue-template"
],
"repository": {
"type": "git",
"url": "git+https://github.com/Ewall1106/panda-vue-template"
},
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint",
"svgo": "svgo -f src/icons/svg --config=src/icons/svgo.yml"
},
"dependencies": {
"axios": "^0.19.2",
"core-js": "^3.6.4",
"js-cookie": "^2.2.1",
"normalize.css": "^8.0.1",
"vant": "^2.5.5",
"vue": "^2.6.11",
"vue-router": "^3.1.5",
"vue-scrollto": "^2.17.1",
"vuex": "^3.1.2"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~4.2.0",
"@vue/cli-plugin-eslint": "~4.2.0",
"@vue/cli-plugin-router": "~4.2.0",
"@vue/cli-plugin-vuex": "~4.2.0",
"@vue/cli-service": "~4.2.0",
"@vue/eslint-config-standard": "^5.1.0",
"babel-eslint": "^10.0.3",
"babel-plugin-import": "^1.13.0",
"eslint": "^6.7.2",
"eslint-plugin-import": "^2.20.1",
"eslint-plugin-node": "^11.0.0",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-standard": "^4.0.0",
"eslint-plugin-vue": "^6.1.2",
"less": "^3.11.1",
"less-loader": "^5.0.0",
"node-sass": "^4.13.1",
"postcss-preset-env": "^6.7.0",
"postcss-px-to-viewport": "^1.1.1",
"sass-loader": "^8.0.2",
"svg-sprite-loader": "^4.2.1",
"svgo": "^1.3.2",
"vue-template-compiler": "^2.6.11"
}
}

@ -0,0 +1,23 @@
module.exports = {
plugins: {
// https://github.com/csstools/postcss-preset-env
'postcss-preset-env': {},
// https://github.com/evrone/postcss-px-to-viewport/blob/master/README_CN.md
'postcss-px-to-viewport': {
unitToConvert: 'px',
viewportWidth: 750,
unitPrecision: 3,
propList: ['*'],
viewportUnit: 'vw',
fontViewportUnit: 'vw',
selectorBlackList: ['.ignore', 'van'],
minPixelValue: 1,
mediaQuery: false,
replace: true,
exclude: [],
landscape: false,
landscapeUnit: 'vw',
landscapeWidth: 568
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

@ -0,0 +1,22 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled.
Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

@ -0,0 +1,5 @@
<template>
<div id="app">
<router-view />
</div>
</template>

@ -0,0 +1,16 @@
import request from '@/utils/request'
export function getInfo() {
return request({
url: '/user/info',
method: 'get'
})
}
export function login(data) {
return request({
url: '/user/login',
method: 'post',
data
})
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

@ -0,0 +1,69 @@
<template>
<div class="back-top" v-show="isShow">
<span v-scroll-to="'body'">
<svg-icon icon-class="backtop" :width="30" :height="30"/>
</span>
</div>
</template>
<script>
import Vue from 'vue'
import VueScrollTo from 'vue-scrollto'
Vue.use(VueScrollTo, {
container: 'body',
duration: 500,
easing: 'ease',
offset: 0,
force: true,
cancelable: true,
onStart: false,
onDone: false,
onCancel: false,
x: false,
y: true
})
export default {
props: {
offsetTop: {
type: Number,
default: 100
}
},
data() {
return {
timer: null,
isShow: false
}
},
mounted() {
window.addEventListener('scroll', this.handleScroll)
},
beforeDestroy() {
window.removeEventListener('scroll', this.handleScroll)
},
methods: {
handleScroll() {
if (this.timer) clearTimeout(this.timer)
this.timer = setTimeout(() => {
const scrollTop =
window.pageYOffset ||
document.documentElement.scrollTop ||
document.body.scrollTop
console.log('asss', scrollTop)
this.isShow = scrollTop > this.offsetTop
}, 15)
}
}
}
</script>
<style lang="scss" scoped>
.back-top {
position: fixed;
right: 24px;
bottom: 296px;
z-index: 999;
}
</style>

@ -0,0 +1,53 @@
<template>
<svg
:class="svgClass"
aria-hidden="true"
v-on="$listeners"
:width="width ? width : size"
:height="height ? height : size"
>
<use :href="iconName" />
</svg>
</template>
<script>
export default {
name: 'SvgIcon',
props: {
iconClass: {
type: String,
required: true
},
className: {
type: String,
default: ''
},
size: {
type: String,
default: '28px'
},
width: Number,
height: Number
},
computed: {
iconName() {
return `#icon-${this.iconClass}`
},
svgClass() {
if (this.className) {
return 'svg-icon ' + this.className
} else {
return 'svg-icon'
}
}
}
}
</script>
<style scoped>
.svg-icon {
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
</style>

@ -0,0 +1,9 @@
import Vue from 'vue'
import SvgIcon from '@/components/SvgIcon'
// register globally
Vue.component('svg-icon', SvgIcon)
const req = require.context('./svg', false, /\.svg$/)
const requireAll = requireContext => requireContext.keys().map(requireContext)
requireAll(req)

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1584620151673" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1238" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M825.568 555.328l-287.392-289.28C531.808 259.648 523.488 256.576 515.2 256.64 514.08 256.544 513.12 256 512 256c-4.672 0-9.024 1.088-13.024 2.88-4.032 1.536-7.872 3.872-11.136 7.136l-259.328 258.88c-12.512 12.48-12.544 32.736-0.032 45.248 6.24 6.272 14.432 9.408 22.656 9.408 8.192 0 16.352-3.136 22.624-9.344L480 364.288 480 928c0 17.696 14.336 32 32 32s32-14.304 32-32L544 362.72l236.192 237.728c6.24 6.272 14.496 9.44 22.688 9.44s16.32-3.104 22.56-9.312C838.016 588.128 838.048 567.84 825.568 555.328z" p-id="1239"></path><path d="M864 192 160 192C142.336 192 128 177.664 128 160s14.336-32 32-32l704 0c17.696 0 32 14.336 32 32S881.696 192 864 192z" p-id="1240"></path></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

@ -0,0 +1 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M18.448 57.545l-.244-.744-.198-.968-.132-.53v-2.181l.236-.859.24-.908.317-.953.428-1.06.561-1.103.794-1.104v-.773l.077-.724.123-.984.34-1.106.313-1.194.25-.548.289-.511.371-.569.405-.423v-2.73l.234-1.407.236-1.633.42-1.955.577-2.035.43-1.118.426-1.217.468-1.135.559-1.216.57-1.332.655-1.247.737-1.331.929-1.33.43-.762.457-.624.995-1.406 1.025-1.403 1.163-1.444 1.246-1.405 1.352-1.384 1.41-1.423 1.708-1.536 1.083-.934 1.322-1.008 1.34-.89 1.448-.855 1.392-.76 1.57-.63 1.667-.775 1.657-.532 1.653-.552 1.787-.548 1.785-.417 1.876-.347L59.128.68l1.879-.245 1.876-.252 2.002-.106h5.912l1.97.243 1.981.231 2.019.207 1.874.441 1.979.413 1.857.475 2.035.53 1.862.646 1.782.738 1.904.78 1.736.853 1.689.95 1.655 1.044 1.425.971.662.548.693.401 1.323 1.1 1.115 1.064 1.112 1.1 1.083 1.214.894 1.178 1.064 1.217.74 1.306.752 1.162.798 1.352.661 1.175 1.113 2.489.546 1.286.428 1.192.428 1.294.384 1.217.267 1.047.347 1.231.607 2.198.388 1.924.253 1.861.217 1.497.342 2.28.077.362.274.41.737 1.18.473.8.42.832.534.892.472 1.07.307 1.093.334 1.2.252 1.232.115.605.106.746v.648l-.106.643v.8l-.192.774-.35 1.5-.403.76-.299.852v.213l.142.264.4.623 1.746 2.53 1.377 1.9.66 1.267.889 1.389.774 1.52.893 1.627.894 1.828 1.006 2.069.567 1.268.518 1.239.447 1.307.44 1.175.336 1.235.342 1.16.432 2.261.343 2.31.235 2.05v2.891l-.158 1.025-.226 1.768-.308 1.59-.48 1.44-.18.588-.336.707-.28.493-.375.607-.33.383-.42.494-.375.4-.401.34-.48.207-.432.207-.355.114h-.543l-.346-.114-.66-.32-.302-.212-.317-.223-.347-.304-.35-.342-.579-.63-.684-.89-.539-.917-.538-.734-.526-.855-.741-1.517-.833-1.579-.098-.055h-.138l-.338.247-.196.415-.326.516-.567 1.533-.856 2.182-1.096 2.626-.824 1.308-.864 1.366-1.027 1.536-1.09 1.503-.557.68-.676.743-1.555 1.497.136.135.21.214.777.446 3.235 1.524 1.41.779 1.347.756 1.332.953 1.187.982.574.443.432.511.445.593.367.643.198.533.242.64.105.554.115.647-.115.433v.44l-.105.454-.242.415-.092.325-.22.394-.587.784-.543.627-.42.47-.35.348-.893.638-1.01.556-1.077.532-1.155.511-1.287.495-.693.207-.608.167-1.496.342-1.545.325-1.552.323-1.689.27-1.74.072-1.785.21h-5.539l-1.998-.114-1.86-.168-2.005-.27-1.99-.209-2.095-.286-2.03-.495-1.981-.374-1.968-.552-2.019-.707-1.98-.585-1.044-.342-.927-.323-.586-.223-.582-.12h-1.647l-1.904-.131-.962-.096-1.24-.135-.795.705-1.085.665-1.471.701-1.628.875-.99.475-1.033.376-2.281.914-1.24.305-1.3.343-1.803.344-1.13.086-1.193.1-1.246.135-1.45.053h-5.926l-3.346-.053-3.25-.321-1.644-.23-1.589-.23-1.546-.227-1.547-.305-1.442-.456-1.434-.325-1.294-.51-1.223-.474-1.142-.533-.99-.583-.984-.71-.336-.343-.44-.415-.334-.362-.3-.417-.278-.415-.215-.42-.311-.89-.109-.46-.138-.51v-.473l.138-.533v-.53l.109-.53v-1.069l.052-.564.259-.647.215-.646.39-.779.286-.3.236-.348.615-.738.49-.38.464-.266.428-.338.676-.21.543-.324.676-.341.77-.227.775-.231.897-.192.85-.11 1.008-.13 1.093-.081.284-.092h.063l.137-.115v-.13l-.2-.266-.58-.27-1.45-1.231-.975-.761-1.127-.967-1.136-1.082-1.181-1.382-1.36-1.558-.508-.843-.672-.87-.58-1.007-.522-1.1-.704-1.047-.459-1.194-.547-1.192-.546-1.33-.397-1.273-.378-1.575-.112-.057h-.115l-.059-.113h-.14l-.23.113-.114.057-.158.264-.057.321-.119.286-.206.477-.664 1.157-.345.701-.546.612-.58.736-.641.816-.677.724-.795.701-.734.658-.814.524-.89.546-.855.325-1.008.247-.99.095h-.233l-.228-.095-.18-.384-.29-.188-.38-.912-.237-.493-.255-.707-.21-.734-.113-.724-.313-1.648-.12-.972v-3.185l.12-2.379.196-1.214.23-1.252.21-1.347.374-1.254.42-1.443.431-1.407.578-1.448.545-1.38.754-1.4.699-1.52.855-1.425 1.006-1.538 1.023-1.382 1.069-1.538.891-1.071 1.142-1.227 1.202-1.237.56-.59.678-.662.985-.836 1.012-.853 1.647-1.446 1.242-.889z"/></svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

@ -0,0 +1 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M124.884 109.812L94.256 79.166c-.357-.357-.757-.629-1.129-.914a50.366 50.366 0 008.186-27.59C101.327 22.689 78.656 0 50.67 0 22.685 0 0 22.688 0 50.663c0 27.989 22.685 50.663 50.656 50.663 10.186 0 19.643-3.03 27.6-8.201.286.385.557.771.9 1.114l30.628 30.632a10.633 10.633 0 007.543 3.129c2.728 0 5.457-1.043 7.543-3.115 4.171-4.157 4.171-10.915.014-15.073M50.671 85.338C31.557 85.338 16 69.78 16 50.663c0-19.102 15.557-34.661 34.67-34.661 19.115 0 34.657 15.559 34.657 34.675 0 19.102-15.557 34.661-34.656 34.661"/></svg>

After

Width:  |  Height:  |  Size: 596 B

@ -0,0 +1 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M70.66 4.328l14.01 29.693c1.088 2.29 3.177 3.882 5.603 4.25l31.347 4.76c6.087.926 8.528 8.756 4.117 13.247L103.05 79.395c-1.75 1.78-2.544 4.352-2.132 6.867l5.352 32.641c1.043 6.337-5.33 11.182-10.778 8.19l-28.039-15.409a7.13 7.13 0 00-6.91 0l-28.039 15.41c-5.448 2.99-11.821-1.854-10.777-8.19l5.352-32.642c.415-2.515-.387-5.088-2.136-6.867L2.264 56.278C-2.146 51.787.286 43.957 6.38 43.031l31.343-4.76c2.419-.368 4.51-1.96 5.595-4.25L57.334 4.328c2.728-5.77 10.605-5.77 13.325 0z"/></svg>

After

Width:  |  Height:  |  Size: 562 B

@ -0,0 +1 @@
<svg width="130" height="130" xmlns="http://www.w3.org/2000/svg"><path d="M63.444 64.996c20.633 0 37.359-14.308 37.359-31.953 0-17.649-16.726-31.952-37.359-31.952-20.631 0-37.36 14.303-37.358 31.952 0 17.645 16.727 31.953 37.359 31.953zM80.57 75.65H49.434c-26.652 0-48.26 18.477-48.26 41.27v2.664c0 9.316 21.608 9.325 48.26 9.325H80.57c26.649 0 48.256-.344 48.256-9.325v-2.663c0-22.794-21.605-41.271-48.256-41.271z" stroke="#979797"/></svg>

After

Width:  |  Height:  |  Size: 440 B

@ -0,0 +1 @@
<svg width="128" height="110" xmlns="http://www.w3.org/2000/svg"><path d="M86.635 33.334c1.467 0 2.917.113 4.358.283C87.078 14.392 67.58.111 45.321.111 20.44.111.055 17.987.055 40.687c0 13.104 6.781 23.863 18.115 32.209l-4.527 14.352 15.82-8.364c5.666 1.182 10.207 2.395 15.858 2.395 1.42 0 2.829-.073 4.227-.189-.886-3.19-1.398-6.53-1.398-9.996 0-20.845 16.98-37.76 38.485-37.76zm-24.34-12.936c3.407 0 5.665 2.363 5.665 5.954 0 3.576-2.258 5.97-5.666 5.97-3.392 0-6.795-2.395-6.795-5.97 0-3.591 3.403-5.954 6.795-5.954zM30.616 32.323c-3.393 0-6.818-2.395-6.818-5.971 0-3.591 3.425-5.954 6.818-5.954 3.392 0 5.65 2.363 5.65 5.954 0 3.576-2.258 5.97-5.65 5.97z"/><path d="M127.945 70.52c0-19.075-18.108-34.623-38.448-34.623-21.537 0-38.5 15.548-38.5 34.623 0 19.108 16.963 34.622 38.5 34.622 4.508 0 9.058-1.2 13.584-2.395l12.414 7.167-3.404-11.923c9.087-7.184 15.854-16.712 15.854-27.471zm-50.928-5.97c-2.254 0-4.53-2.362-4.53-4.773 0-2.378 2.276-4.771 4.53-4.771 3.422 0 5.665 2.393 5.665 4.771 0 2.41-2.243 4.773-5.665 4.773zm24.897 0c-2.24 0-4.498-2.362-4.498-4.773 0-2.378 2.258-4.771 4.498-4.771 3.392 0 5.665 2.393 5.665 4.771 0 2.41-2.273 4.773-5.665 4.773z"/></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@ -0,0 +1,22 @@
# replace default config
# multipass: true
# full: true
plugins:
# - name
#
# or:
# - name: false
# - name: true
#
# or:
# - name:
# param1: 1
# param2: 2
- removeAttrs:
attrs:
- 'fill'
- 'fill-rule'

@ -0,0 +1,42 @@
import Vue from 'vue'
// A modern alternative to CSS resets
import './styles/reset.css'
// https://github.com/necolas/normalize.css
import 'normalize.css/normalize.css'
import App from './App.vue'
import router from './router'
import store from './store'
import './icons'
import './permission'
// vant
import {
Button,
Toast,
Dialog,
Field,
Form,
Notify,
Cell,
CellGroup,
NavBar
} from 'vant'
Vue.use(Toast)
.use(Dialog)
.use(Notify)
.use(Field)
.use(Form)
.use(Button)
.use(Cell)
.use(CellGroup)
.use(NavBar)
Vue.config.productionTip = false
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')

@ -0,0 +1,53 @@
import router from './router'
import store from './store'
import { Toast } from 'vant'
import { getToken } from '@/utils/auth' // get token from cookie
const whiteList = ['/login'] // 白名单
router.beforeEach(async (to, from, next) => {
// 设置标题
document.title = '熊猫商城'
// 根据token判断用户是否登录
const hasToken = getToken()
console.log('adsfa', hasToken)
if (hasToken) {
if (to.path === '/login') {
// 如果已经登录了而去的又是login页就重定向到首页
next({ path: '/' })
} else {
const hasUserInfo = store.getters.name
console.log('asdfas', hasUserInfo)
if (hasUserInfo) {
next()
} else {
// 如果用户刷新了浏览器那么需要重新请求基本信息塞到vuex中进行状态管理
try {
await store.dispatch('user/getInfo')
next()
} catch (error) {
if (whiteList.indexOf(to.path) !== -1) {
next()
} else {
// 清空token重新去登录
await store.dispatch('user/resetToken')
Toast.fail('出错了')
next(`/login?redirect=${encodeURIComponent(location.href)}`)
}
}
}
}
} else {
if (whiteList.indexOf(to.path) !== -1) {
// 白名单没有token也直接放行
next()
} else {
// 反之则去登录页面
next(`/login?redirect=${encodeURIComponent(location.href)}`)
// NProgress.done()
}
}
})

@ -0,0 +1,29 @@
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
const routes = [
{
path: '*',
name: 'Home',
component: () => import('@/views/home')
},
{
path: '/login',
name: 'Login',
component: () => import('@/views/auth/login')
},
{
path: '/iconfont',
name: 'IconFont',
component: () => import('@/views/about/iconfont')
}
]
const router = new VueRouter({
scrollBehavior: () => ({ y: 0 }),
routes
})
export default router

@ -0,0 +1,7 @@
const getters = {
avatar: state => state.user.avatar,
name: state => state.user.name,
token: state => state.user.token
}
export default getters

@ -0,0 +1,22 @@
import Vue from 'vue'
import Vuex from 'vuex'
import getters from './getters'
Vue.use(Vuex)
// https://webpack.js.org/guides/dependency-management/#requirecontext
const modulesFiles = require.context('./modules', true, /\.js$/)
const modules = modulesFiles.keys().reduce((modules, modulePath) => {
const moduleName = modulePath.replace(/^\.\/(.*)\.\w+$/, '$1')
const value = modulesFiles(modulePath)
modules[moduleName] = value.default
return modules
}, {})
const store = new Vuex.Store({
modules,
getters
})
export default store

@ -0,0 +1,90 @@
import { login, logout, getInfo } from '@/api/user'
import { getToken, setToken, removeToken } from '@/utils/auth'
const state = {
token: getToken(),
name: '',
avatar: ''
}
const mutations = {
SET_TOKEN: (state, token) => {
state.token = token
},
SET_NAME: (state, name) => {
state.name = name
},
SET_AVATAR: (state, avatar) => {
state.avatar = avatar
}
}
const actions = {
// 登录
login({ commit }, userInfo) {
const { phonenum, password } = userInfo
return new Promise((resolve, reject) => {
login({ phonenum, password })
.then(res => {
const { token } = res.entry
commit('SET_TOKEN', token)
setToken(token)
resolve()
})
.catch(error => {
reject(error)
})
})
},
// 登出
logout({ commit, state }) {
return new Promise((resolve, reject) => {
logout(state.token)
.then(() => {
commit('SET_TOKEN', '')
removeToken()
resolve()
})
.catch(error => {
reject(error)
})
})
},
// 获取基本用户信息
getInfo({ commit, state }) {
return new Promise((resolve, reject) => {
getInfo(state.token)
.then(res => {
const data = res.entry
if (!data) {
reject(new Error('获取基本信息失败,请重新登录'))
}
commit('SET_NAME', data.name)
commit('SET_AVATAR', data.avatar)
resolve(data)
})
.catch(error => {
reject(error)
})
})
},
// 重置token
resetToken({ commit }) {
return new Promise(resolve => {
commit('SET_TOKEN', '')
removeToken()
resolve()
})
}
}
export default {
namespaced: true,
state,
mutations,
actions
}

@ -0,0 +1,129 @@
/* http://meyerweb.com/eric/tools/css/reset/
v2.0 | 20110126
License: none (public domain)
*/
html,
body,
div,
span,
applet,
object,
iframe,
h1,
h2,
h3,
h4,
h5,
h6,
p,
blockquote,
pre,
a,
abbr,
acronym,
address,
big,
cite,
code,
del,
dfn,
em,
img,
ins,
kbd,
q,
s,
samp,
small,
strike,
strong,
sub,
sup,
tt,
var,
b,
u,
i,
center,
dl,
dt,
dd,
ol,
ul,
li,
fieldset,
form,
label,
legend,
table,
caption,
tbody,
tfoot,
thead,
tr,
th,
td,
article,
aside,
canvas,
details,
embed,
figure,
figcaption,
footer,
header,
hgroup,
menu,
nav,
output,
ruby,
section,
summary,
time,
mark,
audio,
video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article,
aside,
details,
figcaption,
figure,
footer,
header,
hgroup,
menu,
nav,
section {
display: block;
}
body {
line-height: 1;
}
ol,
ul {
list-style: none;
}
blockquote,
q {
quotes: none;
}
blockquote:before,
blockquote:after,
q:before,
q:after {
content: "";
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}

@ -0,0 +1,15 @@
import Cookies from 'js-cookie'
const key = 'MallToken'
export function getToken() {
return Cookies.get(key)
}
export function setToken(token) {
return Cookies.set(key, token)
}
export function removeToken() {
return Cookies.remove(key)
}

@ -0,0 +1,60 @@
import axios from 'axios'
import { Toast, Dialog } from 'vant'
import store from '@/store'
import { getToken } from '@/utils/auth'
// 创建一个axios实例
const service = axios.create({
baseURL: process.env.VUE_APP_BASE_API,
// withCredentials: true,
timeout: 5000
})
// 请求拦截器
service.interceptors.request.use(
config => {
if (store.getters.token) {
// ['X-Token']是我这里自定义测试而塞到请求头中
config.headers['X-TOKEN'] = getToken()
}
return config
},
error => {
console.log(error)
return Promise.reject(error)
}
)
// 响应拦截器
service.interceptors.response.use(
response => {
const res = response.data
// 与后端约定的错误码
if (res.code !== 200) {
Toast.fail(res.message)
// 现约定 50001:无效token 50002:token过期
if (res.code === 50001 || res.code === 50002) {
Dialog.alert({
title: '提示',
message: '您还未登录或登录已过期,请重新登录'
}).then(() => {
store.dispatch('user/resetToken').then(() => {
location.reload()
})
})
}
return Promise.reject(new Error(res.message || 'Error'))
} else {
return res
}
},
error => {
console.log('err' + error)
Toast.fail(error.message)
return Promise.reject(error)
}
)
export default service

@ -0,0 +1,26 @@
<template>
<div class="icon-font">
<van-nav-bar title="svg图标" left-text="" left-arrow @click-left="onClickLeft"/>
<svg-icon icon-class="backtop" :width="30" :height="30" />
<svg-icon icon-class="wechat" :width="30" :height="30" />
<span style="color:red">
<svg-icon icon-class="qq" :width="30" :height="30" />
</span>
<span fill="red">
<svg-icon icon-class="search" :width="30" :height="30" />
</span>
<svg-icon icon-class="user" :width="30" :height="30" />
</div>
</template>
<script>
export default {
name: 'IconFont',
methods: {
onClickLeft() {
this.$router.go(-1)
}
}
}
</script>

@ -0,0 +1,138 @@
<template>
<div class="login-container">
<div class="header">
<img class="header__logo" src="@/assets/logo.png" alt="logo" />
<p class="header__title">panda-vue-template</p>
</div>
<van-form class="form" @submit="onSubmit">
<van-field
v-model="form.phonenum"
type="number"
required
clearable
name="手机号"
label="手机号"
placeholder="请输入手机号"
:rules="[{validator:checkPhoneNum, required: true, message:'请填写正确的手机号!'}]"
/>
<van-field
v-model="form.password"
type="number"
required
center
clearable
name="手机号"
label="验证码"
placeholder="请输入短信验证码"
:rules="[{ required: true, message: '请填写正确的验证码!' }]"
>
<van-button
slot="button"
size="small"
plain
type="info"
native-type="button"
@click.stop="form.password = 1234"
>发送验证码</van-button>
</van-field>
<div style="margin: 36px;">
<van-button
round
block
:loading="loading"
type="info"
loading-text="登录中..."
native-type="submit"
>提交</van-button>
</div>
</van-form>
</div>
</template>
<script>
export default {
name: 'Login',
data() {
return {
form: {
phonenum: 123456789,
password: 1234
},
loading: false
}
},
watch: {
$route: {
handler(route) {
console.log('route:', route)
this.redirect = (route.query && route.query.redirect) || '/'
},
immediate: true
}
},
methods: {
//
checkPhoneNum(num) {
if (num === 123456789) return true
const reg = /^[1][3,4,5,7,8][0-9]{9}$/
if (reg.test(num)) {
return true
}
return false
},
//
onSubmit() {
this.loading = true
this.$store
.dispatch('user/login', this.form)
.then(() => {
this.$notify({
type: 'success',
message: '登录成功',
duration: 2000,
onOpened: () => {
location.href = this.redirect
this.loading = false
}
})
})
.catch(() => {
this.loading = false
})
}
}
}
</script>
<style lang="scss" scoped>
.login-container {
.header {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
.header__logo {
display: block;
width: 150px;
height: 150px;
padding-top: 80px;
}
.header__title {
font-size: 36px;
color: #000;
font-weight: normal;
padding-top: 30px;
}
}
.form {
// background: red;
padding: 24px;
margin-top: 50px;
}
}
</style>

@ -0,0 +1,69 @@
<template>
<div class="home">
<img class="logo" src="@/assets/logo.png" alt="logo" />
<van-cell-group title="相关介绍:">
<van-cell title="vue-mall" is-link url="https://github.com/Ewall1106/mall" />
<van-cell
title="panda-vue-template"
is-link
url="https://github.com/Ewall1106/panda-vue-template"
/>
<van-cell title="yapi接口文档" is-link url="http://yapi.demo.qunar.com/project/17982" />
</van-cell-group>
<van-cell-group title="功能介绍:">
<van-cell
title="一次讲清移动端适配解决方案—rem和vw"
is-link
url="https://github.com/Ewall1106/panda-vue-template"
/>
<van-cell title="vue-cli3.0与vant的引入" is-link url="https://github.com/Ewall1106/mall" />
<van-cell title="axios的封装与数据mock" is-link url="https://github.com/Ewall1106/mall" />
<van-cell title="vuex的基本使用" is-link url="https://github.com/Ewall1106/mall" />
</van-cell-group>
<van-cell-group title="组件补充:">
<van-cell title="svg图标" is-link to="IconFont" />
<!-- <van-cell title="返回顶部" is-link to="About" target="_blank" /> -->
</van-cell-group>
<back-top />
</div>
</template>
<script>
import BackTop from '@/components/BackTop'
export default {
name: 'Home',
components: {
BackTop
},
data() {
return {
val: '11111'
}
},
mounted() {},
methods: {}
}
</script>
<style lang="scss" scoped>
.home {
min-height: 100vh;
background: #f5f5f593;
.logo {
display: block;
width: 200px;
margin: 0 auto;
padding-top: 40px;
}
h2 {
padding: 10px 0;
}
a {
color: #42b983;
}
}
</style>

@ -0,0 +1,103 @@
'use strict'
const path = require('path')
const mockUrl = 'http://yapi.demo.qunar.com/mock/17982'
function resolve (dir) {
return path.join(__dirname, dir)
}
// All configuration item explanations can be find in https://cli.vuejs.org/config/
module.exports = {
publicPath: '/',
outputDir: 'dist',
assetsDir: 'static',
lintOnSave: process.env.NODE_ENV === 'development',
productionSourceMap: false,
devServer: {
port: 8080,
open: true,
overlay: {
warnings: false,
errors: true
},
proxy: {
'/dev-api': {
target: mockUrl,
pathRewrite: { '^/dev-api': '' },
secure: false,
changeOrigin: true
}
}
},
configureWebpack: {
// provide the app's title in webpack's name field, so that
// it can be accessed in index.html to inject the correct title.
name: 'panda vue template',
resolve: {
alias: {
'@': resolve('src')
}
}
},
chainWebpack (config) {
config.plugins.delete('preload') // TODO: need test
config.plugins.delete('prefetch') // TODO: need test
// set svg-sprite-loader
config.module
.rule('svg')
.exclude.add(resolve('src/icons'))
.end()
config.module
.rule('icons')
.test(/\.svg$/)
.include.add(resolve('src/icons'))
.end()
.use('svg-sprite-loader')
.loader('svg-sprite-loader')
.options({
symbolId: 'icon-[name]'
})
.end()
// set preserveWhitespace
config.module
.rule('vue')
.use('vue-loader')
.loader('vue-loader')
.tap(options => {
options.compilerOptions.preserveWhitespace = true
return options
})
.end()
config
// https://webpack.js.org/configuration/devtool/#development
.when(process.env.NODE_ENV === 'development', config =>
config.devtool('cheap-source-map')
)
config.when(process.env.NODE_ENV !== 'development', config => {
config.optimization.splitChunks({
chunks: 'all',
cacheGroups: {
libs: {
name: 'chunk-libs',
test: /[\\/]node_modules[\\/]/,
priority: 10,
chunks: 'initial' // only package third parties that are initially dependent
},
commons: {
name: 'chunk-commons',
test: resolve('src/components'), // can customize your rules
minChunks: 3, // minimum common number
priority: 5,
reuseExistingChunk: true
}
}
})
config.optimization.runtimeChunk('single')
})
}
}
Loading…
Cancel
Save