從0到1構(gòu)建跨平臺(tái)Electron應(yīng)用,這篇文章就夠了

簡介

Electron 基于 Chromium 和 Node.js, 讓你可以使用 HTML, CSS 和 JavaScript 構(gòu)建桌面端應(yīng)用。

這篇文章摘錄了我自己在真實(shí)項(xiàng)目中的要點(diǎn)問題触趴。

可能有的小伙伴會(huì)問,為什么不說一下與Vue渴肉、React或者其他的庫如何結(jié)合開發(fā)冗懦?

這是因?yàn)閂ue或者React以及其他庫都是UI頁面,可以單獨(dú)獨(dú)立開發(fā)仇祭,只需要在代碼打包壓縮后披蕉,使用BrowserWindow中的loadURL或者loadFile加載即可。

剛開始學(xué)習(xí)Electron的小伙伴可以先看 Electron快速入手乌奇,擁有自己的第一個(gè)桌面應(yīng)用 没讲。

Electron核心

Electron實(shí)現(xiàn)跨平臺(tái)桌面應(yīng)用程序的原理

image.png

通過集成瀏覽器內(nèi)核,使用前端的技術(shù)來實(shí)現(xiàn)不同平臺(tái)下的渲染礁苗,并結(jié)合了 Chromium 爬凑、Node.js 和用于調(diào)用系統(tǒng)本地功能的 API 三大板塊。

  1. Chromium 提供強(qiáng)大的 UI 渲染能力试伙,用于顯示網(wǎng)頁內(nèi)容嘁信。
  2. Node.js 用于本地文件系統(tǒng)和操作系統(tǒng)于样,提供GUI 的操作能力(如path、fs潘靖、crypto 等模塊)百宇。
  3. Native API為Electron提供原生系統(tǒng)的 GUI 支持,使用 Electron Api 可以調(diào)用原生應(yīng)用程序接口秘豹。

Electron主要核心點(diǎn)

Electron其實(shí)很簡單携御,基本都是api,我自己整理了主要核心點(diǎn)有進(jìn)程間通信既绕、app生命周期啄刹、BrowserWindowApplication 4個(gè)方面凄贩。

進(jìn)程間通信:處理主進(jìn)程與渲染進(jìn)程的雙向通信誓军。

app生命周期:貫穿桌面端應(yīng)用整個(gè)生命周期(用的不多,但是很重要)疲扎。

BrowserWindow:窗口(大家可以理解成每一個(gè)BWindow都是一個(gè)獨(dú)立的瀏覽器昵时,用于加載渲染我們的前端代碼)。

Application:應(yīng)用程序功能點(diǎn)(為應(yīng)用提供更佳完善的功能點(diǎn))椒丧。

從這4個(gè)主要核心點(diǎn)入手壹甥,開發(fā)人員會(huì)更快進(jìn)入開發(fā)狀態(tài)。

進(jìn)程間通信

ipcMain與ipcRenderer

electron存在多個(gè)進(jìn)程壶熏,那么多進(jìn)程間如何實(shí)現(xiàn)通信句柠,electron官方提供了方法。

image.png

注意:不建議使用同步通信棒假,這會(huì)對應(yīng)用整體性能產(chǎn)生影響溯职。

注意:進(jìn)程通信所傳參數(shù)只能為原始類型數(shù)據(jù)和可被 JSON.stringify 的引用類型數(shù)據(jù)。故不建議使用JSON.stringify帽哑。

異步

通過event.reply(...)將異步消息發(fā)送回發(fā)送者谜酒。

event.reply會(huì)自動(dòng)處理從非主 frame 發(fā)送的消息,建議使用event.sender.send或者win.webContents.send總是把消息發(fā)送到主 frame妻枕。

主進(jìn)程main

const { ipcMain } = require('electron')
// 接收renderer的數(shù)據(jù)
ipcMain.on('asynchronous-message', (event, arg) => { 
    console.log(arg) // 傳遞的數(shù)據(jù) ping
    // event.reply('asynchronous-reply', 'pong') // 發(fā)送數(shù)據(jù)到渲染進(jìn)程
    event.sender.send('asynchronous-reply', 'pong')
})

渲染進(jìn)程renderer

const { ipcRenderer } = require('electron')
// 接收main的數(shù)據(jù)
ipcRenderer.on('asynchronous-reply', (event, arg) => { 
    console.log(arg) // prints "pong"
})
// 發(fā)送數(shù)據(jù)到主進(jìn)程
ipcRenderer.send('asynchronous-message', 'ping')

同步

也可以設(shè)置同步接收(不建議使用),通過event.returnValue設(shè)置

主進(jìn)程main

ipcMain.on('synchronous-message', (event, arg) => {
    event.returnValue = 'sync'
})

渲染進(jìn)程renderer

const syncMsg = ipcRenderer.sendSync('synchronous-message', 'ping');
console.log(syncMsg)

ipcMain.once(channel, listener)只監(jiān)聽實(shí)現(xiàn)一次僻族。

ipcMain.removeListener(channel, listener)刪除某一指定的監(jiān)聽。

ipcMain.removeAllListeners([channel])移除多個(gè)指定 channel 的監(jiān)聽器佳头,無參移除所有鹰贵。

Promise

進(jìn)程發(fā)送消息,并異步等待結(jié)果

ipcMain提供handle康嘉,ipcRenderer提供了invoke。

// 渲染進(jìn)程
ipcRenderer.invoke('some-name', someArgument).then((result) => {
    // ...
})

// 主進(jìn)程
ipcMain.handle('some-name', async (event, someArgument) => {
    const result = await doSomeWork(someArgument)
    return result
})

app

app控制應(yīng)用程序的事件生命周期

const {app, BrowserWindow} = require('electron')
const path = require('path')

function createWindow () {
  const mainWindow = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      preload: path.join(__dirname, 'preload.js')
    }
  })
  mainWindow.loadFile('index.html')
}

app.whenReady().then(() => {
  createWindow()
  app.on('activate', function () {
    if (BrowserWindow.getAllWindows().length === 0) createWindow()
  })
})

app.on('window-all-closed', function () {
  if (process.platform !== 'darwin') app.quit()
})

app的生命周期

1籽前、ready

Electron 完成初始化亭珍,這個(gè)是重點(diǎn)敷钾。

Electron中很多都是需要Electron初始化完成后砾隅,才可以執(zhí)行(如menu铅鲤、窗口......)

為避免出現(xiàn)錯(cuò)誤,建議所有的操作都在ready之后

const { app } = require('electron')
app.on('ready', () => {
    // create menu
    // create browserWindow
})

可以通過app.isReady() 來檢查該事件是否已被觸發(fā)漩怎。

若希望通過Promise實(shí)現(xiàn)众羡,使用 app.whenReady() 侨赡。

2、第二個(gè)實(shí)例創(chuàng)建

當(dāng)運(yùn)行第二個(gè)實(shí)例時(shí)粱侣,聚焦到主窗口

gotTheLock = app.requestSingleInstanceLock();
if (!gotTheLock) {
    app.quit();
} else {
    app.on('second-instance', (event, commandLine, workingDirectory) => {
        let win = mainWindow;
        if (win) {
            if (win.isMinimized()) {
                win.restore();
            }
            if (!win.isFocused()) {
                win.show();
            }
            win.focus();
        }
    });
}

3羊壹、當(dāng)應(yīng)用被激活時(shí)(macos)

app.on('activate', (event, webContents, details) => {
    // 聚焦到最近使用的窗口
});

觸發(fā)此事件的情況很多:
首次啟動(dòng)應(yīng)用程序、嘗試在應(yīng)用程序已運(yùn)行時(shí)或單擊應(yīng)用程序的塢站任務(wù)欄圖標(biāo)時(shí)重新激活它齐婴。

4油猫、當(dāng)所有窗口關(guān)閉,退出應(yīng)用

const { app } = require('electron')
app.on('window-all-closed', () => {
    app.quit()
})

5柠偶、渲染進(jìn)程進(jìn)程奔潰

app.on('render-process-gone', (event, webContents, details) => {
    // 定位原因情妖,可通過log記錄;也可以做重啟retry操作诱担,切記限制count
    // webContents.reloadIgnoringCache(); // 忽略緩存強(qiáng)制刷新頁面
});

details Object

  • reason string - 渲染進(jìn)程消失的原因毡证。 可選值:

    • clean-exit - 以零為退出代碼退出的進(jìn)程
    • abnormal-exit - 以非零退出代碼退出的進(jìn)程
    • killed - 進(jìn)程發(fā)送一個(gè)SIGTERM,否則是被外部殺死的蔫仙。
    • crashed - 進(jìn)程崩潰
    • oom - 進(jìn)程內(nèi)存不足
    • launch-failed - 進(jìn)程從未成功啟動(dòng)
    • integrity-failure - 窗口代碼完整性檢查失敗
  • exitCode Integer - 進(jìn)程的退出代碼情竹,除非在 reasonlaunch-failed 的情況下, exitCode 將是一個(gè)平臺(tái)特定的啟動(dòng)失敗錯(cuò)誤代碼匀哄。

6秦效、應(yīng)用退出

app.on('will-quit', (event, webContents, details) => {
    // timer 或者 關(guān)閉第三方的進(jìn)程
});

app常用方法

const { app } = require('electron')
app.relaunch({ args: process.argv.slice(1).concat(['--relaunch']) }) 
app.exit(0)

1、重啟應(yīng)用

app.relaunch([options])

relaunch被多次調(diào)用時(shí),多個(gè)實(shí)例將會(huì)在當(dāng)前實(shí)例退出后啟動(dòng)涎嚼。

2阱州、所有窗口關(guān)閉

app.exit(0)

所有窗口都將立即被關(guān)閉,而不詢問用戶

3法梯、當(dāng)前應(yīng)用程序目錄

app.getAppPath()

可以理解成獲取應(yīng)用啟動(dòng)(代碼)目錄

4苔货、設(shè)置 "關(guān)于" 面板選項(xiàng)

image.png
app.setAboutPanelOptions({
    applicationName: 'demo',
    applicationVersion: '0.0.1',
    version: '0.0.1'
});

5、注意立哑,部分事件存在著系統(tǒng)的區(qū)別

// 只適用于macOS
app.hide() // 隱藏所有的應(yīng)用窗口夜惭,不是最小化.

BrowserWindow

const { BrowserWindow } = require('electron')

const win = new BrowserWindow({ width: 800, height: 600 })

// 加載地址
win.loadURL('https://github.com')

// 加載文件
win.loadFile('index.html')

自定義窗口

可以通過自定窗口options,實(shí)現(xiàn)自定義樣式铛绰,如桌面窗口頁面诈茧、桌面通知、模態(tài)框捂掰。敢会。曾沈。

const options = { 
    width: 800, 
    height: 600 
}
new BrowserWindow(options)

父子窗口

拖動(dòng)父窗口,子窗口跟隨父窗口而動(dòng)鸥昏。

const top = new BrowserWindow()
const child = new BrowserWindow({ parent: top })

注意:child 窗口將總是顯示在 top 窗口的頂部塞俱。

win.setParentWindow(parent)給窗口設(shè)置父窗口,null取消父窗口
win.getParentWindow()獲取窗口的父窗口
win.getChildWindows()獲取所有子窗口

顯示與隱藏

窗口可以直接展示也可以延后展示吏垮。但每次執(zhí)行show障涯,都會(huì)將當(dāng)前桌面的焦點(diǎn)聚焦到執(zhí)行show的窗口上。

const win = new BrowserWindow({
    show: true, 
})

在加載頁面時(shí)膳汪,渲染進(jìn)程第一次完成繪制時(shí)唯蝶,如果窗口還沒有被顯示,渲染進(jìn)程會(huì)發(fā)出 ready-to-show 事件旅敷。在此事件后顯示窗口將沒有視覺閃爍:

const win = new BrowserWindow({ show: false })
win.once('ready-to-show', () => {
    win.show()
})

win.hide()窗口隱藏生棍,焦點(diǎn)消失

win.show()窗口顯示,獲取焦點(diǎn)

win.showInactive()窗口顯示媳谁,但不聚焦于窗口(多用于當(dāng)其他窗口需要顯示時(shí)涂滴,但是不想中斷當(dāng)前窗口的操作)

win.isVisible()判斷窗口是否顯示

Bounds

1、設(shè)置窗口的size晴音、position

setBounds(bounds[, animate]):同時(shí)設(shè)置size柔纵、position,但是同時(shí)也會(huì)重置窗口锤躁。

setSize(width, height[, animate]): 調(diào)整窗口的寬度和高度搁料。

setPosition(x, y[, animate]):將窗口移動(dòng)到x 和 y。

注意:animate只在macOS上才會(huì)生效系羞。

// 設(shè)置 bounds 邊界屬性
win.setBounds({ x: 440, y: 225, width: 800, height: 600 })
// 設(shè)置單一 bounds 邊界屬性
win.setBounds({ width: 100 })
win.setSize(800, 600)
win.setPosition(440, 225)

2郭计、獲取窗口size、position

getBounds()獲取窗口的邊界信息椒振。

getSize()獲取窗口的寬度和高度昭伸。

getPosition()返回一個(gè)包含當(dāng)前窗口位置的數(shù)組

center()將窗口移動(dòng)到屏幕中央(常用)。

3澎迎、常見問題

win.setSize如果 width 或 height 低于任何設(shè)定的最小尺寸約束庐杨,窗口將對齊到約束的最小尺寸。

win.setPosition有的電腦機(jī)型存在兼容問題夹供,執(zhí)行一次win.setPosition(x,y)不會(huì)生效灵份,需要執(zhí)行兩次。

Application

應(yīng)用包含了很多程序工具哮洽,如Menu填渠、Tray...

Menu

創(chuàng)建原生應(yīng)用菜單和上下文菜單。

image.png

在mac中,菜單展示在應(yīng)用內(nèi)揭蜒;而windows與linux横浑,菜單則會(huì)展示在各個(gè)窗口的頂部剔桨。

可以對某一窗口單獨(dú)設(shè)置或者刪除Menu屉更,但是這只是針對windows、linux生效洒缀。

win.setMenu(menu)設(shè)置為窗口的菜單欄menu(只對windows瑰谜、linux生效)。

win.removeMenu()刪除窗口的菜單欄(只對windows树绩、linux生效)萨脑。

如何全局設(shè)置Menu

const { Menu } = require('electron')

const template = [
{
  label: 'Electron',
  submenu: [
    { role: 'about', label: '關(guān)于' },
    { type: 'separator' },
    { role: 'services', label: '偏好設(shè)置' },
    { type: 'separator' },
    { role: 'hide', label: '隱藏' },
    { role: 'hideOthers', label: '隱藏其他' },
    { type: 'separator' },
    { role: 'quit', label: '退出' }
  ]
},
{
  label: '編輯',
  submenu: [
    { role: 'undo', label: '撤銷' },
    { type: 'separator' },
    { role: 'menu_copy', label: '復(fù)制' },
    { role: 'menu_paste', label: '粘貼' }
  ]
},
{
  label: '窗口',
  submenu: [
    { 
      role: 'minimize',
      label: '最小化',
      click: function (event, focusedWindow, focusedWebContents) {} 
    },
    { role: 'close', label: '關(guān)閉' },
    { role: 'togglefullscreen', label: '全屏', accelerator: 'Cmd+,OrCtrl+,'}
  ]
}];

let menu = Menu.buildFromTemplate(template);
Menu.setApplicationMenu(menu);

role:可以理解為官方命名好的指令,詳見官方menuitemrole

label:我們對指令自定義的展示文字饺饭。

click:觸發(fā)該指令的接受的函數(shù)

Tray

添加圖標(biāo)和上下文菜單到系統(tǒng)通知區(qū)


image.png
const {ipcMain, app, Menu, Tray} = require('electron')

const iconPath = path.join(__dirname, './iconTemplate.png')
const tray = new Tray(iconPath)
const contextMenu = Menu.buildFromTemplate([{
    label: 'tray 1',
    click: () => {}
}, {
    label: 'tray 2',
    click: () => {}
}])
tray.setToolTip('Electron Demo in the tray.')
tray.setContextMenu(contextMenu)

setToolTip設(shè)置鼠標(biāo)指針在托盤圖標(biāo)上懸停時(shí)顯示的文本

setContextMenu設(shè)置圖標(biāo)的內(nèi)容菜單(支持動(dòng)態(tài)添加多個(gè)內(nèi)容)

image.png

dialog

系統(tǒng)對話框

image.png

1.支持多選渤早、默認(rèn)路徑

const { dialog } = require('electron')

const options = {
    title: '標(biāo)題',
    defaultPath: '默認(rèn)地址',
    properties: [  
        openFile, // 容許選擇文件
        openDirectory, // 容許選擇目錄
        multiSelections, // 容許多選
        showHiddenFiles, // 顯示隱藏文件
        createDirectory瘫俊, // 創(chuàng)建新的文件鹊杖,只在mac生效
        promptToCreate,// 文件目錄不存在扛芽,生成新文件夾骂蓖,只在windows生效
    ]
}

dialog.showOpenDialog(win, options); // win是窗口

2.支持過濾文件

過濾文件后綴名gif的文件,顯示所有文件用 * 代替

options.filters = [
    { name: 'Images', extensions: ['gif'] }
]

globalShortcut

鍵盤事件

需要先注冊globalShortcut.register(accelerator, callback)

const { globalShortcut } = require('electron')

globalShortcut.register('CommandOrControl+F', () => {
    // 注冊鍵盤事件是全局性質(zhì)的川尖,各個(gè)窗口都可以觸發(fā)
}) 

globalShortcut.register(accelerator, callback)注冊全局快捷鍵
globalShortcut.isRegistered(accelerator)判斷是否注冊
globalShortcut.unregister(accelerator)取消注冊

注意:應(yīng)用程序退出時(shí)登下,注銷鍵盤事件

app.on('will-quit', () => {
    globalShortcut.unregisterAll()
})

Notification

系統(tǒng)通知

這個(gè)受限于當(dāng)前系統(tǒng)是否支持桌面通知,在mac或windows電腦的設(shè)置中叮喳,需特別注意是否容許通知被芳。

const { Notification } = require('electron');

const isAllowed = Notification.isSupported();
if (isAllowed) {
    const options = {
        title: '標(biāo)題',
        body: '正文文本,顯示在標(biāo)題下方',
        silent: true, // 系統(tǒng)默認(rèn)的通知聲音
        icon: '', // 通知圖標(biāo)
    }
    const notification = new Notification(argConig);
    notification.on('click', () => {  });
    notification.on('show', () => {  });
    notification.on('close', () => {  });
    notification.show();
}

notification.close()關(guān)閉通知

session

管理瀏覽器會(huì)話馍悟、cookie畔濒、緩存、代理設(shè)置等赋朦。

1篓冲、全局

const { session, BrowserWindow } = require('electron')

// 攔截下載
session.defaultSession.on('will-download', (event, item, webContents) => {
    event.preventDefault() // 阻止默認(rèn)行為下載或做指定目錄下載
})

2、單獨(dú)窗口

const win = new BrowserWindow({ width: 800, height: 600 })
win.loadURL('http://github.com')
const ses = win.webContents.session

ses.setProxy(config):設(shè)置代理

ses.cookies:設(shè)置cookies或獲取cookies

3宠哄、瀏覽器UserAgent

設(shè)置UserAgent可以通過app.userAgentFallback全局設(shè)置壹将,也可以通過ses.setUserAgent 設(shè)置。

screen

檢索有關(guān)屏幕大小毛嫉、顯示器诽俯、光標(biāo)位置等的信息。

const primaryDisplay = screen.getPrimaryDisplay() // 獲取光標(biāo)所在屏幕的屏幕信息
const { width, height } = primaryDisplay.workAreaSize // 獲取光標(biāo)下的屏幕尺寸
const allDisplay = screen.getAllDisplays() // 返回?cái)?shù)組,所有的屏幕

screen.getPrimaryDisplay()返回主窗口Display

screen.getAllDisplays()返回所有的窗口Display[]數(shù)組

screen.getDisplayNearestPoint離光標(biāo)最近的窗口

Node

項(xiàng)目中會(huì)用到Node.js暴区,下面是我整理的常用方法闯团。

fs

本地文件讀寫

讀取目錄的內(nèi)容

fs.readdirSync(path[, options])

fs.readdir(path[, options])

讀取文件的內(nèi)容

fs.readFileSync(path[, options])

fs.readFile(path[, options])

文件的信息

const stats = fs.statSync(path);

stats.isDirectory() // 是否為系統(tǒng)目錄
stats.isFile() // 是否為文件
stats.size // 文件大小
。仙粱。房交。

路徑是否存在

fs.existsSync(path)

寫入文件

當(dāng) file 是文件名時(shí),將數(shù)據(jù)寫入文件伐割,如果文件已存在則替換該文件候味。

fs.writeFile(file, data[, options], callback)

fs.writeFileSync(file, data[, options], callback)

移除文件

fs.rmSync(path[, options])

fs.rmdirSync(path[, options])

更改文件的權(quán)限

fs.chmod(path, mode, callback)

fs.chmodSync(path, mode, callback)

注意:mode是8進(jìn)制,可通過parseInt(mode, 8)轉(zhuǎn)化

修改文件夾名

fs.renameSync

拷貝文件到指定地址

fs.copySync

path

拼接絕對路徑

path.resolve([...paths])

拼接路徑

path.join([...paths])

路徑文件夾名字

path.dirname(path)

獲取文件名

path.basename(path)

都是屬于Nodejs隔心,整理到最后懶得整理了~~白群,小伙伴們想研究的話,具體看Node.js

存在的問題

Electron還是會(huì)存在部分坑~~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末硬霍,一起剝皮案震驚了整個(gè)濱河市帜慢,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌唯卖,老刑警劉巖粱玲,帶你破解...
    沈念sama閱讀 219,270評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異耐床,居然都是意外死亡密幔,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評論 3 395
  • 文/潘曉璐 我一進(jìn)店門撩轰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來胯甩,“玉大人,你說我怎么就攤上這事堪嫂≠梭铮” “怎么了?”我有些...
    開封第一講書人閱讀 165,630評論 0 356
  • 文/不壞的土叔 我叫張陵皆串,是天一觀的道長淹办。 經(jīng)常有香客問我,道長恶复,這世上最難降的妖魔是什么怜森? 我笑而不...
    開封第一講書人閱讀 58,906評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮谤牡,結(jié)果婚禮上副硅,老公的妹妹穿的比我還像新娘。我一直安慰自己翅萤,他們只是感情好恐疲,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,928評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般培己。 火紅的嫁衣襯著肌膚如雪碳蛋。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,718評論 1 305
  • 那天省咨,我揣著相機(jī)與錄音肃弟,去河邊找鬼。 笑死茸炒,一個(gè)胖子當(dāng)著我的面吹牛愕乎,可吹牛的內(nèi)容都是我干的阵苇。 我是一名探鬼主播壁公,決...
    沈念sama閱讀 40,442評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼绅项!你這毒婦竟也來了紊册?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,345評論 0 276
  • 序言:老撾萬榮一對情侶失蹤快耿,失蹤者是張志新(化名)和其女友劉穎囊陡,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體掀亥,經(jīng)...
    沈念sama閱讀 45,802評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡撞反,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,984評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了搪花。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片遏片。...
    茶點(diǎn)故事閱讀 40,117評論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖撮竿,靈堂內(nèi)的尸體忽然破棺而出吮便,到底是詐尸還是另有隱情,我是刑警寧澤幢踏,帶...
    沈念sama閱讀 35,810評論 5 346
  • 正文 年R本政府宣布髓需,位于F島的核電站,受9級特大地震影響房蝉,放射性物質(zhì)發(fā)生泄漏僚匆。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,462評論 3 331
  • 文/蒙蒙 一搭幻、第九天 我趴在偏房一處隱蔽的房頂上張望咧擂。 院中可真熱鬧,春花似錦粗卜、人聲如沸屋确。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽攻臀。三九已至焕数,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間刨啸,已是汗流浹背堡赔。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留设联,地道東北人善已。 一個(gè)月前我還...
    沈念sama閱讀 48,377評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像离例,于是被迫代替她去往敵國和親换团。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,060評論 2 355

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