frontend: move the second-level menu to the left of the content

The rail was on the right. It now leads the row instead, so the section menu
reads as a conventional left-hand navigation column.

Which side it lands on was never a stylesheet decision: `.app-body` is a flex
row and the aside is simply its first child now, with `.app-main` after it.
The only style change that had to follow is the rail's separator, which moves
from its left edge to its right, plus the collapse tooltip's placement.

The global shell contract in base.css now says this explicitly, so the next
person to move the rail knows the side comes from DOM order rather than from a
rule in that file.
This commit is contained in:
2026-09-18 16:35:24 +08:00
parent 9ded3bd574
commit 4b826567b8
4 changed files with 28 additions and 18 deletions
+7 -5
View File
@@ -3,9 +3,9 @@
* Application shell.
*
* Three bands, in this order: a header holding the mark, the two top-level
* sections, and the mark again at the far right; then the content area; then
* the second-level menu on the right, present only on routes that declare a
* `section` in their meta.
* sections, and the mark again at the far right; then the row below it, which
* is the second-level menu on the left followed by the content area. The menu
* is present only on routes that declare a `section` in their meta.
*/
import { computed } from 'vue'
import { RouterView, RouterLink, useRoute } from 'vue-router'
@@ -65,11 +65,13 @@ const section = computed(() => (route.meta.section as string | undefined) ?? '')
</el-header>
<el-container class="app-body">
<!-- The second-level menu leads the row. `.app-body` is a flex row, so
being first in the DOM is what puts it on the left of the content. -->
<SectionAside v-if="section" :section="section" />
<el-main class="app-main">
<RouterView />
</el-main>
<SectionAside v-if="section" :section="section" />
</el-container>
</el-container>
</template>
+5 -4
View File
@@ -1,6 +1,6 @@
<script setup lang="ts">
/**
* The second-level menu, pinned to the right of the content area.
* The second-level menu, pinned to the left of the content area.
*
* It is driven entirely by `route.meta.section`, so the menu a URL belongs to
* is a property of the route table rather than of component state — deep
@@ -58,7 +58,7 @@ const collapsed = computed(() => appStore.asideCollapsed)
<span v-if="!collapsed" class="aside-title">{{ menu.title }}</span>
<el-tooltip
:content="collapsed ? '展开菜单' : '收起菜单'"
placement="left"
placement="right"
:show-after="200"
>
<el-button
@@ -94,8 +94,9 @@ const collapsed = computed(() => appStore.asideCollapsed)
.app-aside {
display: flex;
flex-direction: column;
/* The wrapper owns the border so it stays put while the menu scrolls. */
border-left: 1px solid var(--el-border-color);
/* The wrapper owns the border so it stays put while the menu scrolls. The
rail sits at the left edge, so the separator goes on its right. */
border-right: 1px solid var(--el-border-color);
background-color: var(--el-bg-color);
overflow: hidden;
}
+6 -3
View File
@@ -86,9 +86,12 @@ samp {
The shape is:
.app-shell column: header over .app-body
.app-body row: .app-main over .app-aside (if present)
.app-body row: .app-aside (if present) then .app-main
.app-main the single scroll container
.app-aside fixed-width rail, scrolls independently
Which side the rail lands on is decided by its DOM order inside the row,
not by a rule here: first child means left.
--------------------------------------------------------------------------- */
.app-shell {
@@ -156,8 +159,8 @@ samp {
background-color: var(--el-text-color-placeholder);
}
/* The second-level menu on the right. Its own width is set by the component,
so only the shrink behaviour belongs here. */
/* The second-level menu, to the left of the content. Its own width is set by
the component, so only the shrink behaviour belongs here. */
.app-aside {
flex: 0 0 auto;
min-height: 0;