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

26356 lines
1.3 MiB
Raw Normal View History

2026-03-12 11:24:10 +08:00
wpcw/§webpack/lib/cache/PackFileCacheStrategyPackContentItems `#: 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/categories.jsF 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|]|fullySpecified=|false|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/dis
Å/opt/accountbook/frontend/opt/accountbook/frontend/pages/package.jsonÆ/opt/accountbook/frontend/opt/accountbook/frontend/pages/categories.jsÉ/opt/accountbook/frontend/opt/accountbook/frontend/pages/categories.js.jsÊ/opt/accountbook/frontend/opt/accountbook/frontend/pages/categories.js.mjsÊ/opt/accountbook/frontend/opt/accountbook/frontend/pages/categories.js.tsxÉ/opt/accountbook/frontend/opt/accountbook/frontend/pages/categories.js.tsÊ/opt/accountbook/frontend/opt/accountbook/frontend/pages/categories.js.jsxË/opt/accountbook/frontend/opt/accountbook/frontend/pages/categories.js.jsonË/opt/accountbook/frontend/opt/accountbook/frontend/pages/categories.js.wasm¬/opt/accountbook/frontend/pages/package.jsonƒ ¸ÀÍyBÎé/opt/accountbook„/opt/î!@07ØÊyB /7ØÊyBf3b4def2c53d3cadì·ëêµé´@€¿/opt/accountbook/frontend/opt/accountbook/frontend/package.json¶/opt/accountbook/frontend/opt/accountbook/package.jsonª/opt/accountbook/frontend/opt/package.json´ ¸§/opt/accountbook/frontend/pages/_app.js`¸¬/opt/accountbook/frontend/styles/globals.css€€ ¹ÓÔ”./styles/globals.cssÿÿ@‰  ¸ÀÍyBý·Òêé/ /opt/accountbook/frontend/stylesØ!>¶ÀÍyB€>¶ÀÍyBc8cb7b56892252c5Ö!@07ØÊyB /7ØÊyBèÕ ÔŸÓžÒќݭ/opt/accountbook/frontend/styles/package.json ¡ ¥¬/opt/accountbook/frontend/pages/recurring.js«/opt/accountbook/frontend/lib/categories.js€€ ¦ÀÁ“./lib/categories.jsÿÿ@‰ ¸ÀÍyBý¤¿×Ö//opt/accountbook/frontend/libÅ!PݨÀÍyB@ݨÀÍyB6a51e60cacf85ff9Ã!@07ØÊyB /7ØÊyBÕÂÁŒÀ¿Š¾ʪ/opt/accountbook/frontend/lib/package.json¨/opt/accountbook/frontend/lib/categories˜webpack/lib/NormalModule«webpack/lib/util/registerExternalSerializer™webpack-sources/RawSourceòKimport { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
import { useState, useEffect } from "react";
import { useRouter } from "next/router";
import { isLoggedIn, getCategories, createCategory, deleteCategory } from "../lib/api";
import AppShell from "../components/AppShell";
import { Icon } from "../components/icons";
import { useToast } from "../components/ToastProvider";
import { useConfirm } from "../components/ConfirmProvider";
const ICONS = [
"\uD83C\uDF5C",
"\uD83E\uDD58",
"\uD83E\uDD6C",
"\uD83E\uDD69",
"\uD83C\uDF54",
"\uD83C\uDF55",
"☕",
"\uD83C\uDF7A",
"\uD83E\uDDCB",
"\uD83C\uDF4E",
"\uD83C\uDF6A",
"\uD83C\uDF6B",
"\uD83D\uDE97",
"\uD83D\uDE95",
"\uD83D\uDE8C",
"\uD83D\uDE87",
"⛽",
"\uD83D\uDE99",
"\uD83D\uDEF5",
"✈ï¸",
"\uD83D\uDEA2",
"\uD83D\uDED2",
"\uD83D\uDC54",
"\uD83D\uDC55",
"\uD83D\uDC56",
"\uD83D\uDC5F",
"\uD83D\uDC53",
"\uD83E\uDDE2",
"\uD83D\uDC5C",
"\uD83C\uDF92",
"\uD83D\uDC8D",
"\uD83D\uDC84",
"\uD83C\uDFE0",
"\uD83C\uDFE2",
"\uD83C\uDFE6",
"\uD83C\uDFE5",
"\uD83C\uDFE8",
"\uD83C\uDFE9",
"\uD83D\uDD27",
"\uD83D\uDD28",
"\uD83D\uDECBï¸",
"\uD83D\uDCFA",
"\uD83D\uDCBB",
"\uD83D\uDCF1",
"\uD83D\uDCDD",
"\uD83D\uDCB0",
"\uD83C\uDF81",
"\uD83D\uDCC8",
"\uD83E\uDDE7",
"\uD83D\uDCB3",
"\uD83C\uDFAF",
"\uD83D\uDCDA",
"\uD83C\uDFB5",
"\uD83C\uDFAE",
"\uD83C\uDFC3",
"âš½",
"\uD83C\uDFCA",
"\uD83D\uDEB4",
"\uD83C\uDFAD",
"\uD83C\uDFA8",
"\uD83C\uDFAC",
"\uD83D\uDCF7",
"\uD83C\uDFA5",
"\uD83D\uDCFA",
"\uD83E\uDDF8",
"\uD83D\uDC15",
"\uD83D\uDC08",
"\uD83C\uDF38",
"\uD83C\uDF3A"
];
export default function Categories() {
const router = useRouter();
const { showToast } = useToast();
const { confirm } = useConfirm();
const [categories, setCategories] = useState({
expense: [],
income: []
});
const [modalOpen, setModalOpen] = useState(false);
const [categoryType, setCategoryType] = useState("expense");
const [loading, setLoading] = useState(true);
const [submitting, setSubmitting] = useState(false);
const [formError, setFormError] = useState("");
const [formData, setFormData] = useState({
name: "",
icon: "\uD83D\uDCDD"
});
useEffect(()=>{
if (!isLoggedIn()) {
router.push("/login");
return;
}
loadCategories();
}, [
router
]);
const loadCategories = async ()=>{
setLoading(true);
try {
const data = await getCategories();
setCategories(data);
} catch (err) {
console.error(err);
showToast(err.message, {
tone: "error"
});
} finally{
setLoading(false);
}
};
const openAddModal = (type)=>{
setCategoryType(type);
setFormData({
name: "",
icon: "\uD83D\uDCDD"
});
setFormError("");
setModalOpen(true);
};
const closeModal = ()=>{
if (submitting) return;
setModalOpen(false);
setFormError("");
};
const handleSubmit = async (e)=>{
e.preventDefault();
if (!formData.name || !formData.icon) {
setFormError("请填写完整信æ¯");
return;
}
setSubmitting(true);
setFormError("");
try {
await createCategory({
type: categoryType,
name: formData.name,
icon: formData.icon
});
closeModal();
showToast("分类已创建", {
tone: "success"
});
await loadCategories();
} catch (err) {
setFormError(err.message);
showToast(err.message, {
tone: "error"
});
} finally{
setSubmitting(false);
}
};
const handleDelete = async (id)=>{
const shouldDelete = await confirm({
title: "删除此分类?",
description: "默认分类ä¸å¯åˆ é™¤ï¼Œè‡ªå®šä¹‰åˆ†ç±»åˆ é™¤åŽå°†æ— æ³•æ¢å¤ã€‚",
confirmText: "删除"
});
if (!shouldDelete) return;
try {
await deleteCategory(id);
showToast("分类已删除", {
tone: "success"
});
await loadCategories();
} catch (err) {
showToast(err.message, {
tone: "error"
});
}
};
const actions = /*#__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, {
title: "分类",
subtitle: "æ•´ç†å¸¸ç”¨åˆ†ç±»ä¸Žå›¾æ ‡",
actions: actions,
compactHeader: true,
children: [
loading ? /*#__PURE__*/ _jsx("div", {
className: "card loading-state",
children: "加载中..."
}) : /*#__PURE__*/ _jsxs(_Fragment, {
children: [
/*#__PURE__*/ _jsxs("section", {
className: "card category-section category-section--refresh",
children: [
/*#__PURE__*/ _jsxs("div", {
className: "section-header",
children: [
/*#__PURE__*/ _jsxs("div", {
children: [
/*#__PURE__*/ _jsx("p", {
className: "eyebrow",
children: "支出分类"
}),
/*#__PURE__*/ _jsx("h2", {
children: "支出"
})
]
}),
/*#__PURE__*/ _jsx("button", {
type: "button",
className: "btn btn-secondary btn-inline",
onClick: ()=>openAddModal("expense"),
children: /*#__PURE__*/ _jsxs("span", {
className: "button-content",
children: [
/*#__PURE__*/ _jsx(Icon, {
name: "plus",
size: 16
}),
"添加"
]
})
})
]
}),
/*#__PURE__*/ _jsx("div", {
className: "category-list category-list--chips",
children: categories.expense.map((cat)=>/*#__PURE__*/ _jsxs("div", {
className: "category-tag",
children: [
/*#__PURE__*/ _jsx("span", {
className: "cat-icon",
children: cat.icon
}),
/*#__PURE__*/ _jsx("span", {
className: "cat-name",
children: cat.name
}),
!cat.is_default && /*#__PURE__*/ _jsx("button", {
type: "button",
className: "icon-button icon-button--ghost cat-delete",
"aria-label": "删除".concat(cat.name),
onClick: ()=>handleDelete(cat.id),
children: /*#__PURE__*/ _jsx(Icon, {
name: "close",
size: 14
})
})
]
}, cat.id || cat.name))
})
]
}),
/*#__PURE__*/ _jsxs("section", {
className: "card category-section category-section--refresh",
children: [
/*#__PURE__*/ _jsxs("div", {
className: "section-header",
children: [
/*#__PURE__*/ _jsxs("div", {
children: [
/*#__PURE__*/ _jsx("p", {
className: "eyebrow",
children: "收入分类"
}),
/*#__PURE__*/ _jsx("h2", {
children: "æ”¶å…¥"
})
]
}),
/*#__PURE__*/ _jsx("button", {
type: "button",
className: "btn btn-secondary btn-inline",
onClick: ()=>openAddModal("income"),
children: /*#__PURE__*/ _jsxs("span", {
className: "button-content",
children: [
/*#__PURE__*/ _jsx(Icon, {
name: "plus",
size: 16
}),
"添加"
]
})
})
]
}),
/*#__PURE__*/ _jsx("div", {
className: "category-list category-list--chips",
children: categories.income.map((cat)=>/*#__PURE__*/ _jsxs("div", {
className: "category-tag",
children: [
/*#__PURE__*/ _jsx("span", {
className: "cat-icon",
children: cat.icon
}),
/*#__PURE__*/ _jsx("span", {
className: "cat-name",
children: cat.name
}),
!cat.is_default && /*#__PURE__*/ _jsx("button", {
type: "button",
className: "icon-button icon-button--ghost cat-delete",
"aria-label": "删除".concat(cat.name),
onClick: ()=>handleDelete(cat.id),
children: /*#__PURE__*/ _jsx(Icon, {
name: "close",
size: 14
})
})
]
}, cat.id || cat.name))
})
]
}),
/*#__PURE__*/ _jsx("div", {
className: "card category-tip category-tip--refresh",
children: /*#__PURE__*/ _jsx("p", {
children: "默认分类无法删除,自定义分类å¯ä»¥åˆ é™¤ã€‚"
})
})
]
}),
modalOpen && /*#__PURE__*/ _jsx("div", {
className: "modal-overlay",
onClick: closeModal,
children: /*#__PURE__*/ _jsxs("div", {
className: "modal",
onClick: (e)=>e.stopPropagation(),
children: [
/*#__PURE__*/ _jsxs("div", {
className: "modal-header",
children: [
/*#__PURE__*/ _jsxs("div", {
children: [
/*#__PURE__*/ _jsx("p", {
className: "eyebrow",
children: "新增分类"
}),
/*#__PURE__*/ _jsx("h2", {
children: categoryType === "expense" ? "新建支出分类" : "新建收入分类"
})
]
}),
/*#__PURE__*/ _jsx("button", {
type: "button",
className: "icon-button icon-button--ghost",
"aria-label": "关闭弹窗",
onClick: closeModal,
children: /*#__PURE__*/ _jsx(Icon, {
name: "close",
size: 18
})
})
]
}),
formError && /*#__PURE__*/ _jsx("div", {
className: "error-msg",
children: formError
}),
/*#__PURE__*/ _jsxs("form", {
onSubmit: handleSubmit,
children: [
/*#__PURE__*/ _jsxs("div", {
className: "form-group",
children: [
/*#__PURE__*/ _jsx("label", {
htmlFor: "category-name",
children: "分类åç§°"
}),
/*#__PURE__*/ _jsx("input", {
id: "category-name",
type: "text",
value: formData.name,
onChange: (e)=>setFormData({
...formData,
name: e.target.value
}),
placeholder: "请输入分类åç§°",
required: true,
disabled: submitting
})
]
}),
/*#__PURE__*/ _jsxs("div", {
className: "form-group",
children: [
/*#__PURE__*/ _jsx("label", {
children: "选择图标"
}),
/*#__PURE__*/ _jsx("div", {
className: "icon-grid",
children: ICONS.map((icon)=>/*#__PURE__*/ _jsx("button", {
type: "button",
className: "icon-btn ".concat(formData.icon === icon ? "active" : ""),
onClick: ()=>setFormData({
...formData,
icon
}),
disabled: submitting,
children: icon
}, icon))
})
]
}),
/*#__PURE__*/ _jsx("div", {
className: "modal-actions",
children: /*#__PURE__*/ _jsx("button", {
type: "submit",
className: "btn btn-primary",
disabled: submitting,
children: submitting ? /*#__PURE__*/ _jsxs("span", {
className: "button-content",
children: [
/*#__PURE__*/ _jsx(Icon, {
name: "spinner",
size: 16,
className: "is-spinning"
}),
"ä¿å­˜ä¸­..."
]
}) : "ä¿å­˜"
})
})
]
})
]
})
})
]
});
}
exportsTypeŽsideEffectFree‰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…httpsosaÿÿÿˆ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“missingDependenciesbuildDependenciesvalueDependenciesnÿÿÿ†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ˆ898e146914a66e2ab998eac1 “__webpack_exports__…ICONS‰*default*ŠCategories à ¸ÀÍyB :ÿÿÿ_ÿÿÿ
·webpack/lib/dependencies/HarmonyCompatibilityDependency dÿÿý¨webpack/lib/dependencies/ConstDependency`V `V`W@ƒ `,A„¬ `(A­ `WA3 `.A4_ `+A` `7A˜Ó `;¶webpack/lib/dependencies/HarmonyExportHeaderDependency A/ÇJ A ÇJ aP `Zºwebpack/lib/dependencies/HarmonyImportSideEffectDependencyreact/jsx-runtimeÿ `V4ÿÿÿ4ÿÿÿ `,next/routerÿ `(Š../lib/apiÿ `W../components/AppShellÿ `.“../components/iconsÿ `+../components/ToastProviderÿ `7../components/ConfirmProviderÿ `;±webpack/lib/dependencies/PureExpressionDependencya  ‡default aO¹webpack/lib/dependencies/HarmonyExportSpecifierDependencyý aP¹webpack/lib/dependencies/HarmonyImportSpecifierDependencya ‰useRouterþ AZc   øêêý cQQ ˆuseToastþ A  úííþ cRR" ŠuseConfirmþ ¯  õêêþ cSS" ˆuseStateþ ã  ðÿÿÿÿÿÿþ cT(T0 ûú A<D  ì
ÿÿÿ
ÿÿÿþ cX&X. ÷ö Ay  èÿÿÿÿÿÿþ cY,Y4 óò ¸  äÿÿÿÿÿÿþ cZ"Z* ïî ð  àþþÿÿþþÿÿþ c[([0 ëê A '   Üúþÿÿúþÿÿþ c\&\. çæ AQ Y   Øöþÿÿöþÿÿþ c]$], ‰useEffectþ A—     Óñþÿÿñþÿÿþ caa  ŠisLoggedInþ A´ ¾   λ»þ cb b getCategoriesþ A­
º
  Éþ cmm, ŽcreateCategoryþ A   ı±þ @`@`  ŽdeleteCategoryþ A!  ¿¬¬þ ``  ƒjsx„_jsx A:>  ¹¡¡þ `"@µ`& úû ô  µþ ` @º`$ „Iconþ ù 4°¡¡þ `%@º`) „jsxs…_jsxs A\a  ªþ @¿`@¿` ¡ˆAppShell Abj 4¥þ @¿`@¿`' æç A  ¡þ `$@Å`( ñò A’  þ ``$ ˆFragment‰_Fragment A˜¡ 4ÿÿÿÿÿÿþ `%@È`. çè è  {ÿÿÿ{ÿÿÿþ `"@Ê`' ãä Ÿ  wÿÿÿwÿÿÿþ `*@Í`/ ßà ADI  sÿÿÿsÿÿÿþ `2@Ð`7 ÌÍ Å  oÿÿÿoÿÿÿþ `:@Ò`> ÈÉ Â  ƒkÿÿÿkÿÿÿþ `:@Ö`> ÄÅ Á  ÿÿÿgÿÿÿgÿÿÿþ `2@Û`6 ÏÐ ì  {ÿÿÿcÿÿÿcÿÿÿþ `@@ß`E ¼½ º  wÿÿÿ_ÿÿÿ_ÿÿÿþ `>@â`Bà¿ 4tÿÿÿeÿÿÿeÿÿÿþ `C@â`G µ A—  pÿÿÿXÿÿÿXÿÿÿþ `*@ì`. ÀÁ ALQ  lÿÿÿTÿÿÿTÿÿÿþ `V@î`[ ­® A   hÿÿÿPÿÿÿPÿÿÿþ `:@ñ`> ©ª A  dÿÿÿLÿÿÿLÿÿÿþ `:@õ`> ¥¦ A% )   `ÿÿÿHÿÿÿHÿÿÿþ `M@ù`Q ¡¢ AÎ!Ò!  \ÿÿÿDÿÿÿDÿÿÿþ `H@þ`L¨§ AÓ!×! 4YÿÿÿJÿÿÿJÿÿÿþ `M@þ`Q ©ª A#›#  Uÿÿÿ=ÿÿÿ=ÿÿÿþ @`"@`' ¥¦ AM$R$  Qÿÿÿ9ÿÿÿ9ÿÿÿþ @ `*@ `/ ¡¢ A÷$ü$  Mÿÿÿ5ÿÿÿ5ÿÿÿþ @`2@`7 Ž At%x%  Iÿÿÿ1ÿÿÿ1ÿÿÿþ @`:@`> Š Aq&u&  Eÿÿÿ-ÿÿÿ-ÿÿÿþ @`:@`>  Ap't'  Aÿÿÿ)ÿÿÿ)ÿÿÿþ @`2@`6  A™(ž(  =ÿÿÿ%ÿÿÿ%ÿÿÿþ @`@@`E ~ÿÿÿÿÿÿ Ah)l)  9ÿÿÿ!ÿÿÿ!ÿÿÿþ @ `>@ `B Am)q) 46ÿÿÿ'ÿÿÿ'ÿÿÿþ @ `C@ `G wÿÿÿxÿÿÿ AI+M+  2ÿÿÿÿÿÿÿÿÿþ @*`*@*`. ƒ Aý+,  .ÿÿÿÿÿÿÿÿÿþ @,`U@,`Z oÿÿÿpÿÿÿ A½,Á,  *ÿÿÿÿÿÿÿÿÿþ @/`:@/`> kÿÿÿlÿÿÿ AÀ-Ä-  &ÿÿÿÿÿÿÿÿÿþ @3`:@3`> gÿÿÿhÿÿÿ AÖ.Ú.  "ÿÿÿ
ÿÿÿ
ÿÿÿþ @7`M@7`Q cÿÿÿdÿÿÿ A0ƒ0  ÿÿÿÿÿÿÿÿÿþ @<`H@<`Ljÿÿÿiÿÿÿ A„0ˆ0 4ÿÿÿ ÿÿÿ ÿÿÿþ @<`M@<`Q \ÿÿÿ]ÿÿÿ AG2K2  ÿÿÿÿþÿÿÿþÿÿþ @F`"@F`& XÿÿÿYÿÿÿ AÓ2×2  ÿÿÿûþÿÿûþÿÿþ @H`0@H`4 TÿÿÿUÿÿÿ A–3š3  ÿÿÿ÷þÿÿ÷þÿÿþ @N`'@N`+ _ÿÿÿ`ÿÿÿ A4"4   ÿÿÿóþÿÿóþÿÿþ @Q`(@Q`- [ÿÿÿ\ÿÿÿ AÑ4Ö4  ÿÿÿïþÿÿïþÿÿþ @U`&@U`+ WÿÿÿXÿÿÿ Am5r5  ÿÿÿëþÿÿëþÿÿþ @X`.@X`3 DÿÿÿEÿÿÿ Aâ5æ5  ÿþÿÿçþÿÿçþÿÿþ @Z`6@Z`: @ÿÿÿAÿÿÿ AÏ6Ó6  ûþÿÿãþÿÿãþÿÿþ @^`6@^`: <ÿÿÿ=ÿÿÿ Aæ7ê7  ÷þÿÿßþÿÿßþÿÿþ @c`.@c`2 8ÿÿÿ9ÿÿÿ A+9/9  óþÿÿÛþÿÿÛþÿÿþ @h`<@h`@?ÿÿÿ>ÿÿÿ A0949 4ðþÿÿáþÿÿáþÿÿþ @h`A@h`E 1ÿÿÿ2ÿÿÿ AW:[:  ìþÿÿÔþÿÿÔþÿÿþ @o`3@o`7 <ÿÿÿ=ÿÿÿ A ;;  èþÿÿÐþÿÿÐþÿÿþ @s`&@s`+ 8ÿÿÿ9ÿÿÿ A¥;ª;  äþÿÿÌþÿÿÌþÿÿþ @v`.@v`3 %ÿÿÿ&ÿÿÿ AW<[<  àþÿÿÈþÿÿÈþÿÿþ @y`6@y`: !ÿÿÿ"ÿÿÿ AL=P=  ÜþÿÿÄþÿÿÄþÿÿþ @}`6@}`: ,ÿÿÿ-ÿÿÿ A„@‰@  ØþÿÿÀþÿÿÀþÿÿþ @‹`.@‹`3 ÿÿÿÿÿÿ A6A:A  Ôþÿÿ¼þÿÿ¼þÿÿþ `6@Ž`: ÿÿÿÿÿÿ AåAéA  Ðþÿÿ¸þÿÿ¸þÿÿþ @‘`6@‘`: ÿÿÿÿÿÿ AB‘B  Ìþÿÿ´þÿÿ´þÿÿþ @“`V@“`Z  ÿÿÿÿÿÿ AÜEàE  Èþÿÿ°þÿÿ°þÿÿþ `.@ `2  ÿÿÿ
ÿÿÿ AfFjF  Äþÿÿ¬þÿÿ¬þÿÿþ `<@¢`@ ÿÿÿÿÿÿ A€G…G  Àþÿÿ¨þÿÿ¨þÿÿþ `M@¦`R ÿÿÿÿÿÿ AOHSH  ¼þÿÿ¤þÿÿ¤þÿÿþ `>@©`Bÿÿÿÿÿÿ ATHXH 4¹þÿÿªþÿÿªþÿÿþ `C@©`G 
 Zexport const DEFAULT_CATEGORIES = {
expense: [
{
name: "é¤é¥®",
icon: "\uD83C\uDF5C"
},
{
name: "ä¹°èœ",
icon: "\uD83E\uDD6C"
},
{
name: "交通",
icon: "\uD83D\uDE97"
},
{
name: "购物",
icon: "\uD83D\uDED2"
},
{
name: "娱ä¹",
icon: "\uD83C\uDFAE"
},
{
name: "房租",
icon: "\uD83C\uDFE0"
},
{
name: "房贷",
icon: "\uD83C\uDFE6"
},
{
name: "亲情å¡",
icon: "\uD83D\uDCB3"
},
{
name: "水电",
icon: "\uD83D\uDCA7"
},
{
name: "è¡£æœ",
icon: "\uD83D\uDC54"
},
{
name: "通讯",
icon: "\uD83D\uDCF1"
},
{
name: "日用å“",
icon: "\uD83E\uDDF4"
},
{
name: "美妆",
icon: "\uD83D\uDC84"
},
{
name: "医疗",
icon: "\uD83C\uDFE5"
},
{
name: "养娃",
icon: "\uD83D\uDC76"
},
{
name: "家电",
icon: "\uD83D\uDCFA"
},
{
name: "å®¶å…·",
icon: "\uD83D\uDECBï¸"
},
{
name: "装修",
icon: "\uD83D\uDD28"
},
{
name: "宠物",
icon: "\uD83D\uDC15"
},
{
name: "å…¶ä»–",
icon: "\uD83D\uDCDD"
}
],
income: [
{
name: "工资",
icon: "\uD83D\uDCB0"
},
{
name: "奖金",
icon: "\uD83C\uDF81"
},
{
name: "ç†è´¢",
icon: "\uD83D\uDCC8"
},
{
name: "红包",
icon: "\uD83E\uDDE7"
},
{
name: "å…¶ä»–",
icon: "\uD83D\uDCDD"
}
]
};
`+þÿÿ.þÿÿ /þÿÿ@þÿÿgþÿÿ þuþÿÿ vþÿÿwþÿÿxþÿÿbf0a79e01a013d70 yþÿÿ DEFAULT_CATEGORIES à ¸ÀÍyBþÿÿþÿÿ  dÿÿý   aj öö aj 
 '// extracted by mini-css-extract-plugin `þÿÿ  þÿÿ@ðýÿÿ þÿÿþÿÿ$þÿÿPþÿÿþÿÿ  û ` PþÿÿQþÿÿRþÿÿSþÿÿTþÿÿUþÿÿVþÿÿÅýÿÿWþÿÿXþÿÿ[þÿÿ\þÿÿ Þ/opt/accountbook/frontend/node_modules/next/dist/build/webpack/loaders/css-loader/src/index.jsâ/opt/accountbook/frontend/node_modules/next/dist/build/webpack/loaders/postcss-loader/src/index.jsÛ/opt/accountbook/frontend/node_modules/next/dist/compiled/mini-css-extract-plugin/loader.js[þÿÿ\þÿÿf8862f259a3eacf2  @  à ¸ÀÍyBâýÿÿçýÿÿ«/opt/accountbook/frontend/node_modules/nextŒnext@14.2.35 ä/opt/accountbook/frontend/node_modules/next/dist/build/webpack/loaders/css-loader/src/runtime/api.js¸/opt/accountbook/frontend/node_modules/next/package.json ªmini-css-extract-plugin/dist/CssDependency:/opt/accountbook/frontend/node_modules/next/dist/build/webpack/loaders/css-loader/src/index.js??ruleSet[1].rules[7].oneOf[14].use[1]!/opt/accountbook/frontend/node_modules/next/dist/build/webpack/loaders/postcss-loader/src/index.js??ruleSet[1].rules[7].oneOf[14].use[2]!/opt/accountbook/frontend/styles/globals.cssÑ:root {
--bg: #f4f6fb;
--bg-accent: radial-gradient(circle at top, rgba(59, 130, 246, 0.12), transparent 30%), linear-gradient(180deg, #f9fbff 0%, #f2f5fa 100%);
--surface: rgba(255, 255, 255, 0.9);
--surface-strong: rgba(255, 255, 255, 0.98);
--surface-muted: #eef3fb;
--surface-soft: #f6f8fc;
--surface-elevated: rgba(15, 23, 42, 0.86);
--text: #0f172a;
--text-secondary: #475569;
--text-tertiary: #64748b;
--text-on-dark: #f8fafc;
--primary: #2563eb;
--primary-dark: #1d4ed8;
--primary-soft: rgba(37, 99, 235, 0.1);
--income: #059669;
--income-soft: rgba(5, 150, 105, 0.12);
--expense: #dc2626;
--expense-soft: rgba(220, 38, 38, 0.1);
--warning: #d97706;
--warning-soft: rgba(217, 119, 6, 0.12);
--border: rgba(148, 163, 184, 0.2);
--border-strong: rgba(100, 116, 139, 0.28);
--shadow-xs: 0 1px 3px rgba(15, 23, 42, 0.05);
--shadow-sm: 0 10px 28px rgba(15, 23, 42, 0.06);
--shadow-md: 0 18px 44px rgba(15, 23, 42, 0.1);
--radius-xs: 10px;
--radius-sm: 14px;
--radius: 18px;
--radius-lg: 24px;
--space-1: 4px;
--space-2: 8px;
--space-3: 12px;
--space-4: 16px;
--space-5: 20px;
--space-6: 24px;
--space-7: 28px;
--space-8: 32px;
--shell-width: 760px;
--header-height: 44px;
--bottom-nav-height: 84px;
--safe-bottom: max(24px, env(safe-area-inset-bottom));
--safe-top: max(20px, env(safe-area-inset-top));
--transition: 180ms ease;
--font-sans: "Inter", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
--font-mono: "SF Mono", "JetBrains Mono", Monaco, monospace;
color-scheme: light;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html,
body,
#__next {
min-height: 100%;
}
body {
font-family: var(--font-sans);
background: var(--bg-accent);
color: var(--text);
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
}
a,
button,
input,
select,
textarea {
font: inherit;
}
button,
input,
select,
textarea {
border: 0;
}
button {
background: none;
color: inherit;
}
button:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible {
outline: 3px solid rgba(37, 99, 235, 0.18);
outline-offset: 2px;
}
.app-shell {
min-height: 100vh;
padding-bottom: calc(var(--bottom-nav-height) + var(--safe-bottom) + var(--space-3));
}
.app-shell--standalone {
padding-bottom: 0;
}
.app-shell__container {
width: min(100% - 28px, var(--shell-width));
margin: 0 auto;
}
.app-header {
position: -webkit-sticky;
position: sticky;
top: 0;
z-index: 40;
padding-top: var(--safe-top);
-webkit-backdrop-filter: blur(20px);
backdrop-filter: blur(20px);
background: rgba(249, 251, 255, 0.9);
border-bottom: 1px solid rgba(226, 232, 240, 0.9);
}
.app-header::after {
content: none;
}
.app-header--compact {
background: rgba(249, 251, 255, 0.86);
}
.app-header__inner {
position: relative;
min-height: 44px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 4px 0 4px;
}
.app-header--compact .app-header__inner {
min-height: 40px;
gap: 10px;
padding: 2px 0;
}
.app-header__main,
.app-header__spacer {
min-width: 0;
flex: 1 1;
}
.app-header__main {
display: flex;
align-items: center;
flex: 1 1;
}
.app-header--actions-only .app-header__main {
display: none;
}
.app-header__text {
min-width: 0;
display: grid;
grid-gap: 2px;
gap: 2px;
}
.app-header__text h1 {
font-size: 1.02rem;
line-height: 1.15;
letter-spacing: -0.03em;
font-weight: 700;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.app-header--compact .app-header__text h1 {
font-size: 0.98rem;
}
.app-header__text p {
color: var(--text-secondary);
font-size: 0.76rem;
line-height: 1.3;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.app-header--compact .app-header__text p {
font-size: 0.74rem;
}
.app-header__actions {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 8px;
flex-wrap: nowrap;
min-width: 0;
margin-left: auto;
flex-shrink: 0;
}
.app-header--compact .app-header__actions {
gap: 6px;
}
.app-content {
padding: 10px 0 0;
}
.app-content__inner {
display: grid;
grid-gap: 14px;
gap: 14px;
align-content: start;
}
.card {
position: relative;
background: var(--surface);
border: 1px solid rgba(148, 163, 184, 0.18);
border-radius: var(--radius);
box-shadow: 0 10px 26px rgba(15, 23, 42, 0.05);
-webkit-backdrop-filter: blur(18px);
backdrop-filter: blur(18px);
}
.section-card,
.category-section,
.settings-section,
.category-tip,
.dashboard-hero,
.auth-card,
.confirm-card {
overflow: hidden;
}
.section-card,
.category-section,
.settings-section,
.dashboard-hero,
.auth-card {
padding: 16px;
}
.section-card--search {
display: grid;
grid-gap: 14px;
gap: 14px;
}
.section-card--search-panel {
padding: 12px;
gap: 12px;
background: linear-gradient(180deg, rgba(255, 255, 255, 0.94), rgba(247, 249, 253, 0.9));
}
.section-heading,
.section-header,
.modal-header {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 14px;
}
.section-header {
align-items: center;
}
.section-heading--compact,
.section-header {
margin-bottom: 14px;
}
.section-heading h2,
.section-header h2,
.section-header h3,
.modal-header h2 {
font-size: 1.02rem;
line-height: 1.2;
letter-spacing: -0.02em;
}
.eyebrow {
font-size: 0.72rem;
font-weight: 700;
color: var(--primary);
letter-spacing: 0.12em;
text-transform: uppercase;
margin-bottom: 5px;
}
.sub {
color: var(--text-tertiary);
font-size: 0.9rem;
line-height: 1.45;
}
.btn {
width: 100%;
min-height: 44px;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
padding: 11px 15px;
border-radius: 13px;
cursor: pointer;
font-weight: 600;
font-size: 0.95rem;
transition: transform var(--transition), box-shadow var(--transition), background var(--transition), border-color var(--transition), color var(--transition), opacity var(--transition);
border: 1px solid transparent;
box-shadow: none;
}
.btn:hover:not(:disabled) {
transform: translateY(-1px);
}
.btn:active:not(:disabled) {
transform: translateY(0);
}
.btn:disabled,
.icon-button:disabled,
.type-btn:disabled,
.category-btn:disabled,
.icon-btn:disabled {
cursor: not-allowed;
opacity: 0.58;
}
.btn-inline {
width: auto;
min-width: -moz-fit-content;
min-width: fit-content;
}
.btn-small {
min-height: 34px;
padding: 7px 11px;
font-size: 0.84rem;
border-radius: 11px;
}
.btn-primary {
background: linear-gradient(135deg, var(--primary), #4d84f7);
color: #fff;
box-shadow: 0 8px 18px rgba(37, 99, 235, 0.14);
}
.btn-primary:hover:not(:disabled) {
background: linear-gradient(135deg, var(--primary-dark), var(--primary));
}
.btn-secondary {
background: rgba(255, 255, 255, 0.8);
color: var(--text);
border-color: rgba(148, 163, 184, 0.16);
}
.btn-secondary:hover:not(:disabled) {
border-color: rgba(37, 99, 235, 0.22);
background: rgba(255, 255, 255, 0.94);
}
.btn-ghost {
background: transparent;
color: var(--text-secondary);
border-color: rgba(148, 163, 184, 0.18);
}
.btn-ghost:hover:not(:disabled) {
color: var(--text);
background: rgba(148, 163, 184, 0.08);
}
.btn-danger {
background: linear-gradient(135deg, #ef4444, var(--expense));
color: #fff;
box-shadow: 0 10px 22px rgba(220, 38, 38, 0.14);
}
.button-content {
display: inline-flex;
align-items: center;
gap: 8px;
}
.icon-button {
width: 38px;
height: 38px;
border-radius: 12px;
display: inline-flex;
align-items: center;
justify-content: center;
border: 1px solid rgba(226, 232, 240, 0.95);
background: rgba(255, 255, 255, 0.92);
color: var(--text-secondary);
cursor: pointer;
transition: transform var(--transition), background var(--transition), border-color var(--transition), color var(--transition), box-shadow var(--transition);
box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04);
}
.icon-button:hover:not(:disabled) {
transform: translateY(-1px);
color: var(--text);
border-color: rgba(191, 219, 254, 1);
background: #ffffff;
box-shadow: 0 6px 16px rgba(15, 23, 42, 0.06);
}
.icon-button--ghost {
background: transparent;
box-shadow: none;
}
.month-switcher {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 5px 6px;
border-radius: 14px;
background: rgba(255, 255, 255, 0.94);
border: 1px solid rgba(226, 232, 240, 0.95);
}
.header-center-container {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
gap: 8px;
}
.header-left-actions,
.header-right-actions {
display: flex;
align-items: center;
min-width: 40px;
}
.header-spacer {
min-width: 40px;
}
.month-switcher-container {
display: flex;
align-items: center;
justify-content: center;
gap: 12px;
flex: 1 1;
}
.month-switcher-container .icon-button {
width: 36px;
height: 36px;
border-radius: 12px;
}
.month-switcher-container .month-switcher__label {
font-size: 1rem;
font-weight: 700;
min-width: 100px;
text-align: center;
}
.modal-title-container {
display: flex;
align-items: center;
justify-content: center;
flex: 1 1;
}
.modal-title {
font-size: 1.02rem;
font-weight: 700;
color: var(--text);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
letter-spacing: -0.03em;
}
.app-header--compact .modal-title {
font-size: 0.98rem;
}
.month-switcher--header {
min-width: 0;
padding: 3px;
gap: 3px;
border-radius: 14px;
background: rgba(255, 255, 255, 0.96);
box-shadow: 0 1px 2px rgba(15, 23, 42, 0.03);
justify-content: center;
}
.app-header--compact .month-switcher--header {
padding: 3px;
border-radius: 14px;
}
.month-switcher--header .icon-button {
width: 32px;
height: 32px;
border-radius: 10px;
}
.month-switcher--header .month-switcher__label {
min-width: 118px;
padding: 0 8px;
font-size: 0.84rem;
}
.month-switcher__label {
font-size: 0.9rem;
font-weight: 700;
color: var(--text);
min-width: 92px;
text-align: center;
white-space: nowrap;
}
.stats-grid {
display: grid;
grid-gap: 12px;
gap: 12px;
}
.stats-grid--hero {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.stats-grid--split {
grid-template-columns: minmax(0, 0.95fr) minmax(0, 1.05fr);
align-items: start;
}
.dashboard-hero {
display: grid;
grid-gap: 14px;
gap: 14px;
background: linear-gradient(160deg, rgba(255, 255, 255, 0.95), rgba(246, 249, 253, 0.92));
}
.stat-card,
.search-stat {
position: relative;
border-radius: 16px;
}
.stat-card {
display: grid;
grid-gap: 6px;
gap: 6px;
padding: 14px 13px;
text-align: left;
border: 1px solid rgba(148, 163, 184, 0.12);
background: rgba(255, 255, 255, 0.82);
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.45);
cursor: pointer;
transition: transform var(--transition), box-shadow var(--transition), border-color var(--transition), background var(--transition);
}
.stat-card:hover {
transform: translateY(-1px);
box-shadow: var(--shadow-xs);
}
.stat-card.active {
border-color: rgba(37, 99, 235, 0.3);
background: rgba(240, 246, 255, 0.96);
box-shadow: 0 10px 24px rgba(37, 99, 235, 0.08);
}
.stat-card--balance {
background: rgba(255, 255, 255, 0.82);
}
.label {
font-size: 0.74rem;
font-weight: 700;
color: var(--text-tertiary);
letter-spacing: 0.08em;
text-transform: uppercase;
}
.value,
.record-amount,
.bill-amount,
.category-amount,
.day-expense,
.search-stat .value {
font-family: var(--font-mono);
font-feature-settings: "tnum";
font-variant-numeric: tabular-nums;
}
.value {
font-size: 1.08rem;
font-weight: 700;
letter-spacing: -0.03em;
}
.value.income,
.record-amount.income,
.bill-amount.income,
.search-stat .value.income {
color: var(--income);
}
.value.expense,
.record-amount.expense,
.bill-amount.expense,
.search-stat .value.expense {
color: var(--expense);
}
.records-list,
.bills-list,
.category-breakdown {
display: grid;
grid-gap: 12px;
gap: 12px;
}
.records-list--flush {
gap: 16px;
}
.date-group {
display: grid;
grid-gap: 8px;
gap: 8px;
}
.date-label {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
padding: 0 2px;
color: var(--text-secondary);
font-size: 0.85rem;
font-weight: 600;
}
.day-expense {
color: var(--text-tertiary);
font-size: 0.78rem;
}
.record-item,
.bill-item,
.category-item,
.settings-item,
.category-tag {
border: 1px solid rgba(148, 163, 184, 0.14);
background: rgba(255, 255, 255, 0.76);
box-shadow: none;
}
.bill-item,
.settings-item {
position: relative;
}
.bill-item::after,
.settings-item::after {
content: '';
position: absolute;
inset: auto 14px 0;
height: 1px;
background: linear-gradient(90deg, rgba(148, 163, 184, 0), rgba(148, 163, 184, 0.22), rgba(148, 163, 184, 0));
opacity: 0;
transition: opacity var(--transition);
}
.record-item,
.bill-item {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 13px 14px;
border-radius: 16px;
cursor: pointer;
text-align: left;
transition: transform var(--transition), box-shadow var(--transition), border-color var(--transition), background var(--transition);
}
.record-item {
flex-wrap: nowrap;
}
.record-item:hover,
.bill-item:hover {
transform: translateY(-1px);
border-color: rgba(37, 99, 235, 0.14);
background: rgba(255, 255, 255, 0.94);
box-shadow: 0 6px 16px rgba(15, 23, 42, 0.05);
}
.bill-item:hover::after,
.settings-item:hover::after {
opacity: 1;
}
.record-item--static {
cursor: default;
}
.record-item--static:hover {
transform: none;
}
.record-left,
.bill-left {
display: flex;
align-items: center;
gap: 12px;
min-width: 0;
flex: 1 1;
}
.record-icon,
.bill-icon,
.auth-logo__mark,
.empty-state__icon,
.confirm-card__icon {
flex-shrink: 0;
display: inline-flex;
align-items: center;
justify-content: center;
}
.record-icon,
.bill-icon {
width: 40px;
height: 40px;
border-radius: 13px;
background: linear-gradient(135deg, rgba(237, 244, 255, 0.98), rgba(247, 250, 255, 0.92));
font-size: 1.2rem;
box-shadow: inset 0 0 0 1px rgba(148, 163, 184, 0.1);
}
.record-info,
.bill-info,
.settings-info,
.confirm-card__body {
min-width: 0;
display: grid;
grid-gap: 3px;
gap: 3px;
}
.record-info {
flex: 1 1;
}
.record-category,
.bill-category,
.settings-title,
.cat-name {
font-weight: 700;
color: var(--text);
}
.record-note,
.bill-day,
.bill-note,
.settings-desc,
.results-count,
.category-percent,
.about-info .sub,
.search-input-wrap__hint {
color: var(--text-tertiary);
font-size: 0.88rem;
line-height: 1.42;
}
.record-amount,
.bill-amount,
.category-amount {
font-size: 0.98rem;
font-weight: 700;
white-space: nowrap;
}
.record-amount {
flex-shrink: 0;
align-self: center;
}
.loading-state,
.empty-state {
display: grid;
place-items: center;
text-align: center;
grid-gap: 8px;
gap: 8px;
min-height: 148px;
color: var(--text-secondary);
}
.empty-state--card {
padding: 24px 16px;
}
.empty-state--inline {
min-height: 220px;
border-radius: 16px;
background: rgba(246, 248, 252, 0.92);
border: 1px dashed var(--border-strong);
}
.empty-state__icon {
width: 52px;
height: 52px;
border-radius: 16px;
background: rgba(240, 245, 255, 0.92);
color: var(--primary);
}
.fab {
position: fixed;
right: max(18px, env(safe-area-inset-right));
bottom: calc(var(--bottom-nav-height) + var(--safe-bottom) + 12px);
width: 56px;
height: 56px;
border-radius: 18px;
background: linear-gradient(135deg, var(--primary), #4f8dfc);
color: #fff;
display: inline-flex;
align-items: center;
justify-content: center;
box-shadow: 0 18px 34px rgba(37, 99, 235, 0.24);
z-index: 45;
cursor: pointer;
transition: transform var(--transition), box-shadow var(--transition);
}
.fab:hover {
transform: translateY(-2px) scale(1.01);
}
.modal-overlay {
position: fixed;
inset: 0;
display: flex;
align-items: flex-end;
justify-content: center;
padding: 18px 14px max(14px, env(safe-area-inset-bottom));
background: rgba(15, 23, 42, 0.44);
-webkit-backdrop-filter: blur(12px);
backdrop-filter: blur(12px);
z-index: 80;
}
.modal-overlay--fullscreen {
align-items: stretch;
padding: 0;
background: rgba(15, 23, 42, 0.32);
}
.modal-overlay--center {
align-items: center;
}
.modal,
.confirm-card {
width: min(100%, 560px);
background: var(--surface-strong);
border: 1px solid rgba(255, 255, 255, 0.62);
box-shadow: var(--shadow-md);
}
.modal {
border-radius: 24px 24px 20px 20px;
padding: 18px;
max-height: min(88vh, 920px);
overflow-y: auto;
}
.modal--fullscreen {
width: 100%;
max-width: none;
min-height: 100dvh;
max-height: 100dvh;
border-radius: 0;
border: 0;
padding: calc(var(--safe-top) + 8px) 16px calc(var(--safe-bottom) + 20px);
background: linear-gradient(180deg, rgba(249, 251, 255, 0.99), rgba(244, 246, 251, 0.98));
}
.modal--fullscreen .modal-header {
position: -webkit-sticky;
position: sticky;
top: calc(var(--safe-top) * -1 - 8px);
margin: calc(var(--safe-top) * -1 - 8px) -16px 14px;
padding: calc(var(--safe-top) + 8px) 16px 12px;
background: linear-gradient(180deg, rgba(249, 251, 255, 0.98), rgba(249, 251, 255, 0.92));
border-bottom: 1px solid rgba(148, 163, 184, 0.12);
-webkit-backdrop-filter: blur(18px);
backdrop-filter: blur(18px);
z-index: 2;
}
.modal--fullscreen form {
display: grid;
grid-gap: 2px;
gap: 2px;
width: 100%;
}
.confirm-card {
border-radius: 22px;
padding: 20px;
display: grid;
grid-gap: 16px;
gap: 16px;
}
.confirm-card__icon {
width: 46px;
height: 46px;
border-radius: 15px;
}
.confirm-card__icon--danger {
background: var(--expense-soft);
color: var(--expense);
}
.confirm-card__icon--info {
background: var(--primary-soft);
color: var(--primary);
}
.confirm-card__body h2 {
font-size: 1.04rem;
margin-bottom: 4px;
}
.confirm-card__body p {
color: var(--text-secondary);
line-height: 1.5;
}
.confirm-card__actions,
.modal-actions {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.modal-actions {
margin-top: 18px;
justify-content: flex-end;
}
.modal-actions .btn {
flex: 0 0 auto;
min-width: 112px;
}
.confirm-card__actions .btn {
flex: 1 1 180px;
}
.error-msg {
margin-bottom: 12px;
padding: 11px 13px;
border-radius: 13px;
border: 1px solid rgba(220, 38, 38, 0.14);
background: #fef2f2;
color: var(--expense);
font-size: 0.9rem;
}
.form-group {
display: grid;
grid-gap: 7px;
gap: 7px;
margin-bottom: 14px;
width: 100%;
}
.form-group--inline {
gap: 0;
}
.form-group label {
font-size: 0.86rem;
color: var(--text-secondary);
font-weight: 600;
}
.form-group input,
.form-group select,
.form-group textarea,
.search-input-wrap input {
width: 100%;
min-height: 46px;
padding: 11px 13px;
border-radius: 13px;
border: 1px solid rgba(148, 163, 184, 0.18);
background: rgba(255, 255, 255, 0.94);
color: var(--text);
transition: border-color var(--transition), box-shadow var(--transition), background var(--transition);
box-shadow: inset 0 1px 1px rgba(15, 23, 42, 0.02);
}
.form-group textarea {
min-height: 104px;
resize: vertical;
}
.form-group input:focus,
.form-group select:focus,
.form-group textarea:focus,
.search-input-wrap input:focus {
outline: none;
border-color: rgba(37, 99, 235, 0.34);
box-shadow: 0 0 0 4px rgba(37, 99, 235, 0.08);
}
.type-switch,
.period-tabs,
.category-type-tabs,
.auth-switch {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
grid-gap: 6px;
gap: 6px;
padding: 5px;
border-radius: 16px;
background: rgba(246, 248, 252, 0.92);
border: 1px solid rgba(148, 163, 184, 0.14);
}
.period-tabs {
grid-template-columns: repeat(3, minmax(0, 1fr));
margin-bottom: 14px;
}
.category-type-tabs {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.type-btn,
.period-tabs button,
.category-type-tabs button,
.auth-switch button {
min-height: 40px;
padding: 9px 12px;
border-radius: 12px;
font-weight: 700;
color: var(--text-secondary);
cursor: pointer;
transition: background var(--transition), color var(--transition), box-shadow var(--transition), transform var(--transition);
}
.type-btn.active,
.period-tabs button.active,
.category-type-tabs button.active,
.auth-switch .is-active {
background: #fff;
color: var(--text);
box-shadow: 0 8px 18px rgba(15, 23, 42, 0.06);
}
.type-btn.active.income {
color: var(--income);
background: rgba(5, 150, 105, 0.08);
}
.type-btn.active.expense {
color: var(--expense);
background: rgba(220, 38, 38, 0.08);
}
.amount-input {
text-align: center;
font-size: clamp(1.5rem, 4vw, 1.9rem) !important;
font-family: var(--font-mono);
font-weight: 700;
letter-spacing: -0.04em;
}
.category-grid,
.icon-grid {
display: grid;
grid-gap: 9px;
gap: 9px;
}
.category-grid {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
.category-grid .category-btn {
min-width: 0;
}
.icon-grid {
grid-template-columns: repeat(8, minmax(0, 1fr));
}
.icon-grid .icon-btn {
min-height: 48px;
font-size: 1.2rem;
}
.category-btn,
.icon-btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 6px;
min-height: 68px;
padding: 10px 8px;
border-radius: 14px;
border: 1px solid rgba(148, 163, 184, 0.16);
background: rgba(255, 255, 255, 0.86);
cursor: pointer;
transition: transform var(--transition), border-color var(--transition), box-shadow var(--transition), background var(--transition), color var(--transition);
}
.category-btn {
flex-direction: column;
font-size: 0.8rem;
color: var(--text-secondary);
}
.category-btn .icon {
font-size: 1.2rem;
}
.icon-btn {
min-height: 56px;
font-size: 1.3rem;
}
.category-btn:hover:not(:disabled),
.icon-btn:hover:not(:disabled) {
transform: translateY(-1px);
box-shadow: var(--shadow-xs);
}
.category-btn.active,
.icon-btn.active {
border-color: rgba(37, 99, 235, 0.28);
background: var(--primary-soft);
color: var(--primary);
}
.datetime-picker {
display: flex;
gap: 8px;
width: 100%;
box-sizing: border-box;
}
.datetime-picker input {
flex: 1 1;
min-width: 0;
box-sizing: border-box;
}
.search-form {
display: flex;
gap: 10px;
align-items: center;
}
.search-form--refresh {
padding: 4px;
border-radius: 16px;
background: rgba(246, 248, 252, 0.88);
border: 1px solid rgba(148, 163, 184, 0.12);
}
.search-form--refresh .search-input-wrap {
flex: 1 1;
}
.search-form--refresh .search-input-wrap input {
border-radius: 12px;
background: rgba(255, 255, 255, 0.94);
border-color: rgba(148, 163, 184, 0.12);
}
.search-form--refresh .btn {
min-width: 92px;
padding-inline: 18px;
}
.search-input-wrap {
position: relative;
flex: 1 1;
}
.search-input-wrap input {
padding-left: 42px;
}
.search-input-wrap__icon {
position: absolute;
left: 14px;
top: 50%;
transform: translateY(-50%);
color: var(--text-tertiary);
}
.search-summary {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
grid-gap: 10px;
gap: 10px;
}
.search-stat {
padding: 13px;
background: rgba(246, 248, 252, 0.82);
border: 1px solid rgba(148, 163, 184, 0.14);
display: grid;
grid-gap: 6px;
gap: 6px;
}
.section-card--search .results-count {
padding: 0 2px;
font-weight: 600;
}
.category-list {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.category-list--chips {
gap: 10px;
}
.category-tag {
display: inline-flex;
align-items: center;
gap: 9px;
min-height: 40px;
padding: 7px 10px;
border-radius: 14px;
}
.category-section--refresh .section-header .btn-inline {
min-height: 38px;
padding: 8px 12px;
border-radius: 12px;
}
.cat-icon {
width: 28px;
height: 28px;
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 9px;
background: rgba(246, 248, 252, 0.96);
}
.cat-delete {
width: 26px;
height: 26px;
margin-left: 2px;
}
.category-tip {
padding: 14px 16px;
color: var(--text-secondary);
text-align: center;
}
.bills-list {
gap: 10px;
}
.bill-item.inactive {
opacity: 0.72;
}
.bill-right {
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 8px;
flex-shrink: 0;
}
.bill-row {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
}
.bill-actions {
display: flex;
flex-wrap: wrap;
justify-content: flex-end;
gap: 7px;
}
.status-chip {
display: inline-flex;
align-items: center;
min-height: 26px;
padding: 0 9px;
border-radius: 999px;
background: var(--warning-soft);
color: var(--warning);
font-size: 0.75rem;
font-weight: 700;
}
.status-chip--success {
background: var(--income-soft);
color: var(--income);
}
.settings-section {
display: grid;
grid-gap: 8px;
gap: 8px;
}
.settings-section--refresh {
padding: 10px;
background: linear-gradient(180deg, rgba(255, 255, 255, 0.9), rgba(247, 249, 252, 0.82));
border-color: rgba(148, 163, 184, 0.12);
box-shadow: 0 8px 22px rgba(15, 23, 42, 0.04);
}
.settings-section--refresh .section-heading {
margin-bottom: 0;
padding: 4px 4px 2px;
}
.settings-section--refresh .section-heading h2 {
font-size: 0.98rem;
}
.settings-item {
display: flex;
align-items: center;
justify-content: space-between;
gap: 14px;
padding: 14px 15px;
border-radius: 18px;
background: rgba(255, 255, 255, 0.92);
border-color: rgba(148, 163, 184, 0.12);
}
.settings-item--stacked {
align-items: flex-end;
}
.settings-item--stacked .settings-info {
width: 100%;
}
.settings-item .btn-inline {
flex-shrink: 0;
min-height: 38px;
padding: 8px 13px;
border-radius: 12px;
}
.settings-title {
font-size: 0.95rem;
}
.settings-desc {
font-size: 0.84rem;
line-height: 1.5;
}
.about-info {
display: grid;
grid-gap: 6px;
gap: 6px;
padding: 6px 6px 2px;
color: var(--text-secondary);
}
.about-info p:first-child {
color: var(--text);
font-weight: 700;
}
.auth-layout {
min-height: calc(100vh - var(--header-height) - var(--safe-top));
}
.auth-page {
min-height: calc(100vh - var(--header-height) - var(--safe-top));
display: grid;
place-items: center;
padding: 20px 0 36px;
}
.auth-card {
width: min(100%, 420px);
background: linear-gradient(180deg, rgba(255, 255, 255, 0.97), rgba(247, 249, 253, 0.95));
}
.auth-card--refresh {
padding: 20px;
border-radius: 28px;
border-color: rgba(255, 255, 255, 0.72);
box-shadow: 0 20px 40px rgba(15, 23, 42, 0.07);
}
.auth-logo {
display: grid;
justify-items: center;
grid-gap: 9px;
gap: 9px;
text-align: center;
margin-bottom: 20px;
}
.auth-logo--refresh {
margin-bottom: 16px;
}
.auth-logo__mark {
width: 56px;
height: 56px;
border-radius: 18px;
color: #fff;
background: linear-gradient(135deg, var(--primary), #60a5fa);
box-shadow: 0 14px 30px rgba(37, 99, 235, 0.2);
}
.auth-logo h1 {
font-size: 1.58rem;
letter-spacing: -0.04em;
}
.auth-logo p {
color: var(--text-secondary);
max-width: 28ch;
line-height: 1.5;
font-size: 0.92rem;
}
.auth-form {
display: grid;
grid-gap: 4px;
gap: 4px;
}
.auth-form .btn-primary {
margin-top: 4px;
}
.chart-container {
position: relative;
min-height: 248px;
border-radius: 20px;
background: linear-gradient(180deg, rgba(250, 251, 253, 0.92), rgba(255, 255, 255, 0.98));
border: 1px solid rgba(148, 163, 184, 0.12);
padding: 14px;
}
.period-tabs,
.category-type-tabs {
margin-bottom: 14px;
}
.section-card .chart-container {
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.78);
}
.chart-container--large {
min-height: 304px;
}
.chart-container--small {
min-height: 264px;
}
.chart-container--stats {
background: linear-gradient(180deg, rgba(248, 250, 253, 0.92), rgba(255, 255, 255, 0.98));
border-color: rgba(148, 163, 184, 0.1);
}
.chart-container--stats-donut {
padding: 18px;
}
.section-card--stats {
padding: 14px;
background: linear-gradient(180deg, rgba(255, 255, 255, 0.93), rgba(247, 249, 252, 0.9));
}
.stats-grid--stats {
gap: 12px;
}
.category-breakdown {
align-content: start;
}
.category-breakdown--stats {
gap: 10px;
}
.category-item {
display: grid;
grid-template-columns: minmax(72px, auto) minmax(0, 1fr) auto;
align-items: center;
grid-gap: 10px;
gap: 10px;
padding: 12px 14px;
border-radius: 18px;
}
.category-item--stats {
display: grid;
grid-template-rows: auto auto;
grid-gap: 8px;
gap: 8px;
padding: 12px 14px;
}
.category-item-row {
display: flex;
justify-content: space-between;
align-items: center;
}
.category-item--stats .category-amount {
font-family: var(--font-mono);
font-feature-settings: "tnum";
font-variant-numeric: tabular-nums;
font-size: 0.98rem;
font-weight: 700;
}
.category-item .category-name {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.category-info {
display: grid;
grid-gap: 3px;
gap: 3px;
}
.category-name {
font-weight: 700;
}
.category-bar-wrap {
width: 100%;
height: 8px;
border-radius: 999px;
background: #e2e8f0;
overflow: hidden;
}
.category-bar {
height: 100%;
border-radius: inherit;
background: linear-gradient(90deg, var(--primary), #60a5fa);
}
.toast-stack {
position: fixed;
left: 50%;
bottom: calc(var(--bottom-nav-height) + var(--safe-bottom) + 14px);
transform: translateX(-50%);
width: min(calc(100% - 28px), 480px);
display: grid;
grid-gap: 8px;
gap: 8px;
z-index: 90;
}
.toast {
display: grid;
grid-template-columns: auto 1fr auto;
align-items: center;
grid-gap: 10px;
gap: 10px;
padding: 12px 13px;
border-radius: 15px;
background: var(--surface-elevated);
color: var(--text-on-dark);
box-shadow: 0 16px 34px rgba(15, 23, 42, 0.14);
-webkit-backdrop-filter: blur(18px);
backdrop-filter: blur(18px);
}
.toast__icon,
.toast__close {
color: inherit;
}
.toast__message {
line-height: 1.42;
}
.toast__close.icon-button {
width: 30px;
height: 30px;
border-radius: 10px;
}
.toast--success {
background: linear-gradient(135deg, rgba(5, 150, 105, 0.96), rgba(6, 182, 212, 0.9));
}
.toast--error {
background: linear-gradient(135deg, rgba(220, 38, 38, 0.96), rgba(244, 63, 94, 0.92));
}
.toast--info {
background: linear-gradient(135deg, rgba(15, 23, 42, 0.94), rgba(51, 65, 85, 0.94));
}
.bottom-nav {
position: fixed;
left: 0;
right: 0;
bottom: 0;
z-index: 35;
padding: 0 0 var(--safe-bottom);
background: linear-gradient(180deg, rgba(244, 246, 251, 0), rgba(244, 246, 251, 0.92) 24%, rgba(244, 246, 251, 0.98) 100%);
}
.bottom-nav__inner {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
grid-gap: 6px;
gap: 6px;
padding: 10px 10px 11px;
border-radius: 22px 22px 0 0;
background: rgba(255, 255, 255, 0.84);
border: 1px solid rgba(255, 255, 255, 0.66);
box-shadow: 0 -12px 30px rgba(15, 23, 42, 0.06);
-webkit-backdrop-filter: blur(24px);
backdrop-filter: blur(24px);
}
.bottom-nav__item {
min-height: 54px;
display: grid;
justify-items: center;
align-content: center;
grid-gap: 4px;
gap: 4px;
padding: 7px 4px;
border-radius: 16px;
color: var(--text-tertiary);
cursor: pointer;
transition: background var(--transition), color var(--transition), transform var(--transition);
}
.bottom-nav__item span {
font-size: 0.74rem;
font-weight: 700;
}
.bottom-nav__item.is-active {
background: var(--primary-soft);
color: var(--primary);
}
.checkbox-label {
display: inline-flex;
align-items: center;
gap: 10px;
color: var(--text-secondary);
}
.checkbox-label input {
width: 18px;
height: 18px;
}
.is-spinning {
animation: spin 0.9s linear infinite;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
@media (max-width: 1024px) {
.stats-grid--split {
grid-template-columns: 1fr;
}
}
@media (max-width: 768px) {
:root {
--header-height: 42px;
--bottom-nav-height: 82px;
}
.app-shell__container {
width: min(100% - 24px, var(--shell-width));
}
.app-header__inner,
.app-header--compact .app-header__inner {
min-height: auto;
align-items: center;
gap: 8px;
padding: 8px 0;
}
.app-header__text h1 {
font-size: 1rem;
}
.app-header__actions {
gap: 6px;
max-width: none;
flex-wrap: nowrap;
justify-content: flex-end;
}
.month-switcher--header {
min-width: 0;
padding: 3px;
}
.month-switcher--header .month-switcher__label {
min-width: 108px;
font-size: 0.82rem;
}
.month-switcher--header .icon-button,
.icon-button {
width: 38px;
height: 38px;
}
.section-card,
.category-section,
.settings-section,
.dashboard-hero,
.auth-card,
.modal,
.confirm-card {
padding: 16px;
}
.section-card--stats,
.section-card--search-panel {
padding: 12px;
}
.modal--fullscreen {
padding: calc(var(--safe-top) + 6px) 14px calc(var(--safe-bottom) + 18px);
}
.modal--fullscreen .modal-header {
top: calc(var(--safe-top) * -1 - 6px);
margin: calc(var(--safe-top) * -1 - 6px) -14px 12px;
padding: calc(var(--safe-top) + 6px) 14px 10px;
}
.stats-grid--hero,
.search-summary,
.category-grid {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.icon-grid {
grid-template-columns: repeat(6, minmax(0, 1fr));
}
.icon-grid .icon-btn {
min-height: 44px;
font-size: 1.1rem;
}
.search-form,
.settings-item,
.settings-item--stacked {
flex-direction: column;
align-items: stretch;
}
.search-form--refresh .btn {
width: 100%;
}
.record-amount {
align-self: center;
}
.bill-right,
.settings-item--stacked {
width: auto;
}
.bill-actions {
width: auto;
justify-content: flex-end;
}
.bill-actions .btn {
flex: 0 0 auto;
}
.category-item {
grid-template-columns: minmax(72px, auto) minmax(0, 1fr) auto;
}
.toast-stack {
bottom: calc(var(--bottom-nav-height) + var(--safe-bottom) + 10px);
}
}
@media (max-width: 560px) {
.app-header__inner {
flex-direction: row;
align-items: center;
}
.app-header__main,
.app-header__text {
min-width: 0;
flex: 1 1;
}
.app-header__text {
gap: 2px;
}
.app-header__actions {
width: auto;
flex: 1 1;
justify-content: flex-end;
gap: 6px;
}
.month-switcher,
.month-switcher--header {
flex: 1 1;
justify-content: center;
}
.month-switcher--header .month-switcher__label {
min-width: 96px;
flex: 1 1;
}
.search-summary,
.icon-grid,
.auth-switch {
grid-template-columns: 1fr;
}
.record-item,
.bill-item,
.settings-item,
.category-item {
padding: 13px;
}
.stats-grid--hero {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.category-grid {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
.period-tabs {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.category-type-tabs {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.record-left,
.bill-left {
gap: 10px;
}
.record-left,
.record-item,
.bill-item {
min-width: 0;
}
.record-item,
.bill-item {
gap: 10px;
}
.record-icon,
.bill-icon,
.empty-state__icon {
width: 40px;
height: 40px;
border-radius: 13px;
}
.datetime-picker {
gap: 6px;
}
.modal-actions,
.confirm-card__actions {
flex-direction: column;
}
.auth-page {
padding: 12px 0 24px;
}
.auth-card--refresh {
padding: 18px 16px;
border-radius: 24px;
}
.auth-logo {
gap: 8px;
margin-bottom: 16px;
}
.auth-logo h1 {
font-size: 1.46rem;
}
.category-btn,
.icon-btn {
min-height: 54px;
}
.toast {
grid-template-columns: auto 1fr;
}
.toast__close {
grid-column: 2;
justify-self: end;
}
.bottom-nav__inner {
gap: 6px;
padding: 10px;
}
.bottom-nav__item {
min-height: 54px;
}
}
¬ýÿÿ …ýÿÿ ‰ýÿÿÓýÿÿŠýÿÿÒýÿÿ€€ Œýÿÿ¦ýÿÿ§ýÿÿÓýÿÿÓýÿÿÓýÿÿ @‰ À¸ÀÍyBÑýÿÿÔýÿÿÖýÿÿÜýÿÿ
ÝýÿÿÅ/opt/accountbook/frontend/opt/accountbook/frontend/styles/globals.cssÈ/opt/accountbook/frontend/opt/accountbook/frontend/styles/globals.css.jsÉ/opt/accountbook/frontend/opt/accountbook/frontend/styles/globals.css.mjsÉ/opt/accountbook/frontend/opt/accountbook/frontend/styles/globals.css.tsxÈ/opt/accountbook/frontend/opt/accountbook/frontend/styles/globals.css.tsÉ/opt/accountbook/frontend/opt/accountbook/frontend/styles/globals.css.jsxÊ/opt/accountbook/frontend/opt/accountbook/frontend/styles/globals.css.jsonÊ/opt/accountbook/frontend/opt/accountbook/frontend/styles/globals.css.wasmÆ/opt/accountbook/frontend/opt/accountbook/frontend/styles/package.jsonƒ »ýÿÿÀýÿÿ 
Ò–// Imports
import ___CSS_LOADER_API_IMPORT___ from "../node_modules/next/dist/build/webpack/loaders/css-loader/src/runtime/api.js";
var ___CSS_LOADER_EXPORT___ = ___CSS_LOADER_API_IMPORT___(false);
// Module
___CSS_LOADER_EXPORT___.push([module.id, ":root {\n --bg: #f4f6fb;\n --bg-accent: radial-gradient(circle at top, rgba(59, 130, 246, 0.12), transparent 30%), linear-gradient(180deg, #f9fbff 0%, #f2f5fa 100%);\n --surface: rgba(255, 255, 255, 0.9);\n --surface-strong: rgba(255, 255, 255, 0.98);\n --surface-muted: #eef3fb;\n --surface-soft: #f6f8fc;\n --surface-elevated: rgba(15, 23, 42, 0.86);\n --text: #0f172a;\n --text-secondary: #475569;\n --text-tertiary: #64748b;\n --text-on-dark: #f8fafc;\n --primary: #2563eb;\n --primary-dark: #1d4ed8;\n --primary-soft: rgba(37, 99, 235, 0.1);\n --income: #059669;\n --income-soft: rgba(5, 150, 105, 0.12);\n --expense: #dc2626;\n --expense-soft: rgba(220, 38, 38, 0.1);\n --warning: #d97706;\n --warning-soft: rgba(217, 119, 6, 0.12);\n --border: rgba(148, 163, 184, 0.2);\n --border-strong: rgba(100, 116, 139, 0.28);\n --shadow-xs: 0 1px 3px rgba(15, 23, 42, 0.05);\n --shadow-sm: 0 10px 28px rgba(15, 23, 42, 0.06);\n --shadow-md: 0 18px 44px rgba(15, 23, 42, 0.1);\n --radius-xs: 10px;\n --radius-sm: 14px;\n --radius: 18px;\n --radius-lg: 24px;\n --space-1: 4px;\n --space-2: 8px;\n --space-3: 12px;\n --space-4: 16px;\n --space-5: 20px;\n --space-6: 24px;\n --space-7: 28px;\n --space-8: 32px;\n --shell-width: 760px;\n --header-height: 44px;\n --bottom-nav-height: 84px;\n --safe-bottom: max(24px, env(safe-area-inset-bottom));\n --safe-top: max(20px, env(safe-area-inset-top));\n --transition: 180ms ease;\n --font-sans: \"Inter\", \"PingFang SC\", \"Hiragino Sans GB\", \"Microsoft YaHei\", sans-serif;\n --font-mono: \"SF Mono\", \"JetBrains Mono\", Monaco, monospace;\n color-scheme: light;\n}\n\n* {\n box-sizing: border-box;\n margin: 0;\n padding: 0;\n}\n\nhtml,\nbody,\n#__next {\n min-height: 100%;\n}\n\nbody {\n font-family: var(--font-sans);\n background: var(--bg-accent);\n color: var(--text);\n -webkit-font-smoothing: antialiased;\n text-rendering: optimizeLegibility;\n}\n\na,\nbutton,\ninput,\nselect,\ntextarea {\n font: inherit;\n}\n\nbutton,\ninput,\nselect,\ntextarea {\n border: 0;\n}\n\nbutton {\n background: none;\n color: inherit;\n}\n\nbutton:focus-visible,\ninput:focus-visible,\nselect:focus-visible,\ntextarea:focus-visible {\n outline: 3px solid rgba(37, 99, 235, 0.18);\n outline-offset: 2px;\n}\n\n.app-shell {\n min-height: 100vh;\n padding-bottom: calc(var(--bottom-nav-height) + var(--safe-bottom) + var(--space-3));\n}\n\n.app-shell--standalone {\n padding-bottom: 0;\n}\n\n.app-shell__container {\n width: min(100% - 28px, var(--shell-width));\n margin: 0 auto;\n}\n\n.app-header {\n position: -webkit-sticky;\n position: sticky;\n top: 0;\n z-index: 40;\n padding-top: var(--safe-top);\n -webkit-backdrop-filter: blur(20px);\n backdrop-filter: blur(20px);\n background: rgba(249, 251, 255, 0.9);\n border-bottom: 1px solid rgba(226, 232, 240, 0.9);\n}\n\n.app-header::after {\n content: none;\n}\n\n.app-header--compact {\n background: rgba(249, 251, 255, 0.86);\n}\n\n.app-header__inner {\n position: relative;\n min-height: 44px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n padding: 4px 0 4px;\n}\n\n.app-header--compact .app-header__inner {\n min-height: 40px;\n gap: 10px;\n padding: 2px 0;\n}\n\n.app-header__main,\n.app-header__spacer {\n min-width: 0;\n flex: 1 1;\n}\n\n.app-header__main {\n display: flex;\n align-items: center;\n flex: 1 1;\n}\n\n.app-header--actions-only .app-header__main {\n display: none;\n}\n\n.app-header__text {\n min-width: 0;\n display: grid;\n grid-gap: 2px;\n gap: 2px;\n}\n\n.app-header__text h1 {\n font-size: 1.02rem;\n line-height: 1.15;\n letter-spacing: -0.03em;\n font-weight: 700;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.app-header--compact .app-header__text h1 {\n font-size: 0.98rem;\n}\n\n.app-header__text p {\n color: var(--text-secondary);\n font-size: 0.76rem;\n line-height: 1.3;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsi
// Exports
export default ___CSS_LOADER_EXPORT___;
`èýÿÿëýÿÿ ìýÿÿ@Âýÿÿ  ý`$þÿÿ%þÿÿ&þÿÿ'þÿÿ(þÿÿ)þÿÿ*þÿÿ™ýÿÿ+þÿÿ,þÿÿ-þÿÿ.þÿÿšmoduleConcatenationBailout.þÿÿ/þÿÿ  ÓÔ1þÿÿ2þÿÿd1ce34ea7abb4706 3þÿÿ‰module.id —___CSS_LOADER_EXPORT___3þÿÿ à ¸ÀÍyB¶ýÿÿ»ýÿÿ dÿÿý`  `x¶webpack/lib/dependencies/RuntimeRequirementsDependencya ÷ b'A¹–ЖAª–Ñ– `'Í../node_modules/next/dist/build/webpack/loaders/css-loader/src/runtime/api.jsÿ `xOþÿÿ___CSS_LOADER_API_IMPORT___½ úúþ b9ºwebpack/lib/dependencies/HarmonyExportExpressionDependencyö€ `'ôIþÿÿ `'‡sources“runtimeRequirements„dataŠjavascript«webpack/lib/util/registerExternalSerializerœwebpack-sources/CachedSource øš«webpack/lib/util/registerExternalSerializerœwebpack-sources/ConcatSource«webpack/lib/util/registerExternalSerializer™webpack-sources/RawSourceG__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _node_modules_next_dist_build_webpack_loaders_css_loader_src_runtime_api_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("/opt/accountbook/frontend/node_modules/next/dist/build/webpack/loaders/css-loader/src/runtime/api.js");
/* harmony import */ var _node_modules_next_dist_build_webpack_loaders_css_loader_src_runtime_api_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_node_modules_next_dist_build_webpack_loaders_css_loader_src_runtime_api_js__WEBPACK_IMPORTED_MODULE_0__);
«webpack/lib/util/registerExternalSerializerwebpack-sources/ReplaceSourceÒ–// Imports
import ___CSS_LOADER_API_IMPORT___ from "../node_modules/next/dist/build/webpack/loaders/css-loader/src/runtime/api.js";
var ___CSS_LOADER_EXPORT___ = ___CSS_LOADER_API_IMPORT___(false);
// Module
___CSS_LOADER_EXPORT___.push([module.id, ":root {\n --bg: #f4f6fb;\n --bg-accent: radial-gradient(circle at top, rgba(59, 130, 246, 0.12), transparent 30%), linear-gradient(180deg, #f9fbff 0%, #f2f5fa 100%);\n --surface: rgba(255, 255, 255, 0.9);\n --surface-strong: rgba(255, 255, 255, 0.98);\n --surface-muted: #eef3fb;\n --surface-soft: #f6f8fc;\n --surface-elevated: rgba(15, 23, 42, 0.86);\n --text: #0f172a;\n --text-secondary: #475569;\n --text-tertiary: #64748b;\n --text-on-dark: #f8fafc;\n --primary: #2563eb;\n --primary-dark: #1d4ed8;\n --primary-soft: rgba(37, 99, 235, 0.1);\n --income: #059669;\n --income-soft: rgba(5, 150, 105, 0.12);\n --expense: #dc2626;\n --expense-soft: rgba(220, 38, 38, 0.1);\n --warning: #d97706;\n --warning-soft: rgba(217, 119, 6, 0.12);\n --border: rgba(148, 163, 184, 0.2);\n --border-strong: rgba(100, 116, 139, 0.28);\n --shadow-xs: 0 1px 3px rgba(15, 23, 42, 0.05);\n --shadow-sm: 0 10px 28px rgba(15, 23, 42, 0.06);\n --shadow-md: 0 18px 44px rgba(15, 23, 42, 0.1);\n --radius-xs: 10px;\n --radius-sm: 14px;\n --radius: 18px;\n --radius-lg: 24px;\n --space-1: 4px;\n --space-2: 8px;\n --space-3: 12px;\n --space-4: 16px;\n --space-5: 20px;\n --space-6: 24px;\n --space-7: 28px;\n --space-8: 32px;\n --shell-width: 760px;\n --header-height: 44px;\n --bottom-nav-height: 84px;\n --safe-bottom: max(24px, env(safe-area-inset-bottom));\n --safe-top: max(20px, env(safe-area-inset-top));\n --transition: 180ms ease;\n --font-sans: \"Inter\", \"PingFang SC\", \"Hiragino Sans GB\", \"Microsoft YaHei\", sans-serif;\n --font-mono: \"SF Mono\", \"JetBrains Mono\", Monaco, monospace;\n color-scheme: light;\n}\n\n* {\n box-sizing: border-box;\n margin: 0;\n padding: 0;\n}\n\nhtml,\nbody,\n#__next {\n min-height: 100%;\n}\n\nbody {\n font-family: var(--font-sans);\n background: var(--bg-accent);\n color: var(--text);\n -webkit-font-smoothing: antialiased;\n text-rendering: optimizeLegibility;\n}\n\na,\nbutton,\ninput,\nselect,\ntextarea {\n font: inherit;\n}\n\nbutton,\ninput,\nselect,\ntextarea {\n border: 0;\n}\n\nbutton {\n background: none;\n color: inherit;\n}\n\nbutton:focus-visible,\ninput:focus-visible,\nselect:focus-visible,\ntextarea:focus-visible {\n outline: 3px solid rgba(37, 99, 235, 0.18);\n outline-offset: 2px;\n}\n\n.app-shell {\n min-height: 100vh;\n padding-bottom: calc(var(--bottom-nav-height) + var(--safe-bottom) + var(--space-3));\n}\n\n.app-shell--standalone {\n padding-bottom: 0;\n}\n\n.app-shell__container {\n width: min(100% - 28px, var(--shell-width));\n margin: 0 auto;\n}\n\n.app-header {\n position: -webkit-sticky;\n position: sticky;\n top: 0;\n z-index: 40;\n padding-top: var(--safe-top);\n -webkit-backdrop-filter: blur(20px);\n backdrop-filter: blur(20px);\n background: rgba(249, 251, 255, 0.9);\n border-bottom: 1px solid rgba(226, 232, 240, 0.9);\n}\n\n.app-header::after {\n content: none;\n}\n\n.app-header--compact {\n background: rgba(249, 251, 255, 0.86);\n}\n\n.app-header__inner {\n position: relative;\n min-height: 44px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n padding: 4px 0 4px;\n}\n\n.app-header--compact .app-header__inner {\n min-height: 40px;\n gap: 10px;\n padding: 2px 0;\n}\n\n.app-header__main,\n.app-header__spacer {\n min-width: 0;\n flex: 1 1;\n}\n\n.app-header__main {\n display: flex;\n align-items: center;\n flex: 1 1;\n}\n\n.app-header--actions-only .app-header__main {\n display: none;\n}\n\n.app-header__text {\n min-width: 0;\n display: grid;\n grid-gap: 2px;\n gap: 2px;\n}\n\n.app-header__text h1 {\n font-size: 1.02rem;\n line-height: 1.15;\n letter-spacing: -0.03em;\n font-weight: 700;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.app-header--compact .app-header__text h1 {\n font-size: 0.98rem;\n}\n\n.app-header__text p {\n color: var(--text-secondary);\n font-size: 0.76rem;\n line-height: 1.3;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsi
// Exports
export default ___CSS_LOADER_EXPORT___;
` G‚¢¼ª–¸–ª–¸–Ж Úâ@€ò_node_modules_next_dist_build_webpack_loaders_css_loader_src_runtime_api_js__WEBPACK_IMPORTED_MODULE_0___default()¿/* harmony default export */ __webpack_exports__["default"] = ();Òýÿÿ†source„size„mapspýÿÿ)™__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _node_modules_next_dist_build_webpack_loaders_css_loader_src_runtime_api_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("/opt/accountbook/frontend/node_modules/next/dist/build/webpack/loaders/css-loader/src/runtime/api.js");
/* harmony import */ var _node_modules_next_dist_build_webpack_loaders_css_loader_src_runtime_api_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_node_modules_next_dist_build_webpack_loaders_css_loader_src_runtime_api_js__WEBPACK_IMPORTED_MODULE_0__);
// Imports
var ___CSS_LOADER_EXPORT___ = _node_modules_next_dist_build_webpack_loaders_css_loader_src_runtime_api_js__WEBPACK_IMPORTED_MODULE_0___default()(false);
// Module
___CSS_LOADER_EXPORT___.push([module.id, ":root {\n --bg: #f4f6fb;\n --bg-accent: radial-gradient(circle at top, rgba(59, 130, 246, 0.12), transparent 30%), linear-gradient(180deg, #f9fbff 0%, #f2f5fa 100%);\n --surface: rgba(255, 255, 255, 0.9);\n --surface-strong: rgba(255, 255, 255, 0.98);\n --surface-muted: #eef3fb;\n --surface-soft: #f6f8fc;\n --surface-elevated: rgba(15, 23, 42, 0.86);\n --text: #0f172a;\n --text-secondary: #475569;\n --text-tertiary: #64748b;\n --text-on-dark: #f8fafc;\n --primary: #2563eb;\n --primary-dark: #1d4ed8;\n --primary-soft: rgba(37, 99, 235, 0.1);\n --income: #059669;\n --income-soft: rgba(5, 150, 105, 0.12);\n --expense: #dc2626;\n --expense-soft: rgba(220, 38, 38, 0.1);\n --warning: #d97706;\n --warning-soft: rgba(217, 119, 6, 0.12);\n --border: rgba(148, 163, 184, 0.2);\n --border-strong: rgba(100, 116, 139, 0.28);\n --shadow-xs: 0 1px 3px rgba(15, 23, 42, 0.05);\n --shadow-sm: 0 10px 28px rgba(15, 23, 42, 0.06);\n --shadow-md: 0 18px 44px rgba(15, 23, 42, 0.1);\n --radius-xs: 10px;\n --radius-sm: 14px;\n --radius: 18px;\n --radius-lg: 24px;\n --space-1: 4px;\n --space-2: 8px;\n --space-3: 12px;\n --space-4: 16px;\n --space-5: 20px;\n --space-6: 24px;\n --space-7: 28px;\n --space-8: 32px;\n --shell-width: 760px;\n --header-height: 44px;\n --bottom-nav-height: 84px;\n --safe-bottom: max(24px, env(safe-area-inset-bottom));\n --safe-top: max(20px, env(safe-area-inset-top));\n --transition: 180ms ease;\n --font-sans: \"Inter\", \"PingFang SC\", \"Hiragino Sans GB\", \"Microsoft YaHei\", sans-serif;\n --font-mono: \"SF Mono\", \"JetBrains Mono\", Monaco, monospace;\n color-scheme: light;\n}\n\n* {\n box-sizing: border-box;\n margin: 0;\n padding: 0;\n}\n\nhtml,\nbody,\n#__next {\n min-height: 100%;\n}\n\nbody {\n font-family: var(--font-sans);\n background: var(--bg-accent);\n color: var(--text);\n -webkit-font-smoothing: antialiased;\n text-rendering: optimizeLegibility;\n}\n\na,\nbutton,\ninput,\nselect,\ntextarea {\n font: inherit;\n}\n\nbutton,\ninput,\nselect,\ntextarea {\n border: 0;\n}\n\nbutton {\n background: none;\n color: inherit;\n}\n\nbutton:focus-visible,\ninput:focus-visible,\nselect:focus-visible,\ntextarea:focus-visible {\n outline: 3px solid rgba(37, 99, 235, 0.18);\n outline-offset: 2px;\n}\n\n.app-shell {\n min-height: 100vh;\n padding-bottom: calc(var(--bottom-nav-height) + var(--safe-bottom) + var(--space-3));\n}\n\n.app-shell--standalone {\n padding-bottom: 0;\n}\n\n.app-shell__container {\n width: min(100% - 28px, var(--shell-width));\n margin: 0 auto;\n}\n\n.app-header {\n position: -webkit-sticky;\n position: sticky;\n top: 0;\n z-index: 40;\n padding-top: var(--safe-top);\n -webkit-backdrop-filter: blur(20px);\n backdrop-filter: blur(20px);\n background: rgba(249, 251, 255, 0.9);\n border-bottom: 1px solid rgba(226, 232, 240, 0.9);\n}\n\n.app-header::after {\n content: none;\n}\n\n.app-header--compact {\n background: rgba(249, 251, 255, 0.86);\n}\n\n.app-header__inner {\n position: relative;\n min-height: 44px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n padding: 4px 0 4px;\n}\n\n.app-header--compact .app-header__inner {\n min-height: 40px;\n gap: 10px;\n padding: 2px 0;\n}\n\n.app-header__main,\n.app-header__spacer {\n min-width: 0;\n flex: 1 1;\n}\n\n.app-header__main {\n display: flex;\n align-items: center;\n flex: 1 1;\n}\n\n.app-header--actions-only .app-header__main {\n display: none;\n}\n\n.app-header__text {\n min-width: 0;\n display: grid;\n grid-gap: 2px;\n gap: 2px;\n}\n\n.app-header__text h1 {\n font-size: 1.02rem;\n line-height: 1.15;\n letter-spacing: -0.03em;\n font-weight: 700;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.app-header--compact .app-header__text h1 {\n font-size: 0.98rem;\n}\n\n.app-header__text p {\n color: var(--text-secondary);\n font-size: 0.76rem;\n line-height: 1.3;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsi
// Exports
/* harmony default export */ __webpack_exports__["default"] = (___CSS_LOADER_EXPORT___);
“__webpack_require__•__webpack_require__.nþÿÿ•__webpack_require__.rÓ˦mini-css-extract-plugin/dist/CssModule@_ýÿÿ±² css/mini-extract^ýÿÿ   ôýÿÿõýÿÿíýÿÿbýÿÿ 75fab2794a4c01af—webpack/lib/ModuleGraph“RestoreProvidedData<ýÿÿˆprovidedcanMangleProvideterminalBindingexportsInfo#þÿÿ
üzÿÿÿ
ùþÿÿ
ÙÚ X«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 */ Categories; }
/* 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 _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);
/* harmony import */ var _components_ConfirmProvider__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(8846);
«webpack/lib/util/registerExternalSerializerwebpack-sources/ReplaceSourceòKimport { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
import { useState, useEffect } from "react";
import { useRouter } from "next/router";
import { isLoggedIn, getCategories, createCategory, deleteCategory } from "../lib/api";
import AppShell from "../components/AppShell";
import { Icon } from "../components/icons";
import { useToast } from "../components/ToastProvider";
import { useConfirm } from "../components/ConfirmProvider";
const ICONS = [
"\uD83C\uDF5C",
"\uD83E\uDD58",
"\uD83E\uDD6C",
"\uD83E\uDD69",
"\uD83C\uDF54",
"\uD83C\uDF55",
"☕",
"\uD83C\uDF7A",
"\uD83E\uDDCB",
"\uD83C\uDF4E",
"\uD83C\uDF6A",
"\uD83C\uDF6B",
"\uD83D\uDE97",
"\uD83D\uDE95",
"\uD83D\uDE8C",
"\uD83D\uDE87",
"⛽",
"\uD83D\uDE99",
"\uD83D\uDEF5",
"✈ï¸",
"\uD83D\uDEA2",
"\uD83D\uDED2",
"\uD83D\uDC54",
"\uD83D\uDC55",
"\uD83D\uDC56",
"\uD83D\uDC5F",
"\uD83D\uDC53",
"\uD83E\uDDE2",
"\uD83D\uDC5C",
"\uD83C\uDF92",
"\uD83D\uDC8D",
"\uD83D\uDC84",
"\uD83C\uDFE0",
"\uD83C\uDFE2",
"\uD83C\uDFE6",
"\uD83C\uDFE5",
"\uD83C\uDFE8",
"\uD83C\uDFE9",
"\uD83D\uDD27",
"\uD83D\uDD28",
"\uD83D\uDECBï¸",
"\uD83D\uDCFA",
"\uD83D\uDCBB",
"\uD83D\uDCF1",
"\uD83D\uDCDD",
"\uD83D\uDCB0",
"\uD83C\uDF81",
"\uD83D\uDCC8",
"\uD83E\uDDE7",
"\uD83D\uDCB3",
"\uD83C\uDFAF",
"\uD83D\uDCDA",
"\uD83C\uDFB5",
"\uD83C\uDFAE",
"\uD83C\uDFC3",
"âš½",
"\uD83C\uDFCA",
"\uD83D\uDEB4",
"\uD83C\uDFAD",
"\uD83C\uDFA8",
"\uD83C\uDFAC",
"\uD83D\uDCF7",
"\uD83C\uDFA5",
"\uD83D\uDCFA",
"\uD83E\uDDF8",
"\uD83D\uDC15",
"\uD83D\uDC08",
"\uD83C\uDF38",
"\uD83C\uDF3A"
];
export default function Categories() {
const router = useRouter();
const { showToast } = useToast();
const { confirm } = useConfirm();
const [categories, setCategories] = useState({
expense: [],
income: []
});
const [modalOpen, setModalOpen] = useState(false);
const [categoryType, setCategoryType] = useState("expense");
const [loading, setLoading] = useState(true);
const [submitting, setSubmitting] = useState(false);
const [formError, setFormError] = useState("");
const [formData, setFormData] = useState({
name: "",
icon: "\uD83D\uDCDD"
});
useEffect(()=>{
if (!isLoggedIn()) {
router.push("/login");
return;
}
loadCategories();
}, [
router
]);
const loadCategories = async ()=>{
setLoading(true);
try {
const data = await getCategories();
setCategories(data);
} catch (err) {
console.error(err);
showToast(err.message, {
tone: "error"
});
} finally{
setLoading(false);
}
};
const openAddModal = (type)=>{
setCategoryType(type);
setFormData({
name: "",
icon: "\uD83D\uDCDD"
});
setFormError("");
setModalOpen(true);
};
const closeModal = ()=>{
if (submitting) return;
setModalOpen(false);
setFormError("");
};
const handleSubmit = async (e)=>{
e.preventDefault();
if (!formData.name || !formData.icon) {
setFormError("请填写完整信æ¯");
return;
}
setSubmitting(true);
setFormError("");
try {
await createCategory({
type: categoryType,
name: formData.name,
icon: formData.icon
});
closeModal();
showToast("分类已创建", {
tone: "success"
});
await loadCategories();
} catch (err) {
setFormError(err.message);
showToast(err.message, {
tone: "error"
});
} finally{
setSubmitting(false);
}
};
const handleDelete = async (id)=>{
const shouldDelete = await confirm({
title: "删除此分类?",
description: "默认分类ä¸å¯åˆ é™¤ï¼Œè‡ªå®šä¹‰åˆ†ç±»åˆ é™¤åŽå°†æ— æ³•æ¢å¤ã€‚",
confirmText: "删除"
});
if (!shouldDelete) return;
try {
await deleteCategory(id);
showToast("分类已删除", {
tone: "success"
});
await loadCategories();
} catch (err) {
showToast(err.message, {
tone: "error"
});
}
};
const actions = /*#__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, {
title: "分类",
subtitle: "æ•´ç†å¸¸ç”¨åˆ†ç±»ä¸Žå›¾æ ‡",
actions: actions,
compactHeader: true,
children: [
loading ? /*#__PURE__*/ _jsx("div", {
className: "card loading-state",
children: "加载中..."
}) : /*#__PURE__*/ _jsxs(_Fragment, {
children: [
/*#__PURE__*/ _jsxs("section", {
className: "card category-section category-section--refresh",
children: [
/*#__PURE__*/ _jsxs("div", {
className: "section-header",
children: [
/*#__PURE__*/ _jsxs("div", {
children: [
/*#__PURE__*/ _jsx("p", {
className: "eyebrow",
children: "支出分类"
}),
/*#__PURE__*/ _jsx("h2", {
children: "支出"
})
]
}),
/*#__PURE__*/ _jsx("button", {
type: "button",
className: "btn btn-secondary btn-inline",
onClick: ()=>openAddModal("expense"),
children: /*#__PURE__*/ _jsxs("span", {
className: "button-content",
children: [
/*#__PURE__*/ _jsx(Icon, {
name: "plus",
size: 16
}),
"添加"
]
})
})
]
}),
/*#__PURE__*/ _jsx("div", {
className: "category-list category-list--chips",
children: categories.expense.map((cat)=>/*#__PURE__*/ _jsxs("div", {
className: "category-tag",
children: [
/*#__PURE__*/ _jsx("span", {
className: "cat-icon",
children: cat.icon
}),
/*#__PURE__*/ _jsx("span", {
className: "cat-name",
children: cat.name
}),
!cat.is_default && /*#__PURE__*/ _jsx("button", {
type: "button",
className: "icon-button icon-button--ghost cat-delete",
"aria-label": "删除".concat(cat.name),
onClick: ()=>handleDelete(cat.id),
children: /*#__PURE__*/ _jsx(Icon, {
name: "close",
size: 14
})
})
]
}, cat.id || cat.name))
})
]
}),
/*#__PURE__*/ _jsxs("section", {
className: "card category-section category-section--refresh",
children: [
/*#__PURE__*/ _jsxs("div", {
className: "section-header",
children: [
/*#__PURE__*/ _jsxs("div", {
children: [
/*#__PURE__*/ _jsx("p", {
className: "eyebrow",
children: "收入分类"
}),
/*#__PURE__*/ _jsx("h2", {
children: "æ”¶å…¥"
})
]
}),
/*#__PURE__*/ _jsx("button", {
type: "button",
className: "btn btn-secondary btn-inline",
onClick: ()=>openAddModal("income"),
children: /*#__PURE__*/ _jsxs("span", {
className: "button-content",
children: [
/*#__PURE__*/ _jsx(Icon, {
name: "plus",
size: 16
}),
"添加"
]
})
})
]
}),
/*#__PURE__*/ _jsx("div", {
className: "category-list category-list--chips",
children: categories.income.map((cat)=>/*#__PURE__*/ _jsxs("div", {
className: "category-tag",
children: [
/*#__PURE__*/ _jsx("span", {
className: "cat-icon",
children: cat.icon
}),
/*#__PURE__*/ _jsx("span", {
className: "cat-name",
children: cat.name
}),
!cat.is_default && /*#__PURE__*/ _jsx("button", {
type: "button",
className: "icon-button icon-button--ghost cat-delete",
"aria-label": "删除".concat(cat.name),
onClick: ()=>handleDelete(cat.id),
children: /*#__PURE__*/ _jsx(Icon, {
name: "close",
size: 14
})
})
]
}, cat.id || cat.name))
})
]
}),
/*#__PURE__*/ _jsx("div", {
className: "card category-tip category-tip--refresh",
children: /*#__PURE__*/ _jsx("p", {
children: "默认分类无法删除,自定义分类å¯ä»¥åˆ é™¤ã€‚"
})
})
]
}),
modalOpen && /*#__PURE__*/ _jsx("div", {
className: "modal-overlay",
onClick: closeModal,
children: /*#__PURE__*/ _jsxs("div", {
className: "modal",
onClick: (e)=>e.stopPropagation(),
children: [
/*#__PURE__*/ _jsxs("div", {
className: "modal-header",
children: [
/*#__PURE__*/ _jsxs("div", {
children: [
/*#__PURE__*/ _jsx("p", {
className: "eyebrow",
children: "新增分类"
}),
/*#__PURE__*/ _jsx("h2", {
children: categoryType === "expense" ? "新建支出分类" : "新建收入分类"
})
]
}),
/*#__PURE__*/ _jsx("button", {
type: "button",
className: "icon-button icon-button--ghost",
"aria-label": "关闭弹窗",
onClick: closeModal,
children: /*#__PURE__*/ _jsx(Icon, {
name: "close",
size: 18
})
})
]
}),
formError && /*#__PURE__*/ _jsx("div", {
className: "error-msg",
children: formError
}),
/*#__PURE__*/ _jsxs("form", {
onSubmit: handleSubmit,
children: [
/*#__PURE__*/ _jsxs("div", {
className: "form-group",
children: [
/*#__PURE__*/ _jsx("label", {
htmlFor: "category-name",
children: "分类åç§°"
}),
/*#__PURE__*/ _jsx("input", {
id: "category-name",
type: "text",
value: formData.name,
onChange: (e)=>setFormData({
...formData,
name: e.target.value
}),
placeholder: "请输入分类åç§°",
required: true,
disabled: submitting
})
]
}),
/*#__PURE__*/ _jsxs("div", {
className: "form-group",
children: [
/*#__PURE__*/ _jsx("label", {
children: "选择图标"
}),
/*#__PURE__*/ _jsx("div", {
className: "icon-grid",
children: ICONS.map((icon)=>/*#__PURE__*/ _jsx("button", {
type: "button",
className: "icon-btn ".concat(formData.icon === icon ? "active" : ""),
onClick: ()=>setFormData({
...formData,
icon
}),
disabled: submitting,
children: icon
}, icon))
})
]
}),
/*#__PURE__*/ _jsx("div", {
className: "modal-actions",
children: /*#__PURE__*/ _jsx("button", {
type: "submit",
className: "btn btn-primary",
disabled: submitting,
children: submitting ? /*#__PURE__*/ _jsxs("span", {
className: "button-content",
children: [
/*#__PURE__*/ _jsx(Icon, {
name: "spinner",
size: 16,
className: "is-spinning"
}),
"ä¿å­˜ä¸­..."
]
}) : "ä¿å­˜"
})
})
]
})
]
})
})
]
});
}
cYUW_«­24^`˜Ò .Zbˆ¥®Ûâ<Cy°·èï _& Q X Ÿ ´ ½ ­
¹
 :=ðóõø\`bi˜ ãçšDHÁľÁ½Àçë¹»¾šLP % ( Î!Ñ!Ó!Ö!#š#M$_Q$÷$û$t%w%q&t&p's'™((h)k)m)p)I+L+ý+,½,À,À-Ã-Ö.Ù.00„0‡0G2J2Ó2_Ö23™34!4Ñ4Õ4m5q5â5å5Ï6Ò6æ7é7+9.90939W:Z: ;;¥;©;W<Z<L=O=„@ˆ@6AN9AåAèABBÜEßEfFiF€G„GOHRHTHWH¶(0,next_router__WEBPACK_IMPORTED_MODULE_2__.useRouter)Ì(0,_components_ToastProvider__WEBPACK_IMPORTED_MODULE_5__/* .useToast */ .p)Ð(0,_components_ConfirmProvider__WEBPACK_IMPORTED_MODULE_6__/* .useConfirm */ .N)¯(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)ÿÿÿÿÿÿ°(0,react__WEBPACK_IMPORTED_MODULE_1__.useEffect)¾(0,_lib_api__WEBPACK_IMPORTED_MODULE_7__/* .isLoggedIn */ .jl)Á(0,_lib_api__WEBPACK_IMPORTED_MODULE_7__/* .getCategories */ .CP)Â(0,_lib_api__WEBPACK_IMPORTED_MODULE_7__/* .createCategory */ .k4)Â(0,_lib_api__WEBPACK_IMPORTED_MODULE_7__/* .deleteCategory */ .uu)¶(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)ÿ¼_components_icons__WEBPACK_IMPORTED_MODULE_4__/* .Icon */ .J·(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)Å_components_AppShell__WEBPACK_IMPORTED_MODULE_3__/* ["default"] */ .Züþ·react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.FragmentýýýûûûýûüûýûûûûüýýýûûûýûüûýûûûûüûûûýýýûûûûüûýýûûýûûûûûýûüÞM^__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": function() { return /* binding */ Categories; }
/* 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 _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);
/* harmony import */ var _components_ConfirmProvider__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(8846);
const ICONS = [
"\uD83C\uDF5C",
"\uD83E\uDD58",
"\uD83E\uDD6C",
"\uD83E\uDD69",
"\uD83C\uDF54",
"\uD83C\uDF55",
"☕",
"\uD83C\uDF7A",
"\uD83E\uDDCB",
"\uD83C\uDF4E",
"\uD83C\uDF6A",
"\uD83C\uDF6B",
"\uD83D\uDE97",
"\uD83D\uDE95",
"\uD83D\uDE8C",
"\uD83D\uDE87",
"⛽",
"\uD83D\uDE99",
"\uD83D\uDEF5",
"✈ï¸",
"\uD83D\uDEA2",
"\uD83D\uDED2",
"\uD83D\uDC54",
"\uD83D\uDC55",
"\uD83D\uDC56",
"\uD83D\uDC5F",
"\uD83D\uDC53",
"\uD83E\uDDE2",
"\uD83D\uDC5C",
"\uD83C\uDF92",
"\uD83D\uDC8D",
"\uD83D\uDC84",
"\uD83C\uDFE0",
"\uD83C\uDFE2",
"\uD83C\uDFE6",
"\uD83C\uDFE5",
"\uD83C\uDFE8",
"\uD83C\uDFE9",
"\uD83D\uDD27",
"\uD83D\uDD28",
"\uD83D\uDECBï¸",
"\uD83D\uDCFA",
"\uD83D\uDCBB",
"\uD83D\uDCF1",
"\uD83D\uDCDD",
"\uD83D\uDCB0",
"\uD83C\uDF81",
"\uD83D\uDCC8",
"\uD83E\uDDE7",
"\uD83D\uDCB3",
"\uD83C\uDFAF",
"\uD83D\uDCDA",
"\uD83C\uDFB5",
"\uD83C\uDFAE",
"\uD83C\uDFC3",
"âš½",
"\uD83C\uDFCA",
"\uD83D\uDEB4",
"\uD83C\uDFAD",
"\uD83C\uDFA8",
"\uD83C\uDFAC",
"\uD83D\uDCF7",
"\uD83C\uDFA5",
"\uD83D\uDCFA",
"\uD83E\uDDF8",
"\uD83D\uDC15",
"\uD83D\uDC08",
"\uD83C\uDF38",
"\uD83C\uDF3A"
];
function Categories() {
const router = (0,next_router__WEBPACK_IMPORTED_MODULE_2__.useRouter)();
const { showToast } = (0,_components_ToastProvider__WEBPACK_IMPORTED_MODULE_5__/* .useToast */ .p)();
const { confirm } = (0,_components_ConfirmProvider__WEBPACK_IMPORTED_MODULE_6__/* .useConfirm */ .N)();
const [categories, setCategories] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)({
expense: [],
income: []
});
const [modalOpen, setModalOpen] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)(false);
const [categoryType, setCategoryType] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)("expense");
const [loading, setLoading] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)(true);
const [submitting, setSubmitting] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)(false);
const [formError, setFormError] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)("");
const [formData, setFormData] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)({
name: "",
icon: "\uD83D\uDCDD"
});
(0,react__WEBPACK_IMPORTED_MODULE_1__.useEffect)(()=>{
if (!(0,_lib_api__WEBPACK_IMPORTED_MODULE_7__/* .isLoggedIn */ .jl)()) {
router.push("/login");
return;
}
loadCategories();
}, [
router
]);
const loadCategories = async ()=>{
setLoading(true);
try {
const data = await (0,_lib_api__WEBPACK_IMPORTED_MODULE_7__/* .getCategories */ .CP)();
setCategories(data);
} catch (err) {
console.error(err);
showToast(err.message, {
tone: "error"
});
} finally{
setLoading(false);
}
};
const openAddModal = (type)=>{
setCategoryType(type);
setFormData({
name: "",
icon: "\uD83D\uDCDD"
});
setFormError("");
setModalOpen(true);
};
const closeModal = ()=>{
if (submitting) return;
setModalOpen(false);
setFormError("");
};
const handleSubmit = async (e)=>{
e.preventDefault();
if (!formData.name || !formData.icon) {
setFormError("请填写完整信æ¯");
return;
}
setSubmitting(true);
setFormError("");
try {
await (0,_lib_api__WEBPACK_IMPORTED_MODULE_7__/* .createCategory */ .k4)({
type: categoryType,
name: formData.name,
icon: formData.icon
});
closeModal();
showToast("分类已创建", {
tone: "success"
});
await loadCategories();
} catch (err) {
setFormError(err.message);
showToast(err.message, {
tone: "error"
});
} finally{
setSubmitting(false);
}
};
const handleDelete = async (id)=>{
const shouldDelete = await confirm({
title: "删除此分类?",
description: "默认分类ä¸å¯åˆ é™¤ï¼Œè‡ªå®šä¹‰åˆ†ç±»åˆ é™¤åŽå°†æ— æ³•æ¢å¤ã€‚",
confirmText: "删除"
});
if (!shouldDelete) return;
try {
await (0,_lib_api__WEBPACK_IMPORTED_MODULE_7__/* .deleteCategory */ .uu)(id);
showToast("分类已删除", {
tone: "success"
});
await loadCategories();
} catch (err) {
showToast(err.message, {
tone: "error"
});
}
};
const actions = /*#__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, {
title: "分类",
subtitle: "æ•´ç†å¸¸ç”¨åˆ†ç±»ä¸Žå›¾æ ‡",
actions: actions,
compactHeader: true,
children: [
loading ? /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "card loading-state",
children: "加载中..."
}) : /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)(react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.Fragment, {
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("section", {
className: "card category-section category-section--refresh",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "section-header",
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__.jsx)("button", {
type: "button",
className: "btn btn-secondary btn-inline",
onClick: ()=>openAddModal("expense"),
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("span", {
className: "button-content",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_components_icons__WEBPACK_IMPORTED_MODULE_4__/* .Icon */ .J, {
name: "plus",
size: 16
}),
"添加"
]
})
})
]
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "category-list category-list--chips",
children: categories.expense.map((cat)=>/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "category-tag",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("span", {
className: "cat-icon",
children: cat.icon
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("span", {
className: "cat-name",
children: cat.name
}),
!cat.is_default && /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("button", {
type: "button",
className: "icon-button icon-button--ghost cat-delete",
"aria-label": "删除".concat(cat.name),
onClick: ()=>handleDelete(cat.id),
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_components_icons__WEBPACK_IMPORTED_MODULE_4__/* .Icon */ .J, {
name: "close",
size: 14
})
})
]
}, cat.id || cat.name))
})
]
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("section", {
className: "card category-section category-section--refresh",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "section-header",
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__.jsx)("button", {
type: "button",
className: "btn btn-secondary btn-inline",
onClick: ()=>openAddModal("income"),
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("span", {
className: "button-content",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_components_icons__WEBPACK_IMPORTED_MODULE_4__/* .Icon */ .J, {
name: "plus",
size: 16
}),
"添加"
]
})
})
]
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "category-list category-list--chips",
children: categories.income.map((cat)=>/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "category-tag",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("span", {
className: "cat-icon",
children: cat.icon
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("span", {
className: "cat-name",
children: cat.name
}),
!cat.is_default && /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("button", {
type: "button",
className: "icon-button icon-button--ghost cat-delete",
"aria-label": "删除".concat(cat.name),
onClick: ()=>handleDelete(cat.id),
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_components_icons__WEBPACK_IMPORTED_MODULE_4__/* .Icon */ .J, {
name: "close",
size: 14
})
})
]
}, cat.id || cat.name))
})
]
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "card category-tip category-tip--refresh",
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("p", {
children: "默认分类无法删除,自定义分类å¯ä»¥åˆ é™¤ã€‚"
})
})
]
}),
modalOpen && /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "modal-overlay",
onClick: closeModal,
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "modal",
onClick: (e)=>e.stopPropagation(),
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "modal-header",
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: categoryType === "expense" ? "新建支出分类" : "新建收入分类"
})
]
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("button", {
type: "button",
className: "icon-button icon-button--ghost",
"aria-label": "关闭弹窗",
onClick: closeModal,
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_components_icons__WEBPACK_IMPORTED_MODULE_4__/* .Icon */ .J, {
name: "close",
size: 18
})
})
]
}),
formError && /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "error-msg",
children: formError
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("form", {
onSubmit: handleSubmit,
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "form-group",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("label", {
htmlFor: "category-name",
children: "分类åç§°"
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("input", {
id: "category-name",
type: "text",
value: formData.name,
onChange: (e)=>setFormData({
...formData,
name: e.target.value
}),
placeholder: "请输入分类åç§°",
required: true,
disabled: submitting
})
]
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "form-group",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("label", {
children: "选择图标"
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "icon-grid",
children: ICONS.map((icon)=>/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("button", {
type: "button",
className: "icon-btn ".concat(formData.icon === icon ? "active" : ""),
onClick: ()=>setFormData({
...formData,
icon
}),
disabled: submitting,
children: icon
}, icon))
})
]
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "modal-actions",
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("button", {
type: "submit",
className: "btn btn-primary",
disabled: submitting,
children: submitting ? /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("span", {
className: "button-content",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_components_icons__WEBPACK_IMPORTED_MODULE_4__/* .Icon */ .J, {
name: "spinner",
size: 16,
className: "is-spinning"
}),
"ä¿å­˜ä¸­..."
]
}) : "ä¿å­˜"
})
})
]
})
]
})
})
]
});
}
”{"finalSource":true}ƒmapbufferedMap ConcatSourceRawSource__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": function() { return /* binding */ Categories; }
/* 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 _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);
/* harmony import */ var _components_ConfirmProvider__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(8846);
ReplaceSourceRawSourceŽýÿÿn085undefined87130undefined132171undefined173259undefined261306undefined308350undefined352406undefined408466undefined18241838undefined18821890(0,next_router__WEBPACK_IMPORTED_MODULE_2__.useRouter)undefined19211928(0,_components_ToastProvider__WEBPACK_IMPORTED_MODULE_5__/* .useToast */ .p)undefined19571966(0,_components_ConfirmProvider__WEBPACK_IMPORTED_MODULE_6__/* .useConfirm */ .N)undefined20112018(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined21082115(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined21692176(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined22242231(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined22802287(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined23352342(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined23852392(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined24552463(0,react__WEBPACK_IMPORTED_MODULE_1__.useEffect)undefined24842493(0,_lib_api__WEBPACK_IMPORTED_MODULE_7__/* .isLoggedIn */ .jl)undefined27332745(0,_lib_api__WEBPACK_IMPORTED_MODULE_7__/* .getCategories */ .CP)undefined35963609(0,_lib_api__WEBPACK_IMPORTED_MODULE_7__/* .createCategory */ .k4)undefined43714384(0,_lib_api__WEBPACK_IMPORTED_MODULE_7__/* .deleteCategory */ .uu)undefined46664669(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined48484851(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined48534856_components_icons__WEBPACK_IMPORTED_MODULE_4__/* .Icon */ .Jundefined49564960(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined49624969_components_AppShell__WEBPACK_IMPORTED_MODULE_3__/* ["default"] */ .Zundefined51375140(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined52665270(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined52725280react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.Fragmentundefined53475351(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined55305534(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined57005704(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined58255828(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined60786081(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined63336336(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined66316635(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined68386841(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined68436846_components_icons__WEBPACK_IMPORTED_MODULE_4__/* .Icon */ .Jundefined73197322(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined75007504(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined76927695(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined79517954(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined82298232(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined86548657(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined86598662_components_icons__WEBPACK_IMPORTED_MODULE_4__/* .Icon */ .Jundefined91109114(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined92939297(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined94639467(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined95889591(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined98419844(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined1009610099(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined1039310397(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined1060010603(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined1060510608_components_icons__WEBPACK_IMPORTED_MODULE_4__/* .Icon */ .Jundefined1108111084(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined1126111265(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined1145311456(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined1171211715(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined1199011993(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined1241512418(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_
«webpack/lib/util/registerExternalSerializerœwebpack-sources/ConcatSource«webpack/lib/util/registerExternalSerializer™webpack-sources/RawSource­/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ S: function() { return /* binding */ DEFAULT_CATEGORIES; }
/* harmony export */ });
«webpack/lib/util/registerExternalSerializerwebpack-sources/ReplaceSourceZexport const DEFAULT_CATEGORIES = {
expense: [
{
name: "é¤é¥®",
icon: "\uD83C\uDF5C"
},
{
name: "ä¹°èœ",
icon: "\uD83E\uDD6C"
},
{
name: "交通",
icon: "\uD83D\uDE97"
},
{
name: "购物",
icon: "\uD83D\uDED2"
},
{
name: "娱ä¹",
icon: "\uD83C\uDFAE"
},
{
name: "房租",
icon: "\uD83C\uDFE0"
},
{
name: "房贷",
icon: "\uD83C\uDFE6"
},
{
name: "亲情å¡",
icon: "\uD83D\uDCB3"
},
{
name: "水电",
icon: "\uD83D\uDCA7"
},
{
name: "è¡£æœ",
icon: "\uD83D\uDC54"
},
{
name: "通讯",
icon: "\uD83D\uDCF1"
},
{
name: "日用å“",
icon: "\uD83E\uDDF4"
},
{
name: "美妆",
icon: "\uD83D\uDC84"
},
{
name: "医疗",
icon: "\uD83C\uDFE5"
},
{
name: "养娃",
icon: "\uD83D\uDC76"
},
{
name: "家电",
icon: "\uD83D\uDCFA"
},
{
name: "å®¶å…·",
icon: "\uD83D\uDECBï¸"
},
{
name: "装修",
icon: "\uD83D\uDD28"
},
{
name: "宠物",
icon: "\uD83D\uDC15"
},
{
name: "å…¶ä»–",
icon: "\uD83D\uDCDD"
}
],
income: [
{
name: "工资",
icon: "\uD83D\uDCB0"
},
{
name: "奖金",
icon: "\uD83C\uDF81"
},
{
name: "ç†è´¢",
icon: "\uD83D\uDCC8"
},
{
name: "红包",
icon: "\uD83E\uDDE7"
},
{
name: "å…¶ä»–",
icon: "\uD83D\uDCDD"
}
]
};
Ìô­/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ S: function() { return /* binding */ DEFAULT_CATEGORIES; }
/* harmony export */ });
õYÿÿÿ 06undefinedÔýÿÿöVÿÿÿ»¼ «webpack/lib/util/registerExternalSerializerœwebpack-sources/ConcatSource«webpack/lib/util/registerExternalSerializer™webpack-sources/RawSourceÑ:root {
--bg: #f4f6fb;
--bg-accent: radial-gradient(circle at top, rgba(59, 130, 246, 0.12), transparent 30%), linear-gradient(180deg, #f9fbff 0%, #f2f5fa 100%);
--surface: rgba(255, 255, 255, 0.9);
--surface-strong: rgba(255, 255, 255, 0.98);
--surface-muted: #eef3fb;
--surface-soft: #f6f8fc;
--surface-elevated: rgba(15, 23, 42, 0.86);
--text: #0f172a;
--text-secondary: #475569;
--text-tertiary: #64748b;
--text-on-dark: #f8fafc;
--primary: #2563eb;
--primary-dark: #1d4ed8;
--primary-soft: rgba(37, 99, 235, 0.1);
--income: #059669;
--income-soft: rgba(5, 150, 105, 0.12);
--expense: #dc2626;
--expense-soft: rgba(220, 38, 38, 0.1);
--warning: #d97706;
--warning-soft: rgba(217, 119, 6, 0.12);
--border: rgba(148, 163, 184, 0.2);
--border-strong: rgba(100, 116, 139, 0.28);
--shadow-xs: 0 1px 3px rgba(15, 23, 42, 0.05);
--shadow-sm: 0 10px 28px rgba(15, 23, 42, 0.06);
--shadow-md: 0 18px 44px rgba(15, 23, 42, 0.1);
--radius-xs: 10px;
--radius-sm: 14px;
--radius: 18px;
--radius-lg: 24px;
--space-1: 4px;
--space-2: 8px;
--space-3: 12px;
--space-4: 16px;
--space-5: 20px;
--space-6: 24px;
--space-7: 28px;
--space-8: 32px;
--shell-width: 760px;
--header-height: 44px;
--bottom-nav-height: 84px;
--safe-bottom: max(24px, env(safe-area-inset-bottom));
--safe-top: max(20px, env(safe-area-inset-top));
--transition: 180ms ease;
--font-sans: "Inter", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
--font-mono: "SF Mono", "JetBrains Mono", Monaco, monospace;
color-scheme: light;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html,
body,
#__next {
min-height: 100%;
}
body {
font-family: var(--font-sans);
background: var(--bg-accent);
color: var(--text);
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
}
a,
button,
input,
select,
textarea {
font: inherit;
}
button,
input,
select,
textarea {
border: 0;
}
button {
background: none;
color: inherit;
}
button:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible {
outline: 3px solid rgba(37, 99, 235, 0.18);
outline-offset: 2px;
}
.app-shell {
min-height: 100vh;
padding-bottom: calc(var(--bottom-nav-height) + var(--safe-bottom) + var(--space-3));
}
.app-shell--standalone {
padding-bottom: 0;
}
.app-shell__container {
width: min(100% - 28px, var(--shell-width));
margin: 0 auto;
}
.app-header {
position: -webkit-sticky;
position: sticky;
top: 0;
z-index: 40;
padding-top: var(--safe-top);
-webkit-backdrop-filter: blur(20px);
backdrop-filter: blur(20px);
background: rgba(249, 251, 255, 0.9);
border-bottom: 1px solid rgba(226, 232, 240, 0.9);
}
.app-header::after {
content: none;
}
.app-header--compact {
background: rgba(249, 251, 255, 0.86);
}
.app-header__inner {
position: relative;
min-height: 44px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 4px 0 4px;
}
.app-header--compact .app-header__inner {
min-height: 40px;
gap: 10px;
padding: 2px 0;
}
.app-header__main,
.app-header__spacer {
min-width: 0;
flex: 1 1;
}
.app-header__main {
display: flex;
align-items: center;
flex: 1 1;
}
.app-header--actions-only .app-header__main {
display: none;
}
.app-header__text {
min-width: 0;
display: grid;
grid-gap: 2px;
gap: 2px;
}
.app-header__text h1 {
font-size: 1.02rem;
line-height: 1.15;
letter-spacing: -0.03em;
font-weight: 700;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.app-header--compact .app-header__text h1 {
font-size: 0.98rem;
}
.app-header__text p {
color: var(--text-secondary);
font-size: 0.76rem;
line-height: 1.3;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.app-header--compact .app-header__text p {
font-size: 0.74rem;
}
.app-header__actions {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 8px;
flex-wrap: nowrap;
min-width: 0;
margin-left: auto;
flex-shrink: 0;
}
.app-header--compact .app-header__actions {
gap: 6px;
}
.app-content {
padding: 10px 0 0;
}
.app-content__inner {
display: grid;
grid-gap: 14px;
gap: 14px;
align-content: start;
}
.card {
position: relative;
background: var(--surface);
border: 1px solid rgba(148, 163, 184, 0.18);
border-radius: var(--radius);
box-shadow: 0 10px 26px rgba(15, 23, 42, 0.05);
-webkit-backdrop-filter: blur(18px);
backdrop-filter: blur(18px);
}
.section-card,
.category-section,
.settings-section,
.category-tip,
.dashboard-hero,
.auth-card,
.confirm-card {
overflow: hidden;
}
.section-card,
.category-section,
.settings-section,
.dashboard-hero,
.auth-card {
padding: 16px;
}
.section-card--search {
display: grid;
grid-gap: 14px;
gap: 14px;
}
.section-card--search-panel {
padding: 12px;
gap: 12px;
background: linear-gradient(180deg, rgba(255, 255, 255, 0.94), rgba(247, 249, 253, 0.9));
}
.section-heading,
.section-header,
.modal-header {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 14px;
}
.section-header {
align-items: center;
}
.section-heading--compact,
.section-header {
margin-bottom: 14px;
}
.section-heading h2,
.section-header h2,
.section-header h3,
.modal-header h2 {
font-size: 1.02rem;
line-height: 1.2;
letter-spacing: -0.02em;
}
.eyebrow {
font-size: 0.72rem;
font-weight: 700;
color: var(--primary);
letter-spacing: 0.12em;
text-transform: uppercase;
margin-bottom: 5px;
}
.sub {
color: var(--text-tertiary);
font-size: 0.9rem;
line-height: 1.45;
}
.btn {
width: 100%;
min-height: 44px;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
padding: 11px 15px;
border-radius: 13px;
cursor: pointer;
font-weight: 600;
font-size: 0.95rem;
transition: transform var(--transition), box-shadow var(--transition), background var(--transition), border-color var(--transition), color var(--transition), opacity var(--transition);
border: 1px solid transparent;
box-shadow: none;
}
.btn:hover:not(:disabled) {
transform: translateY(-1px);
}
.btn:active:not(:disabled) {
transform: translateY(0);
}
.btn:disabled,
.icon-button:disabled,
.type-btn:disabled,
.category-btn:disabled,
.icon-btn:disabled {
cursor: not-allowed;
opacity: 0.58;
}
.btn-inline {
width: auto;
min-width: -moz-fit-content;
min-width: fit-content;
}
.btn-small {
min-height: 34px;
padding: 7px 11px;
font-size: 0.84rem;
border-radius: 11px;
}
.btn-primary {
background: linear-gradient(135deg, var(--primary), #4d84f7);
color: #fff;
box-shadow: 0 8px 18px rgba(37, 99, 235, 0.14);
}
.btn-primary:hover:not(:disabled) {
background: linear-gradient(135deg, var(--primary-dark), var(--primary));
}
.btn-secondary {
background: rgba(255, 255, 255, 0.8);
color: var(--text);
border-color: rgba(148, 163, 184, 0.16);
}
.btn-secondary:hover:not(:disabled) {
border-color: rgba(37, 99, 235, 0.22);
background: rgba(255, 255, 255, 0.94);
}
.btn-ghost {
background: transparent;
color: var(--text-secondary);
border-color: rgba(148, 163, 184, 0.18);
}
.btn-ghost:hover:not(:disabled) {
color: var(--text);
background: rgba(148, 163, 184, 0.08);
}
.btn-danger {
background: linear-gradient(135deg, #ef4444, var(--expense));
color: #fff;
box-shadow: 0 10px 22px rgba(220, 38, 38, 0.14);
}
.button-content {
display: inline-flex;
align-items: center;
gap: 8px;
}
.icon-button {
width: 38px;
height: 38px;
border-radius: 12px;
display: inline-flex;
align-items: center;
justify-content: center;
border: 1px solid rgba(226, 232, 240, 0.95);
background: rgba(255, 255, 255, 0.92);
color: var(--text-secondary);
cursor: pointer;
transition: transform var(--transition), background var(--transition), border-color var(--transition), color var(--transition), box-shadow var(--transition);
box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04);
}
.icon-button:hover:not(:disabled) {
transform: translateY(-1px);
color: var(--text);
border-color: rgba(191, 219, 254, 1);
background: #ffffff;
box-shadow: 0 6px 16px rgba(15, 23, 42, 0.06);
}
.icon-button--ghost {
background: transparent;
box-shadow: none;
}
.month-switcher {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 5px 6px;
border-radius: 14px;
background: rgba(255, 255, 255, 0.94);
border: 1px solid rgba(226, 232, 240, 0.95);
}
.header-center-container {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
gap: 8px;
}
.header-left-actions,
.header-right-actions {
display: flex;
align-items: center;
min-width: 40px;
}
.header-spacer {
min-width: 40px;
}
.month-switcher-container {
display: flex;
align-items: center;
justify-content: center;
gap: 12px;
flex: 1 1;
}
.month-switcher-container .icon-button {
width: 36px;
height: 36px;
border-radius: 12px;
}
.month-switcher-container .month-switcher__label {
font-size: 1rem;
font-weight: 700;
min-width: 100px;
text-align: center;
}
.modal-title-container {
display: flex;
align-items: center;
justify-content: center;
flex: 1 1;
}
.modal-title {
font-size: 1.02rem;
font-weight: 700;
color: var(--text);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
letter-spacing: -0.03em;
}
.app-header--compact .modal-title {
font-size: 0.98rem;
}
.month-switcher--header {
min-width: 0;
padding: 3px;
gap: 3px;
border-radius: 14px;
background: rgba(255, 255, 255, 0.96);
box-shadow: 0 1px 2px rgba(15, 23, 42, 0.03);
justify-content: center;
}
.app-header--compact .month-switcher--header {
padding: 3px;
border-radius: 14px;
}
.month-switcher--header .icon-button {
width: 32px;
height: 32px;
border-radius: 10px;
}
.month-switcher--header .month-switcher__label {
min-width: 118px;
padding: 0 8px;
font-size: 0.84rem;
}
.month-switcher__label {
font-size: 0.9rem;
font-weight: 700;
color: var(--text);
min-width: 92px;
text-align: center;
white-space: nowrap;
}
.stats-grid {
display: grid;
grid-gap: 12px;
gap: 12px;
}
.stats-grid--hero {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.stats-grid--split {
grid-template-columns: minmax(0, 0.95fr) minmax(0, 1.05fr);
align-items: start;
}
.dashboard-hero {
display: grid;
grid-gap: 14px;
gap: 14px;
background: linear-gradient(160deg, rgba(255, 255, 255, 0.95), rgba(246, 249, 253, 0.92));
}
.stat-card,
.search-stat {
position: relative;
border-radius: 16px;
}
.stat-card {
display: grid;
grid-gap: 6px;
gap: 6px;
padding: 14px 13px;
text-align: left;
border: 1px solid rgba(148, 163, 184, 0.12);
background: rgba(255, 255, 255, 0.82);
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.45);
cursor: pointer;
transition: transform var(--transition), box-shadow var(--transition), border-color var(--transition), background var(--transition);
}
.stat-card:hover {
transform: translateY(-1px);
box-shadow: var(--shadow-xs);
}
.stat-card.active {
border-color: rgba(37, 99, 235, 0.3);
background: rgba(240, 246, 255, 0.96);
box-shadow: 0 10px 24px rgba(37, 99, 235, 0.08);
}
.stat-card--balance {
background: rgba(255, 255, 255, 0.82);
}
.label {
font-size: 0.74rem;
font-weight: 700;
color: var(--text-tertiary);
letter-spacing: 0.08em;
text-transform: uppercase;
}
.value,
.record-amount,
.bill-amount,
.category-amount,
.day-expense,
.search-stat .value {
font-family: var(--font-mono);
font-feature-settings: "tnum";
font-variant-numeric: tabular-nums;
}
.value {
font-size: 1.08rem;
font-weight: 700;
letter-spacing: -0.03em;
}
.value.income,
.record-amount.income,
.bill-amount.income,
.search-stat .value.income {
color: var(--income);
}
.value.expense,
.record-amount.expense,
.bill-amount.expense,
.search-stat .value.expense {
color: var(--expense);
}
.records-list,
.bills-list,
.category-breakdown {
display: grid;
grid-gap: 12px;
gap: 12px;
}
.records-list--flush {
gap: 16px;
}
.date-group {
display: grid;
grid-gap: 8px;
gap: 8px;
}
.date-label {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
padding: 0 2px;
color: var(--text-secondary);
font-size: 0.85rem;
font-weight: 600;
}
.day-expense {
color: var(--text-tertiary);
font-size: 0.78rem;
}
.record-item,
.bill-item,
.category-item,
.settings-item,
.category-tag {
border: 1px solid rgba(148, 163, 184, 0.14);
background: rgba(255, 255, 255, 0.76);
box-shadow: none;
}
.bill-item,
.settings-item {
position: relative;
}
.bill-item::after,
.settings-item::after {
content: '';
position: absolute;
inset: auto 14px 0;
height: 1px;
background: linear-gradient(90deg, rgba(148, 163, 184, 0), rgba(148, 163, 184, 0.22), rgba(148, 163, 184, 0));
opacity: 0;
transition: opacity var(--transition);
}
.record-item,
.bill-item {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 13px 14px;
border-radius: 16px;
cursor: pointer;
text-align: left;
transition: transform var(--transition), box-shadow var(--transition), border-color var(--transition), background var(--transition);
}
.record-item {
flex-wrap: nowrap;
}
.record-item:hover,
.bill-item:hover {
transform: translateY(-1px);
border-color: rgba(37, 99, 235, 0.14);
background: rgba(255, 255, 255, 0.94);
box-shadow: 0 6px 16px rgba(15, 23, 42, 0.05);
}
.bill-item:hover::after,
.settings-item:hover::after {
opacity: 1;
}
.record-item--static {
cursor: default;
}
.record-item--static:hover {
transform: none;
}
.record-left,
.bill-left {
display: flex;
align-items: center;
gap: 12px;
min-width: 0;
flex: 1 1;
}
.record-icon,
.bill-icon,
.auth-logo__mark,
.empty-state__icon,
.confirm-card__icon {
flex-shrink: 0;
display: inline-flex;
align-items: center;
justify-content: center;
}
.record-icon,
.bill-icon {
width: 40px;
height: 40px;
border-radius: 13px;
background: linear-gradient(135deg, rgba(237, 244, 255, 0.98), rgba(247, 250, 255, 0.92));
font-size: 1.2rem;
box-shadow: inset 0 0 0 1px rgba(148, 163, 184, 0.1);
}
.record-info,
.bill-info,
.settings-info,
.confirm-card__body {
min-width: 0;
display: grid;
grid-gap: 3px;
gap: 3px;
}
.record-info {
flex: 1 1;
}
.record-category,
.bill-category,
.settings-title,
.cat-name {
font-weight: 700;
color: var(--text);
}
.record-note,
.bill-day,
.bill-note,
.settings-desc,
.results-count,
.category-percent,
.about-info .sub,
.search-input-wrap__hint {
color: var(--text-tertiary);
font-size: 0.88rem;
line-height: 1.42;
}
.record-amount,
.bill-amount,
.category-amount {
font-size: 0.98rem;
font-weight: 700;
white-space: nowrap;
}
.record-amount {
flex-shrink: 0;
align-self: center;
}
.loading-state,
.empty-state {
display: grid;
place-items: center;
text-align: center;
grid-gap: 8px;
gap: 8px;
min-height: 148px;
color: var(--text-secondary);
}
.empty-state--card {
padding: 24px 16px;
}
.empty-state--inline {
min-height: 220px;
border-radius: 16px;
background: rgba(246, 248, 252, 0.92);
border: 1px dashed var(--border-strong);
}
.empty-state__icon {
width: 52px;
height: 52px;
border-radius: 16px;
background: rgba(240, 245, 255, 0.92);
color: var(--primary);
}
.fab {
position: fixed;
right: max(18px, env(safe-area-inset-right));
bottom: calc(var(--bottom-nav-height) + var(--safe-bottom) + 12px);
width: 56px;
height: 56px;
border-radius: 18px;
background: linear-gradient(135deg, var(--primary), #4f8dfc);
color: #fff;
display: inline-flex;
align-items: center;
justify-content: center;
box-shadow: 0 18px 34px rgba(37, 99, 235, 0.24);
z-index: 45;
cursor: pointer;
transition: transform var(--transition), box-shadow var(--transition);
}
.fab:hover {
transform: translateY(-2px) scale(1.01);
}
.modal-overlay {
position: fixed;
inset: 0;
display: flex;
align-items: flex-end;
justify-content: center;
padding: 18px 14px max(14px, env(safe-area-inset-bottom));
background: rgba(15, 23, 42, 0.44);
-webkit-backdrop-filter: blur(12px);
backdrop-filter: blur(12px);
z-index: 80;
}
.modal-overlay--fullscreen {
align-items: stretch;
padding: 0;
background: rgba(15, 23, 42, 0.32);
}
.modal-overlay--center {
align-items: center;
}
.modal,
.confirm-card {
width: min(100%, 560px);
background: var(--surface-strong);
border: 1px solid rgba(255, 255, 255, 0.62);
box-shadow: var(--shadow-md);
}
.modal {
border-radius: 24px 24px 20px 20px;
padding: 18px;
max-height: min(88vh, 920px);
overflow-y: auto;
}
.modal--fullscreen {
width: 100%;
max-width: none;
min-height: 100dvh;
max-height: 100dvh;
border-radius: 0;
border: 0;
padding: calc(var(--safe-top) + 8px) 16px calc(var(--safe-bottom) + 20px);
background: linear-gradient(180deg, rgba(249, 251, 255, 0.99), rgba(244, 246, 251, 0.98));
}
.modal--fullscreen .modal-header {
position: -webkit-sticky;
position: sticky;
top: calc(var(--safe-top) * -1 - 8px);
margin: calc(var(--safe-top) * -1 - 8px) -16px 14px;
padding: calc(var(--safe-top) + 8px) 16px 12px;
background: linear-gradient(180deg, rgba(249, 251, 255, 0.98), rgba(249, 251, 255, 0.92));
border-bottom: 1px solid rgba(148, 163, 184, 0.12);
-webkit-backdrop-filter: blur(18px);
backdrop-filter: blur(18px);
z-index: 2;
}
.modal--fullscreen form {
display: grid;
grid-gap: 2px;
gap: 2px;
width: 100%;
}
.confirm-card {
border-radius: 22px;
padding: 20px;
display: grid;
grid-gap: 16px;
gap: 16px;
}
.confirm-card__icon {
width: 46px;
height: 46px;
border-radius: 15px;
}
.confirm-card__icon--danger {
background: var(--expense-soft);
color: var(--expense);
}
.confirm-card__icon--info {
background: var(--primary-soft);
color: var(--primary);
}
.confirm-card__body h2 {
font-size: 1.04rem;
margin-bottom: 4px;
}
.confirm-card__body p {
color: var(--text-secondary);
line-height: 1.5;
}
.confirm-card__actions,
.modal-actions {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.modal-actions {
margin-top: 18px;
justify-content: flex-end;
}
.modal-actions .btn {
flex: 0 0 auto;
min-width: 112px;
}
.confirm-card__actions .btn {
flex: 1 1 180px;
}
.error-msg {
margin-bottom: 12px;
padding: 11px 13px;
border-radius: 13px;
border: 1px solid rgba(220, 38, 38, 0.14);
background: #fef2f2;
color: var(--expense);
font-size: 0.9rem;
}
.form-group {
display: grid;
grid-gap: 7px;
gap: 7px;
margin-bottom: 14px;
width: 100%;
}
.form-group--inline {
gap: 0;
}
.form-group label {
font-size: 0.86rem;
color: var(--text-secondary);
font-weight: 600;
}
.form-group input,
.form-group select,
.form-group textarea,
.search-input-wrap input {
width: 100%;
min-height: 46px;
padding: 11px 13px;
border-radius: 13px;
border: 1px solid rgba(148, 163, 184, 0.18);
background: rgba(255, 255, 255, 0.94);
color: var(--text);
transition: border-color var(--transition), box-shadow var(--transition), background var(--transition);
box-shadow: inset 0 1px 1px rgba(15, 23, 42, 0.02);
}
.form-group textarea {
min-height: 104px;
resize: vertical;
}
.form-group input:focus,
.form-group select:focus,
.form-group textarea:focus,
.search-input-wrap input:focus {
outline: none;
border-color: rgba(37, 99, 235, 0.34);
box-shadow: 0 0 0 4px rgba(37, 99, 235, 0.08);
}
.type-switch,
.period-tabs,
.category-type-tabs,
.auth-switch {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
grid-gap: 6px;
gap: 6px;
padding: 5px;
border-radius: 16px;
background: rgba(246, 248, 252, 0.92);
border: 1px solid rgba(148, 163, 184, 0.14);
}
.period-tabs {
grid-template-columns: repeat(3, minmax(0, 1fr));
margin-bottom: 14px;
}
.category-type-tabs {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.type-btn,
.period-tabs button,
.category-type-tabs button,
.auth-switch button {
min-height: 40px;
padding: 9px 12px;
border-radius: 12px;
font-weight: 700;
color: var(--text-secondary);
cursor: pointer;
transition: background var(--transition), color var(--transition), box-shadow var(--transition), transform var(--transition);
}
.type-btn.active,
.period-tabs button.active,
.category-type-tabs button.active,
.auth-switch .is-active {
background: #fff;
color: var(--text);
box-shadow: 0 8px 18px rgba(15, 23, 42, 0.06);
}
.type-btn.active.income {
color: var(--income);
background: rgba(5, 150, 105, 0.08);
}
.type-btn.active.expense {
color: var(--expense);
background: rgba(220, 38, 38, 0.08);
}
.amount-input {
text-align: center;
font-size: clamp(1.5rem, 4vw, 1.9rem) !important;
font-family: var(--font-mono);
font-weight: 700;
letter-spacing: -0.04em;
}
.category-grid,
.icon-grid {
display: grid;
grid-gap: 9px;
gap: 9px;
}
.category-grid {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
.category-grid .category-btn {
min-width: 0;
}
.icon-grid {
grid-template-columns: repeat(8, minmax(0, 1fr));
}
.icon-grid .icon-btn {
min-height: 48px;
font-size: 1.2rem;
}
.category-btn,
.icon-btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 6px;
min-height: 68px;
padding: 10px 8px;
border-radius: 14px;
border: 1px solid rgba(148, 163, 184, 0.16);
background: rgba(255, 255, 255, 0.86);
cursor: pointer;
transition: transform var(--transition), border-color var(--transition), box-shadow var(--transition), background var(--transition), color var(--transition);
}
.category-btn {
flex-direction: column;
font-size: 0.8rem;
color: var(--text-secondary);
}
.category-btn .icon {
font-size: 1.2rem;
}
.icon-btn {
min-height: 56px;
font-size: 1.3rem;
}
.category-btn:hover:not(:disabled),
.icon-btn:hover:not(:disabled) {
transform: translateY(-1px);
box-shadow: var(--shadow-xs);
}
.category-btn.active,
.icon-btn.active {
border-color: rgba(37, 99, 235, 0.28);
background: var(--primary-soft);
color: var(--primary);
}
.datetime-picker {
display: flex;
gap: 8px;
width: 100%;
box-sizing: border-box;
}
.datetime-picker input {
flex: 1 1;
min-width: 0;
box-sizing: border-box;
}
.search-form {
display: flex;
gap: 10px;
align-items: center;
}
.search-form--refresh {
padding: 4px;
border-radius: 16px;
background: rgba(246, 248, 252, 0.88);
border: 1px solid rgba(148, 163, 184, 0.12);
}
.search-form--refresh .search-input-wrap {
flex: 1 1;
}
.search-form--refresh .search-input-wrap input {
border-radius: 12px;
background: rgba(255, 255, 255, 0.94);
border-color: rgba(148, 163, 184, 0.12);
}
.search-form--refresh .btn {
min-width: 92px;
padding-inline: 18px;
}
.search-input-wrap {
position: relative;
flex: 1 1;
}
.search-input-wrap input {
padding-left: 42px;
}
.search-input-wrap__icon {
position: absolute;
left: 14px;
top: 50%;
transform: translateY(-50%);
color: var(--text-tertiary);
}
.search-summary {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
grid-gap: 10px;
gap: 10px;
}
.search-stat {
padding: 13px;
background: rgba(246, 248, 252, 0.82);
border: 1px solid rgba(148, 163, 184, 0.14);
display: grid;
grid-gap: 6px;
gap: 6px;
}
.section-card--search .results-count {
padding: 0 2px;
font-weight: 600;
}
.category-list {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.category-list--chips {
gap: 10px;
}
.category-tag {
display: inline-flex;
align-items: center;
gap: 9px;
min-height: 40px;
padding: 7px 10px;
border-radius: 14px;
}
.category-section--refresh .section-header .btn-inline {
min-height: 38px;
padding: 8px 12px;
border-radius: 12px;
}
.cat-icon {
width: 28px;
height: 28px;
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 9px;
background: rgba(246, 248, 252, 0.96);
}
.cat-delete {
width: 26px;
height: 26px;
margin-left: 2px;
}
.category-tip {
padding: 14px 16px;
color: var(--text-secondary);
text-align: center;
}
.bills-list {
gap: 10px;
}
.bill-item.inactive {
opacity: 0.72;
}
.bill-right {
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 8px;
flex-shrink: 0;
}
.bill-row {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
}
.bill-actions {
display: flex;
flex-wrap: wrap;
justify-content: flex-end;
gap: 7px;
}
.status-chip {
display: inline-flex;
align-items: center;
min-height: 26px;
padding: 0 9px;
border-radius: 999px;
background: var(--warning-soft);
color: var(--warning);
font-size: 0.75rem;
font-weight: 700;
}
.status-chip--success {
background: var(--income-soft);
color: var(--income);
}
.settings-section {
display: grid;
grid-gap: 8px;
gap: 8px;
}
.settings-section--refresh {
padding: 10px;
background: linear-gradient(180deg, rgba(255, 255, 255, 0.9), rgba(247, 249, 252, 0.82));
border-color: rgba(148, 163, 184, 0.12);
box-shadow: 0 8px 22px rgba(15, 23, 42, 0.04);
}
.settings-section--refresh .section-heading {
margin-bottom: 0;
padding: 4px 4px 2px;
}
.settings-section--refresh .section-heading h2 {
font-size: 0.98rem;
}
.settings-item {
display: flex;
align-items: center;
justify-content: space-between;
gap: 14px;
padding: 14px 15px;
border-radius: 18px;
background: rgba(255, 255, 255, 0.92);
border-color: rgba(148, 163, 184, 0.12);
}
.settings-item--stacked {
align-items: flex-end;
}
.settings-item--stacked .settings-info {
width: 100%;
}
.settings-item .btn-inline {
flex-shrink: 0;
min-height: 38px;
padding: 8px 13px;
border-radius: 12px;
}
.settings-title {
font-size: 0.95rem;
}
.settings-desc {
font-size: 0.84rem;
line-height: 1.5;
}
.about-info {
display: grid;
grid-gap: 6px;
gap: 6px;
padding: 6px 6px 2px;
color: var(--text-secondary);
}
.about-info p:first-child {
color: var(--text);
font-weight: 700;
}
.auth-layout {
min-height: calc(100vh - var(--header-height) - var(--safe-top));
}
.auth-page {
min-height: calc(100vh - var(--header-height) - var(--safe-top));
display: grid;
place-items: center;
padding: 20px 0 36px;
}
.auth-card {
width: min(100%, 420px);
background: linear-gradient(180deg, rgba(255, 255, 255, 0.97), rgba(247, 249, 253, 0.95));
}
.auth-card--refresh {
padding: 20px;
border-radius: 28px;
border-color: rgba(255, 255, 255, 0.72);
box-shadow: 0 20px 40px rgba(15, 23, 42, 0.07);
}
.auth-logo {
display: grid;
justify-items: center;
grid-gap: 9px;
gap: 9px;
text-align: center;
margin-bottom: 20px;
}
.auth-logo--refresh {
margin-bottom: 16px;
}
.auth-logo__mark {
width: 56px;
height: 56px;
border-radius: 18px;
color: #fff;
background: linear-gradient(135deg, var(--primary), #60a5fa);
box-shadow: 0 14px 30px rgba(37, 99, 235, 0.2);
}
.auth-logo h1 {
font-size: 1.58rem;
letter-spacing: -0.04em;
}
.auth-logo p {
color: var(--text-secondary);
max-width: 28ch;
line-height: 1.5;
font-size: 0.92rem;
}
.auth-form {
display: grid;
grid-gap: 4px;
gap: 4px;
}
.auth-form .btn-primary {
margin-top: 4px;
}
.chart-container {
position: relative;
min-height: 248px;
border-radius: 20px;
background: linear-gradient(180deg, rgba(250, 251, 253, 0.92), rgba(255, 255, 255, 0.98));
border: 1px solid rgba(148, 163, 184, 0.12);
padding: 14px;
}
.period-tabs,
.category-type-tabs {
margin-bottom: 14px;
}
.section-card .chart-container {
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.78);
}
.chart-container--large {
min-height: 304px;
}
.chart-container--small {
min-height: 264px;
}
.chart-container--stats {
background: linear-gradient(180deg, rgba(248, 250, 253, 0.92), rgba(255, 255, 255, 0.98));
border-color: rgba(148, 163, 184, 0.1);
}
.chart-container--stats-donut {
padding: 18px;
}
.section-card--stats {
padding: 14px;
background: linear-gradient(180deg, rgba(255, 255, 255, 0.93), rgba(247, 249, 252, 0.9));
}
.stats-grid--stats {
gap: 12px;
}
.category-breakdown {
align-content: start;
}
.category-breakdown--stats {
gap: 10px;
}
.category-item {
display: grid;
grid-template-columns: minmax(72px, auto) minmax(0, 1fr) auto;
align-items: center;
grid-gap: 10px;
gap: 10px;
padding: 12px 14px;
border-radius: 18px;
}
.category-item--stats {
display: grid;
grid-template-rows: auto auto;
grid-gap: 8px;
gap: 8px;
padding: 12px 14px;
}
.category-item-row {
display: flex;
justify-content: space-between;
align-items: center;
}
.category-item--stats .category-amount {
font-family: var(--font-mono);
font-feature-settings: "tnum";
font-variant-numeric: tabular-nums;
font-size: 0.98rem;
font-weight: 700;
}
.category-item .category-name {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.category-info {
display: grid;
grid-gap: 3px;
gap: 3px;
}
.category-name {
font-weight: 700;
}
.category-bar-wrap {
width: 100%;
height: 8px;
border-radius: 999px;
background: #e2e8f0;
overflow: hidden;
}
.category-bar {
height: 100%;
border-radius: inherit;
background: linear-gradient(90deg, var(--primary), #60a5fa);
}
.toast-stack {
position: fixed;
left: 50%;
bottom: calc(var(--bottom-nav-height) + var(--safe-bottom) + 14px);
transform: translateX(-50%);
width: min(calc(100% - 28px), 480px);
display: grid;
grid-gap: 8px;
gap: 8px;
z-index: 90;
}
.toast {
display: grid;
grid-template-columns: auto 1fr auto;
align-items: center;
grid-gap: 10px;
gap: 10px;
padding: 12px 13px;
border-radius: 15px;
background: var(--surface-elevated);
color: var(--text-on-dark);
box-shadow: 0 16px 34px rgba(15, 23, 42, 0.14);
-webkit-backdrop-filter: blur(18px);
backdrop-filter: blur(18px);
}
.toast__icon,
.toast__close {
color: inherit;
}
.toast__message {
line-height: 1.42;
}
.toast__close.icon-button {
width: 30px;
height: 30px;
border-radius: 10px;
}
.toast--success {
background: linear-gradient(135deg, rgba(5, 150, 105, 0.96), rgba(6, 182, 212, 0.9));
}
.toast--error {
background: linear-gradient(135deg, rgba(220, 38, 38, 0.96), rgba(244, 63, 94, 0.92));
}
.toast--info {
background: linear-gradient(135deg, rgba(15, 23, 42, 0.94), rgba(51, 65, 85, 0.94));
}
.bottom-nav {
position: fixed;
left: 0;
right: 0;
bottom: 0;
z-index: 35;
padding: 0 0 var(--safe-bottom);
background: linear-gradient(180deg, rgba(244, 246, 251, 0), rgba(244, 246, 251, 0.92) 24%, rgba(244, 246, 251, 0.98) 100%);
}
.bottom-nav__inner {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
grid-gap: 6px;
gap: 6px;
padding: 10px 10px 11px;
border-radius: 22px 22px 0 0;
background: rgba(255, 255, 255, 0.84);
border: 1px solid rgba(255, 255, 255, 0.66);
box-shadow: 0 -12px 30px rgba(15, 23, 42, 0.06);
-webkit-backdrop-filter: blur(24px);
backdrop-filter: blur(24px);
}
.bottom-nav__item {
min-height: 54px;
display: grid;
justify-items: center;
align-content: center;
grid-gap: 4px;
gap: 4px;
padding: 7px 4px;
border-radius: 16px;
color: var(--text-tertiary);
cursor: pointer;
transition: background var(--transition), color var(--transition), transform var(--transition);
}
.bottom-nav__item span {
font-size: 0.74rem;
font-weight: 700;
}
.bottom-nav__item.is-active {
background: var(--primary-soft);
color: var(--primary);
}
.checkbox-label {
display: inline-flex;
align-items: center;
gap: 10px;
color: var(--text-secondary);
}
.checkbox-label input {
width: 18px;
height: 18px;
}
.is-spinning {
animation: spin 0.9s linear infinite;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
@media (max-width: 1024px) {
.stats-grid--split {
grid-template-columns: 1fr;
}
}
@media (max-width: 768px) {
:root {
--header-height: 42px;
--bottom-nav-height: 82px;
}
.app-shell__container {
width: min(100% - 24px, var(--shell-width));
}
.app-header__inner,
.app-header--compact .app-header__inner {
min-height: auto;
align-items: center;
gap: 8px;
padding: 8px 0;
}
.app-header__text h1 {
font-size: 1rem;
}
.app-header__actions {
gap: 6px;
max-width: none;
flex-wrap: nowrap;
justify-content: flex-end;
}
.month-switcher--header {
min-width: 0;
padding: 3px;
}
.month-switcher--header .month-switcher__label {
min-width: 108px;
font-size: 0.82rem;
}
.month-switcher--header .icon-button,
.icon-button {
width: 38px;
height: 38px;
}
.section-card,
.category-section,
.settings-section,
.dashboard-hero,
.auth-card,
.modal,
.confirm-card {
padding: 16px;
}
.section-card--stats,
.section-card--search-panel {
padding: 12px;
}
.modal--fullscreen {
padding: calc(var(--safe-top) + 6px) 14px calc(var(--safe-bottom) + 18px);
}
.modal--fullscreen .modal-header {
top: calc(var(--safe-top) * -1 - 6px);
margin: calc(var(--safe-top) * -1 - 6px) -14px 12px;
padding: calc(var(--safe-top) + 6px) 14px 10px;
}
.stats-grid--hero,
.search-summary,
.category-grid {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.icon-grid {
grid-template-columns: repeat(6, minmax(0, 1fr));
}
.icon-grid .icon-btn {
min-height: 44px;
font-size: 1.1rem;
}
.search-form,
.settings-item,
.settings-item--stacked {
flex-direction: column;
align-items: stretch;
}
.search-form--refresh .btn {
width: 100%;
}
.record-amount {
align-self: center;
}
.bill-right,
.settings-item--stacked {
width: auto;
}
.bill-actions {
width: auto;
justify-content: flex-end;
}
.bill-actions .btn {
flex: 0 0 auto;
}
.category-item {
grid-template-columns: minmax(72px, auto) minmax(0, 1fr) auto;
}
.toast-stack {
bottom: calc(var(--bottom-nav-height) + var(--safe-bottom) + 10px);
}
}
@media (max-width: 560px) {
.app-header__inner {
flex-direction: row;
align-items: center;
}
.app-header__main,
.app-header__text {
min-width: 0;
flex: 1 1;
}
.app-header__text {
gap: 2px;
}
.app-header__actions {
width: auto;
flex: 1 1;
justify-content: flex-end;
gap: 6px;
}
.month-switcher,
.month-switcher--header {
flex: 1 1;
justify-content: center;
}
.month-switcher--header .month-switcher__label {
min-width: 96px;
flex: 1 1;
}
.search-summary,
.icon-grid,
.auth-switch {
grid-template-columns: 1fr;
}
.record-item,
.bill-item,
.settings-item,
.category-item {
padding: 13px;
}
.stats-grid--hero {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.category-grid {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
.period-tabs {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.category-type-tabs {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.record-left,
.bill-left {
gap: 10px;
}
.record-left,
.record-item,
.bill-item {
min-width: 0;
}
.record-item,
.bill-item {
gap: 10px;
}
.record-icon,
.bill-icon,
.empty-state__icon {
width: 40px;
height: 40px;
border-radius: 13px;
}
.datetime-picker {
gap: 6px;
}
.modal-actions,
.confirm-card__actions {
flex-direction: column;
}
.auth-page {
padding: 12px 0 24px;
}
.auth-card--refresh {
padding: 18px 16px;
border-radius: 24px;
}
.auth-logo {
gap: 8px;
margin-bottom: 16px;
}
.auth-logo h1 {
font-size: 1.46rem;
}
.category-btn,
.icon-btn {
min-height: 54px;
}
.toast {
grid-template-columns: auto 1fr;
}
.toast__close {
grid-column: 2;
justify-self: end;
}
.bottom-nav__inner {
gap: 6px;
padding: 10px;
}
.bottom-nav__item {
min-height: 54px;
}
}

¿Ò:root {
--bg: #f4f6fb;
--bg-accent: radial-gradient(circle at top, rgba(59, 130, 246, 0.12), transparent 30%), linear-gradient(180deg, #f9fbff 0%, #f2f5fa 100%);
--surface: rgba(255, 255, 255, 0.9);
--surface-strong: rgba(255, 255, 255, 0.98);
--surface-muted: #eef3fb;
--surface-soft: #f6f8fc;
--surface-elevated: rgba(15, 23, 42, 0.86);
--text: #0f172a;
--text-secondary: #475569;
--text-tertiary: #64748b;
--text-on-dark: #f8fafc;
--primary: #2563eb;
--primary-dark: #1d4ed8;
--primary-soft: rgba(37, 99, 235, 0.1);
--income: #059669;
--income-soft: rgba(5, 150, 105, 0.12);
--expense: #dc2626;
--expense-soft: rgba(220, 38, 38, 0.1);
--warning: #d97706;
--warning-soft: rgba(217, 119, 6, 0.12);
--border: rgba(148, 163, 184, 0.2);
--border-strong: rgba(100, 116, 139, 0.28);
--shadow-xs: 0 1px 3px rgba(15, 23, 42, 0.05);
--shadow-sm: 0 10px 28px rgba(15, 23, 42, 0.06);
--shadow-md: 0 18px 44px rgba(15, 23, 42, 0.1);
--radius-xs: 10px;
--radius-sm: 14px;
--radius: 18px;
--radius-lg: 24px;
--space-1: 4px;
--space-2: 8px;
--space-3: 12px;
--space-4: 16px;
--space-5: 20px;
--space-6: 24px;
--space-7: 28px;
--space-8: 32px;
--shell-width: 760px;
--header-height: 44px;
--bottom-nav-height: 84px;
--safe-bottom: max(24px, env(safe-area-inset-bottom));
--safe-top: max(20px, env(safe-area-inset-top));
--transition: 180ms ease;
--font-sans: "Inter", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
--font-mono: "SF Mono", "JetBrains Mono", Monaco, monospace;
color-scheme: light;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html,
body,
#__next {
min-height: 100%;
}
body {
font-family: var(--font-sans);
background: var(--bg-accent);
color: var(--text);
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
}
a,
button,
input,
select,
textarea {
font: inherit;
}
button,
input,
select,
textarea {
border: 0;
}
button {
background: none;
color: inherit;
}
button:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible {
outline: 3px solid rgba(37, 99, 235, 0.18);
outline-offset: 2px;
}
.app-shell {
min-height: 100vh;
padding-bottom: calc(var(--bottom-nav-height) + var(--safe-bottom) + var(--space-3));
}
.app-shell--standalone {
padding-bottom: 0;
}
.app-shell__container {
width: min(100% - 28px, var(--shell-width));
margin: 0 auto;
}
.app-header {
position: -webkit-sticky;
position: sticky;
top: 0;
z-index: 40;
padding-top: var(--safe-top);
-webkit-backdrop-filter: blur(20px);
backdrop-filter: blur(20px);
background: rgba(249, 251, 255, 0.9);
border-bottom: 1px solid rgba(226, 232, 240, 0.9);
}
.app-header::after {
content: none;
}
.app-header--compact {
background: rgba(249, 251, 255, 0.86);
}
.app-header__inner {
position: relative;
min-height: 44px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 4px 0 4px;
}
.app-header--compact .app-header__inner {
min-height: 40px;
gap: 10px;
padding: 2px 0;
}
.app-header__main,
.app-header__spacer {
min-width: 0;
flex: 1 1;
}
.app-header__main {
display: flex;
align-items: center;
flex: 1 1;
}
.app-header--actions-only .app-header__main {
display: none;
}
.app-header__text {
min-width: 0;
display: grid;
grid-gap: 2px;
gap: 2px;
}
.app-header__text h1 {
font-size: 1.02rem;
line-height: 1.15;
letter-spacing: -0.03em;
font-weight: 700;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.app-header--compact .app-header__text h1 {
font-size: 0.98rem;
}
.app-header__text p {
color: var(--text-secondary);
font-size: 0.76rem;
line-height: 1.3;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.app-header--compact .app-header__text p {
font-size: 0.74rem;
}
.app-header__actions {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 8px;
flex-wrap: nowrap;
min-width: 0;
margin-left: auto;
flex-shrink: 0;
}
.app-header--compact .app-header__actions {
gap: 6px;
}
.app-content {
padding: 10px 0 0;
}
.app-content__inner {
display: grid;
grid-gap: 14px;
gap: 14px;
align-content: start;
}
.card {
position: relative;
background: var(--surface);
border: 1px solid rgba(148, 163, 184, 0.18);
border-radius: var(--radius);
box-shadow: 0 10px 26px rgba(15, 23, 42, 0.05);
-webkit-backdrop-filter: blur(18px);
backdrop-filter: blur(18px);
}
.section-card,
.category-section,
.settings-section,
.category-tip,
.dashboard-hero,
.auth-card,
.confirm-card {
overflow: hidden;
}
.section-card,
.category-section,
.settings-section,
.dashboard-hero,
.auth-card {
padding: 16px;
}
.section-card--search {
display: grid;
grid-gap: 14px;
gap: 14px;
}
.section-card--search-panel {
padding: 12px;
gap: 12px;
background: linear-gradient(180deg, rgba(255, 255, 255, 0.94), rgba(247, 249, 253, 0.9));
}
.section-heading,
.section-header,
.modal-header {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 14px;
}
.section-header {
align-items: center;
}
.section-heading--compact,
.section-header {
margin-bottom: 14px;
}
.section-heading h2,
.section-header h2,
.section-header h3,
.modal-header h2 {
font-size: 1.02rem;
line-height: 1.2;
letter-spacing: -0.02em;
}
.eyebrow {
font-size: 0.72rem;
font-weight: 700;
color: var(--primary);
letter-spacing: 0.12em;
text-transform: uppercase;
margin-bottom: 5px;
}
.sub {
color: var(--text-tertiary);
font-size: 0.9rem;
line-height: 1.45;
}
.btn {
width: 100%;
min-height: 44px;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
padding: 11px 15px;
border-radius: 13px;
cursor: pointer;
font-weight: 600;
font-size: 0.95rem;
transition: transform var(--transition), box-shadow var(--transition), background var(--transition), border-color var(--transition), color var(--transition), opacity var(--transition);
border: 1px solid transparent;
box-shadow: none;
}
.btn:hover:not(:disabled) {
transform: translateY(-1px);
}
.btn:active:not(:disabled) {
transform: translateY(0);
}
.btn:disabled,
.icon-button:disabled,
.type-btn:disabled,
.category-btn:disabled,
.icon-btn:disabled {
cursor: not-allowed;
opacity: 0.58;
}
.btn-inline {
width: auto;
min-width: -moz-fit-content;
min-width: fit-content;
}
.btn-small {
min-height: 34px;
padding: 7px 11px;
font-size: 0.84rem;
border-radius: 11px;
}
.btn-primary {
background: linear-gradient(135deg, var(--primary), #4d84f7);
color: #fff;
box-shadow: 0 8px 18px rgba(37, 99, 235, 0.14);
}
.btn-primary:hover:not(:disabled) {
background: linear-gradient(135deg, var(--primary-dark), var(--primary));
}
.btn-secondary {
background: rgba(255, 255, 255, 0.8);
color: var(--text);
border-color: rgba(148, 163, 184, 0.16);
}
.btn-secondary:hover:not(:disabled) {
border-color: rgba(37, 99, 235, 0.22);
background: rgba(255, 255, 255, 0.94);
}
.btn-ghost {
background: transparent;
color: var(--text-secondary);
border-color: rgba(148, 163, 184, 0.18);
}
.btn-ghost:hover:not(:disabled) {
color: var(--text);
background: rgba(148, 163, 184, 0.08);
}
.btn-danger {
background: linear-gradient(135deg, #ef4444, var(--expense));
color: #fff;
box-shadow: 0 10px 22px rgba(220, 38, 38, 0.14);
}
.button-content {
display: inline-flex;
align-items: center;
gap: 8px;
}
.icon-button {
width: 38px;
height: 38px;
border-radius: 12px;
display: inline-flex;
align-items: center;
justify-content: center;
border: 1px solid rgba(226, 232, 240, 0.95);
background: rgba(255, 255, 255, 0.92);
color: var(--text-secondary);
cursor: pointer;
transition: transform var(--transition), background var(--transition), border-color var(--transition), color var(--transition), box-shadow var(--transition);
box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04);
}
.icon-button:hover:not(:disabled) {
transform: translateY(-1px);
color: var(--text);
border-color: rgba(191, 219, 254, 1);
background: #ffffff;
box-shadow: 0 6px 16px rgba(15, 23, 42, 0.06);
}
.icon-button--ghost {
background: transparent;
box-shadow: none;
}
.month-switcher {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 5px 6px;
border-radius: 14px;
background: rgba(255, 255, 255, 0.94);
border: 1px solid rgba(226, 232, 240, 0.95);
}
.header-center-container {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
gap: 8px;
}
.header-left-actions,
.header-right-actions {
display: flex;
align-items: center;
min-width: 40px;
}
.header-spacer {
min-width: 40px;
}
.month-switcher-container {
display: flex;
align-items: center;
justify-content: center;
gap: 12px;
flex: 1 1;
}
.month-switcher-container .icon-button {
width: 36px;
height: 36px;
border-radius: 12px;
}
.month-switcher-container .month-switcher__label {
font-size: 1rem;
font-weight: 700;
min-width: 100px;
text-align: center;
}
.modal-title-container {
display: flex;
align-items: center;
justify-content: center;
flex: 1 1;
}
.modal-title {
font-size: 1.02rem;
font-weight: 700;
color: var(--text);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
letter-spacing: -0.03em;
}
.app-header--compact .modal-title {
font-size: 0.98rem;
}
.month-switcher--header {
min-width: 0;
padding: 3px;
gap: 3px;
border-radius: 14px;
background: rgba(255, 255, 255, 0.96);
box-shadow: 0 1px 2px rgba(15, 23, 42, 0.03);
justify-content: center;
}
.app-header--compact .month-switcher--header {
padding: 3px;
border-radius: 14px;
}
.month-switcher--header .icon-button {
width: 32px;
height: 32px;
border-radius: 10px;
}
.month-switcher--header .month-switcher__label {
min-width: 118px;
padding: 0 8px;
font-size: 0.84rem;
}
.month-switcher__label {
font-size: 0.9rem;
font-weight: 700;
color: var(--text);
min-width: 92px;
text-align: center;
white-space: nowrap;
}
.stats-grid {
display: grid;
grid-gap: 12px;
gap: 12px;
}
.stats-grid--hero {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.stats-grid--split {
grid-template-columns: minmax(0, 0.95fr) minmax(0, 1.05fr);
align-items: start;
}
.dashboard-hero {
display: grid;
grid-gap: 14px;
gap: 14px;
background: linear-gradient(160deg, rgba(255, 255, 255, 0.95), rgba(246, 249, 253, 0.92));
}
.stat-card,
.search-stat {
position: relative;
border-radius: 16px;
}
.stat-card {
display: grid;
grid-gap: 6px;
gap: 6px;
padding: 14px 13px;
text-align: left;
border: 1px solid rgba(148, 163, 184, 0.12);
background: rgba(255, 255, 255, 0.82);
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.45);
cursor: pointer;
transition: transform var(--transition), box-shadow var(--transition), border-color var(--transition), background var(--transition);
}
.stat-card:hover {
transform: translateY(-1px);
box-shadow: var(--shadow-xs);
}
.stat-card.active {
border-color: rgba(37, 99, 235, 0.3);
background: rgba(240, 246, 255, 0.96);
box-shadow: 0 10px 24px rgba(37, 99, 235, 0.08);
}
.stat-card--balance {
background: rgba(255, 255, 255, 0.82);
}
.label {
font-size: 0.74rem;
font-weight: 700;
color: var(--text-tertiary);
letter-spacing: 0.08em;
text-transform: uppercase;
}
.value,
.record-amount,
.bill-amount,
.category-amount,
.day-expense,
.search-stat .value {
font-family: var(--font-mono);
font-feature-settings: "tnum";
font-variant-numeric: tabular-nums;
}
.value {
font-size: 1.08rem;
font-weight: 700;
letter-spacing: -0.03em;
}
.value.income,
.record-amount.income,
.bill-amount.income,
.search-stat .value.income {
color: var(--income);
}
.value.expense,
.record-amount.expense,
.bill-amount.expense,
.search-stat .value.expense {
color: var(--expense);
}
.records-list,
.bills-list,
.category-breakdown {
display: grid;
grid-gap: 12px;
gap: 12px;
}
.records-list--flush {
gap: 16px;
}
.date-group {
display: grid;
grid-gap: 8px;
gap: 8px;
}
.date-label {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
padding: 0 2px;
color: var(--text-secondary);
font-size: 0.85rem;
font-weight: 600;
}
.day-expense {
color: var(--text-tertiary);
font-size: 0.78rem;
}
.record-item,
.bill-item,
.category-item,
.settings-item,
.category-tag {
border: 1px solid rgba(148, 163, 184, 0.14);
background: rgba(255, 255, 255, 0.76);
box-shadow: none;
}
.bill-item,
.settings-item {
position: relative;
}
.bill-item::after,
.settings-item::after {
content: '';
position: absolute;
inset: auto 14px 0;
height: 1px;
background: linear-gradient(90deg, rgba(148, 163, 184, 0), rgba(148, 163, 184, 0.22), rgba(148, 163, 184, 0));
opacity: 0;
transition: opacity var(--transition);
}
.record-item,
.bill-item {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 13px 14px;
border-radius: 16px;
cursor: pointer;
text-align: left;
transition: transform var(--transition), box-shadow var(--transition), border-color var(--transition), background var(--transition);
}
.record-item {
flex-wrap: nowrap;
}
.record-item:hover,
.bill-item:hover {
transform: translateY(-1px);
border-color: rgba(37, 99, 235, 0.14);
background: rgba(255, 255, 255, 0.94);
box-shadow: 0 6px 16px rgba(15, 23, 42, 0.05);
}
.bill-item:hover::after,
.settings-item:hover::after {
opacity: 1;
}
.record-item--static {
cursor: default;
}
.record-item--static:hover {
transform: none;
}
.record-left,
.bill-left {
display: flex;
align-items: center;
gap: 12px;
min-width: 0;
flex: 1 1;
}
.record-icon,
.bill-icon,
.auth-logo__mark,
.empty-state__icon,
.confirm-card__icon {
flex-shrink: 0;
display: inline-flex;
align-items: center;
justify-content: center;
}
.record-icon,
.bill-icon {
width: 40px;
height: 40px;
border-radius: 13px;
background: linear-gradient(135deg, rgba(237, 244, 255, 0.98), rgba(247, 250, 255, 0.92));
font-size: 1.2rem;
box-shadow: inset 0 0 0 1px rgba(148, 163, 184, 0.1);
}
.record-info,
.bill-info,
.settings-info,
.confirm-card__body {
min-width: 0;
display: grid;
grid-gap: 3px;
gap: 3px;
}
.record-info {
flex: 1 1;
}
.record-category,
.bill-category,
.settings-title,
.cat-name {
font-weight: 700;
color: var(--text);
}
.record-note,
.bill-day,
.bill-note,
.settings-desc,
.results-count,
.category-percent,
.about-info .sub,
.search-input-wrap__hint {
color: var(--text-tertiary);
font-size: 0.88rem;
line-height: 1.42;
}
.record-amount,
.bill-amount,
.category-amount {
font-size: 0.98rem;
font-weight: 700;
white-space: nowrap;
}
.record-amount {
flex-shrink: 0;
align-self: center;
}
.loading-state,
.empty-state {
display: grid;
place-items: center;
text-align: center;
grid-gap: 8px;
gap: 8px;
min-height: 148px;
color: var(--text-secondary);
}
.empty-state--card {
padding: 24px 16px;
}
.empty-state--inline {
min-height: 220px;
border-radius: 16px;
background: rgba(246, 248, 252, 0.92);
border: 1px dashed var(--border-strong);
}
.empty-state__icon {
width: 52px;
height: 52px;
border-radius: 16px;
background: rgba(240, 245, 255, 0.92);
color: var(--primary);
}
.fab {
position: fixed;
right: max(18px, env(safe-area-inset-right));
bottom: calc(var(--bottom-nav-height) + var(--safe-bottom) + 12px);
width: 56px;
height: 56px;
border-radius: 18px;
background: linear-gradient(135deg, var(--primary), #4f8dfc);
color: #fff;
display: inline-flex;
align-items: center;
justify-content: center;
box-shadow: 0 18px 34px rgba(37, 99, 235, 0.24);
z-index: 45;
cursor: pointer;
transition: transform var(--transition), box-shadow var(--transition);
}
.fab:hover {
transform: translateY(-2px) scale(1.01);
}
.modal-overlay {
position: fixed;
inset: 0;
display: flex;
align-items: flex-end;
justify-content: center;
padding: 18px 14px max(14px, env(safe-area-inset-bottom));
background: rgba(15, 23, 42, 0.44);
-webkit-backdrop-filter: blur(12px);
backdrop-filter: blur(12px);
z-index: 80;
}
.modal-overlay--fullscreen {
align-items: stretch;
padding: 0;
background: rgba(15, 23, 42, 0.32);
}
.modal-overlay--center {
align-items: center;
}
.modal,
.confirm-card {
width: min(100%, 560px);
background: var(--surface-strong);
border: 1px solid rgba(255, 255, 255, 0.62);
box-shadow: var(--shadow-md);
}
.modal {
border-radius: 24px 24px 20px 20px;
padding: 18px;
max-height: min(88vh, 920px);
overflow-y: auto;
}
.modal--fullscreen {
width: 100%;
max-width: none;
min-height: 100dvh;
max-height: 100dvh;
border-radius: 0;
border: 0;
padding: calc(var(--safe-top) + 8px) 16px calc(var(--safe-bottom) + 20px);
background: linear-gradient(180deg, rgba(249, 251, 255, 0.99), rgba(244, 246, 251, 0.98));
}
.modal--fullscreen .modal-header {
position: -webkit-sticky;
position: sticky;
top: calc(var(--safe-top) * -1 - 8px);
margin: calc(var(--safe-top) * -1 - 8px) -16px 14px;
padding: calc(var(--safe-top) + 8px) 16px 12px;
background: linear-gradient(180deg, rgba(249, 251, 255, 0.98), rgba(249, 251, 255, 0.92));
border-bottom: 1px solid rgba(148, 163, 184, 0.12);
-webkit-backdrop-filter: blur(18px);
backdrop-filter: blur(18px);
z-index: 2;
}
.modal--fullscreen form {
display: grid;
grid-gap: 2px;
gap: 2px;
width: 100%;
}
.confirm-card {
border-radius: 22px;
padding: 20px;
display: grid;
grid-gap: 16px;
gap: 16px;
}
.confirm-card__icon {
width: 46px;
height: 46px;
border-radius: 15px;
}
.confirm-card__icon--danger {
background: var(--expense-soft);
color: var(--expense);
}
.confirm-card__icon--info {
background: var(--primary-soft);
color: var(--primary);
}
.confirm-card__body h2 {
font-size: 1.04rem;
margin-bottom: 4px;
}
.confirm-card__body p {
color: var(--text-secondary);
line-height: 1.5;
}
.confirm-card__actions,
.modal-actions {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.modal-actions {
margin-top: 18px;
justify-content: flex-end;
}
.modal-actions .btn {
flex: 0 0 auto;
min-width: 112px;
}
.confirm-card__actions .btn {
flex: 1 1 180px;
}
.error-msg {
margin-bottom: 12px;
padding: 11px 13px;
border-radius: 13px;
border: 1px solid rgba(220, 38, 38, 0.14);
background: #fef2f2;
color: var(--expense);
font-size: 0.9rem;
}
.form-group {
display: grid;
grid-gap: 7px;
gap: 7px;
margin-bottom: 14px;
width: 100%;
}
.form-group--inline {
gap: 0;
}
.form-group label {
font-size: 0.86rem;
color: var(--text-secondary);
font-weight: 600;
}
.form-group input,
.form-group select,
.form-group textarea,
.search-input-wrap input {
width: 100%;
min-height: 46px;
padding: 11px 13px;
border-radius: 13px;
border: 1px solid rgba(148, 163, 184, 0.18);
background: rgba(255, 255, 255, 0.94);
color: var(--text);
transition: border-color var(--transition), box-shadow var(--transition), background var(--transition);
box-shadow: inset 0 1px 1px rgba(15, 23, 42, 0.02);
}
.form-group textarea {
min-height: 104px;
resize: vertical;
}
.form-group input:focus,
.form-group select:focus,
.form-group textarea:focus,
.search-input-wrap input:focus {
outline: none;
border-color: rgba(37, 99, 235, 0.34);
box-shadow: 0 0 0 4px rgba(37, 99, 235, 0.08);
}
.type-switch,
.period-tabs,
.category-type-tabs,
.auth-switch {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
grid-gap: 6px;
gap: 6px;
padding: 5px;
border-radius: 16px;
background: rgba(246, 248, 252, 0.92);
border: 1px solid rgba(148, 163, 184, 0.14);
}
.period-tabs {
grid-template-columns: repeat(3, minmax(0, 1fr));
margin-bottom: 14px;
}
.category-type-tabs {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.type-btn,
.period-tabs button,
.category-type-tabs button,
.auth-switch button {
min-height: 40px;
padding: 9px 12px;
border-radius: 12px;
font-weight: 700;
color: var(--text-secondary);
cursor: pointer;
transition: background var(--transition), color var(--transition), box-shadow var(--transition), transform var(--transition);
}
.type-btn.active,
.period-tabs button.active,
.category-type-tabs button.active,
.auth-switch .is-active {
background: #fff;
color: var(--text);
box-shadow: 0 8px 18px rgba(15, 23, 42, 0.06);
}
.type-btn.active.income {
color: var(--income);
background: rgba(5, 150, 105, 0.08);
}
.type-btn.active.expense {
color: var(--expense);
background: rgba(220, 38, 38, 0.08);
}
.amount-input {
text-align: center;
font-size: clamp(1.5rem, 4vw, 1.9rem) !important;
font-family: var(--font-mono);
font-weight: 700;
letter-spacing: -0.04em;
}
.category-grid,
.icon-grid {
display: grid;
grid-gap: 9px;
gap: 9px;
}
.category-grid {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
.category-grid .category-btn {
min-width: 0;
}
.icon-grid {
grid-template-columns: repeat(8, minmax(0, 1fr));
}
.icon-grid .icon-btn {
min-height: 48px;
font-size: 1.2rem;
}
.category-btn,
.icon-btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 6px;
min-height: 68px;
padding: 10px 8px;
border-radius: 14px;
border: 1px solid rgba(148, 163, 184, 0.16);
background: rgba(255, 255, 255, 0.86);
cursor: pointer;
transition: transform var(--transition), border-color var(--transition), box-shadow var(--transition), background var(--transition), color var(--transition);
}
.category-btn {
flex-direction: column;
font-size: 0.8rem;
color: var(--text-secondary);
}
.category-btn .icon {
font-size: 1.2rem;
}
.icon-btn {
min-height: 56px;
font-size: 1.3rem;
}
.category-btn:hover:not(:disabled),
.icon-btn:hover:not(:disabled) {
transform: translateY(-1px);
box-shadow: var(--shadow-xs);
}
.category-btn.active,
.icon-btn.active {
border-color: rgba(37, 99, 235, 0.28);
background: var(--primary-soft);
color: var(--primary);
}
.datetime-picker {
display: flex;
gap: 8px;
width: 100%;
box-sizing: border-box;
}
.datetime-picker input {
flex: 1 1;
min-width: 0;
box-sizing: border-box;
}
.search-form {
display: flex;
gap: 10px;
align-items: center;
}
.search-form--refresh {
padding: 4px;
border-radius: 16px;
background: rgba(246, 248, 252, 0.88);
border: 1px solid rgba(148, 163, 184, 0.12);
}
.search-form--refresh .search-input-wrap {
flex: 1 1;
}
.search-form--refresh .search-input-wrap input {
border-radius: 12px;
background: rgba(255, 255, 255, 0.94);
border-color: rgba(148, 163, 184, 0.12);
}
.search-form--refresh .btn {
min-width: 92px;
padding-inline: 18px;
}
.search-input-wrap {
position: relative;
flex: 1 1;
}
.search-input-wrap input {
padding-left: 42px;
}
.search-input-wrap__icon {
position: absolute;
left: 14px;
top: 50%;
transform: translateY(-50%);
color: var(--text-tertiary);
}
.search-summary {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
grid-gap: 10px;
gap: 10px;
}
.search-stat {
padding: 13px;
background: rgba(246, 248, 252, 0.82);
border: 1px solid rgba(148, 163, 184, 0.14);
display: grid;
grid-gap: 6px;
gap: 6px;
}
.section-card--search .results-count {
padding: 0 2px;
font-weight: 600;
}
.category-list {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.category-list--chips {
gap: 10px;
}
.category-tag {
display: inline-flex;
align-items: center;
gap: 9px;
min-height: 40px;
padding: 7px 10px;
border-radius: 14px;
}
.category-section--refresh .section-header .btn-inline {
min-height: 38px;
padding: 8px 12px;
border-radius: 12px;
}
.cat-icon {
width: 28px;
height: 28px;
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 9px;
background: rgba(246, 248, 252, 0.96);
}
.cat-delete {
width: 26px;
height: 26px;
margin-left: 2px;
}
.category-tip {
padding: 14px 16px;
color: var(--text-secondary);
text-align: center;
}
.bills-list {
gap: 10px;
}
.bill-item.inactive {
opacity: 0.72;
}
.bill-right {
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 8px;
flex-shrink: 0;
}
.bill-row {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
}
.bill-actions {
display: flex;
flex-wrap: wrap;
justify-content: flex-end;
gap: 7px;
}
.status-chip {
display: inline-flex;
align-items: center;
min-height: 26px;
padding: 0 9px;
border-radius: 999px;
background: var(--warning-soft);
color: var(--warning);
font-size: 0.75rem;
font-weight: 700;
}
.status-chip--success {
background: var(--income-soft);
color: var(--income);
}
.settings-section {
display: grid;
grid-gap: 8px;
gap: 8px;
}
.settings-section--refresh {
padding: 10px;
background: linear-gradient(180deg, rgba(255, 255, 255, 0.9), rgba(247, 249, 252, 0.82));
border-color: rgba(148, 163, 184, 0.12);
box-shadow: 0 8px 22px rgba(15, 23, 42, 0.04);
}
.settings-section--refresh .section-heading {
margin-bottom: 0;
padding: 4px 4px 2px;
}
.settings-section--refresh .section-heading h2 {
font-size: 0.98rem;
}
.settings-item {
display: flex;
align-items: center;
justify-content: space-between;
gap: 14px;
padding: 14px 15px;
border-radius: 18px;
background: rgba(255, 255, 255, 0.92);
border-color: rgba(148, 163, 184, 0.12);
}
.settings-item--stacked {
align-items: flex-end;
}
.settings-item--stacked .settings-info {
width: 100%;
}
.settings-item .btn-inline {
flex-shrink: 0;
min-height: 38px;
padding: 8px 13px;
border-radius: 12px;
}
.settings-title {
font-size: 0.95rem;
}
.settings-desc {
font-size: 0.84rem;
line-height: 1.5;
}
.about-info {
display: grid;
grid-gap: 6px;
gap: 6px;
padding: 6px 6px 2px;
color: var(--text-secondary);
}
.about-info p:first-child {
color: var(--text);
font-weight: 700;
}
.auth-layout {
min-height: calc(100vh - var(--header-height) - var(--safe-top));
}
.auth-page {
min-height: calc(100vh - var(--header-height) - var(--safe-top));
display: grid;
place-items: center;
padding: 20px 0 36px;
}
.auth-card {
width: min(100%, 420px);
background: linear-gradient(180deg, rgba(255, 255, 255, 0.97), rgba(247, 249, 253, 0.95));
}
.auth-card--refresh {
padding: 20px;
border-radius: 28px;
border-color: rgba(255, 255, 255, 0.72);
box-shadow: 0 20px 40px rgba(15, 23, 42, 0.07);
}
.auth-logo {
display: grid;
justify-items: center;
grid-gap: 9px;
gap: 9px;
text-align: center;
margin-bottom: 20px;
}
.auth-logo--refresh {
margin-bottom: 16px;
}
.auth-logo__mark {
width: 56px;
height: 56px;
border-radius: 18px;
color: #fff;
background: linear-gradient(135deg, var(--primary), #60a5fa);
box-shadow: 0 14px 30px rgba(37, 99, 235, 0.2);
}
.auth-logo h1 {
font-size: 1.58rem;
letter-spacing: -0.04em;
}
.auth-logo p {
color: var(--text-secondary);
max-width: 28ch;
line-height: 1.5;
font-size: 0.92rem;
}
.auth-form {
display: grid;
grid-gap: 4px;
gap: 4px;
}
.auth-form .btn-primary {
margin-top: 4px;
}
.chart-container {
position: relative;
min-height: 248px;
border-radius: 20px;
background: linear-gradient(180deg, rgba(250, 251, 253, 0.92), rgba(255, 255, 255, 0.98));
border: 1px solid rgba(148, 163, 184, 0.12);
padding: 14px;
}
.period-tabs,
.category-type-tabs {
margin-bottom: 14px;
}
.section-card .chart-container {
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.78);
}
.chart-container--large {
min-height: 304px;
}
.chart-container--small {
min-height: 264px;
}
.chart-container--stats {
background: linear-gradient(180deg, rgba(248, 250, 253, 0.92), rgba(255, 255, 255, 0.98));
border-color: rgba(148, 163, 184, 0.1);
}
.chart-container--stats-donut {
padding: 18px;
}
.section-card--stats {
padding: 14px;
background: linear-gradient(180deg, rgba(255, 255, 255, 0.93), rgba(247, 249, 252, 0.9));
}
.stats-grid--stats {
gap: 12px;
}
.category-breakdown {
align-content: start;
}
.category-breakdown--stats {
gap: 10px;
}
.category-item {
display: grid;
grid-template-columns: minmax(72px, auto) minmax(0, 1fr) auto;
align-items: center;
grid-gap: 10px;
gap: 10px;
padding: 12px 14px;
border-radius: 18px;
}
.category-item--stats {
display: grid;
grid-template-rows: auto auto;
grid-gap: 8px;
gap: 8px;
padding: 12px 14px;
}
.category-item-row {
display: flex;
justify-content: space-between;
align-items: center;
}
.category-item--stats .category-amount {
font-family: var(--font-mono);
font-feature-settings: "tnum";
font-variant-numeric: tabular-nums;
font-size: 0.98rem;
font-weight: 700;
}
.category-item .category-name {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.category-info {
display: grid;
grid-gap: 3px;
gap: 3px;
}
.category-name {
font-weight: 700;
}
.category-bar-wrap {
width: 100%;
height: 8px;
border-radius: 999px;
background: #e2e8f0;
overflow: hidden;
}
.category-bar {
height: 100%;
border-radius: inherit;
background: linear-gradient(90deg, var(--primary), #60a5fa);
}
.toast-stack {
position: fixed;
left: 50%;
bottom: calc(var(--bottom-nav-height) + var(--safe-bottom) + 14px);
transform: translateX(-50%);
width: min(calc(100% - 28px), 480px);
display: grid;
grid-gap: 8px;
gap: 8px;
z-index: 90;
}
.toast {
display: grid;
grid-template-columns: auto 1fr auto;
align-items: center;
grid-gap: 10px;
gap: 10px;
padding: 12px 13px;
border-radius: 15px;
background: var(--surface-elevated);
color: var(--text-on-dark);
box-shadow: 0 16px 34px rgba(15, 23, 42, 0.14);
-webkit-backdrop-filter: blur(18px);
backdrop-filter: blur(18px);
}
.toast__icon,
.toast__close {
color: inherit;
}
.toast__message {
line-height: 1.42;
}
.toast__close.icon-button {
width: 30px;
height: 30px;
border-radius: 10px;
}
.toast--success {
background: linear-gradient(135deg, rgba(5, 150, 105, 0.96), rgba(6, 182, 212, 0.9));
}
.toast--error {
background: linear-gradient(135deg, rgba(220, 38, 38, 0.96), rgba(244, 63, 94, 0.92));
}
.toast--info {
background: linear-gradient(135deg, rgba(15, 23, 42, 0.94), rgba(51, 65, 85, 0.94));
}
.bottom-nav {
position: fixed;
left: 0;
right: 0;
bottom: 0;
z-index: 35;
padding: 0 0 var(--safe-bottom);
background: linear-gradient(180deg, rgba(244, 246, 251, 0), rgba(244, 246, 251, 0.92) 24%, rgba(244, 246, 251, 0.98) 100%);
}
.bottom-nav__inner {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
grid-gap: 6px;
gap: 6px;
padding: 10px 10px 11px;
border-radius: 22px 22px 0 0;
background: rgba(255, 255, 255, 0.84);
border: 1px solid rgba(255, 255, 255, 0.66);
box-shadow: 0 -12px 30px rgba(15, 23, 42, 0.06);
-webkit-backdrop-filter: blur(24px);
backdrop-filter: blur(24px);
}
.bottom-nav__item {
min-height: 54px;
display: grid;
justify-items: center;
align-content: center;
grid-gap: 4px;
gap: 4px;
padding: 7px 4px;
border-radius: 16px;
color: var(--text-tertiary);
cursor: pointer;
transition: background var(--transition), color var(--transition), transform var(--transition);
}
.bottom-nav__item span {
font-size: 0.74rem;
font-weight: 700;
}
.bottom-nav__item.is-active {
background: var(--primary-soft);
color: var(--primary);
}
.checkbox-label {
display: inline-flex;
align-items: center;
gap: 10px;
color: var(--text-secondary);
}
.checkbox-label input {
width: 18px;
height: 18px;
}
.is-spinning {
animation: spin 0.9s linear infinite;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
@media (max-width: 1024px) {
.stats-grid--split {
grid-template-columns: 1fr;
}
}
@media (max-width: 768px) {
:root {
--header-height: 42px;
--bottom-nav-height: 82px;
}
.app-shell__container {
width: min(100% - 24px, var(--shell-width));
}
.app-header__inner,
.app-header--compact .app-header__inner {
min-height: auto;
align-items: center;
gap: 8px;
padding: 8px 0;
}
.app-header__text h1 {
font-size: 1rem;
}
.app-header__actions {
gap: 6px;
max-width: none;
flex-wrap: nowrap;
justify-content: flex-end;
}
.month-switcher--header {
min-width: 0;
padding: 3px;
}
.month-switcher--header .month-switcher__label {
min-width: 108px;
font-size: 0.82rem;
}
.month-switcher--header .icon-button,
.icon-button {
width: 38px;
height: 38px;
}
.section-card,
.category-section,
.settings-section,
.dashboard-hero,
.auth-card,
.modal,
.confirm-card {
padding: 16px;
}
.section-card--stats,
.section-card--search-panel {
padding: 12px;
}
.modal--fullscreen {
padding: calc(var(--safe-top) + 6px) 14px calc(var(--safe-bottom) + 18px);
}
.modal--fullscreen .modal-header {
top: calc(var(--safe-top) * -1 - 6px);
margin: calc(var(--safe-top) * -1 - 6px) -14px 12px;
padding: calc(var(--safe-top) + 6px) 14px 10px;
}
.stats-grid--hero,
.search-summary,
.category-grid {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.icon-grid {
grid-template-columns: repeat(6, minmax(0, 1fr));
}
.icon-grid .icon-btn {
min-height: 44px;
font-size: 1.1rem;
}
.search-form,
.settings-item,
.settings-item--stacked {
flex-direction: column;
align-items: stretch;
}
.search-form--refresh .btn {
width: 100%;
}
.record-amount {
align-self: center;
}
.bill-right,
.settings-item--stacked {
width: auto;
}
.bill-actions {
width: auto;
justify-content: flex-end;
}
.bill-actions .btn {
flex: 0 0 auto;
}
.category-item {
grid-template-columns: minmax(72px, auto) minmax(0, 1fr) auto;
}
.toast-stack {
bottom: calc(var(--bottom-nav-height) + var(--safe-bottom) + 10px);
}
}
@media (max-width: 560px) {
.app-header__inner {
flex-direction: row;
align-items: center;
}
.app-header__main,
.app-header__text {
min-width: 0;
flex: 1 1;
}
.app-header__text {
gap: 2px;
}
.app-header__actions {
width: auto;
flex: 1 1;
justify-content: flex-end;
gap: 6px;
}
.month-switcher,
.month-switcher--header {
flex: 1 1;
justify-content: center;
}
.month-switcher--header .month-switcher__label {
min-width: 96px;
flex: 1 1;
}
.search-summary,
.icon-grid,
.auth-switch {
grid-template-columns: 1fr;
}
.record-item,
.bill-item,
.settings-item,
.category-item {
padding: 13px;
}
.stats-grid--hero {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.category-grid {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
.period-tabs {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.category-type-tabs {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.record-left,
.bill-left {
gap: 10px;
}
.record-left,
.record-item,
.bill-item {
min-width: 0;
}
.record-item,
.bill-item {
gap: 10px;
}
.record-icon,
.bill-icon,
.empty-state__icon {
width: 40px;
height: 40px;
border-radius: 13px;
}
.datetime-picker {
gap: 6px;
}
.modal-actions,
.confirm-card__actions {
flex-direction: column;
}
.auth-page {
padding: 12px 0 24px;
}
.auth-card--refresh {
padding: 18px 16px;
border-radius: 24px;
}
.auth-logo {
gap: 8px;
margin-bottom: 16px;
}
.auth-logo h1 {
font-size: 1.46rem;
}
.category-btn,
.icon-btn {
min-height: 54px;
}
.toast {
grid-template-columns: auto 1fr;
}
.toast__close {
grid-column: 2;
justify-self: end;
}
.bottom-nav__inner {
gap: 6px;
padding: 10px;
}
.bottom-nav__item {
min-height: 54px;
}
}
‚{}ã äxÿÿÿ RawSource
 èò«webpack/lib/util/registerExternalSerializerœwebpack-sources/ConcatSource` «webpack/lib/util/registerExternalSerializer™webpack-sources/RawSourceW(self["webpackChunk_N_E"] = self["webpackChunk_N_E"] || []).push([[161],{
/***/ 5461:
«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([
"/categories",
function () {
return require("private-next-pages/categories.js");
}
]);
if(module.hot) {
module.hot.dispose(function () {
window.__NEXT_P.push(["/categories"])
});
}
bntvD—²»¾#“__webpack_require__„2874…false{}†buffer†source„size„maps„hash¬
(window.__NEXT_P = window.__NEXT_P || []).push([
"/categories",
function () {
return __webpack_require__(2874);
}
]);
if(false) {}
”{"finalSource":true}ƒmapbufferedMap ReplaceSourceRawSource'
(window.__NEXT_P = window.__NEXT_P || []).push([
"/categories",
function () {
return require("private-next-pages/categories.js");
}
]);
if(module.hot) {
module.hot.dispose(function () {
window.__NEXT_P.push(["/categories"])
});
}
Z110116__webpack_require__undefined1181512874undefined178187falseundefined190291{}undefined
/***/ }) †buffer†source„size„maps„hash/***/ (function(__unused_webpack_module, __unused_webpack_exports, __webpack_require__) {
(window.__NEXT_P = window.__NEXT_P || []).push([
"/categories",
function () {
return __webpack_require__(2874);
}
]);
if(false) {}
/***/ }) ”{"finalSource":true}ƒmapbufferedMap ConcatSourceRawSource[/***/ (function(__unused_webpack_module, __unused_webpack_exports, __webpack_require__) {
ReplaceSourceRawSource'
(window.__NEXT_P = window.__NEXT_P || []).push([
"/categories",
function () {
return require("private-next-pages/categories.js");
}
]);
if(module.hot) {
module.hot.dispose(function () {
window.__NEXT_P.push(["/categories"])
});
}
Z110116__webpack_require__undefined1181512874undefined178187falseundefined190291{}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çè,
/***/ 2874:
 á «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 X«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 */ Categories; }
/* 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 _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);
/* harmony import */ var _components_ConfirmProvider__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(8846);
«webpack/lib/util/registerExternalSerializerwebpack-sources/ReplaceSourceòKimport { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
import { useState, useEffect } from "react";
import { useRouter } from "next/router";
import { isLoggedIn, getCategories, createCategory, deleteCategory } from "../lib/api";
import AppShell from "../components/AppShell";
import { Icon } from "../components/icons";
import { useToast } from "../components/ToastProvider";
import { useConfirm } from "../components/ConfirmProvider";
const ICONS = [
"\uD83C\uDF5C",
"\uD83E\uDD58",
"\uD83E\uDD6C",
"\uD83E\uDD69",
"\uD83C\uDF54",
"\uD83C\uDF55",
"☕",
"\uD83C\uDF7A",
"\uD83E\uDDCB",
"\uD83C\uDF4E",
"\uD83C\uDF6A",
"\uD83C\uDF6B",
"\uD83D\uDE97",
"\uD83D\uDE95",
"\uD83D\uDE8C",
"\uD83D\uDE87",
"⛽",
"\uD83D\uDE99",
"\uD83D\uDEF5",
"✈ï¸",
"\uD83D\uDEA2",
"\uD83D\uDED2",
"\uD83D\uDC54",
"\uD83D\uDC55",
"\uD83D\uDC56",
"\uD83D\uDC5F",
"\uD83D\uDC53",
"\uD83E\uDDE2",
"\uD83D\uDC5C",
"\uD83C\uDF92",
"\uD83D\uDC8D",
"\uD83D\uDC84",
"\uD83C\uDFE0",
"\uD83C\uDFE2",
"\uD83C\uDFE6",
"\uD83C\uDFE5",
"\uD83C\uDFE8",
"\uD83C\uDFE9",
"\uD83D\uDD27",
"\uD83D\uDD28",
"\uD83D\uDECBï¸",
"\uD83D\uDCFA",
"\uD83D\uDCBB",
"\uD83D\uDCF1",
"\uD83D\uDCDD",
"\uD83D\uDCB0",
"\uD83C\uDF81",
"\uD83D\uDCC8",
"\uD83E\uDDE7",
"\uD83D\uDCB3",
"\uD83C\uDFAF",
"\uD83D\uDCDA",
"\uD83C\uDFB5",
"\uD83C\uDFAE",
"\uD83C\uDFC3",
"âš½",
"\uD83C\uDFCA",
"\uD83D\uDEB4",
"\uD83C\uDFAD",
"\uD83C\uDFA8",
"\uD83C\uDFAC",
"\uD83D\uDCF7",
"\uD83C\uDFA5",
"\uD83D\uDCFA",
"\uD83E\uDDF8",
"\uD83D\uDC15",
"\uD83D\uDC08",
"\uD83C\uDF38",
"\uD83C\uDF3A"
];
export default function Categories() {
const router = useRouter();
const { showToast } = useToast();
const { confirm } = useConfirm();
const [categories, setCategories] = useState({
expense: [],
income: []
});
const [modalOpen, setModalOpen] = useState(false);
const [categoryType, setCategoryType] = useState("expense");
const [loading, setLoading] = useState(true);
const [submitting, setSubmitting] = useState(false);
const [formError, setFormError] = useState("");
const [formData, setFormData] = useState({
name: "",
icon: "\uD83D\uDCDD"
});
useEffect(()=>{
if (!isLoggedIn()) {
router.push("/login");
return;
}
loadCategories();
}, [
router
]);
const loadCategories = async ()=>{
setLoading(true);
try {
const data = await getCategories();
setCategories(data);
} catch (err) {
console.error(err);
showToast(err.message, {
tone: "error"
});
} finally{
setLoading(false);
}
};
const openAddModal = (type)=>{
setCategoryType(type);
setFormData({
name: "",
icon: "\uD83D\uDCDD"
});
setFormError("");
setModalOpen(true);
};
const closeModal = ()=>{
if (submitting) return;
setModalOpen(false);
setFormError("");
};
const handleSubmit = async (e)=>{
e.preventDefault();
if (!formData.name || !formData.icon) {
setFormError("请填写完整信æ¯");
return;
}
setSubmitting(true);
setFormError("");
try {
await createCategory({
type: categoryType,
name: formData.name,
icon: formData.icon
});
closeModal();
showToast("分类已创建", {
tone: "success"
});
await loadCategories();
} catch (err) {
setFormError(err.message);
showToast(err.message, {
tone: "error"
});
} finally{
setSubmitting(false);
}
};
const handleDelete = async (id)=>{
const shouldDelete = await confirm({
title: "删除此分类?",
description: "默认分类ä¸å¯åˆ é™¤ï¼Œè‡ªå®šä¹‰åˆ†ç±»åˆ é™¤åŽå°†æ— æ³•æ¢å¤ã€‚",
confirmText: "删除"
});
if (!shouldDelete) return;
try {
await deleteCategory(id);
showToast("分类已删除", {
tone: "success"
});
await loadCategories();
} catch (err) {
showToast(err.message, {
tone: "error"
});
}
};
const actions = /*#__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, {
title: "分类",
subtitle: "æ•´ç†å¸¸ç”¨åˆ†ç±»ä¸Žå›¾æ ‡",
actions: actions,
compactHeader: true,
children: [
loading ? /*#__PURE__*/ _jsx("div", {
className: "card loading-state",
children: "加载中..."
}) : /*#__PURE__*/ _jsxs(_Fragment, {
children: [
/*#__PURE__*/ _jsxs("section", {
className: "card category-section category-section--refresh",
children: [
/*#__PURE__*/ _jsxs("div", {
className: "section-header",
children: [
/*#__PURE__*/ _jsxs("div", {
children: [
/*#__PURE__*/ _jsx("p", {
className: "eyebrow",
children: "支出分类"
}),
/*#__PURE__*/ _jsx("h2", {
children: "支出"
})
]
}),
/*#__PURE__*/ _jsx("button", {
type: "button",
className: "btn btn-secondary btn-inline",
onClick: ()=>openAddModal("expense"),
children: /*#__PURE__*/ _jsxs("span", {
className: "button-content",
children: [
/*#__PURE__*/ _jsx(Icon, {
name: "plus",
size: 16
}),
"添加"
]
})
})
]
}),
/*#__PURE__*/ _jsx("div", {
className: "category-list category-list--chips",
children: categories.expense.map((cat)=>/*#__PURE__*/ _jsxs("div", {
className: "category-tag",
children: [
/*#__PURE__*/ _jsx("span", {
className: "cat-icon",
children: cat.icon
}),
/*#__PURE__*/ _jsx("span", {
className: "cat-name",
children: cat.name
}),
!cat.is_default && /*#__PURE__*/ _jsx("button", {
type: "button",
className: "icon-button icon-button--ghost cat-delete",
"aria-label": "删除".concat(cat.name),
onClick: ()=>handleDelete(cat.id),
children: /*#__PURE__*/ _jsx(Icon, {
name: "close",
size: 14
})
})
]
}, cat.id || cat.name))
})
]
}),
/*#__PURE__*/ _jsxs("section", {
className: "card category-section category-section--refresh",
children: [
/*#__PURE__*/ _jsxs("div", {
className: "section-header",
children: [
/*#__PURE__*/ _jsxs("div", {
children: [
/*#__PURE__*/ _jsx("p", {
className: "eyebrow",
children: "收入分类"
}),
/*#__PURE__*/ _jsx("h2", {
children: "æ”¶å…¥"
})
]
}),
/*#__PURE__*/ _jsx("button", {
type: "button",
className: "btn btn-secondary btn-inline",
onClick: ()=>openAddModal("income"),
children: /*#__PURE__*/ _jsxs("span", {
className: "button-content",
children: [
/*#__PURE__*/ _jsx(Icon, {
name: "plus",
size: 16
}),
"添加"
]
})
})
]
}),
/*#__PURE__*/ _jsx("div", {
className: "category-list category-list--chips",
children: categories.income.map((cat)=>/*#__PURE__*/ _jsxs("div", {
className: "category-tag",
children: [
/*#__PURE__*/ _jsx("span", {
className: "cat-icon",
children: cat.icon
}),
/*#__PURE__*/ _jsx("span", {
className: "cat-name",
children: cat.name
}),
!cat.is_default && /*#__PURE__*/ _jsx("button", {
type: "button",
className: "icon-button icon-button--ghost cat-delete",
"aria-label": "删除".concat(cat.name),
onClick: ()=>handleDelete(cat.id),
children: /*#__PURE__*/ _jsx(Icon, {
name: "close",
size: 14
})
})
]
}, cat.id || cat.name))
})
]
}),
/*#__PURE__*/ _jsx("div", {
className: "card category-tip category-tip--refresh",
children: /*#__PURE__*/ _jsx("p", {
children: "默认分类无法删除,自定义分类å¯ä»¥åˆ é™¤ã€‚"
})
})
]
}),
modalOpen && /*#__PURE__*/ _jsx("div", {
className: "modal-overlay",
onClick: closeModal,
children: /*#__PURE__*/ _jsxs("div", {
className: "modal",
onClick: (e)=>e.stopPropagation(),
children: [
/*#__PURE__*/ _jsxs("div", {
className: "modal-header",
children: [
/*#__PURE__*/ _jsxs("div", {
children: [
/*#__PURE__*/ _jsx("p", {
className: "eyebrow",
children: "新增分类"
}),
/*#__PURE__*/ _jsx("h2", {
children: categoryType === "expense" ? "新建支出分类" : "新建收入分类"
})
]
}),
/*#__PURE__*/ _jsx("button", {
type: "button",
className: "icon-button icon-button--ghost",
"aria-label": "关闭弹窗",
onClick: closeModal,
children: /*#__PURE__*/ _jsx(Icon, {
name: "close",
size: 18
})
})
]
}),
formError && /*#__PURE__*/ _jsx("div", {
className: "error-msg",
children: formError
}),
/*#__PURE__*/ _jsxs("form", {
onSubmit: handleSubmit,
children: [
/*#__PURE__*/ _jsxs("div", {
className: "form-group",
children: [
/*#__PURE__*/ _jsx("label", {
htmlFor: "category-name",
children: "分类åç§°"
}),
/*#__PURE__*/ _jsx("input", {
id: "category-name",
type: "text",
value: formData.name,
onChange: (e)=>setFormData({
...formData,
name: e.target.value
}),
placeholder: "请输入分类åç§°",
required: true,
disabled: submitting
})
]
}),
/*#__PURE__*/ _jsxs("div", {
className: "form-group",
children: [
/*#__PURE__*/ _jsx("label", {
children: "选择图标"
}),
/*#__PURE__*/ _jsx("div", {
className: "icon-grid",
children: ICONS.map((icon)=>/*#__PURE__*/ _jsx("button", {
type: "button",
className: "icon-btn ".concat(formData.icon === icon ? "active" : ""),
onClick: ()=>setFormData({
...formData,
icon
}),
disabled: submitting,
children: icon
}, icon))
})
]
}),
/*#__PURE__*/ _jsx("div", {
className: "modal-actions",
children: /*#__PURE__*/ _jsx("button", {
type: "submit",
className: "btn btn-primary",
disabled: submitting,
children: submitting ? /*#__PURE__*/ _jsxs("span", {
className: "button-content",
children: [
/*#__PURE__*/ _jsx(Icon, {
name: "spinner",
size: 16,
className: "is-spinning"
}),
"ä¿å­˜ä¸­..."
]
}) : "ä¿å­˜"
})
})
]
})
]
})
})
]
});
}
cYUW_«­24^`˜Ò .Zbˆ¥®Ûâ<Cy°·èï _& Q X Ÿ ´ ½ ­
¹
 :=ðóõø\`bi˜ ãçšDHÁľÁ½Àçë¹»¾šLP % ( Î!Ñ!Ó!Ö!#š#M$_Q$÷$û$t%w%q&t&p's'™((h)k)m)p)I+L+ý+,½,À,À-Ã-Ö.Ù.00„0‡0G2J2Ó2_Ö23™34!4Ñ4Õ4m5q5â5å5Ï6Ò6æ7é7+9.90939W:Z: ;;¥;©;W<Z<L=O=„@ˆ@6AN9AåAèABBÜEßEfFiF€G„GOHRHTHWH¶(0,next_router__WEBPACK_IMPORTED_MODULE_2__.useRouter)Ì(0,_components_ToastProvider__WEBPACK_IMPORTED_MODULE_5__/* .useToast */ .p)Ð(0,_components_ConfirmProvider__WEBPACK_IMPORTED_MODULE_6__/* .useConfirm */ .N)¯(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)ÿÿÿÿÿÿ°(0,react__WEBPACK_IMPORTED_MODULE_1__.useEffect)¾(0,_lib_api__WEBPACK_IMPORTED_MODULE_7__/* .isLoggedIn */ .jl)Á(0,_lib_api__WEBPACK_IMPORTED_MODULE_7__/* .getCategories */ .CP)Â(0,_lib_api__WEBPACK_IMPORTED_MODULE_7__/* .createCategory */ .k4)Â(0,_lib_api__WEBPACK_IMPORTED_MODULE_7__/* .deleteCategory */ .uu)¶(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)ÿ¼_components_icons__WEBPACK_IMPORTED_MODULE_4__/* .Icon */ .J·(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)Å_components_AppShell__WEBPACK_IMPORTED_MODULE_3__/* ["default"] */ .Züþ·react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.Fragmentýýýûûûýûüûýûûûûüýýýûûûýûüûýûûûûüûûûýýýûûûûüûýýûûýûûûûûýûü†buffer†source„size„maps„hashM^__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": function() { return /* binding */ Categories; }
/* 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 _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);
/* harmony import */ var _components_ConfirmProvider__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(8846);
const ICONS = [
"\uD83C\uDF5C",
"\uD83E\uDD58",
"\uD83E\uDD6C",
"\uD83E\uDD69",
"\uD83C\uDF54",
"\uD83C\uDF55",
"☕",
"\uD83C\uDF7A",
"\uD83E\uDDCB",
"\uD83C\uDF4E",
"\uD83C\uDF6A",
"\uD83C\uDF6B",
"\uD83D\uDE97",
"\uD83D\uDE95",
"\uD83D\uDE8C",
"\uD83D\uDE87",
"⛽",
"\uD83D\uDE99",
"\uD83D\uDEF5",
"✈ï¸",
"\uD83D\uDEA2",
"\uD83D\uDED2",
"\uD83D\uDC54",
"\uD83D\uDC55",
"\uD83D\uDC56",
"\uD83D\uDC5F",
"\uD83D\uDC53",
"\uD83E\uDDE2",
"\uD83D\uDC5C",
"\uD83C\uDF92",
"\uD83D\uDC8D",
"\uD83D\uDC84",
"\uD83C\uDFE0",
"\uD83C\uDFE2",
"\uD83C\uDFE6",
"\uD83C\uDFE5",
"\uD83C\uDFE8",
"\uD83C\uDFE9",
"\uD83D\uDD27",
"\uD83D\uDD28",
"\uD83D\uDECBï¸",
"\uD83D\uDCFA",
"\uD83D\uDCBB",
"\uD83D\uDCF1",
"\uD83D\uDCDD",
"\uD83D\uDCB0",
"\uD83C\uDF81",
"\uD83D\uDCC8",
"\uD83E\uDDE7",
"\uD83D\uDCB3",
"\uD83C\uDFAF",
"\uD83D\uDCDA",
"\uD83C\uDFB5",
"\uD83C\uDFAE",
"\uD83C\uDFC3",
"âš½",
"\uD83C\uDFCA",
"\uD83D\uDEB4",
"\uD83C\uDFAD",
"\uD83C\uDFA8",
"\uD83C\uDFAC",
"\uD83D\uDCF7",
"\uD83C\uDFA5",
"\uD83D\uDCFA",
"\uD83E\uDDF8",
"\uD83D\uDC15",
"\uD83D\uDC08",
"\uD83C\uDF38",
"\uD83C\uDF3A"
];
function Categories() {
const router = (0,next_router__WEBPACK_IMPORTED_MODULE_2__.useRouter)();
const { showToast } = (0,_components_ToastProvider__WEBPACK_IMPORTED_MODULE_5__/* .useToast */ .p)();
const { confirm } = (0,_components_ConfirmProvider__WEBPACK_IMPORTED_MODULE_6__/* .useConfirm */ .N)();
const [categories, setCategories] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)({
expense: [],
income: []
});
const [modalOpen, setModalOpen] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)(false);
const [categoryType, setCategoryType] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)("expense");
const [loading, setLoading] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)(true);
const [submitting, setSubmitting] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)(false);
const [formError, setFormError] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)("");
const [formData, setFormData] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)({
name: "",
icon: "\uD83D\uDCDD"
});
(0,react__WEBPACK_IMPORTED_MODULE_1__.useEffect)(()=>{
if (!(0,_lib_api__WEBPACK_IMPORTED_MODULE_7__/* .isLoggedIn */ .jl)()) {
router.push("/login");
return;
}
loadCategories();
}, [
router
]);
const loadCategories = async ()=>{
setLoading(true);
try {
const data = await (0,_lib_api__WEBPACK_IMPORTED_MODULE_7__/* .getCategories */ .CP)();
setCategories(data);
} catch (err) {
console.error(err);
showToast(err.message, {
tone: "error"
});
} finally{
setLoading(false);
}
};
const openAddModal = (type)=>{
setCategoryType(type);
setFormData({
name: "",
icon: "\uD83D\uDCDD"
});
setFormError("");
setModalOpen(true);
};
const closeModal = ()=>{
if (submitting) return;
setModalOpen(false);
setFormError("");
};
const handleSubmit = async (e)=>{
e.preventDefault();
if (!formData.name || !formData.icon) {
setFormError("请填写完整信æ¯");
return;
}
setSubmitting(true);
setFormError("");
try {
await (0,_lib_api__WEBPACK_IMPORTED_MODULE_7__/* .createCategory */ .k4)({
type: categoryType,
name: formData.name,
icon: formData.icon
});
closeModal();
showToast("分类已创建", {
tone: "success"
});
await loadCategories();
} catch (err) {
setFormError(err.message);
showToast(err.message, {
tone: "error"
});
} finally{
setSubmitting(false);
}
};
const handleDelete = async (id)=>{
const shouldDelete = await confirm({
title: "删除此分类?",
description: "默认分类ä¸å¯åˆ é™¤ï¼Œè‡ªå®šä¹‰åˆ†ç±»åˆ é™¤åŽå°†æ— æ³•æ¢å¤ã€‚",
confirmText: "删除"
});
if (!shouldDelete) return;
try {
await (0,_lib_api__WEBPACK_IMPORTED_MODULE_7__/* .deleteCategory */ .uu)(id);
showToast("分类已删除", {
tone: "success"
});
await loadCategories();
} catch (err) {
showToast(err.message, {
tone: "error"
});
}
};
const actions = /*#__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, {
title: "分类",
subtitle: "æ•´ç†å¸¸ç”¨åˆ†ç±»ä¸Žå›¾æ ‡",
actions: actions,
compactHeader: true,
children: [
loading ? /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "card loading-state",
children: "加载中..."
}) : /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)(react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.Fragment, {
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("section", {
className: "card category-section category-section--refresh",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "section-header",
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__.jsx)("button", {
type: "button",
className: "btn btn-secondary btn-inline",
onClick: ()=>openAddModal("expense"),
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("span", {
className: "button-content",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_components_icons__WEBPACK_IMPORTED_MODULE_4__/* .Icon */ .J, {
name: "plus",
size: 16
}),
"添加"
]
})
})
]
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "category-list category-list--chips",
children: categories.expense.map((cat)=>/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "category-tag",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("span", {
className: "cat-icon",
children: cat.icon
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("span", {
className: "cat-name",
children: cat.name
}),
!cat.is_default && /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("button", {
type: "button",
className: "icon-button icon-button--ghost cat-delete",
"aria-label": "删除".concat(cat.name),
onClick: ()=>handleDelete(cat.id),
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_components_icons__WEBPACK_IMPORTED_MODULE_4__/* .Icon */ .J, {
name: "close",
size: 14
})
})
]
}, cat.id || cat.name))
})
]
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("section", {
className: "card category-section category-section--refresh",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "section-header",
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__.jsx)("button", {
type: "button",
className: "btn btn-secondary btn-inline",
onClick: ()=>openAddModal("income"),
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("span", {
className: "button-content",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_components_icons__WEBPACK_IMPORTED_MODULE_4__/* .Icon */ .J, {
name: "plus",
size: 16
}),
"添加"
]
})
})
]
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "category-list category-list--chips",
children: categories.income.map((cat)=>/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "category-tag",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("span", {
className: "cat-icon",
children: cat.icon
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("span", {
className: "cat-name",
children: cat.name
}),
!cat.is_default && /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("button", {
type: "button",
className: "icon-button icon-button--ghost cat-delete",
"aria-label": "删除".concat(cat.name),
onClick: ()=>handleDelete(cat.id),
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_components_icons__WEBPACK_IMPORTED_MODULE_4__/* .Icon */ .J, {
name: "close",
size: 14
})
})
]
}, cat.id || cat.name))
})
]
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "card category-tip category-tip--refresh",
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("p", {
children: "默认分类无法删除,自定义分类å¯ä»¥åˆ é™¤ã€‚"
})
})
]
}),
modalOpen && /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "modal-overlay",
onClick: closeModal,
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "modal",
onClick: (e)=>e.stopPropagation(),
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "modal-header",
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: categoryType === "expense" ? "新建支出分类" : "新建收入分类"
})
]
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("button", {
type: "button",
className: "icon-button icon-button--ghost",
"aria-label": "关闭弹窗",
onClick: closeModal,
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_components_icons__WEBPACK_IMPORTED_MODULE_4__/* .Icon */ .J, {
name: "close",
size: 18
})
})
]
}),
formError && /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "error-msg",
children: formError
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("form", {
onSubmit: handleSubmit,
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "form-group",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("label", {
htmlFor: "category-name",
children: "分类åç§°"
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("input", {
id: "category-name",
type: "text",
value: formData.name,
onChange: (e)=>setFormData({
...formData,
name: e.target.value
}),
placeholder: "请输入分类åç§°",
required: true,
disabled: submitting
})
]
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "form-group",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("label", {
children: "选择图标"
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "icon-grid",
children: ICONS.map((icon)=>/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("button", {
type: "button",
className: "icon-btn ".concat(formData.icon === icon ? "active" : ""),
onClick: ()=>setFormData({
...formData,
icon
}),
disabled: submitting,
children: icon
}, icon))
})
]
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "modal-actions",
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("button", {
type: "submit",
className: "btn btn-primary",
disabled: submitting,
children: submitting ? /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("span", {
className: "button-content",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_components_icons__WEBPACK_IMPORTED_MODULE_4__/* .Icon */ .J, {
name: "spinner",
size: 16,
className: "is-spinning"
}),
"ä¿å­˜ä¸­..."
]
}) : "ä¿å­˜"
})
})
]
})
]
})
})
]
});
}
”{"finalSource":true}ƒmapbufferedMap ConcatSourceRawSource__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": function() { return /* binding */ Categories; }
/* 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 _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);
/* harmony import */ var _components_ConfirmProvider__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(8846);
ReplaceSourceRawSourceòKimport { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
import { useState, useEffect } from "react";
import { useRouter } from "next/router";
import { isLoggedIn, getCategories, createCategory, deleteCategory } from "../lib/api";
import AppShell from "../components/AppShell";
import { Icon } from "../components/icons";
import { useToast } from "../components/ToastProvider";
import { useConfirm } from "../components/ConfirmProvider";
const ICONS = [
"\uD83C\uDF5C",
"\uD83E\uDD58",
"\uD83E\uDD6C",
"\uD83E\uDD69",
"\uD83C\uDF54",
"\uD83C\uDF55",
"☕",
"\uD83C\uDF7A",
"\uD83E\uDDCB",
"\uD83C\uDF4E",
"\uD83C\uDF6A",
"\uD83C\uDF6B",
"\uD83D\uDE97",
"\uD83D\uDE95",
"\uD83D\uDE8C",
"\uD83D\uDE87",
"⛽",
"\uD83D\uDE99",
"\uD83D\uDEF5",
"✈ï¸",
"\uD83D\uDEA2",
"\uD83D\uDED2",
"\uD83D\uDC54",
"\uD83D\uDC55",
"\uD83D\uDC56",
"\uD83D\uDC5F",
"\uD83D\uDC53",
"\uD83E\uDDE2",
"\uD83D\uDC5C",
"\uD83C\uDF92",
"\uD83D\uDC8D",
"\uD83D\uDC84",
"\uD83C\uDFE0",
"\uD83C\uDFE2",
"\uD83C\uDFE6",
"\uD83C\uDFE5",
"\uD83C\uDFE8",
"\uD83C\uDFE9",
"\uD83D\uDD27",
"\uD83D\uDD28",
"\uD83D\uDECBï¸",
"\uD83D\uDCFA",
"\uD83D\uDCBB",
"\uD83D\uDCF1",
"\uD83D\uDCDD",
"\uD83D\uDCB0",
"\uD83C\uDF81",
"\uD83D\uDCC8",
"\uD83E\uDDE7",
"\uD83D\uDCB3",
"\uD83C\uDFAF",
"\uD83D\uDCDA",
"\uD83C\uDFB5",
"\uD83C\uDFAE",
"\uD83C\uDFC3",
"âš½",
"\uD83C\uDFCA",
"\uD83D\uDEB4",
"\uD83C\uDFAD",
"\uD83C\uDFA8",
"\uD83C\uDFAC",
"\uD83D\uDCF7",
"\uD83C\uDFA5",
"\uD83D\uDCFA",
"\uD83E\uDDF8",
"\uD83D\uDC15",
"\uD83D\uDC08",
"\uD83C\uDF38",
"\uD83C\uDF3A"
];
export default function Categories() {
const router = useRouter();
const { showToast } = useToast();
const { confirm } = useConfirm();
const [categories, setCategories] = useState({
expense: [],
income: []
});
const [modalOpen, setModalOpen] = useState(false);
const [categoryType, setCategoryType] = useState("expense");
const [loading, setLoading] = useState(true);
const [submitting, setSubmitting] = useState(false);
const [formError, setFormError] = useState("");
const [formData, setFormData] = useState({
name: "",
icon: "\uD83D\uDCDD"
});
useEffect(()=>{
if (!isLoggedIn()) {
router.push("/login");
return;
}
loadCategories();
}, [
router
]);
const loadCategories = async ()=>{
setLoading(true);
try {
const data = await getCategories();
setCategories(data);
} catch (err) {
console.error(err);
showToast(err.message, {
tone: "error"
});
} finally{
setLoading(false);
}
};
const openAddModal = (type)=>{
setCategoryType(type);
setFormData({
name: "",
icon: "\uD83D\uDCDD"
});
setFormError("");
setModalOpen(true);
};
const closeModal = ()=>{
if (submitting) return;
setModalOpen(false);
setFormError("");
};
const handleSubmit = async (e)=>{
e.preventDefault();
if (!formData.name || !formData.icon) {
setFormError("请填写完整信æ¯");
return;
}
setSubmitting(true);
setFormError("");
try {
await createCategory({
type: categoryType,
name: formData.name,
icon: formData.icon
});
closeModal();
showToast("分类已创建", {
tone: "success"
});
await loadCategories();
} catch (err) {
setFormError(err.message);
showToast(err.message, {
tone: "error"
});
} finally{
setSubmitting(false);
}
};
const handleDelete = async (id)=>{
const shouldDelete = await confirm({
title: "删除此分类?",
description: "默认分类ä¸å¯åˆ é™¤ï¼Œè‡ªå®šä¹‰åˆ†ç±»åˆ é™¤åŽå°†æ— æ³•æ¢å¤ã€‚",
confirmText: "删除"
});
if (!shouldDelete) return;
try {
await deleteCategory(id);
showToast("分类已删除", {
tone: "success"
});
await loadCategories();
} catch (err) {
showToast(err.message, {
tone: "error"
});
}
};
const actions = /*#__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, {
title: "分类",
subtitle: "æ•´ç†å¸¸ç”¨åˆ†ç±»ä¸Žå›¾æ ‡",
actions: actions,
compactHeader: true,
children: [
loading ? /*#__PURE__*/ _jsx("div", {
className: "card loading-state",
children: "加载中..."
}) : /*#__PURE__*/ _jsxs(_Fragment, {
children: [
/*#__PURE__*/ _jsxs("section", {
className: "card category-section category-section--refresh",
children: [
/*#__PURE__*/ _jsxs("div", {
className: "section-header",
children: [
/*#__PURE__*/ _jsxs("div", {
children: [
/*#__PURE__*/ _jsx("p", {
className: "eyebrow",
children: "支出分类"
}),
/*#__PURE__*/ _jsx("h2", {
children: "支出"
})
]
}),
/*#__PURE__*/ _jsx("button", {
type: "button",
className: "btn btn-secondary btn-inline",
onClick: ()=>openAddModal("expense"),
children: /*#__PURE__*/ _jsxs("span", {
className: "button-content",
children: [
/*#__PURE__*/ _jsx(Icon, {
name: "plus",
size: 16
}),
"添加"
]
})
})
]
}),
/*#__PURE__*/ _jsx("div", {
className: "category-list category-list--chips",
children: categories.expense.map((cat)=>/*#__PURE__*/ _jsxs("div", {
className: "category-tag",
children: [
/*#__PURE__*/ _jsx("span", {
className: "cat-icon",
children: cat.icon
}),
/*#__PURE__*/ _jsx("span", {
className: "cat-name",
children: cat.name
}),
!cat.is_default && /*#__PURE__*/ _jsx("button", {
type: "button",
className: "icon-button icon-button--ghost cat-delete",
"aria-label": "删除".concat(cat.name),
onClick: ()=>handleDelete(cat.id),
children: /*#__PURE__*/ _jsx(Icon, {
name: "close",
size: 14
})
})
]
}, cat.id || cat.name))
})
]
}),
/*#__PURE__*/ _jsxs("section", {
className: "card category-section category-section--refresh",
children: [
/*#__PURE__*/ _jsxs("div", {
className: "section-header",
children: [
/*#__PURE__*/ _jsxs("div", {
children: [
/*#__PURE__*/ _jsx("p", {
className: "eyebrow",
children: "收入分类"
}),
/*#__PURE__*/ _jsx("h2", {
children: "æ”¶å…¥"
})
]
}),
/*#__PURE__*/ _jsx("button", {
type: "button",
className: "btn btn-secondary btn-inline",
onClick: ()=>openAddModal("income"),
children: /*#__PURE__*/ _jsxs("span", {
className: "button-content",
children: [
/*#__PURE__*/ _jsx(Icon, {
name: "plus",
size: 16
}),
"添加"
]
})
})
]
}),
/*#__PURE__*/ _jsx("div", {
className: "category-list category-list--chips",
children: categories.income.map((cat)=>/*#__PURE__*/ _jsxs("div", {
className: "category-tag",
children: [
/*#__PURE__*/ _jsx("span", {
className: "cat-icon",
children: cat.icon
}),
/*#__PURE__*/ _jsx("span", {
className: "cat-name",
children: cat.name
}),
!cat.is_default && /*#__PURE__*/ _jsx("button", {
type: "button",
className: "icon-button icon-button--ghost cat-delete",
"aria-label": "删除".concat(cat.name),
onClick: ()=>handleDelete(cat.id),
children: /*#__PURE__*/ _jsx(Icon, {
name: "close",
size: 14
})
})
]
}, cat.id || cat.name))
})
]
}),
/*#__PURE__*/ _jsx("div", {
className: "card category-tip category-tip--refresh",
children: /*#__PURE__*/ _jsx("p", {
children: "默认分类无法删除,自定义分类å¯ä»¥åˆ é™¤ã€‚"
})
})
]
}),
modalOpen && /*#__PURE__*/ _jsx("div", {
className: "modal-overlay",
onClick: closeModal,
children: /*#__PURE__*/ _jsxs("div", {
className: "modal",
onClick: (e)=>e.stopPropagation(),
children: [
/*#__PURE__*/ _jsxs("div", {
className: "modal-header",
children: [
/*#__PURE__*/ _jsxs("div", {
children: [
/*#__PURE__*/ _jsx("p", {
className: "eyebrow",
children: "新增分类"
}),
/*#__PURE__*/ _jsx("h2", {
children: categoryType === "expense" ? "新建支出分类" : "新建收入分类"
})
]
}),
/*#__PURE__*/ _jsx("button", {
type: "button",
className: "icon-button icon-button--ghost",
"aria-label": "关闭弹窗",
onClick: closeModal,
children: /*#__PURE__*/ _jsx(Icon, {
name: "close",
size: 18
})
})
]
}),
formError && /*#__PURE__*/ _jsx("div", {
className: "error-msg",
children: formError
}),
/*#__PURE__*/ _jsxs("form", {
onSubmit: handleSubmit,
children: [
/*#__PURE__*/ _jsxs("div", {
className: "form-group",
children: [
/*#__PURE__*/ _jsx("label", {
htmlFor: "category-name",
children: "分类åç§°"
}),
/*#__PURE__*/ _jsx("input", {
id: "category-name",
type: "text",
value: formData.name,
onChange: (e)=>setFormData({
...formData,
name: e.target.value
}),
placeholder: "请输入分类åç§°",
required: true,
disabled: submitting
})
]
}),
/*#__PURE__*/ _jsxs("div", {
className: "form-group",
children: [
/*#__PURE__*/ _jsx("label", {
children: "选择图标"
}),
/*#__PURE__*/ _jsx("div", {
className: "icon-grid",
children: ICONS.map((icon)=>/*#__PURE__*/ _jsx("button", {
type: "button",
className: "icon-btn ".concat(formData.icon === icon ? "active" : ""),
onClick: ()=>setFormData({
...formData,
icon
}),
disabled: submitting,
children: icon
}, icon))
})
]
}),
/*#__PURE__*/ _jsx("div", {
className: "modal-actions",
children: /*#__PURE__*/ _jsx("button", {
type: "submit",
className: "btn btn-primary",
disabled: submitting,
children: submitting ? /*#__PURE__*/ _jsxs("span", {
className: "button-content",
children: [
/*#__PURE__*/ _jsx(Icon, {
name: "spinner",
size: 16,
className: "is-spinning"
}),
"ä¿å­˜ä¸­..."
]
}) : "ä¿å­˜"
})
})
]
})
]
})
})
]
});
}
n085undefined87130undefined132171undefined173259undefined261306undefined308350undefined352406undefined408466undefined18241838undefined18821890(0,next_router__WEBPACK_IMPORTED_MODULE_2__.useRouter)undefined19211928(0,_components_ToastProvider__WEBPACK_IMPORTED_MODULE_5__/* .useToast */ .p)undefined19571966(0,_components_ConfirmProvider__WEBPACK_IMPORTED_MODULE_6__/* .useConfirm */ .N)undefined20112018(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined21082115(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined21692176(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined22242231(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined22802287(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined23352342(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined23852392(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined24552463(0,react__WEBPACK_IMPORTED_MODULE_1__.useEffect)undefined24842493(0,_lib_api__WEBPACK_IMPORTED_MODULE_7__/* .isLoggedIn */ .jl)undefined27332745(0,_lib_api__WEBPACK_IMPORTED_MODULE_7__/* .getCategories */ .CP)undefined35963609(0,_lib_api__WEBPACK_IMPORTED_MODULE_7__/* .createCategory */ .k4)undefined43714384(0,_lib_api__WEBPACK_IMPORTED_MODULE_7__/* .deleteCategory */ .uu)undefined46664669(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined48484851(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined48534856_components_icons__WEBPACK_IMPORTED_MODULE_4__/* .Icon */ .Jundefined49564960(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined49624969_components_AppShell__WEBPACK_IMPORTED_MODULE_3__/* ["default"] */ .Zundefined51375140(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined52665270(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined52725280react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.Fragmentundefined53475351(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined55305534(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined57005704(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined58255828(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined60786081(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined63336336(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined66316635(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined68386841(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined68436846_components_icons__WEBPACK_IMPORTED_MODULE_4__/* .Icon */ .Jundefined73197322(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined75007504(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined76927695(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined79517954(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined82298232(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined86548657(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined86598662_components_icons__WEBPACK_IMPORTED_MODULE_4__/* .Icon */ .Jundefined91109114(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined92939297(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined94639467(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined95889591(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined98419844(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined1009610099(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined1039310397(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined1060010603(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined1060510608_components_icons__WEBPACK_IMPORTED_MODULE_4__/* .Icon */ .Jundefined1108111084(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined1126111265(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined1145311456(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined1171211715(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined1199011993(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined1241512418(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined1242012423_comp
/***/ }) Õ»^/***/ (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 */ Categories; }
/* 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 _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);
/* harmony import */ var _components_ConfirmProvider__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(8846);
const ICONS = [
"\uD83C\uDF5C",
"\uD83E\uDD58",
"\uD83E\uDD6C",
"\uD83E\uDD69",
"\uD83C\uDF54",
"\uD83C\uDF55",
"☕",
"\uD83C\uDF7A",
"\uD83E\uDDCB",
"\uD83C\uDF4E",
"\uD83C\uDF6A",
"\uD83C\uDF6B",
"\uD83D\uDE97",
"\uD83D\uDE95",
"\uD83D\uDE8C",
"\uD83D\uDE87",
"⛽",
"\uD83D\uDE99",
"\uD83D\uDEF5",
"✈ï¸",
"\uD83D\uDEA2",
"\uD83D\uDED2",
"\uD83D\uDC54",
"\uD83D\uDC55",
"\uD83D\uDC56",
"\uD83D\uDC5F",
"\uD83D\uDC53",
"\uD83E\uDDE2",
"\uD83D\uDC5C",
"\uD83C\uDF92",
"\uD83D\uDC8D",
"\uD83D\uDC84",
"\uD83C\uDFE0",
"\uD83C\uDFE2",
"\uD83C\uDFE6",
"\uD83C\uDFE5",
"\uD83C\uDFE8",
"\uD83C\uDFE9",
"\uD83D\uDD27",
"\uD83D\uDD28",
"\uD83D\uDECBï¸",
"\uD83D\uDCFA",
"\uD83D\uDCBB",
"\uD83D\uDCF1",
"\uD83D\uDCDD",
"\uD83D\uDCB0",
"\uD83C\uDF81",
"\uD83D\uDCC8",
"\uD83E\uDDE7",
"\uD83D\uDCB3",
"\uD83C\uDFAF",
"\uD83D\uDCDA",
"\uD83C\uDFB5",
"\uD83C\uDFAE",
"\uD83C\uDFC3",
"âš½",
"\uD83C\uDFCA",
"\uD83D\uDEB4",
"\uD83C\uDFAD",
"\uD83C\uDFA8",
"\uD83C\uDFAC",
"\uD83D\uDCF7",
"\uD83C\uDFA5",
"\uD83D\uDCFA",
"\uD83E\uDDF8",
"\uD83D\uDC15",
"\uD83D\uDC08",
"\uD83C\uDF38",
"\uD83C\uDF3A"
];
function Categories() {
const router = (0,next_router__WEBPACK_IMPORTED_MODULE_2__.useRouter)();
const { showToast } = (0,_components_ToastProvider__WEBPACK_IMPORTED_MODULE_5__/* .useToast */ .p)();
const { confirm } = (0,_components_ConfirmProvider__WEBPACK_IMPORTED_MODULE_6__/* .useConfirm */ .N)();
const [categories, setCategories] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)({
expense: [],
income: []
});
const [modalOpen, setModalOpen] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)(false);
const [categoryType, setCategoryType] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)("expense");
const [loading, setLoading] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)(true);
const [submitting, setSubmitting] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)(false);
const [formError, setFormError] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)("");
const [formData, setFormData] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)({
name: "",
icon: "\uD83D\uDCDD"
});
(0,react__WEBPACK_IMPORTED_MODULE_1__.useEffect)(()=>{
if (!(0,_lib_api__WEBPACK_IMPORTED_MODULE_7__/* .isLoggedIn */ .jl)()) {
router.push("/login");
return;
}
loadCategories();
}, [
router
]);
const loadCategories = async ()=>{
setLoading(true);
try {
const data = await (0,_lib_api__WEBPACK_IMPORTED_MODULE_7__/* .getCategories */ .CP)();
setCategories(data);
} catch (err) {
console.error(err);
showToast(err.message, {
tone: "error"
});
} finally{
setLoading(false);
}
};
const openAddModal = (type)=>{
setCategoryType(type);
setFormData({
name: "",
icon: "\uD83D\uDCDD"
});
setFormError("");
setModalOpen(true);
};
const closeModal = ()=>{
if (submitting) return;
setModalOpen(false);
setFormError("");
};
const handleSubmit = async (e)=>{
e.preventDefault();
if (!formData.name || !formData.icon) {
setFormError("请填写完整信æ¯");
return;
}
setSubmitting(true);
setFormError("");
try {
await (0,_lib_api__WEBPACK_IMPORTED_MODULE_7__/* .createCategory */ .k4)({
type: categoryType,
name: formData.name,
icon: formData.icon
});
closeModal();
showToast("分类已创建", {
tone: "success"
});
await loadCategories();
} catch (err) {
setFormError(err.message);
showToast(err.message, {
tone: "error"
});
} finally{
setSubmitting(false);
}
};
const handleDelete = async (id)=>{
const shouldDelete = await confirm({
title: "删除此分类?",
description: "默认分类ä¸å¯åˆ é™¤ï¼Œè‡ªå®šä¹‰åˆ†ç±»åˆ é™¤åŽå°†æ— æ³•æ¢å¤ã€‚",
confirmText: "删除"
});
if (!shouldDelete) return;
try {
await (0,_lib_api__WEBPACK_IMPORTED_MODULE_7__/* .deleteCategory */ .uu)(id);
showToast("分类已删除", {
tone: "success"
});
await loadCategories();
} catch (err) {
showToast(err.message, {
tone: "error"
});
}
};
const actions = /*#__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, {
title: "分类",
subtitle: "æ•´ç†å¸¸ç”¨åˆ†ç±»ä¸Žå›¾æ ‡",
actions: actions,
compactHeader: true,
children: [
loading ? /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "card loading-state",
children: "加载中..."
}) : /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)(react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.Fragment, {
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("section", {
className: "card category-section category-section--refresh",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "section-header",
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__.jsx)("button", {
type: "button",
className: "btn btn-secondary btn-inline",
onClick: ()=>openAddModal("expense"),
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("span", {
className: "button-content",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_components_icons__WEBPACK_IMPORTED_MODULE_4__/* .Icon */ .J, {
name: "plus",
size: 16
}),
"添加"
]
})
})
]
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "category-list category-list--chips",
children: categories.expense.map((cat)=>/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "category-tag",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("span", {
className: "cat-icon",
children: cat.icon
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("span", {
className: "cat-name",
children: cat.name
}),
!cat.is_default && /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("button", {
type: "button",
className: "icon-button icon-button--ghost cat-delete",
"aria-label": "删除".concat(cat.name),
onClick: ()=>handleDelete(cat.id),
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_components_icons__WEBPACK_IMPORTED_MODULE_4__/* .Icon */ .J, {
name: "close",
size: 14
})
})
]
}, cat.id || cat.name))
})
]
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("section", {
className: "card category-section category-section--refresh",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "section-header",
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__.jsx)("button", {
type: "button",
className: "btn btn-secondary btn-inline",
onClick: ()=>openAddModal("income"),
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("span", {
className: "button-content",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_components_icons__WEBPACK_IMPORTED_MODULE_4__/* .Icon */ .J, {
name: "plus",
size: 16
}),
"添加"
]
})
})
]
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "category-list category-list--chips",
children: categories.income.map((cat)=>/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "category-tag",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("span", {
className: "cat-icon",
children: cat.icon
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("span", {
className: "cat-name",
children: cat.name
}),
!cat.is_default && /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("button", {
type: "button",
className: "icon-button icon-button--ghost cat-delete",
"aria-label": "删除".concat(cat.name),
onClick: ()=>handleDelete(cat.id),
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_components_icons__WEBPACK_IMPORTED_MODULE_4__/* .Icon */ .J, {
name: "close",
size: 14
})
})
]
}, cat.id || cat.name))
})
]
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "card category-tip category-tip--refresh",
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("p", {
children: "默认分类无法删除,自定义分类å¯ä»¥åˆ é™¤ã€‚"
})
})
]
}),
modalOpen && /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "modal-overlay",
onClick: closeModal,
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "modal",
onClick: (e)=>e.stopPropagation(),
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "modal-header",
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: categoryType === "expense" ? "新建支出分类" : "新建收入分类"
})
]
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("button", {
type: "button",
className: "icon-button icon-button--ghost",
"aria-label": "关闭弹窗",
onClick: closeModal,
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_components_icons__WEBPACK_IMPORTED_MODULE_4__/* .Icon */ .J, {
name: "close",
size: 18
})
})
]
}),
formError && /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "error-msg",
children: formError
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("form", {
onSubmit: handleSubmit,
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "form-group",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("label", {
htmlFor: "category-name",
children: "分类åç§°"
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("input", {
id: "category-name",
type: "text",
value: formData.name,
onChange: (e)=>setFormData({
...formData,
name: e.target.value
}),
placeholder: "请输入分类åç§°",
required: true,
disabled: submitting
})
]
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "form-group",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("label", {
children: "选择图标"
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "icon-grid",
children: ICONS.map((icon)=>/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("button", {
type: "button",
className: "icon-btn ".concat(formData.icon === icon ? "active" : ""),
onClick: ()=>setFormData({
...formData,
icon
}),
disabled: submitting,
children: icon
}, icon))
})
]
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "modal-actions",
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("button", {
type: "submit",
className: "btn btn-primary",
disabled: submitting,
children: submitting ? /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("span", {
className: "button-content",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_components_icons__WEBPACK_IMPORTED_MODULE_4__/* .Icon */ .J, {
name: "spinner",
size: 16,
className: "is-spinning"
}),
"ä¿å­˜ä¸­..."
]
}) : "ä¿å­˜"
})
})
]
})
]
})
})
]
});
}
/***/ }) ÖÙ  ÚéÚ__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": function() { return /* binding */ Categories; }
/* 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 _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);
/* harmony import */ var _components_ConfirmProvider__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(8846);
ÛòKimport { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
import { useState, useEffect } from "react";
import { useRouter } from "next/router";
import { isLoggedIn, getCategories, createCategory, deleteCategory } from "../lib/api";
import AppShell from "../components/AppShell";
import { Icon } from "../components/icons";
import { useToast } from "../components/ToastProvider";
import { useConfirm } from "../components/ConfirmProvider";
const ICONS = [
"\uD83C\uDF5C",
"\uD83E\uDD58",
"\uD83E\uDD6C",
"\uD83E\uDD69",
"\uD83C\uDF54",
"\uD83C\uDF55",
"☕",
"\uD83C\uDF7A",
"\uD83E\uDDCB",
"\uD83C\uDF4E",
"\uD83C\uDF6A",
"\uD83C\uDF6B",
"\uD83D\uDE97",
"\uD83D\uDE95",
"\uD83D\uDE8C",
"\uD83D\uDE87",
"⛽",
"\uD83D\uDE99",
"\uD83D\uDEF5",
"✈ï¸",
"\uD83D\uDEA2",
"\uD83D\uDED2",
"\uD83D\uDC54",
"\uD83D\uDC55",
"\uD83D\uDC56",
"\uD83D\uDC5F",
"\uD83D\uDC53",
"\uD83E\uDDE2",
"\uD83D\uDC5C",
"\uD83C\uDF92",
"\uD83D\uDC8D",
"\uD83D\uDC84",
"\uD83C\uDFE0",
"\uD83C\uDFE2",
"\uD83C\uDFE6",
"\uD83C\uDFE5",
"\uD83C\uDFE8",
"\uD83C\uDFE9",
"\uD83D\uDD27",
"\uD83D\uDD28",
"\uD83D\uDECBï¸",
"\uD83D\uDCFA",
"\uD83D\uDCBB",
"\uD83D\uDCF1",
"\uD83D\uDCDD",
"\uD83D\uDCB0",
"\uD83C\uDF81",
"\uD83D\uDCC8",
"\uD83E\uDDE7",
"\uD83D\uDCB3",
"\uD83C\uDFAF",
"\uD83D\uDCDA",
"\uD83C\uDFB5",
"\uD83C\uDFAE",
"\uD83C\uDFC3",
"âš½",
"\uD83C\uDFCA",
"\uD83D\uDEB4",
"\uD83C\uDFAD",
"\uD83C\uDFA8",
"\uD83C\uDFAC",
"\uD83D\uDCF7",
"\uD83C\uDFA5",
"\uD83D\uDCFA",
"\uD83E\uDDF8",
"\uD83D\uDC15",
"\uD83D\uDC08",
"\uD83C\uDF38",
"\uD83C\uDF3A"
];
export default function Categories() {
const router = useRouter();
const { showToast } = useToast();
const { confirm } = useConfirm();
const [categories, setCategories] = useState({
expense: [],
income: []
});
const [modalOpen, setModalOpen] = useState(false);
const [categoryType, setCategoryType] = useState("expense");
const [loading, setLoading] = useState(true);
const [submitting, setSubmitting] = useState(false);
const [formError, setFormError] = useState("");
const [formData, setFormData] = useState({
name: "",
icon: "\uD83D\uDCDD"
});
useEffect(()=>{
if (!isLoggedIn()) {
router.push("/login");
return;
}
loadCategories();
}, [
router
]);
const loadCategories = async ()=>{
setLoading(true);
try {
const data = await getCategories();
setCategories(data);
} catch (err) {
console.error(err);
showToast(err.message, {
tone: "error"
});
} finally{
setLoading(false);
}
};
const openAddModal = (type)=>{
setCategoryType(type);
setFormData({
name: "",
icon: "\uD83D\uDCDD"
});
setFormError("");
setModalOpen(true);
};
const closeModal = ()=>{
if (submitting) return;
setModalOpen(false);
setFormError("");
};
const handleSubmit = async (e)=>{
e.preventDefault();
if (!formData.name || !formData.icon) {
setFormError("请填写完整信æ¯");
return;
}
setSubmitting(true);
setFormError("");
try {
await createCategory({
type: categoryType,
name: formData.name,
icon: formData.icon
});
closeModal();
showToast("分类已创建", {
tone: "success"
});
await loadCategories();
} catch (err) {
setFormError(err.message);
showToast(err.message, {
tone: "error"
});
} finally{
setSubmitting(false);
}
};
const handleDelete = async (id)=>{
const shouldDelete = await confirm({
title: "删除此分类?",
description: "默认分类ä¸å¯åˆ é™¤ï¼Œè‡ªå®šä¹‰åˆ†ç±»åˆ é™¤åŽå°†æ— æ³•æ¢å¤ã€‚",
confirmText: "删除"
});
if (!shouldDelete) return;
try {
await deleteCategory(id);
showToast("分类已删除", {
tone: "success"
});
await loadCategories();
} catch (err) {
showToast(err.message, {
tone: "error"
});
}
};
const actions = /*#__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, {
title: "分类",
subtitle: "æ•´ç†å¸¸ç”¨åˆ†ç±»ä¸Žå›¾æ ‡",
actions: actions,
compactHeader: true,
children: [
loading ? /*#__PURE__*/ _jsx("div", {
className: "card loading-state",
children: "加载中..."
}) : /*#__PURE__*/ _jsxs(_Fragment, {
children: [
/*#__PURE__*/ _jsxs("section", {
className: "card category-section category-section--refresh",
children: [
/*#__PURE__*/ _jsxs("div", {
className: "section-header",
children: [
/*#__PURE__*/ _jsxs("div", {
children: [
/*#__PURE__*/ _jsx("p", {
className: "eyebrow",
children: "支出分类"
}),
/*#__PURE__*/ _jsx("h2", {
children: "支出"
})
]
}),
/*#__PURE__*/ _jsx("button", {
type: "button",
className: "btn btn-secondary btn-inline",
onClick: ()=>openAddModal("expense"),
children: /*#__PURE__*/ _jsxs("span", {
className: "button-content",
children: [
/*#__PURE__*/ _jsx(Icon, {
name: "plus",
size: 16
}),
"添加"
]
})
})
]
}),
/*#__PURE__*/ _jsx("div", {
className: "category-list category-list--chips",
children: categories.expense.map((cat)=>/*#__PURE__*/ _jsxs("div", {
className: "category-tag",
children: [
/*#__PURE__*/ _jsx("span", {
className: "cat-icon",
children: cat.icon
}),
/*#__PURE__*/ _jsx("span", {
className: "cat-name",
children: cat.name
}),
!cat.is_default && /*#__PURE__*/ _jsx("button", {
type: "button",
className: "icon-button icon-button--ghost cat-delete",
"aria-label": "删除".concat(cat.name),
onClick: ()=>handleDelete(cat.id),
children: /*#__PURE__*/ _jsx(Icon, {
name: "close",
size: 14
})
})
]
}, cat.id || cat.name))
})
]
}),
/*#__PURE__*/ _jsxs("section", {
className: "card category-section category-section--refresh",
children: [
/*#__PURE__*/ _jsxs("div", {
className: "section-header",
children: [
/*#__PURE__*/ _jsxs("div", {
children: [
/*#__PURE__*/ _jsx("p", {
className: "eyebrow",
children: "收入分类"
}),
/*#__PURE__*/ _jsx("h2", {
children: "æ”¶å…¥"
})
]
}),
/*#__PURE__*/ _jsx("button", {
type: "button",
className: "btn btn-secondary btn-inline",
onClick: ()=>openAddModal("income"),
children: /*#__PURE__*/ _jsxs("span", {
className: "button-content",
children: [
/*#__PURE__*/ _jsx(Icon, {
name: "plus",
size: 16
}),
"添加"
]
})
})
]
}),
/*#__PURE__*/ _jsx("div", {
className: "category-list category-list--chips",
children: categories.income.map((cat)=>/*#__PURE__*/ _jsxs("div", {
className: "category-tag",
children: [
/*#__PURE__*/ _jsx("span", {
className: "cat-icon",
children: cat.icon
}),
/*#__PURE__*/ _jsx("span", {
className: "cat-name",
children: cat.name
}),
!cat.is_default && /*#__PURE__*/ _jsx("button", {
type: "button",
className: "icon-button icon-button--ghost cat-delete",
"aria-label": "删除".concat(cat.name),
onClick: ()=>handleDelete(cat.id),
children: /*#__PURE__*/ _jsx(Icon, {
name: "close",
size: 14
})
})
]
}, cat.id || cat.name))
})
]
}),
/*#__PURE__*/ _jsx("div", {
className: "card category-tip category-tip--refresh",
children: /*#__PURE__*/ _jsx("p", {
children: "默认分类无法删除,自定义分类å¯ä»¥åˆ é™¤ã€‚"
})
})
]
}),
modalOpen && /*#__PURE__*/ _jsx("div", {
className: "modal-overlay",
onClick: closeModal,
children: /*#__PURE__*/ _jsxs("div", {
className: "modal",
onClick: (e)=>e.stopPropagation(),
children: [
/*#__PURE__*/ _jsxs("div", {
className: "modal-header",
children: [
/*#__PURE__*/ _jsxs("div", {
children: [
/*#__PURE__*/ _jsx("p", {
className: "eyebrow",
children: "新增分类"
}),
/*#__PURE__*/ _jsx("h2", {
children: categoryType === "expense" ? "新建支出分类" : "新建收入分类"
})
]
}),
/*#__PURE__*/ _jsx("button", {
type: "button",
className: "icon-button icon-button--ghost",
"aria-label": "关闭弹窗",
onClick: closeModal,
children: /*#__PURE__*/ _jsx(Icon, {
name: "close",
size: 18
})
})
]
}),
formError && /*#__PURE__*/ _jsx("div", {
className: "error-msg",
children: formError
}),
/*#__PURE__*/ _jsxs("form", {
onSubmit: handleSubmit,
children: [
/*#__PURE__*/ _jsxs("div", {
className: "form-group",
children: [
/*#__PURE__*/ _jsx("label", {
htmlFor: "category-name",
children: "分类åç§°"
}),
/*#__PURE__*/ _jsx("input", {
id: "category-name",
type: "text",
value: formData.name,
onChange: (e)=>setFormData({
...formData,
name: e.target.value
}),
placeholder: "请输入分类åç§°",
required: true,
disabled: submitting
})
]
}),
/*#__PURE__*/ _jsxs("div", {
className: "form-group",
children: [
/*#__PURE__*/ _jsx("label", {
children: "选择图标"
}),
/*#__PURE__*/ _jsx("div", {
className: "icon-grid",
children: ICONS.map((icon)=>/*#__PURE__*/ _jsx("button", {
type: "button",
className: "icon-btn ".concat(formData.icon === icon ? "active" : ""),
onClick: ()=>setFormData({
...formData,
icon
}),
disabled: submitting,
children: icon
}, icon))
})
]
}),
/*#__PURE__*/ _jsx("div", {
className: "modal-actions",
children: /*#__PURE__*/ _jsx("button", {
type: "submit",
className: "btn btn-primary",
disabled: submitting,
children: submitting ? /*#__PURE__*/ _jsxs("span", {
className: "button-content",
children: [
/*#__PURE__*/ _jsx(Icon, {
name: "spinner",
size: 16,
className: "is-spinning"
}),
"ä¿å­˜ä¸­..."
]
}) : "ä¿å­˜"
})
})
]
})
]
})
})
]
});
}
n085undefined87130undefined132171undefined173259undefined261306undefined308350undefined352406undefined408466undefined18241838undefined18821890(0,next_router__WEBPACK_IMPORTED_MODULE_2__.useRouter)undefined19211928(0,_components_ToastProvider__WEBPACK_IMPORTED_MODULE_5__/* .useToast */ .p)undefined19571966(0,_components_ConfirmProvider__WEBPACK_IMPORTED_MODULE_6__/* .useConfirm */ .N)undefined20112018(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined21082115(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined21692176(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined22242231(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined22802287(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined23352342(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined23852392(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined24552463(0,react__WEBPACK_IMPORTED_MODULE_1__.useEffect)undefined24842493(0,_lib_api__WEBPACK_IMPORTED_MODULE_7__/* .isLoggedIn */ .jl)undefined27332745(0,_lib_api__WEBPACK_IMPORTED_MODULE_7__/* .getCategories */ .CP)undefined35963609(0,_lib_api__WEBPACK_IMPORTED_MODULE_7__/* .createCategory */ .k4)undefined43714384(0,_lib_api__WEBPACK_IMPORTED_MODULE_7__/* .deleteCategory */ .uu)undefined46664669(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined48484851(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined48534856_components_icons__WEBPACK_IMPORTED_MODULE_4__/* .Icon */ .Jundefined49564960(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined49624969_components_AppShell__WEBPACK_IMPORTED_MODULE_3__/* ["default"] */ .Zundefined51375140(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined52665270(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined52725280react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.Fragmentundefined53475351(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined55305534(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined57005704(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined58255828(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined60786081(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined63336336(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined66316635(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined68386841(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined68436846_components_icons__WEBPACK_IMPORTED_MODULE_4__/* .Icon */ .Jundefined73197322(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined75007504(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined76927695(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined79517954(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined82298232(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined86548657(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined86598662_components_icons__WEBPACK_IMPORTED_MODULE_4__/* .Icon */ .Jundefined91109114(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined92939297(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined94639467(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined95889591(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined98419844(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined1009610099(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined1039310397(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined1060010603(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined1060510608_components_icons__WEBPACK_IMPORTED_MODULE_4__/* .Icon */ .Jundefined1108111084(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined1126111265(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined1145311456(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined1171211715(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined1199011993(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined1241512418(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined1242012423_comp
/***/ 1163:
 g«webpack/lib/util/registerExternalSerializerœwebpack-sources/ConcatSource«webpack/lib/util/registerExternalSerializer™webpack-sources/RawSourceJ/***/ (function(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/RawSource1module.exports = require('./dist/client/router')
`/ªmodule.exports = __webpack_require__(3079)†buffer†source„size„maps„hash+module.exports = __webpack_require__(3079)
”{"finalSource":true}ƒmapbufferedMap ReplaceSourceRawSource1module.exports = require('./dist/client/router')
6047module.exports = __webpack_require__(3079)undefined
/***/ }) Ê/***/ (function(module, __unused_webpack_exports, __webpack_require__) {
module.exports = __webpack_require__(3079)
/***/ }) ËÎ ÏJ/***/ (function(module, __unused_webpack_exports, __webpack_require__) {
Ð1module.exports = require('./dist/client/router')
6047module.exports = __webpack_require__(3079)undefinedÑÒ
},
«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, [888,774,179], function() { return __webpack_exec__(5461); });
var __webpack_exports__ = __webpack_require__.O();
_N_E = __webpack_exports__;
}
]); þ(self["webpackChunk_N_E"] = self["webpackChunk_N_E"] || []).push([[161],{
/***/ 5461:
/***/ (function(__unused_webpack_module, __unused_webpack_exports, __webpack_require__) {
(window.__NEXT_P = window.__NEXT_P || []).push([
"/categories",
function () {
return __webpack_require__(2874);
}
]);
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)
});
}
/***/ }),
/***/ 2874:
/***/ (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 */ Categories; }
/* 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 _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);
/* harmony import */ var _components_ConfirmProvider__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(8846);
const ICONS = [
"\uD83C\uDF5C",
"\uD83E\uDD58",
"\uD83E\uDD6C",
"\uD83E\uDD69",
"\uD83C\uDF54",
"\uD83C\uDF55",
"☕",
"\uD83C\uDF7A",
"\uD83E\uDDCB",
"\uD83C\uDF4E",
"\uD83C\uDF6A",
"\uD83C\uDF6B",
"\uD83D\uDE97",
"\uD83D\uDE95",
"\uD83D\uDE8C",
"\uD83D\uDE87",
"⛽",
"\uD83D\uDE99",
"\uD83D\uDEF5",
"✈ï¸",
"\uD83D\uDEA2",
"\uD83D\uDED2",
"\uD83D\uDC54",
"\uD83D\uDC55",
"\uD83D\uDC56",
"\uD83D\uDC5F",
"\uD83D\uDC53",
"\uD83E\uDDE2",
"\uD83D\uDC5C",
"\uD83C\uDF92",
"\uD83D\uDC8D",
"\uD83D\uDC84",
"\uD83C\uDFE0",
"\uD83C\uDFE2",
"\uD83C\uDFE6",
"\uD83C\uDFE5",
"\uD83C\uDFE8",
"\uD83C\uDFE9",
"\uD83D\uDD27",
"\uD83D\uDD28",
"\uD83D\uDECBï¸",
"\uD83D\uDCFA",
"\uD83D\uDCBB",
"\uD83D\uDCF1",
"\uD83D\uDCDD",
"\uD83D\uDCB0",
"\uD83C\uDF81",
"\uD83D\uDCC8",
"\uD83E\uDDE7",
"\uD83D\uDCB3",
"\uD83C\uDFAF",
"\uD83D\uDCDA",
"\uD83C\uDFB5",
"\uD83C\uDFAE",
"\uD83C\uDFC3",
"âš½",
"\uD83C\uDFCA",
"\uD83D\uDEB4",
"\uD83C\uDFAD",
"\uD83C\uDFA8",
"\uD83C\uDFAC",
"\uD83D\uDCF7",
"\uD83C\uDFA5",
"\uD83D\uDCFA",
"\uD83E\uDDF8",
"\uD83D\uDC15",
"\uD83D\uDC08",
"\uD83C\uDF38",
"\uD83C\uDF3A"
];
function Categories() {
const router = (0,next_router__WEBPACK_IMPORTED_MODULE_2__.useRouter)();
const { showToast } = (0,_components_ToastProvider__WEBPACK_IMPORTED_MODULE_5__/* .useToast */ .p)();
const { confirm } = (0,_components_ConfirmProvider__WEBPACK_IMPORTED_MODULE_6__/* .useConfirm */ .N)();
const [categories, setCategories] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)({
expense: [],
income: []
});
const [modalOpen, setModalOpen] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)(false);
const [categoryType, setCategoryType] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)("expense");
const [loading, setLoading] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)(true);
const [submitting, setSubmitting] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)(false);
const [formError, setFormError] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)("");
const [formData, setFormData] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)({
name: "",
icon: "\uD83D\uDCDD"
});
(0,react__WEBPACK_IMPORTED_MODULE_1__.useEffect)(()=>{
if (!(0,_lib_api__WEBPACK_IMPORTED_MODULE_7__/* .isLoggedIn */ .jl)()) {
router.push("/login");
return;
}
loadCategories();
}, [
router
]);
const loadCategories = async ()=>{
setLoading(true);
try {
const data = await (0,_lib_api__WEBPACK_IMPORTED_MODULE_7__/* .getCategories */ .CP)();
setCategories(data);
} catch (err) {
console.error(err);
showToast(err.message, {
tone: "error"
});
} finally{
setLoading(false);
}
};
const openAddModal = (type)=>{
setCategoryType(type);
setFormData({
name: "",
icon: "\uD83D\uDCDD"
});
setFormError("");
setModalOpen(true);
};
const closeModal = ()=>{
if (submitting) return;
setModalOpen(false);
setFormError("");
};
const handleSubmit = async (e)=>{
e.preventDefault();
if (!formData.name || !formData.icon) {
setFormError("请填写完整信æ¯");
return;
}
setSubmitting(true);
setFormError("");
try {
await (0,_lib_api__WEBPACK_IMPORTED_MODULE_7__/* .createCategory */ .k4)({
type: categoryType,
name: formData.name,
icon: formData.icon
});
closeModal();
showToast("分类已创建", {
tone: "success"
});
await loadCategories();
} catch (err) {
setFormError(err.message);
showToast(err.message, {
tone: "error"
});
} finally{
setSubmitting(false);
}
};
const handleDelete = async (id)=>{
const shouldDelete = await confirm({
title: "删除此分类?",
description: "默认分类ä¸å¯åˆ é™¤ï¼Œè‡ªå®šä¹‰åˆ†ç±»åˆ é™¤åŽå°†æ— æ³•æ¢å¤ã€‚",
confirmText: "删除"
});
if (!shouldDelete) return;
try {
await (0,_lib_api__WEBPACK_IMPORTED_MODULE_7__/* .deleteCategory */ .uu)(id);
showToast("分类已删除", {
tone: "success"
});
await loadCategories();
} catch (err) {
showToast(err.message, {
tone: "error"
});
}
};
const actions = /*#__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, {
title: "分类",
subtitle: "æ•´ç†å¸¸ç”¨åˆ†ç±»ä¸Žå›¾æ ‡",
actions: actions,
compactHeader: true,
children: [
loading ? /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "card loading-state",
children: "加载中..."
}) : /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)(react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.Fragment, {
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("section", {
className: "card category-section category-section--refresh",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "section-header",
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__.jsx)("button", {
type: "button",
className: "btn btn-secondary btn-inline",
onClick: ()=>openAddModal("expense"),
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("span", {
className: "button-content",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_components_icons__WEBPACK_IMPORTED_MODULE_4__/* .Icon */ .J, {
name: "plus",
size: 16
}),
"添加"
]
})
})
]
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "category-list category-list--chips",
children: categories.expense.map((cat)=>/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "category-tag",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("span", {
className: "cat-icon",
children: cat.icon
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("span", {
className: "cat-name",
children: cat.name
}),
!cat.is_default && /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("button", {
type: "button",
className: "icon-button icon-button--ghost cat-delete",
"aria-label": "删除".concat(cat.name),
onClick: ()=>handleDelete(cat.id),
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_components_icons__WEBPACK_IMPORTED_MODULE_4__/* .Icon */ .J, {
name: "close",
size: 14
})
})
]
}, cat.id || cat.name))
})
]
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("section", {
className: "card category-section category-section--refresh",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "section-header",
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__.jsx)("button", {
type: "button",
className: "btn btn-secondary btn-inline",
onClick: ()=>openAddModal("income"),
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("span", {
className: "button-content",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_components_icons__WEBPACK_IMPORTED_MODULE_4__/* .Icon */ .J, {
name: "plus",
size: 16
}),
"添加"
]
})
})
]
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "category-list category-list--chips",
children: categories.income.map((cat)=>/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "category-tag",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("span", {
className: "cat-icon",
children: cat.icon
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("span", {
className: "cat-name",
children: cat.name
}),
!cat.is_default && /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("button", {
type: "button",
className: "icon-button icon-button--ghost cat-delete",
"aria-label": "删除".concat(cat.name),
onClick: ()=>handleDelete(cat.id),
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_components_icons__WEBPACK_IMPORTED_MODULE_4__/* .Icon */ .J, {
name: "close",
size: 14
})
})
]
}, cat.id || cat.name))
})
]
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "card category-tip category-tip--refresh",
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("p", {
children: "默认分类无法删除,自定义分类å¯ä»¥åˆ é™¤ã€‚"
})
})
]
}),
modalOpen && /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "modal-overlay",
onClick: closeModal,
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "modal",
onClick: (e)=>e.stopPropagation(),
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "modal-header",
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: categoryType === "expense" ? "新建支出分类" : "新建收入分类"
})
]
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("button", {
type: "button",
className: "icon-button icon-button--ghost",
"aria-label": "关闭弹窗",
onClick: closeModal,
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_components_icons__WEBPACK_IMPORTED_MODULE_4__/* .Icon */ .J, {
name: "close",
size: 18
})
})
]
}),
formError && /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "error-msg",
children: formError
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("form", {
onSubmit: handleSubmit,
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "form-group",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("label", {
htmlFor: "category-name",
children: "分类åç§°"
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("input", {
id: "category-name",
type: "text",
value: formData.name,
onChange: (e)=>setFormData({
...formData,
name: e.target.value
}),
placeholder: "请输入分类åç§°",
required: true,
disabled: submitting
})
]
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "form-group",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("label", {
children: "选择图标"
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "icon-grid",
children: ICONS.map((icon)=>/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("button", {
type: "button",
className: "icon-btn ".concat(formData.icon === icon ? "active" : ""),
onClick: ()=>setFormData({
...formData,
icon
}),
disabled: submitting,
children: icon
}, icon))
})
]
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "modal-actions",
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("button", {
type: "submit",
className: "btn btn-primary",
disabled: submitting,
children: submitting ? /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("span", {
className: "button-content",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_components_icons__WEBPACK_IMPORTED_MODULE_4__/* .Icon */ .J, {
name: "spinner",
size: 16,
className: "is-spinning"
}),
"ä¿å­˜ä¸­..."
]
}) : "ä¿å­˜"
})
})
]
})
]
})
})
]
});
}
/***/ }),
/***/ 1163:
/***/ (function(module, __unused_webpack_exports, __webpack_require__) {
module.exports = __webpack_require__(3079)
/***/ })
},
/******/ function(__webpack_require__) { // webpackRuntimeModules
/******/ var __webpack_exec__ = function(moduleId) { return __webpack_require__(__webpack_require__.s = moduleId); }
/******/ __webpack_require__.O(0, [888,774,179], function() { return __webpack_exec__(5461); });
/******/ var __webpack_exports__ = __webpack_require__.O();
/******/ _N_E = __webpack_exports__;
/******/ }
]); ÷Û `=ÜW(self["webpackChunk_N_E"] = self["webpackChunk_N_E"] || []).push([[161],{
/***/ 5461:
Û[/***/ (function(__unused_webpack_module, __unused_webpack_exports, __webpack_require__) {
Ü'
(window.__NEXT_P = window.__NEXT_P || []).push([
"/categories",
function () {
return require("private-next-pages/categories.js");
}
]);
if(module.hot) {
module.hot.dispose(function () {
window.__NEXT_P.push(["/categories"])
});
}
Z110116__webpack_require__undefined1181512874undefined178187falseundefined190291{}undefinedô
/***/ })ó,
/***/ 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îúî,
/***/ 610:
ÑûÑ/* 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êöê,
/***/ 2874:
Í÷ÍÎÏ^ýÿÿÐéõé,
/***/ 1163:
ÌJ/***/ (function(module, __unused_webpack_exports, __webpack_require__) {
Í1module.exports = require('./dist/client/router')
6047module.exports = __webpack_require__(3079)undefinedåñå
},
!PrefixSourceConcatSourceRawSource9function(__webpack_require__) { // webpackRuntimeModules
â÷var __webpack_exec__ = function(moduleId) { return __webpack_require__(__webpack_require__.s = moduleId); }
__webpack_require__.O(0, [888,774,179], function() { return __webpack_exec__(5461); });
var __webpack_exports__ = __webpack_require__.O();
á_N_E = __webpack_exports__;
}
/******/ RawSource]); š´«webpack/lib/util/registerExternalSerializerœwebpack-sources/ConcatSource`«webpack/lib/util/registerExternalSerializer™webpack-sources/RawSourceW(self["webpackChunk_N_E"] = self["webpackChunk_N_E"] || []).push([[405],{
/***/ 5557:
«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([
"/",
function () {
return require("private-next-pages/index.js");
}
]);
if(module.hot) {
module.hot.dispose(function () {
window.__NEXT_P.push(["/"])
});
}
bdjlDˆ£¬¯
“__webpack_require__„9828…false{}†buffer†source„size„maps„hash¢
(window.__NEXT_P = window.__NEXT_P || []).push([
"/",
function () {
return __webpack_require__(9828);
}
]);
if(false) {}
”{"finalSource":true}ƒmapbufferedMap ReplaceSourceRawSource
(window.__NEXT_P = window.__NEXT_P || []).push([
"/",
function () {
return require("private-next-pages/index.js");
}
]);
if(module.hot) {
module.hot.dispose(function () {
window.__NEXT_P.push(["/"])
});
}
Z100106__webpack_require__undefined1081369828undefined163172falseundefined175266{}undefined
/***/ }) †buffer†source„size„maps„hashConcatSourceRawSource[/***/ (function(__unused_webpack_module, __unused_webpack_exports, __webpack_require__) {
ReplaceSourceRawSource
(window.__NEXT_P = window.__NEXT_P || []).push([
"/",
function () {
return require("private-next-pages/index.js");
}
]);
if(module.hot) {
module.hot.dispose(function () {
window.__NEXT_P.push(["/"])
});
}
Z100106__webpack_require__undefined1081369828undefined163172falseundefined175266{}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
]
});
}
/***/ }) ”{"finalSource":true}ƒmapbufferedMap  í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ãä,
/***/ 7063:
 ò«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 */ S: function() { return /* binding */ DEFAULT_CATEGORIES; }
/* harmony export */ });
«webpack/lib/util/registerExternalSerializerwebpack-sources/ReplaceSourceZexport const DEFAULT_CATEGORIES = {
expense: [
{
name: "é¤é¥®",
icon: "\uD83C\uDF5C"
},
{
name: "ä¹°èœ",
icon: "\uD83E\uDD6C"
},
{
name: "交通",
icon: "\uD83D\uDE97"
},
{
name: "购物",
icon: "\uD83D\uDED2"
},
{
name: "娱ä¹",
icon: "\uD83C\uDFAE"
},
{
name: "房租",
icon: "\uD83C\uDFE0"
},
{
name: "房贷",
icon: "\uD83C\uDFE6"
},
{
name: "亲情å¡",
icon: "\uD83D\uDCB3"
},
{
name: "水电",
icon: "\uD83D\uDCA7"
},
{
name: "è¡£æœ",
icon: "\uD83D\uDC54"
},
{
name: "通讯",
icon: "\uD83D\uDCF1"
},
{
name: "日用å“",
icon: "\uD83E\uDDF4"
},
{
name: "美妆",
icon: "\uD83D\uDC84"
},
{
name: "医疗",
icon: "\uD83C\uDFE5"
},
{
name: "养娃",
icon: "\uD83D\uDC76"
},
{
name: "家电",
icon: "\uD83D\uDCFA"
},
{
name: "å®¶å…·",
icon: "\uD83D\uDECBï¸"
},
{
name: "装修",
icon: "\uD83D\uDD28"
},
{
name: "宠物",
icon: "\uD83D\uDC15"
},
{
name: "å…¶ä»–",
icon: "\uD83D\uDCDD"
}
],
income: [
{
name: "工资",
icon: "\uD83D\uDCB0"
},
{
name: "奖金",
icon: "\uD83C\uDF81"
},
{
name: "ç†è´¢",
icon: "\uD83D\uDCC8"
},
{
name: "红包",
icon: "\uD83E\uDDE7"
},
{
name: "å…¶ä»–",
icon: "\uD83D\uDCDD"
}
]
};
†buffer†source„size„maps„hashConcatSourceRawSource­/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ S: function() { return /* binding */ DEFAULT_CATEGORIES; }
/* harmony export */ });
ReplaceSourceRawSourceZexport const DEFAULT_CATEGORIES = {
expense: [
{
name: "é¤é¥®",
icon: "\uD83C\uDF5C"
},
{
name: "ä¹°èœ",
icon: "\uD83E\uDD6C"
},
{
name: "交通",
icon: "\uD83D\uDE97"
},
{
name: "购物",
icon: "\uD83D\uDED2"
},
{
name: "娱ä¹",
icon: "\uD83C\uDFAE"
},
{
name: "房租",
icon: "\uD83C\uDFE0"
},
{
name: "房贷",
icon: "\uD83C\uDFE6"
},
{
name: "亲情å¡",
icon: "\uD83D\uDCB3"
},
{
name: "水电",
icon: "\uD83D\uDCA7"
},
{
name: "è¡£æœ",
icon: "\uD83D\uDC54"
},
{
name: "通讯",
icon: "\uD83D\uDCF1"
},
{
name: "日用å“",
icon: "\uD83E\uDDF4"
},
{
name: "美妆",
icon: "\uD83D\uDC84"
},
{
name: "医疗",
icon: "\uD83C\uDFE5"
},
{
name: "养娃",
icon: "\uD83D\uDC76"
},
{
name: "家电",
icon: "\uD83D\uDCFA"
},
{
name: "å®¶å…·",
icon: "\uD83D\uDECBï¸"
},
{
name: "装修",
icon: "\uD83D\uDD28"
},
{
name: "宠物",
icon: "\uD83D\uDC15"
},
{
name: "å…¶ä»–",
icon: "\uD83D\uDCDD"
}
],
income: [
{
name: "工资",
icon: "\uD83D\uDCB0"
},
{
name: "奖金",
icon: "\uD83C\uDF81"
},
{
name: "ç†è´¢",
icon: "\uD83D\uDCC8"
},
{
name: "红包",
icon: "\uD83E\uDDE7"
},
{
name: "å…¶ä»–",
icon: "\uD83D\uDCDD"
}
]
};
 06undefined
/***/ }) × ØëØ­/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ S: function() { return /* binding */ DEFAULT_CATEGORIES; }
/* harmony export */ });
ÙZexport const DEFAULT_CATEGORIES = {
expense: [
{
name: "é¤é¥®",
icon: "\uD83C\uDF5C"
},
{
name: "ä¹°èœ",
icon: "\uD83E\uDD6C"
},
{
name: "交通",
icon: "\uD83D\uDE97"
},
{
name: "购物",
icon: "\uD83D\uDED2"
},
{
name: "娱ä¹",
icon: "\uD83C\uDFAE"
},
{
name: "房租",
icon: "\uD83C\uDFE0"
},
{
name: "房贷",
icon: "\uD83C\uDFE6"
},
{
name: "亲情å¡",
icon: "\uD83D\uDCB3"
},
{
name: "水电",
icon: "\uD83D\uDCA7"
},
{
name: "è¡£æœ",
icon: "\uD83D\uDC54"
},
{
name: "通讯",
icon: "\uD83D\uDCF1"
},
{
name: "日用å“",
icon: "\uD83E\uDDF4"
},
{
name: "美妆",
icon: "\uD83D\uDC84"
},
{
name: "医疗",
icon: "\uD83C\uDFE5"
},
{
name: "养娃",
icon: "\uD83D\uDC76"
},
{
name: "家电",
icon: "\uD83D\uDCFA"
},
{
name: "å®¶å…·",
icon: "\uD83D\uDECBï¸"
},
{
name: "装修",
icon: "\uD83D\uDD28"
},
{
name: "宠物",
icon: "\uD83D\uDC15"
},
{
name: "å…¶ä»–",
icon: "\uD83D\uDCDD"
}
],
income: [
{
name: "工资",
icon: "\uD83D\uDCB0"
},
{
name: "奖金",
icon: "\uD83C\uDF81"
},
{
name: "ç†è´¢",
icon: "\uD83D\uDCC8"
},
{
name: "红包",
icon: "\uD83E\uDDE7"
},
{
name: "å…¶ä»–",
icon: "\uD83D\uDCDD"
}
]
};
 06undefinedÚÛ,
/***/ 9828:
 šß«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 8«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 */ Home; }
/* 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_8__ = __webpack_require__(610);
/* harmony import */ var _lib_categories__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(7063);
/* harmony import */ var _components_AppShell__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(6248);
/* harmony import */ var _components_icons__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(9415);
/* harmony import */ var _components_ToastProvider__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(5546);
/* harmony import */ var _components_ConfirmProvider__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(8846);
«webpack/lib/util/registerExternalSerializerwebpack-sources/ReplaceSourceŸ‚import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { useState, useEffect } from "react";
import { useRouter } from "next/router";
import { isLoggedIn, getRecords, getStats, createRecord, updateRecord, deleteRecord } from "../lib/api";
import { DEFAULT_CATEGORIES } from "../lib/categories";
import AppShell from "../components/AppShell";
import { Icon } from "../components/icons";
import { useToast } from "../components/ToastProvider";
import { useConfirm } from "../components/ConfirmProvider";
export default function Home() {
const router = useRouter();
const { showToast } = useToast();
const { confirm } = useConfirm();
const [currentMonth, setCurrentMonth] = useState(new Date());
const [typeFilter, setTypeFilter] = useState("all");
const [records, setRecords] = useState([]);
const [stats, setStats] = useState({
income: 0,
expense: 0,
balance: 0
});
const [modalOpen, setModalOpen] = useState(false);
const [editingRecord, setEditingRecord] = useState(null);
const [loading, setLoading] = useState(true);
const [submitting, setSubmitting] = useState(false);
const [deleting, setDeleting] = useState(false);
const [formError, setFormError] = useState("");
const [formData, setFormData] = useState({
type: "expense",
amount: "",
category: "",
date: new Date().toISOString().split("T")[0],
time: new Date().toTimeString().slice(0, 5),
note: ""
});
useEffect(()=>{
if (!isLoggedIn()) {
router.push("/login");
return;
}
loadData();
}, [
router,
currentMonth,
typeFilter
]);
const getMonthStr = (date)=>"".concat(date.getFullYear(), "-").concat(String(date.getMonth() + 1).padStart(2, "0"));
const formatMoney = (num)=>(num || 0).toFixed(2);
const formatMonth = (date)=>"".concat(date.getFullYear(), "年").concat(date.getMonth() + 1, "月");
const resetForm = ()=>({
type: "expense",
amount: "",
category: "",
date: new Date().toISOString().split("T")[0],
time: new Date().toTimeString().slice(0, 5),
note: ""
});
const loadData = async ()=>{
setLoading(true);
try {
const [recordsData, statsData] = await Promise.all([
getRecords(getMonthStr(currentMonth), typeFilter),
getStats(getMonthStr(currentMonth))
]);
setRecords(recordsData);
setStats(statsData);
} catch (err) {
console.error(err);
showToast(err.message, {
tone: "error"
});
} finally{
setLoading(false);
}
};
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 handleCardClick = (type)=>{
setTypeFilter(type === typeFilter ? "all" : type);
};
const openAddModal = ()=>{
setEditingRecord(null);
setFormData(resetForm());
setFormError("");
setModalOpen(true);
};
const openEditModal = (record)=>{
const [datePart, timePart] = record.date.split(" ");
setEditingRecord(record);
setFormData({
type: record.type,
amount: record.amount.toString(),
category: record.category,
date: datePart,
time: timePart || "00:00",
note: record.note || ""
});
setFormError("");
setModalOpen(true);
};
const closeModal = ()=>{
if (submitting || deleting) return;
setModalOpen(false);
setEditingRecord(null);
setFormError("");
};
const handleSubmit = async (e)=>{
e.preventDefault();
if (!formData.amount || !formData.category || !formData.date) {
setFormError("请填写完整信æ¯");
return;
}
setSubmitting(true);
setFormError("");
try {
const data = {
type: formData.type,
amount: parseFloat(formData.amount),
category: formData.category,
date: "".concat(formData.date, " ").concat(formData.time || "00:00"),
note: formData.note
};
if (editingRecord) {
await updateRecord(editingRecord.id, data);
showToast("记录已更新", {
tone: "success"
});
} else {
await createRecord(data);
showToast("记录已添加", {
tone: "success"
});
}
closeModal();
await loadData();
} catch (err) {
setFormError(err.message);
showToast(err.message, {
tone: "error"
});
} finally{
setSubmitting(false);
}
};
const handleDelete = async ()=>{
if (!editingRecord) return;
const shouldDelete = await confirm({
title: "删除这æ¡è®°å½•?",
description: "删除åŽå°†æ— æ³•æ¢å¤ï¼Œè¯·ç¡®è®¤æ˜¯å¦ç»§ç»­ã€‚",
confirmText: "删除"
});
if (!shouldDelete) return;
setDeleting(true);
try {
await deleteRecord(editingRecord.id);
closeModal();
showToast("记录已删除", {
tone: "success"
});
await loadData();
} catch (err) {
setFormError(err.message);
showToast(err.message, {
tone: "error"
});
} finally{
setDeleting(false);
}
};
const groupedRecords = records.reduce((groups, record)=>{
const date = record.date.split(" ")[0];
if (!groups[date]) groups[date] = [];
groups[date].push(record);
return groups;
}, {});
const sortedDates = Object.keys(groupedRecords).sort((a, b)=>b.localeCompare(a));
const getDayExpense = (date)=>groupedRecords[date].filter((record)=>record.type === "expense").reduce((sum, record)=>sum + record.amount, 0);
const homeHeaderContent = /*#__PURE__*/ _jsxs("div", {
className: "header-center-container",
children: [
/*#__PURE__*/ _jsx("div", {
className: "header-left-actions",
children: /*#__PURE__*/ _jsx("button", {
type: "button",
className: "icon-button",
"aria-label": "æœç´¢è®°å½•",
onClick: ()=>router.push("/search"),
children: /*#__PURE__*/ _jsx(Icon, {
name: "search",
size: 18
})
})
}),
/*#__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
})
})
})
]
});
const modalHeaderContent = /*#__PURE__*/ _jsxs("div", {
className: "header-center-container",
children: [
/*#__PURE__*/ _jsx("div", {
className: "header-left-actions",
children: /*#__PURE__*/ _jsx("button", {
type: "button",
className: "icon-button",
"aria-label": "关闭",
onClick: closeModal,
children: /*#__PURE__*/ _jsx(Icon, {
name: "chevronLeft",
size: 18
})
})
}),
/*#__PURE__*/ _jsx("div", {
className: "modal-title-container",
children: /*#__PURE__*/ _jsx("span", {
className: "modal-title",
children: editingRecord ? "编辑记录" : "新增记录"
})
}),
/*#__PURE__*/ _jsx("div", {
className: "header-right-actions"
})
]
});
return /*#__PURE__*/ _jsxs(AppShell, {
headerContent: modalOpen ? modalHeaderContent : homeHeaderContent,
compactHeader: true,
hideNav: modalOpen,
children: [
/*#__PURE__*/ _jsx("section", {
className: "dashboard-hero card",
children: /*#__PURE__*/ _jsxs("div", {
className: "stats-grid stats-grid--hero",
children: [
/*#__PURE__*/ _jsxs("button", {
type: "button",
className: "stat-card ".concat(typeFilter === "income" ? "active" : ""),
onClick: ()=>handleCardClick("income"),
children: [
/*#__PURE__*/ _jsx("span", {
className: "label",
children: "æ”¶å…¥"
}),
/*#__PURE__*/ _jsxs("span", {
className: "value income",
children: [
"+",
formatMoney(stats.income)
]
})
]
}),
/*#__PURE__*/ _jsxs("button", {
type: "button",
className: "stat-card ".concat(typeFilter === "expense" ? "active" : ""),
onClick: ()=>handleCardClick("expense"),
children: [
/*#__PURE__*/ _jsx("span", {
className: "label",
children: "支出"
}),
/*#__PURE__*/ _jsxs("span", {
className: "value expense",
children: [
"-",
formatMoney(stats.expense)
]
})
]
}),
/*#__PURE__*/ _jsxs("button", {
type: "button",
className: "stat-card stat-card--balance",
onClick: ()=>handleCardClick("all"),
children: [
/*#__PURE__*/ _jsx("span", {
className: "label",
children: "结余"
}),
/*#__PURE__*/ _jsx("span", {
className: "value",
children: formatMoney(stats.balance)
})
]
})
]
})
}),
/*#__PURE__*/ _jsxs("section", {
className: "card section-card",
children: [
/*#__PURE__*/ _jsxs("div", {
className: "section-heading section-heading--compact",
children: [
/*#__PURE__*/ _jsxs("div", {
children: [
/*#__PURE__*/ _jsx("p", {
className: "eyebrow",
children: "记录列表"
}),
/*#__PURE__*/ _jsx("h2", {
children: typeFilter === "all" ? "全部记录" : typeFilter === "income" ? "仅看收入" : "仅看支出"
})
]
}),
/*#__PURE__*/ _jsx("button", {
type: "button",
className: "btn btn-secondary btn-inline",
onClick: openAddModal,
children: /*#__PURE__*/ _jsxs("span", {
className: "button-content",
children: [
/*#__PURE__*/ _jsx(Icon, {
name: "plus",
size: 16
}),
"新增记录"
]
})
})
]
}),
loading ? /*#__PURE__*/ _jsx("div", {
className: "loading-state",
children: "加载中..."
}) : records.length === 0 ? /*#__PURE__*/ _jsxs("div", {
className: "empty-state empty-state--card",
children: [
/*#__PURE__*/ _jsx("div", {
className: "empty-state__icon",
children: /*#__PURE__*/ _jsx(Icon, {
name: "info",
size: 24
})
}),
/*#__PURE__*/ _jsx("p", {
children: "暂无记录,点击上方按钮添加。"
})
]
}) : /*#__PURE__*/ _jsx("div", {
className: "records-list records-list--flush",
children: sortedDates.map((date)=>/*#__PURE__*/ _jsxs("div", {
className: "date-group",
children: [
/*#__PURE__*/ _jsxs("div", {
className: "date-label",
children: [
/*#__PURE__*/ _jsx("span", {
children: new Date(date).toLocaleDateString("zh-CN", {
month: "short",
day: "numeric",
weekday: "short"
})
}),
/*#__PURE__*/ _jsxs("span", {
className: "day-expense",
children: [
"支出 ",
formatMoney(getDayExpense(date))
]
})
]
}),
groupedRecords[date].map((record)=>{
const categories = DEFAULT_CATEGORIES[record.type];
const cat = categories.find((item)=>item.name === record.category) || {
icon: "\uD83D\uDCDD"
};
return /*#__PURE__*/ _jsxs("button", {
type: "button",
className: "record-item",
onClick: ()=>openEditModal(record),
children: [
/*#__PURE__*/ _jsxs("div", {
className: "record-left",
children: [
/*#__PURE__*/ _jsx("div", {
className: "record-icon",
children: cat.icon
}),
/*#__PURE__*/ _jsxs("div", {
className: "record-info",
children: [
/*#__PURE__*/ _jsx("div", {
className: "record-category",
children: record.category
}),
record.note && /*#__PURE__*/ _jsx("div", {
className: "record-note",
children: record.note
})
]
})
]
}),
/*#__PURE__*/ _jsxs("div", {
className: "record-amount ".concat(record.type),
children: [
record.type === "income" ? "+" : "-",
formatMoney(record.amount)
]
})
]
}, record.id);
})
]
}, date))
})
]
}),
/*#__PURE__*/ _jsx("button", {
type: "button",
className: "fab",
"aria-label": "新增记录",
onClick: openAddModal,
children: /*#__PURE__*/ _jsx(Icon, {
name: "plus",
size: 24
})
}),
modalOpen && /*#__PURE__*/ _jsx("div", {
className: "modal-overlay modal-overlay--fullscreen",
onClick: closeModal,
children: /*#__PURE__*/ _jsxs("div", {
className: "modal modal--fullscreen",
onClick: (e)=>e.stopPropagation(),
children: [
/*#__PURE__*/ _jsxs("div", {
className: "modal-header",
children: [
/*#__PURE__*/ _jsxs("div", {
children: [
/*#__PURE__*/ _jsx("p", {
className: "eyebrow",
children: editingRecord ? "编辑记录" : "新增记录"
}),
editingRecord ? /*#__PURE__*/ _jsx("h2", {
children: "更新账目"
}) : null
]
}),
/*#__PURE__*/ _jsx("button", {
type: "button",
className: "icon-button icon-button--ghost",
"aria-label": "关闭弹窗",
onClick: closeModal,
children: /*#__PURE__*/ _jsx(Icon, {
name: "close",
size: 18
})
})
]
}),
formError && /*#__PURE__*/ _jsx("div", {
className: "error-msg",
children: formError
}),
/*#__PURE__*/ _jsxs("form", {
onSubmit: handleSubmit,
children: [
/*#__PURE__*/ _jsxs("div", {
className: "type-switch",
children: [
/*#__PURE__*/ _jsx("button", {
type: "button",
className: "type-btn expense ".concat(formData.type === "expense" ? "active" : ""),
onClick: ()=>setFormData({
...formData,
type: "expense",
category: ""
}),
disabled: submitting || deleting,
children: "支出"
}),
/*#__PURE__*/ _jsx("button", {
type: "button",
className: "type-btn income ".concat(formData.type === "income" ? "active" : ""),
onClick: ()=>setFormData({
...formData,
type: "income",
category: ""
}),
disabled: submitting || deleting,
children: "æ”¶å…¥"
})
]
}),
/*#__PURE__*/ _jsxs("div", {
className: "form-group",
children: [
/*#__PURE__*/ _jsx("label", {
htmlFor: "record-amount",
children: "金é¢"
}),
/*#__PURE__*/ _jsx("input", {
id: "record-amount",
type: "number",
className: "amount-input",
value: formData.amount,
onChange: (e)=>setFormData({
...formData,
amount: e.target.value
}),
placeholder: "0.00",
step: "0.01",
required: true,
disabled: submitting || deleting
})
]
}),
/*#__PURE__*/ _jsxs("div", {
className: "form-group",
children: [
/*#__PURE__*/ _jsx("label", {
children: "分类"
}),
/*#__PURE__*/ _jsx("div", {
className: "category-grid",
children: DEFAULT_CATEGORIES[formData.type].map((cat)=>/*#__PURE__*/ _jsxs("button", {
type: "button",
className: "category-btn ".concat(formData.category === cat.name ? "active" : ""),
onClick: ()=>setFormData({
...formData,
category: cat.name
}),
disabled: submitting || deleting,
children: [
/*#__PURE__*/ _jsx("span", {
className: "icon",
children: cat.icon
}),
/*#__PURE__*/ _jsx("span", {
children: cat.name
})
]
}, cat.name))
})
]
}),
/*#__PURE__*/ _jsxs("div", {
className: "form-group",
children: [
/*#__PURE__*/ _jsx("label", {
children: "日期"
}),
/*#__PURE__*/ _jsxs("div", {
className: "datetime-picker",
children: [
/*#__PURE__*/ _jsx("input", {
type: "date",
value: formData.date,
onChange: (e)=>setFormData({
...formData,
date: e.target.value
}),
required: true,
disabled: submitting || deleting
}),
/*#__PURE__*/ _jsx("input", {
type: "time",
value: formData.time || "00:00",
onChange: (e)=>setFormData({
...formData,
time: e.target.value
}),
disabled: submitting || deleting
})
]
})
]
}),
/*#__PURE__*/ _jsxs("div", {
className: "form-group",
children: [
/*#__PURE__*/ _jsx("label", {
htmlFor: "record-note",
children: "备注(å¯é€‰ï¼‰"
}),
/*#__PURE__*/ _jsx("input", {
id: "record-note",
type: "text",
value: formData.note,
onChange: (e)=>setFormData({
...formData,
note: e.target.value
}),
placeholder: "添加备注...",
disabled: submitting || deleting
})
]
}),
/*#__PURE__*/ _jsxs("div", {
className: "modal-actions",
children: [
/*#__PURE__*/ _jsx("button", {
type: "submit",
className: "btn btn-primary",
disabled: submitting || deleting,
children: submitting ? /*#__PURE__*/ _jsxs("span", {
className: "button-content",
children: [
/*#__PURE__*/ _jsx(Icon, {
name: "spinner",
size: 16,
className: "is-spinning"
}),
"ä¿å­˜ä¸­..."
]
}) : "ä¿å­˜"
}),
editingRecord && /*#__PURE__*/ _jsx("button", {
type: "button",
className: "btn btn-danger",
onClick: handleDelete,
disabled: submitting || deleting,
children: deleting ? "删除中..." : "删除"
})
]
})
]
})
]
})
})
]
});
}
c>@km_”ýÿ57dfÈÊ:BahŽ¿Æý07\cÏÖ_@Gx­´äëìô f o © ° *ÍØ¦±îñàå芎
º½{~ortwA D ª ­ œ!Ÿ!¡!¤!l"p"×"Ú"?#_B#$"$$$'$Î$Ñ$8%;%ð%ó%j&n&p&w&.'1'š'ž'-(1(j)m)6*:*Í+Ñ+ --Ø-Ü-q/_u/00Y1\1˜2œ233Å3É3245455+6.6&7*7Ý7à7â7å7z9}9%:):Æ:É:L;O;Q;_T;%<(<Ì<Ï<i=m=>>Í>Ð>­@±@
CCDDHDžE¢EF„F¶GºG±H´H J#JJLNL?OBOP_
P PPP P>QBQRR R¤RSS+T.TU"UdVgViVlVW“WDXHXÞXâXY”Y\Ž\Ã__Ç_u`x`haka0e4eâeåeffg,gVgZg2j5jakdkmmÃmÆmpntn?oBo rr4u8uæuNéuÛvÞváyåy–z™zÌ{Ð{§|ª|¬|¯|à~ã~¶(0,next_router__WEBPACK_IMPORTED_MODULE_2__.useRouter)Ì(0,_components_ToastProvider__WEBPACK_IMPORTED_MODULE_6__/* .useToast */ .p)Ð(0,_components_ConfirmProvider__WEBPACK_IMPORTED_MODULE_7__/* .useConfirm */ .N)¯(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)ÿÿÿÿÿÿÿÿÿÿ°(0,react__WEBPACK_IMPORTED_MODULE_1__.useEffect)¾(0,_lib_api__WEBPACK_IMPORTED_MODULE_8__/* .isLoggedIn */ .jl)¾(0,_lib_api__WEBPACK_IMPORTED_MODULE_8__/* .getRecords */ .Q6)¼(0,_lib_api__WEBPACK_IMPORTED_MODULE_8__/* .getStats */ .fy)À(0,_lib_api__WEBPACK_IMPORTED_MODULE_8__/* .updateRecord */ .Vs)À(0,_lib_api__WEBPACK_IMPORTED_MODULE_8__/* .createRecord */ .ae)À(0,_lib_api__WEBPACK_IMPORTED_MODULE_8__/* .deleteRecord */ .JS)·(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)¶(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)ÿÿ¼_components_icons__WEBPACK_IMPORTED_MODULE_5__/* .Icon */ .JýþþÿþþþÿþþþÿýþþþÿþþþýÅ_components_AppShell__WEBPACK_IMPORTED_MODULE_4__/* ["default"] */ .ZýüüýüüýüüýýüüüýýýüýþýüýýþýýüüýüÈ_lib_categories__WEBPACK_IMPORTED_MODULE_3__/* .DEFAULT_CATEGORIES */ .Sûûüûüüûüüýüûûûüüüüýüûûüüûüüûüüÿûüüûüûüüûüüûüûüýü†buffer†source„size„maps„hash__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": function() { return /* binding */ Home; }
/* 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_8__ = __webpack_require__(610);
/* harmony import */ var _lib_categories__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(7063);
/* harmony import */ var _components_AppShell__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(6248);
/* harmony import */ var _components_icons__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(9415);
/* harmony import */ var _components_ToastProvider__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(5546);
/* harmony import */ var _components_ConfirmProvider__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(8846);
function Home() {
const router = (0,next_router__WEBPACK_IMPORTED_MODULE_2__.useRouter)();
const { showToast } = (0,_components_ToastProvider__WEBPACK_IMPORTED_MODULE_6__/* .useToast */ .p)();
const { confirm } = (0,_components_ConfirmProvider__WEBPACK_IMPORTED_MODULE_7__/* .useConfirm */ .N)();
const [currentMonth, setCurrentMonth] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)(new Date());
const [typeFilter, setTypeFilter] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)("all");
const [records, setRecords] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)([]);
const [stats, setStats] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)({
income: 0,
expense: 0,
balance: 0
});
const [modalOpen, setModalOpen] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)(false);
const [editingRecord, setEditingRecord] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)(null);
const [loading, setLoading] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)(true);
const [submitting, setSubmitting] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)(false);
const [deleting, setDeleting] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)(false);
const [formError, setFormError] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)("");
const [formData, setFormData] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)({
type: "expense",
amount: "",
category: "",
date: new Date().toISOString().split("T")[0],
time: new Date().toTimeString().slice(0, 5),
note: ""
});
(0,react__WEBPACK_IMPORTED_MODULE_1__.useEffect)(()=>{
if (!(0,_lib_api__WEBPACK_IMPORTED_MODULE_8__/* .isLoggedIn */ .jl)()) {
router.push("/login");
return;
}
loadData();
}, [
router,
currentMonth,
typeFilter
]);
const getMonthStr = (date)=>"".concat(date.getFullYear(), "-").concat(String(date.getMonth() + 1).padStart(2, "0"));
const formatMoney = (num)=>(num || 0).toFixed(2);
const formatMonth = (date)=>"".concat(date.getFullYear(), "年").concat(date.getMonth() + 1, "月");
const resetForm = ()=>({
type: "expense",
amount: "",
category: "",
date: new Date().toISOString().split("T")[0],
time: new Date().toTimeString().slice(0, 5),
note: ""
});
const loadData = async ()=>{
setLoading(true);
try {
const [recordsData, statsData] = await Promise.all([
(0,_lib_api__WEBPACK_IMPORTED_MODULE_8__/* .getRecords */ .Q6)(getMonthStr(currentMonth), typeFilter),
(0,_lib_api__WEBPACK_IMPORTED_MODULE_8__/* .getStats */ .fy)(getMonthStr(currentMonth))
]);
setRecords(recordsData);
setStats(statsData);
} catch (err) {
console.error(err);
showToast(err.message, {
tone: "error"
});
} finally{
setLoading(false);
}
};
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 handleCardClick = (type)=>{
setTypeFilter(type === typeFilter ? "all" : type);
};
const openAddModal = ()=>{
setEditingRecord(null);
setFormData(resetForm());
setFormError("");
setModalOpen(true);
};
const openEditModal = (record)=>{
const [datePart, timePart] = record.date.split(" ");
setEditingRecord(record);
setFormData({
type: record.type,
amount: record.amount.toString(),
category: record.category,
date: datePart,
time: timePart || "00:00",
note: record.note || ""
});
setFormError("");
setModalOpen(true);
};
const closeModal = ()=>{
if (submitting || deleting) return;
setModalOpen(false);
setEditingRecord(null);
setFormError("");
};
const handleSubmit = async (e)=>{
e.preventDefault();
if (!formData.amount || !formData.category || !formData.date) {
setFormError("请填写完整信æ¯");
return;
}
setSubmitting(true);
setFormError("");
try {
const data = {
type: formData.type,
amount: parseFloat(formData.amount),
category: formData.category,
date: "".concat(formData.date, " ").concat(formData.time || "00:00"),
note: formData.note
};
if (editingRecord) {
await (0,_lib_api__WEBPACK_IMPORTED_MODULE_8__/* .updateRecord */ .Vs)(editingRecord.id, data);
showToast("记录已更新", {
tone: "success"
});
} else {
await (0,_lib_api__WEBPACK_IMPORTED_MODULE_8__/* .createRecord */ .ae)(data);
showToast("记录已添加", {
tone: "success"
});
}
closeModal();
await loadData();
} catch (err) {
setFormError(err.message);
showToast(err.message, {
tone: "error"
});
} finally{
setSubmitting(false);
}
};
const handleDelete = async ()=>{
if (!editingRecord) return;
const shouldDelete = await confirm({
title: "删除这æ¡è®°å½•?",
description: "删除åŽå°†æ— æ³•æ¢å¤ï¼Œè¯·ç¡®è®¤æ˜¯å¦ç»§ç»­ã€‚",
confirmText: "删除"
});
if (!shouldDelete) return;
setDeleting(true);
try {
await (0,_lib_api__WEBPACK_IMPORTED_MODULE_8__/* .deleteRecord */ .JS)(editingRecord.id);
closeModal();
showToast("记录已删除", {
tone: "success"
});
await loadData();
} catch (err) {
setFormError(err.message);
showToast(err.message, {
tone: "error"
});
} finally{
setDeleting(false);
}
};
const groupedRecords = records.reduce((groups, record)=>{
const date = record.date.split(" ")[0];
if (!groups[date]) groups[date] = [];
groups[date].push(record);
return groups;
}, {});
const sortedDates = Object.keys(groupedRecords).sort((a, b)=>b.localeCompare(a));
const getDayExpense = (date)=>groupedRecords[date].filter((record)=>record.type === "expense").reduce((sum, record)=>sum + record.amount, 0);
const homeHeaderContent = /*#__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-left-actions",
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("button", {
type: "button",
className: "icon-button",
"aria-label": "æœç´¢è®°å½•",
onClick: ()=>router.push("/search"),
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_components_icons__WEBPACK_IMPORTED_MODULE_5__/* .Icon */ .J, {
name: "search",
size: 18
})
})
}),
/*#__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_5__/* .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_5__/* .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_5__/* .Icon */ .J, {
name: "settings",
size: 18
})
})
})
]
});
const modalHeaderContent = /*#__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-left-actions",
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("button", {
type: "button",
className: "icon-button",
"aria-label": "关闭",
onClick: closeModal,
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_components_icons__WEBPACK_IMPORTED_MODULE_5__/* .Icon */ .J, {
name: "chevronLeft",
size: 18
})
})
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "modal-title-container",
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("span", {
className: "modal-title",
children: editingRecord ? "编辑记录" : "新增记录"
})
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "header-right-actions"
})
]
});
return /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)(_components_AppShell__WEBPACK_IMPORTED_MODULE_4__/* ["default"] */ .Z, {
headerContent: modalOpen ? modalHeaderContent : homeHeaderContent,
compactHeader: true,
hideNav: modalOpen,
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("section", {
className: "dashboard-hero card",
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "stats-grid stats-grid--hero",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("button", {
type: "button",
className: "stat-card ".concat(typeFilter === "income" ? "active" : ""),
onClick: ()=>handleCardClick("income"),
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("span", {
className: "label",
children: "æ”¶å…¥"
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("span", {
className: "value income",
children: [
"+",
formatMoney(stats.income)
]
})
]
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("button", {
type: "button",
className: "stat-card ".concat(typeFilter === "expense" ? "active" : ""),
onClick: ()=>handleCardClick("expense"),
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("span", {
className: "label",
children: "支出"
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("span", {
className: "value expense",
children: [
"-",
formatMoney(stats.expense)
]
})
]
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("button", {
type: "button",
className: "stat-card stat-card--balance",
onClick: ()=>handleCardClick("all"),
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("span", {
className: "label",
children: "结余"
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("span", {
className: "value",
children: formatMoney(stats.balance)
})
]
})
]
})
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("section", {
className: "card section-card",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("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: typeFilter === "all" ? "全部记录" : typeFilter === "income" ? "仅看收入" : "仅看支出"
})
]
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("button", {
type: "button",
className: "btn btn-secondary btn-inline",
onClick: openAddModal,
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("span", {
className: "button-content",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_components_icons__WEBPACK_IMPORTED_MODULE_5__/* .Icon */ .J, {
name: "plus",
size: 16
}),
"新增记录"
]
})
})
]
}),
loading ? /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "loading-state",
children: "加载中..."
}) : records.length === 0 ? /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "empty-state empty-state--card",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "empty-state__icon",
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_components_icons__WEBPACK_IMPORTED_MODULE_5__/* .Icon */ .J, {
name: "info",
size: 24
})
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("p", {
children: "暂无记录,点击上方按钮添加。"
})
]
}) : /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "records-list records-list--flush",
children: sortedDates.map((date)=>/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "date-group",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "date-label",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("span", {
children: new Date(date).toLocaleDateString("zh-CN", {
month: "short",
day: "numeric",
weekday: "short"
})
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("span", {
className: "day-expense",
children: [
"支出 ",
formatMoney(getDayExpense(date))
]
})
]
}),
groupedRecords[date].map((record)=>{
const categories = _lib_categories__WEBPACK_IMPORTED_MODULE_3__/* .DEFAULT_CATEGORIES */ .S[record.type];
const cat = categories.find((item)=>item.name === record.category) || {
icon: "\uD83D\uDCDD"
};
return /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("button", {
type: "button",
className: "record-item",
onClick: ()=>openEditModal(record),
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "record-left",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "record-icon",
children: cat.icon
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "record-info",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "record-category",
children: record.category
}),
record.note && /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "record-note",
children: record.note
})
]
})
]
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "record-amount ".concat(record.type),
children: [
record.type === "income" ? "+" : "-",
formatMoney(record.amount)
]
})
]
}, record.id);
})
]
}, date))
})
]
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("button", {
type: "button",
className: "fab",
"aria-label": "新增记录",
onClick: openAddModal,
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_components_icons__WEBPACK_IMPORTED_MODULE_5__/* .Icon */ .J, {
name: "plus",
size: 24
})
}),
modalOpen && /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "modal-overlay modal-overlay--fullscreen",
onClick: closeModal,
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "modal modal--fullscreen",
onClick: (e)=>e.stopPropagation(),
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "modal-header",
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: editingRecord ? "编辑记录" : "新增记录"
}),
editingRecord ? /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("h2", {
children: "更新账目"
}) : null
]
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("button", {
type: "button",
className: "icon-button icon-button--ghost",
"aria-label": "关闭弹窗",
onClick: closeModal,
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_components_icons__WEBPACK_IMPORTED_MODULE_5__/* .Icon */ .J, {
name: "close",
size: 18
})
})
]
}),
formError && /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "error-msg",
children: formError
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("form", {
onSubmit: handleSubmit,
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "type-switch",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("button", {
type: "button",
className: "type-btn expense ".concat(formData.type === "expense" ? "active" : ""),
onClick: ()=>setFormData({
...formData,
type: "expense",
category: ""
}),
disabled: submitting || deleting,
children: "支出"
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("button", {
type: "button",
className: "type-btn income ".concat(formData.type === "income" ? "active" : ""),
onClick: ()=>setFormData({
...formData,
type: "income",
category: ""
}),
disabled: submitting || deleting,
children: "æ”¶å…¥"
})
]
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "form-group",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("label", {
htmlFor: "record-amount",
children: "金é¢"
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("input", {
id: "record-amount",
type: "number",
className: "amount-input",
value: formData.amount,
onChange: (e)=>setFormData({
...formData,
amount: e.target.value
}),
placeholder: "0.00",
step: "0.01",
required: true,
disabled: submitting || deleting
})
]
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "form-group",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("label", {
children: "分类"
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "category-grid",
children: _lib_categories__WEBPACK_IMPORTED_MODULE_3__/* .DEFAULT_CATEGORIES */ .S[formData.type].map((cat)=>/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("button", {
type: "button",
className: "category-btn ".concat(formData.category === cat.name ? "active" : ""),
onClick: ()=>setFormData({
...formData,
category: cat.name
}),
disabled: submitting || deleting,
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("span", {
className: "icon",
children: cat.icon
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("span", {
children: cat.name
})
]
}, cat.name))
})
]
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "form-group",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("label", {
children: "日期"
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "datetime-picker",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("input", {
type: "date",
value: formData.date,
onChange: (e)=>setFormData({
...formData,
date: e.target.value
}),
required: true,
disabled: submitting || deleting
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("input", {
type: "time",
value: formData.time || "00:00",
onChange: (e)=>setFormData({
...formData,
time: e.target.value
}),
disabled: submitting || deleting
})
]
})
]
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "form-group",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("label", {
htmlFor: "record-note",
children: "备注(å¯é€‰ï¼‰"
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("input", {
id: "record-note",
type: "text",
value: formData.note,
onChange: (e)=>setFormData({
...formData,
note: e.target.value
}),
placeholder: "添加备注...",
disabled: submitting || deleting
})
]
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "modal-actions",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("button", {
type: "submit",
className: "btn btn-primary",
disabled: submitting || deleting,
children: submitting ? /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("span", {
className: "button-content",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_components_icons__WEBPACK_IMPORTED_MODULE_5__/* .Icon */ .J, {
name: "spinner",
size: 16,
className: "is-spinning"
}),
"ä¿å­˜ä¸­..."
]
}) : "ä¿å­˜"
}),
editingRecord && /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("button", {
type: "button",
className: "btn btn-danger",
onClick: handleDelete,
disabled: submitting || deleting,
children: deleting ? "删除中..." : "删除"
})
]
})
]
})
]
})
})
]
});
}
”{"finalSource":true}ƒmapbufferedMap ConcatSourceRawSourceñ__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": function() { return /* binding */ Home; }
/* 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_8__ = __webpack_require__(610);
/* harmony import */ var _lib_categories__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(7063);
/* harmony import */ var _components_AppShell__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(6248);
/* harmony import */ var _components_icons__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(9415);
/* harmony import */ var _components_ToastProvider__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(5546);
/* harmony import */ var _components_ConfirmProvider__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(8846);
ReplaceSourceRawSourceŸ‚import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { useState, useEffect } from "react";
import { useRouter } from "next/router";
import { isLoggedIn, getRecords, getStats, createRecord, updateRecord, deleteRecord } from "../lib/api";
import { DEFAULT_CATEGORIES } from "../lib/categories";
import AppShell from "../components/AppShell";
import { Icon } from "../components/icons";
import { useToast } from "../components/ToastProvider";
import { useConfirm } from "../components/ConfirmProvider";
export default function Home() {
const router = useRouter();
const { showToast } = useToast();
const { confirm } = useConfirm();
const [currentMonth, setCurrentMonth] = useState(new Date());
const [typeFilter, setTypeFilter] = useState("all");
const [records, setRecords] = useState([]);
const [stats, setStats] = useState({
income: 0,
expense: 0,
balance: 0
});
const [modalOpen, setModalOpen] = useState(false);
const [editingRecord, setEditingRecord] = useState(null);
const [loading, setLoading] = useState(true);
const [submitting, setSubmitting] = useState(false);
const [deleting, setDeleting] = useState(false);
const [formError, setFormError] = useState("");
const [formData, setFormData] = useState({
type: "expense",
amount: "",
category: "",
date: new Date().toISOString().split("T")[0],
time: new Date().toTimeString().slice(0, 5),
note: ""
});
useEffect(()=>{
if (!isLoggedIn()) {
router.push("/login");
return;
}
loadData();
}, [
router,
currentMonth,
typeFilter
]);
const getMonthStr = (date)=>"".concat(date.getFullYear(), "-").concat(String(date.getMonth() + 1).padStart(2, "0"));
const formatMoney = (num)=>(num || 0).toFixed(2);
const formatMonth = (date)=>"".concat(date.getFullYear(), "年").concat(date.getMonth() + 1, "月");
const resetForm = ()=>({
type: "expense",
amount: "",
category: "",
date: new Date().toISOString().split("T")[0],
time: new Date().toTimeString().slice(0, 5),
note: ""
});
const loadData = async ()=>{
setLoading(true);
try {
const [recordsData, statsData] = await Promise.all([
getRecords(getMonthStr(currentMonth), typeFilter),
getStats(getMonthStr(currentMonth))
]);
setRecords(recordsData);
setStats(statsData);
} catch (err) {
console.error(err);
showToast(err.message, {
tone: "error"
});
} finally{
setLoading(false);
}
};
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 handleCardClick = (type)=>{
setTypeFilter(type === typeFilter ? "all" : type);
};
const openAddModal = ()=>{
setEditingRecord(null);
setFormData(resetForm());
setFormError("");
setModalOpen(true);
};
const openEditModal = (record)=>{
const [datePart, timePart] = record.date.split(" ");
setEditingRecord(record);
setFormData({
type: record.type,
amount: record.amount.toString(),
category: record.category,
date: datePart,
time: timePart || "00:00",
note: record.note || ""
});
setFormError("");
setModalOpen(true);
};
const closeModal = ()=>{
if (submitting || deleting) return;
setModalOpen(false);
setEditingRecord(null);
setFormError("");
};
const handleSubmit = async (e)=>{
e.preventDefault();
if (!formData.amount || !formData.category || !formData.date) {
setFormError("请填写完整信æ¯");
return;
}
setSubmitting(true);
setFormError("");
try {
const data = {
type: formData.type,
amount: parseFloat(formData.amount),
category: formData.category,
date: "".concat(formData.date, " ").concat(formData.time || "00:00"),
note: formData.note
};
if (editingRecord) {
await updateRecord(editingRecord.id, data);
showToast("记录已更新", {
tone: "success"
});
} else {
await createRecord(data);
showToast("记录已添加", {
tone: "success"
});
}
closeModal();
await loadData();
} catch (err) {
setFormError(err.message);
showToast(err.message, {
tone: "error"
});
} finally{
setSubmitting(false);
}
};
const handleDelete = async ()=>{
if (!editingRecord) return;
const shouldDelete = await confirm({
title: "删除这æ¡è®°å½•?",
description: "删除åŽå°†æ— æ³•æ¢å¤ï¼Œè¯·ç¡®è®¤æ˜¯å¦ç»§ç»­ã€‚",
confirmText: "删除"
});
if (!shouldDelete) return;
setDeleting(true);
try {
await deleteRecord(editingRecord.id);
closeModal();
showToast("记录已删除", {
tone: "success"
});
await loadData();
} catch (err) {
setFormError(err.message);
showToast(err.message, {
tone: "error"
});
} finally{
setDeleting(false);
}
};
const groupedRecords = records.reduce((groups, record)=>{
const date = record.date.split(" ")[0];
if (!groups[date]) groups[date] = [];
groups[date].push(record);
return groups;
}, {});
const sortedDates = Object.keys(groupedRecords).sort((a, b)=>b.localeCompare(a));
const getDayExpense = (date)=>groupedRecords[date].filter((record)=>record.type === "expense").reduce((sum, record)=>sum + record.amount, 0);
const homeHeaderContent = /*#__PURE__*/ _jsxs("div", {
className: "header-center-container",
children: [
/*#__PURE__*/ _jsx("div", {
className: "header-left-actions",
children: /*#__PURE__*/ _jsx("button", {
type: "button",
className: "icon-button",
"aria-label": "æœç´¢è®°å½•",
onClick: ()=>router.push("/search"),
children: /*#__PURE__*/ _jsx(Icon, {
name: "search",
size: 18
})
})
}),
/*#__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
})
})
})
]
});
const modalHeaderContent = /*#__PURE__*/ _jsxs("div", {
className: "header-center-container",
children: [
/*#__PURE__*/ _jsx("div", {
className: "header-left-actions",
children: /*#__PURE__*/ _jsx("button", {
type: "button",
className: "icon-button",
"aria-label": "关闭",
onClick: closeModal,
children: /*#__PURE__*/ _jsx(Icon, {
name: "chevronLeft",
size: 18
})
})
}),
/*#__PURE__*/ _jsx("div", {
className: "modal-title-container",
children: /*#__PURE__*/ _jsx("span", {
className: "modal-title",
children: editingRecord ? "编辑记录" : "新增记录"
})
}),
/*#__PURE__*/ _jsx("div", {
className: "header-right-actions"
})
]
});
return /*#__PURE__*/ _jsxs(AppShell, {
headerContent: modalOpen ? modalHeaderContent : homeHeaderContent,
compactHeader: true,
hideNav: modalOpen,
children: [
/*#__PURE__*/ _jsx("section", {
className: "dashboard-hero card",
children: /*#__PURE__*/ _jsxs("div", {
className: "stats-grid stats-grid--hero",
children: [
/*#__PURE__*/ _jsxs("button", {
type: "button",
className: "stat-card ".concat(typeFilter === "income" ? "active" : ""),
onClick: ()=>handleCardClick("income"),
children: [
/*#__PURE__*/ _jsx("span", {
className: "label",
children: "æ”¶å…¥"
}),
/*#__PURE__*/ _jsxs("span", {
className: "value income",
children: [
"+",
formatMoney(stats.income)
]
})
]
}),
/*#__PURE__*/ _jsxs("button", {
type: "button",
className: "stat-card ".concat(typeFilter === "expense" ? "active" : ""),
onClick: ()=>handleCardClick("expense"),
children: [
/*#__PURE__*/ _jsx("span", {
className: "label",
children: "支出"
}),
/*#__PURE__*/ _jsxs("span", {
className: "value expense",
children: [
"-",
formatMoney(stats.expense)
]
})
]
}),
/*#__PURE__*/ _jsxs("button", {
type: "button",
className: "stat-card stat-card--balance",
onClick: ()=>handleCardClick("all"),
children: [
/*#__PURE__*/ _jsx("span", {
className: "label",
children: "结余"
}),
/*#__PURE__*/ _jsx("span", {
className: "value",
children: formatMoney(stats.balance)
})
]
})
]
})
}),
/*#__PURE__*/ _jsxs("section", {
className: "card section-card",
children: [
/*#__PURE__*/ _jsxs("div", {
className: "section-heading section-heading--compact",
children: [
/*#__PURE__*/ _jsxs("div", {
children: [
/*#__PURE__*/ _jsx("p", {
className: "eyebrow",
children: "记录列表"
}),
/*#__PURE__*/ _jsx("h2", {
children: typeFilter === "all" ? "全部记录" : typeFilter === "income" ? "仅看收入" : "仅看支出"
})
]
}),
/*#__PURE__*/ _jsx("button", {
type: "button",
className: "btn btn-secondary btn-inline",
onClick: openAddModal,
children: /*#__PURE__*/ _jsxs("span", {
className: "button-content",
children: [
/*#__PURE__*/ _jsx(Icon, {
name: "plus",
size: 16
}),
"新增记录"
]
})
})
]
}),
loading ? /*#__PURE__*/ _jsx("div", {
className: "loading-state",
children: "加载中..."
}) : records.length === 0 ? /*#__PURE__*/ _jsxs("div", {
className: "empty-state empty-state--card",
children: [
/*#__PURE__*/ _jsx("div", {
className: "empty-state__icon",
children: /*#__PURE__*/ _jsx(Icon, {
name: "info",
size: 24
})
}),
/*#__PURE__*/ _jsx("p", {
children: "暂无记录,点击上方按钮添加。"
})
]
}) : /*#__PURE__*/ _jsx("div", {
className: "records-list records-list--flush",
children: sortedDates.map((date)=>/*#__PURE__*/ _jsxs("div", {
className: "date-group",
children: [
/*#__PURE__*/ _jsxs("div", {
className: "date-label",
children: [
/*#__PURE__*/ _jsx("span", {
children: new Date(date).toLocaleDateString("zh-CN", {
month: "short",
day: "numeric",
weekday: "short"
})
}),
/*#__PURE__*/ _jsxs("span", {
className: "day-expense",
children: [
"支出 ",
formatMoney(getDayExpense(date))
]
})
]
}),
groupedRecords[date].map((record)=>{
const categories = DEFAULT_CATEGORIES[record.type];
const cat = categories.find((item)=>item.name === record.category) || {
icon: "\uD83D\uDCDD"
};
return /*#__PURE__*/ _jsxs("button", {
type: "button",
className: "record-item",
onClick: ()=>openEditModal(record),
children: [
/*#__PURE__*/ _jsxs("div", {
className: "record-left",
children: [
/*#__PURE__*/ _jsx("div", {
className: "record-icon",
children: cat.icon
}),
/*#__PURE__*/ _jsxs("div", {
className: "record-info",
children: [
/*#__PURE__*/ _jsx("div", {
className: "record-category",
children: record.category
}),
record.note && /*#__PURE__*/ _jsx("div", {
className: "record-note",
children: record.note
})
]
})
]
}),
/*#__PURE__*/ _jsxs("div", {
className: "record-amount ".concat(record.type),
children: [
record.type === "income" ? "+" : "-",
formatMoney(record.amount)
]
})
]
}, record.id);
})
]
}, date))
})
]
}),
/*#__PURE__*/ _jsx("button", {
type: "button",
className: "fab",
"aria-label": "新增记录",
onClick: openAddModal,
children: /*#__PURE__*/ _jsx(Icon, {
name: "plus",
size: 24
})
}),
modalOpen && /*#__PURE__*/ _jsx("div", {
className: "modal-overlay modal-overlay--fullscreen",
onClick: closeModal,
children: /*#__PURE__*/ _jsxs("div", {
className: "modal modal--fullscreen",
onClick: (e)=>e.stopPropagation(),
children: [
/*#__PURE__*/ _jsxs("div", {
className: "modal-header",
children: [
/*#__PURE__*/ _jsxs("div", {
children: [
/*#__PURE__*/ _jsx("p", {
className: "eyebrow",
children: editingRecord ? "编辑记录" : "新增记录"
}),
editingRecord ? /*#__PURE__*/ _jsx("h2", {
children: "更新账目"
}) : null
]
}),
/*#__PURE__*/ _jsx("button", {
type: "button",
className: "icon-button icon-button--ghost",
"aria-label": "关闭弹窗",
onClick: closeModal,
children: /*#__PURE__*/ _jsx(Icon, {
name: "close",
size: 18
})
})
]
}),
formError && /*#__PURE__*/ _jsx("div", {
className: "error-msg",
children: formError
}),
/*#__PURE__*/ _jsxs("form", {
onSubmit: handleSubmit,
children: [
/*#__PURE__*/ _jsxs("div", {
className: "type-switch",
children: [
/*#__PURE__*/ _jsx("button", {
type: "button",
className: "type-btn expense ".concat(formData.type === "expense" ? "active" : ""),
onClick: ()=>setFormData({
...formData,
type: "expense",
category: ""
}),
disabled: submitting || deleting,
children: "支出"
}),
/*#__PURE__*/ _jsx("button", {
type: "button",
className: "type-btn income ".concat(formData.type === "income" ? "active" : ""),
onClick: ()=>setFormData({
...formData,
type: "income",
category: ""
}),
disabled: submitting || deleting,
children: "æ”¶å…¥"
})
]
}),
/*#__PURE__*/ _jsxs("div", {
className: "form-group",
children: [
/*#__PURE__*/ _jsx("label", {
htmlFor: "record-amount",
children: "金é¢"
}),
/*#__PURE__*/ _jsx("input", {
id: "record-amount",
type: "number",
className: "amount-input",
value: formData.amount,
onChange: (e)=>setFormData({
...formData,
amount: e.target.value
}),
placeholder: "0.00",
step: "0.01",
required: true,
disabled: submitting || deleting
})
]
}),
/*#__PURE__*/ _jsxs("div", {
className: "form-group",
children: [
/*#__PURE__*/ _jsx("label", {
children: "分类"
}),
/*#__PURE__*/ _jsx("div", {
className: "category-grid",
children: DEFAULT_CATEGORIES[formData.type].map((cat)=>/*#__PURE__*/ _jsxs("button", {
type: "button",
className: "category-btn ".concat(formData.category === cat.name ? "active" : ""),
onClick: ()=>setFormData({
...formData,
category: cat.name
}),
disabled: submitting || deleting,
children: [
/*#__PURE__*/ _jsx("span", {
className: "icon",
children: cat.icon
}),
/*#__PURE__*/ _jsx("span", {
children: cat.name
})
]
}, cat.name))
})
]
}),
/*#__PURE__*/ _jsxs("div", {
className: "form-group",
children: [
/*#__PURE__*/ _jsx("label", {
children: "日期"
}),
/*#__PURE__*/ _jsxs("div", {
className: "datetime-picker",
children: [
/*#__PURE__*/ _jsx("input", {
type: "date",
value: formData.date,
onChange: (e)=>setFormData({
...formData,
date: e.target.value
}),
required: true,
disabled: submitting || deleting
}),
/*#__PURE__*/ _jsx("input", {
type: "time",
value: formData.time || "00:00",
onChange: (e)=>setFormData({
...formData,
time: e.target.value
}),
disabled: submitting || deleting
})
]
})
]
}),
/*#__PURE__*/ _jsxs("div", {
className: "form-group",
children: [
/*#__PURE__*/ _jsx("label", {
htmlFor: "record-note",
children: "备注(å¯é€‰ï¼‰"
}),
/*#__PURE__*/ _jsx("input", {
id: "record-note",
type: "text",
value: formData.note,
onChange: (e)=>setFormData({
...formData,
note: e.target.value
}),
placeholder: "添加备注...",
disabled: submitting || deleting
})
]
}),
/*#__PURE__*/ _jsxs("div", {
className: "modal-actions",
children: [
/*#__PURE__*/ _jsx("button", {
type: "submit",
className: "btn btn-primary",
disabled: submitting || deleting,
children: submitting ? /*#__PURE__*/ _jsxs("span", {
className: "button-content",
children: [
/*#__PURE__*/ _jsx(Icon, {
name: "spinner",
size: 16,
className: "is-spinning"
}),
"ä¿å­˜ä¸­..."
]
}) : "ä¿å­˜"
}),
editingRecord && /*#__PURE__*/ _jsx("button", {
type: "button",
className: "btn btn-danger",
onClick: handleDelete,
disabled: submitting || deleting,
children: deleting ? "删除中..." : "删除"
})
]
})
]
})
]
})
})
]
});
}
E%062undefined64107undefined109148undefined150253undefined255309undefined311356undefined358400undefined402456undefined458516undefined518532undefined570578(0,next_router__WEBPACK_IMPORTED_MODULE_2__.useRouter)undefined609616(0,_components_ToastProvider__WEBPACK_IMPORTED_MODULE_6__/* .useToast */ .p)undefined645654(0,_components_ConfirmProvider__WEBPACK_IMPORTED_MODULE_7__/* .useConfirm */ .N)undefined703710(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined765772(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined816823(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined860867(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined975982(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined10381045(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined10881095(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined11441151(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined11971204(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined12521259(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined13021309(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined15161524(0,react__WEBPACK_IMPORTED_MODULE_1__.useEffect)undefined15451554(0,_lib_api__WEBPACK_IMPORTED_MODULE_8__/* .isLoggedIn */ .jl)undefined24062415(0,_lib_api__WEBPACK_IMPORTED_MODULE_8__/* .getRecords */ .Q6)undefined24732480(0,_lib_api__WEBPACK_IMPORTED_MODULE_8__/* .getStats */ .fy)undefined46394650(0,_lib_api__WEBPACK_IMPORTED_MODULE_8__/* .updateRecord */ .Vs)undefined48134824(0,_lib_api__WEBPACK_IMPORTED_MODULE_8__/* .createRecord */ .ae)undefined55425553(0,_lib_api__WEBPACK_IMPORTED_MODULE_8__/* .deleteRecord */ .JS)undefined64276431(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined65346537(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined66386641(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined68806883(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined68856888_components_icons__WEBPACK_IMPORTED_MODULE_5__/* .Icon */ .Jundefined70507054(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined71827185(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined74267429(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined74317434_components_icons__WEBPACK_IMPORTED_MODULE_5__/* .Icon */ .Jundefined76107613(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined78037806(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined80478050(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined80528055_components_icons__WEBPACK_IMPORTED_MODULE_5__/* .Icon */ .Jundefined82578260(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined83628365(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined86048607(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined86098612_components_icons__WEBPACK_IMPORTED_MODULE_5__/* .Icon */ .Jundefined88128816(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined89198922(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined90239026(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined92479250(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined92529255_components_icons__WEBPACK_IMPORTED_MODULE_5__/* .Icon */ .Jundefined94229425(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined95289531(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined97129715(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined98349838(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined98409847_components_AppShell__WEBPACK_IMPORTED_MODULE_4__/* ["default"] */ .Zundefined1003010033(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined1013810142(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined1028510289(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined1060210605(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined1080610810(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined1121311217(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined1153211535(0,react_jsx_runtime__WEBPACK_IMPO
/***/ }) Î ÏâÏñ__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": function() { return /* binding */ Home; }
/* 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_8__ = __webpack_require__(610);
/* harmony import */ var _lib_categories__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(7063);
/* harmony import */ var _components_AppShell__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(6248);
/* harmony import */ var _components_icons__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(9415);
/* harmony import */ var _components_ToastProvider__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(5546);
/* harmony import */ var _components_ConfirmProvider__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(8846);
П‚import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { useState, useEffect } from "react";
import { useRouter } from "next/router";
import { isLoggedIn, getRecords, getStats, createRecord, updateRecord, deleteRecord } from "../lib/api";
import { DEFAULT_CATEGORIES } from "../lib/categories";
import AppShell from "../components/AppShell";
import { Icon } from "../components/icons";
import { useToast } from "../components/ToastProvider";
import { useConfirm } from "../components/ConfirmProvider";
export default function Home() {
const router = useRouter();
const { showToast } = useToast();
const { confirm } = useConfirm();
const [currentMonth, setCurrentMonth] = useState(new Date());
const [typeFilter, setTypeFilter] = useState("all");
const [records, setRecords] = useState([]);
const [stats, setStats] = useState({
income: 0,
expense: 0,
balance: 0
});
const [modalOpen, setModalOpen] = useState(false);
const [editingRecord, setEditingRecord] = useState(null);
const [loading, setLoading] = useState(true);
const [submitting, setSubmitting] = useState(false);
const [deleting, setDeleting] = useState(false);
const [formError, setFormError] = useState("");
const [formData, setFormData] = useState({
type: "expense",
amount: "",
category: "",
date: new Date().toISOString().split("T")[0],
time: new Date().toTimeString().slice(0, 5),
note: ""
});
useEffect(()=>{
if (!isLoggedIn()) {
router.push("/login");
return;
}
loadData();
}, [
router,
currentMonth,
typeFilter
]);
const getMonthStr = (date)=>"".concat(date.getFullYear(), "-").concat(String(date.getMonth() + 1).padStart(2, "0"));
const formatMoney = (num)=>(num || 0).toFixed(2);
const formatMonth = (date)=>"".concat(date.getFullYear(), "年").concat(date.getMonth() + 1, "月");
const resetForm = ()=>({
type: "expense",
amount: "",
category: "",
date: new Date().toISOString().split("T")[0],
time: new Date().toTimeString().slice(0, 5),
note: ""
});
const loadData = async ()=>{
setLoading(true);
try {
const [recordsData, statsData] = await Promise.all([
getRecords(getMonthStr(currentMonth), typeFilter),
getStats(getMonthStr(currentMonth))
]);
setRecords(recordsData);
setStats(statsData);
} catch (err) {
console.error(err);
showToast(err.message, {
tone: "error"
});
} finally{
setLoading(false);
}
};
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 handleCardClick = (type)=>{
setTypeFilter(type === typeFilter ? "all" : type);
};
const openAddModal = ()=>{
setEditingRecord(null);
setFormData(resetForm());
setFormError("");
setModalOpen(true);
};
const openEditModal = (record)=>{
const [datePart, timePart] = record.date.split(" ");
setEditingRecord(record);
setFormData({
type: record.type,
amount: record.amount.toString(),
category: record.category,
date: datePart,
time: timePart || "00:00",
note: record.note || ""
});
setFormError("");
setModalOpen(true);
};
const closeModal = ()=>{
if (submitting || deleting) return;
setModalOpen(false);
setEditingRecord(null);
setFormError("");
};
const handleSubmit = async (e)=>{
e.preventDefault();
if (!formData.amount || !formData.category || !formData.date) {
setFormError("请填写完整信æ¯");
return;
}
setSubmitting(true);
setFormError("");
try {
const data = {
type: formData.type,
amount: parseFloat(formData.amount),
category: formData.category,
date: "".concat(formData.date, " ").concat(formData.time || "00:00"),
note: formData.note
};
if (editingRecord) {
await updateRecord(editingRecord.id, data);
showToast("记录已更新", {
tone: "success"
});
} else {
await createRecord(data);
showToast("记录已添加", {
tone: "success"
});
}
closeModal();
await loadData();
} catch (err) {
setFormError(err.message);
showToast(err.message, {
tone: "error"
});
} finally{
setSubmitting(false);
}
};
const handleDelete = async ()=>{
if (!editingRecord) return;
const shouldDelete = await confirm({
title: "删除这æ¡è®°å½•?",
description: "删除åŽå°†æ— æ³•æ¢å¤ï¼Œè¯·ç¡®è®¤æ˜¯å¦ç»§ç»­ã€‚",
confirmText: "删除"
});
if (!shouldDelete) return;
setDeleting(true);
try {
await deleteRecord(editingRecord.id);
closeModal();
showToast("记录已删除", {
tone: "success"
});
await loadData();
} catch (err) {
setFormError(err.message);
showToast(err.message, {
tone: "error"
});
} finally{
setDeleting(false);
}
};
const groupedRecords = records.reduce((groups, record)=>{
const date = record.date.split(" ")[0];
if (!groups[date]) groups[date] = [];
groups[date].push(record);
return groups;
}, {});
const sortedDates = Object.keys(groupedRecords).sort((a, b)=>b.localeCompare(a));
const getDayExpense = (date)=>groupedRecords[date].filter((record)=>record.type === "expense").reduce((sum, record)=>sum + record.amount, 0);
const homeHeaderContent = /*#__PURE__*/ _jsxs("div", {
className: "header-center-container",
children: [
/*#__PURE__*/ _jsx("div", {
className: "header-left-actions",
children: /*#__PURE__*/ _jsx("button", {
type: "button",
className: "icon-button",
"aria-label": "æœç´¢è®°å½•",
onClick: ()=>router.push("/search"),
children: /*#__PURE__*/ _jsx(Icon, {
name: "search",
size: 18
})
})
}),
/*#__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
})
})
})
]
});
const modalHeaderContent = /*#__PURE__*/ _jsxs("div", {
className: "header-center-container",
children: [
/*#__PURE__*/ _jsx("div", {
className: "header-left-actions",
children: /*#__PURE__*/ _jsx("button", {
type: "button",
className: "icon-button",
"aria-label": "关闭",
onClick: closeModal,
children: /*#__PURE__*/ _jsx(Icon, {
name: "chevronLeft",
size: 18
})
})
}),
/*#__PURE__*/ _jsx("div", {
className: "modal-title-container",
children: /*#__PURE__*/ _jsx("span", {
className: "modal-title",
children: editingRecord ? "编辑记录" : "新增记录"
})
}),
/*#__PURE__*/ _jsx("div", {
className: "header-right-actions"
})
]
});
return /*#__PURE__*/ _jsxs(AppShell, {
headerContent: modalOpen ? modalHeaderContent : homeHeaderContent,
compactHeader: true,
hideNav: modalOpen,
children: [
/*#__PURE__*/ _jsx("section", {
className: "dashboard-hero card",
children: /*#__PURE__*/ _jsxs("div", {
className: "stats-grid stats-grid--hero",
children: [
/*#__PURE__*/ _jsxs("button", {
type: "button",
className: "stat-card ".concat(typeFilter === "income" ? "active" : ""),
onClick: ()=>handleCardClick("income"),
children: [
/*#__PURE__*/ _jsx("span", {
className: "label",
children: "æ”¶å…¥"
}),
/*#__PURE__*/ _jsxs("span", {
className: "value income",
children: [
"+",
formatMoney(stats.income)
]
})
]
}),
/*#__PURE__*/ _jsxs("button", {
type: "button",
className: "stat-card ".concat(typeFilter === "expense" ? "active" : ""),
onClick: ()=>handleCardClick("expense"),
children: [
/*#__PURE__*/ _jsx("span", {
className: "label",
children: "支出"
}),
/*#__PURE__*/ _jsxs("span", {
className: "value expense",
children: [
"-",
formatMoney(stats.expense)
]
})
]
}),
/*#__PURE__*/ _jsxs("button", {
type: "button",
className: "stat-card stat-card--balance",
onClick: ()=>handleCardClick("all"),
children: [
/*#__PURE__*/ _jsx("span", {
className: "label",
children: "结余"
}),
/*#__PURE__*/ _jsx("span", {
className: "value",
children: formatMoney(stats.balance)
})
]
})
]
})
}),
/*#__PURE__*/ _jsxs("section", {
className: "card section-card",
children: [
/*#__PURE__*/ _jsxs("div", {
className: "section-heading section-heading--compact",
children: [
/*#__PURE__*/ _jsxs("div", {
children: [
/*#__PURE__*/ _jsx("p", {
className: "eyebrow",
children: "记录列表"
}),
/*#__PURE__*/ _jsx("h2", {
children: typeFilter === "all" ? "全部记录" : typeFilter === "income" ? "仅看收入" : "仅看支出"
})
]
}),
/*#__PURE__*/ _jsx("button", {
type: "button",
className: "btn btn-secondary btn-inline",
onClick: openAddModal,
children: /*#__PURE__*/ _jsxs("span", {
className: "button-content",
children: [
/*#__PURE__*/ _jsx(Icon, {
name: "plus",
size: 16
}),
"新增记录"
]
})
})
]
}),
loading ? /*#__PURE__*/ _jsx("div", {
className: "loading-state",
children: "加载中..."
}) : records.length === 0 ? /*#__PURE__*/ _jsxs("div", {
className: "empty-state empty-state--card",
children: [
/*#__PURE__*/ _jsx("div", {
className: "empty-state__icon",
children: /*#__PURE__*/ _jsx(Icon, {
name: "info",
size: 24
})
}),
/*#__PURE__*/ _jsx("p", {
children: "暂无记录,点击上方按钮添加。"
})
]
}) : /*#__PURE__*/ _jsx("div", {
className: "records-list records-list--flush",
children: sortedDates.map((date)=>/*#__PURE__*/ _jsxs("div", {
className: "date-group",
children: [
/*#__PURE__*/ _jsxs("div", {
className: "date-label",
children: [
/*#__PURE__*/ _jsx("span", {
children: new Date(date).toLocaleDateString("zh-CN", {
month: "short",
day: "numeric",
weekday: "short"
})
}),
/*#__PURE__*/ _jsxs("span", {
className: "day-expense",
children: [
"支出 ",
formatMoney(getDayExpense(date))
]
})
]
}),
groupedRecords[date].map((record)=>{
const categories = DEFAULT_CATEGORIES[record.type];
const cat = categories.find((item)=>item.name === record.category) || {
icon: "\uD83D\uDCDD"
};
return /*#__PURE__*/ _jsxs("button", {
type: "button",
className: "record-item",
onClick: ()=>openEditModal(record),
children: [
/*#__PURE__*/ _jsxs("div", {
className: "record-left",
children: [
/*#__PURE__*/ _jsx("div", {
className: "record-icon",
children: cat.icon
}),
/*#__PURE__*/ _jsxs("div", {
className: "record-info",
children: [
/*#__PURE__*/ _jsx("div", {
className: "record-category",
children: record.category
}),
record.note && /*#__PURE__*/ _jsx("div", {
className: "record-note",
children: record.note
})
]
})
]
}),
/*#__PURE__*/ _jsxs("div", {
className: "record-amount ".concat(record.type),
children: [
record.type === "income" ? "+" : "-",
formatMoney(record.amount)
]
})
]
}, record.id);
})
]
}, date))
})
]
}),
/*#__PURE__*/ _jsx("button", {
type: "button",
className: "fab",
"aria-label": "新增记录",
onClick: openAddModal,
children: /*#__PURE__*/ _jsx(Icon, {
name: "plus",
size: 24
})
}),
modalOpen && /*#__PURE__*/ _jsx("div", {
className: "modal-overlay modal-overlay--fullscreen",
onClick: closeModal,
children: /*#__PURE__*/ _jsxs("div", {
className: "modal modal--fullscreen",
onClick: (e)=>e.stopPropagation(),
children: [
/*#__PURE__*/ _jsxs("div", {
className: "modal-header",
children: [
/*#__PURE__*/ _jsxs("div", {
children: [
/*#__PURE__*/ _jsx("p", {
className: "eyebrow",
children: editingRecord ? "编辑记录" : "新增记录"
}),
editingRecord ? /*#__PURE__*/ _jsx("h2", {
children: "更新账目"
}) : null
]
}),
/*#__PURE__*/ _jsx("button", {
type: "button",
className: "icon-button icon-button--ghost",
"aria-label": "关闭弹窗",
onClick: closeModal,
children: /*#__PURE__*/ _jsx(Icon, {
name: "close",
size: 18
})
})
]
}),
formError && /*#__PURE__*/ _jsx("div", {
className: "error-msg",
children: formError
}),
/*#__PURE__*/ _jsxs("form", {
onSubmit: handleSubmit,
children: [
/*#__PURE__*/ _jsxs("div", {
className: "type-switch",
children: [
/*#__PURE__*/ _jsx("button", {
type: "button",
className: "type-btn expense ".concat(formData.type === "expense" ? "active" : ""),
onClick: ()=>setFormData({
...formData,
type: "expense",
category: ""
}),
disabled: submitting || deleting,
children: "支出"
}),
/*#__PURE__*/ _jsx("button", {
type: "button",
className: "type-btn income ".concat(formData.type === "income" ? "active" : ""),
onClick: ()=>setFormData({
...formData,
type: "income",
category: ""
}),
disabled: submitting || deleting,
children: "æ”¶å…¥"
})
]
}),
/*#__PURE__*/ _jsxs("div", {
className: "form-group",
children: [
/*#__PURE__*/ _jsx("label", {
htmlFor: "record-amount",
children: "金é¢"
}),
/*#__PURE__*/ _jsx("input", {
id: "record-amount",
type: "number",
className: "amount-input",
value: formData.amount,
onChange: (e)=>setFormData({
...formData,
amount: e.target.value
}),
placeholder: "0.00",
step: "0.01",
required: true,
disabled: submitting || deleting
})
]
}),
/*#__PURE__*/ _jsxs("div", {
className: "form-group",
children: [
/*#__PURE__*/ _jsx("label", {
children: "分类"
}),
/*#__PURE__*/ _jsx("div", {
className: "category-grid",
children: DEFAULT_CATEGORIES[formData.type].map((cat)=>/*#__PURE__*/ _jsxs("button", {
type: "button",
className: "category-btn ".concat(formData.category === cat.name ? "active" : ""),
onClick: ()=>setFormData({
...formData,
category: cat.name
}),
disabled: submitting || deleting,
children: [
/*#__PURE__*/ _jsx("span", {
className: "icon",
children: cat.icon
}),
/*#__PURE__*/ _jsx("span", {
children: cat.name
})
]
}, cat.name))
})
]
}),
/*#__PURE__*/ _jsxs("div", {
className: "form-group",
children: [
/*#__PURE__*/ _jsx("label", {
children: "日期"
}),
/*#__PURE__*/ _jsxs("div", {
className: "datetime-picker",
children: [
/*#__PURE__*/ _jsx("input", {
type: "date",
value: formData.date,
onChange: (e)=>setFormData({
...formData,
date: e.target.value
}),
required: true,
disabled: submitting || deleting
}),
/*#__PURE__*/ _jsx("input", {
type: "time",
value: formData.time || "00:00",
onChange: (e)=>setFormData({
...formData,
time: e.target.value
}),
disabled: submitting || deleting
})
]
})
]
}),
/*#__PURE__*/ _jsxs("div", {
className: "form-group",
children: [
/*#__PURE__*/ _jsx("label", {
htmlFor: "record-note",
children: "备注(å¯é€‰ï¼‰"
}),
/*#__PURE__*/ _jsx("input", {
id: "record-note",
type: "text",
value: formData.note,
onChange: (e)=>setFormData({
...formData,
note: e.target.value
}),
placeholder: "添加备注...",
disabled: submitting || deleting
})
]
}),
/*#__PURE__*/ _jsxs("div", {
className: "modal-actions",
children: [
/*#__PURE__*/ _jsx("button", {
type: "submit",
className: "btn btn-primary",
disabled: submitting || deleting,
children: submitting ? /*#__PURE__*/ _jsxs("span", {
className: "button-content",
children: [
/*#__PURE__*/ _jsx(Icon, {
name: "spinner",
size: 16,
className: "is-spinning"
}),
"ä¿å­˜ä¸­..."
]
}) : "ä¿å­˜"
}),
editingRecord && /*#__PURE__*/ _jsx("button", {
type: "button",
className: "btn btn-danger",
onClick: handleDelete,
disabled: submitting || deleting,
children: deleting ? "删除中..." : "删除"
})
]
})
]
})
]
})
})
]
});
}
E%062undefined64107undefined109148undefined150253undefined255309undefined311356undefined358400undefined402456undefined458516undefined518532undefined570578(0,next_router__WEBPACK_IMPORTED_MODULE_2__.useRouter)undefined609616(0,_components_ToastProvider__WEBPACK_IMPORTED_MODULE_6__/* .useToast */ .p)undefined645654(0,_components_ConfirmProvider__WEBPACK_IMPORTED_MODULE_7__/* .useConfirm */ .N)undefined703710(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined765772(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined816823(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined860867(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined975982(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined10381045(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined10881095(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined11441151(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined11971204(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined12521259(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined13021309(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined15161524(0,react__WEBPACK_IMPORTED_MODULE_1__.useEffect)undefined15451554(0,_lib_api__WEBPACK_IMPORTED_MODULE_8__/* .isLoggedIn */ .jl)undefined24062415(0,_lib_api__WEBPACK_IMPORTED_MODULE_8__/* .getRecords */ .Q6)undefined24732480(0,_lib_api__WEBPACK_IMPORTED_MODULE_8__/* .getStats */ .fy)undefined46394650(0,_lib_api__WEBPACK_IMPORTED_MODULE_8__/* .updateRecord */ .Vs)undefined48134824(0,_lib_api__WEBPACK_IMPORTED_MODULE_8__/* .createRecord */ .ae)undefined55425553(0,_lib_api__WEBPACK_IMPORTED_MODULE_8__/* .deleteRecord */ .JS)undefined64276431(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined65346537(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined66386641(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined68806883(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined68856888_components_icons__WEBPACK_IMPORTED_MODULE_5__/* .Icon */ .Jundefined70507054(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined71827185(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined74267429(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined74317434_components_icons__WEBPACK_IMPORTED_MODULE_5__/* .Icon */ .Jundefined76107613(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined78037806(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined80478050(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined80528055_components_icons__WEBPACK_IMPORTED_MODULE_5__/* .Icon */ .Jundefined82578260(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined83628365(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined86048607(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined86098612_components_icons__WEBPACK_IMPORTED_MODULE_5__/* .Icon */ .Jundefined88128816(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined89198922(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined90239026(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined92479250(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined92529255_components_icons__WEBPACK_IMPORTED_MODULE_5__/* .Icon */ .Jundefined94229425(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined95289531(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined97129715(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined98349838(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined98409847_components_AppShell__WEBPACK_IMPORTED_MODULE_4__/* ["default"] */ .Zundefined1003010033(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined1013810142(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined1028510289(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined1060210605(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined1080610810(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined1121311217(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined1153211535(0,react_jsx_runtime__WEBPACK_IMPO
/***/ 1163:
 g«webpack/lib/util/registerExternalSerializerœwebpack-sources/ConcatSource«webpack/lib/util/registerExternalSerializer™webpack-sources/RawSourceJ/***/ (function(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/RawSource1module.exports = require('./dist/client/router')
`/ªmodule.exports = __webpack_require__(3079)†buffer†source„size„maps„hash+module.exports = __webpack_require__(3079)
”{"finalSource":true}ƒmapbufferedMap ReplaceSourceRawSource1module.exports = require('./dist/client/router')
6047module.exports = __webpack_require__(3079)undefined
/***/ }) Å/***/ (function(module, __unused_webpack_exports, __webpack_require__) {
module.exports = __webpack_require__(3079)
/***/ }) ÓÖ ÄJ/***/ (function(module, __unused_webpack_exports, __webpack_require__) {
Å1module.exports = require('./dist/client/router')
6047module.exports = __webpack_require__(3079)undefinedÆÇ
},
«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, [888,774,179], function() { return __webpack_exec__(5557); });
var __webpack_exports__ = __webpack_require__.O();
_N_E = __webpack_exports__;
}
]); `H¾W(self["webpackChunk_N_E"] = self["webpackChunk_N_E"] || []).push([[405],{
/***/ 5557:
½â¿
(window.__NEXT_P = window.__NEXT_P || []).push([
"/",
function () {
return require("private-next-pages/index.js");
}
]);
if(module.hot) {
module.hot.dispose(function () {
window.__NEXT_P.push(["/"])
});
}
Z100106__webpack_require__undefined1081369828undefined163172falseundefined175266{}undefined×ã×ä»å»æ½çè×ã×é»å»ê½ëì×ã×,
/***/ 7063:
ºäºÆ¼ ÿÿÿÇÖâÖ,
/***/ 9828:
¹ã¹ñ__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": function() { return /* binding */ Home; }
/* 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_8__ = __webpack_require__(610);
/* harmony import */ var _lib_categories__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(7063);
/* harmony import */ var _components_AppShell__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(6248);
/* harmony import */ var _components_icons__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(9415);
/* harmony import */ var _components_ToastProvider__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(5546);
/* harmony import */ var _components_ConfirmProvider__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(8846);
ºŸ‚import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { useState, useEffect } from "react";
import { useRouter } from "next/router";
import { isLoggedIn, getRecords, getStats, createRecord, updateRecord, deleteRecord } from "../lib/api";
import { DEFAULT_CATEGORIES } from "../lib/categories";
import AppShell from "../components/AppShell";
import { Icon } from "../components/icons";
import { useToast } from "../components/ToastProvider";
import { useConfirm } from "../components/ConfirmProvider";
export default function Home() {
const router = useRouter();
const { showToast } = useToast();
const { confirm } = useConfirm();
const [currentMonth, setCurrentMonth] = useState(new Date());
const [typeFilter, setTypeFilter] = useState("all");
const [records, setRecords] = useState([]);
const [stats, setStats] = useState({
income: 0,
expense: 0,
balance: 0
});
const [modalOpen, setModalOpen] = useState(false);
const [editingRecord, setEditingRecord] = useState(null);
const [loading, setLoading] = useState(true);
const [submitting, setSubmitting] = useState(false);
const [deleting, setDeleting] = useState(false);
const [formError, setFormError] = useState("");
const [formData, setFormData] = useState({
type: "expense",
amount: "",
category: "",
date: new Date().toISOString().split("T")[0],
time: new Date().toTimeString().slice(0, 5),
note: ""
});
useEffect(()=>{
if (!isLoggedIn()) {
router.push("/login");
return;
}
loadData();
}, [
router,
currentMonth,
typeFilter
]);
const getMonthStr = (date)=>"".concat(date.getFullYear(), "-").concat(String(date.getMonth() + 1).padStart(2, "0"));
const formatMoney = (num)=>(num || 0).toFixed(2);
const formatMonth = (date)=>"".concat(date.getFullYear(), "年").concat(date.getMonth() + 1, "月");
const resetForm = ()=>({
type: "expense",
amount: "",
category: "",
date: new Date().toISOString().split("T")[0],
time: new Date().toTimeString().slice(0, 5),
note: ""
});
const loadData = async ()=>{
setLoading(true);
try {
const [recordsData, statsData] = await Promise.all([
getRecords(getMonthStr(currentMonth), typeFilter),
getStats(getMonthStr(currentMonth))
]);
setRecords(recordsData);
setStats(statsData);
} catch (err) {
console.error(err);
showToast(err.message, {
tone: "error"
});
} finally{
setLoading(false);
}
};
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 handleCardClick = (type)=>{
setTypeFilter(type === typeFilter ? "all" : type);
};
const openAddModal = ()=>{
setEditingRecord(null);
setFormData(resetForm());
setFormError("");
setModalOpen(true);
};
const openEditModal = (record)=>{
const [datePart, timePart] = record.date.split(" ");
setEditingRecord(record);
setFormData({
type: record.type,
amount: record.amount.toString(),
category: record.category,
date: datePart,
time: timePart || "00:00",
note: record.note || ""
});
setFormError("");
setModalOpen(true);
};
const closeModal = ()=>{
if (submitting || deleting) return;
setModalOpen(false);
setEditingRecord(null);
setFormError("");
};
const handleSubmit = async (e)=>{
e.preventDefault();
if (!formData.amount || !formData.category || !formData.date) {
setFormError("请填写完整信æ¯");
return;
}
setSubmitting(true);
setFormError("");
try {
const data = {
type: formData.type,
amount: parseFloat(formData.amount),
category: formData.category,
date: "".concat(formData.date, " ").concat(formData.time || "00:00"),
note: formData.note
};
if (editingRecord) {
await updateRecord(editingRecord.id, data);
showToast("记录已更新", {
tone: "success"
});
} else {
await createRecord(data);
showToast("记录已添加", {
tone: "success"
});
}
closeModal();
await loadData();
} catch (err) {
setFormError(err.message);
showToast(err.message, {
tone: "error"
});
} finally{
setSubmitting(false);
}
};
const handleDelete = async ()=>{
if (!editingRecord) return;
const shouldDelete = await confirm({
title: "删除这æ¡è®°å½•?",
description: "删除åŽå°†æ— æ³•æ¢å¤ï¼Œè¯·ç¡®è®¤æ˜¯å¦ç»§ç»­ã€‚",
confirmText: "删除"
});
if (!shouldDelete) return;
setDeleting(true);
try {
await deleteRecord(editingRecord.id);
closeModal();
showToast("记录已删除", {
tone: "success"
});
await loadData();
} catch (err) {
setFormError(err.message);
showToast(err.message, {
tone: "error"
});
} finally{
setDeleting(false);
}
};
const groupedRecords = records.reduce((groups, record)=>{
const date = record.date.split(" ")[0];
if (!groups[date]) groups[date] = [];
groups[date].push(record);
return groups;
}, {});
const sortedDates = Object.keys(groupedRecords).sort((a, b)=>b.localeCompare(a));
const getDayExpense = (date)=>groupedRecords[date].filter((record)=>record.type === "expense").reduce((sum, record)=>sum + record.amount, 0);
const homeHeaderContent = /*#__PURE__*/ _jsxs("div", {
className: "header-center-container",
children: [
/*#__PURE__*/ _jsx("div", {
className: "header-left-actions",
children: /*#__PURE__*/ _jsx("button", {
type: "button",
className: "icon-button",
"aria-label": "æœç´¢è®°å½•",
onClick: ()=>router.push("/search"),
children: /*#__PURE__*/ _jsx(Icon, {
name: "search",
size: 18
})
})
}),
/*#__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
})
})
})
]
});
const modalHeaderContent = /*#__PURE__*/ _jsxs("div", {
className: "header-center-container",
children: [
/*#__PURE__*/ _jsx("div", {
className: "header-left-actions",
children: /*#__PURE__*/ _jsx("button", {
type: "button",
className: "icon-button",
"aria-label": "关闭",
onClick: closeModal,
children: /*#__PURE__*/ _jsx(Icon, {
name: "chevronLeft",
size: 18
})
})
}),
/*#__PURE__*/ _jsx("div", {
className: "modal-title-container",
children: /*#__PURE__*/ _jsx("span", {
className: "modal-title",
children: editingRecord ? "编辑记录" : "新增记录"
})
}),
/*#__PURE__*/ _jsx("div", {
className: "header-right-actions"
})
]
});
return /*#__PURE__*/ _jsxs(AppShell, {
headerContent: modalOpen ? modalHeaderContent : homeHeaderContent,
compactHeader: true,
hideNav: modalOpen,
children: [
/*#__PURE__*/ _jsx("section", {
className: "dashboard-hero card",
children: /*#__PURE__*/ _jsxs("div", {
className: "stats-grid stats-grid--hero",
children: [
/*#__PURE__*/ _jsxs("button", {
type: "button",
className: "stat-card ".concat(typeFilter === "income" ? "active" : ""),
onClick: ()=>handleCardClick("income"),
children: [
/*#__PURE__*/ _jsx("span", {
className: "label",
children: "æ”¶å…¥"
}),
/*#__PURE__*/ _jsxs("span", {
className: "value income",
children: [
"+",
formatMoney(stats.income)
]
})
]
}),
/*#__PURE__*/ _jsxs("button", {
type: "button",
className: "stat-card ".concat(typeFilter === "expense" ? "active" : ""),
onClick: ()=>handleCardClick("expense"),
children: [
/*#__PURE__*/ _jsx("span", {
className: "label",
children: "支出"
}),
/*#__PURE__*/ _jsxs("span", {
className: "value expense",
children: [
"-",
formatMoney(stats.expense)
]
})
]
}),
/*#__PURE__*/ _jsxs("button", {
type: "button",
className: "stat-card stat-card--balance",
onClick: ()=>handleCardClick("all"),
children: [
/*#__PURE__*/ _jsx("span", {
className: "label",
children: "结余"
}),
/*#__PURE__*/ _jsx("span", {
className: "value",
children: formatMoney(stats.balance)
})
]
})
]
})
}),
/*#__PURE__*/ _jsxs("section", {
className: "card section-card",
children: [
/*#__PURE__*/ _jsxs("div", {
className: "section-heading section-heading--compact",
children: [
/*#__PURE__*/ _jsxs("div", {
children: [
/*#__PURE__*/ _jsx("p", {
className: "eyebrow",
children: "记录列表"
}),
/*#__PURE__*/ _jsx("h2", {
children: typeFilter === "all" ? "全部记录" : typeFilter === "income" ? "仅看收入" : "仅看支出"
})
]
}),
/*#__PURE__*/ _jsx("button", {
type: "button",
className: "btn btn-secondary btn-inline",
onClick: openAddModal,
children: /*#__PURE__*/ _jsxs("span", {
className: "button-content",
children: [
/*#__PURE__*/ _jsx(Icon, {
name: "plus",
size: 16
}),
"新增记录"
]
})
})
]
}),
loading ? /*#__PURE__*/ _jsx("div", {
className: "loading-state",
children: "加载中..."
}) : records.length === 0 ? /*#__PURE__*/ _jsxs("div", {
className: "empty-state empty-state--card",
children: [
/*#__PURE__*/ _jsx("div", {
className: "empty-state__icon",
children: /*#__PURE__*/ _jsx(Icon, {
name: "info",
size: 24
})
}),
/*#__PURE__*/ _jsx("p", {
children: "暂无记录,点击上方按钮添加。"
})
]
}) : /*#__PURE__*/ _jsx("div", {
className: "records-list records-list--flush",
children: sortedDates.map((date)=>/*#__PURE__*/ _jsxs("div", {
className: "date-group",
children: [
/*#__PURE__*/ _jsxs("div", {
className: "date-label",
children: [
/*#__PURE__*/ _jsx("span", {
children: new Date(date).toLocaleDateString("zh-CN", {
month: "short",
day: "numeric",
weekday: "short"
})
}),
/*#__PURE__*/ _jsxs("span", {
className: "day-expense",
children: [
"支出 ",
formatMoney(getDayExpense(date))
]
})
]
}),
groupedRecords[date].map((record)=>{
const categories = DEFAULT_CATEGORIES[record.type];
const cat = categories.find((item)=>item.name === record.category) || {
icon: "\uD83D\uDCDD"
};
return /*#__PURE__*/ _jsxs("button", {
type: "button",
className: "record-item",
onClick: ()=>openEditModal(record),
children: [
/*#__PURE__*/ _jsxs("div", {
className: "record-left",
children: [
/*#__PURE__*/ _jsx("div", {
className: "record-icon",
children: cat.icon
}),
/*#__PURE__*/ _jsxs("div", {
className: "record-info",
children: [
/*#__PURE__*/ _jsx("div", {
className: "record-category",
children: record.category
}),
record.note && /*#__PURE__*/ _jsx("div", {
className: "record-note",
children: record.note
})
]
})
]
}),
/*#__PURE__*/ _jsxs("div", {
className: "record-amount ".concat(record.type),
children: [
record.type === "income" ? "+" : "-",
formatMoney(record.amount)
]
})
]
}, record.id);
})
]
}, date))
})
]
}),
/*#__PURE__*/ _jsx("button", {
type: "button",
className: "fab",
"aria-label": "新增记录",
onClick: openAddModal,
children: /*#__PURE__*/ _jsx(Icon, {
name: "plus",
size: 24
})
}),
modalOpen && /*#__PURE__*/ _jsx("div", {
className: "modal-overlay modal-overlay--fullscreen",
onClick: closeModal,
children: /*#__PURE__*/ _jsxs("div", {
className: "modal modal--fullscreen",
onClick: (e)=>e.stopPropagation(),
children: [
/*#__PURE__*/ _jsxs("div", {
className: "modal-header",
children: [
/*#__PURE__*/ _jsxs("div", {
children: [
/*#__PURE__*/ _jsx("p", {
className: "eyebrow",
children: editingRecord ? "编辑记录" : "新增记录"
}),
editingRecord ? /*#__PURE__*/ _jsx("h2", {
children: "更新账目"
}) : null
]
}),
/*#__PURE__*/ _jsx("button", {
type: "button",
className: "icon-button icon-button--ghost",
"aria-label": "关闭弹窗",
onClick: closeModal,
children: /*#__PURE__*/ _jsx(Icon, {
name: "close",
size: 18
})
})
]
}),
formError && /*#__PURE__*/ _jsx("div", {
className: "error-msg",
children: formError
}),
/*#__PURE__*/ _jsxs("form", {
onSubmit: handleSubmit,
children: [
/*#__PURE__*/ _jsxs("div", {
className: "type-switch",
children: [
/*#__PURE__*/ _jsx("button", {
type: "button",
className: "type-btn expense ".concat(formData.type === "expense" ? "active" : ""),
onClick: ()=>setFormData({
...formData,
type: "expense",
category: ""
}),
disabled: submitting || deleting,
children: "支出"
}),
/*#__PURE__*/ _jsx("button", {
type: "button",
className: "type-btn income ".concat(formData.type === "income" ? "active" : ""),
onClick: ()=>setFormData({
...formData,
type: "income",
category: ""
}),
disabled: submitting || deleting,
children: "æ”¶å…¥"
})
]
}),
/*#__PURE__*/ _jsxs("div", {
className: "form-group",
children: [
/*#__PURE__*/ _jsx("label", {
htmlFor: "record-amount",
children: "金é¢"
}),
/*#__PURE__*/ _jsx("input", {
id: "record-amount",
type: "number",
className: "amount-input",
value: formData.amount,
onChange: (e)=>setFormData({
...formData,
amount: e.target.value
}),
placeholder: "0.00",
step: "0.01",
required: true,
disabled: submitting || deleting
})
]
}),
/*#__PURE__*/ _jsxs("div", {
className: "form-group",
children: [
/*#__PURE__*/ _jsx("label", {
children: "分类"
}),
/*#__PURE__*/ _jsx("div", {
className: "category-grid",
children: DEFAULT_CATEGORIES[formData.type].map((cat)=>/*#__PURE__*/ _jsxs("button", {
type: "button",
className: "category-btn ".concat(formData.category === cat.name ? "active" : ""),
onClick: ()=>setFormData({
...formData,
category: cat.name
}),
disabled: submitting || deleting,
children: [
/*#__PURE__*/ _jsx("span", {
className: "icon",
children: cat.icon
}),
/*#__PURE__*/ _jsx("span", {
children: cat.name
})
]
}, cat.name))
})
]
}),
/*#__PURE__*/ _jsxs("div", {
className: "form-group",
children: [
/*#__PURE__*/ _jsx("label", {
children: "日期"
}),
/*#__PURE__*/ _jsxs("div", {
className: "datetime-picker",
children: [
/*#__PURE__*/ _jsx("input", {
type: "date",
value: formData.date,
onChange: (e)=>setFormData({
...formData,
date: e.target.value
}),
required: true,
disabled: submitting || deleting
}),
/*#__PURE__*/ _jsx("input", {
type: "time",
value: formData.time || "00:00",
onChange: (e)=>setFormData({
...formData,
time: e.target.value
}),
disabled: submitting || deleting
})
]
})
]
}),
/*#__PURE__*/ _jsxs("div", {
className: "form-group",
children: [
/*#__PURE__*/ _jsx("label", {
htmlFor: "record-note",
children: "备注(å¯é€‰ï¼‰"
}),
/*#__PURE__*/ _jsx("input", {
id: "record-note",
type: "text",
value: formData.note,
onChange: (e)=>setFormData({
...formData,
note: e.target.value
}),
placeholder: "添加备注...",
disabled: submitting || deleting
})
]
}),
/*#__PURE__*/ _jsxs("div", {
className: "modal-actions",
children: [
/*#__PURE__*/ _jsx("button", {
type: "submit",
className: "btn btn-primary",
disabled: submitting || deleting,
children: submitting ? /*#__PURE__*/ _jsxs("span", {
className: "button-content",
children: [
/*#__PURE__*/ _jsx(Icon, {
name: "spinner",
size: 16,
className: "is-spinning"
}),
"ä¿å­˜ä¸­..."
]
}) : "ä¿å­˜"
}),
editingRecord && /*#__PURE__*/ _jsx("button", {
type: "button",
className: "btn btn-danger",
onClick: handleDelete,
disabled: submitting || deleting,
children: deleting ? "删除中..." : "删除"
})
]
})
]
})
]
})
})
]
});
}
E%062undefined64107undefined109148undefined150253undefined255309undefined311356undefined358400undefined402456undefined458516undefined518532undefined570578(0,next_router__WEBPACK_IMPORTED_MODULE_2__.useRouter)undefined609616(0,_components_ToastProvider__WEBPACK_IMPORTED_MODULE_6__/* .useToast */ .p)undefined645654(0,_components_ConfirmProvider__WEBPACK_IMPORTED_MODULE_7__/* .useConfirm */ .N)undefined703710(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined765772(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined816823(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined860867(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined975982(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined10381045(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined10881095(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined11441151(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined11971204(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined12521259(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined13021309(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined15161524(0,react__WEBPACK_IMPORTED_MODULE_1__.useEffect)undefined15451554(0,_lib_api__WEBPACK_IMPORTED_MODULE_8__/* .isLoggedIn */ .jl)undefined24062415(0,_lib_api__WEBPACK_IMPORTED_MODULE_8__/* .getRecords */ .Q6)undefined24732480(0,_lib_api__WEBPACK_IMPORTED_MODULE_8__/* .getStats */ .fy)undefined46394650(0,_lib_api__WEBPACK_IMPORTED_MODULE_8__/* .updateRecord */ .Vs)undefined48134824(0,_lib_api__WEBPACK_IMPORTED_MODULE_8__/* .createRecord */ .ae)undefined55425553(0,_lib_api__WEBPACK_IMPORTED_MODULE_8__/* .deleteRecord */ .JS)undefined64276431(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined65346537(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined66386641(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined68806883(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined68856888_components_icons__WEBPACK_IMPORTED_MODULE_5__/* .Icon */ .Jundefined70507054(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined71827185(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined74267429(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined74317434_components_icons__WEBPACK_IMPORTED_MODULE_5__/* .Icon */ .Jundefined76107613(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined78037806(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined80478050(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined80528055_components_icons__WEBPACK_IMPORTED_MODULE_5__/* .Icon */ .Jundefined82578260(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined83628365(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined86048607(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined86098612_components_icons__WEBPACK_IMPORTED_MODULE_5__/* .Icon */ .Jundefined88128816(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined89198922(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined90239026(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined92479250(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined92529255_components_icons__WEBPACK_IMPORTED_MODULE_5__/* .Icon */ .Jundefined94229425(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined95289531(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined97129715(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined98349838(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined98409847_components_AppShell__WEBPACK_IMPORTED_MODULE_4__/* ["default"] */ .Zundefined1003010033(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined1013810142(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined1028510289(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined1060210605(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined1080610810(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined1121311217(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined1153211535(0,react_jsx_runtime__WEBPACK_IMPO
__webpack_require__.O(0, [888,774,179], function() { return __webpack_exec__(5557); });
var __webpack_exports__ = __webpack_require__.O();
Ñðñò GB«webpack/lib/util/registerExternalSerializerœwebpack-sources/ConcatSource`«webpack/lib/util/registerExternalSerializer™webpack-sources/RawSourceW(self["webpackChunk_N_E"] = self["webpackChunk_N_E"] || []).push([[845],{
/***/ 8208:
«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([
"/recurring",
function () {
return require("private-next-pages/recurring.js");
}
]);
if(module.hot) {
module.hot.dispose(function () {
window.__NEXT_P.push(["/recurring"])
});
}
bmsuD•°¹¼ “__webpack_require__„5190…false{}†buffer†source„size„maps„hash«
(window.__NEXT_P = window.__NEXT_P || []).push([
"/recurring",
function () {
return __webpack_require__(5190);
}
]);
if(false) {}
”{"finalSource":true}ƒmapbufferedMap ReplaceSourceRawSource$
(window.__NEXT_P = window.__NEXT_P || []).push([
"/recurring",
function () {
return require("private-next-pages/recurring.js");
}
]);
if(module.hot) {
module.hot.dispose(function () {
window.__NEXT_P.push(["/recurring"])
});
}
Z109115__webpack_require__undefined1171495190undefined176185falseundefined188288{}undefined
/***/ }) †buffer†source„size„maps„hashConcatSourceRawSource[/***/ (function(__unused_webpack_module, __unused_webpack_exports, __webpack_require__) {
ReplaceSourceRawSource$
(window.__NEXT_P = window.__NEXT_P || []).push([
"/recurring",
function () {
return require("private-next-pages/recurring.js");
}
]);
if(module.hot) {
module.hot.dispose(function () {
window.__NEXT_P.push(["/recurring"])
});
}
Z109115__webpack_require__undefined1171495190undefined176185falseundefined188288{}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
]
});
}
/***/ }) ”{"finalSource":true}ƒmapbufferedMap  í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ãä,
/***/ 7063:
 ò«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 */ S: function() { return /* binding */ DEFAULT_CATEGORIES; }
/* harmony export */ });
«webpack/lib/util/registerExternalSerializerwebpack-sources/ReplaceSourceZexport const DEFAULT_CATEGORIES = {
expense: [
{
name: "é¤é¥®",
icon: "\uD83C\uDF5C"
},
{
name: "ä¹°èœ",
icon: "\uD83E\uDD6C"
},
{
name: "交通",
icon: "\uD83D\uDE97"
},
{
name: "购物",
icon: "\uD83D\uDED2"
},
{
name: "娱ä¹",
icon: "\uD83C\uDFAE"
},
{
name: "房租",
icon: "\uD83C\uDFE0"
},
{
name: "房贷",
icon: "\uD83C\uDFE6"
},
{
name: "亲情å¡",
icon: "\uD83D\uDCB3"
},
{
name: "水电",
icon: "\uD83D\uDCA7"
},
{
name: "è¡£æœ",
icon: "\uD83D\uDC54"
},
{
name: "通讯",
icon: "\uD83D\uDCF1"
},
{
name: "日用å“",
icon: "\uD83E\uDDF4"
},
{
name: "美妆",
icon: "\uD83D\uDC84"
},
{
name: "医疗",
icon: "\uD83C\uDFE5"
},
{
name: "养娃",
icon: "\uD83D\uDC76"
},
{
name: "家电",
icon: "\uD83D\uDCFA"
},
{
name: "å®¶å…·",
icon: "\uD83D\uDECBï¸"
},
{
name: "装修",
icon: "\uD83D\uDD28"
},
{
name: "宠物",
icon: "\uD83D\uDC15"
},
{
name: "å…¶ä»–",
icon: "\uD83D\uDCDD"
}
],
income: [
{
name: "工资",
icon: "\uD83D\uDCB0"
},
{
name: "奖金",
icon: "\uD83C\uDF81"
},
{
name: "ç†è´¢",
icon: "\uD83D\uDCC8"
},
{
name: "红包",
icon: "\uD83E\uDDE7"
},
{
name: "å…¶ä»–",
icon: "\uD83D\uDCDD"
}
]
};
†buffer†source„size„maps„hashConcatSourceRawSource­/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ S: function() { return /* binding */ DEFAULT_CATEGORIES; }
/* harmony export */ });
ReplaceSourceRawSourceZexport const DEFAULT_CATEGORIES = {
expense: [
{
name: "é¤é¥®",
icon: "\uD83C\uDF5C"
},
{
name: "ä¹°èœ",
icon: "\uD83E\uDD6C"
},
{
name: "交通",
icon: "\uD83D\uDE97"
},
{
name: "购物",
icon: "\uD83D\uDED2"
},
{
name: "娱ä¹",
icon: "\uD83C\uDFAE"
},
{
name: "房租",
icon: "\uD83C\uDFE0"
},
{
name: "房贷",
icon: "\uD83C\uDFE6"
},
{
name: "亲情å¡",
icon: "\uD83D\uDCB3"
},
{
name: "水电",
icon: "\uD83D\uDCA7"
},
{
name: "è¡£æœ",
icon: "\uD83D\uDC54"
},
{
name: "通讯",
icon: "\uD83D\uDCF1"
},
{
name: "日用å“",
icon: "\uD83E\uDDF4"
},
{
name: "美妆",
icon: "\uD83D\uDC84"
},
{
name: "医疗",
icon: "\uD83C\uDFE5"
},
{
name: "养娃",
icon: "\uD83D\uDC76"
},
{
name: "家电",
icon: "\uD83D\uDCFA"
},
{
name: "å®¶å…·",
icon: "\uD83D\uDECBï¸"
},
{
name: "装修",
icon: "\uD83D\uDD28"
},
{
name: "宠物",
icon: "\uD83D\uDC15"
},
{
name: "å…¶ä»–",
icon: "\uD83D\uDCDD"
}
],
income: [
{
name: "工资",
icon: "\uD83D\uDCB0"
},
{
name: "奖金",
icon: "\uD83C\uDF81"
},
{
name: "ç†è´¢",
icon: "\uD83D\uDCC8"
},
{
name: "红包",
icon: "\uD83E\uDDE7"
},
{
name: "å…¶ä»–",
icon: "\uD83D\uDCDD"
}
]
};
 06undefined
/***/ }) × ØëØ­/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ S: function() { return /* binding */ DEFAULT_CATEGORIES; }
/* harmony export */ });
ÙZexport const DEFAULT_CATEGORIES = {
expense: [
{
name: "é¤é¥®",
icon: "\uD83C\uDF5C"
},
{
name: "ä¹°èœ",
icon: "\uD83E\uDD6C"
},
{
name: "交通",
icon: "\uD83D\uDE97"
},
{
name: "购物",
icon: "\uD83D\uDED2"
},
{
name: "娱ä¹",
icon: "\uD83C\uDFAE"
},
{
name: "房租",
icon: "\uD83C\uDFE0"
},
{
name: "房贷",
icon: "\uD83C\uDFE6"
},
{
name: "亲情å¡",
icon: "\uD83D\uDCB3"
},
{
name: "水电",
icon: "\uD83D\uDCA7"
},
{
name: "è¡£æœ",
icon: "\uD83D\uDC54"
},
{
name: "通讯",
icon: "\uD83D\uDCF1"
},
{
name: "日用å“",
icon: "\uD83E\uDDF4"
},
{
name: "美妆",
icon: "\uD83D\uDC84"
},
{
name: "医疗",
icon: "\uD83C\uDFE5"
},
{
name: "养娃",
icon: "\uD83D\uDC76"
},
{
name: "家电",
icon: "\uD83D\uDCFA"
},
{
name: "å®¶å…·",
icon: "\uD83D\uDECBï¸"
},
{
name: "装修",
icon: "\uD83D\uDD28"
},
{
name: "宠物",
icon: "\uD83D\uDC15"
},
{
name: "å…¶ä»–",
icon: "\uD83D\uDCDD"
}
],
income: [
{
name: "工资",
icon: "\uD83D\uDCB0"
},
{
name: "奖金",
icon: "\uD83C\uDF81"
},
{
name: "ç†è´¢",
icon: "\uD83D\uDCC8"
},
{
name: "红包",
icon: "\uD83E\uDDE7"
},
{
name: "å…¶ä»–",
icon: "\uD83D\uDCDD"
}
]
};
 06undefinedÚÛ,
/***/ 5190:
 «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 ºz«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 */ Recurring; }
/* 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_8__ = __webpack_require__(610);
/* harmony import */ var _lib_categories__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(7063);
/* harmony import */ var _components_AppShell__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(6248);
/* harmony import */ var _components_icons__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(9415);
/* harmony import */ var _components_ToastProvider__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(5546);
/* harmony import */ var _components_ConfirmProvider__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(8846);
«webpack/lib/util/registerExternalSerializerwebpack-sources/ReplaceSource\limport { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { useState, useEffect } from "react";
import { useRouter } from "next/router";
import { isLoggedIn, getRecurringBills, createRecurringBill, updateRecurringBill, deleteRecurringBill, applyRecurringBill } from "../lib/api";
import { DEFAULT_CATEGORIES } from "../lib/categories";
import AppShell from "../components/AppShell";
import { Icon } from "../components/icons";
import { useToast } from "../components/ToastProvider";
import { useConfirm } from "../components/ConfirmProvider";
export default function Recurring() {
const router = useRouter();
const { showToast } = useToast();
const { confirm } = useConfirm();
const [bills, setBills] = useState([]);
const [modalOpen, setModalOpen] = useState(false);
const [editingBill, setEditingBill] = useState(null);
const [loading, setLoading] = useState(true);
const [submitting, setSubmitting] = useState(false);
const [deleting, setDeleting] = useState(false);
const [applyingId, setApplyingId] = useState(null);
const [formError, setFormError] = useState("");
const [formData, setFormData] = useState({
type: "expense",
amount: "",
category: "",
day: "1",
note: "",
is_active: true
});
useEffect(()=>{
if (!isLoggedIn()) {
router.push("/login");
return;
}
loadBills();
}, [
router
]);
const loadBills = async ()=>{
setLoading(true);
try {
const data = await getRecurringBills();
setBills(data);
} catch (err) {
console.error(err);
showToast(err.message, {
tone: "error"
});
} finally{
setLoading(false);
}
};
const openAddModal = ()=>{
setEditingBill(null);
setFormData({
type: "expense",
amount: "",
category: "",
day: "1",
note: "",
is_active: true
});
setFormError("");
setModalOpen(true);
};
const openEditModal = (bill)=>{
setEditingBill(bill);
setFormData({
type: bill.type,
amount: bill.amount.toString(),
category: bill.category,
day: bill.day.toString(),
note: bill.note || "",
is_active: !!bill.is_active
});
setFormError("");
setModalOpen(true);
};
const closeModal = ()=>{
if (submitting || deleting) return;
setModalOpen(false);
setEditingBill(null);
setFormError("");
};
const handleSubmit = async (e)=>{
e.preventDefault();
if (!formData.amount || !formData.category || !formData.day) {
setFormError("请填写完整信æ¯");
return;
}
setSubmitting(true);
setFormError("");
try {
const data = {
type: formData.type,
amount: parseFloat(formData.amount),
category: formData.category,
day: parseInt(formData.day),
note: formData.note,
is_active: formData.is_active ? 1 : 0
};
if (editingBill) {
await updateRecurringBill(editingBill.id, data);
showToast("周期账å•已更新", {
tone: "success"
});
} else {
await createRecurringBill(data);
showToast("周期账å•已创建", {
tone: "success"
});
}
closeModal();
await loadBills();
} catch (err) {
setFormError(err.message);
showToast(err.message, {
tone: "error"
});
} finally{
setSubmitting(false);
}
};
const handleDelete = async ()=>{
if (!editingBill) return;
const shouldDelete = await confirm({
title: "删除这æ¡å‘¨æœŸè´¦å•?",
description: "删除åŽå°†æ— æ³•æ¢å¤ï¼Œè¯·ç¡®è®¤æ˜¯å¦ç»§ç»­ã€‚",
confirmText: "删除"
});
if (!shouldDelete) return;
setDeleting(true);
try {
await deleteRecurringBill(editingBill.id);
closeModal();
showToast("周期账å•已删除", {
tone: "success"
});
await loadBills();
} catch (err) {
setFormError(err.message);
showToast(err.message, {
tone: "error"
});
} finally{
setDeleting(false);
}
};
const handleApply = async (id)=>{
setApplyingId(id);
try {
await applyRecurringBill(id);
showToast("已添加到账目", {
tone: "success"
});
} catch (err) {
showToast(err.message, {
tone: "error"
});
} finally{
setApplyingId(null);
}
};
const handleToggleActive = async (bill, event)=>{
event.stopPropagation();
try {
await updateRecurringBill(bill.id, {
...bill,
is_active: bill.is_active ? 0 : 1
});
showToast(bill.is_active ? "å·²åœç”¨å‘¨æœŸè´¦å•" : "å·²å¯ç”¨å‘¨æœŸè´¦å•", {
tone: "success"
});
await loadBills();
} catch (err) {
showToast(err.message, {
tone: "error"
});
}
};
const days = Array.from({
length: 28
}, (_, index)=>index + 1);
const actions = /*#__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, {
title: "周期账å•",
subtitle: "管ç†å›ºå®šæ”¶å…¥ä¸Žæ”¯å‡ºèŠ‚å¥",
actions: actions,
compactHeader: true,
children: [
loading ? /*#__PURE__*/ _jsx("div", {
className: "card loading-state",
children: "加载中..."
}) : bills.length === 0 ? /*#__PURE__*/ _jsxs("div", {
className: "card empty-state empty-state--card",
children: [
/*#__PURE__*/ _jsx("div", {
className: "empty-state__icon",
children: /*#__PURE__*/ _jsx(Icon, {
name: "repeat",
size: 24
})
}),
/*#__PURE__*/ _jsx("p", {
children: "暂无周期性账å•"
}),
/*#__PURE__*/ _jsx("p", {
className: "sub",
children: "点击å³ä¸‹è§’新增定期收入或支出。"
})
]
}) : /*#__PURE__*/ _jsxs("section", {
className: "card section-card",
children: [
/*#__PURE__*/ _jsx("div", {
className: "section-heading section-heading--compact",
children: /*#__PURE__*/ _jsxs("div", {
children: [
/*#__PURE__*/ _jsx("p", {
className: "eyebrow",
children: "è´¦å•列表"
}),
/*#__PURE__*/ _jsxs("h2", {
children: [
bills.length,
" æ¡å‘¨æœŸè´¦å•"
]
})
]
})
}),
/*#__PURE__*/ _jsx("div", {
className: "bills-list",
children: bills.map((bill)=>{
const categories = DEFAULT_CATEGORIES[bill.type];
const cat = categories.find((item)=>item.name === bill.category) || {
icon: "\uD83D\uDCDD"
};
return /*#__PURE__*/ _jsxs("button", {
type: "button",
className: "bill-item ".concat(bill.is_active ? "" : "inactive"),
onClick: ()=>openEditModal(bill),
children: [
/*#__PURE__*/ _jsxs("div", {
className: "bill-left",
children: [
/*#__PURE__*/ _jsx("div", {
className: "bill-icon",
children: cat.icon
}),
/*#__PURE__*/ _jsxs("div", {
className: "bill-info",
children: [
/*#__PURE__*/ _jsxs("div", {
className: "bill-row",
children: [
/*#__PURE__*/ _jsx("div", {
className: "bill-category",
children: bill.category
}),
/*#__PURE__*/ _jsx("span", {
className: "status-chip ".concat(bill.is_active ? "status-chip--success" : ""),
children: bill.is_active ? "å¯ç”¨ä¸­" : "å·²åœç”¨"
})
]
}),
/*#__PURE__*/ _jsxs("div", {
className: "bill-day",
children: [
"æ¯æœˆ ",
bill.day,
" æ—¥"
]
}),
bill.note && /*#__PURE__*/ _jsx("div", {
className: "bill-note",
children: bill.note
})
]
})
]
}),
/*#__PURE__*/ _jsxs("div", {
className: "bill-right",
children: [
/*#__PURE__*/ _jsxs("div", {
className: "bill-amount ".concat(bill.type),
children: [
bill.type === "income" ? "+" : "-",
bill.amount.toFixed(2)
]
}),
/*#__PURE__*/ _jsxs("div", {
className: "bill-actions",
children: [
/*#__PURE__*/ _jsx("button", {
type: "button",
className: "btn btn-secondary btn-inline btn-small",
onClick: (e)=>{
e.stopPropagation();
handleApply(bill.id);
},
disabled: applyingId === bill.id,
children: applyingId === bill.id ? "应用中..." : "应用"
}),
/*#__PURE__*/ _jsx("button", {
type: "button",
className: "btn btn-ghost btn-inline btn-small",
onClick: (event)=>handleToggleActive(bill, event),
children: bill.is_active ? "åœç”¨" : "å¯ç”¨"
})
]
})
]
})
]
}, bill.id);
})
})
]
}),
/*#__PURE__*/ _jsx("button", {
type: "button",
className: "fab",
"aria-label": "新增账å•",
onClick: openAddModal,
children: /*#__PURE__*/ _jsx(Icon, {
name: "plus",
size: 24
})
}),
modalOpen && /*#__PURE__*/ _jsx("div", {
className: "modal-overlay",
onClick: closeModal,
children: /*#__PURE__*/ _jsxs("div", {
className: "modal",
onClick: (e)=>e.stopPropagation(),
children: [
/*#__PURE__*/ _jsxs("div", {
className: "modal-header",
children: [
/*#__PURE__*/ _jsxs("div", {
children: [
/*#__PURE__*/ _jsx("p", {
className: "eyebrow",
children: editingBill ? "编辑账å•" : "新增账å•"
}),
/*#__PURE__*/ _jsx("h2", {
children: editingBill ? "更新周期账å•" : "创建周期账å•"
})
]
}),
/*#__PURE__*/ _jsx("button", {
type: "button",
className: "icon-button icon-button--ghost",
"aria-label": "关闭弹窗",
onClick: closeModal,
children: /*#__PURE__*/ _jsx(Icon, {
name: "close",
size: 18
})
})
]
}),
formError && /*#__PURE__*/ _jsx("div", {
className: "error-msg",
children: formError
}),
/*#__PURE__*/ _jsxs("form", {
onSubmit: handleSubmit,
children: [
/*#__PURE__*/ _jsxs("div", {
className: "type-switch",
children: [
/*#__PURE__*/ _jsx("button", {
type: "button",
className: "type-btn expense ".concat(formData.type === "expense" ? "active" : ""),
onClick: ()=>setFormData({
...formData,
type: "expense",
category: ""
}),
disabled: submitting || deleting,
children: "支出"
}),
/*#__PURE__*/ _jsx("button", {
type: "button",
className: "type-btn income ".concat(formData.type === "income" ? "active" : ""),
onClick: ()=>setFormData({
...formData,
type: "income",
category: ""
}),
disabled: submitting || deleting,
children: "æ”¶å…¥"
})
]
}),
/*#__PURE__*/ _jsxs("div", {
className: "form-group",
children: [
/*#__PURE__*/ _jsx("label", {
htmlFor: "bill-amount",
children: "金é¢"
}),
/*#__PURE__*/ _jsx("input", {
id: "bill-amount",
type: "number",
className: "amount-input",
value: formData.amount,
onChange: (e)=>setFormData({
...formData,
amount: e.target.value
}),
placeholder: "0.00",
step: "0.01",
required: true,
disabled: submitting || deleting
})
]
}),
/*#__PURE__*/ _jsxs("div", {
className: "form-group",
children: [
/*#__PURE__*/ _jsx("label", {
children: "分类"
}),
/*#__PURE__*/ _jsx("div", {
className: "category-grid",
children: DEFAULT_CATEGORIES[formData.type].map((cat)=>/*#__PURE__*/ _jsxs("button", {
type: "button",
className: "category-btn ".concat(formData.category === cat.name ? "active" : ""),
onClick: ()=>setFormData({
...formData,
category: cat.name
}),
disabled: submitting || deleting,
children: [
/*#__PURE__*/ _jsx("span", {
className: "icon",
children: cat.icon
}),
/*#__PURE__*/ _jsx("span", {
children: cat.name
})
]
}, cat.name))
})
]
}),
/*#__PURE__*/ _jsxs("div", {
className: "form-group",
children: [
/*#__PURE__*/ _jsx("label", {
htmlFor: "bill-day",
children: "æ—¥æœŸï¼ˆæ¯æœˆå‡ å·ï¼‰"
}),
/*#__PURE__*/ _jsx("select", {
id: "bill-day",
value: formData.day,
onChange: (e)=>setFormData({
...formData,
day: e.target.value
}),
disabled: submitting || deleting,
children: days.map((day)=>/*#__PURE__*/ _jsxs("option", {
value: day,
children: [
day,
" æ—¥"
]
}, day))
})
]
}),
/*#__PURE__*/ _jsxs("div", {
className: "form-group",
children: [
/*#__PURE__*/ _jsx("label", {
htmlFor: "bill-note",
children: "备注(å¯é€‰ï¼‰"
}),
/*#__PURE__*/ _jsx("input", {
id: "bill-note",
type: "text",
value: formData.note,
onChange: (e)=>setFormData({
...formData,
note: e.target.value
}),
placeholder: "添加备注...",
disabled: submitting || deleting
})
]
}),
editingBill && /*#__PURE__*/ _jsx("div", {
className: "form-group form-group--inline",
children: /*#__PURE__*/ _jsxs("label", {
className: "checkbox-label",
children: [
/*#__PURE__*/ _jsx("input", {
type: "checkbox",
checked: formData.is_active,
onChange: (e)=>setFormData({
...formData,
is_active: e.target.checked
}),
disabled: submitting || deleting
}),
¯ç”¨æ­¤è´¦å•"
]
})
}),
/*#__PURE__*/ _jsxs("div", {
className: "modal-actions",
children: [
/*#__PURE__*/ _jsx("button", {
type: "submit",
className: "btn btn-primary",
disabled: submitting || deleting,
children: submitting ? /*#__PURE__*/ _jsxs("span", {
className: "button-content",
children: [
/*#__PURE__*/ _jsx(Icon, {
name: "spinner",
size: 16,
className: "is-spinning"
}),
"ä¿å­˜ä¸­..."
]
}) : "ä¿å­˜"
}),
editingBill && /*#__PURE__*/ _jsx("button", {
type: "button",
className: "btn btn-danger",
onClick: handleDelete,
disabled: submitting || deleting,
children: deleting ? "删除中..." : "删除"
})
]
})
]
})
]
})
})
]
});
}
ej>@km_”#%[]ŠŒ¸îð*,:emŒ°¹ÜãKR}µ¼ê#*Y`!)>G-=  ¸ Ê ­ƒ& ÁÄÆÉ-13_:æé|
 ƒˆ36¥¨W[ØÛeiÊÍøûœ­¢ ¦ æ!_ê!£"¦"¦#ª#{$$g%j%³&¶&®(²(Û*Þ*¡,¥,_-c-F/J/0!0I3L3w6z6?7B7D7G7Õ7_Ø7\8`899¬9°9!:$:%;(;-<0<r=u=w=z=ž>¡>R?V?ì?ð?Ÿ@¢@™CœCÑFÕFƒG†GtH_wH:L>LìLïL™MœM%N6N`NdN<Q?QkRnRTTÍTÐTÁUÄU÷WûWZZÇZÊZº[½[Í^Ð^g_Nk_+`.`@cDcõcøc+e/ef f ff=h@h¶(0,next_router__WEBPACK_IMPORTED_MODULE_2__.useRouter)Ì(0,_components_ToastProvider__WEBPACK_IMPORTED_MODULE_6__/* .useToast */ .p)Ð(0,_components_ConfirmProvider__WEBPACK_IMPORTED_MODULE_7__/* .useConfirm */ .N)¯(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)ÿÿÿÿÿÿÿÿ°(0,react__WEBPACK_IMPORTED_MODULE_1__.useEffect)¾(0,_lib_api__WEBPACK_IMPORTED_MODULE_8__/* .isLoggedIn */ .jl)Å(0,_lib_api__WEBPACK_IMPORTED_MODULE_8__/* .getRecurringBills */ .Vn)Ç(0,_lib_api__WEBPACK_IMPORTED_MODULE_8__/* .updateRecurringBill */ .uO)Ç(0,_lib_api__WEBPACK_IMPORTED_MODULE_8__/* .createRecurringBill */ .z0)Ç(0,_lib_api__WEBPACK_IMPORTED_MODULE_8__/* .deleteRecurringBill */ .TZ)Æ(0,_lib_api__WEBPACK_IMPORTED_MODULE_8__/* .applyRecurringBill */ .Xu)ü¶(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)ÿ¼_components_icons__WEBPACK_IMPORTED_MODULE_5__/* .Icon */ .J·(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)Å_components_AppShell__WEBPACK_IMPORTED_MODULE_4__/* ["default"] */ .ZüþüüýüüþüþüþüÈ_lib_categories__WEBPACK_IMPORTED_MODULE_3__/* .DEFAULT_CATEGORIES */ .Sýýûýýûûýûýýýûûûûüûýýýûûûûüûýýûûýûûýûûÿýûûýûûýýûûûýûýûýûüû†buffer†source„size„maps„hashÒ__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": function() { return /* binding */ Recurring; }
/* 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_8__ = __webpack_require__(610);
/* harmony import */ var _lib_categories__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(7063);
/* harmony import */ var _components_AppShell__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(6248);
/* harmony import */ var _components_icons__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(9415);
/* harmony import */ var _components_ToastProvider__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(5546);
/* harmony import */ var _components_ConfirmProvider__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(8846);
function Recurring() {
const router = (0,next_router__WEBPACK_IMPORTED_MODULE_2__.useRouter)();
const { showToast } = (0,_components_ToastProvider__WEBPACK_IMPORTED_MODULE_6__/* .useToast */ .p)();
const { confirm } = (0,_components_ConfirmProvider__WEBPACK_IMPORTED_MODULE_7__/* .useConfirm */ .N)();
const [bills, setBills] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)([]);
const [modalOpen, setModalOpen] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)(false);
const [editingBill, setEditingBill] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)(null);
const [loading, setLoading] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)(true);
const [submitting, setSubmitting] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)(false);
const [deleting, setDeleting] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)(false);
const [applyingId, setApplyingId] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)(null);
const [formError, setFormError] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)("");
const [formData, setFormData] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)({
type: "expense",
amount: "",
category: "",
day: "1",
note: "",
is_active: true
});
(0,react__WEBPACK_IMPORTED_MODULE_1__.useEffect)(()=>{
if (!(0,_lib_api__WEBPACK_IMPORTED_MODULE_8__/* .isLoggedIn */ .jl)()) {
router.push("/login");
return;
}
loadBills();
}, [
router
]);
const loadBills = async ()=>{
setLoading(true);
try {
const data = await (0,_lib_api__WEBPACK_IMPORTED_MODULE_8__/* .getRecurringBills */ .Vn)();
setBills(data);
} catch (err) {
console.error(err);
showToast(err.message, {
tone: "error"
});
} finally{
setLoading(false);
}
};
const openAddModal = ()=>{
setEditingBill(null);
setFormData({
type: "expense",
amount: "",
category: "",
day: "1",
note: "",
is_active: true
});
setFormError("");
setModalOpen(true);
};
const openEditModal = (bill)=>{
setEditingBill(bill);
setFormData({
type: bill.type,
amount: bill.amount.toString(),
category: bill.category,
day: bill.day.toString(),
note: bill.note || "",
is_active: !!bill.is_active
});
setFormError("");
setModalOpen(true);
};
const closeModal = ()=>{
if (submitting || deleting) return;
setModalOpen(false);
setEditingBill(null);
setFormError("");
};
const handleSubmit = async (e)=>{
e.preventDefault();
if (!formData.amount || !formData.category || !formData.day) {
setFormError("请填写完整信æ¯");
return;
}
setSubmitting(true);
setFormError("");
try {
const data = {
type: formData.type,
amount: parseFloat(formData.amount),
category: formData.category,
day: parseInt(formData.day),
note: formData.note,
is_active: formData.is_active ? 1 : 0
};
if (editingBill) {
await (0,_lib_api__WEBPACK_IMPORTED_MODULE_8__/* .updateRecurringBill */ .uO)(editingBill.id, data);
showToast("周期账å•已更新", {
tone: "success"
});
} else {
await (0,_lib_api__WEBPACK_IMPORTED_MODULE_8__/* .createRecurringBill */ .z0)(data);
showToast("周期账å•已创建", {
tone: "success"
});
}
closeModal();
await loadBills();
} catch (err) {
setFormError(err.message);
showToast(err.message, {
tone: "error"
});
} finally{
setSubmitting(false);
}
};
const handleDelete = async ()=>{
if (!editingBill) return;
const shouldDelete = await confirm({
title: "删除这æ¡å‘¨æœŸè´¦å•?",
description: "删除åŽå°†æ— æ³•æ¢å¤ï¼Œè¯·ç¡®è®¤æ˜¯å¦ç»§ç»­ã€‚",
confirmText: "删除"
});
if (!shouldDelete) return;
setDeleting(true);
try {
await (0,_lib_api__WEBPACK_IMPORTED_MODULE_8__/* .deleteRecurringBill */ .TZ)(editingBill.id);
closeModal();
showToast("周期账å•已删除", {
tone: "success"
});
await loadBills();
} catch (err) {
setFormError(err.message);
showToast(err.message, {
tone: "error"
});
} finally{
setDeleting(false);
}
};
const handleApply = async (id)=>{
setApplyingId(id);
try {
await (0,_lib_api__WEBPACK_IMPORTED_MODULE_8__/* .applyRecurringBill */ .Xu)(id);
showToast("已添加到账目", {
tone: "success"
});
} catch (err) {
showToast(err.message, {
tone: "error"
});
} finally{
setApplyingId(null);
}
};
const handleToggleActive = async (bill, event)=>{
event.stopPropagation();
try {
await (0,_lib_api__WEBPACK_IMPORTED_MODULE_8__/* .updateRecurringBill */ .uO)(bill.id, {
...bill,
is_active: bill.is_active ? 0 : 1
});
showToast(bill.is_active ? "å·²åœç”¨å‘¨æœŸè´¦å•" : "å·²å¯ç”¨å‘¨æœŸè´¦å•", {
tone: "success"
});
await loadBills();
} catch (err) {
showToast(err.message, {
tone: "error"
});
}
};
const days = Array.from({
length: 28
}, (_, index)=>index + 1);
const actions = /*#__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_5__/* .Icon */ .J, {
name: "settings",
size: 18
})
});
return /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)(_components_AppShell__WEBPACK_IMPORTED_MODULE_4__/* ["default"] */ .Z, {
title: "周期账å•",
subtitle: "管ç†å›ºå®šæ”¶å…¥ä¸Žæ”¯å‡ºèŠ‚å¥",
actions: actions,
compactHeader: true,
children: [
loading ? /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "card loading-state",
children: "加载中..."
}) : bills.length === 0 ? /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "card empty-state empty-state--card",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "empty-state__icon",
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_components_icons__WEBPACK_IMPORTED_MODULE_5__/* .Icon */ .J, {
name: "repeat",
size: 24
})
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("p", {
children: "暂无周期性账å•"
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("p", {
className: "sub",
children: "点击å³ä¸‹è§’新增定期收入或支出。"
})
]
}) : /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("section", {
className: "card section-card",
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__.jsxs)("h2", {
children: [
bills.length,
" æ¡å‘¨æœŸè´¦å•"
]
})
]
})
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "bills-list",
children: bills.map((bill)=>{
const categories = _lib_categories__WEBPACK_IMPORTED_MODULE_3__/* .DEFAULT_CATEGORIES */ .S[bill.type];
const cat = categories.find((item)=>item.name === bill.category) || {
icon: "\uD83D\uDCDD"
};
return /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("button", {
type: "button",
className: "bill-item ".concat(bill.is_active ? "" : "inactive"),
onClick: ()=>openEditModal(bill),
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "bill-left",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "bill-icon",
children: cat.icon
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "bill-info",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "bill-row",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "bill-category",
children: bill.category
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("span", {
className: "status-chip ".concat(bill.is_active ? "status-chip--success" : ""),
children: bill.is_active ? "å¯ç”¨ä¸­" : "å·²åœç”¨"
})
]
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "bill-day",
children: [
"æ¯æœˆ ",
bill.day,
" æ—¥"
]
}),
bill.note && /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "bill-note",
children: bill.note
})
]
})
]
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "bill-right",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "bill-amount ".concat(bill.type),
children: [
bill.type === "income" ? "+" : "-",
bill.amount.toFixed(2)
]
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "bill-actions",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("button", {
type: "button",
className: "btn btn-secondary btn-inline btn-small",
onClick: (e)=>{
e.stopPropagation();
handleApply(bill.id);
},
disabled: applyingId === bill.id,
children: applyingId === bill.id ? "应用中..." : "应用"
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("button", {
type: "button",
className: "btn btn-ghost btn-inline btn-small",
onClick: (event)=>handleToggleActive(bill, event),
children: bill.is_active ? "åœç”¨" : "å¯ç”¨"
})
]
})
]
})
]
}, bill.id);
})
})
]
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("button", {
type: "button",
className: "fab",
"aria-label": "新增账å•",
onClick: openAddModal,
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_components_icons__WEBPACK_IMPORTED_MODULE_5__/* .Icon */ .J, {
name: "plus",
size: 24
})
}),
modalOpen && /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "modal-overlay",
onClick: closeModal,
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "modal",
onClick: (e)=>e.stopPropagation(),
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "modal-header",
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: editingBill ? "编辑账å•" : "新增账å•"
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("h2", {
children: editingBill ? "更新周期账å•" : "创建周期账å•"
})
]
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("button", {
type: "button",
className: "icon-button icon-button--ghost",
"aria-label": "关闭弹窗",
onClick: closeModal,
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_components_icons__WEBPACK_IMPORTED_MODULE_5__/* .Icon */ .J, {
name: "close",
size: 18
})
})
]
}),
formError && /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "error-msg",
children: formError
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("form", {
onSubmit: handleSubmit,
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "type-switch",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("button", {
type: "button",
className: "type-btn expense ".concat(formData.type === "expense" ? "active" : ""),
onClick: ()=>setFormData({
...formData,
type: "expense",
category: ""
}),
disabled: submitting || deleting,
children: "支出"
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("button", {
type: "button",
className: "type-btn income ".concat(formData.type === "income" ? "active" : ""),
onClick: ()=>setFormData({
...formData,
type: "income",
category: ""
}),
disabled: submitting || deleting,
children: "æ”¶å…¥"
})
]
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "form-group",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("label", {
htmlFor: "bill-amount",
children: "金é¢"
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("input", {
id: "bill-amount",
type: "number",
className: "amount-input",
value: formData.amount,
onChange: (e)=>setFormData({
...formData,
amount: e.target.value
}),
placeholder: "0.00",
step: "0.01",
required: true,
disabled: submitting || deleting
})
]
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "form-group",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("label", {
children: "分类"
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "category-grid",
children: _lib_categories__WEBPACK_IMPORTED_MODULE_3__/* .DEFAULT_CATEGORIES */ .S[formData.type].map((cat)=>/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("button", {
type: "button",
className: "category-btn ".concat(formData.category === cat.name ? "active" : ""),
onClick: ()=>setFormData({
...formData,
category: cat.name
}),
disabled: submitting || deleting,
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("span", {
className: "icon",
children: cat.icon
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("span", {
children: cat.name
})
]
}, cat.name))
})
]
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "form-group",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("label", {
htmlFor: "bill-day",
children: "æ—¥æœŸï¼ˆæ¯æœˆå‡ å·ï¼‰"
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("select", {
id: "bill-day",
value: formData.day,
onChange: (e)=>setFormData({
...formData,
day: e.target.value
}),
disabled: submitting || deleting,
children: days.map((day)=>/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("option", {
value: day,
children: [
day,
" æ—¥"
]
}, day))
})
]
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "form-group",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("label", {
htmlFor: "bill-note",
children: "备注(å¯é€‰ï¼‰"
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("input", {
id: "bill-note",
type: "text",
value: formData.note,
onChange: (e)=>setFormData({
...formData,
note: e.target.value
}),
placeholder: "添加备注...",
disabled: submitting || deleting
})
]
}),
editingBill && /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "form-group form-group--inline",
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("label", {
className: "checkbox-label",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("input", {
type: "checkbox",
checked: formData.is_active,
onChange: (e)=>setFormData({
...formData,
is_active: e.target.checked
}),
disabled: submitting || deleting
}),
¯ç”¨æ­¤è´¦å•"
]
})
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "modal-actions",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("button", {
type: "submit",
className: "btn btn-primary",
disabled: submitting || deleting,
children: submitting ? /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("span", {
className: "button-content",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_components_icons__WEBPACK_IMPORTED_MODULE_5__/* .Icon */ .J, {
name: "spinner",
size: 16,
className: "is-spinning"
}),
"ä¿å­˜ä¸­..."
]
}) : "ä¿å­˜"
}),
editingBill && /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("button", {
type: "button",
className: "btn btn-danger",
onClick: handleDelete,
disabled: submitting || deleting,
children: deleting ? "删除中..." : "删除"
})
]
})
]
})
]
})
})
]
});
}
”{"finalSource":true}ƒmapbufferedMap ConcatSourceRawSourceö__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": function() { return /* binding */ Recurring; }
/* 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_8__ = __webpack_require__(610);
/* harmony import */ var _lib_categories__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(7063);
/* harmony import */ var _components_AppShell__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(6248);
/* harmony import */ var _components_icons__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(9415);
/* harmony import */ var _components_ToastProvider__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(5546);
/* harmony import */ var _components_ConfirmProvider__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(8846);
ReplaceSourceRawSource\limport { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { useState, useEffect } from "react";
import { useRouter } from "next/router";
import { isLoggedIn, getRecurringBills, createRecurringBill, updateRecurringBill, deleteRecurringBill, applyRecurringBill } from "../lib/api";
import { DEFAULT_CATEGORIES } from "../lib/categories";
import AppShell from "../components/AppShell";
import { Icon } from "../components/icons";
import { useToast } from "../components/ToastProvider";
import { useConfirm } from "../components/ConfirmProvider";
export default function Recurring() {
const router = useRouter();
const { showToast } = useToast();
const { confirm } = useConfirm();
const [bills, setBills] = useState([]);
const [modalOpen, setModalOpen] = useState(false);
const [editingBill, setEditingBill] = useState(null);
const [loading, setLoading] = useState(true);
const [submitting, setSubmitting] = useState(false);
const [deleting, setDeleting] = useState(false);
const [applyingId, setApplyingId] = useState(null);
const [formError, setFormError] = useState("");
const [formData, setFormData] = useState({
type: "expense",
amount: "",
category: "",
day: "1",
note: "",
is_active: true
});
useEffect(()=>{
if (!isLoggedIn()) {
router.push("/login");
return;
}
loadBills();
}, [
router
]);
const loadBills = async ()=>{
setLoading(true);
try {
const data = await getRecurringBills();
setBills(data);
} catch (err) {
console.error(err);
showToast(err.message, {
tone: "error"
});
} finally{
setLoading(false);
}
};
const openAddModal = ()=>{
setEditingBill(null);
setFormData({
type: "expense",
amount: "",
category: "",
day: "1",
note: "",
is_active: true
});
setFormError("");
setModalOpen(true);
};
const openEditModal = (bill)=>{
setEditingBill(bill);
setFormData({
type: bill.type,
amount: bill.amount.toString(),
category: bill.category,
day: bill.day.toString(),
note: bill.note || "",
is_active: !!bill.is_active
});
setFormError("");
setModalOpen(true);
};
const closeModal = ()=>{
if (submitting || deleting) return;
setModalOpen(false);
setEditingBill(null);
setFormError("");
};
const handleSubmit = async (e)=>{
e.preventDefault();
if (!formData.amount || !formData.category || !formData.day) {
setFormError("请填写完整信æ¯");
return;
}
setSubmitting(true);
setFormError("");
try {
const data = {
type: formData.type,
amount: parseFloat(formData.amount),
category: formData.category,
day: parseInt(formData.day),
note: formData.note,
is_active: formData.is_active ? 1 : 0
};
if (editingBill) {
await updateRecurringBill(editingBill.id, data);
showToast("周期账å•已更新", {
tone: "success"
});
} else {
await createRecurringBill(data);
showToast("周期账å•已创建", {
tone: "success"
});
}
closeModal();
await loadBills();
} catch (err) {
setFormError(err.message);
showToast(err.message, {
tone: "error"
});
} finally{
setSubmitting(false);
}
};
const handleDelete = async ()=>{
if (!editingBill) return;
const shouldDelete = await confirm({
title: "删除这æ¡å‘¨æœŸè´¦å•?",
description: "删除åŽå°†æ— æ³•æ¢å¤ï¼Œè¯·ç¡®è®¤æ˜¯å¦ç»§ç»­ã€‚",
confirmText: "删除"
});
if (!shouldDelete) return;
setDeleting(true);
try {
await deleteRecurringBill(editingBill.id);
closeModal();
showToast("周期账å•已删除", {
tone: "success"
});
await loadBills();
} catch (err) {
setFormError(err.message);
showToast(err.message, {
tone: "error"
});
} finally{
setDeleting(false);
}
};
const handleApply = async (id)=>{
setApplyingId(id);
try {
await applyRecurringBill(id);
showToast("已添加到账目", {
tone: "success"
});
} catch (err) {
showToast(err.message, {
tone: "error"
});
} finally{
setApplyingId(null);
}
};
const handleToggleActive = async (bill, event)=>{
event.stopPropagation();
try {
await updateRecurringBill(bill.id, {
...bill,
is_active: bill.is_active ? 0 : 1
});
showToast(bill.is_active ? "å·²åœç”¨å‘¨æœŸè´¦å•" : "å·²å¯ç”¨å‘¨æœŸè´¦å•", {
tone: "success"
});
await loadBills();
} catch (err) {
showToast(err.message, {
tone: "error"
});
}
};
const days = Array.from({
length: 28
}, (_, index)=>index + 1);
const actions = /*#__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, {
title: "周期账å•",
subtitle: "管ç†å›ºå®šæ”¶å…¥ä¸Žæ”¯å‡ºèŠ‚å¥",
actions: actions,
compactHeader: true,
children: [
loading ? /*#__PURE__*/ _jsx("div", {
className: "card loading-state",
children: "加载中..."
}) : bills.length === 0 ? /*#__PURE__*/ _jsxs("div", {
className: "card empty-state empty-state--card",
children: [
/*#__PURE__*/ _jsx("div", {
className: "empty-state__icon",
children: /*#__PURE__*/ _jsx(Icon, {
name: "repeat",
size: 24
})
}),
/*#__PURE__*/ _jsx("p", {
children: "暂无周期性账å•"
}),
/*#__PURE__*/ _jsx("p", {
className: "sub",
children: "点击å³ä¸‹è§’新增定期收入或支出。"
})
]
}) : /*#__PURE__*/ _jsxs("section", {
className: "card section-card",
children: [
/*#__PURE__*/ _jsx("div", {
className: "section-heading section-heading--compact",
children: /*#__PURE__*/ _jsxs("div", {
children: [
/*#__PURE__*/ _jsx("p", {
className: "eyebrow",
children: "è´¦å•列表"
}),
/*#__PURE__*/ _jsxs("h2", {
children: [
bills.length,
" æ¡å‘¨æœŸè´¦å•"
]
})
]
})
}),
/*#__PURE__*/ _jsx("div", {
className: "bills-list",
children: bills.map((bill)=>{
const categories = DEFAULT_CATEGORIES[bill.type];
const cat = categories.find((item)=>item.name === bill.category) || {
icon: "\uD83D\uDCDD"
};
return /*#__PURE__*/ _jsxs("button", {
type: "button",
className: "bill-item ".concat(bill.is_active ? "" : "inactive"),
onClick: ()=>openEditModal(bill),
children: [
/*#__PURE__*/ _jsxs("div", {
className: "bill-left",
children: [
/*#__PURE__*/ _jsx("div", {
className: "bill-icon",
children: cat.icon
}),
/*#__PURE__*/ _jsxs("div", {
className: "bill-info",
children: [
/*#__PURE__*/ _jsxs("div", {
className: "bill-row",
children: [
/*#__PURE__*/ _jsx("div", {
className: "bill-category",
children: bill.category
}),
/*#__PURE__*/ _jsx("span", {
className: "status-chip ".concat(bill.is_active ? "status-chip--success" : ""),
children: bill.is_active ? "å¯ç”¨ä¸­" : "å·²åœç”¨"
})
]
}),
/*#__PURE__*/ _jsxs("div", {
className: "bill-day",
children: [
"æ¯æœˆ ",
bill.day,
" æ—¥"
]
}),
bill.note && /*#__PURE__*/ _jsx("div", {
className: "bill-note",
children: bill.note
})
]
})
]
}),
/*#__PURE__*/ _jsxs("div", {
className: "bill-right",
children: [
/*#__PURE__*/ _jsxs("div", {
className: "bill-amount ".concat(bill.type),
children: [
bill.type === "income" ? "+" : "-",
bill.amount.toFixed(2)
]
}),
/*#__PURE__*/ _jsxs("div", {
className: "bill-actions",
children: [
/*#__PURE__*/ _jsx("button", {
type: "button",
className: "btn btn-secondary btn-inline btn-small",
onClick: (e)=>{
e.stopPropagation();
handleApply(bill.id);
},
disabled: applyingId === bill.id,
children: applyingId === bill.id ? "应用中..." : "应用"
}),
/*#__PURE__*/ _jsx("button", {
type: "button",
className: "btn btn-ghost btn-inline btn-small",
onClick: (event)=>handleToggleActive(bill, event),
children: bill.is_active ? "åœç”¨" : "å¯ç”¨"
})
]
})
]
})
]
}, bill.id);
})
})
]
}),
/*#__PURE__*/ _jsx("button", {
type: "button",
className: "fab",
"aria-label": "新增账å•",
onClick: openAddModal,
children: /*#__PURE__*/ _jsx(Icon, {
name: "plus",
size: 24
})
}),
modalOpen && /*#__PURE__*/ _jsx("div", {
className: "modal-overlay",
onClick: closeModal,
children: /*#__PURE__*/ _jsxs("div", {
className: "modal",
onClick: (e)=>e.stopPropagation(),
children: [
/*#__PURE__*/ _jsxs("div", {
className: "modal-header",
children: [
/*#__PURE__*/ _jsxs("div", {
children: [
/*#__PURE__*/ _jsx("p", {
className: "eyebrow",
children: editingBill ? "编辑账å•" : "新增账å•"
}),
/*#__PURE__*/ _jsx("h2", {
children: editingBill ? "更新周期账å•" : "创建周期账å•"
})
]
}),
/*#__PURE__*/ _jsx("button", {
type: "button",
className: "icon-button icon-button--ghost",
"aria-label": "关闭弹窗",
onClick: closeModal,
children: /*#__PURE__*/ _jsx(Icon, {
name: "close",
size: 18
})
})
]
}),
formError && /*#__PURE__*/ _jsx("div", {
className: "error-msg",
children: formError
}),
/*#__PURE__*/ _jsxs("form", {
onSubmit: handleSubmit,
children: [
/*#__PURE__*/ _jsxs("div", {
className: "type-switch",
children: [
/*#__PURE__*/ _jsx("button", {
type: "button",
className: "type-btn expense ".concat(formData.type === "expense" ? "active" : ""),
onClick: ()=>setFormData({
...formData,
type: "expense",
category: ""
}),
disabled: submitting || deleting,
children: "支出"
}),
/*#__PURE__*/ _jsx("button", {
type: "button",
className: "type-btn income ".concat(formData.type === "income" ? "active" : ""),
onClick: ()=>setFormData({
...formData,
type: "income",
category: ""
}),
disabled: submitting || deleting,
children: "æ”¶å…¥"
})
]
}),
/*#__PURE__*/ _jsxs("div", {
className: "form-group",
children: [
/*#__PURE__*/ _jsx("label", {
htmlFor: "bill-amount",
children: "金é¢"
}),
/*#__PURE__*/ _jsx("input", {
id: "bill-amount",
type: "number",
className: "amount-input",
value: formData.amount,
onChange: (e)=>setFormData({
...formData,
amount: e.target.value
}),
placeholder: "0.00",
step: "0.01",
required: true,
disabled: submitting || deleting
})
]
}),
/*#__PURE__*/ _jsxs("div", {
className: "form-group",
children: [
/*#__PURE__*/ _jsx("label", {
children: "分类"
}),
/*#__PURE__*/ _jsx("div", {
className: "category-grid",
children: DEFAULT_CATEGORIES[formData.type].map((cat)=>/*#__PURE__*/ _jsxs("button", {
type: "button",
className: "category-btn ".concat(formData.category === cat.name ? "active" : ""),
onClick: ()=>setFormData({
...formData,
category: cat.name
}),
disabled: submitting || deleting,
children: [
/*#__PURE__*/ _jsx("span", {
className: "icon",
children: cat.icon
}),
/*#__PURE__*/ _jsx("span", {
children: cat.name
})
]
}, cat.name))
})
]
}),
/*#__PURE__*/ _jsxs("div", {
className: "form-group",
children: [
/*#__PURE__*/ _jsx("label", {
htmlFor: "bill-day",
children: "æ—¥æœŸï¼ˆæ¯æœˆå‡ å·ï¼‰"
}),
/*#__PURE__*/ _jsx("select", {
id: "bill-day",
value: formData.day,
onChange: (e)=>setFormData({
...formData,
day: e.target.value
}),
disabled: submitting || deleting,
children: days.map((day)=>/*#__PURE__*/ _jsxs("option", {
value: day,
children: [
day,
" æ—¥"
]
}, day))
})
]
}),
/*#__PURE__*/ _jsxs("div", {
className: "form-group",
children: [
/*#__PURE__*/ _jsx("label", {
htmlFor: "bill-note",
children: "备注(å¯é€‰ï¼‰"
}),
/*#__PURE__*/ _jsx("input", {
id: "bill-note",
type: "text",
value: formData.note,
onChange: (e)=>setFormData({
...formData,
note: e.target.value
}),
placeholder: "添加备注...",
disabled: submitting || deleting
})
]
}),
editingBill && /*#__PURE__*/ _jsx("div", {
className: "form-group form-group--inline",
children: /*#__PURE__*/ _jsxs("label", {
className: "checkbox-label",
children: [
/*#__PURE__*/ _jsx("input", {
type: "checkbox",
checked: formData.is_active,
onChange: (e)=>setFormData({
...formData,
is_active: e.target.checked
}),
disabled: submitting || deleting
}),
¯ç”¨æ­¤è´¦å•"
]
})
}),
/*#__PURE__*/ _jsxs("div", {
className: "modal-actions",
children: [
/*#__PURE__*/ _jsx("button", {
type: "submit",
className: "btn btn-primary",
disabled: submitting || deleting,
children: submitting ? /*#__PURE__*/ _jsxs("span", {
className: "button-content",
children: [
/*#__PURE__*/ _jsx(Icon, {
name: "spinner",
size: 16,
className: "is-spinning"
}),
"ä¿å­˜ä¸­..."
]
}) : "ä¿å­˜"
}),
editingBill && /*#__PURE__*/ _jsx("button", {
type: "button",
className: "btn btn-danger",
onClick: handleDelete,
disabled: submitting || deleting,
children: deleting ? "删除中..." : "删除"
})
]
})
]
})
]
})
})
]
});
}
H062undefined64107undefined109148undefined150291undefined293347undefined349394undefined396438undefined440494undefined496554undefined556570undefined613621(0,next_router__WEBPACK_IMPORTED_MODULE_2__.useRouter)undefined652659(0,_components_ToastProvider__WEBPACK_IMPORTED_MODULE_6__/* .useToast */ .p)undefined688697(0,_components_ConfirmProvider__WEBPACK_IMPORTED_MODULE_7__/* .useConfirm */ .N)undefined732739(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined784791(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined843850(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined893900(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined949956(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined10021009(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined10591066(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined11131120(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined11631170(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined13131321(0,react__WEBPACK_IMPORTED_MODULE_1__.useEffect)undefined13421351(0,_lib_api__WEBPACK_IMPORTED_MODULE_8__/* .isLoggedIn */ .jl)undefined15811597(0,_lib_api__WEBPACK_IMPORTED_MODULE_8__/* .getRecurringBills */ .Vn)undefined33313349(0,_lib_api__WEBPACK_IMPORTED_MODULE_8__/* .updateRecurringBill */ .uO)undefined35123530(0,_lib_api__WEBPACK_IMPORTED_MODULE_8__/* .createRecurringBill */ .z0)undefined42514269(0,_lib_api__WEBPACK_IMPORTED_MODULE_8__/* .deleteRecurringBill */ .TZ)undefined47394756(0,_lib_api__WEBPACK_IMPORTED_MODULE_8__/* .applyRecurringBill */ .Xu)undefined51405158(0,_lib_api__WEBPACK_IMPORTED_MODULE_8__/* .updateRecurringBill */ .uO)undefined56435646(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined58255828(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined58305833_components_icons__WEBPACK_IMPORTED_MODULE_5__/* .Icon */ .Jundefined59335937(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined59395946_components_AppShell__WEBPACK_IMPORTED_MODULE_4__/* ["default"] */ .Zundefined61186121(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined62686272(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined64106413(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined65286531(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined65336536_components_icons__WEBPACK_IMPORTED_MODULE_5__/* .Icon */ .Jundefined67076710(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined68216824(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined69997003(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined71287131(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined72697273(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined73707373(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined75757579(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined79287931(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined80928109_lib_categories__WEBPACK_IMPORTED_MODULE_3__/* .DEFAULT_CATEGORIES */ .Sundefined83548358(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined86788682(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined88678870(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined91269130(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined93399343(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined95759578(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined99079910(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined1041410418(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined1097110974(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined1142511429(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined1161511619(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined1210212106(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined1231812321(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined1312913132(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined1394313946(0,react_jsx_runtime__WEBPACK
/***/ }) Î ÏâÏö__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": function() { return /* binding */ Recurring; }
/* 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_8__ = __webpack_require__(610);
/* harmony import */ var _lib_categories__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(7063);
/* harmony import */ var _components_AppShell__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(6248);
/* harmony import */ var _components_icons__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(9415);
/* harmony import */ var _components_ToastProvider__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(5546);
/* harmony import */ var _components_ConfirmProvider__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(8846);
Ð\limport { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { useState, useEffect } from "react";
import { useRouter } from "next/router";
import { isLoggedIn, getRecurringBills, createRecurringBill, updateRecurringBill, deleteRecurringBill, applyRecurringBill } from "../lib/api";
import { DEFAULT_CATEGORIES } from "../lib/categories";
import AppShell from "../components/AppShell";
import { Icon } from "../components/icons";
import { useToast } from "../components/ToastProvider";
import { useConfirm } from "../components/ConfirmProvider";
export default function Recurring() {
const router = useRouter();
const { showToast } = useToast();
const { confirm } = useConfirm();
const [bills, setBills] = useState([]);
const [modalOpen, setModalOpen] = useState(false);
const [editingBill, setEditingBill] = useState(null);
const [loading, setLoading] = useState(true);
const [submitting, setSubmitting] = useState(false);
const [deleting, setDeleting] = useState(false);
const [applyingId, setApplyingId] = useState(null);
const [formError, setFormError] = useState("");
const [formData, setFormData] = useState({
type: "expense",
amount: "",
category: "",
day: "1",
note: "",
is_active: true
});
useEffect(()=>{
if (!isLoggedIn()) {
router.push("/login");
return;
}
loadBills();
}, [
router
]);
const loadBills = async ()=>{
setLoading(true);
try {
const data = await getRecurringBills();
setBills(data);
} catch (err) {
console.error(err);
showToast(err.message, {
tone: "error"
});
} finally{
setLoading(false);
}
};
const openAddModal = ()=>{
setEditingBill(null);
setFormData({
type: "expense",
amount: "",
category: "",
day: "1",
note: "",
is_active: true
});
setFormError("");
setModalOpen(true);
};
const openEditModal = (bill)=>{
setEditingBill(bill);
setFormData({
type: bill.type,
amount: bill.amount.toString(),
category: bill.category,
day: bill.day.toString(),
note: bill.note || "",
is_active: !!bill.is_active
});
setFormError("");
setModalOpen(true);
};
const closeModal = ()=>{
if (submitting || deleting) return;
setModalOpen(false);
setEditingBill(null);
setFormError("");
};
const handleSubmit = async (e)=>{
e.preventDefault();
if (!formData.amount || !formData.category || !formData.day) {
setFormError("请填写完整信æ¯");
return;
}
setSubmitting(true);
setFormError("");
try {
const data = {
type: formData.type,
amount: parseFloat(formData.amount),
category: formData.category,
day: parseInt(formData.day),
note: formData.note,
is_active: formData.is_active ? 1 : 0
};
if (editingBill) {
await updateRecurringBill(editingBill.id, data);
showToast("周期账å•已更新", {
tone: "success"
});
} else {
await createRecurringBill(data);
showToast("周期账å•已创建", {
tone: "success"
});
}
closeModal();
await loadBills();
} catch (err) {
setFormError(err.message);
showToast(err.message, {
tone: "error"
});
} finally{
setSubmitting(false);
}
};
const handleDelete = async ()=>{
if (!editingBill) return;
const shouldDelete = await confirm({
title: "删除这æ¡å‘¨æœŸè´¦å•?",
description: "删除åŽå°†æ— æ³•æ¢å¤ï¼Œè¯·ç¡®è®¤æ˜¯å¦ç»§ç»­ã€‚",
confirmText: "删除"
});
if (!shouldDelete) return;
setDeleting(true);
try {
await deleteRecurringBill(editingBill.id);
closeModal();
showToast("周期账å•已删除", {
tone: "success"
});
await loadBills();
} catch (err) {
setFormError(err.message);
showToast(err.message, {
tone: "error"
});
} finally{
setDeleting(false);
}
};
const handleApply = async (id)=>{
setApplyingId(id);
try {
await applyRecurringBill(id);
showToast("已添加到账目", {
tone: "success"
});
} catch (err) {
showToast(err.message, {
tone: "error"
});
} finally{
setApplyingId(null);
}
};
const handleToggleActive = async (bill, event)=>{
event.stopPropagation();
try {
await updateRecurringBill(bill.id, {
...bill,
is_active: bill.is_active ? 0 : 1
});
showToast(bill.is_active ? "å·²åœç”¨å‘¨æœŸè´¦å•" : "å·²å¯ç”¨å‘¨æœŸè´¦å•", {
tone: "success"
});
await loadBills();
} catch (err) {
showToast(err.message, {
tone: "error"
});
}
};
const days = Array.from({
length: 28
}, (_, index)=>index + 1);
const actions = /*#__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, {
title: "周期账å•",
subtitle: "管ç†å›ºå®šæ”¶å…¥ä¸Žæ”¯å‡ºèŠ‚å¥",
actions: actions,
compactHeader: true,
children: [
loading ? /*#__PURE__*/ _jsx("div", {
className: "card loading-state",
children: "加载中..."
}) : bills.length === 0 ? /*#__PURE__*/ _jsxs("div", {
className: "card empty-state empty-state--card",
children: [
/*#__PURE__*/ _jsx("div", {
className: "empty-state__icon",
children: /*#__PURE__*/ _jsx(Icon, {
name: "repeat",
size: 24
})
}),
/*#__PURE__*/ _jsx("p", {
children: "暂无周期性账å•"
}),
/*#__PURE__*/ _jsx("p", {
className: "sub",
children: "点击å³ä¸‹è§’新增定期收入或支出。"
})
]
}) : /*#__PURE__*/ _jsxs("section", {
className: "card section-card",
children: [
/*#__PURE__*/ _jsx("div", {
className: "section-heading section-heading--compact",
children: /*#__PURE__*/ _jsxs("div", {
children: [
/*#__PURE__*/ _jsx("p", {
className: "eyebrow",
children: "è´¦å•列表"
}),
/*#__PURE__*/ _jsxs("h2", {
children: [
bills.length,
" æ¡å‘¨æœŸè´¦å•"
]
})
]
})
}),
/*#__PURE__*/ _jsx("div", {
className: "bills-list",
children: bills.map((bill)=>{
const categories = DEFAULT_CATEGORIES[bill.type];
const cat = categories.find((item)=>item.name === bill.category) || {
icon: "\uD83D\uDCDD"
};
return /*#__PURE__*/ _jsxs("button", {
type: "button",
className: "bill-item ".concat(bill.is_active ? "" : "inactive"),
onClick: ()=>openEditModal(bill),
children: [
/*#__PURE__*/ _jsxs("div", {
className: "bill-left",
children: [
/*#__PURE__*/ _jsx("div", {
className: "bill-icon",
children: cat.icon
}),
/*#__PURE__*/ _jsxs("div", {
className: "bill-info",
children: [
/*#__PURE__*/ _jsxs("div", {
className: "bill-row",
children: [
/*#__PURE__*/ _jsx("div", {
className: "bill-category",
children: bill.category
}),
/*#__PURE__*/ _jsx("span", {
className: "status-chip ".concat(bill.is_active ? "status-chip--success" : ""),
children: bill.is_active ? "å¯ç”¨ä¸­" : "å·²åœç”¨"
})
]
}),
/*#__PURE__*/ _jsxs("div", {
className: "bill-day",
children: [
"æ¯æœˆ ",
bill.day,
" æ—¥"
]
}),
bill.note && /*#__PURE__*/ _jsx("div", {
className: "bill-note",
children: bill.note
})
]
})
]
}),
/*#__PURE__*/ _jsxs("div", {
className: "bill-right",
children: [
/*#__PURE__*/ _jsxs("div", {
className: "bill-amount ".concat(bill.type),
children: [
bill.type === "income" ? "+" : "-",
bill.amount.toFixed(2)
]
}),
/*#__PURE__*/ _jsxs("div", {
className: "bill-actions",
children: [
/*#__PURE__*/ _jsx("button", {
type: "button",
className: "btn btn-secondary btn-inline btn-small",
onClick: (e)=>{
e.stopPropagation();
handleApply(bill.id);
},
disabled: applyingId === bill.id,
children: applyingId === bill.id ? "应用中..." : "应用"
}),
/*#__PURE__*/ _jsx("button", {
type: "button",
className: "btn btn-ghost btn-inline btn-small",
onClick: (event)=>handleToggleActive(bill, event),
children: bill.is_active ? "åœç”¨" : "å¯ç”¨"
})
]
})
]
})
]
}, bill.id);
})
})
]
}),
/*#__PURE__*/ _jsx("button", {
type: "button",
className: "fab",
"aria-label": "新增账å•",
onClick: openAddModal,
children: /*#__PURE__*/ _jsx(Icon, {
name: "plus",
size: 24
})
}),
modalOpen && /*#__PURE__*/ _jsx("div", {
className: "modal-overlay",
onClick: closeModal,
children: /*#__PURE__*/ _jsxs("div", {
className: "modal",
onClick: (e)=>e.stopPropagation(),
children: [
/*#__PURE__*/ _jsxs("div", {
className: "modal-header",
children: [
/*#__PURE__*/ _jsxs("div", {
children: [
/*#__PURE__*/ _jsx("p", {
className: "eyebrow",
children: editingBill ? "编辑账å•" : "新增账å•"
}),
/*#__PURE__*/ _jsx("h2", {
children: editingBill ? "更新周期账å•" : "创建周期账å•"
})
]
}),
/*#__PURE__*/ _jsx("button", {
type: "button",
className: "icon-button icon-button--ghost",
"aria-label": "关闭弹窗",
onClick: closeModal,
children: /*#__PURE__*/ _jsx(Icon, {
name: "close",
size: 18
})
})
]
}),
formError && /*#__PURE__*/ _jsx("div", {
className: "error-msg",
children: formError
}),
/*#__PURE__*/ _jsxs("form", {
onSubmit: handleSubmit,
children: [
/*#__PURE__*/ _jsxs("div", {
className: "type-switch",
children: [
/*#__PURE__*/ _jsx("button", {
type: "button",
className: "type-btn expense ".concat(formData.type === "expense" ? "active" : ""),
onClick: ()=>setFormData({
...formData,
type: "expense",
category: ""
}),
disabled: submitting || deleting,
children: "支出"
}),
/*#__PURE__*/ _jsx("button", {
type: "button",
className: "type-btn income ".concat(formData.type === "income" ? "active" : ""),
onClick: ()=>setFormData({
...formData,
type: "income",
category: ""
}),
disabled: submitting || deleting,
children: "æ”¶å…¥"
})
]
}),
/*#__PURE__*/ _jsxs("div", {
className: "form-group",
children: [
/*#__PURE__*/ _jsx("label", {
htmlFor: "bill-amount",
children: "金é¢"
}),
/*#__PURE__*/ _jsx("input", {
id: "bill-amount",
type: "number",
className: "amount-input",
value: formData.amount,
onChange: (e)=>setFormData({
...formData,
amount: e.target.value
}),
placeholder: "0.00",
step: "0.01",
required: true,
disabled: submitting || deleting
})
]
}),
/*#__PURE__*/ _jsxs("div", {
className: "form-group",
children: [
/*#__PURE__*/ _jsx("label", {
children: "分类"
}),
/*#__PURE__*/ _jsx("div", {
className: "category-grid",
children: DEFAULT_CATEGORIES[formData.type].map((cat)=>/*#__PURE__*/ _jsxs("button", {
type: "button",
className: "category-btn ".concat(formData.category === cat.name ? "active" : ""),
onClick: ()=>setFormData({
...formData,
category: cat.name
}),
disabled: submitting || deleting,
children: [
/*#__PURE__*/ _jsx("span", {
className: "icon",
children: cat.icon
}),
/*#__PURE__*/ _jsx("span", {
children: cat.name
})
]
}, cat.name))
})
]
}),
/*#__PURE__*/ _jsxs("div", {
className: "form-group",
children: [
/*#__PURE__*/ _jsx("label", {
htmlFor: "bill-day",
children: "æ—¥æœŸï¼ˆæ¯æœˆå‡ å·ï¼‰"
}),
/*#__PURE__*/ _jsx("select", {
id: "bill-day",
value: formData.day,
onChange: (e)=>setFormData({
...formData,
day: e.target.value
}),
disabled: submitting || deleting,
children: days.map((day)=>/*#__PURE__*/ _jsxs("option", {
value: day,
children: [
day,
" æ—¥"
]
}, day))
})
]
}),
/*#__PURE__*/ _jsxs("div", {
className: "form-group",
children: [
/*#__PURE__*/ _jsx("label", {
htmlFor: "bill-note",
children: "备注(å¯é€‰ï¼‰"
}),
/*#__PURE__*/ _jsx("input", {
id: "bill-note",
type: "text",
value: formData.note,
onChange: (e)=>setFormData({
...formData,
note: e.target.value
}),
placeholder: "添加备注...",
disabled: submitting || deleting
})
]
}),
editingBill && /*#__PURE__*/ _jsx("div", {
className: "form-group form-group--inline",
children: /*#__PURE__*/ _jsxs("label", {
className: "checkbox-label",
children: [
/*#__PURE__*/ _jsx("input", {
type: "checkbox",
checked: formData.is_active,
onChange: (e)=>setFormData({
...formData,
is_active: e.target.checked
}),
disabled: submitting || deleting
}),
¯ç”¨æ­¤è´¦å•"
]
})
}),
/*#__PURE__*/ _jsxs("div", {
className: "modal-actions",
children: [
/*#__PURE__*/ _jsx("button", {
type: "submit",
className: "btn btn-primary",
disabled: submitting || deleting,
children: submitting ? /*#__PURE__*/ _jsxs("span", {
className: "button-content",
children: [
/*#__PURE__*/ _jsx(Icon, {
name: "spinner",
size: 16,
className: "is-spinning"
}),
"ä¿å­˜ä¸­..."
]
}) : "ä¿å­˜"
}),
editingBill && /*#__PURE__*/ _jsx("button", {
type: "button",
className: "btn btn-danger",
onClick: handleDelete,
disabled: submitting || deleting,
children: deleting ? "删除中..." : "删除"
})
]
})
]
})
]
})
})
]
});
}
H062undefined64107undefined109148undefined150291undefined293347undefined349394undefined396438undefined440494undefined496554undefined556570undefined613621(0,next_router__WEBPACK_IMPORTED_MODULE_2__.useRouter)undefined652659(0,_components_ToastProvider__WEBPACK_IMPORTED_MODULE_6__/* .useToast */ .p)undefined688697(0,_components_ConfirmProvider__WEBPACK_IMPORTED_MODULE_7__/* .useConfirm */ .N)undefined732739(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined784791(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined843850(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined893900(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined949956(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined10021009(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined10591066(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined11131120(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined11631170(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined13131321(0,react__WEBPACK_IMPORTED_MODULE_1__.useEffect)undefined13421351(0,_lib_api__WEBPACK_IMPORTED_MODULE_8__/* .isLoggedIn */ .jl)undefined15811597(0,_lib_api__WEBPACK_IMPORTED_MODULE_8__/* .getRecurringBills */ .Vn)undefined33313349(0,_lib_api__WEBPACK_IMPORTED_MODULE_8__/* .updateRecurringBill */ .uO)undefined35123530(0,_lib_api__WEBPACK_IMPORTED_MODULE_8__/* .createRecurringBill */ .z0)undefined42514269(0,_lib_api__WEBPACK_IMPORTED_MODULE_8__/* .deleteRecurringBill */ .TZ)undefined47394756(0,_lib_api__WEBPACK_IMPORTED_MODULE_8__/* .applyRecurringBill */ .Xu)undefined51405158(0,_lib_api__WEBPACK_IMPORTED_MODULE_8__/* .updateRecurringBill */ .uO)undefined56435646(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined58255828(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined58305833_components_icons__WEBPACK_IMPORTED_MODULE_5__/* .Icon */ .Jundefined59335937(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined59395946_components_AppShell__WEBPACK_IMPORTED_MODULE_4__/* ["default"] */ .Zundefined61186121(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined62686272(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined64106413(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined65286531(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined65336536_components_icons__WEBPACK_IMPORTED_MODULE_5__/* .Icon */ .Jundefined67076710(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined68216824(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined69997003(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined71287131(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined72697273(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined73707373(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined75757579(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined79287931(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined80928109_lib_categories__WEBPACK_IMPORTED_MODULE_3__/* .DEFAULT_CATEGORIES */ .Sundefined83548358(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined86788682(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined88678870(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined91269130(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined93399343(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined95759578(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined99079910(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined1041410418(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined1097110974(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined1142511429(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined1161511619(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined1210212106(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined1231812321(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined1312913132(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined1394313946(0,react_jsx_runtime__WEBPACK
/***/ 1163:
 g«webpack/lib/util/registerExternalSerializerœwebpack-sources/ConcatSource«webpack/lib/util/registerExternalSerializer™webpack-sources/RawSourceJ/***/ (function(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/RawSource1module.exports = require('./dist/client/router')
`/ªmodule.exports = __webpack_require__(3079)†buffer†source„size„maps„hash+module.exports = __webpack_require__(3079)
”{"finalSource":true}ƒmapbufferedMap ReplaceSourceRawSource1module.exports = require('./dist/client/router')
6047module.exports = __webpack_require__(3079)undefined
/***/ }) Å/***/ (function(module, __unused_webpack_exports, __webpack_require__) {
module.exports = __webpack_require__(3079)
/***/ }) ÓÖ ÄJ/***/ (function(module, __unused_webpack_exports, __webpack_require__) {
Å1module.exports = require('./dist/client/router')
6047module.exports = __webpack_require__(3079)undefinedÆÇ
},
«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, [888,774,179], function() { return __webpack_exec__(8208); });
var __webpack_exports__ = __webpack_require__.O();
_N_E = __webpack_exports__;
}
]); `H±W(self["webpackChunk_N_E"] = self["webpackChunk_N_E"] || []).push([[845],{
/***/ 8208:
°Õ²$
(window.__NEXT_P = window.__NEXT_P || []).push([
"/recurring",
function () {
return require("private-next-pages/recurring.js");
}
]);
if(module.hot) {
module.hot.dispose(function () {
window.__NEXT_P.push(["/recurring"])
});
}
Z109115__webpack_require__undefined1171495190undefined176185falseundefined188288{}undefinedÊÖÊ׮خٰÚÛÊÖÊܮخݰÞßÊÖÊ󮨮º°ÿÿÿ»ÊÖÊ,
/***/ 5190:
­×­ö__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": function() { return /* binding */ Recurring; }
/* 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_8__ = __webpack_require__(610);
/* harmony import */ var _lib_categories__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(7063);
/* harmony import */ var _components_AppShell__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(6248);
/* harmony import */ var _components_icons__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(9415);
/* harmony import */ var _components_ToastProvider__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(5546);
/* harmony import */ var _components_ConfirmProvider__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(8846);
®\limport { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { useState, useEffect } from "react";
import { useRouter } from "next/router";
import { isLoggedIn, getRecurringBills, createRecurringBill, updateRecurringBill, deleteRecurringBill, applyRecurringBill } from "../lib/api";
import { DEFAULT_CATEGORIES } from "../lib/categories";
import AppShell from "../components/AppShell";
import { Icon } from "../components/icons";
import { useToast } from "../components/ToastProvider";
import { useConfirm } from "../components/ConfirmProvider";
export default function Recurring() {
const router = useRouter();
const { showToast } = useToast();
const { confirm } = useConfirm();
const [bills, setBills] = useState([]);
const [modalOpen, setModalOpen] = useState(false);
const [editingBill, setEditingBill] = useState(null);
const [loading, setLoading] = useState(true);
const [submitting, setSubmitting] = useState(false);
const [deleting, setDeleting] = useState(false);
const [applyingId, setApplyingId] = useState(null);
const [formError, setFormError] = useState("");
const [formData, setFormData] = useState({
type: "expense",
amount: "",
category: "",
day: "1",
note: "",
is_active: true
});
useEffect(()=>{
if (!isLoggedIn()) {
router.push("/login");
return;
}
loadBills();
}, [
router
]);
const loadBills = async ()=>{
setLoading(true);
try {
const data = await getRecurringBills();
setBills(data);
} catch (err) {
console.error(err);
showToast(err.message, {
tone: "error"
});
} finally{
setLoading(false);
}
};
const openAddModal = ()=>{
setEditingBill(null);
setFormData({
type: "expense",
amount: "",
category: "",
day: "1",
note: "",
is_active: true
});
setFormError("");
setModalOpen(true);
};
const openEditModal = (bill)=>{
setEditingBill(bill);
setFormData({
type: bill.type,
amount: bill.amount.toString(),
category: bill.category,
day: bill.day.toString(),
note: bill.note || "",
is_active: !!bill.is_active
});
setFormError("");
setModalOpen(true);
};
const closeModal = ()=>{
if (submitting || deleting) return;
setModalOpen(false);
setEditingBill(null);
setFormError("");
};
const handleSubmit = async (e)=>{
e.preventDefault();
if (!formData.amount || !formData.category || !formData.day) {
setFormError("请填写完整信æ¯");
return;
}
setSubmitting(true);
setFormError("");
try {
const data = {
type: formData.type,
amount: parseFloat(formData.amount),
category: formData.category,
day: parseInt(formData.day),
note: formData.note,
is_active: formData.is_active ? 1 : 0
};
if (editingBill) {
await updateRecurringBill(editingBill.id, data);
showToast("周期账å•已更新", {
tone: "success"
});
} else {
await createRecurringBill(data);
showToast("周期账å•已创建", {
tone: "success"
});
}
closeModal();
await loadBills();
} catch (err) {
setFormError(err.message);
showToast(err.message, {
tone: "error"
});
} finally{
setSubmitting(false);
}
};
const handleDelete = async ()=>{
if (!editingBill) return;
const shouldDelete = await confirm({
title: "删除这æ¡å‘¨æœŸè´¦å•?",
description: "删除åŽå°†æ— æ³•æ¢å¤ï¼Œè¯·ç¡®è®¤æ˜¯å¦ç»§ç»­ã€‚",
confirmText: "删除"
});
if (!shouldDelete) return;
setDeleting(true);
try {
await deleteRecurringBill(editingBill.id);
closeModal();
showToast("周期账å•已删除", {
tone: "success"
});
await loadBills();
} catch (err) {
setFormError(err.message);
showToast(err.message, {
tone: "error"
});
} finally{
setDeleting(false);
}
};
const handleApply = async (id)=>{
setApplyingId(id);
try {
await applyRecurringBill(id);
showToast("已添加到账目", {
tone: "success"
});
} catch (err) {
showToast(err.message, {
tone: "error"
});
} finally{
setApplyingId(null);
}
};
const handleToggleActive = async (bill, event)=>{
event.stopPropagation();
try {
await updateRecurringBill(bill.id, {
...bill,
is_active: bill.is_active ? 0 : 1
});
showToast(bill.is_active ? "å·²åœç”¨å‘¨æœŸè´¦å•" : "å·²å¯ç”¨å‘¨æœŸè´¦å•", {
tone: "success"
});
await loadBills();
} catch (err) {
showToast(err.message, {
tone: "error"
});
}
};
const days = Array.from({
length: 28
}, (_, index)=>index + 1);
const actions = /*#__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, {
title: "周期账å•",
subtitle: "管ç†å›ºå®šæ”¶å…¥ä¸Žæ”¯å‡ºèŠ‚å¥",
actions: actions,
compactHeader: true,
children: [
loading ? /*#__PURE__*/ _jsx("div", {
className: "card loading-state",
children: "加载中..."
}) : bills.length === 0 ? /*#__PURE__*/ _jsxs("div", {
className: "card empty-state empty-state--card",
children: [
/*#__PURE__*/ _jsx("div", {
className: "empty-state__icon",
children: /*#__PURE__*/ _jsx(Icon, {
name: "repeat",
size: 24
})
}),
/*#__PURE__*/ _jsx("p", {
children: "暂无周期性账å•"
}),
/*#__PURE__*/ _jsx("p", {
className: "sub",
children: "点击å³ä¸‹è§’新增定期收入或支出。"
})
]
}) : /*#__PURE__*/ _jsxs("section", {
className: "card section-card",
children: [
/*#__PURE__*/ _jsx("div", {
className: "section-heading section-heading--compact",
children: /*#__PURE__*/ _jsxs("div", {
children: [
/*#__PURE__*/ _jsx("p", {
className: "eyebrow",
children: "è´¦å•列表"
}),
/*#__PURE__*/ _jsxs("h2", {
children: [
bills.length,
" æ¡å‘¨æœŸè´¦å•"
]
})
]
})
}),
/*#__PURE__*/ _jsx("div", {
className: "bills-list",
children: bills.map((bill)=>{
const categories = DEFAULT_CATEGORIES[bill.type];
const cat = categories.find((item)=>item.name === bill.category) || {
icon: "\uD83D\uDCDD"
};
return /*#__PURE__*/ _jsxs("button", {
type: "button",
className: "bill-item ".concat(bill.is_active ? "" : "inactive"),
onClick: ()=>openEditModal(bill),
children: [
/*#__PURE__*/ _jsxs("div", {
className: "bill-left",
children: [
/*#__PURE__*/ _jsx("div", {
className: "bill-icon",
children: cat.icon
}),
/*#__PURE__*/ _jsxs("div", {
className: "bill-info",
children: [
/*#__PURE__*/ _jsxs("div", {
className: "bill-row",
children: [
/*#__PURE__*/ _jsx("div", {
className: "bill-category",
children: bill.category
}),
/*#__PURE__*/ _jsx("span", {
className: "status-chip ".concat(bill.is_active ? "status-chip--success" : ""),
children: bill.is_active ? "å¯ç”¨ä¸­" : "å·²åœç”¨"
})
]
}),
/*#__PURE__*/ _jsxs("div", {
className: "bill-day",
children: [
"æ¯æœˆ ",
bill.day,
" æ—¥"
]
}),
bill.note && /*#__PURE__*/ _jsx("div", {
className: "bill-note",
children: bill.note
})
]
})
]
}),
/*#__PURE__*/ _jsxs("div", {
className: "bill-right",
children: [
/*#__PURE__*/ _jsxs("div", {
className: "bill-amount ".concat(bill.type),
children: [
bill.type === "income" ? "+" : "-",
bill.amount.toFixed(2)
]
}),
/*#__PURE__*/ _jsxs("div", {
className: "bill-actions",
children: [
/*#__PURE__*/ _jsx("button", {
type: "button",
className: "btn btn-secondary btn-inline btn-small",
onClick: (e)=>{
e.stopPropagation();
handleApply(bill.id);
},
disabled: applyingId === bill.id,
children: applyingId === bill.id ? "应用中..." : "应用"
}),
/*#__PURE__*/ _jsx("button", {
type: "button",
className: "btn btn-ghost btn-inline btn-small",
onClick: (event)=>handleToggleActive(bill, event),
children: bill.is_active ? "åœç”¨" : "å¯ç”¨"
})
]
})
]
})
]
}, bill.id);
})
})
]
}),
/*#__PURE__*/ _jsx("button", {
type: "button",
className: "fab",
"aria-label": "新增账å•",
onClick: openAddModal,
children: /*#__PURE__*/ _jsx(Icon, {
name: "plus",
size: 24
})
}),
modalOpen && /*#__PURE__*/ _jsx("div", {
className: "modal-overlay",
onClick: closeModal,
children: /*#__PURE__*/ _jsxs("div", {
className: "modal",
onClick: (e)=>e.stopPropagation(),
children: [
/*#__PURE__*/ _jsxs("div", {
className: "modal-header",
children: [
/*#__PURE__*/ _jsxs("div", {
children: [
/*#__PURE__*/ _jsx("p", {
className: "eyebrow",
children: editingBill ? "编辑账å•" : "新增账å•"
}),
/*#__PURE__*/ _jsx("h2", {
children: editingBill ? "更新周期账å•" : "创建周期账å•"
})
]
}),
/*#__PURE__*/ _jsx("button", {
type: "button",
className: "icon-button icon-button--ghost",
"aria-label": "关闭弹窗",
onClick: closeModal,
children: /*#__PURE__*/ _jsx(Icon, {
name: "close",
size: 18
})
})
]
}),
formError && /*#__PURE__*/ _jsx("div", {
className: "error-msg",
children: formError
}),
/*#__PURE__*/ _jsxs("form", {
onSubmit: handleSubmit,
children: [
/*#__PURE__*/ _jsxs("div", {
className: "type-switch",
children: [
/*#__PURE__*/ _jsx("button", {
type: "button",
className: "type-btn expense ".concat(formData.type === "expense" ? "active" : ""),
onClick: ()=>setFormData({
...formData,
type: "expense",
category: ""
}),
disabled: submitting || deleting,
children: "支出"
}),
/*#__PURE__*/ _jsx("button", {
type: "button",
className: "type-btn income ".concat(formData.type === "income" ? "active" : ""),
onClick: ()=>setFormData({
...formData,
type: "income",
category: ""
}),
disabled: submitting || deleting,
children: "æ”¶å…¥"
})
]
}),
/*#__PURE__*/ _jsxs("div", {
className: "form-group",
children: [
/*#__PURE__*/ _jsx("label", {
htmlFor: "bill-amount",
children: "金é¢"
}),
/*#__PURE__*/ _jsx("input", {
id: "bill-amount",
type: "number",
className: "amount-input",
value: formData.amount,
onChange: (e)=>setFormData({
...formData,
amount: e.target.value
}),
placeholder: "0.00",
step: "0.01",
required: true,
disabled: submitting || deleting
})
]
}),
/*#__PURE__*/ _jsxs("div", {
className: "form-group",
children: [
/*#__PURE__*/ _jsx("label", {
children: "分类"
}),
/*#__PURE__*/ _jsx("div", {
className: "category-grid",
children: DEFAULT_CATEGORIES[formData.type].map((cat)=>/*#__PURE__*/ _jsxs("button", {
type: "button",
className: "category-btn ".concat(formData.category === cat.name ? "active" : ""),
onClick: ()=>setFormData({
...formData,
category: cat.name
}),
disabled: submitting || deleting,
children: [
/*#__PURE__*/ _jsx("span", {
className: "icon",
children: cat.icon
}),
/*#__PURE__*/ _jsx("span", {
children: cat.name
})
]
}, cat.name))
})
]
}),
/*#__PURE__*/ _jsxs("div", {
className: "form-group",
children: [
/*#__PURE__*/ _jsx("label", {
htmlFor: "bill-day",
children: "æ—¥æœŸï¼ˆæ¯æœˆå‡ å·ï¼‰"
}),
/*#__PURE__*/ _jsx("select", {
id: "bill-day",
value: formData.day,
onChange: (e)=>setFormData({
...formData,
day: e.target.value
}),
disabled: submitting || deleting,
children: days.map((day)=>/*#__PURE__*/ _jsxs("option", {
value: day,
children: [
day,
" æ—¥"
]
}, day))
})
]
}),
/*#__PURE__*/ _jsxs("div", {
className: "form-group",
children: [
/*#__PURE__*/ _jsx("label", {
htmlFor: "bill-note",
children: "备注(å¯é€‰ï¼‰"
}),
/*#__PURE__*/ _jsx("input", {
id: "bill-note",
type: "text",
value: formData.note,
onChange: (e)=>setFormData({
...formData,
note: e.target.value
}),
placeholder: "添加备注...",
disabled: submitting || deleting
})
]
}),
editingBill && /*#__PURE__*/ _jsx("div", {
className: "form-group form-group--inline",
children: /*#__PURE__*/ _jsxs("label", {
className: "checkbox-label",
children: [
/*#__PURE__*/ _jsx("input", {
type: "checkbox",
checked: formData.is_active,
onChange: (e)=>setFormData({
...formData,
is_active: e.target.checked
}),
disabled: submitting || deleting
}),
¯ç”¨æ­¤è´¦å•"
]
})
}),
/*#__PURE__*/ _jsxs("div", {
className: "modal-actions",
children: [
/*#__PURE__*/ _jsx("button", {
type: "submit",
className: "btn btn-primary",
disabled: submitting || deleting,
children: submitting ? /*#__PURE__*/ _jsxs("span", {
className: "button-content",
children: [
/*#__PURE__*/ _jsx(Icon, {
name: "spinner",
size: 16,
className: "is-spinning"
}),
"ä¿å­˜ä¸­..."
]
}) : "ä¿å­˜"
}),
editingBill && /*#__PURE__*/ _jsx("button", {
type: "button",
className: "btn btn-danger",
onClick: handleDelete,
disabled: submitting || deleting,
children: deleting ? "删除中..." : "删除"
})
]
})
]
})
]
})
})
]
});
}
H062undefined64107undefined109148undefined150291undefined293347undefined349394undefined396438undefined440494undefined496554undefined556570undefined613621(0,next_router__WEBPACK_IMPORTED_MODULE_2__.useRouter)undefined652659(0,_components_ToastProvider__WEBPACK_IMPORTED_MODULE_6__/* .useToast */ .p)undefined688697(0,_components_ConfirmProvider__WEBPACK_IMPORTED_MODULE_7__/* .useConfirm */ .N)undefined732739(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined784791(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined843850(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined893900(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined949956(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined10021009(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined10591066(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined11131120(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined11631170(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined13131321(0,react__WEBPACK_IMPORTED_MODULE_1__.useEffect)undefined13421351(0,_lib_api__WEBPACK_IMPORTED_MODULE_8__/* .isLoggedIn */ .jl)undefined15811597(0,_lib_api__WEBPACK_IMPORTED_MODULE_8__/* .getRecurringBills */ .Vn)undefined33313349(0,_lib_api__WEBPACK_IMPORTED_MODULE_8__/* .updateRecurringBill */ .uO)undefined35123530(0,_lib_api__WEBPACK_IMPORTED_MODULE_8__/* .createRecurringBill */ .z0)undefined42514269(0,_lib_api__WEBPACK_IMPORTED_MODULE_8__/* .deleteRecurringBill */ .TZ)undefined47394756(0,_lib_api__WEBPACK_IMPORTED_MODULE_8__/* .applyRecurringBill */ .Xu)undefined51405158(0,_lib_api__WEBPACK_IMPORTED_MODULE_8__/* .updateRecurringBill */ .uO)undefined56435646(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined58255828(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined58305833_components_icons__WEBPACK_IMPORTED_MODULE_5__/* .Icon */ .Jundefined59335937(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined59395946_components_AppShell__WEBPACK_IMPORTED_MODULE_4__/* ["default"] */ .Zundefined61186121(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined62686272(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined64106413(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined65286531(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined65336536_components_icons__WEBPACK_IMPORTED_MODULE_5__/* .Icon */ .Jundefined67076710(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined68216824(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined69997003(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined71287131(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined72697273(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined73707373(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined75757579(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined79287931(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined80928109_lib_categories__WEBPACK_IMPORTED_MODULE_3__/* .DEFAULT_CATEGORIES */ .Sundefined83548358(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined86788682(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined88678870(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined91269130(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined93399343(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined95759578(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined99079910(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined1041410418(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined1097110974(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined1142511429(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined1161511619(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined1210212106(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined1231812321(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined1312913132(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined1394313946(0,react_jsx_runtime__WEBPACK
__webpack_require__.O(0, [888,774,179], function() { return __webpack_exec__(8208); });
var __webpack_exports__ = __webpack_require__.O();
Åäåæ B«webpack/lib/util/registerExternalSerializerœwebpack-sources/ConcatSource`«webpack/lib/util/registerExternalSerializer™webpack-sources/RawSourceW(self["webpackChunk_N_E"] = self["webpackChunk_N_E"] || []).push([[603],{
/***/ 2774:
«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([
"/search",
function () {
return require("private-next-pages/search.js");
}
]);
if(module.hot) {
module.hot.dispose(function () {
window.__NEXT_P.push(["/search"])
});
}
bjprDª³“__webpack_require__„7073…false{}†buffer†source„size„maps„hash¨
(window.__NEXT_P = window.__NEXT_P || []).push([
"/search",
function () {
return __webpack_require__(7073);
}
]);
if(false) {}
”{"finalSource":true}ƒmapbufferedMap ReplaceSourceRawSource
(window.__NEXT_P = window.__NEXT_P || []).push([
"/search",
function () {
return require("private-next-pages/search.js");
}
]);
if(module.hot) {
module.hot.dispose(function () {
window.__NEXT_P.push(["/search"])
});
}
Z106112__webpack_require__undefined1141437073undefined170179falseundefined182279{}undefined
/***/ }) †buffer†source„size„maps„hashConcatSourceRawSource[/***/ (function(__unused_webpack_module, __unused_webpack_exports, __webpack_require__) {
ReplaceSourceRawSource
(window.__NEXT_P = window.__NEXT_P || []).push([
"/search",
function () {
return require("private-next-pages/search.js");
}
]);
if(module.hot) {
module.hot.dispose(function () {
window.__NEXT_P.push(["/search"])
});
}
Z106112__webpack_require__undefined1141437073undefined170179falseundefined182279{}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
]
});
}
/***/ }) ”{"finalSource":true}ƒmapbufferedMap  í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ãä,
/***/ 7063:
 ò«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 */ S: function() { return /* binding */ DEFAULT_CATEGORIES; }
/* harmony export */ });
«webpack/lib/util/registerExternalSerializerwebpack-sources/ReplaceSourceZexport const DEFAULT_CATEGORIES = {
expense: [
{
name: "é¤é¥®",
icon: "\uD83C\uDF5C"
},
{
name: "ä¹°èœ",
icon: "\uD83E\uDD6C"
},
{
name: "交通",
icon: "\uD83D\uDE97"
},
{
name: "购物",
icon: "\uD83D\uDED2"
},
{
name: "娱ä¹",
icon: "\uD83C\uDFAE"
},
{
name: "房租",
icon: "\uD83C\uDFE0"
},
{
name: "房贷",
icon: "\uD83C\uDFE6"
},
{
name: "亲情å¡",
icon: "\uD83D\uDCB3"
},
{
name: "水电",
icon: "\uD83D\uDCA7"
},
{
name: "è¡£æœ",
icon: "\uD83D\uDC54"
},
{
name: "通讯",
icon: "\uD83D\uDCF1"
},
{
name: "日用å“",
icon: "\uD83E\uDDF4"
},
{
name: "美妆",
icon: "\uD83D\uDC84"
},
{
name: "医疗",
icon: "\uD83C\uDFE5"
},
{
name: "养娃",
icon: "\uD83D\uDC76"
},
{
name: "家电",
icon: "\uD83D\uDCFA"
},
{
name: "å®¶å…·",
icon: "\uD83D\uDECBï¸"
},
{
name: "装修",
icon: "\uD83D\uDD28"
},
{
name: "宠物",
icon: "\uD83D\uDC15"
},
{
name: "å…¶ä»–",
icon: "\uD83D\uDCDD"
}
],
income: [
{
name: "工资",
icon: "\uD83D\uDCB0"
},
{
name: "奖金",
icon: "\uD83C\uDF81"
},
{
name: "ç†è´¢",
icon: "\uD83D\uDCC8"
},
{
name: "红包",
icon: "\uD83E\uDDE7"
},
{
name: "å…¶ä»–",
icon: "\uD83D\uDCDD"
}
]
};
†buffer†source„size„maps„hashConcatSourceRawSource­/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ S: function() { return /* binding */ DEFAULT_CATEGORIES; }
/* harmony export */ });
ReplaceSourceRawSourceZexport const DEFAULT_CATEGORIES = {
expense: [
{
name: "é¤é¥®",
icon: "\uD83C\uDF5C"
},
{
name: "ä¹°èœ",
icon: "\uD83E\uDD6C"
},
{
name: "交通",
icon: "\uD83D\uDE97"
},
{
name: "购物",
icon: "\uD83D\uDED2"
},
{
name: "娱ä¹",
icon: "\uD83C\uDFAE"
},
{
name: "房租",
icon: "\uD83C\uDFE0"
},
{
name: "房贷",
icon: "\uD83C\uDFE6"
},
{
name: "亲情å¡",
icon: "\uD83D\uDCB3"
},
{
name: "水电",
icon: "\uD83D\uDCA7"
},
{
name: "è¡£æœ",
icon: "\uD83D\uDC54"
},
{
name: "通讯",
icon: "\uD83D\uDCF1"
},
{
name: "日用å“",
icon: "\uD83E\uDDF4"
},
{
name: "美妆",
icon: "\uD83D\uDC84"
},
{
name: "医疗",
icon: "\uD83C\uDFE5"
},
{
name: "养娃",
icon: "\uD83D\uDC76"
},
{
name: "家电",
icon: "\uD83D\uDCFA"
},
{
name: "å®¶å…·",
icon: "\uD83D\uDECBï¸"
},
{
name: "装修",
icon: "\uD83D\uDD28"
},
{
name: "宠物",
icon: "\uD83D\uDC15"
},
{
name: "å…¶ä»–",
icon: "\uD83D\uDCDD"
}
],
income: [
{
name: "工资",
icon: "\uD83D\uDCB0"
},
{
name: "奖金",
icon: "\uD83C\uDF81"
},
{
name: "ç†è´¢",
icon: "\uD83D\uDCC8"
},
{
name: "红包",
icon: "\uD83E\uDDE7"
},
{
name: "å…¶ä»–",
icon: "\uD83D\uDCDD"
}
]
};
 06undefined
/***/ }) × ØëØ­/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ S: function() { return /* binding */ DEFAULT_CATEGORIES; }
/* harmony export */ });
ÙZexport const DEFAULT_CATEGORIES = {
expense: [
{
name: "é¤é¥®",
icon: "\uD83C\uDF5C"
},
{
name: "ä¹°èœ",
icon: "\uD83E\uDD6C"
},
{
name: "交通",
icon: "\uD83D\uDE97"
},
{
name: "购物",
icon: "\uD83D\uDED2"
},
{
name: "娱ä¹",
icon: "\uD83C\uDFAE"
},
{
name: "房租",
icon: "\uD83C\uDFE0"
},
{
name: "房贷",
icon: "\uD83C\uDFE6"
},
{
name: "亲情å¡",
icon: "\uD83D\uDCB3"
},
{
name: "水电",
icon: "\uD83D\uDCA7"
},
{
name: "è¡£æœ",
icon: "\uD83D\uDC54"
},
{
name: "通讯",
icon: "\uD83D\uDCF1"
},
{
name: "日用å“",
icon: "\uD83E\uDDF4"
},
{
name: "美妆",
icon: "\uD83D\uDC84"
},
{
name: "医疗",
icon: "\uD83C\uDFE5"
},
{
name: "养娃",
icon: "\uD83D\uDC76"
},
{
name: "家电",
icon: "\uD83D\uDCFA"
},
{
name: "å®¶å…·",
icon: "\uD83D\uDECBï¸"
},
{
name: "装修",
icon: "\uD83D\uDD28"
},
{
name: "宠物",
icon: "\uD83D\uDC15"
},
{
name: "å…¶ä»–",
icon: "\uD83D\uDCDD"
}
],
income: [
{
name: "工资",
icon: "\uD83D\uDCB0"
},
{
name: "奖金",
icon: "\uD83C\uDF81"
},
{
name: "ç†è´¢",
icon: "\uD83D\uDCC8"
},
{
name: "红包",
icon: "\uD83E\uDDE7"
},
{
name: "å…¶ä»–",
icon: "\uD83D\uDCDD"
}
]
};
 06undefinedÚÛ,
/***/ 7073:
 £Ï«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__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": function() { return /* binding */ Search; }
/* 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 _lib_categories__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(7063);
/* harmony import */ var _components_AppShell__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(6248);
/* harmony import */ var _components_icons__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(9415);
/* harmony import */ var _components_ToastProvider__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(5546);
«webpack/lib/util/registerExternalSerializerwebpack-sources/ReplaceSourceÀ4import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
import { useState, useEffect } from "react";
import { useRouter } from "next/router";
import { isLoggedIn, searchRecords } from "../lib/api";
import { DEFAULT_CATEGORIES } from "../lib/categories";
import AppShell from "../components/AppShell";
import { Icon } from "../components/icons";
import { useToast } from "../components/ToastProvider";
export default function Search() {
const router = useRouter();
const { showToast } = useToast();
const [keyword, setKeyword] = useState("");
const [results, setResults] = useState([]);
const [loading, setLoading] = useState(false);
useEffect(()=>{
if (!isLoggedIn()) {
router.push("/login");
}
}, [
router
]);
const handleSearch = async (e)=>{
e.preventDefault();
if (!keyword.trim()) {
showToast("请输入æœç´¢å…³é”®è¯", {
tone: "info"
});
return;
}
setLoading(true);
try {
const data = await searchRecords(keyword);
setResults(data);
} catch (err) {
console.error(err);
showToast(err.message, {
tone: "error"
});
} finally{
setLoading(false);
}
};
const formatMoney = (num)=>(num || 0).toFixed(2);
const groupedResults = results.reduce((groups, record)=>{
const date = record.date.split(" ")[0];
if (!groups[date]) groups[date] = [];
groups[date].push(record);
return groups;
}, {});
const sortedDates = Object.keys(groupedResults).sort((a, b)=>b.localeCompare(a));
const totalIncome = results.filter((record)=>record.type === "income").reduce((sum, record)=>sum + record.amount, 0);
const totalExpense = results.filter((record)=>record.type === "expense").reduce((sum, record)=>sum + record.amount, 0);
const totalBalance = totalIncome - totalExpense;
const actions = /*#__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, {
title: "æœç´¢",
subtitle: "快速查找分类ã€å¤‡æ³¨ä¸Žé‡‘é¢",
actions: actions,
compactHeader: true,
children: [
/*#__PURE__*/ _jsxs("section", {
className: "card section-card section-card--search section-card--search-panel",
children: [
/*#__PURE__*/ _jsxs("form", {
className: "search-form search-form--refresh",
onSubmit: handleSearch,
children: [
/*#__PURE__*/ _jsxs("div", {
className: "search-input-wrap",
children: [
/*#__PURE__*/ _jsx(Icon, {
name: "search",
size: 18,
className: "search-input-wrap__icon"
}),
/*#__PURE__*/ _jsx("input", {
type: "text",
value: keyword,
onChange: (e)=>setKeyword(e.target.value),
placeholder: "æœç´¢åˆ†ç±»æˆ–备注...",
autoFocus: true,
disabled: loading
})
]
}),
/*#__PURE__*/ _jsx("button", {
type: "submit",
className: "btn btn-primary btn-inline",
disabled: loading,
children: loading ? /*#__PURE__*/ _jsxs("span", {
className: "button-content",
children: [
/*#__PURE__*/ _jsx(Icon, {
name: "spinner",
size: 16,
className: "is-spinning"
}),
œç´¢ä¸­..."
]
}) : "æœç´¢"
})
]
}),
results.length > 0 ? /*#__PURE__*/ _jsxs(_Fragment, {
children: [
/*#__PURE__*/ _jsxs("div", {
className: "search-summary",
children: [
/*#__PURE__*/ _jsxs("div", {
className: "search-stat",
children: [
/*#__PURE__*/ _jsx("span", {
className: "label",
children: "æ”¶å…¥"
}),
/*#__PURE__*/ _jsxs("span", {
className: "value income",
children: [
"+",
formatMoney(totalIncome)
]
})
]
}),
/*#__PURE__*/ _jsxs("div", {
className: "search-stat",
children: [
/*#__PURE__*/ _jsx("span", {
className: "label",
children: "支出"
}),
/*#__PURE__*/ _jsxs("span", {
className: "value expense",
children: [
"-",
formatMoney(totalExpense)
]
})
]
}),
/*#__PURE__*/ _jsxs("div", {
className: "search-stat",
children: [
/*#__PURE__*/ _jsx("span", {
className: "label",
children: "结余"
}),
/*#__PURE__*/ _jsx("span", {
className: "value",
children: formatMoney(totalBalance)
})
]
})
]
}),
/*#__PURE__*/ _jsxs("div", {
className: "results-count",
children: [
"找到 ",
results.length,
" æ¡è®°å½•"
]
})
]
}) : null
]
}),
loading ? /*#__PURE__*/ _jsx("div", {
className: "card loading-state",
children: "æœç´¢ä¸­..."
}) : results.length > 0 ? /*#__PURE__*/ _jsx("section", {
className: "card section-card",
children: /*#__PURE__*/ _jsx("div", {
className: "records-list records-list--flush",
children: sortedDates.map((date)=>/*#__PURE__*/ _jsxs("div", {
className: "date-group",
children: [
/*#__PURE__*/ _jsx("div", {
className: "date-label",
children: new Date(date).toLocaleDateString("zh-CN", {
month: "short",
day: "numeric",
weekday: "short"
})
}),
groupedResults[date].map((record)=>{
const categories = DEFAULT_CATEGORIES[record.type];
const cat = categories.find((item)=>item.name === record.category) || {
icon: "\uD83D\uDCDD"
};
return /*#__PURE__*/ _jsxs("div", {
className: "record-item record-item--static",
children: [
/*#__PURE__*/ _jsxs("div", {
className: "record-left",
children: [
/*#__PURE__*/ _jsx("div", {
className: "record-icon",
children: cat.icon
}),
/*#__PURE__*/ _jsxs("div", {
className: "record-info",
children: [
/*#__PURE__*/ _jsx("div", {
className: "record-category",
children: record.category
}),
record.note && /*#__PURE__*/ _jsx("div", {
className: "record-note",
children: record.note
})
]
})
]
}),
/*#__PURE__*/ _jsxs("div", {
className: "record-amount ".concat(record.type),
children: [
record.type === "income" ? "+" : "-",
formatMoney(record.amount)
]
})
]
}, record.id);
})
]
}, date))
})
}) : keyword ? /*#__PURE__*/ _jsxs("div", {
className: "card empty-state empty-state--card",
children: [
/*#__PURE__*/ _jsx("div", {
className: "empty-state__icon",
children: /*#__PURE__*/ _jsx(Icon, {
name: "search",
size: 24
})
}),
/*#__PURE__*/ _jsx("p", {
children: "未找到相关记录"
})
]
}) : /*#__PURE__*/ _jsxs("div", {
className: "card empty-state empty-state--card",
children: [
/*#__PURE__*/ _jsx("div", {
className: "empty-state__icon",
children: /*#__PURE__*/ _jsx(Icon, {
name: "search",
size: 24
})
}),
/*#__PURE__*/ _jsx("p", {
children: "输入关键è¯å¼€å§‹æœç´¢"
})
]
})
]
});
}
cDUW_«­ãåJLvx®°¾æî ;Bkr¢°¸ÍÖAM_¹»¾" & ( / Ð Ô
V Z    ! $ ;>:>ñôöùëïñLPöúµ¸±µ³·runrrv14-0Àă !!€!ƒ!""¯"_²"Å$Ö$ï%ó%Â&Æ&™'œ'¾(Â(­)°) ++--È/Ì/V0Y0Ì0Ï0Ñ0Ô011ÿ12223D33 3¶3¹3¶(0,next_router__WEBPACK_IMPORTED_MODULE_2__.useRouter)Ì(0,_components_ToastProvider__WEBPACK_IMPORTED_MODULE_6__/* .useToast */ .p)¯(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)ÿÿ°(0,react__WEBPACK_IMPORTED_MODULE_1__.useEffect)¾(0,_lib_api__WEBPACK_IMPORTED_MODULE_7__/* .isLoggedIn */ .jl)Á(0,_lib_api__WEBPACK_IMPORTED_MODULE_7__/* .searchRecords */ .Kp)¶(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)ÿ¼_components_icons__WEBPACK_IMPORTED_MODULE_5__/* .Icon */ .J·(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)Å_components_AppShell__WEBPACK_IMPORTED_MODULE_4__/* ["default"] */ .Zþþþüýüüþüýþ·react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.FragmentýýûýýûýýûûýûûûýûÈ_lib_categories__WEBPACK_IMPORTED_MODULE_3__/* .DEFAULT_CATEGORIES */ .Süüúüúúüüúúûúüúúûú†buffer†source„size„maps„hash!C__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": function() { return /* binding */ Search; }
/* 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 _lib_categories__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(7063);
/* harmony import */ var _components_AppShell__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(6248);
/* harmony import */ var _components_icons__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(9415);
/* harmony import */ var _components_ToastProvider__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(5546);
function Search() {
const router = (0,next_router__WEBPACK_IMPORTED_MODULE_2__.useRouter)();
const { showToast } = (0,_components_ToastProvider__WEBPACK_IMPORTED_MODULE_6__/* .useToast */ .p)();
const [keyword, setKeyword] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)("");
const [results, setResults] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)([]);
const [loading, setLoading] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)(false);
(0,react__WEBPACK_IMPORTED_MODULE_1__.useEffect)(()=>{
if (!(0,_lib_api__WEBPACK_IMPORTED_MODULE_7__/* .isLoggedIn */ .jl)()) {
router.push("/login");
}
}, [
router
]);
const handleSearch = async (e)=>{
e.preventDefault();
if (!keyword.trim()) {
showToast("请输入æœç´¢å…³é”®è¯", {
tone: "info"
});
return;
}
setLoading(true);
try {
const data = await (0,_lib_api__WEBPACK_IMPORTED_MODULE_7__/* .searchRecords */ .Kp)(keyword);
setResults(data);
} catch (err) {
console.error(err);
showToast(err.message, {
tone: "error"
});
} finally{
setLoading(false);
}
};
const formatMoney = (num)=>(num || 0).toFixed(2);
const groupedResults = results.reduce((groups, record)=>{
const date = record.date.split(" ")[0];
if (!groups[date]) groups[date] = [];
groups[date].push(record);
return groups;
}, {});
const sortedDates = Object.keys(groupedResults).sort((a, b)=>b.localeCompare(a));
const totalIncome = results.filter((record)=>record.type === "income").reduce((sum, record)=>sum + record.amount, 0);
const totalExpense = results.filter((record)=>record.type === "expense").reduce((sum, record)=>sum + record.amount, 0);
const totalBalance = totalIncome - totalExpense;
const actions = /*#__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_5__/* .Icon */ .J, {
name: "settings",
size: 18
})
});
return /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)(_components_AppShell__WEBPACK_IMPORTED_MODULE_4__/* ["default"] */ .Z, {
title: "æœç´¢",
subtitle: "快速查找分类ã€å¤‡æ³¨ä¸Žé‡‘é¢",
actions: actions,
compactHeader: true,
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("section", {
className: "card section-card section-card--search section-card--search-panel",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("form", {
className: "search-form search-form--refresh",
onSubmit: handleSearch,
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "search-input-wrap",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_components_icons__WEBPACK_IMPORTED_MODULE_5__/* .Icon */ .J, {
name: "search",
size: 18,
className: "search-input-wrap__icon"
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("input", {
type: "text",
value: keyword,
onChange: (e)=>setKeyword(e.target.value),
placeholder: "æœç´¢åˆ†ç±»æˆ–备注...",
autoFocus: true,
disabled: loading
})
]
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("button", {
type: "submit",
className: "btn btn-primary btn-inline",
disabled: loading,
children: loading ? /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("span", {
className: "button-content",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_components_icons__WEBPACK_IMPORTED_MODULE_5__/* .Icon */ .J, {
name: "spinner",
size: 16,
className: "is-spinning"
}),
œç´¢ä¸­..."
]
}) : "æœç´¢"
})
]
}),
results.length > 0 ? /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)(react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.Fragment, {
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "search-summary",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "search-stat",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("span", {
className: "label",
children: "æ”¶å…¥"
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("span", {
className: "value income",
children: [
"+",
formatMoney(totalIncome)
]
})
]
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "search-stat",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("span", {
className: "label",
children: "支出"
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("span", {
className: "value expense",
children: [
"-",
formatMoney(totalExpense)
]
})
]
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "search-stat",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("span", {
className: "label",
children: "结余"
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("span", {
className: "value",
children: formatMoney(totalBalance)
})
]
})
]
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "results-count",
children: [
"找到 ",
results.length,
" æ¡è®°å½•"
]
})
]
}) : null
]
}),
loading ? /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "card loading-state",
children: "æœç´¢ä¸­..."
}) : results.length > 0 ? /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("section", {
className: "card section-card",
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "records-list records-list--flush",
children: sortedDates.map((date)=>/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "date-group",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "date-label",
children: new Date(date).toLocaleDateString("zh-CN", {
month: "short",
day: "numeric",
weekday: "short"
})
}),
groupedResults[date].map((record)=>{
const categories = _lib_categories__WEBPACK_IMPORTED_MODULE_3__/* .DEFAULT_CATEGORIES */ .S[record.type];
const cat = categories.find((item)=>item.name === record.category) || {
icon: "\uD83D\uDCDD"
};
return /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "record-item record-item--static",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "record-left",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "record-icon",
children: cat.icon
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "record-info",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "record-category",
children: record.category
}),
record.note && /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "record-note",
children: record.note
})
]
})
]
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "record-amount ".concat(record.type),
children: [
record.type === "income" ? "+" : "-",
formatMoney(record.amount)
]
})
]
}, record.id);
})
]
}, date))
})
}) : keyword ? /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "card empty-state empty-state--card",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "empty-state__icon",
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_components_icons__WEBPACK_IMPORTED_MODULE_5__/* .Icon */ .J, {
name: "search",
size: 24
})
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("p", {
children: "未找到相关记录"
})
]
}) : /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
className: "card empty-state empty-state--card",
children: [
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
className: "empty-state__icon",
children: /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_components_icons__WEBPACK_IMPORTED_MODULE_5__/* .Icon */ .J, {
name: "search",
size: 24
})
}),
/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("p", {
children: "输入关键è¯å¼€å§‹æœç´¢"
})
]
})
]
});
}
”{"finalSource":true}ƒmapbufferedMap ConcatSourceRawSource__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": function() { return /* binding */ Search; }
/* 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 _lib_categories__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(7063);
/* harmony import */ var _components_AppShell__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(6248);
/* harmony import */ var _components_icons__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(9415);
/* harmony import */ var _components_ToastProvider__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(5546);
ReplaceSourceRawSourceÀ4import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
import { useState, useEffect } from "react";
import { useRouter } from "next/router";
import { isLoggedIn, searchRecords } from "../lib/api";
import { DEFAULT_CATEGORIES } from "../lib/categories";
import AppShell from "../components/AppShell";
import { Icon } from "../components/icons";
import { useToast } from "../components/ToastProvider";
export default function Search() {
const router = useRouter();
const { showToast } = useToast();
const [keyword, setKeyword] = useState("");
const [results, setResults] = useState([]);
const [loading, setLoading] = useState(false);
useEffect(()=>{
if (!isLoggedIn()) {
router.push("/login");
}
}, [
router
]);
const handleSearch = async (e)=>{
e.preventDefault();
if (!keyword.trim()) {
showToast("请输入æœç´¢å…³é”®è¯", {
tone: "info"
});
return;
}
setLoading(true);
try {
const data = await searchRecords(keyword);
setResults(data);
} catch (err) {
console.error(err);
showToast(err.message, {
tone: "error"
});
} finally{
setLoading(false);
}
};
const formatMoney = (num)=>(num || 0).toFixed(2);
const groupedResults = results.reduce((groups, record)=>{
const date = record.date.split(" ")[0];
if (!groups[date]) groups[date] = [];
groups[date].push(record);
return groups;
}, {});
const sortedDates = Object.keys(groupedResults).sort((a, b)=>b.localeCompare(a));
const totalIncome = results.filter((record)=>record.type === "income").reduce((sum, record)=>sum + record.amount, 0);
const totalExpense = results.filter((record)=>record.type === "expense").reduce((sum, record)=>sum + record.amount, 0);
const totalBalance = totalIncome - totalExpense;
const actions = /*#__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, {
title: "æœç´¢",
subtitle: "快速查找分类ã€å¤‡æ³¨ä¸Žé‡‘é¢",
actions: actions,
compactHeader: true,
children: [
/*#__PURE__*/ _jsxs("section", {
className: "card section-card section-card--search section-card--search-panel",
children: [
/*#__PURE__*/ _jsxs("form", {
className: "search-form search-form--refresh",
onSubmit: handleSearch,
children: [
/*#__PURE__*/ _jsxs("div", {
className: "search-input-wrap",
children: [
/*#__PURE__*/ _jsx(Icon, {
name: "search",
size: 18,
className: "search-input-wrap__icon"
}),
/*#__PURE__*/ _jsx("input", {
type: "text",
value: keyword,
onChange: (e)=>setKeyword(e.target.value),
placeholder: "æœç´¢åˆ†ç±»æˆ–备注...",
autoFocus: true,
disabled: loading
})
]
}),
/*#__PURE__*/ _jsx("button", {
type: "submit",
className: "btn btn-primary btn-inline",
disabled: loading,
children: loading ? /*#__PURE__*/ _jsxs("span", {
className: "button-content",
children: [
/*#__PURE__*/ _jsx(Icon, {
name: "spinner",
size: 16,
className: "is-spinning"
}),
œç´¢ä¸­..."
]
}) : "æœç´¢"
})
]
}),
results.length > 0 ? /*#__PURE__*/ _jsxs(_Fragment, {
children: [
/*#__PURE__*/ _jsxs("div", {
className: "search-summary",
children: [
/*#__PURE__*/ _jsxs("div", {
className: "search-stat",
children: [
/*#__PURE__*/ _jsx("span", {
className: "label",
children: "æ”¶å…¥"
}),
/*#__PURE__*/ _jsxs("span", {
className: "value income",
children: [
"+",
formatMoney(totalIncome)
]
})
]
}),
/*#__PURE__*/ _jsxs("div", {
className: "search-stat",
children: [
/*#__PURE__*/ _jsx("span", {
className: "label",
children: "支出"
}),
/*#__PURE__*/ _jsxs("span", {
className: "value expense",
children: [
"-",
formatMoney(totalExpense)
]
})
]
}),
/*#__PURE__*/ _jsxs("div", {
className: "search-stat",
children: [
/*#__PURE__*/ _jsx("span", {
className: "label",
children: "结余"
}),
/*#__PURE__*/ _jsx("span", {
className: "value",
children: formatMoney(totalBalance)
})
]
})
]
}),
/*#__PURE__*/ _jsxs("div", {
className: "results-count",
children: [
"找到 ",
results.length,
" æ¡è®°å½•"
]
})
]
}) : null
]
}),
loading ? /*#__PURE__*/ _jsx("div", {
className: "card loading-state",
children: "æœç´¢ä¸­..."
}) : results.length > 0 ? /*#__PURE__*/ _jsx("section", {
className: "card section-card",
children: /*#__PURE__*/ _jsx("div", {
className: "records-list records-list--flush",
children: sortedDates.map((date)=>/*#__PURE__*/ _jsxs("div", {
className: "date-group",
children: [
/*#__PURE__*/ _jsx("div", {
className: "date-label",
children: new Date(date).toLocaleDateString("zh-CN", {
month: "short",
day: "numeric",
weekday: "short"
})
}),
groupedResults[date].map((record)=>{
const categories = DEFAULT_CATEGORIES[record.type];
const cat = categories.find((item)=>item.name === record.category) || {
icon: "\uD83D\uDCDD"
};
return /*#__PURE__*/ _jsxs("div", {
className: "record-item record-item--static",
children: [
/*#__PURE__*/ _jsxs("div", {
className: "record-left",
children: [
/*#__PURE__*/ _jsx("div", {
className: "record-icon",
children: cat.icon
}),
/*#__PURE__*/ _jsxs("div", {
className: "record-info",
children: [
/*#__PURE__*/ _jsx("div", {
className: "record-category",
children: record.category
}),
record.note && /*#__PURE__*/ _jsx("div", {
className: "record-note",
children: record.note
})
]
})
]
}),
/*#__PURE__*/ _jsxs("div", {
className: "record-amount ".concat(record.type),
children: [
record.type === "income" ? "+" : "-",
formatMoney(record.amount)
]
})
]
}, record.id);
})
]
}, date))
})
}) : keyword ? /*#__PURE__*/ _jsxs("div", {
className: "card empty-state empty-state--card",
children: [
/*#__PURE__*/ _jsx("div", {
className: "empty-state__icon",
children: /*#__PURE__*/ _jsx(Icon, {
name: "search",
size: 24
})
}),
/*#__PURE__*/ _jsx("p", {
children: "未找到相关记录"
})
]
}) : /*#__PURE__*/ _jsxs("div", {
className: "card empty-state empty-state--card",
children: [
/*#__PURE__*/ _jsx("div", {
className: "empty-state__icon",
children: /*#__PURE__*/ _jsx(Icon, {
name: "search",
size: 24
})
}),
/*#__PURE__*/ _jsx("p", {
children: "输入关键è¯å¼€å§‹æœç´¢"
})
]
})
]
});
}
R085undefined87130undefined132171undefined173227undefined229283undefined285330undefined332374undefined376430undefined432446undefined486494(0,next_router__WEBPACK_IMPORTED_MODULE_2__.useRouter)undefined525532(0,_components_ToastProvider__WEBPACK_IMPORTED_MODULE_6__/* .useToast */ .p)undefined571578(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined619626(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined667674(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined688696(0,react__WEBPACK_IMPORTED_MODULE_1__.useEffect)undefined717726(0,_lib_api__WEBPACK_IMPORTED_MODULE_7__/* .isLoggedIn */ .jl)undefined10891101(0,_lib_api__WEBPACK_IMPORTED_MODULE_7__/* .searchRecords */ .Kp)undefined20482051(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined22302233(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined22352238_components_icons__WEBPACK_IMPORTED_MODULE_5__/* .Icon */ .Jundefined23382342(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined23442351_components_AppShell__WEBPACK_IMPORTED_MODULE_4__/* ["default"] */ .Zundefined25122516(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined26892693(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined29022906(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined30753078(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined30803083_components_icons__WEBPACK_IMPORTED_MODULE_5__/* .Icon */ .Jundefined33613364(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined38993902(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined41544158(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined43374340(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined43424345_components_icons__WEBPACK_IMPORTED_MODULE_5__/* .Icon */ .Jundefined48434847(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined48494857react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.Fragmentundefined49404944(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined51105114(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined53015304(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined55535557(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined60676071(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined62586261(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined65106514(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined70267030(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined72177220(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined74697472(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined78727876(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined83208323(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined84708473(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined85768579(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined87258729(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined88798882(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined94139430_lib_categories__WEBPACK_IMPORTED_MODULE_3__/* .DEFAULT_CATEGORIES */ .Sundefined97119715(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined99229926(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined1013710140(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined1043010434(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined1066910672(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined1102011023(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined1154211546(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined1223212236(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined1237412377(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined1249212495(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined1249712500_components_icons__WEBPACK_IMPORTED_MODULE_5__/* .Icon */ .Jundefined1267112674(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined1279912803(0,react_jsx_runti
/***/ }) Î ÏâÏ__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": function() { return /* binding */ Search; }
/* 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 _lib_categories__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(7063);
/* harmony import */ var _components_AppShell__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(6248);
/* harmony import */ var _components_icons__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(9415);
/* harmony import */ var _components_ToastProvider__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(5546);
ÐÀ4import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
import { useState, useEffect } from "react";
import { useRouter } from "next/router";
import { isLoggedIn, searchRecords } from "../lib/api";
import { DEFAULT_CATEGORIES } from "../lib/categories";
import AppShell from "../components/AppShell";
import { Icon } from "../components/icons";
import { useToast } from "../components/ToastProvider";
export default function Search() {
const router = useRouter();
const { showToast } = useToast();
const [keyword, setKeyword] = useState("");
const [results, setResults] = useState([]);
const [loading, setLoading] = useState(false);
useEffect(()=>{
if (!isLoggedIn()) {
router.push("/login");
}
}, [
router
]);
const handleSearch = async (e)=>{
e.preventDefault();
if (!keyword.trim()) {
showToast("请输入æœç´¢å…³é”®è¯", {
tone: "info"
});
return;
}
setLoading(true);
try {
const data = await searchRecords(keyword);
setResults(data);
} catch (err) {
console.error(err);
showToast(err.message, {
tone: "error"
});
} finally{
setLoading(false);
}
};
const formatMoney = (num)=>(num || 0).toFixed(2);
const groupedResults = results.reduce((groups, record)=>{
const date = record.date.split(" ")[0];
if (!groups[date]) groups[date] = [];
groups[date].push(record);
return groups;
}, {});
const sortedDates = Object.keys(groupedResults).sort((a, b)=>b.localeCompare(a));
const totalIncome = results.filter((record)=>record.type === "income").reduce((sum, record)=>sum + record.amount, 0);
const totalExpense = results.filter((record)=>record.type === "expense").reduce((sum, record)=>sum + record.amount, 0);
const totalBalance = totalIncome - totalExpense;
const actions = /*#__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, {
title: "æœç´¢",
subtitle: "快速查找分类ã€å¤‡æ³¨ä¸Žé‡‘é¢",
actions: actions,
compactHeader: true,
children: [
/*#__PURE__*/ _jsxs("section", {
className: "card section-card section-card--search section-card--search-panel",
children: [
/*#__PURE__*/ _jsxs("form", {
className: "search-form search-form--refresh",
onSubmit: handleSearch,
children: [
/*#__PURE__*/ _jsxs("div", {
className: "search-input-wrap",
children: [
/*#__PURE__*/ _jsx(Icon, {
name: "search",
size: 18,
className: "search-input-wrap__icon"
}),
/*#__PURE__*/ _jsx("input", {
type: "text",
value: keyword,
onChange: (e)=>setKeyword(e.target.value),
placeholder: "æœç´¢åˆ†ç±»æˆ–备注...",
autoFocus: true,
disabled: loading
})
]
}),
/*#__PURE__*/ _jsx("button", {
type: "submit",
className: "btn btn-primary btn-inline",
disabled: loading,
children: loading ? /*#__PURE__*/ _jsxs("span", {
className: "button-content",
children: [
/*#__PURE__*/ _jsx(Icon, {
name: "spinner",
size: 16,
className: "is-spinning"
}),
œç´¢ä¸­..."
]
}) : "æœç´¢"
})
]
}),
results.length > 0 ? /*#__PURE__*/ _jsxs(_Fragment, {
children: [
/*#__PURE__*/ _jsxs("div", {
className: "search-summary",
children: [
/*#__PURE__*/ _jsxs("div", {
className: "search-stat",
children: [
/*#__PURE__*/ _jsx("span", {
className: "label",
children: "æ”¶å…¥"
}),
/*#__PURE__*/ _jsxs("span", {
className: "value income",
children: [
"+",
formatMoney(totalIncome)
]
})
]
}),
/*#__PURE__*/ _jsxs("div", {
className: "search-stat",
children: [
/*#__PURE__*/ _jsx("span", {
className: "label",
children: "支出"
}),
/*#__PURE__*/ _jsxs("span", {
className: "value expense",
children: [
"-",
formatMoney(totalExpense)
]
})
]
}),
/*#__PURE__*/ _jsxs("div", {
className: "search-stat",
children: [
/*#__PURE__*/ _jsx("span", {
className: "label",
children: "结余"
}),
/*#__PURE__*/ _jsx("span", {
className: "value",
children: formatMoney(totalBalance)
})
]
})
]
}),
/*#__PURE__*/ _jsxs("div", {
className: "results-count",
children: [
"找到 ",
results.length,
" æ¡è®°å½•"
]
})
]
}) : null
]
}),
loading ? /*#__PURE__*/ _jsx("div", {
className: "card loading-state",
children: "æœç´¢ä¸­..."
}) : results.length > 0 ? /*#__PURE__*/ _jsx("section", {
className: "card section-card",
children: /*#__PURE__*/ _jsx("div", {
className: "records-list records-list--flush",
children: sortedDates.map((date)=>/*#__PURE__*/ _jsxs("div", {
className: "date-group",
children: [
/*#__PURE__*/ _jsx("div", {
className: "date-label",
children: new Date(date).toLocaleDateString("zh-CN", {
month: "short",
day: "numeric",
weekday: "short"
})
}),
groupedResults[date].map((record)=>{
const categories = DEFAULT_CATEGORIES[record.type];
const cat = categories.find((item)=>item.name === record.category) || {
icon: "\uD83D\uDCDD"
};
return /*#__PURE__*/ _jsxs("div", {
className: "record-item record-item--static",
children: [
/*#__PURE__*/ _jsxs("div", {
className: "record-left",
children: [
/*#__PURE__*/ _jsx("div", {
className: "record-icon",
children: cat.icon
}),
/*#__PURE__*/ _jsxs("div", {
className: "record-info",
children: [
/*#__PURE__*/ _jsx("div", {
className: "record-category",
children: record.category
}),
record.note && /*#__PURE__*/ _jsx("div", {
className: "record-note",
children: record.note
})
]
})
]
}),
/*#__PURE__*/ _jsxs("div", {
className: "record-amount ".concat(record.type),
children: [
record.type === "income" ? "+" : "-",
formatMoney(record.amount)
]
})
]
}, record.id);
})
]
}, date))
})
}) : keyword ? /*#__PURE__*/ _jsxs("div", {
className: "card empty-state empty-state--card",
children: [
/*#__PURE__*/ _jsx("div", {
className: "empty-state__icon",
children: /*#__PURE__*/ _jsx(Icon, {
name: "search",
size: 24
})
}),
/*#__PURE__*/ _jsx("p", {
children: "未找到相关记录"
})
]
}) : /*#__PURE__*/ _jsxs("div", {
className: "card empty-state empty-state--card",
children: [
/*#__PURE__*/ _jsx("div", {
className: "empty-state__icon",
children: /*#__PURE__*/ _jsx(Icon, {
name: "search",
size: 24
})
}),
/*#__PURE__*/ _jsx("p", {
children: "输入关键è¯å¼€å§‹æœç´¢"
})
]
})
]
});
}
R085undefined87130undefined132171undefined173227undefined229283undefined285330undefined332374undefined376430undefined432446undefined486494(0,next_router__WEBPACK_IMPORTED_MODULE_2__.useRouter)undefined525532(0,_components_ToastProvider__WEBPACK_IMPORTED_MODULE_6__/* .useToast */ .p)undefined571578(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined619626(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined667674(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined688696(0,react__WEBPACK_IMPORTED_MODULE_1__.useEffect)undefined717726(0,_lib_api__WEBPACK_IMPORTED_MODULE_7__/* .isLoggedIn */ .jl)undefined10891101(0,_lib_api__WEBPACK_IMPORTED_MODULE_7__/* .searchRecords */ .Kp)undefined20482051(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined22302233(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined22352238_components_icons__WEBPACK_IMPORTED_MODULE_5__/* .Icon */ .Jundefined23382342(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined23442351_components_AppShell__WEBPACK_IMPORTED_MODULE_4__/* ["default"] */ .Zundefined25122516(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined26892693(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined29022906(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined30753078(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined30803083_components_icons__WEBPACK_IMPORTED_MODULE_5__/* .Icon */ .Jundefined33613364(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined38993902(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined41544158(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined43374340(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined43424345_components_icons__WEBPACK_IMPORTED_MODULE_5__/* .Icon */ .Jundefined48434847(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined48494857react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.Fragmentundefined49404944(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined51105114(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined53015304(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined55535557(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined60676071(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined62586261(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined65106514(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined70267030(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined72177220(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined74697472(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined78727876(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined83208323(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined84708473(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined85768579(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined87258729(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined88798882(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined94139430_lib_categories__WEBPACK_IMPORTED_MODULE_3__/* .DEFAULT_CATEGORIES */ .Sundefined97119715(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined99229926(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined1013710140(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined1043010434(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined1066910672(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined1102011023(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined1154211546(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined1223212236(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined1237412377(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined1249212495(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined1249712500_components_icons__WEBPACK_IMPORTED_MODULE_5__/* .Icon */ .Jundefined1267112674(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined1279912803(0,react_jsx_runti
/***/ 1163:
 g«webpack/lib/util/registerExternalSerializerœwebpack-sources/ConcatSource«webpack/lib/util/registerExternalSerializer™webpack-sources/RawSourceJ/***/ (function(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/RawSource1module.exports = require('./dist/client/router')
`/ªmodule.exports = __webpack_require__(3079)†buffer†source„size„maps„hash+module.exports = __webpack_require__(3079)
”{"finalSource":true}ƒmapbufferedMap ReplaceSourceRawSource1module.exports = require('./dist/client/router')
6047module.exports = __webpack_require__(3079)undefined
/***/ }) Å/***/ (function(module, __unused_webpack_exports, __webpack_require__) {
module.exports = __webpack_require__(3079)
/***/ }) ÓÖ ÄJ/***/ (function(module, __unused_webpack_exports, __webpack_require__) {
Å1module.exports = require('./dist/client/router')
6047module.exports = __webpack_require__(3079)undefinedÆÇ
},
«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, [888,774,179], function() { return __webpack_exec__(2774); });
var __webpack_exports__ = __webpack_require__.O();
_N_E = __webpack_exports__;
}
]); }ÿÿÿ`H¥W(self["webpackChunk_N_E"] = self["webpackChunk_N_E"] || []).push([[603],{
/***/ 2774:
¤É¦
(window.__NEXT_P = window.__NEXT_P || []).push([
"/search",
function () {
return require("private-next-pages/search.js");
}
]);
if(module.hot) {
module.hot.dispose(function () {
window.__NEXT_P.push(["/search"])
});
}
Z106112__webpack_require__undefined1141437073undefined170179falseundefined182279{}undefined¾Ê¾Ë¢Ì¢Í¤ÎϾʾТ̢ѤÒӾʾç¢Ì¢®¤ÿÿÿ¯¾Ê¾,
/***/ 7073:
¡Ë¡__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": function() { return /* binding */ Search; }
/* 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 _lib_categories__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(7063);
/* harmony import */ var _components_AppShell__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(6248);
/* harmony import */ var _components_icons__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(9415);
/* harmony import */ var _components_ToastProvider__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(5546);
¢À4import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
import { useState, useEffect } from "react";
import { useRouter } from "next/router";
import { isLoggedIn, searchRecords } from "../lib/api";
import { DEFAULT_CATEGORIES } from "../lib/categories";
import AppShell from "../components/AppShell";
import { Icon } from "../components/icons";
import { useToast } from "../components/ToastProvider";
export default function Search() {
const router = useRouter();
const { showToast } = useToast();
const [keyword, setKeyword] = useState("");
const [results, setResults] = useState([]);
const [loading, setLoading] = useState(false);
useEffect(()=>{
if (!isLoggedIn()) {
router.push("/login");
}
}, [
router
]);
const handleSearch = async (e)=>{
e.preventDefault();
if (!keyword.trim()) {
showToast("请输入æœç´¢å…³é”®è¯", {
tone: "info"
});
return;
}
setLoading(true);
try {
const data = await searchRecords(keyword);
setResults(data);
} catch (err) {
console.error(err);
showToast(err.message, {
tone: "error"
});
} finally{
setLoading(false);
}
};
const formatMoney = (num)=>(num || 0).toFixed(2);
const groupedResults = results.reduce((groups, record)=>{
const date = record.date.split(" ")[0];
if (!groups[date]) groups[date] = [];
groups[date].push(record);
return groups;
}, {});
const sortedDates = Object.keys(groupedResults).sort((a, b)=>b.localeCompare(a));
const totalIncome = results.filter((record)=>record.type === "income").reduce((sum, record)=>sum + record.amount, 0);
const totalExpense = results.filter((record)=>record.type === "expense").reduce((sum, record)=>sum + record.amount, 0);
const totalBalance = totalIncome - totalExpense;
const actions = /*#__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, {
title: "æœç´¢",
subtitle: "快速查找分类ã€å¤‡æ³¨ä¸Žé‡‘é¢",
actions: actions,
compactHeader: true,
children: [
/*#__PURE__*/ _jsxs("section", {
className: "card section-card section-card--search section-card--search-panel",
children: [
/*#__PURE__*/ _jsxs("form", {
className: "search-form search-form--refresh",
onSubmit: handleSearch,
children: [
/*#__PURE__*/ _jsxs("div", {
className: "search-input-wrap",
children: [
/*#__PURE__*/ _jsx(Icon, {
name: "search",
size: 18,
className: "search-input-wrap__icon"
}),
/*#__PURE__*/ _jsx("input", {
type: "text",
value: keyword,
onChange: (e)=>setKeyword(e.target.value),
placeholder: "æœç´¢åˆ†ç±»æˆ–备注...",
autoFocus: true,
disabled: loading
})
]
}),
/*#__PURE__*/ _jsx("button", {
type: "submit",
className: "btn btn-primary btn-inline",
disabled: loading,
children: loading ? /*#__PURE__*/ _jsxs("span", {
className: "button-content",
children: [
/*#__PURE__*/ _jsx(Icon, {
name: "spinner",
size: 16,
className: "is-spinning"
}),
œç´¢ä¸­..."
]
}) : "æœç´¢"
})
]
}),
results.length > 0 ? /*#__PURE__*/ _jsxs(_Fragment, {
children: [
/*#__PURE__*/ _jsxs("div", {
className: "search-summary",
children: [
/*#__PURE__*/ _jsxs("div", {
className: "search-stat",
children: [
/*#__PURE__*/ _jsx("span", {
className: "label",
children: "æ”¶å…¥"
}),
/*#__PURE__*/ _jsxs("span", {
className: "value income",
children: [
"+",
formatMoney(totalIncome)
]
})
]
}),
/*#__PURE__*/ _jsxs("div", {
className: "search-stat",
children: [
/*#__PURE__*/ _jsx("span", {
className: "label",
children: "支出"
}),
/*#__PURE__*/ _jsxs("span", {
className: "value expense",
children: [
"-",
formatMoney(totalExpense)
]
})
]
}),
/*#__PURE__*/ _jsxs("div", {
className: "search-stat",
children: [
/*#__PURE__*/ _jsx("span", {
className: "label",
children: "结余"
}),
/*#__PURE__*/ _jsx("span", {
className: "value",
children: formatMoney(totalBalance)
})
]
})
]
}),
/*#__PURE__*/ _jsxs("div", {
className: "results-count",
children: [
"找到 ",
results.length,
" æ¡è®°å½•"
]
})
]
}) : null
]
}),
loading ? /*#__PURE__*/ _jsx("div", {
className: "card loading-state",
children: "æœç´¢ä¸­..."
}) : results.length > 0 ? /*#__PURE__*/ _jsx("section", {
className: "card section-card",
children: /*#__PURE__*/ _jsx("div", {
className: "records-list records-list--flush",
children: sortedDates.map((date)=>/*#__PURE__*/ _jsxs("div", {
className: "date-group",
children: [
/*#__PURE__*/ _jsx("div", {
className: "date-label",
children: new Date(date).toLocaleDateString("zh-CN", {
month: "short",
day: "numeric",
weekday: "short"
})
}),
groupedResults[date].map((record)=>{
const categories = DEFAULT_CATEGORIES[record.type];
const cat = categories.find((item)=>item.name === record.category) || {
icon: "\uD83D\uDCDD"
};
return /*#__PURE__*/ _jsxs("div", {
className: "record-item record-item--static",
children: [
/*#__PURE__*/ _jsxs("div", {
className: "record-left",
children: [
/*#__PURE__*/ _jsx("div", {
className: "record-icon",
children: cat.icon
}),
/*#__PURE__*/ _jsxs("div", {
className: "record-info",
children: [
/*#__PURE__*/ _jsx("div", {
className: "record-category",
children: record.category
}),
record.note && /*#__PURE__*/ _jsx("div", {
className: "record-note",
children: record.note
})
]
})
]
}),
/*#__PURE__*/ _jsxs("div", {
className: "record-amount ".concat(record.type),
children: [
record.type === "income" ? "+" : "-",
formatMoney(record.amount)
]
})
]
}, record.id);
})
]
}, date))
})
}) : keyword ? /*#__PURE__*/ _jsxs("div", {
className: "card empty-state empty-state--card",
children: [
/*#__PURE__*/ _jsx("div", {
className: "empty-state__icon",
children: /*#__PURE__*/ _jsx(Icon, {
name: "search",
size: 24
})
}),
/*#__PURE__*/ _jsx("p", {
children: "未找到相关记录"
})
]
}) : /*#__PURE__*/ _jsxs("div", {
className: "card empty-state empty-state--card",
children: [
/*#__PURE__*/ _jsx("div", {
className: "empty-state__icon",
children: /*#__PURE__*/ _jsx(Icon, {
name: "search",
size: 24
})
}),
/*#__PURE__*/ _jsx("p", {
children: "输入关键è¯å¼€å§‹æœç´¢"
})
]
})
]
});
}
R085undefined87130undefined132171undefined173227undefined229283undefined285330undefined332374undefined376430undefined432446undefined486494(0,next_router__WEBPACK_IMPORTED_MODULE_2__.useRouter)undefined525532(0,_components_ToastProvider__WEBPACK_IMPORTED_MODULE_6__/* .useToast */ .p)undefined571578(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined619626(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined667674(0,react__WEBPACK_IMPORTED_MODULE_1__.useState)undefined688696(0,react__WEBPACK_IMPORTED_MODULE_1__.useEffect)undefined717726(0,_lib_api__WEBPACK_IMPORTED_MODULE_7__/* .isLoggedIn */ .jl)undefined10891101(0,_lib_api__WEBPACK_IMPORTED_MODULE_7__/* .searchRecords */ .Kp)undefined20482051(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined22302233(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined22352238_components_icons__WEBPACK_IMPORTED_MODULE_5__/* .Icon */ .Jundefined23382342(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined23442351_components_AppShell__WEBPACK_IMPORTED_MODULE_4__/* ["default"] */ .Zundefined25122516(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined26892693(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined29022906(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined30753078(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined30803083_components_icons__WEBPACK_IMPORTED_MODULE_5__/* .Icon */ .Jundefined33613364(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined38993902(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined41544158(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined43374340(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined43424345_components_icons__WEBPACK_IMPORTED_MODULE_5__/* .Icon */ .Jundefined48434847(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined48494857react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.Fragmentundefined49404944(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined51105114(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined53015304(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined55535557(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined60676071(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined62586261(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined65106514(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined70267030(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined72177220(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined74697472(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined78727876(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined83208323(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined84708473(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined85768579(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined87258729(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined88798882(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined94139430_lib_categories__WEBPACK_IMPORTED_MODULE_3__/* .DEFAULT_CATEGORIES */ .Sundefined97119715(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined99229926(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined1013710140(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined1043010434(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined1066910672(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined1102011023(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined1154211546(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined1223212236(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)undefined1237412377(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined1249212495(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined1249712500_components_icons__WEBPACK_IMPORTED_MODULE_5__/* .Icon */ .Jundefined1267112674(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)undefined1279912803(0,react_jsx_runti
__webpack_require__.O(0, [888,774,179], function() { return __webpack_exec__(2774); });
var __webpack_exports__ = __webpack_require__.O();
¹ØÙÚnÿÿÿ-(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[161],{5461:function(e,n,t){(window.__NEXT_P=window.__NEXT_P||[]).push(["/categories",function(){return t(2874)}])},6248:function(e,n,t){"use strict";t.d(n,{Z:function(){return u}});var a=t(5893),c=t(7294),s=t(1163),r=t(9415);let i=[{href:"/",label:"首页",icon:"home"},{href:"/stats",label:"统计",icon:"chart"},{href:"/recurring",label:"周期",icon:"repeat"},{href:"/categories",label:"分类",icon:"tags"}];function u(e){let{title:n,subtitle:t,actions:u,headerContent:o,children:l,contentClassName:D="",hideNav:d=!1,compactHeader:h=!1}=e,m=(0,s.useRouter)(),p=(0,c.useMemo)(()=>{let e=i.find(e=>e.href===m.pathname);return(null==e?void 0:e.href)||null},[m.pathname]),f=o||(n?(0,a.jsxs)("div",{className:"app-header__text",children:[(0,a.jsx)("h1",{children:n}),t?(0,a.jsx)("p",{children:t}):null]}):null),y=["app-header",h?"app-header--compact":"",f?"app-header--with-content":"app-header--actions-only",u?"app-header--with-actions":""].filter(Boolean).join(" ");return(0,a.jsxs)("div",{className:"app-shell".concat(d?" app-shell--standalone":""),children:[(0,a.jsx)("header",{className:y,children:(0,a.jsxs)("div",{className:"app-shell__container app-header__inner",children:[f?(0,a.jsx)("div",{className:"app-header__main",children:f}):(0,a.jsx)("div",{className:"app-header__spacer","aria-hidden":"true"}),u?(0,a.jsx)("div",{className:"app-header__actions",children:u}):null]})}),(0,a.jsx)("main",{className:"app-content ".concat(D).trim(),children:(0,a.jsx)("div",{className:"app-shell__container app-content__inner",children:l})}),d?null:(0,a.jsx)("nav",{className:"bottom-nav","aria-label":"主导航",children:(0,a.jsx)("div",{className:"app-shell__container bottom-nav__inner",children:i.map(e=>{let n=p===e.href;return(0,a.jsxs)("button",{type:"button",className:"bottom-nav__item".concat(n?" is-active":""),onClick:()=>m.push(e.href),"aria-current":n?"page":void 0,children:[(0,a.jsx)(r.J,{name:e.icon,size:20}),(0,a.jsx)("span",{children:e.label})]},e.href)})})})]})}},610:function(e,n,t){"use strict";async function a(e){let n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},t=await fetch("".concat("").concat(e),{...n,headers:{"Content-Type":"application/json",...function(){let e=localStorage.getItem("token");return e?{Authorization:"Bearer ".concat(e)}:{}}(),...n.headers}}),a=await t.json();if(!t.ok)throw Error(a.error||"请求失败");return a}async function c(e,n){let t=await a("/api/auth/login",{method:"POST",body:JSON.stringify({username:e,password:n})});return localStorage.setItem("token",t.token),t}async function s(e,n){let t=await a("/api/auth/register",{method:"POST",body:JSON.stringify({username:e,password:n})});return localStorage.setItem("token",t.token),t}function r(){localStorage.removeItem("token")}function i(){return!!localStorage.getItem("token")}async function u(e){let n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"all",t="/api/records?month=".concat(e);return"all"!==n&&(t+="&type=".concat(n)),a(t)}async function o(e){return a("/api/stats?month=".concat(e))}async function l(e){return a("/api/records",{method:"POST",body:JSON.stringify(e)})}async function D(e,n){return a("/api/records/".concat(e),{method:"PUT",body:JSON.stringify(n)})}async function d(e){return a("/api/records/".concat(e),{method:"DELETE"})}async function h(e){return a("/api/records/search?q=".concat(encodeURIComponent(e)))}async function m(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"month",n=arguments.length>1?arguments[1]:void 0,t="/api/stats/trend?period=".concat(e);return n&&(t+="&month=".concat(n)),a(t)}async function p(e){let n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"all",t="/api/stats/category?type=".concat(n);return e&&(t+="&month=".concat(e)),a(t)}async function f(){return a("/api/recurring")}async function y(e){return a("/api/recurring",{method:"POST",body:JSON.stringify(e)})}async function x(e,n){return a("/api/recurring/".concat(e),{method:"PUT",body:JSON.stringify(n)})}async function j(e){return a("/api/rec
îñòóôõö÷øùè {
"polyfillFiles": [
"static/chunks/polyfills-42372ed130431b0a.js"
],
"devFiles": [],
"ampDevFiles": [],
"lowPriorityFiles": [
"static/arzRem0c_q1eUEceHYY3m/_buildManifest.js",
"static/arzRem0c_q1eUEceHYY3m/_ssgManifest.js"
],
"rootMainFiles": [],
"pages": {
"/": [
"static/chunks/webpack-8fa1640cc84ba8fe.js",
"static/chunks/framework-64ad27b21261a9ce.js",
"static/chunks/main-aefbcb23b1606112.js",
"static/chunks/pages/index-9134a1c15f8e7e73.js"
],
"/_app": [
"static/chunks/webpack-8fa1640cc84ba8fe.js",
"static/chunks/framework-64ad27b21261a9ce.js",
"static/chunks/main-aefbcb23b1606112.js",
"static/css/aaf95cae7d25f2df.css",
"static/chunks/pages/_app-be10d8e0e3953b07.js"
],
"/_error": [
"static/chunks/webpack-8fa1640cc84ba8fe.js",
"static/chunks/framework-64ad27b21261a9ce.js",
"static/chunks/main-aefbcb23b1606112.js",
"static/chunks/pages/_error-7a92967bea80186d.js"
],
"/categories": [
"static/chunks/webpack-8fa1640cc84ba8fe.js",
"static/chunks/framework-64ad27b21261a9ce.js",
"static/chunks/main-aefbcb23b1606112.js",
"static/chunks/pages/categories-a3255e0a23d19e86.js"
],
"/login": [
"static/chunks/webpack-8fa1640cc84ba8fe.js",
"static/chunks/framework-64ad27b21261a9ce.js",
"static/chunks/main-aefbcb23b1606112.js",
"static/chunks/pages/login-f50164aa2a6805b4.js"
],
"/recurring": [
"static/chunks/webpack-8fa1640cc84ba8fe.js",
"static/chunks/framework-64ad27b21261a9ce.js",
"static/chunks/main-aefbcb23b1606112.js",
"static/chunks/pages/recurring-560011bee3956d16.js"
],
"/search": [
"static/chunks/webpack-8fa1640cc84ba8fe.js",
"static/chunks/framework-64ad27b21261a9ce.js",
"static/chunks/main-aefbcb23b1606112.js",
"static/chunks/pages/search-3f7f0eb3485675fc.js"
],
"/settings": [
"static/chunks/webpack-8fa1640cc84ba8fe.js",
"static/chunks/framework-64ad27b21261a9ce.js",
"static/chunks/main-aefbcb23b1606112.js",
"static/chunks/pages/settings-bb02e29d5162e9fa.js"
],
"/stats": [
"static/chunks/webpack-8fa1640cc84ba8fe.js",
"static/chunks/framework-64ad27b21261a9ce.js",
"static/chunks/main-aefbcb23b1606112.js",
"static/chunks/ee8b1517-5dfbbb6838544537.js",
"static/chunks/925-ca8bea7fb2a62185.js",
"static/chunks/pages/stats-ce96160268c9c2d4.js"
]
},
"ampFirstPages": []
} self.__BUILD_MANIFEST={polyfillFiles:["static/chunks/polyfills-42372ed130431b0a.js"],devFiles:[],ampDevFiles:[],lowPriorityFiles:[],rootMainFiles:[],pages:{"/":["static/chunks/webpack-8fa1640cc84ba8fe.js","static/chunks/framework-64ad27b21261a9ce.js","static/chunks/main-aefbcb23b1606112.js","static/chunks/pages/index-9134a1c15f8e7e73.js"],"/_app":["static/chunks/webpack-8fa1640cc84ba8fe.js","static/chunks/framework-64ad27b21261a9ce.js","static/chunks/main-aefbcb23b1606112.js","static/css/aaf95cae7d25f2df.css","static/chunks/pages/_app-be10d8e0e3953b07.js"],"/_error":["static/chunks/webpack-8fa1640cc84ba8fe.js","static/chunks/framework-64ad27b21261a9ce.js","static/chunks/main-aefbcb23b1606112.js","static/chunks/pages/_error-7a92967bea80186d.js"],"/categories":["static/chunks/webpack-8fa1640cc84ba8fe.js","static/chunks/framework-64ad27b21261a9ce.js","static/chunks/main-aefbcb23b1606112.js","static/chunks/pages/categories-a3255e0a23d19e86.js"],"/login":["static/chunks/webpack-8fa1640cc84ba8fe.js","static/chunks/framework-64ad27b21261a9ce.js","static/chunks/main-aefbcb23b1606112.js","static/chunks/pages/login-f50164aa2a6805b4.js"],"/recurring":["static/chunks/webpack-8fa1640cc84ba8fe.js","static/chunks/framework-64ad27b21261a9ce.js","static/chunks/main-aefbcb23b1606112.js","static/chunks/pages/recurring-560011bee3956d16.js"],"/search":["static/chunks/webpack-8fa1640cc84ba8fe.js","static/chunks/framework-64ad27b21261a9ce.js","static/chunks/main-aefbcb23b1606112.js","static/chunks/pages/search-3f7f0eb3485675fc.js"],"/settings":["static/chunks/webpack-8fa1640cc84ba8fe.js","static/chunks/framework-64ad27b21261a9ce.js","static/chunks/main-aefbcb23b1606112.js","static/chunks/pages/settings-bb02e29d5162e9fa.js"],"/stats":["static/chunks/webpack-8fa1640cc84ba8fe.js","static/chunks/framework-64ad27b21261a9ce.js","static/chunks/main-aefbcb23b1606112.js","static/chunks/ee8b1517-5dfbbb6838544537.js","static/chunks/925-ca8bea7fb2a62185.js","static/chunks/pages/stats-ce96160268c9c2d4.js"]},ampFirstPages:[]},self.__BUILD_MANIFEST.lowPriorityFiles=["/static/"+process.env.__NEXT_BUILD_ID+"/_buildManifest.js",,"/static/"+process.env.__NEXT_BUILD_ID+"/_ssgManifest.js"]; sself.__BUILD_MANIFEST=function(s){return{__rewrites:{afterFiles:[{has:s,source:"/api/:path*",destination:s}],beforeFiles:[],fallback:[]},"/":["static/chunks/pages/index-9134a1c15f8e7e73.js"],"/_error":["static/chunks/pages/_error-7a92967bea80186d.js"],"/categories":["static/chunks/pages/categories-a3255e0a23d19e86.js"],"/login":["static/chunks/pages/login-f50164aa2a6805b4.js"],"/recurring":["static/chunks/pages/recurring-560011bee3956d16.js"],"/search":["static/chunks/pages/search-3f7f0eb3485675fc.js"],"/settings":["static/chunks/pages/settings-bb02e29d5162e9fa.js"],"/stats":["static/chunks/ee8b1517-5dfbbb6838544537.js","static/chunks/925-ca8bea7fb2a62185.js","static/chunks/pages/stats-ce96160268c9c2d4.js"],sortedPages:["/","/_app","/_error","/categories","/login","/recurring","/search","/settings","/stats"]}}(void 0),self.__BUILD_MANIFEST_CB&&self.__BUILD_MANIFEST_CB();