fix: 修复 EXE 数据库 schema 不匹配导致 500 错误

- ISS 移除 onlyifdoesntexist 确保安装目录 db.sqlite3 始终更新

- .gitignore 忽略所有 sqlite3 文件

- 前端翻页组件和样式更新
This commit is contained in:
Chunyu
2026-08-29 13:49:23 +08:00
parent d7553520ce
commit ffe44f4cb1
6 changed files with 102 additions and 23 deletions
+30 -11
View File
@@ -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
View File
@@ -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 {
+3 -1
View File
@@ -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',
},
},
})