Django框架之视图层

一、三板斧的原理介绍

1、HttpResponse

在Django中,HttpResponse是一个类,用于构建HTTP响应并返回给客户端。当视图函数处理完请求后,需要返回一个响应时,就会使用HttpResponse对象。

(1)创建HttpResponse对象

from django.http import HttpResponse

response = HttpResponse(content="Hello, World!", content_type="text/plain", status=200)

(2)参数说明

  • content:响应内容,可以是字符串、字节串或文件对象。
  • content_type:响应内容的MIME类型。
  • status:HTTP状态码,例如200表示成功,404表示未找到等。
  • 其他可选参数还包括charsetheaders等。

(3)设置响应头

response['Custom-Header'] = 'Value'

(4)设置Cookie

response.set_cookie('cookie_name', 'cookie_value')

(5)返回Json响应

import json

data = {'key': 'value'}
return HttpResponse(json.dumps(data), content_type="application/json")

HttpResponse类是Django中处理HTTP响应的基本工具之一,通过使用它,可以方便地构建各种类型的响应并返回给客户端。

2、render

在Django中,render是一个常用的快捷函数,用于将数据渲染到指定的模板文件中,并返回一个包含渲染结果的HttpResponse对象。

(1)导入render函数

from django.shortcuts import render

(2)使用方法

def my_view(request):
    context = {'key': 'value'}
    return render(request, 'template_name.html', context)

(3)参数说明

  • request:视图函数接收到的请求对象。
  • template_name:要渲染的模板文件的名称。
  • context:包含要传递给模板的数据的字典对象。

(4)模板渲染

  • render函数会将context中的数据渲染到指定的模板文件中。
  • 在模板文件中,可以使用模板语言(如Django模板语言)来访问和显示传递的数据。

(5)返回HttpResponse对象

  • render函数返回一个HttpResponse对象,其中包含了渲染后的HTML内容,可以直接返回给客户端。

(6)示例

# views.py
from django.shortcuts import render

def my_view(request):
    context = {'name': 'Alice', 'age': 30}
    return render(request, 'my_template.html', context)
	# 当你需要传的数据特别多的时候可以使用这种方法
    # locals()会将所在的名称空间中所有的名字全部传递给html页面
    return render(request, 'my_template.html',locals())
<!-- my_template.html -->
<html>
<head>
    <title>Hello, {{ name }}</title>
</head>
<body>
    <h1>Hello, {{ name }}</h1>
    <p>Age: {{ age }}</p>
</body>
</html>

在上述示例中,render函数将context中的数据渲染到my_template.html模板文件中,并返回一个包含渲染结果的HttpResponse对象,最终向客户端呈现渲染后的页面内容。render函数简化了将数据传递给模板并渲染的过程,是Django中常用的视图函数工具之一。

3、redirect

在Django中,redirect函数用于重定向用户到另一个URL。

(1)导入redirect函数

from django.shortcuts import redirect

(2)使用方法

def my_view(request):
    ...
    return redirect('redirect_url_name')

(3)参数说明

  • redirect_url_name:要重定向到的URL的名称,也可以是URL路径或视图函数。

(4)重定向方式

  • 当用户访问视图函数时,如果需要将用户重定向到另一个URL,可以使用redirect函数。
  • 重定向可以是到另一个视图函数,也可以是到一个具体的URL路径。

(5)示例

# views.py
from django.shortcuts import redirect

def my_view(request):
    # Some logic here
    return redirect('home')  # Redirects to a URL named 'home'

(6)重定向到URL路径

return redirect('/myapp/my_url/')  # 重定向到一个特定的url

return redirect('/home/')  # 重定向自己的函数不用写前缀

(7)重定向到视图函数

return redirect(another_view_function)  # 重定向到视图函数

(8)重定向到外部URL

return redirect('https://www.example.com')  

(9)重定向时传递参数

  • 可以在重定向时传递参数,例如在URL中包含参数或使用查询字符串。

(10)重定向的特点

  • 重定向会向浏览器发送一个302状态码,告诉浏览器需要重定向到另一个URL。
  • 用户将会看到新的页面内容,URL也会相应地改变。

redirect函数在Django中常用于处理用户请求后的页面跳转,使得开发者可以轻松地引导用户到其他页面或视图。

4、总结

视图函数必须要返回第一个HttpResponse对象,研究三者源码可得出结论。

render:
  Return a HttpResponse whose content is filled with the result of calling django.template.loader.render_to_string() with the passed arguments.

redirect:
  Return an HttpResponseRedirect to the appropriate URL for the arguments passed.

二、JsonResponse对象

1、json格式的数据有什么用?

前后端数据交互需要使用到json作为过渡,实现跨语言传输数据

	前端序列化            			 python序列化
  JSON.stringify()                  json.dumps()
  JSON.parse                        json.loads()

2、具体案例

  • urls.py
from django.contrib import admin
from django.urls import path
from app01 import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('ab_json/', views.ab_json),
]

(1)json模块序列化

def ab_json(request):
    user_dict = {
        'username': 'xiao好牛逼',
        'password': '123',
        'hobby': 'read'
    }
    l = [1, 2, 3, 4, 5]
    # 先转成json格式字符串
    json_str = json.dumps(user_dict,ensure_ascii=False)  # {"username": "xiao", "password": "123", "hobby": "read"}
    # 将该字符串返回
    return HttpResponse(json_str)

(2)JsonResponse对象

问题:使用HttpResponse返回的页面展示的数据中,中文字符被强制编码了,如何解决?

读源码掌握JsonResponse用法,修改json_dumps_params参数来防止汉字乱码。

from django.http import JsonResponse

def ab_json(request):
    user_dict = {
        'username': 'xiao好牛逼',
        'password': '123',
        'hobby': 'read'
    }
    l = [1, 2, 3, 4, 5]
    # 先转成json格式字符串
    json_str = json.dumps(user_dict,ensure_ascii=False)  # {"username": "xiao", "password": "123", "hobby": "read"}
    
    # 读源码掌握JsonResponse用法,修改json_dumps_params参数来防止汉字乱码
    return JsonResponse(user_dict, json_dumps_params={'ensure_ascii': False})
    

(3)其他数据类型序列化

  • 列表参数报错
	return JsonResponse(l)  # In order to allow non-dict objects to be serialized set the safe parameter to False. 
  • 看报错信息添加safe参数
from django.http import JsonResponse

def ab_json(request):
    user_dict = {
        'username': 'xiao好牛逼',
        'password': '123',
        'hobby': 'read'
    }
    l = [1, 2, 3, 4, 5]
    
    return JsonResponse(l,safe=False) # 默认只能序列化字典,序列化其他需要添加safe参数

JsonResponse在序列化字典以外的数据格式时,会有一个安全设置,我们将参数修改即可修改数据

三、form表单文件上传及后端如何获取

1、form表单知识回顾

<form action="" method="post" enctype="multipart/form-data"></form>

form表单上传文件类型的数据

  1. method必须指定post
  2. enctype必须换成formdata(不换成这个接收不到文件)

2、获取不同数据的方式

urls.py

from django.contrib import admin
from django.urls import path
from app01 import views

urlpatterns = [
    path('admin/', admin.site.urls),
    # form 表单上传 下载文件
    path('ab_file/', views.ab_file),
]

前端 ab_file.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
    <link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
    <script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script>

</head>
<body>
<form action="" method="post" enctype="multipart/form-data" class="form form-control">
    <p>username:<input type="text" name="username" class="form-control"></p>
    <p>file:<input type="file" name="file" class="form-control"></p>
    <input type="submit">
</form>
</body>
</html>

(1)POST请求数据

  • csrf记得要注释掉,不然会报错
def ab_file(request):
    if request.method == 'POST':
        # request.POST只能获取普通的键值对数据,获取文件不行
        print(request.POST) 
    return render(request,'ab_file.html')
<QueryDict: {'username': ['xiao']}>

(2)获取文件数据

def ab_file(request):
    if request.method == 'POST':
        print(request.FILES)  # <MultiValueDict: {'file': [<InMemoryUploadedFile: 联想截图_20240124153420.png (image/png)>]}>
        file_obj = request.FILES.get('file')  # 文件对象
        # print(file_obj.name)  # 联想截图_20240124153420.png
        with open(file_obj.name,'wb') as f:
            for line in file_obj.chunks:  # 推荐加上chunks方法,其实不加是一样的,都是一行行的二进制数据读取
                # print(line)  # 二进制数据
                f.write(line)
    return render(request,'ab_file.html')
 <MultiValueDict: {'file': [<InMemoryUploadedFile: 联想截图_20240124153420.png (image/png)>]}>

四、request对象方法扩充

1、request.method

在Django中,request.method是HttpRequest对象的一个属性,用于获取当前请求的HTTP方法。HTTP方法指定了客户端对服务器执行的操作类型。常见的HTTP方法包括 GET、POST、PUT、DELETE 等。

(1)获取请求方法

  • 通过request.method可以获取当前请求的HTTP方法。
  • 例如,如果请求是通过GET方法发送的,request.method将返回字符串'GET'

(2)常见的HTTP方法

  • GET:用于从服务器获取数据。
  • POST:用于向服务器提交数据。
  • PUT:用于更新资源。
  • DELETE:用于删除资源。
  • PATCH:用于部分更新资源。
  • 等等…

(3)使用示例

def my_view(request):
    if request.method == 'GET':
        # 处理GET请求
        pass
    elif request.method == 'POST':
        # 处理POST请求
        pass

(4)注意事项

  • 在处理视图函数时,通常会根据请求的方法类型执行不同的逻辑。
  • 可以使用if语句或switch语句根据不同的请求方法执行相应的操作。

通过检查request.method,开发者可以根据不同的HTTP方法执行相应的操作,从而实现对请求的灵活处理。

2、request.POST

在Django中,request.POST 是一个类似字典的数据结构,用于访问用户通过 POST 方法提交的数据。当用户通过表单提交数据时,这些数据将包含在 request.POST 中。

(1)获取 POST 数据

  • 当用户通过 POST 方法提交表单时,表单中的数据将被存储在 request.POST 中。
  • 可以通过键来访问特定的数据项,就像访问字典一样,例如 request.POST['key']

(2)POST 数据的不可变性

  • request.POST 是不可变的,这意味着您不能直接修改它的内容。
  • 如果您需要修改 POST 数据,可以将其复制到一个可修改的数据结构中,如 QueryDict

(3)处理表单数据

  • 在处理表单数据时,通常会使用 request.POST 来获取用户提交的数据,并进行相应的处理,例如验证、保存到数据库等操作。

总的来说,request.POST 在处理用户通过 POST 方法提交的表单数据时非常有用。

3、request.GET

在Django中,request.GET 是一个类似字典的数据结构,用于访问用户通过 GET 方法提交的数据。当用户通过 URL 查询字符串传递数据时,这些数据将包含在 request.GET 中。

(1)获取 GET 数据

  • 当用户通过 GET 方法传递数据时,这些数据将被存储在 request.GET 中。GET 数据通常以查询字符串的形式附加在 URL 后面,例如 http://example.com/?key1=value1&key2=value2

(2)安全性考虑

  • GET 请求中的数据可以在 URL 中看到,因此不应该用于传输敏感信息,因为这些数据会被保存在浏览器历史记录、服务器日志等地方。

(3)使用示例

  • 开发者可以通过类似字典的方式访问 request.GET 中的数据,例如 request.GET['key']request.GET.get('key')

(4)区别

  • 在处理表单提交时,通常会使用 request.POST 来访问 POST 数据,而在处理通过 URL 传递的参数时,会使用 request.GET 来访问 GET 数据。

4、request.FILES

在Django中,request.FILES 是一个类似字典的数据结构,用于访问通过表单提交的文件数据。当用户通过表单上传文件时,这些文件数据将包含在 request.FILES 中。

(1)处理文件上传

  • 当用户通过表单上传文件时,这些文件数据将被存储在 request.FILES 中。开发者可以通过 request.FILES 访问这些文件数据,进行处理、保存或其他操作。

(2)文件上传表单

  • 在 HTML 表单中,文件上传字段需要使用 <input type="file"> 标签,并且在表单标签中添加 enctype="multipart/form-data" 属性来指示文件上传。

(3)文件对象

  • request.FILES 中的每个文件都是一个类似字典的对象,包含文件的内容、文件名、大小等信息。开发者可以通过这些属性访问和处理文件数据。

(4)示例

  • 通过 request.FILES['file_field_name']request.FILES.get('file_field_name') 可以访问上传的文件数据。

(5)安全性考虑

  • 需要谨慎处理用户上传的文件,以防止安全漏洞,例如文件包含漏洞、文件上传漏洞等。

在处理文件上传时,开发者通常会结合使用 request.POST(用于访问表单中的其他数据)和 request.FILES(用于访问上传的文件数据)来完整处理表单提交的数据。

5、request.body

在Django中,request.body 是一个包含请求主体数据的字节字符串(原生的浏览器发过来的二进制数据)。当客户端向服务器发送 POST 请求时,请求的主体数据将包含在 request.body 中。

(1)字节字符串

  • request.body 包含了 POST 请求的原始数据,以字节字符串的形式呈现。开发者可以通过解析这个字节字符串来获取 POST 请求中的数据。

(2)处理数据

  • 开发者可以根据请求的内容类型(如 JSON、XML 等)来解析 request.body 中的数据。通常情况下,需要使用适当的方法(如 json.loads())来解析请求数据。

(3)获取 POST 数据

  • 在某些情况下,开发者可能需要直接访问 request.body 来处理 POST 请求中的数据,而不是通过 Django 提供的更高级的请求对象属性(如 request.POSTrequest.FILES)。

(4)注意事项

  • 在处理 request.body 中的数据时,开发者需要注意数据的编码方式(如 UTF-8)以及如何正确解析这些数据,以避免出现编码问题或安全漏洞。

总的来说,request.body 提供了一种直接访问 POST 请求主体数据的方式,开发者可以根据需要来处理这些原始数据。

6、request.path

只能获取到路由地址,无法获取到参数

  • request.path:该属性表示请求URL中的路径部分。
  • 它包含在域名之后,在任何查询参数之前。
  • 例如,如果请求的URL是"http://example.com/foo/bar/“,那么request.path将为”/foo/bar/"。

7、request.path_info

只能获取到路由地址,无法获取到参数

  • 用于表示请求URL的路径部分,不包括域名和查询参数。
  • request.path 相比,request.path_info 更加原始和未经解析。
  • 它保留了URL路径中的任何编码、特殊字符或斜杠等信息。
  • 例如,对于以下请求URL:“http://example.com/foo/bar/?page=2”,request.path_info 的值将是 “/foo/bar/”。
  • 通常情况下,您可以使用 request.path 来获取丢弃域名后的路径,而使用 request.path_info 来获取原始的、未解析的路径。这在某些情况下非常有用,例如当您需要对URL进行一些自定义操作或路由处理时。

8、request.get_full_path()

即能获取到路由地址又能获取到完整的路由地址后面的参数

  • request.get_full_path():该方法返回请求URL的完整路径,包括路径部分和任何查询参数。
  • 当您需要将完整URL作为字符串使用时,这个方法非常有用。
  • 例如,如果请求的URL是"http://example.com/foo/bar/?page=2",request.get_full_path()将返回"/foo/bar/?page=2"。

五、FBV与CBV

在 Django 中,视图(Views)是处理 Web 请求并返回 Web 响应的关键部分。Django 支持两种类型的视图:函数视图(Function-Based Views,FBV)和基于类的视图(Class-Based Views,CBV)。

视图函数既可以是函数也可以是类

1、函数视图(FBV):

(1)函数视图的特点

  • 使用函数来处理请求和生成响应。
  • 使用 Python 函数定义视图逻辑。
  • 相对简单,适合处理简单的请求逻辑。

(2)示例

from django.http import HttpResponse

def my_view(request):
    # 处理请求逻辑
    if request.method == 'GET':
        # 处理 GET 请求
        return HttpResponse('Hello, GET Request!')
    elif request.method == 'POST':
        # 处理 POST 请求
        return HttpResponse('Hello, POST Request!')

2、基于类的视图(CBV):

(1)类视图的特点

  • 使用基于类的方式来处理请求和生成响应。
  • 提供了更多的代码复用和组织结构。
  • 能够直接根据请求方式的不同直接匹配到对应的方法执行
  • Django 提供了许多内置的类视图,可以轻松地扩展和定制。

(2)示例

CBV路由

path('login/', views.MyLogin.as_view())

views.py

from django.views import View
from django.http import HttpResponse

class MyView(View):
    def get(self, request):
        return render(request, '02 form.html')

    def post(self, request):
        return HttpResponse('post方法')

3、如何选择 FBV 还是 CBV:

  • FBV 适用于

    • 简单的请求逻辑。
    • 较少的代码复用需求。
    • 对函数式编程更熟悉的开发者。
  • CBV 适用于

    • 复杂的请求逻辑。
    • 需要更多代码复用和组织结构的情况。
    • 对面向对象编程更熟悉的开发者。

在实际开发中,可以根据具体需求和个人喜好选择使用函数视图还是类视图来处理请求。 Django 提供了这两种选择,使开发者可以根据项目的需求来灵活地选择合适的视图类型。

六、CBV源码剖析

1、源码入口分析

FBV路由

path('login/',views.view)

CBV路由

path('login/',views.MyLogin.as_view())

上述代码在启动Django的时候就会立刻执行as_view方法。

分析:

由于函数名/方法名 加括号执行优先级最高

所以猜测as_view()

  • 要么是被@staticmethod修饰的静态方法
  • 要么是被@classmethod修饰的类方法

于是查看源码

as_view()是绑定给类的静态方法,将类作为第一个参数传进去

而view 这是一个闭包函数

  • 返回值是这个闭包函数的内存地址

  • 在启动Django项目时,就会立刻执行as_view方法

path('login/', views.view()),

得出结论

CBV跟FBV一模一样,他们在路由匹配本质上是一样的,都是路由对应函数内存地址。

在看源码的时候,一定要时刻提醒自己面向对象属性方法查找顺序
先从对象本身去找
再从产生对象的类中去找
之后再去父类里面找
...
总结:看源码只要看到了self点一个东西,一定要问自己当前这个self到底是谁

2、view 方法剖析

class View:

    http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']

    def __init__(self, **kwargs):
        for key, value in kwargs.items():
            setattr(self, key, value)

    @classonlymethod
    def as_view(cls, **initkwargs):
        """Main entry point for a request-response process."""
        for key in initkwargs:
            if key in cls.http_method_names:
                raise TypeError(
                    'The method name %s is not accepted as a keyword argument '
                    'to %s().' % (key, cls.__name__)
                )
            if not hasattr(cls, key):
                raise TypeError("%s() received an invalid keyword %r. as_view "
                                "only accepts arguments that are already "
                                "attributes of the class." % (cls.__name__, key))

        def view(request, *args, **kwargs):
            self = cls(**initkwargs)  # cls是我们自己写的类
            # self = MyLogin(**initkwargs)  产生一个我们自己写的类的对象
            self.setup(request, *args, **kwargs)
            if not hasattr(self, 'request'):
                raise AttributeError(
                    "%s instance has no 'request' attribute. Did you override "
                    "setup() and forget to call super()?" % cls.__name__
                )
            return self.dispatch(request, *args, **kwargs)
        view.view_class = cls
        view.view_initkwargs = initkwargs

        update_wrapper(view, cls, updated=())

        update_wrapper(view, cls.dispatch, assigned=())
        return view

    def setup(self, request, *args, **kwargs):
        if hasattr(self, 'get') and not hasattr(self, 'head'):
            self.head = self.get
        self.request = request
        self.args = args
        self.kwargs = kwargs

    # CBV 重点 !!!   
    def dispatch(self, request, *args, **kwargs):
        # 获取当前请求的小写格式,然后比对当前的请求方式是否合法
        if request.method.lower() in self.http_method_names:
            handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
         """
         反射:通过字符串来操作对象的属性或方法
         handler = getattr(自己的类产生的对象,'get',当找不到get属性或者方法的时候就会用第三个参数)
         handler = 自己写的类里面的get方法
         """
        else:
            handler = self.http_method_not_allowed
        return handler(request, *args, **kwargs)

    # 自动调用get方法
    def http_method_not_allowed(self, request, *args, **kwargs):
        logger.warning(
            'Method Not Allowed (%s): %s', request.method, request.path,
            extra={'status_code': 405, 'request': request}
        )
        return HttpResponseNotAllowed(self._allowed_methods())

    def options(self, request, *args, **kwargs):
        response = HttpResponse()
        response.headers['Allow'] = ', '.join(self._allowed_methods())
        response.headers['Content-Length'] = '0'
        return response

    def _allowed_methods(self):
        return [m.upper() for m in self.http_method_names if hasattr(self, m)]

3、小结

  • 当我们启动Django项目时,会自动触发路由中的方法,调用 as_view 方法并自动执行
  • 在执行后我们查看 as_view 方法的源码 发现
    • 在依次给我们的对象赋值后,最终返回了一个自执行的 dispatch 方法
  • 于是我们又去查看了 dispatch 方法
    • 在 dispatch 内部 ,先是将请求方式转换并进行校验
    • 然后开始校验需要调用的方法的调用位置,校验成功并拿到需要执行的方法执行
  • 在自己写的类中如果有相关的方法,会首先调用我们重写的类方法,并返回执行结果
    • 如果自己的类里面没有该方法 会去自己的父类中调用 父类的方法
    • 如果父类 以及 基类 都找不到则报错,抛出异常

七、给视图加装饰器

CBV中django不建议你直接给类的方法添加装饰器,无论该装饰器能否正常运行,都不建议直接加

1、使用装饰器装饰FBV

  • FBV本身就是一个函数,所以和给普通的函数加装饰器没有区别:
def wrapper(func):
    def inner(*args, **kwargs):
        start_time = time.time()
        ret = func(*args, **kwargs)
        end_time = time.time()
        print("used:", end_time-start_time)
        return ret
    return inner


# FBV版添加班级
@wrapper
def add_class(request):
    if request.method == "POST":
        class_name = request.POST.get("class_name")
        models.Classes.objects.create(name=class_name)
        return redirect("/class_list/")
    return render(request, "add_class.html")

2、使用装饰器装饰CBV

  • 类中的方法与独立函数不完全相同,因此不能直接将函数装饰器应用于类中的方法 ,我们需要先将其转换为方法装饰器。
  • Django中提供了method_decorator装饰器用于将函数装饰器转换为方法装饰器。
# CBV版添加班级
from django.views import View
from django.utils.decorators import method_decorator

def wrapper(func):
    def inner(*args, **kwargs):
        start_time = time.time()
        ret = func(*args, **kwargs)
        end_time = time.time()
        print("used:", end_time-start_time)
        return ret
    return inner
  
    
@method_decorator(wrapper, name='get')  # 方式二(可以添加多个针对不同的方法添加不同的装饰器)
@method_decorator(wrapper, name='post')    
class AddClass(View):
	@method_decorator(login_auth)  # 方式三:它会直接作用于当前类里面的所有的方法,但这也是弊端
    def dispatch(self, request, *args, **kwargs):
        pass
    
    @method_decorator(wrapper)  # 方式一:指名道姓
    def get(self, request):
        return render(request, "add_class.html")

    def post(self, request):
        class_name = request.POST.get("class_name")
        models.Classes.objects.create(name=class_name)
        return redirect("/class_list/")

3、CBV的拓展

  • 使用CBV时要注意,请求过来后会先执行dispatch()这个方法
  • 如果需要批量对具体的请求处理方法,如get,post等做一些操作的时候,这里我们可以手动改写dispatch方法,这个dispatch方法就和在FBV上加装饰器的效果一样。
class Login(View):
     
    def dispatch(self, request, *args, **kwargs):
        print('before')
        obj = super(Login,self).dispatch(request, *args, **kwargs)
        print('after')
        return obj
 
    def get(self,request):
        return render(request,'login.html')
 
    def post(self,request):
        print(request.POST.get('user'))
        return HttpResponse('Login.post')
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值