vite + vue3 + setup + pinia + ts 項目實戰

介紹

一個使用 vite + vue3 + pinia + ant-design-vue + typescript 完整技術路線開發的項目,秒級開發更新啟動、新的vue3 composition api 結合 setup縱享絲滑般的開發體驗、全新的 pinia狀態管理器和優秀的設計體驗(1k的size)、antd無障礙過渡使用UI組件庫 ant-design-vue、安全高效的 typescript類型支持、代碼規范驗證、多級別的權限管理~

前言

前兩天接到了一個需求,就是把原來的一個項目的主要功能模塊和用戶模塊權限系統抽出來做一個新后臺項目,并迭代新增一些新功能,看起來好像也沒啥東西

拿到源碼看了下項目,好家伙,原項目是個微應用項目,主應用用戶模塊是react技術棧,子應用模塊是vue2技術棧,這直接 CV大法看樣子是不行了??,我這要做的畢竟是個單頁面應用,確定一個技術路線即可,具體看下代碼邏輯并跑起來看看

跑起來試了下,兩個項目基本都是1分鐘左右啟動,看代碼vue項目整個業務邏輯代碼都擰在一塊寫了

想到之前問老大要源碼的時候,說那個是老項目了,重新搭一個寫應該會快點

這話沒毛病啊,話不多說,直接開整,這次直接上 vite + vue3

emoji

特性

  • ?腳手架工具:高效、快速的 Vite
  • ??前端框架:眼下最時髦的 Vue3
  • ??狀態管理器:vue3新秀 Pinia,猶如 react zustand般的體驗,友好的api和異步處理
  • ??開發語言:政治正確 TypeScript
  • ??UI組件:antd開發者無障礙過渡使用 ant-design-vue,熟悉的配方熟悉的味道
  • ??css樣式:lesspostcss
  • ??代碼規范:EslintPrettier、Commitlint
  • ??權限管理:頁面級、菜單級、按鈕級、接口級
  • ?依賴按需加載:unplugin-auto-import,可自動導入使用到的vue、vue-router等依賴
  • ??組件按需導入:unplugin-vue-components,無論是第三方UI組件還是自定義組件都可實現自動按需導入以及TS語法提示

項目目錄

├── .husky                              // husky git hooks配置目錄
    ├── _                               // husky 腳本生成的目錄文件
    ├── commit-msg                      // commit-msg鉤子,用于驗證 message格式
    ├── pre-commit                      // pre-commit鉤子,主要是和eslint配合
├── config                              // 全局配置文件
    ├── vite                            // vite 相關配置
    ├── constant.ts                     // 項目配置
    ├── themeConfig.ts                  // 主題配置
├── dist                                // 默認的 build 輸出目錄
├── mock                                // 前端數據mock
├── public                              // vite項目下的靜態目錄
└── src                                 // 源碼目錄
    ├── api                             // 接口相關
    ├── assets                          // 公共的文件(如image、css、font等)
    ├── components                      // 項目組件
    ├── directives                      // 自定義 指令
    ├── enums                           // 自定義 常量(枚舉寫法)
    ├── hooks                           // 自定義 hooks
    ├── layout                          // 全局布局
    ├── router                          // 路由
    ├── store                           // 狀態管理器
    ├── utils                           // 工具庫
    ├── views                           // 頁面模塊目錄
        ├── login                       // login頁面模塊
        ├── ...
    ├── App.vue                         // vue頂層文件
    ├── auto-imports.d.ts               // unplugin-auto-import 插件生成
    ├── components.d.d.ts               // unplugin-vue-components 插件生成
    ├── main.ts                         // 項目入口文件
    ├── shimes-vue.d.ts                 // vite默認ts類型文件
    ├── types                           // 項目type類型定義文件夾
├── .editorconfig                       // IDE格式規范
├── .env                                // 環境變量
├── .eslintignore                       // eslint忽略
├── .eslintrc                           // eslint配置文件
├── .gitignore                          // git忽略
├── .npmrc                              // npm配置文件
├── .prettierignore                     // prettierc忽略
├── .prettierrc                         // prettierc配置文件
├── index.html                          // 入口文件
├── LICENSE.md                          // LICENSE
├── package.json                        // package
├── pnpm-lock.yaml                      // pnpm-lock
├── postcss.config.js                   // postcss
├── README.md                           // README
├── tsconfig.json                       // typescript配置文件
└── vite.config.ts                      // vite

開發

項目初始化

如果使用vscode編輯器開發vue3,請務必安裝Volar插件與vue3配合使用更佳(與原本的Vetur不兼容)

使用 vite cli 快速創建項目

yarn create vite project-name --template vue-ts

安裝相關依賴

推薦使用新一代 pnpm 包管理工具,性能和速度以及 node_modules依賴管理都很優秀

建議配合 .npmrc 配置使用

# 提升一些依賴包至 node_modules
# 解決部分包模塊not found的問題
# 用于配合 pnpm
shamefully-hoist = true

# node-sass 下載問題
# sass_binary_site="https://npm.taobao.org/mirrors/node-sass/"

代碼規范

工具:huskyeslint、prettier

具體使用方式,網上很多,我在之前另一篇文章也有說過,這里不再贅述~

a Vite2 + Typescript + React + Antd + Less + Eslint + Prettier + Precommit template

主要就是自動化的概念,在一個合適的時機完成規定的事

  • 結合VsCode編輯器(保存時自動執行格式化:editor.formatOnSave: true
  • 配合Git hooks鉤子(commit前或提交前執行:pre-commit => npm run lint:lint-staged

注意

針對不同系統 commitlint安裝方式有所不同 commitlint,安裝錯誤可能會無效哦~

# Install commitlint cli and conventional config
npm install --save-dev @commitlint/{config-conventional,cli}
# For Windows:
npm install --save-dev @commitlint/config-conventional @commitlint/cli

功能

vue能力支持

模板語法配合jsx語法,使用起來非常方便、靈活~

一些必須的插件

{
    // "@vitejs/plugin-legacy": "^1.6.2", // 低版本瀏覽器兼容
    "@vitejs/plugin-vue": "^1.9.3", // vue 支持
    "@vitejs/plugin-vue-jsx": "^1.2.0", // jsx 支持
}

狀態管理器 Pinia

vue新一代狀態管理器,用過 react zustand的同學應該會有很熟悉的感覺

Pinia是一個圍繞Vue 3 Composition API的封裝器。因此,你不必把它作為一個插件來初始化,除非你需要Vue devtools支持、SSR支持和webpack代碼分割的情況

  • 非常輕量化,僅有 1 KB
  • 直觀的API使用,符合直覺,易于學習
  • 模塊化設計,便于拆分狀態
  • 全面的TS支持
// ... 引入相關依賴

interface IUserInfoProps{
  name: string;
  avatar: string;
  mobile: number;
  auths: string[]
}

interface UserState {
  userInfo: Nullable<IUserInfoProps>;
}

// 創建 store
export const useUserStore = defineStore({
  id: 'app-user', // 唯一 ID,可以配合 Vue devtools 使用
  state: (): UserState => ({
    // userInfo
    userInfo: null,
  }),
  getters: {
    getUserInfo(): Nullable<IUserInfoProps> {
      return this.userInfo || null;
    },
  },
  actions: {
    setUserInfo(info: Nullable<IUserInfoProps>) {
      this.userInfo = info ?? null;
    },
    resetState() {
      this.userInfo = null;
    },

    /**
     * @description: fetchUserInfo
     */
    async fetchUserInfo(params: ReqParams) {
      const res = await fetchApi.userInfo(params);
      if (res) {
        this.setUserInfo(res);
      }
    },
  },
})

組件中使用

// TS 類型推斷、異步函數使用都很方便
import { useHomeStore } from '/@/store/modules/home';

const store = useHomeStore();
const userInfo = computed(() => store.getUserInfo);

onMounted(async () => {
  await store.fetchInfo(); // 異步函數
  // ...
});

UI組件按需加載、自動導入

了解基本概念:vite 自帶按需加載(針對js),我們這里主要針對樣式做按需加載處理

方案一:vite-plugin-style-import

import styleImport from 'vite-plugin-style-import'

// 
plugins:[
  styleImport({
    libs: [
      {
        libraryName: 'ant-design-vue',
        esModule: true,
        resolveStyle: (name) => {
          return `ant-design-vue/es/${name}/style/index`
        },
      }
    ]
  })
]

方案二:unplugin-vue-components

推薦使用 unplugin-vue-components 插件

該插件只需在 vite plugin中添加對應 AntDesignVueResolver 即可,也支持自定義的 components 自動注冊,很方便

import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers';
import Components from 'unplugin-vue-components/vite';

// vite.config.ts plugins 添加如下配置
export default defineConfig({
  plugins: [
    Components({
      resolvers: [
        AntDesignVueResolver(), // ant-design-vue
        // ElementPlusResolver(), // Element Plus
        // VantResolver(), // Vant
      ]
    })
  ]
})

當然這里如果沒有你使用的對應的UI框架的 Resolver加載器,也沒關系,也支持自定義配置

Components({
  resolvers: [
    // example of importing Vant
    (name) => {
      // where `name` is always CapitalCase
      if (name.startsWith('Van'))
        return { importName: name.slice(3), path: 'vant' }
    }
  ]
})

另一強悍功能:該插件不僅支持UI框架組件的按需導入,也支持項目組件的自動按需導入

具體表現就是:如我們使用 ant-design-vue的 Card組件或我們自己定義的 components/Icon 等其他組件時,我們不用導入,直接用即可,插件會為我們自動按需導入,結合 TS語法提示,開發效率杠杠的~

配置如下:

Components({
  // allow auto load markdown components under `./src/components/`
  extensions: ['vue'],

  // allow auto import and register components
  include: [/\.vue$/, /\.vue\?vue/],

  dts: 'src/components.d.ts',
})

需要在src目錄下添加 components.d.ts文件配合使用,該文件會被插件自動更新

  • components.d.ts 作用

直接的作用是:在項目下生成對應.d.tstype類型文件,用于語法提示與類型檢測通過

  • 注意

"unplugin-vue-components": "^0.17.2"

當前版本已知問題:issues 174

對于 ant-design-vuenotification / message 組件,當在 js中使用時,該插件不會執行自動導入能力(樣式不會被導入)

最終效果是:message.success('xx')可以創建 DOM元素,但是沒有相關樣式代碼

因為該插件的設計原理是根據 vue template 模板中的組件使用進行處理的,函數式調用時插件查詢不到

解決方案:

  • 改用vite-plugin-style-import 插件
  • 手動全局引入 message組件樣式,import 'ant-design-vue/es/message/style'
  • 在vue組件的 template中手動添加 <a-message /> 供插件索引依賴時使用

依賴按需自動導入

  • unplugin-auto-import

vue相關 defineComponent 、computed 、watch等模塊依賴根據使用,插件自動導入,你無需關心 import,直接使用即可

該插件默認支持:

  • vue
  • vue-router
  • vue-i18n
  • @vueuse/head
  • @vueuse/core
  • ...

當然你也可以自定義配置 unplugin-auto-import

用法如下:

import AutoImport from 'unplugin-auto-import/vite'

export default defineConfig({
  // ...
  plugins: [
    AutoImport({
      imports: [
        'vue',
        'vue-router',
        'vue-i18n',
        '@vueuse/head',
        '@vueuse/core',
      ],
      dts: 'src/auto-imports.d.ts',
    })
  ]
})

需要在src目錄下添加 auto-imports.d.ts文件配合使用,該文件會被插件自動更新

最終效果為:

ref方法我們可以直接使用并有相應的TS語法提示,而不需要手動的去 import { ref } from 'vue'

自定義主題

自定義主題設置參考官方文檔配置即可,兩種常規方式

  1. 按需加載配合 webpack/vite loader屬性修改變量
  2. 全量引入,配合 variables.less自定義樣式覆蓋框架主題樣式

這里我們采用第一種方法通過loader配置配合按需加載食用

vite項目下,請手動安裝 less,pnpm add less -D

css: {
  preprocessorOptions: {
    less: {
      modifyVars: { 'primary-color': 'red' },
      javascriptEnabled: true, // 這是必須的
    },
  },
}

注意:在使用了 unplugin-vue-components進行按需加載配置后,相關 less變量設置需要同步開啟 importStyle: 'less',unplugin-vue-components issues 160

AntDesignVueResolver({ importStyle: 'less' }) // 這里很重要

mock數據

  • vite-plugin-mock 插件

vite plugin配置

viteMockServe({
  ignore: /^\_/,
  mockPath: 'mock',
  localEnabled: true,
  prodEnabled: false,
  // 開發環境無需關心
  // injectCode 只受prodEnabled影響
  // https://github.com/anncwb/vite-plugin-mock/issues/9
  // 下面這段代碼會被注入 main.ts
  injectCode: `
      import { setupProdMockServer } from '../mock/_createProductionServer';

      setupProdMockServer();
      `,
})

根目錄下創建 _createProductionServer.ts文件

import { createProdMockServer } from 'vite-plugin-mock/es/createProdMockServer';

// 批量加載
const modules = import.meta.globEager('./**/*.ts');

const mockModules: any[] = [];
Object.keys(modules).forEach((key) => {
  if (key.includes('/_')) {
    return;
  }
  mockModules.push(...modules[key].default);
});

/**
 * Used in a production environment. Need to manually import all modules
 */
export function setupProdMockServer() {
  createProdMockServer(mockModules);
}

這樣mock目錄下的非 _開頭文件都會被自動加載成mock文件

如:

import Mock from 'mockjs';

const data = Mock.mock({
  'items|30': [
    {
      id: '@id',
      title: '@sentence(10, 20)',
      account: '@phone',
      true_name: '@name',
      created_at: '@datetime',
      role_name: '@name',
    },
  ],
});

export default [
  {
    url: '/table/list',
    method: 'get',
    response: () => {
      const items = data.items;
      return {
        code: 0,
        result: {
          total: items.length,
          list: items,
        },
      };
    },
  },
];

配置好代理直接請求 /api/table/list 就可以得到數據了

Proxy代理

import proxy from './config/vite/proxy';

export default defineConfig({
  // server
  server: {
    hmr: { overlay: false }, // 禁用或配置 HMR 連接 設置 server.hmr.overlay 為 false 可以禁用服務器錯誤遮罩層
    // 服務配置
    port: VITE_PORT, // 類型: number 指定服務器端口;
    open: false, // 類型: boolean | string在服務器啟動時自動在瀏覽器中打開應用程序;
    cors: false, // 類型: boolean | CorsOptions 為開發服務器配置 CORS。默認啟用并允許任何源
    host: '0.0.0.0', // 支持從IP啟動訪問
    proxy,
  },
})

proxy 如下

import {
  API_BASE_URL,
  API_TARGET_URL,
} from '../../config/constant';
import { ProxyOptions } from 'vite';

type ProxyTargetList = Record<string, ProxyOptions>;

const ret: ProxyTargetList = {
  // test
  [API_BASE_URL]: {
    target: API_TARGET_URL,
    changeOrigin: true,
    rewrite: (path) => path.replace(new RegExp(`^${API_BASE_URL}`), ''),
  },
  // mock
  // [MOCK_API_BASE_URL]: {
  //   target: MOCK_API_TARGET_URL,
  //   changeOrigin: true,
  //   rewrite: (path) => path.replace(new RegExp(`^${MOCK_API_BASE_URL}`), '/api'),
  // },
};

export default ret;

環境變量 .env

我這邊是把系統配置放到 config/constant.ts 管理了

為了方便管理不同環境的接口和參數配置,可以使用環境變量 .env,如 .env、.env.local、.env.development、.env.production

配合 dotenv庫 使用還是很方便的

包依賴分析可視化

插件:rollup-plugin-visualizer

import visualizer from 'rollup-plugin-visualizer';

visualizer({
  filename: './node_modules/.cache/visualizer/stats.html',
  open: true,
  gzipSize: true,
  brotliSize: true,
})

代碼壓縮

插件:vite-plugin-compression

import compressPlugin from 'vite-plugin-compression';

compressPlugin({
  ext: '.gz',
  deleteOriginFile: false,
})

Chunk 拆包

如果想把類似 ant-design-vue這樣的包依賴單獨拆分出來,也可以手動配置 manualChunks屬性

// vite.config.ts
build: {
  rollupOptions: {
    output: {
      manualChunks: configManualChunk
    }
  }
}
// optimizer.ts
const vendorLibs: { match: string[]; output: string }[] = [
  {
    match: ['ant-design-vue'],
    output: 'antdv',
  },
  {
    match: ['echarts'],
    output: 'echarts',
  },
];

export const configManualChunk = (id: string) => {
  if (/[\\/]node_modules[\\/]/.test(id)) {
    const matchItem = vendorLibs.find((item) => {
      const reg = new RegExp(`[\\/]node_modules[\\/]_?(${item.match.join('|')})(.*)`, 'ig');
      return reg.test(id);
    });
    return matchItem ? matchItem.output : null;
  }
};

兼容處理

插件:@vitejs/plugin-legacy

兼容不支持 <script type="module">特性的瀏覽器,或 IE瀏覽器

// Native ESM
legacy({
  targets: ['defaults', 'not IE 11']
})

// IE11
// 需要 regenerator-runtime
legacy({
  targets: ['ie >= 11'],
  additionalLegacyPolyfills: ['regenerator-runtime/runtime']
})

效果圖

首頁

vite-vue3-4

包依賴分析可視化,部分截圖

[圖片上傳失敗...(image-1df5e5-1642403544292)]


開啟壓縮、開啟兼容后生產打包的產物

vite-vue3-1

路由和布局

// router/index.ts
import { createRouter, createWebHashHistory } from 'vue-router'
import routes from './router.config'

const router = createRouter({
  history: createWebHashHistory(), //
  routes,
})

// main.ts
app.use(router); // 掛載后可全局使用實列,如模板中 <div @click="$router.push('xx')"></div>

用法如下:

// router.config.ts
import BasicLayout from '/@/layouts/BasicLayout/index.vue'; // 基本布局
import BlankLayout from '/@/layouts/BlankLayout.vue'; // 空布局
import type { RouteRecordRaw } from 'vue-router';

const routerMap: RouteRecordRaw[] = [
  {
    path: '/app',
    name: 'index',
    component: BasicLayout,
    redirect: '/app/home',
    meta: { title: '首頁' },
    children: [
      {
        path: '/app/home',
        component: () => import('/@/views/home/index.vue'),
        name: 'home',
        meta: {
          title: '首頁',
          icon: 'liulanqi',
          auth: ['home'],
        },
      },
      {
        path: '/app/others',
        name: 'others',
        component: BlankLayout,
        redirect: '/app/others/about',
        meta: {
          title: '其他菜單',
          icon: 'xitongrizhi',
          auth: ['others'],
        },
        children: [
          {
            path: '/app/others/about',
            name: 'about',
            component: () => import('/@/views/others/about/index.vue'),
            meta: { title: '關于', keepAlive: true, hiddenWrap: true },
          },
          {
            path: '/app/others/antdv',
            name: 'antdv',
            component: () => import('/@/views/others/antdv/index.vue'),
            meta: { title: '組件', keepAlive: true, breadcrumb: true },
          },
        ],
      },
    ]
  }
  ...
]

權限

  • 支持頁面和菜單級別的權限管理、路由管理
  • 支持按鈕級別的權限管理
  • 支持接口級別的權限管理

幾個關鍵詞:router.addRoutes動態路由、v-auth指令、axios攔截

使用 router.beforeEach 全局路由鉤子

核心邏輯如下,詳情見倉庫代碼 router/permission.ts

// 沒有獲取,請求數據
await permissioStore.fetchAuths();
// 過濾權限路由
const routes = await permissioStore.buildRoutesAction();
// 404 路由一定要放在 權限路由后面
routes.forEach((route) => {
  router.addRoute(route);
});
// hack 方法
// 不使用 next() 是因為,在執行完 router.addRoute 后,
// 原本的路由表內還沒有添加進去的路由,會 No match
// replace 使路由從新進入一遍,進行匹配即可
next({ ...to, replace: true });

使用v-auth指令控制按鈕級別的權限

function isAuth(el: Element, binding: any) {
  const { hasPermission } = usePermission();

  const value = binding.value;
  if (!value) return;
  if (!hasPermission(value)) {
    el.parentNode?.removeChild(el);
  }
}

axios攔截

axios請求攔截器 interceptors.request.use 添加

// 接口權限攔截
const store = usePermissioStoreWithOut();
const { url = '' } = config;
if (!WhiteList.includes(url) && store.getIsAdmin === 0) {
  if (!store.getAuths.includes(url)) {
    return Promise.reject('沒有操作權限');
  }
}

總結

在開始使用 vite + vue3的時候,也是邊踩坑邊學習開發的過程,好在現在社區比較活躍,很多問題都有對應的解決方案,配合文檔和github issue一起食用基本ok,該項目也是參考了 vue-vben-admin的一些實現和代碼管理,本文作為 vue3使用學習記錄~

使用過之后會發現 vue3vue2有著完全不同的開發體驗,現在的 vue3TS有著極好的支持,開發效率和質量上上升了一個層次啊,而且也支持 JSX語法,類似 React的形式開發也是可行的,當然,配合 vue模板使用時,也有著極大的靈活性,可自行根據場景定制自己的代碼,在結合目前的 script setup開發,直接爽到起飛呀~

在使用 vue3composition api開發模式時,一定要摒棄之前的 options api的開發邏輯,配和 hooks可以自由組合拆分代碼,靈活性極高,方便維護管理,不會再出現 vue2時代的整個代碼都擰在一起的情況

一句話:vite + vue3 + setup + ts + vscode volar 插件,誰用誰知道,爽的一批~

倉庫地址:https://github.com/JS-banana/vite-vue3-ts

emoji

參考

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

推薦閱讀更多精彩內容