7 changed files with 410 additions and 7 deletions
@ -0,0 +1,109 @@ |
|||||
|
import { useCallback, useEffect, useState } from "react"; |
||||
|
|
||||
|
const STORAGE_KEY = "wuyuanbiaoba_location_config"; |
||||
|
const DEFAULT_LOCATION = { |
||||
|
longitude: 0, |
||||
|
latitude: 0, |
||||
|
}; |
||||
|
|
||||
|
const normalizeNumber = (value) => { |
||||
|
const num = Number(value); |
||||
|
return Number.isFinite(num) ? num : 0; |
||||
|
}; |
||||
|
|
||||
|
const normalizeLocation = (data) => ({ |
||||
|
longitude: normalizeNumber(data?.longitude), |
||||
|
latitude: normalizeNumber(data?.latitude), |
||||
|
}); |
||||
|
|
||||
|
const readFromLocalStorage = () => { |
||||
|
try { |
||||
|
const raw = localStorage.getItem(STORAGE_KEY); |
||||
|
return raw ? normalizeLocation(JSON.parse(raw)) : DEFAULT_LOCATION; |
||||
|
} catch (error) { |
||||
|
console.error("读取本地经纬度配置失败:", error); |
||||
|
return DEFAULT_LOCATION; |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
const saveToLocalStorage = (location) => { |
||||
|
try { |
||||
|
localStorage.setItem(STORAGE_KEY, JSON.stringify(location)); |
||||
|
} catch (error) { |
||||
|
console.error("保存本地经纬度配置失败:", error); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
export const useLocationConfig = () => { |
||||
|
const [location, setLocation] = useState(DEFAULT_LOCATION); |
||||
|
const [loading, setLoading] = useState(true); |
||||
|
const [storageMode, setStorageMode] = useState("device"); |
||||
|
|
||||
|
const loadLocation = useCallback(async () => { |
||||
|
setLoading(true); |
||||
|
try { |
||||
|
const response = await fetch("/location-config"); |
||||
|
if (!response.ok) { |
||||
|
throw new Error(`HTTP ${response.status}`); |
||||
|
} |
||||
|
|
||||
|
const data = normalizeLocation(await response.json()); |
||||
|
setLocation(data); |
||||
|
saveToLocalStorage(data); |
||||
|
setStorageMode("device"); |
||||
|
return data; |
||||
|
} catch (error) { |
||||
|
console.warn("设备端经纬度配置不可用,回退至浏览器存储:", error); |
||||
|
const fallback = readFromLocalStorage(); |
||||
|
setLocation(fallback); |
||||
|
setStorageMode("browser"); |
||||
|
return fallback; |
||||
|
} finally { |
||||
|
setLoading(false); |
||||
|
} |
||||
|
}, []); |
||||
|
|
||||
|
const saveLocation = useCallback(async (nextLocation) => { |
||||
|
const normalized = normalizeLocation(nextLocation); |
||||
|
|
||||
|
try { |
||||
|
const response = await fetch("/location-config", { |
||||
|
method: "POST", |
||||
|
headers: { |
||||
|
"Content-Type": "application/json", |
||||
|
}, |
||||
|
body: JSON.stringify(normalized), |
||||
|
}); |
||||
|
|
||||
|
if (!response.ok) { |
||||
|
throw new Error(`HTTP ${response.status}`); |
||||
|
} |
||||
|
|
||||
|
const saved = normalizeLocation(await response.json()); |
||||
|
setLocation(saved); |
||||
|
saveToLocalStorage(saved); |
||||
|
setStorageMode("device"); |
||||
|
return { success: true, data: saved, storageMode: "device" }; |
||||
|
} catch (error) { |
||||
|
console.warn("设备端保存失败,改为浏览器本地保存:", error); |
||||
|
saveToLocalStorage(normalized); |
||||
|
setLocation(normalized); |
||||
|
setStorageMode("browser"); |
||||
|
return { success: true, data: normalized, storageMode: "browser" }; |
||||
|
} |
||||
|
}, []); |
||||
|
|
||||
|
useEffect(() => { |
||||
|
loadLocation(); |
||||
|
}, [loadLocation]); |
||||
|
|
||||
|
return { |
||||
|
location, |
||||
|
loading, |
||||
|
storageMode, |
||||
|
loadLocation, |
||||
|
saveLocation, |
||||
|
}; |
||||
|
}; |
||||
|
|
||||
|
export default useLocationConfig; |
||||
@ -0,0 +1,96 @@ |
|||||
|
const fs = require('fs-extra'); |
||||
|
const path = require('path'); |
||||
|
|
||||
|
const CONFIG_DIR = path.join(process.cwd(), 'data'); |
||||
|
const CONFIG_PATH = path.join(CONFIG_DIR, 'location-config.json'); |
||||
|
|
||||
|
const DEFAULT_LOCATION = { |
||||
|
longitude: 0, |
||||
|
latitude: 0, |
||||
|
}; |
||||
|
|
||||
|
const readRawBody = (req) => |
||||
|
new Promise((resolve, reject) => { |
||||
|
let raw = ''; |
||||
|
req.setEncoding('utf8'); |
||||
|
req.on('data', (chunk) => { |
||||
|
raw += chunk; |
||||
|
}); |
||||
|
req.on('end', () => resolve(raw)); |
||||
|
req.on('error', reject); |
||||
|
}); |
||||
|
|
||||
|
const parseRequestBody = async (ctx) => { |
||||
|
if (ctx.request?.body && Object.keys(ctx.request.body).length > 0) { |
||||
|
return ctx.request.body; |
||||
|
} |
||||
|
|
||||
|
const raw = await readRawBody(ctx.req); |
||||
|
if (!raw) { |
||||
|
return {}; |
||||
|
} |
||||
|
|
||||
|
try { |
||||
|
return JSON.parse(raw); |
||||
|
} catch (error) { |
||||
|
console.error('解析经纬度配置请求体失败:', error); |
||||
|
return {}; |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
const normalizeNumber = (value) => { |
||||
|
const num = Number(value); |
||||
|
return Number.isFinite(num) ? num : 0; |
||||
|
}; |
||||
|
|
||||
|
const ensureConfigDir = async () => { |
||||
|
await fs.ensureDir(CONFIG_DIR); |
||||
|
}; |
||||
|
|
||||
|
const readLocationConfig = async () => { |
||||
|
try { |
||||
|
await ensureConfigDir(); |
||||
|
const exists = await fs.pathExists(CONFIG_PATH); |
||||
|
if (!exists) { |
||||
|
return DEFAULT_LOCATION; |
||||
|
} |
||||
|
|
||||
|
const content = await fs.readJson(CONFIG_PATH); |
||||
|
return { |
||||
|
longitude: normalizeNumber(content?.longitude), |
||||
|
latitude: normalizeNumber(content?.latitude), |
||||
|
}; |
||||
|
} catch (error) { |
||||
|
console.error('读取经纬度配置失败:', error); |
||||
|
return DEFAULT_LOCATION; |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
module.exports.getLocationConfig = async (ctx) => { |
||||
|
ctx.status = 200; |
||||
|
ctx.body = await readLocationConfig(); |
||||
|
}; |
||||
|
|
||||
|
module.exports.saveLocationConfig = async (ctx) => { |
||||
|
try { |
||||
|
const requestBody = await parseRequestBody(ctx); |
||||
|
const longitude = normalizeNumber(requestBody?.longitude); |
||||
|
const latitude = normalizeNumber(requestBody?.latitude); |
||||
|
|
||||
|
await ensureConfigDir(); |
||||
|
const data = { longitude, latitude }; |
||||
|
await fs.writeJson(CONFIG_PATH, data, { spaces: 2 }); |
||||
|
ctx.status = 200; |
||||
|
ctx.body = { |
||||
|
success: true, |
||||
|
...data, |
||||
|
}; |
||||
|
} catch (error) { |
||||
|
console.error('保存经纬度配置失败:', error); |
||||
|
ctx.status = 500; |
||||
|
ctx.body = { |
||||
|
success: false, |
||||
|
message: 'save location config failed', |
||||
|
}; |
||||
|
} |
||||
|
}; |
||||
Loading…
Reference in new issue