django项目--通用视图

自定义通用视图

web后端开发的工作就是对数据的增删改查!回顾前面的各种功能的代码,会发现有很多的代码冗余。我们在做重复的事情。django框架的一个强大的功能就是提供了一个即插即用的管理后台,django-admin。接下来,我去做一些代码的优化,抽取代码,自定义通用视图。

一、MyListView

后台管理功能中,总是需要以数据表的形式展示数据。复杂一点会有过滤,查询,分页等操作。但它们的业务逻辑一致,都是接收请求,然后去数据库中获取数据,再进行过滤,分页,然后展示。抽象出一个MyListView来完成这些通用功能,我们只需要做少量配置就可以实现对不同的模型的数据展示。

1. 通用的类属性

找出通用属性,定义在类属性中,子视图类通过覆盖这些属性来完成自定义。

class MyListView(View):
    model = None                            # 模型
    template_name = None                    # 模板名称
    is_paginate = False                     # 是否分页
    per_page = None                         # 每页条数
    page_header = None                      # 页头大标题
    page_option = None                      # 页头小标题
    table_title = None                      # 内容标题

    fields = None                           # 需要展示的字段

2.方法

2.1 get

get方法是用来响应get请求的,根据请求,调用相应模型,查询数据然后渲染模板。

    def get(self, request):
        context = self.get_context_data()
        return render(request, self.get_template_name(), context=context)

2.2 get_queryset

获取查询集,根据是否提供fields属性,来确定返回的数据。如果有搜索,过滤,可以复写该方法。

    def get_queryset(self):
        """获取查询集,如需过滤,请复写此方法"""
        if self.model is not None:
            queryset = self.model._default_manager.all()
        else:
            raise ImproperlyConfigured(
                "%(cls)s 是不是傻,你没有指定model,我怎么知道呢?" % {
                    'cls': self.__class__.__name__
                }
            )
        if self.fields:
            queryset = queryset.only(*self.fields)
        return queryset

2.3 get_context_data

获取视图的上下文变量,如果需要额外添加,可以复写次方法。

    def get_context_data(self, **kwargs):
        """获取视图的上下文变量,如要添加额外变量,请复写此方法"""
        queryset = self.get_queryset()
        if self.is_paginate:
            page_size = self.per_page

            if page_size:
                page = self.paginate_queryset(queryset, page_size)

            else:
                page = self.paginate_queryset(queryset, 10)
        else:
            page = queryset

        context = {
            'page_obj': page,
            'page_header': self.page_header,
            'page_option': self.page_option,
            'table_title': self.table_title
        }

        context.update(kwargs)
        return context

2.4 get_template_name

获取模板名称,如果不提供类属性template_name,会根据模型名称拼接出默认的模板myadmin/model_name/model_name_list.html

    def get_template_name(self):
        """获取模板名"""
        if self.template_name is None:
            self.template_name = 'myadmin/{0}/{0}_list.html'.format(self.model._meta.model_name)

        return self.template_name

2.5 paginate_queryset

分页

    def paginate_queryset(self, queryset, page_size):
        """如果需要进行分页"""
        paginator = Paginator(queryset, page_size)
        try:
            page_number = int(self.request.GET.get('page', 1))
        except Exception as e:
            page_number = 1
        page = paginator.get_page(page_number)
        return page

3. 模板

在模板中将自定义的地方挖坑。obj_list.html

{% load static %}
{% load news_customer_filters %}
<section class="content-header">
    <h1>
        {{ page_header }}
        <small>{{ page_option }}</small>
    </h1>

</section>

<!-- Main content -->
<section class="content container-fluid">
    <div class="box">
        <div class="box-header with-border">
            <h3 class="box-title">{{ table_title }}</h3>
            <div class="box-tools">
                {% block add_button %}
                   
                {% endblock %}
            </div>
        </div>

        <!-- /.box-header -->

        <div class="box-body">
            {% block search_form %}
                
            {% endblock %}
            <table class="table table-bordered">
                <tbody>
                {% block table_content %}
                  
                {% endblock %}
                </tbody>
            </table>


        </div>
        <!-- 分页 -->
        {% if page_obj.paginator %}
            <div class="box-footer clearfix">
                <div class="row">
                    <div class="col-sm-6">
                        <div class="dataTables_info" id="example2_info" role="status" aria-live="polite">
                            总共:{{ page_obj.paginator.count }}条 第{{ page_obj.start_index }}到{{ page_obj.end_index }}条
                        </div>
                    </div>
                    <div class="col-sm-6">
                        <ul class="pagination pagination-sm no-margin pull-right">
                            <li {% if not page_obj.has_previous %}class="disabled"{% endif %}
                                data-page="{{ page_obj.number|add:-1 }}"><a href="#">«</a></li>
                            {% for n in page_obj|page_bar %}
                                <li {% if n == page_obj.number %}class="active" {% endif %} data-page="{{ n }}"><a
                                        href="#">{{ n }}</a></li>
                            {% endfor %}
                            <li {% if not page_obj.has_next %}class="disabled"{% endif %}
                                data-page="{{ page_obj.number|add:1 }}"><a href="#">»</a></li>
                        </ul>
                    </div>
                </div>

            </div>


        {% endif %}
    </div>
</section>
{% block script %}
    <script>
        $(() => {
            {% block query_script %}
                let $queryForm = $('form.user-query');       // 查询表单
                let $queryBtn = $('form.user-query button.query');    // 查询按钮
                let $resetBtn = $('form.user-query button.reset');    // 重置按钮
                // 查询
                $queryBtn.click(() => {
                    let url = $('.sidebar-menu li.active a').data('url');
                    if (!url) {
                        return
                    }
                    $
                        .ajax({
                            url: url,
                            data: $queryForm.serialize(),
                            type: 'GET'
                        })
                        .done((res) => {
                            $('#content').html(res)
                        })
                        .fail(() => {
                            message.showError('服务器超时,请重试!')
                        })
                });

                // 重置
                $resetBtn.click(() => {
                    $queryForm[0].reset();
                    let url = $('.sidebar-menu li.active a').data('url');
                    if (!url) {
                        return
                    }
                    $
                        .ajax({
                            url: url,
                            type: 'GET'
                        })
                        .done((res) => {
                            $('#content').html(res)
                        })
                        .fail(() => {
                            message.showError('服务器超时,请重试!')
                        })
                });
            {% endblock query_script %}
            // 分页
            let $pageLi = $('ul.pagination li').not('.active').not('.disabled');
            $pageLi.click(function () {
                let $this = $(this);

                $
                    .ajax({
                        url: $('.sidebar-menu li.active a').data('url'),
                        data: $queryForm.serialize() + '&page=' + $this.data('page'),
                        type: 'GET'
                    })
                    .done((res) => {
                        $('#content').html(res)
                    })
                    .fail(() => {
                        message.showError('服务器超时,请重试!')
                    })

            });

            // 实例详情
            $('tr').each(function () {
                $(this).children('td:first').click(function () {
                    let url = $(this).data('url');
                    if (!url) {
                        return
                    }
                    $
                        .ajax({
                            url: url,
                            type: 'GET'
                        })
                        .done((res) => {
                            if (res.errno === '4105') {
                                message.showError(res.errmsg)
                            } else if (res.errno === '4101') {
                                message.showError(res.errmsg);
                                setTimeout(() => {
                                    window.location.href = res.data.url
                                }, 1000)

                            } else {
                                $('#content').html(res)
                            }
                        })
                        .fail(() => {
                            message.showError('服务器超时,请重试!')
                        })

                })
            });

            // 添加实例
            $('.box-tools button').click(function () {
                let url = $(this).data('url');
                if (!url) {
                    return
                }
                $
                    .ajax({
                        url: url,
                        type: 'GET'
                    })
                    .done((res) => {
                        if (res.errno === '4105') {
                            message.showError(res.errmsg)
                        } else if (res.errno === '4101') {
                            message.showError(res.errmsg);
                            setTimeout(() => {
                                window.location.href = res.data.url
                            }, 1000)

                        } else {
                            $('#content').html(res)
                        }
                    })
                    .fail(() => {
                        message.showError('服务器超时,请重试!')
                    })
            });
        });
    </script>
{% endblock %}

<!-- /.content -->

4. 自定义过滤器

4.1 page_bar

根据分页页面对象生成分页页码

from django import template

register = template.Library()


@register.filter
def page_bar(page):
    page_list = []
    if page.number != 1:
        page_list.append(1)
    if page.number - 3 > 1:
        page_list.append('...')
    if page.number - 2 > 1:
        page_list.append(page.number - 2)
    if page.number - 1 > 1:
        page_list.append(page.number - 1)
    page_list.append(page.number)
    if page.paginator.num_pages > page.number + 1:
        page_list.append(page.number + 1)
    if page.paginator.num_pages > page.number + 2:
        page_list.append(page.number + 2)
    if page.paginator.num_pages > page.number + 3:
        page_list.append('...')
    if page.paginator.num_pages != page.number:
        page_list.append(page.paginator.num_pages)
    return page_list

二、UpdateView

1. 通用的类属性

找出通用属性,定义在类属性中,子视图类通过覆盖这些属性来完成自定义。

class UpdateView(View):
    model = None
    form_class = None
    template_name = None
    fields = None       # 需要修改的字段
    page_header = None  # 页头大标题
    page_option = None  # 页头小标题
    table_title = None  # 内容标题

    pk = None           # url路径参数名,默认pk 对象的pk字段

2.方法

2.1 get

get方法是用来响应get请求的,根据请求,获取模型对象,生成表单,渲染页面。

    def get(self, request, **kwargs):
        self.obj = self.get_obj(**kwargs)
        context = self.get_context_data()
        return render(request, self.get_template_name(), context=context)

2.2 put

put方法是用来响应put请求的,根据请求,获取模型对象,获取参数,填充表单,校验,修改对象。

    def put(self, request, **kwargs):
        self.obj = self.get_obj(**kwargs)
        self.form_class = self.get_form_class()
        form = self.form_class(QueryDict(request.body), instance=self.obj)
        if form.is_valid():
            self.save(form)
            return json_response(errmsg='修改数据成功!')
        else:
            context = self.get_context_data(form=form)

            return render(request, self.get_template_name(), context=context)

2.3 get_obj_id

获取传入的对象主键

    def get_obj_id(self, **kwargs):
        if self.pk is None:
            self.obj_id = kwargs.get('pk')
        else:
            self.obj_id = kwargs.get(self.pk)

2.4 get_obj

根据传入的对象主键获取需要修改的对象

    def get_obj(self, **kwargs):
        self.get_obj_id(**kwargs)
        if self.model is None:
            raise ImproperlyConfigured('没有设置模型')
        obj = self.model.objects.filter(pk=self.obj_id).first()
        if not obj:
            raise ObjectDoesNotExist('找不到pk=%s的对象' % self.obj_id)
        return obj

2.5 get_template_name

获取模板名称,如果不提供类属性template_name,会根据模型名称拼接出默认的模板myadmin/model_name/model_name_detail.html

    def get_template_name(self):
        """获取模板名"""
        if self.template_name is None:
            self.template_name = 'myadmin/{0}/{0}_detail.html'.format(self.model._meta.model_name)

        return self.template_name

2.6 get_context_data

获取视图的上下文变量,如果需要额外添加,可以复写次方法。

    def get_context_data(self, **kwargs):

        self.form_class = self.get_form_class()
        form = self.form_class(instance=self.obj)
        context = {
            'form': form,
            'page_header': self.page_header,
            'page_option': self.page_option,
            'table_title': self.table_title
        }
        context.update(kwargs)
        return context

2.7get_form_class

获取表单类,如果没有提供,通过给定字段属性自动生成

    def get_form_class(self):
        if self.form_class is None:
            if self.fields is None:
                raise ImproperlyConfigured('你有不设置form,又不设置fields字段,那怎么生成表单呢?')
            return modelform_factory(self.model, fields=self.fields)
        else:
            return self.form_class

2.8 save

保存对象,如果有额外操作,复写此方法。

    def save(self, form):
        if form.has_changed():
            instance = form.save(commit=False)
            instance.save(update_fields=form.changed_data)

3.模板

{% load static %}
{% load news_customer_filters %}
{% load admin_customer_tags %}
{% load admin_customer_filters %}
<section class="content-header">
    <h1>
        {{ page_header }}
        <small>{{ page_option }}</small>
    </h1>

</section>

<!-- Main content -->
<section class="content container-fluid">
    <div class="box">
        <div class="box-header with-border">
            <h3 class="box-title">{{ table_title }}</h3>
        </div>

        <!-- /.box-header -->

        <div class="box-body">
            <form class="form-horizontal">
                {% csrf_token %}
                {% block form_content %}
                    {% for field in form %}
                        {% if field|is_checkbox %}
                            <div class="form-group">

                                <div class="col-sm-offset-1 col-sm-11">

                                    <div class="checkbox">
                                        <label for="{{ field.id_for_label }}">{{ field }}{{ field.label }}</label>
                                    </div>
                                </div>

                            </div>
                        {% elif field|is_url_field %}
                            <div class="form-group {% if field.errors %}has-error{% endif %}">
                                <label for="{{ field.id_for_label }}"
                                       class="col-sm-1 control-label">{{ field.label }}</label>
                                <div class="col-sm-11">
                                    {% for error in field.errors %}
                                        <label class="control-label"
                                               for="{{ field.id_for_label }}">{{ error }}</label>
                                    {% endfor %}
                                    <div class="input-group">
                                        {% add_class field 'form-control' %}
                                        <span class="input-group-btn"><input class="hidden" type="file">
                      <button type="button" class="btn btn-info btn-flat">上传文件</button>
                    </span>
                                    </div>

                                </div>
                            </div>
                        {% else %}
                            <div class="form-group {% if field.errors %}has-error{% endif %}">

                                <label for="{{ field.id_for_label }}"
                                       class="col-sm-1 control-label">{{ field.label }}</label>

                                <div class="col-sm-11">
                                    {% for error in field.errors %}
                                        <label class="control-label"
                                               for="{{ field.id_for_label }}">{{ error }}</label>
                                    {% endfor %}
                                    {% add_class field 'form-control' %}
                                </div>
                            </div>
                        {% endif %}
                    {% endfor %}
                {% endblock %}
            </form>


        </div>
        <div class="box-footer">

            <button type="button" class="btn btn-default back">返回</button>
            <button type="button" {% if form.instance.id %}
                    data-url="{% block update_url %}{% endblock %}"
                    data-type="PUT"
            {% else %}
                    data-url="{% block add_url %}{% endblock %}"
                    data-type="POST"
            {% endif %}
                    class="btn btn-primary pull-right save">保存
            </button>


        </div>

    </div>
</section>
{% block script %}
    <script>
        $(() => {
            {% block back_button %}
                // 返回按钮
                $('.box-footer button.back').click(() => {
                    let url = $('.sidebar-menu li.active a').data('url');
                    if (!url) {
                        return
                    }
                    $('#content').load(
                        url,
                        (response, status, xhr) => {
                            if (status !== 'success') {
                                message.showError('服务器超时,请重试!')
                            }
                        }
                    );
                });
            {% endblock %}

            {% block save_button %}
                $('.box-footer button.save').click(function () {
                    let url = $(this).data('url');
                    if (!url) {
                        return
                    }
                    $
                        .ajax({
                            url: url,
                            data: $('form').serialize(),
                            type: $(this).data('type')
                        })
                        .done((res) => {
                            if (res.errno === '0') {
                                message.showSuccess(res.errmsg);
                                $('#content').load(
                                    $('.sidebar-menu li.active a').data('url'),
                                    (response, status, xhr) => {
                                        if (status !== 'success') {
                                            message.showError('服务器超时,请重试!')
                                        }
                                    }
                                );
                            } else {
                                $('#content').html(res)
                            }
                        })
                        .fail((res) => {
                            message.showError('服务器超时,请重试!')
                        })
                });
            {% endblock %}

            {% block upload %}
                // 上传文件input
                let $fileInput = $('.input-group-btn input');
                let $uploadBtn = $('.input-group-btn button');
                $uploadBtn.click(function () {
                        $(this).prev('input[type="file"]').click()
                    }
                );
                // 自动上传文件
                $fileInput.change(function () {
                    $this = $(this);
                    if ($this.val() !== '') {
                        let formData = new FormData();
                        formData.append('upload', $this[0].files[0]);
                        formData.append('csrfmiddlewaretoken', $('input[name="csrfmiddlewaretoken"]').val());
                        $
                            .ajax({
                                url: '/admin/upload/',
                                // 使用ckeditor_uploader 就使用下面的url
                                // url: '/ckeditor/upload/&responseType=json',
                                type: 'POST',
                                data: formData,
                                processData: false,
                                contentType: false
                            })
                            .done((res) => {
                                if (res.uploaded === '1') {
                                    message.showSuccess('封面图片上传成功!');
                                    $this.parent().prev('input').val(res.url);
                                    // 清空一下
                                    $this.val('')
                                } else {
                                    message.showError('封面图片上传失败!')
                                }
                            })
                            .fail(() => {
                                message.showError('服务器超时, 请重新尝试!')
                            })
                    }
                });
            {% endblock %}

        });
    </script>
{% endblock %}

<!-- /.content -->

模板中用到了自定义过滤器和自定义标签

4. 自定义过滤器和自定义标签

4.1 is_checkbox

判断字段是否checkbox 类型,决定渲染方式

from django.template import Library
from django.forms.widgets import CheckboxInput

register = Library()


@register.filter()
def is_checkbox(field):
    return isinstance(field.field.widget, CheckboxInput)

4.2 is_url_field

判断字段是否url类型,决定渲染方式

@register.filter()
def is_url_field(field):
    return True if 'url' in field.label else False

4.3add_class

给form字段添加class样式

from django.template import Library
from django.shortcuts import reverse

register = Library()


@register.simple_tag()
def add_class(field, class_str):
    return field.as_widget(attrs={'class': class_str})

三、AddView

1.通用类属性

class AddView(View):
    model = None
    form_class = None
    template_name = None
    fields = None       # 新建对象需要的字段
    page_header = None  # 页头大标题
    page_option = None  # 页头小标题
    table_title = None

2.方法

2.1 get

渲染一个对于模型的表单页面返回。

    def get(self, request):
        context = self.get_context_data()
        return render(request, self.get_template_name(), context=context)

2.2 post

根据post参数生成表单,校验,创建新对象。

    def post(self, request):
        self.form_class = self.get_form_class()
        form = self.form_class(request.POST)
        if form.is_valid():
            self.save(form)
            return json_response(errmsg='添加数据成功!')
        else:
            context = self.get_context_data(form=form)

            return render(request, self.get_template_name(), context=context)

2.3 get_context_data

获取视图的上下文变量,如果需要额外添加,可以复写次方法。

    def get_context_data(self, **kwargs):

        self.form_class = self.get_form_class()
        form = self.form_class()
        context = {
            'form': form,
            'page_header': self.page_header,
            'page_option': self.page_option,
            'table_title': self.table_title
        }
        context.update(kwargs)
        return context

2.4 get_template_name

获取模板名称,如果不提供类属性template_name,会根据模型名称拼接出默认的模板`myadmin/model_name/model_name_detail.html

    def get_template_name(self):
        """获取模板名"""
        if self.template_name is None:
            self.template_name = 'myadmin/{0}/{0}_detail.html'.format(self.model._meta.model_name)

        return self.template_name

2.5 get_form_class

获取表单类,如果没有提供,通过给定字段属性自动生成

    def get_form_class(self):
        if self.form_class is None:
            if self.fields is None:
                raise ImproperlyConfigured('你有不设置form,又不设置fields字段,那怎么生成表单呢?')
            return modelform_factory(self.model, fields=self.fields)
        else:
            return self.form_class

2.6 save

保存对象,如果有额外操作,复写此方法。

    def save(self, form):
        form.save()

3. 模板

与UpdateView视图一致

四、继续抽象

仔细观察上面的三个视图,发现有很多共同的方法,还需要将这些代码进一步的抽离,然后通过继承来达到减少冗余和解耦的作用。

1.TemplateView

class TemplateView(View):
    model = None  # 模型
    template_name = None
    page_header = None  # 页头大标题
    page_option = None  # 页头小标题
    table_title = None  # 内容标题
    fields = None  # 需要展示的字段

    def get(self, request):
        context = self.get_context_data()
        return render(request, self.get_template_name(), context=context)

    def get_context_data(self, **kwargs):
        context = {
            'page_header': self.page_header,
            'page_option': self.page_option,
            'table_title': self.table_title
        }

        context.update(kwargs)
        return context

    def get_template_name(self):
        """获取模板名"""
        if self.template_name is None:
            if isinstance(self, MyListView):
                self.template_name = 'myadmin/{0}/{0}_list.html'.format(self.model._meta.model_name)
            else:
                self.template_name = 'myadmin/{0}/{0}_detail.html'.format(self.model._meta.model_name)
        return self.template_name

2. DetailView

class DetailView(View):
    form_class = None

    def get_form_class(self):
        if self.form_class is None:
            if self.fields is None:
                raise ImproperlyConfigured('你有不设置form,又不设置fields字段,那怎么生成表单呢?')
            return modelform_factory(self.model, fields=self.fields)
        else:
            return self.form_class

    def save(self, form):
        form.save()

3.MyListView

class MyListView(TemplateView):

    is_paginate = False                     # 是否分页
    per_page = None                         # 每页条数

    def get_queryset(self):
        """获取查询集,如需过滤,请复写此方法"""
        if self.model is not None:
            queryset = self.model._default_manager.all()
        else:
            raise ImproperlyConfigured(
                "%(cls)s 是不是傻,你没有指定model,我怎么知道呢?" % {
                    'cls': self.__class__.__name__
                }
            )
        if self.fields:
            queryset = queryset.only(*self.fields)
        return queryset

    def get_context_data(self, **kwargs):
        """获取视图的上下文变量,如要添加额外变量,请继承此方法"""
        queryset = self.get_queryset()
        if self.is_paginate:
            page_size = self.per_page

            if page_size:
                page = self.paginate_queryset(queryset, page_size)

            else:
                page = self.paginate_queryset(queryset, 10)
        else:
            page = queryset
        return super().get_context_data(page_obj=page)


    def paginate_queryset(self, queryset, page_size):
        """如果需要进行分页"""
        paginator = Paginator(queryset, page_size)
        try:
            page_number = int(self.request.GET.get('page', 1))
        except Exception as e:
            page_number = 1
        page = paginator.get_page(page_number)
        return page

4.UpdateView

class UpdateView(TemplateView, DetailView):

    pk = None           # url路径参数名,默认pk 对象的pk字段

    def get(self, request, **kwargs):
        self.obj = self.get_obj(**kwargs)
        return super().get(request)

    def put(self, request, **kwargs):
        self.obj = self.get_obj(**kwargs)
        self.form_class = self.get_form_class()
        form = self.form_class(QueryDict(request.body), instance=self.obj)
        if form.is_valid():
            self.save(form)
            return json_response(errmsg='修改数据成功!')
        else:
            context = self.get_context_data(form=form)

            return render(request, self.get_template_name(), context=context)

    def get_obj_pk(self, **kwargs):
        if self.pk is None:
            self.obj_id = kwargs.get('pk')
        else:
            self.obj_id = kwargs.get(self.pk)

    def get_obj(self, **kwargs):
        self.get_obj_pk(**kwargs)
        if self.model is None:
            raise ImproperlyConfigured('没有设置模型')
        obj = self.model.objects.filter(pk=self.obj_id).first()
        if not obj:
            raise ObjectDoesNotExist('找不到pk=%s的对象' % self.obj_id)
        return self.model.objects.filter(pk=self.obj_id).first()

    def get_context_data(self, **kwargs):

        self.form_class = self.get_form_class()
        form = self.form_class(instance=self.obj)
        context = super().get_context_data(form=form)
        context.update(**kwargs)
        return context

    def save(self, form):
        if form.has_changed():
            instance = form.save(commit=False)
            instance.save(update_fields=form.changed_data)

5. AddView

class AddView(TemplateView, DetailView):

    def post(self, request):
        self.form_class = self.get_form_class()
        form = self.form_class(request.POST)
        if form.is_valid():
            self.save(form)
            return json_response(errmsg='添加数据成功!')
        else:
            context = self.get_context_data(form=form)

            return render(request, self.get_template_name(), context=context)

    def get_context_data(self, **kwargs):

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

推荐阅读更多精彩内容