实现地图定位

This commit is contained in:
WindowBird 2025-10-29 14:44:18 +08:00
parent f8ef69117a
commit 860cf08dbd
7 changed files with 1047 additions and 250 deletions

View File

@ -1,256 +1,10 @@
<template>
<UApp>
<NuxtLayout>
<UPage>
<!-- 主要内容区域 -->
<main role="main">
<img src="/img/banner.png" alt="" class="w-full">
<UPageSection :ui="{container:'lg:py-0 lg:px-0' }">
<!-- 服务展示区域 -->
<section aria-label="服务展示" class="mb-16">
<div class="grid grid-cols-1">
<div class=" cursor-pointer">
<img src="/img/1.jpg" alt="服务项目展示图片1" class="w-full rounded-lg transition-shadow duration-300">
</div>
<div class=" cursor-pointer">
<img src="/img/2.jpg" alt="服务项目展示图片2" class="w-full rounded-lg transition-shadow duration-300">
</div>
<div class=" cursor-pointer">
<img src="/img/3.jpg" alt="服务项目展示图片3" class="w-full rounded-lg transition-shadow duration-300">
</div>
<div class=" cursor-pointer">
<img src="/img/4.jpg" alt="服务项目展示图片4" class="w-full rounded-lg transition-shadow duration-300">
</div>
<div class=" cursor-pointer">
<img src="/img/5.jpg" alt="服务项目展示图片5" class="w-full rounded-lg transition-shadow duration-300">
</div>
<UCarousel v-slot="{ item }" arrows :prev="{ color: 'primary' }"
:next="{ color: 'primary' }" loop :items="items" class="w-full max-w-xl mx-auto">
<img :src="item" width="620" height="320" class="rounded-lg" alt="">
</UCarousel>
<div class="cursor-pointer">
<img src="/img/6.png" alt="服务项目展示图片6" class="w-full rounded-lg transition-shadow duration-300">
</div>
<div class=" cursor-pointer">
<img src="/img/7.jpg" alt="服务项目展示图片7" class="w-full rounded-lg transition-shadow duration-300">
</div>
<div class=" cursor-pointer">
<img src="/img/8.jpg" alt="服务项目展示图片8" class="w-full rounded-lg transition-shadow duration-300">
</div>
<div class=" cursor-pointer">
<img src="/img/9.jpg" alt="服务项目展示图片9" class="w-full rounded-lg transition-shadow duration-300">
</div>
</div>
</section>
<section aria-label="服务保障" class="mb-16">
<div class="text-center mb-12">
<h2 class="text-3xl font-bold text-gray-800 mb-4">服务保障</h2>
<p class="text-gray-600 text-lg">我们承诺为您提供最优质的服务保障</p>
</div>
<div class="grid md:grid-cols-3 gap-8">
<!-- 协助维权 -->
<div class="bg-white rounded-xl shadow-lg p-8 text-center hover:shadow-xl transition-shadow duration-300">
<div class="w-16 h-16 bg-green-100 rounded-full flex items-center justify-center mx-auto mb-6">
<UIcon name="i-lucide-shield-check" class="w-8 h-8 text-green-600" />
</div>
<h3 class="text-xl font-semibold text-gray-800 mb-4">协助维权</h3>
<p class="text-gray-600 leading-relaxed">蒙受经济损失可申请协助维权服务我们为您提供专业的法律支持</p>
</div>
<!-- 虚假赔偿 -->
<div class="bg-white rounded-xl shadow-lg p-8 text-center hover:shadow-xl transition-shadow duration-300">
<div class="w-16 h-16 bg-blue-100 rounded-full flex items-center justify-center mx-auto mb-6">
<UIcon name="i-lucide-badge-check" class="w-8 h-8 text-blue-600" />
</div>
<h3 class="text-xl font-semibold text-gray-800 mb-4">虚假赔偿</h3>
<p class="text-gray-600 leading-relaxed">遇到品牌或资质冒用可申请保障服务维护您的合法权益</p>
</div>
<!-- 欺诈赔偿 -->
<div class="bg-white rounded-xl shadow-lg p-8 text-center hover:shadow-xl transition-shadow duration-300">
<div class="w-16 h-16 bg-red-100 rounded-full flex items-center justify-center mx-auto mb-6">
<UIcon name="i-lucide-shield-alert" class="w-8 h-8 text-red-600" />
</div>
<h3 class="text-xl font-semibold text-gray-800 mb-4">欺诈赔偿</h3>
<p class="text-gray-600 leading-relaxed">遇到欺诈行为经核查属实可申请保障退还费用</p>
</div>
</div>
</section>
<!-- 联系信息区域 -->
<section aria-label="联系信息" class="mb-16">
<div class="text-center mb-12">
<h2 class="text-3xl font-bold text-gray-800 mb-4">联系我们</h2>
<p class="text-gray-600 text-lg">多种方式联系我们我们随时为您服务</p>
</div>
<div class="grid lg:grid-cols-12 gap-8 items-start">
<!-- 联系方式卡片 -->
<div class="col-span-12 lg:col-span-5">
<div class="bg-white rounded-xl shadow-lg p-8">
<h3 class="text-2xl font-semibold text-gray-800 mb-6 text-center">联系方式</h3>
<UPageList divide>
<UPageCard
v-for="(user, index) in users"
:key="index"
:target="user.target"
variant="ghost"
class="hover:bg-gray-50 transition-colors duration-200"
>
<template #body>
<UUser :avatar="user.avatar" :description="user.description" :name="user.name" size="xl"/>
</template>
</UPageCard>
</UPageList>
</div>
</div>
<!-- 地图区域 -->
<div class="col-span-12 lg:col-span-7">
<div class="bg-white rounded-xl shadow-lg p-6">
<h3 class="text-2xl font-semibold text-gray-800 mb-6 text-center">公司位置</h3>
<img
alt="公司位置地图 - 福建省福鼎市太姥山镇海埕路13号"
class="w-full rounded-lg shadow-md"
src="/img/map.png"
>
<div class="mt-4 text-center">
<p class="text-gray-600">
<UIcon name="i-lucide-map-pin" class="w-5 h-5 inline mr-2 text-blue-500" />
福建省福鼎市太姥山镇海埕路13号
</p>
</div>
</div>
</div>
</div>
</section>
</UPageSection>
</main>
<!-- 悬浮置顶按钮组件 -->
<ScrollToTop />
</UPage>
<NuxtPage/>
</NuxtLayout>
<ScrollToTop/>
</UApp>
</template>
<script setup lang="ts">
// SEO
useHead({
title: '小鹿骑行',
meta: [
{
name: 'description',
content: '创特物联科技提供OEM电动车共享系统解决方案含客户端/商户端小程序、智能中控硬件及管理系统。厂家直销支持定制品质可靠。联系电话15280659990'
},
{
name: 'keywords',
content: '共享电动车系统,电动车小程序开发,智能中控模块,OEM电动车方案,共享电单车管理系统,电动车物联网,创特物联科技,福建电动车解决方案,电动车定制开发,共享电动车中控,电动车智能硬件'
},
{
name: 'author',
content: '创特物联科技(福鼎)有限公司'
},
{
name: 'robots',
content: 'index, follow, max-snippet:150'
},
{
name: 'copyright',
content: 'Chuangte IoT Technology'
},
{ property: 'og:title', content: '联系我们 - 专业服务公司' },
{ property: 'og:description', content: '联系我们获取专业服务。地址福建省福鼎市太姥山镇海埕路13号电话15280659990' },
{ property: 'og:type', content: 'website' },
{ property: 'og:url', content: 'https://yourwebsite.com/contact' },
{ property: 'og:image', content: '/img/banner.png' },
{ property: 'og:site_name', content: '专业服务公司' },
{ name: 'twitter:card', content: 'summary_large_image' },
{ name: 'twitter:title', content: '联系我们 - 专业服务公司' },
{ name: 'twitter:description', content: '联系我们获取专业服务。地址福建省福鼎市太姥山镇海埕路13号电话15280659990' },
{ name: 'twitter:image', content: '/img/banner.png' }
],
link: [
{ rel: 'canonical', href: 'https://yourwebsite.com/contact' }
],
script: [
{
type: 'application/ld+json',
innerHTML: JSON.stringify({
"@context": "https://schema.org",
"@type": "Organization",
"name": "专业服务公司",
"description": "提供专业服务的公司",
"url": "https://yourwebsite.com",
"contactPoint": {
"@type": "ContactPoint",
"telephone": "+86-15280659990",
"contactType": "customer service",
"email": "564737095@qq.com",
"availableLanguage": "Chinese"
},
"address": {
"@type": "PostalAddress",
"streetAddress": "海埕路13号",
"addressLocality": "太姥山镇",
"addressRegion": "福鼎市",
"addressCountry": "CN",
"postalCode": "355200"
},
"openingHours": "Mo-Sa 08:30-12:00,13:30-18:00",
"sameAs": [
"https://yourwebsite.com"
]
})
}
]
})
const users = ref([
{
name: '电话',
description: '15280659990',
target: '_blank',
avatar: {
icon: 'i-lucide-phone'
}
},
{
name: '邮箱',
description: '564737095@qq.com',
target: '_blank',
avatar: {
icon: 'i-lucide-mail'
}
},
{
name: '地址',
description: '福建省福鼎市太姥山镇海埕路13号',
target: '_blank',
avatar: {
icon: 'i-lucide-map-pin',
class: 'text-blue-500'
}
},
{
name: '工作时间',
description: '周一至周六 (8:30-12:00 13:30-18:00)',
target: '_blank',
avatar: {
icon: 'i-lucide-clock'
}
}
])
const items = [
'https://picsum.photos/640/640?random=1',
'https://picsum.photos/640/640?random=2',
'https://picsum.photos/640/640?random=3',
]
</script>
<script lang="ts" setup>
</script>

View File

@ -0,0 +1,330 @@
<template>
<div class="map-container">
<div
ref="mapContainer"
id="amap-container"
class="map-wrapper"
></div>
<!-- 加载状态 -->
<div v-if="isLoading" class="loading-overlay">
<div class="loading-spinner"></div>
<p>地图加载中...</p>
</div>
<!-- 错误状态 -->
<div v-if="error" class="error-overlay">
<div class="error-content">
<h3>地图加载失败</h3>
<p>{{ error }}</p>
<button @click="retryLoad" class="retry-btn">重试</button>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
import { amapConfig, getAMapScriptUrl, validateKeys } from '~/config/amap'
//
const props = defineProps({
// [, ]
center: {
type: Array,
default: () => [117.397428, 31.90923] //
},
//
zoom: {
type: Number,
default: 11
},
// 2D 3D
viewMode: {
type: String,
default: '2D',
validator: (value) => ['2D', '3D'].includes(value)
},
//
height: {
type: String,
default: '400px'
}
})
//
const mapContainer = ref(null)
const map = ref(null)
const isLoading = ref(true)
const error = ref(null)
//
const loadAMapScript = () => {
return new Promise((resolve, reject) => {
if (window.AMap) {
resolve()
return
}
const script = document.createElement('script')
script.src = getAMapScriptUrl()
script.async = true
script.onload = () => {
console.log('高德地图API加载成功')
resolve()
}
script.onerror = () => {
console.error('高德地图API加载失败')
reject(new Error('高德地图API加载失败'))
}
document.head.appendChild(script)
})
}
//
const initMap = async () => {
if (!mapContainer.value) return
try {
isLoading.value = true
error.value = null
//
const keyValidation = validateKeys()
if (!keyValidation.isValid) {
error.value = '请配置高德地图API密钥和安全密钥'
isLoading.value = false
return
}
// API
if (!window.AMap) {
await loadAMapScript()
}
//
window.AMap.plugin('AMap.Security', () => {
window.AMap.Security.setSecurityKey(amapConfig.securityKey)
})
//
map.value = new window.AMap.Map('amap-container', {
viewMode: props.viewMode, // 2D 3D
zoom: props.zoom, //
center: props.center, //
mapStyle: 'amap://styles/normal', //
features: ['bg', 'road', 'building', 'point'], //
showLabel: true, //
resizeEnable: true, //
rotateEnable: true, //
pitchEnable: true, //
zoomEnable: true, //
dragEnable: true, //
keyboardEnable: true, //
doubleClickZoom: true, //
scrollWheel: true, //
touchZoom: true, //
mapStyle: 'amap://styles/normal' //
})
//
map.value.on('complete', () => {
console.log('地图加载完成')
isLoading.value = false
//
emit('mapReady', map.value)
})
//
map.value.on('click', (e) => {
emit('mapClick', {
lng: e.lnglat.getLng(),
lat: e.lnglat.getLat(),
pixel: e.pixel
})
})
//
map.value.on('zoomchange', () => {
emit('zoomChange', map.value.getZoom())
})
//
map.value.on('moveend', () => {
const center = map.value.getCenter()
emit('centerChange', {
lng: center.getLng(),
lat: center.getLat()
})
})
} catch (err) {
console.error('地图初始化失败:', err)
error.value = err.message || '地图初始化失败'
isLoading.value = false
}
}
//
const retryLoad = () => {
initMap()
}
//
const emit = defineEmits(['mapReady', 'mapClick', 'zoomChange', 'centerChange'])
//
defineExpose({
map: map,
getMap: () => map.value,
setCenter: (center) => {
if (map.value) {
map.value.setCenter(center)
}
},
setZoom: (zoom) => {
if (map.value) {
map.value.setZoom(zoom)
}
},
addMarker: (position, options = {}) => {
if (map.value) {
return new window.AMap.Marker({
position: position,
map: map.value,
...options
})
}
}
})
//
onMounted(() => {
initMap()
})
onUnmounted(() => {
if (map.value) {
map.value.destroy()
}
})
</script>
<style scoped>
.map-container {
position: relative;
width: 100%;
height: v-bind(height);
border-radius: 8px;
overflow: hidden;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.map-wrapper {
width: 100%;
height: 100%;
background-color: #f5f5f5;
}
/* 加载状态样式 */
.loading-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(255, 255, 255, 0.9);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 1000;
}
.loading-spinner {
width: 40px;
height: 40px;
border: 4px solid #f3f3f3;
border-top: 4px solid #007bff;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: 16px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.loading-overlay p {
color: #666;
font-size: 14px;
margin: 0;
}
/* 错误状态样式 */
.error-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(255, 255, 255, 0.95);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.error-content {
text-align: center;
padding: 20px;
background: white;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
max-width: 300px;
}
.error-content h3 {
color: #dc3545;
margin: 0 0 12px 0;
font-size: 16px;
}
.error-content p {
color: #666;
margin: 0 0 16px 0;
font-size: 14px;
line-height: 1.4;
}
.retry-btn {
background-color: #007bff;
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
transition: background-color 0.2s;
}
.retry-btn:hover {
background-color: #0056b3;
}
/* 响应式设计 */
@media (max-width: 768px) {
.map-container {
height: 300px;
}
.error-content {
margin: 0 16px;
}
}
</style>

78
app/config/amap.ts Normal file
View File

@ -0,0 +1,78 @@
// 高德地图配置
export const amapConfig = {
// 高德地图Web服务API Key
// 在高德开放平台申请https://console.amap.com/dev/key/app
key: '11da89fddf9340d0a69d4fff53c0ec4b',
// 高德地图安全密钥Security Key
// 在高德开放平台的安全设置中获取用于保护API Key
securityKey: '32dca5ef246f3b96234cd8ef891e4d59',
// 地图默认配置
defaultCenter: [39.9042, 116.4074], // 北京坐标
defaultZoom: 13,
// 地图样式
styles: {
normal: 'amap://styles/normal',
dark: 'amap://styles/dark',
light: 'amap://styles/light',
fresh: 'amap://styles/fresh',
grey: 'amap://styles/grey',
graffiti: 'amap://styles/graffiti',
macaron: 'amap://styles/macaron',
blue: 'amap://styles/blue',
darkblue: 'amap://styles/darkblue',
wine: 'amap://styles/wine'
},
// 服务网点坐标(示例数据)
servicePoints: [
{
id: 1,
name: '北京总部',
address: '北京市朝阳区xxx街道xxx号',
position: [116.4074, 39.9042],
phone: '010-12345678',
hours: '9:00-18:00'
},
{
id: 2,
name: '上海分公司',
address: '上海市浦东新区xxx路xxx号',
position: [121.4737, 31.2304],
phone: '021-87654321',
hours: '9:00-18:00'
},
{
id: 3,
name: '广州分公司',
address: '广州市天河区xxx大道xxx号',
position: [113.2644, 23.1291],
phone: '020-11223344',
hours: '9:00-18:00'
}
]
}
// 获取地图API URL
export const getAMapScriptUrl = () => {
return `https://webapi.amap.com/maps?v=2.0&key=${amapConfig.key}`
}
// 获取安全密钥(用于服务端签名)
export const getSecurityKey = () => {
return amapConfig.securityKey
}
// 验证密钥配置
export const validateKeys = () => {
const hasKey = amapConfig.key && amapConfig.key !== 'YOUR_AMAP_KEY'
const hasSecurityKey = amapConfig.securityKey && amapConfig.securityKey !== 'YOUR_SECURITY_KEY'
return {
hasKey,
hasSecurityKey,
isValid: hasKey && hasSecurityKey
}
}

254
app/pages/index/index.vue Normal file
View File

@ -0,0 +1,254 @@
<template>
<UPage>
<!-- 主要内容区域 -->
<main role="main">
<img src="/img/banner.png" alt="" class="w-full">
<UPageSection :ui="{container:'lg:py-0 lg:px-0' }">
<!-- 服务展示区域 -->
<section aria-label="服务展示" class="mb-16">
<div class="grid grid-cols-1">
<div class=" cursor-pointer">
<img src="/img/1.jpg" alt="服务项目展示图片1" class="w-full rounded-lg transition-shadow duration-300">
</div>
<div class=" cursor-pointer">
<img src="/img/2.jpg" alt="服务项目展示图片2" class="w-full rounded-lg transition-shadow duration-300">
</div>
<div class=" cursor-pointer">
<img src="/img/3.jpg" alt="服务项目展示图片3" class="w-full rounded-lg transition-shadow duration-300">
</div>
<div class=" cursor-pointer">
<img src="/img/4.jpg" alt="服务项目展示图片4" class="w-full rounded-lg transition-shadow duration-300">
</div>
<div class=" cursor-pointer">
<img src="/img/5.jpg" alt="服务项目展示图片5" class="w-full rounded-lg transition-shadow duration-300">
</div>
<UCarousel v-slot="{ item }" arrows :prev="{ color: 'primary' }"
:next="{ color: 'primary' }" loop :items="items" class="w-full max-w-xl mx-auto">
<img :src="item" width="620" height="320" class="rounded-lg" alt="">
</UCarousel>
<div class="cursor-pointer">
<img src="/img/6.png" alt="服务项目展示图片6" class="w-full rounded-lg transition-shadow duration-300">
</div>
<div class=" cursor-pointer">
<img src="/img/7.jpg" alt="服务项目展示图片7" class="w-full rounded-lg transition-shadow duration-300">
</div>
<div class=" cursor-pointer">
<img src="/img/8.jpg" alt="服务项目展示图片8" class="w-full rounded-lg transition-shadow duration-300">
</div>
<div class=" cursor-pointer">
<img src="/img/9.jpg" alt="服务项目展示图片9" class="w-full rounded-lg transition-shadow duration-300">
</div>
</div>
</section>
<section aria-label="服务保障" class="mb-16">
<div class="text-center mb-12">
<h2 class="text-3xl font-bold text-gray-800 mb-4">服务保障</h2>
<p class="text-gray-600 text-lg">我们承诺为您提供最优质的服务保障</p>
</div>
<div class="grid md:grid-cols-3 gap-8">
<!-- 协助维权 -->
<div class="bg-white rounded-xl shadow-lg p-8 text-center hover:shadow-xl transition-shadow duration-300">
<div class="w-16 h-16 bg-green-100 rounded-full flex items-center justify-center mx-auto mb-6">
<UIcon name="i-lucide-shield-check" class="w-8 h-8 text-green-600" />
</div>
<h3 class="text-xl font-semibold text-gray-800 mb-4">协助维权</h3>
<p class="text-gray-600 leading-relaxed">蒙受经济损失可申请协助维权服务我们为您提供专业的法律支持</p>
</div>
<!-- 虚假赔偿 -->
<div class="bg-white rounded-xl shadow-lg p-8 text-center hover:shadow-xl transition-shadow duration-300">
<div class="w-16 h-16 bg-blue-100 rounded-full flex items-center justify-center mx-auto mb-6">
<UIcon name="i-lucide-badge-check" class="w-8 h-8 text-blue-600" />
</div>
<h3 class="text-xl font-semibold text-gray-800 mb-4">虚假赔偿</h3>
<p class="text-gray-600 leading-relaxed">遇到品牌或资质冒用可申请保障服务维护您的合法权益</p>
</div>
<!-- 欺诈赔偿 -->
<div class="bg-white rounded-xl shadow-lg p-8 text-center hover:shadow-xl transition-shadow duration-300">
<div class="w-16 h-16 bg-red-100 rounded-full flex items-center justify-center mx-auto mb-6">
<UIcon name="i-lucide-shield-alert" class="w-8 h-8 text-red-600" />
</div>
<h3 class="text-xl font-semibold text-gray-800 mb-4">欺诈赔偿</h3>
<p class="text-gray-600 leading-relaxed">遇到欺诈行为经核查属实可申请保障退还费用</p>
</div>
</div>
</section>
<!-- 联系信息区域 -->
<section aria-label="联系信息" class="mb-16">
<div class="text-center mb-12">
<h2 class="text-3xl font-bold text-gray-800 mb-4">联系我们</h2>
<p class="text-gray-600 text-lg">多种方式联系我们我们随时为您服务</p>
</div>
<div class="grid lg:grid-cols-12 gap-8 items-start">
<!-- 联系方式卡片 -->
<div class="col-span-12 lg:col-span-5">
<div class="bg-white rounded-xl shadow-lg p-8">
<h3 class="text-2xl font-semibold text-gray-800 mb-6 text-center">联系方式</h3>
<UPageList divide>
<UPageCard
v-for="(user, index) in users"
:key="index"
:target="user.target"
variant="ghost"
class="hover:bg-gray-50 transition-colors duration-200"
>
<template #body>
<UUser :avatar="user.avatar" :description="user.description" :name="user.name" size="xl"/>
</template>
</UPageCard>
</UPageList>
</div>
</div>
<!-- 地图区域 -->
<div class="col-span-12 lg:col-span-7">
<div class="bg-white rounded-xl shadow-lg p-6">
<h3 class="text-2xl font-semibold text-gray-800 mb-6 text-center">公司位置</h3>
<img
alt="公司位置地图 - 福建省福鼎市太姥山镇海埕路13号"
class="w-full rounded-lg shadow-md"
src="/img/map.png"
>
<div class="mt-4 text-center">
<p class="text-gray-600">
<UIcon name="i-lucide-map-pin" class="w-5 h-5 inline mr-2 text-blue-500" />
福建省福鼎市太姥山镇海埕路13号
</p>
</div>
</div>
</div>
</div>
</section>
</UPageSection>
</main>
<!-- 悬浮置顶按钮组件 -->
<ScrollToTop />
</UPage>
</template>
<script setup lang="ts">
// SEO
useHead({
title: '小鹿骑行',
meta: [
{
name: 'description',
content: '创特物联科技提供OEM电动车共享系统解决方案含客户端/商户端小程序、智能中控硬件及管理系统。厂家直销支持定制品质可靠。联系电话15280659990'
},
{
name: 'keywords',
content: '共享电动车系统,电动车小程序开发,智能中控模块,OEM电动车方案,共享电单车管理系统,电动车物联网,创特物联科技,福建电动车解决方案,电动车定制开发,共享电动车中控,电动车智能硬件'
},
{
name: 'author',
content: '创特物联科技(福鼎)有限公司'
},
{
name: 'robots',
content: 'index, follow, max-snippet:150'
},
{
name: 'copyright',
content: 'Chuangte IoT Technology'
},
{ property: 'og:title', content: '联系我们 - 专业服务公司' },
{ property: 'og:description', content: '联系我们获取专业服务。地址福建省福鼎市太姥山镇海埕路13号电话15280659990' },
{ property: 'og:type', content: 'website' },
{ property: 'og:url', content: 'https://yourwebsite.com/contact' },
{ property: 'og:image', content: '/img/banner.png' },
{ property: 'og:site_name', content: '专业服务公司' },
{ name: 'twitter:card', content: 'summary_large_image' },
{ name: 'twitter:title', content: '联系我们 - 专业服务公司' },
{ name: 'twitter:description', content: '联系我们获取专业服务。地址福建省福鼎市太姥山镇海埕路13号电话15280659990' },
{ name: 'twitter:image', content: '/img/banner.png' }
],
link: [
{ rel: 'canonical', href: 'https://yourwebsite.com/contact' }
],
script: [
{
type: 'application/ld+json',
innerHTML: JSON.stringify({
"@context": "https://schema.org",
"@type": "Organization",
"name": "专业服务公司",
"description": "提供专业服务的公司",
"url": "https://yourwebsite.com",
"contactPoint": {
"@type": "ContactPoint",
"telephone": "+86-15280659990",
"contactType": "customer service",
"email": "564737095@qq.com",
"availableLanguage": "Chinese"
},
"address": {
"@type": "PostalAddress",
"streetAddress": "海埕路13号",
"addressLocality": "太姥山镇",
"addressRegion": "福鼎市",
"addressCountry": "CN",
"postalCode": "355200"
},
"openingHours": "Mo-Sa 08:30-12:00,13:30-18:00",
"sameAs": [
"https://yourwebsite.com"
]
})
}
]
})
const users = ref([
{
name: '电话',
description: '15280659990',
target: '_blank',
avatar: {
icon: 'i-lucide-phone'
}
},
{
name: '邮箱',
description: '564737095@qq.com',
target: '_blank',
avatar: {
icon: 'i-lucide-mail'
}
},
{
name: '地址',
description: '福建省福鼎市太姥山镇海埕路13号',
target: '_blank',
avatar: {
icon: 'i-lucide-map-pin',
class: 'text-blue-500'
}
},
{
name: '工作时间',
description: '周一至周六 (8:30-12:00 13:30-18:00)',
target: '_blank',
avatar: {
icon: 'i-lucide-clock'
}
}
])
const items = [
'https://picsum.photos/640/640?random=1',
'https://picsum.photos/640/640?random=2',
'https://picsum.photos/640/640?random=3',
]
</script>

372
app/pages/map-demo.vue Normal file
View File

@ -0,0 +1,372 @@
<template>
<div class="map-demo-page">
<div class="page-header">
<h1>高德地图组件演示</h1>
<p>基于您提供的HTML示例创建的Vue组件</p>
</div>
<div class="demo-section">
<h2>基础地图</h2>
<div class="map-controls">
<div class="control-group">
<label>中心点坐标</label>
<input
v-model="centerLng"
type="number"
step="0.000001"
placeholder="经度"
class="coord-input"
/>
<input
v-model="centerLat"
type="number"
step="0.000001"
placeholder="纬度"
class="coord-input"
/>
<button @click="updateCenter" class="update-btn">更新中心点</button>
</div>
<div class="control-group">
<label>缩放级别</label>
<input
v-model.number="zoomLevel"
type="range"
min="3"
max="18"
class="zoom-slider"
/>
<span class="zoom-value">{{ zoomLevel }}</span>
</div>
<div class="control-group">
<label>视图模式</label>
<select v-model="viewMode" class="view-select">
<option value="2D">2D模式</option>
<option value="3D">3D模式</option>
</select>
</div>
</div>
<AMapComponent
ref="mapRef"
:center="mapCenter"
:zoom="zoomLevel"
:view-mode="viewMode"
height="500px"
@map-ready="onMapReady"
@map-click="onMapClick"
@zoom-change="onZoomChange"
@center-change="onCenterChange"
/>
</div>
<div class="demo-section">
<h2>地图事件信息</h2>
<div class="event-info">
<div class="info-item">
<strong>地图状态</strong>
<span :class="mapStatusClass">{{ mapStatus }}</span>
</div>
<div class="info-item" v-if="lastClick">
<strong>最后点击位置</strong>
<span>{{ lastClick.lng.toFixed(6) }}, {{ lastClick.lat.toFixed(6) }}</span>
</div>
<div class="info-item" v-if="currentCenter">
<strong>当前中心点</strong>
<span>{{ currentCenter.lng.toFixed(6) }}, {{ currentCenter.lat.toFixed(6) }}</span>
</div>
<div class="info-item">
<strong>当前缩放级别</strong>
<span>{{ currentZoom }}</span>
</div>
</div>
</div>
<div class="demo-section">
<h2>快速定位</h2>
<div class="quick-locations">
<button
v-for="location in quickLocations"
:key="location.name"
@click="goToLocation(location)"
class="location-btn"
>
{{ location.name }}
</button>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
import AMapComponent from '~/components/AMapComponent.vue'
//
const mapRef = ref(null)
const centerLng = ref(120.258419)
const centerLat = ref(27.11402)
const zoomLevel = ref(17)
const viewMode = ref('2D')
const mapStatus = ref('加载中...')
const lastClick = ref(null)
const currentCenter = ref(null)
const currentZoom = ref(11)
//
const mapCenter = computed(() => [centerLng.value, centerLat.value])
const mapStatusClass = computed(() => {
switch (mapStatus.value) {
case '地图加载完成':
return 'status-success'
case '加载中...':
return 'status-loading'
default:
return 'status-error'
}
})
//
const quickLocations = [
{ name: '北京', center: [116.397428, 39.90923], zoom: 10 },
{ name: '上海', center: [121.473701, 31.230416], zoom: 10 },
{ name: '广州', center: [113.264385, 23.129163], zoom: 10 },
{ name: '深圳', center: [114.085947, 22.547], zoom: 10 },
{ name: '武汉', center: [112.397428, 31.90923], zoom: 11 },
{ name: '成都', center: [104.066541, 30.572269], zoom: 10 }
]
//
const onMapReady = (mapInstance) => {
console.log('地图实例:', mapInstance)
mapStatus.value = '地图加载完成'
//
if (mapRef.value) {
mapRef.value.addMarker(mapCenter.value, {
title: '当前位置',
content: '<div style="background: #007bff; color: white; padding: 4px 8px; border-radius: 4px; font-size: 12px;">标记点</div>'
})
}
}
const onMapClick = (event) => {
lastClick.value = event
console.log('地图点击:', event)
}
const onZoomChange = (zoom) => {
currentZoom.value = zoom
console.log('缩放级别变化:', zoom)
}
const onCenterChange = (center) => {
currentCenter.value = center
centerLng.value = center.lng
centerLat.value = center.lat
console.log('中心点变化:', center)
}
//
const updateCenter = () => {
if (mapRef.value) {
mapRef.value.setCenter(mapCenter.value)
}
}
//
const goToLocation = (location) => {
centerLng.value = location.center[0]
centerLat.value = location.center[1]
zoomLevel.value = location.zoom
if (mapRef.value) {
mapRef.value.setCenter(location.center)
mapRef.value.setZoom(location.zoom)
}
}
</script>
<style scoped>
.map-demo-page {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
.page-header {
text-align: center;
margin-bottom: 40px;
}
.page-header h1 {
color: #333;
margin-bottom: 8px;
}
.page-header p {
color: #666;
font-size: 16px;
}
.demo-section {
margin-bottom: 40px;
background: white;
border-radius: 8px;
padding: 24px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.demo-section h2 {
color: #333;
margin-bottom: 20px;
font-size: 20px;
}
.map-controls {
display: flex;
flex-wrap: wrap;
gap: 20px;
margin-bottom: 20px;
padding: 16px;
background: #f8f9fa;
border-radius: 6px;
}
.control-group {
display: flex;
align-items: center;
gap: 8px;
}
.control-group label {
font-weight: 500;
color: #555;
white-space: nowrap;
}
.coord-input {
width: 120px;
padding: 6px 8px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
}
.update-btn {
background: #007bff;
color: white;
border: none;
padding: 6px 12px;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
transition: background-color 0.2s;
}
.update-btn:hover {
background: #0056b3;
}
.zoom-slider {
width: 120px;
}
.zoom-value {
font-weight: 500;
color: #007bff;
min-width: 20px;
}
.view-select {
padding: 6px 8px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
}
.event-info {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 16px;
}
.info-item {
padding: 12px;
background: #f8f9fa;
border-radius: 6px;
border-left: 4px solid #007bff;
}
.info-item strong {
color: #333;
margin-right: 8px;
}
.status-success {
color: #28a745;
font-weight: 500;
}
.status-loading {
color: #ffc107;
font-weight: 500;
}
.status-error {
color: #dc3545;
font-weight: 500;
}
.quick-locations {
display: flex;
flex-wrap: wrap;
gap: 12px;
}
.location-btn {
background: #6c757d;
color: white;
border: none;
padding: 8px 16px;
border-radius: 20px;
cursor: pointer;
font-size: 14px;
transition: all 0.2s;
}
.location-btn:hover {
background: #5a6268;
transform: translateY(-1px);
}
/* 响应式设计 */
@media (max-width: 768px) {
.map-demo-page {
padding: 16px;
}
.map-controls {
flex-direction: column;
align-items: stretch;
}
.control-group {
justify-content: space-between;
}
.coord-input {
width: 100px;
}
.event-info {
grid-template-columns: 1fr;
}
.quick-locations {
justify-content: center;
}
}
</style>

View File

@ -10,6 +10,7 @@
"postinstall": "nuxt prepare"
},
"dependencies": {
"@amap/amap-jsapi-loader": "^1.0.1",
"@nuxt/eslint": "1.9.0",
"@nuxt/image": "1.11.0",
"@nuxt/ui": "4.1.0",

View File

@ -8,6 +8,9 @@ importers:
.:
dependencies:
'@amap/amap-jsapi-loader':
specifier: ^1.0.1
version: 1.0.1
'@nuxt/eslint':
specifier: 1.9.0
version: 1.9.0(@typescript-eslint/utils@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(@vue/compiler-sfc@3.5.22)(eslint@9.38.0(jiti@2.6.1))(magicast@0.3.5)(typescript@5.9.3)(vite@7.1.12(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1))
@ -73,6 +76,9 @@ packages:
resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
engines: {node: '>=10'}
'@amap/amap-jsapi-loader@1.0.1':
resolution: {integrity: sha512-nPyLKt7Ow/ThHLkSvn2etQlUzqxmTVgK7bIgwdBRTg2HK5668oN7xVxkaiRe3YZEzGzfV2XgH5Jmu2T73ljejw==}
'@antfu/install-pkg@1.1.0':
resolution: {integrity: sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==}
@ -4764,6 +4770,8 @@ snapshots:
'@alloc/quick-lru@5.2.0': {}
'@amap/amap-jsapi-loader@1.0.1': {}
'@antfu/install-pkg@1.1.0':
dependencies:
package-manager-detector: 1.5.0