diff --git a/README.md b/README.md index 073adf0..6dd49a7 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,7 @@ src/ 打卡为匿名本机 POC:`src/data/check-in/tasks.ts` 会为当前 POI 仓库中的全部已发布点位生成打卡任务;点位下线后会自动停止新打卡,重新发布后仍保留本机历史记录以防重复领奖。所有点位的游客入口坐标与安全到达范围仍需 iOS 现场复核后才能用于正式验收。每个开放点位首次成功打卡增加 10 积分,累计完成 1、2、3 个开放点位时依次解锁徽章。积分、徽章与足迹使用独立版本化存储键;v1 旧格式首次升级时只迁移当前开放点位记录,进入 v2 后则保留合法历史足迹。清除操作只删除打卡数据,不影响路线和偏好。打卡记录不保存经纬度、定位精度或轨迹。微信客户端无法可靠识别系统级缓存位置或虚拟定位,因此本期只承诺每次点击重新调用定位接口并校验微信返回的精度,不提供虚拟定位测试入口或生产级反作弊承诺。 -路由来自页面 SFC 的 ``,由 UniPages 在编译时生成 `src/pages.json`;应用清单由 `manifest.config.ts` 生成 `src/manifest.json`。不要直接修改这两个生成文件,也不要让微信开发者工具读取 `src`;IDE 只读取 `dist/dev/mp-weixin`。 +路由来自页面 SFC 的 ``,由 UniPages 在编译时生成 `src/pages.json`;应用清单由 `manifest.config.ts` 生成 `src/manifest.json`。不要直接修改这两个生成文件,也不要让微信开发者工具读取 `src`;IDE 只读取 `dist/dev/mp-weixin`。仓库中的 `src/manifest.json` 以开发模式快照为准,`【dev】` / `【prod】` 项目名前缀由构建模式自动生成;提交时不应单独保留由最后一次构建造成的前缀漂移。 ## POC 数据声明 diff --git a/project.config.json b/project.config.json index 93967fa..9af4b53 100644 --- a/project.config.json +++ b/project.config.json @@ -24,7 +24,7 @@ "ignore": [], "include": [] }, - "appid": "wx946b42048d117dde", + "appid": "wxda4fb5048b5dc9a1", "editorSetting": {}, "condition": {} } diff --git a/server/app/schemas.py b/server/app/schemas.py index ec8b66b..95098d2 100644 --- a/server/app/schemas.py +++ b/server/app/schemas.py @@ -25,11 +25,6 @@ class PlanningMode(str, Enum): CUSTOM = "custom" -class Duration(str, Enum): - HALF_DAY = "half_day" - FULL_DAY = "full_day" - - class Pace(str, Enum): RELAXED = "relaxed" MODERATE = "moderate" @@ -42,12 +37,6 @@ class Transport(str, Enum): PUBLIC_TRANSPORT = "public_transport" -class BudgetLevel(str, Enum): - ECONOMY = "economy" - STANDARD = "standard" - QUALITY = "quality" - - Theme = Literal["亲子", "情侣", "朋友", "银发", "研学"] Interest = Literal["自然风光", "文化场馆", "生态科普", "美食", "摄影"] diff --git a/src/domain/travel/local-planner.ts b/src/domain/travel/local-planner.ts index fbc65ef..959fcb1 100644 --- a/src/domain/travel/local-planner.ts +++ b/src/domain/travel/local-planner.ts @@ -294,6 +294,13 @@ function selectQuickPois( cursor = next } + if (selected.length === 0 && remaining.length > 0) { + throw new TravelValidationError( + '当前起点、交通方式与所选时长无法容纳首个点位', + 'quick_route_unreachable', + ) + } + return selected } diff --git a/src/domain/travel/preferences.ts b/src/domain/travel/preferences.ts index acc4791..73b7bca 100644 --- a/src/domain/travel/preferences.ts +++ b/src/domain/travel/preferences.ts @@ -16,8 +16,10 @@ import { const DESTINATION = '深圳市光明区' as const +export type TravelValidationErrorCode = 'quick_route_unreachable' + export class TravelValidationError extends Error { - constructor(message: string) { + constructor(message: string, readonly code?: TravelValidationErrorCode) { super(message) this.name = 'TravelValidationError' } diff --git a/src/manifest.json b/src/manifest.json index ffb7ccf..5c91b34 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -46,7 +46,7 @@ }, "quickapp": {}, "mp-weixin": { - "appid": "wx946b42048d117dde", + "appid": "wxda4fb5048b5dc9a1", "setting": { "urlCheck": true }, diff --git a/src/pages/check-in/records.vue b/src/pages/check-in/records.vue index b3ffb68..86c4167 100644 --- a/src/pages/check-in/records.vue +++ b/src/pages/check-in/records.vue @@ -12,6 +12,7 @@ interface BadgeView extends CheckInBadgeDefinition { const profile = ref({ points: 0, badges: [], checkIns: [] }) const storageError = ref('') +const poiDirectoryError = ref('') const availablePois = shallowRef([]) const badgeViews = computed(() => CHECK_IN_BADGES.map(definition => ({ ...definition, @@ -31,7 +32,20 @@ function formatTime(value: string): string { } function openPoi(poiId: string) { - if (!getPoiRepository().getPoiById(poiId)) { + let poi: PoiResolved | null = null + try { + poi = getPoiRepository().getPoiById(poiId) + } + catch (error) { + poiDirectoryError.value = error instanceof Error ? error.message : '开放打卡点位暂时无法读取' + uni.showModal({ + title: '暂时无法打开点位', + content: '开放点位目录暂时无法读取,请稍后重新加载。', + showCancel: false, + }) + return + } + if (!poi) { uni.showModal({ title: '点位已下线', content: '该点位暂不在当前开放列表中,本机历史打卡记录仍会保留。', @@ -42,12 +56,35 @@ function openPoi(poiId: string) { uni.navigateTo({ url: `/pages/poi/detail?poiId=${encodeURIComponent(poiId)}` }) } +function loadProfile() { + try { + profile.value = getCheckInProfile() + storageError.value = '' + } + catch (error) { + profile.value = { points: 0, badges: [], checkIns: [] } + storageError.value = error instanceof Error ? error.message : '本机打卡记录暂时无法读取' + } +} + function loadAvailablePois() { - const repository = getPoiRepository() - availablePois.value = getCheckInTasks().flatMap((task) => { - const poi = repository.getPoiById(task.poiId) - return poi ? [poi] : [] - }) + try { + const repository = getPoiRepository() + availablePois.value = getCheckInTasks().flatMap((task) => { + const poi = repository.getPoiById(task.poiId) + return poi ? [poi] : [] + }) + poiDirectoryError.value = '' + } + catch (error) { + availablePois.value = [] + poiDirectoryError.value = error instanceof Error ? error.message : '开放打卡点位暂时无法读取' + } +} + +function loadRecords() { + loadProfile() + loadAvailablePois() } function poiCheckedIn(poiId: string): boolean { @@ -80,16 +117,7 @@ function clearLocalRecords() { }) } -onShow(() => { - loadAvailablePois() - try { - profile.value = getCheckInProfile() - storageError.value = '' - } - catch (error) { - storageError.value = error instanceof Error ? error.message : '本机打卡记录暂时无法读取' - } -}) +onShow(loadRecords)