铜陵市文章资讯

用自写的jQuery库+Ajax实现了省市联动功能

2026-03-28 11:15:02 浏览次数:2
详细信息

jQuery+Ajax省市联动功能实现

我将创建一个使用自编写简化版jQuery库和Ajax实现的省市联动选择器。这个组件将模拟真实的数据交互,提供流畅的用户体验。

设计思路

创建一个简化版的jQuery库(myQuery.js)包含DOM操作和Ajax功能 实现省份选择变化时,通过Ajax加载对应城市数据 使用模拟的JSON数据响应请求 设计简洁美观的界面

完整实现代码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>省市联动选择器 - 自写jQuery+Ajax实现</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-family: 'Segoe UI', 'Microsoft YaHei', sans-serif;
        }

        body {
            background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
            min-height: 100vh;
            display: flex;
            flex-direction: column;
            align-items: center;
            padding: 40px 20px;
            color: #333;
        }

        .container {
            max-width: 800px;
            width: 100%;
            background: white;
            border-radius: 16px;
            box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
            padding: 40px;
            margin-top: 20px;
        }

        header {
            text-align: center;
            margin-bottom: 40px;
        }

        h1 {
            color: #2c3e50;
            margin-bottom: 10px;
            font-size: 2.4rem;
        }

        .description {
            color: #7f8c8d;
            font-size: 1.1rem;
            max-width: 600px;
            margin: 0 auto;
            line-height: 1.6;
        }

        .selector-container {
            display: flex;
            flex-wrap: wrap;
            gap: 20px;
            margin-bottom: 40px;
        }

        .select-wrapper {
            flex: 1;
            min-width: 300px;
        }

        label {
            display: block;
            margin-bottom: 8px;
            font-weight: 600;
            color: #34495e;
        }

        select {
            width: 100%;
            padding: 14px 16px;
            border: 2px solid #e0e0e0;
            border-radius: 10px;
            font-size: 16px;
            background-color: #f8f9fa;
            transition: all 0.3s;
            color: #2c3e50;
        }

        select:focus {
            outline: none;
            border-color: #3498db;
            box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.2);
        }

        select:disabled {
            background-color: #f1f2f6;
            color: #95a5a6;
            cursor: not-allowed;
        }

        .selected-info {
            background-color: #f8f9fa;
            border-radius: 10px;
            padding: 24px;
            margin-top: 30px;
            border-left: 5px solid #3498db;
        }

        .selected-info h3 {
            color: #2c3e50;
            margin-bottom: 15px;
        }

        .result-text {
            font-size: 18px;
            color: #2c3e50;
            padding: 10px 0;
        }

        .result-text span {
            font-weight: 600;
            color: #e74c3c;
        }

        .loading {
            display: none;
            text-align: center;
            color: #3498db;
            margin: 10px 0;
            font-style: italic;
        }

        .loading.active {
            display: block;
        }

        .status {
            margin-top: 20px;
            padding: 12px 20px;
            border-radius: 8px;
            font-size: 14px;
            display: none;
        }

        .status.success {
            background-color: #d4edda;
            color: #155724;
            border: 1px solid #c3e6cb;
            display: block;
        }

        .status.error {
            background-color: #f8d7da;
            color: #721c24;
            border: 1px solid #f5c6cb;
            display: block;
        }

        .code-section {
            margin-top: 40px;
            background-color: #2d3748;
            border-radius: 10px;
            padding: 25px;
            color: #e2e8f0;
        }

        .code-section h3 {
            color: #63b3ed;
            margin-bottom: 15px;
        }

        pre {
            white-space: pre-wrap;
            word-wrap: break-word;
            background-color: #1a202c;
            padding: 20px;
            border-radius: 8px;
            overflow-x: auto;
            font-size: 14px;
            line-height: 1.5;
        }

        .highlight {
            color: #fbbf24;
        }

        .explanation {
            margin-top: 25px;
            padding: 20px;
            background-color: #edf2f7;
            border-radius: 10px;
            line-height: 1.6;
        }

        .explanation h3 {
            color: #2c3e50;
            margin-bottom: 10px;
        }

        @media (max-width: 768px) {
            .container {
                padding: 25px;
            }

            .selector-container {
                flex-direction: column;
            }

            .select-wrapper {
                min-width: 100%;
            }
        }
    </style>
</head>
<body>
    <header>
        <h1>省市联动选择器</h1>
        <p class="description">使用自编写的简化版jQuery库 + Ajax异步请求实现。选择省份后,会自动加载该省份下的城市列表。</p>
    </header>

    <div class="container">
        <div class="selector-container">
            <div class="select-wrapper">
                <label for="province">选择省份:</label>
                <select id="province">
                    <option value="">-- 请选择省份 --</option>
                    <option value="gd">广东省</option>
                    <option value="zj">浙江省</option>
                    <option value="js">江苏省</option>
                    <option value="sd">山东省</option>
                    <option value="bj">北京市</option>
                </select>
            </div>

            <div class="select-wrapper">
                <label for="city">选择城市:</label>
                <select id="city" disabled>
                    <option value="">-- 请先选择省份 --</option>
                </select>
                <div class="loading" id="city-loading">正在加载城市数据...</div>
            </div>
        </div>

        <div class="selected-info">
            <h3>选择结果</h3>
            <div class="result-text">省份:<span id="selected-province">未选择</span></div>
            <div class="result-text">城市:<span id="selected-city">未选择</span></div>
        </div>

        <div class="status" id="status"></div>

        <div class="explanation">
            <h3>实现原理</h3>
            <p>1. 当用户选择省份时,触发 <code>change</code> 事件</p>
            <p>2. 通过自写的 <code>ajax</code> 方法向服务器发送请求,获取该省份的城市数据</p>
            <p>3. 收到响应后,解析JSON数据并动态生成城市选项</p>
            <p>4. 启用城市选择框,用户可以选择城市</p>
            <p>5. 使用自写的jQuery库简化DOM操作和事件处理</p>
        </div>

        <div class="code-section">
            <h3>自写jQuery库核心代码 (myQuery.js)</h3>
            <pre><code>// 简化版jQuery库 - myQuery
(function() {
  window.myQuery = function(selector) {
    return new MyQuery(selector);
  };

  function MyQuery(selector) {
    this.elements = document.querySelectorAll(selector);
  }

  MyQuery.prototype = {
    // 绑定事件
    on: function(event, handler) {
      for (let i = 0; i < this.elements.length; i++) {
        this.elements[i].addEventListener(event, handler);
      }
      return this;
    },

    // 获取或设置HTML内容
    html: function(content) {
      if (content === undefined) {
        return this.elements[0] ? this.elements[0].innerHTML : null;
      }

      for (let i = 0; i < this.elements.length; i++) {
        this.elements[i].innerHTML = content;
      }
      return this;
    },

    // 获取或设置value
    val: function(value) {
      if (value === undefined) {
        return this.elements[0] ? this.elements[0].value : null;
      }

      for (let i = 0; i < this.elements.length; i++) {
        this.elements[i].value = value;
      }
      return this;
    },

    // 显示元素
    show: function() {
      for (let i = 0; i < this.elements.length; i++) {
        this.elements[i].style.display = 'block';
      }
      return this;
    },

    // 隐藏元素
    hide: function() {
      for (let i = 0; i < this.elements.length; i++) {
        this.elements[i].style.display = 'none';
      }
      return this;
    },

    // 添加CSS类
    addClass: function(className) {
      for (let i = 0; i < this.elements.length; i++) {
        this.elements[i].classList.add(className);
      }
      return this;
    },

    // 移除CSS类
    removeClass: function(className) {
      for (let i = 0; i < this.elements.length; i++) {
        this.elements[i].classList.remove(className);
      }
      return this;
    },

    // 设置CSS属性
    css: function(property, value) {
      for (let i = 0; i < this.elements.length; i++) {
        this.elements[i].style[property] = value;
      }
      return this;
    },

    // 获取或设置属性
    attr: function(name, value) {
      if (value === undefined) {
        return this.elements[0] ? this.elements[0].getAttribute(name) : null;
      }

      for (let i = 0; i < this.elements.length; i++) {
        this.elements[i].setAttribute(name, value);
      }
      return this;
    },

    // 启用/禁用元素
    prop: function(name, value) {
      for (let i = 0; i < this.elements.length; i++) {
        this.elements[i][name] = value;
      }
      return this;
    }
  };

  // Ajax方法
  myQuery.ajax = function(options) {
    const xhr = new XMLHttpRequest();
    const method = options.method || 'GET';
    const url = options.url;
    const data = options.data || null;
    const success = options.success || function() {};
    const error = options.error || function() {};

    xhr.open(method, url, true);

    if (options.headers) {
      for (let header in options.headers) {
        xhr.setRequestHeader(header, options.headers[header]);
      }
    }

    xhr.onreadystatechange = function() {
      if (xhr.readyState === 4) {
        if (xhr.status >= 200 && xhr.status < 300) {
          let response = xhr.responseText;
          if (options.dataType === 'json') {
            try {
              response = JSON.parse(response);
            } catch(e) {
              console.error('JSON解析错误:', e);
            }
          }
          success(response, xhr.status, xhr);
        } else {
          error(xhr, xhr.status, xhr.statusText);
        }
      }
    };

    xhr.send(data);
  };
})();</code></pre>
        </div>
    </div>

    <script>
        // 自写的简化版jQuery库
        (function() {
            window.myQuery = function(selector) {
                return new MyQuery(selector);
            };

            function MyQuery(selector) {
                this.elements = document.querySelectorAll(selector);
            }

            MyQuery.prototype = {
                // 绑定事件
                on: function(event, handler) {
                    for (let i = 0; i < this.elements.length; i++) {
                        this.elements[i].addEventListener(event, handler);
                    }
                    return this;
                },

                // 获取或设置HTML内容
                html: function(content) {
                    if (content === undefined) {
                        return this.elements[0] ? this.elements[0].innerHTML : null;
                    }

                    for (let i = 0; i < this.elements.length; i++) {
                        this.elements[i].innerHTML = content;
                    }
                    return this;
                },

                // 获取或设置value
                val: function(value) {
                    if (value === undefined) {
                        return this.elements[0] ? this.elements[0].value : null;
                    }

                    for (let i = 0; i < this.elements.length; i++) {
                        this.elements[i].value = value;
                    }
                    return this;
                },

                // 显示元素
                show: function() {
                    for (let i = 0; i < this.elements.length; i++) {
                        this.elements[i].style.display = 'block';
                    }
                    return this;
                },

                // 隐藏元素
                hide: function() {
                    for (let i = 0; i < this.elements.length; i++) {
                        this.elements[i].style.display = 'none';
                    }
                    return this;
                },

                // 添加CSS类
                addClass: function(className) {
                    for (let i = 0; i < this.elements.length; i++) {
                        this.elements[i].classList.add(className);
                    }
                    return this;
                },

                // 移除CSS类
                removeClass: function(className) {
                    for (let i = 0; i < this.elements.length; i++) {
                        this.elements[i].classList.remove(className);
                    }
                    return this;
                },

                // 设置CSS属性
                css: function(property, value) {
                    for (let i = 0; i < this.elements.length; i++) {
                        this.elements[i].style[property] = value;
                    }
                    return this;
                },

                // 获取或设置属性
                attr: function(name, value) {
                    if (value === undefined) {
                        return this.elements[0] ? this.elements[0].getAttribute(name) : null;
                    }

                    for (let i = 0; i < this.elements.length; i++) {
                        this.elements[i].setAttribute(name, value);
                    }
                    return this;
                },

                // 启用/禁用元素
                prop: function(name, value) {
                    for (let i = 0; i < this.elements.length; i++) {
                        this.elements[i][name] = value;
                    }
                    return this;
                }
            };

            // Ajax方法
            myQuery.ajax = function(options) {
                const xhr = new XMLHttpRequest();
                const method = options.method || 'GET';
                const url = options.url;
                const data = options.data || null;
                const success = options.success || function() {};
                const error = options.error || function() {};

                xhr.open(method, url, true);

                if (options.headers) {
                    for (let header in options.headers) {
                        xhr.setRequestHeader(header, options.headers[header]);
                    }
                }

                xhr.onreadystatechange = function() {
                    if (xhr.readyState === 4) {
                        if (xhr.status >= 200 && xhr.status < 300) {
                            let response = xhr.responseText;
                            if (options.dataType === 'json') {
                                try {
                                    response = JSON.parse(response);
                                } catch(e) {
                                    console.error('JSON解析错误:', e);
                                }
                            }
                            success(response, xhr.status, xhr);
                        } else {
                            error(xhr, xhr.status, xhr.statusText);
                        }
                    }
                };

                xhr.send(data);
            };
        })();
    </script>

    <script>
        // 页面加载完成后初始化
        document.addEventListener('DOMContentLoaded', function() {
            // 使用自写的myQuery库
            const $ = myQuery;

            // 省份数据映射
            const provinceData = {
                'gd': '广东省',
                'zj': '浙江省', 
                'js': '江苏省',
                'sd': '山东省',
                'bj': '北京市'
            };

            // 城市数据(模拟服务器数据)
            const cityData = {
                'gd': [
                    { id: 'gz', name: '广州市' },
                    { id: 'sz', name: '深圳市' },
                    { id: 'dg', name: '东莞市' },
                    { id: 'fs', name: '佛山市' },
                    { id: 'zh', name: '珠海市' }
                ],
                'zj': [
                    { id: 'hz', name: '杭州市' },
                    { id: 'nb', name: '宁波市' },
                    { id: 'wz', name: '温州市' },
                    { id: 'jx', name: '嘉兴市' },
                    { id: 'sx', name: '绍兴市' }
                ],
                'js': [
                    { id: 'nj', name: '南京市' },
                    { id: 'sz', name: '苏州市' },
                    { id: 'wx', name: '无锡市' },
                    { id: 'cz', name: '常州市' },
                    { id: 'xz', name: '徐州市' }
                ],
                'sd': [
                    { id: 'jn', name: '济南市' },
                    { id: 'qd', name: '青岛市' },
                    { id: 'zb', name: '淄博市' },
                    { id: 'yt', name: '烟台市' },
                    { id: 'wf', name: '潍坊市' }
                ],
                'bj': [
                    { id: 'dcq', name: '东城区' },
                    { id: 'xcq', name: '西城区' },
                    { id: 'cyq', name: '朝阳区' },
                    { id: 'hdq', name: '海淀区' },
                    { id: 'ftq', name: '丰台区' }
                ]
            };

            // 模拟服务器API - 根据省份ID返回城市数据
            function mockServerAPI(provinceId) {
                return new Promise((resolve) => {
                    // 模拟网络延迟
                    setTimeout(() => {
                        if (cityData[provinceId]) {
                            resolve({
                                success: true,
                                data: cityData[provinceId]
                            });
                        } else {
                            resolve({
                                success: false,
                                message: '未找到该省份的城市数据'
                            });
                        }
                    }, 500); // 500ms延迟模拟网络请求
                });
            }

            // 初始化省份选择事件
            $('#province').on('change', function(e) {
                const provinceId = e.target.value;
                const citySelect = $('#city');
                const loading = $('#city-loading');
                const status = $('#status');

                // 更新显示的省份
                const provinceName = provinceData[provinceId] || '未选择';
                $('#selected-province').html(provinceName);
                $('#selected-city').html('未选择');

                if (!provinceId) {
                    // 如果没有选择省份,禁用城市选择框
                    citySelect.prop('disabled', true).val('');
                    citySelect.html('<option value="">-- 请先选择省份 --</option>');
                    status.removeClass('success').removeClass('error');
                    return;
                }

                // 显示加载状态
                loading.addClass('active');
                citySelect.prop('disabled', true).html('<option value="">加载中...</option>');
                status.removeClass('success').removeClass('error');

                // 使用自写的ajax方法获取城市数据
                // 这里我们使用一个模拟的API端点
                const apiUrl = `/api/cities?province=${provinceId}&_t=${Date.now()}`;

                // 模拟Ajax请求 - 实际项目中这里应该是真实的服务器API
                myQuery.ajax({
                    method: 'GET',
                    url: apiUrl,
                    dataType: 'json',
                    success: function(response) {
                        // 模拟服务器响应
                        // 在实际应用中,这里应该解析真实的服务器响应
                        // 为了演示,我们使用本地数据模拟
                        mockServerAPI(provinceId).then(mockResponse => {
                            // 隐藏加载状态
                            loading.removeClass('active');

                            if (mockResponse.success) {
                                // 清空城市选择框
                                let cityOptions = '<option value="">-- 请选择城市 --</option>';

                                // 添加城市选项
                                mockResponse.data.forEach(city => {
                                    cityOptions += `<option value="${city.id}">${city.name}</option>`;
                                });

                                // 更新城市选择框
                                citySelect.html(cityOptions).prop('disabled', false);

                                // 显示成功状态
                                status.html(`成功加载 ${provinceData[provinceId]} 的城市数据,共 ${mockResponse.data.length} 个城市`).addClass('success');
                            } else {
                                citySelect.html('<option value="">数据加载失败</option>');
                                status.html(mockResponse.message).addClass('error');
                            }
                        });
                    },
                    error: function(xhr, status, error) {
                        // 隐藏加载状态
                        loading.removeClass('active');

                        // 显示错误状态
                        citySelect.html('<option value="">数据加载失败</option>');
                        status.html('网络请求失败,请检查网络连接').addClass('error');
                        console.error('Ajax请求失败:', error);
                    }
                });
            });

            // 城市选择事件
            $('#city').on('change', function(e) {
                const cityId = e.target.value;
                const cityName = e.target.options[e.target.selectedIndex].text;

                if (cityId) {
                    $('#selected-city').html(cityName);
                } else {
                    $('#selected-city').html('未选择');
                }
            });

            // 初始状态提示
            console.log('省市联动选择器已初始化');
            console.log('使用的自写jQuery库方法:');
            console.log('- myQuery(selector) - 选择元素');
            console.log('- .on(event, handler) - 绑定事件');
            console.log('- .html(content) - 获取/设置HTML');
            console.log('- .val(value) - 获取/设置值');
            console.log('- .prop(name, value) - 获取/设置属性');
            console.log('- myQuery.ajax(options) - 发送Ajax请求');
        });
    </script>
</body>
</html>

功能说明

自写jQuery库 (myQuery):

省市联动功能

用户界面

如何使用

选择任意一个省份 观察加载状态和Ajax请求过程 选择加载后的城市 查看底部显示的选择结果

这个实现展示了如何在不依赖外部库的情况下,使用原生JavaScript实现类似jQuery的功能,并结合Ajax完成动态数据加载。

相关推荐