Python 自动化(十五)请求和响应

准备工作

将不同day下的代码分目录管理,方便后续复习查阅

(testenv) [root@localhost projects]# ls
mysite1
(testenv) [root@localhost projects]# mkdir day01 day02
(testenv) [root@localhost projects]# cp -rf mysite1/ day01/
(testenv) [root@localhost projects]# cp -rf mysite1/ day02/
(testenv) [root@localhost projects]# ls day01/
mysite1
(testenv) [root@localhost projects]# ls day02/
mysite1
(testenv) [root@localhost projects]# rm -rf mysite1/

使用Pycharm打开day02下的mysite1项目进行操作

HTTP协议的请求和响应

定义

请求是指浏览器端通过HTTP协议发送给服务器端的数据

响应是值服务器端接收到请求后做相应的处理后再回复给浏览器端的数据

请求

样例
请求中的方法
  • 根据HTTP标准,HTTP请求可以使用多种请求方法

  • HTTP1.0定义了三种请求方法:GET/POST/HEAD方法(最常用)

  • HTTP1.1新增了五种请求方法:OPTIONS/PUT/DELETE/TRACE/CONNECT方法

Django中的请求
  • 请求在Django中实则就是视图函数的第一个参数,即HttpRequest对象
  • Django接收到http协议的请求后,会根据请求数据报文创建HttpRequest对象
  • HttpRequest对象通过属性描述了请求的所有相关信息
HttpRequest对象属性
path_info:URL字符串
method:字符串,表示HTTP请求方法,常用值:GET/POST
GET:QueryDict查询字典的对象,包含GET请求方式的所有数据
POST:QueryDict查询字典的对象,包含POST请求方式的所有数据
FILES:类似于字典的对象,包含所有的上传文件信息
COOKIES:Python字典,包含所有的cookie,键和值都是字符串
session:类似于字典的对象,表示当前的会话
body:字符串,请求体的内容(POST/PUT)
scheme:请求协议(http/https)
request.get_full_path():请求的完整路径
request.META:请求中的元数据(消息头)
request.META['REMOTE_ADDR']:客户端IP地址
实验:查看HttpRequest对象属性值
  • 修改 mysite1/mysite1/urls.py 文件,添加路由配置

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

  urlpatterns = [
      # ......
      ###day02###
      path("test_request", views.test_request),
  ]
  • 修改 mysite1/mysite1/views.py,添加指定视图函数

  def test_request(request):
      print("path info is:", request.path_info)
      print("method is:", request.method)
      print("query string:", request.GET)
      print("full path:", request.get_full_path())
      return HttpResponse("test request ok~")

响应

样例

响应状态码

HTTP状态码的英文为 Http Status Code,下面是常见的HTTP状态码

  • 200:请求成功

  • 301:永久重定向,资源/网页等被永久转移到其他URL地址

  • 302:临时重定向

  • 404:请求的资源/网页等不存在

    • 可以访问一个不存在的URL,显示404
  • 500:服务器内部错误

HTTP状态码由三个十进制数字组成,第一个十进制数字定义了状态码的类型,后两个数字没有分类的作用。HTTP状态码共分为 5种类型

分类 分类描述
1** 信息,服务器收到请求,需要请求者继续执行操作
2** 成功,操作被成功接收并处理
3** 重定向,需要进---步的操作l以完成请求
4** 客户端错误,请求包含语法错误或无法完成请求
5** 服务器错误,服务器在处理请求的过程中发生了错误
Django中的响应
  • 构造函数格式:HttpResponse(content=响应体, content_type=响应体数据类型, status=状态码)

  • 作用:向客户端浏览器返回响应,同时携带响应体内容

  • 参数

    • content:表示返回的内容
    • status_code:返回的HTTP响应状态码(默认为200)
    • content_type:指定返回数据的MIME类型(默认是text/html)
  • 常见的Content_Type如下

  text/html:默认的,html文件
  text/plain:纯文本
  text/css:css文件
  text/javascript:javascript文件
  application/json:json数据传输
  multipart/form-data:文件提交
  • HttpResponse子类
实验:测试重定向
  • 修改 mysite1/mysite1/views.py,修改指定视图函数

    from django.http import HttpResponse, HttpResponseRedirect # 导入重定向

    def test_request(request):
    print("path info is:", request.path_info)
    print("method is:", request.method)
    print("query string:", request.GET)
    print("full path:", request.get_full_path())
    # return HttpResponse("test request ok~") # 注释掉普通响应
    return HttpResponseRedirect("/page/1") # 改为重定向,会自动跳转到/page/1

查看服务是否重启,浏览器访问:http://127.0.0.1:8000/test_request,观察浏览器地址变化以及响应数据

Django处理请求

定义与说明

  • 无论是GET还是POST请求,统一都由视图函数接收请求,通过判断request.method属性来区分具体的请求动作

  • 样例

    if request.method == "GET":
    处理GET请求时的业务逻辑
    elif request.method == "POST":
    处理POST请求时的业务逻辑
    else:
    其他请求业务逻辑

处理方式

def test_get_post(request):
    if request.method == "GET":
        pass
    elif request.method == "POST":
        # 处理用户提交数据
        pass
    else:
        pass
    return HttpResponse("--test get post is ok --")GET处理

处理GET请求

定义与说明
  • GET请求动作,一般用于向服务器获取数据,能够产生GET请求的场景:

    • 浏览器地址栏中输入URL,回车后
    • <a href="地址?参数=值&参数=值"/>
    • form 表单中的 method 为 get
  • GET请求方式中,如果有数据需要传递给服务器,通常会用查询字符串(QueryString)传递

  • 服务器端接收参数

    • 获取客户端请求GET请求提交的数据
  • 方法示例

  request.GET["参数名"]  # QueryDict
  request.GET.get("参数名", "参数值")
  request.GET.getlist("参数名")
  # page?a=100&b=200&c=300&b=400
  # request.GET的值是--->QueryDict({"a": ["100"], "b": ["200", "400"], "c": ["300"],})
  # a = request.GET["a"]
实验:处理GET请求
  • 修改 mysite1/mysite1/views.py,修改指定视图函数

    def test_get_post(request):
    if request.method == "GET": # 处理GET请求
    print(request.GET) # 打印QueryDict
    print(request.GET["a"])
    print(request.GET.get("c", "no c"))
    elif request.method == "POST":
    # 处理用户提交数据
    pass
    else:
    pass
    return HttpResponse("--test get post is ok --")

修改 mysite1/mysite1/urls.py 文件,添加路由配置

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

urlpatterns = [
    # ......
    ###day02###
    path("test_request", views.test_request),
    path("test_get_post", views.test_get_post),
]

查看服务是否重启,浏览器访问:http://127.0.0.1:8000/test_get_post?a=400,并查看终端打印数据

浏览器访问:http://127.0.0.1:8000/test_get_post,不带参数,请求报错,查看报错信息

浏览器访问:http://127.0.0.1:8000/test_get_post?a=100&a=200&a=300,传递三个参数a

  • 可得,request.GET["a"] 并不能获取a参数的所有值

  • 修改 mysite1/mysite1/views.py,修改指定视图函数

    def test_get_post(request):
    if request.method == "GET": # 处理GET请求
    print(request.GET) # 打印QueryDict
    print(request.GET["a"])
    # 应用场景--复选框,例如:兴趣爱好:篮球,足球,跑步......
    print(request.GET.getlist("a")) # ###获取a参数的所有值###
    print(request.GET.get("c", "no c"))
    elif request.method == "POST":
    # 处理用户提交数据
    pass
    else:
    pass
    return HttpResponse("--test get post is ok --")

浏览器访问:http://127.0.0.1:8000/test_get_post?a=100&a=200&a=300,传递三个参数a,查看终端显示

处理POST请求

定义与说明
  • POST请求动作,一般用于向服务器提交大量/隐私数据,客户端通过表单等POST请求将数据传递给服务器端,如

服务器端接收参数,通过request.method来判断是否为POST请求,如:

if request.method == "POST":
    处理POST请求的数据并响应
else:
    处理非POST请求时的业务逻辑

使用POST方式接收客户端数据

request.POST["参数名"]  # QueryDict
request.POST.get("参数名", "参数值")
request.POST.getlist("参数名")
  • 取消csrf验证,否则Django将会拒绝客户端发来的POST请求,报403响应
实验:处理POST请求
  • 修改 mysite1/mysite1/views.py,修改指定视图函数

    定义GET请求的响应数据,返回一个FORM表单

    POST_FORM = """

    <form method="post" action="/test_get_post"> 用户名: </form> """

    def test_get_post(request):
    if request.method == "GET":
    print(request.GET)
    # print(request.GET["a"]) # 注释掉此处代码,防止服务报错
    print(request.GET.getlist("a"))
    print(request.GET.get("c", "no c"))
    return HttpResponse(POST_FORM) # GET请求响应一个表单数据
    elif request.method == "POST":
    # 处理用户提交数据
    print("uname is:", request.POST.get("uname")) # 处理POST请求
    return HttpResponse("post is ok~") # 返回响应数据
    else:
    pass
    return HttpResponse("--test get post is ok --")

查看服务是否重启,浏览器访问:http://127.0.0.1:8000/test_get_post,返回表单数据

填写表单数据,点击提交,发送POST请求,查看浏览器显示内容,POST请求失败,403响应

填写表单数据,点击提交,发送POST请求,查看浏览器显示内容,POST请求失败,403响应

取消csrf验证,注释掉 settings.py 中的 MIDDLEWARE 中的 CsrfViewsMiddleWare 的中间件

# settings.py
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    # 'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

重新提交表单数据,数据提交成功

查看终端数据显示

Django设计模式和模板层

MVC和MTV

传统的MVC
  • MVC设计模式把Web框架分为三个基础部分:

    • M 模型层**(Model)**:主要用于对数据库层的封装
    • V 视图层**(View)** :用于向用户展示结果**(what+how)**
    • C 控制层**(Controller)**:用于处理请求获取数据,返回结果
  • 作用:降低模块间的耦合度**(解耦)**

MTV模式

MTV和MVC本质上是一样的,MTV代表 Model-Template-View (模型-模板-视图)模式

  • M 模型层**(Model)**:负责与数据库交互
  • T 模板层**(Template)** :负责呈现内容到浏览器**(how)**
  • V 视图层**(View)** :是核心,负责接收请求,获取数据,返回结果**(what)**

模板层

定义与说明
  • 模板是可以根据字典数据 动态变化的HTML网页
  • 模板可以根据视图中 传递的字典数据动态生成相应的HTML页面
模板配置
  • 创建模板文件夹 <项目名>/templates

  • 修改 settings.py 中 TEMPLATES 配置项

    • BACKEND:指定模板的引擎
    • DIRS:模板的搜索目录
    • APP_DIRS:是否要在 templates 文件夹中搜索模板文件
    • OPTIONS:有关模板的选项
  • 配置项**(settings.py)**中需要修改的部分

    • 设置 DIRS

      • 'DIRS': [os.path.join(BASE_DIR, 'templates')]
模板渲染方式
  • 方案1:通过 loader 获取模板,通过 HttpResponse 进行响应在视图函数中

**方案2:**使用 render() 直接加载并响应模板,在视图函数中实现

实验:模板加载
  • 在项目中创建 templates 目录,注意创建路径

修改 settings.py 文件,设置 DIRS 配置

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, "templates")],  # 修改DIRS配置
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

测试模板加载方案一: 修改 mysite1/mysite1/views.py,添加指定视图函数

from django.http import HttpResponse, HttpResponseRedirect

# ......之前视图函数省略
def test_html(request):
    from django.template import loader
    t = loader.get_template("test_html.html")  # 指定渲染模板名称
    html = t.render()
    return HttpResponse(html)

在 mysite1/templates 目录中创建指定模板文件:test_html.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h3>我是模板层!!!</h3>
</body>
</html>

修改 mysite1/mysite1/urls.py 文件,添加路由配置

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

urlpatterns = [
    # ......
    ###day02###
    path("test_request", views.test_request),
    path("test_get_post", views.test_get_post),
    path("test_html", views.test_html),  # 添加新的路由配置
]

查看服务是否重启,浏览器访问:http://127.0.0.1:8000/test_html,查看模板加载是否成功

测试模板加载方案二(推荐使用): 修改 mysite1/mysite1/views.py,修改指定视图函数

from django.http import HttpResponse, HttpResponseRedirect

# ......之前视图函数省略
def test_html(request):
    # 方案一:
    # from django.template import loader
    # t = loader.get_template("test_html.html")  # 指定渲染模板名称
    # html = t.render()
    # return HttpResponse(html)

    # 方案二:
    from django.shortcuts import render
    return render(request, "test_html.html")

查看服务是否重启,浏览器访问:http://127.0.0.1:8000/test_html,查看模板加载是否成功

视图层与模板层之间的交互
  • 视图函数中可以将Python变量封装到 字典 中传递到模板,样例:
  • 模板中可以使用 {{ 变量名 }} 的语法调用视图传进来的变量
实验:与模板层的数据交互
  • 修改 mysite1/mysite1/views.py,修改指定视图函数

  from django.http import HttpResponse, HttpResponseRedirect

  # ......之前视图函数省略
  def test_html(request):
      from django.shortcuts import render
      dic = {"username": "nfx", "age": 18}
      return render(request, "test_html.html", dic)
  • 修改 mysite1/templates 目录的模板文件:test_html.html

  <!DOCTYPE html>
  <html lang="en">
  <head>
      <meta charset="UTF-8">
      <title>Title</title>
  </head>
  <body>
  <h3> {{ username }}  我是模板层!!!</h3>
  </body>
  </html>
模板层---变量
  • 视图函数中可以将Python变量封装到 字典 中传递到模板,样例:
实验:变量渲染语法
  • 修改 mysite1/mysite1/views.py,创建指定视图函数

    from django.http import HttpResponse, HttpResponseRedirect
    from django.shortcuts import render

    ......之前视图函数省略

    def test_html_param(request):
    dic = {}
    dic["int"] = 88
    dic["str"] = "nfx"
    dic["lst"] = ["Tom", "Jack", "Lily"]
    dic["dict"] = {"a": 9, "b": 8}
    dic["func"] = say_hi
    dic["class_obj"] = Dog()
    return render("test_html_param.html")

    def say_hi():
    return "hehehe"

    class Dog:
    def say(self):
    return "wangwang~"

在 mysite1/templates 目录中创建指定模板文件:test_html_param.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h3>int is {{ int }}</h3>
    <h3>str is {{ str }}</h3>
    <h3>lst is {{ lst.0 }}</h3>
    <h3>dict is {{ dict }}</h3>
    <h3>dict["a"] is {{ dict.a }}</h3>
    <h3>function is {{ func }}</h3>
    <h3>class_obj is {{ class_obj.say }}</h3>
</body>
</html>

修改 mysite1/mysite1/urls.py 文件,添加路由配置

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

urlpatterns = [
    # ......
    ###day02###
    path("test_request", views.test_request),
    path("test_get_post", views.test_get_post),
    path("test_html", views.test_html),
    path("test_html_param", views.test_html_param),  # 添加新的路由配置
]

查看服务是否重启,浏览器访问:http://127.0.0.1:8000/test_html_param,查看模板加载是否成功

模板层---标签
  • 作用:将一些服务器端的功能嵌入到模板层,例如流程控制等

  • 标签语法:

    {% 标签 %}
    ......
    {% 结束标签 %}

if 标签
  • 语法:

  • 注意:

  • if 条件表达式里可以用的运算符 ==、!=、<、>、<=、>=、in、not in、is、is not、not、and、or

  • 在 if 标记中使用实际括号是无效的语法,如果需要指示优先级,则应使用嵌套的 if 标记

实验:if 标签测试
  • 修改 mysite1/mysite1/views.py,创建指定视图函数

  from django.http import HttpResponse, HttpResponseRedirect
  from django.shortcuts import render

  # ......之前视图函数省略
  def test_if_for(request):
      dic = {}
      dic["x"] = 10
      return render(request, "test_if_for.html", dic)
  • 修改 mysite1/mysite1/urls.py 文件,添加路由配置

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

  urlpatterns = [
      # ......
      ###day02###
      path("test_request", views.test_request),
      path("test_get_post", views.test_get_post),
      path("test_html", views.test_html),
      path("test_html_param", views.test_html_param),
      path("test_if_for", views.test_if_for),  # 添加新的路由配置
  ]
  • 在 mysite1/templates 目录中创建指定模板文件:test_if_for.html

  <!DOCTYPE html>
  <html lang="en">
  <head>
      <meta charset="UTF-8">
      <title>Title</title>
  </head>
  <body>
  {% if x > 10 %}
  今天天气很好
  {% else %}
  今天天气非常好
  {% endif %}
  </body>
  </html>
练习:if 标签练习

需求

  • 写一个简单的计算器页面,能够在服务端进行简单的加减乘除计算

处理思路

  • 修改 mysite1/mysite1/views.py,创建指定视图函数

    from django.http import HttpResponse, HttpResponseRedirect
    from django.shortcuts import render

    ......之前视图函数省略

    def test_mycal(request):
    if request.method == "GET":
    return render(request, "mycal.html")
    elif request.method == "POST":
    # 处理计算
    x = int(request.POST["x"])
    y = int(request.POST["y"])
    op = request.POST["op"]

          result = 0
          if op == "add":
              result = x + y
          elif op == "sub":
              result = x - y
          elif op == "mul":
              result = x * y
          elif op == "div":
              result = x / y
          # locals()相当于:dic = {"x": x, "y": y, "op": op, "result": result}
          return render(request, "mycal.html", locals())
    

修改 mysite1/mysite1/urls.py 文件,添加路由配置

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

urlpatterns = [
    # ......
    ###day02###
    path("test_request", views.test_request),
    path("test_get_post", views.test_get_post),
    path("test_html", views.test_html),
    path("test_html_param", views.test_html_param),
    path("test_if_for", views.test_if_for),
    path("mycal", views.test_mycal),  # 添加新的路由配置
]

在 mysite1/templates 目录中创建指定模板文件:mycal.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="/mycal" method="post">
    <input type="text" name="x" value="{{ x }}">
    <select name="op">
        <option value="add" {% if op == "add" %}selected{% endif %}>+ 加</option>
        <option value="sub" {% if op == "sub" %}selected{% endif %}>- 减</option>
        <option value="mul" {% if op == "mul" %}selected{% endif %}>* 乘</option>
        <option value="div" {% if op == "div" %}selected{% endif %}>/ 除</option>
    </select>
    <input type="text" name="y" value="{{ y }}"> =
    <span>{{ result }}</span>
    <div><input type="submit" value="开始计算"></div>
</form>
</body>
</html>

查看服务是否重启,浏览器访问:
http://127.0.0.1:8000/mycal

for 标签
  • 语法:

内置变量 --- forloop

实验:for 标签测试
  • 修改 mysite1/mysite1/views.py,修改指定视图函数

  from django.http import HttpResponse, HttpResponseRedirect
  from django.shortcuts import render

  # ......之前视图函数省略
  def test_if_for(request):
      dic = {}
      dic["x"] = 10
      dic["lst"] = ["nfx", "benben", "gangzi"]  # 添加一个列表数据
      return render(request, "test_if_for.html", dic)
  • 修改 mysite1/templates 目录的模板文件:test_if_for.html

  <!DOCTYPE html>
  <html lang="en">
  <head>
      <meta charset="UTF-8">
      <title>Title</title>
  </head>
  <body>
  {% if x > 10 %}
  今天天气很好
  {% else %}
  今天天气非常好
  {% endif %}

  <br>

  {% for name in lst %}
      {% if forloop.first %} ------- {% endif %}
      <p>{{ forloop.counter }}  {{ name }}</p>
      {% if forloop.last %} ======= {% endif %}
  {% empty %}
      当前没数据
  {% endfor %}
  </body>
  </html>
模板继承

模板继承可以使用父模板的内容重用,子模板直接继承父模板的全部内容并可以覆盖父模板中相应的块

  • 语法---父模板:

    • 定义父模板中的块 block 标签
    • 标识出哪些在子模板中式允许被修改的
    • block 标签:在父模板中定义,可以在子模板中覆盖
  • 语法---子模板:

    • 继承模板 extends标签**(写在模板文件的第一行)**

    • 例如:{% extends 'base.html' %}

    • 子模板重写父模板中的内容块、

  • 重写的覆盖规则

    • 不重写,将按照父模板的效果显示
    • 重写,则按照重写效果显示
  • 注意

    • 模板继承时,服务器端的动态内容无法继承
实验:模板继承
  • 修改 mysite1/mysite1/views.py,创建指定视图函数

    from django.shortcuts import render

    之前的视图函数省略......

    def base_view(request):
    return render(request, "base.html")

    def sport_view(request):
    return render(request, "sport.html")

    def music_view(request):
    return render(request, "music.html")

在 mysite1/templates 目录中创建指定模板文件:base.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    {% block mytitle %}
    <title>主页</title>
    {% endblock %}
</head>
<body>
<a href="/music_index">音乐频道</a>
<a href="/sport_index">体育频道</a>
<br>
{% block info %}
    这是主页
{% endblock %}
<br>
<h3>有任何问题请联系xxxxxx</h3>
</body>
</html>

在 mysite1/templates 目录中创建指定模板文件:sport.html

{% extends "base.html" %}
{% block mytitle %}
<title>体育频道</title>
{% endblock %}
{% block info %}
  欢迎来到体育频道
{% endblock %}

在 mysite1/templates 目录中创建指定模板文件:music.html

{% extends "base.html" %}
{% block mytitle %}
<title>音乐频道</title>
{% endblock %}
{% block info %}
  欢迎来到音乐频道
{% endblock %}

修改 mysite1/mysite1/urls.py 文件,添加路由配置

###day02###
# ......
path("base_index", views.base_view),
path("music_index", views.music_view),
path("sport_index", views.sport_view),

查看服务是否重启,浏览器访问:http://127.0.0.1:8000/base_index,查看模板加载是否成功

相关推荐
weixin_514548894 分钟前
机器学习课程学习周报十五
人工智能·学习·机器学习
秋风起,再归来~8 分钟前
【Linux庖丁解牛】—Linux基本指令(中)!
linux·指令
Themberfue10 分钟前
基础算法之双指针--Java实现(下)--LeetCode题解:有效三角形的个数-查找总价格为目标值的两个商品-三数之和-四数之和
java·开发语言·学习·算法·leetcode·双指针
AIGC破防黑吗喽11 分钟前
Midjourney零基础学习
人工智能·gpt·学习·ai·stable diffusion·midjourney·ai绘画
DanCheng-studio17 分钟前
毕设 大数据抖音短视频数据分析与可视化(源码)
python·毕业设计·毕设
Eternal-Student19 分钟前
预处理、编译、汇编、链接
linux·汇编·windows
LearnTech_12325 分钟前
【学习笔记】手写一个简单的 Spring MVC
笔记·学习·spring·spring mvc
一尘之中1 小时前
网 络 安 全
网络·人工智能·学习·安全
TNT_JQR1 小时前
电子信息类专业技术学习及比赛路线总结(大一到大三)
单片机·嵌入式硬件·学习
漏刻有时1 小时前
微信小程序学习实录9:掌握wx.chooseMedia实现多图片文件上传功能(选择图片、预览图片、上传图片)
学习·微信小程序·notepad++