vue3 配置

一、vue3組件通信方式

1.1props

props可以實(shí)現(xiàn)父子組件通信,在vue3中我們可以通過defineProps獲取父組件傳遞的數(shù)據(jù)借浊。且在組件內(nèi)部不需要引入defineProps方法可以直接使用耘柱!

父組件給子組件傳遞數(shù)據(jù)

<Child info="我愛祖國" :money="money"></Child>

子組件獲取父組件傳遞數(shù)據(jù):方式1

let props = defineProps({
  info:{
   type:String,//接受的數(shù)據(jù)類型
   default:'默認(rèn)參數(shù)',//接受默認(rèn)數(shù)據(jù)
  },
  money:{
   type:Number,
   default:0
}})

子組件獲取父組件傳遞數(shù)據(jù):方式2

let props = defineProps(["info",'money']);

子組件獲取到props數(shù)據(jù)就可以在模板中使用了,但是切記props是只讀的(只能讀取违寿,不能修改)

1.2自定義事件

在vue框架中事件分為兩種:一種是原生的DOM事件,另外一種自定義事件钢拧。

原生DOM事件可以讓用戶與網(wǎng)頁進(jìn)行交互雄妥,比如click最蕾、dbclick依溯、change老厌、mouseenter、mouseleave....

自定義事件可以實(shí)現(xiàn)子組件給父組件傳遞數(shù)據(jù)

1.2.1原生DOM事件

代碼如下:

 <pre @click="handler">
      我是祖國的老花骨朵
 </pre>

當(dāng)前代碼級給pre標(biāo)簽綁定原生DOM事件點(diǎn)擊事件,默認(rèn)會給事件回調(diào)注入event事件對象黎炉。當(dāng)然點(diǎn)擊事件想注入多個(gè)參數(shù)可以按照下圖操作枝秤。但是切記注入的事件對象務(wù)必叫做$event.

  <div @click="handler1(1,2,3,$event)">我要傳遞多個(gè)參數(shù)</div>

在vue3框架click、dbclick慷嗜、change(這類原生DOM事件),不管是在標(biāo)簽淀弹、自定義標(biāo)簽上(組件標(biāo)簽)都是原生DOM事件。

****

1.2.2自定義事件

自定義事件可以實(shí)現(xiàn)子組件給父組件傳遞數(shù)據(jù).在項(xiàng)目中是比較常用的庆械。

比如在父組件內(nèi)部給子組件(Event2)綁定一個(gè)自定義事件

<Event2  @xxx="handler3"></Event2>

在Event2子組件內(nèi)部觸發(fā)這個(gè)自定義事件

<template>
  <div>
    <h1>我是子組件2</h1>
    <button @click="handler">點(diǎn)擊我觸發(fā)xxx自定義事件</button>
  </div>
</template>

<script setup lang="ts">
let $emit = defineEmits(["xxx"]);
const handler = () => {
  $emit("xxx", "法拉利", "茅臺");
};
</script>
<style scoped>
</style>

我們會發(fā)現(xiàn)在script標(biāo)簽內(nèi)部,使用了defineEmits方法薇溃,此方法是vue3提供的方法,不需要引入直接使用。defineEmits方法執(zhí)行缭乘,傳遞一個(gè)數(shù)組沐序,數(shù)組元素即為將來組件需要觸發(fā)的自定義事件類型,此方執(zhí)行會返回一個(gè)$emit方法用于觸發(fā)自定義事件堕绩。

當(dāng)點(diǎn)擊按鈕的時(shí)候策幼,事件回調(diào)內(nèi)部調(diào)用$emit方法去觸發(fā)自定義事件,第一個(gè)參數(shù)為觸發(fā)事件類型,第二個(gè)奴紧、三個(gè)特姐、N個(gè)參數(shù)即為傳遞給父組件的數(shù)據(jù)。

需要注意的是:代碼如下

<Event2  @xxx="handler3" @click="handler"></Event2>

正常說組件標(biāo)簽書寫@click應(yīng)該為原生DOM事件,但是如果子組件內(nèi)部通過defineEmits定義就變?yōu)樽远x事件了

let $emit = defineEmits(["xxx",'click']);

1.3全局事件總線

全局事件總線可以實(shí)現(xiàn)任意組件通信黍氮,在vue2中可以根據(jù)VM與VC關(guān)系推出全局事件總線唐含。

但是在vue3中沒有Vue構(gòu)造函數(shù),也就沒有Vue.prototype.以及組合式API寫法沒有this沫浆,

那么在Vue3想實(shí)現(xiàn)全局事件的總線功能就有點(diǎn)不現(xiàn)實(shí)啦捷枯,如果想在Vue3中使用全局事件總線功能

可以使用插件mitt實(shí)現(xiàn)。

mitt:官網(wǎng)地址:https://www.npmjs.com/package/mitt

1.4v-model

v-model指令可是收集表單數(shù)據(jù)(數(shù)據(jù)雙向綁定)件缸,除此之外它也可以實(shí)現(xiàn)父子組件數(shù)據(jù)同步铜靶。

而v-model實(shí)指利用props[modelValue]與自定義事件[update:modelValue]實(shí)現(xiàn)的喷户。

下方代碼:相當(dāng)于給組件Child傳遞一個(gè)props(modelValue)與綁定一個(gè)自定義事件update:modelValue

實(shí)現(xiàn)父子組件數(shù)據(jù)同步

<Child v-model="msg"></Child>

在vue3中一個(gè)組件可以通過使用多個(gè)v-model,讓父子組件多個(gè)數(shù)據(jù)同步,下方代碼相當(dāng)于給組件Child傳遞兩個(gè)props分別是pageNo與pageSize雷蹂,以及綁定兩個(gè)自定義事件update:pageNo與update:pageSize實(shí)現(xiàn)父子數(shù)據(jù)同步

<Child v-model:pageNo="msg" v-model:pageSize="msg1"></Child>

1.5useAttrs

在Vue3中可以利用useAttrs方法獲取組件的屬性與事件(包含:原生DOM事件或者自定義事件),次函數(shù)功能類似于Vue2框架中attrs屬性與listeners方法。

比如:在父組件內(nèi)部使用一個(gè)子組件my-button

<my-button type="success" size="small" title='標(biāo)題' @click="handler"></my-button>

子組件內(nèi)部可以通過useAttrs方法獲取組件屬性與事件.因此你也發(fā)現(xiàn)了,它類似于props,可以接受父組件傳遞過來的屬性與屬性值匀哄。需要注意如果defineProps接受了某一個(gè)屬性,useAttrs方法返回的對象身上就沒有相應(yīng)屬性與屬性值松申。

<script setup lang="ts">
import {useAttrs} from 'vue';
let $attrs = useAttrs();
</script>

1.6ref與$parent

ref,提及到ref可能會想到它可以獲取元素的DOM或者獲取子組件實(shí)例的VC检眯。既然可以在父組件內(nèi)部通過ref獲取子組件實(shí)例VC,那么子組件內(nèi)部的方法與響應(yīng)式數(shù)據(jù)父組件可以使用的涩笤。

比如:在父組件掛載完畢獲取組件實(shí)例

父組件內(nèi)部代碼:

<template>
  <div>
    <h1>ref與$parent</h1>
    <Son ref="son"></Son>
  </div>
</template>
<script setup lang="ts">
import Son from "./Son.vue";
import { onMounted, ref } from "vue";
const son = ref();
onMounted(() => {
  console.log(son.value);
});
</script>

但是需要注意嚼吞,如果想讓父組件獲取子組件的數(shù)據(jù)或者方法需要通過defineExpose對外暴露,因?yàn)関ue3中組件內(nèi)部的數(shù)據(jù)對外“關(guān)閉的”,外部不能訪問

<script setup lang="ts">
import { ref } from "vue";
//數(shù)據(jù)
let money = ref(1000);
//方法
const handler = ()=>{
}
defineExpose({
  money,
   handler
})
</script>

$parent可以獲取某一個(gè)組件的父組件實(shí)例VC,因此可以使用父組件內(nèi)部的數(shù)據(jù)與方法蹬碧。必須子組件內(nèi)部擁有一個(gè)按鈕點(diǎn)擊時(shí)候獲取父組件實(shí)例舱禽,當(dāng)然父組件的數(shù)據(jù)與方法需要通過defineExpose方法對外暴露

<button @click="handler($parent)">點(diǎn)擊我獲取父組件實(shí)例</button>

1.7provide與inject

provide[提供]

inject[注入]

vue3提供兩個(gè)方法provide與inject,可以實(shí)現(xiàn)隔輩組件傳遞參數(shù)

組件組件提供數(shù)據(jù):

provide方法用于提供數(shù)據(jù),此方法執(zhí)需要傳遞兩個(gè)參數(shù),分別提供數(shù)據(jù)的key與提供數(shù)據(jù)value

<script setup lang="ts">
import {provide} from 'vue'
provide('token','admin_token');
</script>

后代組件可以通過inject方法獲取數(shù)據(jù),通過key獲取存儲的數(shù)值

<script setup lang="ts">
import {inject} from 'vue'
let token = inject('token');
</script>

1.8pinia

pinia官網(wǎng):https://pinia.web3doc.top/

pinia也是集中式管理狀態(tài)容器,類似于vuex恩沽。但是核心概念沒有mutation誊稚、modules,使用方式參照官網(wǎng)

1.9slot

插槽:默認(rèn)插槽、具名插槽罗心、作用域插槽可以實(shí)現(xiàn)父子組件通信.

默認(rèn)插槽:

在子組件內(nèi)部的模板中書寫slot全局組件標(biāo)簽

<template>
  <div>
    <slot></slot>
  </div>
</template>
<script setup lang="ts">
</script>
<style scoped>
</style>

在父組件內(nèi)部提供結(jié)構(gòu):Todo即為子組件,在父組件內(nèi)部使用的時(shí)候里伯,在雙標(biāo)簽內(nèi)部書寫結(jié)構(gòu)傳遞給子組件

注意開發(fā)項(xiàng)目的時(shí)候默認(rèn)插槽一般只有一個(gè)

<Todo>
  <h1>我是默認(rèn)插槽填充的結(jié)構(gòu)</h1>
</Todo>

具名插槽:

顧名思義,此插槽帶有名字在組件內(nèi)部留多個(gè)指定名字的插槽渤闷。

下面是一個(gè)子組件內(nèi)部,模板中留兩個(gè)插槽

<template>
  <div>
    <h1>todo</h1>
    <slot name="a"></slot>
    <slot name="b"></slot>
  </div>
</template>
<script setup lang="ts">
</script>

<style scoped>
</style>

父組件內(nèi)部向指定的具名插槽傳遞結(jié)構(gòu)疾瓮。需要注意v-slot:可以替換為#

<template>
  <div>
    <h1>slot</h1>
    <Todo>
      <template v-slot:a>  //可以用#a替換
        <div>填入組件A部分的結(jié)構(gòu)</div>
      </template>
      <template v-slot:b>//可以用#b替換
        <div>填入組件B部分的結(jié)構(gòu)</div>
      </template>
    </Todo>
  </div>
</template>

<script setup lang="ts">
import Todo from "./Todo.vue";
</script>
<style scoped>
</style>

作用域插槽

作用域插槽:可以理解為,子組件數(shù)據(jù)由父組件提供飒箭,但是子組件內(nèi)部決定不了自身結(jié)構(gòu)與外觀(樣式)

子組件Todo代碼如下:

<template>
  <div>
    <h1>todo</h1>
    <ul>
     <!--組件內(nèi)部遍歷數(shù)組-->
      <li v-for="(item,index) in todos" :key="item.id">
         <!--作用域插槽將數(shù)據(jù)回傳給父組件-->
         <slot :$row="item" :$index="index"></slot>
      </li>
    </ul>
  </div>
</template>
<script setup lang="ts">
defineProps(['todos']);//接受父組件傳遞過來的數(shù)據(jù)
</script>
<style scoped>
</style>

父組件內(nèi)部代碼如下:

<template>
  <div>
    <h1>slot</h1>
    <Todo :todos="todos">
      <template v-slot="{$row,$index}">
         <!--父組件決定子組件的結(jié)構(gòu)與外觀-->
         <span :style="{color:$row.done?'green':'red'}">{{$row.title}}</span>
      </template>
    </Todo>
  </div>
</template>

<script setup lang="ts">
import Todo from "./Todo.vue";
import { ref } from "vue";
//父組件內(nèi)部數(shù)據(jù)
let todos = ref([
  { id: 1, title: "吃飯", done: true },
  { id: 2, title: "睡覺", done: false },
  { id: 3, title: "打豆豆", done: true },
]);
</script>
<style scoped>
</style>

二狼电、搭建后臺管理系統(tǒng)模板

2.1項(xiàng)目初始化

今天來帶大家從0開始搭建一個(gè)vue3版本的后臺管理系統(tǒng)。一個(gè)項(xiàng)目要有統(tǒng)一的規(guī)范补憾,需要使用eslint+stylelint+prettier來對我們的代碼質(zhì)量做檢測和修復(fù)漫萄,需要使用husky來做commit攔截,需要使用commitlint來統(tǒng)一提交規(guī)范盈匾,需要使用preinstall來統(tǒng)一包管理工具腾务。

下面我們就用這一套規(guī)范來初始化我們的項(xiàng)目,集成一個(gè)規(guī)范的模版削饵。

2.1.1環(huán)境準(zhǔn)備

  • node v16.14.2
  • pnpm 8.0.0

2.1.2初始化項(xiàng)目

本項(xiàng)目使用vite進(jìn)行構(gòu)建岩瘦,vite官方中文文檔參考:cn.vitejs.dev/guide/

pnpm:performant npm ,意味“高性能的 npm”窿撬。pnpm由npm/yarn衍生而來启昧,解決了npm/yarn內(nèi)部潛在的bug,極大的優(yōu)化了性能劈伴,擴(kuò)展了使用場景密末。被譽(yù)為“最先進(jìn)的包管理工具”

pnpm安裝指令

npm i -g pnpm

項(xiàng)目初始化命令:

pnpm create vite

進(jìn)入到項(xiàng)目根目錄pnpm install安裝全部依賴.安裝完依賴運(yùn)行程序:pnpm run dev

運(yùn)行完畢項(xiàng)目跑在http://127.0.0.1:5173/,可以訪問你得項(xiàng)目啦

2.2項(xiàng)目配置

一、eslint配置

eslint中文官網(wǎng):http://eslint.cn/

ESLint最初是由Nicholas C. Zakas 于2013年6月創(chuàng)建的開源項(xiàng)目。它的目標(biāo)是提供一個(gè)插件化的javascript代碼檢測工具

首先安裝eslint

pnpm i eslint -D

生成配置文件:.eslint.cjs

npx eslint --init

.eslint.cjs配置文件

module.exports = {
   //運(yùn)行環(huán)境
    "env": { 
        "browser": true,//瀏覽器端
        "es2021": true,//es2021
    },
    //規(guī)則繼承
    "extends": [ 
       //全部規(guī)則默認(rèn)是關(guān)閉的,這個(gè)配置項(xiàng)開啟推薦規(guī)則,推薦規(guī)則參照文檔
       //比如:函數(shù)不能重名严里、對象不能出現(xiàn)重復(fù)key
        "eslint:recommended",
        //vue3語法規(guī)則
        "plugin:vue/vue3-essential",
        //ts語法規(guī)則
        "plugin:@typescript-eslint/recommended"
    ],
    //要為特定類型的文件指定處理器
    "overrides": [
    ],
    //指定解析器:解析器
    //Esprima 默認(rèn)解析器
    //Babel-ESLint babel解析器
    //@typescript-eslint/parser ts解析器
    "parser": "@typescript-eslint/parser",
    //指定解析器選項(xiàng)
    "parserOptions": {
        "ecmaVersion": "latest",//校驗(yàn)ECMA最新版本
        "sourceType": "module"http://設(shè)置為"script"(默認(rèn))新啼,或者"module"代碼在ECMAScript模塊中
    },
    //ESLint支持使用第三方插件。在使用插件之前刹碾,您必須使用npm安裝它
    //該eslint-plugin-前綴可以從插件名稱被省略
    "plugins": [
        "vue",
        "@typescript-eslint"
    ],
    //eslint規(guī)則
    "rules": {
    }
}
1.1vue3環(huán)境代碼校驗(yàn)插件
# 讓所有與prettier規(guī)則存在沖突的Eslint rules失效燥撞,并使用prettier進(jìn)行代碼檢查
"eslint-config-prettier": "^8.6.0",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-node": "^11.1.0",
# 運(yùn)行更漂亮的Eslint,使prettier規(guī)則優(yōu)先級更高迷帜,Eslint優(yōu)先級低
"eslint-plugin-prettier": "^4.2.1",
# vue.js的Eslint插件(查找vue語法錯誤物舒,發(fā)現(xiàn)錯誤指令,查找違規(guī)風(fēng)格指南
"eslint-plugin-vue": "^9.9.0",
# 該解析器允許使用Eslint校驗(yàn)所有babel code
"@babel/eslint-parser": "^7.19.1",

安裝指令

pnpm install -D eslint-plugin-import eslint-plugin-vue eslint-plugin-node eslint-plugin-prettier eslint-config-prettier eslint-plugin-node @babel/eslint-parser
1.2修改.eslintrc.cjs配置文件
// @see https://eslint.bootcss.com/docs/rules/

module.exports = {
  env: {
    browser: true,
    es2021: true,
    node: true,
    jest: true,
  },
  /* 指定如何解析語法 */
  parser: 'vue-eslint-parser',
  /** 優(yōu)先級低于 parse 的語法解析配置 */
  parserOptions: {
    ecmaVersion: 'latest',
    sourceType: 'module',
    parser: '@typescript-eslint/parser',
    jsxPragma: 'React',
    ecmaFeatures: {
      jsx: true,
    },
  },
  /* 繼承已有的規(guī)則 */
  extends: [
    'eslint:recommended',
    'plugin:vue/vue3-essential',
    'plugin:@typescript-eslint/recommended',
    'plugin:prettier/recommended',
  ],
  plugins: ['vue', '@typescript-eslint'],
  /*
   * "off" 或 0    ==>  關(guān)閉規(guī)則
   * "warn" 或 1   ==>  打開的規(guī)則作為警告(不影響代碼執(zhí)行)
   * "error" 或 2  ==>  規(guī)則作為一個(gè)錯誤(代碼不能執(zhí)行戏锹,界面報(bào)錯)
   */
  rules: {
    // eslint(https://eslint.bootcss.com/docs/rules/)
    'no-var': 'error', // 要求使用 let 或 const 而不是 var
    'no-multiple-empty-lines': ['warn', { max: 1 }], // 不允許多個(gè)空行
    'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
    'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
    'no-unexpected-multiline': 'error', // 禁止空余的多行
    'no-useless-escape': 'off', // 禁止不必要的轉(zhuǎn)義字符

    // typeScript (https://typescript-eslint.io/rules)
    '@typescript-eslint/no-unused-vars': 'error', // 禁止定義未使用的變量
    '@typescript-eslint/prefer-ts-expect-error': 'error', // 禁止使用 @ts-ignore
    '@typescript-eslint/no-explicit-any': 'off', // 禁止使用 any 類型
    '@typescript-eslint/no-non-null-assertion': 'off',
    '@typescript-eslint/no-namespace': 'off', // 禁止使用自定義 TypeScript 模塊和命名空間冠胯。
    '@typescript-eslint/semi': 'off',

    // eslint-plugin-vue (https://eslint.vuejs.org/rules/)
    'vue/multi-word-component-names': 'off', // 要求組件名稱始終為 “-” 鏈接的單詞
    'vue/script-setup-uses-vars': 'error', // 防止<script setup>使用的變量<template>被標(biāo)記為未使用
    'vue/no-mutating-props': 'off', // 不允許組件 prop的改變
    'vue/attribute-hyphenation': 'off', // 對模板中的自定義組件強(qiáng)制執(zhí)行屬性命名樣式
  },
}

1.3.eslintignore忽略文件
# npm包
/node_modules
package-lock.json
 
# build產(chǎn)物
/dist
/types
 
# eslint
.eslintcache
 
# jest
/coverage
 
# docs api文檔
/docs
 
# webpack 配置
/scripts
 
#webstorm
.idea
  # 自動引入生成文件
  auto-imports.d.ts
  components.d.ts
1.4運(yùn)行腳本

package.json新增兩個(gè)運(yùn)行腳本

"scripts": {
    "lint": "eslint src",
    "fix": "eslint src --fix",
}

二、配置prettier

有了eslint景用,為什么還要有prettier涵叮?eslint針對的是javascript,他是一個(gè)檢測工具伞插,包含js語法以及少部分格式問題,在eslint看來盾碗,語法對了就能保證代碼正常運(yùn)行媚污,格式問題屬于其次;

而prettier屬于格式化工具廷雅,它看不慣格式不統(tǒng)一耗美,所以它就把eslint沒干好的事接著干,另外航缀,prettier支持

包含js在內(nèi)的多種語言商架。

總結(jié)起來,eslint和prettier這倆兄弟一個(gè)保證js代碼質(zhì)量芥玉,一個(gè)保證代碼美觀蛇摸。

2.1安裝依賴包
pnpm install -D eslint-plugin-prettier prettier eslint-config-prettier
2.2.prettierrc.json添加規(guī)則
{
  "singleQuote": true,
  "semi": false,
  "bracketSpacing": true,
  "htmlWhitespaceSensitivity": "ignore",
  "endOfLine": "auto",
  "trailingComma": "all",
  "tabWidth": 2
}
2.3.prettierignore忽略文件
/dist/*
/html/*
.local
/node_modules/**
**/*.svg
**/*.sh
/public/*

通過pnpm run lint去檢測語法,如果出現(xiàn)不規(guī)范格式,通過pnpm run fix 修改

三灿巧、配置stylelint

stylelint為css的lint工具赶袄。可格式化css代碼抠藕,檢查css語法錯誤與不合理的寫法饿肺,指定css書寫順序等。

我們的項(xiàng)目中使用scss作為預(yù)處理器盾似,安裝以下依賴:

pnpm add sass sass-loader stylelint postcss postcss-scss postcss-html stylelint-config-prettier stylelint-config-recess-order stylelint-config-recommended-scss stylelint-config-standard stylelint-config-standard-vue stylelint-scss stylelint-order stylelint-config-standard-scss -D
3.1.stylelintrc.cjs配置文件

官網(wǎng):https://stylelint.bootcss.com/

// @see https://stylelint.bootcss.com/

module.exports = {
  extends: [
    'stylelint-config-standard', // 配置stylelint拓展插件
    'stylelint-config-html/vue', // 配置 vue 中 template 樣式格式化
    'stylelint-config-standard-scss', // 配置stylelint scss插件
    'stylelint-config-recommended-vue/scss', // 配置 vue 中 scss 樣式格式化
    'stylelint-config-recess-order', // 配置stylelint css屬性書寫順序插件,
    'stylelint-config-prettier', // 配置stylelint和prettier兼容
  ],
  overrides: [
    {
      files: ['**/*.(scss|css|vue|html)'],
      customSyntax: 'postcss-scss',
    },
    {
      files: ['**/*.(html|vue)'],
      customSyntax: 'postcss-html',
    },
  ],
  ignoreFiles: [
    '**/*.js',
    '**/*.jsx',
    '**/*.tsx',
    '**/*.ts',
    '**/*.json',
    '**/*.md',
    '**/*.yaml',
  ],
  /**
   * null  => 關(guān)閉該規(guī)則
   * always => 必須
   */
  rules: {
    'value-keyword-case': null, // 在 css 中使用 v-bind敬辣,不報(bào)錯
    'no-descending-specificity': null, // 禁止在具有較高優(yōu)先級的選擇器后出現(xiàn)被其覆蓋的較低優(yōu)先級的選擇器
    'function-url-quotes': 'always', // 要求或禁止 URL 的引號 "always(必須加上引號)"|"never(沒有引號)"
    'no-empty-source': null, // 關(guān)閉禁止空源碼
    'selector-class-pattern': null, // 關(guān)閉強(qiáng)制選擇器類名的格式
    'property-no-unknown': null, // 禁止未知的屬性(true 為不允許)
    'block-opening-brace-space-before': 'always', //大括號之前必須有一個(gè)空格或不能有空白符
    'value-no-vendor-prefix': null, // 關(guān)閉 屬性值前綴 --webkit-box
    'property-no-vendor-prefix': null, // 關(guān)閉 屬性前綴 -webkit-mask
    'selector-pseudo-class-no-unknown': [
      // 不允許未知的選擇器
      true,
      {
        ignorePseudoClasses: ['global', 'v-deep', 'deep'], // 忽略屬性,修改element默認(rèn)樣式的時(shí)候能使用到
      },
    ],
  },
}
3.2.stylelintignore忽略文件
/node_modules/*
/dist/*
/html/*
/public/*
3.3運(yùn)行腳本
"scripts": {
    "lint:style": "stylelint src/**/*.{css,scss,vue} --cache --fix"
}

最后配置統(tǒng)一的prettier來格式化我們的js和css,html代碼

 "scripts": {
    "dev": "vite --open",
    "build": "vue-tsc && vite build",
    "preview": "vite preview",
    "lint": "eslint src",
    "fix": "eslint src --fix",
    "format": "prettier --write \"./**/*.{html,vue,ts,js,json,md}\"",
    "lint:eslint": "eslint src/**/*.{ts,vue} --cache --fix",
    "lint:style": "stylelint src/**/*.{css,scss,vue} --cache --fix"
  },

當(dāng)我們運(yùn)行pnpm run format的時(shí)候溉跃,會把代碼直接格式化

四汰聋、配置husky

在上面我們已經(jīng)集成好了我們代碼校驗(yàn)工具,但是需要每次手動的去執(zhí)行命令才會格式化我們的代碼喊积。如果有人沒有格式化就提交了遠(yuǎn)程倉庫中烹困,那這個(gè)規(guī)范就沒什么用。所以我們需要強(qiáng)制讓開發(fā)人員按照代碼規(guī)范來提交乾吻。

要做到這件事情髓梅,就需要利用husky在代碼提交之前觸發(fā)git hook(git在客戶端的鉤子),然后執(zhí)行pnpm run format來自動的格式化我們的代碼绎签。

安裝husky

pnpm install -D husky

執(zhí)行

npx husky-init

會在根目錄下生成個(gè)一個(gè).husky目錄枯饿,在這個(gè)目錄下面會有一個(gè)pre-commit文件,這個(gè)文件里面的命令在我們執(zhí)行commit的時(shí)候就會執(zhí)行

.husky/pre-commit文件添加如下命令:

#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
pnpm run format

當(dāng)我們對代碼進(jìn)行commit操作的時(shí)候诡必,就會執(zhí)行命令奢方,對代碼進(jìn)行格式化,然后再提交爸舒。

五蟋字、配置commitlint

對于我們的commit信息,也是有統(tǒng)一規(guī)范的扭勉,不能隨便寫,要讓每個(gè)人都按照統(tǒng)一的標(biāo)準(zhǔn)來執(zhí)行鹊奖,我們可以利用commitlint來實(shí)現(xiàn)。

安裝包

pnpm add @commitlint/config-conventional @commitlint/cli -D

添加配置文件涂炎,新建commitlint.config.cjs(注意是cjs)忠聚,然后添加下面的代碼:

module.exports = {
  extends: ['@commitlint/config-conventional'],
  // 校驗(yàn)規(guī)則
  rules: {
    'type-enum': [
      2,
      'always',
      [
        'feat',
        'fix',
        'docs',
        'style',
        'refactor',
        'perf',
        'test',
        'chore',
        'revert',
        'build',
      ],
    ],
    'type-case': [0],
    'type-empty': [0],
    'scope-empty': [0],
    'scope-case': [0],
    'subject-full-stop': [0, 'never'],
    'subject-case': [0, 'never'],
    'header-max-length': [0, 'always', 72],
  },
}

package.json中配置scripts命令

# 在scrips中添加下面的代碼
{
"scripts": {
    "commitlint": "commitlint --config commitlint.config.cjs -e -V"
  },
}

配置結(jié)束,現(xiàn)在當(dāng)我們填寫commit信息的時(shí)候唱捣,前面就需要帶著下面的subject

'feat',//新特性两蟀、新功能
'fix',//修改bug
'docs',//文檔修改
'style',//代碼格式修改, 注意不是 css 修改
'refactor',//代碼重構(gòu)
'perf',//優(yōu)化相關(guān),比如提升性能震缭、體驗(yàn)
'test',//測試用例修改
'chore',//其他修改, 比如改變構(gòu)建流程赂毯、或者增加依賴庫、工具等
'revert',//回滾到上一個(gè)版本
'build',//編譯相關(guān)的修改蛀序,例如發(fā)布版本欢瞪、對項(xiàng)目構(gòu)建或者依賴的改動

配置husky

npx husky add .husky/commit-msg 

在生成的commit-msg文件中添加下面的命令

#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
pnpm commitlint

當(dāng)我們 commit 提交信息時(shí),就不能再隨意寫了徐裸,必須是 git commit -m 'fix: xxx' 符合類型的才可以遣鼓,需要注意的是類型的后面需要用英文的 :,并且冒號后面是需要空一格的重贺,這個(gè)是不能省略的

六骑祟、強(qiáng)制使用pnpm包管理器工具

團(tuán)隊(duì)開發(fā)項(xiàng)目的時(shí)候回懦,需要統(tǒng)一包管理器工具,因?yàn)椴煌芾砥鞴ぞ呦螺d同一個(gè)依賴,可能版本不一樣,

導(dǎo)致項(xiàng)目出現(xiàn)bug問題,因此包管理器工具需要統(tǒng)一管理!4纹蟆怯晕!

在根目錄創(chuàng)建scritps/preinstall.js文件,添加下面的內(nèi)容

if (!/pnpm/.test(process.env.npm_execpath || '')) {
  console.warn(
    `\u001b[33mThis repository must using pnpm as the package manager ` +
    ` for scripts to work properly.\u001b[39m\n`,
  )
  process.exit(1)
}

配置命令

"scripts": {
    "preinstall": "node ./scripts/preinstall.js"
}

當(dāng)我們使用npm或者yarn來安裝包的時(shí)候缸棵,就會報(bào)錯了舟茶。原理就是在install的時(shí)候會觸發(fā)preinstall(npm提供的生命周期鉤子)這個(gè)文件里面的代碼。

三堵第、項(xiàng)目集成

3.1集成element-plus

硅谷甄選運(yùn)營平臺,UI組件庫采用的element-plus吧凉,因此需要集成element-plus插件!Lぶ尽阀捅!

官網(wǎng)地址:https://element-plus.gitee.io/zh-CN/

pnpm install element-plus @element-plus/icons-vue

入口文件main.ts全局安裝element-plus,element-plus默認(rèn)支持語言英語設(shè)置為中文

import ElementPlus from 'element-plus';
import 'element-plus/dist/index.css'
//@ts-ignore忽略當(dāng)前文件ts類型的檢測否則有紅色提示(打包會失敗)
import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
app.use(ElementPlus, {
    locale: zhCn
})

Element Plus全局組件類型聲明

// tsconfig.json
{
  "compilerOptions": {
    // ...
    "types": ["element-plus/global"]
  }
}

配置完畢可以測試element-plus組件與圖標(biāo)的使用.

3.2src別名的配置

在開發(fā)項(xiàng)目的時(shí)候文件與文件關(guān)系可能很復(fù)雜,因此我們需要給src文件夾配置一個(gè)別名U胗唷K潜伞!

// vite.config.ts
import {defineConfig} from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'
export default defineConfig({
    plugins: [vue()],
    resolve: {
        alias: {
            "@": path.resolve("./src") // 相對路徑別名配置圆雁,使用 @ 代替 src
        }
    }
})

TypeScript 編譯配置

// tsconfig.app.json
{
  "compilerOptions": {
    "baseUrl": "./", // 解析非相對模塊的基地址忍级,默認(rèn)是當(dāng)前目錄
    "paths": { //路徑映射,相對于baseUrl
      "@/*": ["src/*"] 
    }
  }
}

3.3環(huán)境變量的配置

項(xiàng)目開發(fā)過程中摸柄,至少會經(jīng)歷開發(fā)環(huán)境颤练、測試環(huán)境和生產(chǎn)環(huán)境(即正式環(huán)境)三個(gè)階段。不同階段請求的狀態(tài)(如接口地址等)不盡相同驱负,若手動切換接口地址是相當(dāng)繁瑣且易出錯的。于是環(huán)境變量配置的需求就應(yīng)運(yùn)而生患雇,我們只需做簡單的配置跃脊,把環(huán)境狀態(tài)切換的工作交給代碼。

開發(fā)環(huán)境(development)
顧名思義苛吱,開發(fā)使用的環(huán)境酪术,每位開發(fā)人員在自己的dev分支上干活,開發(fā)到一定程度翠储,同事會合并代碼绘雁,進(jìn)行聯(lián)調(diào)。

測試環(huán)境(testing)
測試同事干活的環(huán)境啦援所,一般會由測試同事自己來部署庐舟,然后在此環(huán)境進(jìn)行測試

生產(chǎn)環(huán)境(production)
生產(chǎn)環(huán)境是指正式提供對外服務(wù)的,一般會關(guān)掉錯誤報(bào)告住拭,打開錯誤日志挪略。(正式提供給客戶使用的環(huán)境历帚。)

注意:一般情況下,一個(gè)環(huán)境對應(yīng)一臺服務(wù)器,也有的公司開發(fā)與測試環(huán)境是一臺服務(wù)器8苡椤M炖巍!

項(xiàng)目根目錄分別添加 開發(fā)摊求、生產(chǎn)和測試環(huán)境的文件!

.env.development
.env.production
.env.test

文件內(nèi)容

# 變量必須以 VITE_ 為前綴才能暴露給外部讀取
NODE_ENV = 'development'
VITE_APP_TITLE = '硅谷甄選運(yùn)營平臺'
VITE_APP_BASE_API = '/dev-api'
NODE_ENV = 'production'
VITE_APP_TITLE = '硅谷甄選運(yùn)營平臺'
VITE_APP_BASE_API = '/prod-api'
# 變量必須以 VITE_ 為前綴才能暴露給外部讀取
NODE_ENV = 'test'
VITE_APP_TITLE = '硅谷甄選運(yùn)營平臺'
VITE_APP_BASE_API = '/test-api'

配置運(yùn)行命令:package.json

 "scripts": {
    "dev": "vite --open",
    "build:test": "vue-tsc && vite build --mode test",
    "build:pro": "vue-tsc && vite build --mode production",
    "preview": "vite preview"
  },

通過import.meta.env獲取環(huán)境變量

3.4SVG圖標(biāo)配置

在開發(fā)項(xiàng)目的時(shí)候經(jīng)常會用到svg矢量圖,而且我們使用SVG以后禽拔,頁面上加載的不再是圖片資源,

這對頁面性能來說是個(gè)很大的提升,而且我們SVG文件比img要小的很多室叉,放在項(xiàng)目中幾乎不占用資源睹栖。

安裝SVG依賴插件

pnpm install vite-plugin-svg-icons -D

vite.config.ts中配置插件

import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
import path from 'path'
export default () => {
  return {
    plugins: [
      createSvgIconsPlugin({
        // Specify the icon folder to be cached
        iconDirs: [path.resolve(process.cwd(), 'src/assets/icons')],
        // Specify symbolId format
        symbolId: 'icon-[dir]-[name]',
      }),
    ],
  }
}

入口文件導(dǎo)入

import 'virtual:svg-icons-register'

3.4.1svg封裝為全局組件

因?yàn)轫?xiàng)目很多模塊需要使用圖標(biāo),因此把它封裝為全局組件!L荨磨淌!

在src/components目錄下創(chuàng)建一個(gè)SvgIcon組件:代表如下

<template>
  <div>
    <svg :style="{ width: width, height: height }">
      <use :xlink:href="prefix + name" :fill="color"></use>
    </svg>
  </div>
</template>

<script setup lang="ts">
defineProps({
  //xlink:href屬性值的前綴
  prefix: {
    type: String,
    default: '#icon-'
  },
  //svg矢量圖的名字
  name: String,
  //svg圖標(biāo)的顏色
  color: {
    type: String,
    default: ""
  },
  //svg寬度
  width: {
    type: String,
    default: '16px'
  },
  //svg高度
  height: {
    type: String,
    default: '16px'
  }

})
</script>
<style scoped></style>

在src文件夾目錄下創(chuàng)建一個(gè)index.ts文件:用于注冊components文件夾內(nèi)部全部全局組件!T湓ā梁只!

import SvgIcon from './SvgIcon/index.vue';
import type { App, Component } from 'vue';
const components: { [name: string]: Component } = { SvgIcon };
export default {
    install(app: App) {
        Object.keys(components).forEach((key: string) => {
            app.component(key, components[key]);
        })
    }
}

在入口文件引入src/index.ts文件,通過app.use方法安裝自定義插件

import gloablComponent from './components/index';
app.use(gloablComponent);

3.5集成sass

我們目前在組件內(nèi)部已經(jīng)可以使用scss樣式,因?yàn)樵谂渲胹tyleLint工具的時(shí)候,項(xiàng)目當(dāng)中已經(jīng)安裝過sass sass-loader,因此我們再組件內(nèi)可以使用scss語法0T唷L侣唷!需要加上lang="scss"

<style scoped lang="scss"></style>

接下來我們?yōu)轫?xiàng)目添加一些全局的樣式

在src/styles目錄下創(chuàng)建一個(gè)index.scss文件彩掐,當(dāng)然項(xiàng)目中需要用到清除默認(rèn)樣式构舟,因此在index.scss引入reset.scss

@import reset.scss

在入口文件引入

import '@/styles'

但是你會發(fā)現(xiàn)在src/styles/index.scss全局樣式文件中沒有辦法使用變量.因此需要給項(xiàng)目中引入全局變量.

在style/variable.scss創(chuàng)建一個(gè)variable.scss文件!

在vite.config.ts文件配置如下:

export default defineConfig((config) => {
    css: {
      preprocessorOptions: {
        scss: {
          javascriptEnabled: true,
          additionalData: '@import "./src/styles/variable.scss";',
        },
      },
    },
    }
}

@import "./src/styles/variable.less";后面的;不要忘記堵幽,不然會報(bào)錯!

配置完畢你會發(fā)現(xiàn)scss提供這些全局變量可以在組件樣式中使用了9烦!朴下!

3.6mock數(shù)據(jù)

安裝依賴:https://www.npmjs.com/package/vite-plugin-mock

pnpm install -D vite-plugin-mock mockjs

在 vite.config.js 配置文件啟用插件努咐。

import { UserConfigExport, ConfigEnv } from 'vite'
import { viteMockServe } from 'vite-plugin-mock'
import vue from '@vitejs/plugin-vue'
export default ({ command })=> {
  return {
    plugins: [
      vue(),
      viteMockServe({
        localEnabled: command === 'serve',
      }),
    ],
  }
}

在根目錄創(chuàng)建mock文件夾:去創(chuàng)建我們需要mock數(shù)據(jù)與接口!E闺省渗稍!

在mock文件夾內(nèi)部創(chuàng)建一個(gè)user.ts文件

//用戶信息數(shù)據(jù)
function createUserList() {
    return [
        {
            userId: 1,
            avatar:
                'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
            username: 'admin',
            password: '111111',
            desc: '平臺管理員',
            roles: ['平臺管理員'],
            buttons: ['cuser.detail'],
            routes: ['home'],
            token: 'Admin Token',
        },
        {
            userId: 2,
            avatar:
                'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
            username: 'system',
            password: '111111',
            desc: '系統(tǒng)管理員',
            roles: ['系統(tǒng)管理員'],
            buttons: ['cuser.detail', 'cuser.user'],
            routes: ['home'],
            token: 'System Token',
        },
    ]
}

export default [
    // 用戶登錄接口
    {
        url: '/api/user/login',//請求地址
        method: 'post',//請求方式
        response: ({ body }) => {
            //獲取請求體攜帶過來的用戶名與密碼
            const { username, password } = body;
            //調(diào)用獲取用戶信息函數(shù),用于判斷是否有此用戶
            const checkUser = createUserList().find(
                (item) => item.username === username && item.password === password,
            )
            //沒有用戶返回失敗信息
            if (!checkUser) {
                return { code: 201, data: { message: '賬號或者密碼不正確' } }
            }
            //如果有返回成功信息
            const { token } = checkUser
            return { code: 200, data: { token } }
        },
    },
    // 獲取用戶信息
    {
        url: '/api/user/info',
        method: 'get',
        response: (request) => {
            //獲取請求頭攜帶token
            const token = request.headers.token;
            //查看用戶信息是否包含有次token用戶
            const checkUser = createUserList().find((item) => item.token === token)
            //沒有返回失敗的信息
            if (!checkUser) {
                return { code: 201, data: { message: '獲取用戶信息失敗' } }
            }
            //如果有返回成功信息
            return { code: 200, data: {checkUser} }
        },
    },
]

安裝axios

pnpm install axios

最后通過axios測試接口!M爬摹竿屹!

3.7axios二次封裝

在開發(fā)項(xiàng)目的時(shí)候避免不了與后端進(jìn)行交互,因此我們需要使用axios插件實(shí)現(xiàn)發(fā)送網(wǎng)絡(luò)請求。在開發(fā)項(xiàng)目的時(shí)候

我們經(jīng)常會把a(bǔ)xios進(jìn)行二次封裝灸姊。

目的:

1:使用請求攔截器拱燃,可以在請求攔截器中處理一些業(yè)務(wù)(開始進(jìn)度條、請求頭攜帶公共參數(shù))

2:使用響應(yīng)攔截器厨钻,可以在響應(yīng)攔截器中處理一些業(yè)務(wù)(進(jìn)度條結(jié)束扼雏、簡化服務(wù)器返回的數(shù)據(jù)坚嗜、處理http網(wǎng)絡(luò)錯誤)

在根目錄下創(chuàng)建utils/request.ts

import axios from "axios";
import { ElMessage } from "element-plus";
//創(chuàng)建axios實(shí)例
let request = axios.create({
    baseURL: import.meta.env.VITE_APP_BASE_API,
    timeout: 5000
})
//請求攔截器
request.interceptors.request.use(config => {
    return config;
});
//響應(yīng)攔截器
request.interceptors.response.use((response) => {
    return response.data;
}, (error) => {
    //處理網(wǎng)絡(luò)錯誤
    let msg = '';
    let status = error.response.status;
    switch (status) {
        case 401:
            msg = "token過期";
            break;
        case 403:
            msg = '無權(quán)訪問';
            break;
        case 404:
            msg = "請求地址錯誤";
            break;
        case 500:
            msg = "服務(wù)器出現(xiàn)問題";
            break;
        default:
            msg = "無網(wǎng)絡(luò)";

    }
    ElMessage({
        type: 'error',
        message: msg
    })
    return Promise.reject(error);
});
export default request;

3.8API接口統(tǒng)一管理

在開發(fā)項(xiàng)目的時(shí)候,接口可能很多需要統(tǒng)一管理。在src目錄下去創(chuàng)建api文件夾去統(tǒng)一管理項(xiàng)目的接口诗充;

比如:下面方式

//統(tǒng)一管理咱們項(xiàng)目用戶相關(guān)的接口

import request from '@/utils/request'

import type {

 loginFormData,

 loginResponseData,

 userInfoReponseData,

} from './type'

//項(xiàng)目用戶相關(guān)的請求地址

enum API {

 LOGIN_URL = '/admin/acl/index/login',

 USERINFO_URL = '/admin/acl/index/info',

 LOGOUT_URL = '/admin/acl/index/logout',

}
//登錄接口
export const reqLogin = (data: loginFormData) =>
 request.post<any, loginResponseData>(API.LOGIN_URL, data)
//獲取用戶信息

export const reqUserInfo = () =>

 request.get<any, userInfoReponseData>(API.USERINFO_URL)

//退出登錄

export const reqLogout = () => request.post<any, any>(API.LOGOUT_URL)
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末苍蔬,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子蝴蜓,更是在濱河造成了極大的恐慌碟绑,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,657評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件茎匠,死亡現(xiàn)場離奇詭異格仲,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)诵冒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評論 3 394
  • 文/潘曉璐 我一進(jìn)店門凯肋,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人汽馋,你說我怎么就攤上這事侮东。” “怎么了豹芯?”我有些...
    開封第一講書人閱讀 164,057評論 0 354
  • 文/不壞的土叔 我叫張陵悄雅,是天一觀的道長。 經(jīng)常有香客問我铁蹈,道長宽闲,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,509評論 1 293
  • 正文 為了忘掉前任握牧,我火速辦了婚禮容诬,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘沿腰。我一直安慰自己放案,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,562評論 6 392
  • 文/花漫 我一把揭開白布矫俺。 她就那樣靜靜地躺著,像睡著了一般掸冤。 火紅的嫁衣襯著肌膚如雪厘托。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,443評論 1 302
  • 那天稿湿,我揣著相機(jī)與錄音铅匹,去河邊找鬼。 笑死饺藤,一個(gè)胖子當(dāng)著我的面吹牛包斑,可吹牛的內(nèi)容都是我干的流礁。 我是一名探鬼主播,決...
    沈念sama閱讀 40,251評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼罗丰,長吁一口氣:“原來是場噩夢啊……” “哼神帅!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起萌抵,我...
    開封第一講書人閱讀 39,129評論 0 276
  • 序言:老撾萬榮一對情侶失蹤找御,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后绍填,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體霎桅,經(jīng)...
    沈念sama閱讀 45,561評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,779評論 3 335
  • 正文 我和宋清朗相戀三年讨永,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了滔驶。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,902評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡卿闹,死狀恐怖揭糕,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情比原,我是刑警寧澤插佛,帶...
    沈念sama閱讀 35,621評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站量窘,受9級特大地震影響雇寇,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蚌铜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,220評論 3 328
  • 文/蒙蒙 一锨侯、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧冬殃,春花似錦囚痴、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至涣觉,卻和暖如春痴荐,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背官册。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評論 1 269
  • 我被黑心中介騙來泰國打工生兆, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人膝宁。 一個(gè)月前我還...
    沈念sama閱讀 48,025評論 2 370
  • 正文 我出身青樓鸦难,卻偏偏與公主長得像根吁,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子合蔽,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,843評論 2 354

推薦閱讀更多精彩內(nèi)容