vue+ts搭建项目

Posted cczlovexw

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了vue+ts搭建项目相关的知识,希望对你有一定的参考价值。

Tip: 为了避免浪费您的时间,本文符合满足以下条件的同学借鉴参考

1.本文模版不适用于小型项目,两三个页面的也没必要用vue
2.对typescriptvue全家桶能够掌握和运用

此次项目模版主要涉及的技术框架:
  1. vue2.5
  2. vuex3.0
  3. vue-router3.0
  4. axios
  5. typescript3.2

Tip: 由于vue-cli3.0帮我们简化了webpack的配置,我们只需要在根目录下的vue.config.js文件的chainWebpack进行配置即可。

接下来进入正题(前提是你已经安装并配置好了node环境)

一.初始化项目

安装vue-cli3.0

如果你之前安装的是vue-cli2.0版本的,那么你需要先卸载2.0版本,再安装3.0版本

// cnpm 为淘宝镜像
cnpm uninstall vue-cli -g // 卸载
cnpm install -g @vue/cli // 安装新版本
创建项目
vue create vue-cli3-tpl

选择Manually select features回车,按照下图所示选中(空格选中)回车安装插件

 
技术图片


然后一路回车,放一下配置图

 
技术图片
配置图


等安装完之后,进入项目并启动项目

 

cd vue-cli3-tpl
cnpm run serve

启动后显示如下,第一步完成。

 

 
技术图片
启动成功显示界面

二.删除不必要的文件

1.删除assetscomponentsviews目录下的所有文件。
2.删除./src/store.ts
3.删除./src/router.ts

三.添加并配置文件

1.添加文件夹并创建文件

1.在根目录下创建scripts文件夹,并添加template.jscomponent.js
2.在./src目录下创建api文件夹
3.在./src目录下创建config文件夹,并添加index.tsrequestConfig.ts
4.在./src目录下创建router文件夹,并添加index.tsrouter.ts
5.在./src目录下创建store文件夹,并添加index.tsmodule文件夹
6.在./src目录下创建types文件夹,并添加index.tscomponents文件夹和views文件夹
7.在./src目录下创建utils文件夹,并添加common.tsrequest.ts
8.在./src/assets目录下创建imagesscss两个文件夹,并在scss文件夹下添加common.scssvariables.scss

2.配置文件

首先配置一下根目录下tslint.json,编码习惯还是根据团队的要求来,我自己关闭了很多在我看来很鸡肋的东西,下面是个人的配置,仅供参考


  "defaultSeverity": "warning",
  "extends": [
    "tslint:recommended"
  ],
  "linterOptions": 
    "exclude": [
      "node_modules/**"
    ]
  ,
  "rules": 
    "quotemark": false, // 字符串文字需要单引号或双引号。
    "indent": false, // 使用制表符或空格强制缩进。
    "member-access": false, // 需要类成员的显式可见性声明。
    "interface-name": false, // 接口名要求大写开头
    "ordered-imports": false, // 要求将import语句按字母顺序排列并进行分组。
    "object-literal-sort-keys": false, // 检查对象文字中键的排序。
    "no-consecutive-blank-lines": false, // 不允许连续出现一个或多个空行。
    "no-shadowed-variable": false, // 不允许隐藏变量声明。
    "no-trailing-whitespace": false, // 不允许在行尾添加尾随空格。
    "semicolon": false, // 是否分号结尾
    "trailing-comma": false, // 是否强象添加逗号
    "eofline": false, // 是否末尾另起一行
    "prefer-conditional-expression": false, // for (... in ...)语句必须用if语句过滤
    "curly": true, //for if do while 要有括号
    "forin": false, //用for in 必须用if进行过滤
    "import-blacklist": true, //允许使用import require导入具体的模块
    "no-arg": true, //不允许使用 argument.callee
    "no-bitwise": true, //不允许使用按位运算符
    "no-console": false, //不能使用console
    "no-construct": true, //不允许使用 String/Number/Boolean的构造函数
    "no-debugger": true, //不允许使用debugger
    "no-duplicate-super": true, //构造函数两次用super会发出警告
    "no-empty": true, //不允许空的块
    "no-eval": true, //不允许使用eval
    "no-floating-promises": false, //必须正确处理promise的返回函数
    "no-for-in-array": false, //不允许使用for in 遍历数组
    "no-implicit-dependencies": false, //不允许在项目的package.json中导入未列为依赖项的模块
    "no-inferred-empty-object-type": false, //不允许在函数和构造函数中使用的类型推断
    "no-invalid-template-strings": true, //警告在非模板字符中使用$
    "no-invalid-this": true, //不允许在非class中使用 this关键字
    "no-misused-new": true, //禁止定义构造函数或new class
    "no-null-keyword": false, //不允许使用null关键字
    "no-object-literal-type-assertion": false, //禁止object出现在类型断言表达式中
    "no-return-await": true, //不允许return await
    "arrow-parens": false, //箭头函数定义的参数需要括号
    "adjacent-overload-signatures": false, //  Enforces function overloads to be consecutive.
    "ban-comma-operator": true, //禁止逗号运算符。
    "no-any": false, //不需使用any类型
    "no-empty-interface": true, //禁止空接口 
    "no-internal-module": true, //不允许内部模块
    "no-magic-numbers": false, //不允许在变量赋值之外使用常量数值。当没有指定允许值列表时,默认允许-1,0和1
    "no-namespace": [true, "allpw-declarations"], //不允许使用内部modules和命名空间
    "no-non-null-assertion": true, //不允许使用!后缀操作符的非空断言。
    "no-parameter-reassignment": true, //不允许重新分配参数
    "no-reference": true, // 禁止使用/// <reference path=> 导入 ,使用import代替
    "no-unnecessary-type-assertion": false, //如果类型断言没有改变表达式的类型就发出警告
    "no-var-requires": false, //不允许使用var module = require("module"),用 import foo = require(‘foo‘)导入
    "prefer-for-of": true, //建议使用for(..of)
    "promise-function-async": false, //要求异步函数返回promise
    "max-classes-per-file": [true, 2], // 一个脚本最多几个申明类
    "variable-name": false,
    "prefer-const": false // 提示可以用const的地方
  

1.打开./scripts/template.js,并添加以下内容

/*
 * @Description: 页面快速生成脚本
 * @Date: 2018-12-06 10:28:08
 * @LastEditTime: 2018-12-10 09:43:50
 */
const fs = require(‘fs‘)
const path = require(‘path‘)
const basePath = path.resolve(__dirname, ‘../src‘)

const dirName = process.argv[2]
const capPirName = dirName.substring(0, 1).toUpperCase() + dirName.substring(1)
if (!dirName) 
    console.log(‘文件夹名称不能为空!‘)
    console.log(‘示例:npm run tep $capPirName‘)
    process.exit(0)


/**
 * @msg: vue页面模版
 */
const VueTep = `<template>
  <div class="$dirName-wrap">
    data.pageName
  </div>
</template>

<script lang="ts" src="./$dirName.ts"></script>

<style lang="scss">
  @import ‘./$dirName.scss‘
</style>

`

// ts 模版
const tsTep = `import  Component, Vue  from "vue-property-decorator"
import  Getter, Action  from "vuex-class"
import  $capPirNameData  from ‘@/types/views/$dirName.interface‘
// import    from "@/components" // 组件

@Component()
export default class About extends Vue 
  // Getter
  // @Getter $dirName.author
    
  // Action
  // @Action GET_DATA_ASYN

  // data
  data: $capPirNameData = 
    pageName: ‘$dirName‘
  

  created() 
    //
  
    
  activated() 
    //
  

  mounted() 
    //
  

  // 初始化函数
  init() 
    //
  
    

`

// scss 模版
const scssTep = `@import "@/assets/scss/variables.scss";

.$dirName-wrap 
  width: 100%;

`

// interface 模版
const interfaceTep = `// $dirName.Data 参数类型
export interface $capPirNameData 
  pageName: string


// VUEX $dirName.State 参数类型
export interface $capPirNameState 
  data?: any


// GET_DATA_ASYN 接口参数类型
// export interface DataOptions 

`

// vuex 模版
const vuexTep = `import  $capPirNameState  from ‘@/types/views/$dirName.interface‘
import  GetterTree, MutationTree, ActionTree  from ‘vuex‘
import * as $capPirNameApi from ‘@/api/$dirName‘

const state: $capPirNameState = 
  $dirName: 
   author: undefined
  


// 强制使用getter获取state
const getters: GetterTree<$capPirNameState, any> = 
  author: (state: $capPirNameState) => state.$dirName.author


// 更改state
const mutations: MutationTree<$capPirNameState> = 
  // 更新state都用该方法
  UPDATE_STATE(state: $capPirNameState, data: $capPirNameState) 
    for (const key in data) 
      if (!data.hasOwnProperty(key))  return 
      state[key] = data[key]
    
  


const actions: ActionTree<$capPirNameState, any> = 
  UPDATE_STATE_ASYN( commit, state: $capPirNameState , data: $capPirNameState) 
    commit(‘UPDATE_STATE‘, data)
  ,
  // GET_DATA_ASYN( commit, state: LoginState ) 
  //   $capPirName.getData()
  // 


export default 
  state,
  getters,
  mutations,
  actions


`

// api 接口模版
const apiTep = `import Api from ‘@/utils/request‘

export const getData = () => 
  return Api.getData()


`

fs.mkdirSync(`$basePath/views/$dirName`) // mkdir

process.chdir(`$basePath/views/$dirName`) // cd views
fs.writeFileSync(`$dirName.vue`, VueTep) // vue 
fs.writeFileSync(`$dirName.ts`, tsTep) // ts
fs.writeFileSync(`$dirName.scss`, scssTep) // scss

process.chdir(`$basePath/types/views`); // cd types
fs.writeFileSync(`$dirName.interface.ts`, interfaceTep) // interface

process.chdir(`$basePath/store/module`); // cd store
fs.writeFileSync(`$dirName.ts`, vuexTep) // vuex

process.chdir(`$basePath/api`); // cd api
fs.writeFileSync(`$dirName.ts`, apiTep) // api

process.exit(0)

2.打开./scripts/component.js,并添加以下内容

/*
 * @Description: 组件快速生成脚本
 * @Date: 2018-12-06 10:26:23
 * @LastEditTime: 2018-12-10 09:44:19
 */

const fs = require(‘fs‘)
const path = require(‘path‘)
const basePath = path.resolve(__dirname, ‘../src‘)

const dirName = process.argv[2]
const capPirName = dirName.substring(0, 1).toUpperCase() + dirName.substring(1)
if (!dirName) 
  console.log(‘文件夹名称不能为空!‘)
  console.log(‘示例:npm run tep $capPirName‘)
  process.exit(0)


/**
 * @msg: vue页面模版
 */
const VueTep = `<template>
  <div class="$dirName-wrap">
    data.componentName
  </div>
</template>

<script lang="ts">
  import  Component, Vue, Prop  from "vue-property-decorator"
  import  Getter, Action  from ‘vuex-class‘
  import  $capPirNameData  from ‘@/types/components/$dirName.interface‘
  // import    from "@/components" // 组件

  @Component()
  export default class About extends Vue 
    // prop
    @Prop(
      required: false,
      default: ‘‘
    ) name!: string

    // data
    data: $capPirNameData = 
      componentName: ‘$dirName‘
    

    created() 
      //
    
    
    activated() 
      //
    

    mounted() 
      //
    

  
</script>

<style lang="scss">
  @import "@/assets/scss/variables";

  .$dirName-wrap 
    width: 100%;
  
</style>

`

// interface 模版
const interfaceTep = `// $dirName.Data 参数类型
export interface $capPirNameData 
  componentName: string


`

fs.mkdirSync(`$basePath/components/$dirName`) // mkdir

process.chdir(`$basePath/components/$dirName`) // cd views
fs.writeFileSync(`$dirName.vue`, VueTep) // vue 

process.chdir(`$basePath/types/components`) // cd components
fs.writeFileSync(`$dirName.interface.ts`, interfaceTep) // interface 

process.exit(0)

3.打开./src/config/index.ts,并添加以下内容

/** 
 * 线上环境
 */
export const ONLINEHOST: string = ‘https://xxx.com‘

/** 
 * 测试环境
 */
export const QAHOST: string = ‘http://xxx.com‘

/** 
 * 线上mock
 */
export const MOCKHOST: string = ‘http://xxx.com‘

/** 
 * 是否mock
 */
export const ISMOCK: boolean = true

/**
 * 当前的host  ONLINEHOST | QAHOST | MOCKHOST
 */
export const MAINHOST: string = ONLINEHOST

/**
 * 请求的公共参数
 */
export const conmomPrams: any = 

/**
 * @description token在Cookie中存储的天数,默认1天
 */
export const cookieExpires: number = 1

4.打开./src/config/requestConfig.ts,并添加以下内容

export default 
  getData: ‘/mock/5c09ca373601b6783189502a/example/mock‘, // 随机数据 来自 easy mock

5.打开./src/router/index.ts,并添加以下内容

import Vue from ‘vue‘
import Router from ‘vue-router‘
import routes from ‘./router‘
import  getToken  from ‘@/utils/common‘

Vue.use(Router)

const router = new Router(
  routes,
  mode: ‘history‘
)

// 登陆页面路由 name
const LOGIN_PAGE_NAME = ‘login‘

// 跳转之前
router.beforeEach((to, from, next) => 
  const token = getToken()
  if (!token && to.name !== LOGIN_PAGE_NAME) 
    // 未登录且要跳转的页面不是登录页
    next(
      name: LOGIN_PAGE_NAME // 跳转到登录页
    )
   else if (!token && to.name === LOGIN_PAGE_NAME) 
    // 未登陆且要跳转的页面是登录页
    next() // 跳转
   else if (token && to.name === LOGIN_PAGE_NAME) 
    // 已登录且要跳转的页面是登录页
    next(
      name: ‘index‘ // 跳转到 index 页
    )
   else 
    if (token) 
      next() // 跳转
     else 
      next(
        name: LOGIN_PAGE_NAME
      )
    
  
)


// 跳转之后
router.afterEach(to => 
  //
)

export default router

6.打开./src/router/router.ts,并添加以下内容

/**
 * meta 可配置参数
 * @param boolean icon 页面icon
 * @param boolean keepAlive 是否缓存页面
 * @param string title 页面标题
 */
export default [
  
    path: ‘/‘,
    redirect: ‘/index‘
  ,
  
    path: ‘/login‘,
    name: ‘login‘,
    component: () => import(‘@/views/login/login.vue‘),
    meta: 
      icon: ‘‘,
      keepAlive: true,
      title: ‘login‘
    
  ,
  
    path: ‘/index‘,
    name: ‘index‘,
    component: () => import(‘@/views/index/index.vue‘),
    meta: 
      icon: ‘‘,
      keepAlive: true,
      title: ‘index‘
    
  
]

7.打开./src/store/index.ts,并添加以下内容

import Vue from ‘vue‘
import Vuex from ‘vuex‘

Vue.use(Vuex)

export default new Vuex.Store(
  state: 
    //
  ,
  mutations: 
    //
  ,
  actions: 
    //
  ,
  modules: 
    // 
  
)

8.打开./src/utils/common.ts,在此之前先下载js-cookie,并添加以下内容

// 下载js-cookie
cnpm i js-cookie --S
cnpm install @types/js-cookie --D
/*
 * @Description: 公共函数
 * @Author: asheng
 * @Date: 2018-12-07 11:36:27
 * @LastEditors: asheng
 * @LastEditTime: 2018-12-12 13:37:30
 */

import Cookies from ‘js-cookie‘
import  cookieExpires  from ‘@/config‘ // cookie保存的天数

/**
 * @Author: asheng
 * @msg: 存取token
 * @param string token
 */
export const TOKEN_KEY: string = ‘token‘
export const setToken = (token: string) => 
  Cookies.set(TOKEN_KEY, token,  expires: cookieExpires || 1 )

export const getToken = () => 
  const token = Cookies.get(TOKEN_KEY)
  if (token) 
    return token
   else 
    return false
  


/**
 * @param String url
 * @description 从URL中解析参数
 */
export const getParams = (url: string) => 
  const keyValueArr = url.split(‘?‘)[1].split(‘&‘)
  let paramObj: any = 
  keyValueArr.forEach(item => 
    const keyValue = item.split(‘=‘)
    paramObj[keyValue[0]] = keyValue[1]
  )
  return paramObj


/**
 * 判断一个对象是否存在key,如果传入第二个参数key,则是判断这个obj对象是否存在key这个属性
 * 如果没有传入key这个参数,则判断obj对象是否有键值对
 */
export const hasKey = (obj: any, key: string | number) => 
  if (key) 
    return key in obj
   else 
    const keysArr = Object.keys(obj)
    return keysArr.length
  


/**
 * @msg: 获取系统当前时间
 * @param string fmt 时间格式 具体看代码
 * @return: string
 */
export const getDate = (fmt: any) => 
  let time = ‘‘
  const date = new Date()
  const o: any = 
    "M+": date.getMonth() + 1, // 月份 
    "d+": date.getDate(), // 日 
    "H+": date.getHours(), // 小时 
    "m+": date.getMinutes(), // 分 
    "s+": date.getSeconds(), // 秒 
    "q+": Math.floor((date.getMonth() + 3) / 3), // 季度 
    "S": date.getMilliseconds() // 毫秒 
  
  if (/(y+)/.test(fmt)) 
    time = fmt.replace(RegExp.$1, (date.getFullYear() + "").substr(4 - RegExp.$1.length))
  
  for (const k in o) 
    if (new RegExp("(" + k + ")").test(fmt)) 
      time = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)))
    
  
  return time


/**
 * @msg: 获取系统当前时间
 * @param string date 时间
 * @param string fmt 时间格式
 * @return: string
 */
export const formatDate = (date: any, fmt: string) => 
  let time = ‘‘
  const o: any = 
    "M+": date.getMonth() + 1, // 月份 
    "d+": date.getDate(), // 日 
    "H+": date.getHours(), // 小时 
    "m+": date.getMinutes(), // 分 
    "s+": date.getSeconds(), // 秒 
    "q+": Math.floor((date.getMonth() + 3) / 3), // 季度 
    "S": date.getMilliseconds() // 毫秒 
  
  if (/(y+)/.test(fmt)) 
    time = fmt.replace(RegExp.$1, (date.getFullYear() + "").substr(4 - RegExp.$1.length))
  
  for (const k in o) 
    if (new RegExp("(" + k + ")").test(fmt)) 
      time = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)))
    
  
  return time


// copy in the ‘fx-fuli‘ utils
/**
 * 校验手机号是否正确
 * @param phone 手机号
 */

export const verifyPhone = (phone: string | number) => 
  const reg = /^1[34578][0-9]9$/
  const _phone = phone.toString().trim()
  let toastStr = _phone === ‘‘ ? ‘手机号不能为空~‘ : !reg.test(_phone) && ‘请输入正确手机号~‘
  return 
    errMsg: toastStr,
    done: !toastStr,
    value: _phone
  


export const verifyStr = (str: string | number, text: string) => 
  const _str = str.toString().trim()
  const toastStr = _str.length ? false : `请填写$text`
  return 
    errMsg: toastStr,
    done: !toastStr,
    value: _str
  


// 截取字符串
export const sliceStr = (str: any, sliceLen: number) => 
  if (!str)  return ‘‘ 
  let realLength = 0
  const len = str.length
  let charCode = -1
  for (let i = 0; i < len; i++) 
    charCode = str.charCodeAt(i)
    if (charCode >= 0 && charCode <= 128) 
      realLength += 1
     else 
      realLength += 2
    
    if (realLength > sliceLen) 
      return `$str.slice(0, i)...`
    
  

  return str



/**
 * JSON 克隆
 * @param Object | Json jsonObj json对象
 * @return Object | Json 新的json对象
 */
export function objClone(jsonObj: any) 
  let buf: any
  if (jsonObj instanceof Array) 
    buf = []
    let i = jsonObj.length
    while (i--) 
      buf[i] = objClone(jsonObj[i])
    
    return buf
   else if (jsonObj instanceof Object) 
    buf = 
    for (let k in jsonObj) 
      buf[k] = objClone(jsonObj[k])
    
    return buf
   else 
    return jsonObj
  

9.打开./src/utils/request.ts,先下载axios,并添加以下内容

// 下载axios
cnpm i axios --S
import axios,  AxiosResponse, AxiosRequestConfig  from ‘axios‘
import  MAINHOST, ISMOCK, conmomPrams  from ‘@/config‘
import requestConfig from ‘@/config/requestConfig‘
import  getToken  from ‘@/utils/common‘
import router from ‘@/router‘

declare type Methods = "GET" | "OPTIONS" | "HEAD" | "POST" | "PUT" | "DELETE" | "TRACE" | "CONNECT"
declare interface Datas 
  method?: Methods
  [key: string]: any

const baseURL = process.env.NODE_ENV === ‘production‘ ? MAINHOST : location.origin
const token = getToken()

class HttpRequest 
  public queue: any // 请求的url集合
  public constructor() 
    this.queue = 
  
  destroy(url: string) 
    delete this.queue[url]
    if (!Object.keys(this.queue).length) 
      // hide loading
    
  
  interceptors(instance: any, url?: string) 
    // 请求拦截
    instance.interceptors.request.use((config: AxiosRequestConfig) => 
      // 添加全局的loading...
      if (!Object.keys(this.queue).length) 
        // show loading
      
      if (url) 
        this.queue[url] = true
      
      return config
    , (error: any) => 
      console.error(error)
    )
    // 响应拦截
    instance.interceptors.response.use((res: AxiosResponse) => 
      if (url) 
        this.destroy(url)
      
      const  data, status  = res
      if (status === 200 && ISMOCK)  return data  // 如果是mock数据,直接返回
      if (status === 200 && data && data.code === 0)  return data  // 请求成功
      return requestFail(res) // 失败回调
    , (error: any) => 
      if (url) 
        this.destroy(url)
      
      console.error(error)
    )
  
  async request(options: AxiosRequestConfig) 
    const instance = axios.create()
    await this.interceptors(instance, options.url)
    return instance(options)
  


// 请求失败
const requestFail = (res: AxiosResponse) => 
  let errStr = ‘网络繁忙!‘
  // token失效重新登陆
  if (res.data.code === 1000001) 
    return router.replace( name: ‘login‘ )
  

  return 
    err: console.error(
      code: res.data.errcode || res.data.code,
      msg: res.data.errmsg || errStr
    )
  


// 合并axios参数
const conbineOptions = (_opts: any, data: Datas, method: Methods): AxiosRequestConfig => 
  let opts = _opts
  if (typeof opts === ‘string‘) 
    opts =  url: opts 
  
  const _data =  ...conmomPrams, ...opts.data, ...data 
  const options = 
    method: opts.method || data.method || method || ‘GET‘,
    url: opts.url,
    header:  ‘user-token‘: token ,
    baseURL
  
  return options.method !== ‘GET‘ ? Object.assign(options,  data: _data ) : Object.assign(options,  params: _data )


const HTTP = new HttpRequest()

/**
 * 抛出整个项目的api方法
 */
const Api = (() => 
  const apiObj: any = 
  const requestList: any = requestConfig
  const fun = (opts: AxiosRequestConfig | string) => 
    return async (data = , method: Methods = "GET") => 
      if (!token) 
        console.error(‘No Token‘)
        return router.replace( name: ‘login‘ )
      
      const newOpts = conbineOptions(opts, data, method)
      const res = await HTTP.request(newOpts)
      return res
    
  
  Object.keys(requestConfig).forEach((key) => 
    apiObj[key] = fun(requestList[key])
  )

  return apiObj
)()

export default Api as any

10.打开./src/main.ts,引用common.scss

import "@/assets/scss/common.scss"

11.打开./src/App.vue,按照如下修改(未贴代码表示不变)

<template>
    <div id="app">
        <keep-alive>
            <router-view v-if="$route.meta.keepAlive"/>
        </keep-alive>
        <router-view v-if="!$route.meta.keepAlive"/>
    </div>
</template>

12.还有非常重要的一步,打开根目录下的vue.config.js,如果没有就自己创建一个,放在根目录下,并添加以下内容

const path = require(‘path‘)

const resolve = dir => 
  return path.join(__dirname, dir)


// 线上打包路径,请根据项目实际线上情况
const BASE_URL = process.env.NODE_ENV === ‘production‘ ? ‘/‘ : ‘/‘

module.exports = 
  baseUrl: BASE_URL,
  outputDir: ‘dist‘, // 打包生成的生产环境构建文件的目录
  assetsDir: ‘‘, // 放置生成的静态资源路径,默认在outputDir
  indexPath: ‘index.html, // 指定生成的 index.html 输入路径,默认outputDir
  pages: undefined, // 构建多页
  productionSourceMap: false, // 开启 生产环境的 source map?
  chainWebpack: config => 
    // 配置路径别名
    config.resolve.alias
      .set(‘@‘, resolve(‘src‘))
      .set(‘_c‘, resolve(‘src/components‘))
  ,
  css: 
    modules: false, // 启用 CSS modules
    extract: true, // 是否使用css分离插件
    sourceMap: false, // 开启 CSS source maps?
    loaderOptions:  // css预设器配置项
  ,
  devServer: 
    port: 8080, // 端口
    proxy: ‘https://www.easy-mock.com‘ // 设置代理
  

13.打开根目录下的package.json,在scripts中添加如下代码

"scripts": 
    ...
    "tep": "node scripts/template",
    "com": "node scripts/component"

四.编写业务代码

1.编写page页

运行我们之前添加的脚本命令,创建page,也就是运行之前写的template.js这个脚本,实现快速创建我们所需要的page模版,而不需要一个一个的再创建,大大节省了时间,如果不用用脚本跑也是可以的,分别需要创建以下文件夹:

  • views文件夹下创建login文件夹,再向login文件夹下添加login.vuelogin.tslogin.scss
  • ./src/api下添加login.ts
  • ./src/store/module下添加login.ts
  • ./src/types/views下添加login.interface.ts

是不是非常繁琐,还可能搞错(不推荐,浪费时间 0.0),而使用脚本只需要在命令行敲一条命令搞定(推荐)如下(根据demo需求,我们创建两个页面index、login):

cnpm run tep index
cnpm run tep login

打开./src/views/login/login.ts,发现报错,没有安装模块vuex-class,安装一下就好了

 cnpm i vuex-class --S

再运行创建组件脚本,随意创建一个test组件

cnpm run com test

ok,这时候发现./src/components目录下创建了test组件,为了引用组件更方便看起来更优雅,我们在./src/components目录下添加一个index.ts,把所有组件都引入到这里,作为一个中转文件,如下:

import Test from ‘./test/test.vue‘

export 
  Test

引用组件

上面创建好了组件后,打开./src/views/login/login.ts,如下引用:

import  Test  from "@/components" // 组件

@Component(
    components: 
        Test
    
)

./src/views/login/login.vue中添加组件

<template>
    <div class="login-wrap">
        data.pageName
        <div>
            <Test></Test>
        </div>
    </div>
</template>

这时候页面显示为如下:

 

 
技术图片
组件示意图

调用http请求示例

最后再说一下怎么调用http请求吧,在这之前,先重启一遍服务

cnpm run serve

按照我的步骤来,启动是不会报错的,如果报错,那么可以留言看到会回复,或者重新走一遍。
没问题的话,我们按照如下步骤:
1.打开./src/config/requestConfig.ts文件添加接口,由于我们之前的添加过了,那么我们进行下一步。
2.打开./src/api/login.ts文件添加请求函数,我们之前也写好了,跳过。
3.进入./src/store/module/login.ts文件,把GET_DATA_ASYN函数的注释打开。
4.在./src/store/index.ts中的module添加login,如下:

// modules
import Login from ‘./module/login‘
import Index from ‘./module/index‘

export default new Vuex.Store(
  ...
  modules: 
    Login,
    Index
  

完成上述动作后就可以在任意页面调用了,我们打开./src/views/index/index.ts,如下调用:

export default class About extends Vue 
  @Action GET_DATA_ASYN

  created() 
    this.GET_DATA_ASYN()
  

到现在有没有发现不管怎么样路由都到不了index页面,是因为做了路由拦截,我们在cookies添加一个token,如下:

 
技术图片
token


添加完之后,刷新就可以正常跳转到index了,(顺带一句,对于需要登录的项目路由拦截是很有必要的),这时候就可以发现在network里面发现我们的接口请求成功了,如下:

 
技术图片
接口请求成功示意图

 

篇幅比较长,终于写完了,有问题大家可以提,有更好的优化方法欢迎提出来,共同进步。

原文链接: https://www.jianshu.com/p/44500385abdd

以上是关于vue+ts搭建项目的主要内容,如果未能解决你的问题,请参考以下文章

vue3+vite+ts 搭建项目

vue+ts搭建项目

使用vite搭建vue3.0和ts项目过程

Webpack5 搭建 Vue3 + TS 项目

vue3+vite2+element-plus+ts搭建一个项目

实战 | Vite2.0搭建Vue3移动端项目