帶你進(jìn)入異步Django+Vue的世界 - Didi打車實(shí)戰(zhàn)(2)

帶你進(jìn)入異步Django+Vue的世界 - Didi打車實(shí)戰(zhàn)(1)
Demo: https://didi-taxi.herokuapp.com/

本篇來完成前端的框架和注冊(cè)登錄頁面残制。

UI框架大家隨意選擇,符合自己需求就行之众。
比如你只需要桌面端盲链,那iView比較合適蝇率。如果只需要手機(jī)端,那選Framework7刽沾、Element等等瓢剿。如果要同時(shí)適配桌面+手機(jī)端,Vuetify悠轩、Bootstrap比較合適间狂。
我們這里使用Github 18k星的Vuetify

添加Vuetify到前端:

vue-cli命令行添加就行:
vue add vuetify
然后火架,會(huì)自動(dòng)更新main.js, App.vue, package.json等文件鉴象。

打開新終端,運(yùn)行:
yarn lint --fix
yarn serve
瀏覽器打開http://localhost:8080何鸡,就能看到Vuetify的demo頁面了:

image.png

UI設(shè)計(jì)

編寫前端代碼之前纺弊,先對(duì)我們的設(shè)計(jì)目標(biāo)進(jìn)行規(guī)劃。大家可以先畫藍(lán)圖骡男,發(fā)揮自己的想像力淆游,對(duì)用戶要友好。

總體UI

  1. 最上面為導(dǎo)航條
  • 按鈕:商標(biāo)、登錄犹菱、注冊(cè)拾稳、退出登錄、叫車/接單
  • 桌面使用時(shí)腊脱,顯示完整按鈕名稱访得,手機(jī)端只顯示圖標(biāo)。Vuetify會(huì)自動(dòng)調(diào)整陕凹。
  • 針對(duì)注冊(cè)和未注冊(cè)用戶悍抑,顯示不同菜單

桌面版:


image.png

手機(jī)版:


image.png
  1. 內(nèi)容區(qū)
    在導(dǎo)航條下方,通過Vue-Router來導(dǎo)航杜耙。
    首頁顯示當(dāng)前進(jìn)行中的打車搜骡,和打車歷史

  2. 注冊(cè)頁面:


    image.png
  3. 登錄頁面:


    image.png
  4. 全局提示:
    對(duì)于操作成功、失敗佑女,有明顯的提示:


    image.png
  5. 打車頁面:
    使用Modal彈出框來實(shí)現(xiàn)记靡,TBD

前端代碼

我們?cè)诘谝黄铮呀?jīng)導(dǎo)入了前端代碼的框架珊豹,支持Vue-Router, Vuex, axios簸呈。可以方便地以此為基礎(chǔ)開發(fā)店茶。

1. 靜態(tài)主頁index.html

添加icon鏈接蜕便,你也可以選擇font-awesome等其它icon

  <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="/static/favicon.ico">
    <title>Didi Taxi</title>
    <link rel="stylesheet" >
    <link rel="stylesheet" >
2. 導(dǎo)航條

寫到主組件App.vue即可。根據(jù)用戶是否已經(jīng)登錄贩幻,顯示不同的菜單轿腺。

# /src/App.vue
<template>
  <v-app>
    <v-toolbar app>
      <v-avatar v-if="userIsAuthenticated">
        <img src="https://randomuser.me/api/portraits/men/95.jpg" :title="user.username" />
      </v-avatar>
      <v-toolbar-title class="headline text-uppercase">
        <v-btn flat to="/">
          <span>Didi</span>
        </v-btn>
      </v-toolbar-title>
      <v-spacer></v-spacer>
      <v-toolbar-items v-for="item in menuItems" :key="item.id">
        <v-btn flat :key="item.title" :to="item.route" @click.prevent="menu_click(item.title)">
          <v-icon left>{{ item.icon }}</v-icon>
          <div class="hidden-xs-only">{{ item.title }}</div>
        </v-btn>
      </v-toolbar-items>
    </v-toolbar>

。丛楚。族壳。
  </v-app>
</template>
3. 全局提示

我們通過v-alert組件,顯示Vuex store里的提示數(shù)據(jù)趣些。

# /src/App.vue
<template>
  <v-app>

仿荆。。坏平。
    <v-content>
      <v-layout row v-if="alert != null">
        <v-flex xs12 sm8 offset-sm2>
          <v-alert @input="clearAlert" dismissible :value="true" :type="alert.type">
            {{ alert != null ? alert.msg : '' }}
          </v-alert>
        </v-flex>
      </v-layout>
      <v-container fluid>
        <router-view></router-view>
      </v-container>
    </v-content>
  </v-app>
</template>

Vuex里拢操,alert為這種格式:
alert: { type: 'success', msg: 'Sign up success!' }
type: success/error/info/warning

需要更新Vuex store:
添加相應(yīng)的state/mutations/actions。

  • state:全局變量
  • mutations:更新變量的值舶替,必須是同步的
  • actions:操作事務(wù)令境,是異步的,可以操作多個(gè)mutations
  • getters:在返回變量值之前顾瞪,可以添加其它運(yùn)算舔庶,比如只返回部分符合條件的數(shù)值
# /src/store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import messages from './modules/messages'

Vue.use(Vuex)

export default new Vuex.Store({
  modules: {
    messages
  },
  state: {
    loading: false,
    alert: null,
    // alert: { type: 'success', msg: 'Login success!' },
    // user: { id: 1, username: 'admin', first_name: '', last_name: '' }
    user: null
  },
  mutations: {
    setLoading (state, payload) {
      state.loading = payload
    },
    setAlert (state, payload) {
      state.alert = payload
    },
    clearAlert (state) {
      state.alert = null
    },
    setUser (state, payload) {
      state.user = payload
    }
  },
  actions: {
    setUserInfo ({ commit }) {
        let u = localStorage.getItem('user')
        if (u) {
          u = JSON.parse(u)
        } else {
          console.log('>>> no user info found in localStorage')
        }
        commit('setUser', u)
    },
    clearAlert ({ commit }) {
      commit('clearAlert')
    }
  },
  getters: {
    loading (state) {
      return state.loading
    },
    alert (state) {
      return state.alert
    },
    user (state) {
      return state.user
    }
  }
})

我們把上面文件里的注釋去掉抛蚁,測(cè)試一下:

  state: {
    //alert: null
    alert: { type: 'success', msg: 'Login success!' }
  },

你應(yīng)該能成功看到提示:


image.png
4. home路由

更新路由文件,支持以下路由:

# /src/router.js
import Vue from 'vue'
import Router from 'vue-router'
import Home from './views/Home.vue'
import My404 from './views/My404.vue'
import Signup from './views/Signup.vue'
import Signin from './views/Signin.vue'

Vue.use(Router)

export default new Router({
  mode: 'history',
  base: process.env.BASE_URL,
  routes: [
    {
      path: '/',
      name: 'home',
      component: Home
    },
    {
      path: '/sign_up',
      name: 'sign_up',
      component: Signup
    },
    {
      path: '/log_in',
      name: 'log_in',
      component: Signin
    },
    {
      path: '/messages',
      name: 'messages',
      // route level code-splitting
      // this generates a separate chunk (xxx.[hash].js) for this route
      // which is lazy-loaded when the route is visited.
      component: () => import(/* webpackChunkName: "messages" */ './views/Messages.vue')
    },
    { path: '*', name: 'my404', component: My404 }
  ]
})

編輯頁面文件home.vue惕橙,先顯示空白打車記錄:

<template>
  <v-layout row wrap>
    <v-flex xs12 sm6 offset-sm3>
      <v-card class="mb-4">
        <v-img
          src="https://cdn.vuetifyjs.com/images/parallax/material2.jpg"
          aspect-ratio="5" class="white--text">
          <v-container fill-height fluid>
                <span class="display-2">On-going Trip</span>
          </v-container>
        </v-img>
        <v-card-title primary-title>
            <div class="grey--text"> {{ card_text }} </div>
        </v-card-title>
        <v-card-actions>
          <v-btn flat color="red">Cancel</v-btn>
          <v-spacer></v-spacer>
          <v-btn flat color="blue">View</v-btn>
        </v-card-actions>
      </v-card>
    </v-flex>
    <v-flex xs12 sm6 offset-sm3>
      <v-card class="mb-4">
        <v-img
          src="https://cdn.vuetifyjs.com/images/cards/docks.jpg"
          aspect-ratio="5" class="white--text">
          <v-container fill-height fluid>
                <span class="display-2">Trip History</span>
          </v-container>
        </v-img>
        <v-card-title primary-title>
            <div class="grey--text"> {{ card_text }} </div>
        </v-card-title>
        <v-card-actions>
          <v-spacer />
          <v-btn flat color="blue">View ALL</v-btn>
        </v-card-actions>
      </v-card>
    </v-flex>
  </v-layout>
</template>

<script>
export default {
  data () {
    return {
      card_text: 'No data'
    }
  }
}
</script>

后續(xù)會(huì)使用服務(wù)器返回的數(shù)據(jù)瞧甩,來更新顯示。

5. 注冊(cè)路由

注冊(cè)頁面吕漂,顯示三條輸入行:username, password1, password2
針對(duì)兩次密碼亲配,進(jìn)行對(duì)比提示

# /src/views/Signup.vue
<template>
  <v-container>
    <v-layout row>
      <v-flex xs12 sm6 offset-sm3>
        <v-card>
          <v-card-text>
            <v-container>
              <form @submit.prevent="onSignup">
                <v-layout row>
                  <v-flex xs12>
                    <v-text-field
                      name="username"
                      label="Username"
                      id="username"
                      v-model="username"
                      type="text"
                      required></v-text-field>
                  </v-flex>
                </v-layout>
                <v-layout row>
                  <v-flex xs12>
                    <v-text-field
                      name="password"
                      label="Password"
                      id="password"
                      v-model="password"
                      type="password"
                      required></v-text-field>
                  </v-flex>
                </v-layout>
                <v-layout row>
                  <v-flex xs12>
                    <v-text-field
                      name="confirmPassword"
                      label="Validate Password"
                      id="confirmPassword"
                      v-model="confirmPassword"
                      type="password"
                      :rules="[comparePasswords]"></v-text-field>
                  </v-flex>
                </v-layout>
                <v-layout>
                  <v-flex xs12>
                    <v-card-actions>
                    <v-spacer />
                    <v-btn round type="submit" :loading="loading" class="orange">Register</v-btn>
                  </v-card-actions>
                  </v-flex>
                </v-layout>
              </form>
            </v-container>
          </v-card-text>
        </v-card>
      </v-flex>
    </v-layout>
  </v-container>
</template>

<script>
export default {
  data () {
    return {
      username: '',
      password: '',
      confirmPassword: ''
    }
  },
  computed: {
    comparePasswords () {
      return this.password !== this.confirmPassword ? 'Passwords do not match.' : true
    },
    user () {
      return this.$store.getters.user
    },
    alert () {
      return this.$store.getters.alert
    },
    loading () {
      return this.$store.getters.loading
    }
  },
  watch: {
    user (value) {
      if (value !== null && value !== undefined) {
        this.$router.push('/')
      }
    }
  },
  methods: {
    onSignup () {
      this.$store.dispatch('messages/signUserUp', { username: this.username, password2: this.confirmPassword, password1: this.password })
    },
    onDismissed () {
      this.$store.dispatch('clearAlert')
    }
  }
}
</script>

當(dāng)點(diǎn)擊Register注冊(cè)時(shí)尘应,發(fā)送請(qǐng)求到后端惶凝。
這里的最佳實(shí)踐是,所有跟后端的API交互犬钢,都統(tǒng)一提取出來放在Vuex苍鲜,方便更新和管理。
Vuex添加signUserUp 注冊(cè)action:

  • 更新loading - 按鈕的狀態(tài)在交互時(shí)玷犹,會(huì)提示正在跟后臺(tái)通信
  • 通過messageService.signUserUp()發(fā)送POST
  • 更新setAlert - 顯示注冊(cè)成功提示
  • 注冊(cè)成功后混滔,轉(zhuǎn)向Home路由
# /src/store/modules/messages.js
const actions = {
  signUserUp ({ commit }, payload) {
    commit('setLoading', true, { root: true })
    messageService.signUserUp(payload)
      .then(messages => {
        commit('setAlert', { type: 'success', msg: 'Sign up success!' }, { root: true })
        commit('setLoading', false, { root: true })
        router.push('/')
      })
  },

API統(tǒng)一放在/src/services/messageService.js

import api from '@/services/api'

export default {
  signUserUp (payload) {
    return api.post(`sign_up/`, payload)
      .then(response => response.data)
  },

確保后臺(tái)Django程序運(yùn)行中:
python manage.py runserver
測(cè)試一下,應(yīng)該能順利注冊(cè)新用戶了歹颓。

但是坯屿,當(dāng)前對(duì)異常處理沒有任何處理,用戶不知道為什么注冊(cè)失敗了巍扛。
我們可以對(duì)后端返回值處理领跛,然后提示。
但對(duì)于100個(gè)API呢撤奸?也一次次處理么吠昭?太低效了!我們來歸納一下胧瓜。

axios統(tǒng)一處理header和異常

對(duì)于后端矢棚,可能要前端提供一些額外的header信息,比如csrf, token府喳。
前端收到返回值蒲肋,也要提示用戶。

  • header加上csrf: 'X-CSRFToken': Cookies.get('csrftoken')
  • error信息钝满,通過Vuex提示給用戶:store.commit('setAlert', { type: 'error', msg: error.response.data })
# /src/services/api.js
import axios from 'axios'
import Cookies from 'js-cookie'

import vueconfig from '@/config'
import store from '@/store'

axios.interceptors.request.use(
  config => {
    config.baseURL = `${vueconfig.baseUrl}/api/`
    config.withCredentials = true // 允許攜帶token 解決跨域產(chǎn)生的相關(guān)問題
    config.timeout = 10000 // 10s

    config.headers = {
      'Content-Type': 'application/json',
      'X-CSRFToken': Cookies.get('csrftoken')
    }
    return config
  },
  error => {
    return Promise.reject(error)
  }
)

// 在 response 攔截器實(shí)現(xiàn)
axios.interceptors.response.use(
  response => {
    // console.log(response)
    return response
  },
  error => {
    console.log(error.response)
    if (error.response.status === 400) {
      // Bad Request. within module: { root: true } ??
      store.commit('setAlert', { type: 'error', msg: error.response.data })
    } else if (error.response.status === 403) {
      // Forbidden 403
      store.commit('setAlert', { type: 'error', msg: error.response.data.detail })
      localStorage.removeItem('user')
      store.commit('setUser', null)
    } else if ([405].includes(error.response.status)) {
      // Method Not Allowed 405
      store.commit('setAlert', { type: 'error', msg: error.response.data.detail })
    } else {
      console.log(`>>> un-handled error code! ${error.response.status}`)
    }
    store.commit('setLoading', false)
    return Promise.reject(error)
  }
)

export default axios

axios配置文件:
配置后端的Django服務(wù)器地址兜粘,我們順便把Websockets也加上

# /src/config.js
const wsProtocol = location.protocol === 'http:' ? 'ws:' : 'wss:'
let baseUrl = location.origin
let wsUrl = `${wsProtocol}//${location.host}`

if (process.env.NODE_ENV === 'development') {
  baseUrl = 'http://localhost:8080'
  wsUrl = 'ws://localhost:8080'
}

export default {
  baseUrl,
  wsUrl
}

再次測(cè)試,如果有任何ajax出錯(cuò)舱沧,用戶都能看到提示:
比如:


image.png
6. 登錄路由

有了前面的鋪墊妹沙,就很簡(jiǎn)單了
先創(chuàng)建view頁面:

  • 顯示兩條輸入行:username, password
  • 點(diǎn)擊登錄時(shí),執(zhí)行Vuex signUserIn action
# /src/views/Sigin.vue
<template>
  <v-container>
    <v-layout row>
      <v-flex xs12 sm6 offset-sm3>
        <v-card>
          <v-card-text>
            <v-container>
              <form @submit.prevent="onSignin">
                <v-layout row>
                  <v-flex xs12>
                    <v-text-field
                      name="username"
                      label="Username"
                      id="username"
                      v-model="username"
                      type="text"
                      required></v-text-field>
                  </v-flex>
                </v-layout>
                <v-layout row>
                  <v-flex xs12>
                    <v-text-field
                      name="password"
                      label="Password"
                      id="password"
                      v-model="password"
                      type="password"
                      required></v-text-field>
                  </v-flex>
                </v-layout>
                <v-layout row>
                  <v-flex xs12>
                    <v-card-actions>
                      <v-spacer></v-spacer>
                      <v-btn type="submit" :loading="loading" round class="primary">Login</v-btn>
                    </v-card-actions>
                  </v-flex>
                </v-layout>
              </form>
            </v-container>
          </v-card-text>
        </v-card>
      </v-flex>
    </v-layout>
  </v-container>
</template>

<script>
export default {
  data () {
    return {
      username: '',
      password: ''
    }
  },
  computed: {
    user () {
      return this.$store.getters.user
    },
    loading () {
      return this.$store.getters.loading
    }
  },
  watch: {
    user (value) {
      if (value !== null && value !== undefined) {
        this.$router.push('/')
      }
    }
  },
  methods: {
    onSignin () {
      this.$store.dispatch('messages/signUserIn', { username: this.username, password: this.password })
    }
  }
}
</script>

更新Vuex:

  • ajax調(diào)用messageService.signUserIn(payload)
  • 為了保存登錄狀態(tài)熟吏,我們使用LocalStorage來保存距糖。這樣玄窝,用戶登錄過后,關(guān)閉瀏覽器悍引,再打開瀏覽器恩脂,直接為已登錄狀態(tài),直到Django session過期趣斤。
# /src/store/modules/message.js
const actions = {
  signUserIn ({ commit }, payload) {
    commit('setLoading', true, { root: true })
    messageService.signUserIn(payload)
      .then(messages => {
        commit('setAlert', { type: 'success', msg: 'Login success!' }, { root: true })
        commit('setUser', messages, { root: true })
        localStorage.setItem('user', JSON.stringify(messages)) 
        commit('setLoading', false, { root: true })
        router.push('/')
      })
  },

ajax交互 /src/services/messageService.js

export default {
  signUserIn (payload) {
    return api.post(`log_in/`, payload)
      .then(response => response.data)
  },

登錄后俩块,會(huì)顯示用戶頭像,叫車和退出按鈕:


image.png
7. 注銷登錄

這個(gè)不需要?jiǎng)?chuàng)建新的vue頁面文件浓领。
更新Vuex:

  • ajax調(diào)用messageService.signUserOut()
  • 清除LocalStorage里保存的登錄狀態(tài)
# /src/store/modules/message.js
const actions = {
  signUserOut ({ commit }) {
    commit('setLoading', true, { root: true })
    messageService.signUserOut()
      .then(messages => {
        commit('setAlert', { type: 'info', msg: 'Log-out success!' }, { root: true })
        commit('setUser', null, { root: true })
        localStorage.removeItem('user') 
        commit('setLoading', false, { root: true })
      })
  },

ajax交互 /src/services/messageService.js

export default {
  signUserOut () {
    return api.post(`log_out/`, '')
      .then(response => response.data)
  },

導(dǎo)航欄的退出按鈕玉凯,添加方法:

# /src/App.vue
  computed: {
    ...mapState(['alert', 'user']),
    menuItems () {
      let items = [
        { icon: 'face', title: 'Register', route: '/sign_up' },
        { icon: 'lock_open', title: 'Login', route: '/log_in' }
      ]
      if (this.userIsAuthenticated) {
        items = [
          { icon: 'local_taxi', title: 'Call', route: '' },
          { icon: 'exit_to_app', title: 'Exit', route: '' }
        ]
      }
      return items
    },
    userIsAuthenticated () {
      return this.$store.getters.user !== null && this.$store.getters.user !== undefined
    }
  },
  methods: {
    ...mapActions(['clearAlert']),
    menu_click (title) {
      if (title === 'Exit') {
        this.$store.dispatch('messages/signUserOut')
      } else if (title === 'Call') {
        this.$store.dispatch('messages/callTaxi')
      }
    }
  }
image.png

總結(jié)

這套鑒權(quán)系統(tǒng),非常通用联贩,其它項(xiàng)目都可以借鑒使用漫仆。
下一篇,會(huì)進(jìn)入到Django后臺(tái)數(shù)據(jù)庫設(shè)計(jì)泪幌。

帶你進(jìn)入異步Django+Vue的世界 - Didi打車實(shí)戰(zhàn)(3)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末盲厌,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子祸泪,更是在濱河造成了極大的恐慌吗浩,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件没隘,死亡現(xiàn)場(chǎng)離奇詭異懂扼,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)升略,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門微王,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人品嚣,你說我怎么就攤上這事炕倘。” “怎么了翰撑?”我有些...
    開封第一講書人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵罩旋,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我眶诈,道長(zhǎng)涨醋,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任逝撬,我火速辦了婚禮浴骂,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘宪潮。我一直安慰自己溯警,他們只是感情好趣苏,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著梯轻,像睡著了一般食磕。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上喳挑,一...
    開封第一講書人閱讀 48,970評(píng)論 1 284
  • 那天彬伦,我揣著相機(jī)與錄音,去河邊找鬼伊诵。 笑死单绑,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的日戈。 我是一名探鬼主播询张,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼孙乖,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼浙炼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起唯袄,我...
    開封第一講書人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤弯屈,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后恋拷,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體资厉,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年蔬顾,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了宴偿。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡诀豁,死狀恐怖窄刘,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情舷胜,我是刑警寧澤娩践,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布,位于F島的核電站烹骨,受9級(jí)特大地震影響翻伺,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜沮焕,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一吨岭、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧峦树,春花似錦辣辫、人聲如沸簿废。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽族檬。三九已至,卻和暖如春化戳,著一層夾襖步出監(jiān)牢的瞬間单料,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來泰國打工点楼, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留扫尖,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓掠廓,卻偏偏與公主長(zhǎng)得像换怖,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子蟀瞧,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345