為了實(shí)踐微前端精置,重構(gòu)了自己的導(dǎo)航網(wǎng)站

筆者早期開發(fā)了一個(gè)導(dǎo)航網(wǎng)站,一直想要重構(gòu)锣杂,因?yàn)閼型狭撕脦啄曛耄K于,在了解到微前端大法后下了決心元莫,因?yàn)楣ぷ魃弦恢睕](méi)有機(jī)會(huì)實(shí)踐赖阻,沒(méi)辦法,只能用自己的網(wǎng)站試試踱蠢,思來(lái)想去火欧,訪問(wèn)量最高的也就是這個(gè)破導(dǎo)航網(wǎng)站了,于是用最快的時(shí)間完成了基本功能的重構(gòu)茎截,然后準(zhǔn)備通過(guò)微前端來(lái)擴(kuò)展網(wǎng)站的功能苇侵,比如天氣、待辦企锌、筆記榆浓、秒表計(jì)時(shí)等等,這些功能屬于附加的功能撕攒,可能會(huì)越來(lái)越多陡鹃,所以不能和導(dǎo)航本身強(qiáng)耦合在一起烘浦,需要做到能獨(dú)立開發(fā),獨(dú)立上線萍鲸,所以使用微前端再合適不過(guò)了闷叉。

另外,因?yàn)橛行┕δ芸赡芊浅:?jiǎn)單脊阴,比如秒表計(jì)時(shí)握侧,單獨(dú)創(chuàng)建一個(gè)項(xiàng)目顯得沒(méi)有必要,但是又不想直接寫在導(dǎo)航的代碼里蹬叭,最好是能直接通過(guò)Vue單文件來(lái)開發(fā)藕咏,然后頁(yè)面上動(dòng)態(tài)的進(jìn)行加載渲染,所以會(huì)在微前端方式之外再嘗試一下動(dòng)態(tài)組件秽五。

本文內(nèi)的項(xiàng)目都使用Vue CLI創(chuàng)建孽查,Vue使用的是3.x版本,路由使用的都是hash模式

小程序注冊(cè)

為了顯得高大上一點(diǎn)坦喘,擴(kuò)展功能我把它稱為小程序盲再,首先要實(shí)現(xiàn)的是一個(gè)小程序的注冊(cè)功能,詳細(xì)來(lái)說(shuō)就是:

1.提供一個(gè)表單瓣铣,輸入小程序名稱答朋、描述、圖標(biāo)棠笑、url梦碗、類型(微前端方式還需要配置激活規(guī)則,組件方式需要配置樣式文件的url)蓖救,如下:

【粘貼圖片】

2.導(dǎo)航頁(yè)面上顯示注冊(cè)的小程序列表洪规,點(diǎn)擊后渲染對(duì)應(yīng)的小程序:

【粘貼圖片】

微前端方式

先來(lái)看看微前端的實(shí)現(xiàn)方式,筆者選擇的是qiankun框架循捺。

主應(yīng)用

主應(yīng)用也就是導(dǎo)航網(wǎng)站斩例,首先安裝qiankun

npm i qiankun -S

主應(yīng)用需要做的很簡(jiǎn)單,注冊(cè)微應(yīng)用并啟動(dòng)从橘,然后提供一個(gè)容器給微應(yīng)用掛載念赶,最后打開指定的url即可。

因?yàn)槲?yīng)用列表都存儲(chǔ)在數(shù)據(jù)庫(kù)里恰力,所以需要先獲取然后進(jìn)行注冊(cè)叉谜,創(chuàng)建qiankun.js文件:

// qiankun.js
import { registerMicroApps, start } from 'qiankun'
import api from '@/api';

// 注冊(cè)及啟動(dòng)
const registerAndStart = (appList) => {
  // 注冊(cè)微應(yīng)用
  registerMicroApps(appList)

  // 啟動(dòng) qiankun
  start()
}

// 判斷是否激活微應(yīng)用
const getActiveRule = (hash) => (location) => location.hash.startsWith(hash);

// 初始化小程序
export const initMicroApp = async () => {
  try {
    // 請(qǐng)求小程序列表數(shù)據(jù)
    let { data } = await api.getAppletList()
    // 過(guò)濾出微應(yīng)用
    let appList = data.data.filter((item) => {
      return item.type === 'microApp';
    }).map((item) => {
      return {
        container: '#appletContainer',
        name: item.name,
        entry: item.url,
        activeRule: getActiveRule(item.activeRule)
      };
    })
    // 注冊(cè)并啟動(dòng)微應(yīng)用
    registerAndStart(appList)
  } catch (e) {
    console.log(e);
  }
}

一個(gè)微應(yīng)用的數(shù)據(jù)示例如下:

{
  container: '#appletContainer',
  name: '后閣樓',
  entry: 'http://lxqnsys.com/applets/hougelou/',
  activeRule: getActiveRule('#/index/applet/hougelou')
}

可以看到提供給微應(yīng)用掛載的容器為#appletContainer,微應(yīng)用的訪問(wèn)urlhttp://lxqnsys.com/applets/hougelou/踩萎,注意最后面的/不可省略正罢,否則微應(yīng)用的資源路徑可能會(huì)出現(xiàn)錯(cuò)誤。

另外解釋一下激活規(guī)則activeRule,導(dǎo)航網(wǎng)站的url為:http://lxqnsys.com/d/#/index翻具,微應(yīng)用的路由規(guī)則為:applet/:appletId履怯,所以一個(gè)微應(yīng)用的激活規(guī)則為頁(yè)面urlhash部分,但是這里activeRule沒(méi)有直接使用字符串的方式:#/index/applet/hougelou裆泳,這是因?yàn)楣P者的導(dǎo)航網(wǎng)站并沒(méi)有部署在根路徑叹洲,而是在/d目錄下,所以#/index/applet/hougelou這個(gè)規(guī)則是匹配不到http://lxqnsys.com/d/#/index/applet/hougelou這個(gè)url的工禾,需要這樣才行:/d/#/index/applet/hougelou运提,但是部署的路徑有可能會(huì)變,不方便直接寫到微應(yīng)用的activeRule里闻葵,所以這里使用函數(shù)的方式民泵,自行判斷是否匹配,也就是根據(jù)頁(yè)面的location.hash是否是以activeRule開頭的來(lái)判斷槽畔,是的話代表匹配到了栈妆。

微應(yīng)用

微應(yīng)用也就是我們的小程序項(xiàng)目,根據(jù)官方文檔的介紹Vue 微應(yīng)用厢钧,首先需要在src目錄新增一個(gè)public-path.js

// public-path.js
if (window.__POWERED_BY_QIANKUN__) {
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}

然后修改main.js鳞尔,增加qiankun的生命周期函數(shù):

// main.js
import './public-path';
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'

let app = null
const render = (props = {}) => {
    // 微應(yīng)用使用方式時(shí)掛載的元素需要在容器的范圍下查找
    const { container } = props;
    app = createApp(App)
    app.use(router)
    app.mount(container ? container.querySelector('#app') : '#app')
}

// 獨(dú)立運(yùn)行時(shí)直接初始化
if (!window.__POWERED_BY_QIANKUN__) {
    render();
}

// 三個(gè)生命周期函數(shù)
export async function bootstrap() {
    console.log('[后閣樓] 啟動(dòng)');
}
export async function mount(props) {
    console.log('[后閣樓] 掛載');
    render(props);
}
export async function unmount() {
    console.log('[后閣樓] 卸載');
    app.unmount();
    app = null;
}

接下來(lái)修改打包配置vue.config.js

module.exports = {
    // ...
    configureWebpack: {
        devServer: {
            // 主應(yīng)用需要請(qǐng)求微應(yīng)用的資源,所以需要允許跨域訪問(wèn)
            headers: {
                'Access-Control-Allow-Origin': '*'
            }
        },
        output: {
            // 打包為umd格式
            library: `hougelou`,
            libraryTarget: 'umd'
        }
    }
}

最后早直,還需要修改一下路由配置寥假,有兩種方式:

1.設(shè)置base

import { createRouter, createWebHashHistory } from 'vue-router';

let routes = routes = [
    { path: '/', name: 'List', component: List },
    { path: '/detail/:id', name: 'Detail', component: Detail },
]

const router = createRouter({
    history: createWebHashHistory(window.__POWERED_BY_QIANKUN__ ? '/d/#/index/applet/hougelou/' : '/'),
    routes
})

export default router

這種方式的缺點(diǎn)也是把主應(yīng)用的部署路徑寫死在base里,不是很優(yōu)雅霞扬。

2.使用子路由

import { createRouter, createWebHashHistory } from 'vue-router';
import List from '@/pages/List';
import Detail from '@/pages/Detail';
import Home from '@/pages/Home';

let routes = []

if (window.__POWERED_BY_QIANKUN__) {
    routes = [{
        path: '/index/applet/hougelou/',
        name: 'Home',
        component: Home,
        children: [
            { path: '', name: 'List', component: List },
            { path: 'detail/:id', name: 'Detail', component: Detail },
        ],
    }]
} else {
    routes = [
        { path: '/', name: 'List', component: List },
        { path: '/detail/:id', name: 'Detail', component: Detail },
    ]
}

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

export default router

在微前端環(huán)境下把路由都作為/index/applet/hougelou/的子路由糕韧。

效果如下:

圖片名稱

優(yōu)化

1.返回按鈕

如上面的效果所示,微應(yīng)用內(nèi)部頁(yè)面跳轉(zhuǎn)后喻圃,如果要回到上一個(gè)頁(yè)面只能通過(guò)瀏覽器的返回按鈕兔沃,顯然不是很方便,可以在標(biāo)題欄上添加一個(gè)返回按鈕:

<div class="backBtn" v-if="isMicroApp" @click="back">
  <span class="iconfont icon-fanhui"></span>
</div>
const back = () => {
  router.go(-1);
};

這樣當(dāng)小程序?yàn)槲?yīng)用時(shí)會(huì)顯示一個(gè)返回按鈕级及,但是有一個(gè)問(wèn)題,當(dāng)在微應(yīng)用的首頁(yè)時(shí)顯然是不需要這個(gè)返回按鈕的额衙,我們可以通過(guò)判斷當(dāng)前的路由和微應(yīng)用的activeRule是否一致饮焦,一樣的話就代表是在微應(yīng)用首頁(yè),那么就不顯示返回按鈕:

<div class="backBtn" v-if="isMicroApp && isInHome" @click="back">
  <span class="iconfont icon-fanhui"></span>
</div>
router.afterEach(() => {
  if (!isMicroApp.value) {
    return;
  }
  let reg = new RegExp("^#" + route.fullPath + "?$");
  isInHome.value = reg.test(payload.value.activeRule);
});
圖片名稱

2.微應(yīng)用頁(yè)面切換時(shí)滾動(dòng)位置恢復(fù)

如上面的動(dòng)圖所示窍侧,當(dāng)從列表頁(yè)進(jìn)入到詳情頁(yè)再返回列表時(shí)县踢,列表回到了頂部,這樣的體驗(yàn)是很糟糕的伟件,我們需要記住滾動(dòng)的位置并恢復(fù)硼啤。

可以通過(guò)把url和滾動(dòng)位置關(guān)聯(lián)并記錄起來(lái),在router.beforeEach時(shí)獲取當(dāng)前的滾動(dòng)位置斧账,然后和當(dāng)前的url關(guān)聯(lián)起來(lái)并存儲(chǔ)谴返,當(dāng)router.afterEach時(shí)根據(jù)當(dāng)前url獲取存儲(chǔ)的數(shù)據(jù)并恢復(fù)滾動(dòng)位置:

const scrollTopCache = {};
let scrollTop = 0;

// 監(jiān)聽容器滾動(dòng)位置
appletContainer.value.addEventListener("scroll", () => {
  scrollTop = appletContainer.value.scrollTop;
});

router.beforeEach(() => {
  // 緩存滾動(dòng)位置
  scrollTopCache[route.fullPath] = scrollTop;
});

router.afterEach(() => {
  if (!isMicroApp.value) {
    return;
  }
  // ...
  // 恢復(fù)滾動(dòng)位置
  appletContainer.value.scrollTop = scrollTopCache[route.fullPath];
});
圖片名稱

3.初始url為小程序url的問(wèn)題

正常在關(guān)閉小程序時(shí)會(huì)把頁(yè)面的路由恢復(fù)至頁(yè)面原本的路由煞肾,但是比如我在打開小程序的情況下直接刷新頁(yè)面,那么因?yàn)?code>url滿足小程序的激活規(guī)則嗓袱,所以qiankun會(huì)去加載對(duì)應(yīng)的微應(yīng)用籍救,然而可能這時(shí)頁(yè)面上連微應(yīng)用的容器都沒(méi)有,所以會(huì)報(bào)錯(cuò)渠抹,解決這個(gè)問(wèn)題可以在頁(yè)面加載后判斷初始路由是否是小程序的路由蝙昙,是的話就恢復(fù)一下,然后再去注冊(cè)微應(yīng)用:

if (/\/index\/applet\//.test(route.fullPath)) {
  router.replace("/index");
}
initMicroApp();

Vue組件方式

接下來(lái)看看使用Vue組件的方式梧却,筆者的想法是直接使用Vue單文件來(lái)開發(fā)奇颠,開發(fā)完成后打包成一個(gè)js文件,然后在導(dǎo)航網(wǎng)站上請(qǐng)求該js文件放航,并把它作為動(dòng)態(tài)組件渲染出來(lái)烈拒。

簡(jiǎn)單起見(jiàn)我們直接在導(dǎo)航項(xiàng)目下新建一個(gè)文件夾作為小程序的目錄,這樣可以直接使用項(xiàng)目的打包工具三椿,新增一個(gè)stopwatch測(cè)試組件缺菌,目前目錄結(jié)構(gòu)如下:

【粘貼圖片】

組件App.vue內(nèi)容如下:

<template>
  <div class="countContainer">
    <div class="count">{{ count }}</div>
    <button @click="start">開始</button>
  </div>
</template>

<script setup>
import { ref } from "vue";

const count = ref(0);
const start = () => {
  setInterval(() => {
    count.value++;
  }, 1000);
};
</script>

<style lang="less" scoped>
.countContainer {
  text-align: center;

  .count {
    color: red;
  }
}
</style>

index.js用來(lái)導(dǎo)出組件:

import App from './App.vue';

export default App

// 配置數(shù)據(jù)
const config = {
    width: 450
}

export {
    config
}

為了個(gè)性化,還支持導(dǎo)出它的配置數(shù)據(jù)搜锰。

接下來(lái)需要對(duì)組件進(jìn)行打包伴郁,我們直接使用vue-clivue-cli支持指定不同的構(gòu)建目標(biāo)蛋叼,默認(rèn)為應(yīng)用模式焊傅,我們平常項(xiàng)目打包運(yùn)行的npm run build,其實(shí)運(yùn)行的就是vue-cli-service build命令狈涮,可以通過(guò)選項(xiàng)來(lái)修改打包行為:

vue-cli-service build --target lib --dest dist_applets/stopwatch --name stopwatch --entry src/applets/stopwatch/index.js

上面這個(gè)配置就可以打包我們的stopwatch組件狐胎,選項(xiàng)含義如下:

--target      app | lib | wc | wc-async (默認(rèn)為app應(yīng)用模式,我們使用lib作為庫(kù)打包模式)
--dest        指定輸出目錄 (默認(rèn)輸出到dist目錄歌馍,我們改成dist_applets目錄下)
--name        庫(kù)或 Web Components 模式下的名字 (默認(rèn)值:package.json 中的 "name" 字段或入口文件名握巢,我們改成組件名稱)
--entry       指定打包的入口,可以是.js或.vue文件(也就是組件的index.js路徑)

更詳細(xì)的信息可以移步官方文檔:構(gòu)建目標(biāo)松却、CLI 服務(wù)暴浦。

但是我們的組件是不定的,數(shù)量可能會(huì)越來(lái)越多晓锻,所以直接在命令行輸入命令打包會(huì)非常的麻煩歌焦,我們可以通過(guò)腳本來(lái)完成,在/applets/目錄下新增build.js

// build.js
const { exec } = require('child_process');
const path = require('path')
const fs = require('fs')

// 獲取組件列表
const getComps = () => {
    let res = []
    let files = fs.readdirSync(__dirname)
    files.forEach((filename) => {
        // 是否是目錄
        let dir = path.join(__dirname, filename)
        let isDir = fs.statSync(dir).isDirectory
        // 入口文件是否存在
        let entryFile = path.join(dir, 'index.js')
        let entryExist = fs.existsSync(entryFile)
        if (isDir && entryExist) {
            res.push(filename)
        }
    })
    return res
}
let compList = getComps()
// 創(chuàng)建打包任務(wù)
let taskList = compList.map((comp) => {
    return new Promise((resolve, reject) => {
        exec(`vue-cli-service build --target lib --dest dist_applets/${comp} --name ${comp} --entry src/applets/${comp}/index.js`, (error, stdout, stderr) => {
            if (error) {
                reject(error)
            } else {
                resolve()
            }
        })
    });
})
Promise.all(taskList)
    .then(() => {
        console.log('打包成功');
    })
    .catch((e) => {
        console.error('打包失敗');
        console.error(e);
    })

然后去package.json新增如下命令:

{
  "scripts": {
    "buildApplets": "node ./src/applets/build.js"
  }
}

運(yùn)行命令npm run buildApplets砚哆,可以看到打包結(jié)果如下:

【粘貼圖片】

我們使用其中css文件和umd類型的js文件独撇,打開.umd.js文件看看:

【粘貼圖片】

factory函數(shù)執(zhí)行返回的結(jié)果就是組件index.js里面導(dǎo)出的數(shù)據(jù),另外可以看到引入vue的代碼,這表明Vue是沒(méi)有包含在打包后的文件里的纷铣,這是vue-cli刻意為之的卵史,這在通過(guò)構(gòu)建工具使用打包后的庫(kù)來(lái)說(shuō)是很方便的,但是我們是需要直接在頁(yè)面運(yùn)行的時(shí)候動(dòng)態(tài)的引入組件关炼,不經(jīng)過(guò)打包工具的處理程腹,所以exportsmodule儒拂、define寸潦、require等對(duì)象或方法都是沒(méi)有的,沒(méi)有沒(méi)關(guān)系社痛,我們可以手動(dòng)注入见转,我們使用第二個(gè)else if,也就是我們需要手動(dòng)來(lái)提供exports對(duì)象和require函數(shù)蒜哀。

當(dāng)我們點(diǎn)擊Vue組件類型的小程序時(shí)我們使用axios來(lái)請(qǐng)求組件的js文件斩箫,獲取到的是js字符串,然后使用new Function來(lái)執(zhí)行js撵儿,注入我們提供的exports對(duì)象和require函數(shù)乘客,然后就可以通過(guò)exports對(duì)象獲取到組件導(dǎo)出的數(shù)據(jù),最后再使用動(dòng)態(tài)組件渲染出組件即可淀歇,同時(shí)如果存在樣式文件的話也要?jiǎng)討B(tài)加載樣式文件易核。

<template>
  <component v-if="comp" :is="comp"></component>
</template>
import * as Vue from 'vue';

const comp = ref(null);
const load = async () => {
    try {
      // 加載樣式文件
      if (payload.value.styleUrl) {
        loadStyle(payload.value.styleUrl)
      }
      // 請(qǐng)求組件js資源
      let { data } = await axios.get(payload.value.url);
      // 執(zhí)行組件js
      let run = new Function('exports', 'require', `return ${data}`)
      // 手動(dòng)提供exports對(duì)象和require函數(shù)
      const exports = {}
      const require = () => {
        return Vue;
      }
      // 執(zhí)行函數(shù)
      run(exports, require)
      // 獲取組件選項(xiàng)對(duì)象,扔給動(dòng)態(tài)組件進(jìn)行渲染
      comp.value = exports.stopwatch.default
    } catch (error) {
      console.error(error);
    }
};

執(zhí)行完組件的js后我們注入的exports對(duì)象如下:

【粘貼圖片】

所以通過(guò)exports.stopwatch.default就能獲取到組件的選項(xiàng)對(duì)象傳遞給動(dòng)態(tài)組件進(jìn)行渲染浪默,效果如下:

圖片名稱

大功告成牡直,最后我們?cè)偕晕⑿薷囊幌拢驗(yàn)橥ㄟ^(guò)exports.stopwatch.default獲取組件導(dǎo)出內(nèi)容我們還需要知道組件的打包名稱stopwatch纳决,這顯然有點(diǎn)麻煩碰逸,我們可以改成一個(gè)固定的名稱,比如就叫comp阔加,修改打包命令:

// build.js

// ...
exec(`vue-cli-service build --target lib --dest dist_applets/${comp} --name comp --entry src/applets/${comp}/index.js`, (error, stdout, stderr) => {
  if (error) {
    reject(error)
  } else {
    resolve()
  }
})
// ...

--name參數(shù)由之前的${name}改成寫死comp即可饵史,打包結(jié)果如下:

【粘貼圖片】

exports對(duì)象結(jié)構(gòu)變成如下:

【粘貼圖片】

然后我們就可以通過(guò)comp名稱來(lái)應(yīng)對(duì)任何組件了comp.value = exports.comp.default

當(dāng)然胜榔,小程序關(guān)閉的時(shí)候不要忘記刪除添加的樣式節(jié)點(diǎn)胳喷。

總結(jié)

本文簡(jiǎn)單了嘗試兩種網(wǎng)站功能的擴(kuò)展方式,各位如果有更好的方式的話可以評(píng)論留言分享苗分,線上效果演示地址http://lxqnsys.com/d/

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末牵辣,一起剝皮案震驚了整個(gè)濱河市摔癣,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖择浊,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件戴卜,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡琢岩,警方通過(guò)查閱死者的電腦和手機(jī)投剥,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)担孔,“玉大人江锨,你說(shuō)我怎么就攤上這事「馄” “怎么了啄育?”我有些...
    開封第一講書人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)拌消。 經(jīng)常有香客問(wèn)我挑豌,道長(zhǎng),這世上最難降的妖魔是什么墩崩? 我笑而不...
    開封第一講書人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任氓英,我火速辦了婚禮,結(jié)果婚禮上鹦筹,老公的妹妹穿的比我還像新娘铝阐。我一直安慰自己,他們只是感情好盛龄,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開白布饰迹。 她就那樣靜靜地躺著,像睡著了一般余舶。 火紅的嫁衣襯著肌膚如雪啊鸭。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,631評(píng)論 1 305
  • 那天匿值,我揣著相機(jī)與錄音赠制,去河邊找鬼。 笑死挟憔,一個(gè)胖子當(dāng)著我的面吹牛钟些,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播绊谭,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼政恍,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了达传?” 一聲冷哼從身側(cè)響起篙耗,我...
    開封第一講書人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤迫筑,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后宗弯,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體脯燃,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年蒙保,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了辕棚。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡邓厕,死狀恐怖逝嚎,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情邑狸,我是刑警寧澤懈糯,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站单雾,受9級(jí)特大地震影響赚哗,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜硅堆,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一屿储、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧渐逃,春花似錦够掠、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至面殖,卻和暖如春竖哩,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背脊僚。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工相叁, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人辽幌。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓增淹,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親乌企。 傳聞我的和親對(duì)象是個(gè)殘疾皇子虑润,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

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

  • 微前端架構(gòu)之single-spa single-spa是什么 Single-spa 是一個(gè)將多個(gè)單頁(yè)面應(yīng)用聚合為一...
    yolkpie閱讀 761評(píng)論 0 0
  • —— 一個(gè)基于single-spa的vue2升級(jí)至vue3的項(xiàng)目Author 柯雨 Date 2021-01-2...
    我們都是架構(gòu)師閱讀 1,000評(píng)論 0 4
  • 前言 大家好,我是海怪加酵。最近換到了新部門拳喻,在做智能平臺(tái)相關(guān)的內(nèi)容梁剔。我接到的第一個(gè)任務(wù)就是把以前前端的項(xiàng)目重構(gòu)一次。...
    寫代碼的海怪閱讀 390評(píng)論 0 1
  • 最近負(fù)責(zé)的新項(xiàng)目用到了qiankun舞蔽,寫篇文章分享下實(shí)戰(zhàn)中遇到的一些問(wèn)題和思考。 示例代碼: https://gi...
    fengxianqi閱讀 3,317評(píng)論 2 20
  • 微前端概念 微前端借鑒了微服務(wù)的思想码撰,將前端應(yīng)用分解成一些更小渗柿、更簡(jiǎn)單的能夠獨(dú)立開發(fā)、測(cè)試脖岛、部署的子應(yīng)用朵栖,而在用戶...
    alfalfaw閱讀 577評(píng)論 0 0