Vue项目实战(一)

其实这段时间我自己感觉很迷茫,在国内高强度的工作压力承受习惯之后,突然处于一个相对比较轻松的环境反而还不适应,我总担心到了国内自己毫无竞争力,所以我依然保持对新技术的学习和关注,今天正儿八经的教大家如何写vue2,前提是有一定基础spa和node的同学。

1 项目目录结构

  • asserts 放置静态资源的目录,包括css和image。
  • components 这是大家比较熟悉的组件目录。
  • fetch 如果对es6fetch 比较熟悉的同学就知道,抓取数据的。
  • page 自定义的小组件目录,往往都是component里面的子组件。
  • router 路由控制页面的跳转,spa的关键。
  • util 自定义工具类函数。
  • vuex vue的状态管理工具。

我们来看一下入口app.vue的代码

<template>
  <div id="app">
    <router-view></router-view>
  </div>
</template>

<script>
export default {
  name: 'app',
  components: {
  }
}
</script>

<style >
@import './assets/style/reset.css';
</style>

通过这里你能够看到如何引用css文件,所有的内容都会被渲染到<router-view></router-view>里面。
如果我需要引用的是scss文件

step 1
npm install sass-loader node-sass --save-dev

step 2  webpack.base.config.js在loaders里面加上
{
      test: /\.scss$/,
      loaders: ["style", "css", "sass"]
    }

step 3
<style lang="scss" scope>

</style>

接下来就是app.js这个入口js文件了,这个很关键。

import Vue from 'vue'
import App from './App'
import router from './router'
import MintUI from 'mint-ui'
import 'mint-ui/lib/style.css'
// 引入swiper
import VueAwesomeSwiper from 'vue-awesome-swiper'
import iView from 'iview'
import 'iview/dist/styles/iview.css'
// Vuex
import Vuex from 'vuex'
import store from './vuex/store'
require('vue2-animate/dist/vue2-animate.min.css')
Vue.config.productionTip = false
Vue.use(Vuex)
Vue.use(VueAwesomeSwiper)
Vue.use(MintUI)
Vue.use(iView)

new Vue({
  el: '#app',
  router,
  Vuex,
  store,
  template: '<App/>',
  components: { App }
})

这个入口文件有许多写的是Vue.use,这就是想在项目中用插件的方式,本例中有VueAwesomeSwiper,MintUI,iView三个控件都是视图方面的,如果我想用jquery,那么你需要自己安装jquery,然后import进来,用Vue.use(jquery)。
需要注意的是vuex也需要这样操作。

问题来了,当我们npm run dev之后首先进入的是哪个页面?
来看一下router目录下的index.js

import Vue from 'vue'
import Router from 'vue-router'

// 首页
import Index from '@/page/index/index'
import Recommend from '@/page/index/recommend'
import Limit from '@/page/index/limit'
import Home from '@/page/index/home'
import Cook from '@/page/index/cook'
import Parts from '@/page/index/parts'
import Cloth from '@/page/index/cloth'
import Wash from '@/page/index/wash'
import Baby from '@/page/index/baby'
import Messy from '@/page/index/messy'
import Drink from '@/page/index/drink'
import Hobby from '@/page/index/hobby'
Vue.use(Router)

export default new Router({
  routes: [
    {
      path: '/',
      name: 'Index',
      component: Index,
      meta: { scrollToTop: true },
      children: [
        {
          path: '/',
          name: 'indexIndex',
          component: Recommend
        },
        {
          path: '/recommend',
          name: 'Recommend',
          component: Recommend
        },
        {
          path: '/limit',
          name: 'Limit',
          component: Limit
        },
        {
          path: '/home',
          name: 'Home',
          component: Home
        },
        {
          path: 'cook',
          name: 'Cook',
          component: Cook
        },
        {
          path: '/parts',
          name: 'Parts',
          component: Parts
        },
        {
          path: '/cloth',
          name: 'Cloth',
          component: Cloth
        },
        {
          path: '/wash',
          name: 'Wash',
          component: Wash
        },
        {
          path: '/baby',
          name: 'Baby',
          component: Baby
        },
        {
          path: '/messy',
          name: 'Messy',
          component: Messy
        },
        {
          path: '/drink',
          name: 'Drink',
          component: Drink
        },
        {
          path: '/hobby',
          name: 'Hobby',
          component: Hobby
        }
      ]
    }
]

一上来就搞事情,这么复杂的一个路由。。。
分析:进入项目,第一步是 '/',那么就会使用name=Index组件,注意他还有chilren路由,所以默认还会在Index组件里面加载name=indexIndex组件,也就是Recommend。想到这里我们肯定能想到Index组件里肯定有 <router-view></router-view>,话不多说,来看吧。

@/page/index/index
<template>
  <div class="Home">
    <v-header class="header"></v-header>
    <router-view class="content"></router-view>
    <v-footer class="footer"></v-footer>
    <go-top></go-top>
  </div>
</template>

我们看到这段template代码就会知道,这是一个普通的首页模式,一个header,content用来占坑,一个footer,一个gotop返回顶部。

问题来了,这里有router-view占坑,但是router-link在哪里呢?先卖个官司,看一下这个文件里的js代码。

import Header from '@/components/public/Header'
import Footer from '@/components/public/Footer'
import IndexTabs from '@/components/public/Tabs'
import goTop from '@/components/public/GoTop'
export default {
  name: 'index',
  created () {
    console.log('created')
    this.$Loading.config({
      color: '#b4282d',
      failedColor: '#f0ad4e',
      height: 5
    })
    this.$Loading.start()
    this.$store.dispatch('changeActive', 0)
  },
  mounted () {
    this.$Loading.finish()
    console.log('recommend mounted')
  },
  components: {
    'v-header': Header,
    'v-footer': Footer,
    'v-indexTabs': IndexTabs,
    goTop
  }
}

我们看到了create和mounted这种关键钩子函数,create在mounted之前,mouted是挂载dom节点,具体这里不讲了。

this.$store.dispatch这是vuex里面的,等会儿会讲到。
需要注意的是

components: {
'v-header': Header,
'v-footer': Footer,
'v-indexTabs': IndexTabs,
goTop
}

我们要将引进的组件注册,你可以重新命名,也可以不必,如goTop组件。

我们来看看header头部是如何写的。

@/component/public/header
<template>
  <div class="header-container">
    <div class="line" >
      <router-link to="/"  class="logo"></router-link>
      <router-link to="/search" class="m-topSearchIpt ipt" >
          <i class="icon" ></i>
          <span class="placeholder" >
            <span >商品搜索, 共</span> <span >5116</span> <span >款好物</span>
          </span>
      </router-link>
    </div>
    <v-indexTabs :tabs="tabs"></v-indexTabs>
  </div>
</template>

<script>
import IndexTabs from '@/components/public/Tabs'
export default {
  name: 'index',
  data () {
    return {
    }
  },
  components: {
    'v-indexTabs': IndexTabs
  },
  computed: {
    tabs () {
      return this.$store.getters.headertabList
    }
  }
}
</script>

好了我们这里看到了v-indexTabs就知道所有的菜单选择都在这个里面,
:tabs="tabs"父组件传递数据给子组件,这里tabs = this.$store.getters.headertabList,之前我有写过vuex的文章,看过的都知道这是在干嘛,等下再讲,咱们继续看IndexTabs。

@/components/public/Tabs
 <template>
    <header >
          <div class="inner" >
               <div class="list" >
                   <div class="list-container">
                    <div class="tab"  :class="{active: item.isActive}"
                             v-for="item in tabs" :key="item.id">
                    <router-link :to="item.linkTo">
                             <span class="txt" @click="activethis(item.id)">  {{item.name}} </span>
                 </router-link>
          </div>
          </div>
        </div>
      </div>
  </header>
</template>

<script>
export default {
  props: ['tabs'],
  data () {
    return {
    }
  },
  methods: {
    activethis (id) {
      // 找出当前激活的选项,当前点击的选项
      if (this.$route.path.indexOf('type') >= 0) {
        this.$store.dispatch('changeTypesabActive', id)
        return false
      }
      if (this.$route.path.indexOf('mylist') >= 0) {
        this.$store.dispatch('changeMylistActive', id)
        return false
      }
      console.log(id)
      this.$store.dispatch('changeHeadertabActive', id)
    }
  }
}
</script>

这里通过一个v-for指令把一个router-link渲染出来了,:class="{active: item.isActive}"这个我也不多说了,相信大家都懂,注意router-link里面要写:to,通过activethis控制跳转。

this.$route.path.indexOf('type') >= 0  这里是判断当前路由里面是否包含type

到这里大家宏观上应该已经完全把控,接下里就看一下vuex里面是如何写的,因为这里路由跳转也是通过dispatch来实现的。

2 状态管理

下面我们来看下vuex目录结构

我们来看下store.js里面是如何写的

store.js
import Vue from 'vue'
import Vuex from 'vuex'

import user from './modules/user'
import footer from './modules/footer'
// 头部分类
import headerTabs from './modules/headertabs'
import home from './modules/home'
import cook from './modules/cook'
import type from './modules/type'
// 脚部分类
import footclassification from './modules/footclassification'
import shopCart from './modules/shopCart'
import order from './modules/order'
import mylist from './modules/mylists'

Vue.use(Vuex)

export default new Vuex.Store({
  modules: {
    user,
    footer,
    home,
    cook,
    type,
    shopCart,
    order,
    mylist,
    footclassification,
    headerTabs
  }
})

如果没记错我们是通过tabs是通过this.$store.getters.headertabList获得的,这个store把modules里面的每一个状态都包含进来了。我们来看下hedertabs的内容

import * as types from '../types'
const state = {
  headertabList: [
        {id: 0, name: '推荐', isActive: true, linkTo: '/recommend'},
        {id: 1, name: '居家', isActive: false, linkTo: '/home'},
        {id: 2, name: '餐厨', isActive: false, linkTo: '/cook'},
        {id: 3, name: '配件', isActive: false, linkTo: '/parts'},
        {id: 4, name: '服装', isActive: false, linkTo: '/cloth'},
        {id: 5, name: '洗护', isActive: false, linkTo: '/wash'},
        {id: 6, name: '婴童', isActive: false, linkTo: '/baby'},
        {id: 7, name: '杂货', isActive: false, linkTo: '/messy'},
        {id: 8, name: '饮食', isActive: false, linkTo: '/drink'},
        {id: 9, name: '志趣', isActive: false, linkTo: '/hobby'}
  ]
}

const actions = {
  changeHeadertabActive ({commit}, id) {
    commit(types.CHANGE_HEADER_TAB, id)
  },
  changeTypesabActive ({commit}, id) {
    commit(types.CHANGE_TYPES_TAB, id)
  },
  changeMylistActive ({commit}, id) {
    commit(types.CHANGE_MYLIST_TAB, id)
  }
}
const getters = {
  headertabList: state => state.headertabList,
  typesTabs: state => state.typesTabs,
  selfmylist: state => state.mylist
}
const mutations = {
  [types.CHANGE_HEADER_TAB] (state, id) {
    state.headertabList.forEach(list => {
      list.isActive = false
    })
    state.headertabList[id].isActive = true
  },
  [types.CHANGE_TYPES_TAB] (state, id) {
    state.typesTabs.forEach(list => {
      list.isActive = false
    })
    state.typesTabs[id].isActive = true
  },
  [types.CHANGE_MYLIST_TAB] (state, id) {
    state.mylist.forEach(list => {
      list.isActive = false
    })
    state.mylist[id].isActive = true
  }
}
export default {
  state,
  actions,
  getters,
  mutations
}

注意在module下面的文件格式都是这样的,一个state,一个actions,一个getters,一个mutations,最后别忘了

export default {
  state,
  actions,
  getters,
  mutations
}

所以我们弄清楚了tabs内容的来源,就是headertabs里面的state.headertabList。

我们继续看一个module下面的文件

shopCart.js
import * as types from '../types'
import Util from '../../util/common'
const STORAGE_CARTLIST_KEY = 'STORAGE_CARTLIST_KEY'
const state = {
  cartList: Util.getLocal(STORAGE_CARTLIST_KEY) || [],
  isExist: false
}
const actions = {
  // set
  setCartList ({commit}, obj) {
    commit(types.SET_CART_LISTS, obj)
  },
  saveCartList ({commit}) {
    commit(types.SAVE_CART_LIST)
  },
  checkIsExist ({commit}, obj) {
    commit(types.CHECK_CART_ISEXIST, obj)
  },
  delCart ({commit}, obj) {
    commit(types.DEL_CART_CART, obj)
  }
}
const getters = {
  cartList: state => state.cartList,
  total: state => state.cartList.length,
  isExist: state => state.isExist,
  // 已经加入购物车的商品总量
  allNum: state => {
    let total = 0
    state.cartList.forEach(item => {
      total += item.number
    })
    return total
  }
}
const mutations = {
  [types.SET_CART_LISTS] (state, obj) {
    state.cartList.push(obj)
  },
  // 保存到购物车到本地
  [types.SAVE_CART_LIST] (state) {
    Util.setLocal(state.cartList, STORAGE_CARTLIST_KEY)
  },
  // exist this.++ else insert a new record
  [types.CHECK_CART_ISEXIST] (state, obj) {
    // 没有数据不做检查
    if (state.cartList.length === 0) return false
    let existIndex = state.cartList.findIndex((item) => {
      return item.type === obj.type && item.gid === obj.gid && item.picked === obj.picked
    })
    console.log(existIndex)
    // exist
    if (existIndex >= 0) {
      console.log(state.cartList[existIndex].number)
      state.cartList[existIndex].number ++
      state.isExist = true
    } else {
      state.isExist = false
    }
  },
  [types.DEL_CART_CART] (state, objs) {
    console.log(objs.length)
    objs.forEach(obj => {
      let index = state.cartList.findIndex((item) => {
        return item.gid === obj.id && item.type === obj.type
      })
       // 找出索引删除一个
      state.cartList.splice(index, 1)
    })
    Util.setLocal(state.cartList, STORAGE_CARTLIST_KEY)
  }
}
export default {
  state,
  actions,
  getters,
  mutations
}

里面的内容不重要,关键是我们看得出来他的写法,都是这种形式,另外可以通过this.$store.getters. 获取任意一个module下面的数据。

当我们初次进入'/'会调用this.$store.dispatch('changeHeadertabActive', 0)。

调用路线:

changeHeadertabActive ({commit}, id) {
    commit(types.CHANGE_HEADER_TAB, id)
  }

[types.CHANGE_HEADER_TAB] (state, id) {
    state.headertabList.forEach(list => {
      list.isActive = false
    })
    state.headertabList[id].isActive = true
  }

所以其实任何改动都是在mutation里面进行的。

3 页面分析

当我们点击tab为home时,会用home组件加载。

<template>
  <div>
     <each-tab :desc="homeDesc" ></each-tab>
  </div>
</template>
<script>
import { Indicator } from 'mint-ui'
import eachTab from '@/components/public/EachTab'
export default {
  data () {
    return {
    }
  },
  components: {
    eachTab
  },
  created () {
    this.$store.dispatch('changeHeadertabActive', 1)
    Indicator.open('加载中...')
    this.$store.dispatch('gethomeDesc', 'home')
  },
  computed: {
    homeDesc () {
      Indicator.close()
      return this.$store.getters.homeDesc
    }
  }
}
</script>

each-tab很明显是home下面每个商品的描述组件,在进入此组件时同样的会调用changeHeadertabActive来改变tab状态切换,同时会调用gethomeDesc来填充数据,最后在computed的时候就会将数据呈上。

所以我只需看下home.js里面是如何写的即可

@/index/home.js
import * as types from '../types'
import data from '@/fetch/api'
const state = {
  homeDesc: {},
  homeDetail: {}
}
const actions = {
  // home简要
  gethomeDesc ({commit}, type) {
    console.log('type', type)
    data.getTypeDesc(type).then(res => {
      // console.log('type data:', res)
      commit(types.SET_HOME_DESC, res)
    })
  },
  gethomeDetail ({commit}, type, id) {
    data.getTypeDetail(type, id).then(res => {
      console.log('type data:', res)
      commit(types.SET_HOME_DETAIL, res)
    })
  }

}
const getters = {
  homeDesc: state => state.homeDesc
}
const mutations = {
  [types.SET_HOME_DESC] (state, res) {
    state.homeDesc = res
  },
  [types.SET_HOME_DETAIL] (state, res) {
    state.homeDetail = res
  }
}
export default {
  state,
  actions,
  getters,
  mutations
}

我们可以看出来gethomeDesc是按照type来fetch数据填充到homedesc里面,这个fetch涉及到promise,下节我们再讲。

接下来看each-tab

<template>
  <div>
      <div class="slideWarp">
        ![](desc.adpic)
  </div>
  <goods-grid :data="desc.data"></goods-grid>
  </div>
</template>

<script>
import goodsGrid from '@/components/public/GoodsGrid'

export default {
  props: ['desc'],
  data () {
    return {
    }
  },
  components: {
    goodsGrid
  }
}
</script>

很明显最后商品列表都被渲染进了goods-grid。

goods-grid
<template>
  <div>
<div class="goodsgrid" v-for="eachdata in data">
 <header>
   <h3 class="title" >{{eachdata.title}} <p>{{eachdata.subtitle}}</P></h3>
   
 </header>
 <div class="m-goodGrid">
   <ul class="list clearfix" >
     <li class="item" v-for="item in eachdata.lists" :key="item.id">
       <router-link :to="{ name: 'seeDetails', params: { type: item.type, id: item.id }}">
       ![](item.src)
       <div class="desc">{{item.desc}}</div>
       <div class="name" >{{item.name}}</div>
       <div class="price">¥{{item.price}}</div>
       </router-link>
       
     </li>
   </ul>
 </div>
</div>
</div>
</template>
<script>
export default {
  props: ['data']
}
</script>

每个li.item就是具体商品,然后点击它可以跳转到这个商品详情页,注意这里的路由。

 {
          path: '/detail/:type/id/:id',
          name: 'seeDetails',
          component: seeDetails
        }

router里面有这样一段,我们就知道了上面的意思是说到seeDetails,并把params的参数携带过去。问题来了,商品详情都是用seeDeatils组件,那么这个数据填充是如何做的。。
来看goodsDetail.vue

<template>
  <div class="details">
    <div class="swiper">
      <swiper :options="swiperOption">
        <swiper-slide v-for="pic in detail.detailPic" :key="pic.id">
          ![](pic)
        </swiper-slide>
        <div class="swiper-pagination" slot="pagination"></div>
      </swiper>
    </div>
    <div class="m-detailBaseInfo">
      <goods-info :info="detail"></goods-info>
      <!--服务-->
      <goods-service ></goods-service>
      <!--评价-->
      <goods-comment :comment="detail.comment"></goods-comment>
    </div>
     <!--商品参数-->
     <goods-attr :attr="detail.attr"></goods-attr>
    <!--图片描述-->
    <div class="dt-section dt-section-1">
      <div class="m-detailHtml">
        <p v-for="item in detail.detailHtml">![](item)</p>
      </div>
    </div>
    <go-top></go-top>
  </div>
</template>
<script>
import { Indicator } from 'mint-ui'
import goodsComment from './goodsComment'
import goodsAttr from './goodsAttr'
import goodsService from './goodsService'
import goodsInfo from './goodsInfo'
import goTop from '@/components/public/GoTop'
export default {
  name: 'Detail',
  data () {
    return {
      swiperOption: {
        autoplay: 3500,
        loop: true,
        setWrapperSize: true,
        pagination: '.swiper-pagination',
        paginationType: 'fraction',
        paginationClickable: true,
        mousewheelControl: true,
        observeParents: true
      }
    }
  },
  components: {
    goodsComment,
    goodsAttr,
    goodsService,
    goodsInfo,
    goTop
  },
  created () {
    Indicator.open('加载中...')
    console.log(1)
    let type = this.$route.path.split('/')[2]
    let id = this.$route.path.split('/')[4]
    console.log('detail', {type, id})
    this.$store.dispatch('getDetail', {type, id})
  },
  mounted () {
    console.log(this.$route.path)
  },
  computed: {
    detail () {
      Indicator.close()
      console.log(this.$store.getters.Detail)
      return this.$store.getters.Detail
    }
  }
}
</script>
  }

这里面有许多swiper这种ui插件,暂且不管,我们来看看是如何做到把商品详情的数据拿到的

let type = this.$route.path.split('/')[2]
    let id = this.$route.path.split('/')[4]
    console.log('detail', {type, id})
    this.$store.dispatch('getDetail', {type, id})

computed: {
    detail () {
      Indicator.close()
      console.log(this.$store.getters.Detail)
      return this.$store.getters.Detail
    }
  }

所以我们看到是用this.$route.path拿到地址的参数进而去dispatch改变数据,做到的。

getDetail ({commit}, obj) {
    console.log(`post ${obj.type}${obj.id} data:`)
    data.getTypeDetail(obj.type, obj.id).then(res => {
      // console.log('res', res)
      commit(types.SET_TYPE_DETAIL, res)
    })
  }

[types.SET_TYPE_DESC] (state, res) {
    state.Desc = res
  }

之前一直在搞angular,现在发现框架对这样的问题处理都一样,所以我劝那些想把前端学好的同学先不要慌着搞这些,js基础才是王道,基础好了学什么都是一下午的事。。。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容

  • 转载 :OpenDiggawesome-github-vue 是由OpenDigg整理并维护的Vue相关开源项目库...
    果汁密码阅读 23,083评论 8 124
  • 岳母资助的小伙子考上研究生后,来到广州,我们一起吃过一次饭。小伙人长得挺精神,瘦瘦高高,看不出是农家子弟,我观察过...
    妖娥子广州阅读 232评论 0 2
  • alamu阅读 1,321评论 1 0
  • 当幸福来敲门观后感,如果有梦想一定要去实现。
    秦岭云阅读 148评论 0 0