fix(phase3): subscription leak, image mime type validation

- Ledger store: add stopSubscription() to properly clean up realtime
  subscriptions, matching asset store pattern
- LedgerList: call stopSubscription on unmount
- Assets migration: restrict image upload to image/* mime types
  (C3 updateRule is a known tradeoff — PocketBase lacks field-level
  permissions, frontend enforces edit restrictions instead)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
congsh
2026-04-18 19:52:34 +08:00
parent c5413644f9
commit e4b730c8db
3 changed files with 18 additions and 5 deletions
@@ -112,7 +112,7 @@ migrate((db) => {
"presentable": false, "presentable": false,
"unique": false, "unique": false,
"options": { "options": {
"mimeTypes": null, "mimeTypes": ["image/*"],
"thumbs": ["200x200"], "thumbs": ["200x200"],
"maxSelect": 1, "maxSelect": 1,
"maxSize": 5242880, "maxSize": 5242880,
@@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, onMounted, watch } from 'vue' import { ref, computed, onMounted, onUnmounted, watch } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus' import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus } from '@element-plus/icons-vue' import { Plus } from '@element-plus/icons-vue'
import { useLedgerStore } from '@/stores/ledger' import { useLedgerStore } from '@/stores/ledger'
@@ -139,6 +139,10 @@ onMounted(() => {
} }
}) })
onUnmounted(() => {
ledgerStore.stopSubscription()
})
watch(() => groupStore.currentGroupId, (newId, oldId) => { watch(() => groupStore.currentGroupId, (newId, oldId) => {
if (newId && newId !== oldId) { if (newId && newId !== oldId) {
loadData() loadData()
+12 -3
View File
@@ -16,6 +16,7 @@ export const useLedgerStore = defineStore('ledger', () => {
const loading = ref(false) const loading = ref(false)
const summary = ref({ totalIncome: 0, totalExpense: 0, balance: 0 }) const summary = ref({ totalIncome: 0, totalExpense: 0, balance: 0 })
const currentMonth = ref(new Date().toISOString().slice(0, 7)) const currentMonth = ref(new Date().toISOString().slice(0, 7))
let unsubFn: (() => Promise<void> | void) | null = null
// 加载账目列表和汇总 // 加载账目列表和汇总
async function loadLedgers(groupId: string, month?: string) { async function loadLedgers(groupId: string, month?: string) {
@@ -78,12 +79,19 @@ export const useLedgerStore = defineStore('ledger', () => {
// 订阅实时更新 // 订阅实时更新
async function startSubscription(groupId: string) { async function startSubscription(groupId: string) {
await subscribeLedgers(groupId, () => { stopSubscription()
// 收到变更后重新加载当前月份的数据 unsubFn = await subscribeLedgers(groupId, () => {
loadLedgers(groupId) loadLedgers(groupId)
}) })
} }
function stopSubscription() {
if (unsubFn) {
unsubFn()
unsubFn = null
}
}
return { return {
ledgers, ledgers,
loading, loading,
@@ -94,6 +102,7 @@ export const useLedgerStore = defineStore('ledger', () => {
addLedger, addLedger,
editLedger, editLedger,
removeLedger, removeLedger,
startSubscription startSubscription,
stopSubscription
} }
}) })