Files
accountbook/frontend/.next/cache/webpack/client-production/5.pack
T

5568 lines
366 KiB
Plaintext
Raw Normal View History

2026-03-12 11:24:10 +08:00
wpc
¸§webpack/lib/cache/PackFileCacheStrategyPackContentItems `5 ResolverCachePlugin|normal|default|alias=[|server-only$=|next/dist/compiled/server-only/index|client-only$=|next/dist/compiled/client-only/index|next/dist/compiled/client-only$=|next/dist/compiled/client-only/index|next/dist/compiled/server-only=|next/dist/compiled/server-only/index|]|fallback=[|assert=|/opt/accountbook/frontend/node_modules/next/dist/compiled/assert/assert.js|buffer=|/opt/accountbook/frontend/node_modules/next/dist/compiled/buffer/index.js|constants=|/opt/accountbook/frontend/node_modules/next/dist/compiled/constants-browserify/constants.json|crypto=|/opt/accountbook/frontend/node_modules/next/dist/compiled/crypto-browserify/index.js|domain=|/opt/accountbook/frontend/node_modules/next/dist/compiled/domain-browser/index.js|http=|/opt/accountbook/frontend/node_modules/next/dist/compiled/stream-http/index.js|https=|/opt/accountbook/frontend/node_modules/next/dist/compiled/https-browserify/index.js|os=|/opt/accountbook/frontend/node_modules/next/dist/compiled/os-browserify/browser.js|path=|/opt/accountbook/frontend/node_modules/next/dist/compiled/path-browserify/index.js|punycode=|/opt/accountbook/frontend/node_modules/next/dist/compiled/punycode/punycode.js|process=|/opt/accountbook/frontend/node_modules/next/dist/build/polyfills/process.js|querystring=|/opt/accountbook/frontend/node_modules/next/dist/compiled/querystring-es3/index.js|stream=|/opt/accountbook/frontend/node_modules/next/dist/compiled/stream-browserify/index.js|string_decoder=|/opt/accountbook/frontend/node_modules/next/dist/compiled/string_decoder/string_decoder.js|sys=|/opt/accountbook/frontend/node_modules/next/dist/compiled/util/util.js|timers=|/opt/accountbook/frontend/node_modules/next/dist/compiled/timers-browserify/main.js|tty=|/opt/accountbook/frontend/node_modules/next/dist/compiled/tty-browserify/index.js|util=|/opt/accountbook/frontend/node_modules/next/dist/compiled/util/util.js|vm=|/opt/accountbook/frontend/node_modules/next/dist/compiled/vm-browserify/index.js|zlib=|/opt/accountbook/frontend/node_modules/next/dist/compiled/browserify-zlib/index.js|events=|/opt/accountbook/frontend/node_modules/next/dist/compiled/events/events.js|setImmediate=|/opt/accountbook/frontend/node_modules/next/dist/compiled/setimmediate/setImmediate.js|]|dependencyType=|commonjs|path=|/opt/accountbook/frontend|request=|private-next-pages/stats.js»Compilation/modules|/opt/accountbook/frontend/node_modules/next/dist/build/webpack/loaders/next-swc-loader.js??ruleSet[1].rules[7].oneOf[3].use[0]!/opt/accountbook/frontend/pages/stats.jsÃFlagDependencyExportsPlugin|/opt/accountbook/frontend/node_modules/next/dist/build/webpack/loaders/next-swc-loader.js??ruleSet[1].rules[7].oneOf[3].use[0]!/opt/accountbook/frontend/pages/stats.jsÊCompilation/codeGeneration|/opt/accountbook/frontend/node_modules/next/dist/build/webpack/loaders/next-swc-loader.js??ruleSet[1].rules[7].oneOf[3].use[0]!/opt/accountbook/frontend/pages/stats.js|webpackCompilation/assets|chunk368ÁTerserWebpackPlugin|static/chunks/pages/stats-a46a81a16144d550.jsÀTerserWebpackPlugin|static/_M7rxAs3mhJSob0Sf8p-A/_ssgManifest.jsÂTerserWebpackPlugin|static/_M7rxAs3mhJSob0Sf8p-A/_buildManifest.js²CssMinimizerPlugin|static/css/121c17d24ded1c57.css½RealContentHashPlugin|analyse|static/css/121c17d24ded1c57.cssËRealContentHashPlugin|analyse|static/chunks/pages/stats-a46a81a16144d550.jsÊRealContentHashPlugin|analyse|static/_M7rxAs3mhJSob0Sf8p-A/_ssgManifest.jsÌRealContentHashPlugin|analyse|static/_M7rxAs3mhJSob0Sf8p-A/_buildManifest.jsÍRealContentHashPlugin|generate|static/_M7rxAs3mhJSob0Sf8p-A/_buildManifest.js¥webpack/lib/cache/ResolverCachePlugin`_ResolverCachePluginCacheMiss‡context„path‡request…queryˆfragment†module‰directory„fileˆinternalŽfullySpecified“descriptionFilePath“descriptionFileData“descriptionFileRootŒrelativePath__innerRequest_request__innerRequest_relativePathŽ__innerRequest †issuerissuerLayerˆcompiler†client¨/opt/accountbook/frontend/pages/stats.js€€ ¦/opt/accountbook/frontend/pack
Å/opt/accountbook/frontend/opt/accountbook/frontend/pages/package.jsonÁ/opt/accountbook/frontend/opt/accountbook/frontend/pages/stats.jsÄ/opt/accountbook/frontend/opt/accountbook/frontend/pages/stats.js.jsÅ/opt/accountbook/frontend/opt/accountbook/frontend/pages/stats.js.mjsÅ/opt/accountbook/frontend/opt/accountbook/frontend/pages/stats.js.tsxÄ/opt/accountbook/frontend/opt/accountbook/frontend/pages/stats.js.tsÅ/opt/accountbook/frontend/opt/accountbook/frontend/pages/stats.js.jsxÆ/opt/accountbook/frontend/opt/accountbook/frontend/pages/stats.js.jsonÆ/opt/accountbook/frontend/opt/accountbook/frontend/pages/stats.js.wasm¬/opt/accountbook/frontend/pages/package.jsonƒ@€¿/opt/accountbook/frontend/opt/accountbook/frontend/package.json¶/opt/accountbook/frontend/opt/accountbook/package.jsonª/opt/accountbook/frontend/opt/package.json˜webpack/lib/NormalModule«webpack/lib/util/registerExternalSerializer™webpack-sources/RawSourceŽ8import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { useState, useEffect, useRef } from "react";
import { useRouter } from "next/router";
import { isLoggedIn, getTrendStats, getCategoryStats } from "../lib/api";
import { Chart, registerables } from "chart.js";
import AppShell from "../components/AppShell";
import { Icon } from "../components/icons";
import { useToast } from "../components/ToastProvider";
Chart.register(...registerables);
export default function Stats() {
const router = useRouter();
const { showToast } = useToast();
const [period, setPeriod] = useState("month");
const [currentMonth, setCurrentMonth] = useState(new Date());
const [trendData, setTrendData] = useState([]);
const [categoryData, setCategoryData] = useState([]);
const [categoryType, setCategoryType] = useState("expense");
const [trendLoading, setTrendLoading] = useState(true);
const [categoryLoading, setCategoryLoading] = useState(true);
const trendChartRef = useRef(null);
const categoryChartRef = useRef(null);
const trendChartInstance = useRef(null);
const categoryChartInstance = useRef(null);
useEffect(()=>{
if (!isLoggedIn()) {
router.push("/login");
return;
}
loadTrendData();
}, [
router,
period,
currentMonth
]);
useEffect(()=>{
if (!isLoggedIn()) return;
loadCategoryData();
}, [
router,
currentMonth,
categoryType
]);
const getMonthStr = (date)=>"".concat(date.getFullYear(), "-").concat(String(date.getMonth() + 1).padStart(2, "0"));
const formatMonth = (date)=>"".concat(date.getFullYear(), "年").concat(date.getMonth() + 1, "月");
const loadTrendData = async ()=>{
setTrendLoading(true);
try {
const trend = await getTrendStats(period, getMonthStr(currentMonth));
setTrendData(trend);
} catch (err) {
console.error(err);
showToast(err.message, {
tone: "error"
});
} finally{
setTrendLoading(false);
}
};
const loadCategoryData = async ()=>{
setCategoryLoading(true);
try {
const category = await getCategoryStats(getMonthStr(currentMonth), categoryType);
setCategoryData(category);
} catch (err) {
console.error(err);
showToast(err.message, {
tone: "error"
});
} finally{
setCategoryLoading(false);
}
};
useEffect(()=>{
if (trendChartInstance.current) {
trendChartInstance.current.destroy();
}
if (trendChartRef.current && trendData.length > 0) {
const ctx = trendChartRef.current.getContext("2d");
trendChartInstance.current = new Chart(ctx, {
type: "line",
data: {
labels: trendData.map((item)=>item.period),
datasets: [
{
label: "æ”¶å…¥",
data: trendData.map((item)=>item.income),
borderColor: "#10b981",
backgroundColor: "rgba(16, 185, 129, 0.1)",
fill: true,
tension: 0.4
},
{
label: "支出",
data: trendData.map((item)=>item.expense),
borderColor: "#ef4444",
backgroundColor: "rgba(239, 68, 68, 0.1)",
fill: true,
tension: 0.4
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: "top"
}
},
scales: {
y: {
beginAtZero: true
}
}
}
});
}
}, [
trendData
]);
useEffect(()=>{
if (categoryChartInstance.current) {
categoryChartInstance.current.destroy();
}
if (categoryChartRef.current && categoryData.length > 0) {
const ctx = categoryChartRef.current.getContext("2d");
const colors = [
"#2563eb",
"#10b981",
"#f59e0b",
"#ef4444",
"#8b5cf6",
"#ec4899",
"#06b6d4",
"#84cc16",
"#f97316",
"#6366f1"
];
categoryChartInstance.current = new Chart(ctx, {
type: "doughnut",
data: {
labels: categoryData.map((item)=>item.category),
datasets: [
{
data: categoryData.map((item)=>item.total),
backgroundColor: colors.slice(0, categoryData.length)
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: "bottom"
}
}
}
});
}
}, [
categoryData
]);
const prevMonth = ()=>{
const newDate = new Date(currentMonth);
newDate.setMonth(newDate.getMonth() - 1);
setCurrentMonth(newDate);
};
const nextMonth = ()=>{
const newDate = new Date(currentMonth);
newDate.setMonth(newDate.getMonth() + 1);
setCurrentMonth(newDate);
};
const totalCategoryAmount = categoryData.reduce((sum, item)=>sum + item.total, 0);
const headerContent = /*#__PURE__*/ _jsxs("div", {
className: "header-center-container",
children: [
/*#__PURE__*/ _jsx("div", {
className: "header-spacer"
}),
/*#__PURE__*/ _jsxs("div", {
className: "month-switcher-container",
children: [
/*#__PURE__*/ _jsx("button", {
type: "button",
className: "icon-button",
"aria-label": "上个月",
onClick: prevMonth,
children: /*#__PURE__*/ _jsx(Icon, {
name: "chevronLeft",
size: 18
})
}),
/*#__PURE__*/ _jsx("span", {
className: "month-switcher__label",
children: formatMonth(currentMonth)
}),
/*#__PURE__*/ _jsx("button", {
type: "button",
className: "icon-button",
"aria-label": "下个月",
onClick: nextMonth,
children: /*#__PURE__*/ _jsx(Icon, {
name: "chevronRight",
size: 18
})
})
]
}),
/*#__PURE__*/ _jsx("div", {
className: "header-right-actions",
children: /*#__PURE__*/ _jsx("button", {
type: "button",
className: "icon-button",
"aria-label": "设置",
onClick: ()=>router.push("/settings"),
children: /*#__PURE__*/ _jsx(Icon, {
name: "settings",
size: 18
})
})
})
]
});
return /*#__PURE__*/ _jsxs(AppShell, {
headerContent: headerContent,
compactHeader: true,
children: [
/*#__PURE__*/ _jsxs("section", {
className: "card section-card section-card--stats",
children: [
/*#__PURE__*/ _jsx("div", {
className: "section-heading section-heading--compact",
children: /*#__PURE__*/ _jsxs("div", {
children: [
/*#__PURE__*/ _jsx("p", {
className: "eyebrow",
children: "趋势"
}),
/*#__PURE__*/ _jsx("h2", {
children: "收支趋势"
})
]
})
}),
/*#__PURE__*/ _jsxs("div", {
className: "period-tabs",
children: [
/*#__PURE__*/ _jsx("button", {
type: "button",
className: period === "week" ? "active" : "",
onClick: ()=>setPeriod("week"),
children: "周"
}),
/*#__PURE__*/ _jsx("button", {
type: "button",
className: period === "month" ? "active" : "",
onClick: ()=>setPeriod("month"),
children: "月"
}),
/*#__PURE__*/ _jsx("button", {
type: "button",
className: period === "year" ? "active" : "",
onClick: ()=>setPeriod("year"),
children: "å¹´"
})
]
}),
/*#__PURE__*/ _jsx("div", {
className: "chart-container chart-container--large chart-container--stats",
children: trendLoading ? /*#__PURE__*/ _jsx("div", {
className: "loading-state",
children: "加载中..."
}) : /*#__PURE__*/ _jsx("canvas", {
ref: trendChartRef
})
})
]
}),
/*#__PURE__*/ _jsxs("section", {
className: "card section-card section-card--stats",
children: [
/*#__PURE__*/ _jsx("div", {
className: "section-heading section-heading--compact",
children: /*#__PURE__*/ _jsx("div", {
children: /*#__PURE__*/ _jsx("h2", {
children: "分类明细"
})
})
}),
/*#__PURE__*/ _jsxs("div", {
className: "category-type-tabs",
children: [
/*#__PURE__*/ _jsx("button", {
type: "button",
className: categoryType === "expense" ? "active" : "",
onClick: ()=>setCategoryType("expense"),
children: "支出"
}),
/*#__PURE__*/ _jsx("button", {
type: "button",
className: categoryType === "income" ? "active" : "",
onClick: ()=>setCategoryType("income"),
children: "æ”¶å…¥"
})
]
}),
/*#__PURE__*/ _jsx("div", {
className: "category-breakdown category-breakdown--stats",
children: categoryData.length > 0 ? categoryData.map((cat, index)=>{
const percent = totalCategoryAmount ? (cat.total / totalCategoryAmount * 100).toFixed(1) : "0.0";
return /*#__PURE__*/ _jsxs("div", {
className: "stats-category-item",
children: [
/*#__PURE__*/ _jsxs("div", {
className: "stats-category-header",
children: [
/*#__PURE__*/ _jsx("span", {
className: "stats-category-name",
children: cat.category
}),
/*#__PURE__*/ _jsx("span", {
className: "stats-category-amount",
children: cat.total.toFixed(2)
})
]
}),
/*#__PURE__*/ _jsx("div", {
className: "stats-category-bar-wrap",
children: /*#__PURE__*/ _jsx("div", {
className: "stats-category-bar",
style: {
width: "".concat(percent, "%")
}
})
})
]
}, index);
}) : /*#__PURE__*/ _jsx("div", {
className: "empty-state empty-state--inline",
children: "暂无数æ®"
})
})
]
})
]
});
}
exportsType‰namespace
javascript/auto`×…alias¦ˆfallbackŒserver-only$Œclient-only$Ÿnext/dist/compiled/client-only$žnext/dist/compiled/server-only¤next/dist/compiled/server-only/index¤next/dist/compiled/client-only/indexÿþ
`†assert†buffer‰constants†crypto†domain„http…httpsosŒˆpunycode‡processquerystring†streamŽstring_decoderƒsys†timersƒtty„utilvm„zlib†eventsŒsetImmediateÊ/opt/accountbook/frontend/node_modules/next/dist/compiled/assert/assert.jsÉ/opt/accountbook/frontend/node_modules/next/dist/compiled/buffer/index.jsÝ/opt/accountbook/frontend/node_modules/next/dist/compiled/constants-browserify/constants.jsonÔ/opt/accountbook/frontend/node_modules/next/dist/compiled/crypto-browserify/index.jsÑ/opt/accountbook/frontend/node_modules/next/dist/compiled/domain-browser/index.jsÎ/opt/accountbook/frontend/node_modules/next/dist/compiled/stream-http/index.jsÓ/opt/accountbook/frontend/node_modules/next/dist/compiled/https-browserify/index.jsÒ/opt/accountbook/frontend/node_modules/next/dist/compiled/os-browserify/browser.jsÒ/opt/accountbook/frontend/node_modules/next/dist/compiled/path-browserify/index.jsÎ/opt/accountbook/frontend/node_modules/next/dist/compiled/punycode/punycode.jsË/opt/accountbook/frontend/node_modules/next/dist/build/polyfills/process.jsÒ/opt/accountbook/frontend/node_modules/next/dist/compiled/querystring-es3/index.jsÔ/opt/accountbook/frontend/node_modules/next/dist/compiled/stream-browserify/index.jsÚ/opt/accountbook/frontend/node_modules/next/dist/compiled/string_decoder/string_decoder.jsÆ/opt/accountbook/frontend/node_modules/next/dist/compiled/util/util.jsÓ/opt/accountbook/frontend/node_modules/next/dist/compiled/timers-browserify/main.jsÑ/opt/accountbook/frontend/node_modules/next/dist/compiled/tty-browserify/index.jsýÐ/opt/accountbook/frontend/node_modules/next/dist/compiled/vm-browserify/index.jsÒ/opt/accountbook/frontend/node_modules/next/dist/compiled/browserify-zlib/index.jsÊ/opt/accountbook/frontend/node_modules/next/dist/compiled/events/events.jsÖ/opt/accountbook/frontend/node_modules/next/dist/compiled/setimmediate/setImmediate.js
Å`‰cacheable†parsedfileDependencies“contextDependencies“missingDependenciesbuildDependenciesvalueDependencies›†assetsŠassetsInfo†strictexportsArgument”topLevelDeclarationsˆsnapshot ˜webpack/lib/util/LazySetÙ/opt/accountbook/frontend/node_modules/next/dist/build/webpack/loaders/next-swc-loader.js ™webpack/DefinePlugin_hashˆ898e1469b493a1635fba6b04 “__webpack_exports__‰*default*…Stats 9®ÂÍyB fÿÿÿ
·webpack/lib/dependencies/HarmonyCompatibilityDependency
dÿÿý¨webpack/lib/dependencies/ConstDependency`?
`?a@t
`4`u@
`(ç
`I
`0AG
`.AHs
`+At«
`7¶webpack/lib/dependencies/HarmonyExportHeaderDependency A8 A8
@\ `Mºwebpack/lib/dependencies/HarmonyImportSideEffectDependencyreact/jsx-runtimeÿ
`?`ÿÿÿ`ÿÿÿ
`4next/routerÿ
`(Š../lib/apiÿ
`IYÿÿÿYÿÿÿ
`0../components/AppShellÿ
`.“../components/iconsÿ
`+../components/ToastProviderÿ
`7¹webpack/lib/dependencies/HarmonyImportSpecifierDependencya …Chartˆregisterý º  ±
 LÿÿÿLÿÿÿý
` registerablesþ Ë tGÿÿÿGÿÿÿþ
b ¹webpack/lib/dependencies/HarmonyExportSpecifierDependency`̇default
@\ ‰useRouterþ A   
úââý
c   ˆuseToastþ A*2  úææþ
c  " ˆuseStateþ AV^  õ7ÿÿÿ7ÿÿÿþ
c
( ûú A•  ñ3ÿÿÿ3ÿÿÿþ
c,4 ÷ö Ù  í/ÿÿÿ/ÿÿÿþ
c&. óò A   é+ÿÿÿ+ÿÿÿþ
c,4 ïî AEM  å'ÿÿÿ'ÿÿÿþ
c,4 ëê A†Ž  á#ÿÿÿ#ÿÿÿþ
c,4 çæ Ð  Ýÿÿÿÿÿÿþ
c2: †useRefþ ø  Øÿÿÿÿÿÿþ
c  ûú A#  Ôÿÿÿÿÿÿþ
c# ÷ö AJP  Ðÿÿÿÿÿÿþ
c% óò Az  Ìÿÿÿÿÿÿþ
c"( ‰useEffectþ   Ç ÿÿÿ ÿÿÿþ
c
 ŠisLoggedInþ ³  §§þ
c
 öõ A]f  ¾ÿÿÿÿÿÿþ
c##
 ÷ö Az  ºŸŸþ
c$
$ getTrendStatsþ AIV  µššþ
c0 0- getCategoryStatsþ û  °þ
c>#>3 äã A'
0
  ¬îþÿÿîþÿÿþ
cII
 ˜ AG L  4¨èþÿÿèþÿÿþ
cO-O2 ÜÛ ø  ¤æþÿÿæþÿÿþ
cyy
ø AZ_ 4¡áþÿÿáþÿÿþ
@`0@`5 „jsxs…_jsxs A-2  {ÿÿÿ{ÿÿÿþ
`(@¯`- ƒjsx„_jsx A˜œ  uÿÿÿuÿÿÿþ
`` ôõ   qÿÿÿqÿÿÿþ
`` ö÷ Aƒ  mÿÿÿmÿÿÿþ
@¸`"@¸`& òó Asw  iÿÿÿiÿÿÿþ
`0@½`4 „Iconþ Ax| 4nÿÿÿnÿÿÿþ
`5@½`9 éê A+/  `ÿÿÿ`ÿÿÿþ
`"@Â`& åæ ð  |ÿÿÿ\ÿÿÿ\ÿÿÿþ
`"@Æ`& áâ ä  xÿÿÿXÿÿÿXÿÿÿþ
`0@Ë`4ðï é 4uÿÿÿ_ÿÿÿ_ÿÿÿþ
`5@Ë`9 ÚÛ   qÿÿÿQÿÿÿQÿÿÿþ
`` Ö× A  mÿÿÿMÿÿÿMÿÿÿþ
`(@Ô`, ÒÓ A
  iÿÿÿIÿÿÿIÿÿÿþ
`,@Ù`0áà A 4fÿÿÿPÿÿÿPÿÿÿþ
`1@Ù`5 ÅÆ Î  bÿÿÿBÿÿÿBÿÿÿþ
`` [ÿÿÿˆAppShell × 4]ÿÿÿEÿÿÿEÿÿÿþ
``' ¼½ AL Q   Yÿÿÿ9ÿÿÿ9ÿÿÿþ
`` ¾¿ å   Uÿÿÿ5ÿÿÿ5ÿÿÿþ
`"@è`& ´µ An!s!  Qÿÿÿ1ÿÿÿ1ÿÿÿþ
`0@ê`5 · AÓ!×!  Mÿÿÿ-ÿÿÿ-ÿÿÿþ
`.@ì`2 ²³ Až"¢"  Iÿÿÿ)ÿÿÿ)ÿÿÿþ
`.@ð`2 ¨© Av#{#  Eÿÿÿ%ÿÿÿ%ÿÿÿþ
`"@ö`' ª« A$ $  Aÿÿÿ!ÿÿÿ!ÿÿÿþ
`*@ù`. ¦§ AL%P%  =ÿÿÿÿÿÿÿÿÿþ
@ÿ`*@ÿ`. ¢£ A•&™&  9ÿÿÿÿÿÿÿÿÿþ
@`*@`. žŸ A( (  5ÿÿÿÿÿÿÿÿÿþ
@
`"@
`& š A¶(º(  1ÿÿÿÿÿÿÿÿÿþ
@`?@`C  AV)Z)  -ÿÿÿ
ÿÿÿ
ÿÿÿþ
@`+@`/ Œ A* *  )ÿÿÿ ÿÿÿ ÿÿÿþ
@`@` Ž A™**  %ÿÿÿÿÿÿÿÿÿþ
@`"@`& Š A&+*+  !ÿÿÿÿÿÿÿÿÿþ
@`0@`4  Ah+l+  ÿÿÿýþÿÿýþÿÿþ
@`4@`8 |ÿÿÿ}ÿÿÿ A,,  ÿÿÿùþÿÿùþÿÿþ
@#`"@#`' ~ÿÿÿÿÿÿ A°,´,  ÿÿÿõþÿÿõþÿÿþ
@&`*@&`. zÿÿÿ{ÿÿÿ A
..  ÿÿÿñþÿÿñþÿÿþ
@,`*@,`. vÿÿÿwÿÿÿ A‹//  
ÿÿÿíþÿÿíþÿÿþ
@4`"@4`& lÿÿÿmÿÿÿ Aø0ý0   ÿÿÿéþÿÿéþÿÿþ
@8`1@8`6 hÿÿÿiÿÿÿ A§1¬1  ÿÿÿåþÿÿåþÿÿþ
@;`2@;`7 jÿÿÿkÿÿÿ Ap2t2  ÿÿÿáþÿÿáþÿÿþ
@>`:@>`> fÿÿÿgÿÿÿ A‚3†3  ýþÿÿÝþÿÿÝþÿÿþ
@B`:@B`> bÿÿÿcÿÿÿ Aç4ë4  ùþÿÿÙþÿÿÙþÿÿþ
@H`2@H`6 ^ÿÿÿ_ÿÿÿ Aƒ5‡5  õþÿÿÕþÿÿÕþÿÿþ
@J`@@J`D Zÿÿÿ[ÿÿÿ AV7Z7  ñþÿÿÑþÿÿÑþÿÿþ
@S`+@S`/ —webpack/lib/ModuleGraph“RestoreProvidedData

þÿÿˆprovidedcanMangleProvideterminalBindingexportsInfoâþÿÿ

‡sources“runtimeRequirements„dataŠjavascript«webpack/lib/util/registerExternalSerializerœwebpack-sources/CachedSource `D«webpack/lib/util/registerExternalSerializerœwebpack-sources/ConcatSource«webpack/lib/util/registerExternalSerializer™webpack-sources/RawSource|__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": function() { return /* binding */ Stats; }
/* harmony export */ });
/* harmony import */ var react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(5893);
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(7294);
/* harmony import */ var next_router__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(1163);
/* harmony import */ var next_router__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(next_router__WEBPACK_IMPORTED_MODULE_2__);
/* harmony import */ var _lib_api__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(610);
/* harmony import */ var chart_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(3148);
/* harmony import */ var _components_AppShell__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(6248);
/* harmony import */ var _components_icons__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(9415);
/* harmony import */ var _components_ToastProvider__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(5546);
«webpack/lib/util/registerExternalSerializerwebpack-sources/ReplaceSourceŽ8import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { useState, useEffect, useRef } from "react";
import { useRouter } from "next/router";
import { isLoggedIn, getTrendStats, getCategoryStats } from "../lib/api";
import { Chart, registerables } from "chart.js";
import AppShell from "../components/AppShell";
import { Icon } from "../components/icons";
import { useToast } from "../components/ToastProvider";
Chart.register(...registerables);
export default function Stats() {
const router = useRouter();
const { showToast } = useToast();
const [period, setPeriod] = useState("month");
const [currentMonth, setCurrentMonth] = useState(new Date());
const [trendData, setTrendData] = useState([]);
const [categoryData, setCategoryData] = useState([]);
const [categoryType, setCategoryType] = useState("expense");
const [trendLoading, setTrendLoading] = useState(true);
const [categoryLoading, setCategoryLoading] = useState(true);
const trendChartRef = useRef(null);
const categoryChartRef = useRef(null);
const trendChartInstance = useRef(null);
const categoryChartInstance = useRef(null);
useEffect(()=>{
if (!isLoggedIn()) {
router.push("/login");
return;
}
loadTrendData();
}, [
router,
period,
currentMonth
]);
useEffect(()=>{
if (!isLoggedIn()) return;
loadCategoryData();
}, [
router,
currentMonth,
categoryType
]);
const getMonthStr = (date)=>"".concat(date.getFullYear(), "-").concat(String(date.getMonth() + 1).padStart(2, "0"));
const formatMonth = (date)=>"".concat(date.getFullYear(), "年").concat(date.getMonth() + 1, "月");
const loadTrendData = async ()=>{
setTrendLoading(true);
try {
const trend = await getTrendStats(period, getMonthStr(currentMonth));
setTrendData(trend);
} catch (err) {
console.error(err);
showToast(err.message, {
tone: "error"
});
} finally{
setTrendLoading(false);
}
};
const loadCategoryData = async ()=>{
setCategoryLoading(true);
try {
const category = await getCategoryStats(getMonthStr(currentMonth), categoryType);
setCategoryData(category);
} catch (err) {
console.error(err);
showToast(err.message, {
tone: "error"
});
} finally{
setCategoryLoading(false);
}
};
useEffect(()=>{
if (trendChartInstance.current) {
trendChartInstance.current.destroy();
}
if (trendChartRef.current && trendData.length > 0) {
const ctx = trendChartRef.current.getContext("2d");
trendChartInstance.current = new Chart(ctx, {
type: "line",
data: {
labels: trendData.map((item)=>item.period),
datasets: [
{
label: "æ”¶å…¥",
data: trendData.map((item)=>item.income),
borderColor: "#10b981",
backgroundColor: "rgba(16, 185, 129, 0.1)",
fill: true,
tension: 0.4
},
{
label: "支出",
data: trendData.map((item)=>item.expense),
borderColor: "#ef4444",
backgroundColor: "rgba(239, 68, 68, 0.1)",
fill: true,
tension: 0.4
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: "top"
}
},
scales: {
y: {
beginAtZero: true
}
}
}
});
}
}, [
trendData
]);
useEffect(()=>{
if (categoryChartInstance.current) {
categoryChartInstance.current.destroy();
}
if (categoryChartRef.current && categoryData.length > 0) {
const ctx = categoryChartRef.current.getContext("2d");
const colors = [
"#2563eb",
"#10b981",
"#f59e0b",
"#ef4444",
"#8b5cf6",
"#ec4899",
"#06b6d4",
"#84cc16",
"#f97316",
"#6366f1"
];
categoryChartInstance.current = new Chart(ctx, {
type: "doughnut",
data: {
labels: categoryData.map((item)=>item.category),
datasets: [
{
data: categoryData.map((item)=>item.total),
backgroundColor: colors.slice(0, categoryData.length)
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: "bottom"
}
}
}
});
}
}, [
categoryData
]);
const prevMonth = ()=>{
const newDate = new Date(currentMonth);
newDate.setMonth(newDate.getMonth() - 1);
setCurrentMonth(newDate);
};
const nextMonth = ()=>{
const newDate = new Date(currentMonth);
newDate.setMonth(newDate.getMonth() + 1);
setCurrentMonth(newDate);
};
const totalCategoryAmount = categoryData.reduce((sum, item)=>sum + item.total, 0);
const headerContent = /*#__PURE__*/ _jsxs("div", {
className: "header-center-container",
children: [
/*#__PURE__*/ _jsx("div", {
className: "header-spacer"
}),
/*#__PURE__*/ _jsxs("div", {
className: "month-switcher-container",
children: [
/*#__PURE__*/ _jsx("button", {
type: "button",
className: "icon-button",
"aria-label": "上个月",
onClick: prevMonth,
children: /*#__PURE__*/ _jsx(Icon, {
name: "chevronLeft",
size: 18
})
}),
/*#__PURE__*/ _jsx("span", {
className: "month-switcher__label",
children: formatMonth(currentMonth)
}),
/*#__PURE__*/ _jsx("button", {
type: "button",
className: "icon-button",
"aria-label": "下个月",
onClick: nextMonth,
children: /*#__PURE__*/ _jsx(Icon, {
name: "chevronRight",
size: 18
})
})
]
}),
/*#__PURE__*/ _jsx("div", {
className: "header-right-actions",
children: /*#__PURE__*/ _jsx("button", {
type: "button",
className: "icon-button",
"aria-label": "设置",
onClick: ()=>router.push("/settings"),
children: /*#__PURE__*/ _jsx(Icon, {
name: "settings",
size: 18
})
})
})
]
});
return /*#__PURE__*/ _jsxs(AppShell, {
headerContent: headerContent,
compactHeader: true,
children: [
/*#__PURE__*/ _jsxs("section", {
className: "card section-card section-card--stats",
children: [
/*#__PURE__*/ _jsx("div", {
className: "section-heading section-heading--compact",
children: /*#__PURE__*/ _jsxs("div", {
children: [
/*#__PURE__*/ _jsx("p", {
className: "eyebrow",
children: "趋势"
}),
/*#__PURE__*/ _jsx("h2", {
children: "收支趋势"
})
]
})
}),
/*#__PURE__*/ _jsxs("div", {
className: "period-tabs",
children: [
/*#__PURE__*/ _jsx("button", {
type: "button",
className: period === "week" ? "active" : "",
onClick: ()=>setPeriod("week"),
children: "周"
}),
/*#__PURE__*/ _jsx("button", {
type: "button",
className: period === "month" ? "active" : "",
onClick: ()=>setPeriod("month"),
children: "月"
}),
/*#__PURE__*/ _jsx("button", {
type: "button",
className: period === "year" ? "active" : "",
onClick: ()=>setPeriod("year"),
children: "å¹´"
})
]
}),
/*#__PURE__*/ _jsx("div", {
className: "chart-container chart-container--large chart-container--stats",
children: trendLoading ? /*#__PURE__*/ _jsx("div", {
className: "loading-state",
children: "加载中..."
}) : /*#__PURE__*/ _jsx("canvas", {
ref: trendChartRef
})
})
]
}),
/*#__PURE__*/ _jsxs("section", {
className: "card section-card section-card--stats",
children: [
/*#__PURE__*/ _jsx("div", {
className: "section-heading section-heading--compact",
children: /*#__PURE__*/ _jsx("div", {
children: /*#__PURE__*/ _jsx("h2", {
children: "分类明细"
})
})
}),
/*#__PURE__*/ _jsxs("div", {
className: "category-type-tabs",
children: [
/*#__PURE__*/ _jsx("button", {
type: "button",
className: categoryType === "expense" ? "active" : "",
onClick: ()=>setCategoryType("expense"),
children: "支出"
}),
/*#__PURE__*/ _jsx("button", {
type: "button",
className: categoryType === "income" ? "active" : "",
onClick: ()=>setCategoryType("income"),
children: "æ”¶å…¥"
})
]
}),
/*#__PURE__*/ _jsx("div", {
className: "category-breakdown category-breakdown--stats",
children: categoryData.length > 0 ? categoryData.map((cat, index)=>{
const percent = totalCategoryAmount ? (cat.total / totalCategoryAmount * 100).toFixed(1) : "0.0";
return /*#__PURE__*/ _jsxs("div", {
className: "stats-category-item",
children: [
/*#__PURE__*/ _jsxs("div", {
className: "stats-category-header",
children: [
/*#__PURE__*/ _jsx("span", {
className: "stats-category-name",
children: cat.category
}),
/*#__PURE__*/ _jsx("span", {
className: "stats-category-amount",
children: cat.total.toFixed(2)
})
]
}),
/*#__PURE__*/ _jsx("div", {
className: "stats-category-bar-wrap",
children: /*#__PURE__*/ _jsx("div", {
className: "stats-category-bar",
style: {
width: "".concat(percent, "%")
}
})
})
]
}, index);
}) : /*#__PURE__*/ _jsx("div", {
className: "empty-state empty-state--inline",
children: "暂无数æ®"
})
})
]
})
]
});
}
eM>@su_œžæèFHrtª¬°¾ÊÎÜ *1V]œÑØ EL_ÈÏò÷"JOzŒ©²]ezƒIUëú'
/
G K ï÷Z^-_1˜ûÿsvx{+.ìïàãåè²µ
ÉÍÏÖL _P á ä n!r!Ó!Ö!ž"¡"v#z#$$L%O%•&˜&((¶(¹(V)Y)**™*œ*&+)+h+k+,T,°,³,
.
./Ž/ø0ü0§1«1p2s23…3ç4ê4ƒ5†5V7Y7µchart_js__WEBPACK_IMPORTED_MODULE_6__/* .Chart */ .kL½chart_js__WEBPACK_IMPORTED_MODULE_6__/* .registerables */ .zX¶(0,next_router__WEBPACK_IMPORTED_MODULE_2__.useRouter)Ì(0,_components_ToastProvider__WEBPACK_IMPORTED_MODULE_5__/* .useToast */ .p)¯(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)ÿÿÿÿÿÿ­(0,react__WEBPACK_IMPORTED_MODULE_1__.useRef)ÿÿÿ°(0,react__WEBPACK_IMPORTED_MODULE_1__.useEffect)¾(0,_lib_api__WEBPACK_IMPORTED_MODULE_7__/* .isLoggedIn */ .jl)þÿÁ(0,_lib_api__WEBPACK_IMPORTED_MODULE_7__/* .getTrendStats */ .gR)Ä(0,_lib_api__WEBPACK_IMPORTED_MODULE_7__/* .getCategoryStats */ .Ap)üöüö·(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)¶(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)þÿÿ¼_components_icons__WEBPACK_IMPORTED_MODULE_4__/* .Icon */ .JþþþÿþþþÿýÅ_components_AppShell__WEBPACK_IMPORTED_MODULE_3__/* ["default"] */ .Züýüýýüýýýýýýüýýýüýýýüüýýýýýdþÿÿ†source„size„maps/þÿÿ$H__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": function() { return /* binding */ Stats; }
/* harmony export */ });
/* harmony import */ var react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(5893);
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(7294);
/* harmony import */ var next_router__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(1163);
/* harmony import */ var next_router__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(next_router__WEBPACK_IMPORTED_MODULE_2__);
/* harmony import */ var _lib_api__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(610);
/* harmony import */ var chart_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(3148);
/* harmony import */ var _components_AppShell__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(6248);
/* harmony import */ var _components_icons__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(9415);
/* harmony import */ var _components_ToastProvider__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(5546);
chart_js__WEBPACK_IMPORTED_MODULE_6__/* .Chart */ .kL.register(...chart_js__WEBPACK_IMPORTED_MODULE_6__/* .registerables */ .zX);
function Stats() {
const router = (0,next_router__WEBPACK_IMPORTED_MODULE_2__.useRouter)();
const { showToast } = (0,_components_ToastProvider__WEBPACK_IMPORTED_MODULE_5__/* .useToast */ .p)();
const [period, setPeriod] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)("month");
const [currentMonth, setCurrentMonth] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)(new Date());
const [trendData, setTrendData] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)([]);
const [categoryData, setCategoryData] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)([]);
const [categoryType, setCategoryType] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)("expense");
const [trendLoading, setTrendLoading] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)(true);
const [categoryLoading, setCategoryLoading] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)(true);
const trendChartRef = (0,react__WEBPACK_IMPORTED_MODULE_1__.useRef)(null);
const categoryChartRef = (0,react__WEBPACK_IMPORTED_MODULE_1__.useRef)(null);
const trendChartInstance = (0,react__WEBPACK_IMPORTED_MODULE_1__.useRef)(null);
const categoryChartInstance = (0,react__WEBPACK_IMPORTED_MODULE_1__.useRef)(null);
(0,react__WEBPACK_IMPORTED_MODULE_1__.useEffect)(()=>{
if (!(0,_lib_api__WEBPACK_IMPORTED_MODULE_7__/* .isLoggedIn */ .jl)()) {
router.push("/login");
return;
}
loadTrendData();
}, [
router,
period,
currentMonth
]);
(0,react__WEBPACK_IMPORTED_MODULE_1__.useEffect)(()=>{
if (!(0,_lib_api__WEBPACK_IMPORTED_MODULE_7__/* .isLoggedIn */ .jl)()) return;
loadCategoryData();
}, [
router,
currentMonth,
categoryType
]);
const getMonthStr = (date)=>"".concat(date.getFullYear(), "-").concat(String(date.getMonth() + 1).padStart(2, "0"));
const formatMonth = (date)=>"".concat(date.getFullYear(), "年").concat(date.getMonth() + 1, "月");
const loadTrendData = async ()=>{
setTrendLoading(true);
try {
const trend = await (0,_lib_api__WEBPACK_IMPORTED_MODULE_7__/* .getTrendStats */ .gR)(period, getMonthStr(currentMonth));
setTrendData(trend);
} catch (err) {
console.error(err);
showToast(err.message, {
tone: "error"
});
} finally{
setTrendLoading(false);
}
};
const loadCategoryData = async ()=>{
setCategoryLoading(true);
try {
const category = await (0,_lib_api__WEBPACK_IMPORTED_MODULE_7__/* .getCategoryStats */ .Ap)(getMonthStr(currentMonth), categoryType);
setCategoryData(category);
} catch (err) {
console.error(err);
showToast(err.message, {
tone: "error"
});
} finally{
setCategoryLoading(false);
}
};
(0,react__WEBPACK_IMPORTED_MODULE_1__.useEffect)(()=>{
if (trendChartInstance.current) {
trendChartInstance.current.destroy();
}
if (trendChartRef.current && trendData.length > 0) {
const ctx = trendChartRef.current.getContext("2d");
trendChartInstance.current = new chart_js__WEBPACK_IMPORTED_MODULE_6__/* .Chart */ .kL(ctx, {
type: "line",
data: {
labels: trendData.map((item)=>item.period),
datasets: [
{
label: "æ”¶å…¥",
data: trendData.map((item)=>item.income),
borderColor: "#10b981",
backgroundColor: "rgba(16, 185, 129, 0.1)",
fill: true,
tension: 0.4
},
{
label: "支出",
data: trendData.map((item)=>item.expense),
borderColor: "#ef4444",
backgroundColor: "rgba(239, 68, 68, 0.1)",
fill: true,
tension: 0.4
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: "top"
}
},
scales: {
y: {
beginAtZero: true
}
}
}
});
}
}, [
trendData
]);
(0,react__WEBPACK_IMPORTED_MODULE_1__.useEffect)(()=>{
if (categoryChartInstance.current) {
categoryChartInstance.current.destroy();
}
if (categoryChartRef.current && categoryData.length > 0) {
const ctx = categoryChartRef.current.getContext("2d");
const colors = [
"#2563eb",
"#10b981",
"#f59e0b",
"#ef4444",
"#8b5cf6",
"#ec4899",
"#06b6d4",
"#84cc16",
"#f97316",
"#6366f1"
];
categoryChartInstance.current = new chart_js__WEBPACK_IMPORTED_MODULE_6__/* .Chart */ .kL(ctx, {
type: "doughnut",
data: {
labels: categoryData.map((item)=>item.category),
datasets: [
{
data: categoryData.map((item)=>item.total),
backgroundColor: colors.slice(0, categoryData.length)
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: "bottom"
}
}
}
});
}
}, [
categoryData
]);
const prevMonth = ()=>{
const newDate = new Date(currentMonth);
newDate.setMonth(newDate.getMonth() - 1);
setCurrentMonth(newDate);
};
const nextMonth = ()=>{
const newDate = new Date(currentMonth);
newDate.setMonth(newDate.getMonth() + 1);
setCurrentMonth(newDate);
};
const totalCategoryAmount = categoryData.reduce((sum, item)=>sum + item.total, 0);
const headerContent = /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "header-center-container",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "header-spacer"
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "month-switcher-container",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("button", {
type: "button",
className: "icon-button",
"aria-label": "上个月",
onClick: prevMonth,
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_components_icons__WEBPACK_IMPORTED_MODULE_4__/* .Icon */ .J, {
name: "chevronLeft",
size: 18
})
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("span", {
className: "month-switcher__label",
children: formatMonth(currentMonth)
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("button", {
type: "button",
className: "icon-button",
"aria-label": "下个月",
onClick: nextMonth,
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_components_icons__WEBPACK_IMPORTED_MODULE_4__/* .Icon */ .J, {
name: "chevronRight",
size: 18
})
})
]
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "header-right-actions",
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("button", {
type: "button",
className: "icon-button",
"aria-label": "设置",
onClick: ()=>router.push("/settings"),
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_components_icons__WEBPACK_IMPORTED_MODULE_4__/* .Icon */ .J, {
name: "settings",
size: 18
})
})
})
]
});
return /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)(_components_AppShell__WEBPACK_IMPORTED_MODULE_3__/* ["default"] */ .Z, {
headerContent: headerContent,
compactHeader: true,
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("section", {
className: "card section-card section-card--stats",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "section-heading section-heading--compact",
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("p", {
className: "eyebrow",
children: "趋势"
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("h2", {
children: "收支趋势"
})
]
})
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "period-tabs",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("button", {
type: "button",
className: period === "week" ? "active" : "",
onClick: ()=>setPeriod("week"),
children: "周"
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("button", {
type: "button",
className: period === "month" ? "active" : "",
onClick: ()=>setPeriod("month"),
children: "月"
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("button", {
type: "button",
className: period === "year" ? "active" : "",
onClick: ()=>setPeriod("year"),
children: "å¹´"
})
]
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "chart-container chart-container--large chart-container--stats",
children: trendLoading ? /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "loading-state",
children: "加载中..."
}) : /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("canvas", {
ref: trendChartRef
})
})
]
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("section", {
className: "card section-card section-card--stats",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "section-heading section-heading--compact",
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("h2", {
children: "分类明细"
})
})
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "category-type-tabs",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("button", {
type: "button",
className: categoryType === "expense" ? "active" : "",
onClick: ()=>setCategoryType("expense"),
children: "支出"
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("button", {
type: "button",
className: categoryType === "income" ? "active" : "",
onClick: ()=>setCategoryType("income"),
children: "æ”¶å…¥"
})
]
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "category-breakdown category-breakdown--stats",
children: categoryData.length > 0 ? categoryData.map((cat, index)=>{
const percent = totalCategoryAmount ? (cat.total / totalCategoryAmount * 100).toFixed(1) : "0.0";
return /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "stats-category-item",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "stats-category-header",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("span", {
className: "stats-category-name",
children: cat.category
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("span", {
className: "stats-category-amount",
children: cat.total.toFixed(2)
})
]
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "stats-category-bar-wrap",
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "stats-category-bar",
style: {
width: "".concat(percent, "%")
}
})
})
]
}, index);
}) : /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "empty-state empty-state--inline",
children: "暂无数æ®"
})
})
]
})
]
});
}
”{"finalSource":true}ƒmapbufferedMap ConcatSourceRawSource|__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": function() { return /* binding */ Stats; }
/* harmony export */ });
/* harmony import */ var react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(5893);
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(7294);
/* harmony import */ var next_router__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(1163);
/* harmony import */ var next_router__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(next_router__WEBPACK_IMPORTED_MODULE_2__);
/* harmony import */ var _lib_api__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(610);
/* harmony import */ var chart_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(3148);
/* harmony import */ var _components_AppShell__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(6248);
/* harmony import */ var _components_icons__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(9415);
/* harmony import */ var _components_ToastProvider__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(5546);
ReplaceSourceRawSourceCþÿÿn062undefined64115undefined117156undefined158230undefined232279undefined281326undefined328370undefined372426undefined428432chart_js__WEBPACK_IMPORTED_MODULE_6__/* .Chart */ .kLundefined446458chart_js__WEBPACK_IMPORTED_MODULE_6__/* .registerables */ .zXundefined462476undefined515523(0,next_router__WEBPACK_IMPORTED_MODULE_2__.useRouter)undefined554561(0,_components_ToastProvider__WEBPACK_IMPORTED_MODULE_5__/* .useToast */ .p)undefined598605(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined661668(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined721728(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined779786(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined837844(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined902909(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined968975(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined10101015(0,react__WEBPACK_IMPORTED_MODULE_1__.useRef)undefined10531058(0,react__WEBPACK_IMPORTED_MODULE_1__.useRef)undefined10981103(0,react__WEBPACK_IMPORTED_MODULE_1__.useRef)undefined11461151(0,react__WEBPACK_IMPORTED_MODULE_1__.useRef)undefined11641172(0,react__WEBPACK_IMPORTED_MODULE_1__.useEffect)undefined11931202(0,_lib_api__WEBPACK_IMPORTED_MODULE_7__/* .isLoggedIn */ .jl)undefined13731381(0,react__WEBPACK_IMPORTED_MODULE_1__.useEffect)undefined14021411(0,_lib_api__WEBPACK_IMPORTED_MODULE_7__/* .isLoggedIn */ .jl)undefined18651877(0,_lib_api__WEBPACK_IMPORTED_MODULE_7__/* .getTrendStats */ .gR)undefined22832298(0,_lib_api__WEBPACK_IMPORTED_MODULE_7__/* .getCategoryStats */ .Ap)undefined25992607(0,react__WEBPACK_IMPORTED_MODULE_1__.useEffect)undefined28872891chart_js__WEBPACK_IMPORTED_MODULE_6__/* .Chart */ .kLundefined43354343(0,react__WEBPACK_IMPORTED_MODULE_1__.useEffect)undefined49544958chart_js__WEBPACK_IMPORTED_MODULE_6__/* .Chart */ .kLundefined61896193(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined62966299(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined63956399(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined65276530(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined67716774(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined67766779_components_icons__WEBPACK_IMPORTED_MODULE_4__/* .Icon */ .Jundefined69556958(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined71487151(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined73927395(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined73977400_components_icons__WEBPACK_IMPORTED_MODULE_4__/* .Icon */ .Jundefined76027605(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined77077710(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined79497952(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined79547957_components_icons__WEBPACK_IMPORTED_MODULE_4__/* .Icon */ .Jundefined81378141(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined81438150_components_AppShell__WEBPACK_IMPORTED_MODULE_3__/* ["default"] */ .Zundefined82688272(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined84178420(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined85588562(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined86598662(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined88628865(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined90789082(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined92219224(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined95489551(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined98779880(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined1024510248(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined1042210425(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined1058210585(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined1075610760(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined1090510908(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined1104611049(0,react_jsx_runtime__WEBP
/***/ 4841:
«webpack/lib/util/registerExternalSerializerœwebpack-sources/CachedSource ö«webpack/lib/util/registerExternalSerializerœwebpack-sources/ConcatSource«webpack/lib/util/registerExternalSerializer™webpack-sources/RawSource[/***/ (function(__unused_webpack_module, __unused_webpack_exports, __webpack_require__) {
«webpack/lib/util/registerExternalSerializerœwebpack-sources/CachedSource ô«webpack/lib/util/registerExternalSerializerwebpack-sources/ReplaceSource«webpack/lib/util/registerExternalSerializer™webpack-sources/RawSource
(window.__NEXT_P = window.__NEXT_P || []).push([
"/stats",
function () {
return require("private-next-pages/stats.js");
}
]);
if(module.hot) {
module.hot.dispose(function () {
window.__NEXT_P.push(["/stats"])
});
}
bioqD¨±´“__webpack_require__„8011…false{}†buffer†source„size„maps„hash§
(window.__NEXT_P = window.__NEXT_P || []).push([
"/stats",
function () {
return __webpack_require__(8011);
}
]);
if(false) {}
”{"finalSource":true}ƒmapbufferedMap ReplaceSourceRawSource
(window.__NEXT_P = window.__NEXT_P || []).push([
"/stats",
function () {
return require("private-next-pages/stats.js");
}
]);
if(module.hot) {
module.hot.dispose(function () {
window.__NEXT_P.push(["/stats"])
});
}
Z105111__webpack_require__undefined1131418011undefined168177falseundefined180276{}undefined
/***/ }) †buffer†source„size„maps„hash /***/ (function(__unused_webpack_module, __unused_webpack_exports, __webpack_require__) {
(window.__NEXT_P = window.__NEXT_P || []).push([
"/stats",
function () {
return __webpack_require__(8011);
}
]);
if(false) {}
/***/ }) ”{"finalSource":true}ƒmapbufferedMap ConcatSourceRawSource[/***/ (function(__unused_webpack_module, __unused_webpack_exports, __webpack_require__) {
ReplaceSourceRawSource
(window.__NEXT_P = window.__NEXT_P || []).push([
"/stats",
function () {
return require("private-next-pages/stats.js");
}
]);
if(module.hot) {
module.hot.dispose(function () {
window.__NEXT_P.push(["/stats"])
});
}
Z105111__webpack_require__undefined1131418011undefined168177falseundefined180276{}undefined RawSource
/***/ }),
/***/ 6248:
 F«webpack/lib/util/registerExternalSerializerœwebpack-sources/ConcatSource«webpack/lib/util/registerExternalSerializer™webpack-sources/RawSourced/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
"use strict";
«webpack/lib/util/registerExternalSerializerœwebpack-sources/CachedSource ó«webpack/lib/util/registerExternalSerializerœwebpack-sources/ConcatSource«webpack/lib/util/registerExternalSerializer™webpack-sources/RawSource´/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ Z: function() { return /* binding */ AppShell; }
/* harmony export */ });
/* harmony import */ var react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(5893);
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(7294);
/* harmony import */ var next_router__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(1163);
/* harmony import */ var next_router__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(next_router__WEBPACK_IMPORTED_MODULE_2__);
/* harmony import */ var _icons__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(9415);
«webpack/lib/util/registerExternalSerializerwebpack-sources/ReplaceSourceUimport { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { useMemo } from "react";
import { useRouter } from "next/router";
import { Icon } from "./icons";
const tabs = [
{
href: "/",
label: "首页",
icon: "home"
},
{
href: "/stats",
label: "统计",
icon: "chart"
},
{
href: "/recurring",
label: "周期",
icon: "repeat"
},
{
href: "/categories",
label: "分类",
icon: "tags"
}
];
export default function AppShell(param) {
let { title, subtitle, actions, headerContent, children, contentClassName = "", hideNav = false, compactHeader = false } = param;
const router = useRouter();
const activeTab = useMemo(()=>{
const matched = tabs.find((tab)=>tab.href === router.pathname);
return (matched === null || matched === void 0 ? void 0 : matched.href) || null;
}, [
router.pathname
]);
const headerNode = headerContent || (title ? /*#__PURE__*/ _jsxs("div", {
className: "app-header__text",
children: [
/*#__PURE__*/ _jsx("h1", {
children: title
}),
subtitle ? /*#__PURE__*/ _jsx("p", {
children: subtitle
}) : null
]
}) : null);
const headerClassName = [
"app-header",
compactHeader ? "app-header--compact" : "",
headerNode ? "app-header--with-content" : "app-header--actions-only",
actions ? "app-header--with-actions" : ""
].filter(Boolean).join(" ");
return /*#__PURE__*/ _jsxs("div", {
className: "app-shell".concat(hideNav ? " app-shell--standalone" : ""),
children: [
/*#__PURE__*/ _jsx("header", {
className: headerClassName,
children: /*#__PURE__*/ _jsxs("div", {
className: "app-shell__container app-header__inner",
children: [
headerNode ? /*#__PURE__*/ _jsx("div", {
className: "app-header__main",
children: headerNode
}) : /*#__PURE__*/ _jsx("div", {
className: "app-header__spacer",
"aria-hidden": "true"
}),
actions ? /*#__PURE__*/ _jsx("div", {
className: "app-header__actions",
children: actions
}) : null
]
})
}),
/*#__PURE__*/ _jsx("main", {
className: "app-content ".concat(contentClassName).trim(),
children: /*#__PURE__*/ _jsx("div", {
className: "app-shell__container app-content__inner",
children: children
})
}),
!hideNav ? /*#__PURE__*/ _jsx("nav", {
className: "bottom-nav",
"aria-label": "主导航",
children: /*#__PURE__*/ _jsx("div", {
className: "app-shell__container bottom-nav__inner",
children: tabs.map((tab)=>{
const isActive = activeTab === tab.href;
return /*#__PURE__*/ _jsxs("button", {
type: "button",
className: "bottom-nav__item".concat(isActive ? " is-active" : ""),
onClick: ()=>router.push(tab.href),
"aria-current": isActive ? "page" : undefined,
children: [
/*#__PURE__*/ _jsx(Icon, {
name: tab.icon,
size: 20
}),
/*#__PURE__*/ _jsx("span", {
children: tab.label
})
]
}, tab.href);
})
})
}) : null
]
});
}
e>@_a_ˆŠ¨þ ÁÉäêûÿ_bÁÄBFÏÒ48ßâM P <
?
¾
Á
þ 
}=@¶(0,next_router__WEBPACK_IMPORTED_MODULE_2__.useRouter)®(0,react__WEBPACK_IMPORTED_MODULE_1__.useMemo)·(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)¶(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)ÿþÿþÿÿÿÿÿÿÿþÿ±_icons__WEBPACK_IMPORTED_MODULE_3__/* .Icon */ .Jþ†buffer†source„size„maps„hashõ/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ Z: function() { return /* binding */ AppShell; }
/* harmony export */ });
/* harmony import */ var react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(5893);
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(7294);
/* harmony import */ var next_router__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(1163);
/* harmony import */ var next_router__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(next_router__WEBPACK_IMPORTED_MODULE_2__);
/* harmony import */ var _icons__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(9415);
const tabs = [
{
href: "/",
label: "首页",
icon: "home"
},
{
href: "/stats",
label: "统计",
icon: "chart"
},
{
href: "/recurring",
label: "周期",
icon: "repeat"
},
{
href: "/categories",
label: "分类",
icon: "tags"
}
];
function AppShell(param) {
let { title, subtitle, actions, headerContent, children, contentClassName = "", hideNav = false, compactHeader = false } = param;
const router = (0,next_router__WEBPACK_IMPORTED_MODULE_2__.useRouter)();
const activeTab = (0,react__WEBPACK_IMPORTED_MODULE_1__.useMemo)(()=>{
const matched = tabs.find((tab)=>tab.href === router.pathname);
return (matched === null || matched === void 0 ? void 0 : matched.href) || null;
}, [
router.pathname
]);
const headerNode = headerContent || (title ? /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "app-header__text",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("h1", {
children: title
}),
subtitle ? /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("p", {
children: subtitle
}) : null
]
}) : null);
const headerClassName = [
"app-header",
compactHeader ? "app-header--compact" : "",
headerNode ? "app-header--with-content" : "app-header--actions-only",
actions ? "app-header--with-actions" : ""
].filter(Boolean).join(" ");
return /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "app-shell".concat(hideNav ? " app-shell--standalone" : ""),
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("header", {
className: headerClassName,
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "app-shell__container app-header__inner",
children: [
headerNode ? /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "app-header__main",
children: headerNode
}) : /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "app-header__spacer",
"aria-hidden": "true"
}),
actions ? /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "app-header__actions",
children: actions
}) : null
]
})
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("main", {
className: "app-content ".concat(contentClassName).trim(),
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "app-shell__container app-content__inner",
children: children
})
}),
!hideNav ? /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("nav", {
className: "bottom-nav",
"aria-label": "主导航",
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "app-shell__container bottom-nav__inner",
children: tabs.map((tab)=>{
const isActive = activeTab === tab.href;
return /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("button", {
type: "button",
className: "bottom-nav__item".concat(isActive ? " is-active" : ""),
onClick: ()=>router.push(tab.href),
"aria-current": isActive ? "page" : undefined,
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_icons__WEBPACK_IMPORTED_MODULE_3__/* .Icon */ .J, {
name: tab.icon,
size: 20
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("span", {
children: tab.label
})
]
}, tab.href);
})
})
}) : null
]
});
}
”{"finalSource":true}ƒmapbufferedMap ConcatSourceRawSource´/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ Z: function() { return /* binding */ AppShell; }
/* harmony export */ });
/* harmony import */ var react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(5893);
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(7294);
/* harmony import */ var next_router__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(1163);
/* harmony import */ var next_router__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(next_router__WEBPACK_IMPORTED_MODULE_2__);
/* harmony import */ var _icons__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(9415);
ReplaceSourceRawSourceUimport { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { useMemo } from "react";
import { useRouter } from "next/router";
import { Icon } from "./icons";
const tabs = [
{
href: "/",
label: "首页",
icon: "home"
},
{
href: "/stats",
label: "统计",
icon: "chart"
},
{
href: "/recurring",
label: "周期",
icon: "repeat"
},
{
href: "/categories",
label: "分类",
icon: "tags"
}
];
export default function AppShell(param) {
let { title, subtitle, actions, headerContent, children, contentClassName = "", hideNav = false, compactHeader = false } = param;
const router = useRouter();
const activeTab = useMemo(()=>{
const matched = tabs.find((tab)=>tab.href === router.pathname);
return (matched === null || matched === void 0 ? void 0 : matched.href) || null;
}, [
router.pathname
]);
const headerNode = headerContent || (title ? /*#__PURE__*/ _jsxs("div", {
className: "app-header__text",
children: [
/*#__PURE__*/ _jsx("h1", {
children: title
}),
subtitle ? /*#__PURE__*/ _jsx("p", {
children: subtitle
}) : null
]
}) : null);
const headerClassName = [
"app-header",
compactHeader ? "app-header--compact" : "",
headerNode ? "app-header--with-content" : "app-header--actions-only",
actions ? "app-header--with-actions" : ""
].filter(Boolean).join(" ");
return /*#__PURE__*/ _jsxs("div", {
className: "app-shell".concat(hideNav ? " app-shell--standalone" : ""),
children: [
/*#__PURE__*/ _jsx("header", {
className: headerClassName,
children: /*#__PURE__*/ _jsxs("div", {
className: "app-shell__container app-header__inner",
children: [
headerNode ? /*#__PURE__*/ _jsx("div", {
className: "app-header__main",
children: headerNode
}) : /*#__PURE__*/ _jsx("div", {
className: "app-header__spacer",
"aria-hidden": "true"
}),
actions ? /*#__PURE__*/ _jsx("div", {
className: "app-header__actions",
children: actions
}) : null
]
})
}),
/*#__PURE__*/ _jsx("main", {
className: "app-content ".concat(contentClassName).trim(),
children: /*#__PURE__*/ _jsx("div", {
className: "app-shell__container app-content__inner",
children: children
})
}),
!hideNav ? /*#__PURE__*/ _jsx("nav", {
className: "bottom-nav",
"aria-label": "主导航",
children: /*#__PURE__*/ _jsx("div", {
className: "app-shell__container bottom-nav__inner",
children: tabs.map((tab)=>{
const isActive = activeTab === tab.href;
return /*#__PURE__*/ _jsxs("button", {
type: "button",
className: "bottom-nav__item".concat(isActive ? " is-active" : ""),
onClick: ()=>router.push(tab.href),
"aria-current": isActive ? "page" : undefined,
children: [
/*#__PURE__*/ _jsx(Icon, {
name: tab.icon,
size: 20
}),
/*#__PURE__*/ _jsx("span", {
children: tab.label
})
]
}, tab.href);
})
})
}) : null
]
});
}
}062undefined6495undefined97136undefined138168undefined510524undefined705713(0,next_router__WEBPACK_IMPORTED_MODULE_2__.useRouter)undefined740746(0,react__WEBPACK_IMPORTED_MODULE_1__.useMemo)undefined10191023(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined11191122(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined12171220(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined16021606(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined17431746(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined18441848(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined20152018(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined21802183(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined23812384(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined26202623(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined27502753(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined29492952(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined30813084(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined33263330(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined37093712(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined37143717_icons__WEBPACK_IMPORTED_MODULE_3__/* .Icon */ .Jundefined39013904(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined
/***/ }) ìc/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
"use strict";
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ Z: function() { return /* binding */ AppShell; }
/* harmony export */ });
/* harmony import */ var react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(5893);
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(7294);
/* harmony import */ var next_router__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(1163);
/* harmony import */ var next_router__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(next_router__WEBPACK_IMPORTED_MODULE_2__);
/* harmony import */ var _icons__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(9415);
const tabs = [
{
href: "/",
label: "首页",
icon: "home"
},
{
href: "/stats",
label: "统计",
icon: "chart"
},
{
href: "/recurring",
label: "周期",
icon: "repeat"
},
{
href: "/categories",
label: "分类",
icon: "tags"
}
];
function AppShell(param) {
let { title, subtitle, actions, headerContent, children, contentClassName = "", hideNav = false, compactHeader = false } = param;
const router = (0,next_router__WEBPACK_IMPORTED_MODULE_2__.useRouter)();
const activeTab = (0,react__WEBPACK_IMPORTED_MODULE_1__.useMemo)(()=>{
const matched = tabs.find((tab)=>tab.href === router.pathname);
return (matched === null || matched === void 0 ? void 0 : matched.href) || null;
}, [
router.pathname
]);
const headerNode = headerContent || (title ? /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "app-header__text",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("h1", {
children: title
}),
subtitle ? /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("p", {
children: subtitle
}) : null
]
}) : null);
const headerClassName = [
"app-header",
compactHeader ? "app-header--compact" : "",
headerNode ? "app-header--with-content" : "app-header--actions-only",
actions ? "app-header--with-actions" : ""
].filter(Boolean).join(" ");
return /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "app-shell".concat(hideNav ? " app-shell--standalone" : ""),
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("header", {
className: headerClassName,
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "app-shell__container app-header__inner",
children: [
headerNode ? /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "app-header__main",
children: headerNode
}) : /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "app-header__spacer",
"aria-hidden": "true"
}),
actions ? /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "app-header__actions",
children: actions
}) : null
]
})
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("main", {
className: "app-content ".concat(contentClassName).trim(),
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "app-shell__container app-content__inner",
children: children
})
}),
!hideNav ? /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("nav", {
className: "bottom-nav",
"aria-label": "主导航",
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "app-shell__container bottom-nav__inner",
children: tabs.map((tab)=>{
const isActive = activeTab === tab.href;
return /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("button", {
type: "button",
className: "bottom-nav__item".concat(isActive ? " is-active" : ""),
onClick: ()=>router.push(tab.href),
"aria-current": isActive ? "page" : undefined,
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_icons__WEBPACK_IMPORTED_MODULE_3__/* .Icon */ .J, {
name: tab.icon,
size: 20
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("span", {
children: tab.label
})
]
}, tab.href);
})
})
}) : null
]
});
}
/***/ }) íð  ñd/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
"use strict";
ð´/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ Z: function() { return /* binding */ AppShell; }
/* harmony export */ });
/* harmony import */ var react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(5893);
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(7294);
/* harmony import */ var next_router__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(1163);
/* harmony import */ var next_router__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(next_router__WEBPACK_IMPORTED_MODULE_2__);
/* harmony import */ var _icons__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(9415);
ñUimport { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { useMemo } from "react";
import { useRouter } from "next/router";
import { Icon } from "./icons";
const tabs = [
{
href: "/",
label: "首页",
icon: "home"
},
{
href: "/stats",
label: "统计",
icon: "chart"
},
{
href: "/recurring",
label: "周期",
icon: "repeat"
},
{
href: "/categories",
label: "分类",
icon: "tags"
}
];
export default function AppShell(param) {
let { title, subtitle, actions, headerContent, children, contentClassName = "", hideNav = false, compactHeader = false } = param;
const router = useRouter();
const activeTab = useMemo(()=>{
const matched = tabs.find((tab)=>tab.href === router.pathname);
return (matched === null || matched === void 0 ? void 0 : matched.href) || null;
}, [
router.pathname
]);
const headerNode = headerContent || (title ? /*#__PURE__*/ _jsxs("div", {
className: "app-header__text",
children: [
/*#__PURE__*/ _jsx("h1", {
children: title
}),
subtitle ? /*#__PURE__*/ _jsx("p", {
children: subtitle
}) : null
]
}) : null);
const headerClassName = [
"app-header",
compactHeader ? "app-header--compact" : "",
headerNode ? "app-header--with-content" : "app-header--actions-only",
actions ? "app-header--with-actions" : ""
].filter(Boolean).join(" ");
return /*#__PURE__*/ _jsxs("div", {
className: "app-shell".concat(hideNav ? " app-shell--standalone" : ""),
children: [
/*#__PURE__*/ _jsx("header", {
className: headerClassName,
children: /*#__PURE__*/ _jsxs("div", {
className: "app-shell__container app-header__inner",
children: [
headerNode ? /*#__PURE__*/ _jsx("div", {
className: "app-header__main",
children: headerNode
}) : /*#__PURE__*/ _jsx("div", {
className: "app-header__spacer",
"aria-hidden": "true"
}),
actions ? /*#__PURE__*/ _jsx("div", {
className: "app-header__actions",
children: actions
}) : null
]
})
}),
/*#__PURE__*/ _jsx("main", {
className: "app-content ".concat(contentClassName).trim(),
children: /*#__PURE__*/ _jsx("div", {
className: "app-shell__container app-content__inner",
children: children
})
}),
!hideNav ? /*#__PURE__*/ _jsx("nav", {
className: "bottom-nav",
"aria-label": "主导航",
children: /*#__PURE__*/ _jsx("div", {
className: "app-shell__container bottom-nav__inner",
children: tabs.map((tab)=>{
const isActive = activeTab === tab.href;
return /*#__PURE__*/ _jsxs("button", {
type: "button",
className: "bottom-nav__item".concat(isActive ? " is-active" : ""),
onClick: ()=>router.push(tab.href),
"aria-current": isActive ? "page" : undefined,
children: [
/*#__PURE__*/ _jsx(Icon, {
name: tab.icon,
size: 20
}),
/*#__PURE__*/ _jsx("span", {
children: tab.label
})
]
}, tab.href);
})
})
}) : null
]
});
}
}062undefined6495undefined97136undefined138168undefined510524undefined705713(0,next_router__WEBPACK_IMPORTED_MODULE_2__.useRouter)undefined740746(0,react__WEBPACK_IMPORTED_MODULE_1__.useMemo)undefined10191023(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined11191122(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined12171220(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined16021606(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined17431746(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined18441848(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined20152018(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined21802183(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined23812384(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined26202623(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined27502753(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined29492952(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined30813084(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined33263330(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined37093712(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined37143717_icons__WEBPACK_IMPORTED_MODULE_3__/* .Icon */ .Jundefined39013904(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefinedòó,
/***/ 610:
 O«webpack/lib/util/registerExternalSerializerœwebpack-sources/ConcatSource«webpack/lib/util/registerExternalSerializer™webpack-sources/RawSourced/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
"use strict";
«webpack/lib/util/registerExternalSerializerœwebpack-sources/CachedSource ö«webpack/lib/util/registerExternalSerializerœwebpack-sources/ConcatSource«webpack/lib/util/registerExternalSerializer™webpack-sources/RawSource/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ Ap: function() { return /* binding */ getCategoryStats; },
/* harmony export */ CP: function() { return /* binding */ getCategories; },
/* harmony export */ JS: function() { return /* binding */ deleteRecord; },
/* harmony export */ Kp: function() { return /* binding */ searchRecords; },
/* harmony export */ Q6: function() { return /* binding */ getRecords; },
/* harmony export */ TZ: function() { return /* binding */ deleteRecurringBill; },
/* harmony export */ Vn: function() { return /* binding */ getRecurringBills; },
/* harmony export */ Vs: function() { return /* binding */ updateRecord; },
/* harmony export */ Xu: function() { return /* binding */ applyRecurringBill; },
/* harmony export */ ae: function() { return /* binding */ createRecord; },
/* harmony export */ dk: function() { return /* binding */ importData; },
/* harmony export */ fy: function() { return /* binding */ getStats; },
/* harmony export */ gR: function() { return /* binding */ getTrendStats; },
/* harmony export */ jl: function() { return /* binding */ isLoggedIn; },
/* harmony export */ k4: function() { return /* binding */ createCategory; },
/* harmony export */ kS: function() { return /* binding */ logout; },
/* harmony export */ u1: function() { return /* binding */ exportData; },
/* harmony export */ uO: function() { return /* binding */ updateRecurringBill; },
/* harmony export */ uu: function() { return /* binding */ deleteCategory; },
/* harmony export */ x4: function() { return /* binding */ login; },
/* harmony export */ z0: function() { return /* binding */ createRecurringBill; },
/* harmony export */ z2: function() { return /* binding */ register; }
/* harmony export */ });
«webpack/lib/util/registerExternalSerializerwebpack-sources/ReplaceSource²const API_BASE = ""; // ä½¿ç”¨ç›¸å¯¹è·¯å¾„ï¼ŒåŒæºä»£ç
function getAuthHeaders() {
const token = "object" !== "undefined" ? localStorage.getItem("token") : null;
return token ? {
Authorization: "Bearer ".concat(token)
} : {};
}
async function apiCall(url) {
let options = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : {};
const res = await fetch("".concat(API_BASE).concat(url), {
...options,
headers: {
"Content-Type": "application/json",
...getAuthHeaders(),
...options.headers
}
});
const data = await res.json();
if (!res.ok) {
throw new Error(data.error || "请求失败");
}
return data;
}
// 认è¯
export async function login(username, password) {
const data = await apiCall("/api/auth/login", {
method: "POST",
body: JSON.stringify({
username,
password
})
});
if ("object" !== "undefined") {
localStorage.setItem("token", data.token);
}
return data;
}
export async function register(username, password) {
const data = await apiCall("/api/auth/register", {
method: "POST",
body: JSON.stringify({
username,
password
})
});
if ("object" !== "undefined") {
localStorage.setItem("token", data.token);
}
return data;
}
export function logout() {
if ("object" !== "undefined") {
localStorage.removeItem("token");
}
}
export function isLoggedIn() {
if ("object" === "undefined") return false;
return !!localStorage.getItem("token");
}
// 账目
export async function getRecords(month) {
let type = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : "all";
let url = "/api/records?month=".concat(month);
if (type !== "all") {
url += "&type=".concat(type);
}
return apiCall(url);
}
export async function getStats(month) {
return apiCall("/api/stats?month=".concat(month));
}
export async function createRecord(data) {
return apiCall("/api/records", {
method: "POST",
body: JSON.stringify(data)
});
}
export async function updateRecord(id, data) {
return apiCall("/api/records/".concat(id), {
method: "PUT",
body: JSON.stringify(data)
});
}
export async function deleteRecord(id) {
return apiCall("/api/records/".concat(id), {
method: "DELETE"
});
}
// æœç´¢
export async function searchRecords(keyword) {
return apiCall("/api/records/search?q=".concat(encodeURIComponent(keyword)));
}
// 统计
export async function getTrendStats() {
let period = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : "month", month = arguments.length > 1 ? arguments[1] : void 0;
let url = "/api/stats/trend?period=".concat(period);
if (month) url += "&month=".concat(month);
return apiCall(url);
}
export async function getCategoryStats(month) {
let type = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : "all";
let url = "/api/stats/category?type=".concat(type);
if (month) url += "&month=".concat(month);
return apiCall(url);
}
// 周期性账å
export async function getRecurringBills() {
return apiCall("/api/recurring");
}
export async function createRecurringBill(data) {
return apiCall("/api/recurring", {
method: "POST",
body: JSON.stringify(data)
});
}
export async function updateRecurringBill(id, data) {
return apiCall("/api/recurring/".concat(id), {
method: "PUT",
body: JSON.stringify(data)
});
}
export async function deleteRecurringBill(id) {
return apiCall("/api/recurring/".concat(id), {
method: "DELETE"
});
}
export async function applyRecurringBill(id) {
return apiCall("/api/recurring/".concat(id, "/apply"), {
method: "POST"
});
}
// 分类
export async function getCategories() {
return apiCall("/api/categories");
}
export async function createCategory(data) {
return apiCall("/api/categories", {
method: "POST",
body: JSON.stringify(data)
});
}
export async function deleteCategory(id) {
return apiCall("/api/categories/".concat(id), {
method: "DELETE"
});
}
// æ•°æ®å¯¼å…¥å¯¼å‡º
export async function exportData() {
return apiCall("/api/export");
}
export async function importData(data) {
return apiCall("/api/import", {
method: "POST",
body: JSON.stringify(data)
});
}
bRi_ÆÌ©ÀúbhœÓÙú V\kqÌÒag ˆ Ž W

M S _ e ³ ¹ Q
W
þ
ŠhnŽØÞ… true0„trueÿÿ…false‚{}†buffer†source„size„maps„hashÄ/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ Ap: function() { return /* binding */ getCategoryStats; },
/* harmony export */ CP: function() { return /* binding */ getCategories; },
/* harmony export */ JS: function() { return /* binding */ deleteRecord; },
/* harmony export */ Kp: function() { return /* binding */ searchRecords; },
/* harmony export */ Q6: function() { return /* binding */ getRecords; },
/* harmony export */ TZ: function() { return /* binding */ deleteRecurringBill; },
/* harmony export */ Vn: function() { return /* binding */ getRecurringBills; },
/* harmony export */ Vs: function() { return /* binding */ updateRecord; },
/* harmony export */ Xu: function() { return /* binding */ applyRecurringBill; },
/* harmony export */ ae: function() { return /* binding */ createRecord; },
/* harmony export */ dk: function() { return /* binding */ importData; },
/* harmony export */ fy: function() { return /* binding */ getStats; },
/* harmony export */ gR: function() { return /* binding */ getTrendStats; },
/* harmony export */ jl: function() { return /* binding */ isLoggedIn; },
/* harmony export */ k4: function() { return /* binding */ createCategory; },
/* harmony export */ kS: function() { return /* binding */ logout; },
/* harmony export */ u1: function() { return /* binding */ exportData; },
/* harmony export */ uO: function() { return /* binding */ updateRecurringBill; },
/* harmony export */ uu: function() { return /* binding */ deleteCategory; },
/* harmony export */ x4: function() { return /* binding */ login; },
/* harmony export */ z0: function() { return /* binding */ createRecurringBill; },
/* harmony export */ z2: function() { return /* binding */ register; }
/* harmony export */ });
const API_BASE = ""; // ä½¿ç”¨ç›¸å¯¹è·¯å¾„ï¼ŒåŒæºä»£ç
function getAuthHeaders() {
const token = true ? localStorage.getItem("token") : 0;
return token ? {
Authorization: "Bearer ".concat(token)
} : {};
}
async function apiCall(url) {
let options = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : {};
const res = await fetch("".concat(API_BASE).concat(url), {
...options,
headers: {
"Content-Type": "application/json",
...getAuthHeaders(),
...options.headers
}
});
const data = await res.json();
if (!res.ok) {
throw new Error(data.error || "请求失败");
}
return data;
}
// 认è¯
async function login(username, password) {
const data = await apiCall("/api/auth/login", {
method: "POST",
body: JSON.stringify({
username,
password
})
});
if (true) {
localStorage.setItem("token", data.token);
}
return data;
}
async function register(username, password) {
const data = await apiCall("/api/auth/register", {
method: "POST",
body: JSON.stringify({
username,
password
})
});
if (true) {
localStorage.setItem("token", data.token);
}
return data;
}
function logout() {
if (true) {
localStorage.removeItem("token");
}
}
function isLoggedIn() {
if (false) {}
return !!localStorage.getItem("token");
}
// 账目
async function getRecords(month) {
let type = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : "all";
let url = "/api/records?month=".concat(month);
if (type !== "all") {
url += "&type=".concat(type);
}
return apiCall(url);
}
async function getStats(month) {
return apiCall("/api/stats?month=".concat(month));
}
async function createRecord(data) {
return apiCall("/api/records", {
method: "POST",
body: JSON.stringify(data)
});
}
async function updateRecord(id, data) {
return apiCall("/api/records/".concat(id), {
method: "PUT",
body: JSON.stringify(data)
});
}
async function deleteRecord(id) {
return apiCall("/api/records/".concat(id), {
method: "DELETE"
});
}
// æœç´¢
async function searchRecords(keyword) {
return apiCall("/api/records/search?q=".concat(encodeURIComponent(keyword)));
}
// 统计
async function getTrendStats() {
let period = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : "month", month = arguments.length > 1 ? arguments[1] : void 0;
let url = "/api/stats/trend?period=".concat(period);
if (month) url += "&month=".concat(month);
return apiCall(url);
}
async function getCategoryStats(month) {
let type = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : "all";
let url = "/api/stats/category?type=".concat(type);
if (month) url += "&month=".concat(month);
return apiCall(url);
}
// 周期性账å
async function getRecurringBills() {
return apiCall("/api/recurring");
}
async function createRecurringBill(data) {
return apiCall("/api/recurring", {
method: "POST",
body: JSON.stringify(data)
});
}
async function updateRecurringBill(id, data) {
return apiCall("/api/recurring/".concat(id), {
method: "PUT",
body: JSON.stringify(data)
});
}
async function deleteRecurringBill(id) {
return apiCall("/api/recurring/".concat(id), {
method: "DELETE"
});
}
async function applyRecurringBill(id) {
return apiCall("/api/recurring/".concat(id, "/apply"), {
method: "POST"
});
}
// 分类
async function getCategories() {
return apiCall("/api/categories");
}
async function createCategory(data) {
return apiCall("/api/categories", {
method: "POST",
body: JSON.stringify(data)
});
}
async function deleteCategory(id) {
return apiCall("/api/categories/".concat(id), {
method: "DELETE"
});
}
// æ•°æ®å¯¼å…¥å¯¼å‡º
async function exportData() {
return apiCall("/api/export");
}
async function importData(data) {
return apiCall("/api/import", {
method: "POST",
body: JSON.stringify(data)
});
}
”{"finalSource":true}ƒmapbufferedMap ConcatSourceRawSource/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ Ap: function() { return /* binding */ getCategoryStats; },
/* harmony export */ CP: function() { return /* binding */ getCategories; },
/* harmony export */ JS: function() { return /* binding */ deleteRecord; },
/* harmony export */ Kp: function() { return /* binding */ searchRecords; },
/* harmony export */ Q6: function() { return /* binding */ getRecords; },
/* harmony export */ TZ: function() { return /* binding */ deleteRecurringBill; },
/* harmony export */ Vn: function() { return /* binding */ getRecurringBills; },
/* harmony export */ Vs: function() { return /* binding */ updateRecord; },
/* harmony export */ Xu: function() { return /* binding */ applyRecurringBill; },
/* harmony export */ ae: function() { return /* binding */ createRecord; },
/* harmony export */ dk: function() { return /* binding */ importData; },
/* harmony export */ fy: function() { return /* binding */ getStats; },
/* harmony export */ gR: function() { return /* binding */ getTrendStats; },
/* harmony export */ jl: function() { return /* binding */ isLoggedIn; },
/* harmony export */ k4: function() { return /* binding */ createCategory; },
/* harmony export */ kS: function() { return /* binding */ logout; },
/* harmony export */ u1: function() { return /* binding */ exportData; },
/* harmony export */ uO: function() { return /* binding */ updateRecurringBill; },
/* harmony export */ uu: function() { return /* binding */ deleteCategory; },
/* harmony export */ x4: function() { return /* binding */ login; },
/* harmony export */ z0: function() { return /* binding */ createRecurringBill; },
/* harmony export */ z2: function() { return /* binding */ register; }
/* harmony export */ });
ReplaceSourceRawSource²const API_BASE = ""; // ä½¿ç”¨ç›¸å¯¹è·¯å¾„ï¼ŒåŒæºä»£ç
function getAuthHeaders() {
const token = "object" !== "undefined" ? localStorage.getItem("token") : null;
return token ? {
Authorization: "Bearer ".concat(token)
} : {};
}
async function apiCall(url) {
let options = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : {};
const res = await fetch("".concat(API_BASE).concat(url), {
...options,
headers: {
"Content-Type": "application/json",
...getAuthHeaders(),
...options.headers
}
});
const data = await res.json();
if (!res.ok) {
throw new Error(data.error || "请求失败");
}
return data;
}
// 认è¯
export async function login(username, password) {
const data = await apiCall("/api/auth/login", {
method: "POST",
body: JSON.stringify({
username,
password
})
});
if ("object" !== "undefined") {
localStorage.setItem("token", data.token);
}
return data;
}
export async function register(username, password) {
const data = await apiCall("/api/auth/register", {
method: "POST",
body: JSON.stringify({
username,
password
})
});
if ("object" !== "undefined") {
localStorage.setItem("token", data.token);
}
return data;
}
export function logout() {
if ("object" !== "undefined") {
localStorage.removeItem("token");
}
}
export function isLoggedIn() {
if ("object" === "undefined") return false;
return !!localStorage.getItem("token");
}
// 账目
export async function getRecords(month) {
let type = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : "all";
let url = "/api/records?month=".concat(month);
if (type !== "all") {
url += "&type=".concat(type);
}
return apiCall(url);
}
export async function getStats(month) {
return apiCall("/api/stats?month=".concat(month));
}
export async function createRecord(data) {
return apiCall("/api/records", {
method: "POST",
body: JSON.stringify(data)
});
}
export async function updateRecord(id, data) {
return apiCall("/api/records/".concat(id), {
method: "PUT",
body: JSON.stringify(data)
});
}
export async function deleteRecord(id) {
return apiCall("/api/records/".concat(id), {
method: "DELETE"
});
}
// æœç´¢
export async function searchRecords(keyword) {
return apiCall("/api/records/search?q=".concat(encodeURIComponent(keyword)));
}
// 统计
export async function getTrendStats() {
let period = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : "month", month = arguments.length > 1 ? arguments[1] : void 0;
let url = "/api/stats/trend?period=".concat(period);
if (month) url += "&month=".concat(month);
return apiCall(url);
}
export async function getCategoryStats(month) {
let type = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : "all";
let url = "/api/stats/category?type=".concat(type);
if (month) url += "&month=".concat(month);
return apiCall(url);
}
// 周期性账å
export async function getRecurringBills() {
return apiCall("/api/recurring");
}
export async function createRecurringBill(data) {
return apiCall("/api/recurring", {
method: "POST",
body: JSON.stringify(data)
});
}
export async function updateRecurringBill(id, data) {
return apiCall("/api/recurring/".concat(id), {
method: "PUT",
body: JSON.stringify(data)
});
}
export async function deleteRecurringBill(id) {
return apiCall("/api/recurring/".concat(id), {
method: "DELETE"
});
}
export async function applyRecurringBill(id) {
return apiCall("/api/recurring/".concat(id, "/apply"), {
method: "POST"
});
}
// 分类
export async function getCategories() {
return apiCall("/api/categories");
}
export async function createCategory(data) {
return apiCall("/api/categories", {
method: "POST",
body: JSON.stringify(data)
});
}
export async function deleteCategory(id) {
return apiCall("/api/categories/".concat(id), {
method: "DELETE"
});
}
// æ•°æ®å¯¼å…¥å¯¼å‡º
export async function exportData() {
return apiCall("/api/export");
}
export async function importData(data) {
return apiCall("/api/import", {
method: "POST",
body: JSON.stringify(data)
});
}
ý82105 trueundefined1411440undefined710716undefined937960trueundefined10411047undefined12741297trueundefined13781384undefined14131436trueundefined14911497undefined15301553falseundefined15561568{}undefined16221628undefined18991905undefined19962002undefined21452151undefined23092315undefined24402446undefined25772583undefined28932899undefined31673173undefined32513257undefined34093415undefined35823588undefined37163722undefined38633869undefined39443950undefined40984104undefined42384244undefined43124318undefined
/***/ }) à2/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
"use strict";
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ Ap: function() { return /* binding */ getCategoryStats; },
/* harmony export */ CP: function() { return /* binding */ getCategories; },
/* harmony export */ JS: function() { return /* binding */ deleteRecord; },
/* harmony export */ Kp: function() { return /* binding */ searchRecords; },
/* harmony export */ Q6: function() { return /* binding */ getRecords; },
/* harmony export */ TZ: function() { return /* binding */ deleteRecurringBill; },
/* harmony export */ Vn: function() { return /* binding */ getRecurringBills; },
/* harmony export */ Vs: function() { return /* binding */ updateRecord; },
/* harmony export */ Xu: function() { return /* binding */ applyRecurringBill; },
/* harmony export */ ae: function() { return /* binding */ createRecord; },
/* harmony export */ dk: function() { return /* binding */ importData; },
/* harmony export */ fy: function() { return /* binding */ getStats; },
/* harmony export */ gR: function() { return /* binding */ getTrendStats; },
/* harmony export */ jl: function() { return /* binding */ isLoggedIn; },
/* harmony export */ k4: function() { return /* binding */ createCategory; },
/* harmony export */ kS: function() { return /* binding */ logout; },
/* harmony export */ u1: function() { return /* binding */ exportData; },
/* harmony export */ uO: function() { return /* binding */ updateRecurringBill; },
/* harmony export */ uu: function() { return /* binding */ deleteCategory; },
/* harmony export */ x4: function() { return /* binding */ login; },
/* harmony export */ z0: function() { return /* binding */ createRecurringBill; },
/* harmony export */ z2: function() { return /* binding */ register; }
/* harmony export */ });
const API_BASE = ""; // ä½¿ç”¨ç›¸å¯¹è·¯å¾„ï¼ŒåŒæºä»£ç
function getAuthHeaders() {
const token = true ? localStorage.getItem("token") : 0;
return token ? {
Authorization: "Bearer ".concat(token)
} : {};
}
async function apiCall(url) {
let options = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : {};
const res = await fetch("".concat(API_BASE).concat(url), {
...options,
headers: {
"Content-Type": "application/json",
...getAuthHeaders(),
...options.headers
}
});
const data = await res.json();
if (!res.ok) {
throw new Error(data.error || "请求失败");
}
return data;
}
// 认è¯
async function login(username, password) {
const data = await apiCall("/api/auth/login", {
method: "POST",
body: JSON.stringify({
username,
password
})
});
if (true) {
localStorage.setItem("token", data.token);
}
return data;
}
async function register(username, password) {
const data = await apiCall("/api/auth/register", {
method: "POST",
body: JSON.stringify({
username,
password
})
});
if (true) {
localStorage.setItem("token", data.token);
}
return data;
}
function logout() {
if (true) {
localStorage.removeItem("token");
}
}
function isLoggedIn() {
if (false) {}
return !!localStorage.getItem("token");
}
// 账目
async function getRecords(month) {
let type = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : "all";
let url = "/api/records?month=".concat(month);
if (type !== "all") {
url += "&type=".concat(type);
}
return apiCall(url);
}
async function getStats(month) {
return apiCall("/api/stats?month=".concat(month));
}
async function createRecord(data) {
return apiCall("/api/records", {
method: "POST",
body: JSON.stringify(data)
});
}
async function updateRecord(id, data) {
return apiCall("/api/records/".concat(id), {
method: "PUT",
body: JSON.stringify(data)
});
}
async function deleteRecord(id) {
return apiCall("/api/records/".concat(id), {
method: "DELETE"
});
}
// æœç´¢
async function searchRecords(keyword) {
return apiCall("/api/records/search?q=".concat(encodeURIComponent(keyword)));
}
// 统计
async function getTrendStats() {
let period = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : "month", month = arguments.length > 1 ? arguments[1] : void 0;
let url = "/api/stats/trend?period=".concat(period);
if (month) url += "&month=".concat(month);
return apiCall(url);
}
async function getCategoryStats(month) {
let type = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : "all";
let url = "/api/stats/category?type=".concat(type);
if (month) url += "&month=".concat(month);
return apiCall(url);
}
// 周期性账å
async function getRecurringBills() {
return apiCall("/api/recurring");
}
async function createRecurringBill(data) {
return apiCall("/api/recurring", {
method: "POST",
body: JSON.stringify(data)
});
}
async function updateRecurringBill(id, data) {
return apiCall("/api/recurring/".concat(id), {
method: "PUT",
body: JSON.stringify(data)
});
}
async function deleteRecurringBill(id) {
return apiCall("/api/recurring/".concat(id), {
method: "DELETE"
});
}
async function applyRecurringBill(id) {
return apiCall("/api/recurring/".concat(id, "/apply"), {
method: "POST"
});
}
// 分类
async function getCategories() {
return apiCall("/api/categories");
}
async function createCategory(data) {
return apiCall("/api/categories", {
method: "POST",
body: JSON.stringify(data)
});
}
async function deleteCategory(id) {
return apiCall("/api/categories/".concat(id), {
method: "DELETE"
});
}
// æ•°æ®å¯¼å…¥å¯¼å‡º
async function exportData() {
return apiCall("/api/export");
}
async function importData(data) {
return apiCall("/api/import", {
method: "POST",
body: JSON.stringify(data)
});
}
/***/ }) áä  åôå/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ Ap: function() { return /* binding */ getCategoryStats; },
/* harmony export */ CP: function() { return /* binding */ getCategories; },
/* harmony export */ JS: function() { return /* binding */ deleteRecord; },
/* harmony export */ Kp: function() { return /* binding */ searchRecords; },
/* harmony export */ Q6: function() { return /* binding */ getRecords; },
/* harmony export */ TZ: function() { return /* binding */ deleteRecurringBill; },
/* harmony export */ Vn: function() { return /* binding */ getRecurringBills; },
/* harmony export */ Vs: function() { return /* binding */ updateRecord; },
/* harmony export */ Xu: function() { return /* binding */ applyRecurringBill; },
/* harmony export */ ae: function() { return /* binding */ createRecord; },
/* harmony export */ dk: function() { return /* binding */ importData; },
/* harmony export */ fy: function() { return /* binding */ getStats; },
/* harmony export */ gR: function() { return /* binding */ getTrendStats; },
/* harmony export */ jl: function() { return /* binding */ isLoggedIn; },
/* harmony export */ k4: function() { return /* binding */ createCategory; },
/* harmony export */ kS: function() { return /* binding */ logout; },
/* harmony export */ u1: function() { return /* binding */ exportData; },
/* harmony export */ uO: function() { return /* binding */ updateRecurringBill; },
/* harmony export */ uu: function() { return /* binding */ deleteCategory; },
/* harmony export */ x4: function() { return /* binding */ login; },
/* harmony export */ z0: function() { return /* binding */ createRecurringBill; },
/* harmony export */ z2: function() { return /* binding */ register; }
/* harmony export */ });
æ²const API_BASE = ""; // ä½¿ç”¨ç›¸å¯¹è·¯å¾„ï¼ŒåŒæºä»£ç
function getAuthHeaders() {
const token = "object" !== "undefined" ? localStorage.getItem("token") : null;
return token ? {
Authorization: "Bearer ".concat(token)
} : {};
}
async function apiCall(url) {
let options = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : {};
const res = await fetch("".concat(API_BASE).concat(url), {
...options,
headers: {
"Content-Type": "application/json",
...getAuthHeaders(),
...options.headers
}
});
const data = await res.json();
if (!res.ok) {
throw new Error(data.error || "请求失败");
}
return data;
}
// 认è¯
export async function login(username, password) {
const data = await apiCall("/api/auth/login", {
method: "POST",
body: JSON.stringify({
username,
password
})
});
if ("object" !== "undefined") {
localStorage.setItem("token", data.token);
}
return data;
}
export async function register(username, password) {
const data = await apiCall("/api/auth/register", {
method: "POST",
body: JSON.stringify({
username,
password
})
});
if ("object" !== "undefined") {
localStorage.setItem("token", data.token);
}
return data;
}
export function logout() {
if ("object" !== "undefined") {
localStorage.removeItem("token");
}
}
export function isLoggedIn() {
if ("object" === "undefined") return false;
return !!localStorage.getItem("token");
}
// 账目
export async function getRecords(month) {
let type = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : "all";
let url = "/api/records?month=".concat(month);
if (type !== "all") {
url += "&type=".concat(type);
}
return apiCall(url);
}
export async function getStats(month) {
return apiCall("/api/stats?month=".concat(month));
}
export async function createRecord(data) {
return apiCall("/api/records", {
method: "POST",
body: JSON.stringify(data)
});
}
export async function updateRecord(id, data) {
return apiCall("/api/records/".concat(id), {
method: "PUT",
body: JSON.stringify(data)
});
}
export async function deleteRecord(id) {
return apiCall("/api/records/".concat(id), {
method: "DELETE"
});
}
// æœç´¢
export async function searchRecords(keyword) {
return apiCall("/api/records/search?q=".concat(encodeURIComponent(keyword)));
}
// 统计
export async function getTrendStats() {
let period = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : "month", month = arguments.length > 1 ? arguments[1] : void 0;
let url = "/api/stats/trend?period=".concat(period);
if (month) url += "&month=".concat(month);
return apiCall(url);
}
export async function getCategoryStats(month) {
let type = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : "all";
let url = "/api/stats/category?type=".concat(type);
if (month) url += "&month=".concat(month);
return apiCall(url);
}
// 周期性账å
export async function getRecurringBills() {
return apiCall("/api/recurring");
}
export async function createRecurringBill(data) {
return apiCall("/api/recurring", {
method: "POST",
body: JSON.stringify(data)
});
}
export async function updateRecurringBill(id, data) {
return apiCall("/api/recurring/".concat(id), {
method: "PUT",
body: JSON.stringify(data)
});
}
export async function deleteRecurringBill(id) {
return apiCall("/api/recurring/".concat(id), {
method: "DELETE"
});
}
export async function applyRecurringBill(id) {
return apiCall("/api/recurring/".concat(id, "/apply"), {
method: "POST"
});
}
// 分类
export async function getCategories() {
return apiCall("/api/categories");
}
export async function createCategory(data) {
return apiCall("/api/categories", {
method: "POST",
body: JSON.stringify(data)
});
}
export async function deleteCategory(id) {
return apiCall("/api/categories/".concat(id), {
method: "DELETE"
});
}
// æ•°æ®å¯¼å…¥å¯¼å‡º
export async function exportData() {
return apiCall("/api/export");
}
export async function importData(data) {
return apiCall("/api/import", {
method: "POST",
body: JSON.stringify(data)
});
}
ý82105 trueundefined1411440undefined710716undefined937960trueundefined10411047undefined12741297trueundefined13781384undefined14131436trueundefined14911497undefined15301553falseundefined15561568{}undefined16221628undefined18991905undefined19962002undefined21452151undefined23092315undefined24402446undefined25772583undefined28932899undefined31673173undefined32513257undefined34093415undefined35823588undefined37163722undefined38633869undefined39443950undefined40984104undefined42384244undefined43124318undefinedçè,
/***/ 8011:
 
ß«webpack/lib/util/registerExternalSerializerœwebpack-sources/ConcatSource«webpack/lib/util/registerExternalSerializer™webpack-sources/RawSourced/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
"use strict";
«webpack/lib/util/registerExternalSerializerœwebpack-sources/CachedSource `D«webpack/lib/util/registerExternalSerializerœwebpack-sources/ConcatSource«webpack/lib/util/registerExternalSerializer™webpack-sources/RawSource|__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": function() { return /* binding */ Stats; }
/* harmony export */ });
/* harmony import */ var react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(5893);
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(7294);
/* harmony import */ var next_router__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(1163);
/* harmony import */ var next_router__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(next_router__WEBPACK_IMPORTED_MODULE_2__);
/* harmony import */ var _lib_api__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(610);
/* harmony import */ var chart_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(3148);
/* harmony import */ var _components_AppShell__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(6248);
/* harmony import */ var _components_icons__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(9415);
/* harmony import */ var _components_ToastProvider__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(5546);
«webpack/lib/util/registerExternalSerializerwebpack-sources/ReplaceSourceŽ8import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { useState, useEffect, useRef } from "react";
import { useRouter } from "next/router";
import { isLoggedIn, getTrendStats, getCategoryStats } from "../lib/api";
import { Chart, registerables } from "chart.js";
import AppShell from "../components/AppShell";
import { Icon } from "../components/icons";
import { useToast } from "../components/ToastProvider";
Chart.register(...registerables);
export default function Stats() {
const router = useRouter();
const { showToast } = useToast();
const [period, setPeriod] = useState("month");
const [currentMonth, setCurrentMonth] = useState(new Date());
const [trendData, setTrendData] = useState([]);
const [categoryData, setCategoryData] = useState([]);
const [categoryType, setCategoryType] = useState("expense");
const [trendLoading, setTrendLoading] = useState(true);
const [categoryLoading, setCategoryLoading] = useState(true);
const trendChartRef = useRef(null);
const categoryChartRef = useRef(null);
const trendChartInstance = useRef(null);
const categoryChartInstance = useRef(null);
useEffect(()=>{
if (!isLoggedIn()) {
router.push("/login");
return;
}
loadTrendData();
}, [
router,
period,
currentMonth
]);
useEffect(()=>{
if (!isLoggedIn()) return;
loadCategoryData();
}, [
router,
currentMonth,
categoryType
]);
const getMonthStr = (date)=>"".concat(date.getFullYear(), "-").concat(String(date.getMonth() + 1).padStart(2, "0"));
const formatMonth = (date)=>"".concat(date.getFullYear(), "年").concat(date.getMonth() + 1, "月");
const loadTrendData = async ()=>{
setTrendLoading(true);
try {
const trend = await getTrendStats(period, getMonthStr(currentMonth));
setTrendData(trend);
} catch (err) {
console.error(err);
showToast(err.message, {
tone: "error"
});
} finally{
setTrendLoading(false);
}
};
const loadCategoryData = async ()=>{
setCategoryLoading(true);
try {
const category = await getCategoryStats(getMonthStr(currentMonth), categoryType);
setCategoryData(category);
} catch (err) {
console.error(err);
showToast(err.message, {
tone: "error"
});
} finally{
setCategoryLoading(false);
}
};
useEffect(()=>{
if (trendChartInstance.current) {
trendChartInstance.current.destroy();
}
if (trendChartRef.current && trendData.length > 0) {
const ctx = trendChartRef.current.getContext("2d");
trendChartInstance.current = new Chart(ctx, {
type: "line",
data: {
labels: trendData.map((item)=>item.period),
datasets: [
{
label: "æ”¶å…¥",
data: trendData.map((item)=>item.income),
borderColor: "#10b981",
backgroundColor: "rgba(16, 185, 129, 0.1)",
fill: true,
tension: 0.4
},
{
label: "支出",
data: trendData.map((item)=>item.expense),
borderColor: "#ef4444",
backgroundColor: "rgba(239, 68, 68, 0.1)",
fill: true,
tension: 0.4
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: "top"
}
},
scales: {
y: {
beginAtZero: true
}
}
}
});
}
}, [
trendData
]);
useEffect(()=>{
if (categoryChartInstance.current) {
categoryChartInstance.current.destroy();
}
if (categoryChartRef.current && categoryData.length > 0) {
const ctx = categoryChartRef.current.getContext("2d");
const colors = [
"#2563eb",
"#10b981",
"#f59e0b",
"#ef4444",
"#8b5cf6",
"#ec4899",
"#06b6d4",
"#84cc16",
"#f97316",
"#6366f1"
];
categoryChartInstance.current = new Chart(ctx, {
type: "doughnut",
data: {
labels: categoryData.map((item)=>item.category),
datasets: [
{
data: categoryData.map((item)=>item.total),
backgroundColor: colors.slice(0, categoryData.length)
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: "bottom"
}
}
}
});
}
}, [
categoryData
]);
const prevMonth = ()=>{
const newDate = new Date(currentMonth);
newDate.setMonth(newDate.getMonth() - 1);
setCurrentMonth(newDate);
};
const nextMonth = ()=>{
const newDate = new Date(currentMonth);
newDate.setMonth(newDate.getMonth() + 1);
setCurrentMonth(newDate);
};
const totalCategoryAmount = categoryData.reduce((sum, item)=>sum + item.total, 0);
const headerContent = /*#__PURE__*/ _jsxs("div", {
className: "header-center-container",
children: [
/*#__PURE__*/ _jsx("div", {
className: "header-spacer"
}),
/*#__PURE__*/ _jsxs("div", {
className: "month-switcher-container",
children: [
/*#__PURE__*/ _jsx("button", {
type: "button",
className: "icon-button",
"aria-label": "上个月",
onClick: prevMonth,
children: /*#__PURE__*/ _jsx(Icon, {
name: "chevronLeft",
size: 18
})
}),
/*#__PURE__*/ _jsx("span", {
className: "month-switcher__label",
children: formatMonth(currentMonth)
}),
/*#__PURE__*/ _jsx("button", {
type: "button",
className: "icon-button",
"aria-label": "下个月",
onClick: nextMonth,
children: /*#__PURE__*/ _jsx(Icon, {
name: "chevronRight",
size: 18
})
})
]
}),
/*#__PURE__*/ _jsx("div", {
className: "header-right-actions",
children: /*#__PURE__*/ _jsx("button", {
type: "button",
className: "icon-button",
"aria-label": "设置",
onClick: ()=>router.push("/settings"),
children: /*#__PURE__*/ _jsx(Icon, {
name: "settings",
size: 18
})
})
})
]
});
return /*#__PURE__*/ _jsxs(AppShell, {
headerContent: headerContent,
compactHeader: true,
children: [
/*#__PURE__*/ _jsxs("section", {
className: "card section-card section-card--stats",
children: [
/*#__PURE__*/ _jsx("div", {
className: "section-heading section-heading--compact",
children: /*#__PURE__*/ _jsxs("div", {
children: [
/*#__PURE__*/ _jsx("p", {
className: "eyebrow",
children: "趋势"
}),
/*#__PURE__*/ _jsx("h2", {
children: "收支趋势"
})
]
})
}),
/*#__PURE__*/ _jsxs("div", {
className: "period-tabs",
children: [
/*#__PURE__*/ _jsx("button", {
type: "button",
className: period === "week" ? "active" : "",
onClick: ()=>setPeriod("week"),
children: "周"
}),
/*#__PURE__*/ _jsx("button", {
type: "button",
className: period === "month" ? "active" : "",
onClick: ()=>setPeriod("month"),
children: "月"
}),
/*#__PURE__*/ _jsx("button", {
type: "button",
className: period === "year" ? "active" : "",
onClick: ()=>setPeriod("year"),
children: "å¹´"
})
]
}),
/*#__PURE__*/ _jsx("div", {
className: "chart-container chart-container--large chart-container--stats",
children: trendLoading ? /*#__PURE__*/ _jsx("div", {
className: "loading-state",
children: "加载中..."
}) : /*#__PURE__*/ _jsx("canvas", {
ref: trendChartRef
})
})
]
}),
/*#__PURE__*/ _jsxs("section", {
className: "card section-card section-card--stats",
children: [
/*#__PURE__*/ _jsx("div", {
className: "section-heading section-heading--compact",
children: /*#__PURE__*/ _jsx("div", {
children: /*#__PURE__*/ _jsx("h2", {
children: "分类明细"
})
})
}),
/*#__PURE__*/ _jsxs("div", {
className: "category-type-tabs",
children: [
/*#__PURE__*/ _jsx("button", {
type: "button",
className: categoryType === "expense" ? "active" : "",
onClick: ()=>setCategoryType("expense"),
children: "支出"
}),
/*#__PURE__*/ _jsx("button", {
type: "button",
className: categoryType === "income" ? "active" : "",
onClick: ()=>setCategoryType("income"),
children: "æ”¶å…¥"
})
]
}),
/*#__PURE__*/ _jsx("div", {
className: "category-breakdown category-breakdown--stats",
children: categoryData.length > 0 ? categoryData.map((cat, index)=>{
const percent = totalCategoryAmount ? (cat.total / totalCategoryAmount * 100).toFixed(1) : "0.0";
return /*#__PURE__*/ _jsxs("div", {
className: "stats-category-item",
children: [
/*#__PURE__*/ _jsxs("div", {
className: "stats-category-header",
children: [
/*#__PURE__*/ _jsx("span", {
className: "stats-category-name",
children: cat.category
}),
/*#__PURE__*/ _jsx("span", {
className: "stats-category-amount",
children: cat.total.toFixed(2)
})
]
}),
/*#__PURE__*/ _jsx("div", {
className: "stats-category-bar-wrap",
children: /*#__PURE__*/ _jsx("div", {
className: "stats-category-bar",
style: {
width: "".concat(percent, "%")
}
})
})
]
}, index);
}) : /*#__PURE__*/ _jsx("div", {
className: "empty-state empty-state--inline",
children: "暂无数æ®"
})
})
]
})
]
});
}
eM>@su_œžæèFHrtª¬°¾ÊÎÜ *1V]œÑØ EL_ÈÏò÷"JOzŒ©²]ezƒIUëú'
/
G K ï÷Z^-_1˜ûÿsvx{+.ìïàãåè²µ
ÉÍÏÖL _P á ä n!r!Ó!Ö!ž"¡"v#z#$$L%O%•&˜&((¶(¹(V)Y)**™*œ*&+)+h+k+,T,°,³,
.
./Ž/ø0ü0§1«1p2s23…3ç4ê4ƒ5†5V7Y7µchart_js__WEBPACK_IMPORTED_MODULE_6__/* .Chart */ .kL½chart_js__WEBPACK_IMPORTED_MODULE_6__/* .registerables */ .zX¶(0,next_router__WEBPACK_IMPORTED_MODULE_2__.useRouter)Ì(0,_components_ToastProvider__WEBPACK_IMPORTED_MODULE_5__/* .useToast */ .p)¯(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)ÿÿÿÿÿÿ­(0,react__WEBPACK_IMPORTED_MODULE_1__.useRef)ÿÿÿ°(0,react__WEBPACK_IMPORTED_MODULE_1__.useEffect)¾(0,_lib_api__WEBPACK_IMPORTED_MODULE_7__/* .isLoggedIn */ .jl)þÿÁ(0,_lib_api__WEBPACK_IMPORTED_MODULE_7__/* .getTrendStats */ .gR)Ä(0,_lib_api__WEBPACK_IMPORTED_MODULE_7__/* .getCategoryStats */ .Ap)üöüö·(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)¶(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)þÿÿ¼_components_icons__WEBPACK_IMPORTED_MODULE_4__/* .Icon */ .JþþþÿþþþÿýÅ_components_AppShell__WEBPACK_IMPORTED_MODULE_3__/* ["default"] */ .Züýüýýüýýýýýýüýýýüýýýüüýýýýý†buffer†source„size„maps„hash$H__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": function() { return /* binding */ Stats; }
/* harmony export */ });
/* harmony import */ var react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(5893);
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(7294);
/* harmony import */ var next_router__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(1163);
/* harmony import */ var next_router__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(next_router__WEBPACK_IMPORTED_MODULE_2__);
/* harmony import */ var _lib_api__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(610);
/* harmony import */ var chart_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(3148);
/* harmony import */ var _components_AppShell__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(6248);
/* harmony import */ var _components_icons__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(9415);
/* harmony import */ var _components_ToastProvider__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(5546);
chart_js__WEBPACK_IMPORTED_MODULE_6__/* .Chart */ .kL.register(...chart_js__WEBPACK_IMPORTED_MODULE_6__/* .registerables */ .zX);
function Stats() {
const router = (0,next_router__WEBPACK_IMPORTED_MODULE_2__.useRouter)();
const { showToast } = (0,_components_ToastProvider__WEBPACK_IMPORTED_MODULE_5__/* .useToast */ .p)();
const [period, setPeriod] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)("month");
const [currentMonth, setCurrentMonth] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)(new Date());
const [trendData, setTrendData] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)([]);
const [categoryData, setCategoryData] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)([]);
const [categoryType, setCategoryType] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)("expense");
const [trendLoading, setTrendLoading] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)(true);
const [categoryLoading, setCategoryLoading] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)(true);
const trendChartRef = (0,react__WEBPACK_IMPORTED_MODULE_1__.useRef)(null);
const categoryChartRef = (0,react__WEBPACK_IMPORTED_MODULE_1__.useRef)(null);
const trendChartInstance = (0,react__WEBPACK_IMPORTED_MODULE_1__.useRef)(null);
const categoryChartInstance = (0,react__WEBPACK_IMPORTED_MODULE_1__.useRef)(null);
(0,react__WEBPACK_IMPORTED_MODULE_1__.useEffect)(()=>{
if (!(0,_lib_api__WEBPACK_IMPORTED_MODULE_7__/* .isLoggedIn */ .jl)()) {
router.push("/login");
return;
}
loadTrendData();
}, [
router,
period,
currentMonth
]);
(0,react__WEBPACK_IMPORTED_MODULE_1__.useEffect)(()=>{
if (!(0,_lib_api__WEBPACK_IMPORTED_MODULE_7__/* .isLoggedIn */ .jl)()) return;
loadCategoryData();
}, [
router,
currentMonth,
categoryType
]);
const getMonthStr = (date)=>"".concat(date.getFullYear(), "-").concat(String(date.getMonth() + 1).padStart(2, "0"));
const formatMonth = (date)=>"".concat(date.getFullYear(), "年").concat(date.getMonth() + 1, "月");
const loadTrendData = async ()=>{
setTrendLoading(true);
try {
const trend = await (0,_lib_api__WEBPACK_IMPORTED_MODULE_7__/* .getTrendStats */ .gR)(period, getMonthStr(currentMonth));
setTrendData(trend);
} catch (err) {
console.error(err);
showToast(err.message, {
tone: "error"
});
} finally{
setTrendLoading(false);
}
};
const loadCategoryData = async ()=>{
setCategoryLoading(true);
try {
const category = await (0,_lib_api__WEBPACK_IMPORTED_MODULE_7__/* .getCategoryStats */ .Ap)(getMonthStr(currentMonth), categoryType);
setCategoryData(category);
} catch (err) {
console.error(err);
showToast(err.message, {
tone: "error"
});
} finally{
setCategoryLoading(false);
}
};
(0,react__WEBPACK_IMPORTED_MODULE_1__.useEffect)(()=>{
if (trendChartInstance.current) {
trendChartInstance.current.destroy();
}
if (trendChartRef.current && trendData.length > 0) {
const ctx = trendChartRef.current.getContext("2d");
trendChartInstance.current = new chart_js__WEBPACK_IMPORTED_MODULE_6__/* .Chart */ .kL(ctx, {
type: "line",
data: {
labels: trendData.map((item)=>item.period),
datasets: [
{
label: "æ”¶å…¥",
data: trendData.map((item)=>item.income),
borderColor: "#10b981",
backgroundColor: "rgba(16, 185, 129, 0.1)",
fill: true,
tension: 0.4
},
{
label: "支出",
data: trendData.map((item)=>item.expense),
borderColor: "#ef4444",
backgroundColor: "rgba(239, 68, 68, 0.1)",
fill: true,
tension: 0.4
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: "top"
}
},
scales: {
y: {
beginAtZero: true
}
}
}
});
}
}, [
trendData
]);
(0,react__WEBPACK_IMPORTED_MODULE_1__.useEffect)(()=>{
if (categoryChartInstance.current) {
categoryChartInstance.current.destroy();
}
if (categoryChartRef.current && categoryData.length > 0) {
const ctx = categoryChartRef.current.getContext("2d");
const colors = [
"#2563eb",
"#10b981",
"#f59e0b",
"#ef4444",
"#8b5cf6",
"#ec4899",
"#06b6d4",
"#84cc16",
"#f97316",
"#6366f1"
];
categoryChartInstance.current = new chart_js__WEBPACK_IMPORTED_MODULE_6__/* .Chart */ .kL(ctx, {
type: "doughnut",
data: {
labels: categoryData.map((item)=>item.category),
datasets: [
{
data: categoryData.map((item)=>item.total),
backgroundColor: colors.slice(0, categoryData.length)
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: "bottom"
}
}
}
});
}
}, [
categoryData
]);
const prevMonth = ()=>{
const newDate = new Date(currentMonth);
newDate.setMonth(newDate.getMonth() - 1);
setCurrentMonth(newDate);
};
const nextMonth = ()=>{
const newDate = new Date(currentMonth);
newDate.setMonth(newDate.getMonth() + 1);
setCurrentMonth(newDate);
};
const totalCategoryAmount = categoryData.reduce((sum, item)=>sum + item.total, 0);
const headerContent = /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "header-center-container",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "header-spacer"
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "month-switcher-container",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("button", {
type: "button",
className: "icon-button",
"aria-label": "上个月",
onClick: prevMonth,
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_components_icons__WEBPACK_IMPORTED_MODULE_4__/* .Icon */ .J, {
name: "chevronLeft",
size: 18
})
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("span", {
className: "month-switcher__label",
children: formatMonth(currentMonth)
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("button", {
type: "button",
className: "icon-button",
"aria-label": "下个月",
onClick: nextMonth,
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_components_icons__WEBPACK_IMPORTED_MODULE_4__/* .Icon */ .J, {
name: "chevronRight",
size: 18
})
})
]
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "header-right-actions",
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("button", {
type: "button",
className: "icon-button",
"aria-label": "设置",
onClick: ()=>router.push("/settings"),
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_components_icons__WEBPACK_IMPORTED_MODULE_4__/* .Icon */ .J, {
name: "settings",
size: 18
})
})
})
]
});
return /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)(_components_AppShell__WEBPACK_IMPORTED_MODULE_3__/* ["default"] */ .Z, {
headerContent: headerContent,
compactHeader: true,
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("section", {
className: "card section-card section-card--stats",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "section-heading section-heading--compact",
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("p", {
className: "eyebrow",
children: "趋势"
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("h2", {
children: "收支趋势"
})
]
})
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "period-tabs",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("button", {
type: "button",
className: period === "week" ? "active" : "",
onClick: ()=>setPeriod("week"),
children: "周"
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("button", {
type: "button",
className: period === "month" ? "active" : "",
onClick: ()=>setPeriod("month"),
children: "月"
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("button", {
type: "button",
className: period === "year" ? "active" : "",
onClick: ()=>setPeriod("year"),
children: "å¹´"
})
]
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "chart-container chart-container--large chart-container--stats",
children: trendLoading ? /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "loading-state",
children: "加载中..."
}) : /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("canvas", {
ref: trendChartRef
})
})
]
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("section", {
className: "card section-card section-card--stats",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "section-heading section-heading--compact",
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("h2", {
children: "分类明细"
})
})
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "category-type-tabs",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("button", {
type: "button",
className: categoryType === "expense" ? "active" : "",
onClick: ()=>setCategoryType("expense"),
children: "支出"
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("button", {
type: "button",
className: categoryType === "income" ? "active" : "",
onClick: ()=>setCategoryType("income"),
children: "æ”¶å…¥"
})
]
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "category-breakdown category-breakdown--stats",
children: categoryData.length > 0 ? categoryData.map((cat, index)=>{
const percent = totalCategoryAmount ? (cat.total / totalCategoryAmount * 100).toFixed(1) : "0.0";
return /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "stats-category-item",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "stats-category-header",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("span", {
className: "stats-category-name",
children: cat.category
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("span", {
className: "stats-category-amount",
children: cat.total.toFixed(2)
})
]
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "stats-category-bar-wrap",
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "stats-category-bar",
style: {
width: "".concat(percent, "%")
}
})
})
]
}, index);
}) : /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "empty-state empty-state--inline",
children: "暂无数æ®"
})
})
]
})
]
});
}
”{"finalSource":true}ƒmapbufferedMap ConcatSourceRawSource|__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": function() { return /* binding */ Stats; }
/* harmony export */ });
/* harmony import */ var react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(5893);
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(7294);
/* harmony import */ var next_router__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(1163);
/* harmony import */ var next_router__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(next_router__WEBPACK_IMPORTED_MODULE_2__);
/* harmony import */ var _lib_api__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(610);
/* harmony import */ var chart_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(3148);
/* harmony import */ var _components_AppShell__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(6248);
/* harmony import */ var _components_icons__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(9415);
/* harmony import */ var _components_ToastProvider__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(5546);
ReplaceSourceRawSourceŽ8import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { useState, useEffect, useRef } from "react";
import { useRouter } from "next/router";
import { isLoggedIn, getTrendStats, getCategoryStats } from "../lib/api";
import { Chart, registerables } from "chart.js";
import AppShell from "../components/AppShell";
import { Icon } from "../components/icons";
import { useToast } from "../components/ToastProvider";
Chart.register(...registerables);
export default function Stats() {
const router = useRouter();
const { showToast } = useToast();
const [period, setPeriod] = useState("month");
const [currentMonth, setCurrentMonth] = useState(new Date());
const [trendData, setTrendData] = useState([]);
const [categoryData, setCategoryData] = useState([]);
const [categoryType, setCategoryType] = useState("expense");
const [trendLoading, setTrendLoading] = useState(true);
const [categoryLoading, setCategoryLoading] = useState(true);
const trendChartRef = useRef(null);
const categoryChartRef = useRef(null);
const trendChartInstance = useRef(null);
const categoryChartInstance = useRef(null);
useEffect(()=>{
if (!isLoggedIn()) {
router.push("/login");
return;
}
loadTrendData();
}, [
router,
period,
currentMonth
]);
useEffect(()=>{
if (!isLoggedIn()) return;
loadCategoryData();
}, [
router,
currentMonth,
categoryType
]);
const getMonthStr = (date)=>"".concat(date.getFullYear(), "-").concat(String(date.getMonth() + 1).padStart(2, "0"));
const formatMonth = (date)=>"".concat(date.getFullYear(), "年").concat(date.getMonth() + 1, "月");
const loadTrendData = async ()=>{
setTrendLoading(true);
try {
const trend = await getTrendStats(period, getMonthStr(currentMonth));
setTrendData(trend);
} catch (err) {
console.error(err);
showToast(err.message, {
tone: "error"
});
} finally{
setTrendLoading(false);
}
};
const loadCategoryData = async ()=>{
setCategoryLoading(true);
try {
const category = await getCategoryStats(getMonthStr(currentMonth), categoryType);
setCategoryData(category);
} catch (err) {
console.error(err);
showToast(err.message, {
tone: "error"
});
} finally{
setCategoryLoading(false);
}
};
useEffect(()=>{
if (trendChartInstance.current) {
trendChartInstance.current.destroy();
}
if (trendChartRef.current && trendData.length > 0) {
const ctx = trendChartRef.current.getContext("2d");
trendChartInstance.current = new Chart(ctx, {
type: "line",
data: {
labels: trendData.map((item)=>item.period),
datasets: [
{
label: "æ”¶å…¥",
data: trendData.map((item)=>item.income),
borderColor: "#10b981",
backgroundColor: "rgba(16, 185, 129, 0.1)",
fill: true,
tension: 0.4
},
{
label: "支出",
data: trendData.map((item)=>item.expense),
borderColor: "#ef4444",
backgroundColor: "rgba(239, 68, 68, 0.1)",
fill: true,
tension: 0.4
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: "top"
}
},
scales: {
y: {
beginAtZero: true
}
}
}
});
}
}, [
trendData
]);
useEffect(()=>{
if (categoryChartInstance.current) {
categoryChartInstance.current.destroy();
}
if (categoryChartRef.current && categoryData.length > 0) {
const ctx = categoryChartRef.current.getContext("2d");
const colors = [
"#2563eb",
"#10b981",
"#f59e0b",
"#ef4444",
"#8b5cf6",
"#ec4899",
"#06b6d4",
"#84cc16",
"#f97316",
"#6366f1"
];
categoryChartInstance.current = new Chart(ctx, {
type: "doughnut",
data: {
labels: categoryData.map((item)=>item.category),
datasets: [
{
data: categoryData.map((item)=>item.total),
backgroundColor: colors.slice(0, categoryData.length)
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: "bottom"
}
}
}
});
}
}, [
categoryData
]);
const prevMonth = ()=>{
const newDate = new Date(currentMonth);
newDate.setMonth(newDate.getMonth() - 1);
setCurrentMonth(newDate);
};
const nextMonth = ()=>{
const newDate = new Date(currentMonth);
newDate.setMonth(newDate.getMonth() + 1);
setCurrentMonth(newDate);
};
const totalCategoryAmount = categoryData.reduce((sum, item)=>sum + item.total, 0);
const headerContent = /*#__PURE__*/ _jsxs("div", {
className: "header-center-container",
children: [
/*#__PURE__*/ _jsx("div", {
className: "header-spacer"
}),
/*#__PURE__*/ _jsxs("div", {
className: "month-switcher-container",
children: [
/*#__PURE__*/ _jsx("button", {
type: "button",
className: "icon-button",
"aria-label": "上个月",
onClick: prevMonth,
children: /*#__PURE__*/ _jsx(Icon, {
name: "chevronLeft",
size: 18
})
}),
/*#__PURE__*/ _jsx("span", {
className: "month-switcher__label",
children: formatMonth(currentMonth)
}),
/*#__PURE__*/ _jsx("button", {
type: "button",
className: "icon-button",
"aria-label": "下个月",
onClick: nextMonth,
children: /*#__PURE__*/ _jsx(Icon, {
name: "chevronRight",
size: 18
})
})
]
}),
/*#__PURE__*/ _jsx("div", {
className: "header-right-actions",
children: /*#__PURE__*/ _jsx("button", {
type: "button",
className: "icon-button",
"aria-label": "设置",
onClick: ()=>router.push("/settings"),
children: /*#__PURE__*/ _jsx(Icon, {
name: "settings",
size: 18
})
})
})
]
});
return /*#__PURE__*/ _jsxs(AppShell, {
headerContent: headerContent,
compactHeader: true,
children: [
/*#__PURE__*/ _jsxs("section", {
className: "card section-card section-card--stats",
children: [
/*#__PURE__*/ _jsx("div", {
className: "section-heading section-heading--compact",
children: /*#__PURE__*/ _jsxs("div", {
children: [
/*#__PURE__*/ _jsx("p", {
className: "eyebrow",
children: "趋势"
}),
/*#__PURE__*/ _jsx("h2", {
children: "收支趋势"
})
]
})
}),
/*#__PURE__*/ _jsxs("div", {
className: "period-tabs",
children: [
/*#__PURE__*/ _jsx("button", {
type: "button",
className: period === "week" ? "active" : "",
onClick: ()=>setPeriod("week"),
children: "周"
}),
/*#__PURE__*/ _jsx("button", {
type: "button",
className: period === "month" ? "active" : "",
onClick: ()=>setPeriod("month"),
children: "月"
}),
/*#__PURE__*/ _jsx("button", {
type: "button",
className: period === "year" ? "active" : "",
onClick: ()=>setPeriod("year"),
children: "å¹´"
})
]
}),
/*#__PURE__*/ _jsx("div", {
className: "chart-container chart-container--large chart-container--stats",
children: trendLoading ? /*#__PURE__*/ _jsx("div", {
className: "loading-state",
children: "加载中..."
}) : /*#__PURE__*/ _jsx("canvas", {
ref: trendChartRef
})
})
]
}),
/*#__PURE__*/ _jsxs("section", {
className: "card section-card section-card--stats",
children: [
/*#__PURE__*/ _jsx("div", {
className: "section-heading section-heading--compact",
children: /*#__PURE__*/ _jsx("div", {
children: /*#__PURE__*/ _jsx("h2", {
children: "分类明细"
})
})
}),
/*#__PURE__*/ _jsxs("div", {
className: "category-type-tabs",
children: [
/*#__PURE__*/ _jsx("button", {
type: "button",
className: categoryType === "expense" ? "active" : "",
onClick: ()=>setCategoryType("expense"),
children: "支出"
}),
/*#__PURE__*/ _jsx("button", {
type: "button",
className: categoryType === "income" ? "active" : "",
onClick: ()=>setCategoryType("income"),
children: "æ”¶å…¥"
})
]
}),
/*#__PURE__*/ _jsx("div", {
className: "category-breakdown category-breakdown--stats",
children: categoryData.length > 0 ? categoryData.map((cat, index)=>{
const percent = totalCategoryAmount ? (cat.total / totalCategoryAmount * 100).toFixed(1) : "0.0";
return /*#__PURE__*/ _jsxs("div", {
className: "stats-category-item",
children: [
/*#__PURE__*/ _jsxs("div", {
className: "stats-category-header",
children: [
/*#__PURE__*/ _jsx("span", {
className: "stats-category-name",
children: cat.category
}),
/*#__PURE__*/ _jsx("span", {
className: "stats-category-amount",
children: cat.total.toFixed(2)
})
]
}),
/*#__PURE__*/ _jsx("div", {
className: "stats-category-bar-wrap",
children: /*#__PURE__*/ _jsx("div", {
className: "stats-category-bar",
style: {
width: "".concat(percent, "%")
}
})
})
]
}, index);
}) : /*#__PURE__*/ _jsx("div", {
className: "empty-state empty-state--inline",
children: "暂无数æ®"
})
})
]
})
]
});
}
n062undefined64115undefined117156undefined158230undefined232279undefined281326undefined328370undefined372426undefined428432chart_js__WEBPACK_IMPORTED_MODULE_6__/* .Chart */ .kLundefined446458chart_js__WEBPACK_IMPORTED_MODULE_6__/* .registerables */ .zXundefined462476undefined515523(0,next_router__WEBPACK_IMPORTED_MODULE_2__.useRouter)undefined554561(0,_components_ToastProvider__WEBPACK_IMPORTED_MODULE_5__/* .useToast */ .p)undefined598605(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined661668(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined721728(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined779786(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined837844(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined902909(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined968975(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined10101015(0,react__WEBPACK_IMPORTED_MODULE_1__.useRef)undefined10531058(0,react__WEBPACK_IMPORTED_MODULE_1__.useRef)undefined10981103(0,react__WEBPACK_IMPORTED_MODULE_1__.useRef)undefined11461151(0,react__WEBPACK_IMPORTED_MODULE_1__.useRef)undefined11641172(0,react__WEBPACK_IMPORTED_MODULE_1__.useEffect)undefined11931202(0,_lib_api__WEBPACK_IMPORTED_MODULE_7__/* .isLoggedIn */ .jl)undefined13731381(0,react__WEBPACK_IMPORTED_MODULE_1__.useEffect)undefined14021411(0,_lib_api__WEBPACK_IMPORTED_MODULE_7__/* .isLoggedIn */ .jl)undefined18651877(0,_lib_api__WEBPACK_IMPORTED_MODULE_7__/* .getTrendStats */ .gR)undefined22832298(0,_lib_api__WEBPACK_IMPORTED_MODULE_7__/* .getCategoryStats */ .Ap)undefined25992607(0,react__WEBPACK_IMPORTED_MODULE_1__.useEffect)undefined28872891chart_js__WEBPACK_IMPORTED_MODULE_6__/* .Chart */ .kLundefined43354343(0,react__WEBPACK_IMPORTED_MODULE_1__.useEffect)undefined49544958chart_js__WEBPACK_IMPORTED_MODULE_6__/* .Chart */ .kLundefined61896193(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined62966299(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined63956399(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined65276530(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined67716774(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined67766779_components_icons__WEBPACK_IMPORTED_MODULE_4__/* .Icon */ .Jundefined69556958(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined71487151(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined73927395(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined73977400_components_icons__WEBPACK_IMPORTED_MODULE_4__/* .Icon */ .Jundefined76027605(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined77077710(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined79497952(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined79547957_components_icons__WEBPACK_IMPORTED_MODULE_4__/* .Icon */ .Jundefined81378141(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined81438150_components_AppShell__WEBPACK_IMPORTED_MODULE_3__/* ["default"] */ .Zundefined82688272(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined84178420(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined85588562(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined86598662(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined88628865(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined90789082(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined92219224(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined95489551(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined98779880(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined1024510248(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined1042210425(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined1058210585(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined1075610760(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined1090510908(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined1104611049(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)unde
/***/ }) ÕH/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": function() { return /* binding */ Stats; }
/* harmony export */ });
/* harmony import */ var react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(5893);
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(7294);
/* harmony import */ var next_router__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(1163);
/* harmony import */ var next_router__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(next_router__WEBPACK_IMPORTED_MODULE_2__);
/* harmony import */ var _lib_api__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(610);
/* harmony import */ var chart_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(3148);
/* harmony import */ var _components_AppShell__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(6248);
/* harmony import */ var _components_icons__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(9415);
/* harmony import */ var _components_ToastProvider__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(5546);
chart_js__WEBPACK_IMPORTED_MODULE_6__/* .Chart */ .kL.register(...chart_js__WEBPACK_IMPORTED_MODULE_6__/* .registerables */ .zX);
function Stats() {
const router = (0,next_router__WEBPACK_IMPORTED_MODULE_2__.useRouter)();
const { showToast } = (0,_components_ToastProvider__WEBPACK_IMPORTED_MODULE_5__/* .useToast */ .p)();
const [period, setPeriod] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)("month");
const [currentMonth, setCurrentMonth] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)(new Date());
const [trendData, setTrendData] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)([]);
const [categoryData, setCategoryData] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)([]);
const [categoryType, setCategoryType] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)("expense");
const [trendLoading, setTrendLoading] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)(true);
const [categoryLoading, setCategoryLoading] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)(true);
const trendChartRef = (0,react__WEBPACK_IMPORTED_MODULE_1__.useRef)(null);
const categoryChartRef = (0,react__WEBPACK_IMPORTED_MODULE_1__.useRef)(null);
const trendChartInstance = (0,react__WEBPACK_IMPORTED_MODULE_1__.useRef)(null);
const categoryChartInstance = (0,react__WEBPACK_IMPORTED_MODULE_1__.useRef)(null);
(0,react__WEBPACK_IMPORTED_MODULE_1__.useEffect)(()=>{
if (!(0,_lib_api__WEBPACK_IMPORTED_MODULE_7__/* .isLoggedIn */ .jl)()) {
router.push("/login");
return;
}
loadTrendData();
}, [
router,
period,
currentMonth
]);
(0,react__WEBPACK_IMPORTED_MODULE_1__.useEffect)(()=>{
if (!(0,_lib_api__WEBPACK_IMPORTED_MODULE_7__/* .isLoggedIn */ .jl)()) return;
loadCategoryData();
}, [
router,
currentMonth,
categoryType
]);
const getMonthStr = (date)=>"".concat(date.getFullYear(), "-").concat(String(date.getMonth() + 1).padStart(2, "0"));
const formatMonth = (date)=>"".concat(date.getFullYear(), "年").concat(date.getMonth() + 1, "月");
const loadTrendData = async ()=>{
setTrendLoading(true);
try {
const trend = await (0,_lib_api__WEBPACK_IMPORTED_MODULE_7__/* .getTrendStats */ .gR)(period, getMonthStr(currentMonth));
setTrendData(trend);
} catch (err) {
console.error(err);
showToast(err.message, {
tone: "error"
});
} finally{
setTrendLoading(false);
}
};
const loadCategoryData = async ()=>{
setCategoryLoading(true);
try {
const category = await (0,_lib_api__WEBPACK_IMPORTED_MODULE_7__/* .getCategoryStats */ .Ap)(getMonthStr(currentMonth), categoryType);
setCategoryData(category);
} catch (err) {
console.error(err);
showToast(err.message, {
tone: "error"
});
} finally{
setCategoryLoading(false);
}
};
(0,react__WEBPACK_IMPORTED_MODULE_1__.useEffect)(()=>{
if (trendChartInstance.current) {
trendChartInstance.current.destroy();
}
if (trendChartRef.current && trendData.length > 0) {
const ctx = trendChartRef.current.getContext("2d");
trendChartInstance.current = new chart_js__WEBPACK_IMPORTED_MODULE_6__/* .Chart */ .kL(ctx, {
type: "line",
data: {
labels: trendData.map((item)=>item.period),
datasets: [
{
label: "æ”¶å…¥",
data: trendData.map((item)=>item.income),
borderColor: "#10b981",
backgroundColor: "rgba(16, 185, 129, 0.1)",
fill: true,
tension: 0.4
},
{
label: "支出",
data: trendData.map((item)=>item.expense),
borderColor: "#ef4444",
backgroundColor: "rgba(239, 68, 68, 0.1)",
fill: true,
tension: 0.4
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: "top"
}
},
scales: {
y: {
beginAtZero: true
}
}
}
});
}
}, [
trendData
]);
(0,react__WEBPACK_IMPORTED_MODULE_1__.useEffect)(()=>{
if (categoryChartInstance.current) {
categoryChartInstance.current.destroy();
}
if (categoryChartRef.current && categoryData.length > 0) {
const ctx = categoryChartRef.current.getContext("2d");
const colors = [
"#2563eb",
"#10b981",
"#f59e0b",
"#ef4444",
"#8b5cf6",
"#ec4899",
"#06b6d4",
"#84cc16",
"#f97316",
"#6366f1"
];
categoryChartInstance.current = new chart_js__WEBPACK_IMPORTED_MODULE_6__/* .Chart */ .kL(ctx, {
type: "doughnut",
data: {
labels: categoryData.map((item)=>item.category),
datasets: [
{
data: categoryData.map((item)=>item.total),
backgroundColor: colors.slice(0, categoryData.length)
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: "bottom"
}
}
}
});
}
}, [
categoryData
]);
const prevMonth = ()=>{
const newDate = new Date(currentMonth);
newDate.setMonth(newDate.getMonth() - 1);
setCurrentMonth(newDate);
};
const nextMonth = ()=>{
const newDate = new Date(currentMonth);
newDate.setMonth(newDate.getMonth() + 1);
setCurrentMonth(newDate);
};
const totalCategoryAmount = categoryData.reduce((sum, item)=>sum + item.total, 0);
const headerContent = /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "header-center-container",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "header-spacer"
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "month-switcher-container",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("button", {
type: "button",
className: "icon-button",
"aria-label": "上个月",
onClick: prevMonth,
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_components_icons__WEBPACK_IMPORTED_MODULE_4__/* .Icon */ .J, {
name: "chevronLeft",
size: 18
})
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("span", {
className: "month-switcher__label",
children: formatMonth(currentMonth)
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("button", {
type: "button",
className: "icon-button",
"aria-label": "下个月",
onClick: nextMonth,
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_components_icons__WEBPACK_IMPORTED_MODULE_4__/* .Icon */ .J, {
name: "chevronRight",
size: 18
})
})
]
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "header-right-actions",
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("button", {
type: "button",
className: "icon-button",
"aria-label": "设置",
onClick: ()=>router.push("/settings"),
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_components_icons__WEBPACK_IMPORTED_MODULE_4__/* .Icon */ .J, {
name: "settings",
size: 18
})
})
})
]
});
return /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)(_components_AppShell__WEBPACK_IMPORTED_MODULE_3__/* ["default"] */ .Z, {
headerContent: headerContent,
compactHeader: true,
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("section", {
className: "card section-card section-card--stats",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "section-heading section-heading--compact",
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("p", {
className: "eyebrow",
children: "趋势"
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("h2", {
children: "收支趋势"
})
]
})
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "period-tabs",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("button", {
type: "button",
className: period === "week" ? "active" : "",
onClick: ()=>setPeriod("week"),
children: "周"
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("button", {
type: "button",
className: period === "month" ? "active" : "",
onClick: ()=>setPeriod("month"),
children: "月"
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("button", {
type: "button",
className: period === "year" ? "active" : "",
onClick: ()=>setPeriod("year"),
children: "å¹´"
})
]
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "chart-container chart-container--large chart-container--stats",
children: trendLoading ? /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "loading-state",
children: "加载中..."
}) : /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("canvas", {
ref: trendChartRef
})
})
]
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("section", {
className: "card section-card section-card--stats",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "section-heading section-heading--compact",
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("h2", {
children: "分类明细"
})
})
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "category-type-tabs",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("button", {
type: "button",
className: categoryType === "expense" ? "active" : "",
onClick: ()=>setCategoryType("expense"),
children: "支出"
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("button", {
type: "button",
className: categoryType === "income" ? "active" : "",
onClick: ()=>setCategoryType("income"),
children: "æ”¶å…¥"
})
]
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "category-breakdown category-breakdown--stats",
children: categoryData.length > 0 ? categoryData.map((cat, index)=>{
const percent = totalCategoryAmount ? (cat.total / totalCategoryAmount * 100).toFixed(1) : "0.0";
return /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "stats-category-item",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "stats-category-header",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("span", {
className: "stats-category-name",
children: cat.category
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("span", {
className: "stats-category-amount",
children: cat.total.toFixed(2)
})
]
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "stats-category-bar-wrap",
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "stats-category-bar",
style: {
width: "".concat(percent, "%")
}
})
})
]
}, index);
}) : /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "empty-state empty-state--inline",
children: "暂无数æ®"
})
})
]
})
]
});
}
/***/ }) ÖÙ  ÚéÚ|__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": function() { return /* binding */ Stats; }
/* harmony export */ });
/* harmony import */ var react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(5893);
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(7294);
/* harmony import */ var next_router__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(1163);
/* harmony import */ var next_router__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(next_router__WEBPACK_IMPORTED_MODULE_2__);
/* harmony import */ var _lib_api__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(610);
/* harmony import */ var chart_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(3148);
/* harmony import */ var _components_AppShell__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(6248);
/* harmony import */ var _components_icons__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(9415);
/* harmony import */ var _components_ToastProvider__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(5546);
ÛŽ8import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { useState, useEffect, useRef } from "react";
import { useRouter } from "next/router";
import { isLoggedIn, getTrendStats, getCategoryStats } from "../lib/api";
import { Chart, registerables } from "chart.js";
import AppShell from "../components/AppShell";
import { Icon } from "../components/icons";
import { useToast } from "../components/ToastProvider";
Chart.register(...registerables);
export default function Stats() {
const router = useRouter();
const { showToast } = useToast();
const [period, setPeriod] = useState("month");
const [currentMonth, setCurrentMonth] = useState(new Date());
const [trendData, setTrendData] = useState([]);
const [categoryData, setCategoryData] = useState([]);
const [categoryType, setCategoryType] = useState("expense");
const [trendLoading, setTrendLoading] = useState(true);
const [categoryLoading, setCategoryLoading] = useState(true);
const trendChartRef = useRef(null);
const categoryChartRef = useRef(null);
const trendChartInstance = useRef(null);
const categoryChartInstance = useRef(null);
useEffect(()=>{
if (!isLoggedIn()) {
router.push("/login");
return;
}
loadTrendData();
}, [
router,
period,
currentMonth
]);
useEffect(()=>{
if (!isLoggedIn()) return;
loadCategoryData();
}, [
router,
currentMonth,
categoryType
]);
const getMonthStr = (date)=>"".concat(date.getFullYear(), "-").concat(String(date.getMonth() + 1).padStart(2, "0"));
const formatMonth = (date)=>"".concat(date.getFullYear(), "年").concat(date.getMonth() + 1, "月");
const loadTrendData = async ()=>{
setTrendLoading(true);
try {
const trend = await getTrendStats(period, getMonthStr(currentMonth));
setTrendData(trend);
} catch (err) {
console.error(err);
showToast(err.message, {
tone: "error"
});
} finally{
setTrendLoading(false);
}
};
const loadCategoryData = async ()=>{
setCategoryLoading(true);
try {
const category = await getCategoryStats(getMonthStr(currentMonth), categoryType);
setCategoryData(category);
} catch (err) {
console.error(err);
showToast(err.message, {
tone: "error"
});
} finally{
setCategoryLoading(false);
}
};
useEffect(()=>{
if (trendChartInstance.current) {
trendChartInstance.current.destroy();
}
if (trendChartRef.current && trendData.length > 0) {
const ctx = trendChartRef.current.getContext("2d");
trendChartInstance.current = new Chart(ctx, {
type: "line",
data: {
labels: trendData.map((item)=>item.period),
datasets: [
{
label: "æ”¶å…¥",
data: trendData.map((item)=>item.income),
borderColor: "#10b981",
backgroundColor: "rgba(16, 185, 129, 0.1)",
fill: true,
tension: 0.4
},
{
label: "支出",
data: trendData.map((item)=>item.expense),
borderColor: "#ef4444",
backgroundColor: "rgba(239, 68, 68, 0.1)",
fill: true,
tension: 0.4
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: "top"
}
},
scales: {
y: {
beginAtZero: true
}
}
}
});
}
}, [
trendData
]);
useEffect(()=>{
if (categoryChartInstance.current) {
categoryChartInstance.current.destroy();
}
if (categoryChartRef.current && categoryData.length > 0) {
const ctx = categoryChartRef.current.getContext("2d");
const colors = [
"#2563eb",
"#10b981",
"#f59e0b",
"#ef4444",
"#8b5cf6",
"#ec4899",
"#06b6d4",
"#84cc16",
"#f97316",
"#6366f1"
];
categoryChartInstance.current = new Chart(ctx, {
type: "doughnut",
data: {
labels: categoryData.map((item)=>item.category),
datasets: [
{
data: categoryData.map((item)=>item.total),
backgroundColor: colors.slice(0, categoryData.length)
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: "bottom"
}
}
}
});
}
}, [
categoryData
]);
const prevMonth = ()=>{
const newDate = new Date(currentMonth);
newDate.setMonth(newDate.getMonth() - 1);
setCurrentMonth(newDate);
};
const nextMonth = ()=>{
const newDate = new Date(currentMonth);
newDate.setMonth(newDate.getMonth() + 1);
setCurrentMonth(newDate);
};
const totalCategoryAmount = categoryData.reduce((sum, item)=>sum + item.total, 0);
const headerContent = /*#__PURE__*/ _jsxs("div", {
className: "header-center-container",
children: [
/*#__PURE__*/ _jsx("div", {
className: "header-spacer"
}),
/*#__PURE__*/ _jsxs("div", {
className: "month-switcher-container",
children: [
/*#__PURE__*/ _jsx("button", {
type: "button",
className: "icon-button",
"aria-label": "上个月",
onClick: prevMonth,
children: /*#__PURE__*/ _jsx(Icon, {
name: "chevronLeft",
size: 18
})
}),
/*#__PURE__*/ _jsx("span", {
className: "month-switcher__label",
children: formatMonth(currentMonth)
}),
/*#__PURE__*/ _jsx("button", {
type: "button",
className: "icon-button",
"aria-label": "下个月",
onClick: nextMonth,
children: /*#__PURE__*/ _jsx(Icon, {
name: "chevronRight",
size: 18
})
})
]
}),
/*#__PURE__*/ _jsx("div", {
className: "header-right-actions",
children: /*#__PURE__*/ _jsx("button", {
type: "button",
className: "icon-button",
"aria-label": "设置",
onClick: ()=>router.push("/settings"),
children: /*#__PURE__*/ _jsx(Icon, {
name: "settings",
size: 18
})
})
})
]
});
return /*#__PURE__*/ _jsxs(AppShell, {
headerContent: headerContent,
compactHeader: true,
children: [
/*#__PURE__*/ _jsxs("section", {
className: "card section-card section-card--stats",
children: [
/*#__PURE__*/ _jsx("div", {
className: "section-heading section-heading--compact",
children: /*#__PURE__*/ _jsxs("div", {
children: [
/*#__PURE__*/ _jsx("p", {
className: "eyebrow",
children: "趋势"
}),
/*#__PURE__*/ _jsx("h2", {
children: "收支趋势"
})
]
})
}),
/*#__PURE__*/ _jsxs("div", {
className: "period-tabs",
children: [
/*#__PURE__*/ _jsx("button", {
type: "button",
className: period === "week" ? "active" : "",
onClick: ()=>setPeriod("week"),
children: "周"
}),
/*#__PURE__*/ _jsx("button", {
type: "button",
className: period === "month" ? "active" : "",
onClick: ()=>setPeriod("month"),
children: "月"
}),
/*#__PURE__*/ _jsx("button", {
type: "button",
className: period === "year" ? "active" : "",
onClick: ()=>setPeriod("year"),
children: "å¹´"
})
]
}),
/*#__PURE__*/ _jsx("div", {
className: "chart-container chart-container--large chart-container--stats",
children: trendLoading ? /*#__PURE__*/ _jsx("div", {
className: "loading-state",
children: "加载中..."
}) : /*#__PURE__*/ _jsx("canvas", {
ref: trendChartRef
})
})
]
}),
/*#__PURE__*/ _jsxs("section", {
className: "card section-card section-card--stats",
children: [
/*#__PURE__*/ _jsx("div", {
className: "section-heading section-heading--compact",
children: /*#__PURE__*/ _jsx("div", {
children: /*#__PURE__*/ _jsx("h2", {
children: "分类明细"
})
})
}),
/*#__PURE__*/ _jsxs("div", {
className: "category-type-tabs",
children: [
/*#__PURE__*/ _jsx("button", {
type: "button",
className: categoryType === "expense" ? "active" : "",
onClick: ()=>setCategoryType("expense"),
children: "支出"
}),
/*#__PURE__*/ _jsx("button", {
type: "button",
className: categoryType === "income" ? "active" : "",
onClick: ()=>setCategoryType("income"),
children: "æ”¶å…¥"
})
]
}),
/*#__PURE__*/ _jsx("div", {
className: "category-breakdown category-breakdown--stats",
children: categoryData.length > 0 ? categoryData.map((cat, index)=>{
const percent = totalCategoryAmount ? (cat.total / totalCategoryAmount * 100).toFixed(1) : "0.0";
return /*#__PURE__*/ _jsxs("div", {
className: "stats-category-item",
children: [
/*#__PURE__*/ _jsxs("div", {
className: "stats-category-header",
children: [
/*#__PURE__*/ _jsx("span", {
className: "stats-category-name",
children: cat.category
}),
/*#__PURE__*/ _jsx("span", {
className: "stats-category-amount",
children: cat.total.toFixed(2)
})
]
}),
/*#__PURE__*/ _jsx("div", {
className: "stats-category-bar-wrap",
children: /*#__PURE__*/ _jsx("div", {
className: "stats-category-bar",
style: {
width: "".concat(percent, "%")
}
})
})
]
}, index);
}) : /*#__PURE__*/ _jsx("div", {
className: "empty-state empty-state--inline",
children: "暂无数æ®"
})
})
]
})
]
});
}
n062undefined64115undefined117156undefined158230undefined232279undefined281326undefined328370undefined372426undefined428432chart_js__WEBPACK_IMPORTED_MODULE_6__/* .Chart */ .kLundefined446458chart_js__WEBPACK_IMPORTED_MODULE_6__/* .registerables */ .zXundefined462476undefined515523(0,next_router__WEBPACK_IMPORTED_MODULE_2__.useRouter)undefined554561(0,_components_ToastProvider__WEBPACK_IMPORTED_MODULE_5__/* .useToast */ .p)undefined598605(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined661668(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined721728(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined779786(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined837844(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined902909(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined968975(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined10101015(0,react__WEBPACK_IMPORTED_MODULE_1__.useRef)undefined10531058(0,react__WEBPACK_IMPORTED_MODULE_1__.useRef)undefined10981103(0,react__WEBPACK_IMPORTED_MODULE_1__.useRef)undefined11461151(0,react__WEBPACK_IMPORTED_MODULE_1__.useRef)undefined11641172(0,react__WEBPACK_IMPORTED_MODULE_1__.useEffect)undefined11931202(0,_lib_api__WEBPACK_IMPORTED_MODULE_7__/* .isLoggedIn */ .jl)undefined13731381(0,react__WEBPACK_IMPORTED_MODULE_1__.useEffect)undefined14021411(0,_lib_api__WEBPACK_IMPORTED_MODULE_7__/* .isLoggedIn */ .jl)undefined18651877(0,_lib_api__WEBPACK_IMPORTED_MODULE_7__/* .getTrendStats */ .gR)undefined22832298(0,_lib_api__WEBPACK_IMPORTED_MODULE_7__/* .getCategoryStats */ .Ap)undefined25992607(0,react__WEBPACK_IMPORTED_MODULE_1__.useEffect)undefined28872891chart_js__WEBPACK_IMPORTED_MODULE_6__/* .Chart */ .kLundefined43354343(0,react__WEBPACK_IMPORTED_MODULE_1__.useEffect)undefined49544958chart_js__WEBPACK_IMPORTED_MODULE_6__/* .Chart */ .kLundefined61896193(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined62966299(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined63956399(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined65276530(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined67716774(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined67766779_components_icons__WEBPACK_IMPORTED_MODULE_4__/* .Icon */ .Jundefined69556958(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined71487151(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined73927395(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined73977400_components_icons__WEBPACK_IMPORTED_MODULE_4__/* .Icon */ .Jundefined76027605(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined77077710(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined79497952(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined79547957_components_icons__WEBPACK_IMPORTED_MODULE_4__/* .Icon */ .Jundefined81378141(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined81438150_components_AppShell__WEBPACK_IMPORTED_MODULE_3__/* ["default"] */ .Zundefined82688272(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined84178420(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined85588562(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined86598662(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined88628865(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined90789082(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined92219224(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined95489551(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined98779880(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined1024510248(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined1042210425(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined1058210585(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined1075610760(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined1090510908(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined1104611049(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)unde
},
«webpack/lib/util/registerExternalSerializerœwebpack-sources/PrefixSource‰/******/ 9function(__webpack_require__) { // webpackRuntimeModules
ÿvar __webpack_exec__ = function(moduleId) { return __webpack_require__(__webpack_require__.s = moduleId); }
__webpack_require__.O(0, [196,925,888,774,179], function() { return __webpack_exec__(4841); });
var __webpack_exports__ = __webpack_require__.O();
_N_E = __webpack_exports__;
}
]); êJz(self["webpackChunk_N_E"] = self["webpackChunk_N_E"] || []).push([[368],{
/***/ 4841:
/***/ (function(__unused_webpack_module, __unused_webpack_exports, __webpack_require__) {
(window.__NEXT_P = window.__NEXT_P || []).push([
"/stats",
function () {
return __webpack_require__(8011);
}
]);
if(false) {}
/***/ }),
/***/ 6248:
/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
"use strict";
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ Z: function() { return /* binding */ AppShell; }
/* harmony export */ });
/* harmony import */ var react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(5893);
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(7294);
/* harmony import */ var next_router__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(1163);
/* harmony import */ var next_router__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(next_router__WEBPACK_IMPORTED_MODULE_2__);
/* harmony import */ var _icons__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(9415);
const tabs = [
{
href: "/",
label: "首页",
icon: "home"
},
{
href: "/stats",
label: "统计",
icon: "chart"
},
{
href: "/recurring",
label: "周期",
icon: "repeat"
},
{
href: "/categories",
label: "分类",
icon: "tags"
}
];
function AppShell(param) {
let { title, subtitle, actions, headerContent, children, contentClassName = "", hideNav = false, compactHeader = false } = param;
const router = (0,next_router__WEBPACK_IMPORTED_MODULE_2__.useRouter)();
const activeTab = (0,react__WEBPACK_IMPORTED_MODULE_1__.useMemo)(()=>{
const matched = tabs.find((tab)=>tab.href === router.pathname);
return (matched === null || matched === void 0 ? void 0 : matched.href) || null;
}, [
router.pathname
]);
const headerNode = headerContent || (title ? /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "app-header__text",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("h1", {
children: title
}),
subtitle ? /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("p", {
children: subtitle
}) : null
]
}) : null);
const headerClassName = [
"app-header",
compactHeader ? "app-header--compact" : "",
headerNode ? "app-header--with-content" : "app-header--actions-only",
actions ? "app-header--with-actions" : ""
].filter(Boolean).join(" ");
return /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "app-shell".concat(hideNav ? " app-shell--standalone" : ""),
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("header", {
className: headerClassName,
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "app-shell__container app-header__inner",
children: [
headerNode ? /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "app-header__main",
children: headerNode
}) : /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "app-header__spacer",
"aria-hidden": "true"
}),
actions ? /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "app-header__actions",
children: actions
}) : null
]
})
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("main", {
className: "app-content ".concat(contentClassName).trim(),
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "app-shell__container app-content__inner",
children: children
})
}),
!hideNav ? /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("nav", {
className: "bottom-nav",
"aria-label": "主导航",
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "app-shell__container bottom-nav__inner",
children: tabs.map((tab)=>{
const isActive = activeTab === tab.href;
return /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("button", {
type: "button",
className: "bottom-nav__item".concat(isActive ? " is-active" : ""),
onClick: ()=>router.push(tab.href),
"aria-current": isActive ? "page" : undefined,
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_icons__WEBPACK_IMPORTED_MODULE_3__/* .Icon */ .J, {
name: tab.icon,
size: 20
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("span", {
children: tab.label
})
]
}, tab.href);
})
})
}) : null
]
});
}
/***/ }),
/***/ 610:
/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
"use strict";
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ Ap: function() { return /* binding */ getCategoryStats; },
/* harmony export */ CP: function() { return /* binding */ getCategories; },
/* harmony export */ JS: function() { return /* binding */ deleteRecord; },
/* harmony export */ Kp: function() { return /* binding */ searchRecords; },
/* harmony export */ Q6: function() { return /* binding */ getRecords; },
/* harmony export */ TZ: function() { return /* binding */ deleteRecurringBill; },
/* harmony export */ Vn: function() { return /* binding */ getRecurringBills; },
/* harmony export */ Vs: function() { return /* binding */ updateRecord; },
/* harmony export */ Xu: function() { return /* binding */ applyRecurringBill; },
/* harmony export */ ae: function() { return /* binding */ createRecord; },
/* harmony export */ dk: function() { return /* binding */ importData; },
/* harmony export */ fy: function() { return /* binding */ getStats; },
/* harmony export */ gR: function() { return /* binding */ getTrendStats; },
/* harmony export */ jl: function() { return /* binding */ isLoggedIn; },
/* harmony export */ k4: function() { return /* binding */ createCategory; },
/* harmony export */ kS: function() { return /* binding */ logout; },
/* harmony export */ u1: function() { return /* binding */ exportData; },
/* harmony export */ uO: function() { return /* binding */ updateRecurringBill; },
/* harmony export */ uu: function() { return /* binding */ deleteCategory; },
/* harmony export */ x4: function() { return /* binding */ login; },
/* harmony export */ z0: function() { return /* binding */ createRecurringBill; },
/* harmony export */ z2: function() { return /* binding */ register; }
/* harmony export */ });
const API_BASE = ""; // ä½¿ç”¨ç›¸å¯¹è·¯å¾„ï¼ŒåŒæºä»£ç
function getAuthHeaders() {
const token = true ? localStorage.getItem("token") : 0;
return token ? {
Authorization: "Bearer ".concat(token)
} : {};
}
async function apiCall(url) {
let options = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : {};
const res = await fetch("".concat(API_BASE).concat(url), {
...options,
headers: {
"Content-Type": "application/json",
...getAuthHeaders(),
...options.headers
}
});
const data = await res.json();
if (!res.ok) {
throw new Error(data.error || "请求失败");
}
return data;
}
// 认è¯
async function login(username, password) {
const data = await apiCall("/api/auth/login", {
method: "POST",
body: JSON.stringify({
username,
password
})
});
if (true) {
localStorage.setItem("token", data.token);
}
return data;
}
async function register(username, password) {
const data = await apiCall("/api/auth/register", {
method: "POST",
body: JSON.stringify({
username,
password
})
});
if (true) {
localStorage.setItem("token", data.token);
}
return data;
}
function logout() {
if (true) {
localStorage.removeItem("token");
}
}
function isLoggedIn() {
if (false) {}
return !!localStorage.getItem("token");
}
// 账目
async function getRecords(month) {
let type = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : "all";
let url = "/api/records?month=".concat(month);
if (type !== "all") {
url += "&type=".concat(type);
}
return apiCall(url);
}
async function getStats(month) {
return apiCall("/api/stats?month=".concat(month));
}
async function createRecord(data) {
return apiCall("/api/records", {
method: "POST",
body: JSON.stringify(data)
});
}
async function updateRecord(id, data) {
return apiCall("/api/records/".concat(id), {
method: "PUT",
body: JSON.stringify(data)
});
}
async function deleteRecord(id) {
return apiCall("/api/records/".concat(id), {
method: "DELETE"
});
}
// æœç´¢
async function searchRecords(keyword) {
return apiCall("/api/records/search?q=".concat(encodeURIComponent(keyword)));
}
// 统计
async function getTrendStats() {
let period = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : "month", month = arguments.length > 1 ? arguments[1] : void 0;
let url = "/api/stats/trend?period=".concat(period);
if (month) url += "&month=".concat(month);
return apiCall(url);
}
async function getCategoryStats(month) {
let type = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : "all";
let url = "/api/stats/category?type=".concat(type);
if (month) url += "&month=".concat(month);
return apiCall(url);
}
// 周期性账å
async function getRecurringBills() {
return apiCall("/api/recurring");
}
async function createRecurringBill(data) {
return apiCall("/api/recurring", {
method: "POST",
body: JSON.stringify(data)
});
}
async function updateRecurringBill(id, data) {
return apiCall("/api/recurring/".concat(id), {
method: "PUT",
body: JSON.stringify(data)
});
}
async function deleteRecurringBill(id) {
return apiCall("/api/recurring/".concat(id), {
method: "DELETE"
});
}
async function applyRecurringBill(id) {
return apiCall("/api/recurring/".concat(id, "/apply"), {
method: "POST"
});
}
// 分类
async function getCategories() {
return apiCall("/api/categories");
}
async function createCategory(data) {
return apiCall("/api/categories", {
method: "POST",
body: JSON.stringify(data)
});
}
async function deleteCategory(id) {
return apiCall("/api/categories/".concat(id), {
method: "DELETE"
});
}
// æ•°æ®å¯¼å…¥å¯¼å‡º
async function exportData() {
return apiCall("/api/export");
}
async function importData(data) {
return apiCall("/api/import", {
method: "POST",
body: JSON.stringify(data)
});
}
/***/ }),
/***/ 8011:
/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": function() { return /* binding */ Stats; }
/* harmony export */ });
/* harmony import */ var react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(5893);
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(7294);
/* harmony import */ var next_router__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(1163);
/* harmony import */ var next_router__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(next_router__WEBPACK_IMPORTED_MODULE_2__);
/* harmony import */ var _lib_api__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(610);
/* harmony import */ var chart_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(3148);
/* harmony import */ var _components_AppShell__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(6248);
/* harmony import */ var _components_icons__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(9415);
/* harmony import */ var _components_ToastProvider__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(5546);
chart_js__WEBPACK_IMPORTED_MODULE_6__/* .Chart */ .kL.register(...chart_js__WEBPACK_IMPORTED_MODULE_6__/* .registerables */ .zX);
function Stats() {
const router = (0,next_router__WEBPACK_IMPORTED_MODULE_2__.useRouter)();
const { showToast } = (0,_components_ToastProvider__WEBPACK_IMPORTED_MODULE_5__/* .useToast */ .p)();
const [period, setPeriod] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)("month");
const [currentMonth, setCurrentMonth] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)(new Date());
const [trendData, setTrendData] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)([]);
const [categoryData, setCategoryData] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)([]);
const [categoryType, setCategoryType] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)("expense");
const [trendLoading, setTrendLoading] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)(true);
const [categoryLoading, setCategoryLoading] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)(true);
const trendChartRef = (0,react__WEBPACK_IMPORTED_MODULE_1__.useRef)(null);
const categoryChartRef = (0,react__WEBPACK_IMPORTED_MODULE_1__.useRef)(null);
const trendChartInstance = (0,react__WEBPACK_IMPORTED_MODULE_1__.useRef)(null);
const categoryChartInstance = (0,react__WEBPACK_IMPORTED_MODULE_1__.useRef)(null);
(0,react__WEBPACK_IMPORTED_MODULE_1__.useEffect)(()=>{
if (!(0,_lib_api__WEBPACK_IMPORTED_MODULE_7__/* .isLoggedIn */ .jl)()) {
router.push("/login");
return;
}
loadTrendData();
}, [
router,
period,
currentMonth
]);
(0,react__WEBPACK_IMPORTED_MODULE_1__.useEffect)(()=>{
if (!(0,_lib_api__WEBPACK_IMPORTED_MODULE_7__/* .isLoggedIn */ .jl)()) return;
loadCategoryData();
}, [
router,
currentMonth,
categoryType
]);
const getMonthStr = (date)=>"".concat(date.getFullYear(), "-").concat(String(date.getMonth() + 1).padStart(2, "0"));
const formatMonth = (date)=>"".concat(date.getFullYear(), "年").concat(date.getMonth() + 1, "月");
const loadTrendData = async ()=>{
setTrendLoading(true);
try {
const trend = await (0,_lib_api__WEBPACK_IMPORTED_MODULE_7__/* .getTrendStats */ .gR)(period, getMonthStr(currentMonth));
setTrendData(trend);
} catch (err) {
console.error(err);
showToast(err.message, {
tone: "error"
});
} finally{
setTrendLoading(false);
}
};
const loadCategoryData = async ()=>{
setCategoryLoading(true);
try {
const category = await (0,_lib_api__WEBPACK_IMPORTED_MODULE_7__/* .getCategoryStats */ .Ap)(getMonthStr(currentMonth), categoryType);
setCategoryData(category);
} catch (err) {
console.error(err);
showToast(err.message, {
tone: "error"
});
} finally{
setCategoryLoading(false);
}
};
(0,react__WEBPACK_IMPORTED_MODULE_1__.useEffect)(()=>{
if (trendChartInstance.current) {
trendChartInstance.current.destroy();
}
if (trendChartRef.current && trendData.length > 0) {
const ctx = trendChartRef.current.getContext("2d");
trendChartInstance.current = new chart_js__WEBPACK_IMPORTED_MODULE_6__/* .Chart */ .kL(ctx, {
type: "line",
data: {
labels: trendData.map((item)=>item.period),
datasets: [
{
label: "æ”¶å…¥",
data: trendData.map((item)=>item.income),
borderColor: "#10b981",
backgroundColor: "rgba(16, 185, 129, 0.1)",
fill: true,
tension: 0.4
},
{
label: "支出",
data: trendData.map((item)=>item.expense),
borderColor: "#ef4444",
backgroundColor: "rgba(239, 68, 68, 0.1)",
fill: true,
tension: 0.4
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: "top"
}
},
scales: {
y: {
beginAtZero: true
}
}
}
});
}
}, [
trendData
]);
(0,react__WEBPACK_IMPORTED_MODULE_1__.useEffect)(()=>{
if (categoryChartInstance.current) {
categoryChartInstance.current.destroy();
}
if (categoryChartRef.current && categoryData.length > 0) {
const ctx = categoryChartRef.current.getContext("2d");
const colors = [
"#2563eb",
"#10b981",
"#f59e0b",
"#ef4444",
"#8b5cf6",
"#ec4899",
"#06b6d4",
"#84cc16",
"#f97316",
"#6366f1"
];
categoryChartInstance.current = new chart_js__WEBPACK_IMPORTED_MODULE_6__/* .Chart */ .kL(ctx, {
type: "doughnut",
data: {
labels: categoryData.map((item)=>item.category),
datasets: [
{
data: categoryData.map((item)=>item.total),
backgroundColor: colors.slice(0, categoryData.length)
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: "bottom"
}
}
}
});
}
}, [
categoryData
]);
const prevMonth = ()=>{
const newDate = new Date(currentMonth);
newDate.setMonth(newDate.getMonth() - 1);
setCurrentMonth(newDate);
};
const nextMonth = ()=>{
const newDate = new Date(currentMonth);
newDate.setMonth(newDate.getMonth() + 1);
setCurrentMonth(newDate);
};
const totalCategoryAmount = categoryData.reduce((sum, item)=>sum + item.total, 0);
const headerContent = /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "header-center-container",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "header-spacer"
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "month-switcher-container",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("button", {
type: "button",
className: "icon-button",
"aria-label": "上个月",
onClick: prevMonth,
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_components_icons__WEBPACK_IMPORTED_MODULE_4__/* .Icon */ .J, {
name: "chevronLeft",
size: 18
})
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("span", {
className: "month-switcher__label",
children: formatMonth(currentMonth)
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("button", {
type: "button",
className: "icon-button",
"aria-label": "下个月",
onClick: nextMonth,
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_components_icons__WEBPACK_IMPORTED_MODULE_4__/* .Icon */ .J, {
name: "chevronRight",
size: 18
})
})
]
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "header-right-actions",
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("button", {
type: "button",
className: "icon-button",
"aria-label": "设置",
onClick: ()=>router.push("/settings"),
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_components_icons__WEBPACK_IMPORTED_MODULE_4__/* .Icon */ .J, {
name: "settings",
size: 18
})
})
})
]
});
return /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)(_components_AppShell__WEBPACK_IMPORTED_MODULE_3__/* ["default"] */ .Z, {
headerContent: headerContent,
compactHeader: true,
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("section", {
className: "card section-card section-card--stats",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "section-heading section-heading--compact",
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("p", {
className: "eyebrow",
children: "趋势"
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("h2", {
children: "收支趋势"
})
]
})
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "period-tabs",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("button", {
type: "button",
className: period === "week" ? "active" : "",
onClick: ()=>setPeriod("week"),
children: "周"
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("button", {
type: "button",
className: period === "month" ? "active" : "",
onClick: ()=>setPeriod("month"),
children: "月"
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("button", {
type: "button",
className: period === "year" ? "active" : "",
onClick: ()=>setPeriod("year"),
children: "å¹´"
})
]
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "chart-container chart-container--large chart-container--stats",
children: trendLoading ? /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "loading-state",
children: "加载中..."
}) : /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("canvas", {
ref: trendChartRef
})
})
]
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("section", {
className: "card section-card section-card--stats",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "section-heading section-heading--compact",
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("h2", {
children: "分类明细"
})
})
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "category-type-tabs",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("button", {
type: "button",
className: categoryType === "expense" ? "active" : "",
onClick: ()=>setCategoryType("expense"),
children: "支出"
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("button", {
type: "button",
className: categoryType === "income" ? "active" : "",
onClick: ()=>setCategoryType("income"),
children: "æ”¶å…¥"
})
]
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "category-breakdown category-breakdown--stats",
children: categoryData.length > 0 ? categoryData.map((cat, index)=>{
const percent = totalCategoryAmount ? (cat.total / totalCategoryAmount * 100).toFixed(1) : "0.0";
return /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "stats-category-item",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "stats-category-header",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("span", {
className: "stats-category-name",
children: cat.category
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("span", {
className: "stats-category-amount",
children: cat.total.toFixed(2)
})
]
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "stats-category-bar-wrap",
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "stats-category-bar",
style: {
width: "".concat(percent, "%")
}
})
})
]
}, index);
}) : /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "empty-state empty-state--inline",
children: "暂无数æ®"
})
})
]
})
]
});
}
/***/ })
},
/******/ function(__webpack_require__) { // webpackRuntimeModules
/******/ var __webpack_exec__ = function(moduleId) { return __webpack_require__(__webpack_require__.s = moduleId); }
/******/ __webpack_require__.O(0, [196,925,888,774,179], function() { return __webpack_exec__(4841); });
/******/ var __webpack_exports__ = __webpack_require__.O();
/******/ _N_E = __webpack_exports__;
/******/ }
]); {}í `4îW(self["webpackChunk_N_E"] = self["webpackChunk_N_E"] || []).push([[368],{
/***/ 4841:
í[/***/ (function(__unused_webpack_module, __unused_webpack_exports, __webpack_require__) {
î
(window.__NEXT_P = window.__NEXT_P || []).push([
"/stats",
function () {
return require("private-next-pages/stats.js");
}
]);
if(module.hot) {
module.hot.dispose(function () {
window.__NEXT_P.push(["/stats"])
});
}
Z105111__webpack_require__undefined1131418011undefined168177falseundefined180276{}undefined RawSource
/***/ })þ,
/***/ 6248:
çd/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
"use strict";
æ´/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ Z: function() { return /* binding */ AppShell; }
/* harmony export */ });
/* harmony import */ var react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(5893);
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(7294);
/* harmony import */ var next_router__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(1163);
/* harmony import */ var next_router__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(next_router__WEBPACK_IMPORTED_MODULE_2__);
/* harmony import */ var _icons__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(9415);
çUimport { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { useMemo } from "react";
import { useRouter } from "next/router";
import { Icon } from "./icons";
const tabs = [
{
href: "/",
label: "首页",
icon: "home"
},
{
href: "/stats",
label: "统计",
icon: "chart"
},
{
href: "/recurring",
label: "周期",
icon: "repeat"
},
{
href: "/categories",
label: "分类",
icon: "tags"
}
];
export default function AppShell(param) {
let { title, subtitle, actions, headerContent, children, contentClassName = "", hideNav = false, compactHeader = false } = param;
const router = useRouter();
const activeTab = useMemo(()=>{
const matched = tabs.find((tab)=>tab.href === router.pathname);
return (matched === null || matched === void 0 ? void 0 : matched.href) || null;
}, [
router.pathname
]);
const headerNode = headerContent || (title ? /*#__PURE__*/ _jsxs("div", {
className: "app-header__text",
children: [
/*#__PURE__*/ _jsx("h1", {
children: title
}),
subtitle ? /*#__PURE__*/ _jsx("p", {
children: subtitle
}) : null
]
}) : null);
const headerClassName = [
"app-header",
compactHeader ? "app-header--compact" : "",
headerNode ? "app-header--with-content" : "app-header--actions-only",
actions ? "app-header--with-actions" : ""
].filter(Boolean).join(" ");
return /*#__PURE__*/ _jsxs("div", {
className: "app-shell".concat(hideNav ? " app-shell--standalone" : ""),
children: [
/*#__PURE__*/ _jsx("header", {
className: headerClassName,
children: /*#__PURE__*/ _jsxs("div", {
className: "app-shell__container app-header__inner",
children: [
headerNode ? /*#__PURE__*/ _jsx("div", {
className: "app-header__main",
children: headerNode
}) : /*#__PURE__*/ _jsx("div", {
className: "app-header__spacer",
"aria-hidden": "true"
}),
actions ? /*#__PURE__*/ _jsx("div", {
className: "app-header__actions",
children: actions
}) : null
]
})
}),
/*#__PURE__*/ _jsx("main", {
className: "app-content ".concat(contentClassName).trim(),
children: /*#__PURE__*/ _jsx("div", {
className: "app-shell__container app-content__inner",
children: children
})
}),
!hideNav ? /*#__PURE__*/ _jsx("nav", {
className: "bottom-nav",
"aria-label": "主导航",
children: /*#__PURE__*/ _jsx("div", {
className: "app-shell__container bottom-nav__inner",
children: tabs.map((tab)=>{
const isActive = activeTab === tab.href;
return /*#__PURE__*/ _jsxs("button", {
type: "button",
className: "bottom-nav__item".concat(isActive ? " is-active" : ""),
onClick: ()=>router.push(tab.href),
"aria-current": isActive ? "page" : undefined,
children: [
/*#__PURE__*/ _jsx(Icon, {
name: tab.icon,
size: 20
}),
/*#__PURE__*/ _jsx("span", {
children: tab.label
})
]
}, tab.href);
})
})
}) : null
]
});
}
}062undefined6495undefined97136undefined138168undefined510524undefined705713(0,next_router__WEBPACK_IMPORTED_MODULE_2__.useRouter)undefined740746(0,react__WEBPACK_IMPORTED_MODULE_1__.useMemo)undefined10191023(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined11191122(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined12171220(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined16021606(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined17431746(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined18441848(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined20152018(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined21802183(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined23812384(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined26202623(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined27502753(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined29492952(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined30813084(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined33263330(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined37093712(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined37143717_icons__WEBPACK_IMPORTED_MODULE_3__/* .Icon */ .Jundefined39013904(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefinedùúù,