feat: 添加HTTP客户端、API接口、地图组件及复用函数

This commit is contained in:
zhouxhere 2025-03-01 13:08:30 +08:00
parent c3004d0780
commit c5c713c908
6 changed files with 219 additions and 0 deletions

32
web/apis/feature.ts Normal file
View File

@ -0,0 +1,32 @@
const base = "/api/v1/feature";
export interface FeatureListParams {
name?: string;
status?: string;
page: number;
size: number;
}
export interface FeatureCreateParams {}
export interface FeatureUpdateParams {}
export const list = async <T>(params: FeatureListParams) => {
return useHttp<T>(base + "s", { method: "GET", params });
};
// export const detail = async (id: string) => {
// return useHttp.get(base + "/" + id);
// };
// export const create = async (params: FeatureCreateParams) => {
// return useHttp.post(base, params);
// };
// export const update = async (params: FeatureUpdateParams) => {
// return useHttp.put(base, params);
// };
// export const remove = async (id: string) => {
// return useHttp.delete(base + "/" + id);
// };

5
web/apis/index.ts Normal file
View File

@ -0,0 +1,5 @@
import * as feature from "./feature";
export default {
feature,
};

View File

@ -0,0 +1,90 @@
<script setup lang="ts">
import maplibregl from "maplibre-gl";
import "maplibre-gl/dist/maplibre-gl.css";
import * as turf from "@turf/turf";
import MapboxDraw from "@mapbox/mapbox-gl-draw";
import "@mapbox/mapbox-gl-draw/dist/mapbox-gl-draw.css";
const mapContainer = useTemplateRef("mapContainer");
const map = ref<maplibregl.Map>();
const {feature} = useApi();
MapboxDraw.constants.classes.CANVAS = 'maplibregl-canvas';
MapboxDraw.constants.classes.CONTROL_BASE = 'maplibregl-ctrl';
MapboxDraw.constants.classes.CONTROL_PREFIX = 'maplibregl-ctrl-';
MapboxDraw.constants.classes.CONTROL_GROUP = 'maplibregl-ctrl-group';
MapboxDraw.constants.classes.ATTRIBUTION = 'maplibregl-ctrl-attrib';
MapboxDraw.lib.theme.find((x: any) => x.id === 'gl-draw-lines').paint['line-dasharray'] = [0.2, 2]
onMounted(async () => {
if (!mapContainer.value) return;
map.value = new maplibregl.Map({
container: mapContainer.value, // container id
style: "https://demotiles.maplibre.org/style.json", // style URL
center: [0, 0], // starting position [lng, lat]
zoom: 12, // starting zoom
});
// map.value.removeControl(map.value._controls[0]);
const draw = new MapboxDraw({
displayControlsDefault: false,
controls: {
polygon: true,
trash: true
}
})
const updateArea = (e: any) => {
const data = draw.getAll();
console.log(data)
}
map.value.addControl(draw, "top-right");
map.value.on('draw.create', updateArea);
map.value.on('draw.delete', updateArea);
map.value.on('draw.update', updateArea);
map.value.on('draw.selectionchange', (e: any) => {
console.log(e)
});
const {data, status} = await feature.list<any>({page: 1, size: 10});
if (status.value !== 'success') return;
let jsonData = turf.featureCollection(data.value.data.map((x: any) => turf.feature(x.geometry, {
id: x.id,
name: x.name
})));
console.log(jsonData);
map.value.addSource("data", {type: "geojson", data: jsonData});
map.value.addLayer({
id: "data-symbol",
type: "symbol",
source: "data",
paint: {
"text-color": "#000",
},
layout: {
"text-field": ["get", "name"],
"text-font": ["Open Sans Regular", "Arial Unicode MS Regular"],
"text-size": 12,
},
});
map.value.addLayer({
id: "data-fill",
type: "fill",
source: "data",
paint: {
"fill-color": "#000",
"fill-opacity": 0.5,
"fill-outline-color": "#FF0000", // 线
},
})
});
</script>
<template>
<div ref="mapContainer" class="w-full h-full"/>
</template>

3
web/composables/index.ts Normal file
View File

@ -0,0 +1,3 @@
import apis from "~/apis";
export const useApi = () => apis

View File

@ -0,0 +1,8 @@
import type { UseFetchOptions } from "nuxt/app";
export const useHttp = <T>(
url: string | (() => string),
options: UseFetchOptions<T> = {}
) => {
return useFetch(url, { ...options, $fetch: useNuxtApp().$http });
};

81
web/plugins/http.ts Normal file
View File

@ -0,0 +1,81 @@
import type { FetchResponse, SearchParameters } from "ofetch";
export interface ResponseOptions<T> {
data: T;
code: number;
message: string;
}
export default defineNuxtPlugin((nuxtApp) => {
const err = (text: string) => {
console.log(text);
};
const handleStatus: { [key: number]: () => void } = {
404: () => err("服务器资源不存在"),
500: () => err("服务器内部错误"),
403: () => err("没有权限访问该资源"),
401: () => err("登录状态已过期,需要重新登录"),
};
const handleError = <T>(
response: FetchResponse<ResponseOptions<T>> & FetchResponse<ResponseType>
) => {
if (!response._data) {
err("请求超时,服务器无响应!");
return;
}
if (handleStatus[response.status]) {
handleStatus[response.status]();
} else {
err("未知错误!");
}
};
const paramsSerializer = (params?: SearchParameters) => {
if (!params) return;
const query = useCloned(params, { deep: true }) as any;
Object.entries(query).forEach(([key, value]) => {
if (typeof value === "object" && Array.isArray(value) && value !== null) {
query[`${key}[]`] = toRaw(value).map((val: any) => JSON.stringify(val));
delete query[key];
}
});
return query;
};
const http = $fetch.create({
onRequest({ options }) {
options.params = paramsSerializer(options.params);
//TODO 添加token
},
onResponse({ response }) {
if (
response.headers.get("content-disposition") &&
response.status === 200
) {
return
}
if (response._data.code !== 200) {
handleError(response);
return Promise.reject(response._data);
}
response._data = response._data.data
return
},
onResponseError({ response }) {
handleError(response);
return Promise.reject(response?._data ?? null);
},
});
return {
provide: {
http: http,
},
};
});