diff --git a/.browserslistrc b/.browserslistrc
new file mode 100644
index 0000000..d6471a3
--- /dev/null
+++ b/.browserslistrc
@@ -0,0 +1,2 @@
+> 1%
+last 2 versions
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..7053c49
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,5 @@
+[*.{js,jsx,ts,tsx,vue}]
+indent_style = space
+indent_size = 2
+trim_trailing_whitespace = true
+insert_final_newline = true
diff --git a/.env.development b/.env.development
new file mode 100644
index 0000000..8f5856d
--- /dev/null
+++ b/.env.development
@@ -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
diff --git a/.env.production b/.env.production
new file mode 100644
index 0000000..80c8103
--- /dev/null
+++ b/.env.production
@@ -0,0 +1,6 @@
+# just a flag
+ENV = 'production'
+
+# base api
+VUE_APP_BASE_API = '/prod-api'
+
diff --git a/.eslintignore b/.eslintignore
new file mode 100644
index 0000000..e6529fc
--- /dev/null
+++ b/.eslintignore
@@ -0,0 +1,4 @@
+build/*.js
+src/assets
+public
+dist
diff --git a/.eslintrc.js b/.eslintrc.js
new file mode 100644
index 0000000..c18ca28
--- /dev/null
+++ b/.eslintrc.js
@@ -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,
+ }
+}
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..997c6eb
--- /dev/null
+++ b/.gitignore
@@ -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?
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..232db83
--- /dev/null
+++ b/LICENSE
@@ -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.
diff --git a/babel.config.js b/babel.config.js
new file mode 100644
index 0000000..6c909a4
--- /dev/null
+++ b/babel.config.js
@@ -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'
+ ]
+ ]
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..d27b5e0
--- /dev/null
+++ b/package.json
@@ -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"
+ }
+}
diff --git a/postcss.config.js b/postcss.config.js
new file mode 100644
index 0000000..ed64934
--- /dev/null
+++ b/postcss.config.js
@@ -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
+ }
+ }
+}
diff --git a/public/favicon.ico b/public/favicon.ico
new file mode 100644
index 0000000..df36fcf
Binary files /dev/null and b/public/favicon.ico differ
diff --git a/public/index.html b/public/index.html
new file mode 100644
index 0000000..bb172ba
--- /dev/null
+++ b/public/index.html
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+ <%= htmlWebpackPlugin.options.title %>
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/App.vue b/src/App.vue
new file mode 100644
index 0000000..d3e2cd8
--- /dev/null
+++ b/src/App.vue
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/src/api/user.js b/src/api/user.js
new file mode 100644
index 0000000..d155005
--- /dev/null
+++ b/src/api/user.js
@@ -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
+ })
+}
diff --git a/src/assets/logo.png b/src/assets/logo.png
new file mode 100644
index 0000000..89ffdd0
Binary files /dev/null and b/src/assets/logo.png differ
diff --git a/src/components/BackTop/index.vue b/src/components/BackTop/index.vue
new file mode 100644
index 0000000..74f857f
--- /dev/null
+++ b/src/components/BackTop/index.vue
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/SvgIcon/index.vue b/src/components/SvgIcon/index.vue
new file mode 100644
index 0000000..a4f0252
--- /dev/null
+++ b/src/components/SvgIcon/index.vue
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
diff --git a/src/icons/index.js b/src/icons/index.js
new file mode 100644
index 0000000..28ae463
--- /dev/null
+++ b/src/icons/index.js
@@ -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)
diff --git a/src/icons/svg/backtop.svg b/src/icons/svg/backtop.svg
new file mode 100644
index 0000000..f142331
--- /dev/null
+++ b/src/icons/svg/backtop.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/svg/qq.svg b/src/icons/svg/qq.svg
new file mode 100644
index 0000000..ee13d4e
--- /dev/null
+++ b/src/icons/svg/qq.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/svg/search.svg b/src/icons/svg/search.svg
new file mode 100644
index 0000000..3110f2a
--- /dev/null
+++ b/src/icons/svg/search.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/svg/star.svg b/src/icons/svg/star.svg
new file mode 100644
index 0000000..63a68b3
--- /dev/null
+++ b/src/icons/svg/star.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/svg/user.svg b/src/icons/svg/user.svg
new file mode 100644
index 0000000..0ba0716
--- /dev/null
+++ b/src/icons/svg/user.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/svg/wechat.svg b/src/icons/svg/wechat.svg
new file mode 100644
index 0000000..c586e55
--- /dev/null
+++ b/src/icons/svg/wechat.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/svgo.yml b/src/icons/svgo.yml
new file mode 100644
index 0000000..d11906a
--- /dev/null
+++ b/src/icons/svgo.yml
@@ -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'
diff --git a/src/main.js b/src/main.js
new file mode 100644
index 0000000..a9ef71e
--- /dev/null
+++ b/src/main.js
@@ -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')
diff --git a/src/permission.js b/src/permission.js
new file mode 100644
index 0000000..57f6d3d
--- /dev/null
+++ b/src/permission.js
@@ -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()
+ }
+ }
+})
diff --git a/src/router/index.js b/src/router/index.js
new file mode 100644
index 0000000..86dab02
--- /dev/null
+++ b/src/router/index.js
@@ -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
diff --git a/src/store/getters.js b/src/store/getters.js
new file mode 100644
index 0000000..1d8bf04
--- /dev/null
+++ b/src/store/getters.js
@@ -0,0 +1,7 @@
+const getters = {
+ avatar: state => state.user.avatar,
+ name: state => state.user.name,
+ token: state => state.user.token
+}
+
+export default getters
diff --git a/src/store/index.js b/src/store/index.js
new file mode 100644
index 0000000..d12f06b
--- /dev/null
+++ b/src/store/index.js
@@ -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
diff --git a/src/store/modules/user.js b/src/store/modules/user.js
new file mode 100644
index 0000000..20b9456
--- /dev/null
+++ b/src/store/modules/user.js
@@ -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
+}
diff --git a/src/styles/reset.css b/src/styles/reset.css
new file mode 100644
index 0000000..45a05ec
--- /dev/null
+++ b/src/styles/reset.css
@@ -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;
+}
diff --git a/src/utils/auth.js b/src/utils/auth.js
new file mode 100644
index 0000000..b04f3de
--- /dev/null
+++ b/src/utils/auth.js
@@ -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)
+}
diff --git a/src/utils/request.js b/src/utils/request.js
new file mode 100644
index 0000000..4ea801e
--- /dev/null
+++ b/src/utils/request.js
@@ -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
diff --git a/src/views/about/iconfont.vue b/src/views/about/iconfont.vue
new file mode 100644
index 0000000..61f45c7
--- /dev/null
+++ b/src/views/about/iconfont.vue
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/auth/login.vue b/src/views/auth/login.vue
new file mode 100644
index 0000000..f9bf375
--- /dev/null
+++ b/src/views/auth/login.vue
@@ -0,0 +1,138 @@
+
+
+
+
+
+
+
+
+ 发送验证码
+
+
+
+ 提交
+
+
+
+
+
+
+
+
diff --git a/src/views/home/index.vue b/src/views/home/index.vue
new file mode 100644
index 0000000..15c67a6
--- /dev/null
+++ b/src/views/home/index.vue
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/vue.config.js b/vue.config.js
new file mode 100644
index 0000000..3f8e120
--- /dev/null
+++ b/vue.config.js
@@ -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')
+ })
+ }
+}