From dacd4887aadd3504f64aea3f1934ea6564a71ecf Mon Sep 17 00:00:00 2001 From: Hera Zhao Date: Sat, 18 Apr 2026 13:27:49 +0000 Subject: [PATCH 1/7] =?UTF-8?q?fix:=20=E6=89=8B=E6=9C=BA=E7=BC=96=E8=BE=91?= =?UTF-8?q?=E9=85=8D=E6=96=B9=20overlay=20=E5=90=88=E9=80=82=E5=AE=BD?= =?UTF-8?q?=E5=BA=A6=20+=20=E5=B1=8F=E8=94=BD=20tab=20=E6=BB=91=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 移动端下 .overlay-panel 内边距从 24px 缩小到 16px,配合标题 字号、容器外边距和 ratio 提示换行调整,使编辑配方弹层在手机 上视觉更紧凑。 另外把 .overlay 加入 onSwipeEnd 的 modal 选择器,编辑弹层 打开时不再被左右滑动误触切换 tab。 --- frontend/src/App.vue | 2 +- frontend/src/views/RecipeManager.vue | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/frontend/src/App.vue b/frontend/src/App.vue index b766d69..4c3cf58 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -196,7 +196,7 @@ function onSwipeEnd() { // Carousel area excluded if (swipeStartTarget.value?.closest?.('[data-no-tab-swipe]')) return // Skip when modal/overlay is open - if (document.querySelector('.modal-overlay, .detail-overlay, .dialog-overlay')) return + if (document.querySelector('.modal-overlay, .detail-overlay, .dialog-overlay, .overlay')) return const tabs = visibleTabs.value.map(t => t.key) const currentIdx = tabs.indexOf(ui.currentSection) diff --git a/frontend/src/views/RecipeManager.vue b/frontend/src/views/RecipeManager.vue index 042ce8b..c8c1bc5 100644 --- a/frontend/src/views/RecipeManager.vue +++ b/frontend/src/views/RecipeManager.vue @@ -2483,5 +2483,22 @@ watch(() => recipeStore.recipes, () => { .manage-toolbar { flex-direction: column; } + .overlay-panel { + padding: 16px; + border-radius: 12px; + max-height: calc(100vh - 32px); + } + .overlay-header { + margin-bottom: 12px; + } + .overlay-header h3 { + font-size: 15px; + } + .ratio-hint { + white-space: normal; + } + .editor-section { + margin-bottom: 12px; + } } -- 2.49.1 From 2a2b1b8928622c51f89c264c4a27d122ca6e64b2 Mon Sep 17 00:00:00 2001 From: Hera Zhao Date: Sat, 18 Apr 2026 18:12:52 +0000 Subject: [PATCH 2/7] =?UTF-8?q?fix:=20=E7=B2=BE=E6=B2=B9=E6=90=9C=E7=B4=A2?= =?UTF-8?q?=E6=A1=86=E6=92=91=E5=AE=BD=E6=95=B4=E8=A1=A8=EF=BC=8C=E9=99=90?= =?UTF-8?q?=E5=88=B6=20width=20100%=20=E8=AE=A9=204=20=E5=88=97=E9=83=BD?= =?UTF-8?q?=E8=83=BD=E5=B1=95=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit .form-select 之前没设 width, 默认 size=20 宽约 180px, 把整个 editor-table 撑出面板,后面几列被挤出屏幕。加 width 100% + min-width 0 + box-sizing border-box,让精油列占剩余空间即可。 --- frontend/src/views/RecipeManager.vue | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/frontend/src/views/RecipeManager.vue b/frontend/src/views/RecipeManager.vue index c8c1bc5..1c7921c 100644 --- a/frontend/src/views/RecipeManager.vue +++ b/frontend/src/views/RecipeManager.vue @@ -2307,6 +2307,9 @@ watch(() => recipeStore.recipes, () => { .form-select { flex: 1; + width: 100%; + min-width: 0; + box-sizing: border-box; padding: 8px 10px; border: 1.5px solid #d4cfc7; border-radius: 8px; @@ -2328,6 +2331,7 @@ watch(() => recipeStore.recipes, () => { .oil-search-wrap { flex: 1; + width: 100%; position: relative; } -- 2.49.1 From 8cc06d4e75e445a47b5289a633c8e96be2c0c467 Mon Sep 17 00:00:00 2001 From: Hera Zhao Date: Sat, 18 Apr 2026 18:20:27 +0000 Subject: [PATCH 3/7] =?UTF-8?q?test(e2e):=20=E6=89=8B=E6=9C=BA=E7=BC=96?= =?UTF-8?q?=E8=BE=91=E9=85=8D=E6=96=B9=20overlay=20=E5=AE=BD=E5=BA=A6=20+?= =?UTF-8?q?=20swipe=20=E5=B1=8F=E8=94=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增 responsive.cy.js 里的 Mobile edit recipe overlay 块,覆盖: - overlay-panel 无横向溢出 - 成分/用量/单价/小计 4 列 header 都在 panel 内 - 精油搜索输入不会撑出 panel 右边 - editor 打开时模拟横向 swipe,当前 tab 不切换 --- frontend/cypress/e2e/responsive.cy.js | 69 +++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/frontend/cypress/e2e/responsive.cy.js b/frontend/cypress/e2e/responsive.cy.js index 8cc5689..04efa25 100644 --- a/frontend/cypress/e2e/responsive.cy.js +++ b/frontend/cypress/e2e/responsive.cy.js @@ -39,6 +39,75 @@ describe('Responsive Design', () => { }) }) + describe('Mobile edit recipe overlay (375x667)', () => { + let adminToken + + before(() => { + cy.getAdminToken().then(token => { adminToken = token }) + }) + + beforeEach(() => { + cy.viewport(375, 667) + cy.visit('/manage', { + onBeforeLoad(win) { + win.localStorage.setItem('oil_auth_token', adminToken) + } + }) + cy.get('.recipe-manager', { timeout: 10000 }).should('exist') + cy.contains('公共配方库', { timeout: 10000 }).should('be.visible').click() + cy.get('.recipe-row', { timeout: 10000 }).should('have.length.gte', 1) + cy.get('.recipe-row .row-info').first().click() + cy.get('.overlay-panel', { timeout: 5000 }).should('be.visible') + }) + + it('editor table fits within panel without horizontal overflow', () => { + cy.get('.overlay-panel').then($panel => { + const panel = $panel[0] + expect(panel.scrollWidth, 'panel has no horizontal overflow') + .to.be.lte(panel.clientWidth + 1) + }) + cy.get('.overlay-panel .editor-table').then($table => { + const tableRect = $table[0].getBoundingClientRect() + const panelRect = Cypress.$('.overlay-panel')[0].getBoundingClientRect() + expect(tableRect.right, 'table right edge').to.be.lte(panelRect.right + 1) + expect(tableRect.left, 'table left edge').to.be.gte(panelRect.left - 1) + }) + }) + + it('all 4 data column headers (成分/用量/单价/小计) are visible in panel', () => { + const headers = ['成分', '用量', '单价', '小计'] + headers.forEach(label => { + cy.get('.overlay-panel .editor-table thead th').contains(label).then($th => { + const thRect = $th[0].getBoundingClientRect() + const panelRect = Cypress.$('.overlay-panel')[0].getBoundingClientRect() + expect(thRect.right, `${label} header right`).to.be.lte(panelRect.right + 1) + expect(thRect.left, `${label} header left`).to.be.gte(panelRect.left - 1) + }) + }) + }) + + it('oil search input does not push the row past panel edge', () => { + cy.get('.overlay-panel .form-select').first().then($input => { + const inputRect = $input[0].getBoundingClientRect() + const panelRect = Cypress.$('.overlay-panel')[0].getBoundingClientRect() + expect(inputRect.right, 'oil input right').to.be.lte(panelRect.right + 1) + }) + }) + + it('horizontal swipe does not switch tabs while editor overlay is open', () => { + cy.get('.nav-tab.active').invoke('text').then(activeBefore => { + // Overlay covers .main — touch events bubble from .overlay up to .main's handler + cy.get('.overlay') + .trigger('touchstart', { touches: [{ clientX: 320, clientY: 400 }], force: true }) + .trigger('touchmove', { touches: [{ clientX: 60, clientY: 400 }], force: true }) + .trigger('touchend', { force: true }) + cy.wait(200) + cy.get('.overlay-panel').should('be.visible') + cy.get('.nav-tab.active').invoke('text').should('eq', activeBefore) + }) + }) + }) + describe('Tablet viewport (768x1024)', () => { beforeEach(() => { cy.viewport(768, 1024) -- 2.49.1 From 9b495794c0a362d8e5553682e7889b34099a96e3 Mon Sep 17 00:00:00 2001 From: Hera Zhao Date: Sat, 18 Apr 2026 18:29:05 +0000 Subject: [PATCH 4/7] ci: retrigger after batch 2 hang on recipe-detail -- 2.49.1 From 1df4cf7f027b9afd66daeaec507b250dbac8e0fb Mon Sep 17 00:00:00 2001 From: Hera Zhao Date: Sat, 18 Apr 2026 18:37:25 +0000 Subject: [PATCH 5/7] ci: retrigger (batch 2 e2e flaky) -- 2.49.1 From b983d7dc867d7b4a3f2f410b2a14a23adf327578 Mon Sep 17 00:00:00 2001 From: Hera Zhao Date: Sat, 18 Apr 2026 18:50:28 +0000 Subject: [PATCH 6/7] ci: retrigger -- 2.49.1 From 8739674c42cddefa8feba0bcc1e5f7ae9180ed09 Mon Sep 17 00:00:00 2001 From: Hera Zhao Date: Sat, 18 Apr 2026 19:08:27 +0000 Subject: [PATCH 7/7] =?UTF-8?q?ci:=20=E6=8B=86=20e2e=20batch=20=E6=9C=80?= =?UTF-8?q?=E5=A4=9A=206=20=E4=B8=AA=20spec=EF=BC=8C=E9=81=BF=E5=85=8D=20E?= =?UTF-8?q?lectron=20=E6=8C=82=E6=AD=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 之前 batch 2 跑 10 个 spec 经常挂在第 5 个 spec 前后:runner log 显示 Cypress 完全静默 4+ 分钟,到 300s 外层 timeout 被 kill。本地跑同样批 次 60s 就结束,判断是 runner 资源紧张下 Electron 进程内存吃满、主 线程卡死;单次 run 的 spec 数越多越容易触发。 拆成 7 个小 batch(每批 ≤6 spec),单批结束回收 Cypress 进程+浏览 器,避免跨 spec 累积内存压力。同时把 batch 结果汇总从 3 个变量改 成 FAIL flag 里累加,方便扩展。 --- .gitea/workflows/test.yml | 49 +++++++++++++++++++++++++-------------- 1 file changed, 31 insertions(+), 18 deletions(-) diff --git a/.gitea/workflows/test.yml b/.gitea/workflows/test.yml index 11eb7cf..316fd41 100644 --- a/.gitea/workflows/test.yml +++ b/.gitea/workflows/test.yml @@ -62,35 +62,48 @@ jobs: exit 1 fi - # Run all specs in 3 batches to avoid Electron memory crashes + # Run specs in smaller batches (≤6 per run) to avoid Electron memory + # hangs seen when a single cypress run handles 10+ specs on the CI runner. cd frontend CYPRESS_CFG="video=false,defaultCommandTimeout=5000,pageLoadTimeout=10000,requestTimeout=5000,responseTimeout=10000,baseUrl=http://localhost:$FE_PORT,experimentalMemoryManagement=true,numTestsKeptInMemory=0" + FAIL=0 - echo "=== Batch 1: API & data tests ===" - timeout 300 npx cypress run \ - --spec "cypress/e2e/api-crud.cy.js,cypress/e2e/api-health.cy.js,cypress/e2e/oil-data-integrity.cy.js,cypress/e2e/recipe-cost-parity.cy.js,cypress/e2e/endpoint-parity.cy.js,cypress/e2e/registration-flow.cy.js,cypress/e2e/pr27-features.cy.js,cypress/e2e/kit-export.cy.js" \ - --config "$CYPRESS_CFG" --env "ADMIN_TOKEN=$ADMIN_TOKEN" - B1=$? + run_batch() { + local name="$1"; shift + echo "=== $name ===" + timeout 300 npx cypress run \ + --spec "$1" \ + --config "$CYPRESS_CFG" --env "ADMIN_TOKEN=$ADMIN_TOKEN" + local rc=$? + if [ $rc -ne 0 ]; then + echo "!!! $name failed (rc=$rc)" + FAIL=1 + fi + } - echo "=== Batch 2: UI flow tests ===" - timeout 300 npx cypress run \ - --spec "cypress/e2e/auth-flow.cy.js,cypress/e2e/admin-flow.cy.js,cypress/e2e/navigation.cy.js,cypress/e2e/recipe-detail.cy.js,cypress/e2e/recipe-search.cy.js,cypress/e2e/manage-recipes.cy.js,cypress/e2e/diary-flow.cy.js,cypress/e2e/favorites.cy.js,cypress/e2e/inventory-flow.cy.js,cypress/e2e/demo-walkthrough.cy.js" \ - --config "$CYPRESS_CFG" --env "ADMIN_TOKEN=$ADMIN_TOKEN" - B2=$? + run_batch "Batch 1a: API parity" \ + "cypress/e2e/api-crud.cy.js,cypress/e2e/api-health.cy.js,cypress/e2e/oil-data-integrity.cy.js,cypress/e2e/recipe-cost-parity.cy.js,cypress/e2e/endpoint-parity.cy.js" + run_batch "Batch 1b: features" \ + "cypress/e2e/registration-flow.cy.js,cypress/e2e/pr27-features.cy.js,cypress/e2e/kit-export.cy.js" - echo "=== Batch 3: Remaining tests ===" - timeout 300 npx cypress run \ - --spec "cypress/e2e/app-load.cy.js,cypress/e2e/account-settings.cy.js,cypress/e2e/audit-log-advanced.cy.js,cypress/e2e/batch-operations.cy.js,cypress/e2e/bug-tracker-flow.cy.js,cypress/e2e/category-modules.cy.js,cypress/e2e/notification-flow.cy.js,cypress/e2e/oil-reference.cy.js,cypress/e2e/oil-smart-paste.cy.js,cypress/e2e/performance.cy.js,cypress/e2e/price-display.cy.js,cypress/e2e/projects-flow.cy.js,cypress/e2e/responsive.cy.js,cypress/e2e/search-advanced.cy.js,cypress/e2e/user-management-flow.cy.js,cypress/e2e/visual-check.cy.js" \ - --config "$CYPRESS_CFG" --env "ADMIN_TOKEN=$ADMIN_TOKEN" - B3=$? + run_batch "Batch 2a: auth & nav" \ + "cypress/e2e/auth-flow.cy.js,cypress/e2e/admin-flow.cy.js,cypress/e2e/navigation.cy.js,cypress/e2e/recipe-detail.cy.js,cypress/e2e/recipe-search.cy.js" + run_batch "Batch 2b: user flows" \ + "cypress/e2e/manage-recipes.cy.js,cypress/e2e/diary-flow.cy.js,cypress/e2e/favorites.cy.js,cypress/e2e/inventory-flow.cy.js,cypress/e2e/demo-walkthrough.cy.js" + + run_batch "Batch 3a: misc flows" \ + "cypress/e2e/app-load.cy.js,cypress/e2e/account-settings.cy.js,cypress/e2e/audit-log-advanced.cy.js,cypress/e2e/batch-operations.cy.js,cypress/e2e/bug-tracker-flow.cy.js,cypress/e2e/category-modules.cy.js" + run_batch "Batch 3b: pages & perf" \ + "cypress/e2e/notification-flow.cy.js,cypress/e2e/oil-reference.cy.js,cypress/e2e/oil-smart-paste.cy.js,cypress/e2e/performance.cy.js,cypress/e2e/price-display.cy.js,cypress/e2e/projects-flow.cy.js" + run_batch "Batch 3c: responsive & admin" \ + "cypress/e2e/responsive.cy.js,cypress/e2e/search-advanced.cy.js,cypress/e2e/user-management-flow.cy.js,cypress/e2e/visual-check.cy.js" # Cleanup kill $BE_PID $FE_PID 2>/dev/null pkill -f "Cypress" 2>/dev/null || true rm -f "$DB_FILE" - echo "Results: Batch1=$B1 Batch2=$B2 Batch3=$B3" - if [ $B1 -ne 0 ] || [ $B2 -ne 0 ] || [ $B3 -ne 0 ]; then + if [ $FAIL -ne 0 ]; then exit 1 fi -- 2.49.1