7
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import apiClient from '@/api/client'
|
||||
@@ -7,17 +7,23 @@ import apiClient from '@/api/client'
|
||||
const router = useRouter()
|
||||
const userStore = useUserStore()
|
||||
const isCollapsed = ref(false)
|
||||
const isMobile = ref(window.innerWidth <= 768)
|
||||
const showMobileMenu = ref(false)
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const { data } = await apiClient.get('/auth/me')
|
||||
userStore.setUser(data)
|
||||
} catch {
|
||||
// 未登录,跳转登录页
|
||||
if (router.currentRoute.value.path !== '/login' && router.currentRoute.value.path !== '/register') {
|
||||
router.push('/login')
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('resize', () => {
|
||||
isMobile.value = window.innerWidth <= 768
|
||||
if (!isMobile.value) showMobileMenu.value = false
|
||||
})
|
||||
})
|
||||
|
||||
async function handleLogout() {
|
||||
@@ -27,20 +33,22 @@ async function handleLogout() {
|
||||
userStore.clearUser()
|
||||
router.push('/login')
|
||||
}
|
||||
|
||||
function navigateTo(path: string) {
|
||||
router.push(path)
|
||||
showMobileMenu.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-container style="min-height: 100vh">
|
||||
<el-aside :width="isCollapsed ? '64px' : '220px'" style="transition: width 0.3s">
|
||||
<!-- 桌面端侧边栏 -->
|
||||
<el-aside v-if="!isMobile" :width="isCollapsed ? '64px' : '220px'" style="transition: width 0.3s">
|
||||
<div style="padding: 20px; text-align: center; font-weight: bold; font-size: 18px; color: #409eff; white-space: nowrap; overflow: hidden">
|
||||
<span v-if="!isCollapsed">AI Knowledge Link</span>
|
||||
<span v-else>AKL</span>
|
||||
</div>
|
||||
<el-menu
|
||||
:default-active="router.currentRoute.value.path"
|
||||
:collapse="isCollapsed"
|
||||
router
|
||||
>
|
||||
<el-menu :default-active="router.currentRoute.value.path" :collapse="isCollapsed" router>
|
||||
<el-menu-item index="/">
|
||||
<el-icon><svg viewBox="0 0 24 24" fill="currentColor"><path d="M3 13h8V3H3v10zm0 8h8v-6H3v6zm10 0h8V11h-8v10zm0-18v6h8V3h-8z"/></svg></el-icon>
|
||||
<template #title>仪表盘</template>
|
||||
@@ -55,15 +63,37 @@ async function handleLogout() {
|
||||
</el-menu-item>
|
||||
</el-menu>
|
||||
</el-aside>
|
||||
|
||||
<el-container>
|
||||
<el-header style="display: flex; align-items: center; justify-content: space-between; border-bottom: 1px solid #eee">
|
||||
<el-button :icon="isCollapsed ? 'Expand' : 'Fold'" text @click="isCollapsed = !isCollapsed" />
|
||||
<div style="display: flex; align-items: center; gap: 16px">
|
||||
<span>{{ userStore.username }}</span>
|
||||
<el-button type="danger" text @click="handleLogout">退出登录</el-button>
|
||||
<!-- 顶部栏 -->
|
||||
<el-header style="display: flex; align-items: center; justify-content: space-between; border-bottom: 1px solid #eee; padding: 0 16px; height: 56px">
|
||||
<div style="display: flex; align-items: center; gap: 12px">
|
||||
<!-- 手机端菜单按钮 -->
|
||||
<el-button v-if="isMobile" @click="showMobileMenu = !showMobileMenu" text style="font-size: 20px">☰</el-button>
|
||||
<!-- 桌面端折叠按钮 -->
|
||||
<el-button v-else @click="isCollapsed = !isCollapsed" text>
|
||||
{{ isCollapsed ? '→' : '←' }}
|
||||
</el-button>
|
||||
<span v-if="isMobile" style="font-weight: bold; color: #409eff; font-size: 16px">AKL</span>
|
||||
</div>
|
||||
<div style="display: flex; align-items: center; gap: 12px">
|
||||
<span style="font-size: 14px">{{ userStore.username }}</span>
|
||||
<el-button type="danger" text @click="handleLogout" style="font-size: 14px">退出</el-button>
|
||||
</div>
|
||||
</el-header>
|
||||
<el-main style="padding: 20px">
|
||||
|
||||
<!-- 手机端抽屉菜单 -->
|
||||
<div v-if="isMobile && showMobileMenu" style="position: fixed; top: 56px; left: 0; right: 0; bottom: 0; z-index: 999; background: rgba(0,0,0,0.5)" @click="showMobileMenu = false">
|
||||
<div style="width: 260px; height: 100%; background: #fff; padding: 20px" @click.stop>
|
||||
<div style="font-weight: bold; font-size: 18px; color: #409eff; margin-bottom: 20px">AI Knowledge Link</div>
|
||||
<div style="padding: 12px 0; cursor: pointer; font-size: 16px" @click="navigateTo('/')">📊 仪表盘</div>
|
||||
<div style="padding: 12px 0; cursor: pointer; font-size: 16px" @click="navigateTo('/knowledge-bases')">📚 知识库</div>
|
||||
<div style="padding: 12px 0; cursor: pointer; font-size: 16px" @click="navigateTo('/settings')">⚙️ 设置</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 主内容 -->
|
||||
<el-main style="padding: 16px">
|
||||
<router-view />
|
||||
</el-main>
|
||||
</el-container>
|
||||
|
||||
@@ -5,17 +5,23 @@ import apiClient from '@/api/client';
|
||||
const router = useRouter();
|
||||
const userStore = useUserStore();
|
||||
const isCollapsed = ref(false);
|
||||
const isMobile = ref(window.innerWidth <= 768);
|
||||
const showMobileMenu = ref(false);
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const { data } = await apiClient.get('/auth/me');
|
||||
userStore.setUser(data);
|
||||
}
|
||||
catch {
|
||||
// 未登录,跳转登录页
|
||||
if (router.currentRoute.value.path !== '/login' && router.currentRoute.value.path !== '/register') {
|
||||
router.push('/login');
|
||||
}
|
||||
}
|
||||
window.addEventListener('resize', () => {
|
||||
isMobile.value = window.innerWidth <= 768;
|
||||
if (!isMobile.value)
|
||||
showMobileMenu.value = false;
|
||||
});
|
||||
});
|
||||
async function handleLogout() {
|
||||
try {
|
||||
@@ -25,6 +31,10 @@ async function handleLogout() {
|
||||
userStore.clearUser();
|
||||
router.push('/login');
|
||||
}
|
||||
function navigateTo(path) {
|
||||
router.push(path);
|
||||
showMobileMenu.value = false;
|
||||
}
|
||||
debugger; /* PartiallyEnd: #3632/scriptSetup.vue */
|
||||
const __VLS_ctx = {};
|
||||
let __VLS_components;
|
||||
@@ -40,127 +50,129 @@ const __VLS_2 = __VLS_1({
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_1));
|
||||
var __VLS_4 = {};
|
||||
__VLS_3.slots.default;
|
||||
const __VLS_5 = {}.ElAside;
|
||||
/** @type {[typeof __VLS_components.ElAside, typeof __VLS_components.elAside, typeof __VLS_components.ElAside, typeof __VLS_components.elAside, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_6 = __VLS_asFunctionalComponent(__VLS_5, new __VLS_5({
|
||||
width: (__VLS_ctx.isCollapsed ? '64px' : '220px'),
|
||||
...{ style: {} },
|
||||
}));
|
||||
const __VLS_7 = __VLS_6({
|
||||
width: (__VLS_ctx.isCollapsed ? '64px' : '220px'),
|
||||
...{ style: {} },
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_6));
|
||||
__VLS_8.slots.default;
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ style: {} },
|
||||
});
|
||||
if (!__VLS_ctx.isCollapsed) {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({});
|
||||
if (!__VLS_ctx.isMobile) {
|
||||
const __VLS_5 = {}.ElAside;
|
||||
/** @type {[typeof __VLS_components.ElAside, typeof __VLS_components.elAside, typeof __VLS_components.ElAside, typeof __VLS_components.elAside, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_6 = __VLS_asFunctionalComponent(__VLS_5, new __VLS_5({
|
||||
width: (__VLS_ctx.isCollapsed ? '64px' : '220px'),
|
||||
...{ style: {} },
|
||||
}));
|
||||
const __VLS_7 = __VLS_6({
|
||||
width: (__VLS_ctx.isCollapsed ? '64px' : '220px'),
|
||||
...{ style: {} },
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_6));
|
||||
__VLS_8.slots.default;
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ style: {} },
|
||||
});
|
||||
if (!__VLS_ctx.isCollapsed) {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({});
|
||||
}
|
||||
else {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({});
|
||||
}
|
||||
const __VLS_9 = {}.ElMenu;
|
||||
/** @type {[typeof __VLS_components.ElMenu, typeof __VLS_components.elMenu, typeof __VLS_components.ElMenu, typeof __VLS_components.elMenu, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_10 = __VLS_asFunctionalComponent(__VLS_9, new __VLS_9({
|
||||
defaultActive: (__VLS_ctx.router.currentRoute.value.path),
|
||||
collapse: (__VLS_ctx.isCollapsed),
|
||||
router: true,
|
||||
}));
|
||||
const __VLS_11 = __VLS_10({
|
||||
defaultActive: (__VLS_ctx.router.currentRoute.value.path),
|
||||
collapse: (__VLS_ctx.isCollapsed),
|
||||
router: true,
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_10));
|
||||
__VLS_12.slots.default;
|
||||
const __VLS_13 = {}.ElMenuItem;
|
||||
/** @type {[typeof __VLS_components.ElMenuItem, typeof __VLS_components.elMenuItem, typeof __VLS_components.ElMenuItem, typeof __VLS_components.elMenuItem, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_14 = __VLS_asFunctionalComponent(__VLS_13, new __VLS_13({
|
||||
index: "/",
|
||||
}));
|
||||
const __VLS_15 = __VLS_14({
|
||||
index: "/",
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_14));
|
||||
__VLS_16.slots.default;
|
||||
const __VLS_17 = {}.ElIcon;
|
||||
/** @type {[typeof __VLS_components.ElIcon, typeof __VLS_components.elIcon, typeof __VLS_components.ElIcon, typeof __VLS_components.elIcon, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_18 = __VLS_asFunctionalComponent(__VLS_17, new __VLS_17({}));
|
||||
const __VLS_19 = __VLS_18({}, ...__VLS_functionalComponentArgsRest(__VLS_18));
|
||||
__VLS_20.slots.default;
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.svg, __VLS_intrinsicElements.svg)({
|
||||
viewBox: "0 0 24 24",
|
||||
fill: "currentColor",
|
||||
});
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.path)({
|
||||
d: "M3 13h8V3H3v10zm0 8h8v-6H3v6zm10 0h8V11h-8v10zm0-18v6h8V3h-8z",
|
||||
});
|
||||
var __VLS_20;
|
||||
{
|
||||
const { title: __VLS_thisSlot } = __VLS_16.slots;
|
||||
}
|
||||
var __VLS_16;
|
||||
const __VLS_21 = {}.ElMenuItem;
|
||||
/** @type {[typeof __VLS_components.ElMenuItem, typeof __VLS_components.elMenuItem, typeof __VLS_components.ElMenuItem, typeof __VLS_components.elMenuItem, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_22 = __VLS_asFunctionalComponent(__VLS_21, new __VLS_21({
|
||||
index: "/knowledge-bases",
|
||||
}));
|
||||
const __VLS_23 = __VLS_22({
|
||||
index: "/knowledge-bases",
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_22));
|
||||
__VLS_24.slots.default;
|
||||
const __VLS_25 = {}.ElIcon;
|
||||
/** @type {[typeof __VLS_components.ElIcon, typeof __VLS_components.elIcon, typeof __VLS_components.ElIcon, typeof __VLS_components.elIcon, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_26 = __VLS_asFunctionalComponent(__VLS_25, new __VLS_25({}));
|
||||
const __VLS_27 = __VLS_26({}, ...__VLS_functionalComponentArgsRest(__VLS_26));
|
||||
__VLS_28.slots.default;
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.svg, __VLS_intrinsicElements.svg)({
|
||||
viewBox: "0 0 24 24",
|
||||
fill: "currentColor",
|
||||
});
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.path)({
|
||||
d: "M4 6H2v14c0 1.1.9 2 2 2h14v-2H4V6zm16-4H8c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-1 9H9V9h10v2zm-4 4H9v-2h6v2zm4-8H9V5h10v2z",
|
||||
});
|
||||
var __VLS_28;
|
||||
{
|
||||
const { title: __VLS_thisSlot } = __VLS_24.slots;
|
||||
}
|
||||
var __VLS_24;
|
||||
const __VLS_29 = {}.ElMenuItem;
|
||||
/** @type {[typeof __VLS_components.ElMenuItem, typeof __VLS_components.elMenuItem, typeof __VLS_components.ElMenuItem, typeof __VLS_components.elMenuItem, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_30 = __VLS_asFunctionalComponent(__VLS_29, new __VLS_29({
|
||||
index: "/settings",
|
||||
}));
|
||||
const __VLS_31 = __VLS_30({
|
||||
index: "/settings",
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_30));
|
||||
__VLS_32.slots.default;
|
||||
const __VLS_33 = {}.ElIcon;
|
||||
/** @type {[typeof __VLS_components.ElIcon, typeof __VLS_components.elIcon, typeof __VLS_components.ElIcon, typeof __VLS_components.elIcon, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_34 = __VLS_asFunctionalComponent(__VLS_33, new __VLS_33({}));
|
||||
const __VLS_35 = __VLS_34({}, ...__VLS_functionalComponentArgsRest(__VLS_34));
|
||||
__VLS_36.slots.default;
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.svg, __VLS_intrinsicElements.svg)({
|
||||
viewBox: "0 0 24 24",
|
||||
fill: "currentColor",
|
||||
});
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.path)({
|
||||
d: "M19.14 12.94c.04-.3.06-.61.06-.94 0-.32-.02-.64-.07-.94l2.03-1.58c.18-.14.23-.41.12-.61l-1.92-3.32c-.12-.22-.37-.29-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54c-.04-.24-.24-.41-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.05.3-.07.62-.07.94s.02.64.07.94l-2.03 1.58c-.18.14-.23.41-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z",
|
||||
});
|
||||
var __VLS_36;
|
||||
{
|
||||
const { title: __VLS_thisSlot } = __VLS_32.slots;
|
||||
}
|
||||
var __VLS_32;
|
||||
var __VLS_12;
|
||||
var __VLS_8;
|
||||
}
|
||||
else {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({});
|
||||
}
|
||||
const __VLS_9 = {}.ElMenu;
|
||||
/** @type {[typeof __VLS_components.ElMenu, typeof __VLS_components.elMenu, typeof __VLS_components.ElMenu, typeof __VLS_components.elMenu, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_10 = __VLS_asFunctionalComponent(__VLS_9, new __VLS_9({
|
||||
defaultActive: (__VLS_ctx.router.currentRoute.value.path),
|
||||
collapse: (__VLS_ctx.isCollapsed),
|
||||
router: true,
|
||||
}));
|
||||
const __VLS_11 = __VLS_10({
|
||||
defaultActive: (__VLS_ctx.router.currentRoute.value.path),
|
||||
collapse: (__VLS_ctx.isCollapsed),
|
||||
router: true,
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_10));
|
||||
__VLS_12.slots.default;
|
||||
const __VLS_13 = {}.ElMenuItem;
|
||||
/** @type {[typeof __VLS_components.ElMenuItem, typeof __VLS_components.elMenuItem, typeof __VLS_components.ElMenuItem, typeof __VLS_components.elMenuItem, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_14 = __VLS_asFunctionalComponent(__VLS_13, new __VLS_13({
|
||||
index: "/",
|
||||
}));
|
||||
const __VLS_15 = __VLS_14({
|
||||
index: "/",
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_14));
|
||||
__VLS_16.slots.default;
|
||||
const __VLS_17 = {}.ElIcon;
|
||||
/** @type {[typeof __VLS_components.ElIcon, typeof __VLS_components.elIcon, typeof __VLS_components.ElIcon, typeof __VLS_components.elIcon, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_18 = __VLS_asFunctionalComponent(__VLS_17, new __VLS_17({}));
|
||||
const __VLS_19 = __VLS_18({}, ...__VLS_functionalComponentArgsRest(__VLS_18));
|
||||
__VLS_20.slots.default;
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.svg, __VLS_intrinsicElements.svg)({
|
||||
viewBox: "0 0 24 24",
|
||||
fill: "currentColor",
|
||||
});
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.path)({
|
||||
d: "M3 13h8V3H3v10zm0 8h8v-6H3v6zm10 0h8V11h-8v10zm0-18v6h8V3h-8z",
|
||||
});
|
||||
var __VLS_20;
|
||||
{
|
||||
const { title: __VLS_thisSlot } = __VLS_16.slots;
|
||||
}
|
||||
var __VLS_16;
|
||||
const __VLS_21 = {}.ElMenuItem;
|
||||
/** @type {[typeof __VLS_components.ElMenuItem, typeof __VLS_components.elMenuItem, typeof __VLS_components.ElMenuItem, typeof __VLS_components.elMenuItem, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_22 = __VLS_asFunctionalComponent(__VLS_21, new __VLS_21({
|
||||
index: "/knowledge-bases",
|
||||
}));
|
||||
const __VLS_23 = __VLS_22({
|
||||
index: "/knowledge-bases",
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_22));
|
||||
__VLS_24.slots.default;
|
||||
const __VLS_25 = {}.ElIcon;
|
||||
/** @type {[typeof __VLS_components.ElIcon, typeof __VLS_components.elIcon, typeof __VLS_components.ElIcon, typeof __VLS_components.elIcon, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_26 = __VLS_asFunctionalComponent(__VLS_25, new __VLS_25({}));
|
||||
const __VLS_27 = __VLS_26({}, ...__VLS_functionalComponentArgsRest(__VLS_26));
|
||||
__VLS_28.slots.default;
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.svg, __VLS_intrinsicElements.svg)({
|
||||
viewBox: "0 0 24 24",
|
||||
fill: "currentColor",
|
||||
});
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.path)({
|
||||
d: "M4 6H2v14c0 1.1.9 2 2 2h14v-2H4V6zm16-4H8c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-1 9H9V9h10v2zm-4 4H9v-2h6v2zm4-8H9V5h10v2z",
|
||||
});
|
||||
var __VLS_28;
|
||||
{
|
||||
const { title: __VLS_thisSlot } = __VLS_24.slots;
|
||||
}
|
||||
var __VLS_24;
|
||||
const __VLS_29 = {}.ElMenuItem;
|
||||
/** @type {[typeof __VLS_components.ElMenuItem, typeof __VLS_components.elMenuItem, typeof __VLS_components.ElMenuItem, typeof __VLS_components.elMenuItem, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_30 = __VLS_asFunctionalComponent(__VLS_29, new __VLS_29({
|
||||
index: "/settings",
|
||||
}));
|
||||
const __VLS_31 = __VLS_30({
|
||||
index: "/settings",
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_30));
|
||||
__VLS_32.slots.default;
|
||||
const __VLS_33 = {}.ElIcon;
|
||||
/** @type {[typeof __VLS_components.ElIcon, typeof __VLS_components.elIcon, typeof __VLS_components.ElIcon, typeof __VLS_components.elIcon, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_34 = __VLS_asFunctionalComponent(__VLS_33, new __VLS_33({}));
|
||||
const __VLS_35 = __VLS_34({}, ...__VLS_functionalComponentArgsRest(__VLS_34));
|
||||
__VLS_36.slots.default;
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.svg, __VLS_intrinsicElements.svg)({
|
||||
viewBox: "0 0 24 24",
|
||||
fill: "currentColor",
|
||||
});
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.path)({
|
||||
d: "M19.14 12.94c.04-.3.06-.61.06-.94 0-.32-.02-.64-.07-.94l2.03-1.58c.18-.14.23-.41.12-.61l-1.92-3.32c-.12-.22-.37-.29-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54c-.04-.24-.24-.41-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.05.3-.07.62-.07.94s.02.64.07.94l-2.03 1.58c-.18.14-.23.41-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z",
|
||||
});
|
||||
var __VLS_36;
|
||||
{
|
||||
const { title: __VLS_thisSlot } = __VLS_32.slots;
|
||||
}
|
||||
var __VLS_32;
|
||||
var __VLS_12;
|
||||
var __VLS_8;
|
||||
const __VLS_37 = {}.ElContainer;
|
||||
/** @type {[typeof __VLS_components.ElContainer, typeof __VLS_components.elContainer, typeof __VLS_components.ElContainer, typeof __VLS_components.elContainer, ]} */ ;
|
||||
// @ts-ignore
|
||||
@@ -177,71 +189,155 @@ const __VLS_43 = __VLS_42({
|
||||
...{ style: {} },
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_42));
|
||||
__VLS_44.slots.default;
|
||||
const __VLS_45 = {}.ElButton;
|
||||
/** @type {[typeof __VLS_components.ElButton, typeof __VLS_components.elButton, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_46 = __VLS_asFunctionalComponent(__VLS_45, new __VLS_45({
|
||||
...{ 'onClick': {} },
|
||||
icon: (__VLS_ctx.isCollapsed ? 'Expand' : 'Fold'),
|
||||
text: true,
|
||||
}));
|
||||
const __VLS_47 = __VLS_46({
|
||||
...{ 'onClick': {} },
|
||||
icon: (__VLS_ctx.isCollapsed ? 'Expand' : 'Fold'),
|
||||
text: true,
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_46));
|
||||
let __VLS_49;
|
||||
let __VLS_50;
|
||||
let __VLS_51;
|
||||
const __VLS_52 = {
|
||||
onClick: (...[$event]) => {
|
||||
__VLS_ctx.isCollapsed = !__VLS_ctx.isCollapsed;
|
||||
}
|
||||
};
|
||||
var __VLS_48;
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ style: {} },
|
||||
});
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({});
|
||||
if (__VLS_ctx.isMobile) {
|
||||
const __VLS_45 = {}.ElButton;
|
||||
/** @type {[typeof __VLS_components.ElButton, typeof __VLS_components.elButton, typeof __VLS_components.ElButton, typeof __VLS_components.elButton, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_46 = __VLS_asFunctionalComponent(__VLS_45, new __VLS_45({
|
||||
...{ 'onClick': {} },
|
||||
text: true,
|
||||
...{ style: {} },
|
||||
}));
|
||||
const __VLS_47 = __VLS_46({
|
||||
...{ 'onClick': {} },
|
||||
text: true,
|
||||
...{ style: {} },
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_46));
|
||||
let __VLS_49;
|
||||
let __VLS_50;
|
||||
let __VLS_51;
|
||||
const __VLS_52 = {
|
||||
onClick: (...[$event]) => {
|
||||
if (!(__VLS_ctx.isMobile))
|
||||
return;
|
||||
__VLS_ctx.showMobileMenu = !__VLS_ctx.showMobileMenu;
|
||||
}
|
||||
};
|
||||
__VLS_48.slots.default;
|
||||
var __VLS_48;
|
||||
}
|
||||
else {
|
||||
const __VLS_53 = {}.ElButton;
|
||||
/** @type {[typeof __VLS_components.ElButton, typeof __VLS_components.elButton, typeof __VLS_components.ElButton, typeof __VLS_components.elButton, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_54 = __VLS_asFunctionalComponent(__VLS_53, new __VLS_53({
|
||||
...{ 'onClick': {} },
|
||||
text: true,
|
||||
}));
|
||||
const __VLS_55 = __VLS_54({
|
||||
...{ 'onClick': {} },
|
||||
text: true,
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_54));
|
||||
let __VLS_57;
|
||||
let __VLS_58;
|
||||
let __VLS_59;
|
||||
const __VLS_60 = {
|
||||
onClick: (...[$event]) => {
|
||||
if (!!(__VLS_ctx.isMobile))
|
||||
return;
|
||||
__VLS_ctx.isCollapsed = !__VLS_ctx.isCollapsed;
|
||||
}
|
||||
};
|
||||
__VLS_56.slots.default;
|
||||
(__VLS_ctx.isCollapsed ? '→' : '←');
|
||||
var __VLS_56;
|
||||
}
|
||||
if (__VLS_ctx.isMobile) {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
|
||||
...{ style: {} },
|
||||
});
|
||||
}
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ style: {} },
|
||||
});
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
|
||||
...{ style: {} },
|
||||
});
|
||||
(__VLS_ctx.userStore.username);
|
||||
const __VLS_53 = {}.ElButton;
|
||||
const __VLS_61 = {}.ElButton;
|
||||
/** @type {[typeof __VLS_components.ElButton, typeof __VLS_components.elButton, typeof __VLS_components.ElButton, typeof __VLS_components.elButton, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_54 = __VLS_asFunctionalComponent(__VLS_53, new __VLS_53({
|
||||
...{ 'onClick': {} },
|
||||
type: "danger",
|
||||
text: true,
|
||||
}));
|
||||
const __VLS_55 = __VLS_54({
|
||||
...{ 'onClick': {} },
|
||||
type: "danger",
|
||||
text: true,
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_54));
|
||||
let __VLS_57;
|
||||
let __VLS_58;
|
||||
let __VLS_59;
|
||||
const __VLS_60 = {
|
||||
onClick: (__VLS_ctx.handleLogout)
|
||||
};
|
||||
__VLS_56.slots.default;
|
||||
var __VLS_56;
|
||||
var __VLS_44;
|
||||
const __VLS_61 = {}.ElMain;
|
||||
/** @type {[typeof __VLS_components.ElMain, typeof __VLS_components.elMain, typeof __VLS_components.ElMain, typeof __VLS_components.elMain, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_62 = __VLS_asFunctionalComponent(__VLS_61, new __VLS_61({
|
||||
...{ 'onClick': {} },
|
||||
type: "danger",
|
||||
text: true,
|
||||
...{ style: {} },
|
||||
}));
|
||||
const __VLS_63 = __VLS_62({
|
||||
...{ 'onClick': {} },
|
||||
type: "danger",
|
||||
text: true,
|
||||
...{ style: {} },
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_62));
|
||||
let __VLS_65;
|
||||
let __VLS_66;
|
||||
let __VLS_67;
|
||||
const __VLS_68 = {
|
||||
onClick: (__VLS_ctx.handleLogout)
|
||||
};
|
||||
__VLS_64.slots.default;
|
||||
const __VLS_65 = {}.RouterView;
|
||||
var __VLS_64;
|
||||
var __VLS_44;
|
||||
if (__VLS_ctx.isMobile && __VLS_ctx.showMobileMenu) {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ onClick: (...[$event]) => {
|
||||
if (!(__VLS_ctx.isMobile && __VLS_ctx.showMobileMenu))
|
||||
return;
|
||||
__VLS_ctx.showMobileMenu = false;
|
||||
} },
|
||||
...{ style: {} },
|
||||
});
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ onClick: () => { } },
|
||||
...{ style: {} },
|
||||
});
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ style: {} },
|
||||
});
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ onClick: (...[$event]) => {
|
||||
if (!(__VLS_ctx.isMobile && __VLS_ctx.showMobileMenu))
|
||||
return;
|
||||
__VLS_ctx.navigateTo('/');
|
||||
} },
|
||||
...{ style: {} },
|
||||
});
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ onClick: (...[$event]) => {
|
||||
if (!(__VLS_ctx.isMobile && __VLS_ctx.showMobileMenu))
|
||||
return;
|
||||
__VLS_ctx.navigateTo('/knowledge-bases');
|
||||
} },
|
||||
...{ style: {} },
|
||||
});
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ onClick: (...[$event]) => {
|
||||
if (!(__VLS_ctx.isMobile && __VLS_ctx.showMobileMenu))
|
||||
return;
|
||||
__VLS_ctx.navigateTo('/settings');
|
||||
} },
|
||||
...{ style: {} },
|
||||
});
|
||||
}
|
||||
const __VLS_69 = {}.ElMain;
|
||||
/** @type {[typeof __VLS_components.ElMain, typeof __VLS_components.elMain, typeof __VLS_components.ElMain, typeof __VLS_components.elMain, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_70 = __VLS_asFunctionalComponent(__VLS_69, new __VLS_69({
|
||||
...{ style: {} },
|
||||
}));
|
||||
const __VLS_71 = __VLS_70({
|
||||
...{ style: {} },
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_70));
|
||||
__VLS_72.slots.default;
|
||||
const __VLS_73 = {}.RouterView;
|
||||
/** @type {[typeof __VLS_components.RouterView, typeof __VLS_components.routerView, ]} */ ;
|
||||
// @ts-ignore
|
||||
const __VLS_66 = __VLS_asFunctionalComponent(__VLS_65, new __VLS_65({}));
|
||||
const __VLS_67 = __VLS_66({}, ...__VLS_functionalComponentArgsRest(__VLS_66));
|
||||
var __VLS_64;
|
||||
const __VLS_74 = __VLS_asFunctionalComponent(__VLS_73, new __VLS_73({}));
|
||||
const __VLS_75 = __VLS_74({}, ...__VLS_functionalComponentArgsRest(__VLS_74));
|
||||
var __VLS_72;
|
||||
var __VLS_40;
|
||||
var __VLS_3;
|
||||
var __VLS_dollars;
|
||||
@@ -251,7 +347,10 @@ const __VLS_self = (await import('vue')).defineComponent({
|
||||
router: router,
|
||||
userStore: userStore,
|
||||
isCollapsed: isCollapsed,
|
||||
isMobile: isMobile,
|
||||
showMobileMenu: showMobileMenu,
|
||||
handleLogout: handleLogout,
|
||||
navigateTo: navigateTo,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
+320
-151
@@ -7,6 +7,14 @@ import apiClient from '@/api/client'
|
||||
const route = useRoute()
|
||||
const kbId = route.params.id as string
|
||||
|
||||
const isMobile = ref(window.innerWidth <= 768)
|
||||
const showMobileSidebar = ref(false)
|
||||
|
||||
window.addEventListener('resize', () => {
|
||||
isMobile.value = window.innerWidth <= 768
|
||||
if (!isMobile.value) showMobileSidebar.value = false
|
||||
})
|
||||
|
||||
const kb = ref<any>(null)
|
||||
const docs = ref<any[]>([])
|
||||
const categories = ref<any[]>([])
|
||||
@@ -247,159 +255,160 @@ function getCategoryName(catId: string) {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="kb" style="display: flex; gap: 24px; height: calc(100vh - 120px)">
|
||||
<!-- 左侧:目录树 -->
|
||||
<div style="width: 300px; flex-shrink: 0; overflow-y: auto; border-right: 1px solid #e4e7ed; padding-right: 20px">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px">
|
||||
<h3 style="margin: 0; font-size: 16px">📁 目录结构</h3>
|
||||
<el-button size="small" type="primary" @click="openAddCategory(null)">+ 新建</el-button>
|
||||
<div v-if="kb">
|
||||
<!-- 手机端:顶部操作栏 -->
|
||||
<div class="mobile-header">
|
||||
<h1 style="margin: 0; font-size: 20px">{{ kb.name }}</h1>
|
||||
<div style="display: flex; gap: 8px; margin-top: 10px">
|
||||
<el-button @click="handleCopyLink" size="small" style="flex: 1">📋 复制链接</el-button>
|
||||
<el-button type="warning" @click="handleRegenerateLink" size="small" style="flex: 1">🔄 重置链接</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 全部文档 -->
|
||||
<div
|
||||
style="padding: 10px 12px; cursor: pointer; border-radius: 6px; margin-bottom: 8px; font-size: 14px; transition: all 0.2s"
|
||||
:style="{ background: !selectedCategoryId ? '#ecf5ff' : '#f5f7fa', color: !selectedCategoryId ? '#409eff' : '#333', fontWeight: !selectedCategoryId ? 'bold' : 'normal' }"
|
||||
@click="clearCategoryFilter"
|
||||
>
|
||||
📋 全部文档
|
||||
</div>
|
||||
|
||||
<!-- 目录树 -->
|
||||
<el-tree
|
||||
:data="categories"
|
||||
node-key="id"
|
||||
default-expand-all
|
||||
:expand-on-click-node="false"
|
||||
:props="{ children: 'children', label: 'name' }"
|
||||
>
|
||||
<template #default="{ node, data }">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; width: 100%; padding: 6px 0">
|
||||
<span
|
||||
style="cursor: pointer; font-size: 14px; flex: 1"
|
||||
:style="{ color: selectedCategoryId === data.id ? '#409eff' : '#333', fontWeight: selectedCategoryId === data.id ? 'bold' : 'normal' }"
|
||||
@click="selectCategory(data)"
|
||||
>
|
||||
{{ data.is_folder ? '📁' : '📄' }} {{ data.name }}
|
||||
<span v-if="data.doc_count > 0" style="color: #999; font-size: 12px; margin-left: 4px">({{ data.doc_count }})</span>
|
||||
</span>
|
||||
<span style="display: flex; gap: 2px">
|
||||
<el-button size="small" text @click.stop="openAddCategory(data.id)" style="font-size: 12px">+</el-button>
|
||||
<el-button size="small" text @click.stop="openEditCategory(data)" style="font-size: 12px">✎</el-button>
|
||||
<el-button size="small" text type="danger" @click.stop="handleDeleteCategory(data)" style="font-size: 12px">×</el-button>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-tree>
|
||||
</div>
|
||||
|
||||
<!-- 右侧:文档列表 -->
|
||||
<div style="flex: 1; overflow-y: auto">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px">
|
||||
<h1 style="margin: 0; font-size: 24px">{{ kb.name }}</h1>
|
||||
<div style="display: flex; gap: 12px">
|
||||
<el-button @click="handleCopyLink" size="large">📋 复制 AI 链接</el-button>
|
||||
<el-button type="warning" @click="handleRegenerateLink" size="large">🔄 重新生成链接</el-button>
|
||||
<div class="main-layout">
|
||||
<!-- 左侧:目录树(桌面端常驻,手机端抽屉) -->
|
||||
<aside class="sidebar" :class="{ 'mobile-show': showMobileSidebar }">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px">
|
||||
<h3 style="margin: 0; font-size: 16px">📁 目录</h3>
|
||||
<el-button size="small" type="primary" @click="openAddCategory(null)">+ 新建</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- AI 链接显示 -->
|
||||
<el-card style="margin-bottom: 20px" shadow="hover">
|
||||
<div style="display: flex; align-items: center; gap: 12px">
|
||||
<span style="font-weight: bold; font-size: 14px; white-space: nowrap">AI 访问链接:</span>
|
||||
<el-input :model-value="aiUrl" readonly style="flex: 1" size="large">
|
||||
<template #append>
|
||||
<el-button @click="handleCopyLink">复制</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
<div
|
||||
class="cat-item"
|
||||
:class="{ active: !selectedCategoryId }"
|
||||
@click="clearCategoryFilter"
|
||||
>
|
||||
📋 全部文档
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<el-card shadow="hover">
|
||||
<template #header>
|
||||
<div style="display: flex; justify-content: space-between; align-items: center">
|
||||
<span style="font-size: 16px; font-weight: bold">
|
||||
📄 文档列表
|
||||
<el-tag v-if="selectedCategoryPath" closable @close="clearCategoryFilter" style="margin-left: 8px" size="large">
|
||||
{{ selectedCategoryPath }}
|
||||
</el-tag>
|
||||
</span>
|
||||
<div style="display: flex; gap: 10px">
|
||||
<el-button size="large" @click="showTextDialog = true">✏️ 添加文本</el-button>
|
||||
<el-upload :show-file-list="false" :http-request="handleUpload" accept=".docx,.pdf">
|
||||
<el-button type="primary" :loading="uploading" size="large">📤 上传文档</el-button>
|
||||
</el-upload>
|
||||
<el-tree :data="categories" node-key="id" default-expand-all :expand-on-click-node="false">
|
||||
<template #default="{ data }">
|
||||
<div class="tree-node">
|
||||
<span class="tree-label" :class="{ selected: selectedCategoryId === data.id }" @click="selectCategory(data)">
|
||||
{{ data.is_folder ? '📁' : '📄' }} {{ data.name }}
|
||||
<span v-if="data.doc_count > 0" style="color: #999; font-size: 12px">({{ data.doc_count }})</span>
|
||||
</span>
|
||||
<span class="tree-actions">
|
||||
<el-button size="small" text @click.stop="openAddCategory(data.id)">+</el-button>
|
||||
<el-button size="small" text @click.stop="openEditCategory(data)">✎</el-button>
|
||||
<el-button size="small" text type="danger" @click.stop="handleDeleteCategory(data)">×</el-button>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-tree>
|
||||
</aside>
|
||||
|
||||
<!-- 手机端遮罩 -->
|
||||
<div v-if="showMobileSidebar" class="mobile-overlay" @click="showMobileSidebar = false"></div>
|
||||
|
||||
<!-- 右侧:文档列表 -->
|
||||
<main class="content">
|
||||
<!-- 桌面端标题 -->
|
||||
<div class="desktop-header">
|
||||
<h1 style="margin: 0; font-size: 24px">{{ kb.name }}</h1>
|
||||
<div style="display: flex; gap: 12px">
|
||||
<el-button @click="handleCopyLink" size="large">📋 复制 AI 链接</el-button>
|
||||
<el-button type="warning" @click="handleRegenerateLink" size="large">🔄 重新生成链接</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- AI 链接 -->
|
||||
<el-card style="margin-bottom: 16px" shadow="hover">
|
||||
<div style="display: flex; align-items: center; gap: 10px; flex-wrap: wrap">
|
||||
<span style="font-weight: bold; font-size: 14px">AI 链接:</span>
|
||||
<el-input :model-value="aiUrl" readonly style="flex: 1; min-width: 200px" size="large">
|
||||
<template #append>
|
||||
<el-button @click="handleCopyLink">复制</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 手机端目录按钮 + 操作按钮 -->
|
||||
<div class="mobile-actions">
|
||||
<el-button @click="showMobileSidebar = true" style="flex: 1">📁 目录</el-button>
|
||||
<el-button @click="showTextDialog = true" style="flex: 1">✏️ 添加文本</el-button>
|
||||
<el-upload :show-file-list="false" :http-request="handleUpload" accept=".docx,.pdf" style="flex: 1">
|
||||
<el-button type="primary" :loading="uploading" style="width: 100%">📤 上传</el-button>
|
||||
</el-upload>
|
||||
</div>
|
||||
|
||||
<el-card shadow="hover">
|
||||
<template #header>
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 10px">
|
||||
<span style="font-size: 16px; font-weight: bold">
|
||||
📄 文档列表
|
||||
<el-tag v-if="selectedCategoryPath" closable @close="clearCategoryFilter" style="margin-left: 8px">
|
||||
{{ selectedCategoryPath }}
|
||||
</el-tag>
|
||||
</span>
|
||||
<div class="desktop-actions">
|
||||
<el-button @click="showTextDialog = true">✏️ 添加文本</el-button>
|
||||
<el-upload :show-file-list="false" :http-request="handleUpload" accept=".docx,.pdf">
|
||||
<el-button type="primary" :loading="uploading">📤 上传文档</el-button>
|
||||
</el-upload>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 桌面端:表格 -->
|
||||
<div class="desktop-table">
|
||||
<el-table :data="docs" v-loading="loading" style="width: 100%">
|
||||
<el-table-column prop="original_filename" label="标题" min-width="180" show-overflow-tooltip />
|
||||
<el-table-column label="目录" width="180">
|
||||
<template #default="{ row }">
|
||||
<el-select :model-value="row.category_id" @change="(val: string) => handleChangeDocCategory(row, val)" placeholder="选择目录" size="small" style="width: 100%">
|
||||
<el-option v-for="cat in allCategoriesFlat" :key="cat.id" :label="cat.name" :value="cat.id" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" width="90" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="statusType(row.status)" size="small">{{ row.status }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="大小" width="80" align="center">
|
||||
<template #default="{ row }">{{ formatSize(row.file_size) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="180" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-button size="small" @click="handleReprocess(row)">重新解析</el-button>
|
||||
<el-button size="small" type="danger" @click="handleDeleteDoc(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
<!-- 手机端:卡片列表 -->
|
||||
<div class="mobile-cards">
|
||||
<div v-for="doc in docs" :key="doc.id" class="doc-card">
|
||||
<div style="font-weight: bold; font-size: 15px; margin-bottom: 8px">{{ doc.original_filename }}</div>
|
||||
<div style="display: flex; align-items: center; gap: 8px; margin-bottom: 8px; flex-wrap: wrap">
|
||||
<el-tag :type="statusType(doc.status)" size="small">{{ doc.status }}</el-tag>
|
||||
<span style="color: #999; font-size: 12px">{{ formatSize(doc.file_size) }}</span>
|
||||
</div>
|
||||
<el-select :model-value="doc.category_id" @change="(val: string) => handleChangeDocCategory(doc, val)" placeholder="选择目录" size="small" style="width: 100%; margin-bottom: 8px">
|
||||
<el-option v-for="cat in allCategoriesFlat" :key="cat.id" :label="cat.name" :value="cat.id" />
|
||||
</el-select>
|
||||
<div style="display: flex; gap: 8px">
|
||||
<el-button size="small" style="flex: 1" @click="handleReprocess(doc)">重新解析</el-button>
|
||||
<el-button size="small" type="danger" style="flex: 1" @click="handleDeleteDoc(doc)">删除</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-table :data="docs" v-loading="loading" style="width: 100%" size="large">
|
||||
<el-table-column prop="original_filename" label="标题/文件名" min-width="200" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
<span style="font-size: 14px; font-weight: 500">{{ row.original_filename }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="所属目录" width="200">
|
||||
<template #default="{ row }">
|
||||
<el-select
|
||||
:model-value="row.category_id"
|
||||
@change="(val: string) => handleChangeDocCategory(row, val)"
|
||||
placeholder="选择目录"
|
||||
size="default"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="cat in allCategoriesFlat"
|
||||
:key="cat.id"
|
||||
:label="cat.name"
|
||||
:value="cat.id"
|
||||
/>
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="statusType(row.status)" size="default">{{ row.status }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="类型" width="80" align="center">
|
||||
<template #default="{ row }">
|
||||
<span style="font-size: 13px">{{ row.file_ext }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="大小" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<span style="font-size: 13px">{{ formatSize(row.file_size) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="title" label="解析标题" min-width="150" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
<span style="font-size: 13px; color: #666">{{ row.title || '-' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="200" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-button size="default" @click="handleReprocess(row)">🔄 重新解析</el-button>
|
||||
<el-button size="default" type="danger" @click="handleDeleteDoc(row)">🗑️ 删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
</el-card>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<!-- 文本内容对话框 -->
|
||||
<el-dialog v-model="showTextDialog" title="✏️ 添加文本内容" width="700px">
|
||||
<!-- 对话框 -->
|
||||
<el-dialog v-model="showTextDialog" title="✏️ 添加文本内容" :width="isMobile ? '95%' : '700px'">
|
||||
<el-form :model="textForm" label-position="top">
|
||||
<el-form-item label="标题" required>
|
||||
<el-input v-model="textForm.title" placeholder="文档标题" size="large" />
|
||||
</el-form-item>
|
||||
<el-form-item label="所属目录">
|
||||
<el-select v-model="textForm.category_id" placeholder="选择目录(可选)" clearable style="width: 100%" size="large">
|
||||
<el-option
|
||||
v-for="cat in allCategoriesFlat"
|
||||
:key="cat.id"
|
||||
:label="cat.name"
|
||||
:value="cat.id"
|
||||
/>
|
||||
<el-select v-model="textForm.category_id" placeholder="选择目录" clearable style="width: 100%" size="large">
|
||||
<el-option v-for="cat in allCategoriesFlat" :key="cat.id" :label="cat.name" :value="cat.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="格式">
|
||||
@@ -409,13 +418,7 @@ function getCategoryName(catId: string) {
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="内容" required>
|
||||
<el-input
|
||||
v-model="textForm.content"
|
||||
type="textarea"
|
||||
:rows="15"
|
||||
placeholder="输入文本内容(支持 Markdown 格式)"
|
||||
size="large"
|
||||
/>
|
||||
<el-input v-model="textForm.content" type="textarea" :rows="12" placeholder="输入文本内容(支持 Markdown)" size="large" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
@@ -424,25 +427,191 @@ function getCategoryName(catId: string) {
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 目录管理对话框 -->
|
||||
<el-dialog v-model="showCatDialog" :title="editingCatId ? '✏️ 编辑目录' : '📁 新建目录'" width="450px">
|
||||
<el-dialog v-model="showCatDialog" :title="editingCatId ? '✏️ 编辑目录' : '📁 新建目录'" :width="isMobile ? '95%' : '450px'">
|
||||
<el-form :model="catForm" label-position="top">
|
||||
<el-form-item label="目录名称" required>
|
||||
<el-input v-model="catForm.name" placeholder="如:公司基本信息" size="large" />
|
||||
</el-form-item>
|
||||
<el-form-item label="类型">
|
||||
<el-radio-group v-model="catForm.is_folder" size="large">
|
||||
<el-radio :value="true">📁 文件夹(可包含子目录)</el-radio>
|
||||
<el-radio :value="false">📄 叶子分类(存放文档)</el-radio>
|
||||
<el-radio :value="true">📁 文件夹</el-radio>
|
||||
<el-radio :value="false">📄 叶子分类</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="showCatDialog = false" size="large">取消</el-button>
|
||||
<el-button type="primary" :loading="catLoading" @click="handleSaveCategory" size="large">
|
||||
{{ editingCatId ? '保存' : '创建' }}
|
||||
</el-button>
|
||||
<el-button type="primary" :loading="catLoading" @click="handleSaveCategory" size="large">{{ editingCatId ? '保存' : '创建' }}</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.main-layout {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
width: 280px;
|
||||
flex-shrink: 0;
|
||||
overflow-y: auto;
|
||||
max-height: calc(100vh - 180px);
|
||||
position: sticky;
|
||||
top: 20px;
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.cat-item {
|
||||
padding: 10px 12px;
|
||||
cursor: pointer;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 6px;
|
||||
font-size: 14px;
|
||||
background: #f5f7fa;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.cat-item:hover {
|
||||
background: #e8e8e8;
|
||||
}
|
||||
|
||||
.cat-item.active {
|
||||
background: #ecf5ff;
|
||||
color: #409eff;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.tree-node {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
.tree-label {
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.tree-label.selected {
|
||||
color: #409eff;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.tree-actions {
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.mobile-header {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mobile-actions {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mobile-cards {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.desktop-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.desktop-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.mobile-overlay {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* 手机端适配 */
|
||||
@media (max-width: 768px) {
|
||||
.main-layout {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 80%;
|
||||
max-width: 300px;
|
||||
height: 100vh;
|
||||
background: #fff;
|
||||
z-index: 1000;
|
||||
padding: 20px;
|
||||
box-shadow: 2px 0 10px rgba(0, 0, 0, 0.2);
|
||||
overflow-y: auto;
|
||||
max-height: 100vh;
|
||||
}
|
||||
|
||||
.sidebar.mobile-show {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.mobile-overlay {
|
||||
display: block;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.content {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.mobile-header {
|
||||
display: block;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.desktop-header {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mobile-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.desktop-actions {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.desktop-table {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mobile-cards {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.doc-card {
|
||||
border: 1px solid #eee;
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
+867
-624
File diff suppressed because it is too large
Load Diff
@@ -10,6 +10,7 @@ export default defineConfig({
|
||||
},
|
||||
},
|
||||
server: {
|
||||
host: '0.0.0.0',
|
||||
port: 5173,
|
||||
proxy: {
|
||||
// 管理端 API
|
||||
|
||||
Reference in New Issue
Block a user