Loading

第八篇:forms组件,cookie和session

forms组件

常常用于注册页面的用户输入的数据进行校验

  • 渲染页面
  • 校验数据
  • 展示信息

在使用forms组件的时候,需要在应用下新建一个py文件,例如:
myforms.py

在文件中写一个类:

from django import forms
 
 
class MyRegForm(forms.Form):
    # 用户名最少3位最多8位
    username = forms.CharField(max_length=8,min_length=3)
    password = forms.CharField(max_length=8,min_length=3)
    # email字段必须填写符合邮箱格式的数据
    email = forms.EmailField()

forms校验数据

数据校验语法:

  • .is_valid() 校验是否符合规则
  • .cleaned_data 获取通过校验的数据
  • .errors 获取校验失败以及校验失败的原因,返回的是一个li套ul的,前端展示会打乱布局
# 1.传入待校验的数据  用自己写的类 传入字典格式的待校验的数据
form_obj = views.MyRegForm({'username':'jason','password':'12','email':'123456'})
# 2.判断数据是否符合校验规则
form_obj.is_valid()  # 该方法只有在所有的数据全部符合校验规则才会返回True
False
# 3.如何获取校验之后通过的数据
form_obj.cleaned_data
{'username': 'jason'}
# 4.如何获取校验失败及失败的原因
form_obj.errors
{
'password': ['Ensure this value has at least 3 characters (it has 2).'],
'email': ['Enter a valid email address.']
}
# 5.注意 forms组件默认所有的字段都必须传值 也就意味着传少了是肯定不行的 而传多了则没有任何关系 只校验类里面写的字段 多传的直接忽略了
form_obj = views.MyRegForm({'username':'jason','password':'123456'})
form_obj.is_valid()
Out[12]: False
form_obj.errors
Out[18]: {'email': ['This field is required.']}
 
# 多传的hobby字段,在MyRegForm类中并没有限制,因此,这里的hobby会被忽略,只校验类中的字段
form_obj = views.MyRegForm({'username':'jason','password':'123456',"email":'123@qq.com',"hobby":'hahahaha'})
form_obj.is_valid()
Out[14]: True
form_obj.cleaned_data
Out[15]: {'username': 'jason', 'password': '123456', 'email': '123@qq.com'}
form_obj.errors
Out[16]: {}

forms渲染标签

forms组件只帮忙渲染获取用户输入、选择、下拉、文件的标签,不渲染按钮和form表单的标签

渲染出来的每一个input提示信息都是类中字段,首字母大写(除非在类中的字段括号内利用label定义提示信息)

1、先写一个类

from django import forms
 
 
class MyRegForm(forms.Form):
    # 用户名最少3位最多8位
    username = forms.CharField(max_length=8,min_length=3)
    password = forms.CharField(max_length=8,min_length=3)
    # email字段必须填写符合邮箱格式的数据
    email = forms.EmailField()

2、urls.py

url(r'^reg/', views.reg)

3、views.py

def teg(request):
    # 1、先生成一个空的对象
    form_obj = MyRegForm()
    # 2、直接将该对象传给前端页面
    return render(request, 'reg.html',  locals())

4、在reg.html页面中进行测试

第一种渲染方式:多个p标签 本地测试方便 封装程度太高了 不便于扩展

# reg.html
 
{{ form_obj.as_p }}

# reg.html
{{ form_obj.as_ul }}

# reg.html
{{ form_obj.as_table }}

第二种渲染方式:扩展性较高,书写较为繁琐

{{ form_obj.username.id_for_label }}  # 获取字段的id
{{ form_obj.username.label }}  # 获取的是字段的提示信息User
{{ form_obj.username }}  # 为类中的username产生一个input框
 
# reg.html
<label for="{{ form_obj.username.id_for_label }}">{{ form_obj.username.label }}</label>{{ form_obj.username }}
{{ form_obj.password.label }}{{ form_obj.password }}
{{ form_obj.email.label }}{{ form_obj.email }}

第三种渲染方式,推荐使用:

不管class类中定义多个字段,for循环下,所有的字段都可以渲染出来

# reg.html
 
{% for form in form_obj %}
<p>{{ form.label }}{{ form }}</p>
{% endfor %}

forms渲染错误信息(提示信息)

1、前端自动校验

前端代码:

# reg.html
 
<form action="" method="post">
    {% for form in form_obj %}
    <p>{{ form.label }}{{ form }}</p>
    {% endfor %}
    <input type="submit">
</form>

后端代码:

# views.py
 
def reg(request):
    # 1、先生成一个空的对象
    form_obj = MyRegForm()
    if request.method == 'POST':
        # 3、获取用户输入的所有数据并交给forms组件校验
        form_obj = MyRegForm(request.POST)  # 对上面的空对象重新赋值
        # 4、获取校验结果
        if form_obj.is_valid():
            return HttpResponse('数据没有问题!')
        else:
            # 5.后端打印失败字段和提示信息
            print(form_obj.errors)
 
    # 2、直接将该对象传给前端页面
    return render(request, 'reg.html', locals())

数据校验必须前后端都有,但是前端校验弱不禁风,可有可无,而后端的校验则必须要非常全面

因此们可以取消浏览器自动帮我们校验的功能:

# form表单取消前端浏览器自动校验功能
 
<form action="" method="post" novalidate>  # 在form标签内设置novalidate

前端代码:

<form action="" method="post" novalidate>
    {% for form in form_obj %}
    <p>
        {{ form.label }}{{ form }}
    <sanp>{{ form.errors.0 }}</sanp>
    </p>
    {% endfor %}
    <input type="submit">
</form>

前端代码form.errors如果不索引0,那么取出的是一个ul套li的形式,会打乱页面布局:

def reg(request):
    # 1、先生成一个空的对象
    form_obj = MyRegForm()  # 第一次get请求传入空字典,只是为了渲染页面
    if request.method == 'POST':
        # 3、获取用户输入的所有数据并交给forms组件校验
        form_obj = MyRegForm(request.POST)  # 对上面的空对象重新赋值,此时post请求,是为了校验
        # 4、获取校验结果
        if form_obj.is_valid():
            return HttpResponse('数据没有问题!')
        else:
            # 5.后端打印失败字段和提示信息
            print(form_obj.errors)
 
    # 2、直接将该对象传给前端页面
    return render(request, 'reg.html', locals())

效果如下:

但我们发现,上面的效果并不是很好,提示信息为英文,报错信息也没办法自定义,input框中输入密码也是明文,因此我们需要更近一步的完善,为此我们需要forms组件中的常用参数。

forms组件中常用参数

  • label input框的提示信息
  • error_messages 自定义报错的提示信息
  • required 设置字段是否允许为空
  • initial 设置默认值
  • widget 控制type类型及属性(调整input框的样式)

label设置input框的提示信息:

# 在定义的类中的字段括号内添加参数
username = forms.CharField(max_length=8, min_length=3, label='用户名:')

效果如下:

error_messages自定义报错信息:

class MyRegForm(forms.Form):
    # 用户名最少3位最多8位
    username = forms.CharField(
        max_length=8, min_length=3, label='用户名:',
        error_messages={
            'max_length': '用户名最长8个字符!',
            'min_length': '用户名最短3个字符!',
            'required': '用户名不能为空!',
        }
                               )
 
    # 设置邮箱校验
    # email字段必须填写符合邮箱格式的数据
    email = forms.EmailField(label='邮箱',error_messages={
        'required':'邮箱必填',
        'invalid':'邮箱格式不正确'
    })

字段默认必填,可以通过required设置字段为空:

class MyRegForm(forms.Form):
    # 用户名最少3位最多8位
    username = forms.CharField(
        max_length=8, min_length=3, label='用户名:',
        error_messages={
            'max_length': '用户名最长8个字符!',
            'min_length': '用户名最短3个字符!'
        },
        required=False
                               )

initial设置为默认值:

class MyRegForm(forms.Form):
    # 用户名最少3位最多8位
    username = forms.CharField(
        max_length=8, min_length=3, label='用户名:',
        error_messages={
            'max_length': '用户名最长8个字符!',
            'min_length': '用户名最短3个字符!'
        },
        required=False, initial='我是默认值'
                               )

widget控制type类型及属性(调整input框的样式)

# 设置widget=forms.widgets.passwordInput(),使密码变为密文显示
# attr设置input框的样式
 
password = forms.CharField(max_length=8,min_length=3,label='密码',
                           widget=forms.widgets.PasswordInput(attrs={'class':'form-control'})
                           )
widget=forms.widgets.TextInput(attrs={'class':'form-control c1 c2'})

全局或局部钩子

使用全局或者局部钩子,其实相当于第二层的校验,多了一层校验

全局钩子:针对多个字段,一般用于第二次确认密码是否一致

局部钩子:针对单个字段,校验用户名中不能包含666

# 定义类
 
class MyRegForm(forms.Form):
    # 用户名最少3位最多8位
    username = forms.CharField(max_length=8, min_length=3, label='用户名:')
    password = forms.CharField(max_length=8, min_length=3, label='密码:')
    confirm_password = forms.CharField(max_length=8, min_length=3, label='密码:')
# 全局钩子
 
def clean(self):
    # 校验密码和确认密码是否一致
    password = self.cleaned_data.get('password')
    confirm_password = self.cleaned_data.get('confirm_password')
    if not password == confirm_password:
        # 展示提示信息
        self.add_error('confirm_password','两次密码不一致')
    return self.cleaned_data
 
# 局部钩子
def clean_username(self):
    username = self.cleaned_data.get('username')
    if '666' in username:
        self.add_error('username','光喊666是不行的')
    return username
 
# 如果你想同时操作多个字段的数据你就用全局钩子
# 如果你想操作单个字段的数据 你就用局部钩子

forms补充知识点

正则校验

  • RegexValidator
phone = forms.CharField(
    validators=[
        RegexValidator(r'^[0-9]+$', '请输入数字'),
        RegexValidator(r'^159[0-9]+$', '数字必须以159开头')
    ]
)

利用forms组件校验注册功能

首先在应用下新建一个myforms.py文件

# myforms.py

from django import forms
from app01 import models

class MyRegForm(forms.Form):
    username = forms.CharField(
        min_length=3, max_length=8, label='用户名:',
  error_messages={
            'min_length': '用户名最短3个字符!',
  'max_length': '用户名最长8个字符!',
  'required': '用户名不能为空!'
  },
  widget=forms.widgets.TextInput(attrs={'class': 'form-control'})
    )
    password = forms.CharField(
        min_length=3, max_length=8, label='密码:',
  error_messages={
            'min_length': '密码最短3个字符!',
  'max_length': '密码最长8个字符!',
  'required': '密码不能为空!'
  },
  widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'})
    )
    confirm_password = forms.CharField(
        min_length=3, max_length=8, label='确认密码:',
  error_messages={
            'min_length': '确认密码最短3个字符!',
  'max_length': '确认密码最长8个字符!',
  'required': '确认密码不能为空!'
  },
  widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'})
    )
    email = forms.EmailField(
        label='邮箱',
  error_messages={
            'required': '邮箱不能为空',
  'invalid': '邮箱格式不正确'
  },
  widget=forms.widgets.TextInput(attrs={'class': 'form-control'})
    )

    # 钩子函数
  # 局部钩子校验用户名是否已存在
  def clean_username(self):
        username = self.cleaned_data.get('username')
        is_alive = models.UserInfo.objects.filter(username=username)
        if is_alive:
            self.add_error('username', '用户名已存在!')
        return username

    # 全局钩子校验密码与确认密码是否一致
  def clean(self):
        password = self.cleaned_data.get('password')
        confirm_password = self.cleaned_data.get('confirm_password')
        if password != confirm_password:
            self.add_error('confirm_password', '两次密码不一致!')
        return self.cleaned_data

在views.py中书写逻辑代码:

# views.py

from app01.myforms import MyRegForm

def register(request):
    # 1、先生成form_obj对象
  form_obj = MyRegForm()
    # if request.is_ajax():  # 判断当前请求是否是ajax
  if request.method == 'POST':
        # 定义一个与ajax回调函数交互的字典
  back_dic = {'code': 1000, 'msg': ''}

        # 校验数据 用户名 密码和确认密码
  form_obj = MyRegForm(request.POST)
        if form_obj.is_valid():
            clean_data = form_obj.cleaned_data  # 用变量接收正确的结果,clean_data = {'username', 'password', 'confirm_password', 'email'}
 # 将确认密码的键值对删除
  clean_data.pop('confirm_password')
            # 获取用户头像文件
  avatar_obj = request.FILES.get('avatar')
            # 判断用户头像是否为空
  if avatar_obj:
                # 添加到clean_data中
  clean_data['avatar'] = avatar_obj  # clean_data = {'username', 'password', 'avatar_obj', 'email'}
  models.UserInfo.objects.create_user(**clean_data)
                back_dic['msg'] = '注册成功!'
  back_dic['url'] = '/login/'
  else:
            back_dic['code'] = 2000
  back_dic['msg'] = form_obj.errors
        return JsonResponse(back_dic)

    # 2、将form_obj对象返回给html页面
  return render(request, 'register.html', locals())

html页面

# register.py

<html lang="en">
<head>
 <meta charset="UTF-8">
 <title>注册页面</title>
 <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
  {% load static %}
    <link rel="stylesheet" href="{% static 'bootstrap-3.4.1-dist/css/bootstrap.min.css' %}">
 <script src="{% static 'bootstrap-3.4.1-dist/js/bootstrap.min.js' %}"></script>
</head>
<body>
<div class="container">
 <div class="row">
 <h2 class="text-center">注册页面</h2>
 <div class="col-md-8 col-md-offset-2">
 <form id="myform">
  {% csrf_token %}

                {% for form in form_obj %}
                    <div class="form-group">
 <label for="{{ form.id_for_label }}">{{ form.label }}</label>
  {{ form }}
                    <span style="color: red" class="pull-right"></span>
 </div>  {% endfor %}

                <div class="form-group">
 <label for="id_avatar">用户头像:<img src="/static/image/default.jpg" alt="" width="80" style="margin-left: 10px" id="id_img"></label>
 <input type="file" name="myfile" id="id_avatar" style="display: none">
 </div>
 <input type="button" value="取消" class="btn btn-default btn-sm">
 <input type="button" value="注册" class="btn btn-primary btn-sm pull-right" id="id_submit">
             </form>

 </div> </div></div>
<script>
  $('#id_avatar').change(function () {
        // 1、先获取用户上传的头像文件
  var avatarFile = $(this)[0].files[0];
  // 2、利用文件阅读器对象
  var myFileReader = new FileReader();
  // 3、将文件交由阅读器对象读取
  myFileReader.readAsDataURL(avatarFile);
  // 4、修改img标签的src属性,等待文件阅读器对象读取文件之后再操作img标签
  myFileReader.onload = function(){
            $('#id_img').attr('src', myFileReader.result)
        }
    });

  // 点击按钮触发ajax提交动作
  $('#id_submit').on('click', function () {
        // 1、先生成一个内置对象 FormData  var myFormData = new FormData();
  // 2、添加普通键值对
  {#console.log($('#myform').serializeArray())#}
  $.each($('#myform').serializeArray(), function (index, obj) {
            myFormData.append(obj.name, obj.value)
        });
  // 3、添加文件数据
  myFormData.append('avatar', $('#id_avatar')[0].files[0])
        // 4、发送数据
  $.ajax({
            url: '',
  type: 'post',
  data: myFormData,
  // 两个关键性参数
  contentType: false,
  processData: false,

  success: function (data) {
                if (data.code===1000){
                    // 注册成功之后 应该跳转到后端返回过来的url
  location.href = data.url
  }else{
                    $.each(data.msg, function (index, obj) {
                        // 1、先手动拼接字段名所对应的input框的id值
  var targetId = '#id_' + index; // id_username
 // 2、利用id选择器查找标签
  $(targetId).next().text(obj[0]).parent().addClass('has-error')
                    })
                }
            }
        })
    });
  $('input').focus(function () {
        // 移除span标签内部的文本 还需要移除div标签的class中的has-error属性
  $(this).next().text('').parent().removeClass('has-error')
    })
</script>
</body>
</html>

django的cookie和session

cookie:保存在服务端的键值对

session:保存在服务端上的键值对

cookie与session的作用:

  • 解决http协议的无状态特征
  • 保存信息

cookie产生

当用户第一次登陆成功之后,服务端会返回一个随机字符串,保存在客户端浏览器上,之后再次超服务端发送请求,只需要携带该随机字符串,服务端就能够识别当前用户身份。

cookie可以设置超时时间,超过一定的时间,cookie就会失效。

cookie虽然是保存在客户端浏览器上的,但是是服务端设置的,浏览器也是可以拒绝服务端的要求,不保存cookies。

操作cookie其实就是在操作HttpResponse(render,redirect,,,一次请求一次响应):

obj = HttpResponse('...')
return obj
 
obj1 = render(...)
return obj1
 
obj2 = redirect(...)
 
"""
通过对象的方式进行键值对的赋值
"""
 
# 设置cookie
obj.set_cookie()
# 获取cookie
request.COOKIES.get()
# 删除cookie
obj.delete_cookie()

Django中操作Cookie

获取Cookie

request.COOKIES['key']
request.COOKIES.get(key)
request.get_signed_cookie(key, default=RAISE_ERROR, salt='', max_age=None)

参数:

  • default: 默认值
  • salt: 加密盐
  • max_age: 后台控制过期时间

设置Cookie

rep = HttpResponse(...)
rep = render(request, ...)
 
rep.set_cookie(key,value,...)
rep.set_signed_cookie(key,value,salt='加密盐', max_age=None, ...)

参数:

  • key, 键
  • value='', 值
  • max_age=None, 超时时间
  • expires=None, 超时时间(IE requires expires, so set it if hasn't been already.)
  • path='/', Cookie生效的路径,/ 表示根路径,特殊的:根路径的cookie可以被任何url的页面访问
  • domain=None, Cookie生效的域名
  • secure=False, https传输
  • httponly=False 只能http协议传输,无法被JavaScript获取(不是绝对,底层抓包可以获取到也可以被覆盖)

删除Cookie

def logout(request):
    rep = redirect("/login/")
    rep.delete_cookie("user")  # 删除用户浏览器上之前设置的usercookie值
    return rep

利用cookie写登录功能

1、先定义路由和视图的关系

# urls.py
 
url(r'^login/', views.login)

2、编写登录页面

# login.html
 
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
    <link href="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
    <script src="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
</head>
<body>
<form action="" method="post">
    <p>username:<input type="text" name="username"></p>
    <p>password:<input type="text" name="password"></p>
    <input type="submit">
</form>
</body>
</html>

3、编写视图函数

# views.py
 
# 登录函数
def login(request):
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')
        if username == 'jason' and password == '123':
            # 保存用户登录状态
            obj = redirect('/home')
            # 设置cookie
            obj.set_cookie('username', 'jason')
            return obj
    return render(request, 'login.html')
 
 
# 功能函数
def home(request):
    # 检验浏览器是否有对应的cookie
    if request.COOKIES.get('username'):
        return HttpResponse('我是home页面,只要登录成功就可以看到我。')
    else:
        return redirect('/login')

上面简单的写了利用cookie的登录功能,但是如果有很多功能都需要验证是否携带cookie,我们不可能在每个功能中去验证,因此需要我们写一个登录认证装饰器。

登录认证装饰器版

views.py中添加登录认证装饰器

# 登录认证装饰器
from functools import wraps
 
 
def login_auth(func):
    @wraps(func)
    def inner(request, *args, **kwargs):
        if request.COOKIES.get('username'):
            res = func(request, *args, **kwargs)
            return res
        else:
            return redirect('/login')
    return inner
 
 
def login(request):
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')
        if username == 'jason' and password == '123':
            # 保存用户登录状态
            obj = redirect('/home')
            # 设置cookie
            obj.set_cookie('username', 'jason')
            return obj
    return render(request, 'login.html')
 
 
@login_auth
def home(request):
    return HttpResponse('我是home页面,只要登录成功就可以看到我。')

高级版登录功能

需求一:

当用户访问A网站,但是发现没有登录,将用户跳转到登录页面,用户登录成功后,自动跳转到原来想访问的A页面。

需求二:

当用户没有访问其他页面,直接访问登录页面,登录成功后,将用户跳转到home页面。

request的方法:

通过request拿到用户想要访问的路径。

print('request.path_info:',request.path_info)  # 只拿路径部分 不拿参数
print('request.get_full_path():',request.get_full_path())  # 路径加参数
 
# 打印结果
request.path_info: /home/
request.get_full_path(): /home/?username=jason&password=123

views.py文件改动代码如下:

# 登录认证装饰器
from functools import wraps
 
 
def login_auth(func):
    @wraps(func)
    def inner(request, *args, **kwargs):
        target_url = request.path_info  # 获取路径
        if request.COOKIES.get('username'):
            res = func(request, *args, **kwargs)
            return res
        else:
            return redirect(f'/login/?next={target_url}')  # 将路径拼接到url后面
    return inner
 
 
def login(request):
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')
        if username == 'jason' and password == '123':
            # 第一种方式,若next后面没有路径,直接跳转到默认值
            # target_url = request.GET.get('next', '/home/')
            # 第二种方式,对next后面有没有路径进行判断
            target_url = request.GET.get('next')
            if target_url:
                # 保存用户登录状态
                obj = redirect(target_url)
            else:
                obj = redirect('/home/')
            # 设置cookie
            obj.set_cookie('username', 'jason')
            return obj
    return render(request, 'login.html')

退出登录功能,清除cookie

urls.py新退出登录的路由与视图的关系

# urls.py
 
url(r'^logout/', views.logout)

views.py中新增退出的相关视图函数

# views.py
 
@login_auth
def logout(request):
    obj = HttpResponse('注销成功!')
    obj.delete_cookie('username')
    return obj

设置cookie的超时时间

obj.set_cookie('username', 'jason', max_age=3)

session语法

# 获取、设置、删除Session中数据
request.session['k1']
request.session.get('k1',None)
request.session['k1'] = 123
request.session.setdefault('k1',123) # 存在则不设置
del request.session['k1']
 
 
# 所有 键、值、键值对
request.session.keys()
request.session.values()
request.session.items()
request.session.iterkeys()
request.session.itervalues()
request.session.iteritems()
 
# 会话session的key
request.session.session_key
 
# 将所有Session失效日期小于当前日期的数据删除
request.session.clear_expired()
 
# 检查会话session的key在数据库中是否存在
request.session.exists("session_key")
 
# 删除当前会话的所有Session数据
request.session.delete()
 
# 删除当前的会话数据并删除会话的Cookie。
request.session.flush()
    这用于确保前面的会话数据不可以再次被用户的浏览器访问
    例如,django.contrib.auth.logout() 函数中就会调用它。
 
# 设置会话Session和Cookie的超时时间
request.session.set_expiry(value)
    * 如果value是个整数,session会在些秒数后失效。
    * 如果value是个datatime或timedelta,session就会在这个时间后失效。
    * 如果value是0,用户关闭浏览器session就会失效。
    * 如果value是None,session会依赖全局session失效策略。

保存在服务端上的键值对

设置:request.session['key'] = value

  • django内部会自动生成一个随机字符串
  • 去django_session表中存储数据,键就是随机字符串,值就是要保存的数据(中间件干的)
  • 将生成的随机字符串返回给客户端浏览器,浏览器保存键值对(sessionid:随机字符串)
# urls.py
 
url(r'^set_session/', views.set_session)
 
 
# views.py
 
def set_session(request):
    request.session['username'] = 'sean'
    return HttpResponse('set_session')

获取:request.session.get('key')

  • django会在自动取出浏览器的cookie查找sessionid键值对,获取随机字符串
  • 拿着该随机字符串去django_session表中对比数据
  • 如果对比上了,就讲随机字符串对应的数据获取出来并封装到request.session供用户调用
# urls.py
 
url(r'^get_session/', views.get_session)
 
 
 
# views.py
 
def get_session(request):
    print(request.session.get('username'))
    return HttpResponse('get_session')

django中默认的超时时间为14天

posted @ 2020-02-07 19:48  开花的马铃薯  阅读(195)  评论(0编辑  收藏  举报