1
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
# Hide Layout Chrome for `common` Role Users
|
||||
|
||||
Date: 2026-08-04
|
||||
Status: Approved (design phase)
|
||||
|
||||
## Goal
|
||||
|
||||
When the logged-in user's role list contains `"common"`, hide all layout chrome — left sidebar, top navbar, tags view, and the floating Settings button — leaving only `<app-main />` filling the viewport. Non-`common` users see the existing full layout unchanged.
|
||||
|
||||
## Trigger Condition
|
||||
|
||||
`useUserStore().roles` (array) includes the string `"common"`.
|
||||
|
||||
The check is `roles.includes('common')`. A user with `['admin', 'common']` is treated as `common` because the array includes it; only roles where `common` is present trigger the new behavior.
|
||||
|
||||
## Scope
|
||||
|
||||
- Applies to **all routes**, unconditionally.
|
||||
- No per-route opt-out, no per-route opt-in.
|
||||
- Only the layout chrome is affected. `<app-main />` keeps rendering any page contents exactly as before.
|
||||
|
||||
## Files Touched
|
||||
|
||||
Only one file: `src/layout/index.vue`.
|
||||
|
||||
No stores, no router, no permission utilities, no new files.
|
||||
|
||||
## Design
|
||||
|
||||
### 1. Add a `isCommonUser` computed in `src/layout/index.vue`
|
||||
|
||||
```js
|
||||
import useUserStore from '@/store/modules/user'
|
||||
|
||||
const isCommonUser = computed(() =>
|
||||
useUserStore().roles?.includes('common') ?? false
|
||||
)
|
||||
```
|
||||
|
||||
Defensive `?.includes` so an undefined `roles` collapses to `false` rather than throwing.
|
||||
|
||||
### 2. Gate the four chrome elements with `v-if`
|
||||
|
||||
```vue
|
||||
<sidebar v-if="!sidebar.hide && !isCommonUser" class="sidebar-container" />
|
||||
<div :class="{ hasTagsView: needTagsView, sidebarHide: sidebar.hide, fullscreen: isCommonUser }" class="main-container">
|
||||
<div :class="{ 'fixed-header': fixedHeader }">
|
||||
<navbar v-if="!isCommonUser" @setLayout="setLayout" />
|
||||
<tags-view v-if="needTagsView && !isCommonUser" />
|
||||
</div>
|
||||
<app-main />
|
||||
<settings v-if="!isCommonUser" ref="settingRef" />
|
||||
</div>
|
||||
```
|
||||
|
||||
Notes:
|
||||
- `Settings` is hidden even though the original requirement listed only sidebar / navbar / tagsview. The user explicitly confirmed in brainstorming that Settings should also be hidden for cleanliness.
|
||||
- The outer `<div :class="classObj">` (with `hideSidebar` / `openSidebar` / `mobile`) is left untouched; it still affects the `<sidebar>` slot even when sidebar is hidden, which is harmless.
|
||||
|
||||
### 3. Fullscreen styling for `<app-main />`
|
||||
|
||||
Add a scoped SCSS rule:
|
||||
|
||||
```scss
|
||||
.main-container.fullscreen {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
```
|
||||
|
||||
This overrides any inherited width / margin-left from the sidebar-open state so `<app-main />` truly fills the viewport.
|
||||
|
||||
`<app-main />` internals (page padding, scoped styles) are **not** touched. If a future page wants a chrome-less padding experience, it owns that decision.
|
||||
|
||||
## Out of Scope
|
||||
|
||||
- Not changing user store APIs.
|
||||
- Not adding a getter to `useUserStore` (single use site — YAGNI).
|
||||
- Not introducing a second layout component or per-route layout switching.
|
||||
- Not modifying router meta or permission middleware.
|
||||
- Not changing `<app-main />` internals or any page component.
|
||||
|
||||
## Error / Edge Cases
|
||||
|
||||
- `roles` is `undefined` or `null` → `?.includes('common')` returns `undefined`, `?? false` collapses to `false`. Safe.
|
||||
- `roles` is a non-array → `Array.prototype.includes` would throw. The user store initializes `roles: []`, so this is only a risk if a future change sets it to a non-array. No mitigation needed; treat as a contract violation.
|
||||
- User role changes mid-session (does not happen in this app) → computed reacts automatically, layout re-renders. Reactive by design.
|
||||
- Page refresh → `useUserStore` is repopulated via `getInfo()` during router guard; the computed is reactive and picks up the new value on next render. No flicker handling needed because the layout renders after auth is resolved.
|
||||
|
||||
## Testing
|
||||
|
||||
The repo has no frontend unit / e2e tests configured (`package.json` scripts only cover `dev` / `build`). Verification is manual:
|
||||
|
||||
1. **Admin role**: log in as a user without `common` in roles. All four chrome areas render as before.
|
||||
2. **Common role**: log in as a user with `common` in roles. Only `<app-main />` is visible, filling the viewport edge-to-edge.
|
||||
3. **Negative variant**: log in as a user with `['common', 'other-role']` — chrome still hidden (array `.includes` semantics).
|
||||
4. **Page interaction**: inside `<app-main />`, the page's own links / buttons / forms work normally.
|
||||
|
||||
## Future Considerations (not implemented)
|
||||
|
||||
- If a second role-driven layout variant appears (e.g. compact admin view), lift `isCommonUser` into a `useUserStore` getter and consider per-route layout meta.
|
||||
- If pages ever need to opt out of fullscreen (e.g. a "preview" page that wants the chrome visible for a `common` user), introduce per-route meta and gate on it. Not a current requirement.
|
||||
Reference in New Issue
Block a user