如何用PHP和JavaScript实现Country->State->City->按钮的有序追加
问题分析
你遇到的追加内容顺序随机问题,核心原因是三个并行的AJAX请求响应返回时间不固定。在add_function()里,你同时发起了获取国家、州、城市数据的三个异步请求,它们的响应返回顺序不受控制,谁先回来就会先把对应的HTML插入页面,最终导致Country->State->City的顺序被打乱。
另外还有个小bug:你用document.createElement('unique')创建元素是错误的,unique是容器的id,不是合法的HTML标签名,应该创建div元素。
解决方案
我们可以通过Promise封装AJAX请求,用async/await确保请求按顺序执行并渲染,保证Country、State、City的输出顺序严格符合预期。下面是修复后的完整代码:
<div id="unique"> <div class="form-group"> <label class="col-sm-2 control-label">Country Name</label> <div class="col-md-6"> <select name="cname" class="form-control" id="coid<?php echo $c;?>" fd="0" onChange="countryfunction(this);" required> <option >choose country</option> <?php include('include/db.php'); $sql="select id,country_name from country"; $query=mysqli_query($con,$sql) or die("error"); while($row=mysqli_fetch_array($query,MYSQLI_BOTH)) { ?> <option value="<?php echo $country_id=$row['id'];?>"><?php echo $row['country_name']; ?></option> <?php } ?> </select> <span class="help-block"></span> </div> </div> <div class="form-group"> <label class="col-sm-2 control-label">State Name</label> <div class="col-md-6"> <select name="state" class="form-control" id="sid<?php echo $c;?>" md="0" onChange="statefunction(this);" > <option >Select state</option> </select> <span class="help-block"></span> </div> </div> <div class="form-group"> <label class="col-sm-2 control-label">City Name</label> <div class="col-md-6"> <select name="city" class="form-control" id="cid<?php echo $c;?>" > <option >Select City</option> </select> <span class="help-block"></span> </div> </div> <div class="form-group"> <label class="col-sm-2 control-label"> </label> <div class="col-md-6"> <input type="button" value="+" onClick="add_function()" id="btnid<?php echo $i;?>" /> <span class="help-block"></span> </div> </div> </div> <script> // 封装AJAX为Promise,方便用async/await控制顺序 function ajaxPost(url, data) { return new Promise((resolve, reject) => { const xmlhttp = new XMLHttpRequest(); xmlhttp.onreadystatechange = function() { if (this.readyState === 4) { if (this.status === 200) { resolve(JSON.parse(this.responseText)); } else { reject(new Error('请求加载失败')); } } }; xmlhttp.open("POST", url, true); xmlhttp.setRequestHeader("Content-type","application/x-www-form-urlencoded"); xmlhttp.send(data); }); } function countryfunction(see) { const nnn = $(see).attr('fd'); const DATA = document.getElementById("coid"+nnn).value; const sid = document.getElementById("sid"+nnn); const xhttp = new XMLHttpRequest(); xhttp.onreadystatechange = function(){ if (this.readyState === 4 && this.status === 200) { sid.innerHTML = this.responseText; } }; xhttp.open("GET", "stateid1.php?r="+DATA, true); xhttp.send(); } function statefunction(dee) { const mmm = $(dee).attr('md'); const data = document.getElementById("sid"+mmm).value; const Y = document.getElementById("cid"+mmm); const xhttp = new XMLHttpRequest(); xhttp.onreadystatechange = function(){ if (this.readyState === 4 && this.status === 200) { Y.innerHTML = this.responseText; } }; xhttp.open("GET", "cityidr.php?r="+data, true); xhttp.send(); } let i = 0; let c = 0; async function add_function() { // 修复元素创建错误:创建div而非不存在的unique标签 const objTo = document.getElementById('unique'); const divtest = document.createElement('div'); i++; c++; try { // 1. 先获取并渲染国家下拉框 const countries = await ajaxPost("demo6.php", "x=" + JSON.stringify({})); let countryHtml = "<div class='form-group' > <label class='col-sm-2 control-label'>Country Name</label><div class='col-md-6'><select name='country[]' onChange='countryfunction(this);' fd='"+c+"' id='coid"+c+"' class='form-control'><option>--Choose Country--</option>"; for (const key in countries) { countryHtml += `<option value="${countries[key].id}">${countries[key].country_name}</option>`; } countryHtml += "</select><span class='help-block'></span></div></div>"; divtest.innerHTML += countryHtml; // 2. 再获取并渲染州下拉框 const states = await ajaxPost("demo9.php", "y=" + JSON.stringify({})); let stateHtml = "<div class='form-group' ><label class='col-sm-2 control-label'>State Name</label><div class='col-md-6'><select name='state[]' onChange='statefunction(this);' md='"+c+"' id='sid"+c+"' class='form-control'><option>--Select state--</option>"; for (const key in states) { stateHtml += `<option value="${states[key].id}">${states[key].state_name}</option>`; } stateHtml += "</select><span class='help-block'></span></div></div>"; divtest.innerHTML += stateHtml; // 3. 最后获取并渲染城市下拉框 const cities = await ajaxPost("demo11.php", "z=" + JSON.stringify({})); let cityHtml = "<div class='form-group'><label class='col-sm-2 control-label'>City Name</label><div class='col-md-6'><select name='city[]' id='cid"+c+"' class='form-control'><option>--Select City--</option>"; for (const key in cities) { cityHtml += `<option value="${cities[key].id}">${cities[key].city_name}</option>`; } cityHtml += "</select><span class='help-block'></span></div></div>"; divtest.innerHTML += cityHtml; // 4. 渲染+按钮 const btnHtml = `<div class='form-group'><label class='col-sm-2 control-label'></label><div class='col-md-6'> <input type='button' value='+' onClick='add_function()' id='btnid${i}' /><span class='help-block'></span></div></div>`; divtest.innerHTML += btnHtml; // 将组装好的整行内容添加到容器 objTo.appendChild(divtest); } catch (error) { console.error('加载数据出错:', error); } } </script>
关键改进点
- Promise+async/await控制异步顺序:把AJAX请求封装成Promise,用
await确保前一个请求完成并渲染后,再执行下一个请求,严格保证Country->State->City的顺序。 - 修复元素创建错误:将
document.createElement('unique')改为document.createElement('div'),避免创建无效标签。 - 优化HTML组装逻辑:每个模块的HTML一次性组装完成后再插入,减少
innerHTML的修改次数,避免潜在的渲染问题。
额外优化建议:如果后端支持,可以把三个请求合并成一个接口,一次性返回国家、州、城市的初始数据,减少HTTP请求数量,提升页面加载效率。
内容的提问来源于stack exchange,提问作者shivam
相关产品推荐
相关产品推荐

