Vue-Cli3 MPA

創(chuàng)建應用

$ node -v
v12.14.0

$ npm -v 
6.13.4

$ npm i -g @vue/cli

$ vue -V
@vue/cli 4.1.2

$ vue create stats

創(chuàng)建Vue項目選擇基礎預設,其中包括babel和eslint插件。

文件結(jié)構

文件 描述
public 靜態(tài)資源文件夾
src 項目源代碼包括目錄
src/config 應用配置文件夾,推薦使用JSON格式。
src/utils 工具類庫文件夾
src/assets 靜態(tài)資源文件夾
src/components 組件文件夾,根據(jù)單頁劃分模塊,一個單頁一個文件夾,保存單頁所使用的組件。
src/entry 多頁入口文件夾,主要是JS文件。
src/pages 多頁文件夾,主要是vue文件。
src/router 路由文件夾,每個單頁一個路由。

項目文件結(jié)構說明,為合理規(guī)劃文件組織結(jié)構。會將MPA應用中每個page的.html.js.vue三個文件分別劃分到publicsrc/entrysrc/pages文件夾下。

應用配置

創(chuàng)建vue核心配置

$ cd stats
$ vim vue.config.js

安裝組件

$ npm i -S path
const path = require("path");
const utils = require("./src/utils/utils");

//是否開發(fā)調(diào)試模式
const debug = process.env.NODE_ENV === "development" ? true : false;

module.exports = {
    publicPath:debug?"/":"",
    outputDir:"dist",
    assetsDir:"assets",
    filenameHashing:true,
    lintOnSave:!debug,
    runtimeCompiler:!debug,
    pages:utils.getPages(),
    configureWebpack:config=>{
        const extensions = [".js", ".json", ".vue", ".css"];
        const alias = {
            "@":path.join(__dirname, "src"),
            "src":path.join(__dirname, "../src"),
            "assets":path.join(__dirname, "../src/assets"),
            "components":path.join(__dirname, "../src/components")
        };
        config.resolve = {extensions, alias};
    }
};

pages選項

vue核心配置項中的pages選項為多頁應用MPA的配置位置,提取出來放到工具類庫utils/utils.js文件中。

pages配置是Object類型,默認值為undefined,在multi-page模式下構建應用。每個page對應一個JavaScript入口文件。

pages的值是一個對象,對象的key為page單頁入口名稱,value是一個指定entry、template、filename、title、chunks的對象。其中entry為必填且為字符串。

page選項

page選項 必填 描述
entry page入口JS文件路徑
template page模板文件路徑
filename 輸出到dist目錄中的文件名
title 頁面title標簽內(nèi)容
chunks page中包含的塊,默認會提取通用塊。

示例代碼

module.exports = {
  pages: {
    index: {
      // page 的入口
      entry: 'src/index/main.js',
      // 模板來源
      template: 'public/index.html',
      // 在 dist/index.html 的輸出
      filename: 'index.html',
      // 當使用 title 選項時,
      // template 中的 title 標簽需要是 <title><%= htmlWebpackPlugin.options.title %></title>
      title: 'Index Page',
      // 在這個頁面中包含的塊,默認情況下會包含
      // 提取出來的通用 chunk 和 vendor chunk。
      chunks: ['chunk-vendors', 'chunk-common', 'index']
    },
    // 當使用只有入口的字符串格式時,
    // 模板會被推導為 `public/subpage.html`
    // 并且如果找不到的話,就回退到 `public/index.html`。
    // 輸出文件名會被推導為 `subpage.html`。
    subpage: 'src/subpage/main.js'
  }
}

根據(jù)page選項,并配合項目組織結(jié)構,將每個pageentry入口文件都保存到src/entry文件夾下,入口文件均為JS文件,template模板文件均使用public/index.htmltitle標簽內(nèi)容每個頁面均不一樣,后續(xù)會進行處理,默認使用入口名稱。這些內(nèi)容均會在提取到工具類庫src/utils/utils.js文件的getPages()方法中。

工具類庫

創(chuàng)建工具類庫

$ vim src/utils/utils.js

安裝組件

$ npm i -S fs glob
const fs = require("fs");
const path = require("path");
const glob = require("glob");

const pagePath = path.resolve(__dirname, "..", "pages");
const entryPath = path.resolve(__dirname, "..", "entry");
const configPath = path.resolve(__dirname, "..", "config");

/*獲取配置*/
exports.config = (filename,field="")=>{
    const file = path.join(configPath, filename);
    let value = require(file);
    if(field!==""){
        value = value[field];
    }
    return value;
};

/*獲取多頁面配置選項*/
exports.getPages = ()=>{
    let pages = {};
    //獲取所有vue文件
    let files = glob.sync(`${pagePath}/*/*.vue`);
    if(files.length < 1){
        console.error("util getPages no file");
    }
    files.forEach(filepath=>{
        const extname = path.extname(filepath);
        const basename = path.basename(filepath, extname);
        //統(tǒng)一入口文件保存路徑
        const entry = path.join(entryPath, `${basename}.js`);//絕對路徑
        //自動生成入口文件
        const exists = fs.existsSync(entry);
        console.log(exists, entry);
        if(!exists){
            let code = `import Vue from 'vue';\n`;
            code += `import App from '${filepath}';\n`;
            code += `Vue.config.productionTip = false;\n`;
            code += `new Vue({render:h=>h(App)}).$mount('#${basename}');`;
            fs.writeFileSync(entry, code);
        }
        //頁面配置選項
        const template = "index.html";
        const filename = `${basename}.html`;
        const chunks = ['chunk-vendors', 'chunk-common', basename];
        const chunksSortMode = "manual";
        const minify = false;
        const inject = true;
        //自定義頁面數(shù)據(jù)
        const pageData = this.config("page", basename) || {};
        if(pageData.title === undefined){
            Object.assign(pageData, {title:basename});
        }
        if(pageData.idname === undefined){
            Object.assign(pageData, {idname:basename});
        }
        pages[basename] = {entry, template, filename, pageData, chunks, chunksSortMode, minify, inject};
    });
    return pages;
};

getPages()方法對vue.config.js中的pages參數(shù)進行提取并根據(jù)提前規(guī)劃好的結(jié)構進行組織文件,其中會判斷入口文件是否已經(jīng)存在,若不存在則會生成。

模板文件

$ vim public/index.html
<% const page = htmlWebpackPlugin.options.pageData; %>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
    <title><%= page.title %></title>
</head>
<body>
<noscript>
    <strong>很抱歉,如果沒有啟用javascript,vue-cli3無法正常工作。請啟用它以繼續(xù)。</strong>
</noscript>
<div id="app">
    <div id="<%= page.idname %>"></div>
</div>
</body>
</html>

頁面配置

$ vim src/config/page.json
{
  "index":{"title":"index page"}
}

單頁應用

多頁應用基礎結(jié)構搭建完畢后,接下來針對index單頁進行開發(fā)。每個單頁分為入口、路由、布局、模板、組件、樣式等一系列部分組成。

$ vim src/pages/index/index.vue
<template>
    <div class="page">
        <router-view></router-view>
    </div>
</template>

<script>
    export default {
        name: "index.vue"
    }
</script>

<style scoped>

</style>

作為index單頁,默認會加載頁面布局組件,創(chuàng)建.vue文件會自動生成基礎的入口文件。入口文件中會加載路由和UI組件。

入口文件

安裝組件

$ npm i -S vant less less-loader

項目使用vant作為UI組件,vant是有贊團隊基于有贊統(tǒng)一的規(guī)范實現(xiàn)的一個輕量、可靠的移動端Vue組件庫,用于移動端開發(fā)。由于vant使用less,因此需配置less和less-loader。

$ vim src/entry/index.js
import Vue from 'vue';
import Vant from  "vant";

import router from "../router/index.js";
import app from '../pages/index/index.vue';
import layout from "../components/index/layout.vue";
// import "vant/lib/index.less";

Vue.config.productionTip = false;
Vue.use(Vant);


new Vue({
    render:h=>h(app),
    router:router,
    components:{layout},
    template:"<layout/>"
}).$mount('#index');

路由文件

安裝組件

$ npm i -S vue-router
$ vim src/router/index.js
import Vue from "vue";
import Router from "vue-router";

import layout from "../components/index/layout.vue";

Vue.use(Router);

const routes = [
    {
        path:"/index/layout",
        name:"layout",
        meta:{title:"index layout", requireAuth:false},
        component:layout
    }
];
export default new Router({
    mode:"history",
    base:process.env.BASE_URL,
    routes
});

vant

Vant是由有贊前端團隊開發(fā)的一款輕量、可靠的移動端Vue組件庫。

安裝組件

$ npm i -S vant
$ yarn add vant

babel-plugin-import

引入組件

babel-plugin-import是一款babel插件,會在編譯過程中將import的寫法自動轉(zhuǎn)換為按需引入的方式。

使用vant組件可以一次性全局導入組件,但這種做法會帶來增加代碼包的體積,因此推薦使用babel-plugin-import進行按需加載,以節(jié)省資源。

$ npm i -D babel-plugin-import
$ npm i --save-dev babel-plugin-import

備注:若項目僅在開發(fā)環(huán)境下需要npm包而在上線后不需要,則是可使用--save-dev-D。若上線后需要使用依賴包則需使用--save-S

查看babel-plugin-import版本

$ npm view babel-plugin-import version
1.13.0
$ npm ls babel-plugin-import
sxyh_web_stats@0.1.0 D:\vue\workspace\sxyh_web_stats
`-- babel-plugin-import@1.13.0

查看babel版本

$ npm info babel version
6.23.0

配置插件

$ vim babel.config.js
module.exports = {
    presets: ['@vue/cli-plugin-babel/preset'],
    plugins: [
        ['import', {
            libraryName: 'vant',
            libraryDirectory: 'es',
            style: true
        }, 'vant']
    ]
};

配置按需引入后將不再允許直接導入所有組件,雖然Vant支持一次性導入所有組件,但直接導入所有組件會增加代碼包體積,因此并不推薦這種做法。

按需引入后,在vue文件中使用組件時,首先需要導入所需使用的組件,然后在Vue中進行注冊組件。注冊組件后,才能使用vant提供的組件標簽。

$ vim home.vue
<template>
    <div class="layout">
        <van-nav-bar title="排行榜" left-text="返回" right-text="按鈕" left-arrow @click-left="onClickLeft" @click-right="onClickRight"/>
        <van-image round width="5rem" height="5rem" src="https://img.yzcdn.cn/vant/cat.jpeg"/>
        <van-panel title="我的昵稱" desc="ID:123456" status="第10名"></van-panel>
        <van-list v-model="loading" :finished="finished" finished-text="沒有更多了" @load="onLoad">
            <van-cell v-for="item in list" :key="item" :title="item" />
        </van-list>
        <van-tabbar v-model="active">
            <van-tabbar-item icon="home-o">排行榜</van-tabbar-item>
            <van-tabbar-item icon="search">積分</van-tabbar-item>
            <van-tabbar-item icon="friends-o">茶館</van-tabbar-item>
            <van-tabbar-item icon="setting-o">分組</van-tabbar-item>
        </van-tabbar>
    </div>
</template>

<script>
    import {NavBar, Image, Panel, List, Cell, Tabbar, TabbarItem, Toast} from "vant";
    export default {
        name: "layout",
        data(){
            return {
                active:0,
                list:[],
                loading:false,
                finished:false
            };
        },
        //注冊組件
        components:{
            [NavBar.name]:NavBar,
            [Image.name]:Image,
            [Panel.name]:Panel,
            [List.name]:List,
            [Cell.name]:Cell,
            [Tabbar.name]:Tabbar,
            [TabbarItem.name]:TabbarItem,
        },
        methods:{
            goback(){
                this.$router.go(-1);
            },
            onClickLeft(){
                Toast("返回");
            },
            onClickRight(){
                Toast("按鈕");
            },
            onLoad(){
                setTimeout(()=>{
                    for(let i=0; i<10; i++){
                        this.list.push(this.list.length + 1);
                    }
                    this.loading = false;
                    if(this.list.length>=40){
                        this.finished = true;
                    }
                },1000);
            }
        }
    }
</script>

<style scoped>
</style>

postcss-px-to-viewport

移動端適配可采用viewport單位,由于viewport單位得到眾多瀏覽器的兼容,flexible的過渡方案可以放棄了。

viewport以vw和vh作為單位,以viewport為基準,其中1vw表示view width的1/100, 1vh表示 view height的1/100。

使用viewport單位需提前設置meta標簽中的viewport

<!-- 在 head 標簽中添加 meta 標簽,并設置 viewport-fit=cover 值 -->
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, viewport-fit=cover">

postcss-px-to-viewport 會將px單位自動轉(zhuǎn)化為視口單位vw、vh、vmin、vmax的PostCSS插件。

安裝組件

$ npm i -S postcss-loader postcss-px-to-viewport

@vue/cli3中由于postcss-px-to-viewport配置項中的exclude選項只能支持正則表達式,因此無法在package.json中進行配置,可配置在vue.config.js文件中。

$ vim vue.config.js
const pxtoviewport = require("postcss-px-to-viewport");
module.exports = {
    css:{
        loaderOptions:{
            postcss:{
                plugins:[
                    pxtoviewport({
                        unitToConvert:"px",
                        unitPrecision:3,
                        viewportWidth:750,
                        viewportUnit:"vw",
                        fontViewportUnit:"vw",
                        minPixelValue:1,
                        mediaQuery:false,
                        replace:true,
                        propList:["*"],
                        selectorBlackList:[],
                        exclude:/(\/|\\)(node_modules)(\/|\\)/
                        landscape:false,
                        landscapeUnit:"vh",
                        landscapeWidth:1334
                    })
                ]
            }
        }
    }
};
配置項 配置值 描述
unitToConvert "px" 需要轉(zhuǎn)換的單位,默認為"px"像素。
viewportWidth 750 設計稿視口寬度,配置后將根據(jù)視口做比例換算。
unitPrecison 2 轉(zhuǎn)化進度,轉(zhuǎn)換后保留小數(shù)位數(shù)。
propList [] 允許轉(zhuǎn)換為vw的屬性列表
viewportUnit "vw" 視口單位
fontViewportUnit "vw" 字體使用的視口單位
selectorBlackList [] 需忽略的CSS選擇器
minPixelValue 1 最小轉(zhuǎn)換數(shù)值
mediaQuery true 媒體查詢中的單位是否需要轉(zhuǎn)換
replace true 轉(zhuǎn)換后是否需要添加備用單位
exclude ["node_modules"] 需要忽略的文件夾
landscape false 是否添加根據(jù)landscopeWidth生成媒體查詢條件@media(orientation:landscape)
landscapeUnit "vh" 橫屏時使用的單位
landscapeWidth 1334 橫屏時使用的視口寬度

fastclick

移動端開發(fā)存在touchstart、touchmove、touchend、touchcancel等事件,而click點擊事件執(zhí)行時瀏覽器需要等待300毫秒,以判斷用戶是否再次點擊了屏幕。這就造成了很多問題,比如點擊穿透等。那么為什么click事件執(zhí)行時瀏覽器會等待300ms呢?

2007年蘋果為了解決iPhone Safari訪問PC頁面縮放的問題,提供了一個雙擊縮放功能。當用戶第一次觸摸屏幕時瀏覽器等待300ms才會判斷用戶是需要click點擊還是zoom縮放。這就造成用戶觸摸屏幕到click點擊事件觸發(fā)存在300ms的延遲。

隨著iPhone的成功,后續(xù)的無限瀏覽器復制了其大部分操作系統(tǒng),其中就包括雙擊縮放,這也成為主流瀏覽器的一個功能。雖然300ms延遲在平時瀏覽網(wǎng)頁時并不會帶來嚴重問題,但對于高性能的web app則是一個嚴重的問題,另外響應式設計的流行也讓雙擊縮放逐漸失去了用武之地。

一般而言,觸摸屏幕時間觸發(fā)流程是這樣的:

  1. touchstart
  2. touchmove
  3. touchend
  4. wait 300ms in case of another tap
  5. click

因為這300ms的存在,受到這個延遲影響的場景有:

  • JavaScript監(jiān)聽的click事件
  • 基于click事件交互的元素,比如鏈接、表單元素。

FastClick是FT Labs專門為解決移動端瀏覽器300ms點擊延遲問題所開發(fā)的輕量級庫。

使用FastClick時input文本框在iOS設備上點擊輸入時調(diào)取手機自帶鍵盤存在不靈敏,有時甚至無法調(diào)起的情況。而在Android上則完全沒有問題,這個原因是因為FastClick的點擊穿透所帶來的。

axios

axios是一個基于promise的HTTP庫,可用于瀏覽器和Node.js環(huán)境中。

axios主要特性

  • 從瀏覽器中創(chuàng)建XMLHttpRequests請求
  • 從Node.js中創(chuàng)建HTTP請求
  • 支持Promise API
  • 支持攔截請求和響應
  • 支持轉(zhuǎn)換請求數(shù)據(jù)和響應數(shù)據(jù)
  • 支持取消請求
  • 支持自動轉(zhuǎn)換JSON數(shù)據(jù)
  • 客戶端支持防御XSRF攻擊

vue cli 3配置axios插件

$ vue add axios
$ npm ls axios version
$ npm ls axios version
sxyh_web_stats@0.1.0 D:\vue\workspace\sxyh_web_stats
`-- axios@0.18.1

vue cli 3配置中設置代理

如果前端應用和端口API服務器沒有運行在同一個主機上,則需在開發(fā)環(huán)境下將API請求代理到API服務器,此時可通過vue.config.js中的devServer.proxy選項來配置。devServer.proxy使用http-proxy-middleware中間件。

http-proxy-middleware可用于將后臺請求轉(zhuǎn)發(fā)給其他服務器,比如在當前主機為http://127.0.0.1:3000,瀏覽器訪問當前主機的/api接口,請求的數(shù)據(jù)確在另一臺服務器 http://127.0.0.1:40 上。此時可通過在當前主機上設置代理,將請求轉(zhuǎn)發(fā)給數(shù)據(jù)所在的服務器上。

選項 描述
target 設置目標服務器的主機地址
changeOrigin 是否需要更改原始主機頭為目標URL
ws 是否代理websocket
pathRewrite 重寫目標URL路徑
router 重寫指定請求轉(zhuǎn)發(fā)目標
$ vim vue.config.js
module.exports = {
    //開發(fā)服務器
    devServer:{
        //設置代理
        proxy:{
            "/api":{
                target:"http://127.0.0.1:8080",
                ws:true,
                changeOrigin:true
            }
        }
    }
};

這里由于前端應用使用的是 http://127.0.0.1:8080地址,因此相當于訪問前端應用自身。

模擬數(shù)據(jù)

$ vim public/api/db.json

模擬數(shù)據(jù)為接口返回的數(shù)據(jù),為此需要提前統(tǒng)一規(guī)劃好數(shù)據(jù)格式。

{
  "code":200,
  "message":"success",
  "data":[
    "alice", "bob", "carl"
  ]
}

組件中使用axios

由于這里使用MPA多頁應用,針對每個單頁的入口文件需要單獨引入axios。

$ vim src/entry/index.js
import Vue from 'vue';
import Axios  from "axios";

import router from "../router/index.js";
import app from '../pages/index/index.vue';
import layout from "../components/index/layout.vue";

Vue.config.productionTip = false;
Vue.prototype.axios = Axios;

new Vue({
    render:h=>h(app),
    router:router,
    components:{layout},
    template:"<layout/>"
}).$mount('#index');

接在在index.vue組件內(nèi)使用axios獲取db.json中的數(shù)據(jù)

$ vim src/pages/index/index.vue
<template>
    <div class="page">
        <router-view></router-view>
    </div>
</template>

<script>
    export default {
        name: "index.vue",
        data(){
            return {
                ranklist:[]
            }
        },
        created(){
            this.fetchRanklist();
        },
        methods:{
            fetchRanklist(){
                let self = this;
                this.axios.get("/api/db.json").then(res=>{
                   const data = res.data;
                   console.log(res, data);
                   if(data.code === 200){
                       self.ranklist = data.data;
                   }
                }).catch(err=>{
                    console.error(err);
                });
            }
        }
    }
</script>

<style scoped>

</style>

此時訪問 http://127.0.0.1:8080/index 會自動向 http://127.0.0.1:8080/api/db.json 發(fā)送請求獲取數(shù)據(jù)。

使用axios返回的數(shù)據(jù)格式為

{
  config:{...},
  data:{...},
  headers:{...},
  request:...
  status:...,
  statusText:...
}

其中接口返回的數(shù)據(jù)在data選項中

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,117評論 6 537
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,860評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,128評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,291評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,025評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,421評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,477評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,642評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,177評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,970評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,157評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,717評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,410評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,821評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,053評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,896評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,157評論 2 375

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