upgrade cal

This commit is contained in:
Julian Freeman
2026-03-27 12:36:35 -04:00
parent 07faa81d21
commit 0e39ad7ed8
9 changed files with 103 additions and 10 deletions

View File

@@ -1,7 +1,7 @@
{
"name": "chrono-snap",
"private": true,
"version": "0.2.2",
"version": "0.2.3",
"type": "module",
"scripts": {
"dev": "vite",

2
src-tauri/Cargo.lock generated
View File

@@ -667,7 +667,7 @@ dependencies = [
[[package]]
name = "chrono-snap"
version = "0.2.2"
version = "0.2.3"
dependencies = [
"anyhow",
"base64 0.22.1",

View File

@@ -1,6 +1,6 @@
[package]
name = "chrono-snap"
version = "0.2.2"
version = "0.2.3"
description = "An app to record screens and events"
authors = ["you"]
edition = "2021"

View File

@@ -227,3 +227,37 @@ pub fn get_overdue_reminders_count(path: &str, date: &str, minute: i32) -> anyho
let count: i32 = stmt.query_row(params![date, minute], |row| row.get(0))?;
Ok(count)
}
#[derive(Serialize, Deserialize)]
pub struct DayStatus {
pub date: String,
pub has_overdue: bool,
pub has_upcoming: bool,
}
pub fn get_reminders_by_month(path: &str, year_month: &str, today: &str, now_minute: i32) -> anyhow::Result<Vec<DayStatus>> {
let conn = Connection::open(path)?;
// 查找该月份内有提醒的所有日期
let mut stmt = conn.prepare("
SELECT date,
MAX(CASE WHEN is_completed = 0 AND (date < ?2 OR (date = ?2 AND minute < ?3)) THEN 1 ELSE 0 END) as has_overdue,
MAX(CASE WHEN is_completed = 0 AND (date > ?2 OR (date = ?2 AND minute >= ?3)) THEN 1 ELSE 0 END) as has_upcoming
FROM reminders
WHERE date LIKE ?1
GROUP BY date
")?;
let rows = stmt.query_map(params![format!("{}%", year_month), today, now_minute], |row| {
Ok(DayStatus {
date: row.get(0)?,
has_overdue: row.get::<_, i32>(1)? == 1,
has_upcoming: row.get::<_, i32>(2)? == 1,
})
})?;
let mut results = Vec::new();
for row in rows {
results.push(row?);
}
Ok(results)
}

View File

@@ -107,6 +107,13 @@ pub fn get_overdue_reminders_count(state: tauri::State<'_, AppState>, date: Stri
crate::db::get_overdue_reminders_count(path, &date, minute).map_err(|e| e.to_string())
}
#[tauri::command]
pub fn get_reminders_by_month(state: tauri::State<'_, AppState>, year_month: String, today: String, now_minute: i32) -> Result<Vec<crate::db::DayStatus>, String> {
let path = state.db_path.lock().unwrap();
let path = path.as_ref().ok_or("Database path not set")?;
crate::db::get_reminders_by_month(path, &year_month, &today, now_minute).map_err(|e| e.to_string())
}
#[tauri::command]
pub fn update_interval(state: tauri::State<'_, AppState>, seconds: u64) {
state.capture_interval_secs.store(seconds, Ordering::SeqCst);

View File

@@ -37,6 +37,7 @@ pub fn run() {
engine::delete_reminder,
engine::toggle_reminder,
engine::get_overdue_reminders_count,
engine::get_reminders_by_month,
engine::write_file
])
.setup(|app| {

View File

@@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "ChronoSnap",
"version": "0.2.2",
"version": "0.2.3",
"identifier": "top.volan.chrono-snap",
"build": {
"beforeDevCommand": "pnpm dev",
@@ -12,7 +12,7 @@
"app": {
"windows": [
{
"title": "瞬影 - 时间记录 v0.2.2",
"title": "瞬影 - 时间记录 v0.2.3",
"width": 1760,
"height": 1100
}

View File

@@ -14,7 +14,8 @@ import {
retainDays, captureInterval,
TIME_OFFSET_MINUTES, TOTAL_MINUTES, getTagColor, getTagName, mainTags, getSubTags,
logicalMinutesToTime, logicalMinutesFromTime, formatDuration,
loadTags, loadEvents, loadReminders, toISODate, refreshBadgeCount
loadTags, loadEvents, loadReminders, toISODate, refreshBadgeCount,
calendarStatus, loadCalendarStatus, currentCalendarMonthStr
} from "./store";
import { DBEvent, TimelineItem } from "./types";
@@ -90,7 +91,7 @@ const checkReminders = async (currentMin: number) => {
for (const r of dueReminders) {
if (hasPermission) {
sendNotification({ title: '', body: r.content });
sendNotification({ title: '瞬影 提醒', body: r.content });
}
}
}
@@ -108,7 +109,7 @@ const updateCurrentMinute = () => {
} else {
currentLogicalMinute.value = -1;
}
// 无论是否看今天,每分钟都尝试刷新全局徽章
// 无论是否看今天,每分钟都尝试刷新全局徽章和日历状态点
refreshBadgeCount();
};
@@ -126,6 +127,13 @@ const startMinuteTimer = () => {
}, msUntilNextMinute);
};
watch(calendarMonth, (newMonth) => {
const y = newMonth.getFullYear();
const m = String(newMonth.getMonth() + 1).padStart(2, '0');
currentCalendarMonthStr.value = `${y}-${m}`;
loadCalendarStatus(currentCalendarMonthStr.value);
}, { immediate: true });
watch(currentDate, () => {
updateCurrentMinute();
});
@@ -392,8 +400,23 @@ const togglePause = async () => {
</div>
<div class="grid grid-cols-7 gap-1 text-center mb-2"><div v-for="d in ['一','二','三','四','五','六','日']" :key="d" class="text-[10px] font-bold text-text-sec">{{d}}</div></div>
<div class="grid grid-cols-7 gap-1">
<div v-for="(date, i) in calendarDays" :key="i" class="aspect-square flex items-center justify-center">
<button v-if="date" @click="selectCalendarDate(date)" class="w-8 h-8 rounded-full text-xs font-medium transition-all" :class="date.toLocaleDateString('sv') === currentDate ? 'bg-[#007AFF] text-white font-bold' : 'hover:bg-bg-input text-main'">{{ date.getDate() }}</button>
<div v-for="(date, i) in calendarDays" :key="i" class="aspect-square flex flex-col items-center justify-center relative">
<button v-if="date" @click="selectCalendarDate(date)"
class="w-8 h-8 rounded-full text-xs font-medium transition-all flex items-center justify-center relative"
:class="[
date.toLocaleDateString('sv') === getLogicDateStr()
? 'bg-[#007AFF] text-white font-black shadow-[0_4px_12px_rgba(0,122,255,0.4)] ' + (date.toLocaleDateString('sv') === currentDate ? 'ring-2 ring-offset-2 ring-[#007AFF]' : '')
: (date.toLocaleDateString('sv') === currentDate
? 'bg-bg-input text-[#007AFF] font-black ring-1 ring-border-main shadow-sm'
: 'hover:bg-bg-input text-main')
]">
{{ date.getDate() }}
</button>
<!-- Status Dots -->
<div v-if="date && calendarStatus[date.toLocaleDateString('sv')]" class="absolute -bottom-1.5 flex gap-0.5">
<div v-if="calendarStatus[date.toLocaleDateString('sv')].has_overdue" class="w-1.5 h-1.5 rounded-full bg-[#FF3B30] border-[1.5px] border-bg-card shadow-sm"></div>
<div v-if="calendarStatus[date.toLocaleDateString('sv')].has_upcoming" class="w-1.5 h-1.5 rounded-full bg-[#007AFF] border-[1.5px] border-bg-card shadow-sm"></div>
</div>
</div>
</div>
</div>

View File

@@ -52,6 +52,7 @@ export const lockedImage = ref<TimelineItem | null>(null);
export const tags = ref<Tag[]>([]);
export const dayEvents = ref<DBEvent[]>([]);
export const reminders = ref<Reminder[]>([]);
export const calendarStatus = ref<Record<string, { has_overdue: boolean, has_upcoming: boolean }>>({});
export const timelineImages = ref<TimelineItem[]>([]);
export const refreshSignal = ref(0); // Counter to trigger dashboard refreshes
@@ -114,6 +115,33 @@ export const loadEvents = async () => {
export const loadReminders = async () => {
reminders.value = await invoke("get_reminders", { date: currentDate.value });
refreshBadgeCount();
refreshCalendarStatus(); // 提醒更新时也刷新日历状态
};
export const loadCalendarStatus = async (yearMonth: string) => {
if (!dbPath.value) return;
try {
const { date, minute } = getRealNowLogicalTime();
const statuses: any[] = await invoke("get_reminders_by_month", {
yearMonth,
today: date,
nowMinute: minute
});
const map: Record<string, any> = {};
statuses.forEach(s => { map[s.date] = s; });
calendarStatus.value = map;
} catch (e) {
console.error("Failed to load calendar status:", e);
}
};
// 当前显示的日历月份,用于自动刷新状态
export const currentCalendarMonthStr = ref("");
export const refreshCalendarStatus = () => {
if (currentCalendarMonthStr.value) {
loadCalendarStatus(currentCalendarMonthStr.value);
}
};
export const overdueCount = ref(0);