Day24-Ajax操作、图片验证码、KindEditor使用-转
参考源:http://blog.csdn.net/fgf00/article/details/54917439
三、Ajax操作
ajax操作基于浏览器的xmlHttpRequest对象,IE低版本是另外一个对象,jQuery 1 版本对那两个对象做了封装,兼容性最好,2 、3版本不再支持IE低版本了。
Ajax操作,用来偷偷发请求。
参考博客:
http://www.cnblogs.com/wupeiqi/articles/5703697.html
1、原生Ajax操作
XmlHttpRequest对象介绍
- XmlHttpRequest对象的主要方法:
- void open(String method,String url,Boolen async)
- 用于创建请求
- 参数:
- method: 请求方式(字符串类型),如:POST、GET、DELETE...
- url: 要请求的地址(字符串类型)
- async: 是否异步(布尔类型)
- void send(String body)
- 用于发送请求
- 参数:
- body: 要发送的数据(字符串类型)
- void setRequestHeader(String header,String value)
- 用于设置请求头
- 参数:
- header: 请求头的key(字符串类型)
- vlaue: 请求头的value(字符串类型)
- String getAllResponseHeaders()
- 获取所有响应头
- 返回值:
- 响应头数据(字符串类型)
- String getResponseHeader(String header)
- 获取响应头中指定header的值
- 参数:
- header: 响应头的key(字符串类型)
- 返回值:
- 响应头中指定的header对应的值
- void abort()
- 终止请求
XmlHttpRequest对象的主要属性:
- a. Number readyState
- 状态值(整数)
- 详细:
- 0-未初始化,尚未调用open()方法;
- 1-启动,调用了open()方法,未调用send()方法;
- 2-发送,已经调用了send()方法,未接收到响应;
- 3-接收,已经接收到部分响应数据;
- 4-完成,已经接收到全部响应数据;
- b. Function onreadystatechange
- 当readyState的值改变时自动触发执行其对应的函数(回调函数)
- c. String responseText
- 服务器返回的数据(字符串类型)
- d. XmlDocument responseXML
- 服务器返回的数据(Xml对象)
- e. Number states
- 状态码(整数),如:200、404...
- f. String statesText
- 状态文本(字符串),如:OK、NotFound...
原生ajax示例
ajax.html
- <body>
- <input type="text" />
- <input type="button" value="Ajax1" onclick="Ajax1();" />
- <script>
- function Ajax1(){
- var xhr = new XMLHttpRequest(); // 创建XMLHttpRequest对象
- xhr.open('GET','/ajax_json/',true);
- xhr.onreadystatechange = function(){
- if(xhr.readyState == 4){
- // 接收完毕
- var obj = JSON.parse(xhr.responseText);
- console.log(obj)
- }
- };
- xhr.setRequestHeader('k1','v1'); // 设置数据头
- xhr.send("name=root;pwd=123");
- }
- </script>
- </body>
urls.py
- url(r'^ajax_json/', views.ajax_json),
- url(r'^ajax/', views.ajax),
views.py
- def ajax(request):
- return render(request, "ajax.html")
- def ajax_json(request):
- print(request.POST)
- ret = {'code':True, 'data':None}
- import json
- # return HttpResponse(json.dumps(ret),status=404,reason='Not Found') # 定义状态码及状态信息
- return HttpResponse(json.dumps(ret))
上面发送的是GET请求,如果是POST请求呢?
如上如果是POST请求,views里print(request.POST)
是没有数据的,因为POST请求需要给加上请求头。
- <script>
- function Ajax1(){
- var xhr = new XMLHttpRequest(); // 创建XMLHttpRequest对象
- xhr.open('POST','/ajax_json/',true);
- xhr.onreadystatechange = function(){
- if(xhr.readyState == 4){
- // 接收完毕
- var obj = JSON.parse(xhr.responseText);
- console.log(obj)
- }
- };
- xhr.setRequestHeader('k1','v1'); // 设置数据头
- xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset-UTF-8');
- xhr.send("name=root;pwd=123");
- }
- </script>
兼容性问题
以下几种写法,都是一样的效果
- > XMLHttpRequest
- XMLHttpRequest()
- > window.XMLHttpRequest
- XMLHttpRequest()
- > window['XMLHttpRequest']
- XMLHttpRequest()
- XmlHttpRequest IE7+, Firefox, Chrome, Opera, etc.
- ActiveXObject(“Microsoft.XMLHTTP”) IE6, IE5
- <script type="text/javascript">
- function getXHR(){ // 兼容性判断
- var xhr = null;
- if(XMLHttpRequest){
- xhr = new XMLHttpRequest();
- }else{
- xhr = new ActiveXObject("Microsoft.XMLHTTP");
- }
- return xhr;
- }
- function XhrPostRequest(){
- var xhr = getXHR();
- // 定义回调函数
- xhr.onreadystatechange = function(){
- if(xhr.readyState == 4){
- // 已经接收到全部响应数据,执行以下操作
- var data = xhr.responseText;
- console.log(data);
- }
- };
- // 指定连接方式和地址----文件方式
- xhr.open('POST', "/test/", true);
- // 设置请求头
- xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset-UTF-8');
- // 发送请求
- xhr.send('n1=1;n2=2;');
- }
- function XhrGetRequest(){
- var xhr = GetXHR();
- // 定义回调函数
- xhr.onreadystatechange = function(){
- if(xhr.readyState == 4){
- // 已经接收到全部响应数据,执行以下操作
- var data = xhr.responseText;
- console.log(data);
- }
- };
- // 指定连接方式和地址----文件方式
- xhr.open('get', "/test/", true);
- // 发送请求
- xhr.send();
- }
- </script>
jQuery的ajax
如果在jQuery的ajax的回调函数里,再写上一些参数,里面的参数接收的就是xmlHttpRequest对象,功能和上面是一模一样的。
2、伪ajax(iframe标签)
iframe标签,会把网址嵌套在网页中,iframe里的网址更改,网页是不刷新的。
- <iframe src="http://blog.csdn.net/fgf00"></iframe>
示例:输入http://……
网址,跳转
- <body>
- <input type="text" id="url" />
- <input type="button" value="Iframe请求" onclick="iframeRequest();" />
- <iframe src="http://blog.csdn.net/fgf00" id="ifm"></iframe>
- <script src="/static/jquery-1.12.4.js"></script>
- <script>
- function iframeRequest(){
- var url = $('#url').val();
- console.log(url);
- $('#ifm').attr('src',url);
- }
- </script>
- </body>
示例:iframe伪ajax提交
可以把form提交转交给iframe,iframe提交,利用这个特性,实现伪ajax操作。
ajax.html
- <form action="/ajax_json/" method="POST" target="ifm1">
- {% csrf_token %}
- <iframe id="ifm1" name="ifm1"></iframe>
- <input type="text" name="username" placeholder="用户名"/>
- <input type="text" name="email" placeholder="邮箱地址"/>
- <input type="submit" value="Form提交">
- </form>
views.py
- def ajax_json(request):
- print(request.POST)
- ret = {'code':True, 'data':request.POST.get('username')}
- import json
- return HttpResponse(json.dumps(ret))
阻止iframe里引用的网页自动跳转
在一些新版本的浏览器里,iframe标签居然自动跳转,禁用如下:
在iframe标签中增加两个属性:
- security="restricted" sandbox=""
前者是IE的禁止js的功能,后者是HTML5的功能。刚好就可以让IE,Chrome,Firefox这三大浏览器都实现了禁止iframe的自动跳转。
iframe获取返回的数据
浏览器审查元素中,iframe加载的时候在document对象里,相当于一个上下文或者空间管理,在HTML里面又嵌套了一个HTML。不能通过以前的方法获取到。
iframe接收到服务端返回的数据后,会执行onload
事件,获取返回数据如下:
- <form action="/ajax_json/" method="POST" target="ifm1">
- {% csrf_token %}
- <iframe id="ifm1" name="ifm1" onload="iframeLoad();"></iframe>
- <input type="text" name="username" placeholder="用户名"/>
- <input type="text" name="email" placeholder="邮箱地址"/>
- <input type="submit" value="Form提交" onclick="submitForm();"/>
- </form>
- <script src="/static/jquery-1.12.4.js"></script>
- <script>
- function submitForm(){ // 当点击提交的时候,才给iframe绑定load事件
- // 比在html中这样添加好一点:<iframe name="ifm1" onload="iframeLoad();"></iframe>
- $('#ifm1').load(function(){
- var text = $('#ifm1').contents().find('body').text(); // document对象下面的值
- var obj = JSON.parse(text);
- console.log(obj);
- })
- }
- function iiframeLoad(){
- console.log(123)
- }
- </script>
ajax操作,使用方式选择
- 如果发送的是【普通数据】 : jQuery > XMLHttpRequest > iframe
3、ajax 文件上传(三种方式)及 图片预览
urls.py
- url(r'^upload/$', views.upload),
- url(r'^upload_file/', views.upload_file),
views.py
- def upload(request):
- return render(request,'upload.html')
- def upload_file(request):
- username = request.POST.get('username')
- fafafa = request.FILES.get('fafafa')
- import os
- img_path = os.path.join('static/imgs/',fafafa.name)
- with open(img_path,'wb') as f:
- for item in fafafa.chunks():
- f.write(item)
- ret = {'code': True , 'data': img_path} # 返回文件路径(图片预览用)
- import json
- return HttpResponse(json.dumps(ret))
upload.html
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <title></title>
- <style>
- .upload{
- display: inline-block;padding: 10px;
- background-color: #2459A2;
- color: white;
- position: absolute;
- top: 0;
- bottom: 0;
- right: 0;
- left: 0;
- z-index: 90;
- }
- .file{
- width: 60px;height: 30px;opacity: 0;
- position: absolute;
- top: 0;
- bottom: 0;
- right: 0;
- left: 0;
- z-index: 100;
- }
- </style>
- </head>
- <body>
- <div style="position: relative;width: 60px;height: 30px;">
- <input class="file" type="file" id="fafafa" name="afafaf" />
- <a class="upload">上传</a>
- </div>
- <input type="button" value="提交XHR" onclick="xhrSubmit();" />
- <input type="button" value="提交jQuery" onclick="jqSubmit();" />
- <hr/>
- <form id="form1" action="/upload_file/" method="POST" enctype="multipart/form-data" target="ifm1">
- <iframe id="ifm1" name="ifm1" style="display: none;"></iframe>
- <input type="file" name="fafafa"/>
- <input type="submit" onclick="iframeSubmit();" value="iframe提交"/> <!--iframe方式提交-->
- <hr>选中后,就上传、图片预览 <!--iframe方式提交,选中后,就上传,并图片预览-->
- <input type="file" name="fafafa" onchange="changeUpalod();" /> <!--iframe方式提交,选择后,就图片预览-->
- </form>
- <div id="preview"></div> <!-- 图片预览使用 -->
- <script src="/static/jquery-1.12.4.js"></script>
- <script>
- // 选中后,就上传文件,并图片预览
- function changeUpalod(){
- $('#ifm1').load(function(){
- var text = $('#ifm1').contents().find('body').text();
- var obj = JSON.parse(text);
- $('#preview').empty();
- var imgTag = document.createElement('img');
- imgTag.src = "/" + obj.data;
- $('#preview').append(imgTag);
- });
- $('#form1').submit();
- }
- // 第二种方式:基于jQuery方式上传文件
- function jqSubmit(){
- // $('#fafafa')[0]
- var file_obj = document.getElementById('fafafa').files[0]; // files代表上传的文件
- var fd = new FormData(); // 相当于表单
- fd.append('username','root');
- fd.append('fafafa',file_obj);
- $.ajax({
- url: '/upload_file/',
- type: 'POST',
- data: fd,
- // 上传文件时,添加以下两个参数是告诉jQuery,不要做特殊处理
- processData: false, // tell jQuery not to process the data
- contentType: false, // tell jQuery not to set contentType
- success:function(arg,a1,a2){
- console.log(arg);
- console.log(a1);
- console.log(a2); // 多写几个参数,这个参数包含xmlHttpRequest对象
- }
- })
- }
- // 第一种方式:基于xmlHttpRequest方式上传文件
- function xhrSubmit(){
- // $('#fafafa')[0]
- var file_obj = document.getElementById('fafafa').files[0];
- var fd = new FormData();
- fd.append('username','root');
- fd.append('fafafa',file_obj);
- var xhr = new XMLHttpRequest();
- xhr.open('POST', '/upload_file/',true);
- xhr.onreadystatechange = function(){
- if(xhr.readyState == 4){
- // 接收完毕
- var obj = JSON.parse(xhr.responseText);
- console.log(obj);
- }
- };
- xhr.send(fd);
- }
- // 第三种方式:基于iframe方式上传文件
- function iframeSubmit(){
- $('#ifm1').load(function(){
- var text = $('#ifm1').contents().find('body').text();
- var obj = JSON.parse(text);
- // 图片预览
- $('#preview').empty(); // 清空之前的预览图片
- var imgTag = document.createElement('img');
- imgTag.src = "/" + obj.data; // 绑定预览图片路径
- $('#preview').append(imgTag);
- })
- }
- </script>
- </body>
- </html>
xmlHttpRequest和jQuery上传文件,都是基于FormData,但是FormData对于IE一些低版本浏览器是不支持的。
一般情况下,上传图片、头像都是用iframe来实现的。
ajax操作,使用方式选择
- 如果发送的是【文件】 : iframe > jQuery(FormData) > XMLHttpRequest(FormData)
四、图片验证码
流程:
- 访问页面
`/login/
- 创建一个图片并给用户返回
- Session存放验证码
- 用户POST提交数据,对比
实现:
页面生成返回和生成图片:生成网页html、生成图片url分开,这样更新验证码图片是,网页是不用刷新的。
views
- from io import BytesIO
- from django.shortcuts import HttpResponse
- from utils.check_code import create_validate_code
- def check_code(request):
- """
- 验证码
- :param request:
- :return:
- """
- # 直接打开图片,返回
- # data = open('static/imgs/avatar/20130809170025.png','rb').read()
- # return HttpResponse(data)
- # 通过模块生成图片并返回
- # 1. 创建一张图片 pip3 install Pillow
- # 2. 在图片中写入随机字符串
- # obj = object()
- # 3. 将图片写入到指定文件
- # 4. 打开指定目录文件,读取内容
- # 5. HttpResponse(data)
- stream = BytesIO() # 在内存里开辟一块空间,在内存里直接读写,相当于打开一个文件
- img, code = create_validate_code()
- img.save(stream,'PNG') # 把生成的图片进行保存
- request.session['CheckCode'] = code # 把验证码放入session中
- return HttpResponse(stream.getvalue()) # 在内存中读取并返回
create_validate_code
- font_type="Monaco.ttf", # 依赖的字体
html
- <img src="/check_code.html" onclick="changeCheckCode(this);">
- <script>
- function changeCheckCode(ths){
- ths.src = ths.src + '?'; // URL不变:浏览器不发请求;加上?号,get参数请求,向后台发请求
- }
- </script>
check_code.py(依赖:Pillow,字体文件)
- pip3 install Pillow
五、KindEditor富文本编辑器
常用的富文本编辑器:CKEditor,UEEditor,TinyEditor,KindEditor
文件夹说明
- ├── asp asp示例
- ├── asp.net asp.net示例
- ├── attached 空文件夹,放置关联文件attached
- ├── examples HTML示例
- ├── jsp java示例
- ├── kindeditor-all-min.js 全部JS(压缩)
- ├── kindeditor-all.js 全部JS(未压缩)
- ├── kindeditor-min.js 仅KindEditor JS(压缩)
- ├── kindeditor.js 仅KindEditor JS(未压缩)
- ├── lang 支持语言
- ├── license.txt License
- ├── php PHP示例
- ├── plugins KindEditor内部使用的插件
- └── themes KindEditor主题
基本使用
- <textarea name="content" id="content"></textarea>
- <script src="/static/jquery-1.12.4.js"></script>
- <script src="/static/plugins/kind-editor/kindeditor-all.js"></script>
- <script>
- $(function () {
- initKindEditor();
- });
- function initKindEditor() {
- var kind = KindEditor.create('#content', {
- width: '100%', // 文本框宽度(可以百分比或像素)
- height: '300px', // 文本框高度(只能像素)
- minWidth: 200, // 最小宽度(数字)
- minHeight: 400 // 最小高度(数字)
- });
- }
- </script>
详细参数
- http://kindeditor.net/docs/option.html
常用参数
- items¶ # 配置显示多少个工具
- noDisableItems¶ # designMode 为false时,要保留的工具,置灰
- filterMode¶ # true时根据 htmlTags 过滤HTML代码,false时允许输入任何代码。
- resizeType¶ # 2或1或0,2时可以拖动改变宽度和高度,1时只能改变高度,0时不能拖动。
- syncType¶ # 设置”“、”form”,值为form时提交form时自动提交,空时不会自动提交。
- uploadJson¶ # 指定上传文件的服务器端程序。
- autoHeightMode¶ # 内容多时,自动调整高度。
uploadjson
- <textarea id="content"></textarea>
- <script src="/static/jquery-1.12.4.js"></script>
- <script src="/static/kindeditor-4.1.10/kindeditor-all.js"></script>
- <script>
- KindEditor.create('#content', {
- uploadJson: '/upload_img/',
- fileManagerJson: '/file_manager/', // 文件管理路径
- allowImageRemote: true, // 是否允许远程上传
- allowImageUpload: true, // 是否允许本地上传
- allowFileManager: true, // 图片空间,文件预览功能
- extraFileUploadParams: { // CSRF限制,提交csrf
- csrfmiddlewaretoken: "{{ csrf_token }}"
- },
- filePostName: 'fafafa' // 设置文件发送的name值,方便后台获取
- });
- })
- </script>
- def upload_img(request):
- request.GET.get('dir')
- print(request.FILES.get('fafafa'))
- # 获取文件保存
- import json
- dic = {
- 'error': 0,
- 'url': '/static/imgs/20130809170025.png',
- 'message': '错误了...'
- }
- return HttpResponse(json.dumps(dic))
- import os, time, json
- def file_manager(request):
- """
- 文件管理,照片空间
- :param request:
- :return:
- """
- dic = {}
- root_path = 'C:/Users/Administrator/PycharmProjects/day24/static/'
- static_root_path = '/static/'
- request_path = request.GET.get('path')
- if request_path:
- abs_current_dir_path = os.path.join(root_path, request_path)
- move_up_dir_path = os.path.dirname(request_path.rstrip('/'))
- dic['moveup_dir_path'] = move_up_dir_path + '/' if move_up_dir_path else move_up_dir_path
- else:
- abs_current_dir_path = root_path
- dic['moveup_dir_path'] = ''
- dic['current_dir_path'] = request_path
- dic['current_url'] = os.path.join(static_root_path, request_path)
- file_list = []
- for item in os.listdir(abs_current_dir_path):
- abs_item_path = os.path.join(abs_current_dir_path, item)
- a, exts = os.path.splitext(item)
- is_dir = os.path.isdir(abs_item_path)
- if is_dir:
- temp = {
- 'is_dir': True,
- 'has_file': True,
- 'filesize': 0,
- 'dir_path': '',
- 'is_photo': False,
- 'filetype': '',
- 'filename': item,
- 'datetime': time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime(os.path.getctime(abs_item_path)))
- }
- else:
- temp = {
- 'is_dir': False,
- 'has_file': False,
- 'filesize': os.stat(abs_item_path).st_size,
- 'dir_path': '',
- 'is_photo': True if exts.lower() in ['.jpg', '.png', '.jpeg'] else False,
- 'filetype': exts.lower().strip('.'),
- 'filename': item,
- 'datetime': time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime(os.path.getctime(abs_item_path)))
- }
- file_list.append(temp)
- dic['file_list'] = file_list
- return HttpResponse(json.dumps(dic))
Day24-Ajax操作、图片验证码、KindEditor使用-转的更多相关文章
- Django(九)下:Ajax操作、图片验证码、KindEditor使用
三.Ajax操作 ajax操作基于浏览器的xmlHttpRequest对象,IE低版本是另外一个对象,jQuery 1 版本对那两个对象做了封装,兼容性最好,2 .3版本不再支持IE低版本了. Aja ...
- day20 project+查看新闻列表 + 点赞 + 图片验证码 + 评论和多级评论 + 后台管理 + webSocket + kindEditor
Day20回顾: 1. 请求生命周期 2. 中间件 md = [ "file_path.classname" ] process_request[可有可无] process_res ...
- Django项目开发,XSS攻击,图片防盗链,图片验证码,kindeditor编辑器
目录 一.Django项目开发 1. 项目开发流程 2. auth模块的补充 (1)django的admin可视化管理页面 (2)将admin可视化管理页面的模型表显示成中文 (3)auth模块的用户 ...
- Python开发【Django】:图片验证码、KindEditor
图片验证码 生成图片验证码需要以下: session check_code.py(依赖:Pillow,字体文件) 模块安装 pip install Pillow src属性后面加? 在utils下拷贝 ...
- 【Spring】基于SpringMVC的图片验证码功能实现
后台实现代码: ImgController.java 文件 package cn.shop.controller; import java.awt.Color; import java.awt.Fon ...
- 图片验证码——base64编码的使用
一.介绍: 1.base64编码简介: Base64就是一种编码格式.Base64要求把每三个8Bit的字节转换为四个6Bit的字节(3*8 = 4*6 = 24),然后把6Bit再添两位高位0,组成 ...
- django(7)modelform操作及验证、ajax操作普通表单数据提交、文件上传、富文本框基本使用
一.modelForm操作及验证 1.获取数据库数据,界面展示数据并且获取前端提交的数据,并动态显示select框中的数据 views.py from django.shortcuts import ...
- Django 六——自定义标签、图片验证码、发送邮件、评论树、组合搜索
1.自定义标签 2.图片验证码 3.生成邮箱验证码.发送邮件 4.评论树实现 5.组合搜索(Q) 1.自定义标签 配置: a.在app中新建文件夹 templatetags,里面新建 xx.py文 ...
- vue 弹窗式 滑动图片验证码
效果图: 具体代码: test.vue //整个页面是个弹窗 visible 控制弹窗的显示关闭 默认打开 <template> <div class="mask_laye ...
随机推荐
- R 语言-基础
R语言 1997年成为GNU项目 开源免费 R官方网址 www.r-project.org R是数据分析领域的语言小巧灵活,通过扩展包来增强功能绘图功能代码简单 开发环境R + RStudio 1.数 ...
- Python3入门(三)——Python基础语法
一.基本语法 1.行和缩进 Python中,不使用括号来表示代码的类和函数定义块或流程控制. 代码块是由行缩进,缩进位的数目是可变的,但是在块中的所有语句必须缩进相同的量. 如下所示: a = 100 ...
- controlfile作为RMAN的repository时,对 keep time 的测试
4月2日,首先查看系统状况: SQL> show parameter control NAME TYPE VALUE ...
- mfc 虚函数
知识点 类虚函数概念 类虚函数定义virtual 一.虚函数 简单地说,那些被virtual关键字修饰的成员函数,就是虚函数. 二.虚函数定义 定义:在某基类中声明为 virtual 并在一个或多个派 ...
- GIT命令基本使用
记录摘选自廖雪峰的官方网站归纳总结 1.centos下安装git [root@cdw-lj ~]# yum install git 2.配置用户名以及邮箱 [root@cdw-lj opt]# git ...
- Shell基础入门
目录 Shell基础入门 1.什么是Shell? 2.Shell脚本的结构 3.Shell的变量 3.1.自定义环境变量 3.2.普通变量 3.3.位置参数变量 3.4.状态变量 4.条件测试和比较 ...
- P2371 [国家集训队]墨墨的等式
膜意义下最短路. 把最小的\(a\)抠出来,作为模数\(mod\),然后建点编号为\(0\)到\(mod-1\),对每个数\(a\)连边\((i,(a+i)\mod mod)\)点\(i\)的最短路就 ...
- 项目 - RM 部署上centos7 之后出现的一些问题和解决方法
系统版本: [root@localhost logs]# cat /etc/redhat-release CentOS Linux release (Core) 获取方法来自:https://www. ...
- REST-framework快速构建API--权限
我们在访问资源时,有些资源保密程度较高,需要特殊的人员才能访问.比如,获取公司的每日收入流水的API接口,只能CEO才能查看. 这时,我们就需要将资源设定权限了. REST-framework实现如下 ...
- docker之搭建私有仓库
一.私有仓库 1.防止网络原因:下载慢,访问不到的情况,需要在内网搭建一个私有仓库. 二.仓库镜像下载 [root@node03 ~]# docker pull registry 三.创建私有仓库容器 ...