Headline 專案總結中

Qsy發表於2021-06-14

1.專案準備

技術棧:

  1. Vue
  2. vue-router
  3. vant
  4. axios,抽取api
  5. vue-cli腳手架
  6. Vuex狀態管理

1.1 rem適配

頭條專案使用rem適配

需求:瀏覽器尺寸改變之後,頁面元素自動適配

步驟

下包導包

npm i amfe-flexible

main.js匯入

通過modele的對應模組可以分隔螢幕

image-20210609093335754

在對應檔案可以設定根文字的大小

image-20210609093530800

1.2 通用樣式CSS

  1. 存放目錄在 /src/styles/base.less

image-20210609093712805

  1. main.js中匯入樣式,順序應在元件庫之後,自己寫的要覆蓋其他的,在後面引入

image-20210609094151743

1.3刪除測試程式碼

在建立專案的時候會自動建立一些測試程式碼

針對刪除

  1. App.vue內容幹掉
    1. 保留掛載點 #app 和 router-view渲染結構
  2. views目錄內容,清空多餘的元件,建立需要的元件
  3. /router/index.js
    1. 自定義路由規則
  4. components/HelloWorld.vue刪掉

1.4Git託管

https 或 ssh託管

webstorm一鍵連結和管理

image-20210609094916522

2.login頁面

2.1 頁面佈局和表單校驗

佈局

​ 1.導航欄

​ 2.表單

​ 3.提交按鈕

校驗

​ 1.新增rule物件規則

​ required必填項和提示資訊

​ 正規表示式規定輸入資訊格式

vant-field元件

vant-form元件

  1. van-form
    1. @submit:表單驗證成功之後觸發的回撥函式
      1. 引數是一個物件
      2. 內部的輸入元素的name屬性和value值,拼接為一個物件
  2. 輸入元素
    1. rules:校驗規則
      1. 陣列
      2. 每一條規則是一個物件
      3. required:必填
      4. message:提示資訊
      5. pattern:正則的規則
<template>
  <div class="login-container">
    <!-- 導航欄 -->
    <van-nav-bar title="登入" class="my-nav-bar" />
    <!-- 表單 -->
      //  內建的submit事件呼叫method的onSubmit自定義方法
    <van-form @submit="onSubmit">
      <van-field
	
        v-model="username"
		//name提交的引數名
		//value在input框由使用者輸入
        name="使用者名稱"
        label="手機號"
		//必填項
        required
        placeholder="請輸入手機號"
        :rules="[
		//校驗規則
          { required: true, message: '請輸入手機號' },
          {
            pattern: /^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$/,
            message: '手機號格式不對'
          }
        ]"
      />
      <van-field
        v-model="password"
        name="密碼"
        label="驗證碼"
        required
        placeholder="請輸入驗證碼"
        :rules="[
          { required: true, message: '請填寫驗證碼' },
          { pattern: /\d{6}/, message: '驗證碼格式不對' }
        ]"
      />
      <div style="margin: 16px;">
        <van-button round block type="info" native-type="submit"
          >提交</van-button
        >
      </div>
    </van-form>
  </div>
</template>

<script>
export default {
//元件暴露的名稱
  name: 'login',
//login元件的資料,以函式形式表達
  data () {
    return {
      username: '',
      password: ''
    }
  },
//對應表單的自定義方法
  methods: {
      //values是一個物件,name(程式碼定義)-value(使用者輸入)
    onSubmit (values) {
      console.log('submit', values)
    }
  }
}
</script>

<style lang="less">
  //設定登入條的樣式
.login-container {
  .my-nav-bar {
    background-color: #3196fa;
    .van-nav-bar__title {
      color: white;
    }
  }
}
</style>

2.2login頁的介面抽取

下包導包

  1. 下包:npm i axios

  2. 匯入在/src/api/login.js

  3. create方法建立一個副本

    image-20210608105527869

4.可用資訊

  1. 手機號很多個
  2. mobile:13912345678
  3. 驗證碼固定的
  4. code:246810

image-20210609113458157

image-20210609113541768

2.5.loading效果

避免使用者頻繁提交,為按鈕增加loading效果,並且切換啟用禁用狀態

需求:

  1. 資料提交時,為按鈕增加loading效果
  2. 切換啟用/禁用狀態,避免重複點選
  3. 通過button按鈕的屬性實現

Example

<template>
  <div>
    <button @click="isLoading = !isLoading">切換loading</button>
    <br />
    <van-button :loading="isLoading" type="primary" />
    <br />
    <van-button :loading="isLoading" type="primary" loading-type="spinner" />
    <br />
    <van-button
	//當載入中的時候,禁止提交按鈕的點選,其狀態為true是禁止
      :disabled="isLoading"
	//當載入中的時候,其狀態時true,顯示載入動畫
      :loading="isLoading"
	//type =info,是資訊按鈕,為藍色
      type="info"
	//載入中的文字
      loading-text="載入中..."
      >按鈕</van-button
    >
  </div>
</template>

<script>
          
export default {
  data () {
    return {
      isLoading: false
    }
  }
}

</script>

<style></style>

2.6封裝token方法

實現功能,把token儲存到快取中

步驟:

  1. sessionStorage重新整理不在了
  2. localStorage重新整理還在
  3. .then
    1. 儲存起來
      1. 預設無法直接儲存複雜型別
      2. 除非轉為JSON格式的字串
      3. JSON.stringify(複雜型別)-->字串

token在多個地方都需要使用,比如登出,介面我們把它抽取一下,方便呼叫,同時避免出錯,為了方便操作快取,封裝工具函式

  1. /src/utils/token.js

image-20210609114010247

  1. 提供3個方法並暴露出來
    1. saveToken
      1. 儲存token
      2. 接收引數
    2. removeToken
      1. 刪除token
      2. 無引數,無返回至
    3. getToken
      1. 返回token

實現token工具函式的封裝

// 定義key
const TOKENKEY = 'top-line-token'

// 儲存 token
const saveToken = tokenObj => {
  window.localStorage.setItem(TOKENKEY, JSON.stringify(tokenObj))
}
// 刪除 token
const removeToken = () => {
  window.localStorage.removeItem(TOKENKEY)
}
// 獲取 token
const getToken = () => {
   //getToken需要return
  // str-->obj
  return JSON.parse(window.localStorage.getItem(TOKENKEY))
}
//將定義的方法暴露出去
export { saveToken, removeToken, getToken }

2.7輕提示toast

在呼叫onsumit方法提交成功之後要列印數

據和彈出輕提示

1.匯入請求方法

2.在點選提交按鈕時呼叫onsumit方法,提交請求之後通過then和catch判斷是否請求成功

3.如果請求成功就彈出輕提示--陳工

4.如果請求失敗就彈出輕提示--失敗

image-20210608111934092

2.8整合三部分程式碼

loading-MV程式碼、token工具函式封裝、、toast-MV程式碼、

1.在請求成功時除了改變toast的輕提示,還有改變loading動畫的狀態

2.在請求時時除了改變toast的輕提示,還有改變loading動畫的狀態

3.為了防止loading響應太快,出現閃頓,設定一個定時器給關閉loading的語句

4.在Model結構中控制改變 過渡動畫的狀態 以及 禁用的狀態

實現程式碼如下

<template>
  <div class = "login-container">
    <!-- 導航欄 -->
    <van-nav-bar title = "登入" class = "my-nav-bar"/>
    <!-- 表單 -->
    <van-form @submit = "onSubmit">
      <van-field
		//繫結data中的mobile資料
        v-model = "mobile"
		//設定請求的鍵名
        name = "mobile"
		//繫結要顯示的文字
        label = "手機號"
		//設定必選項
        required
        //設定佔位符placeholder,同時給佔位符文字內容
        placeholder = "請輸入手機號"
		//對文字框的內容通過正則進行限制
        :rules = "[
          { required: true, message: '請輸入手機號' },
          {
            pattern: /^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$/,
            message: '手機號格式不對'
          }
        ]"
      />
      <van-field
        v-model = "code"	
        name = "code"
        label = "驗證碼"
        required
        placeholder = "請輸入驗證碼"
		//對文字框的內容通過正則進行限制
        :rules = "[
          { required: true, message: '請填寫驗證碼' },
          { pattern: /\d{6}/, message: '驗證碼格式不對' }
        ]"
      />
      <div style = "margin: 16px;">
        <van-button
          :loading = "isLoading"
          :disabled = "isLoading"
          loading-text = "登入ing"
          round
          block
          type = "info"
          native-type = "submit"
        >提交
        </van-button
        >
      </div>
    </van-form>
  </div>
</template>

<script>
// 匯入api的請求方法
import { userLogin } from '../../api/login'
// 匯入 token工具函式
import { saveToken } from '../../utils/token'

export default {
  name: 'login',

  data () {
    return {
      // 在頁面進入的時候載入一個手機號和驗證嗎,方便測試。
      // 邏輯上說,應該由使用者輸入和驗證
      mobile: '13912345678', // 手機號
      code: '246810', // 驗證碼
      isLoading: false// 是否正在載入中
    }
  },
  methods: {
    onSubmit (values) {
      //鍵名是name,鍵值是使用者在表單輸入的,為了方便測試設定了一個預設的值
      // values是一個物件{mobile:'xxx',code:'xxx'}
      // 開啟loading
      this.isLoading = true
      //userlogin是封裝的請求方法,傳入請求的物件{ mobile: '13912345678', code: '246810' }
      userLogin(values)
        .then(res => {
          console.log('res:', res)
          // 儲存token
          saveToken(res.data.data)
          //設定定時器的目的是為了在呈現效果的時候不會閃頓,更加可控和順滑
          //請求成功之後關閉輕提示,關閉動畫
          setTimeout(() => {
            this.$toast.success('登入成功!')
            this.isLoading = false
          }, 500)
        })
        .catch(errRes => {
          console.log('errRes:', errRes)
           //設定定時器的目的是為了在呈現效果的時候不會閃頓,更加可控和順滑
           //請求失敗之後關閉輕提示,關閉動畫
          setTimeout(() => {
            this.$toast.fail('登入失敗!')
            this.isLoading = false
          }, 500)
        })
    }
  }
}
</script>

//設定導航欄nav的樣式
<style lang = "less">
.login-container {
  .my-nav-bar {
    background-color: #3196fa;
    .van-nav-bar__title {
      color: white;
    }
  }
}
</style>

2.9使用token的儲存方法

image-20210609151121252

2.10跳轉到home

userLogin(values).then(res => {
        /* 進入到then就表示已經成功請求到了 */
        console.log('res', res)
        saveToken(res.data.data)

        /* 防止太快看不到loading動畫,設一個定時器 */
        setTimeout(() => {
          this.$toast.success('登入成功!')
          this.isLoading = false
          this.$router.push({
            path: '/home'
          })
        }, 300)

2.11 重定向

在沒有輸入url時,開啟專案看到的是白色介面,最後我們們通過重定向來解決這個問題 '/'跳轉到home

  routes: [
    {	
        // 預設地址
        path: '/a', 
        // 重定向的地址 需要被註冊
        redirect: '/b' 
    }
  ]

image-20210610095105648

3.layout頁面

3.1整合底部layout結構

<template>
  <div class = "layout-container">
    layout
    <van-tabbar v-model = "active" route>
        //根據樣式找到對應的圖示類名
      <van-tabbar-item icon = "home-o">首頁</van-tabbar-item>
      <van-tabbar-item icon = "chat-o">問答</van-tabbar-item>
      <van-tabbar-item icon = "video-o">視訊</van-tabbar-item>
      <van-tabbar-item icon = "user-o">我的</van-tabbar-item>
    </van-tabbar>
  </div>
</template>

<script>
export default {
  name: 'layout',
  data () {
    return {
        //高亮第一個圖示
      active: 0
    }
  }
}
</script>

<style></style>

<van-tabbar v-model = "active" route> route的作用是開啟路由模式,vant的Tabbar元件的引數

3.2layout巢狀路由

1.檔案結構

image-20210610091129197

2.路由匯入

import home from '../views/layout/home'
import question from '../views/layout/question'
import movie from '../views/layout/movie'
import user from '../views/layout/user'

3.渲染結構的出口在layout的index.vue中的vue-router

	<!--巢狀路由的出口-->
    <router-view></router-view>

4.巢狀路由出口的渲染(router-view)

image-20210610094447574

5.children在定義的時候也決定了它的路由出口在父元件上,所以元件的router-view最終渲染的位置在layout元件上,通過chrome外掛可以檢視 children: [{},{},{}]

4.user頁面

4.1.整合路由

SSR直連,解決timeout

4.2頂部區域

<template>
  <div class = "user-container">
    <div class = "info-box">
        
      <van-image
        class = "my-image"
        round
        src = "https://img01.yzcdn.cn/vant/cat.jpeg"
      />
      <h2 class = "name">
        起飛
        <br>
        <van-tag color = "#fff" text-color = "#3296fa" type = "primary">2021-6-10</van-tag>
      </h2>
    </div>
  </div>
</template>

<script>
            
export default {
  name: 'user'
}

</script>

<style lang = "less">
.user-container {
  .info-box {
    height: 100px;
    background-color: #3296fa;
    display: flex;
    padding-left: 18px;
    // 從 彈性佈局 左邊開始
    justify-content: flex-start;
    align-items: center;

    .my-image {
      width: 60px;
      height: 60px;
      margin-right: 5px;
    }

    .name {
      margin-left: 5px;
      color: white;
      font-size: 15px;
      font-weight: normal;
    }
  }
}
</style>

拓--justify-content:的樣式展示

justify-content:flex-start

4.3操作按鈕

less語法有&操作符 用法:&符號有2中用法,其一:父選擇符;其二:且的意思,在這裡使用的

設定操作連結的佈局和樣式

image-20210610115646777

實現結構

<van-row class = "my-control-box">
      <van-col class = "my-col" span = "8"
      >
        <van-icon class = "my-icon my" name = "newspaper-o"/>
        我的作品
      </van-col
      >
      <van-col class = "my-col " span = "8"
      >
        <van-icon class = "my-icon star" name = "star-o"/>
        我的收藏
      </van-col
      >
      <van-col class = "my-col " span = "8"
      >
        <van-icon class = "my-icon history" name = "tosend"/>
        閱讀歷史
      </van-col
      >
    </van-row>

實現樣式

  //設定每一列的字型大小,並且居中
  .my-col {
    font-size: 12px;
    text-align: center;
  }

  //單獨給icon字型圖示設定大小,並且轉為塊級元素
  .my-icon {
    font-size: 28px;
    display: block;

    //且的意思,my-icon且my
    &.my {
      color: #77aaff;
    }

    &.star {
      color: #ff0000;
    }

    &.history {
      color: #ffaa00;
    }
  }
}

4.4底部區域

1.icon:左側圖示

2.title:左側文字

3.is-link:右側箭頭

image-20210610121347328

實現結構

<template>
  <div>
    <van-cell-group>
      <van-cell title="編輯資料" icon="edit" is-link />
      <van-cell title="小智同學" icon="chat-o" is-link />
      <van-cell title="系統設定" icon="setting-o" is-link />
      <van-cell title="退出登入" icon="warning-o" is-link />
    </van-cell-group>
  </div>
</template>

<script>
export default {}
</script>

<style></style>

實現樣式

.my-control-box {
  //設定上下內邊距,把三個小圖示擠進去
  padding-top: 20px;
  padding-bottom: 20px;

  //設定每一列的字型大小,並且居中
  .my-col {
    font-size: 12px;
    text-align: center;
  }

  //單獨給icon字型圖示設定大小,並且轉為塊級元素
  .my-icon {
    font-size: 28px;
    display: block;

    //且的意思,my-icon且my
    &.my {
      color: #77aaff;
    }

    &.star {
      color: #ff0000;
    }

    &.history {
      color: #ffaa00;
    }
  }
}

4.5使用者資訊

1.測試介面

image-20210610144411928

image-20210610144532828

2.api抽取

在src/api資料夾封裝一個user.js用來儲存user頁面用到的api

//匯入請求元件axios
import axios from 'axios'

// 匯入獲取token的工具函式
import { getToken } from '@/utils/token'

//設定基地址
const request = axios.create({
  baseURL: 'http://toutiao-app.itheima.net'
})

//封裝獲取使用者資訊的方法
const getUserInfo = () => {
  return request({
      //剩餘的請求地址
    url: '/v1_0/user/profile',
      //請求的方式
    method: 'get',
      //請求頭的設定
    headers: {
        
      Authorization: `Bearer ${getToken().token}`
    }
  })
}

//暴露獲取使用者資訊的方法
export { getUserInfo }


//儲存的token

//{token: "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2M…HNlfQ.4XdqWBCJ-Q_IGxNY5jEekiqrmzOg6zZIYLQkbK5WIWE", refresh_token: "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2M…ydWV9.UycupnAiENN4AZROq7n8LbxkNEAuUYZHcXe52SFfVgs"}

////獲得請求頭的授權token,通過呼叫token工具函式,因為在saveToken的時候是一個物件,所以呼叫函式得到的是一個物件 setItem(TOKENKEY, JSON.stringify(tokenObj))

3.使用新增資料

實現邏輯

//匯入預設的圖片
import defaultImg from '../../../assets/logo.png'
// 匯入 請求的 api方法
import { getUserInfo } from '../../../../src/api/user'

//暴露介面
export default {
  name: 'user',
  data () {
    return {
      defaultImg,
      // 設定一個空物件儲存使用者資訊
      userInfo: {}
    }
  },
  created () {
    // 在鉤子新增請求到的資訊
    getUserInfo().then(res => {
      console.log('res:', res)
        //如果獲取成功使用者資訊,新增到  userInfo: {}
      this.userInfo = res.data.data
    })
  }
}

實現結構

<div class = "info-box">
      <van-image
        class = "my-image"
        round
        //設定返回資料的頭像
        :src = "userInfo.photo"
      />
      <h2 class = "name">
          //設定返回資料的名稱
        {{ userInfo.name }}
        <br>
            //設定返回資料的生日
        <van-tag color = "#fff" text-color = "#3296fa" type = "primary"> {{ userInfo.birthday }}</van-tag>
      </h2>
   </div>

4.6登出功能

通過dialog模態框實現彈出

清空資訊的語句在後面有改善,通過Vuex在登出方法中做出優化

this.$store.commit('setUserInfo', {})

實現邏輯

methods: {
    logout () {
        //
      this.$dialog
        .confirm({})
        .then(() => {
          // 登出後刪除token資料
          removeToken()
          // 登出後將使用者資訊清空,之前儲存的使用者資訊是一個物件
          this.userInfo = {}
          // 登出後通過router元件跳轉頁面到home,因為不是後臺管理系統所以不需要跳轉到login登入頁面
          this.$router.push({
            path: '/home'
          })
          // 登出失敗返回一個catch
        }).catch(() => {
          console.log('catch')
        })
    }
  }

實現功能

在登出的標籤註冊一個點選事件

<van-cell title = "退出登入" icon = "warning-o" is-link @click = "logout"/>

4.7登入判斷token--前置守衛

限制在user頁面進行判斷,其他頁面不登陸也可以訪問

image-20210611093740951

image-20210611095014458

檔案目錄 --src/router/index.js

實現邏輯

導航守衛的三個引數必須按順序書寫

//routes引數的設定
{
        path: 'user',
        name: 'user',
        component: user,
        meta: {
          //自定義的規則欄位
          needLogin: true
        }
}

// 1.在導航守衛中先獲取meta是否需要登入
router.beforeEach((to, from, next) => {
    //判斷是否需要
  if (to.meta.needLogin !== true) {
      //如果不需要元資訊,直接返回next()
    return next()
  }

  // 拿到令牌
  const tokenObj = getToken()
//2.判斷token令牌是否存在
  if (tokenObj === null) {
      //如果令牌為空,彈出輕提示
    Toast.fail('請先登入')
       //如果令牌為空,跳轉到login頁面
    return next({ path: '/login' })
  }
    //token既不是空,而且需要元資訊
  next()
    
    
//3.判斷token令牌是否正確  
     // 拿到個人的資訊
     //getUserInfo根據已有的token發起請求,並且返回使用者的資料
  getUserInfo()
    // 請求成功之後攜帶資料直接到下一站
    .then(res => {
      console.log(res)
      next()
    })
    // 請求失敗,根據返回的狀態碼401判斷token不正確(後端完成)
    .catch(errRes => {
      if (errRes.response.status === 401) {
        removeToken()
        // 如果token給出提示請先登入
        Toast.fail('請先登入')
        // 如果token不對跳轉登入頁面
        next({ path: '/login' })
      }
    })
})

console.dir()可以解析屬性的值

解決Vuerouter的Promise報錯(吞掉)

// 吞掉 沒有處理的 promise的錯誤
const originalPush = VueRouter.prototype.push


VueRouter.prototype.push = function push (location, onResolve, onReject) {
  if (onResolve || onReject) {
    // 執行之後 阻斷後續程式碼
    return originalPush.call(this, location, onResolve, onReject)
  }
  return originalPush.call(this, location).catch(err => err)
}

4.8 共享使用者資訊-vuex

1.新增vuex元件

vue add vuex

在mian.js檔案上

  // 將Vuex倉庫註冊Vue建構函式上
  store,

2.修改使用者資訊

src/router/index.js

// 將獲取的使用者資訊共享到Vuex的倉庫的state中,通過定義的 mutations的setUserInfo方法修改預設定的資訊資訊
//第一個引數是呼叫的修改資料的方法,第二個資料是修改後的資料
store.commit('setUserInfo', res.data.data)

3.設定路由元資訊

export default new Vuex.Store({
    //設定原始資料
  state: {
    userInfo: {}
  },
  
  mutations: {
        //定義修改資料的方法
      	//這個方法接收一個原始的資料,第二個引數是傳入的修改後的資料
    setUserInfo (state, newUserInfo) {
      state.userInfo = newUserInfo
    }
},

4.檢視共享的資料

image-20210611112813047

4.9使用者頁面調整

1.導航守衛中請求了一次

導航守衛屬於router,在router資料夾的index.js當中

​ 目的有三

​ 為了判斷是否需要判斷needLogin

​ 為了判斷請求的資料是否存在token

​ 為了判斷token的值是否正確,401?

這裡拿到了使用者的資料只進行了判斷,沒有進行使用

2.載入user頁面請求一次

這裡通過呼叫 getUserInfo()方法獲取,用來渲染頁面。

請求使用者資訊的方法getUserInfo()定義在src/api/user中,因為也算是請求,放在api檔案當中

3.解決多次請求

Vuex本質是共享資料,統一放在新建的src/store的檔案中的index.js

使用者資訊儲存到Vuex中,調整使用者頁面資訊的資料來源(目的是為了只請求一次放在倉庫中,減少請求次數),之前是兩次裡請求

image-20210611120628898

在登出方法logout(),修改清空資料的語句

// 登出後將使用者資訊清空,之前儲存的使用者資訊是一個物件
          // this.userInfo = {}
          // 優化登出資訊清空,現在拿到的資訊是從Vuex中來的
this.$store.commit('setUserInfo', {})

4.10登陸成功返回訪問頁

1.正常訪問login頁面之後區home頁面

2.因為沒有登入被轉到login,在登入成功之後要返回

​ 1.login/index.vue登陸成功之後進行判斷

​ 1.有redirect引數

​ 2.沒有redirect引數

實現邏輯

在router檔案的index.js中實現redirect引數的攜帶

1.沒有token攜帶redirect引數

 if (tokenObj === null) {
    Toast.fail('請先登入')
    // 如果沒有token令牌跳轉到登陸頁面,並且攜帶redirect引數
    return next({
      path: '/login',
      // 定義重新定向的到哪去的路徑引數,這個路徑引數指向當前沒登陸的頁面
      query: { redirect: to.path }
    })
  }

2.token令牌錯誤攜帶redirect引數

.catch(errRes => {
      if (errRes.response.status === 401) {
        removeToken()
        // 如果token給出提示請先登入
        Toast.fail('請先登入')
        // 如果token不對跳轉登入頁面
        next({
          path: '/login',
          // 如果請求的token令牌是錯誤的也要重新定向
          query: { redirect: to.path }
        })
      }
    })

3.在login檔案中的index.vue對redirect引數進行判斷

該邏輯寫在表單提交成功的then之中

 const redirect = this.$route.query.redirect
          if (redirect) {
            return this.$router.push({
              path: redirect
            })
          }
          // 沒有redirect引數
          this.$router.push({
            path: '/home'
})

5.edit頁面

5.1整合路由

1.建立edit頁面在src/views/layout/edit/index.vue

<template>
  <div class = "edit-container">
    <!-- 導航條 -->
    <van-nav-bar left-arrow title = "編輯資料"></van-nav-bar>
    <!-- 頭像部分 -->
    <div class = "avatar">
      <van-image fit = "cover" round src = "https://img.yzcdn.cn/vant/cat.jpeg"/>
    </div>
    <!-- 資訊展示 -->
    <van-cell-group>
      <van-cell is-link title = "名稱" value = "暱稱"/>
      <van-cell is-link title = "性別" value = "男"/>
      <van-cell is-link title = "生日" value = "2020-1-1"/>
    </van-cell-group>
  </div>
</template>

<script>
export default {
  name: 'editUser'
}
</script>

<style lang = "less">
.edit-container {
  // 導航條部分
  .van-nav-bar {
    background-color: #3196fa;

    .van-nav-bar__title {
      color: #fff;
    }

    .van-icon-arrow-left {
      color: #fff;
    }
  }

  // 頭像部分
  .avatar {
    padding: 20px 0;
    text-align: center;

    .van-image {
      width: 120px;
      height: 120px;
    }
  }
}
</style>

2.在src/router/index.js建立對應的edit路由

路由安放的位置在layout路由的children序列當中

layout是所有chidren路由渲染的出口<router-view></router-view>

{
        path: 'edit',
        name: 'edit',
        component: edit
},

5.2user跳轉edit

1.新增跳轉屬性

操作目錄:src/views/layout/user/index.vue

<van-cell title = "編輯資料" icon = "edit" is-link to = "/edit"/>

2.從edit返回user的兩種方法

操作目錄src/views/layout/edit/index.vue

go方法

onClickleft () {
      this.$router.go(-1)
}

back方法

onClickleft () {
      this.$router.back()
}

5.3 edit登入判斷

需求:

  1. 編輯頁面需要登入才可以訪問

步驟:

  1. 給任意希望登入才可以訪問的路由(頁面)
  2. 新增元資訊即可
    1. meta:{needLogin:true}

5.4編輯隱藏Tabbar

需求:

  1. 訪問/edit頁面時隱藏tabbar
  2. 結合路由元資訊實現

步驟:

  1. 配置路由元資訊 ,src/router/index.js
{
        path: 'edit',
        name: 'edit',
        component: edit,
        meta: {
          needLogin: true,
          // 給他設定單獨的路由元,為false,其他沒有true預設顯示,在V-show取反時直接v-show:false隱藏
          hideTabbar: true
        }
}

2.在layout頁面隱藏底部導航條,src/views/layout/index.vue

​ 獲取元路由資訊

​ 對預設的hideTabbar取反

 <van-tabbar v-model = "active" route v-show = "!$route.meta.showTabbar">

3.不隱藏的效果:

image-20210611172812623

4.隱藏的實現效果:

image-20210611172840519

5.5渲染edit頁面

需求:

  1. 進入編輯頁面,把使用者資料渲染到頁面上

步驟:

  1. 路由中對token進行了判斷,並且將資料儲存到了Vuex的倉庫當中 store.commit('setUserInfo', res.data.data)

  2. 在edit頁面獲取倉庫中的資料

    ​ 在計算屬性中定義一個使用者資訊的方法,並且返回使用者的資訊

    computed: {
        // 在計算屬性內部定義一個方法,呼叫時不需要加括號
     userInfo () {
     return  this.$store.state.userInfo
    }
    

3.將獲得資料新增到頁面, src/views/layout/edit/index.vue

<div class = "avatar">
      <van-image fit = "cover" round :src = "userInfo.photo"/>
</div>
    <!-- 資訊展示 -->
    <van-cell-group>
      <van-cell is-link title = "名稱" :value = "userInfo.name"/>
      <van-cell is-link title = "性別" :value = "userInfo.gender === 0?'男':'女'"/>
      <van-cell is-link title = "生日" :value = "userInfo.birthday"/>
    </van-cell-group>

在渲染性別的時候要對繫結的資料的值進行判斷

因為在行內可以通過三元表示式

<van-cell is-link title = "性別" :value = "userInfo.gender === 0?'男':'女'"/>

5.6 mapState整合

1.匯入mapstate函式

import { mapState } from 'vuex'

2.整合computed

computed: mapState(['userInfo']),
  //因為只有一個資料所以不用使用物件和擴充運算子直接進行復制操作
    
  // 之前的定義
  //computed: {
  //   // 在計算屬性內部定義一個方法,呼叫時不需要加括號
  //   userInfo () {
  //     return this.$store.state.userInfo
  //   }
  // },

5.7使用者資訊請求請求優化

(2到1)-- 登陸判斷

在router的index.js對使用者資訊進行判斷

一次是在user頁面,一次是在編輯頁面

1.user頁面的請求

computed: {
    userInfo () {
      return this.$store.state.userInfo
    }
},

2.編輯頁面的請求

 computed: {
    // 在計算屬性內部定義一個方法,呼叫時不需要加括號
    userInfo () {
      return this.$store.state.userInfo
    }
 },

3.優化之後

// 為了防止重複的對使用者資訊發起請求,
  if (store.state.userInfo.name) {
    return next
  }
// 不能通過 store.state.userInfo 來判斷,因為是個空物件,空物件判斷之後也是true
//return可以打斷程式碼不再往下執行,而且如果有使用者資訊證明一定有正確的token
//console.log({}===true)//true

5.8 編輯使用者名稱

1.dialog彈出模態框

實現結構

​ 1.繫結點選事件

<van-cell is-link title = "名稱" :value = "userInfo.name" @click = "showEditName"/>

​ 2.整合使用者名稱編輯框

<!--姓名編輯框-->
<van-dialog v-model = "showName" title = "修改姓名" show-cancel-button>
  <van-field ref = "nameField" v-model = "name" placeholder = "請輸入使用者名稱"/>
</van-dialog>

2.cell繫結點選事件

1.彈框

2.使用者的資訊填入輸入框

3.輸入框獲取焦點

​ 設定filed框設定ref

​ this.$refs.屬性名獲取標籤新增點選事件

實現邏輯

data () {
    return {
      showName: false,
      name: ''
    }
  },


methods : {
showEditName () {
    //點選模態框,修改showName的屬性為true
      this.showName = true
    //獲取使用者資訊的name賦值給定義的name資料
      this.name = this.userInfo.name
      this.$nextTick(() => {
        this.$refs.nameField.focus()
      })
 }
}

4.修改模態框的樣式

​ 邊框

​ 父盒子設定內邊距

.van-dialog__content {
    padding: 10px;
  }

  .van-field {
    border: 1px solid #ccc;
  }

3.注意事項

vue更新資料和更新dom是非同步的

vue資料更新--->dom更新是非同步的

可以通過$nextTick註冊一個回撥函式

dom更新之後執行

5.9 抽取edit的api

方便發起請求和維護

1.設定請求攔截器的資訊

2.封裝編輯使用者資訊的請求方法

// 匯入
import axios from 'axios'
// 匯入token工具函式
import { getToken } from '../utils/token'
// create方法設定基地址的請求request
const request = axios.create({
  baseURL: 'http://toutiao-app.itheima.net'
})


// 註冊攔截器
// 新增請求攔截器
request.interceptors.request.use(
  function (config) {
    // 在傳送請求之前做些什麼
    console.log('請求攔截器執行啦')
      //設定請求攔截器的資訊token令牌!!!
    config.headers.Authorization = `Bearer ${getToken().token}`
    return config
  },
  function (error) {
    // 對請求錯誤做些什麼
    return Promise.reject(error)
  }
)


// 抽取方法 - 編輯使用者資訊
const editUserInfo = data => {
  return request({
    url: '/v1_0/user/profile',
      //後臺文件定義的請求方式
    method: 'patch',
      // data:{...data}不縮寫的引數
    data:data
  })
}


// 暴露出去
export { editUserInfo }

5.10 儲存使用者名稱資料

實現結構

新增修改暱稱的事件

//顯示編輯的使用者名稱
<van-cell is-link title = "名稱" :value = "userInfo.name" @click = "showEditName"/>

//儲存修改後的使用者名稱
 <van-dialog v-model = "showName" title = "修改姓名" show-cancel-button @confirm = "saveNameEdit">

實現邏輯

  
//顯示編輯的使用者名稱
showEditName () {
      this.showName = true
      this.name = this.userInfo.name
      this.$nextTick(() => {
        this.$refs.nameField.focus()
      })
}





// 定義一個儲存使用者名稱的方法
  saveNameEdit () {
      // 發起修改資料的請求,傳入已經存在的值
      editUserInfo({ name: this.name })
        // 請求成功之後返回資料,在then回撥中處理
        .then(res => {
          // 在倉庫中更新使用者的資訊
          this.$store.commit('setUserInfo', {
            // 展開所有的使用者資訊
            ...this.userInfo,
            // 覆蓋使用者的暱稱資訊
            name: this.name
          })
        })
    },

5.11 編輯性別資訊

實現結構

<van-cell is-link title = "性別" :value = "userInfo.gender === 0?'男':'女'" @click = "showGender = true"/>





//popup編輯框
<van-popup v-model = "showGender" position = "bottom">
      <van-nav-bar title = "修改性別" left-text = "取消"/>
      <van-cell-group>
        <van-cell title = "男" is-link/>
        <van-cell title = "女" is-link/>
      </van-cell-group>
</van-popup>

實現邏輯

//在data中定義
 showGender: false,

點選切換cell元件,修改popub的布林值,切換彈出的狀態

跳轉不了看一下路由router

5.12儲存性別資訊

1.修改資料庫中的資料

2.關閉顯示的模態框(點選取消,男,女)

實現結構

   1.<!--在渲染性別的時候要對繫結的資料的值進行判斷 因為在行內可以通過三元表示式,在點選性別的時候顯示性別框-->
      <van-cell is-link title = "性別" :value = "userInfo.gender === 0?'男':'女'" @click = "showGender = true"/>



 //2.
<van-nav-bar title = "修改性別" left-text = "取消" @click-left = "showGender = false"/>


//3.
<van-cell-group>
        <van-cell title = "男" is-link @click = "saveGenderEdit(0)"/>
        <van-cell title = "女" is-link @click = "saveGenderEdit(1)"/>
      </van-cell-group>

實現邏輯

 saveGenderEdit (gender) {
      editUserInfo({ gender }).then(res => {
        this.showGender = false
        this.$store.commit('setUserInfo', {
          // 展開所有的使用者資訊
          ...this.userInfo,
          // 覆蓋使用者的性別資訊
          gender
        })
      })
    },

5.13編輯日期

data(){
birthday: '',
minDate: new Date(1970, 0, 1)//設定最小值,月份從0開始,看文件
}



showBirthdayPop () {
      this.showBirthday = true
      this.birthday = this.userInfo.birthday
    }
   <van-cell is-link title = "生日" :value = "userInfo.birthday" @click="showBirthdayPop"/>//為生日模態框繫結點選事件
   
   
   <!--生日編輯框-->
    <van-popup v-model = "birthday" position = "bottom">
      <van-datetime-picker
        v-model = "birthday"
        type = "date"
        title = "選擇年月日"
        :min-date = "minDate"
      />
    </van-popup>

5.14儲存編輯日期

實現結構

<van-datetime-picker
        v-model = "birthday"
        type = "date"
        title = "選擇年月日"
        :min-date = "minDate"
        @cancel = "showBirthday = false"
        @confirm = "saveBirthday"
/>

實現邏輯

saveBirthday () {
      // 處理生日的格式,符合要求
      const birthday = moment(this.birthday).format('YYYY-MM-DD')
      editUserInfo({ birthday }).then(res => {
        this.showBirthday = false
        this.$store.commit('setUserInfo', {
          ...this.userInfo,
          birthday
        })
      })
    }

6.xxx

7.知識點的補充

1.具名插槽

2.axios-create建立例項

​ 通過axios-create建立例項來抽取api

this.$axios的方式呼叫介面會有兩個問題

​ 1.沒有辦法在審查程式碼時立即定位

​ 2.修改引數、地址比較麻煩,不利於後期的維護

抽取案例

image-20210607110708642

image-20210607110712757

image-20210607110717877

3.輕提示toast

輕提示

4.vuex的基本使用

Vuex是什麼:

​ Vuex 是一個專為 Vue.js 應用程式開發的狀態管理模式。它採用集中式儲存管理應用的所有元件的狀態,並以相應的規則保證狀態以一種可預測的方式發生變化。

image-20210609153404883

每一個 Vuex 應用的核心就是 store(倉庫)。“store”基本上就是一個容器,它包含著你的應用中大部分的狀態 (state)。Vuex 和單純的全域性物件有以下兩點不同:

  1. Vuex 的狀態儲存是響應式的。當 Vue 元件從 store 中讀取狀態的時候,若 store 中的狀態發生變化,那麼相應的元件也會相應地得到高效更新。
  2. 你不能直接改變 store 中的狀態。改變 store 中的狀態的唯一途徑就是顯式地提交 (commit) mutation。這樣使得我們可以方便地跟蹤每一個狀態的變化,從而讓我們能夠實現一些工具幫助我們更好地瞭解我們的應用

基礎使用

​ 1.在main.js中宣告以store倉庫,通過Vue的store方法,傳入引數物件count,這樣就可以在main.js之外的頁面使用count

  <div class = "info-box">
      <van-image
        class = "my-image"
        round
        :src = "userInfo.photo"
      />
      <h2 class = "name">
        {{ userInfo.name }}
        <br>
        <van-tag color = "#fff" text-color = "#3296fa" type = "primary"> {{ userInfo.birthday }}</van-tag>
      </h2>
    </div>

在其他頁面通過語句

this.$store.state.xxx修改和獲取資料

5.巢狀路由

目的:為了搭建更為複雜的專案

語法:

  1. /login登入頁
  2. /home首頁
    1. /home/index
    2. /home/news
    3. /home/vip
    4. /home/hots
      1. router-view
// 巢狀路由的規則
import index from '../views/home/index/index.vue'
import news from '../views/home/news'
import vip from '../views/home/vip'
import hots from '../views/home/hots'

Vue.use(VueRouter)

const routes = [
  {
    path: '/login',
    name: 'login',
    component: login
  },
  {
    path: '/home',
    name: 'home',
    component: home,
      //通過children物件來設定巢狀路由的地址
    children: [
      {
        path: 'index', // /home/index
        component: index
      },
      {
        path: 'news', // /home/news
        component: news
      },
      {
        path: 'vip', // /home/vip
        component: vip
      },
      {
        path: 'hots', // /home/hots
        component: hots
      }
    ]
  }
]

const router = new VueRouter({
  routes
})

export default router

6.路由元資訊

Router中的mata

是一個物件

與path、name、component同級

image-20210610160343617

image-20210610160322106

7.導航守衛

Router中的,

不止一個導航守衛

三個引數:

​ 1.to去的路由資訊

​ 2.from來的路由資訊

​ 3.next()是否繼續執行

語法

const router = new VueRouter({
  routes
})

// 新增全域性前置守衛
router.beforeEach((to, from, next) => {
  // to 即將到達的路由資訊 this.$route獲取到的是一致的
  console.log('to:', to)
  // from 離開的路由資訊 this.$route獲取到的是一致的
  console.log('from:', from)
  // console.log('next:', next)
  // 不執行next 卡在這個地方 路由的切換就有問題啦!
  // next(),類似於node中的中介軟體,如果沒有next到這就停了
  // 如果你要去的就是 404 直接放走
  if (to.path === '/404') {
    // 直接放走
    next()
  } else {
    // 除他之外 去404
    // 直接跳轉到指定頁面
    next({ path: '/404' })
  }
})

導航守衛有多個,不止是beforeEach()

8.vuex的mutations

主要作用是修改資料

this.$store.state.xxx取值/賦值

image-20210611090156488

這種方法可以修改,但是外掛無法捕捉,不方便除錯和修改

2.元件中呼叫這個方法

this.$store.commit('mutations',引數)取值/賦值

實現原理

image-20210611090536460

setFood要和mutation中一樣,state是被修改的資料,setFood方法內將傳進的資料引數對原始state資料進行賦值修改

類似於

​ this.$emit()

9.Vuex的mapState輔助函式

意義:方便計算屬性的定義取值

作用:

​ 1.自動生成計算屬性:this.$store.state.userInfo

​ 2.直接通過mapState({'userInfo'})獲取倉庫的資料

​ 返回值:

​ {userIfo:function(){return this.$store.state.userInfo}}

展開了物件{food:f}

food:function(){}

image-20210611181546529

輔助函式的兩種方法

1.有自己的資料要繫結,通過展開運算子

computed: {
    //本地的計算屬性
  localComputed () { /* ... */ },
  // 使用物件展開運算子將此物件混入到外部物件中
  ...mapState({
    // ...
  })
}

2.沒有自己的資料直接使用

computed: mapState([
  // 對映 this.count 為 store.state.count
  'state中儲存的名字'
])

10.axios的攔截器

邏輯關係

image-20210611162930413

實現程式碼

// 新增請求攔截器
axios.interceptors.request.use(function (config) {
    // 在傳送請求之前做些什麼
    return config;
  }, function (error) {
    // 對請求錯誤做些什麼
    return Promise.reject(error);
  });

// 新增響應攔截器
axios.interceptors.response.use(function (response) {
    // 對響應資料做點什麼
    return response;
  }, function (error) {
    // 對響應錯誤做點什麼
    return Promise.reject(error);
  });

11.點選user無效

在晚上點選user按鈕之後沒有任何的反應(一段時間之後會報錯),首先是伺服器的響應失效了。

沒有任何反應的原因是,在單擊我的之後。user的vue頁面一直在傳送請求但是沒有請求到資料,所以一直在等待載入被

if (store.state.userInfo.name) {
    return next()
}

卡住了,請求不到使用者的名字,就不能next,所以一直在等待請求

頁面有四種方法到指定頁面但是都不符合

​ 1.不要登陸(不符合)

​ 2.有請求的使用者資訊名稱(不符合)

​ 3.沒有token next跳轉到login(不符合)

​ 4.定義的方法沒有呼叫所以不餓能next(不符合)

相關文章