fix: 修复 EXE 数据库 schema 不匹配导致 500 错误
- ISS 移除 onlyifdoesntexist 确保安装目录 db.sqlite3 始终更新 - .gitignore 忽略所有 sqlite3 文件 - 前端翻页组件和样式更新
This commit is contained in:
+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',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user