第一步

This commit is contained in:
amb
2026-09-01 11:53:59 +08:00
commit 47bf6cc5ca
66 changed files with 6501 additions and 0 deletions
+18
View File
@@ -0,0 +1,18 @@
# AI Knowledge Link — Frontend Dockerfile (multi-stage)
# Stage 1: build
FROM node:20-alpine AS build
WORKDIR /app
COPY package.json package-lock.json* ./
RUN npm ci
COPY . .
RUN npm run build
# Stage 2: serve with nginx
FROM nginx:1.27-alpine AS serve
COPY --from=build /app/dist /usr/share/nginx/html
# 自定义 nginx 配置由外层 docker-compose 挂载,这里只 serve 静态产物
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
+12
View File
@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>AI Knowledge Link</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
+3815
View File
File diff suppressed because it is too large Load Diff
+33
View File
@@ -0,0 +1,33 @@
{
"name": "ai-knowledge-link-frontend",
"version": "0.1.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "vue-tsc -b && vite build",
"preview": "vite preview",
"lint": "eslint . --ext .vue,.ts,.tsx --fix",
"format": "prettier --write src/"
},
"dependencies": {
"vue": "^3.5",
"vue-router": "^4.4",
"pinia": "^2.2",
"element-plus": "^2.9",
"axios": "^1.7",
"@element-plus/icons-vue": "^2.3"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.2",
"vite": "^6.1",
"typescript": "^5.7",
"vue-tsc": "^2.2",
"@types/node": "^22.0",
"eslint": "^9.0",
"eslint-plugin-vue": "^9.28",
"@typescript-eslint/eslint-plugin": "^8.0",
"@typescript-eslint/parser": "^8.0",
"prettier": "^3.4"
}
}
+7
View File
@@ -0,0 +1,7 @@
<script setup lang="ts">
import { RouterView } from 'vue-router'
</script>
<template>
<RouterView />
</template>
+26
View File
@@ -0,0 +1,26 @@
import { RouterView } from 'vue-router';
debugger; /* PartiallyEnd: #3632/scriptSetup.vue */
const __VLS_ctx = {};
let __VLS_components;
let __VLS_directives;
const __VLS_0 = {}.RouterView;
/** @type {[typeof __VLS_components.RouterView, ]} */ ;
// @ts-ignore
const __VLS_1 = __VLS_asFunctionalComponent(__VLS_0, new __VLS_0({}));
const __VLS_2 = __VLS_1({}, ...__VLS_functionalComponentArgsRest(__VLS_1));
var __VLS_4 = {};
var __VLS_3;
var __VLS_dollars;
const __VLS_self = (await import('vue')).defineComponent({
setup() {
return {
RouterView: RouterView,
};
},
});
export default (await import('vue')).defineComponent({
setup() {
return {};
},
});
; /* PartiallyEnd: #4569/main.vue */
+35
View File
@@ -0,0 +1,35 @@
import axios from 'axios';
import { ElMessage } from 'element-plus';
const apiClient = axios.create({
baseURL: '/api',
timeout: 30000,
withCredentials: true,
headers: {
'X-Requested-With': 'XMLHttpRequest',
},
});
// 响应拦截:统一错误处理
apiClient.interceptors.response.use((response) => response, (error) => {
if (error.response) {
const { status, data } = error.response;
// 未登录 → 跳转登录页
if (status === 401) {
// 避免在登录页循环跳转
if (window.location.pathname !== '/login') {
window.location.href = '/login';
}
return Promise.reject(error);
}
// 业务错误:显示服务端中文文案
const message = data?.message || '请求失败';
ElMessage.error(message);
}
else if (error.request) {
ElMessage.error('网络错误,请检查连接。');
}
else {
ElMessage.error('请求配置错误。');
}
return Promise.reject(error);
});
export default apiClient;
+41
View File
@@ -0,0 +1,41 @@
import axios from 'axios'
import { ElMessage } from 'element-plus'
const apiClient = axios.create({
baseURL: '/api',
timeout: 30000,
withCredentials: true,
headers: {
'X-Requested-With': 'XMLHttpRequest',
},
})
// 响应拦截:统一错误处理
apiClient.interceptors.response.use(
(response) => response,
(error) => {
if (error.response) {
const { status, data } = error.response
// 未登录 → 跳转登录页
if (status === 401) {
// 避免在登录页循环跳转
if (window.location.pathname !== '/login') {
window.location.href = '/login'
}
return Promise.reject(error)
}
// 业务错误:显示服务端中文文案
const message = data?.message || '请求失败'
ElMessage.error(message)
} else if (error.request) {
ElMessage.error('网络错误,请检查连接。')
} else {
ElMessage.error('请求配置错误。')
}
return Promise.reject(error)
},
)
export default apiClient
+7
View File
@@ -0,0 +1,7 @@
/// <reference types="vite/client" />
declare module '*.vue' {
import type { DefineComponent } from 'vue'
const component: DefineComponent<object, object, unknown>
export default component
}
+12
View File
@@ -0,0 +1,12 @@
import { createApp } from 'vue';
import { createPinia } from 'pinia';
import ElementPlus from 'element-plus';
import zhCn from 'element-plus/es/locale/lang/zh-cn';
import 'element-plus/dist/index.css';
import App from './App.vue';
import router from './router';
const app = createApp(App);
app.use(createPinia());
app.use(router);
app.use(ElementPlus, { locale: zhCn });
app.mount('#app');
+14
View File
@@ -0,0 +1,14 @@
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import ElementPlus from 'element-plus'
import zhCn from 'element-plus/es/locale/lang/zh-cn'
import 'element-plus/dist/index.css'
import App from './App.vue'
import router from './router'
const app = createApp(App)
app.use(createPinia())
app.use(router)
app.use(ElementPlus, { locale: zhCn })
app.mount('#app')
+37
View File
@@ -0,0 +1,37 @@
import { createRouter, createWebHistory } from 'vue-router';
const router = createRouter({
history: createWebHistory(),
routes: [
{
path: '/login',
name: 'Login',
component: () => import('@/views/Login.vue'),
},
{
path: '/register',
name: 'Register',
component: () => import('@/views/Register.vue'),
},
{
path: '/',
name: 'Dashboard',
component: () => import('@/views/Dashboard.vue'),
},
{
path: '/knowledge-bases',
name: 'KnowledgeBases',
component: () => import('@/views/KnowledgeBases.vue'),
},
{
path: '/knowledge-bases/:id',
name: 'KbDetail',
component: () => import('@/views/KbDetail.vue'),
},
{
path: '/settings',
name: 'Settings',
component: () => import('@/views/Settings.vue'),
},
],
});
export default router;
+39
View File
@@ -0,0 +1,39 @@
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(),
routes: [
{
path: '/login',
name: 'Login',
component: () => import('@/views/Login.vue'),
},
{
path: '/register',
name: 'Register',
component: () => import('@/views/Register.vue'),
},
{
path: '/',
name: 'Dashboard',
component: () => import('@/views/Dashboard.vue'),
},
{
path: '/knowledge-bases',
name: 'KnowledgeBases',
component: () => import('@/views/KnowledgeBases.vue'),
},
{
path: '/knowledge-bases/:id',
name: 'KbDetail',
component: () => import('@/views/KbDetail.vue'),
},
{
path: '/settings',
name: 'Settings',
component: () => import('@/views/Settings.vue'),
},
],
})
export default router
+18
View File
@@ -0,0 +1,18 @@
import { defineStore } from 'pinia';
import { ref } from 'vue';
export const useUserStore = defineStore('user', () => {
const isLoggedIn = ref(false);
const username = ref('');
const userId = ref('');
function setUser(data) {
isLoggedIn.value = true;
userId.value = data.id;
username.value = data.username;
}
function clearUser() {
isLoggedIn.value = false;
userId.value = '';
username.value = '';
}
return { isLoggedIn, username, userId, setUser, clearUser };
});
+22
View File
@@ -0,0 +1,22 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
export const useUserStore = defineStore('user', () => {
const isLoggedIn = ref(false)
const username = ref('')
const userId = ref('')
function setUser(data: { id: string; username: string }) {
isLoggedIn.value = true
userId.value = data.id
username.value = data.username
}
function clearUser() {
isLoggedIn.value = false
userId.value = ''
username.value = ''
}
return { isLoggedIn, username, userId, setUser, clearUser }
})
+10
View File
@@ -0,0 +1,10 @@
<script setup lang="ts">
// Phase 14 完善仪表盘
</script>
<template>
<div class="dashboard-page">
<h1>仪表盘</h1>
<p>DashboardPhase 14 完善</p>
</div>
</template>
+22
View File
@@ -0,0 +1,22 @@
debugger; /* PartiallyEnd: #3632/scriptSetup.vue */
const __VLS_ctx = {};
let __VLS_components;
let __VLS_directives;
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "dashboard-page" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.h1, __VLS_intrinsicElements.h1)({});
__VLS_asFunctionalElement(__VLS_intrinsicElements.p, __VLS_intrinsicElements.p)({});
/** @type {__VLS_StyleScopedClasses['dashboard-page']} */ ;
var __VLS_dollars;
const __VLS_self = (await import('vue')).defineComponent({
setup() {
return {};
},
});
export default (await import('vue')).defineComponent({
setup() {
return {};
},
});
; /* PartiallyEnd: #4569/main.vue */
+10
View File
@@ -0,0 +1,10 @@
<script setup lang="ts">
// Phase 4 实现知识库详情
</script>
<template>
<div>
<h1>知识库详情</h1>
<p>知识库详情Phase 4 实现</p>
</div>
</template>
+19
View File
@@ -0,0 +1,19 @@
debugger; /* PartiallyEnd: #3632/scriptSetup.vue */
const __VLS_ctx = {};
let __VLS_components;
let __VLS_directives;
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({});
__VLS_asFunctionalElement(__VLS_intrinsicElements.h1, __VLS_intrinsicElements.h1)({});
__VLS_asFunctionalElement(__VLS_intrinsicElements.p, __VLS_intrinsicElements.p)({});
var __VLS_dollars;
const __VLS_self = (await import('vue')).defineComponent({
setup() {
return {};
},
});
export default (await import('vue')).defineComponent({
setup() {
return {};
},
});
; /* PartiallyEnd: #4569/main.vue */
+10
View File
@@ -0,0 +1,10 @@
<script setup lang="ts">
// Phase 4 实现知识库列表
</script>
<template>
<div>
<h1>知识库</h1>
<p>知识库列表Phase 4 实现</p>
</div>
</template>
+19
View File
@@ -0,0 +1,19 @@
debugger; /* PartiallyEnd: #3632/scriptSetup.vue */
const __VLS_ctx = {};
let __VLS_components;
let __VLS_directives;
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({});
__VLS_asFunctionalElement(__VLS_intrinsicElements.h1, __VLS_intrinsicElements.h1)({});
__VLS_asFunctionalElement(__VLS_intrinsicElements.p, __VLS_intrinsicElements.p)({});
var __VLS_dollars;
const __VLS_self = (await import('vue')).defineComponent({
setup() {
return {};
},
});
export default (await import('vue')).defineComponent({
setup() {
return {};
},
});
; /* PartiallyEnd: #4569/main.vue */
+27
View File
@@ -0,0 +1,27 @@
<script setup lang="ts">
// Phase 3 实现登录逻辑
</script>
<template>
<div class="login-page">
<el-card class="login-card">
<template #header>
<h2>AI Knowledge Link</h2>
</template>
<p>登录页面Phase 3 实现</p>
</el-card>
</div>
</template>
<style scoped>
.login-page {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
background: #f5f7fa;
}
.login-card {
width: 400px;
}
</style>
+39
View File
@@ -0,0 +1,39 @@
debugger; /* PartiallyEnd: #3632/scriptSetup.vue */
const __VLS_ctx = {};
let __VLS_components;
let __VLS_directives;
// CSS variable injection
// CSS variable injection end
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "login-page" },
});
const __VLS_0 = {}.ElCard;
/** @type {[typeof __VLS_components.ElCard, typeof __VLS_components.elCard, typeof __VLS_components.ElCard, typeof __VLS_components.elCard, ]} */ ;
// @ts-ignore
const __VLS_1 = __VLS_asFunctionalComponent(__VLS_0, new __VLS_0({
...{ class: "login-card" },
}));
const __VLS_2 = __VLS_1({
...{ class: "login-card" },
}, ...__VLS_functionalComponentArgsRest(__VLS_1));
__VLS_3.slots.default;
{
const { header: __VLS_thisSlot } = __VLS_3.slots;
__VLS_asFunctionalElement(__VLS_intrinsicElements.h2, __VLS_intrinsicElements.h2)({});
}
__VLS_asFunctionalElement(__VLS_intrinsicElements.p, __VLS_intrinsicElements.p)({});
var __VLS_3;
/** @type {__VLS_StyleScopedClasses['login-page']} */ ;
/** @type {__VLS_StyleScopedClasses['login-card']} */ ;
var __VLS_dollars;
const __VLS_self = (await import('vue')).defineComponent({
setup() {
return {};
},
});
export default (await import('vue')).defineComponent({
setup() {
return {};
},
});
; /* PartiallyEnd: #4569/main.vue */
+27
View File
@@ -0,0 +1,27 @@
<script setup lang="ts">
// Phase 3 实现注册逻辑
</script>
<template>
<div class="register-page">
<el-card class="register-card">
<template #header>
<h2>注册</h2>
</template>
<p>注册页面Phase 3 实现</p>
</el-card>
</div>
</template>
<style scoped>
.register-page {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
background: #f5f7fa;
}
.register-card {
width: 400px;
}
</style>
+39
View File
@@ -0,0 +1,39 @@
debugger; /* PartiallyEnd: #3632/scriptSetup.vue */
const __VLS_ctx = {};
let __VLS_components;
let __VLS_directives;
// CSS variable injection
// CSS variable injection end
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "register-page" },
});
const __VLS_0 = {}.ElCard;
/** @type {[typeof __VLS_components.ElCard, typeof __VLS_components.elCard, typeof __VLS_components.ElCard, typeof __VLS_components.elCard, ]} */ ;
// @ts-ignore
const __VLS_1 = __VLS_asFunctionalComponent(__VLS_0, new __VLS_0({
...{ class: "register-card" },
}));
const __VLS_2 = __VLS_1({
...{ class: "register-card" },
}, ...__VLS_functionalComponentArgsRest(__VLS_1));
__VLS_3.slots.default;
{
const { header: __VLS_thisSlot } = __VLS_3.slots;
__VLS_asFunctionalElement(__VLS_intrinsicElements.h2, __VLS_intrinsicElements.h2)({});
}
__VLS_asFunctionalElement(__VLS_intrinsicElements.p, __VLS_intrinsicElements.p)({});
var __VLS_3;
/** @type {__VLS_StyleScopedClasses['register-page']} */ ;
/** @type {__VLS_StyleScopedClasses['register-card']} */ ;
var __VLS_dollars;
const __VLS_self = (await import('vue')).defineComponent({
setup() {
return {};
},
});
export default (await import('vue')).defineComponent({
setup() {
return {};
},
});
; /* PartiallyEnd: #4569/main.vue */
+10
View File
@@ -0,0 +1,10 @@
<script setup lang="ts">
// Phase 14 实现设置页
</script>
<template>
<div>
<h1>设置</h1>
<p>设置页面Phase 14 实现</p>
</div>
</template>
+19
View File
@@ -0,0 +1,19 @@
debugger; /* PartiallyEnd: #3632/scriptSetup.vue */
const __VLS_ctx = {};
let __VLS_components;
let __VLS_directives;
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({});
__VLS_asFunctionalElement(__VLS_intrinsicElements.h1, __VLS_intrinsicElements.h1)({});
__VLS_asFunctionalElement(__VLS_intrinsicElements.p, __VLS_intrinsicElements.p)({});
var __VLS_dollars;
const __VLS_self = (await import('vue')).defineComponent({
setup() {
return {};
},
});
export default (await import('vue')).defineComponent({
setup() {
return {};
},
});
; /* PartiallyEnd: #4569/main.vue */
+25
View File
@@ -0,0 +1,25 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"jsx": "preserve",
"importHelpers": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"useDefineForClassFields": true,
"resolveJsonModule": true,
"isolatedModules": true,
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
},
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"types": ["vite/client"]
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
"exclude": ["node_modules", "dist"]
}
+1
View File
@@ -0,0 +1 @@
{"root":["./src/env.d.ts","./src/main.ts","./src/api/client.ts","./src/router/index.ts","./src/stores/user.ts","./src/app.vue","./src/views/dashboard.vue","./src/views/kbdetail.vue","./src/views/knowledgebases.vue","./src/views/login.vue","./src/views/register.vue","./src/views/settings.vue"],"version":"5.9.3"}
+27
View File
@@ -0,0 +1,27 @@
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': resolve(__dirname, 'src'),
},
},
server: {
port: 5173,
proxy: {
// 管理端 API
'/api': {
target: 'http://localhost:8000',
changeOrigin: true,
},
// 公共 AI 页面
'/k': {
target: 'http://localhost:8000',
changeOrigin: true,
},
},
},
})