项目初始化(-- 郑郑日上项目【Vue2】【PC 端】【商城】)

297 阅读9分钟

使用 vue-cli 脚手架创建项目

预设: vue2 / node-sass / babel / router / vuex / eslint (Use config files)

在 vue ui 图形界面中启动一次项目

配置 ESLint(可以根据个人的喜好配置)(-- .eslintrc.js)

module.exports = {
  root: true, // 当前项目使用这个配置文件, 不会往父级目录找 .eslintrc.js 文件
  parserOptions: { // 对新语法使用eslint
    parser: 'babel-eslint',  // 使用 babel-eslint 来解析新语法 ES6
    sourceType: 'module'
  },
  env: { // 指定 eslint 启动环境
    browser: true,
    node: true,
    es6: true,
  },
  extends: ['plugin:vue/recommended', 'eslint:recommended'],

  // add your custom rules here
  //it is base on https://github.com/vuejs/eslint-config-vue
  rules: { // 自定义规则
    /* value 值:
    (1)"off"/0:关闭规则
    (2)"warn"/1:将规则视为一个警告(不会影响退出码), 只警告,不会退出程序
    (3)"error"/2:将规则视为一个错误 (退出码为1),报错并退出程序
    */

    "vue/max-attributes-per-line": [2, {
      "singleline": 10,
      "multiline": {
        "max": 1,
        "allowFirstLine": false
      }
    }],
    "vue/singleline-html-element-content-newline": "off",
    "vue/multiline-html-element-content-newline":"off",
    "vue/name-property-casing": ["error", "PascalCase"],
    "vue/no-v-html": "off",
    'accessor-pairs': 2,
    'arrow-spacing': [2, {
      'before': true,
      'after': true
    }],
    'block-spacing': [2, 'always'],
    'brace-style': [2, '1tbs', {
      'allowSingleLine': true
    }],
    'camelcase': [0, {
      'properties': 'always'
    }],
    'comma-dangle': [2, 'never'],
    'comma-spacing': [2, {
      'before': false,
      'after': true
    }],
    'comma-style': [2, 'last'],
    'constructor-super': 2,
    'curly': [2, 'multi-line'],
    'dot-location': [2, 'property'],
    'eol-last': 2,
    'eqeqeq': ["error", "always", {"null": "ignore"}],
    'generator-star-spacing': [2, {
      'before': true,
      'after': true
    }],
    'handle-callback-err': [2, '^(err|error)$'],
    'indent': [2, 2, {
      'SwitchCase': 1
    }],
    'jsx-quotes': [2, 'prefer-single'],
    'key-spacing': [2, {
      'beforeColon': false,
      'afterColon': true
    }],
    'keyword-spacing': [2, {
      'before': true,
      'after': true
    }],
    'new-cap': [2, {
      'newIsCap': true,
      'capIsNew': false
    }],
    'new-parens': 2,
    'no-array-constructor': 2,
    'no-caller': 2,
    'no-class-assign': 2,
    'no-cond-assign': 2,
    'no-const-assign': 2,
    'no-control-regex': 0,
    'no-delete-var': 2,
    'no-dupe-args': 2,
    'no-dupe-class-members': 2,
    'no-dupe-keys': 2,
    'no-duplicate-case': 2,
    'no-empty-character-class': 2,
    'no-empty-pattern': 2,
    'no-eval': 2,
    'no-ex-assign': 2,
    'no-extend-native': 2,
    'no-extra-bind': 2,
    'no-extra-boolean-cast': 2,
    'no-extra-parens': [2, 'functions'],
    'no-fallthrough': 2,
    'no-floating-decimal': 2,
    'no-func-assign': 2,
    'no-implied-eval': 2,
    'no-inner-declarations': [2, 'functions'],
    'no-invalid-regexp': 2,
    'no-irregular-whitespace': 2,
    'no-iterator': 2,
    'no-label-var': 2,
    'no-labels': [2, {
      'allowLoop': false,
      'allowSwitch': false
    }],
    'no-lone-blocks': 2,
    'no-mixed-spaces-and-tabs': 2,
    'no-multi-spaces': 2,
    'no-multi-str': 2,
    'no-multiple-empty-lines': [2, {  // 不允许有连续多行空行
      'max': 1
    }],
    'no-native-reassign': 2,
    'no-negated-in-lhs': 2,
    'no-new-object': 2,
    'no-new-require': 2,
    'no-new-symbol': 2,
    'no-new-wrappers': 2,
    'no-obj-calls': 2,
    'no-octal': 2,
    'no-octal-escape': 2,
    'no-path-concat': 2,
    'no-proto': 2,
    'no-redeclare': 2,
    'no-regex-spaces': 2,
    'no-return-assign': [2, 'except-parens'],
    'no-self-assign': 2,
    'no-self-compare': 2,
    'no-sequences': 2,
    'no-shadow-restricted-names': 2,
    'no-spaced-func': 2,
    'no-sparse-arrays': 2,
    'no-this-before-super': 2,
    'no-throw-literal': 2,
    'no-trailing-spaces': 2,
    'no-undef': 2,
    'no-undef-init': 2,
    'no-unexpected-multiline': 2,
    'no-unmodified-loop-condition': 2,
    'no-unneeded-ternary': [2, {
      'defaultAssignment': false
    }],
    'no-unreachable': 2,
    'no-unsafe-finally': 2,
    'no-unused-vars': 1,
    'no-useless-call': 2,
    'no-useless-computed-key': 2,
    'no-useless-constructor': 2,
    'no-useless-escape': 0,
    'no-whitespace-before-property': 2,
    'no-with': 2,
    'one-var': [2, {
      'initialized': 'never'
    }],
    'operator-linebreak': [2, 'after', {
      'overrides': {
        '?': 'before',
        ':': 'before'
      }
    }],
    'padded-blocks': [2, 'never'],
    'quotes': [2, 'single', {
      'avoidEscape': true,
      'allowTemplateLiterals': true
    }],
    'semi': [2, 'never'],
    'semi-spacing': [2, {
      'before': false,
      'after': true
    }],
    'space-before-blocks': [2, 'always'],
    'space-before-function-paren': [2, 'never'],
    'space-in-parens': [2, 'never'],
    'space-infix-ops': 2,
    'space-unary-ops': [2, {
      'words': true,
      'nonwords': false
    }],
    'spaced-comment': [2, 'always', {
      'markers': ['global', 'globals', 'eslint', 'eslint-disable', '*package', '!', ',']
    }],
    'template-curly-spacing': [2, 'never'],
    'use-isnan': 2,
    'valid-typeof': 2,
    'wrap-iife': [2, 'any'],
    'yield-star-spacing': [2, 'both'],
    'yoda': [2, 'never'],
    'prefer-const': 2,
    'object-curly-spacing': [2, 'always', {
      objectsInObjects: false
    }],
    'array-bracket-spacing': [2, 'never'],
    'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', // 上线环境用打印就报警告, 开发环境关闭此规则
    'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off', // debugger 可以终止代码执行
    // 要求使用骆驼拼写法
    // 'camelcase': 'none',
    // 要求使用 === 和 !==
    'eqeqeq': 0,
    // 函数和圆括号之间没有空格
    'space-before-function-paren': 0,
    // 要求或禁止使用拖尾逗号
    // "comma-dangle": ["error", {
    //   "arrays": "always-multiline",
    //   "objects": "always-multiline",
    //   "imports": "never",
    //   "exports": "always",
    //   "functions": "never"
    // }],
    // 要求或禁止文件末尾保留一行空行
    'eol-last': ['error', 'always'],
    // 要求对象字面量属性名称用引号括起来:"consistent" 要求对象字面量属性名称使用一致的引号,要么全部用引号,要么都不用
    'quote-props': ['error', 'consistent'],
    // 要求注释后要有一个空格
    'spaced-comment': ['error', 'always'],
    // 要求操作符前后都要有一个空格
    'space-infix-ops': 'error',
    // 要求圆括号中的前后都要有一个空格
    /* "space-in-parens": ["error", "always"], */
    // 要求花括号中的前后都要有一个空格
    'object-curly-spacing': ['error', 'always'],
    // 要求数组中逗号后要使用一个空格
    'comma-spacing': ['error', { 'before': false, 'after': true }],
    // 自动补充分号
    // "semi": [2, "always"],
    // 禁止多个空格
    'no-multi-spaces': 'error',
    // 使用单引号
    'quotes': ['error', 'single'],
    //在computed properties中禁用异步actions
    'vue/no-async-in-computed-properties': 'error',
    //不允许重复的keys
    'vue/no-dupe-keys': 'error',
    //不允许重复的attributes
    'vue/no-duplicate-attributes': 'warn',
    //在 <template> 标签下不允许解析错误
    'vue/no-parsing-error': [
      'error',
      {
        'x-invalid-end-tag': false,
      },
    ],
    //不允许覆盖保留关键字
    'vue/no-reserved-keys': 'error',
    //强制data必须是一个带返回值的函数
    // 'vue/no-shared-component-data': 'error',
    //不允许在computed properties中出现副作用。
    'vue/no-side-effects-in-computed-properties': 'error',
    //<template>不允许key属性
    'vue/no-template-key': 'warn',
    //在 <textarea> 中不允许mustaches
    'vue/no-textarea-mustache': 'error',
    //不允许在v-for或者范围内的属性出现未使用的变量定义
    'vue/no-unused-vars': 'warn',
    //<component>标签需要v-bind:is属性
    'vue/require-component-is': 'error',
    // render 函数必须有一个返回值
    'vue/require-render-return': 'error',
    //保证 v-bind:key 和 v-for 指令成对出现
    'vue/require-v-for-key': 'error',
    // 检查默认的prop值是否有效
    'vue/require-valid-default-prop': 'error',
    // 保证computed属性中有return语句
    'vue/return-in-computed-property': 'error',
    // 强制校验 template 根节点
    'vue/valid-template-root': 'error',
    // 强制校验 v-bind 指令
    'vue/valid-v-bind': 'error',
    // 强制校验 v-cloak 指令
    'vue/valid-v-cloak': 'error',
    // 强制校验 v-else-if 指令
    'vue/valid-v-else-if': 'error',
    // 强制校验 v-else 指令
    'vue/valid-v-else': 'error',
    // 强制校验 v-for 指令
    'vue/valid-v-for': 'error',
    // 强制校验 v-html 指令
    'vue/valid-v-html': 'error',
    // 强制校验 v-if 指令
    'vue/valid-v-if': 'error',
    // 强制校验 v-model 指令
    'vue/valid-v-model': 'error',
    // 强制校验 v-on 指令
    'vue/valid-v-on': 'error',
    // 强制校验 v-once 指令
    'vue/valid-v-once': 'error',
    // 强制校验 v-pre 指令
    'vue/valid-v-pre': 'error',
    // 强制校验 v-show 指令
    'vue/valid-v-show': 'error',
    // 强制校验 v-text 指令
    'vue/valid-v-text': 'error',
    'vue/comment-directive': 0,
    // 标签内没有内容时,删除结尾标签
    "vue/html-self-closing": ["off", {
      "html": {
        "void": "never",
        "normal": "always",
        "component": "always"
      },
      "svg": "always",
      "math": "always"
    }]
  }
}

项目结构调整:

  • 删除 assets/logo.png
  • 删除 components 目录里的所有文件
  • 删除 router 里的 index.js 里的 routes 数组中的所有路由
  • 删除 views 目录中的所有文件
  • 删除 App.vue 中的示例代码

创建目录、文件:

node_modules                         // 存放 第三方包
public                               // 存放 页面图标、index.html
src                                  // 存放 项目源代码
api                       【手动创建】 // 存放 可复用的 api 接口       
  assets                             // 存放 静态资源
    product               【手动创建】 // 存放 商品图片(大图片)          
    images                【手动创建】 // 存放 小图标(小图片)(该目录里的图片会被转换为 base64)     
  components                         // 存放 组件
  router                             // 存放 路由模块
  store                              // vuex(状态管理)
    modules               【手动创建】 // 存储数据模块。让 vuex 中的数据持久化         
  styles                  【手动创建】 // 存放 CSS样式表               
  utils                   【手动创建】 // 存放 工具性质的函数和模块       
      request.js          【手动创建】 // 请求相关配置        
  vender                  【手动创建】 // 手动添加的第三方 js 库(npm 下载不到的)     
  views                              // 存放 通过路由动态切换的组件
  storage                 【手动创建】 // 数据存储工具箱                
  App.vue                            // 项目 根组件
  main.js                            // 项目 入口文件(主要职责:创建 vue 应用)
  store.js                           // vuex 配置文件
  theme.scss              【手动创建】 // 主题定制                    
.browserslistrc                      // 将 css 转换为浏览器兼容的版本(在 CSS 前面添加浏览器对应的兼容前缀)
.editorconfig                        // 编辑器配置文件(配置缩进、空格、字体大小 等)
.eslintrc.js                         // ESLint 配置文件
.gitignore                           // 忽略清单
babel.config.js                      // babel 配置文件(ES降级配置文件)
package.json                         // 包管理配置文件
vue.config.js             【手动创建】 // 项目配置文件                 

为空目录添加 .gitkeep(解决远程仓库不能上传空目录的问题)

初始化样式

1、定义公共代码(-- style/新建 index.scss)

body {
  height: 100%;
  -moz-osx-font-smoothing: grayscale;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  font-family: Helvetica Neue, Helvetica, Arial, Microsoft Yahei, Hiragino Sans GB, Heiti SC, WenQuanYi Micro Hei, sans-serif;
  font-weight: bold;
}

label {
  font-weight: 700;
}

html {
  height: 100%;
  box-sizing: border-box;
}

#app {
  height: 100%;
}

*,
*:before,
*:after {
  box-sizing: inherit;
}

a:focus,
a:active {
  outline: none;
}

a,
a:focus,
a:hover {
  cursor: pointer;
  color: inherit;
  text-decoration: none;
}

div:focus {
  outline: none;
}

.clearfix {
  &:after {
    visibility: hidden;
    display: block;
    font-size: 0;
    content: " ";
    clear: both;
    height: 0;
  }
}

// main-container global css
.app-container {
  padding: 20px;
}

input,
button {
  border: 0;
  font-weight: bold;
}

button {
  cursor: pointer;
}

input {
  outline: none;
}

li {
  list-style: none;
}

.w {
  width: 1300px;
  margin: 0 auto;
}

ul,
li,
h1,
h2,
h3,
h4,
h5,
h6 {
  margin: 0;
  padding: 0;
}

img {
  vertical-align: bottom;
}

2、导入公共代码(-- main.js)

import '@/styles/index.scss'

定义 Demo 路由【可选】(个人喜好,用于测试代码)

下载插件

1、规范化 css 代码 2、axios 数据请求的库 3、element-ui 组件库 4、vuex 数据持久化 5、cookie 库

npm i normalize.css@8.0.1 axios@0.25.0 element-ui@2.15.6 vuex-persistedstate@4.1.0 js-cookie@2.2.0

导入 规范化 css 代码(-- main.js)

import 'normalize.css/normalize.css'

配置 element-ui

1、完整引入 element ui 组件(-- main.js)

import ElementUI from 'element-ui'

2、导入 element-ui 组件的样式(-- 同上)

import 'element-ui/lib/theme-chalk/index.css'

3、把 Element 注册为 vue 的插件(-- 同上)

Vue.use(ElementUI)

配置 vuex-persistedstate 数据持久化

1、配置 vuex-persistedstate(重构)(-- store/index.js)

import Vue from 'vue'
import Vuex from 'vuex'
import createPersistedState from 'vuex-persistedstate'
import demo from '@/store/modules/demo.js'

Vue.use(Vuex)

const store = new Vuex.Store({
  modules: { // 注册模块
    demo
  },
  plugins: [
    createPersistedState({ // 数据持久化插件 配置
      key: 'store', // 本地仓库名字
      paths: ['demo'] // 指定需要持久化的模块
    })
  ]
})

export default store

2、模块骨架示例(-- store/modules/新建 demo.js)

import { getUsernameLogin } from '@/api/user-management.js'

const state = { // 公共数据
  token: ''
}

const mutations = { // 修改 store 中的数据
  setToken(state, token) {
    state.token = token
  }
}

const actions = { // 处理异步任务
  async getUsernameLogin(context, data) { // 用户登录[用户名]
    const result = await getUsernameLogin(data)
    context.commit('setToken', result)
  }
}

const modules = { // 计算属性。监听 state 数据

}

export default {
  namespaced: true,
  state,
  mutations,
  actions,
  modules
}

配置 axios(-- utils/request.js)

import axios from 'axios'
import { Message } from 'element-ui'

// 创建 axios 实例
const service = axios.create({
  baseURL: '/api', // 请求根路径
  timeout: 8000 // 请求超时时间
})

// 请求 拦截器
service.interceptors.request.use(request => {
  return request
}, error => {
  return Promise.reject(error)
})

// 响应 拦截器
service.interceptors.response.use(response => {
  // 返回响应结果
  if (response.data.msg === 'success') {
    return response.data.data
  } else {
    Message.error(response.data.msg)
    // 把异步API执行失败的结果传递出去
    return Promise.reject(response.data.msg)
  }
}, error => {
  return Promise.reject(error)
})

export default service

【可选】解决请求参数格式和服务端参数格式不一致的问题(-- main.jx)

解决方法: 将请求参数转换成字符串格式。因为 content-type 会根据请求参数的格式自动变换请求格式

  • application/x-www-form-urlencoded 格式:categoryId=263919
  • application/json 格式:{"categoryId": 263919}

1、下载 qs 包

作用: 将 application/json 格式转换为 application/x-www-form-urlencoded 格式

npm i qs@6.10.3

2、导入 qs 包(-- utils/request.js)

import qs from 'qs'

2、将 application/json 格式转换为 application/x-www-form-urlencoded 格式

// 请求 拦截器
axios.interceptors.request.use(request => {

  -- 增    
  if (request.data) {
    request.data = qs.stringify(request.data) // 将 application/json 格式转换为 application/x-www-form-urlencoded 格式
  }
  --

  return request
})

配置 cookie 库

1、导入以下代码(-- utils/新建 auth.js)

import Cookies from 'js-cookie'

const TokenKey = 'vue_admin_template_token'

export function getToken() {
  return Cookies.get(TokenKey)
}

export function setToken(token) {
  return Cookies.set(TokenKey, token)
}

export function removeToken() {
  return Cookies.remove(TokenKey)
}

2、按需导入(-- store/modules/新建 user.js)

import { getToken, setToken, removeToken, setTimeStamp } from '@/utils/auth'

配置 开发 环境下的接口代理(-- vue.config.js)

module.exports = {
  devServer: { // 开发环境下的服务器配置
    host: 'localhost',
    port: 8080,
    // 代理配置
    proxy:{
      // 拦截携带 /api 的接口
      '/api': {
        // 代理到的目标地址
        target: 'https://api.it120.cc/zcr',
        // 是否开启跨域
        changeOrigin: true,
        // 路径重写
        pathRewrite:{
          // 将 /api 转换为 空
          '^/api': ''
        }
      }
    }
  },
  configureWebpack: {
    devtool: 'source-map'
  },
}

【可选】配置远程仓库