zoukankan      html  css  js  c++  java
  • stark组件(11):组合搜索

    效果图:

    新增函数和类

    Option  获取字段的对象或元组
    
    SearchGroupRow 封装数据,展示到前端
    
    get_search_group 获取组合搜索的字段
    
    get_search_group_condition 获取组合搜索的筛选条件

    一、stark组件

    import functools
    from types import FunctionType
    
    from django import forms
    from django.db.models import Q
    from django.db.models import ForeignKey, ManyToManyField
    from django.http import QueryDict
    from django.urls import path
    from django.utils.safestring import mark_safe
    from django.shortcuts import HttpResponse, render, reverse, redirect
    
    from stark.utils.pagination import Pagination
    
    
    class StarkModelForm(forms.ModelForm):
        def __init__(self, *args, **kwargs):
            super(StarkModelForm, self).__init__(*args, **kwargs)
            # 统一给ModelForm生成字段添加样式
            for name, field in self.fields.items():
                field.widget.attrs['class'] = 'form-control'
    
    
    class SearchGroupRow(object):
        def __init__(self, title, queryset_or_tutple, option, query_dict):
            """
            :param title: 组合搜索的列名称
            :param queryset_or_tutple:组合搜索关联获取到的数据
            :param 配置,Option的对象
            :param query_dict : request.GET
            """
            self.title = title
            self.queryset_or_tutple = queryset_or_tutple
            self.option = option
            self.query_dict = query_dict
    
        def __iter__(self):
            yield '<div class="whole">'
            yield self.title + ':'
            yield '</div>'
            yield '<div class="others">'
            total_query_dict = self.query_dict.copy()
            total_query_dict._mutable = True
            origin_value_list = self.query_dict.getlist(self.option.field)
            if not origin_value_list:
                # 比如说:没有传递gender,那么就还是当前的url。不能写成井号,因为如果传递了department,gender的全部的url应该是?department=x
                yield f'<a class="active" href="?{total_query_dict.urlencode()}">全部</a>'
            else:
                # 如果全部没有被选中,那么全部的url就是移除掉被选中字段后的url
                total_query_dict.pop(self.option.field)
                yield f'<a href="?{total_query_dict.urlencode()}">全部</a>'
    
            for item in self.queryset_or_tutple:
                text = self.option.get_text(item)
                value = str(self.option.get_values(item))
    
                # 需要request.GET
                # 获取组合搜索按钮文本背后对应的值
                # QueryDict = {gender: ['1', ], depart: ['2', ]}  gender=1&depart=2
                # print(self.query_dict)
                query_dict = self.query_dict.copy()
                query_dict._mutable = True
                # 如果url有参数,比如?gender=1,query_dict就会带着这个参数。下面给query_dict赋值的时候,如果字段是gender会把gender覆盖掉。
                # 如果字段不是gender,就会变成加上这个字段,那部门来举例就会变成?gender=1&department=1
                if not self.option.is_multi:
                    query_dict[self.option.field] = value  
    
                    if value in origin_value_list:
                        query_dict.pop(self.option.field)  # gender=x移除,此url就会编程href="?",当再次点击的时候就等于取消选择了
                        yield f'<a class="active" href="?{query_dict.urlencode()}">{text}</a>'
                    else:
                        yield f'<a href="?{query_dict.urlencode()}">{text}</a>'
                else:
                    multi_value_list = query_dict.getlist(self.option.field)
                    if value in multi_value_list:
                        multi_value_list.remove(value)
                        # 我们操作的是multi_value_list,query_dict并没有被修改,所以要把multi_value_dict的值赋给query_dict
                        query_dict.setlist(self.option.field, multi_value_list)
                        yield f'<a class="active" href="?{query_dict.urlencode()}">{text}</a>'
                    else:
                        multi_value_list.append(value)
                        query_dict.setlist(self.option.field, multi_value_list)
                        yield f'<a href="?{query_dict.urlencode()}">{text}</a>'
            yield '</div>'
    
    
    class Option(object):
        def __init__(self, field, is_multi=False, db_condition=None, text_func=None, value_func=None):
            """
            :param field: 组合搜索关联的字段
            :param is_multi: 是否支持多选
            :param db_condition: 数据库关联查询时的条件
            :param text_func: 此函数用于显示组合搜索的按钮页面文本
            :param value_func: 此函数用于显示组合搜索的按钮值
            """
            self.field = field
            self.is_multi = is_multi
            if not db_condition:
                db_condition = {}
            self.db_condition = db_condition
            self.text_func = text_func
            self.value_func = value_func
            self.is_choice = False
    
        def get_db_condition(self, request, *args, **kwargs):
            """
            获取筛选条件
            :param request:
            :param args:
            :param kwargs:
            :return:
            """
            return self.db_condition
    
        def get_queryset_or_tuple(self, model_class, request, *args, **kwargs):
            """
            根据字段去获取数据库关联的数据
            :param model_class:
            :param request:
            :param args:
            :param kwargs:
            :return:
            """
    
            # 去自己对应的Model类中找到字段对象,例:web.UserInfo.department,web.UserInfo.gender
            filed_object_or_tuple = model_class._meta.get_field(self.field)
            title = filed_object_or_tuple.verbose_name
            if isinstance(filed_object_or_tuple, ForeignKey) or isinstance(filed_object_or_tuple, ManyToManyField):
                # FK和M2M,应该去获取其关联表中的数据
                # django2用field_obj.related_model来获取model
                # django1用field_obj.rel.model来获取model
                db_condition = self.get_db_condition(request, *args, **kwargs)
                return SearchGroupRow(title, filed_object_or_tuple.related_model.objects.filter(**db_condition), self,
                                      request.GET)
            else:
                # 获取choice中的数据
                self.is_choice = True
                return SearchGroupRow(title, filed_object_or_tuple.choices, self, request.GET)
    
        def get_text(self, filed_object_or_tuple):
            """
            获取文本函数
            :param filed_object:
            :return:
            """
            if self.text_func:
                return self.text_func(filed_object_or_tuple)
            if self.is_choice:
                return filed_object_or_tuple[1]
            return str(filed_object_or_tuple)
    
        def get_values(self, filed_object_or_tuple):
            """
            获取文本的值
            :param filed_object_or_tuple:
            :return:
            """
            if self.value_func:
                return self.value_func(filed_object_or_tuple)
            if self.is_choice:
                return filed_object_or_tuple[0]
            return filed_object_or_tuple.pk
    
    
    def get_choice_text(title, field):
        """
        对于Stark组件中定义列时,choice如果想要显示中文信息,调用此方法即可。
        :param title: 希望页面显示的表头
        :param field:  字段名称
        :return:
        """
    
        def inner(self, obj=None, is_header=None, *args, **kwargs):
            if is_header:
                return title
            method = f"get_{field}_display"
            return getattr(obj, method)()
            # GENDER_CHOICES = ((MALE, '男'),(FEMALE, '女'),)
            # 对于choice字段,如果想获取获取第二个值,可以通过:对象.get_字段名_display()
    
        return inner
    
    
    class StarkHandler(object):
        list_display = []
        order_list = []
        search_list = []
        search_group = []
        per_page_data = 10
        has_add_btn = True
        model_form_class = None
        list_template = None
        add_template = None
        edit_template = None
        delete_template = None
        action_list = []
    
        def __init__(self, site, model_class, prev):
            self.site = site
            self.model_class = model_class
            self.prev = prev
            self.request = None
    
        def display_checkbox(self, obj=None, is_header=None, *args, **kwargs):
            """
            复选框
            :param obj:
            :param is_header:
            :param args:
            :param kwargs:
            :return:
            """
            if is_header:
                return '选择'
            return mark_safe(f'<input type="checkbox" name="pk" value="{obj.pk}" />')
    
        def display_edit(self, obj=None, is_header=None, *args, **kwargs):
            """
            自定义页面显示的列(表头和内容)
            :param obj:
            :param is_header:
            :return:
            """
            if is_header:
                return '编辑'
            name = f'{self.site.namespace}:{self.get_edit_url_name}'
            return mark_safe(f'<a href="{reverse(name, args=(obj.pk,))}">编辑</a>')
    
        def display_delete(self, obj=None, is_header=None, *args, **kwargs):
            if is_header:
                return '删除'
            name = f'{self.site.namespace}:{self.get_delete_url_name}'
            return mark_safe(f'<a href="{reverse(name, args=(obj.pk,))}">删除</a>')
    
        def get_list_display(self, request, *args, **kwargs):
            """
            获取页面上应该显示的列,预留的自定义扩展,例如:以后根据用户的不同显示不同的列
            :return:
            """
            value = []
            value.extend(self.list_display)
            return value
    
        def get_search_list(self):
            return self.search_list
    
        def get_add_btn(self, *args, **kwargs):
            if self.has_add_btn:
                return f'<a class="btn btn-primary" href="{self.reverse_add_url(*args, **kwargs)}">添加</a>'
    
        def get_model_form_class(self, request, *args, **kwargs):
            if self.model_form_class:
                return self.model_form_class
    
            class DynamicModelForm(StarkModelForm):
                class Meta:
                    model = self.model_class
                    fields = '__all__'
    
            return DynamicModelForm
    
        def get_order_list(self):
            return self.order_list or ['-id', ]
    
        def get_action_list(self):
            return self.action_list
    
        def action_multi_delete(self, request, *args, **kwargs):
            """
            批量删除(如果想要定制执行成功后的返回值,那么就为action函数设置返回值即可)
            :param request:
            :param args:
            :param kwargs:
            :return:
            """
            pk_list = request.POST.getlist('pk')
            self.model_class.objects.filter(id__in=pk_list).delete()
    
        action_multi_delete.text = '批量删除'
    
        def get_search_group(self):
            return self.search_group
    
        def get_search_group_condition(self, request):
            """
            获取组合搜索的条件
            :param request:
            :return:
            """
            condition = {}
            for option in self.get_search_group():
                if option.is_multi:
                    values_list = request.GET.getlist(option.field)
                    if not values_list:
                        continue
                    condition[f'{option.field}__in'] = values_list
                else:
                    values = request.GET.get(option.field)
                    if not values:
                        continue
                    condition[f'{option.field}'] = values
            return condition
    
        def list_view(self, request, *args, **kwargs):
            """
            列表页面
            :param request:
            :return:
            """
    
            # 1. 处理Action
            action_list = self.get_action_list()
            action_dict = {func.__name__: func.text for func in action_list}
            if request.method == 'POST':
                action_func_name = request.POST.get('action')
                if action_func_name and action_func_name in action_dict:
                    action_response = getattr(self, action_func_name)(request, *args, **kwargs)
                    if action_response:
                        return action_response
            # 2. 处理搜索
            # 搜索列表写ORM语句,如:['name__contains','email__contains','id__gt','gender']
            search_list = self.get_search_list()
            search_value = request.GET.get('q', '')
            conn = Q()
            conn.connector = 'OR'  # 通过or链接
            if search_value:
                for item in search_list:
                    conn.children.append((item, search_value))  # conn.children.append('name__contains','张三')
    
            # 3. 获取排序
            order_list = self.get_order_list()
            search_group_condition = self.get_search_group_condition(request)  # 获取组合搜索的条件
            queryset = self.model_class.objects.filter(conn).filter(**search_group_condition).order_by(*order_list)
    
            # 4. 分页处理
            all_count = queryset.count()
            query_params = request.GET.copy()  # 深copy
            query_params._mutable = True  # query_params默认不可修改
    
            pager = Pagination(
                current_page=request.GET.get('page'),
                all_count=all_count,
                base_url=request.path_info,
                query_params=query_params,
                per_page_data=self.per_page_data,
            )
            data_list = queryset[pager.start:pager.end]
    
            # 5. 处理表格
            list_display = self.get_list_display(request, *args, **kwargs)  # 会优先调用UserInfoHandler里的get_list_display()方法。
            # 5.1 处理表格的表头
            header_list = []
            if list_display:
                for field_or_func in list_display:
                    if isinstance(field_or_func, FunctionType):
                        verbose_name = field_or_func(self, obj=None, is_header=True, *args, **kwargs)
                    else:
                        verbose_name = self.model_class._meta.get_field(field_or_func).verbose_name
                    header_list.append(verbose_name)
            else:
                header_list.append(self.model_class._meta.model_name)  # 如果用户没有填写list_display,就显示表名
    
            # 5.2 处理表的内容
            body_list = []
            for obj in data_list:
                tr_list = []
                if list_display:
                    for field_or_func in list_display:
                        if isinstance(field_or_func, FunctionType):
                            tr_list.append(field_or_func(self, obj, is_header=False, *args, **kwargs))
                        else:
                            tr_list.append(getattr(obj, field_or_func))
                else:
                    tr_list.append(obj)  # 如果用户没有填写list_display,就显示表对象,所以表类要定义__str__方法
                body_list.append(tr_list)
    
            # 6. 添加按钮
            add_btn = self.get_add_btn(*args, **kwargs)
    
            # 7. 组合搜索
            search_group_row_list = []  # 放的是SearchGroupRow的对象
            search_group = self.get_search_group()
            for option_object in search_group:
                queryset_or_tuple = option_object.get_queryset_or_tuple(self.model_class, request, *args, **kwargs)
                search_group_row_list.append(queryset_or_tuple)
    
            context = {
                'data_list': data_list,
                'header_list': header_list,
                'body_list': body_list,
                'pager': pager,
                'add_btn': add_btn,
                'search_list': search_list,
                'search_value': search_value,
                'action_dict': action_dict,
                'search_group_row_list': search_group_row_list,
            }
    
            return render(request, self.list_template or 'stark/data_list.html', context)
    
        def save(self, form, is_update=False, *args, **kwargs):
            """
            在使用ModelForm保存数据之前预留的钩子方法
            :param form:
            :param is_update:
            :return:
            """
            form.save()
    
        def add_view(self, request, *args, **kwargs):
            """
            添加页面
            :param request:
            :return:
            """
            model_form_class = self.get_model_form_class(request, *args, **kwargs)
            if request.method == 'GET':
                form = model_form_class()
                return render(request, 'stark/change.html', {'form': form})
            form = model_form_class(data=request.POST)
            if form.is_valid():
                self.save(form, False, *args, **kwargs)
                # 在数据库保存成功后,跳转回列表页面(携带原来的参数)。
                return redirect(self.reverse_list_url(*args, **kwargs))
            return render(request, self.add_template or 'stark/change.html', {'form': form})
    
        def edit_view(self, request, pk, *args, **kwargs):
            """
            编辑页面
            :param request:
            :return:
            """
            current_edit_object = self.model_class.objects.filter(pk=pk).first()
            if not current_edit_object:
                return HttpResponse('要修改的数据不存在,请重新选择')
            model_form_class = self.get_model_form_class(request, *args, **kwargs)
            if request.method == 'GET':
                form = model_form_class(instance=current_edit_object)
                return render(request, 'stark/change.html', {'form': form})
            form = self.model_form_class(data=request.POST, instance=current_edit_object)
            if form.is_valid:
                self.save(form, True, *args, **kwargs)
                # 在数据库保存成功后,跳转回列表页面(携带原来的参数)
                return redirect(self.reverse_list_url(*args, **kwargs))
            return render(request, 'stark/change.html', {'form': form})
    
        def delete_view(self, request, pk, *args, **kwargs):
            """
            删除页面
            :param request:
            :param pk:
            :return:
            """
            original_list_url = self.reverse_list_url(*args, **kwargs)
            if request.method == 'GET':
                return render(request, 'stark/delete.html', {'cancel': original_list_url})
            self.model_class.objects.filter(pk=pk).delete()
            return redirect(original_list_url)
    
        def get_url_name(self, params):
            app_label, model_name = self.model_class._meta.app_label, self.model_class._meta.model_name
            if self.prev:
                return f'{app_label}_{model_name}_{self.prev}_{params}'
            return f'{app_label}_{model_name}__{params}'
    
        @property
        def get_list_url_name(self):
            """
            获取列表页面URL的name
            :return:
            """
            return self.get_url_name('list')
    
        @property
        def get_add_url_name(self):
            """
            获取添加页面URL的name
            :return:
            """
            return self.get_url_name('add')
    
        @property
        def get_edit_url_name(self):
            """
            获取编辑页面URL的name
            :return:
            """
            return self.get_url_name('edit')
    
        @property
        def get_delete_url_name(self):
            """
            获取删除页面URL的name
            :return:
            """
            return self.get_url_name('delete')
    
        def reverse_common_url(self, name, *args, **kwargs):
            """
            生成带有原搜索条件的URL
            :param name: url
            :param args:
            :param kwargs:
            :return:
            """
            name = f'{self.site.namespace}:{name}'
            base_url = reverse(name, args=args, kwargs=kwargs)
            if not self.request.GET:
                reverse_url = base_url
            else:
                params = self.request.GET.urlencode()
                new_query_dict = QueryDict(mutable=True)
                new_query_dict['_filter'] = params
                reverse_url = f'{base_url}?{new_query_dict.urlencode()}'
            return reverse_url
    
        def reverse_add_url(self, *args, **kwargs):
            """
            带有原搜索条件的增加URL
            :param args:
            :param kwargs:
            :return:
            """
            return self.reverse_common_url(self.get_add_url_name, *args, **kwargs)
    
        def reverse_edit_url(self, *args, **kwargs):
            """
            带有原搜索条件的编辑URL
            :param args:
            :param kwargs:
            :return:
            """
            return self.reverse_common_url(self.get_edit_url_name, *args, **kwargs)
    
        def reverse_delete_url(self, *args, **kwargs):
            """
            带有原搜索条件的删除URL
            :param args:
            :param kwargs:
            :return:
            """
            return self.reverse_common_url(self.get_delete_url_name, *args, **kwargs)
    
        def reverse_list_url(self, *args, **kwargs):
            name = f'{self.site.namespace}:{self.get_list_url_name}'
            base_url = reverse(name, args=args, kwargs=kwargs)
            params = self.request.GET.get('_filter')
            if not params:
                return base_url
            return f'{base_url}?{params}'
    
        def wrapper(self, func):
            """
            当每一个request请求进来的时候,把request赋值给类的数据属性self.request
            :param func: request请求对应的视图函数
            :return:
            """
    
            @functools.wraps(func)  # 保留原函数的原信息,写装饰器建议写上这个。
            def inner(request, *args, **kwargs):
                self.request = request
                return func(request, *args, **kwargs)
    
            return inner
    
        def get_urls(self):
            patterns = [
                path('list/', self.wrapper(self.list_view), name=self.get_list_url_name),
                path('add/', self.wrapper(self.add_view), name=self.get_add_url_name),
                path('edit/<int:pk>/', self.wrapper(self.edit_view), name=self.get_edit_url_name),
                path('delete/<int:pk>/', self.wrapper(self.delete_view), name=self.get_delete_url_name),
            ]
    
            patterns.extend(self.extra_urls())
            return patterns
    
        def extra_urls(self):
            return []
    
    
    class StarkSite(object):
        def __init__(self):
            self._registry = []
            self.app_name = 'stark'
            self.namespace = 'stark'
    
        def register(self, model_class, handler_class=None, prev=None):
            """
            :param model_class: 是models中的数据库表对应的类。
            :param handler_class: 处理请求的视图函数所在的类
            :param prev: 生成URL的前缀
            :return:
            """
    
            if not handler_class:
                handler_class = StarkHandler
            self._registry.append(
                {'model_class': model_class, 'handler': handler_class(self, model_class, prev), 'prev': prev})
    
        def get_urls(self):
            patterns = []
            for item in self._registry:
                model_class = item['model_class']
                handler = item['handler']
                prev = item['prev']
                app_name, model_name = model_class._meta.app_label, model_class._meta.model_name
                if prev:
                    patterns.append(
                        path(f'{app_name}/{model_name}/{prev}/', (handler.get_urls(), None, None)))
                else:
                    patterns.append(
                        path(f'{app_name}/{model_name}/', (handler.get_urls(), None, None)))
    
            return patterns
    
        @property
        def urls(self):
            return self.get_urls(), self.app_name, self.namespace
    
    
    site = StarkSite()

    二、业务代码

    from stark.bin.core import (site, StarkHandler, StarkModelForm,
                                get_choice_text, Option)
    
    from web import models
    
    
    class UserInfoModelForm(StarkModelForm):
        class Meta:
            model = models.UserInfo
            fields = ['name', 'gender', 'classes', 'age', 'email']
    
    
    class DepartmentHandler(StarkHandler):
        list_display = ['title']
    
    
    class UserInfoHandler(StarkHandler):
        per_page_data = 5
        order_list = ['gender']
        model_form_class = UserInfoModelForm
        search_list = ['name__contains', 'email__contains', ]
        action_list = [StarkHandler.action_multi_delete, ]
        search_group = [Option('gender', is_multi=True, ), Option('department', text_func=lambda x: x.title ), ]
        list_display = [
            StarkHandler.display_checkbox,
            'name',
            get_choice_text('性别', 'gender', ),
            get_choice_text('班级', 'classes'),
            'age', 'email', 'department',
            StarkHandler.display_edit,
            StarkHandler.display_delete,
        ]
    
        def save(self, form, is_update=False, *args, **kwargs):
            form.instance.department_id = 1
            form.save()
    
    
    site.register(models.Department, DepartmentHandler)
    site.register(models.UserInfo, UserInfoHandler)

    三、模板渲染

    {% extends 'layout.html' %}
    
    {% block css %}
        <link rel="stylesheet" href="">
    {% endblock css %}
    
    
    {% block content %}
        <div class="custom-container">
    
            <!-- 组合搜索 -->
            {% if search_group_row_list %}
                <div class="panel panel-default">
                    <div class="panel-heading">
                        <i class="fa fa-filter" aria-hidden="true"></i> 快速筛选
                    </div>
                    <div class="panel-body">
                        <div class="search-group">
                            {% for row in search_group_row_list %}
                                <div class="row">
                                    {% for obj in row %}
                                        {{ obj|safe  }}
                                    {% endfor %}
                                </div>
                            {% endfor %}
                        </div>
                    </div>
                </div>
            {% endif %}
            <!-- 组合搜搜结束 -->
    
            <!-- 搜索 -->
            {% if search_list %}
                <div class="up-down-space right">
                    <form method="get" class="form-inline">
                        <div class="form-group">
                            <input class="form-control" type="text" name="q" value="{{ search_value }}"
                                   placeholder="关键字搜搜">
                            <button class="btn btn-primary" type="submit">
                                <i class="fa fa-search" aria-hidden="true"></i>
                            </button>
                        </div>
                    </form>
                </div>
            {% endif %}
            <!-- 搜索结束 -->
    
            <!-- 批量操作 -->
            <form method="post" class="form-inline">
                {% csrf_token %}
                {% if action_dict %}
                    <div class="left up-down-space">
                        <div class="form-group">
                            <select class="form-control" name="action">
                                <option value="">请选择操作</option>
                                {% for func_name,func_text in action_dict.items %}
                                    <option value="{{ func_name }}">{{ func_text }}</option>
                                {% endfor %}
                            </select>
                            <input class="btn btn-primary" type="submit" value="执行"/>
                        </div>
                    </div>
                {% endif %}
                <!-- 批量操作结束 -->
    
                <!-- 添加按钮开始 -->
                {% if add_btn %}
                    <div class="up-down-space left add_btn">
                        {{ add_btn|safe }}
                    </div>
                {% endif %}
                <!-- 添加按钮结束 -->
    
    
                <table class="table table-bordered">
                    <thead>
                    <tr>
                        {% for item in header_list %}
                            <th>{{ item }}</th>
                        {% endfor %}
                    </tr>
                    </thead>
                    <tbody>
                    {% for row in body_list %}
                        <tr>
                            {% for ele in row %}
                                <td>{{ ele }}</td>
                            {% endfor %}
                        </tr>
                    {% endfor %}
                    </tbody>
                </table>
            </form>
            <nav>
                <ul class="pagination">
                    {{ pager.page_html|safe }}
                </ul>
            </nav>
        </div>
    {% endblock content %}

    四、css

    .search-group {
        padding: 5px 10px;
    }
    
    .search-group .row .whole {
        min-width: 40px;
        float: left;
        display: inline-block;
        padding: 5px 0 5px 8px;
        margin: 3px;
        font-weight: bold;
    
    }
    
    .search-group .row .others {
        padding-left: 60px;
    }
    
    .search-group .row a {
        display: inline-block;
        padding: 5px 8px;
        margin: 3px;
        border: 1px solid #d4d4d4;
    
    }
    
    .search-group .row a {
        display: inline-block;
        padding: 5px 8px;
        margin: 3px;
        border: 1px solid #d4d4d4;
    }
    
    .search-group a.active {
        color: #fff;
        background-color: #337ab7;
        border-color: #2e6da4;
    }
  • 相关阅读:
    集合---Map
    一个机器部署多个tomcat
    JavaScript要不要加分号";"
    Nodejs 路径 /, ./, ../, ..// 的区别
    玩转Vue的24个小程序---基础篇
    如何创建Node.js Web服务器
    为什么Ajax XMLHttpRequest POST方法传递参数失败了
    字典元素如何遍历
    Beautiful Soup 如何获取到href
    如何查看Ajax请求
  • 原文地址:https://www.cnblogs.com/lshedward/p/10616610.html
Copyright © 2011-2022 走看看