feat: Verdaccio 缓存可视化管理面板(前端 + 后端 + Docker)
- 前端 Vue3+Vite+Element Plus 按需引入,双主题(bright/dark)CSS 变量 - 三端响应式(桌面/平板/手机),登录/概览/检索/阈值/忽略/动态 6 页面 - 后端 Express + better-sqlite3,目录扫描 + demo 模式,清理原子写入 - Dockerfile 多阶段构建 + docker-compose(storage 只读挂载)
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import AppTopbar from './components/AppTopbar.vue'
|
||||
import MobileTabbar from './components/MobileTabbar.vue'
|
||||
import SettingDrawer from './components/SettingDrawer.vue'
|
||||
import { useStore } from './store'
|
||||
|
||||
const route = useRoute()
|
||||
const { refreshAll } = useStore()
|
||||
const settingsOpen = ref(false)
|
||||
|
||||
const isLogin = () => route.path === '/login'
|
||||
const isMobile = () => window.matchMedia('(max-width: 767px)').matches
|
||||
|
||||
onMounted(() => {
|
||||
if (!isLogin()) refreshAll()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="ambient"></div>
|
||||
|
||||
<div v-if="!isLogin()">
|
||||
<!-- 桌面/平板顶栏 -->
|
||||
<AppTopbar class="desktop-topbar" @open-settings="settingsOpen = true" />
|
||||
<!-- 移动端顶栏:☰ + 品牌 -->
|
||||
<header class="m-topbar">
|
||||
<div class="m-brand">
|
||||
<div class="m-logo">📦</div>
|
||||
<div><b>Verdaccio</b> <span>Manager</span></div>
|
||||
</div>
|
||||
<div class="m-menu" @click="settingsOpen = true">☰</div>
|
||||
</header>
|
||||
</div>
|
||||
|
||||
<router-view v-slot="{ Component }">
|
||||
<transition name="fade-slide" mode="out-in">
|
||||
<component :is="Component" />
|
||||
</transition>
|
||||
</router-view>
|
||||
|
||||
<MobileTabbar v-if="!isLogin() && isMobile()" />
|
||||
<SettingDrawer v-model="settingsOpen" />
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.desktop-topbar { display: block; }
|
||||
.m-topbar { display: none; }
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.desktop-topbar { display: none; }
|
||||
.m-topbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 14px 16px 10px;
|
||||
background: var(--card);
|
||||
border-bottom: 1px solid var(--line);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
}
|
||||
.m-brand { display: flex; align-items: center; gap: 8px; font-size: 15px; font-weight: 700; }
|
||||
.m-logo {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 10px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
font-size: 15px;
|
||||
background: var(--grad-main);
|
||||
}
|
||||
.m-brand b { color: var(--orange); }
|
||||
.m-brand span { color: var(--ink-faint); font-weight: 600; font-size: 12px; }
|
||||
.m-menu {
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
border-radius: 11px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
background: var(--card-2);
|
||||
border: 1px solid var(--line);
|
||||
font-size: 16px;
|
||||
color: var(--ink-dim);
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user