fix: 修复 EXE 数据库 schema 不匹配导致 500 错误
- ISS 移除 onlyifdoesntexist 确保安装目录 db.sqlite3 始终更新 - .gitignore 忽略所有 sqlite3 文件 - 前端翻页组件和样式更新
This commit is contained in:
+1
-1
@@ -9,7 +9,7 @@ frontend/node_modules/
|
||||
frontend/dist/
|
||||
|
||||
# Data
|
||||
backend/db.sqlite3
|
||||
*.sqlite3
|
||||
backend/staticfiles/
|
||||
|
||||
# Logs
|
||||
|
||||
+14
-2
@@ -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
|
||||
+30
-11
@@ -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 }) {
|
||||
/>
|
||||
页
|
||||
</span>
|
||||
{onPageSizeChange && (
|
||||
<span className="pg-size">
|
||||
每页
|
||||
<select
|
||||
className="pg-size-select"
|
||||
value={pageSize}
|
||||
onChange={(e) => { onPageSizeChange(Number(e.target.value)); onChange(1) }}
|
||||
>
|
||||
{PAGE_SIZE_OPTIONS.map(n => (
|
||||
<option key={n} value={n}>{n}</option>
|
||||
))}
|
||||
</select>
|
||||
条
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
@@ -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 (
|
||||
<div className="flat-dish-list">
|
||||
@@ -189,13 +206,14 @@ function FlatDishList({ items, currentId, hover, setHover, onSelect, renderItem
|
||||
))}
|
||||
{pageItems.length === 0 && <p className="hint warn">没有匹配的菜品</p>}
|
||||
</div>
|
||||
<Pagination page={page} total={items.length} pageSize={MODAL_PAGE_SIZE} onChange={setPage} />
|
||||
<Pagination page={page} total={items.length} pageSize={pageSize} onPageSizeChange={setPageSize} onChange={setPage} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
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 (
|
||||
<div
|
||||
@@ -284,7 +302,7 @@ function SwapModal({ editing, menus, dishes, pinyinMap, swapKeyword, swapType, s
|
||||
))}
|
||||
{pageItems.length === 0 && <p className="hint warn">没有匹配的菜品</p>}
|
||||
</div>
|
||||
<Pagination page={swapPage} total={flatList.length} pageSize={MODAL_PAGE_SIZE} onChange={setSwapPage} />
|
||||
<Pagination page={swapPage} total={flatList.length} pageSize={swapPageSize} onPageSizeChange={setSwapPageSize} onChange={setSwapPage} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -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 (
|
||||
<div
|
||||
@@ -381,7 +400,7 @@ function PickModal({ pick, slots, dishes, pinyinMap, keyword, pickType, setKeywo
|
||||
))}
|
||||
{pageItems.length === 0 && <p className="hint warn">没有匹配的菜品</p>}
|
||||
</div>
|
||||
<Pagination page={pickPage} total={flatList.length} pageSize={MODAL_PAGE_SIZE} onChange={setPickPage} />
|
||||
<Pagination page={pickPage} total={flatList.length} pageSize={pickPageSize} onPageSizeChange={setPickPageSize} onChange={setPickPage} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+31
-1
@@ -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 {
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
+23
-7
@@ -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;
|
||||
Reference in New Issue
Block a user