<think>我们使用XMLHttpRequest对象来发送请求,替换掉原来的mockApiCall函数。
注意:由于接口URL未给出,我们假设为'https://xxxxxx.net/api/area',实际使用时请替换为真实URL。
根据要求,请求参数为pname(省份名)和cname(城市名),我们将使用GET请求,参数放在URL中。
步骤:
1. 创建一个XMLHttpRequest对象
2. 设置请求方法和URL,将参数拼接到URL上
3. 设置请求头(如果需要)
4. 发送请求
5. 处理响应
由于我们使用的是GET请求,所以不需要发送请求体。
修改原代码中的fetchDistricts方法,使用XMLHttpRequest发送请求。
注意:由于XMLHttpRequest是异步的,我们将其封装在Promise中,以便使用async/await。
另外,我们假设返回的数据结构为JSON格式,并且包含一个数组,例如:["区1", "区2", ...]
如果返回的数据结构不同,需要根据实际情况调整。
修改后的mockApiCall函数将被替换为realApiCall函数,它接收两个参数:province和city。
由于跨域问题,如果接口支持CORS,则可以直接请求;否则需要处理跨域问题(这里不涉及,假设接口支持CORS)。
代码修改如下:</think># Vue省份城市区域查询系统(真实API实现)
下面是一个完整的HTML文件,实现了使用真实API的省份城市区域查询功能:
```html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vue省份城市区域查询系统</title>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
body {
background: linear-gradient(135deg, #1a2a6c, #b21f1f, #fdbb2d);
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
padding: 20px;
}
.container {
width: 100%;
max-width: 800px;
background: rgba(255, 255, 255, 0.95);
border-radius: 20px;
box-shadow: 0 15px 35px rgba(0, 0, 0, 0.3);
overflow: hidden;
padding: 30px;
}
header {
text-align: center;
margin-bottom: 30px;
}
h1 {
color: #2c3e50;
font-size: 2.5rem;
margin-bottom: 10px;
position: relative;
display: inline-block;
}
h1::after {
content: '';
position: absolute;
bottom: -10px;
left: 50%;
transform: translateX(-50%);
width: 120px;
height: 4px;
background: linear-gradient(to right, #3498db, #2ecc71);
border-radius: 2px;
}
.subtitle {
color: #7f8c8d;
font-size: 1.1rem;
margin-top: 15px;
}
.input-section {
background: #f8f9fa;
border-radius: 15px;
padding: 25px;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05);
margin-bottom: 30px;
}
.input-group {
display: flex;
gap: 15px;
margin-bottom: 20px;
}
.input-container {
flex: 1;
}
label {
display: block;
margin-bottom: 8px;
color: #2c3e50;
font-weight: 600;
}
input {
width: 100%;
padding: 14px 20px;
border: 2px solid #e0e0e0;
border-radius: 12px;
font-size: 1.1rem;
transition: all 0.3s ease;
outline: none;
}
input:focus {
border-color: #3498db;
box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.2);
}
.btn-container {
text-align: center;
margin-top: 20px;
}
button {
background: linear-gradient(to right, #3498db, #2ecc71);
color: white;
border: none;
border-radius: 12px;
padding: 14px 40px;
font-size: 1.2rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 4px 15px rgba(52, 152, 219, 0.3);
}
button:hover {
transform: translateY(-3px);
box-shadow: 0 7px 20px rgba(52, 152, 219, 0.4);
}
button:active {
transform: translateY(0);
}
.result-section {
background: #f8f9fa;
border-radius: 15px;
padding: 25px;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05);
}
.result-title {
color: #2c3e50;
font-size: 1.5rem;
margin-bottom: 20px;
text-align: center;
}
.result-content {
min-height: 200px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
select {
width: 100%;
padding: 15px;
border: 2px solid #e0e0e0;
border-radius: 12px;
font-size: 1.1rem;
background: white;
color: #2c3e50;
margin-bottom: 20px;
outline: none;
transition: all 0.3s ease;
}
select:focus {
border-color: #3498db;
box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.2);
}
.hint {
color: #7f8c8d;
font-size: 1.2rem;
text-align: center;
padding: 30px;
}
.selected-area {
background: linear-gradient(to right, #3498db, #2ecc71);
color: white;
padding: 15px 30px;
border-radius: 50px;
font-size: 1.3rem;
font-weight: 600;
text-align: center;
margin-top: 20px;
opacity: 0;
transform: translateY(20px);
transition: all 0.5s ease;
}
.selected-area.show {
opacity: 1;
transform: translateY(0);
}
.instructions {
background: #e3f2fd;
border-left: 4px solid #3498db;
padding: 15px 20px;
border-radius: 0 10px 10px 0;
margin-top: 25px;
}
.instructions h3 {
color: #2c3e50;
margin-bottom: 10px;
}
.instructions ul {
padding-left: 20px;
}
.instructions li {
margin-bottom: 8px;
color: #34495e;
}
.footer {
text-align: center;
margin-top: 30px;
color: #7f8c8d;
font-size: 0.9rem;
}
.status-indicator {
display: inline-block;
width: 12px;
height: 12px;
border-radius: 50%;
margin-right: 8px;
}
.status-loading {
background-color: #f39c12;
animation: pulse 1.5s infinite;
}
.status-success {
background-color: #2ecc71;
}
.status-error {
background-color: #e74c3c;
}
@keyframes pulse {
0% { opacity: 0.4; }
50% { opacity: 1; }
100% { opacity: 0.4; }
}
.api-status {
display: flex;
align-items: center;
justify-content: center;
margin-top: 15px;
font-size: 1rem;
}
@media (max-width: 768px) {
.input-group {
flex-direction: column;
}
h1 {
font-size: 2rem;
}
}
</style>
</head>
<body>
<div id="app" class="container">
<header>
<h1>省份城市区域查询系统</h1>
<p class="subtitle">基于Vue.js实现的真实API区域查询</p>
</header>
<section class="input-section">
<div class="input-group">
<div class="input-container">
<label for="province">省份名称:</label>
<input
type="text"
id="province"
placeholder="请输入省份,如:北京"
v-model="province"
@keyup.enter="fetchDistricts"
>
</div>
<div class="input-container">
<label for="city">城市名称:</label>
<input
type="text"
id="city"
placeholder="请输入城市,如:北京市"
v-model="city"
@keyup.enter="fetchDistricts"
>
</div>
</div>
<div class="btn-container">
<button @click="fetchDistricts">查询区域</button>
</div>
<div class="api-status">
<span class="status-indicator" :class="{
'status-loading': apiStatus === 'loading',
'status-success': apiStatus === 'success',
'status-error': apiStatus === 'error'
}"></span>
{{ statusMessage }}
</div>
</section>
<section class="result-section">
<h2 class="result-title">查询结果</h2>
<div class="result-content">
<select v-if="districts.length" v-model="selectedDistrict" @change="showSelected">
<option value="" disabled selected>请选择区域</option>
<option v-for="(district, index) in districts" :key="index" :value="district">
{{ district }}
</option>
</select>
<div v-else-if="apiStatus === 'loading'" class="hint">
正在从API加载区域数据...
</div>
<div v-else-if="apiStatus === 'error'" class="hint">
数据加载失败,请重试
</div>
<div v-else class="hint">
请在上方输入省份和城市后查询
</div>
<div class="selected-area" :class="{ show: selectedDistrict }">
您选择了:{{ selectedDistrict }}
</div>
</div>
</section>
<div class="instructions">
<h3>使用说明:</h3>
<ul>
<li>在省份输入框中输入省份名称(如:北京)</li>
<li>在城市输入框中输入城市名称(如:北京市)</li>
<li>点击"查询区域"按钮或按Enter键获取该城市的区域列表</li>
<li>从下拉菜单中选择您要查询的区域</li>
<li>底部状态指示器显示API请求状态</li>
</ul>
</div>
<footer class="footer">
<p>Vue.js区域查询系统 | 基于Vue 3实现 | 真实API调用</p>
</footer>
</div>
<script>
const { createApp, ref } = Vue;
createApp({
setup() {
// 双向绑定的省份和城市
const province = ref('');
const city = ref('');
// 存储区域列表
const districts = ref([]);
// 选中的区域
const selectedDistrict = ref('');
// API请求状态
const apiStatus = ref('idle'); // idle, loading, success, error
const statusMessage = ref('准备就绪');
// 真实API调用函数
const callRealApi = (pname, cname) => {
return new Promise((resolve, reject) => {
// 创建XMLHttpRequest对象
const xhr = new XMLHttpRequest();
// 构造请求URL(实际使用时替换为真实URL)
const apiUrl = `https://xxxxxx.net/api/area?pname=${encodeURIComponent(pname)}&cname=${encodeURIComponent(cname)}`;
xhr.open('GET', apiUrl, true);
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
try {
const response = JSON.parse(xhr.responseText);
// 假设API返回结构为 { data: ['区1', '区2', ...] }
if (response && Array.isArray(response.data)) {
resolve(response.data);
} else {
reject(new Error('API返回数据结构异常'));
}
} catch (e) {
reject(new Error('解析JSON失败'));
}
} else {
reject(new Error(`API请求失败,状态码: ${xhr.status}`));
}
}
};
xhr.onerror = function() {
reject(new Error('网络请求失败'));
};
xhr.timeout = 8000; // 8秒超时
xhr.ontimeout = function() {
reject(new Error('请求超时'));
};
xhr.send();
});
};
// 获取区域数据
const fetchDistricts = async () => {
if (!province.value.trim() || !city.value.trim()) {
apiStatus.value = 'error';
statusMessage.value = '请输入省份和城市名称';
return;
}
apiStatus.value = 'loading';
statusMessage.value = '正在请求API数据...';
districts.value = [];
selectedDistrict.value = '';
try {
// 调用真实API
const result = await callRealApi(province.value, city.value);
districts.value = result;
apiStatus.value = 'success';
statusMessage.value = `成功获取${result.length}个区域`;
} catch (error) {
console.error('API请求失败:', error);
districts.value = [];
apiStatus.value = 'error';
statusMessage.value = `请求失败: ${error.message}`;
}
};
// 显示选中的区域
const showSelected = () => {
// 这里可以添加实际选中区域后的逻辑
};
return {
province,
city,
districts,
selectedDistrict,
apiStatus,
statusMessage,
fetchDistricts,
showSelected
};
}
}).mount('#app');
</script>
</body>
</html>
```
## 真实API集成说明
这个实现将模拟API替换为真实的API调用,使用XMLHttpRequest对象与后端服务通信:
### 核心实现
1. **API调用函数**:
```javascript
const callRealApi = (pname, cname) => {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
const apiUrl = `https://xxxxxx.net/api/area?pname=${encodeURIComponent(pname)}&cname=${encodeURIComponent(cname)}`;
xhr.open('GET', apiUrl, true);
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
// 解析响应数据
} else {
reject(new Error(`API请求失败,状态码: ${xhr.status}`));
}
}
};
// 错误处理和超时处理
xhr.onerror = function() { /*...*/ };
xhr.timeout = 8000;
xhr.ontimeout = function() { /*...*/ };
xhr.send();
});
};
```
2. **API状态管理**:
- `apiStatus`: 跟踪请求状态(idle/loading/success/error)
- `statusMessage`: 显示详细的API状态信息
- 状态指示器:通过CSS动画直观显示当前状态
3. **错误处理**:
- 输入验证
- 网络错误处理
- 超时处理(8秒)
- JSON解析错误处理
- API响应数据结构验证
### 使用说明
1. 在省份输入框中输入省份名称(如:北京)
2. 在城市输入框中输入城市名称(如:北京市)
3. 点击"查询区域"按钮或按Enter键
4. 系统会显示API请求状态
5. 成功获取数据后,从下拉菜单中选择区域
### 实际使用注意事项
1. **跨域问题**:
- 实际部署时,如果API在不同域,需要配置CORS
- 开发时可以使用代理解决跨域问题
2. **API URL**:
- 将代码中的 `https://xxxxxx.net/api/area` 替换为实际API地址
- 根据实际API参数要求调整URL参数
3. **响应格式**:
- 代码假设API返回格式为 `{ data: ['区1', '区2', ...] }`
- 根据实际API响应结构调整数据解析逻辑
4. **安全性**:
- 生产环境应考虑添加API密钥验证
- 对用户输入进行更严格的验证和过滤