109 lines
3.7 KiB
JavaScript
109 lines
3.7 KiB
JavaScript
/* 应用外壳:左侧菜单 + 顶栏 + 内容区,最后加载并挂载
|
||||
|
|
(ref / computed / createApp 等已在 util.js 里解构到全局) */
|
|||
|
|
|
|||
|
|
const App = {
|
|||
|
|
template: `
|
|||
|
|
<div class="layout">
|
|||
|
|
<aside class="sidebar">
|
|||
|
|
<div class="brand">
|
|||
|
|
<div class="brand-mark">沃</div>
|
|||
|
|
<div class="brand-text">
|
|||
|
|
<div class="brand-title">沃林学生管理系统</div>
|
|||
|
|
<div class="brand-sub">Student Manager</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<nav class="nav">
|
|||
|
|
<template v-for="g in groups" :key="g.name">
|
|||
|
|
<div class="nav-group">{{ g.name }}</div>
|
|||
|
|
<div v-for="p in g.items" :key="p.key"
|
|||
|
|
class="nav-item" :class="{ active: p.key === currentKey }"
|
|||
|
|
@click="go(p.key)">
|
|||
|
|
<span v-html="icon(p.icon)"></span>
|
|||
|
|
<span>{{ p.title }}</span>
|
|||
|
|
</div>
|
|||
|
|
</template>
|
|||
|
|
</nav>
|
|||
|
|
|
|||
|
|
<div class="sidebar-foot">
|
|||
|
|
接口文档:<a :href="docsUrl" target="_blank" style="color:#4F46E5">/docs</a><br>
|
|||
|
|
后端 127.0.0.1:8001
|
|||
|
|
</div>
|
|||
|
|
</aside>
|
|||
|
|
|
|||
|
|
<div class="main">
|
|||
|
|
<header class="topbar">
|
|||
|
|
<div>
|
|||
|
|
<div class="topbar-title">{{ current.title }}</div>
|
|||
|
|
<div class="topbar-sub">{{ current.sub }}</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="topbar-actions">
|
|||
|
|
<el-button @click="reload">
|
|||
|
|
<span style="margin-right:4px">↻</span>刷新本页
|
|||
|
|
</el-button>
|
|||
|
|
</div>
|
|||
|
|
</header>
|
|||
|
|
|
|||
|
|
<section class="content">
|
|||
|
|
<component :is="current.component" :key="currentKey + '#' + reloadTick" />
|
|||
|
|
</section>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
`,
|
|||
|
|
|
|||
|
|
setup() {
|
|||
|
|
const pages = window.PAGES;
|
|||
|
|
|
|||
|
|
// 分组:按注册顺序把页面归到各自的分组里(总览 / 数据管理 / 统计分析)
|
|||
|
|
const groups = [];
|
|||
|
|
pages.forEach(p => {
|
|||
|
|
const name = p.group || '其他';
|
|||
|
|
let g = groups.find(x => x.name === name);
|
|||
|
|
if (!g) { g = { name, items: [] }; groups.push(g); }
|
|||
|
|
g.items.push(p);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
const fallback = (pages[0] && pages[0].key) || '';
|
|||
|
|
const currentKey = ref(location.hash.replace(/^#\/?/, '') || fallback);
|
|||
|
|
|
|||
|
|
// 刷新本页:改 key 让组件重新挂载,页面自己会重新拉数据
|
|||
|
|
const reloadTick = ref(0);
|
|||
|
|
const reload = () => { reloadTick.value++; };
|
|||
|
|
|
|||
|
|
const current = computed(() =>
|
|||
|
|
pages.find(p => p.key === currentKey.value) || pages[0] || { title: '', sub: '', component: null });
|
|||
|
|
|
|||
|
|
const go = (key) => {
|
|||
|
|
if (key === currentKey.value) { reload(); return; }
|
|||
|
|
location.hash = '#/' + key;
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 地址栏直接改 hash / 浏览器前进后退也能切页
|
|||
|
|
const onHash = () => {
|
|||
|
|
const k = location.hash.replace(/^#\/?/, '');
|
|||
|
|
if (k && k !== currentKey.value) currentKey.value = k;
|
|||
|
|
};
|
|||
|
|
onMounted(() => window.addEventListener('hashchange', onHash));
|
|||
|
|
onUnmounted(() => window.removeEventListener('hashchange', onHash));
|
|||
|
|
|
|||
|
|
// 把地址栏补齐,刷新页面能停在当前页
|
|||
|
|
if (!location.hash) location.hash = '#/' + currentKey.value;
|
|||
|
|
|
|||
|
|
// 前后端不同源了,/docs 这种站内相对路径会指到前端服务器上,必须用后端地址
|
|||
|
|
const docsUrl = API_BASE + '/docs';
|
|||
|
|
|
|||
|
|
return { groups, currentKey, current, go, reload, reloadTick, icon, docsUrl };
|
|||
|
|
},
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const app = createApp(App);
|
|||
|
|
|
|||
|
|
// 模板里写错组件名之类的错误,直接在界面上弹出来,比只看控制台快
|
|||
|
|
app.config.errorHandler = (err) => {
|
|||
|
|
console.error(err);
|
|||
|
|
toastErr(err && err.message ? err.message : '页面出错了,请按 F12 看控制台');
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
app.use(ElementPlus, { locale: ElementPlusLocaleZhCn });
|
|||
|
|
app.mount('#app');
|