diff --git a/.gitignore b/.gitignore
index d78fad5..f2954c0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,7 +9,7 @@ frontend/node_modules/
frontend/dist/
# Data
-backend/db.sqlite3
+*.sqlite3
backend/staticfiles/
# Logs
diff --git a/build_installer.bat b/build_installer.bat
index e6a9a5b..14ec4a9 100644
--- a/build_installer.bat
+++ b/build_installer.bat
@@ -1,4 +1,4 @@
-@echo off
+@echo off
chcp 65001 >nul
echo ==========================================
echo 学校订餐菜单生成器 - 一键打包安装包
@@ -40,6 +40,8 @@ if exist "C:\Program Files (x86)\Inno Setup 6\ISCC.exe" (
set "ISCC=C:\Program Files\Inno Setup 6\ISCC.exe"
) else if exist "C:\Program Files (x86)\Inno Setup 5\ISCC.exe" (
set "ISCC=C:\Program Files (x86)\Inno Setup 5\ISCC.exe"
+) else if exist "C:\Program Files\Inno Setup 5\ISCC.exe" (
+ set "ISCC=C:\Program Files\Inno Setup 5\ISCC.exe"
) else (
where iscc >nul 2>&1
if not errorlevel 1 (
@@ -60,6 +62,16 @@ if "%ISCC%"=="" (
)
echo 找到 Inno Setup: %ISCC%
+echo.
+echo [3.5/4] 验证语言文件...
+for %%I in ("%ISCC%") do set "ISDIR=%%~dpI"
+if not exist "%ISDIR%Languages\ChineseSimplified.isl" (
+ echo.
+ echo 警告: 未找到中文语言文件 ChineseSimplified.isl
+ echo 安装向导将使用英文界面。
+ echo 如需中文界面,请确保 Inno Setup 完整安装。
+)
+
echo.
echo [4/4] 编译安装包...
"%ISCC%" school-meal-setup.iss
@@ -78,4 +90,4 @@ echo 输出目录: installer\
echo 安装包: installer\学校订餐菜单生成器-安装包.exe
echo ==========================================
echo.
-pause
+pause
\ No newline at end of file
diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx
index c2fbadb..09aca01 100644
--- a/frontend/src/App.jsx
+++ b/frontend/src/App.jsx
@@ -20,7 +20,8 @@ const TYPE_LABELS = { meats: '荤菜', vegs: '素菜', soup: '营养汤', staple
const TYPE_TO_TAB = { meats: 'meat', vegs: 'veg', soup: 'soup', staple: 'staple' }
const TYPE_ALL_LABELS = { meat: '荤菜', veg: '素菜', soup: '营养汤', staple: '主食' }
const DEFAULT_SCHOOL = '学校菜单'
-const MODAL_PAGE_SIZE = 20
+const DEFAULT_PAGE_SIZE = 20
+const PAGE_SIZE_OPTIONS = [10, 20, 50, 100]
/** Compute ISO week Monday/Friday as Date objects */
const weekDates = (weekNo) => {
@@ -63,7 +64,7 @@ const slotKey = (menu, day, slot, idx) => `${menu}|${day}|${slot}|${idx}`
/* ============================
Modern Pagination Component
============================ */
-function Pagination({ page, total, pageSize, onChange }) {
+function Pagination({ page, total, pageSize, onPageSizeChange, onChange }) {
const totalPages = Math.max(1, Math.ceil(total / pageSize))
const currentPage = Math.min(Math.max(1, page), totalPages)
if (totalPages <= 1) return null
@@ -149,6 +150,21 @@ function Pagination({ page, total, pageSize, onChange }) {
/>
页
+ {onPageSizeChange && (
+
+ 每页
+
+ 条
+
+ )}
)
@@ -158,6 +174,7 @@ function Pagination({ page, total, pageSize, onChange }) {
FlatDishList — paginated dish grid for modals
============================ */
function FlatDishList({ items, currentId, hover, setHover, onSelect, renderItem }) {
+ const [pageSize, setPageSize] = useState(DEFAULT_PAGE_SIZE)
const [page, setPage] = useState(1)
// Reset page when items change (e.g. search/filter)
@@ -167,8 +184,8 @@ function FlatDishList({ items, currentId, hover, setHover, onSelect, renderItem
// Use effect-less pattern: we'll reset via key prop
}
- const startIdx = (page - 1) * MODAL_PAGE_SIZE
- const pageItems = items.slice(startIdx, startIdx + MODAL_PAGE_SIZE)
+ const startIdx = (page - 1) * pageSize
+ const pageItems = items.slice(startIdx, startIdx + pageSize)
return (
@@ -189,13 +206,14 @@ function FlatDishList({ items, currentId, hover, setHover, onSelect, renderItem
))}
{pageItems.length === 0 &&
没有匹配的菜品
}
-
+
)
}
function SwapModal({ editing, menus, dishes, pinyinMap, swapKeyword, swapType, setSwapKeyword, setSwapType, onReplace, onClose }) {
const [hover, setHover] = useState(null)
+ const [swapPageSize, setSwapPageSize] = useState(DEFAULT_PAGE_SIZE)
const [swapPage, setSwapPage] = useState(1)
// Reset page on filter/search change
@@ -229,8 +247,8 @@ function SwapModal({ editing, menus, dishes, pinyinMap, swapKeyword, swapType, s
return result
}, [swapType, kw, dishes])
- const startIdx = (swapPage - 1) * MODAL_PAGE_SIZE
- const pageItems = flatList.slice(startIdx, startIdx + MODAL_PAGE_SIZE)
+ const startIdx = (swapPage - 1) * swapPageSize
+ const pageItems = flatList.slice(startIdx, startIdx + swapPageSize)
return (
没有匹配的菜品}
-
+
@@ -295,6 +313,7 @@ function SwapModal({ editing, menus, dishes, pinyinMap, swapKeyword, swapType, s
function PickModal({ pick, slots, dishes, pinyinMap, keyword, pickType, setKeyword, setPickType, onChoose, onClose }) {
const [hover, setHover] = useState(null)
+ const [pickPageSize, setPickPageSize] = useState(DEFAULT_PAGE_SIZE)
const [pickPage, setPickPage] = useState(1)
// Reset page on filter/search change
@@ -324,8 +343,8 @@ function PickModal({ pick, slots, dishes, pinyinMap, keyword, pickType, setKeywo
return result
}, [pickType, kw, dishes])
- const startIdx = (pickPage - 1) * MODAL_PAGE_SIZE
- const pageItems = flatList.slice(startIdx, startIdx + MODAL_PAGE_SIZE)
+ const startIdx = (pickPage - 1) * pickPageSize
+ const pageItems = flatList.slice(startIdx, startIdx + pickPageSize)
return (
没有匹配的菜品}
-
+
diff --git a/frontend/src/index.css b/frontend/src/index.css
index a9b0c81..5d1cc1e 100644
--- a/frontend/src/index.css
+++ b/frontend/src/index.css
@@ -1,4 +1,4 @@
-/* ============================================
+/* ============================================
学校订餐菜单生成器 — Modern Design System
Based on 2025-2026 mainstream web trends
============================================ */
@@ -1072,6 +1072,36 @@ table.excel-like.nutri-table td {
box-shadow: 0 0 0 3px var(--c-accent-light);
}
+/* Page size selector */
+.pg-size {
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+ margin-left: 10px;
+ font-size: 12px;
+ color: var(--c-text-muted);
+ white-space: nowrap;
+}
+
+.pg-size-select {
+ height: 30px;
+ padding: 0 6px;
+ border: 1.5px solid var(--c-border);
+ border-radius: var(--radius-sm);
+ font-size: 12px;
+ background: var(--c-surface);
+ color: var(--c-text);
+ outline: none;
+ cursor: pointer;
+ transition: border-color 0.18s ease, box-shadow 0.18s ease;
+ font-family: inherit;
+}
+
+.pg-size-select:focus {
+ border-color: var(--c-accent);
+ box-shadow: 0 0 0 3px var(--c-accent-light);
+}
+
/* Pagination responsive */
@media (max-width: 600px) {
.pagination {
diff --git a/frontend/vite.config.js b/frontend/vite.config.js
index bac987a..5a3acfe 100644
--- a/frontend/vite.config.js
+++ b/frontend/vite.config.js
@@ -1,4 +1,4 @@
-import { defineConfig } from 'vite'
+import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
@@ -8,6 +8,8 @@ export default defineConfig({
port: 5173,
proxy: {
'/api': 'http://127.0.0.1:8000',
+ '/admin': 'http://127.0.0.1:8000',
+ '/static': 'http://127.0.0.1:8000',
},
},
})
diff --git a/school-meal-setup.iss b/school-meal-setup.iss
index 578e090..4b64652 100644
--- a/school-meal-setup.iss
+++ b/school-meal-setup.iss
@@ -1,4 +1,4 @@
-; Inno Setup script for 学校订餐菜单生成器
+; Inno Setup script for 学校订餐菜单生成器
; Build with: iscc school-meal-setup.iss
; Prerequisites: Inno Setup 6.x (https://jrsoftware.org/isinfo.php)
;
@@ -11,7 +11,7 @@ AppName=学校订餐菜单生成器
AppVersion=1.0.0
AppPublisher=学校食堂管理
AppPublisherURL=
-DefaultDirName={autopf}\学校订餐菜单生成器
+DefaultDirName={autopf}\SchoolMeal
DefaultGroupName=学校订餐菜单生成器
OutputDir=installer
OutputBaseFilename=学校订餐菜单生成器-安装包
@@ -25,9 +25,18 @@ ArchitecturesAllowed=x64compatible
ArchitecturesInstallIn64BitMode=x64compatible
CloseApplications=force
RestartApplications=no
-DisableProgramGroupPage=yes
UninstallDisplayIcon={app}\app.ico
-LicenseFile=
+VersionInfoVersion=1.0.0.0
+VersionInfoCompany=学校食堂管理
+VersionInfoDescription=学校订餐菜单生成器安装程序
+VersionInfoProductName=学校订餐菜单生成器
+VersionInfoProductVersion=1.0.0
+MinVersion=10.0.17763
+SetupLogging=yes
+UninstallLogMode=append
+UsePreviousAppDir=no
+UsePreviousGroup=no
+UsePreviousSetupType=no
[Languages]
Name: "chinesesimplified"; MessagesFile: "compiler:Languages\ChineseSimplified.isl"
@@ -35,6 +44,7 @@ Name: "english"; MessagesFile: "compiler:Default.isl"
[Tasks]
Name: "desktopicon"; Description: "创建桌面快捷方式"; GroupDescription: "附加选项:"; Flags: checkedonce
+Name: "startupicon"; Description: "开机自动启动"; GroupDescription: "附加选项:"; Flags: unchecked
[Files]
; Main executable
@@ -47,9 +57,10 @@ Source: "dist\school-meal\_internal\*"; DestDir: "{app}\_internal"; Flags: ignor
Source: "app.ico"; DestDir: "{app}"; Flags: ignoreversion
[Icons]
-Name: "{group}\学校订餐菜单生成器"; Filename: "{app}\school-meal.exe"; IconFilename: "{app}\app.ico"
-Name: "{group}\卸载 学校订餐菜单生成器"; Filename: "{uninstallexe}"; IconFilename: "{app}\app.ico"
-Name: "{autodesktop}\学校订餐菜单生成器"; Filename: "{app}\school-meal.exe"; Tasks: desktopicon; IconFilename: "{app}\app.ico"
+Name: "{group}\学校订餐菜单生成器"; Filename: "{app}\school-meal.exe"; WorkingDir: "{app}"; IconFilename: "{app}\app.ico"
+Name: "{group}\卸载 学校订餐菜单生成器"; Filename: "{uninstallexe}"; WorkingDir: "{app}"; IconFilename: "{app}\app.ico"
+Name: "{autodesktop}\学校订餐菜单生成器"; Filename: "{app}\school-meal.exe"; WorkingDir: "{app}"; Tasks: desktopicon; IconFilename: "{app}\app.ico"
+Name: "{userstartup}\学校订餐菜单生成器"; Filename: "{app}\school-meal.exe"; WorkingDir: "{app}"; Tasks: startupicon; IconFilename: "{app}\app.ico"
[Run]
Filename: "{app}\school-meal.exe"; Description: "启动 菜单生成器"; Flags: nowait postinstall skipifsilent
@@ -79,3 +90,8 @@ begin
Result := '';
Exec('taskkill', '/F /IM school-meal.exe', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
end;
+
+function ShouldSkipPage(PageID: Integer): Boolean;
+begin
+ Result := False;
+end;
\ No newline at end of file