🍋我用Tauri开发的待办效率工具开源了!

开源仓库地址 gitee

Git仓库地址:https://gitee.com/zhanhongzhu/zhanhongzhu.git

应用地址

windows应用地址下载 https://kestrel-task.cn

具体内容

也可以看🎉使用Tauri+vite+koa2+mysql开发了一款待办效率应用 这篇文章。

💻技术栈

  • Tauri: Tauri是一个用于构建现代桌面应用程序的工具,结合了Rust、Vue.js和Web技术,提供了强大的跨平台能力。
  • Vue3: Vue3是流行的JavaScript框架Vue.js的最新版本,具有更好的性能、更好的TypeScript支持和更多的特性。
  • Vite5: Vite是一个现代化的构建工具,Vite5是其最新版本,具有快速的冷启动、热模块替换和原生ES模块支持。
  • Koa2: Koa2是一个基于Node.js的轻量级Web框架,使用异步函数处理中间件,提供了简洁而强大的Web开发体验。
  • MySQL: MySQL是一个流行的关系型数据库管理系统,具有高性能、可靠性和广泛的应用领域,适用于各种规模的应用程序。

我的待办

快速添加待办任务,快速查看任务进度,摘要等。新增标签,分类,更好管理待办任务。通过标签、分类筛选待办任务,方便快捷。

[图片上传失败...(image-c98267-1721834837063)]

[图片上传失败...(image-683b1c-1721834837063)]

OKR目标管理

我的想法是通过OKR管理系列的任务,这样每完成一个小任务,就可以关闭一个小任务,直观又方便,等到所有关键的小任务都完成了,整个任务也就完成了。

[图片上传失败...(image-a61a3-1721834837063)]

番茄工作法

主要是一个计时的时钟,可以在专注计时的时候,专注地完成某项任务,快捷方便,使用番茄工作法,选择一个待完成的任务,将番茄时间设为25分钟,也可以选择其他的区间,专注工作,中途不允许做任何与该任务无关的事。时刻保持专注。

[图片上传失败...(image-9bd314-1721834837063)]

日历视图

打开日历界面,通过视图的形式查看公历或农历日历下每个日期的待办提醒或任务事项。也可以在日历视图,添加任务。

[图片上传失败...(image-8b539b-1721834837063)]

MEMO快速记录

为了更好地帮你捕捉想法与灵感,提供了快速记录的输入框。专注记录想法,无需思考标题和排版。控制记录长度,降低记录压力,快速捕捉。

[图片上传失败...(image-8e11af-1721834837063)]

统计功能

展示了每天的待办数,以及每天新增的待办数量。

[图片上传失败...(image-317334-1721834837063)]

展示功能点

打包发布版本

脚本命令

  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview",
    "tauri": "tauri",
    "pub": "cd build && node ./updateVersion.js && pnpm tauri build && node ./publish.js"
  }
npm run pub

更新版本号以及更新publicKey

//build/publish.js
import fs from 'fs'
// 读取 tauri.conf.json
const tauriConf = JSON.parse(fs.readFileSync('../src-tauri/tauri.conf.json', 'utf8'));
let newVersion = tauriConf.package.version; //更新的版本号

// 读取 update.json
let updateJson = JSON.parse(fs.readFileSync('update.json', 'utf8'));
// 更新 update.json version
updateJson.version = newVersion;

//获取版本更新的内容
//签名、版本路径、发版日期
const signature =  fs.readFileSync(`../src-tauri/target/release/bundle/msi/kestrel-task_${newVersion}_x64_zh-CN.msi.zip.sig`, 'utf8');
updateJson.platforms['windows-x86_64'].signature = signature;
updateJson.platforms['windows-x86_64'].url = `https://kestrel-task.cn/kestrel-task_${newVersion}_x64_zh-CN.msi.zip`
updateJson.pub_date = new Date();
fs.writeFileSync('update.json', JSON.stringify(updateJson, null, 2));

版本json信息

//build/update.json

{
  "version": "1.0.8",
  "notes": "kestrel-task",
  "pub_date": "2024-03-23T04:23:39.799Z",
  "platforms": {
    "windows-x86_64": {
      "signature": "dW50cnVzdGVkIGNvbW1lbnQ6IHNpZ25hdHVyZSBmcm9tIHRhdXJpIHNlY3JldCBrZXkKUlVSNVRCUit5Zjc1Y3JLV085djl6eTMza2NqMXFIV0paNkl2ckgrTGZTRm9wcEJwcUlkaTBhM2hvN3pSVkRUZXlTZ2NSejJremg2Vklja041VkZmdGlZZ0hxTGVVM2xlL3dFPQp0cnVzdGVkIGNvbW1lbnQ6IHRpbWVzdGFtcDoxNzExMTY3ODE5CWZpbGU6a2VzdHJlbC10YXNrXzEuMC44X3g2NF96aC1DTi5tc2kuemlwCldSWVdwb0dwRU1aQUJ2ckFra2FTMjBkcnZtL0FWU3grd3MzeHZVTDhWRFFFUC9QWkpzdUNvUG9HZXBrVmhWMkoxTkpGc2pkYU5rRHYwcVdHdlk5dkFBPT0K",
      "url": "https://kestrel-task.cn/kestrel-task_1.0.8_x64_zh-CN.msi.zip"
    }
  }
}

更新tauri.conf.json版本信息

// build/updateVersion.js
import fs from 'fs' // 读取 tauri.conf.json
const tauriConf = JSON.parse(fs.readFileSync('../src-tauri/tauri.conf.json', 'utf8'));
let version = tauriConf.package.version; //更新的版本号

//更新版本号
let versionParts = version.split('.').map(Number);
versionParts[2] += 1;
let newVersion =versionParts.join('.');
// 更新 tauri.conf.json version
tauriConf.package.version = newVersion;
fs.writeFileSync('../src-tauri/tauri.conf.json', JSON.stringify(tauriConf, null, 2));

使用tauri的http模块

此处进行简单的封装,接口请求在控制台无法被查看到。如果觉得不方便,完全可以使用axios库。也是可以的。

import { http } from "@tauri-apps/api";

export function request(config) {
  return new Promise((resolve, reject) => {
    http
      .fetch("https://kestrel-task.cn" + config.url, {
        method: "POST",
        body: http.Body.json(config.data),
        headers: {
          Authorization: token,
        },
      })
      .then((res) => {
        resolve(res.data.data);
      })
      .catch((err) => {
        reject(err);
      });
  });
}

http封装get请求

export function requestGet(config, d) {
  let url = d ? config.url : "https://kestrel-task.cn" + config.url;
  return new Promise((resolve, reject) => {
    http
      .fetch(url, {
        method: "get",
        headers: {
          Authorization: token,
        },
      })
      .then((res) => {
        resolve(d ? res.data : res.data.data);
      })
      .catch((err) => {
        reject(err);
      });
  });
}

使用封装的request函数

export const login = (data) => {
  return request({
    url: "/web/login",
    method: "post",
    data,
  });
};

使用WebviewWindow封装公共的窗口

import { WebviewWindow } from '@tauri-apps/api/window'
import { emit } from '@tauri-apps/api/event'

// 创建新窗口
export async function createWin(args) {
    await emit('win-create', args)
}

// 获取窗口
export async function getWin(label) {
    return await WebviewWindow.getByLabel(label)
}

/**
 * @desc 设置窗口
 * @param type {string} 'show'|'hide'|'close'|'min'|'max'|'max2min'|'exit'|'relaunch'
 */
export async function setWin(type) {
    await emit('win-' + type)
}

// 登录窗口
export async function loginWin() {
    await createWin({
        label: 'Login',
        title: '登录',
        url: '/login',
        width: 320,
        height: 420,
        resizable: false,
        alwaysOnTop: true,
    })
}

// ...
/**
 * @desc    封装新开多窗体
 */

import {
  WebviewWindow,
  appWindow,
  getAll,
  getCurrent,
} from "@tauri-apps/api/window";
import { relaunch, exit } from "@tauri-apps/api/process";
import { emit, listen } from "@tauri-apps/api/event";

import { setWin } from "./actions.js";

// 系统参数配置
export const windowConfig = {
  label: null, // 窗口唯一label
  title: "", // 窗口标题
  url: "", // 路由地址url
  width: 900, // 窗口宽度
  height: 640, // 窗口高度
  minWidth: null, // 窗口最小宽度
  minHeight: null, // 窗口最小高度
  x: null, // 窗口相对于屏幕左侧坐标
  y: null, // 窗口相对于屏幕顶端坐标
  center: true, // 窗口居中显示
  resizable: true, // 是否支持缩放
  maximized: false, // 最大化窗口
  decorations: true, // 窗口是否无边框及导航条
  alwaysOnTop: false, // 置顶窗口
};

class Windows {
  constructor() {
    this.mainWin = null;
  }

  // 获取窗口
  getWin(label) {
    return WebviewWindow.getByLabel(label);
  }

  // 获取全部窗口
  getAllWin() {
    return getAll();
  }

  // 创建新窗口
  async createWin(options) {
    const args = Object.assign({}, windowConfig, options);

    // 判断窗口是否存在
    const existWin = getAll().find((w) => w.label == args.label);
    if (existWin) {
      if (existWin.label.indexOf("main") == -1) {
        await existWin?.unminimize();
        await existWin?.setFocus();
        return;
      }
      await existWin?.close();
    }

    // 创建窗口对象
    let win = new WebviewWindow(args.label, args);

    // 是否最大化
    if (args.maximized && args.resizable) {
      win.maximize();
    }

    // 窗口创建完毕/失败
    win.once("tauri://created", async () => {
      console.log("window create success!");
    });

    win.once("tauri://error", async () => {
      console.log("window create error!");
    });
  }

  // 开启主进程监听事件
  async listen() {
    // 创建新窗体
    await listen("win-create", (event) => {
      this.createWin(JSON.parse(event.payload));
    });

    // 显示窗体
    await listen("win-show", async (event) => {
      if (appWindow.label.indexOf("main") == -1) return;
      await appWindow.show();
      await appWindow.unminimize();
      await appWindow.setFocus();
    });

    // 隐藏窗体
    await listen("win-hide", async (event) => {
      if (appWindow.label.indexOf("main") == -1) return;
      await appWindow.hide();
    });

    // 退出应用
    await listen("win-exit", async (event) => {
      setWin("logout");
      await exit();
    });

    // 重启应用
    await listen("win-relaunch", async (event) => {
      await relaunch();
    });

    // 主/渲染进程传参
    await listen("win-setdata", async (event) => {
      await emit("win-postdata", JSON.parse(event.payload));
    });
  }
}

export default Windows;

封装Echart组件,便于使用

[图片上传失败...(image-e7e272-1721834837063)]

<template>
    <div ref="MyEcharts" :style="{ height: height, width: width }"></div>
  </template>
  
  <script>
  import * as echarts from 'echarts'
  import T from './echarts-theme-T.js'
  echarts.registerTheme('T', T)
  const unwarp = obj => obj && (obj.__v_raw || obj.valueOf() || obj)
  export default {
      ...echarts,
      name: 'Charts',
      props: {
          // 高度
          height: { type: String, default: '100%' },
          // 宽度
          width: { type: String, default: '100%' },
          // 是否无数据
          nodata: { type: Boolean, default: false },
          // 配置项
          option: { type: Object, default: () => {} }
      },
      data() {
          return {
              isActivat: false,
              myChart: null,
              MyEcharts:null
          }
      },
      watch: {
          option: {
              deep: true,
              handler(v) {
                  unwarp(this.myChart).setOption(v)
              }
          }
      },
      computed: {
          myOptions: function() {
              return this.option || {}
          }
      },
      activated() {
          if (!this.isActivat) {
              this.$nextTick(() => {
                  this.myChart.resize()
              })
          }
      },
      deactivated() {
          this.isActivat = false
      },
      mounted() {
          this.isActivat = true
          this.$nextTick(() => {
              this.draw()
          })
      },
      methods: {
          draw() {
              const myChart = echarts.init(this.$refs.MyEcharts, 'T')
              myChart.setOption(this.myOptions)
              this.myChart = myChart
              window.addEventListener('resize', () => myChart.resize())
          }
      }
  }
  </script>

echart主题模块

//echarts-theme-T.js
const T = {
    color: ['#409EFF', '#36CE9E', '#f56e6a', '#626c91', '#edb00d', '#909399'], // 颜色数组
    grid: { // 网格
        left: '3%', // 左边距
        right: '3%', // 右边距
        bottom: '10', // 下边距
        top: '40', // 上边距
        containLabel: true // 包含标签
    },
    legend: { // 图例
        textStyle: { // 文本样式
            color: '#999' // 颜色
        },
        inactiveColor: 'rgba(128,128,128,0.4)' // 不活跃颜色
    },
    categoryAxis: { // 类别轴
        axisLine: { // 轴线
            show: true, // 显示
            lineStyle: { // 线条样式
                color: 'rgba(128,128,128,0.2)', // 颜色
                width: 1 // 宽度
            }
        },
        axisTick: { // 刻度线
            show: false, // 不显示
            lineStyle: { // 线条样式
                color: '#000' // 颜色
            }
        },
        axisLabel: { // 轴标签
            color: '#999' // 颜色
        },
        splitLine: { // 分隔线
            show: false, // 不显示
            lineStyle: { // 线条样式
                color: ['#eee'] // 颜色
            }
        },
        splitArea: { // 分隔区域
            show: false, // 不显示
            areaStyle: { // 区域样式
                color: ['rgba(255,255,255,0.01)', 'rgba(0,0,0,0.01)'] // 颜色
            }
        }
    },
    valueAxis: { // 数值轴
        axisLine: { // 轴线
            show: false, // 不显示
            lineStyle: { // 线条样式
                color: '#999' // 颜色
            }
        },
        splitLine: { // 分隔线
            show: true, // 显示
            lineStyle: { // 线条样式
                color: 'rgba(128,128,128,0.2)' // 颜色
            }
        }
    }
}

export default T

封装公共的弹窗组件

[图片上传失败...(image-f08344-1721834837063)]

<template>
  <el-dialog class="my-dialog" draggable v-bind="$attrs" v-model="modelValue" :modal-append-to-body="modalAppendToBody"
    :append-to-body="appendToBody" :fullscreen="fullscreen" :close-on-click-modal="closeOnClickModal"
    :close-on-press-escape="closeOnPressEscape" :width="comWidth" :top="top" @closed="closed">
    <template v-slot:title>
      <slot name="title">
        <span class="my-dialog-title">{{ dialogTitle || ''}}</span>
      </slot>
    </template>
    <div v-loading="loading" class="body-content" :style="{'height':comHeight}">
      <slot></slot>
    </div>
    <template #footer>
      <div class="dialog-footer" v-if="closeBtn">
        <el-button type="close" size="small1" @click="closed">关闭</el-button>
      </div>
    </template>
  </el-dialog>
</template>
<script setup>
import { computed } from 'vue'
const props = defineProps(
  {
    visible: { type: Boolean, default: false }, // 是否可见
    loading: { type: Boolean, default: false }, // 是否加载中
    top: { type: String, default: '20vh' }, // 距离顶部的距离
    fullscreen: { type: Boolean, default: false }, // 是否全屏
    size: { type: String, default: 'big' }, // 大小
    width: { type: [Number, String], default: 0 }, // 宽度
    height: { type: [Number, String], default: '55vh' }, // 宽度
    dialogTitle: { type: String, default: '' }, // 弹出框标题
    modalAppendToBody: { type: Boolean, default: false }, // 是否将弹出框插入到body中
    appendToBody: { type: Boolean, default: false }, // 是否将内容插入到body中
    closeOnClickModal: { type: Boolean, default: false }, // 是否在点击模态框时关闭
    closeOnPressEscape: { type: Boolean, default: false }, // 是否在按下ESC键时关闭
    dblclickDisabled: { type: Boolean, default: false }, // 是否禁用双击放大
    closeBtn: { type: Boolean, default: false }, // 关闭按钮
  },
  ['modelValue']
)

const comWidth = computed(() => {
  if (props.size === 'small') {
    return props.width || '30%'
  } else if (props.size === 'middle') {
    return props.width || '40%'
  } else if (props.size === 'big') {
    return props.width || '60%'
  }
  return props.width || '40%'
})

const comHeight = computed(() => {
  return props.height || '55vh'
})

const emit = defineEmits(['update:modelValue','closed'])
const closed = () => {
  emit('update:modelValue')
  emit('closed',false)
}
</script>

封装ResizeObserver函数

主要是监听元素的变化或者窗口的变化。有一部分用到了可以拉伸的左右布局的模块。

//directive/index.js
// 监听元素大小变化的指令
const map = new WeakMap();
const ob = new ResizeObserver((entries) => {
  for (const entry of entries) {
    // 获取dom元素的回调
    const handler = map.get(entry.target);
    //存在回调函数
    if (handler) {
      // 将监听的值给回调函数
      handler({
        width: entry.borderBoxSize[0].inlineSize,
        height: entry.borderBoxSize[0].blockSize,
      });
    }
  }
});

export const Resize = {
  mounted(el, binding) {
    //将dom与回调的关系塞入map
    map.set(el, binding.value);
    //监听el元素的变化
    ob.observe(el);
  },
  unmounted(el) {
    //取消监听
    ob.unobserve(el);
  },
};
const directives = { Resize };

const registerDirective = (app) => {
  Object.keys(directives).forEach((key) => {
    app.directive(key, directives[key]);
  });
};
export default registerDirective;

公共拖曳布局的函数

主要是用于左右布局宽度的变化,可以使用鼠标进行拖曳,改变左右盒子的高度。

export const useCommon = ()=>{
    function setLayoutDrag(dragId) {
        const resize = document.getElementById(dragId)
        let previousElement = resize.previousSibling
        let nextElement = resize.nextSibling
        let previousTag = previousElement.tagName
        let nextTag = nextElement.tagName
        resize.onmousedown = (e) => {
          const startX = e.clientX
          const startY = e.clientY
          let type = ''
          if (previousTag === 'ASIDE' && nextTag === 'MAIN') {
            type = 'ASIDE-MAIN'
          } else if (previousTag === 'MAIN' && nextTag === 'ASIDE') {
            type = 'MAIN-ASIDE'
          } else if (
            (previousTag === 'HEADER' && nextTag === 'MAIN') ||
            (previousTag === 'FOOTER' && nextTag === 'MAIN')
          ) {
            type = 'HEADER-MAIN'
          } else if (
            (previousTag === 'MAIN' && nextTag === 'HEADER') ||
            (previousTag === 'MAIN' && nextTag === 'FOOTER')
          ) {
            type = 'MAIN-HEADER'
          }
          let initWidth = 0,
            initHeight = 0
          if (type === 'ASIDE-MAIN') {
            initWidth = previousElement.clientWidth // 初始位置
          } else if (type === 'MAIN-ASIDE') {
            initWidth = nextElement.clientWidth // 初始位置
          } else if (type === 'HEADER-MAIN') {
            initHeight = previousElement.clientHeight
          } else if (type === 'MAIN-HEADER') {
            initHeight = nextElement.clientHeight
          }
          document.onmousemove = (k) => {
            const endX = k.clientX
            const endY = k.clientY
            let moveLen = endX - startX // 横向移动宽度
            let moveHeight = endY - startY // 纵向移动高度
            switch (type) {
              case 'ASIDE-MAIN':
                let asideMainWidth = initWidth + moveLen
                if (moveLen < 0) {
                  // 向左移
                  if (asideMainWidth > 400) {
                    // 左侧剩90
                    previousElement.style.width = asideMainWidth + 'px'
                  }
                } else {
                  // 向右移动
                  if (nextElement.clientWidth > 400) {
                    // 右侧剩90
                    previousElement.style.width = asideMainWidth + 'px'
                  }
                }
                break
              case 'MAIN-ASIDE':
                let mainAsideWidth = initWidth - moveLen
                if (moveLen < 0) {
                  // 向左移
                  if (previousElement.clientWidth > 400) {
                    // 左侧剩90
                    nextElement.style.width = mainAsideWidth + 'px'
                  }
                } else {
                  // 向右移动
                  if (mainAsideWidth > 400) {
                    nextElement.style.width = mainAsideWidth + 'px'
                  }
                }
                break
              case 'HEADER-MAIN': {
                let headerMainHeight = initHeight + moveHeight
                if (moveHeight < 0) {
                  // 向上移
                  if (headerMainHeight > 60) {
                    // 上侧剩90
                    previousElement.style.height = headerMainHeight + 'px'
                  }
                } else {
                  // 向下移动
                  if (nextElement.clientHeight > 60) {
                    // 下侧剩90
                    previousElement.style.height = headerMainHeight + 'px'
                  }
                }
                break
              }
              case 'MAIN-HEADER': {
                let mainHeaderHeight = initHeight - moveHeight
                if (moveHeight < 0) {
                  // 向上移
                  if (previousElement.clientHeight > 60) {
                    // 左侧剩90
                    nextElement.style.height = mainHeaderHeight + 'px'
                  }
                } else {
                  // 向下移动
                  if (mainHeaderHeight > 60) {
                    nextElement.style.height = mainHeaderHeight + 'px'
                  }
                }
                break
              }
      
              default:
            }
          }
          document.onmouseup = (evt) => {
            document.onmousemove = null
            document.onmouseup = null
            resize.releaseCapture && resize.releaseCapture()
          }
          resize.setCapture && resize.setCapture()
          return false
        }
      }

      return {
        setLayoutDrag
      }
}

公共布局

此处的可以自己查看代码。

[图片上传失败...(image-be7369-1721834837063)]

invoke调用rust函数,关闭splash

import { invoke } from '@tauri-apps/api/tauri'

onMounted(() => {
  // window.addEventListener('contextmenu', (e) => e.preventDefault(), false)
  document.addEventListener('DOMContentLoaded', () => {
    // This will wait for the window to load, but you could
    // run this function on whatever trigger you want
    setTimeout(() => {
      invoke('close_splashscreen')
    }, 1000)
  })
})

🎉结语 感兴趣的可以试试,有不清楚的问题,关于tauri开发方面的问题,也可以一起交流。欢迎加我:zhan_1337608148。一起成长,一起进步。

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

推荐阅读更多精彩内容