support clicking on map to set specified geographic location

This commit is contained in:
MaysWind
2025-05-02 00:32:22 +08:00
parent 65a0e48988
commit 381d063295
23 changed files with 191 additions and 24 deletions
+21 -2
View File
@@ -13,7 +13,8 @@ import { ref, computed, useTemplateRef } from 'vue';
import { useI18n } from '@/locales/helpers.ts';
import type { MapInstance, MapPosition } from '@/lib/map/base.ts';
import type { MapPosition } from '@/core/map.ts';
import type { MapInstance } from '@/lib/map/base.ts';
import { createMapInstance } from '@/lib/map/index.ts';
const props = defineProps<{
@@ -23,6 +24,10 @@ const props = defineProps<{
geoLocation?: MapPosition;
}>();
const emit = defineEmits<{
(e: 'click', geoLocation: MapPosition): void;
}>();
const { tt, getCurrentLanguageInfo } = useI18n();
const mapContainer = useTemplateRef<HTMLElement>('mapContainer');
@@ -86,6 +91,9 @@ function initMapView(): void {
text: {
zoomIn: tt('Zoom in'),
zoomOut: tt('Zoom out'),
},
onClick: (geoLocation: MapPosition) => {
emit('click', geoLocation);
}
});
@@ -105,7 +113,18 @@ function initMapView(): void {
}
}
function setMarkerPosition(geoLocation?: MapPosition): void {
if (!mapInstance.value) {
return;
}
if (geoLocation) {
mapInstance.value.setMapCenterMarker(geoLocation);
}
}
defineExpose({
initMapView
initMapView,
setMarkerPosition
});
</script>
+21 -3
View File
@@ -3,13 +3,16 @@
:opened="show" @sheet:open="onSheetOpen" @sheet:closed="onSheetClosed">
<f7-toolbar>
<div class="swipe-handler"></div>
<div class="left"></div>
<div class="left">
<f7-link :text="tt('Disable Click to Set Location')" @click="switchSetGeoLocationByClickMap(false)" v-if="isSupportGetGeoLocationByClick() && props.setGeoLocationByClickMap"></f7-link>
<f7-link :text="tt('Enable Click to Set Location')" @click="switchSetGeoLocationByClickMap(true)" v-if="isSupportGetGeoLocationByClick() && !props.setGeoLocationByClickMap"></f7-link>
</div>
<div class="right">
<f7-link :text="tt('Done')" @click="save"></f7-link>
</div>
</f7-toolbar>
<f7-page-content class="no-margin-vertical no-padding-vertical">
<map-view ref="map" height="400px" :geo-location="geoLocation">
<map-view ref="map" height="400px" :geo-location="geoLocation" @click="updateSpecifiedGeoLocation">
<template #error-title="{ mapSupported, mapDependencyLoaded }">
<div class="display-flex padding justify-content-space-between align-items-center">
<div class="ebk-sheet-title" v-if="!mapSupported"><b>{{ tt('Unsupported Map Provider') }}</b></div>
@@ -36,17 +39,21 @@ import MapView from '@/components/common/MapView.vue';
import { useI18n } from '@/locales/helpers.ts';
import type { MapPosition } from '@/lib/map/base.ts';
import type { MapPosition } from '@/core/map.ts';
import { isSupportGetGeoLocationByClick } from '@/lib/map/index.ts';
type MapViewType = InstanceType<typeof MapView>;
const props = defineProps<{
modelValue?: MapPosition;
setGeoLocationByClickMap?: boolean;
show: boolean;
}>();
const emit = defineEmits<{
(e: 'update:modelValue', value: MapPosition | undefined): void;
(e: 'update:setGeoLocationByClickMap', value: boolean): void;
(e: 'update:show', value: boolean): void;
}>();
@@ -63,6 +70,17 @@ const geoLocation = computed<MapPosition | undefined>({
}
});
function updateSpecifiedGeoLocation(mapPosition: MapPosition): void {
if (isSupportGetGeoLocationByClick() && props.setGeoLocationByClickMap) {
geoLocation.value = mapPosition;
map.value?.setMarkerPosition(mapPosition);
}
}
function switchSetGeoLocationByClickMap(value: boolean): void {
emit('update:setGeoLocationByClickMap', value);
}
function save(): void {
emit('update:show', false);
}
+4
View File
@@ -0,0 +1,4 @@
export interface MapPosition {
latitude: number;
longitude: number;
}
+15 -1
View File
@@ -1,6 +1,7 @@
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-nocheck
import type { MapProvider, MapInstance, MapInstanceInitOptions, MapPosition } from './base.ts';
import type { MapPosition } from '@/core/map.ts';
import type { MapProvider, MapInstance, MapInstanceInitOptions } from './base.ts';
import { asyncLoadAssets } from '@/lib/misc.ts';
import services from '@/lib/services.ts';
@@ -18,6 +19,10 @@ export class AmapMapProvider implements MapProvider {
return 'https://www.amap.com';
}
public isSupportGetGeoLocationByClick(): boolean {
return false;
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
public asyncLoadAssets(language?: string): Promise<unknown> {
if (AmapMapProvider.AMap) {
@@ -86,6 +91,15 @@ export class AmapMapInstance implements MapInstance {
});
amapInstance.addControl(amapToolbar);
amapInstance.on('click', function(e) {
if (options.onClick) {
options.onClick({
latitude: e.lnglat.lat,
longitude: e.lnglat.lng
});
}
});
this.amapInstance = amapInstance;
this.amapToolbar = amapToolbar;
this.inited = true;
+15 -1
View File
@@ -1,6 +1,7 @@
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-nocheck
import type { MapProvider, MapInstance, MapInstanceInitOptions, MapPosition } from './base.ts';
import type { MapPosition } from '@/core/map.ts';
import type { MapProvider, MapInstance, MapInstanceInitOptions } from './base.ts';
import { asyncLoadAssets } from '@/lib/misc.ts';
import services from '@/lib/services.ts';
@@ -17,6 +18,10 @@ export class BaiduMapProvider implements MapProvider {
return 'https://map.baidu.com';
}
public isSupportGetGeoLocationByClick(): boolean {
return false;
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
public asyncLoadAssets(language?: string): Promise<unknown> {
if (BaiduMapProvider.BMap) {
@@ -76,6 +81,15 @@ export class BaiduMapInstance implements MapInstance {
baiduMapInstance.addControl(baiduMapNavigationControl);
baiduMapInstance.centerAndZoom(new BMap.Point(options.initCenter.longitude, options.initCenter.latitude), options.zoomLevel);
baiduMapInstance.addEventListener('click', function(e) {
if (options.onClick) {
options.onClick({
latitude: e.point.lat,
longitude: e.point.lng
});
}
});
this.baiduMapInstance = baiduMapInstance;
this.baiduMapConverter = new BMap.Convertor();
this.baiduMapNavigationControl = baiduMapNavigationControl;
+5 -6
View File
@@ -1,5 +1,8 @@
import type { MapPosition } from '@/core/map.ts';
export interface MapProvider {
getWebsite(): string;
isSupportGetGeoLocationByClick(): boolean;
asyncLoadAssets(language?: string): Promise<unknown>;
createMapInstance(): MapInstance | null;
}
@@ -22,10 +25,6 @@ export interface MapInstanceInitOptions {
readonly text: {
readonly zoomIn: string;
readonly zoomOut: string;
}
}
export interface MapPosition {
latitude: number;
longitude: number;
};
readonly onClick?: (position: MapPosition) => void;
}
+16 -1
View File
@@ -1,6 +1,7 @@
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-nocheck
import type { MapProvider, MapInstance, MapInstanceInitOptions, MapPosition } from './base.ts';
import type { MapPosition } from '@/core/map.ts';
import type { MapProvider, MapInstance, MapInstanceInitOptions } from './base.ts';
import { asyncLoadAssets } from '@/lib/misc.ts';
import services from '@/lib/services.ts';
@@ -15,6 +16,10 @@ export class GoogleMapProvider implements MapProvider {
return 'https://maps.google.com';
}
public isSupportGetGeoLocationByClick(): boolean {
return true;
}
public asyncLoadAssets(language?: string): Promise<unknown> {
if (GoogleMapProvider.GoogleMap) {
return Promise.resolve();
@@ -76,6 +81,16 @@ export class GoogleMapInstance implements MapInstance {
position: GoogleMapProvider.ControlPosition.LEFT_TOP
}
});
this.googleMapInstance.addListener('click', function(e) {
if (options.onClick) {
options.onClick({
latitude: e.latLng.lat(),
longitude: e.latLng.lng()
});
}
});
this.inited = true;
}
+4
View File
@@ -31,6 +31,10 @@ export function getMapWebsite(): string {
return mapProvider?.getWebsite() || '';
}
export function isSupportGetGeoLocationByClick(): boolean {
return mapProvider?.isSupportGetGeoLocationByClick() || false;
}
export function createMapInstance(): MapInstance | null {
return mapProvider?.createMapInstance() || null;
}
+16 -1
View File
@@ -1,8 +1,10 @@
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-nocheck
import type { MapPosition } from '@/core/map.ts';
import { type LeafletTileSource, type LeafletTileSourceExtraParam, LEAFLET_TILE_SOURCES } from '@/consts/map.ts';
import type { MapProvider, MapInstance, MapInstanceInitOptions, MapPosition } from './base.ts';
import type { MapProvider, MapInstance, MapInstanceInitOptions } from './base.ts';
import {
isMapDataFetchProxyEnabled,
@@ -35,6 +37,10 @@ export class LeafletMapProvider implements MapProvider {
}
}
public isSupportGetGeoLocationByClick(): boolean {
return true;
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
public asyncLoadAssets(language?: string): Promise<unknown> {
return Promise.all([
@@ -158,6 +164,15 @@ export class LeafletMapInstance implements MapInstance {
this.leafletAttribution = attribution;
}
leafletInstance.addEventListener('click', function(e) {
if (options.onClick) {
options.onClick({
latitude: e.latlng.lat,
longitude: e.latlng.lng
});
}
});
this.leafletInstance = leafletInstance;
this.leafletTileLayer = tileLayer;
this.leafletZoomControl = zoomControl;
+3
View File
@@ -1639,6 +1639,9 @@
"Location on Map": "Standort auf der Karte",
"Update Geographic Location": "Geografischen Standort aktualisieren",
"Clear Geographic Location": "Geografischen Standort löschen",
"Click on Map to Set Geographic Location": "Click on Map to Set Geographic Location",
"Enable Click to Set Location": "Enable Click to Set Location",
"Disable Click to Set Location": "Disable Click to Set Location",
"Unable to retrieve current position": "Aktuelle Position kann nicht abgerufen werden",
"Cannot Initialize Map": "Karte kann nicht initialisiert werden",
"Unsupported Map Provider": "Nicht unterstützter Kartenanbieter",
+3
View File
@@ -1639,6 +1639,9 @@
"Location on Map": "Location on Map",
"Update Geographic Location": "Update Geographic Location",
"Clear Geographic Location": "Clear Geographic Location",
"Click on Map to Set Geographic Location": "Click on Map to Set Geographic Location",
"Enable Click to Set Location": "Enable Click to Set Location",
"Disable Click to Set Location": "Disable Click to Set Location",
"Unable to retrieve current position": "Unable to retrieve current position",
"Cannot Initialize Map": "Cannot Initialize Map",
"Unsupported Map Provider": "Unsupported Map Provider",
+3
View File
@@ -1638,6 +1638,9 @@
"Location on Map": "Ubicación en el mapa",
"Update Geographic Location": "Actualizar ubicación geográfica",
"Clear Geographic Location": "Borrar ubicación geográfica",
"Click on Map to Set Geographic Location": "Click on Map to Set Geographic Location",
"Enable Click to Set Location": "Enable Click to Set Location",
"Disable Click to Set Location": "Disable Click to Set Location",
"Unable to retrieve current position": "No se puede recuperar la posición actual",
"Cannot Initialize Map": "No se puede inicializar el mapa",
"Unsupported Map Provider": "Proveedor de mapas no compatible",
+3
View File
@@ -1639,6 +1639,9 @@
"Location on Map": "Posizione sulla mappa",
"Update Geographic Location": "Aggiorna posizione geografica",
"Clear Geographic Location": "Cancella posizione geografica",
"Click on Map to Set Geographic Location": "Click on Map to Set Geographic Location",
"Enable Click to Set Location": "Enable Click to Set Location",
"Disable Click to Set Location": "Disable Click to Set Location",
"Unable to retrieve current position": "Impossibile recuperare la posizione corrente",
"Cannot Initialize Map": "Impossibile inizializzare la mappa",
"Unsupported Map Provider": "Fornitore mappa non supportato",
+3
View File
@@ -1639,6 +1639,9 @@
"Location on Map": "マップ上の座標",
"Update Geographic Location": "地理座標の更新",
"Clear Geographic Location": "地理座標を削除",
"Click on Map to Set Geographic Location": "Click on Map to Set Geographic Location",
"Enable Click to Set Location": "Enable Click to Set Location",
"Disable Click to Set Location": "Disable Click to Set Location",
"Unable to retrieve current position": "現在位置を取得できません",
"Cannot Initialize Map": "マップを初期化できません",
"Unsupported Map Provider": "サポートされていないマッププロバイダーです",
+3
View File
@@ -1639,6 +1639,9 @@
"Location on Map": "Местоположение на карте",
"Update Geographic Location": "Обновить географическое местоположение",
"Clear Geographic Location": "Очистить географическое местоположение",
"Click on Map to Set Geographic Location": "Click on Map to Set Geographic Location",
"Enable Click to Set Location": "Enable Click to Set Location",
"Disable Click to Set Location": "Disable Click to Set Location",
"Unable to retrieve current position": "Не удалось получить текущее местоположение",
"Cannot Initialize Map": "Не удалось инициализировать карту",
"Unsupported Map Provider": "Неподдерживаемый поставщик карт",
+3
View File
@@ -1639,6 +1639,9 @@
"Location on Map": "Розташування на карті",
"Update Geographic Location": "Оновити геолокацію",
"Clear Geographic Location": "Очистити геолокацію",
"Click on Map to Set Geographic Location": "Click on Map to Set Geographic Location",
"Enable Click to Set Location": "Enable Click to Set Location",
"Disable Click to Set Location": "Disable Click to Set Location",
"Unable to retrieve current position": "Не вдалося отримати поточне розташування",
"Cannot Initialize Map": "Не вдалося ініціалізувати карту",
"Unsupported Map Provider": "Непідтримуваний провайдер карт",
+3
View File
@@ -1639,6 +1639,9 @@
"Location on Map": "Vị trí trên bản đồ",
"Update Geographic Location": "Cập nhật vị trí địa lý",
"Clear Geographic Location": "Xóa vị trí địa lý",
"Click on Map to Set Geographic Location": "Click on Map to Set Geographic Location",
"Enable Click to Set Location": "Enable Click to Set Location",
"Disable Click to Set Location": "Disable Click to Set Location",
"Unable to retrieve current position": "Không thể lấy vị trí hiện tại",
"Cannot Initialize Map": "Không thể khởi tạo bản đồ",
"Unsupported Map Provider": "Nhà cung cấp bản đồ không được hỗ trợ",
+3
View File
@@ -1639,6 +1639,9 @@
"Location on Map": "地图上的位置",
"Update Geographic Location": "更新地理位置",
"Clear Geographic Location": "清除地理位置",
"Click on Map to Set Geographic Location": "地图上点击设置地理位置",
"Enable Click to Set Location": "启用点击设置位置",
"Disable Click to Set Location": "禁用点击设置位置",
"Unable to retrieve current position": "无法获取当前地理位置",
"Cannot Initialize Map": "无法初始化地图",
"Unsupported Map Provider": "不支持的地图提供方",
+3
View File
@@ -1639,6 +1639,9 @@
"Location on Map": "地圖上的位置",
"Update Geographic Location": "更新地理位置",
"Clear Geographic Location": "清除地理位置",
"Click on Map to Set Geographic Location": "地圖上點選設定地理位置",
"Enable Click to Set Location": "啟用點選設定位置",
"Disable Click to Set Location": "停用點選設定位置",
"Unable to retrieve current position": "無法取得目前位置",
"Cannot Initialize Map": "無法初始化地圖",
"Unsupported Map Provider": "不支援的地圖提供者",
+9 -6
View File
@@ -1,5 +1,6 @@
import type { PartialRecord } from '@/core/base.ts';
import type { YearMonth, StartEndTime } from '@/core/datetime.ts';
import type { MapPosition } from '@/core/map.ts';
import { TransactionType } from '@/core/transaction.ts';
import { Account, type AccountInfoResponse } from './account.ts';
@@ -72,6 +73,11 @@ export class Transaction implements TransactionInfoResponse {
return this._geoLocation;
}
public set geoLocation(value: MapPosition) {
this._geoLocation = TransactionGeoLocation.of(value);
}
public get categoryId(): string {
return this.getCategoryId();
}
@@ -404,8 +410,8 @@ export class TransactionGeoLocation implements TransactionGeoLocationRequest {
return new TransactionGeoLocation(latitude, longitude);
}
public static of(geoLocation: TransactionGeoLocationRequest): TransactionGeoLocation {
return new TransactionGeoLocation(geoLocation.latitude, geoLocation.longitude);
public static of(mapPosition: MapPosition): TransactionGeoLocation {
return new TransactionGeoLocation(mapPosition.latitude, mapPosition.longitude);
}
}
@@ -496,10 +502,7 @@ export interface TransactionListInMonthByPageRequest {
readonly keyword: string;
}
export interface TransactionGeoLocationResponse {
readonly latitude: number;
readonly longitude: number;
}
export type TransactionGeoLocationResponse = MapPosition;
export interface TransactionInfoResponse {
readonly id: string;
@@ -79,6 +79,7 @@ export function useTransactionEditPageBase(type: TransactionEditPageType, initMo
const submitting = ref<boolean>(false);
const uploadingPicture = ref<boolean>(false);
const geoLocationStatus = ref<GeoLocationStatus | null>(null);
const setGeoLocationByClickMap = ref<boolean>(false);
const transaction = ref<Transaction | TransactionTemplate>(createNewTransactionModel(transactionDefaultType));
@@ -378,6 +379,7 @@ export function useTransactionEditPageBase(type: TransactionEditPageType, initMo
submitting,
uploadingPicture,
geoLocationStatus,
setGeoLocationByClickMap,
transaction,
// computed states
currentTimezoneOffsetMinutes,
@@ -8,10 +8,10 @@
<v-progress-circular indeterminate size="22" class="ml-2" v-if="loading"></v-progress-circular>
</div>
<v-btn density="comfortable" color="default" variant="text" class="ml-2" :icon="true"
:disabled="loading || submitting" v-if="mode !== TransactionEditPageMode.View">
:disabled="loading || submitting" v-if="mode !== TransactionEditPageMode.View && (activeTab === 'basicInfo' || (activeTab === 'map' && isSupportGetGeoLocationByClick()))">
<v-icon :icon="mdiDotsVertical" />
<v-menu activator="parent">
<v-list>
<v-list v-if="activeTab === 'basicInfo'">
<v-list-item :prepend-icon="mdiSwapHorizontal"
:title="tt('Swap Account')"
v-if="transaction.type === TransactionType.Transfer"
@@ -32,6 +32,19 @@
:title="tt('Hide Amount')"
v-if="!transaction.hideAmount" @click="transaction.hideAmount = true"></v-list-item>
</v-list>
<v-list v-if="activeTab === 'map'">
<v-list-item key="setGeoLocationByClickMap" value="setGeoLocationByClickMap"
:prepend-icon="mdiMapMarkerOutline"
:disabled="!transaction.geoLocation" v-if="isSupportGetGeoLocationByClick()">
<v-list-item-title class="cursor-pointer" @click="setGeoLocationByClickMap = !setGeoLocationByClickMap; geoMenuState = false">
<div class="d-flex align-center">
<span>{{ tt('Click on Map to Set Geographic Location') }}</span>
<v-spacer/>
<v-icon :icon="mdiCheck" v-if="setGeoLocationByClickMap" />
</div>
</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
</v-btn>
</div>
@@ -346,7 +359,7 @@
<v-window-item value="map">
<v-row>
<v-col cols="12" md="12">
<map-view ref="map" map-class="transaction-edit-map-view" :geo-location="transaction.geoLocation">
<map-view ref="map" map-class="transaction-edit-map-view" :geo-location="transaction.geoLocation" @click="updateSpecifiedGeoLocation">
<template #error-title="{ mapSupported, mapDependencyLoaded }">
<span class="text-subtitle-1" v-if="!mapSupported"><b>{{ tt('Unsupported Map Provider') }}</b></span>
<span class="text-subtitle-1" v-else-if="!mapDependencyLoaded"><b>{{ tt('Cannot Initialize Map') }}</b></span>
@@ -463,6 +476,7 @@ import { useTransactionTagsStore } from '@/stores/transactionTag.ts';
import { useTransactionsStore } from '@/stores/transaction.ts';
import { useTransactionTemplatesStore } from '@/stores/transactionTemplate.ts';
import type { MapPosition } from '@/core/map.ts';
import { CategoryType } from '@/core/category.ts';
import { TransactionType, TransactionEditScopeType } from '@/core/transaction.ts';
import { TemplateType, ScheduledTemplateFrequencyType } from '@/core/template.ts';
@@ -488,6 +502,9 @@ import {
isTransactionPicturesEnabled,
getMapProvider
} from '@/lib/server_settings.ts';
import {
isSupportGetGeoLocationByClick
} from '@/lib/map/index.ts';
import logger from '@/lib/logger.ts';
import {
@@ -495,6 +512,8 @@ import {
mdiEyeOffOutline,
mdiEyeOutline,
mdiSwapHorizontal,
mdiMapMarkerOutline,
mdiCheck,
mdiPound,
mdiMenuDown,
mdiImagePlusOutline,
@@ -537,6 +556,7 @@ const {
submitting,
uploadingPicture,
geoLocationStatus,
setGeoLocationByClickMap,
transaction,
defaultCurrency,
defaultAccountId,
@@ -671,6 +691,7 @@ function open(options: TransactionEditOptions): Promise<TransactionEditResponse
loading.value = true;
submitting.value = false;
geoLocationStatus.value = null;
setGeoLocationByClickMap.value = false;
originalTransactionEditable.value = false;
initCategoryId.value = options.categoryId;
@@ -1044,6 +1065,13 @@ function updateGeoLocation(forceUpdate: boolean): void {
geoLocationStatus.value = GeoLocationStatus.Getting;
}
function updateSpecifiedGeoLocation(mapPosition: MapPosition): void {
if (isSupportGetGeoLocationByClick() && setGeoLocationByClickMap.value) {
transaction.value.setLatitudeAndLongitude(mapPosition.latitude, mapPosition.longitude);
map.value?.setMarkerPosition(transaction.value.geoLocation);
}
}
function clearGeoLocation(): void {
geoMenuState.value = false;
geoLocationStatus.value = null;
@@ -344,6 +344,7 @@
</template>
<map-sheet v-model="transaction.geoLocation"
v-model:set-geo-location-by-click-map="setGeoLocationByClickMap"
v-model:show="showGeoLocationMapSheet">
</map-sheet>
</f7-list-item>
@@ -549,6 +550,7 @@ const {
submitting,
uploadingPicture,
geoLocationStatus,
setGeoLocationByClickMap,
transaction,
currentTimezoneOffsetMinutes,
defaultCurrency,