diff --git a/apps/web/src/components/QuickEntryDrawer.vue b/apps/web/src/components/QuickEntryDrawer.vue
index d7fba4e..9a4250e 100644
--- a/apps/web/src/components/QuickEntryDrawer.vue
+++ b/apps/web/src/components/QuickEntryDrawer.vue
@@ -24,7 +24,7 @@ const props = defineProps<{
ledgerId: string;
ledgerName: string;
personalLedgerId: string;
- ledgerOptions: Array<{ id: string; name: string; color: string; isPersonal: boolean }>;
+ ledgerOptions: Array<{ id: string; name: string; gradient: string; isPersonal: boolean }>;
}>();
const emit = defineEmits<{
@@ -541,7 +541,7 @@ function saveEntry() {
diff --git a/apps/web/src/components/QuickEntryHost.vue b/apps/web/src/components/QuickEntryHost.vue
index 0afdd33..a473d23 100644
--- a/apps/web/src/components/QuickEntryHost.vue
+++ b/apps/web/src/components/QuickEntryHost.vue
@@ -3,6 +3,7 @@ import { BarChart3, BookOpen, Check, CircleUserRound, LibraryBig, Plus } from "@
import { computed, onMounted, ref } from "vue";
import { useRoute } from "vue-router";
import { createEntry } from "../data/entries";
+import { ledgerTheme } from "../data/ledgers";
import { useEntryStore } from "../stores/entries";
import { useAuthStore } from "../stores/auth";
import { useLedgerStore } from "../stores/ledgers";
@@ -16,7 +17,7 @@ const drawerOpen = ref(false);
const savedToast = ref(false);
const ledgerOptions = computed(() =>
- ledgerStore.ledgers.map((ledger) => ({ id: ledger.id, name: ledger.name, color: ledger.color, isPersonal: ledger.isPersonal })),
+ ledgerStore.ledgers.map((ledger) => ({ id: ledger.id, name: ledger.name, gradient: ledgerTheme(ledger.theme).gradient, isPersonal: ledger.isPersonal })),
);
onMounted(async () => {
diff --git a/apps/web/src/data/ledgers.ts b/apps/web/src/data/ledgers.ts
index e2d88ea..c265162 100644
--- a/apps/web/src/data/ledgers.ts
+++ b/apps/web/src/data/ledgers.ts
@@ -1,9 +1,28 @@
-import type { CurrencyCode } from "@cents/domain";
+import { ledgerThemeAccents, type CurrencyCode, type LedgerThemeId } from "@cents/domain";
+
+export const ledgerThemes: ReadonlyArray<{
+ id: LedgerThemeId;
+ name: string;
+ accent: string;
+ gradient: string;
+}> = [
+ { id: "jade", name: "翡翠", accent: ledgerThemeAccents.jade, gradient: "linear-gradient(135deg, #118b79 0%, #087f72 48%, #285765 100%)" },
+ { id: "ocean", name: "海湾", accent: ledgerThemeAccents.ocean, gradient: "linear-gradient(135deg, #167b93 0%, #3459a4 52%, #4b3f7f 100%)" },
+ { id: "coral", name: "晚霞", accent: ledgerThemeAccents.coral, gradient: "linear-gradient(135deg, #c94f45 0%, #ad4869 54%, #6e4d82 100%)" },
+ { id: "berry", name: "莓果", accent: ledgerThemeAccents.berry, gradient: "linear-gradient(135deg, #a84378 0%, #71468d 52%, #40578a 100%)" },
+ { id: "meadow", name: "森野", accent: ledgerThemeAccents.meadow, gradient: "linear-gradient(135deg, #25805f 0%, #4e794b 54%, #766b35 100%)" },
+ { id: "graphite", name: "暮色", accent: ledgerThemeAccents.graphite, gradient: "linear-gradient(135deg, #35585d 0%, #4e556e 52%, #303f55 100%)" },
+];
+
+export function ledgerTheme(themeId?: string) {
+ return ledgerThemes.find((theme) => theme.id === themeId) ?? ledgerThemes[0];
+}
export type LedgerRecord = {
id: string;
name: string;
color: string;
+ theme: LedgerThemeId;
defaultCurrency: CurrencyCode;
createdAt: string;
updatedAt: string;
@@ -31,6 +50,7 @@ export function createDefaultLedgers(): LedgerRecord[] {
id: "local-ledger",
name: "家庭日常",
color: "#087f72",
+ theme: "jade",
defaultCurrency: "CNY",
createdAt: now,
updatedAt: now,
@@ -41,6 +61,7 @@ export function createDefaultLedgers(): LedgerRecord[] {
id: "travel-ledger",
name: "旅行账本",
color: "#3478e5",
+ theme: "ocean",
defaultCurrency: "CNY",
createdAt: now,
updatedAt: now,
diff --git a/apps/web/src/stores/ledgers.ts b/apps/web/src/stores/ledgers.ts
index 350252c..37bcc84 100644
--- a/apps/web/src/stores/ledgers.ts
+++ b/apps/web/src/stores/ledgers.ts
@@ -112,13 +112,13 @@ export const useLedgerStore = defineStore("ledgers", {
memberName(userId: string) {
return this.currentMembers.find((member) => member.id === userId)?.name ?? "历史用户";
},
- async createLedger(input: Pick
) {
+ async createLedger(input: Pick) {
const result = await apiRequest<{ ledger: LedgerRecord }>("/api/ledgers", { method: "POST", body: input });
await this.reloadLedgers();
await this.setCurrentLedger(result.ledger.id);
return result.ledger;
},
- async updateLedger(input: Pick) {
+ async updateLedger(input: Pick) {
await apiRequest(`/api/ledgers/${encodeURIComponent(input.id)}`, { method: "PATCH", body: input });
await this.reloadLedgers();
return this.ledgers.find((ledger) => ledger.id === input.id)!;
diff --git a/apps/web/src/styles.css b/apps/web/src/styles.css
index b95b76d..17ac00f 100644
--- a/apps/web/src/styles.css
+++ b/apps/web/src/styles.css
@@ -75,7 +75,7 @@ input:focus-visible {
z-index: 1;
min-height: 180px;
padding: max(18px, env(safe-area-inset-top)) 18px 20px;
- background: #087f72;
+ background: var(--ledger-gradient, #087f72);
color: #ffffff;
}
@@ -112,7 +112,7 @@ input:focus-visible {
display: grid;
flex: 0 0 auto;
place-items: center;
- border: 2px solid #087f72;
+ border: 2px solid var(--ledger-accent, #087f72);
border-radius: 50%;
color: #ffffff;
font-size: 10px;
diff --git a/apps/web/src/views/EntryDetailView.vue b/apps/web/src/views/EntryDetailView.vue
index e4557cd..ec83de6 100644
--- a/apps/web/src/views/EntryDetailView.vue
+++ b/apps/web/src/views/EntryDetailView.vue
@@ -22,6 +22,7 @@ import {
findCategoryPath,
type Category,
} from "../data/categories";
+import { ledgerTheme } from "../data/ledgers";
import { useEntryStore } from "../stores/entries";
import { useLedgerStore } from "../stores/ledgers";
@@ -349,7 +350,7 @@ async function deleteEntry() {
diff --git a/apps/web/src/views/LedgerSettingsView.vue b/apps/web/src/views/LedgerSettingsView.vue
index 7afb401..8f280e2 100644
--- a/apps/web/src/views/LedgerSettingsView.vue
+++ b/apps/web/src/views/LedgerSettingsView.vue
@@ -1,14 +1,15 @@