<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>智能定位权限请求系统</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
:root {
--primary: #3b82f6;
--primary-dark: #2563eb;
--secondary: #8b5cf6;
--success: #10b981;
--danger: #ef4444;
--warning: #f59e0b;
--info: #3b82f6;
--light: #f8fafc;
--dark: #0f172a;
--gray: #94a3b8;
--border: #e2e8f0;
--card-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1);
--transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
body {
background: linear-gradient(135deg, #e0f2fe 0%, #dbeafe 100%);
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
padding: 20px;
position: relative;
overflow-x: hidden;
}
.container {
width: 100%;
max-width: 1000px;
display: flex;
flex-direction: column;
gap: 30px;
}
.header {
text-align: center;
padding: 20px;
animation: fadeInDown 0.8s ease;
}
.header h1 {
font-size: 2.5rem;
background: linear-gradient(135deg, var(--primary), var(--secondary));
-webkit-background-clip: text;
background-clip: text;
color: transparent;
margin-bottom: 10px;
font-weight: 800;
}
.header p {
color: var(--gray);
font-size: 1.1rem;
max-width: 600px;
margin: 0 auto;
}
.main-content {
display: flex;
gap: 30px;
flex-wrap: wrap;
}
.card {
background: white;
border-radius: 20px;
box-shadow: var(--card-shadow);
padding: 30px;
transition: var(--transition);
position: relative;
overflow: hidden;
}
.card::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 4px;
background: linear-gradient(90deg, var(--primary), var(--secondary));
}
.permission-section {
flex: 1;
min-width: 350px;
animation: slideInLeft 0.6s ease;
}
.location-section {
width: 350px;
animation: slideInRight 0.6s ease;
}
.section-title {
font-size: 1.5rem;
color: var(--dark);
margin-bottom: 20px;
display: flex;
align-items: center;
gap: 10px;
}
.permission-prompt {
background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%);
border-radius: 15px;
padding: 25px;
text-align: center;
margin-bottom: 25px;
}
.permission-icon {
font-size: 3.5rem;
color: var(--primary);
margin-bottom: 20px;
}
.permission-title {
font-size: 1.4rem;
color: var(--dark);
margin-bottom: 15px;
}
.permission-desc {
color: var(--gray);
font-size: 1rem;
line-height: 1.6;
margin-bottom: 25px;
}
.permission-benefits {
text-align: left;
margin: 20px 0;
}
.benefit-item {
display: flex;
align-items: flex-start;
gap: 12px;
margin-bottom: 15px;
}
.benefit-icon {
color: var(--success);
margin-top: 3px;
}
.benefit-text {
color: var(--dark);
font-size: 0.95rem;
}
.btn {
width: 100%;
padding: 14px;
border: none;
border-radius: 12px;
font-size: 1.1rem;
font-weight: 600;
cursor: pointer;
transition: var(--transition);
position: relative;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
margin-top: 15px;
}
.btn-primary {
background: linear-gradient(135deg, var(--primary), var(--secondary));
color: white;
box-shadow: 0 4px 15px rgba(59, 130, 246, 0.3);
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(59, 130, 246, 0.4);
}
.btn-outline {
background: transparent;
border: 2px solid var(--primary);
color: var(--primary);
}
.btn-outline:hover {
background: var(--primary);
color: white;
}
.btn-warning {
background: linear-gradient(135deg, var(--warning), #f97316);
color: white;
}
.btn-warning:hover {
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(249, 115, 22, 0.4);
}
.message {
padding: 15px 20px;
border-radius: 12px;
margin-bottom: 20px;
display: none;
animation: slideInUp 0.3s ease;
font-size: 0.95rem;
font-weight: 500;
}
.message.success {
background: rgba(16, 185, 129, 0.1);
border: 1px solid var(--success);
color: var(--success);
}
.message.error {
background: rgba(239, 68, 68, 0.1);
border: 1px solid var(--danger);
color: var(--danger);
}
.message.info {
background: rgba(59, 130, 246, 0.1);
border: 1px solid var(--info);
color: var(--info);
}
.message.warning {
background: rgba(245, 158, 11, 0.1);
border: 1px solid var(--warning);
color: var(--warning);
}
.location-info {
background: #f0f9ff;
border-radius: 15px;
padding: 20px;
margin-bottom: 25px;
}
.location-item {
display: flex;
justify-content: space-between;
padding: 12px 0;
border-bottom: 1px solid var(--border);
}
.location-item:last-child {
border-bottom: none;
}
.location-label {
font-weight: 500;
color: var(--dark);
}
.location-value {
color: var(--gray);
}
.status-indicator {
display: flex;
align-items: center;
gap: 10px;
padding: 15px;
border-radius: 12px;
margin-top: 20px;
background: #f0f9ff;
}
.status-dot {
width: 12px;
height: 12px;
border-radius: 50%;
background: var(--gray);
}
.status-dot.active {
background: var(--success);
box-shadow: 0 0 10px var(--success);
}
.status-dot.warning {
background: var(--warning);
box-shadow: 0 0 10px var(--warning);
}
.status-text {
font-size: 0.95rem;
color: var(--dark);
}
.loading {
display: inline-block;
width: 20px;
height: 20px;
border: 3px solid rgba(255,255,255,.3);
border-radius: 50%;
border-top-color: #fff;
animation: spin 1s ease-in-out infinite;
}
.denied-prompt {
display: none;
background: #fff7ed;
border-radius: 15px;
padding: 25px;
text-align: center;
margin-bottom: 25px;
border: 1px solid #fed7aa;
}
.denied-icon {
font-size: 3rem;
color: var(--warning);
margin-bottom: 20px;
}
.denied-title {
font-size: 1.4rem;
color: var(--dark);
margin-bottom: 15px;
}
.denied-desc {
color: var(--gray);
font-size: 1rem;
line-height: 1.6;
margin-bottom: 25px;
}
.manual-instructions {
text-align: left;
background: #ffedd5;
border-radius: 10px;
padding: 15px;
margin: 20px 0;
}
.instruction-title {
font-weight: 600;
margin-bottom: 10px;
color: var(--dark);
}
.instruction-steps {
padding-left: 20px;
}
.instruction-steps li {
margin-bottom: 8px;
color: var(--gray);
font-size: 0.9rem;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes fadeInDown {
from {
opacity: 0;
transform: translateY(-20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes slideInLeft {
from {
opacity: 0;
transform: translateX(-30px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
@keyframes slideInRight {
from {
opacity: 0;
transform: translateX(30px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
@keyframes slideInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes spin {
to { transform: rotate(360deg); }
}
@media (max-width: 768px) {
.main-content {
flex-direction: column;
}
.permission-section, .location-section {
min-width: 100%;
}
.header h1 {
font-size: 2rem;
}
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1><i class="fas fa-map-marker-alt"></i> 智能定位权限请求</h1>
<p>优雅地处理用户定位权限请求,提供重新请求选项</p>
</div>
<div class="main-content">
<div class="permission-section">
<div class="card">
<h2 class="section-title"><i class="fas fa-key"></i> 定位权限请求</h2>
<div id="permissionMessage" class="message"></div>
<!-- 权限请求提示 -->
<div class="permission-prompt" id="permissionPrompt">
<div class="permission-icon">
<i class="fas fa-location-dot"></i>
</div>
<h3 class="permission-title">我们需要您的位置信息</h3>
<p class="permission-desc">
为了为您提供准确的天气信息和个性化服务,我们需要获取您的当前位置。
您的位置信息仅用于改善您的使用体验。
</p>
<div class="permission-benefits">
<div class="benefit-item">
<i class="fas fa-check-circle benefit-icon"></i>
<div class="benefit-text">获取当前位置的实时天气信息</div>
</div>
<div class="benefit-item">
<i class="fas fa-check-circle benefit-icon"></i>
<div class="benefit-text">提供基于位置的个性化服务</div>
</div>
<div class="benefit-item">
<i class="fas fa-check-circle benefit-icon"></i>
<div class="benefit-text">您的隐私信息将得到严格保护</div>
</div>
</div>
<button class="btn btn-primary" id="requestPermissionBtn">
<span id="requestPermissionText"><i class="fas fa-location-arrow"></i> 授予位置权限</span>
</button>
<button class="btn btn-outline" id="skipPermissionBtn">
<i class="fas fa-times"></i> 暂时跳过
</button>
</div>
<!-- 权限被拒绝后的提示 -->
<div class="denied-prompt" id="deniedPrompt">
<div class="denied-icon">
<i class="fas fa-exclamation-triangle"></i>
</div>
<h3 class="denied-title">位置权限被拒绝</h3>
<p class="denied-desc">
您之前拒绝了位置权限请求。为了提供完整功能,我们建议您授予位置权限。
</p>
<div class="manual-instructions">
<div class="instruction-title">如何手动授予权限:</div>
<ol class="instruction-steps">
<li>点击浏览器地址栏左侧的锁图标</li>
<li>选择"网站设置"</li>
<li>找到"位置"选项并设置为"允许"</li>
<li>刷新页面以重新请求权限</li>
</ol>
</div>
<button class="btn btn-warning" id="retryPermissionBtn">
<span id="retryPermissionText"><i class="fas fa-redo"></i> 重新请求权限</span>
</button>
<button class="btn btn-outline" id="continueWithoutLocationBtn">
<i class="fas fa-arrow-right"></i> 继续使用(无位置功能)
</button>
</div>
<div class="status-indicator">
<div class="status-dot" id="permissionStatus"></div>
<div class="status-text" id="permissionStatusText">等待用户操作</div>
</div>
</div>
</div>
<div class="location-section">
<div class="card">
<h2 class="section-title"><i class="fas fa-location-dot"></i> 位置信息</h2>
<div id="locationMessage" class="message"></div>
<div class="location-info">
<div class="location-item">
<span class="location-label">纬度:</span>
<span class="location-value" id="latitude">--</span>
</div>
<div class="location-item">
<span class="location-label">经度:</span>
<span class="location-value" id="longitude">--</span>
</div>
<div class="location-item">
<span class="location-label">精度:</span>
<span class="location-value" id="accuracy">-- 米</span>
</div>
<div class="location-item">
<span class="location-label">获取时间:</span>
<span class="location-value" id="timestamp">--</span>
</div>
<div class="location-item">
<span class="location-label">地址:</span>
<span class="location-value" id="address">--</span>
</div>
</div>
<button class="btn btn-primary" id="getLocationBtn" disabled>
<span id="getLocationText"><i class="fas fa-sync-alt"></i> 刷新位置</span>
</button>
<div class="status-indicator">
<div class="status-dot" id="locationStatus"></div>
<div class="status-text" id="locationStatusText">等待权限授予</div>
</div>
</div>
</div>
</div>
</div>
<script>
// 全局变量
let currentPosition = null;
let permissionState = 'prompt'; // 'prompt', 'granted', 'denied'
let permissionRequested = false;
// 页面加载完成后初始化
document.addEventListener('DOMContentLoaded', function() {
// 绑定按钮事件
document.getElementById('requestPermissionBtn').addEventListener('click', requestLocationPermission);
document.getElementById('skipPermissionBtn').addEventListener('click', skipPermission);
document.getElementById('retryPermissionBtn').addEventListener('click', retryPermission);
document.getElementById('continueWithoutLocationBtn').addEventListener('click', continueWithoutLocation);
document.getElementById('getLocationBtn').addEventListener('click', refreshLocation);
// 检查初始权限状态
checkInitialPermission();
});
// 检查初始权限状态
function checkInitialPermission() {
if ('permissions' in navigator) {
navigator.permissions.query({name: 'geolocation'})
.then(function(permissionStatus) {
permissionState = permissionStatus.state;
updatePermissionStatus();
// 如果权限已授予,自动获取位置
if (permissionState === 'granted') {
getLocation();
}
})
.catch(function(error) {
console.log('权限API不可用:', error);
// 降级到直接请求位置
getLocation();
});
} else {
// 降级到直接请求位置
getLocation();
}
}
// 请求位置权限
function requestLocationPermission() {
const btn = document.getElementById('requestPermissionBtn');
const btnText = document.getElementById('requestPermissionText');
// 显示加载状态
btn.disabled = true;
btnText.innerHTML = '<span class="loading"></span> 请求权限中...';
// 更新状态
updatePermissionStatus('请求中...', 'warning');
permissionRequested = true;
// 请求位置权限
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(
function(position) {
// 权限授予成功
permissionState = 'granted';
updatePermissionStatus('权限已授予', 'active');
showMessage('permissionMessage', '位置权限授予成功!', 'success');
// 获取位置信息
currentPosition = {
latitude: position.coords.latitude,
longitude: position.coords.longitude,
accuracy: position.coords.accuracy,
timestamp: new Date(position.timestamp)
};
updateLocationDisplay();
enableLocationButton();
// 获取地址信息
getAddressFromCoordinates(currentPosition.latitude, currentPosition.longitude);
// 恢复按钮状态
btn.disabled = false;
btnText.innerHTML = '<i class="fas fa-location-arrow"></i> 授予位置权限';
},
function(error) {
// 权限被拒绝或获取位置失败
permissionState = 'denied';
updatePermissionStatus('权限被拒绝', 'error');
let errorMessage = '';
switch(error.code) {
case error.PERMISSION_DENIED:
errorMessage = '用户拒绝了位置权限请求';
showDeniedPrompt();
break;
case error.POSITION_UNAVAILABLE:
errorMessage = '位置信息不可用';
break;
case error.TIMEOUT:
errorMessage = '获取位置超时';
break;
default:
errorMessage = '获取位置时发生未知错误';
}
showMessage('permissionMessage', errorMessage, 'error');
// 恢复按钮状态
btn.disabled = false;
btnText.innerHTML = '<i class="fas fa-location-arrow"></i> 授予位置权限';
},
{
enableHighAccuracy: true,
timeout: 15000,
maximumAge: 0
}
);
} else {
showMessage('permissionMessage', '您的浏览器不支持地理定位功能', 'error');
btn.disabled = false;
btnText.innerHTML = '<i class="fas fa-location-arrow"></i> 授予位置权限';
updatePermissionStatus('浏览器不支持', 'error');
}
}
// 跳过权限请求
function skipPermission() {
permissionState = 'prompt';
updatePermissionStatus('用户跳过', 'warning');
showMessage('permissionMessage', '您已跳过位置权限请求,可以稍后重新请求', 'info');
showDeniedPrompt();
}
// 重新请求权限
function retryPermission() {
// 重置状态
permissionState = 'prompt';
updatePermissionStatus('重新请求中...', 'warning');
// 隐藏拒绝提示,显示权限请求提示
document.getElementById('deniedPrompt').style.display = 'none';
document.getElementById('permissionPrompt').style.display = 'block';
// 自动触发权限请求
setTimeout(requestLocationPermission, 500);
}
// 继续使用(无位置功能)
function continueWithoutLocation() {
permissionState = 'denied';
updatePermissionStatus('无位置功能', 'warning');
showMessage('permissionMessage', '您已选择继续使用(无位置功能)', 'info');
// 隐藏拒绝提示
document.getElementById('deniedPrompt').style.display = 'none';
}
// 刷新位置
function refreshLocation() {
if (permissionState === 'granted') {
getLocation();
} else {
showMessage('locationMessage', '请先授予位置权限', 'warning');
}
}
// 获取位置信息
function getLocation() {
if (!navigator.geolocation) {
showMessage('locationMessage', '您的浏览器不支持地理定位功能', 'error');
updateLocationStatus('浏览器不支持', 'error');
return;
}
const btn = document.getElementById('getLocationBtn');
const btnText = document.getElementById('getLocationText');
// 显示加载状态
btn.disabled = true;
btnText.innerHTML = '<span class="loading"></span> 获取位置中...';
// 更新状态
updateLocationStatus('获取中...', 'warning');
navigator.geolocation.getCurrentPosition(
function(position) {
// 成功获取位置
currentPosition = {
latitude: position.coords.latitude,
longitude: position.coords.longitude,
accuracy: position.coords.accuracy,
timestamp: new Date(position.timestamp)
};
updateLocationDisplay();
updateLocationStatus('位置已获取', 'active');
showMessage('locationMessage', '位置获取成功!', 'success');
// 获取地址信息
getAddressFromCoordinates(currentPosition.latitude, currentPosition.longitude);
// 恢复按钮状态
btn.disabled = false;
btnText.innerHTML = '<i class="fas fa-sync-alt"></i> 刷新位置';
},
function(error) {
// 获取位置失败
updateLocationStatus('获取失败', 'error');
let errorMessage = '';
switch(error.code) {
case error.PERMISSION_DENIED:
errorMessage = '位置权限被拒绝';
permissionState = 'denied';
updatePermissionStatus('权限被拒绝', 'error');
showDeniedPrompt();
break;
case error.POSITION_UNAVAILABLE:
errorMessage = '位置信息不可用';
break;
case error.TIMEOUT:
errorMessage = '获取位置超时';
break;
default:
errorMessage = '获取位置时发生未知错误';
}
showMessage('locationMessage', errorMessage, 'error');
// 恢复按钮状态
btn.disabled = false;
btnText.innerHTML = '<i class="fas fa-sync-alt"></i> 刷新位置';
},
{
enableHighAccuracy: true,
timeout: 15000,
maximumAge: 0
}
);
}
// 通过坐标获取地址信息
function getAddressFromCoordinates(lat, lon) {
// 使用免费的Nominatim服务获取地址(无需API密钥)
const apiUrl = `https://nominatim.openstreetmap.org/reverse?format=json&lat=${lat}&lon=${lon}&accept-language=zh-CN&addressdetails=1`;
fetch(apiUrl)
.then(response => {
if (!response.ok) {
throw new Error('地址获取失败');
}
return response.json();
})
.then(data => {
if (data && data.display_name) {
document.getElementById('address').textContent = data.display_name;
} else {
document.getElementById('address').textContent = '地址信息不可用';
}
})
.catch(error => {
console.error('获取地址信息失败:', error);
document.getElementById('address').textContent = '地址获取失败';
});
}
// 更新位置显示
function updateLocationDisplay() {
if (currentPosition) {
document.getElementById('latitude').textContent = currentPosition.latitude.toFixed(6);
document.getElementById('longitude').textContent = currentPosition.longitude.toFixed(6);
document.getElementById('accuracy').textContent = currentPosition.accuracy.toFixed(0) + ' 米';
document.getElementById('timestamp').textContent = currentPosition.timestamp.toLocaleString();
}
}
// 启用位置按钮
function enableLocationButton() {
document.getElementById('getLocationBtn').disabled = false;
}
// 显示权限被拒绝提示
function showDeniedPrompt() {
document.getElementById('permissionPrompt').style.display = 'none';
document.getElementById('deniedPrompt').style.display = 'block';
}
// 更新权限状态指示器
function updatePermissionStatus(text, status) {
const statusDot = document.getElementById('permissionStatus');
const statusText = document.getElementById('permissionStatusText');
if (text) statusText.textContent = text;
// 移除所有状态类
statusDot.className = 'status-dot';
// 添加相应状态类
if (status === 'active') {
statusDot.classList.add('active');
} else if (status === 'error') {
statusDot.style.background = 'var(--danger)';
} else if (status === 'warning') {
statusDot.classList.add('warning');
}
}
// 更新位置状态指示器
function updateLocationStatus(text, status) {
const statusDot = document.getElementById('locationStatus');
const statusText = document.getElementById('locationStatusText');
if (text) statusText.textContent = text;
// 移除所有状态类
statusDot.className = 'status-dot';
// 添加相应状态类
if (status === 'active') {
statusDot.classList.add('active');
} else if (status === 'error') {
statusDot.style.background = 'var(--danger)';
} else if (status === 'warning') {
statusDot.classList.add('warning');
}
}
// 显示消息
function showMessage(elementId, message, type) {
const messageElement = document.getElementById(elementId);
if (messageElement) {
messageElement.textContent = message;
messageElement.className = 'message ' + type;
messageElement.style.display = 'block';
// 3秒后自动隐藏消息
setTimeout(() => {
messageElement.style.display = 'none';
}, 3000);
}
}
</script>
</body>
</html>
index.html
index.html