| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761 |
- <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 class="head_right">
- <!-- <span class="cuIcon-search" @click="goSearch"></span> -->
- <view class="manage" @click="confirm">
- 确定
- </view>
- </view>
- </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" />
- <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)">
- <view class="result-name">{{ result.name }}</view>
- <view class="result-address">{{ result.address }}</view>
- <view class="result-coord">经度:{{ result.lng.toFixed(6) }} 纬度:{{ result.lat.toFixed(6) }}</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 {
- map: null,
- geocoder: null,
- localSearch: null,
- tk: '92c4ddd919f4e41a30e765680e3a29b7',
- longitude: '未选择',
- latitude: '未选择',
- address: '未获取',
- markers: [],
- labels: [],
- infoWindows: [],
- mapInitialized: false,
- searchKeyword: '',
- searchResults: [],
- currentInfoWindow: null,
- lng: '',
- lat: '',
- addressname: '',
- selectedPosition: null,
- backurl: ''
- };
- },
- watch: {
- initialPosition: {
- immediate: true,
- handler(newVal) {
- if (this.mapInitialized && newVal && this.isValidPosition(newVal)) {
- this.processPosition(newVal);
- }
- }
- },
- allowSelection(newVal) {
- if (this.map) {
- if (newVal) {
- this.enableMapSelection();
- } else {
- this.disableMapSelection();
- }
- }
- }
- },
- mounted() {
- // 确保在H5环境下运行
- // #ifdef H5
- this.$nextTick(() => {
- setTimeout(() => {
- this.initMap();
- }, 300);
- });
- // #endif
- // 非H5环境提示
- // #ifndef H5
- console.warn("天地图组件仅支持H5环境");
- uni.showToast({
- title: '地图功能仅在H5环境可用',
- icon: 'none'
- });
- // #endif
- },
- beforeUnmount() {
- this.cleanupMap();
- },
- onLoad(option) {
- console.log(option)
- this.backurl = option.url
- },
- methods: {
- confirm() {
- console.log('确认选择位置:', this.lng, this.lat, this.addressname);
- if (!this.lng || !this.lat) {
- uni.showToast({
- title: '请先选择位置',
- icon: 'none'
- });
- return;
- }
- const params = {
- lng: this.lng,
- lat: this.lat,
- address: this.addressname
- };
- uni.setStorageSync('address', params);
- uni.navigateBack({
- delta: 1 // 返回的页面数,默认为1
- })
- // 使用encodeURIComponent编码参数
- // const queryString = Object.keys(params)
- // .map(key => `${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`)
- // .join('&');
- // // 拼接完整URL
- // const url = `${this.backurl}?${queryString}`;
- // uni.navigateTo({
- // url: url
- // })
- },
- 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));
- },
- processPosition(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));
- if (lng && lat) {
- this.setMapCenter(parseFloat(lng), parseFloat(lat));
- this.reverseGeocode(parseFloat(lng), parseFloat(lat));
- }
- },
- initMap() {
- if (typeof T === 'undefined') {
- this.loadTiandituAPI().then(() => {
- this.createMap();
- }).catch(err => {
- console.error("天地图加载失败:", err);
- uni.showToast({
- title: '地图加载失败',
- icon: 'none'
- });
- });
- } else {
- this.createMap();
- }
- },
- loadTiandituAPI() {
- return new Promise((resolve, reject) => {
- if (window.T) return resolve();
- const script = document.createElement('script');
- script.src = `https://api.tianditu.gov.cn/api?v=4.0&tk=${this.tk}`;
- script.onload = resolve;
- script.onerror = () => reject(new Error('天地图脚本加载失败'));
- document.head.appendChild(script);
- });
- },
- createMap() {
- try {
- this.map = new T.Map('mapContainer');
- let centerLng = 112.55;
- let centerLat = 37.87;
- if (this.initialPosition && this.isValidPosition(this.initialPosition)) {
- const lng = this.initialPosition.lng ||
- (this.initialPosition.lnglat && (this.initialPosition.lnglat.lng || this.initialPosition.lnglat
- .longitude));
- const lat = this.initialPosition.lat ||
- (this.initialPosition.lnglat && (this.initialPosition.lnglat.lat || this.initialPosition.lnglat.latitude));
- centerLng = parseFloat(lng);
- centerLat = parseFloat(lat);
- this.longitude = centerLng.toFixed(6);
- this.latitude = centerLat.toFixed(6);
- this.address = '正在查询地址...';
- }
- this.map.centerAndZoom(new T.LngLat(centerLng, centerLat), 12);
- this.geocoder = new T.Geocoder();
- // 初始化本地搜索
- const searchConfig = {
- pageCapacity: 10,
- onSearchComplete: this.handleSearchComplete.bind(this)
- };
- this.localSearch = new T.LocalSearch(this.map, searchConfig);
- const vecLayer = new T.TileLayer(
- `https://t{s}.tianditu.gov.cn/vec_c/wmts?tk=${this.tk}`, {
- subdomains: ['0', '1', '2', '3', '4', '5', '6', '7']
- }
- );
- const cvaLayer = new T.TileLayer(
- `https://t{s}.tianditu.gov.cn/cva_c/wmts?tk=${this.tk}`, {
- subdomains: ['0', '1', '2', '3', '4', '5', '6', '7']
- }
- );
- this.map.addLayer(vecLayer);
- this.map.addLayer(cvaLayer);
- this.map.addControl(new T.Control.Zoom());
- if (this.allowSelection) this.enableMapSelection();
- if (this.initialPosition && this.isValidPosition(this.initialPosition)) {
- const lng = this.initialPosition.lng ||
- (this.initialPosition.lnglat && (this.initialPosition.lnglat.lng || this.initialPosition.lnglat
- .longitude));
- const lat = this.initialPosition.lat ||
- (this.initialPosition.lnglat && (this.initialPosition.lnglat.lat || this.initialPosition.lnglat.latitude));
- this.addMarker(parseFloat(lng), parseFloat(lat));
- this.reverseGeocode(parseFloat(lng), parseFloat(lat));
- }
- this.mapInitialized = true;
- } catch (e) {
- console.error('地图初始化错误:', e);
- }
- },
- // 搜索相关方法
- handleSearch() {
- if (!this.searchKeyword.trim()) {
- uni.showToast({
- title: '请输入搜索关键词',
- icon: 'none'
- });
- return;
- }
- this.localSearch.search(this.searchKeyword);
- },
- handleSearchComplete(result) {
- this.clearSearchMarkers();
- this.searchResults = [];
- if (result.getResultType() === 1) {
- const pois = result.getPois();
- if (pois && pois.length > 0) {
- this.searchResults = pois.map(poi => ({
- name: poi.name,
- address: poi.address,
- lng: parseFloat(poi.lonlat.split(',')[0]),
- lat: parseFloat(poi.lonlat.split(',')[1])
- }));
- // 在地图上显示搜索结果标记
- pois.forEach(poi => {
- const lng = parseFloat(poi.lonlat.split(',')[0]);
- const lat = parseFloat(poi.lonlat.split(',')[1]);
- this.addSearchMarker(lng, lat, poi.name, poi.address);
- });
- // 调整地图视图以显示所有结果
- const points = pois.map(poi => {
- const lnglat = poi.lonlat.split(',');
- return new T.LngLat(parseFloat(lnglat[0]), parseFloat(lnglat[1]));
- });
- this.map.setViewport(points);
- } else {
- uni.showToast({
- title: '未找到相关结果',
- icon: 'none'
- });
- }
- }
- },
- selectSearchResult(result) {
- this.setMapCenter(result.lng, result.lat);
- this.address = result.address;
- this.lng = result.lng;
- this.lat = result.lat;
- this.addressname = result.name;
- console.log('选择搜索结果:', this.lng, this.lat, this.addressname);
- this.updateLabelContent();
- this.notifyParent();
- // 关闭其他信息窗口,打开当前结果的信息窗口
- this.closeAllInfoWindows();
- this.openInfoWindow(result.lng, result.lat, result.name, result.address);
- },
- clearSearchResults() {
- this.searchResults = [];
- this.searchKeyword = '';
- this.clearSearchMarkers();
- },
- // 地图交互方法
- enableMapSelection() {
- this.map.on('click', this.handleMapClick);
- },
- disableMapSelection() {
- this.map.off('click', this.handleMapClick);
- },
- setMapCenter(lng, lat) {
- this.longitude = parseFloat(lng).toFixed(6);
- this.latitude = parseFloat(lat).toFixed(6);
- this.clearMarkers();
- this.addMarker(lng, lat);
- this.map.panTo(new T.LngLat(lng, lat));
- this.notifyParent();
- },
- reverseGeocode(lng, lat) {
- if (!this.geocoder) return;
- this.address = '正在查询...';
- const point = new T.LngLat(lng, lat);
- this.geocoder.getLocation(point, (result) => {
- if (result.getStatus() === 0) {
- this.address = result.getAddress();
- this.addressname = this.address;
- } else {
- this.address = '查询失败';
- }
- this.updateLabelContent();
- this.notifyParent();
- });
- },
- handleMapClick(e) {
- if (!this.allowSelection) return;
- this.setMapCenter(e.lnglat.lng, e.lnglat.lat);
- this.reverseGeocode(e.lnglat.lng, e.lnglat.lat);
- // 点击地图时也显示信息窗口
- this.openInfoWindow(e.lnglat.lng, e.lnglat.lat, '选中位置', this.address);
- },
- notifyParent() {
- this.$emit('update-location', {
- longitude: this.longitude,
- latitude: this.latitude,
- address: this.address
- });
- },
- // 标记和信息窗口管理
- addMarker(lng, lat) {
- try {
- const point = new T.LngLat(lng, lat);
- const markerIcon = new T.Icon({
- iconUrl: 'data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10" fill="%231296db" stroke="%23ffffff" stroke-width="2"/></svg>',
- iconSize: new T.Point(24, 24),
- iconAnchor: new T.Point(12, 24)
- });
- const marker = new T.Marker(point, {
- icon: markerIcon
- });
- // 为标记添加点击事件,显示信息窗口
- marker.addEventListener('click', () => {
- this.openInfoWindow(lng, lat, '选中位置', this.address);
- });
- this.map.addOverLay(marker);
- this.markers.push(marker);
- this.addLabel(lng, lat, this.address);
- } catch (error) {
- console.error('添加标记失败:', error);
- }
- },
- addSearchMarker(lng, lat, name, address) {
- try {
- const point = new T.LngLat(lng, lat);
- const markerIcon = new T.Icon({
- iconUrl: 'data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24"><circle cx="12" cy="12" r="8" fill="%23ff6b6b" stroke="%23ffffff" stroke-width="2"/></svg>',
- iconSize: new T.Point(20, 20),
- iconAnchor: new T.Point(10, 20)
- });
- const marker = new T.Marker(point, {
- icon: markerIcon
- });
- // 为搜索结果的标记添加点击事件
- marker.addEventListener('click', () => {
- this.openInfoWindow(lng, lat, name, address);
- });
- this.map.addOverLay(marker);
- this.markers.push(marker);
- } catch (error) {
- console.error('添加搜索标记失败:', error);
- }
- },
- openInfoWindow(lng, lat, title, content) {
- this.closeAllInfoWindows();
- const point = new T.LngLat(lng, lat);
- const infoContent = `
- <div style="min-width: 250px; padding: 10px;">
- <div style="font-weight: bold; margin-bottom: 8px; color: #1890ff;">${title}</div>
- <div style="margin-bottom: 5px; color: #666;">${content}</div>
- <div style="font-size: 12px; color: #999;">
- 经度: ${lng.toFixed(6)}<br/>
- 纬度: ${lat.toFixed(6)}
- </div>
- </div>
- `;
- const infoWindow = new T.InfoWindow(infoContent, {
- autoPan: true,
- width: 280,
- height: 'auto'
- });
- this.map.openInfoWindow(infoWindow, point);
- this.currentInfoWindow = infoWindow;
- },
- closeAllInfoWindows() {
- if (this.currentInfoWindow) {
- this.map.closeInfoWindow();
- this.currentInfoWindow = null;
- }
- },
- addLabel(lng, lat, content) {
- try {
- const point = new T.LngLat(lng, lat);
- const label = new T.Label({
- text: content || '未知地址',
- position: point,
- offset: new T.Point(0, -35)
- });
- label.setStyle({
- color: '#333',
- backgroundColor: 'rgba(255, 255, 255, 0.95)',
- border: '1px solid #ddd',
- borderRadius: '4px',
- padding: '6px 12px',
- fontSize: '12px',
- fontWeight: 'normal',
- boxShadow: '0 2px 6px rgba(0,0,0,0.15)',
- maxWidth: '200px',
- textAlign: 'center',
- whiteSpace: 'nowrap',
- overflow: 'hidden',
- textOverflow: 'ellipsis'
- });
- this.map.addOverLay(label);
- this.labels.push(label);
- } catch (error) {
- console.error('添加标签失败:', error);
- }
- },
- updateLabelContent() {
- if (this.labels.length > 0) {
- const lastLabel = this.labels[this.labels.length - 1];
- lastLabel.setText(this.address);
- }
- },
- clearMarkers() {
- this.markers.forEach(marker => {
- try {
- this.map.removeOverLay(marker);
- } catch (error) {
- console.error('移除标记失败:', error);
- }
- });
- this.markers = [];
- this.labels.forEach(label => {
- try {
- this.map.removeOverLay(label);
- } catch (error) {
- console.error('移除标签失败:', error);
- }
- });
- this.labels = [];
- this.closeAllInfoWindows();
- },
- clearSearchMarkers() {
- // 清除搜索相关的标记,保留主标记
- const mainMarkers = this.markers.filter(marker => {
- // 这里可以根据标记的特定属性来区分主标记和搜索标记
- // 简化处理:保留最后一个标记(通常是用户选择的主标记)
- return this.markers.indexOf(marker) === this.markers.length - 1;
- });
- this.markers = mainMarkers;
- },
- cleanupMap() {
- if (this.map) {
- this.clearMarkers();
- this.map.destroy();
- this.map = null;
- }
- }
- }
- };
- </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;
- }
- .head_right {
- display: flex;
- justify-content: space-between;
- align-items: center;
- span {
- width: 40rpx;
- height: 40rpx;
- display: flex;
- font-size: 28rpx;
- justify-content: center;
- align-items: center;
- }
- }
- }
- }
- .container {
- width: 100%;
- height: 100vh;
- display: flex;
- flex-direction: column;
- position: relative;
- z-index: 0;
- }
- .map-container {
- width: 100%;
- height: 600rpx;
- border: 1px solid #eee;
- background-color: #f8f8f8;
- }
- /* 搜索面板样式 */
- .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;
- }
- .search-btn {
- height: 70rpx;
- background-color: #1890ff;
- color: white;
- border-radius: 8rpx;
- display: flex;
- align-items: center;
- justify-content: center;
- }
- .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;
- }
- .clear-results {
- color: #1890ff;
- font-size: 12px;
- }
- .results-list {
- max-height: 700rpx;
- }
- .result-item {
- padding: 20rpx;
- border-bottom: 1px solid #f5f5f5;
- cursor: pointer;
- }
- .result-item:last-child {
- border-bottom: none;
- }
- .result-item:hover {
- background-color: #f8f9fa;
- }
- .result-name {
- font-weight: bold;
- margin-bottom: 5rpx;
- color: #333;
- }
- .result-address {
- font-size: 12px;
- color: #666;
- margin-bottom: 5rpx;
- }
- .result-coord {
- font-size: 11px;
- color: #999;
- }
- ::v-deep .tdt-bottom {
- z-index: 0;
- }
- ::v-deep .tdt-top {
- z-index: 0;
- }
- ::v-deep .tdt-bottom,
- .tdt-control {
- z-index: 0;
- }
- </style>
|