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