Django完整入门规范介绍与使用

1. Django 介绍

MVC 模型

  • 大部分开发语言中都有 MVC 开发模型。
  • MVC 框架的核心思想是:解耦——降低各功能模块之间的耦合性,方便变更,更容易重构代码,最大程度上实现代码的重用。
  • M 表示 Model,主要用于对数据库层的封装。
  • V 表示 View,用于向用户展示结果。
  • C 表示 Controller,是核心,用于处理请求、获取数据、返回结果。

MVT 模型

  • Django 是一款 python 的 web 开发框架。
  • 与 MVC 有所不同,属于 MVT 框架。
  • M 表示 Model,负责与数据库交互。
  • V 表示 View,是核心,负责接收请求、获取数据、返回结果。
  • T 表示 Template,负责呈现内容到浏览器。

Django 开发流程范例

  1. 安装配置 Django 的运行环境, 参考上一篇文章:首次创建Django项目初始化-CSDN博客
  2. 创建项目、创建应用。
  3. 在应用的 model.py 定义模型类,执行迁移,生成对应的数据表(可使用简单 API 与数据库交互测试)。
  4. 使用 Django 的后台管理界面维护数据。
  5. 在应用的 views.py 定义视图,即处理核心逻辑的函数。
  6. 在项目目录下创建对应应用的模板目录,编写 html 页面(这种方式为前后端一体,也可以使用更加好维护的方式,前后端分离,vue作为前端部分)。
  7. 在项目和应用的 urls.py 中配置 URL 与视图的映射。
  8. 通过视图接收指定 URL 的访问请求,通过模型操作数据,通过模板填充数据并完成页面展示。

2. Django 环境搭建

参考上一篇文章:首次创建Django项目初始化-CSDN博客

2)创建工程项目

参考上一篇文章:首次创建Django项目初始化-CSDN博客

我们在上一篇文章中说过,创建完Django项目后,会得到一个完整的Django项目目录,接着上一篇,我们创建的项目名称为django-rebort

项目名称 django-rebort 中的目录说明

  • manage.py:一个命令行工具,可以使你用多种方式对 Django 项目进行交互。
  • django-rebort:与我们命名的项目名称一致,真正的本项目 python 包。
  • _init _.py:一个空文件,它告诉 python 这个目录应该被看做一个 python 包。
  • settings.py:项目的配置。
  • urls.py:项目的 url 声明。
  • wsgi.py:项目与 wsgi 兼容的 web 服务器入口。

3)创建应用

在一个项目中可以创建一到多个应用,一个应用专门处理一种业务。

参考上一篇文章 :首次创建Django项目初始化-CSDN博客

应用的目录结构如下图:

  • migrations 包:根据模型类生成 SQL 语句,并作用到对应的数据库。
  • admin.py:对本应用进行相关的管理。
  • models.py:定义模型类(有多少个数据表,就有多少个模型类与之对应)。
  • tests.py:Django 自带的测试模块,这里我没用,所以删除了
  • views.py:定义视图相关函数。

将创建的应用配置进项目的 settings.py 中:(当不需要迁移时则可不做此步配置)

INSTALLED_APPS = (
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rebort-app'  # 新增的应用
)

3. 模型(Model)

设计介绍

本示例完成“图书-英雄”两种信息的维护。“图书”、“英雄”的关系为一对多。

“图书”表结构设计:

  • 表名:BookInfo
  • 图书名称:btitle
  • 图书发布时间:bpub_date

“英雄”表结构设计:

  • 表名:HeroInfo
  • 英雄姓名:hname
  • 英雄性别:hgender
  • 英雄简介:hcontent
  • 所属图书:hbook

数据库配置

  • 在 settings.py 文件中,通过 DATABASES 项进行数据库设置。
  • Django 支持的数据库包括 SQLite、MySQL 等主流数据库。
  • Django 默认使用自带的 SQLite 数据库。

定义模型类

步骤如下:

  1. 打开应用的 models.py 文件
  2. 引入包 from django.db import models
  3. 模型类继承 models.Model 类
  4. 说明:不需要定义主键列,在生成时会自动添加,并且值为自动增长
  5. 当输出对象时,会调用对象的 str 方法
 1 from django.db import models
 2 
 3 # 声明表名
 4 class BookInfo(models.Model):
 5     # 声明表字段
 6     book_title = models.CharField(max_length=20)  # 字符串类型且限定数据长度
 7     book_public_date = models.DateTimeField()  # 时间类型
 8 
 9     def __str__(self):
10         return "%d" % self.pk  # 返回主键列的值
11 
12 
13 class HeroInfo(models.Model):
14 
15     name = models.CharField(max_length=20)
16     gender = models.BooleanField()  # 布尔类型
17     content = models.CharField(max_length=100)
18     Book = models.ForeignKey('BookInfo')  # 主/外键关联(BookInfo 加不加引号都行)
19 
20     def __str__(self):
21         return "%d" % self.pk

迁移:生成数据表

1)激活模型:编辑 settings.py 文件,将 创建的app应用加入到 INSTALLED_APPS中

2)生成迁移文件:根据模型类生成表结构的相关 sql 脚本(记录关于 models.py 的所有改动,但是还没有作用到数据库中)

python manage.py makemigrations

迁移文件被生成到应用的 migrations 目录,迁移文件中就是模型定义的对应 sql 脚本。

3)执行迁移:即执行 sql 脚本,生成数据表(将 models.py 的所有改动作用到数据库中)

python manage.py migrate

4)Django模型修改以及迁移操作

Migrations

Django中对Model进行修改是件比较麻烦的事情,syncdb命令仅仅创建数据库里还没有的表,它并不对已存在的数据表进行同步修改,也不处理数据模型的删除。 如果你新增或修改数据模型里的字段,或是删除了一个数据模型,你需要手动在数据库里进行相应的修改或者使用South。Django 1.7中已经集成了South的代码,提供了3个新命令:

  • migrate: 用于执行迁移动作,具有syncdb的功能
  • makemigrations: 基于当前的model创建新的迁移策略文件
  • sqlmigrate: 显示迁移的SQL语句,具有sqlall的功能

使用起来很简单,对Model做了修改后,使用makemigrations记录修改:

$ python manage.py makemigrations

Migrations for 'books':

  0003_auto.py:

    - Alter field author on book

 

你的Model会被扫描, 然后与migrations文件夹中以前的版本作比较, 然后生成本次迁移文件。

 

有了新的migration文件,就可以使用migrate修改数据库模式:

$ python manage.py migrate

Operations to perform:

  Synchronize unmigrated apps: sessions, admin, messages, auth, staticfiles, contenttypes

  Apply all migrations: books

Synchronizing apps without migrations:

  Creating tables...

  Installing custom SQL...

  Installing indexes...

Installed 0 object(s) from 0 fixture(s)

Running migrations:

  Applying books.0003_auto... OK

 

也可以针对单独的app生成migration:

 

$ python manage.py makemigrations rebort-app

 

也可以对数据库中的数据进行修改,首先建立一个空的migration文件:

 

python manage.py makemigrations --empty rebort-app

 

文件的内容如下:

 

# -*- coding: utf-8 -*-

from django.db import models, migrations



class Migration(migrations.Migration):



    dependencies = [

        ('rebort-app', '0001_initial'),

    ]



    operations = [

    ]

 

如果想修改某个Model例如Person的数据,设置其name字段:

 

# -*- coding: utf-8 -*-

from django.db import models, migrations



def combine_names(apps, schema_editor):

    # We can't import the Person model directly as it may be a newer

    # version than this migration expects. We use the historical version.

    Person = apps.get_model("yourappname", "Person")

    for person in Person.objects.all():

        person.name = "%s %s" % (person.first_name, person.last_name)

        person.save()



class Migration(migrations.Migration):



    dependencies = [

        ('rebort-app', '0001_initial'),

    ]



    operations = [

        migrations.RunPython(combine_names),

    ]

 

最后运行 python manage.py migrate即可。这样Person中的所有对象的name字段都设置好了。

 

依据Model修改关系数据库是开发中的一个重要的问题,解决这个问题可以提升开发速度,不过要在生产环境中随便使用migrate操作数据库还是很危险的,有时候需要手动修改数据库。

手动修改数据库

当处理模型修改的时候:

  • 如果模型包含一个未曾在数据库里建立的字段,Django会报出错信息。 当你第一次用Django的数据库API请求表中不存在的字段时会导致错误。
  • Django不关心数据库表中是否存在未在模型中定义的列。
  • Django不关心数据库中是否存在未被模型表示的table。

添加字段

  1. 在你的模型里添加字段。下例向Book模型添加num_pages字段:

class Book(models.Model):

    title = models.CharField(max_length=100)

    authors = models.ManyToManyField(Author)

    publisher = models.ForeignKey(Publisher)

    publication_date = models.DateField()

    **num_pages = models.IntegerField(blank=True, null=True)**



    def __unicode__(self):

        return self.title 
  1. 运行manage.py sqlall yourappname来测试模型新的CREATE TABLE语句。

CREATE TABLE "books_book" (

    "id" serial NOT NULL PRIMARY KEY,

    "title" varchar(100) NOT NULL,

    "publisher_id" integer NOT NULL REFERENCES "books_publisher" ("id"),

    "publication_date" date NOT NULL,

    "num_pages" integer NULL

);
  1. 开启你的数据库的交互命令界面(比如,psql或者mysql,或者可以使用manage.py dbshell。 执行ALTER TABLE语句来添加新列。

ALTER TABLE books_book ADD COLUMN num_pages integer; 

添加 非NULL 字段

先创建 NULL 型的字段,然后将该字段的值填充为某个默认值,然后再将该字段改为 NOT NULL 型

BEGIN;

ALTER TABLE books_book ADD COLUMN num_pages integer;

UPDATE books_book SET num_pages=0;

UPDATE books_book SET num_pages = NULL;

COMMIT;

 

或者

 

ALTER TABLE <YourTable> ADD <NewColumn> <NewColumnType> NOT NULL DEFAULT <DefaultValue>;

 

添加ForeignKey或ManyToManyField

 

添加外键即是添加key_id的integer字段,添加多对多字段是创建一个新的数据表。

删除字段

比较简单,将表中的某列删掉即可

ALTER TABLE books_book DROP COLUMN num_pages;

 

使用sqlite3时,会有些麻烦,sqlite3不支持删除列操作,只有有限地 ALTER TABLE 支持。你可以使用它来在表的末尾增加一列,可更改表的名称。 如果需要对表结构做更复杂的改变,则必须重新建表。重建时可以先将已存在的数据放到一个临时表中,删除原表, 创建新表,然后将数据从临时表中复制回来。

 

如,假设有一个 t1 表,其中有 "a", "b", "c" 三列, 如果要删除列 c :

BEGIN TRANSACTION;

CREATE TEMPORARY TABLE t1_backup(a,b);

INSERT INTO t1_backup SELECT a,b FROM t1;

DROP TABLE t1;

CREATE TABLE t1(a,b);

INSERT INTO t1 SELECT a,b FROM t1_backup;

DROP TABLE t1_backup;

COMMIT;

 

删除多对多关联字段

 

删掉多对多关联的数据表即可

DROP TABLE books_book_authors;

 

删除模型

 

删除数据表即可

DROP TABLE books_book;

 

数据迁移

 

django 项目提供了一个导出的方法 python manage.py dumpdata, 不指定 appname 时默认为导出所有的app

python manage.py dumpdata rebort-app > rebort-app.json

 

导出的文件内容格式:

 

[

  {

    "model": "rebort-app.person",

    "pk": 1,

    "fields": {

      "first_name": "John",

      "last_name": "Lennon"

    }

  },

  {

    "model": "rebort-app.person",

    "pk": 2,

    "fields": {

      "first_name": "Paul",

      "last_name": "McCartney"

    }

  }

]

 

数据导入:

 

python manage.py loaddata rebort-app.json

 

导出用户数据:

 

python manage.py dumpdata auth > auth.json

 

数据操作测试

 

进入 python shell,进行简单的模型 API 练习:

python manage.py shell

操作“图书”对象

# 引入需要的包
>>> from booktest.models import BookInfo,HeroInfo
>>> from django.utils import timezone
>>> from datetime import *

# 查询所有图书数据
>>> BookInfo.objects.all()
[]

# 新建图书数据
>>> b = BookInfo()  # 映射表名
>>> b.book_title = "射雕英雄传"  # 映射表字段
>>> b.book_public_date = datetime(year=1990, month=1, day=10)
>>> b.save()
  
# 查找图书数据
>>> b = BookInfo.objects.get(pk=1)  # 根据主键查找

# 输出图书数据
>>> b
<BookInfo: 1>
>>> b.id
1
>>> b.book_title
'射雕英雄传'

# 修改图书数据
>>> b.book_title = "天龙八部"
>>> b.save()

# 删除图书数据
>>> b.delete()

操作“英雄”对象:关联对象的操作

  • 对于 HeroInfo 可以按照上面的操作方式进行。
  • 注意添加关联对象(图书)。
>>> h = HeroInfo()
>>> h.name = "郭靖"
>>> h.gender = True
>>> h.content = "降龙十八掌"
>>> h.Book = b
>>> h.save()

# 获得关联集合:返回当前BookInfo对象的所有HeroInfo对象
>>> b.heroinfo_set.all()
[<HeroInfo: 1>]

# 有一个HeroInfo对象存在,就必须对应一个BookInfo对象
# 另一种创建关联的方式
>>> h = b.heroinfo_set.create(name="黄蓉", gender=False, content="打狗棒法")
>>> h
<HeroInfo: 2>
>>> h.name
'黄蓉'

4. 站点管理 

1)服务器启停

运行如下命令可以开启服务器:

python manage.py runserver ip:port

修改端口:

python manage.py runserver 8080
  • 可以不写 ip,默认端口为 8000。
  • 这是一个纯 python 编写的轻量级 web 服务器,仅在开发阶段使用。
  • 服务器成功启动后,提示如下信息:
E:\DjangoDemo>python manage.py runserver
Performing system checks...

System check identified no issues (0 silenced).
March 31, 2025 - 9:23:43
Django version 1.8.2, using settings 'DjangoDemo.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.
  • 打开浏览器,输入网址“127.0.0.1:8000”可以打开默认页面。
  • 修改文件后不需要重启服务器;如果是增删文件则需要重启服务器。
  • 通过 ctrl+c 停止服务器。

2)管理操作

  • 站点分为“内容发布”和“公共访问”两部分。
  • “内容发布”的部分负责添加、修改、删除内容,开发这些重复的功能是一件单调乏味、缺乏创造力的工作。为此,Django 会根据定义的模型类完全自动地生成管理模块。

使用 django 的管理

  • 执行以下命令创建一个管理员用户,按提示输入用户名、密码、邮件:
python manage.py createsuperuser
  • 启动服务器,通过“127.0.0.1:8000/admin”访问,输入上面创建的用户名、密码完成登录;
  • 进入管理站点,默认可以对 groups、users 进行管理。

管理界面本地化

  • 编辑 settings.py 文件,设置中文编码和中国时区,上一篇中有提到,可以参考上一篇文章
LANGUAGE_CODE = 'zh-Hans'
TIME_ZONE = 'Asia/Shanghai'

向 admin 注册 hero_info 的相关模型

  • 打开 hero_book/admin.py 文件,注册模型
from django.contrib import admin
from models import BookInfo

admin.site.register(BookInfo)
admin.site.register(HeroInfo)
  • 刷新管理页面,可以对 BookInfo 的数据进行增删改查操作。
  • 问题:如果要在 str() 方法中返回中文,在修改和添加时会报 ascii 的错误。
  • 解决:在 str() 方法中,将字符串末尾添加“.encode('utf-8')”。

自定义管理页面

  • Django 提供了 admin.ModelAdmin 类。
  • 通过自定义 ModelAdmin 的子类,来定义模型在站点管理界面的显示方式。

列表页的相关属性

 1 from django.contrib import admin
 2 from .models import *
 3 
 4 
 5 # 自定义ModelAdmin的子类,定义BookInfo模型在站点管理界面的显示方式
 6 class BookInfoAdmin(admin.ModelAdmin):
 7     list_display = ['pk', 'book_title', 'book_public_date']  # list_display:显示字段,可以点击列头进行排序
 8     list_filter = ['book_title']  # list_filter:过滤字段,过滤框会出现在右侧
 9     search_fields = ['book_title']  # search_fields:搜索字段,搜索框会出现在上侧
10     list_per_page = 10  # list_per_page:分页,分页框会出现在下侧
11 
12 
13 admin.site.register(BookInfo, BookInfoAdmin)  # 将ModelAdmin子类与对应的模型类放一起
14 admin.site.register(HeroInfo)

界面效果:

 添加/修改页的相关属性

 1 from django.contrib import admin
 2 from .models import *
 3 
 4 
 5 # 自定义ModelAdmin的子类,定义BookInfo模型在站点管理界面的显示方式
 6 class BookInfoAdmin(admin.ModelAdmin):
 7 
 8     '''列表页属性'''
 9     list_display = ['pk', 'book_title', 'book_public_date']  # list_display:显示字段,可以点击列头进行排序
10     list_filter = ['book_title']  # list_filter:过滤字段,过滤框会出现在右侧
11     search_fields = ['book_title']  # search_fields:搜索字段,搜索框会出现在上侧
12     list_per_page = 10  # list_per_page:分页,分页框会出现在下侧
13 
14     '''添加、修改页属性'''
15     # fields:属性的先后顺序
16     # fields = ['book_public_date', 'book_title']
17     # fieldsets:属性分组
18     fieldsets = [
19         ('basic', {'fields': ['book_title']}),
20         ('more', {'fields': ['book_public_date']}),
21     ]
22 
23 
24 admin.site.register(BookInfo, BookInfoAdmin)  # 将ModelAdmin子类与对应的模型类放一起
25 admin.site.register(HeroInfo)

页面效果:

3)关联对象

对于 HeroInfo 模型类,有两种注册方式:

  • 方式一:与 BookInfo 模型类相同的注册方式
  • 方式二:关联注册

接下来实现关联注册

 1 from django.contrib import admin
 2 from .models import *
 3 
 4 
 5 # 自定义admin.StackedInline的子类,实现关联对象的注册
 6 class HeroInfoInline(admin.StackedInline):
 7     model = HeroInfo
 8     extra = 2
 9 
10 
11 # 自定义ModelAdmin的子类,定义BookInfo模型在站点管理界面的显示方式
12 class BookInfoAdmin(admin.ModelAdmin):
13 
14     '''列表页的相关属性'''
15     list_display = ['pk', 'book_title', 'book_public_date']  # list_display:显示字段,可以点击列头进行排序
16     list_filter = ['book_title']  # list_filter:过滤字段,过滤框会出现在右侧
17     search_fields = ['book_title']  # search_fields:搜索字段,搜索框会出现在上侧
18     list_per_page = 10  # list_per_page:分页,分页框会出现在下侧
19 
20     '''添加/修改页的相关属性'''
21     # fields:属性的先后顺序
22     # fields = ['book_public_date', 'book_title']
23     # fieldsets:属性分组
24     fieldsets = [
25         ('basic', {'fields': ['book_title']}),
26         ('more', {'fields': ['book_public_date']}),
27     ]
28 
29     '''实现关联对象的方式二'''
30     inlines = [HeroInfoInline]
31 
32 
33 admin.site.register(BookInfo, BookInfoAdmin)  # 将ModelAdmin子类与对应的模型类放一起
34 # admin.site.register(HeroInfo)  # 实现关联对象的方式一

还可以将内嵌的方式改为表格,只需替换继承的父类:

class HeroInfoInline(admin.TabularInline)

4)布尔值的显示

发布性别的显示不是一个直观的结果,可以使用方法进行封装:

1 def gender(self):
2     if self.hgender:
3         return '男'
4     else:
5         return '女'
6 
7 gender.short_description = '性别'

在 admin 注册中使用函数名 gender 代替类属性 gender:

class HeroInfoAdmin(admin.ModelAdmin):
    list_display = ['id', 'name', 'gender', 'content']

5. 视图(View)

views.py

  • 在 Django 中,视图负责对 web 请求进行回应。
  • 视图接收 reqeust 对象作为第一个参数,该参数包含了请求的信息。
  • 视图就是一个 python 函数,被定义在 views.py 中。
1 from django.http import HttpResponse  # 引入响应对象
2 
3 # 定义访问主页时的响应
4 def index(request):  # 视图函数的第一个参数必须是request(请求)对象
5     return HttpResponse("welcome index page!")  # 视图函数返回的必须是HttpResponse(响应)对象或其子类
6 
7 # 定义访问详情页时的响应
8 def detail(request, id):
9     return HttpResponse("detail:%s" % id)

定义完成视图后,需要配置 URLconf,否则无法处理请求。

URLconf

  • 在 Django 中,定义 URLconf 包括两方面内容:配置正则表达式和视图。
  • Django 使用正则表达式匹配请求 URL,一旦匹配成功,则调用对应的视图。
  • 注意:只匹配路径部分,即除去域名、参数后的字符串。
  • 在 DjangoDemo/urls.py 中新增 hero_book.urls,使主 URLconf 连接到 hero_book.urls 模块。

注意:django 2.0 起,urls 不支持正则表达式问题。如果需要使用正则,需要导入 re_path,使用方法如下:

from django.urls import path, re_path

urlpatterns = [
    path('admin/', admin.site.urls),
    re_path(r'^test-(\d+)-(\d+)/', views.test),
    path('index/', views.index),
]

示例

DjangoDemo/urls.py:

urlpatterns = [
    url(r'^admin/', include(admin.site.urls)),
    url(r'^', include('hero_book.urls')),  # 关联hero_book包下的urls模块
]

hero_book/urls.py:

1 from django.conf.urls import url
2 from . import views
3 
4 
5 urlpatterns = [
6     url(r'^$', views.index),  # URI不带多余字符时访问index函数
7     url(r'^book/([0-9]+)/$', views.detail),  # URI带数字时访问detail函数
8 ]

页面效果

 

 

 

6. 模板(Template)

模板其实就是 html 页面,可以根据视图中传递的数据填充值。

(实际上,文件扩展名并不一定要是 html。只要文件中是 html 的内容,使用其他文件扩展名也可。)

创建模板目录

1)在项目目录下创建模板的目录,如下图:

 

为了清晰起见,一个模板目录对应一个应用,存放该应用的所有页面。

2)修改应用的 settings.py 文件,设置 TEMPLATES 的 DIRS 值:

'DIRS': [os.path.join(BASE_DIR, "templates")],  # 关联到项目目录下的templates目录(BASE_DIR是setings.py中已定义好的变量)

定义模板

在模板中访问视图传递的数据的方式:

{{输出值,可以是变量,也可以是对象.属性}}
{%执行代码段%}

index.html

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5     <title>首页</title>
 6 </head>
 7 <body>
 8 <h1>图书列表</h1>
 9 <ul>
10     {%for book in booklist%}
11     <li>
12         <a href="book/{{book.id}}">{{book.book_title}}</a>
13     </li>
14     {%endfor%}
15 </ul>
16 </body>
17 </html>

detail.html

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5     <title>详情页</title>
 6 </head>
 7 <body>
 8 <h1>编号:{{book.id}}</h1>
 9 <ul>
10     {%for hero in book.heroinfo_set.all%}
11     <li>{{hero.name}}——{{hero.content}}</li>
12     {%endfor%}
13 </ul>
14 </body>
15 </html>

使用模板

编辑 views.py,在方法中调用对应的模板:

 1 from django.shortcuts import render
 2 from .models import BookInfo
 3 
 4 # 首页
 5 def index(request):
 6     # 获取所有BookInfo对象
 7     booklist = BookInfo.objects.all()
 8     # render 参数2:指定模板文件;参数3:将booklist对象列表传递给模板页面中引用
 9     return render(request, 'hero_book/index.html', {'booklist': booklist})
10 
11 # 详情页
12 def detail(request, id):
13     # 将url正则中的分组作为主键,获取对应的BookInfo对象
14     book = BookInfo.objects.get(pk=id)
15     return render(request, 'hero_book/detail.html', {'book': book})

页面效果

 

 

Django相对于Flask框架,稍微会在操作上有点复杂,如果你对Flask感兴趣,可以看我Flask框架部分,将带你从0开发一个平台的完整介绍

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值