Laravel中使用wangEditor

wangEditor 5

官网:https://www.wangeditor.com
demo 示例:https://www.wangeditor.com/demo/
快速开始:https://www.wangeditor.com/v5/getting-started.html
内容处理:https://www.wangeditor.com/v5/content.html
上传图片:https://www.wangeditor.com/v5/menu-config.html#%E4%B8%8A%E4%BC%A0%E5%9B%BE%E7%89%87
上传视频:https://www.wangeditor.com/v5/menu-config.html#%E4%B8%8A%E4%BC%A0%E8%A7%86%E9%A2%91

1、 创建空白编辑器

引入 CSS 定义样式
<link href="https://unpkg.com/@wangeditor/editor@latest/dist/css/style.css" rel="stylesheet">
<style>
  #editor—wrapper {
    border: 1px solid #ccc;
    z-index: 100; /* 按需定义 */
  }
  #toolbar-container { border-bottom: 1px solid #ccc; }
  #editor-container { height: 500px; }
</style>
定义 HTML 结构
<div id="editor—wrapper">
    <div id="toolbar-container"><!-- 工具栏 --></div>
    <div id="editor-container"><!-- 编辑器 --></div>
</div>
引入 JS 创建编辑器
<script src="https://unpkg.com/@wangeditor/editor@latest/dist/index.js"></script>
<script>
const { createEditor, createToolbar } = window.wangEditor

const editorConfig = {
    placeholder: 'Type here...',
    onChange(editor) {
      const html = editor.getHtml()
      console.log('editor content', html)
      // 也可以同步到 <textarea>
    }
}

const editor = createEditor({
    selector: '#editor-container',
    html: '<p><br></p>',
    config: editorConfig,
    mode: 'default', // or 'simple'
})

const toolbarConfig = {}

const toolbar = createToolbar({
    editor,
    selector: '#toolbar-container',
    config: toolbarConfig,
    mode: 'default', // or 'simple'
})
</script>
  • mode: 'default' 默认模式 - 集成了 wangEditor 所有功能
    default模式.png
  • mode: 'simple' 简洁模式 - 仅有部分常见功能,但更加简洁易用
    simple模式.png

2、 内容处理

获取 HTML 和 Text

使用 editor.getHtml() 获取 HTML 内容,可参考 demo。使用 editor.getText() 获取纯文本内容。

推荐使用 HTML 格式存储数据。

设置内容

设置 HTML

【注意】这里的 HTML 内容必须是 wangEditor 生成的(即 editor.getHtml() 返回的) HTML 格式,不可以自己随意写。HTML 格式非常灵活,wangEditor 无法兼容所有的 HTML 格式。

例如,wangEditor 可以识别 <strong>hello</strong> 为加粗,但无法识别 <span style="font-weight: bold;">hello</span> 等其他加粗方式。

创建时设置 HTML

const editor = createEditor({
  html: '<p>hello <strong>world</strong></p>', // 从 editor.getHtml() 获取的 html 内容
  // 其他属性...
})

动态设置 HTML

参考 demo

editor.setHtml('<p>hello <strong>world</strong></p>')

3、 工具栏配置

使用toolbar.getConfig().toolbarKeys获取所有工具栏的配置信息

const toolbarConfig = {}

// 自定义工具栏
toolbarConfig.toolbarKeys = [
      'color',
      'bgColor',
      'clearStyle',
      'justifyLeft',
      'justifyRight',
      'justifyCenter',
      // 菜单组,包含多个菜单
     {
          key: 'group-image',
          title: '图片',
          iconSvg: '<svg viewBox="0 0 1024 1024"><path d="M959.877 128l0.123 0.123v767.775l-0.123 0.122H64.102l-0.122-0.122V128.123l0.122-0.123h895.775zM960 64H64C28.795 64 0 92.795 0 128v768c0 35.205 28.795 64 64 64h896c35.205 0 64-28.795 64-64V128c0-35.205-28.795-64-64-64zM832 288.01c0 53.023-42.988 96.01-96.01 96.01s-96.01-42.987-96.01-96.01S682.967 192 735.99 192 832 234.988 832 288.01zM896 832H128V704l224.01-384 256 320h64l224.01-192z"></path></svg>',
           menuKeys: ["insertImage", "uploadImage"]
     },
     {
           key: 'group-video',
           title: '视频',
           iconSvg: '<svg viewBox="0 0 1024 1024"><path d="M981.184 160.096C837.568 139.456 678.848 128 512 128S186.432 139.456 42.816 160.096C15.296 267.808 0 386.848 0 512s15.264 244.16 42.816 351.904C186.464 884.544 345.152 896 512 896s325.568-11.456 469.184-32.096C1008.704 756.192 1024 637.152 1024 512s-15.264-244.16-42.816-351.904zM384 704V320l320 192-320 192z"></path></svg>',
                menuKeys: ["insertVideo", "uploadVideo"]
     },
     'undo',
     'redo',
]
工具栏显示.png

4、上传图片

上传图片的配置比较复杂,拆分为几个部分来讲解。可参考这个 demo

editorConfig.MENU_CONF['uploadImage'] = {
    // 上传图片的配置
}
服务端地址

必填,否则上传图片会报错。

editorConfig.MENU_CONF['uploadImage'] = {
     server: '/api/upload',
}

【特别注意】服务端 response body 格式要求如下:
上传成功的返回格式:

{
    "errno": 0, // 注意:值是数字,不能是字符串
    "data": {
        "url": "xxx", // 图片 src ,必须
        "alt": "yyy", // 图片描述文字,非必须
        "href": "zzz" // 图片的链接,非必须
    }
}

上传失败的返回格式:

{
    "errno": 1, // 只要不等于 0 就行
    "message": "失败信息"
}

如果你的服务端 response body 无法按照上述格式,可以使用下文的 customInsert

自定义功能

如果用于 Typescript ,则要定义插入函数的类型。

type InsertFnType = (url: string, alt: string, href: string) => void

自定义插入

如果你的服务端 response body 无法按照上文规定的格式,则无法插入图片,提示失败。
但你可以使用 customInsert 来自定义插入图片。

editorConfig.MENU_CONF['uploadImage'] = {
    // 自定义插入图片
    customInsert(res: any, insertFn: InsertFnType) {  // TS 语法
    // customInsert(res, insertFn) {                  // JS 语法
        // res 即服务端的返回结果

        // 从 res 中找到 url alt href ,然后插图图片
        insertFn(url, alt, href)
    },
}
基本配置
editorConfig.MENU_CONF['uploadImage'] = {
    // form-data fieldName ,默认值 'wangeditor-uploaded-image'
    fieldName: 'your-custom-name',

    // 单个文件的最大体积限制,默认为 2M
    maxFileSize: 1 * 1024 * 1024, // 1M

    // 最多可上传几个文件,默认为 100
    maxNumberOfFiles: 10,

    // 选择文件时的类型限制,默认为 ['image/*'] 。如不想限制,则设置为 []
    allowedFileTypes: ['image/*'],

    // 自定义上传参数,例如传递验证的 token 等。参数会被添加到 formData 中,一起上传到服务端。
    meta: {
        token: 'xxx',
        otherKey: 'yyy'
    },

    // 将 meta 拼接到 url 参数中,默认 false
    metaWithUrl: false,

    // 自定义增加 http  header
    headers: {
        Accept: 'text/x-json',
        otherKey: 'xxx'
    },

    // 跨域是否传递 cookie ,默认为 false
    withCredentials: true,

    // 超时时间,默认为 10 秒
    timeout: 5 * 1000, // 5 秒
}

回调函数
editorConfig.MENU_CONF['uploadImage'] = {
    // 上传之前触发
    onBeforeUpload(file: File) { // TS 语法
    // onBeforeUpload(file) {    // JS 语法
        // file 选中的文件,格式如 { key: file }
        return file

        // 可以 return
        // 1\. return file 或者 new 一个 file ,接下来将上传
        // 2\. return false ,不上传这个 file
    },

    // 上传进度的回调函数
    onProgress(progress: number) {  // TS 语法
    // onProgress(progress) {       // JS 语法
        // progress 是 0-100 的数字
        console.log('progress', progress)
    },

    // 单个文件上传成功之后
    onSuccess(file: File, res: any) {  // TS 语法
    // onSuccess(file, res) {          // JS 语法
        console.log(`${file.name} 上传成功`, res)
    },

    // 单个文件上传失败
    onFailed(file: File, res: any) {   // TS 语法
    // onFailed(file, res) {           // JS 语法
        console.log(`${file.name} 上传失败`, res)
    },

    // 上传错误,或者触发 timeout 超时
    onError(file: File, err: any, res: any) {  // TS 语法
    // onError(file, err, res) {               // JS 语法
        console.log(`${file.name} 上传出错`, err, res)
    },
}

5、上传视频

上传视频的配置比较复杂,拆分为几个部分来讲解。可参考这个 demo

editorConfig.MENU_CONF['uploadVideo'] = {
    // 上传视频的配置
}

服务端地址

必填,否则上传视频会报错。

editorConfig.MENU_CONF['uploadVideo'] = {
     server: '/api/upload',
}

【特别注意】服务端 response body 格式要求如下:
上传成功的返回格式:

{
    "errno": 0, // 注意:值是数字,不能是字符串
    "data": {
        "url": "xxx", // 视频 src ,必须
        "poster": "xxx.png" // 视频封面图片 url ,可选
    }
}

// 注意:@wangeditor/editor 版本 >= 5.1.8 才支持 video poster

上传失败的返回格式:

{
    "errno": 1, // 只要不等于 0 就行
    "message": "失败信息"
}

如果你的服务端 response body 无法按照上述格式,可以使用下文的 customInsert

基本配置
editorConfig.MENU_CONF['uploadVideo'] = {
    // form-data fieldName ,默认值 'wangeditor-uploaded-video'
    fieldName: 'your-custom-name',

    // 单个文件的最大体积限制,默认为 10M
    maxFileSize: 5 * 1024 * 1024, // 5M

    // 最多可上传几个文件,默认为 5
    maxNumberOfFiles: 3,

    // 选择文件时的类型限制,默认为 ['video/*'] 。如不想限制,则设置为 []
    allowedFileTypes: ['video/*'],

    // 自定义上传参数,例如传递验证的 token 等。参数会被添加到 formData 中,一起上传到服务端。
    meta: {
        token: 'xxx',
        otherKey: 'yyy'
    },

    // 将 meta 拼接到 url 参数中,默认 false
    metaWithUrl: false,

    // 自定义增加 http  header
    headers: {
        Accept: 'text/x-json',
        otherKey: 'xxx'
    },

    // 跨域是否传递 cookie ,默认为 false
    withCredentials: true,

    // 超时时间,默认为 30 秒
    timeout: 15 * 1000, // 15 秒

    // 视频不支持 base64 格式插入
}

回调函数
editorConfig.MENU_CONF['uploadVideo'] = {
    // 上传之前触发
    onBeforeUpload(file: File) {   // TS 语法
    // onBeforeUpload(file) {      // JS 语法
        // file 选中的文件,格式如 { key: file }
        return file

        // 可以 return
        // 1\. return file 或者 new 一个 file ,接下来将上传
        // 2\. return false ,不上传这个 file
    },

    // 上传进度的回调函数
    onProgress(progress: number) {  // TS 语法
    // onProgress(progress) {       // JS 语法
        // progress 是 0-100 的数字
        console.log('progress', progress)
    },

    // 单个文件上传成功之后
    onSuccess(file: File, res: any) {  // TS 语法
    // onSuccess(file, res) {          // JS 语法
        console.log(`${file.name} 上传成功`, res)
    },

    // 单个文件上传失败
    onFailed(file: File, res: any) {  // TS 语法
    // onFailed(file, res) {          // JS 语法
        console.log(`${file.name} 上传失败`, res)
    },

    // 上传错误,或者触发 timeout 超时
    onError(file: File, err: any, res: any) {  // TS 语法
    // onError(file, err, res) {               // JS 语法
        console.log(`${file.name} 上传出错`, err, res)
    },
}

自定义功能

如果用于 Typescript ,则要定义插入函数的类型。

type InsertFnType = (url: string, poster: string = '') => void

自定义插入

如果你的服务端 response body 无法按照上文规定的格式,则无法插入视频,提示失败。
但你可以使用 customInsert 来自定义插入视频。

editorConfig.MENU_CONF['uploadVideo'] = {
    // 自定义插入视频
    customInsert(res: any, insertFn: InsertFnType) {  // TS 语法
    // customInsert(res, insertFn) {                  // JS 语法
        // res 即服务端的返回结果

        // 从 res 中找到 url poster ,然后插入视频
        insertFn(url, poster)
    },
}

示例代码:

ajax-create.blade.php
<style>
    #editor—wrapper {
        border: 1px solid #ccc;
        z-index: 100; /* 按需定义 */
    }
    #toolbar-container { border-bottom: 1px solid #ccc; }
    #editor-container { height: 500px; }
</style>
<form action="{{route('order.note.ajax.create')}}" method="post">
    @csrf
    <input type="hidden" name="order_id" value="{{$orderId}}"/>
    <div class="mb-3">
        <textarea style="display:none;" class="form-control" name="content" rows="5"></textarea>
        <div id="editor—wrapper">
            <div id="toolbar-container"><!-- 工具栏 --></div>
            <div id="editor-container"><!-- 编辑器 --></div>
        </div>
        <div class="invalid-feedback"></div>
    </div>
    <button type="button" class="btn btn-primary" onclick="ajaxSubmitForm(this)">保存</button>
    <div class="js-ajax-msg"></div>
</form>

<script>

    $(document).ready(function(){
        // 自定义校验图片
        function customCheckImageFn(src, alt, url) {
            if (!src) {
                return
            }
            if (src.indexOf('http') !== 0) {
                return '图片网址必须以 http/https 开头'
            }
            return true

            // 返回值有三种选择:
            // 1. 返回 true ,说明检查通过,编辑器将正常插入图片
            // 2. 返回一个字符串,说明检查未通过,编辑器会阻止插入。会 alert 出错误信息(即返回的字符串)
            // 3. 返回 undefined(即没有任何返回),说明检查未通过,编辑器会阻止插入。但不会提示任何信息
        }

        // 转换图片链接
        function customParseImageSrc(src) {
            if (src.indexOf('http') !== 0) {
                return `http://${src}`
            }
            return src
        }

        const { createEditor, createToolbar } = window.wangEditor

        const editorConfig = {
            MENU_CONF: {},
            placeholder: 'Type here...',
            onChange(editor) {
                const html = editor.getHtml()
                console.log('editor content', html)

                // 同步到 <textarea>
                if('<p><br></p>' !== html){
                    $("textarea[name=content]").text(html)
                }
            }
        }

        // 配置上传图片
        editorConfig.MENU_CONF['uploadImage'] = {
            server: '{{ route("api.upload.file") }}',
            fieldName: 'image',
            // 单个文件的最大体积限制,默认为 2M
            maxFileSize: 1 * 1024 * 1024, // 1M
            // 最多可上传几个文件,默认为 100
            maxNumberOfFiles: 10,
            // 选择文件时的类型限制,默认为 ['image/*'] 。如不想限制,则设置为 []
            allowedFileTypes: ['image/*'],
            // 自定义上传参数,例如传递验证的 token 等。参数会被添加到 formData 中,一起上传到服务端。
            meta: {
                _token: "{{ csrf_token() }}"
            },
            // 将 meta 拼接到 url 参数中,默认 false
            metaWithUrl: false,
            // 自定义增加 http  header
            headers: {
                'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
            },
            // 跨域是否传递 cookie ,默认为 false
            withCredentials: true,
            // 超时时间,默认为 10 秒
            timeout: 5 * 1000, // 5 秒

            // 上传之前触发
            onBeforeUpload(file) {
                // file 选中的文件,格式如 { key: file }
                return file

                // 可以 return
                // 1. return file 或者 new 一个 file ,接下来将上传
                // 2. return false ,不上传这个 file
            },

            // 上传进度的回调函数
            onProgress(progress) {
                // progress 是 0-100 的数字
                console.log('progress', progress)
            },

            // 单个文件上传成功之后
            onSuccess(file, res) {
                console.log(`${file.name} 上传成功`, res)
            },

            // 单个文件上传失败
            onFailed(file, res) {
                console.log(`${file.name} 上传失败`, res)
            },

            // 上传错误,或者触发 timeout 超时
            onError(file, err, res) {
                // onError(file, err, res) {
                console.log(`${file.name} 上传出错`, err, res)
            },
        }

        // 配置上传视频
        editorConfig.MENU_CONF['uploadVideo'] = {

            server: '{{ route("api.upload.file") }}',
            fieldName: 'video',

            // 单个文件的最大体积限制,默认为 10M
            maxFileSize: 5 * 1024 * 1024, // 5M
            // 最多可上传几个文件,默认为 5
            maxNumberOfFiles: 3,
            // 选择文件时的类型限制,默认为 ['video/*'] 。如不想限制,则设置为 []
            allowedFileTypes: ['video/*'],
            // 自定义上传参数,例如传递验证的 token 等。参数会被添加到 formData 中,一起上传到服务端。
            meta: {
                _token: "{{ csrf_token() }}"
            },
            // 将 meta 拼接到 url 参数中,默认 false
            metaWithUrl: false,

            // 自定义增加 http  header
            headers: {
                'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
            },

            // 跨域是否传递 cookie ,默认为 false
            withCredentials: true,

            // 超时时间,默认为 30 秒
            timeout: 15 * 1000, // 15 秒
            // 视频不支持 base64 格式插入
            // 上传之前触发
            onBeforeUpload(file) {
                // file 选中的文件,格式如 { key: file }
                return file

                // 可以 return
                // 1. return file 或者 new 一个 file ,接下来将上传
                // 2. return false ,不上传这个 file
            },

            // 上传进度的回调函数
            onProgress(progress) {
                // progress 是 0-100 的数字
                console.log('progress', progress)
            },

            // 单个文件上传成功之后
            onSuccess(file, res) {
                console.log(`${file.name} 上传成功`, res)
            },

            // 单个文件上传失败
            onFailed(file, res) {
                console.log(`${file.name} 上传失败`, res)
            },

            // 上传错误,或者触发 timeout 超时
            onError(file, err, res) {
                console.log(`${file.name} 上传出错`, err, res)
            },
        }

        // 插入图片
        editorConfig.MENU_CONF['insertImage'] = {
            onInsertedImage(imageNode) {
                if (imageNode == null) return

                const { src, alt, url, href } = imageNode
                console.log('inserted image', src, alt, url, href)
            },
            checkImage: customCheckImageFn, // 也支持 async 函数
            parseImageSrc: customParseImageSrc, // 也支持 async 函数
        }
        // 编辑图片
        editorConfig.MENU_CONF['editImage'] = {
            onUpdatedImage(imageNode) {
                if (imageNode == null) return

                const { src, alt, url } = imageNode
                console.log('updated image', src, alt, url)
            },
            checkImage: customCheckImageFn, // 也支持 async 函数
            parseImageSrc: customParseImageSrc, // 也支持 async 函数
        }


        // 自定义校验视频
        function customCheckVideoFn(src){
            if (!src) {
                return
            }
            if (src.indexOf('http') !== 0) {
                return '视频地址必须以 http/https 开头'
            }
            return true

            // 返回值有三种选择:
            // 1. 返回 true ,说明检查通过,编辑器将正常插入视频
            // 2. 返回一个字符串,说明检查未通过,编辑器会阻止插入。会 alert 出错误信息(即返回的字符串)
            // 3. 返回 undefined(即没有任何返回),说明检查未通过,编辑器会阻止插入。但不会提示任何信息
        }

        // 自定义转换视频
        function customParseVideoSrc(src) {
            if (src.includes('.bilibili.com')) {
                // 转换 bilibili url 为 iframe (仅作为示例,不保证代码正确和完整)
                const arr = location.pathname.split('/')
                const vid = arr[arr.length - 1]
                return `<iframe src="//player.bilibili.com/player.html?bvid=${vid}" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true"> </iframe>`
            }
            return src
        }

        editorConfig.MENU_CONF['insertVideo'] = {
            onInsertedVideo(videoNode) {
                if (videoNode == null) return

                const { src } = videoNode
                console.log('inserted video', src)
            },
            checkVideo: customCheckVideoFn, // 也支持 async 函数
            parseVideoSrc: customParseVideoSrc, // 也支持 async 函数
        }


        const editor = createEditor({
            selector: '#editor-container',
            // html: '<p>hello <strong>world</strong></p>',
            config: editorConfig,
            mode: 'default', // or 'simple'
        })

        const toolbarConfig = {}

        // 自定义工具栏
        toolbarConfig.toolbarKeys = [
            'color',
            'bgColor',
            'clearStyle',
            'justifyLeft',
            'justifyRight',
            'justifyCenter',
            // 菜单组,包含多个菜单
            {
                key: 'group-image',
                title: '图片',
                iconSvg: '<svg viewBox="0 0 1024 1024"><path d="M959.877 128l0.123 0.123v767.775l-0.123 0.122H64.102l-0.122-0.122V128.123l0.122-0.123h895.775zM960 64H64C28.795 64 0 92.795 0 128v768c0 35.205 28.795 64 64 64h896c35.205 0 64-28.795 64-64V128c0-35.205-28.795-64-64-64zM832 288.01c0 53.023-42.988 96.01-96.01 96.01s-96.01-42.987-96.01-96.01S682.967 192 735.99 192 832 234.988 832 288.01zM896 832H128V704l224.01-384 256 320h64l224.01-192z"></path></svg>',
                menuKeys: ["insertImage", "uploadImage"]
            },
            {
                key: 'group-video',
                title: '视频',
                iconSvg: '<svg viewBox="0 0 1024 1024"><path d="M981.184 160.096C837.568 139.456 678.848 128 512 128S186.432 139.456 42.816 160.096C15.296 267.808 0 386.848 0 512s15.264 244.16 42.816 351.904C186.464 884.544 345.152 896 512 896s325.568-11.456 469.184-32.096C1008.704 756.192 1024 637.152 1024 512s-15.264-244.16-42.816-351.904zM384 704V320l320 192-320 192z"></path></svg>',
                menuKeys: ["insertVideo", "uploadVideo"]
            },
            'undo',
            'redo',
        ]

        const toolbar = createToolbar({
            editor,
            selector: '#toolbar-container',
            config: toolbarConfig,
            mode: 'default', // 'simple' or 'default'
        })

    });

</script>
UploadController.php
<?php

namespace App\Http\Controllers\WangEditor;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Storage;

class UploadController extends Controller
{

    public function handle(Request $request)
    {
        try{

            $resData = [
                'errno'=>0,
                'data'=>[]
            ];

            // 图片上传
            if($file = $request->file('image')) {

                // wangeditor/image/20220819/OZqFtvH6vAu52ZMFLeq3w5bfPgeHPhGyqYVUeQFr.png
                $imageFile = $file->store('wangeditor/image/'.date('Ymd'));
                $resData = [
                    'errno'=>0,
                    'data'=>
                        [
                            'url'=> Storage::url($imageFile), // 图片 src ,必须
                            'alt'=> '', // 图片描述文字,非必须
                            'href'=> '' // 图片的链接,非必须
                        ]
                ];

            }

            // 视频上传
            if ($file = $request->file('video')){

                // wangeditor/video/20220819/GOVnjEqXH1aI7hhuAskf05QR8t62vKKXPKThYDfH.mp4
                $videoFile = $file->store('wangeditor/video/'.date('Ymd'));

                $resData = [
                    'errno'=>0,
                    'data'=>
                        [
                            'url'=> Storage::url($videoFile), // 视频 src ,必须
                            'poster'=>'' // 视频封面图片 url ,可选
                        ]
                ];
            }

            return response()->json($resData);

        }catch(\Exception $e){

            return response()->json([
                'errno'=>1,
                'message'=> $e->getMessage()
            ]);
        }

    }

}

web.php
<?php

namespace App\Http\Controllers;
use Illuminate\Support\Facades\Route;


Route::middleware('auth.user')->group(function () {
    // wangeditor 图片、视频上传
    Route::post('api-upload-file',[WangEditor\UploadController::class,'handle'])->name('api.upload.file');

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

推荐阅读更多精彩内容