| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809 |
- <template>
- <view class="container">
- <view class="top"></view>
- <view class="head">
- <view class="handle">
- <view class="head_left">
- <span class="cuIcon-back" @click="back"></span>
- </view>
- <span class="center">选择地址</span>
- </view>
- </view>
- <!-- 地图容器 -->
- <view id="mapContainer" class="map-container"></view>
- <!-- 搜索功能区域 -->
- <view class="search-panel" v-if="showSearch">
- <view class="search-input-group">
- <input class="search-input" v-model="searchKeyword" placeholder="输入关键词搜索地点" @confirm="handleSearch"
- @input="onSearchInput" />
- <button class="search-btn" @click="handleSearch">搜索</button>
- </view>
- <view class="search-results" v-if="searchResults.length > 0">
- <view class="result-header">
- <text>搜索结果 ({{ searchResults.length }} 个)</text>
- <text class="clear-results" @click="clearSearchResults">清除</text>
- </view>
- <scroll-view class="results-list" scroll-y>
- <view v-for="(result, index) in searchResults" :key="index"
- class="result-item"
- @click="selectSearchResult(result)"
- :class="{'selected': isSelected(result)}">
- <view class="left">
- <view class="result-name">{{ result.name }}</view>
- <view class="result-address">{{ result.address }}</view>
- </view>
-
- <view class="manage" @click.stop="confirmResult(result)">确定</view>
- </view>
- </scroll-view>
- </view>
- </view>
- </view>
- </template>
- <script>
- export default {
- props: {
- initialPosition: {
- type: Object,
- default: () => ({
- lnglat: null,
- address: ''
- })
- },
- allowSelection: {
- type: Boolean,
- default: true
- },
- showCoordinates: {
- type: Boolean,
- default: true
- },
- showSearch: {
- type: Boolean,
- default: true
- }
- },
- data() {
- return {
- tk: '9bcf63358817bfb878e8236de8bf1423',
- searchKeyword: '',
- searchResults: [],
- selectedResult: null,
- renderjsReady: false
- };
- },
- mounted() {
- console.log('组件mounted');
- // 等待renderjs初始化
- setTimeout(() => {
- this.waitForRenderjs();
- }, 1000);
- },
- methods: {
- // 等待renderjs就绪
- waitForRenderjs() {
- if (this.renderjsReady) return;
-
- const checkInterval = setInterval(() => {
- if (this.$refs && this.$refs.renderjsMap) {
- clearInterval(checkInterval);
- this.renderjsReady = true;
- console.log('renderjs就绪');
-
- // 如果有初始位置,设置地图中心
- if (this.initialPosition && this.isValidPosition(this.initialPosition)) {
- this.setMapCenterFromProps();
- }
- }
- }, 100);
-
- // 10秒后超时
- setTimeout(() => {
- clearInterval(checkInterval);
- console.warn('等待renderjs超时');
- }, 10000);
- },
-
- // 从props设置地图中心
- setMapCenterFromProps() {
- const mapData = {
- lng: this.initialPosition.lng ||
- (this.initialPosition.lnglat && this.initialPosition.lnglat.lng) ||
- (this.initialPosition.lnglat && this.initialPosition.lnglat.longitude),
- lat: this.initialPosition.lat ||
- (this.initialPosition.lnglat && this.initialPosition.lnglat.lat) ||
- (this.initialPosition.lnglat && this.initialPosition.lnglat.latitude),
- address: this.initialPosition.address || ''
- };
-
- this.callRenderjsMethod('updateMapWithMarker', mapData);
- },
-
- // 调用renderjs方法
- callRenderjsMethod(methodName, data) {
-
- if (!this.renderjsReady) {
- console.warn('renderjs未就绪,延迟执行');
- setTimeout(() => {
- this.callRenderjsMethod(methodName, data);
- }, 300);
- return;
- }
-
- // 直接调用renderjs模块的方法
- try {
- // 方法1: 通过模块名调用
- if (this.$refs.renderjsMap) {
- this.$refs.renderjsMap.callMethod(methodName, data);
- return;
- }
-
- // 方法2: 使用uni的callMethod
- const ownerInstance = this.$ownerInstance;
- if (ownerInstance && ownerInstance.callMethod) {
- ownerInstance.callMethod('callRenderjsMethod', {
- method: methodName,
- data: data
- });
- return;
- }
-
- console.error('无法调用renderjs方法');
-
- } catch (error) {
- console.error(`调用renderjs方法 ${methodName} 失败:`, error);
-
- // 重试
- setTimeout(() => {
- if (this.renderjsReady) {
- this.callRenderjsMethod(methodName, data);
- }
- }, 500);
- }
- },
-
- // 选择搜索结果
- selectSearchResult(result) {
- console.log('选择搜索结果:', result);
-
- // 设置为当前选中
- this.selectedResult = result;
-
- // 提取经纬度信息
- const coords = this.extractCoordinates(result);
- if (!coords) {
- uni.showToast({
- title: '无法获取位置坐标',
- icon: 'none'
- });
- console.error('无法解析坐标:', result);
- return;
- }
-
- const { lng, lat } = coords;
-
- // 创建地图数据
- const mapData = {
- lng: lng,
- lat: lat,
- address: result.address || result.name,
- name: result.name,
- fromSearch: true
- };
-
- // 更新地图并添加标记
- this.callRenderjsMethod('updateMapWithMarker', mapData);
-
- console.log('地图已定位到:', lng, lat, result.name);
- },
-
- // 确定并提交结果(选中+提交一步完成)
- confirmResult(result) {
- if (result) {
- this.selectedResult = result;
- this.submit(result);
- return;
- }
-
- },
-
- // 提交选中的结果
- submitSelectedResult() {
- this.confirmResult(this.selectedResult);
- },
-
- // 判断结果是否被选中
- isSelected(result) {
- if (!result || !this.selectedResult) return false;
-
- // 提取当前结果的坐标
- const resultCoords = this.extractCoordinates(result);
- const selectedCoords = this.extractCoordinates(this.selectedResult);
-
- if (!resultCoords || !selectedCoords) return false;
-
- // 使用更精确的比较方式
- return Math.abs(resultCoords.lng - selectedCoords.lng) < 0.000001 &&
- Math.abs(resultCoords.lat - selectedCoords.lat) < 0.000001;
- },
-
- // 提取坐标信息
- extractCoordinates(item) {
- let lng, lat;
-
- if (item.lonlat) {
- const coords = item.lonlat.split(',');
- if (coords.length >= 2) {
- lng = parseFloat(coords[0]);
- lat = parseFloat(coords[1]);
- }
- } else if (item.longitude && item.latitude) {
- lng = item.longitude;
- lat = item.latitude;
- } else if (item.lng && item.lat) {
- lng = item.lng;
- lat = item.lat;
- }
-
- // 验证坐标有效性
- if (lng !== undefined && lat !== undefined &&
- !isNaN(lng) && !isNaN(lat) &&
- lng >= -180 && lng <= 180 &&
- lat >= -90 && lat <= 90) {
- return { lng, lat };
- }
-
- console.warn('无法提取有效坐标:', item);
- return null;
- },
-
- // 提交选择
- submit(item) {
- const coords = this.extractCoordinates(item);
- if (!coords) {
- uni.showToast({
- title: '无法获取位置坐标',
- icon: 'none'
- });
- return;
- }
-
- const params = {
- lng: coords.lng,
- lat: coords.lat,
- address: item.name,
- name: item.name
- };
-
- console.log('提交参数:', params);
- uni.setStorageSync('address', params);
- uni.navigateBack({
- delta: 1
- });
- },
-
- back() {
- uni.navigateBack();
- },
-
- isValidPosition(position) {
- const lng = position.lng ||
- (position.lnglat && (position.lnglat.lng || position.lnglat.longitude));
- const lat = position.lat ||
- (position.lnglat && (position.lnglat.lat || position.lnglat.latitude));
- return lng && lat && !isNaN(parseFloat(lng)) && !isNaN(parseFloat(lat));
- },
-
- handleSearch() {
- if (!this.searchKeyword.trim()) {
- uni.showToast({
- title: '请输入搜索关键词',
- icon: 'none'
- });
- return;
- }
-
- this.searchByRestAPI(this.searchKeyword);
- },
-
- searchByRestAPI(keyword) {
- let cityCode = uni.getStorageSync('cityCode') || '140100';
- let postStr = {
- "keyWord": keyword,
- "queryType": 12,
- "start": 0,
- "count": 10,
- "specify": "156" + cityCode
- };
-
- uni.request({
- url: `https://api.tianditu.gov.cn/v2/search?postStr=${encodeURIComponent(JSON.stringify(postStr))}&type=query&tk=${this.tk}`,
- success: (res) => {
- if (res.statusCode == 200 && res.data) {
- this.searchResults = res.data.pois.map(item => ({
- ...item,
- location: item.lonlat
- }));
- } else {
- uni.showToast({
- title: '搜索失败,请重试',
- icon: 'none'
- });
- }
- },
- fail: (error) => {
- console.error('搜索请求失败:', error);
- uni.showToast({
- title: '网络错误,请重试',
- icon: 'none'
- });
- }
- });
- },
-
- onSearchInput(e) {
- this.searchKeyword = e.detail.value;
- },
-
- clearSearchResults() {
- this.searchResults = [];
- this.searchKeyword = '';
- this.selectedResult = null;
- }
- }
- };
- </script>
- <!-- 使用module属性定义renderjs模块 -->
- <script module="renderjsMap" lang="renderjs">
- let mapInstance = null;
- let markerInstance = null;
- let labelInstance = null;
-
- export default {
- data() {
- return {
- mapLoaded: false,
- lastDataHash: ''
- };
- },
- methods: {
- // Vue组件调用的方法
- updateMapWithMarker(mapData) {
- console.log('【RenderJS】updateMapWithMarker 被调用', mapData);
-
- if (!this.mapLoaded || !mapInstance) {
- console.warn('地图未加载,延迟执行');
- setTimeout(() => {
- this.updateMapWithMarker(mapData);
- }, 300);
- return;
- }
-
- // 验证数据
- const dataHash = JSON.stringify(mapData);
- if (dataHash === this.lastDataHash) {
- console.log('数据未变化,跳过更新');
- return;
- }
- this.lastDataHash = dataHash;
-
- // 验证坐标
- const lng = Number(mapData.lng);
- const lat = Number(mapData.lat);
- const address = mapData.address || mapData.name || '未知位置';
-
- if (isNaN(lng) || isNaN(lat)) {
- console.error('坐标无效:', mapData);
- return;
- }
-
- // 移动地图中心
- const targetLngLat = new window.T.LngLat(lng, lat);
- mapInstance.panTo(targetLngLat);
-
- // 设置缩放级别
- if (mapData.fromSearch) {
- mapInstance.setZoom(15);
- }
-
- // 创建标记
- this.createMarker(targetLngLat, address, mapData.name || '');
- },
-
- clearMarkers() {
- if (markerInstance && mapInstance) {
- mapInstance.removeOverLay(markerInstance);
- markerInstance = null;
- }
- if (labelInstance && mapInstance) {
- mapInstance.removeOverLay(labelInstance);
- labelInstance = null;
- }
- },
-
- createMarker(lngLat, address, name) {
- try {
- const T = window.T;
-
- // 清除旧标记
- this.clearMarkers();
-
- // 创建自定义图标
- const iconUrl = this.generateMarkerIcon('#ff0000');
- const icon = new T.Icon({
- iconUrl: iconUrl,
- iconSize: new T.Point(30, 30),
- iconAnchor: new T.Point(15, 30)
- });
-
- // 创建标记
- markerInstance = new T.Marker(lngLat, {
- icon: icon,
- title: name || address
- });
-
- // 创建标签
- labelInstance = new T.Label({
- text: address,
- position: lngLat,
- style: {
- color: '#333',
- backgroundColor: 'rgba(255, 255, 255, 0.9)',
- borderColor: '#ff0000',
- borderWidth: 1,
- borderRadius: 4,
- fontSize: '14px',
- fontWeight: '500',
- padding: '6px 10px'
- },
- offset: new T.Point(0, -40)
- });
-
- // 添加到地图
- mapInstance.addOverLay(markerInstance);
- mapInstance.addOverLay(labelInstance);
-
- // 标记点击事件
- markerInstance.addEventListener('click', () => {
- this.sendToVue('showLocationInfo', {
- lng: lngLat.getLng().toFixed(6),
- lat: lngLat.getLat().toFixed(6),
- address: address,
- name: name
- });
- });
-
- console.log('标记创建成功');
-
- } catch (error) {
- console.error('创建标记失败:', error);
- }
- },
-
- generateMarkerIcon(color) {
- const size = 30;
- const canvas = document.createElement('canvas');
- canvas.width = size;
- canvas.height = size;
- const ctx = canvas.getContext('2d');
-
- ctx.beginPath();
- ctx.arc(size/2, size/2, size/2 - 1, 0, Math.PI * 2);
- ctx.fillStyle = '#ffffff';
- ctx.fill();
-
- ctx.beginPath();
- ctx.arc(size/2, size/2, size/2 - 4, 0, Math.PI * 2);
- ctx.fillStyle = color;
- ctx.fill();
-
- return canvas.toDataURL();
- },
-
- // 初始化地图
- async initMap() {
- try {
- console.log('【RenderJS】开始初始化地图');
-
- // 加载API
- await this.loadTiandituAPI();
-
- // 获取容器
- const container = document.getElementById('mapContainer');
- if (!container) {
- throw new Error('地图容器不存在');
- }
-
- // 创建地图实例
- const T = window.T;
- mapInstance = new T.Map(container);
-
- // 设置默认中心点
- const center = new T.LngLat(112.55, 37.87);
- mapInstance.centerAndZoom(center, 12);
-
- // 添加图层
- const vecLayer = new T.TileLayer(
- `https://t{s}.tianditu.gov.cn/vec_c/wmts?tk=9bcf63358817bfb878e8236de8bf1423`, {
- subdomains: ['0', '1', '2', '3', '4', '5', '6', '7']
- }
- );
-
- const cvaLayer = new T.TileLayer(
- `https://t{s}.tianditu.gov.cn/cva_c/wmts?tk=9bcf63358817bfb878e8236de8bf1423`, {
- subdomains: ['0', '1', '2', '3', '4', '5', '6', '7']
- }
- );
-
- mapInstance.addLayer(vecLayer);
- mapInstance.addLayer(cvaLayer);
- mapInstance.addControl(new T.Control.Zoom());
-
- // 添加点击事件
- mapInstance.addEventListener('click', (e) => {
- this.handleMapClick(e);
- });
-
- this.mapLoaded = true;
- console.log('【RenderJS】地图初始化完成');
-
- } catch (error) {
- console.error('【RenderJS】地图初始化失败:', error);
- }
- },
-
- // 加载天地图API
- loadTiandituAPI() {
- return new Promise((resolve, reject) => {
- if (window.T && window.T.Map) {
- resolve();
- return;
- }
-
- const script = document.createElement('script');
- script.src = 'https://api.tianditu.gov.cn/api?v=4.0&tk=9bcf63358817bfb878e8236de8bf1423';
-
- script.onload = () => {
- // 等待T对象初始化
- const checkInterval = setInterval(() => {
- if (window.T && window.T.Map) {
- clearInterval(checkInterval);
- resolve();
- }
- }, 100);
-
- setTimeout(() => {
- clearInterval(checkInterval);
- reject(new Error('T对象初始化超时'));
- }, 5000);
- };
-
- script.onerror = reject;
- document.head.appendChild(script);
- });
- },
-
- handleMapClick(e) {
- if (!this.mapLoaded) return;
-
- const lng = e.lnglat.lng;
- const lat = e.lnglat.lat;
-
- const mapData = {
- lng: lng,
- lat: lat,
- address: '点击位置'
- };
-
- this.updateMapWithMarker(mapData);
-
- // 通知Vue组件
- this.sendToVue('onMapClick', {
- lng: lng,
- lat: lat
- });
- },
-
- // 向Vue发送消息
- sendToVue(method, data) {
- try {
- if (this.$ownerInstance && this.$ownerInstance.callMethod) {
- this.$ownerInstance.callMethod(method, data);
- }
- } catch (error) {
- console.error('发送消息到Vue失败:', error);
- }
- },
-
- cleanupMap() {
- this.clearMarkers();
- if (mapInstance) {
- try {
- mapInstance.destroy();
- } catch (e) {}
- mapInstance = null;
- }
- this.mapLoaded = false;
- }
- },
- mounted() {
- console.log('【RenderJS】模块挂载');
-
- // 延迟初始化
- setTimeout(() => {
- this.initMap();
- }, 500);
- },
- beforeDestroy() {
- console.log('【RenderJS】模块销毁');
- this.cleanupMap();
- }
- };
- </script>
- <style scoped lang="scss">
- .top {
- width: 100%;
- height: var(--status-bar-height);
- background: linear-gradient(87deg, #FFFFFF 0%, #A3CDFF 100%), linear-gradient(360deg, #FFFFFF 0%, rgba(255, 255, 255, 0) 100%);
- }
- .head {
- width: 100%;
- background: #ffffff;
- .handle {
- height: 112rpx;
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 0px 24rpx;
- position: relative;
- .head_left {
- font-size: 40rpx;
- }
- .center {
- position: absolute;
- left: 50%;
- top: 50%;
- transform: translate(-50%, -50%);
- font-size: 30rpx;
- font-weight: 600;
- }
- }
- }
- .container {
- width: 100%;
- height: 100vh;
- display: flex;
- flex-direction: column;
- position: relative;
- }
- .map-container {
- width: 100%;
- height: 300px;
- background-color: #f8f8f8;
- position: relative;
- }
- .search-panel {
- margin-top: 20rpx;
- padding: 20rpx;
- background-color: #f8f9fa;
- border-radius: 8rpx;
- flex: 1;
- display: flex;
- flex-direction: column;
- }
- .search-input-group {
- display: flex;
- gap: 10rpx;
- margin-bottom: 20rpx;
- }
- .search-input {
- flex: 1;
- height: 70rpx;
- padding: 0 20rpx;
- border: 1px solid #ddd;
- border-radius: 8rpx;
- background-color: white;
- font-size: 28rpx;
- }
- .search-btn {
- height: 70rpx;
- padding: 0 20rpx;
- background-color: #1890ff;
- color: white;
- border-radius: 8rpx;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 28rpx;
- border: none;
- }
- .search-results {
- border: 1px solid #e8e8e8;
- border-radius: 8rpx;
- flex: 1;
- background-color: white;
- }
- .result-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 20rpx;
- border-bottom: 1px solid #f0f0f0;
- font-weight: bold;
- font-size: 28rpx;
- }
- .clear-results {
- color: #1890ff;
- font-size: 24rpx;
- }
- .results-list {
- max-height: 700rpx;
- }
- .result-item {
- padding: 20rpx;
- border-bottom: 1px solid #f5f5f5;
- cursor: pointer;
- display: flex;
- justify-content: space-between;
- align-items: center;
-
- &.selected {
- background-color: #e6f7ff;
- border-left: 4rpx solid #1890ff;
- }
-
- .left {
- flex: 1;
- margin-right: 20rpx;
- }
-
- .manage {
- width: 100rpx;
- height: 50rpx;
- display: flex;
- align-items: center;
- justify-content: center;
- background-color: #1890ff;
- border-radius: 10rpx;
- color: #FFFFFF;
- font-size: 24rpx;
- }
- }
-
- .result-item.selected .manage {
- background-color: #52c41a;
- }
- .result-name {
- font-weight: bold;
- margin-bottom: 5rpx;
- color: #333;
- font-size: 28rpx;
- }
- .result-address {
- font-size: 24rpx;
- color: #666;
- }
- </style>
|