前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >BBS项目(三)

BBS项目(三)

作者头像
HammerZe
发布2022-03-24 15:22:58
7030
发布2022-03-24 15:22:58
举报
文章被收录于专栏:Hammer随笔

BBS项目(三)

注册forms类编写局部钩子和全局钩子

代码语言:javascript
复制
'''forms校验'''

from django import forms
from django.forms import widgets
from  blog import  models
from django.core.exceptions import ValidationError
'''注册校验,写入样式'''


class RegisterForm(forms.Form):
    # 名称校验
    username = forms.CharField(
        max_length=18, min_length=3,label='用户名',
        error_messages={'required': '该字段必填', 'max_length': '名字过长,不能超过18个字符',
                        'min_length': '名字过短,不能少于三个字符'},
        widget=widgets.TextInput(attrs={'class': 'form-control'})
    )
    # 密码校验
    password = forms.CharField(
        max_length=18, min_length=3,label='密码',
        error_messages={'required': '该字段必填', 'max_length': '密码过长,不能超过18个字符',
                        'min_length': '密码过短,不能少于三个字符'},
        widget=widgets.PasswordInput(attrs={'class': 'form-control'})
    )
    re_password = forms.CharField(
        max_length=18, min_length=3,label='确认密码',
        error_messages={'required': '该字段必填', 'max_length': '密码过长,不能超过18个字符',
                        'min_length': '密码过短,不能少于三个字符'},
        widget=widgets.PasswordInput(attrs={'class': 'form-control'})
    )

    # 邮箱
    email = forms.EmailField(
        error_messages={'required': '该字段必填', 'invalid': '邮箱格式不正确'},label='邮箱',
        widget=widgets.EmailInput(attrs={'class': 'form-control'})
    )

    # 局部钩子,校验用户是否存在
    def clean_username(self):
        username = self.cleaned_data.get('username')
        # 拿到数据判断用户在不在表中
        user = models.UserInfo.objects.filter(username=username).first()
        if user:
            # 用户存在抛异常
            raise ValidationError('该用户以存在')
        else:
            return username

    # 全局钩子,校验密码是否一致
    def clean(self):
        password = self.cleaned_data.get('password')
        re_password = self.cleaned_data.get('re_password')
        if not password == re_password:
            raise ValidationError('两次密码不一致')

注册功能前端

代码语言:javascript
复制
# 发送ajax请求,使用的Formdata
#form标签.serializeArray()

# 整体代码
$('#id_submit').click(function () {
        let formdata = new FormData()
        formdata.append('myfile', $('#myfile')[0].files[0])
        //方案一
        /*
        formdata.append('username',$('#id_username').val())
        formdata.append('password',$('#password').val())
        formdata.append('re_password',$('#id_re_password').val())
        formdata.append('email',$('#id_email').val())
         */

        //方案二
        let form_data = $('#my_form').serializeArray()
        //console.log(form_data)
        $.each(form_data, function (index, element) {
            //console.log(index)
            //console.log(element)
            formdata.append(element.name, element.value)
        })
        //console.log(formdata.get('username'))
        $.ajax({
            url: '/register/',
            method: 'post',
            contentType: false,
            processData: false,
            data: formdata,
            success: function (data) {
                console.log(data)
                if (data.status == 100) {
                    location.href = data.next_url
                    //location.href='/login/'
                } else {
                    $.each(data.msg, function (key, value) {
                        //console.log('#id_'+key)
                        if (key == '__all__') {
                            $('#id_error').html(value[0])
                        } else {
                            //取到input标签的下一个
                            //$('#id_'+key).next().html(value[0])
                            //链式调用
                            //$('#id_'+key).parent().addClass('has-error')
                            //链式调用
                            $('#id_' + key).next().html(value[0]).parent().addClass('has-error')

                        }

                    })

                    //加了一个定时器,3s以后干某些事
                    setTimeout(function () {
                        //清除红色框
                        $('.form-group').removeClass('has-error')
                        //清空所有错误信息
                        $('.error').html('')
                    }, 3000)
                }


            }
        })


    })

注册功能后端

代码语言:javascript
复制
def register(request):
    if request.method == 'GET':
        register_form = RegisterForm()
        return render(request, 'register.html', context={'form': register_form})
    elif request.method == 'POST':
        response = {'status': 100, 'msg': None}
        register_form = RegisterForm(request.POST)
        if register_form.is_valid():
            # 数据校验通过
            # 可能传头像,可能没传头像
            clean_data=register_form.cleaned_data
            print(clean_data)
            my_file=request.FILES.get('myfile')
            if my_file: # 传了头像
                # FileField字段类型直接接受一个文件对象,
                # 它会把文件存到upload_to='avatar/',然后把路径存到数据库中
                # 相当于with open 打开文件,把文件存到avatar路径下,把路径赋值给avatar这个字段
                clean_data['avatar']=my_file
            clean_data.pop('re_password')
            models.UserInfo.objects.create_user(**clean_data)
            response['msg']='恭喜你,注册成功'
            response['next_url']='/login/'

        else:
            response['status']=101
            response['msg'] = register_form.errors

        return JsonResponse(response)

注册功能前端错误渲染

代码语言:javascript
复制
success: function (data) {
    console.log(data)
    if (data.status == 100) {
        location.href = data.next_url
    } else {
        $.each(data.msg, function (key, value) {
            if (key == '__all__') {
                $('#id_error').html(value[0])
            } else {
                $('#id_' + key).next().html(value[0]).parent().addClass('has-error')
            }
        })
        setTimeout(function () {
            //清除红色框
            $('.form-group').removeClass('has-error')
            //清空所有错误信息
            $('.error').html('')
                    }, 3000)
                }
}

登录页面搭建

代码语言:javascript
复制
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登录页面</title>
    <link rel="stylesheet" href="/static/bootstrap-3.3.7-dist/css/bootstrap.min.css">
    <script src="/static/jquery-3.3.1.js"></script>
</head>
<body>
<div class="container-fluid">
    <div class="row">
        <div class="col-md-6 col-md-offset-3">
            <div class="panel panel-primary">
                <div class="panel-heading">
                    <h3 class="panel-title">登录功能</h3>
                </div>
                <div class="panel-body">
                    <form id="my_form">
                        {% csrf_token %}

                        <div class="form-group">
                            <label for="">用户名</label>
                            <input type="text" id="id_username" class="form-control">
                            <span class="danger pull-right error"></span>
                        </div>
                        <div class="form-group">
                            <label for="">密码</label>
                            <input type="text" id="id_password" class="form-control">
                            <span class="danger pull-right error"></span>
                        </div>

                        <div class="form-group">
                            <label for="">验证码</label>

                            <div class="row">
                                <div class="col-md-6">
                                    <input type="text" id="id_code" class="form-control">
                                </div>
                                <div class="col-md-6">

                                    <img src="/get_code/" alt="" height="35px" width="300px">
                                </div>
                            </div>

                        </div>


                        <div class="text-center">
                            <input type="button" value="登录" class="btn btn-warning" id="id_submit"><span
                                class="danger error"
                                id="id_error"
                                style="margin-left: 10px"></span>
                        </div>

                    </form>
                </div>
            </div>

        </div>
    </div>
</div>
</body>
</html>

验证码

代码语言:javascript
复制
'''手写验证码模板'''

def get_random():
    return (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))

def get_code(request):
    # 最终方案
    # img = Image.new('RGB', (300, 30), get_random())
    img = Image.new('RGB', (250, 30), (250, 250, 250))
    # 第一个参数,是文字格式的文件,ttf格式,第二个参数是文字大小
    img_font = ImageFont.truetype('./static/font/ss.TTF', 20)
    # 拿到一个画板对象,把图片放到画板上,指定写的字的字体是什么
    img_draw = ImageDraw.Draw(img)
    # 在画板上写文字
    # 随机生成5位 小写字母,大写字母,和数字
    code = ''
    for i in range(5):
        low_char = chr(random.randint(97, 122))
        up_char = chr(random.randint(65, 90))
        number_char = str(random.randint(0, 9))
        res = random.choice([low_char, up_char, number_char])
        code += res
        img_draw.text((20 + i * 40, 0), res, fill=get_random(), font=img_font)
    print(code)
    request.session['code'] = code
    # 画点和线
    # 画线和点圈
    width = 250
    height = 30
    for i in range(5):
        x1 = random.randint(0, width)
        x2 = random.randint(0, width)
        y1 = random.randint(0, height)
        y2 = random.randint(0, height)
        # 在图片上画线
        img_draw.line((x1, y1, x2, y2), fill=get_random())

    for i in range(20):
        # 画点
        img_draw.point([random.randint(0, width), random.randint(0, height)], fill=get_random())
        x = random.randint(0, width)
        y = random.randint(0, height)
        # 画弧形
        img_draw.arc((x, y, x + 4, y + 4), 0, 90, fill=get_random())

    bytes_io = BytesIO()
    img.save(bytes_io, 'png')  # 写到内存中,需要传format,图片格式
    return HttpResponse(bytes_io.getvalue())  # 把内容读出来

点击刷新验证码

代码语言:javascript
复制
$('#id_img').click(function () {
        let img_url = $('#id_img')[0].src
        $('#id_img')[0].src = img_url + '?'
    })

登录功能前后端

前端

代码语言:javascript
复制
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Login</title>
    <script src="/static/element/jQuery3.4.js"></script>
    <script src="/static/element/bootstrap.min.js"></script>
    <link rel="stylesheet" href="/static/element/bootstrap.min.css">

    <style>
        .danger {
            color: tomato;
        }
    </style>

</head>
<body>
<div class="container">
    <div class="row" style="margin-top: 15px">
        <div class="col-md-8 col-md-offset-2">
            <div class="panel panel-info">
                <div class="panel-heading">
                    <h3 class="panel-title">登录</h3>
                </div>
                <div class="panel-body">
                    <form action="" id='my_form' method="post">
                        {% csrf_token %}
                        {# 用户名、密码、验证码  #}
                        <div class="form-group">
                            {#  后端blog_forms.py获取label #}
                            {#    item.auto_id获取当前id自动聚焦    #}
                            <label for="{{ item.auto_id }}">用户名</label>
                            <input type="text" id="id_username" class="form-control">
                            <span class="danger pull-right error"></span>
                        </div>
                        <div class="form-group">
                            {#  后端blog_forms.py获取label #}
                            {#    item.auto_id获取当前id自动聚焦    #}
                            <label for="{{ item.auto_id }}">密码</label>
                            <input type="password" id="id_password" class="form-control">
                            <span class="danger pull-right error"></span>
                        </div>

                        <div class="form-group">
                            <label for="{{ item.auto_id }}">验证码</label>
                            <div class="row">
                                <div class="col-md-6">
                                    <input type="text" id="id_code" class="form-control">
                                </div>
                                <div class="col-md-6">

                                    <img src="/get_code/" alt="验证码图片" height="35px" width="300px" id="id_imgcode">
                                </div>
                            </div>
                        </div>

                        {#   提交,使用form表单,类型要写成button,如果是submit的话会触发表单提交  #}
                        <div class="text-center">
                            <input type="button" class="btn btn-info" id="id_submit" value="登录">
                            <a href="/admin/" class="btn btn-warning table-hover">退出</a>
                            <span class="danger error" id="id_error" style="margin-left: 20px"></span>


                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>
</div>
</body>
<script>
    {# 点击变图,发送不同请求 #}
    $('#id_imgcode').click(function () {
        var img_url = $('#id_imgcode')[0].src
        $('#id_imgcode')[0].src = img_url + '?' + Math.floor(Math.random() * 100)
    })
    {#提交数据#}
    $('#id_submit').click(function () {
        $.ajax({
            url: '/login/',
            method: 'post',
            data: {
                username: $('#id_username').val(),
                password: $('#id_password').val(),
                code: $('#id_code').val(),
                csrfmiddlewaretoken: '{{csrf_token}}',
            },
            success: function (data) {
                if (data.status == 100) {
                    {#location.href='/index/'#}
                    location.href = data.url
                } else {
                    $('#id_error').html(data.msg)
                    {#alert($('#id_error').html(data.msg)[0].innerText)#}
                    setTimeout(function (){
                        $('.error').html('')
                    },3000)
                }
            },

        })
    })

</script>
</html>

后端

代码语言:javascript
复制
# 验证码
def get_code(request):
    # width = 300
    # height = 30
    # image = Image.new('RGB', (width, height), (255, 255, 255))
    #
    # with open('code.png','wb')as code_f:
    #     image.save(code_f)
    # with open('./code.png','rb')as f:
    #     res = f.read()
    # return HttpResponse(res)
    from PIL import Image, ImageDraw, ImageFont
    from io import BytesIO


    # 随机字母:
    def rndChar():

        return chr(random.randint(65, 90))

    # 随机颜色1:
    def rndColor():
        return (random.randint(64, 255), random.randint(64, 255), random.randint(64, 255))

    # 随机颜色2:
    def rndColor2():
        return (random.randint(32, 127), random.randint(32, 127), random.randint(32, 127))

    width = 300
    height = 50
    image = Image.new('RGB', (width, height), (255, 255, 255))
    # 创建Font对象:
    # 创建ttf格式文件
    font = ImageFont.truetype(r'E:\BBS\static\code\arial.ttf', 36)
    # 创建Draw对象:
    draw = ImageDraw.Draw(image)
    # 填充每个像素:
    for x in range(width):
        for y in range(height):
            draw.point((x, y), fill=rndColor())
    # 输出文字:

    code = ''
    for t in range(5):
        '''如果想使用数字和大小写字母拼接的验证码可以用chr(),用ASCII,然后随机获取'''
        res = rndChar()
        code += res
        draw.text((60 * t + 10, 10), res, font=font, fill=rndColor2())
    print(code)
    '''将验证码写入code,需要注意的是,如果同一个浏览器打开两个页面,那么原来的session_data就会被覆盖掉,如果打开另外一个浏览器就会重新生成新的session_data记录'''
    request.session['code'] = code

    # 模糊:
    # image = image.filter(ImageFilter.BLUR)

    # 存入硬盘,读出
    # image.save('code.jpg', 'jpeg')
    # with open('./code.jpg', 'rb') as f:
    #      res = f.read()
    # 存入内存,读出
    bytes_io = BytesIO()
    image.save(bytes_io, 'png')
    return HttpResponse(bytes_io.getvalue())
# 登录
def login(request):
    if request.method == 'GET':
        return render(request, 'login.html')
    else:
        response = {'status': 100, 'msg': None}
        username = request.POST.get('username')
        password = request.POST.get('password')
        code = request.POST.get('code')
        if not code:
            response['status'] = 101
            response['msg'] = '验证码不能为空'
        if code.lower() == request.session.get('code').lower():
            user = authenticate(username=username, password=password)
            if user:
                auth.login(request,user)
                response['msg'] = '登录成功'
                response['url'] = '/index/'
            else:
                response['status'] = 102
                response['msg'] = '用户名或密码错误'
        else:
            response['status'] = 101
            response['msg'] = '验证码错误'
        return JsonResponse(response)

首页页面搭建

代码语言:javascript
复制
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Blog-index</title>
    <script src="/static/element/jQuery3.4.js"></script>
    <script src="/static/element/bootstrap.min.js"></script>
    <link rel="stylesheet" href="/static/element/bootstrap.min.css">

    <style>
        footer#footer {
            padding-top: 32px;
            padding-bottom: 32px;
            display: flex;
            flex-direction: column;
            align-items: center;
            color: #888;
            background-color: #f3f3f3;
            font-size: 13px;
            font-weight: 400;
            text-align: center;
        }

        footer {
            display: block;
        }

        body {
            font-family: "PingFang SC", "Microsoft YaHei", "Helvetica Neue", "Helvetica", "Arial", sans-serif;
            font-weight: normal;
            background-color: #f9f9f9;
        }
    </style>
</head>
<body>
<div class="container-fluid">
    {# 头部 #}
    <div class="head">
        <nav class="navbar navbar-inverse">
            <div class="container-fluid">
                <!-- Brand and toggle get grouped for better mobile display -->
                <div class="navbar-header">
                    <button type="button" class="navbar-toggle collapsed" data-toggle="collapse"
                            data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
                        <span class="sr-only">Toggle navigation</span>
                        <span class="icon-bar"></span>
                        <span class="icon-bar"></span>
                        <span class="icon-bar"></span>
                    </button>
                    <li class="navbar-branding">
                        <a href="/index/" class="navbar-brand" title="开发者的网上家园" role="banner">
                            博客园
                        </a>
                    </li>
                </div>

                <!-- Collect the nav links, forms, and other content for toggling -->
                <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
                    <ul class="nav navbar-nav">
                        <li class="active"><a href="/index/">首页 <span class="sr-only">(current)</span></a></li>
                        <li><a href="https://news.cnblogs.com/">新闻</a></li>
                        <li class="dropdown">
                            <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
                               aria-haspopup="true" aria-expanded="false">发现 <span class="caret"></span></a>
                            <ul class="dropdown-menu">
                                <li><a href="https://zzk.cnblogs.com/">找找看</a></li>
                                <li><a href="#">收藏</a></li>
                                <li><a href="https://www.lagou.com/">招聘</a></li>
                                <li role="separator" class="divider"></li>
                                <li><a href="/login/">个人园子</a></li>

                            </ul>
                        </li>
                    </ul>
                    {% if request.user.is_authenticated %}
                        <ul class="nav navbar-nav navbar-right">
                            <li><a href="#">{{ request.user.username }}</a></li>
                            <li class="dropdown">
                                <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
                                   aria-haspopup="true" aria-expanded="false">更多操作 <span class="caret"></span></a>
                                <ul class="dropdown-menu">
                                    <li><a href="/">个人主页</a></li>
                                    <li><a href="#">设置</a></li>
                                    <li><a href="admin">后台管理</a></li>
                                    <li role="separator" class="divider"></li>
                                    <li><a href="/logout/">退出</a></li>
                                </ul>
                            </li>
                        </ul>
                    {% else %}
                        <ul class="nav navbar-nav navbar-right">
                            <li><a href="/login/">登录</a></li>
                            <li><a href="/register/">注册</a></li>

                        </ul>

                    {% endif %}

                </div><!-- /.navbar-collapse -->
            </div><!-- /.container-fluid -->
        </nav>
    </div>

    {# 主体 #}
    <div class="body row">
        {#  左侧   #}
        <div class="col-md-2">
            <div class="panel panel-success">
                <div class="panel-heading">
                    <h3 class="panel-title">Panel title</h3>
                </div>
                <div class="panel-body">
                    Panel content
                </div>
            </div>
            <div class="panel panel-info">
                <div class="panel-heading">
                    <h3 class="panel-title">Panel title</h3>
                </div>
                <div class="panel-body">
                    Panel content
                </div>
            </div>
            <div class="panel panel-warning">
                <div class="panel-heading">
                    <h3 class="panel-title">Panel title</h3>
                </div>
                <div class="panel-body">
                    Panel content
                </div>
            </div>
        </div>
        {#   中间   #}
        <div class="col-md-7">
            <div class="lunbotu">
                <div id="carousel-example-generic" class="carousel slide" data-ride="carousel">
                    <!-- Indicators -->
                    <ol class="carousel-indicators">
                        <li data-target="#carousel-example-generic" data-slide-to="0" class="active"></li>
                        <li data-target="#carousel-example-generic" data-slide-to="1"></li>
                        <li data-target="#carousel-example-generic" data-slide-to="2"></li>
                    </ol>

                    <!-- Wrapper for slides -->
                    <div class="carousel-inner" role="listbox">
                        {% for banner in banner_list %}
                            {% if forloop.first %}
                                <div class="item active">
                                    <img src="{{ banner.url }}" alt="首页图">
                                    {#  <div class="carousel-caption">#}
                                    {#    {{ banner.name }}#}
                                    {#  </div>#}
                                </div>
                            {% else %}
                                <div class="item ">
                                    <img src="{{ banner.url }}" alt="首页图">
                                </div>
                            {% endif %}

                        {% endfor %}


                    </div>

                    <!-- Controls -->
                    <a class="left carousel-control" href="#carousel-example-generic" role="button" data-slide="prev">
                        <span class="glyphicon glyphicon-chevron-left" aria-hidden="true"></span>
                        <span class="sr-only">Previous</span>
                    </a>
                    <a class="right carousel-control" href="#carousel-example-generic" role="button" data-slide="next">
                        <span class="glyphicon glyphicon-chevron-right" aria-hidden="true"></span>
                        <span class="sr-only">Next</span>
                    </a>
                </div>
            </div>
            {#  文章   #}
            <div class="article">
                {% for article in article_list %}
                    {{ article.title }}
                {% endfor %}
                
            </div>

        </div>
        {#   右侧  #}
        <div class="col-md-3">
            <ul class="list-group">
                <li class="list-group-item">Cras justo odio</li>
                <li class="list-group-item">Dapibus ac facilisis in</li>
                <li class="list-group-item">Morbi leo risus</li>
                <li class="list-group-item">Porta ac consectetur ac</li>
                <li class="list-group-item">Vestibulum at eros</li>
            </ul>
        </div>
    </div>


    {# 页脚 #}
    <div>
        <br><br><br><br><br><br><br><br><br><br>
        <br><br><br><br><br><br><br><br><br><br>
    </div>
    <div class="footer text-center" style="background-color: rgb(243,243,243)">
        <footer id="footer" class="footer">
            <div id="friend_link" class="link-list friend-link">
                友情链接:
                <a href="//www.aliyun.com" target="_blank">阿里云</a>
                <a href="//cloud.tencent.com" target="_blank">腾讯云</a>
                <a href="//www.huaweicloud.com" target="_blank">华为云</a>
                <a href="//cloud.baidu.com" target="_blank">百度云</a>
                <a href="//www.jdcloud.com" target="_blank">京东云</a>
                <a href="http://www.ucancode.com/" target="_blank">工控组态源码</a>
                <a href="//www.shanhaibi.com/" target="_blank">山海鲸可视化</a>
                <a href="http://www.gcpowertools.com.cn" target="_blank">葡萄城控件</a><a href="//www.chinaz.com/"
                                                                                     target="_blank">站长之家</a><a
                    href="http://dev.yesky.com" target="_blank">天极网</a><a href="//wetest.qq.com/?from=links_cnblogs"
                                                                          target="_blank">腾讯WeTest</a>
            </div>

            <div class="footer-splitter"></div>
            <div id="footer_bottom">
                <div class="poweredby">Powered by .NET 6 on Kubernetes</div>
                <div class="about"><a href="//about.cnblogs.com/">关于博客园</a><a
                        href="//about.cnblogs.com/contact">联系我们</a><a href="//about.cnblogs.com/ad">广告服务</a><a
                        href="//about.cnblogs.com/brandzone">专区合作</a><span>©2004-2022</span>
                </div>

                <div class="report-contact">举报电话:0571-88079867,举报邮箱:contact@cnblogs.com <a href="http://www.shjbzx.cn"
                                                                                           target="_blank"><img
                        src="/images/jblogo.png?v=20200730" alt=""></a></div>

            </div>
        </footer>
    </div>
</div>
</body>
</html>
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-03-14 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • BBS项目(三)
    • 注册forms类编写局部钩子和全局钩子
      • 注册功能前端
        • 注册功能后端
          • 注册功能前端错误渲染
            • 登录页面搭建
              • 验证码
                • 点击刷新验证码
                  • 登录功能前后端
                    • 首页页面搭建
                    相关产品与服务
                    验证码
                    腾讯云新一代行为验证码(Captcha),基于十道安全栅栏, 为网页、App、小程序开发者打造立体、全面的人机验证。最大程度保护注册登录、活动秒杀、点赞发帖、数据保护等各大场景下业务安全的同时,提供更精细化的用户体验。
                    领券
                    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档