feat(GOV-009): 建立 Vue 前端工程基线

This commit is contained in:
2026-08-13 16:42:25 +08:00
parent 258a2d21f6
commit 28d54186ad
27 changed files with 3909 additions and 14 deletions
+25
View File
@@ -0,0 +1,25 @@
import { createPinia } from 'pinia';
import { mount } from '@vue/test-utils';
import { createMemoryHistory } from 'vue-router';
import { describe, expect, it } from 'vitest';
import App from './App.vue';
import { createAppRouter } from './router';
describe('应用外壳', () => {
it('提供可访问的应用标题和主内容区域', async () => {
const router = createAppRouter(createMemoryHistory());
await router.push('/');
await router.isReady();
const wrapper = mount(App, {
global: {
plugins: [createPinia(), router],
},
});
expect(wrapper.get('header').attributes('aria-label')).toBe('应用导航');
expect(wrapper.get('main').attributes('id')).toBe('main-content');
expect(wrapper.text()).toContain('A 股市场分析工具');
});
});
+22
View File
@@ -0,0 +1,22 @@
<script setup lang="ts">
import { NConfigProvider, NGlobalStyle, NMessageProvider } from 'naive-ui';
const appTitle = import.meta.env.VITE_APP_TITLE || 'A 股市场分析工具';
</script>
<template>
<NConfigProvider>
<NGlobalStyle />
<NMessageProvider>
<div class="app-shell">
<header class="app-header" aria-label="应用导航">
<span class="app-mark" aria-hidden="true">A</span>
<strong>{{ appTitle }}</strong>
</header>
<main id="main-content" class="app-main">
<RouterView />
</main>
</div>
</NMessageProvider>
</NConfigProvider>
</template>
+9
View File
@@ -0,0 +1,9 @@
import { createPinia } from 'pinia';
import type { App } from 'vue';
import { createAppRouter } from './router';
export function installAppProviders(app: App) {
app.use(createPinia());
app.use(createAppRouter());
}
+20
View File
@@ -0,0 +1,20 @@
import {
createRouter,
createWebHistory,
type RouterHistory,
type RouteRecordRaw,
} from 'vue-router';
const routes: RouteRecordRaw[] = [
{
path: '/',
name: 'home',
component: {
template: '<section aria-label="应用就绪"><p>工程基线已就绪</p></section>',
},
},
];
export function createAppRouter(history: RouterHistory = createWebHistory()) {
return createRouter({ history, routes });
}
+11
View File
@@ -0,0 +1,11 @@
/// <reference types="vite/client" />
interface ImportMetaEnv {
readonly VITE_APP_ENV?: string;
readonly VITE_API_BASE_URL?: string;
readonly VITE_APP_TITLE?: string;
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}
+9
View File
@@ -0,0 +1,9 @@
import { createApp } from 'vue';
import App from './app/App.vue';
import { installAppProviders } from './app/providers';
import './styles/index.scss';
const app = createApp(App);
installAppProviders(app);
app.mount('#app');
+78
View File
@@ -0,0 +1,78 @@
:root {
color: #171a1f;
background: #f4f6f8;
font-family:
Inter,
'PingFang SC',
'Microsoft YaHei',
system-ui,
-apple-system,
BlinkMacSystemFont,
sans-serif;
font-synthesis: none;
}
* {
box-sizing: border-box;
}
html,
body,
#app {
min-width: 320px;
min-height: 100%;
margin: 0;
}
button,
a,
input,
select,
textarea {
letter-spacing: 0;
}
:focus-visible {
outline: 2px solid #1677ff;
outline-offset: 2px;
}
.app-shell {
min-height: 100vh;
}
.app-header {
display: flex;
align-items: center;
gap: 10px;
min-height: 52px;
padding: 0 20px;
color: #fff;
background: #1d2229;
border-bottom: 1px solid #343b44;
}
.app-mark {
display: inline-grid;
width: 28px;
height: 28px;
place-items: center;
color: #1d2229;
font-weight: 700;
background: #50c878;
border-radius: 4px;
}
.app-main {
width: min(100%, 1600px);
margin: 0 auto;
padding: 20px;
}
@media (max-width: 600px) {
.app-header,
.app-main {
padding-right: 14px;
padding-left: 14px;
}
}