Django 模型继承问题
文章目录
- Django 模型继承问题
- 继承出现的情况
- `Meta` 和多表继承
- `Meta` 和多表继承
- 继承与反向关系
- 指定父类连接字段
- 代理模型
- `QuerySet` 仍会返回请求的模型
- 基类约束
- 代理模型管理器
- 代理继承和未托管的模型间的区别
- 多重继承
- 不能用字段名 "hiding"
- 在一个包中管理模型
Django 模型继承问题
Django 模型 ORM 继承最典型一个就是内置 RABC 权限六表中 auth_user 表的扩展。
首先我们先看看源码与实现方式。
auth_user 表源码
。
class User(AbstractUser):"""Users within the Django authentication system are represented by thismodel.Username and password are required. Other fields are optional."""class Meta(AbstractUser.Meta):swappable = 'AUTH_USER_MODEL'
AbstractUser 类源码
class AbstractUser(AbstractBaseUser, PermissionsMixin):"""An abstract base class implementing a fully featured User model withadmin-compliant permissions.Username and password are required. Other fields are optional."""username_validator = UnicodeUsernameValidator()username = models.CharField(_('username'),max_length=150,unique=True,help_text=_('Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.'),validators=[username_validator],error_messages={'unique': _("A user with that username already exists."),},)first_name = models.CharField(_('first name'), max_length=30, blank=True)last_name = models.CharField(_('last name'), max_length=150, blank=True)email = models.EmailField(_('email address'), blank=True)is_staff = models.BooleanField(_('staff status'),default=False,help_text=_('Designates whether the user can log into this admin site.'),)is_active = models.BooleanField(_('active'),default=True,help_text=_('Designates whether this user should be treated as active. ''Unselect this instead of deleting accounts.'),)date_joined = models.DateTimeField(_('date joined'), default=timezone.now)objects = UserManager()EMAIL_FIELD = 'email'USERNAME_FIELD = 'username'REQUIRED_FIELDS = ['email']class Meta:verbose_name = _('user')verbose_name_plural = _('users')abstract = Trues
我们可以发现, User 类基本上就是 继承了 AbstractUser 类, 其他什么都没写。
我们在 扩展 User 表时 也是去继承 AbstractUser 类,而不是 User 类。扩展方法如下:
class UserInfo(AbstractUser):phone = models.BigIntegerField()...需要注意的是:1. 在扩展之前没有执行过数据库迁移命令auth_user没有被创建,如果当前库已经创建了那么就需要重新换一个库2. 继承的类里面不要覆盖AbstractUser里面的字段名表里面的字段都不要动,只扩展额外字段即可3. 需要在配置文件中告诉django你要用UserInfo代理auth_user(******) AUTH_USER_MODEL = 'app01.UserInfo' # '应用名.表名' 声明认证用户表
那么问题来了。为啥要用User类去建表?为啥扩展 User 表时不继承 User 类 而是去继承 AbstractUser 类?
原因有多条,其中最广而易知的是 AbstractUser 是一个 抽象表,Meta 属性中设置了 abstract = Trues
属性。该类在执行建表命令 makemigrations 和 migrate 时不会去实现。
在本文中,会描述第二个原因。
继承出现的情况
举个栗子:
class Place(models.Model):name = models.CharField(max_length=50)address = models.CharField(max_length=80)class Restaurant(Place):serves_hot_dogs = models.BooleanField(default=False)serves_pizza = models.BooleanField(default=False)
当 Restaurant 表继承了 Place 表,执行建表语句后结果(我插入数据后截下来的图)
实际上 继承非抽象表 类 ,默认会加上一个 OnetoOneField 外键关系
class Place(models.Model):name = models.CharField(max_length=50)address = models.CharField(max_length=80)class Restaurant(Place):place_ptr = models.OneToOneField(Place, on_delete=models.CASCADE, parent_link=False)serves_hot_dogs = models.BooleanField(default=False)serves_pizza = models.BooleanField(default=False)
插入数据
pl = models.Place.objects.create(name="Bod2", address="shanghai")
print(pl)
Re = models.Restaurant.objects.create(name="Bod2", address="shanghai")
print(Re)
执行完后,你会发现 place 表中插入了两条数据,restaurant 表中只有一条数据。并且 place_ptr_id 值指向 place 表中的第二条数据。
Place
的所有字段均在 Restaurant
中可用,虽然数据分别存在不同的表中。所有,以下操作均可:
Place.objects.filter(name="Bob's Cafe")
Restaurant.objects.filter(name="Bob's Cafe")
若有一个 Place
同时也是 Restaurant
,你可以通过小写的模型名将 Place
对象转为 Restaurant
对象。
r = Restaurant.objects.get(id=3)
r.place_ptr p = Place.objects.get(id=3)
p.restaurant
<Restaurant: ...>
然而,若上述例子中的 p
不是 一个 Restaurant
(它仅是个 Place
对象或是其它类的父类),指向 p.restaurant
会抛出一个 Restaurant.DoesNotExist
异常。
但是我们可以通过设置 parent_link = True 来解决这个报错,允许 其通过父类 访问子类
class Place(models.Model):name = models.CharField(max_length=50)address = models.CharField(max_length=80)class Restaurant(Place):place_ptr = models.OneToOneField(Place, on_delete=models.CASCADE, parent_link=True)serves_hot_dogs = models.BooleanField(default=False)serves_pizza = models.BooleanField(default=False)
Meta
和多表继承
多表继承情况下,子类不会继承父类的 Meta。所以的 Meta 类选项已被应用至父类,在子类中再次应用会导致行为冲突(与抽象基类中应用场景对比,这种情况下,基类并不存在)。
故,子类模型无法访问父类的 Meta 类。不过,有限的几种情况下:若子类未指定 ordering
属性或 get_latest_by
属性,子类会从父类继承这些。
如果父类有排序,而你并不期望子类有排序,你可以显示的禁止它:
class ChildModel(ParentModel):# ...class Meta:# Remove parent's ordering effectordering = []
Meta
和多表继承
多表继承情况下,子类不会继承父类的 Meta。所以的 Meta 类选项已被应用至父类,在子类中再次应用会导致行为冲突(与抽象基类中应用场景对比,这种情况下,基类并不存在)。
故,子类模型无法访问父类的 Meta 类。不过,有限的几种情况下:若子类未指定 ordering
属性或 get_latest_by
属性,子类会从父类继承这些。
如果父类有排序,而你并不期望子类有排序,你可以显示的禁止它:
class ChildModel(ParentModel):# ...class Meta:# Remove parent's ordering effectordering = []
继承与反向关系
由于多表继承使用隐式的 OneToOneField
连接子类和父类,所以直接从父类访问子类是可能的,就像上述例子展示的那样。然而,使用的名字是ForeignKey
和 ManyToManyField
关系的默认值。如果你在继承父类模型的子类中添加了这些关联,你 必须 指定 related_name
属性。假如你忘了,Django 会抛出一个合法性错误。
比如,让我们用上面的 Place
类创建另一个子类,包含一个 ManyToManyField
:
class Supplier(Place):customers = models.ManyToManyField(Place)
这会导致以下错误:
Reverse query name for 'Supplier.customers' clashes with reverse query
name for 'Supplier.place_ptr'.HINT: Add or change a related_name argument to the definition for
'Supplier.customers' or 'Supplier.place_ptr'.
将 related_name
像下面这样加至 customers
字段能解决此错误: models.ManyToManyField(Place, related_name='provider')
指定父类连接字段
如上所述,Django 会自动创建一个 OneToOneField
,将子类连接回非抽象的父类。如果你想修改连接回父类的属性名,你可以自己创建 OneToOneField
,并设置 parent_link=True
,表明该属性用于连接回父类。
代理模型
使用多表继承时,每个子类模型都会创建一张新表。这一般是期望的行为,因为子类需要一个地方存储基类中不存在的额外数据字段。不过,有时候你只想修改模型的 Python 级行为——可能是修改默认管理器,或添加一个方法。
这是代理模型继承的目的:为原模型创建一个 代理。你可以创建,删除和更新代理模型的实例,所以的数据都会存储的像你使用原模型(未代理的)一样。不同点是你可以修改代理默认的模型排序和默认管理器,而不需要修改原模型。
代理模型就像普通模型一样申明。你需要告诉 Django 这是一个代理模型,通过将 Meta
类的proxy
属性设置为 True
。
例如,假设你想为 Person
模型添加一个方法。你可以这么做:
from django.db import modelsclass Person(models.Model):first_name = models.CharField(max_length=30)last_name = models.CharField(max_length=30)class MyPerson(Person):class Meta:proxy = Truedef do_something(self):# ...pass
MyPerson
类与父类 Person
操作同一张数据表。特别提醒, Person
的实例能通过 MyPerson
访问,反之亦然。
p = Person.objects.create(first_name="foobar")
MyPerson.objects.get(first_name="foobar")
<MyPerson: foobar>
你也可以用代理模型定义模型的另一种不同的默认排序方法。你也许不期望总对 “Persion” 进行排序,但是在使用代理时,总是依据 “last_name” 属性进行排序。这很简单:
class OrderedPerson(Person):class Meta:ordering = ["last_name"]proxy = True
现在,普通的 Person
查询结果不会被排序,但 OrderdPerson
查询接轨会按 last_name
排序。
代理模型继承“Meta”属性 和普通模型一样。
QuerySet
仍会返回请求的模型
当你用 Person
对象查询时,Django 永远不会返回 MyPerson
对象。Person
对象的查询结果集总是返回对应类型。代理对象存在的全部意义是帮你复用原 Person
提供的代码和自定义的功能代码(并未依赖其它代码)。不存在什么方法能在你创建完代理后,帮你替换所有 Person
(或其它)模型。
基类约束
一个代理模型必须继承自一个非抽象模型类。你不能继承多个非抽象模型类,因为代理模型无法在不同数据表之间提供任何行间连接。一个代理模型可以继承任意数量的抽象模型类,假如他们 没有 定义任何的模型字段。一个代理模型也可以继承任意数量的代理模型,只需他们共享同一个非抽象父类。
代理模型管理器
若你未在代理模型中指定模型管理器,它会从父类模型中继承。如果你在代理模型中指定了管理器,它会成为默认管理器,但父类中定义的管理器仍是可用的。
随着上面的例子一路走下来,你可以在查询 Person
模型时这样修改默认管理器:
from django.db import modelsclass NewManager(models.Manager):# ...passclass MyPerson(Person):objects = NewManager()class Meta:proxy = True
若你在不替换已存在的默认管理器的情况下,为代理添加新管理器,可以去自定义管理器中介绍的技巧:创建一个包含新管理器的基类,在继承列表中,主类后追加这个基类:
# Create an abstract class for the new manager.
class ExtraManagers(models.Model):secondary = NewManager()class Meta:abstract = Trueclass MyPerson(Person, ExtraManagers):class Meta:proxy = True
通常情况下,你可能不需要这么做。然而,你需要的时候,这也是可以的。
代理继承和未托管的模型间的区别
代理模型继承可能看起来和创建未托管的模型很类似,通过在模型的 Meta
类中定义 managed
属性。
通过小心地配置 Meta.db_table
,你将创建一个未托管的模型,该模型将对现有模型进行阴影处理,并添加一些 Python 方法。然而,这会是个经常重复的且容易出错的过程,因为你要在做任何修改时保持两个副本的同步。
另一方面,代理模型意在表现的和所代理的模型一样。它们总是与父模型保持一致,因为它们直接从福利继承字段和管理器。
通用性规则:
- 当你克隆一个已存在模型或数据表时,并且不想要所以的原数据表列,配置
Meta.managed=False
。这个选项在模型化未受 Django 控制的数据库视图和表格时很有用。 - 如果你只想修改模型的 Python 行为,并保留原有字段,配置
Meta.proxy=True
。这个配置使得代理模型在保存数据时,确保数据结构和原模型的完全一样。
多重继承
和 Python 中的继承一样,Django 模型也能继承自多个父类模型。请记住,Python 的命名规则这里也有效。第一个出现的基类(比如 Meta )就是会被使用的那个;举个例子,如果存在多个父类包含 Meta,只有第一个会被使用,其它的都会被忽略。
一般来说,你并不会同时继承多个父类。常见的应用场景是 “混合” 类:为每个继承此类的添加额外的字段或方法。试着保持你的继承层级尽可能的简单和直接,这样未来你就不用为了确认某段信息是哪来的而拔你为数不多的头发了。
注意,继承自多个包含 id
主键的字段会抛出错误。正确的使用多继承,你可以在基类中显示使用 AutoField
class Article(models.Model):article_id = models.AutoField(primary_key=True)...class Book(models.Model):book_id = models.AutoField(primary_key=True)...class BookReview(Book, Article):pass
或者在公共祖先中存储 AutoField
。这会要求为每个父类模型和公共祖先使用显式的 OneToOneField
,避免与子类自动生成或继承的字段发生冲突:
class Piece(models.Model):passclass Article(Piece):article_piece = models.OneToOneField(Piece, on_delete=models.CASCADE, parent_link=True)...class Book(Piece):book_piece = models.OneToOneField(Piece, on_delete=models.CASCADE, parent_link=True)...class BookReview(Book, Article):pass
不能用字段名 “hiding”
在普通的 Python 类继承中,允许子类重写父类的任何属性。在 Django 中,针对模型字段在,这一般是不允许的。如果有个非抽象模型基类,拥有一个名为 author
字段,你可以任意继承自基类的类中创建另一个模型字段,或定义一个叫 author
的属性。
此规范不针对从抽象模型基类继承获得的字段。这些字段可被其它字段或值重写,也可以通过配置 field_name = None
删除。
警告
模型管理器是由抽象基类继承来的。重写由 Manager
指定的字段可能会导致精细的 bug。
注解
某些字段在模型内定义了额外的属性,比如,一个 ForeignKey
定义了一个额外属性,名称为字段名接 _id
,并在外部模型中的添加 related_name
和 related_query_name
。
这些额外属性不能被重写,除非定义该属性的字段被修改或删除,这样就不会定义额外属性了。
在父模型中重写字段会在很多方面造成困难,比如创建新实例(特指那些字段在 Model.__init__
中初始化的那些)和序列化。这些特性,普通的 Python 类继承不需要用完全一样的方式处理,故此, Django 的模型继承和 Python 的类继承之间的区别不是随意的。
这些限制只针对那些是 Field
实例的属性。普通的 Python 属性可被随便重写。它还对 Python 能识别的属性生效:如果你同时在子类和多表继承的祖先类中指定了数据表的列名(它们是两张不同的数据表中的列)。
若你在祖先模型中重写了任何模型字段,Django 会抛出一个 FieldError
。
在一个包中管理模型
manage.py startapp
命令创建了一个应用结构,包含一个 models.py
文件。若你有很多 models.py
文件,用独立的文件管理它们会很实用。
为了达到此目的,创建一个 models
包。删除 models.py
,创建一个 myapp/models
目录,包含一个 __init__.py
文件和存储模型的文件。你必须在 __init__.py
文件中导入这些模块。
比如,若你在 models
目录下有 organic.py
和 synthetic.py
:
myapp/models/init.py
from .organic import Person
from .synthetic import Robot
显式导入每个模块,而不是使用 from .models import *
有助于不打乱命名空间,使代码更具可读性,让代码分析工具更有用。
相关文章:

Django 模型继承问题
文章目录Django 模型继承问题继承出现的情况Meta 和多表继承Meta 和多表继承继承与反向关系指定父类连接字段代理模型QuerySet 仍会返回请求的模型基类约束代理模型管理器代理继承和未托管的模型间的区别多重继承不能用字段名 "hiding"在一个包中管理模型Django 模型…...

Vue3篇.01-简介及基本使用,项目创建方式, 模板语法, 事件监听, 修饰符
一.简介1.概念Vue 是一款用于构建用户界面的 JS框架, 基于标准 HTML、CSS 和 JavaScript 构建,并提供了一套声明式的、组件化的编程模型, 高效地开发用户界面。渐进式框架, 适应不同需求进行开发。两个核心功能:声明式…...

别学英语了,真的
文 / 王不留(微信公众号:王不留) 这两年,很多朋友加我微信后,第一句常是,学英语有什么用啊? 我会统一给出真诚答复:没用,真的。 看新闻,中文海量信息已经严重…...

CRM系统五大技巧集成Excel为销售流程赋能
销售过程中有很多情况会降低团队的效率。通过正确的实施CRM客户管理系统,可以帮助您的企业自动执行手动任务、减少错误并专注于完成交易。这里有5个技巧,可以帮助您的销售人员通过CRM集成Excel为销售流程赋能并提高他们的整体效率。 技巧1:将…...

交通部互通互联码的根证书规则
引言 为了更好的服务交通互通互联码而更新这篇文章。 中金根证书其实是可以自己生成的。 代码内调整 中心公钥索引要保证自己的唯一性。 此处的唯一,是要保证在机具侧的唯一,因为他要根据这个索引去查找证书以及公钥。 提供根公钥给机具侧 生成的公钥…...

Map和Set(Java详解)
在开始详解之前,先来看看集合的框架: 可以看到Set实现了Collection接口,而Map又是一个单独存在的接口。 而最下面又分别各有两个类,分别是TreeSet(Map)和 HashSet(Map)。 TreeSet&…...
Vue 3的响应式机制
什么是响应式 Js代码是自上而下执行的,结合下面代码看,代码执行后,会打印两次double的结果,结果也都是2,即使修改了代码中count的值后,double的值也不会发生任何改变。 let count 1 let double count * …...

30岁了,说几句大实话
是的,我 30 岁了,还是周岁。 就在这上个月末,我度过了自己 30 岁的生日。 都说三十而立,要对自己有一个正确的认识,明确自己以后想做什么,能做什么。 想想时间,过得真快。 过五关斩六将&…...
AsyncTask使用及源码查看Android P
AsyncTask AsyncTask用于处理耗时任务,可以即时通知进度,最终返回结果。可以用于下载等处理。 使用 实现类继承三个方法 1. doInBackground后台执行,在此方法中进行延时操作 /*** Override this method to perform a computation on a back…...

花2个月面过华为测开岗,拿个30K不过分吧?
背景介绍 美本计算机专业,代码能力一般,之前有过两段实习以及一个学校项目经历。第一份实习是大二暑期在深圳的一家互联网公司做前端开发,第二份实习由于大三暑假回国的时间比较短(小于两个月),于是找的实…...
JAVA练习51-最大子数组和
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 目录 前言 一、题目-最大子数组和 1.题目描述 2.思路与代码 2.1 思路 2.2 代码 总结 前言 提示:这里可以添加本文要记录的大概内容: 2月15日练…...

Inception Transformer
paper链接: https://arxiv.org/abs/2205.12956v2 code链接: https://github.com/sail-sg/iFormer Inception Transformer一、引言二、实现细节三、实验一、分类二、检测三、分割四、消融实验一、引言 最近的研究表明,Transformer具有很强的建立远程依赖关系的能力…...

10分钟学会数据库压力测试,你敢信?
目录 前言 查看数据库版本 下载驱动: 菜单路径 配置 Variable Name Bound to Pool模块配置 Connection pool configuration模块配置 Database Connection Configuration模块配置 菜单路径 Variable Name Bound to Pool 脚本结构 脚本(执行查询…...

论文阅读 | Video Super-Resolution Transformer
引言:2021年用Transformer实现视频超分VSR的文章,改进了SA并在FFN中加入了光流引导 论文:【here】 代码:【here】 Video Super-Resolution Transformer 引言 视频超分中有一组待超分的图片,因此视频超分也经常被看做…...
7-6 带头节点的双向循环链表操作
本题目要求读入一系列整数,依次插入到双向循环链表的头部和尾部,然后顺序和逆序输出链表。 链表节点类型可以定义为 typedef int DataType; typedef struct LinkedNode{DataType data;struct LinkedNode *prev;struct LinkedNode *next; }LinkedNode;链…...
npm publish 、 npm adduser 提示 403 的问题
0. 查看使用的源:npm config get registry1. 如果使用的不是官方的源,切换:npm config set registry https://registry.npmjs.org/2. 登录:npm adduser3. 查看是否登录成功:npm whoami4. 执行发布命令:npm …...
Java 8的函数式接口使用示例
什么是函数式接口 有且只有一个抽象方法的接口被称为函数式接口,函数式接口适用于函数式编程的场景,Lambda就是Java中函数式编程的体现,可以使用Lambda表达式创建一个函数式接口的对象,一定要确保接口中有且只有一个抽象方法&…...

2023年企业如何改善员工体验?为什么员工体验很重要?
什么是员工体验?大约 96% 的企业领导者表示,专注于员工体验可以更轻松地留住顶尖人才。[1] 这还不是全部。令人震惊的是,87%的企业领导者还表示,优先考虑员工的幸福感将给他们带来竞争优势。尽管有这些发现,但只有19%的…...

设计模式:桥接模式让抽象和实现解耦,各自独立变化
一、问题场景 现在对”不同手机类型“的 “不同品牌”实现操作编程(比如: 开机、关机、上网,打电话等) 二、传统解决方案 传统方案解决手机使用问题类图: 三、传统方案分析 传统方案解决手机操作问题分析 1、扩展性问题(类爆炸),如果我们…...

C++学习记录——십 STL初级认识、标准库string类
文章目录1、什么是STL2、STL简介3、什么是string类4、string类的常用接口说明1、常见构造函数2、容量操作3、迭代器4、其他的标准库的string类关于string类的内容,可以在cplusplus.com查看到。 1、什么是STL STL是C标准库的重要组成部分,不仅是一个可复…...

eNSP-Cloud(实现本地电脑与eNSP内设备之间通信)
说明: 想象一下,你正在用eNSP搭建一个虚拟的网络世界,里面有虚拟的路由器、交换机、电脑(PC)等等。这些设备都在你的电脑里面“运行”,它们之间可以互相通信,就像一个封闭的小王国。 但是&#…...
【网络】每天掌握一个Linux命令 - iftop
在Linux系统中,iftop是网络管理的得力助手,能实时监控网络流量、连接情况等,帮助排查网络异常。接下来从多方面详细介绍它。 目录 【网络】每天掌握一个Linux命令 - iftop工具概述安装方式核心功能基础用法进阶操作实战案例面试题场景生产场景…...
Vue记事本应用实现教程
文章目录 1. 项目介绍2. 开发环境准备3. 设计应用界面4. 创建Vue实例和数据模型5. 实现记事本功能5.1 添加新记事项5.2 删除记事项5.3 清空所有记事 6. 添加样式7. 功能扩展:显示创建时间8. 功能扩展:记事项搜索9. 完整代码10. Vue知识点解析10.1 数据绑…...
ssc377d修改flash分区大小
1、flash的分区默认分配16M、 / # df -h Filesystem Size Used Available Use% Mounted on /dev/root 1.9M 1.9M 0 100% / /dev/mtdblock4 3.0M...

2025盘古石杯决赛【手机取证】
前言 第三届盘古石杯国际电子数据取证大赛决赛 最后一题没有解出来,实在找不到,希望有大佬教一下我。 还有就会议时间,我感觉不是图片时间,因为在电脑看到是其他时间用老会议系统开的会。 手机取证 1、分析鸿蒙手机检材&#x…...
Element Plus 表单(el-form)中关于正整数输入的校验规则
目录 1 单个正整数输入1.1 模板1.2 校验规则 2 两个正整数输入(联动)2.1 模板2.2 校验规则2.3 CSS 1 单个正整数输入 1.1 模板 <el-formref"formRef":model"formData":rules"formRules"label-width"150px"…...

关键领域软件测试的突围之路:如何破解安全与效率的平衡难题
在数字化浪潮席卷全球的今天,软件系统已成为国家关键领域的核心战斗力。不同于普通商业软件,这些承载着国家安全使命的软件系统面临着前所未有的质量挑战——如何在确保绝对安全的前提下,实现高效测试与快速迭代?这一命题正考验着…...
IP如何挑?2025年海外专线IP如何购买?
你花了时间和预算买了IP,结果IP质量不佳,项目效率低下不说,还可能带来莫名的网络问题,是不是太闹心了?尤其是在面对海外专线IP时,到底怎么才能买到适合自己的呢?所以,挑IP绝对是个技…...

深入浅出深度学习基础:从感知机到全连接神经网络的核心原理与应用
文章目录 前言一、感知机 (Perceptron)1.1 基础介绍1.1.1 感知机是什么?1.1.2 感知机的工作原理 1.2 感知机的简单应用:基本逻辑门1.2.1 逻辑与 (Logic AND)1.2.2 逻辑或 (Logic OR)1.2.3 逻辑与非 (Logic NAND) 1.3 感知机的实现1.3.1 简单实现 (基于阈…...

Selenium常用函数介绍
目录 一,元素定位 1.1 cssSeector 1.2 xpath 二,操作测试对象 三,窗口 3.1 案例 3.2 窗口切换 3.3 窗口大小 3.4 屏幕截图 3.5 关闭窗口 四,弹窗 五,等待 六,导航 七,文件上传 …...