This commit is contained in:
amb
2026-09-01 13:57:34 +08:00
parent dfd38c99a0
commit 4db2d1a411
23 changed files with 2496 additions and 140 deletions
+5 -1
View File
@@ -163,9 +163,12 @@ def kb_index_html(
doc_url = f"/k/{token}/doc/{decrypt_token(doc.doc_token_encrypted) if doc.doc_token_encrypted else ''}" doc_url = f"/k/{token}/doc/{decrypt_token(doc.doc_token_encrypted) if doc.doc_token_encrypted else ''}"
keywords_html = f'<span class="keywords">关键词:{doc.keywords}</span>' if doc.keywords else "" keywords_html = f'<span class="keywords">关键词:{doc.keywords}</span>' if doc.keywords else ""
summary_html = f'<p class="summary">{doc.content_summary or ""}</p>' if doc.content_summary else "" summary_html = f'<p class="summary">{doc.content_summary or ""}</p>' if doc.content_summary else ""
status_badge = ""
if doc.status != "READY":
status_badge = f' <span class="status-badge">[{doc.status}]</span>'
doc_rows += f""" doc_rows += f"""
<div class="doc-item"> <div class="doc-item">
<h3><a href="{doc_url}">{doc.title or doc.original_filename}</a></h3> <h3><a href="{doc_url}">{doc.title or doc.original_filename}</a>{status_badge}</h3>
<p class="meta">类型:{doc.file_ext} | 更新:{doc.updated_at}</p> <p class="meta">类型:{doc.file_ext} | 更新:{doc.updated_at}</p>
{summary_html} {summary_html}
{keywords_html} {keywords_html}
@@ -195,6 +198,7 @@ def kb_index_html(
.summary {{ color: #444; font-size: 0.95em; margin: 5px 0; }} .summary {{ color: #444; font-size: 0.95em; margin: 5px 0; }}
.keywords {{ color: #888; font-size: 0.85em; }} .keywords {{ color: #888; font-size: 0.85em; }}
.pagination {{ color: #666; font-size: 0.9em; text-align: center; }} .pagination {{ color: #666; font-size: 0.9em; text-align: center; }}
.status-badge {{ color: #e67e22; font-size: 0.8em; font-weight: normal; }}
.footer {{ margin-top: 30px; padding-top: 15px; border-top: 1px solid #eee; color: #999; font-size: 0.85em; }} .footer {{ margin-top: 30px; padding-top: 15px; border-top: 1px solid #eee; color: #999; font-size: 0.85em; }}
</style> </style>
</head> </head>
+2 -2
View File
@@ -29,9 +29,9 @@ class KbPublicService:
def list_documents( def list_documents(
self, kb: KnowledgeBase, *, page: int = 1, page_size: int = 50 self, kb: KnowledgeBase, *, page: int = 1, page_size: int = 50
) -> tuple[list[Document], int]: ) -> tuple[list[Document], int]:
"""获取知识库的文档列表(仅 READY 状态)。""" """获取知识库的文档列表(排除 DELETED)。"""
return self._doc_repo.list_by_knowledge_base( return self._doc_repo.list_by_knowledge_base(
kb.id, page=page, page_size=page_size, status="READY" kb.id, page=page, page_size=page_size
) )
def get_document_by_token(self, kb: KnowledgeBase, doc_token: str) -> Document: def get_document_by_token(self, kb: KnowledgeBase, doc_token: str) -> Document:
+2 -2
View File
@@ -20,8 +20,8 @@ python-multipart>=0.0.7
# --- 文档解析 --- # --- 文档解析 ---
# MIME 嗅探:纯 PythonWindows 无需 libmagic DLL # MIME 嗅探:纯 PythonWindows 无需 libmagic DLL
filetype>=1.2 filetype>=1.2
# MarkItDownMIT # MarkItDownMIT—— 含 docx/pdf 支持
markitdown>=0.1.0 markitdown[docx,pdf]>=0.1.0
# PDF fallback: PyMuPDFAGPL-3.0 # PDF fallback: PyMuPDFAGPL-3.0
# TODO: 商品化前替换为 pypdfium2Apache-2.0/BSD),见 docs/technical-review.md R3 # TODO: 商品化前替换为 pypdfium2Apache-2.0/BSD),见 docs/technical-review.md R3
PyMuPDF>=1.24.0 PyMuPDF>=1.24.0
+1 -1
View File
@@ -23,7 +23,7 @@
| 11 | Markdown/TXT/JSON 输出 | ✅ 完成 | | 11 | Markdown/TXT/JSON 输出 | ✅ 完成 |
| 12 | 搜索 | ✅ 完成(LIKE 搜索,可升级 FTS5) | | 12 | 搜索 | ✅ 完成(LIKE 搜索,可升级 FTS5) |
| 13 | 安全 | 🔄 已实现:IDOR 防护、限流、XSS 防护、路径穿越防护、Session 安全 | | 13 | 安全 | 🔄 已实现:IDOR 防护、限流、XSS 防护、路径穿越防护、Session 安全 |
| 14 | 前端完善 | | | 14 | 前端完善 | ✅ 完成 |
| 15 | 测试 | 🔄 后端 51/51 通过;前端测试待 Phase 14 | | 15 | 测试 | 🔄 后端 51/51 通过;前端测试待 Phase 14 |
| 16 | Docker | 🔄 compose/Dockerfile 已写(Phase 1),待 Docker 环境验证 | | 16 | Docker | 🔄 compose/Dockerfile 已写(Phase 1),待 Docker 环境验证 |
| 17 | Ubuntu 部署文档 | ⬜ | | 17 | Ubuntu 部署文档 | ⬜ |
+13 -40
View File
@@ -9,7 +9,7 @@
"version": "0.1.0", "version": "0.1.0",
"dependencies": { "dependencies": {
"@element-plus/icons-vue": "^2.3", "@element-plus/icons-vue": "^2.3",
"axios": "^1.7", "axios": "^1.7.9",
"element-plus": "^2.9", "element-plus": "^2.9",
"pinia": "^2.2", "pinia": "^2.2",
"vue": "^3.5", "vue": "^3.5",
@@ -1809,18 +1809,6 @@
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
} }
}, },
"node_modules/agent-base": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
"integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
"license": "MIT",
"dependencies": {
"debug": "4"
},
"engines": {
"node": ">= 6.0.0"
}
},
"node_modules/ajv": { "node_modules/ajv": {
"version": "6.15.0", "version": "6.15.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz",
@@ -1881,15 +1869,14 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/axios": { "node_modules/axios": {
"version": "1.20.0", "version": "1.7.9",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.20.0.tgz", "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz",
"integrity": "sha512-r8aOh8j9cGKpgQAqpzrUHnSIc6a59Y3Xf/cv8sy1DrHCkZHzQGEuoq1tARk6qSyDdtQGSDgpb9kFlruzPvrgwg==", "integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"follow-redirects": "^1.16.0", "follow-redirects": "^1.15.6",
"form-data": "^4.0.6", "form-data": "^4.0.0",
"https-proxy-agent": "^5.0.1", "proxy-from-env": "^1.1.0"
"proxy-from-env": "^2.1.0"
} }
}, },
"node_modules/balanced-match": { "node_modules/balanced-match": {
@@ -2052,6 +2039,7 @@
"version": "4.4.3", "version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"ms": "^2.1.3" "ms": "^2.1.3"
@@ -2784,19 +2772,6 @@
"he": "bin/he" "he": "bin/he"
} }
}, },
"node_modules/https-proxy-agent": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
"integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
"license": "MIT",
"dependencies": {
"agent-base": "6",
"debug": "4"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/ignore": { "node_modules/ignore": {
"version": "7.0.8", "version": "7.0.8",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.8.tgz", "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.8.tgz",
@@ -3043,6 +3018,7 @@
"version": "2.1.3", "version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/muggle-string": { "node_modules/muggle-string": {
@@ -3296,13 +3272,10 @@
} }
}, },
"node_modules/proxy-from-env": { "node_modules/proxy-from-env": {
"version": "2.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
"license": "MIT", "license": "MIT"
"engines": {
"node": ">=10"
}
}, },
"node_modules/punycode": { "node_modules/punycode": {
"version": "2.3.1", "version": "2.3.1",
+12 -12
View File
@@ -11,23 +11,23 @@
"format": "prettier --write src/" "format": "prettier --write src/"
}, },
"dependencies": { "dependencies": {
"vue": "^3.5", "@element-plus/icons-vue": "^2.3",
"vue-router": "^4.4", "axios": "^1.7.9",
"pinia": "^2.2",
"element-plus": "^2.9", "element-plus": "^2.9",
"axios": "^1.7", "pinia": "^2.2",
"@element-plus/icons-vue": "^2.3" "vue": "^3.5",
"vue-router": "^4.4"
}, },
"devDependencies": { "devDependencies": {
"@vitejs/plugin-vue": "^5.2",
"vite": "^6.1",
"typescript": "^5.7",
"vue-tsc": "^2.2",
"@types/node": "^22.0", "@types/node": "^22.0",
"eslint": "^9.0",
"eslint-plugin-vue": "^9.28",
"@typescript-eslint/eslint-plugin": "^8.0", "@typescript-eslint/eslint-plugin": "^8.0",
"@typescript-eslint/parser": "^8.0", "@typescript-eslint/parser": "^8.0",
"prettier": "^3.4" "@vitejs/plugin-vue": "^5.2",
"eslint": "^9.0",
"eslint-plugin-vue": "^9.28",
"prettier": "^3.4",
"typescript": "^5.7",
"vite": "^6.1",
"vue-tsc": "^2.2"
} }
} }
+71
View File
@@ -0,0 +1,71 @@
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { useUserStore } from '@/stores/user'
import apiClient from '@/api/client'
const router = useRouter()
const userStore = useUserStore()
const isCollapsed = 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')
}
}
})
async function handleLogout() {
try {
await apiClient.post('/auth/logout')
} catch { /* ignore */ }
userStore.clearUser()
router.push('/login')
}
</script>
<template>
<el-container style="min-height: 100vh">
<el-aside :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-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>
</el-menu-item>
<el-menu-item index="/knowledge-bases">
<el-icon><svg viewBox="0 0 24 24" fill="currentColor"><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"/></svg></el-icon>
<template #title>知识库</template>
</el-menu-item>
<el-menu-item index="/settings">
<el-icon><svg viewBox="0 0 24 24" fill="currentColor"><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"/></svg></el-icon>
<template #title>设置</template>
</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>
</div>
</el-header>
<el-main style="padding: 20px">
<router-view />
</el-main>
</el-container>
</el-container>
</template>
+263
View File
@@ -0,0 +1,263 @@
import { ref, onMounted } from 'vue';
import { useRouter } from 'vue-router';
import { useUserStore } from '@/stores/user';
import apiClient from '@/api/client';
const router = useRouter();
const userStore = useUserStore();
const isCollapsed = 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');
}
}
});
async function handleLogout() {
try {
await apiClient.post('/auth/logout');
}
catch { /* ignore */ }
userStore.clearUser();
router.push('/login');
}
debugger; /* PartiallyEnd: #3632/scriptSetup.vue */
const __VLS_ctx = {};
let __VLS_components;
let __VLS_directives;
const __VLS_0 = {}.ElContainer;
/** @type {[typeof __VLS_components.ElContainer, typeof __VLS_components.elContainer, typeof __VLS_components.ElContainer, typeof __VLS_components.elContainer, ]} */ ;
// @ts-ignore
const __VLS_1 = __VLS_asFunctionalComponent(__VLS_0, new __VLS_0({
...{ style: {} },
}));
const __VLS_2 = __VLS_1({
...{ style: {} },
}, ...__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)({});
}
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
const __VLS_38 = __VLS_asFunctionalComponent(__VLS_37, new __VLS_37({}));
const __VLS_39 = __VLS_38({}, ...__VLS_functionalComponentArgsRest(__VLS_38));
__VLS_40.slots.default;
const __VLS_41 = {}.ElHeader;
/** @type {[typeof __VLS_components.ElHeader, typeof __VLS_components.elHeader, typeof __VLS_components.ElHeader, typeof __VLS_components.elHeader, ]} */ ;
// @ts-ignore
const __VLS_42 = __VLS_asFunctionalComponent(__VLS_41, new __VLS_41({
...{ style: {} },
}));
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)({});
(__VLS_ctx.userStore.username);
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': {} },
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({
...{ style: {} },
}));
const __VLS_63 = __VLS_62({
...{ style: {} },
}, ...__VLS_functionalComponentArgsRest(__VLS_62));
__VLS_64.slots.default;
const __VLS_65 = {}.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;
var __VLS_40;
var __VLS_3;
var __VLS_dollars;
const __VLS_self = (await import('vue')).defineComponent({
setup() {
return {
router: router,
userStore: userStore,
isCollapsed: isCollapsed,
handleLogout: handleLogout,
};
},
});
export default (await import('vue')).defineComponent({
setup() {
return {};
},
});
; /* PartiallyEnd: #4569/main.vue */
+9 -3
View File
@@ -14,24 +14,30 @@ const router = createRouter({
}, },
{ {
path: '/', path: '/',
component: () => import('@/layouts/DefaultLayout.vue'),
children: [
{
path: '',
name: 'Dashboard', name: 'Dashboard',
component: () => import('@/views/Dashboard.vue'), component: () => import('@/views/Dashboard.vue'),
}, },
{ {
path: '/knowledge-bases', path: 'knowledge-bases',
name: 'KnowledgeBases', name: 'KnowledgeBases',
component: () => import('@/views/KnowledgeBases.vue'), component: () => import('@/views/KnowledgeBases.vue'),
}, },
{ {
path: '/knowledge-bases/:id', path: 'knowledge-bases/:id',
name: 'KbDetail', name: 'KbDetail',
component: () => import('@/views/KbDetail.vue'), component: () => import('@/views/KbDetail.vue'),
}, },
{ {
path: '/settings', path: 'settings',
name: 'Settings', name: 'Settings',
component: () => import('@/views/Settings.vue'), component: () => import('@/views/Settings.vue'),
}, },
], ],
},
],
}); });
export default router; export default router;
+9 -3
View File
@@ -15,25 +15,31 @@ const router = createRouter({
}, },
{ {
path: '/', path: '/',
component: () => import('@/layouts/DefaultLayout.vue'),
children: [
{
path: '',
name: 'Dashboard', name: 'Dashboard',
component: () => import('@/views/Dashboard.vue'), component: () => import('@/views/Dashboard.vue'),
}, },
{ {
path: '/knowledge-bases', path: 'knowledge-bases',
name: 'KnowledgeBases', name: 'KnowledgeBases',
component: () => import('@/views/KnowledgeBases.vue'), component: () => import('@/views/KnowledgeBases.vue'),
}, },
{ {
path: '/knowledge-bases/:id', path: 'knowledge-bases/:id',
name: 'KbDetail', name: 'KbDetail',
component: () => import('@/views/KbDetail.vue'), component: () => import('@/views/KbDetail.vue'),
}, },
{ {
path: '/settings', path: 'settings',
name: 'Settings', name: 'Settings',
component: () => import('@/views/Settings.vue'), component: () => import('@/views/Settings.vue'),
}, },
], ],
},
],
}) })
export default router export default router
+80 -3
View File
@@ -1,10 +1,87 @@
<script setup lang="ts"> <script setup lang="ts">
// Phase 14 完善仪表盘 import { ref, onMounted } from 'vue'
import apiClient from '@/api/client'
const stats = ref({
kb_count: 0,
doc_count: 0,
storage_used_mb: 0,
storage_quota_mb: 100,
})
onMounted(async () => {
try {
const [kbRes, storageRes] = await Promise.all([
apiClient.get('/knowledge-bases'),
apiClient.get('/auth/storage'),
])
stats.value.kb_count = kbRes.data.total
stats.value.storage_used_mb = storageRes.data.storage_used_mb
stats.value.storage_quota_mb = storageRes.data.storage_quota_mb
} catch { /* ignore */ }
})
</script> </script>
<template> <template>
<div class="dashboard-page"> <div>
<h1>仪表盘</h1> <h1>仪表盘</h1>
<p>DashboardPhase 14 完善</p> <el-row :gutter="20" style="margin-bottom: 20px">
<el-col :span="8">
<el-card>
<div class="stat-item">
<div class="stat-value">{{ stats.kb_count }}</div>
<div class="stat-label">知识库</div>
</div>
</el-card>
</el-col>
<el-col :span="8">
<el-card>
<div class="stat-item">
<div class="stat-value">{{ stats.storage_used_mb }} MB</div>
<div class="stat-label">已用空间 / {{ stats.storage_quota_mb }} MB</div>
</div>
</el-card>
</el-col>
<el-col :span="8">
<el-card>
<div class="stat-item">
<el-progress
:percentage="Math.min(100, Math.round(stats.storage_used_mb / stats.storage_quota_mb * 100))"
:stroke-width="20"
:text-inside="true"
/>
<div class="stat-label">存储用量</div>
</div>
</el-card>
</el-col>
</el-row>
<el-card>
<template #header>
<div style="display: flex; justify-content: space-between; align-items: center">
<span>快速开始</span>
</div> </div>
</template> </template>
<el-steps :active="0" direction="vertical">
<el-step title="创建知识库" description="点击左侧「知识库」菜单,创建你的第一个知识库" />
<el-step title="上传文档" description="支持 .docx 和 .pdf 文件" />
<el-step title="分享 AI 链接" description="复制知识库的 AI 专属链接,发送给任意 AI" />
</el-steps>
</el-card>
</div>
</template>
<style scoped>
.stat-item {
text-align: center;
padding: 10px 0;
}
.stat-value {
font-size: 28px;
font-weight: bold;
color: #409eff;
}
.stat-label {
color: #999;
margin-top: 8px;
}
</style>
+201 -6
View File
@@ -1,17 +1,212 @@
import { ref, onMounted } from 'vue';
import apiClient from '@/api/client';
const stats = ref({
kb_count: 0,
doc_count: 0,
storage_used_mb: 0,
storage_quota_mb: 100,
});
onMounted(async () => {
try {
const [kbRes, storageRes] = await Promise.all([
apiClient.get('/knowledge-bases'),
apiClient.get('/auth/storage'),
]);
stats.value.kb_count = kbRes.data.total;
stats.value.storage_used_mb = storageRes.data.storage_used_mb;
stats.value.storage_quota_mb = storageRes.data.storage_quota_mb;
}
catch { /* ignore */ }
});
debugger; /* PartiallyEnd: #3632/scriptSetup.vue */ debugger; /* PartiallyEnd: #3632/scriptSetup.vue */
const __VLS_ctx = {}; const __VLS_ctx = {};
let __VLS_components; let __VLS_components;
let __VLS_directives; let __VLS_directives;
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({ // CSS variable injection
...{ class: "dashboard-page" }, // CSS variable injection end
}); __VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({});
__VLS_asFunctionalElement(__VLS_intrinsicElements.h1, __VLS_intrinsicElements.h1)({}); __VLS_asFunctionalElement(__VLS_intrinsicElements.h1, __VLS_intrinsicElements.h1)({});
__VLS_asFunctionalElement(__VLS_intrinsicElements.p, __VLS_intrinsicElements.p)({}); const __VLS_0 = {}.ElRow;
/** @type {__VLS_StyleScopedClasses['dashboard-page']} */ ; /** @type {[typeof __VLS_components.ElRow, typeof __VLS_components.elRow, typeof __VLS_components.ElRow, typeof __VLS_components.elRow, ]} */ ;
// @ts-ignore
const __VLS_1 = __VLS_asFunctionalComponent(__VLS_0, new __VLS_0({
gutter: (20),
...{ style: {} },
}));
const __VLS_2 = __VLS_1({
gutter: (20),
...{ style: {} },
}, ...__VLS_functionalComponentArgsRest(__VLS_1));
__VLS_3.slots.default;
const __VLS_4 = {}.ElCol;
/** @type {[typeof __VLS_components.ElCol, typeof __VLS_components.elCol, typeof __VLS_components.ElCol, typeof __VLS_components.elCol, ]} */ ;
// @ts-ignore
const __VLS_5 = __VLS_asFunctionalComponent(__VLS_4, new __VLS_4({
span: (8),
}));
const __VLS_6 = __VLS_5({
span: (8),
}, ...__VLS_functionalComponentArgsRest(__VLS_5));
__VLS_7.slots.default;
const __VLS_8 = {}.ElCard;
/** @type {[typeof __VLS_components.ElCard, typeof __VLS_components.elCard, typeof __VLS_components.ElCard, typeof __VLS_components.elCard, ]} */ ;
// @ts-ignore
const __VLS_9 = __VLS_asFunctionalComponent(__VLS_8, new __VLS_8({}));
const __VLS_10 = __VLS_9({}, ...__VLS_functionalComponentArgsRest(__VLS_9));
__VLS_11.slots.default;
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "stat-item" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "stat-value" },
});
(__VLS_ctx.stats.kb_count);
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "stat-label" },
});
var __VLS_11;
var __VLS_7;
const __VLS_12 = {}.ElCol;
/** @type {[typeof __VLS_components.ElCol, typeof __VLS_components.elCol, typeof __VLS_components.ElCol, typeof __VLS_components.elCol, ]} */ ;
// @ts-ignore
const __VLS_13 = __VLS_asFunctionalComponent(__VLS_12, new __VLS_12({
span: (8),
}));
const __VLS_14 = __VLS_13({
span: (8),
}, ...__VLS_functionalComponentArgsRest(__VLS_13));
__VLS_15.slots.default;
const __VLS_16 = {}.ElCard;
/** @type {[typeof __VLS_components.ElCard, typeof __VLS_components.elCard, typeof __VLS_components.ElCard, typeof __VLS_components.elCard, ]} */ ;
// @ts-ignore
const __VLS_17 = __VLS_asFunctionalComponent(__VLS_16, new __VLS_16({}));
const __VLS_18 = __VLS_17({}, ...__VLS_functionalComponentArgsRest(__VLS_17));
__VLS_19.slots.default;
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "stat-item" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "stat-value" },
});
(__VLS_ctx.stats.storage_used_mb);
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "stat-label" },
});
(__VLS_ctx.stats.storage_quota_mb);
var __VLS_19;
var __VLS_15;
const __VLS_20 = {}.ElCol;
/** @type {[typeof __VLS_components.ElCol, typeof __VLS_components.elCol, typeof __VLS_components.ElCol, typeof __VLS_components.elCol, ]} */ ;
// @ts-ignore
const __VLS_21 = __VLS_asFunctionalComponent(__VLS_20, new __VLS_20({
span: (8),
}));
const __VLS_22 = __VLS_21({
span: (8),
}, ...__VLS_functionalComponentArgsRest(__VLS_21));
__VLS_23.slots.default;
const __VLS_24 = {}.ElCard;
/** @type {[typeof __VLS_components.ElCard, typeof __VLS_components.elCard, typeof __VLS_components.ElCard, typeof __VLS_components.elCard, ]} */ ;
// @ts-ignore
const __VLS_25 = __VLS_asFunctionalComponent(__VLS_24, new __VLS_24({}));
const __VLS_26 = __VLS_25({}, ...__VLS_functionalComponentArgsRest(__VLS_25));
__VLS_27.slots.default;
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "stat-item" },
});
const __VLS_28 = {}.ElProgress;
/** @type {[typeof __VLS_components.ElProgress, typeof __VLS_components.elProgress, ]} */ ;
// @ts-ignore
const __VLS_29 = __VLS_asFunctionalComponent(__VLS_28, new __VLS_28({
percentage: (Math.min(100, Math.round(__VLS_ctx.stats.storage_used_mb / __VLS_ctx.stats.storage_quota_mb * 100))),
strokeWidth: (20),
textInside: (true),
}));
const __VLS_30 = __VLS_29({
percentage: (Math.min(100, Math.round(__VLS_ctx.stats.storage_used_mb / __VLS_ctx.stats.storage_quota_mb * 100))),
strokeWidth: (20),
textInside: (true),
}, ...__VLS_functionalComponentArgsRest(__VLS_29));
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "stat-label" },
});
var __VLS_27;
var __VLS_23;
var __VLS_3;
const __VLS_32 = {}.ElCard;
/** @type {[typeof __VLS_components.ElCard, typeof __VLS_components.elCard, typeof __VLS_components.ElCard, typeof __VLS_components.elCard, ]} */ ;
// @ts-ignore
const __VLS_33 = __VLS_asFunctionalComponent(__VLS_32, new __VLS_32({}));
const __VLS_34 = __VLS_33({}, ...__VLS_functionalComponentArgsRest(__VLS_33));
__VLS_35.slots.default;
{
const { header: __VLS_thisSlot } = __VLS_35.slots;
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ style: {} },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({});
}
const __VLS_36 = {}.ElSteps;
/** @type {[typeof __VLS_components.ElSteps, typeof __VLS_components.elSteps, typeof __VLS_components.ElSteps, typeof __VLS_components.elSteps, ]} */ ;
// @ts-ignore
const __VLS_37 = __VLS_asFunctionalComponent(__VLS_36, new __VLS_36({
active: (0),
direction: "vertical",
}));
const __VLS_38 = __VLS_37({
active: (0),
direction: "vertical",
}, ...__VLS_functionalComponentArgsRest(__VLS_37));
__VLS_39.slots.default;
const __VLS_40 = {}.ElStep;
/** @type {[typeof __VLS_components.ElStep, typeof __VLS_components.elStep, ]} */ ;
// @ts-ignore
const __VLS_41 = __VLS_asFunctionalComponent(__VLS_40, new __VLS_40({
title: "创建知识库",
description: "点击左侧「知识库」菜单,创建你的第一个知识库",
}));
const __VLS_42 = __VLS_41({
title: "创建知识库",
description: "点击左侧「知识库」菜单,创建你的第一个知识库",
}, ...__VLS_functionalComponentArgsRest(__VLS_41));
const __VLS_44 = {}.ElStep;
/** @type {[typeof __VLS_components.ElStep, typeof __VLS_components.elStep, ]} */ ;
// @ts-ignore
const __VLS_45 = __VLS_asFunctionalComponent(__VLS_44, new __VLS_44({
title: "上传文档",
description: "支持 .docx 和 .pdf 文件",
}));
const __VLS_46 = __VLS_45({
title: "上传文档",
description: "支持 .docx 和 .pdf 文件",
}, ...__VLS_functionalComponentArgsRest(__VLS_45));
const __VLS_48 = {}.ElStep;
/** @type {[typeof __VLS_components.ElStep, typeof __VLS_components.elStep, ]} */ ;
// @ts-ignore
const __VLS_49 = __VLS_asFunctionalComponent(__VLS_48, new __VLS_48({
title: "分享 AI 链接",
description: "复制知识库的 AI 专属链接,发送给任意 AI",
}));
const __VLS_50 = __VLS_49({
title: "分享 AI 链接",
description: "复制知识库的 AI 专属链接,发送给任意 AI",
}, ...__VLS_functionalComponentArgsRest(__VLS_49));
var __VLS_39;
var __VLS_35;
/** @type {__VLS_StyleScopedClasses['stat-item']} */ ;
/** @type {__VLS_StyleScopedClasses['stat-value']} */ ;
/** @type {__VLS_StyleScopedClasses['stat-label']} */ ;
/** @type {__VLS_StyleScopedClasses['stat-item']} */ ;
/** @type {__VLS_StyleScopedClasses['stat-value']} */ ;
/** @type {__VLS_StyleScopedClasses['stat-label']} */ ;
/** @type {__VLS_StyleScopedClasses['stat-item']} */ ;
/** @type {__VLS_StyleScopedClasses['stat-label']} */ ;
var __VLS_dollars; var __VLS_dollars;
const __VLS_self = (await import('vue')).defineComponent({ const __VLS_self = (await import('vue')).defineComponent({
setup() { setup() {
return {}; return {
stats: stats,
};
}, },
}); });
export default (await import('vue')).defineComponent({ export default (await import('vue')).defineComponent({
+158 -4
View File
@@ -1,10 +1,164 @@
<script setup lang="ts"> <script setup lang="ts">
// Phase 4 实现知识库详情 import { ref, onMounted } from 'vue'
import { useRoute } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
import apiClient from '@/api/client'
const route = useRoute()
const kbId = route.params.id as string
const kb = ref<any>(null)
const docs = ref<any[]>([])
const loading = ref(false)
const uploading = ref(false)
const aiUrl = ref('')
onMounted(async () => {
await loadKb()
await loadDocs()
await loadLink()
})
async function loadKb() {
try {
const { data } = await apiClient.get(`/knowledge-bases/${kbId}`)
kb.value = data
} catch { /* ignore */ }
}
async function loadDocs() {
loading.value = true
try {
const { data } = await apiClient.get(`/documents?kb_id=${kbId}`)
docs.value = data.items
} catch { /* ignore */ }
loading.value = false
}
async function loadLink() {
try {
const { data } = await apiClient.get(`/knowledge-bases/${kbId}/link`)
aiUrl.value = `${window.location.origin}/k/${data.token}`
} catch { /* ignore */ }
}
async function handleUpload(options: any) {
uploading.value = true
try {
const formData = new FormData()
formData.append('kb_id', kbId)
formData.append('file', options.file)
await apiClient.post('/documents/upload', formData, {
headers: { 'Content-Type': 'multipart/form-data' },
})
ElMessage.success('文档上传成功!')
loadDocs()
} catch { /* ignore */ }
uploading.value = false
}
async function handleDeleteDoc(doc: any) {
try {
await ElMessageBox.confirm(`确定删除文档「${doc.original_filename}」?`, '确认删除', { type: 'warning' })
await apiClient.delete(`/documents/${doc.id}`)
ElMessage.success('已删除。')
loadDocs()
} catch { /* ignore */ }
}
async function handleReprocess(doc: any) {
try {
await apiClient.post(`/documents/${doc.id}/reprocess`)
ElMessage.success('重新解析已提交。')
loadDocs()
} catch { /* ignore */ }
}
async function handleCopyLink() {
try {
await navigator.clipboard.writeText(aiUrl.value)
ElMessage.success('AI 链接已复制!')
} catch { /* ignore */ }
}
async function handleRegenerate() {
try {
await ElMessageBox.confirm('重新生成链接后,旧链接将立即失效。确定继续?', '确认', { type: 'warning' })
const { data } = await apiClient.post(`/knowledge-bases/${kbId}/regenerate-token`)
aiUrl.value = `${window.location.origin}/k/${data.token}`
ElMessage.success('链接已重新生成。')
} catch { /* ignore */ }
}
function formatSize(bytes: number) {
if (bytes < 1024) return bytes + ' B'
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB'
return (bytes / (1024 * 1024)).toFixed(1) + ' MB'
}
function statusType(status: string) {
if (status === 'READY') return 'success'
if (status === 'FAILED') return 'danger'
return 'warning'
}
</script> </script>
<template> <template>
<div> <div v-if="kb">
<h1>知识库详情</h1> <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px">
<p>知识库详情Phase 4 实现</p> <h1 style="margin: 0">{{ kb.name }}</h1>
<el-button type="primary" @click="handleCopyLink">复制 AI 链接</el-button>
</div>
<el-card style="margin-bottom: 20px">
<el-descriptions :column="2" border>
<el-descriptions-item label="描述">{{ kb.description || '无' }}</el-descriptions-item>
<el-descriptions-item label="状态">
<el-tag :type="kb.enabled ? 'success' : 'danger'">{{ kb.enabled ? '启用' : '禁用' }}</el-tag>
</el-descriptions-item>
<el-descriptions-item label="AI 链接">
<div style="display: flex; align-items: center; gap: 8px">
<el-input :model-value="aiUrl" readonly size="small" style="flex: 1" />
<el-button size="small" @click="handleRegenerate">重新生成</el-button>
</div>
</el-descriptions-item>
<el-descriptions-item label="文档数">{{ docs.length }}</el-descriptions-item>
</el-descriptions>
</el-card>
<el-card>
<template #header>
<div style="display: flex; justify-content: space-between; align-items: center">
<span>文档列表</span>
<el-upload
:show-file-list="false"
:http-request="handleUpload"
accept=".docx,.pdf"
>
<el-button type="primary" :loading="uploading">上传文档</el-button>
</el-upload>
</div>
</template>
<el-table :data="docs" v-loading="loading" style="width: 100%">
<el-table-column prop="original_filename" label="文件名" min-width="200" show-overflow-tooltip />
<el-table-column label="状态" width="120" 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="100" align="center">
<template #default="{ row }">{{ formatSize(row.file_size) }}</template>
</el-table-column>
<el-table-column prop="title" label="标题" min-width="150" show-overflow-tooltip />
<el-table-column prop="keywords" label="关键词" min-width="150" show-overflow-tooltip />
<el-table-column label="操作" width="200" 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>
</el-card>
</div> </div>
</template> </template>
+483 -3
View File
@@ -1,14 +1,494 @@
import { ref, onMounted } from 'vue';
import { useRoute } from 'vue-router';
import { ElMessage, ElMessageBox } from 'element-plus';
import apiClient from '@/api/client';
const route = useRoute();
const kbId = route.params.id;
const kb = ref(null);
const docs = ref([]);
const loading = ref(false);
const uploading = ref(false);
const aiUrl = ref('');
onMounted(async () => {
await loadKb();
await loadDocs();
await loadLink();
});
async function loadKb() {
try {
const { data } = await apiClient.get(`/knowledge-bases/${kbId}`);
kb.value = data;
}
catch { /* ignore */ }
}
async function loadDocs() {
loading.value = true;
try {
const { data } = await apiClient.get(`/documents?kb_id=${kbId}`);
docs.value = data.items;
}
catch { /* ignore */ }
loading.value = false;
}
async function loadLink() {
try {
const { data } = await apiClient.get(`/knowledge-bases/${kbId}/link`);
aiUrl.value = `${window.location.origin}/k/${data.token}`;
}
catch { /* ignore */ }
}
async function handleUpload(options) {
uploading.value = true;
try {
const formData = new FormData();
formData.append('kb_id', kbId);
formData.append('file', options.file);
await apiClient.post('/documents/upload', formData, {
headers: { 'Content-Type': 'multipart/form-data' },
});
ElMessage.success('文档上传成功!');
loadDocs();
}
catch { /* ignore */ }
uploading.value = false;
}
async function handleDeleteDoc(doc) {
try {
await ElMessageBox.confirm(`确定删除文档「${doc.original_filename}」?`, '确认删除', { type: 'warning' });
await apiClient.delete(`/documents/${doc.id}`);
ElMessage.success('已删除。');
loadDocs();
}
catch { /* ignore */ }
}
async function handleReprocess(doc) {
try {
await apiClient.post(`/documents/${doc.id}/reprocess`);
ElMessage.success('重新解析已提交。');
loadDocs();
}
catch { /* ignore */ }
}
async function handleCopyLink() {
try {
await navigator.clipboard.writeText(aiUrl.value);
ElMessage.success('AI 链接已复制!');
}
catch { /* ignore */ }
}
async function handleRegenerate() {
try {
await ElMessageBox.confirm('重新生成链接后,旧链接将立即失效。确定继续?', '确认', { type: 'warning' });
const { data } = await apiClient.post(`/knowledge-bases/${kbId}/regenerate-token`);
aiUrl.value = `${window.location.origin}/k/${data.token}`;
ElMessage.success('链接已重新生成。');
}
catch { /* ignore */ }
}
function formatSize(bytes) {
if (bytes < 1024)
return bytes + ' B';
if (bytes < 1024 * 1024)
return (bytes / 1024).toFixed(1) + ' KB';
return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
}
function statusType(status) {
if (status === 'READY')
return 'success';
if (status === 'FAILED')
return 'danger';
return 'warning';
}
debugger; /* PartiallyEnd: #3632/scriptSetup.vue */ debugger; /* PartiallyEnd: #3632/scriptSetup.vue */
const __VLS_ctx = {}; const __VLS_ctx = {};
let __VLS_components; let __VLS_components;
let __VLS_directives; let __VLS_directives;
if (__VLS_ctx.kb) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({}); __VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({});
__VLS_asFunctionalElement(__VLS_intrinsicElements.h1, __VLS_intrinsicElements.h1)({}); __VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
__VLS_asFunctionalElement(__VLS_intrinsicElements.p, __VLS_intrinsicElements.p)({}); ...{ style: {} },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.h1, __VLS_intrinsicElements.h1)({
...{ style: {} },
});
(__VLS_ctx.kb.name);
const __VLS_0 = {}.ElButton;
/** @type {[typeof __VLS_components.ElButton, typeof __VLS_components.elButton, typeof __VLS_components.ElButton, typeof __VLS_components.elButton, ]} */ ;
// @ts-ignore
const __VLS_1 = __VLS_asFunctionalComponent(__VLS_0, new __VLS_0({
...{ 'onClick': {} },
type: "primary",
}));
const __VLS_2 = __VLS_1({
...{ 'onClick': {} },
type: "primary",
}, ...__VLS_functionalComponentArgsRest(__VLS_1));
let __VLS_4;
let __VLS_5;
let __VLS_6;
const __VLS_7 = {
onClick: (__VLS_ctx.handleCopyLink)
};
__VLS_3.slots.default;
var __VLS_3;
const __VLS_8 = {}.ElCard;
/** @type {[typeof __VLS_components.ElCard, typeof __VLS_components.elCard, typeof __VLS_components.ElCard, typeof __VLS_components.elCard, ]} */ ;
// @ts-ignore
const __VLS_9 = __VLS_asFunctionalComponent(__VLS_8, new __VLS_8({
...{ style: {} },
}));
const __VLS_10 = __VLS_9({
...{ style: {} },
}, ...__VLS_functionalComponentArgsRest(__VLS_9));
__VLS_11.slots.default;
const __VLS_12 = {}.ElDescriptions;
/** @type {[typeof __VLS_components.ElDescriptions, typeof __VLS_components.elDescriptions, typeof __VLS_components.ElDescriptions, typeof __VLS_components.elDescriptions, ]} */ ;
// @ts-ignore
const __VLS_13 = __VLS_asFunctionalComponent(__VLS_12, new __VLS_12({
column: (2),
border: true,
}));
const __VLS_14 = __VLS_13({
column: (2),
border: true,
}, ...__VLS_functionalComponentArgsRest(__VLS_13));
__VLS_15.slots.default;
const __VLS_16 = {}.ElDescriptionsItem;
/** @type {[typeof __VLS_components.ElDescriptionsItem, typeof __VLS_components.elDescriptionsItem, typeof __VLS_components.ElDescriptionsItem, typeof __VLS_components.elDescriptionsItem, ]} */ ;
// @ts-ignore
const __VLS_17 = __VLS_asFunctionalComponent(__VLS_16, new __VLS_16({
label: "描述",
}));
const __VLS_18 = __VLS_17({
label: "描述",
}, ...__VLS_functionalComponentArgsRest(__VLS_17));
__VLS_19.slots.default;
(__VLS_ctx.kb.description || '无');
var __VLS_19;
const __VLS_20 = {}.ElDescriptionsItem;
/** @type {[typeof __VLS_components.ElDescriptionsItem, typeof __VLS_components.elDescriptionsItem, typeof __VLS_components.ElDescriptionsItem, typeof __VLS_components.elDescriptionsItem, ]} */ ;
// @ts-ignore
const __VLS_21 = __VLS_asFunctionalComponent(__VLS_20, new __VLS_20({
label: "状态",
}));
const __VLS_22 = __VLS_21({
label: "状态",
}, ...__VLS_functionalComponentArgsRest(__VLS_21));
__VLS_23.slots.default;
const __VLS_24 = {}.ElTag;
/** @type {[typeof __VLS_components.ElTag, typeof __VLS_components.elTag, typeof __VLS_components.ElTag, typeof __VLS_components.elTag, ]} */ ;
// @ts-ignore
const __VLS_25 = __VLS_asFunctionalComponent(__VLS_24, new __VLS_24({
type: (__VLS_ctx.kb.enabled ? 'success' : 'danger'),
}));
const __VLS_26 = __VLS_25({
type: (__VLS_ctx.kb.enabled ? 'success' : 'danger'),
}, ...__VLS_functionalComponentArgsRest(__VLS_25));
__VLS_27.slots.default;
(__VLS_ctx.kb.enabled ? '启用' : '禁用');
var __VLS_27;
var __VLS_23;
const __VLS_28 = {}.ElDescriptionsItem;
/** @type {[typeof __VLS_components.ElDescriptionsItem, typeof __VLS_components.elDescriptionsItem, typeof __VLS_components.ElDescriptionsItem, typeof __VLS_components.elDescriptionsItem, ]} */ ;
// @ts-ignore
const __VLS_29 = __VLS_asFunctionalComponent(__VLS_28, new __VLS_28({
label: "AI 链接",
}));
const __VLS_30 = __VLS_29({
label: "AI 链接",
}, ...__VLS_functionalComponentArgsRest(__VLS_29));
__VLS_31.slots.default;
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ style: {} },
});
const __VLS_32 = {}.ElInput;
/** @type {[typeof __VLS_components.ElInput, typeof __VLS_components.elInput, ]} */ ;
// @ts-ignore
const __VLS_33 = __VLS_asFunctionalComponent(__VLS_32, new __VLS_32({
modelValue: (__VLS_ctx.aiUrl),
readonly: true,
size: "small",
...{ style: {} },
}));
const __VLS_34 = __VLS_33({
modelValue: (__VLS_ctx.aiUrl),
readonly: true,
size: "small",
...{ style: {} },
}, ...__VLS_functionalComponentArgsRest(__VLS_33));
const __VLS_36 = {}.ElButton;
/** @type {[typeof __VLS_components.ElButton, typeof __VLS_components.elButton, typeof __VLS_components.ElButton, typeof __VLS_components.elButton, ]} */ ;
// @ts-ignore
const __VLS_37 = __VLS_asFunctionalComponent(__VLS_36, new __VLS_36({
...{ 'onClick': {} },
size: "small",
}));
const __VLS_38 = __VLS_37({
...{ 'onClick': {} },
size: "small",
}, ...__VLS_functionalComponentArgsRest(__VLS_37));
let __VLS_40;
let __VLS_41;
let __VLS_42;
const __VLS_43 = {
onClick: (__VLS_ctx.handleRegenerate)
};
__VLS_39.slots.default;
var __VLS_39;
var __VLS_31;
const __VLS_44 = {}.ElDescriptionsItem;
/** @type {[typeof __VLS_components.ElDescriptionsItem, typeof __VLS_components.elDescriptionsItem, typeof __VLS_components.ElDescriptionsItem, typeof __VLS_components.elDescriptionsItem, ]} */ ;
// @ts-ignore
const __VLS_45 = __VLS_asFunctionalComponent(__VLS_44, new __VLS_44({
label: "文档数",
}));
const __VLS_46 = __VLS_45({
label: "文档数",
}, ...__VLS_functionalComponentArgsRest(__VLS_45));
__VLS_47.slots.default;
(__VLS_ctx.docs.length);
var __VLS_47;
var __VLS_15;
var __VLS_11;
const __VLS_48 = {}.ElCard;
/** @type {[typeof __VLS_components.ElCard, typeof __VLS_components.elCard, typeof __VLS_components.ElCard, typeof __VLS_components.elCard, ]} */ ;
// @ts-ignore
const __VLS_49 = __VLS_asFunctionalComponent(__VLS_48, new __VLS_48({}));
const __VLS_50 = __VLS_49({}, ...__VLS_functionalComponentArgsRest(__VLS_49));
__VLS_51.slots.default;
{
const { header: __VLS_thisSlot } = __VLS_51.slots;
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ style: {} },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({});
const __VLS_52 = {}.ElUpload;
/** @type {[typeof __VLS_components.ElUpload, typeof __VLS_components.elUpload, typeof __VLS_components.ElUpload, typeof __VLS_components.elUpload, ]} */ ;
// @ts-ignore
const __VLS_53 = __VLS_asFunctionalComponent(__VLS_52, new __VLS_52({
showFileList: (false),
httpRequest: (__VLS_ctx.handleUpload),
accept: ".docx,.pdf",
}));
const __VLS_54 = __VLS_53({
showFileList: (false),
httpRequest: (__VLS_ctx.handleUpload),
accept: ".docx,.pdf",
}, ...__VLS_functionalComponentArgsRest(__VLS_53));
__VLS_55.slots.default;
const __VLS_56 = {}.ElButton;
/** @type {[typeof __VLS_components.ElButton, typeof __VLS_components.elButton, typeof __VLS_components.ElButton, typeof __VLS_components.elButton, ]} */ ;
// @ts-ignore
const __VLS_57 = __VLS_asFunctionalComponent(__VLS_56, new __VLS_56({
type: "primary",
loading: (__VLS_ctx.uploading),
}));
const __VLS_58 = __VLS_57({
type: "primary",
loading: (__VLS_ctx.uploading),
}, ...__VLS_functionalComponentArgsRest(__VLS_57));
__VLS_59.slots.default;
var __VLS_59;
var __VLS_55;
}
const __VLS_60 = {}.ElTable;
/** @type {[typeof __VLS_components.ElTable, typeof __VLS_components.elTable, typeof __VLS_components.ElTable, typeof __VLS_components.elTable, ]} */ ;
// @ts-ignore
const __VLS_61 = __VLS_asFunctionalComponent(__VLS_60, new __VLS_60({
data: (__VLS_ctx.docs),
...{ style: {} },
}));
const __VLS_62 = __VLS_61({
data: (__VLS_ctx.docs),
...{ style: {} },
}, ...__VLS_functionalComponentArgsRest(__VLS_61));
__VLS_asFunctionalDirective(__VLS_directives.vLoading)(null, { ...__VLS_directiveBindingRestFields, value: (__VLS_ctx.loading) }, null, null);
__VLS_63.slots.default;
const __VLS_64 = {}.ElTableColumn;
/** @type {[typeof __VLS_components.ElTableColumn, typeof __VLS_components.elTableColumn, ]} */ ;
// @ts-ignore
const __VLS_65 = __VLS_asFunctionalComponent(__VLS_64, new __VLS_64({
prop: "original_filename",
label: "文件名",
minWidth: "200",
showOverflowTooltip: true,
}));
const __VLS_66 = __VLS_65({
prop: "original_filename",
label: "文件名",
minWidth: "200",
showOverflowTooltip: true,
}, ...__VLS_functionalComponentArgsRest(__VLS_65));
const __VLS_68 = {}.ElTableColumn;
/** @type {[typeof __VLS_components.ElTableColumn, typeof __VLS_components.elTableColumn, typeof __VLS_components.ElTableColumn, typeof __VLS_components.elTableColumn, ]} */ ;
// @ts-ignore
const __VLS_69 = __VLS_asFunctionalComponent(__VLS_68, new __VLS_68({
label: "状态",
width: "120",
align: "center",
}));
const __VLS_70 = __VLS_69({
label: "状态",
width: "120",
align: "center",
}, ...__VLS_functionalComponentArgsRest(__VLS_69));
__VLS_71.slots.default;
{
const { default: __VLS_thisSlot } = __VLS_71.slots;
const [{ row }] = __VLS_getSlotParams(__VLS_thisSlot);
const __VLS_72 = {}.ElTag;
/** @type {[typeof __VLS_components.ElTag, typeof __VLS_components.elTag, typeof __VLS_components.ElTag, typeof __VLS_components.elTag, ]} */ ;
// @ts-ignore
const __VLS_73 = __VLS_asFunctionalComponent(__VLS_72, new __VLS_72({
type: (__VLS_ctx.statusType(row.status)),
size: "small",
}));
const __VLS_74 = __VLS_73({
type: (__VLS_ctx.statusType(row.status)),
size: "small",
}, ...__VLS_functionalComponentArgsRest(__VLS_73));
__VLS_75.slots.default;
(row.status);
var __VLS_75;
}
var __VLS_71;
const __VLS_76 = {}.ElTableColumn;
/** @type {[typeof __VLS_components.ElTableColumn, typeof __VLS_components.elTableColumn, typeof __VLS_components.ElTableColumn, typeof __VLS_components.elTableColumn, ]} */ ;
// @ts-ignore
const __VLS_77 = __VLS_asFunctionalComponent(__VLS_76, new __VLS_76({
label: "大小",
width: "100",
align: "center",
}));
const __VLS_78 = __VLS_77({
label: "大小",
width: "100",
align: "center",
}, ...__VLS_functionalComponentArgsRest(__VLS_77));
__VLS_79.slots.default;
{
const { default: __VLS_thisSlot } = __VLS_79.slots;
const [{ row }] = __VLS_getSlotParams(__VLS_thisSlot);
(__VLS_ctx.formatSize(row.file_size));
}
var __VLS_79;
const __VLS_80 = {}.ElTableColumn;
/** @type {[typeof __VLS_components.ElTableColumn, typeof __VLS_components.elTableColumn, ]} */ ;
// @ts-ignore
const __VLS_81 = __VLS_asFunctionalComponent(__VLS_80, new __VLS_80({
prop: "title",
label: "标题",
minWidth: "150",
showOverflowTooltip: true,
}));
const __VLS_82 = __VLS_81({
prop: "title",
label: "标题",
minWidth: "150",
showOverflowTooltip: true,
}, ...__VLS_functionalComponentArgsRest(__VLS_81));
const __VLS_84 = {}.ElTableColumn;
/** @type {[typeof __VLS_components.ElTableColumn, typeof __VLS_components.elTableColumn, ]} */ ;
// @ts-ignore
const __VLS_85 = __VLS_asFunctionalComponent(__VLS_84, new __VLS_84({
prop: "keywords",
label: "关键词",
minWidth: "150",
showOverflowTooltip: true,
}));
const __VLS_86 = __VLS_85({
prop: "keywords",
label: "关键词",
minWidth: "150",
showOverflowTooltip: true,
}, ...__VLS_functionalComponentArgsRest(__VLS_85));
const __VLS_88 = {}.ElTableColumn;
/** @type {[typeof __VLS_components.ElTableColumn, typeof __VLS_components.elTableColumn, typeof __VLS_components.ElTableColumn, typeof __VLS_components.elTableColumn, ]} */ ;
// @ts-ignore
const __VLS_89 = __VLS_asFunctionalComponent(__VLS_88, new __VLS_88({
label: "操作",
width: "200",
align: "center",
}));
const __VLS_90 = __VLS_89({
label: "操作",
width: "200",
align: "center",
}, ...__VLS_functionalComponentArgsRest(__VLS_89));
__VLS_91.slots.default;
{
const { default: __VLS_thisSlot } = __VLS_91.slots;
const [{ row }] = __VLS_getSlotParams(__VLS_thisSlot);
const __VLS_92 = {}.ElButton;
/** @type {[typeof __VLS_components.ElButton, typeof __VLS_components.elButton, typeof __VLS_components.ElButton, typeof __VLS_components.elButton, ]} */ ;
// @ts-ignore
const __VLS_93 = __VLS_asFunctionalComponent(__VLS_92, new __VLS_92({
...{ 'onClick': {} },
size: "small",
}));
const __VLS_94 = __VLS_93({
...{ 'onClick': {} },
size: "small",
}, ...__VLS_functionalComponentArgsRest(__VLS_93));
let __VLS_96;
let __VLS_97;
let __VLS_98;
const __VLS_99 = {
onClick: (...[$event]) => {
if (!(__VLS_ctx.kb))
return;
__VLS_ctx.handleReprocess(row);
}
};
__VLS_95.slots.default;
var __VLS_95;
const __VLS_100 = {}.ElButton;
/** @type {[typeof __VLS_components.ElButton, typeof __VLS_components.elButton, typeof __VLS_components.ElButton, typeof __VLS_components.elButton, ]} */ ;
// @ts-ignore
const __VLS_101 = __VLS_asFunctionalComponent(__VLS_100, new __VLS_100({
...{ 'onClick': {} },
size: "small",
type: "danger",
}));
const __VLS_102 = __VLS_101({
...{ 'onClick': {} },
size: "small",
type: "danger",
}, ...__VLS_functionalComponentArgsRest(__VLS_101));
let __VLS_104;
let __VLS_105;
let __VLS_106;
const __VLS_107 = {
onClick: (...[$event]) => {
if (!(__VLS_ctx.kb))
return;
__VLS_ctx.handleDeleteDoc(row);
}
};
__VLS_103.slots.default;
var __VLS_103;
}
var __VLS_91;
var __VLS_63;
var __VLS_51;
}
var __VLS_dollars; var __VLS_dollars;
const __VLS_self = (await import('vue')).defineComponent({ const __VLS_self = (await import('vue')).defineComponent({
setup() { setup() {
return {}; return {
kb: kb,
docs: docs,
loading: loading,
uploading: uploading,
aiUrl: aiUrl,
handleUpload: handleUpload,
handleDeleteDoc: handleDeleteDoc,
handleReprocess: handleReprocess,
handleCopyLink: handleCopyLink,
handleRegenerate: handleRegenerate,
formatSize: formatSize,
statusType: statusType,
};
}, },
}); });
export default (await import('vue')).defineComponent({ export default (await import('vue')).defineComponent({
+113 -3
View File
@@ -1,10 +1,120 @@
<script setup lang="ts"> <script setup lang="ts">
// Phase 4 实现知识库列表 import { ref, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
import apiClient from '@/api/client'
const router = useRouter()
const kbs = ref<any[]>([])
const loading = ref(false)
const showCreate = ref(false)
const createForm = ref({ name: '', description: '' })
const createLoading = ref(false)
onMounted(() => {
loadKbs()
})
async function loadKbs() {
loading.value = true
try {
const { data } = await apiClient.get('/knowledge-bases')
kbs.value = data.items
} catch { /* ignore */ }
loading.value = false
}
async function handleCreate() {
if (!createForm.value.name.trim()) {
ElMessage.warning('请输入知识库名称。')
return
}
createLoading.value = true
try {
const { data } = await apiClient.post('/knowledge-bases', createForm.value)
ElMessage.success('知识库创建成功!')
showCreate.value = false
createForm.value = { name: '', description: '' }
router.push(`/knowledge-bases/${data.id}`)
} catch { /* ignore */ }
createLoading.value = false
}
async function handleDelete(kb: any) {
try {
await ElMessageBox.confirm(`确定删除知识库「${kb.name}」?`, '确认删除', { type: 'warning' })
await apiClient.delete(`/knowledge-bases/${kb.id}`)
ElMessage.success('已删除。')
loadKbs()
} catch { /* ignore */ }
}
async function handleToggle(kb: any) {
const action = kb.enabled ? 'disable' : 'enable'
await apiClient.post(`/knowledge-bases/${kb.id}/${action}`)
ElMessage.success(kb.enabled ? '已禁用。' : '已启用。')
loadKbs()
}
async function handleCopyLink(kb: any) {
try {
const { data } = await apiClient.get(`/knowledge-bases/${kb.id}/link`)
const url = `${window.location.origin}${data.ai_url}`
await navigator.clipboard.writeText(url)
ElMessage.success('AI 链接已复制到剪贴板!')
} catch { /* ignore */ }
}
</script> </script>
<template> <template>
<div> <div>
<h1>知识库</h1> <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px">
<p>知识库列表Phase 4 实现</p> <h1 style="margin: 0">知识库</h1>
<el-button type="primary" @click="showCreate = true">创建知识库</el-button>
</div>
<el-table :data="kbs" v-loading="loading" style="width: 100%">
<el-table-column prop="name" label="名称" min-width="200">
<template #default="{ row }">
<router-link :to="`/knowledge-bases/${row.id}`" style="color: #409eff; text-decoration: none">
{{ row.name }}
</router-link>
</template>
</el-table-column>
<el-table-column prop="description" label="描述" min-width="200" show-overflow-tooltip />
<el-table-column prop="document_count" label="文档数" width="100" align="center" />
<el-table-column label="状态" width="100" align="center">
<template #default="{ row }">
<el-tag :type="row.enabled ? 'success' : 'danger'" size="small">
{{ row.enabled ? '启用' : '禁用' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="300" align="center">
<template #default="{ row }">
<el-button size="small" @click="handleCopyLink(row)">复制链接</el-button>
<el-button size="small" @click="handleToggle(row)">
{{ row.enabled ? '禁用' : '启用' }}
</el-button>
<el-button size="small" type="danger" @click="handleDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 创建对话框 -->
<el-dialog v-model="showCreate" title="创建知识库" width="500px">
<el-form :model="createForm" label-position="top">
<el-form-item label="名称" required>
<el-input v-model="createForm.name" placeholder="例如:公司知识库" />
</el-form-item>
<el-form-item label="描述">
<el-input v-model="createForm.description" type="textarea" :rows="3" placeholder="可选描述" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="showCreate = false">取消</el-button>
<el-button type="primary" :loading="createLoading" @click="handleCreate">创建</el-button>
</template>
</el-dialog>
</div> </div>
</template> </template>
+416 -3
View File
@@ -1,14 +1,427 @@
import { ref, onMounted } from 'vue';
import { useRouter } from 'vue-router';
import { ElMessage, ElMessageBox } from 'element-plus';
import apiClient from '@/api/client';
const router = useRouter();
const kbs = ref([]);
const loading = ref(false);
const showCreate = ref(false);
const createForm = ref({ name: '', description: '' });
const createLoading = ref(false);
onMounted(() => {
loadKbs();
});
async function loadKbs() {
loading.value = true;
try {
const { data } = await apiClient.get('/knowledge-bases');
kbs.value = data.items;
}
catch { /* ignore */ }
loading.value = false;
}
async function handleCreate() {
if (!createForm.value.name.trim()) {
ElMessage.warning('请输入知识库名称。');
return;
}
createLoading.value = true;
try {
const { data } = await apiClient.post('/knowledge-bases', createForm.value);
ElMessage.success('知识库创建成功!');
showCreate.value = false;
createForm.value = { name: '', description: '' };
router.push(`/knowledge-bases/${data.id}`);
}
catch { /* ignore */ }
createLoading.value = false;
}
async function handleDelete(kb) {
try {
await ElMessageBox.confirm(`确定删除知识库「${kb.name}」?`, '确认删除', { type: 'warning' });
await apiClient.delete(`/knowledge-bases/${kb.id}`);
ElMessage.success('已删除。');
loadKbs();
}
catch { /* ignore */ }
}
async function handleToggle(kb) {
const action = kb.enabled ? 'disable' : 'enable';
await apiClient.post(`/knowledge-bases/${kb.id}/${action}`);
ElMessage.success(kb.enabled ? '已禁用。' : '已启用。');
loadKbs();
}
async function handleCopyLink(kb) {
try {
const { data } = await apiClient.get(`/knowledge-bases/${kb.id}/link`);
const url = `${window.location.origin}${data.ai_url}`;
await navigator.clipboard.writeText(url);
ElMessage.success('AI 链接已复制到剪贴板!');
}
catch { /* ignore */ }
}
debugger; /* PartiallyEnd: #3632/scriptSetup.vue */ debugger; /* PartiallyEnd: #3632/scriptSetup.vue */
const __VLS_ctx = {}; const __VLS_ctx = {};
let __VLS_components; let __VLS_components;
let __VLS_directives; let __VLS_directives;
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({}); __VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({});
__VLS_asFunctionalElement(__VLS_intrinsicElements.h1, __VLS_intrinsicElements.h1)({}); __VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
__VLS_asFunctionalElement(__VLS_intrinsicElements.p, __VLS_intrinsicElements.p)({}); ...{ style: {} },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.h1, __VLS_intrinsicElements.h1)({
...{ style: {} },
});
const __VLS_0 = {}.ElButton;
/** @type {[typeof __VLS_components.ElButton, typeof __VLS_components.elButton, typeof __VLS_components.ElButton, typeof __VLS_components.elButton, ]} */ ;
// @ts-ignore
const __VLS_1 = __VLS_asFunctionalComponent(__VLS_0, new __VLS_0({
...{ 'onClick': {} },
type: "primary",
}));
const __VLS_2 = __VLS_1({
...{ 'onClick': {} },
type: "primary",
}, ...__VLS_functionalComponentArgsRest(__VLS_1));
let __VLS_4;
let __VLS_5;
let __VLS_6;
const __VLS_7 = {
onClick: (...[$event]) => {
__VLS_ctx.showCreate = true;
}
};
__VLS_3.slots.default;
var __VLS_3;
const __VLS_8 = {}.ElTable;
/** @type {[typeof __VLS_components.ElTable, typeof __VLS_components.elTable, typeof __VLS_components.ElTable, typeof __VLS_components.elTable, ]} */ ;
// @ts-ignore
const __VLS_9 = __VLS_asFunctionalComponent(__VLS_8, new __VLS_8({
data: (__VLS_ctx.kbs),
...{ style: {} },
}));
const __VLS_10 = __VLS_9({
data: (__VLS_ctx.kbs),
...{ style: {} },
}, ...__VLS_functionalComponentArgsRest(__VLS_9));
__VLS_asFunctionalDirective(__VLS_directives.vLoading)(null, { ...__VLS_directiveBindingRestFields, value: (__VLS_ctx.loading) }, null, null);
__VLS_11.slots.default;
const __VLS_12 = {}.ElTableColumn;
/** @type {[typeof __VLS_components.ElTableColumn, typeof __VLS_components.elTableColumn, typeof __VLS_components.ElTableColumn, typeof __VLS_components.elTableColumn, ]} */ ;
// @ts-ignore
const __VLS_13 = __VLS_asFunctionalComponent(__VLS_12, new __VLS_12({
prop: "name",
label: "名称",
minWidth: "200",
}));
const __VLS_14 = __VLS_13({
prop: "name",
label: "名称",
minWidth: "200",
}, ...__VLS_functionalComponentArgsRest(__VLS_13));
__VLS_15.slots.default;
{
const { default: __VLS_thisSlot } = __VLS_15.slots;
const [{ row }] = __VLS_getSlotParams(__VLS_thisSlot);
const __VLS_16 = {}.RouterLink;
/** @type {[typeof __VLS_components.RouterLink, typeof __VLS_components.routerLink, typeof __VLS_components.RouterLink, typeof __VLS_components.routerLink, ]} */ ;
// @ts-ignore
const __VLS_17 = __VLS_asFunctionalComponent(__VLS_16, new __VLS_16({
to: (`/knowledge-bases/${row.id}`),
...{ style: {} },
}));
const __VLS_18 = __VLS_17({
to: (`/knowledge-bases/${row.id}`),
...{ style: {} },
}, ...__VLS_functionalComponentArgsRest(__VLS_17));
__VLS_19.slots.default;
(row.name);
var __VLS_19;
}
var __VLS_15;
const __VLS_20 = {}.ElTableColumn;
/** @type {[typeof __VLS_components.ElTableColumn, typeof __VLS_components.elTableColumn, ]} */ ;
// @ts-ignore
const __VLS_21 = __VLS_asFunctionalComponent(__VLS_20, new __VLS_20({
prop: "description",
label: "描述",
minWidth: "200",
showOverflowTooltip: true,
}));
const __VLS_22 = __VLS_21({
prop: "description",
label: "描述",
minWidth: "200",
showOverflowTooltip: true,
}, ...__VLS_functionalComponentArgsRest(__VLS_21));
const __VLS_24 = {}.ElTableColumn;
/** @type {[typeof __VLS_components.ElTableColumn, typeof __VLS_components.elTableColumn, ]} */ ;
// @ts-ignore
const __VLS_25 = __VLS_asFunctionalComponent(__VLS_24, new __VLS_24({
prop: "document_count",
label: "文档数",
width: "100",
align: "center",
}));
const __VLS_26 = __VLS_25({
prop: "document_count",
label: "文档数",
width: "100",
align: "center",
}, ...__VLS_functionalComponentArgsRest(__VLS_25));
const __VLS_28 = {}.ElTableColumn;
/** @type {[typeof __VLS_components.ElTableColumn, typeof __VLS_components.elTableColumn, typeof __VLS_components.ElTableColumn, typeof __VLS_components.elTableColumn, ]} */ ;
// @ts-ignore
const __VLS_29 = __VLS_asFunctionalComponent(__VLS_28, new __VLS_28({
label: "状态",
width: "100",
align: "center",
}));
const __VLS_30 = __VLS_29({
label: "状态",
width: "100",
align: "center",
}, ...__VLS_functionalComponentArgsRest(__VLS_29));
__VLS_31.slots.default;
{
const { default: __VLS_thisSlot } = __VLS_31.slots;
const [{ row }] = __VLS_getSlotParams(__VLS_thisSlot);
const __VLS_32 = {}.ElTag;
/** @type {[typeof __VLS_components.ElTag, typeof __VLS_components.elTag, typeof __VLS_components.ElTag, typeof __VLS_components.elTag, ]} */ ;
// @ts-ignore
const __VLS_33 = __VLS_asFunctionalComponent(__VLS_32, new __VLS_32({
type: (row.enabled ? 'success' : 'danger'),
size: "small",
}));
const __VLS_34 = __VLS_33({
type: (row.enabled ? 'success' : 'danger'),
size: "small",
}, ...__VLS_functionalComponentArgsRest(__VLS_33));
__VLS_35.slots.default;
(row.enabled ? '启用' : '禁用');
var __VLS_35;
}
var __VLS_31;
const __VLS_36 = {}.ElTableColumn;
/** @type {[typeof __VLS_components.ElTableColumn, typeof __VLS_components.elTableColumn, typeof __VLS_components.ElTableColumn, typeof __VLS_components.elTableColumn, ]} */ ;
// @ts-ignore
const __VLS_37 = __VLS_asFunctionalComponent(__VLS_36, new __VLS_36({
label: "操作",
width: "300",
align: "center",
}));
const __VLS_38 = __VLS_37({
label: "操作",
width: "300",
align: "center",
}, ...__VLS_functionalComponentArgsRest(__VLS_37));
__VLS_39.slots.default;
{
const { default: __VLS_thisSlot } = __VLS_39.slots;
const [{ row }] = __VLS_getSlotParams(__VLS_thisSlot);
const __VLS_40 = {}.ElButton;
/** @type {[typeof __VLS_components.ElButton, typeof __VLS_components.elButton, typeof __VLS_components.ElButton, typeof __VLS_components.elButton, ]} */ ;
// @ts-ignore
const __VLS_41 = __VLS_asFunctionalComponent(__VLS_40, new __VLS_40({
...{ 'onClick': {} },
size: "small",
}));
const __VLS_42 = __VLS_41({
...{ 'onClick': {} },
size: "small",
}, ...__VLS_functionalComponentArgsRest(__VLS_41));
let __VLS_44;
let __VLS_45;
let __VLS_46;
const __VLS_47 = {
onClick: (...[$event]) => {
__VLS_ctx.handleCopyLink(row);
}
};
__VLS_43.slots.default;
var __VLS_43;
const __VLS_48 = {}.ElButton;
/** @type {[typeof __VLS_components.ElButton, typeof __VLS_components.elButton, typeof __VLS_components.ElButton, typeof __VLS_components.elButton, ]} */ ;
// @ts-ignore
const __VLS_49 = __VLS_asFunctionalComponent(__VLS_48, new __VLS_48({
...{ 'onClick': {} },
size: "small",
}));
const __VLS_50 = __VLS_49({
...{ 'onClick': {} },
size: "small",
}, ...__VLS_functionalComponentArgsRest(__VLS_49));
let __VLS_52;
let __VLS_53;
let __VLS_54;
const __VLS_55 = {
onClick: (...[$event]) => {
__VLS_ctx.handleToggle(row);
}
};
__VLS_51.slots.default;
(row.enabled ? '禁用' : '启用');
var __VLS_51;
const __VLS_56 = {}.ElButton;
/** @type {[typeof __VLS_components.ElButton, typeof __VLS_components.elButton, typeof __VLS_components.ElButton, typeof __VLS_components.elButton, ]} */ ;
// @ts-ignore
const __VLS_57 = __VLS_asFunctionalComponent(__VLS_56, new __VLS_56({
...{ 'onClick': {} },
size: "small",
type: "danger",
}));
const __VLS_58 = __VLS_57({
...{ 'onClick': {} },
size: "small",
type: "danger",
}, ...__VLS_functionalComponentArgsRest(__VLS_57));
let __VLS_60;
let __VLS_61;
let __VLS_62;
const __VLS_63 = {
onClick: (...[$event]) => {
__VLS_ctx.handleDelete(row);
}
};
__VLS_59.slots.default;
var __VLS_59;
}
var __VLS_39;
var __VLS_11;
const __VLS_64 = {}.ElDialog;
/** @type {[typeof __VLS_components.ElDialog, typeof __VLS_components.elDialog, typeof __VLS_components.ElDialog, typeof __VLS_components.elDialog, ]} */ ;
// @ts-ignore
const __VLS_65 = __VLS_asFunctionalComponent(__VLS_64, new __VLS_64({
modelValue: (__VLS_ctx.showCreate),
title: "创建知识库",
width: "500px",
}));
const __VLS_66 = __VLS_65({
modelValue: (__VLS_ctx.showCreate),
title: "创建知识库",
width: "500px",
}, ...__VLS_functionalComponentArgsRest(__VLS_65));
__VLS_67.slots.default;
const __VLS_68 = {}.ElForm;
/** @type {[typeof __VLS_components.ElForm, typeof __VLS_components.elForm, typeof __VLS_components.ElForm, typeof __VLS_components.elForm, ]} */ ;
// @ts-ignore
const __VLS_69 = __VLS_asFunctionalComponent(__VLS_68, new __VLS_68({
model: (__VLS_ctx.createForm),
labelPosition: "top",
}));
const __VLS_70 = __VLS_69({
model: (__VLS_ctx.createForm),
labelPosition: "top",
}, ...__VLS_functionalComponentArgsRest(__VLS_69));
__VLS_71.slots.default;
const __VLS_72 = {}.ElFormItem;
/** @type {[typeof __VLS_components.ElFormItem, typeof __VLS_components.elFormItem, typeof __VLS_components.ElFormItem, typeof __VLS_components.elFormItem, ]} */ ;
// @ts-ignore
const __VLS_73 = __VLS_asFunctionalComponent(__VLS_72, new __VLS_72({
label: "名称",
required: true,
}));
const __VLS_74 = __VLS_73({
label: "名称",
required: true,
}, ...__VLS_functionalComponentArgsRest(__VLS_73));
__VLS_75.slots.default;
const __VLS_76 = {}.ElInput;
/** @type {[typeof __VLS_components.ElInput, typeof __VLS_components.elInput, ]} */ ;
// @ts-ignore
const __VLS_77 = __VLS_asFunctionalComponent(__VLS_76, new __VLS_76({
modelValue: (__VLS_ctx.createForm.name),
placeholder: "例如:公司知识库",
}));
const __VLS_78 = __VLS_77({
modelValue: (__VLS_ctx.createForm.name),
placeholder: "例如:公司知识库",
}, ...__VLS_functionalComponentArgsRest(__VLS_77));
var __VLS_75;
const __VLS_80 = {}.ElFormItem;
/** @type {[typeof __VLS_components.ElFormItem, typeof __VLS_components.elFormItem, typeof __VLS_components.ElFormItem, typeof __VLS_components.elFormItem, ]} */ ;
// @ts-ignore
const __VLS_81 = __VLS_asFunctionalComponent(__VLS_80, new __VLS_80({
label: "描述",
}));
const __VLS_82 = __VLS_81({
label: "描述",
}, ...__VLS_functionalComponentArgsRest(__VLS_81));
__VLS_83.slots.default;
const __VLS_84 = {}.ElInput;
/** @type {[typeof __VLS_components.ElInput, typeof __VLS_components.elInput, ]} */ ;
// @ts-ignore
const __VLS_85 = __VLS_asFunctionalComponent(__VLS_84, new __VLS_84({
modelValue: (__VLS_ctx.createForm.description),
type: "textarea",
rows: (3),
placeholder: "可选描述",
}));
const __VLS_86 = __VLS_85({
modelValue: (__VLS_ctx.createForm.description),
type: "textarea",
rows: (3),
placeholder: "可选描述",
}, ...__VLS_functionalComponentArgsRest(__VLS_85));
var __VLS_83;
var __VLS_71;
{
const { footer: __VLS_thisSlot } = __VLS_67.slots;
const __VLS_88 = {}.ElButton;
/** @type {[typeof __VLS_components.ElButton, typeof __VLS_components.elButton, typeof __VLS_components.ElButton, typeof __VLS_components.elButton, ]} */ ;
// @ts-ignore
const __VLS_89 = __VLS_asFunctionalComponent(__VLS_88, new __VLS_88({
...{ 'onClick': {} },
}));
const __VLS_90 = __VLS_89({
...{ 'onClick': {} },
}, ...__VLS_functionalComponentArgsRest(__VLS_89));
let __VLS_92;
let __VLS_93;
let __VLS_94;
const __VLS_95 = {
onClick: (...[$event]) => {
__VLS_ctx.showCreate = false;
}
};
__VLS_91.slots.default;
var __VLS_91;
const __VLS_96 = {}.ElButton;
/** @type {[typeof __VLS_components.ElButton, typeof __VLS_components.elButton, typeof __VLS_components.ElButton, typeof __VLS_components.elButton, ]} */ ;
// @ts-ignore
const __VLS_97 = __VLS_asFunctionalComponent(__VLS_96, new __VLS_96({
...{ 'onClick': {} },
type: "primary",
loading: (__VLS_ctx.createLoading),
}));
const __VLS_98 = __VLS_97({
...{ 'onClick': {} },
type: "primary",
loading: (__VLS_ctx.createLoading),
}, ...__VLS_functionalComponentArgsRest(__VLS_97));
let __VLS_100;
let __VLS_101;
let __VLS_102;
const __VLS_103 = {
onClick: (__VLS_ctx.handleCreate)
};
__VLS_99.slots.default;
var __VLS_99;
}
var __VLS_67;
var __VLS_dollars; var __VLS_dollars;
const __VLS_self = (await import('vue')).defineComponent({ const __VLS_self = (await import('vue')).defineComponent({
setup() { setup() {
return {}; return {
kbs: kbs,
loading: loading,
showCreate: showCreate,
createForm: createForm,
createLoading: createLoading,
handleCreate: handleCreate,
handleDelete: handleDelete,
handleToggle: handleToggle,
handleCopyLink: handleCopyLink,
};
}, },
}); });
export default (await import('vue')).defineComponent({ export default (await import('vue')).defineComponent({
+43 -3
View File
@@ -1,14 +1,54 @@
<script setup lang="ts"> <script setup lang="ts">
// Phase 3 实现登录逻辑 import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { useUserStore } from '@/stores/user'
import apiClient from '@/api/client'
const router = useRouter()
const userStore = useUserStore()
const form = ref({
username_or_email: '',
password: '',
})
const loading = ref(false)
async function handleLogin() {
loading.value = true
try {
const { data } = await apiClient.post('/auth/login', form.value)
userStore.setUser(data)
router.push('/')
} catch {
// 错误由拦截器处理
} finally {
loading.value = false
}
}
</script> </script>
<template> <template>
<div class="login-page"> <div class="login-page">
<el-card class="login-card"> <el-card class="login-card">
<template #header> <template #header>
<h2>AI Knowledge Link</h2> <h2 style="text-align: center; margin: 0">AI Knowledge Link</h2>
</template> </template>
<p>登录页面Phase 3 实现</p> <el-form :model="form" label-position="top" @submit.prevent="handleLogin">
<el-form-item label="用户名或邮箱">
<el-input v-model="form.username_or_email" placeholder="请输入用户名或邮箱" />
</el-form-item>
<el-form-item label="密码">
<el-input v-model="form.password" type="password" placeholder="请输入密码" show-password />
</el-form-item>
<el-form-item>
<el-button type="primary" :loading="loading" style="width: 100%" native-type="submit">
登录
</el-button>
</el-form-item>
</el-form>
<div style="text-align: center">
<router-link to="/register">没有账号立即注册</router-link>
</div>
</el-card> </el-card>
</div> </div>
</template> </template>
+140 -3
View File
@@ -1,3 +1,28 @@
import { ref } from 'vue';
import { useRouter } from 'vue-router';
import { useUserStore } from '@/stores/user';
import apiClient from '@/api/client';
const router = useRouter();
const userStore = useUserStore();
const form = ref({
username_or_email: '',
password: '',
});
const loading = ref(false);
async function handleLogin() {
loading.value = true;
try {
const { data } = await apiClient.post('/auth/login', form.value);
userStore.setUser(data);
router.push('/');
}
catch {
// 错误由拦截器处理
}
finally {
loading.value = false;
}
}
debugger; /* PartiallyEnd: #3632/scriptSetup.vue */ debugger; /* PartiallyEnd: #3632/scriptSetup.vue */
const __VLS_ctx = {}; const __VLS_ctx = {};
let __VLS_components; let __VLS_components;
@@ -19,16 +44,128 @@ const __VLS_2 = __VLS_1({
__VLS_3.slots.default; __VLS_3.slots.default;
{ {
const { header: __VLS_thisSlot } = __VLS_3.slots; const { header: __VLS_thisSlot } = __VLS_3.slots;
__VLS_asFunctionalElement(__VLS_intrinsicElements.h2, __VLS_intrinsicElements.h2)({}); __VLS_asFunctionalElement(__VLS_intrinsicElements.h2, __VLS_intrinsicElements.h2)({
...{ style: {} },
});
} }
__VLS_asFunctionalElement(__VLS_intrinsicElements.p, __VLS_intrinsicElements.p)({}); const __VLS_4 = {}.ElForm;
/** @type {[typeof __VLS_components.ElForm, typeof __VLS_components.elForm, typeof __VLS_components.ElForm, typeof __VLS_components.elForm, ]} */ ;
// @ts-ignore
const __VLS_5 = __VLS_asFunctionalComponent(__VLS_4, new __VLS_4({
...{ 'onSubmit': {} },
model: (__VLS_ctx.form),
labelPosition: "top",
}));
const __VLS_6 = __VLS_5({
...{ 'onSubmit': {} },
model: (__VLS_ctx.form),
labelPosition: "top",
}, ...__VLS_functionalComponentArgsRest(__VLS_5));
let __VLS_8;
let __VLS_9;
let __VLS_10;
const __VLS_11 = {
onSubmit: (__VLS_ctx.handleLogin)
};
__VLS_7.slots.default;
const __VLS_12 = {}.ElFormItem;
/** @type {[typeof __VLS_components.ElFormItem, typeof __VLS_components.elFormItem, typeof __VLS_components.ElFormItem, typeof __VLS_components.elFormItem, ]} */ ;
// @ts-ignore
const __VLS_13 = __VLS_asFunctionalComponent(__VLS_12, new __VLS_12({
label: "用户名或邮箱",
}));
const __VLS_14 = __VLS_13({
label: "用户名或邮箱",
}, ...__VLS_functionalComponentArgsRest(__VLS_13));
__VLS_15.slots.default;
const __VLS_16 = {}.ElInput;
/** @type {[typeof __VLS_components.ElInput, typeof __VLS_components.elInput, ]} */ ;
// @ts-ignore
const __VLS_17 = __VLS_asFunctionalComponent(__VLS_16, new __VLS_16({
modelValue: (__VLS_ctx.form.username_or_email),
placeholder: "请输入用户名或邮箱",
}));
const __VLS_18 = __VLS_17({
modelValue: (__VLS_ctx.form.username_or_email),
placeholder: "请输入用户名或邮箱",
}, ...__VLS_functionalComponentArgsRest(__VLS_17));
var __VLS_15;
const __VLS_20 = {}.ElFormItem;
/** @type {[typeof __VLS_components.ElFormItem, typeof __VLS_components.elFormItem, typeof __VLS_components.ElFormItem, typeof __VLS_components.elFormItem, ]} */ ;
// @ts-ignore
const __VLS_21 = __VLS_asFunctionalComponent(__VLS_20, new __VLS_20({
label: "密码",
}));
const __VLS_22 = __VLS_21({
label: "密码",
}, ...__VLS_functionalComponentArgsRest(__VLS_21));
__VLS_23.slots.default;
const __VLS_24 = {}.ElInput;
/** @type {[typeof __VLS_components.ElInput, typeof __VLS_components.elInput, ]} */ ;
// @ts-ignore
const __VLS_25 = __VLS_asFunctionalComponent(__VLS_24, new __VLS_24({
modelValue: (__VLS_ctx.form.password),
type: "password",
placeholder: "请输入密码",
showPassword: true,
}));
const __VLS_26 = __VLS_25({
modelValue: (__VLS_ctx.form.password),
type: "password",
placeholder: "请输入密码",
showPassword: true,
}, ...__VLS_functionalComponentArgsRest(__VLS_25));
var __VLS_23;
const __VLS_28 = {}.ElFormItem;
/** @type {[typeof __VLS_components.ElFormItem, typeof __VLS_components.elFormItem, typeof __VLS_components.ElFormItem, typeof __VLS_components.elFormItem, ]} */ ;
// @ts-ignore
const __VLS_29 = __VLS_asFunctionalComponent(__VLS_28, new __VLS_28({}));
const __VLS_30 = __VLS_29({}, ...__VLS_functionalComponentArgsRest(__VLS_29));
__VLS_31.slots.default;
const __VLS_32 = {}.ElButton;
/** @type {[typeof __VLS_components.ElButton, typeof __VLS_components.elButton, typeof __VLS_components.ElButton, typeof __VLS_components.elButton, ]} */ ;
// @ts-ignore
const __VLS_33 = __VLS_asFunctionalComponent(__VLS_32, new __VLS_32({
type: "primary",
loading: (__VLS_ctx.loading),
...{ style: {} },
nativeType: "submit",
}));
const __VLS_34 = __VLS_33({
type: "primary",
loading: (__VLS_ctx.loading),
...{ style: {} },
nativeType: "submit",
}, ...__VLS_functionalComponentArgsRest(__VLS_33));
__VLS_35.slots.default;
var __VLS_35;
var __VLS_31;
var __VLS_7;
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ style: {} },
});
const __VLS_36 = {}.RouterLink;
/** @type {[typeof __VLS_components.RouterLink, typeof __VLS_components.routerLink, typeof __VLS_components.RouterLink, typeof __VLS_components.routerLink, ]} */ ;
// @ts-ignore
const __VLS_37 = __VLS_asFunctionalComponent(__VLS_36, new __VLS_36({
to: "/register",
}));
const __VLS_38 = __VLS_37({
to: "/register",
}, ...__VLS_functionalComponentArgsRest(__VLS_37));
__VLS_39.slots.default;
var __VLS_39;
var __VLS_3; var __VLS_3;
/** @type {__VLS_StyleScopedClasses['login-page']} */ ; /** @type {__VLS_StyleScopedClasses['login-page']} */ ;
/** @type {__VLS_StyleScopedClasses['login-card']} */ ; /** @type {__VLS_StyleScopedClasses['login-card']} */ ;
var __VLS_dollars; var __VLS_dollars;
const __VLS_self = (await import('vue')).defineComponent({ const __VLS_self = (await import('vue')).defineComponent({
setup() { setup() {
return {}; return {
form: form,
loading: loading,
handleLogin: handleLogin,
};
}, },
}); });
export default (await import('vue')).defineComponent({ export default (await import('vue')).defineComponent({
+61 -3
View File
@@ -1,14 +1,72 @@
<script setup lang="ts"> <script setup lang="ts">
// Phase 3 实现注册逻辑 import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { useUserStore } from '@/stores/user'
import apiClient from '@/api/client'
const router = useRouter()
const userStore = useUserStore()
const form = ref({
username: '',
email: '',
password: '',
confirmPassword: '',
})
const loading = ref(false)
async function handleRegister() {
if (form.value.password !== form.value.confirmPassword) {
ElMessage.error('两次输入的密码不一致。')
return
}
loading.value = true
try {
const { data } = await apiClient.post('/auth/register', {
username: form.value.username,
email: form.value.email,
password: form.value.password,
})
userStore.setUser(data)
router.push('/')
} catch {
// 错误由拦截器处理
} finally {
loading.value = false
}
}
import { ElMessage } from 'element-plus'
</script> </script>
<template> <template>
<div class="register-page"> <div class="register-page">
<el-card class="register-card"> <el-card class="register-card">
<template #header> <template #header>
<h2>注册</h2> <h2 style="text-align: center; margin: 0">注册</h2>
</template> </template>
<p>注册页面Phase 3 实现</p> <el-form :model="form" label-position="top" @submit.prevent="handleRegister">
<el-form-item label="用户名">
<el-input v-model="form.username" placeholder="2-32 位字母、数字、下划线" />
</el-form-item>
<el-form-item label="邮箱">
<el-input v-model="form.email" placeholder="your@email.com" />
</el-form-item>
<el-form-item label="密码">
<el-input v-model="form.password" type="password" placeholder="至少 8 位" show-password />
</el-form-item>
<el-form-item label="确认密码">
<el-input v-model="form.confirmPassword" type="password" placeholder="再次输入密码" show-password />
</el-form-item>
<el-form-item>
<el-button type="primary" :loading="loading" style="width: 100%" native-type="submit">
注册
</el-button>
</el-form-item>
</el-form>
<div style="text-align: center">
<router-link to="/login">已有账号立即登录</router-link>
</div>
</el-card> </el-card>
</div> </div>
</template> </template>
+199 -3
View File
@@ -1,3 +1,39 @@
import { ref } from 'vue';
import { useRouter } from 'vue-router';
import { useUserStore } from '@/stores/user';
import apiClient from '@/api/client';
const router = useRouter();
const userStore = useUserStore();
const form = ref({
username: '',
email: '',
password: '',
confirmPassword: '',
});
const loading = ref(false);
async function handleRegister() {
if (form.value.password !== form.value.confirmPassword) {
ElMessage.error('两次输入的密码不一致。');
return;
}
loading.value = true;
try {
const { data } = await apiClient.post('/auth/register', {
username: form.value.username,
email: form.value.email,
password: form.value.password,
});
userStore.setUser(data);
router.push('/');
}
catch {
// 错误由拦截器处理
}
finally {
loading.value = false;
}
}
import { ElMessage } from 'element-plus';
debugger; /* PartiallyEnd: #3632/scriptSetup.vue */ debugger; /* PartiallyEnd: #3632/scriptSetup.vue */
const __VLS_ctx = {}; const __VLS_ctx = {};
let __VLS_components; let __VLS_components;
@@ -19,16 +55,176 @@ const __VLS_2 = __VLS_1({
__VLS_3.slots.default; __VLS_3.slots.default;
{ {
const { header: __VLS_thisSlot } = __VLS_3.slots; const { header: __VLS_thisSlot } = __VLS_3.slots;
__VLS_asFunctionalElement(__VLS_intrinsicElements.h2, __VLS_intrinsicElements.h2)({}); __VLS_asFunctionalElement(__VLS_intrinsicElements.h2, __VLS_intrinsicElements.h2)({
...{ style: {} },
});
} }
__VLS_asFunctionalElement(__VLS_intrinsicElements.p, __VLS_intrinsicElements.p)({}); const __VLS_4 = {}.ElForm;
/** @type {[typeof __VLS_components.ElForm, typeof __VLS_components.elForm, typeof __VLS_components.ElForm, typeof __VLS_components.elForm, ]} */ ;
// @ts-ignore
const __VLS_5 = __VLS_asFunctionalComponent(__VLS_4, new __VLS_4({
...{ 'onSubmit': {} },
model: (__VLS_ctx.form),
labelPosition: "top",
}));
const __VLS_6 = __VLS_5({
...{ 'onSubmit': {} },
model: (__VLS_ctx.form),
labelPosition: "top",
}, ...__VLS_functionalComponentArgsRest(__VLS_5));
let __VLS_8;
let __VLS_9;
let __VLS_10;
const __VLS_11 = {
onSubmit: (__VLS_ctx.handleRegister)
};
__VLS_7.slots.default;
const __VLS_12 = {}.ElFormItem;
/** @type {[typeof __VLS_components.ElFormItem, typeof __VLS_components.elFormItem, typeof __VLS_components.ElFormItem, typeof __VLS_components.elFormItem, ]} */ ;
// @ts-ignore
const __VLS_13 = __VLS_asFunctionalComponent(__VLS_12, new __VLS_12({
label: "用户名",
}));
const __VLS_14 = __VLS_13({
label: "用户名",
}, ...__VLS_functionalComponentArgsRest(__VLS_13));
__VLS_15.slots.default;
const __VLS_16 = {}.ElInput;
/** @type {[typeof __VLS_components.ElInput, typeof __VLS_components.elInput, ]} */ ;
// @ts-ignore
const __VLS_17 = __VLS_asFunctionalComponent(__VLS_16, new __VLS_16({
modelValue: (__VLS_ctx.form.username),
placeholder: "2-32 位字母、数字、下划线",
}));
const __VLS_18 = __VLS_17({
modelValue: (__VLS_ctx.form.username),
placeholder: "2-32 位字母、数字、下划线",
}, ...__VLS_functionalComponentArgsRest(__VLS_17));
var __VLS_15;
const __VLS_20 = {}.ElFormItem;
/** @type {[typeof __VLS_components.ElFormItem, typeof __VLS_components.elFormItem, typeof __VLS_components.ElFormItem, typeof __VLS_components.elFormItem, ]} */ ;
// @ts-ignore
const __VLS_21 = __VLS_asFunctionalComponent(__VLS_20, new __VLS_20({
label: "邮箱",
}));
const __VLS_22 = __VLS_21({
label: "邮箱",
}, ...__VLS_functionalComponentArgsRest(__VLS_21));
__VLS_23.slots.default;
const __VLS_24 = {}.ElInput;
/** @type {[typeof __VLS_components.ElInput, typeof __VLS_components.elInput, ]} */ ;
// @ts-ignore
const __VLS_25 = __VLS_asFunctionalComponent(__VLS_24, new __VLS_24({
modelValue: (__VLS_ctx.form.email),
placeholder: "your@email.com",
}));
const __VLS_26 = __VLS_25({
modelValue: (__VLS_ctx.form.email),
placeholder: "your@email.com",
}, ...__VLS_functionalComponentArgsRest(__VLS_25));
var __VLS_23;
const __VLS_28 = {}.ElFormItem;
/** @type {[typeof __VLS_components.ElFormItem, typeof __VLS_components.elFormItem, typeof __VLS_components.ElFormItem, typeof __VLS_components.elFormItem, ]} */ ;
// @ts-ignore
const __VLS_29 = __VLS_asFunctionalComponent(__VLS_28, new __VLS_28({
label: "密码",
}));
const __VLS_30 = __VLS_29({
label: "密码",
}, ...__VLS_functionalComponentArgsRest(__VLS_29));
__VLS_31.slots.default;
const __VLS_32 = {}.ElInput;
/** @type {[typeof __VLS_components.ElInput, typeof __VLS_components.elInput, ]} */ ;
// @ts-ignore
const __VLS_33 = __VLS_asFunctionalComponent(__VLS_32, new __VLS_32({
modelValue: (__VLS_ctx.form.password),
type: "password",
placeholder: "至少 8 位",
showPassword: true,
}));
const __VLS_34 = __VLS_33({
modelValue: (__VLS_ctx.form.password),
type: "password",
placeholder: "至少 8 位",
showPassword: true,
}, ...__VLS_functionalComponentArgsRest(__VLS_33));
var __VLS_31;
const __VLS_36 = {}.ElFormItem;
/** @type {[typeof __VLS_components.ElFormItem, typeof __VLS_components.elFormItem, typeof __VLS_components.ElFormItem, typeof __VLS_components.elFormItem, ]} */ ;
// @ts-ignore
const __VLS_37 = __VLS_asFunctionalComponent(__VLS_36, new __VLS_36({
label: "确认密码",
}));
const __VLS_38 = __VLS_37({
label: "确认密码",
}, ...__VLS_functionalComponentArgsRest(__VLS_37));
__VLS_39.slots.default;
const __VLS_40 = {}.ElInput;
/** @type {[typeof __VLS_components.ElInput, typeof __VLS_components.elInput, ]} */ ;
// @ts-ignore
const __VLS_41 = __VLS_asFunctionalComponent(__VLS_40, new __VLS_40({
modelValue: (__VLS_ctx.form.confirmPassword),
type: "password",
placeholder: "再次输入密码",
showPassword: true,
}));
const __VLS_42 = __VLS_41({
modelValue: (__VLS_ctx.form.confirmPassword),
type: "password",
placeholder: "再次输入密码",
showPassword: true,
}, ...__VLS_functionalComponentArgsRest(__VLS_41));
var __VLS_39;
const __VLS_44 = {}.ElFormItem;
/** @type {[typeof __VLS_components.ElFormItem, typeof __VLS_components.elFormItem, typeof __VLS_components.ElFormItem, typeof __VLS_components.elFormItem, ]} */ ;
// @ts-ignore
const __VLS_45 = __VLS_asFunctionalComponent(__VLS_44, new __VLS_44({}));
const __VLS_46 = __VLS_45({}, ...__VLS_functionalComponentArgsRest(__VLS_45));
__VLS_47.slots.default;
const __VLS_48 = {}.ElButton;
/** @type {[typeof __VLS_components.ElButton, typeof __VLS_components.elButton, typeof __VLS_components.ElButton, typeof __VLS_components.elButton, ]} */ ;
// @ts-ignore
const __VLS_49 = __VLS_asFunctionalComponent(__VLS_48, new __VLS_48({
type: "primary",
loading: (__VLS_ctx.loading),
...{ style: {} },
nativeType: "submit",
}));
const __VLS_50 = __VLS_49({
type: "primary",
loading: (__VLS_ctx.loading),
...{ style: {} },
nativeType: "submit",
}, ...__VLS_functionalComponentArgsRest(__VLS_49));
__VLS_51.slots.default;
var __VLS_51;
var __VLS_47;
var __VLS_7;
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ style: {} },
});
const __VLS_52 = {}.RouterLink;
/** @type {[typeof __VLS_components.RouterLink, typeof __VLS_components.routerLink, typeof __VLS_components.RouterLink, typeof __VLS_components.routerLink, ]} */ ;
// @ts-ignore
const __VLS_53 = __VLS_asFunctionalComponent(__VLS_52, new __VLS_52({
to: "/login",
}));
const __VLS_54 = __VLS_53({
to: "/login",
}, ...__VLS_functionalComponentArgsRest(__VLS_53));
__VLS_55.slots.default;
var __VLS_55;
var __VLS_3; var __VLS_3;
/** @type {__VLS_StyleScopedClasses['register-page']} */ ; /** @type {__VLS_StyleScopedClasses['register-page']} */ ;
/** @type {__VLS_StyleScopedClasses['register-card']} */ ; /** @type {__VLS_StyleScopedClasses['register-card']} */ ;
var __VLS_dollars; var __VLS_dollars;
const __VLS_self = (await import('vue')).defineComponent({ const __VLS_self = (await import('vue')).defineComponent({
setup() { setup() {
return {}; return {
form: form,
loading: loading,
handleRegister: handleRegister,
};
}, },
}); });
export default (await import('vue')).defineComponent({ export default (await import('vue')).defineComponent({
+41 -2
View File
@@ -1,10 +1,49 @@
<script setup lang="ts"> <script setup lang="ts">
// Phase 14 实现设置页 import { ref } from 'vue'
import { ElMessage } from 'element-plus'
import apiClient from '@/api/client'
const form = ref({
password: '',
confirmPassword: '',
})
const loading = ref(false)
async function handleChangePassword() {
if (form.value.password !== form.value.confirmPassword) {
ElMessage.error('两次输入的密码不一致。')
return
}
if (form.value.password.length < 8) {
ElMessage.error('密码长度不能少于 8 位。')
return
}
loading.value = true
try {
await apiClient.patch('/auth/me', { password: form.value.password })
ElMessage.success('密码修改成功。')
form.value = { password: '', confirmPassword: '' }
} catch { /* ignore */ }
loading.value = false
}
</script> </script>
<template> <template>
<div> <div>
<h1>设置</h1> <h1>设置</h1>
<p>设置页面Phase 14 实现</p> <el-card>
<template #header>修改密码</template>
<el-form :model="form" label-position="top" style="max-width: 400px">
<el-form-item label="新密码">
<el-input v-model="form.password" type="password" placeholder="至少 8 位" show-password />
</el-form-item>
<el-form-item label="确认新密码">
<el-input v-model="form.confirmPassword" type="password" placeholder="再次输入新密码" show-password />
</el-form-item>
<el-form-item>
<el-button type="primary" :loading="loading" @click="handleChangePassword">修改密码</el-button>
</el-form-item>
</el-form>
</el-card>
</div> </div>
</template> </template>
+136 -2
View File
@@ -1,14 +1,148 @@
import { ref } from 'vue';
import { ElMessage } from 'element-plus';
import apiClient from '@/api/client';
const form = ref({
password: '',
confirmPassword: '',
});
const loading = ref(false);
async function handleChangePassword() {
if (form.value.password !== form.value.confirmPassword) {
ElMessage.error('两次输入的密码不一致。');
return;
}
if (form.value.password.length < 8) {
ElMessage.error('密码长度不能少于 8 位。');
return;
}
loading.value = true;
try {
await apiClient.patch('/auth/me', { password: form.value.password });
ElMessage.success('密码修改成功。');
form.value = { password: '', confirmPassword: '' };
}
catch { /* ignore */ }
loading.value = false;
}
debugger; /* PartiallyEnd: #3632/scriptSetup.vue */ debugger; /* PartiallyEnd: #3632/scriptSetup.vue */
const __VLS_ctx = {}; const __VLS_ctx = {};
let __VLS_components; let __VLS_components;
let __VLS_directives; let __VLS_directives;
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({}); __VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({});
__VLS_asFunctionalElement(__VLS_intrinsicElements.h1, __VLS_intrinsicElements.h1)({}); __VLS_asFunctionalElement(__VLS_intrinsicElements.h1, __VLS_intrinsicElements.h1)({});
__VLS_asFunctionalElement(__VLS_intrinsicElements.p, __VLS_intrinsicElements.p)({}); 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({}));
const __VLS_2 = __VLS_1({}, ...__VLS_functionalComponentArgsRest(__VLS_1));
__VLS_3.slots.default;
{
const { header: __VLS_thisSlot } = __VLS_3.slots;
}
const __VLS_4 = {}.ElForm;
/** @type {[typeof __VLS_components.ElForm, typeof __VLS_components.elForm, typeof __VLS_components.ElForm, typeof __VLS_components.elForm, ]} */ ;
// @ts-ignore
const __VLS_5 = __VLS_asFunctionalComponent(__VLS_4, new __VLS_4({
model: (__VLS_ctx.form),
labelPosition: "top",
...{ style: {} },
}));
const __VLS_6 = __VLS_5({
model: (__VLS_ctx.form),
labelPosition: "top",
...{ style: {} },
}, ...__VLS_functionalComponentArgsRest(__VLS_5));
__VLS_7.slots.default;
const __VLS_8 = {}.ElFormItem;
/** @type {[typeof __VLS_components.ElFormItem, typeof __VLS_components.elFormItem, typeof __VLS_components.ElFormItem, typeof __VLS_components.elFormItem, ]} */ ;
// @ts-ignore
const __VLS_9 = __VLS_asFunctionalComponent(__VLS_8, new __VLS_8({
label: "新密码",
}));
const __VLS_10 = __VLS_9({
label: "新密码",
}, ...__VLS_functionalComponentArgsRest(__VLS_9));
__VLS_11.slots.default;
const __VLS_12 = {}.ElInput;
/** @type {[typeof __VLS_components.ElInput, typeof __VLS_components.elInput, ]} */ ;
// @ts-ignore
const __VLS_13 = __VLS_asFunctionalComponent(__VLS_12, new __VLS_12({
modelValue: (__VLS_ctx.form.password),
type: "password",
placeholder: "至少 8 位",
showPassword: true,
}));
const __VLS_14 = __VLS_13({
modelValue: (__VLS_ctx.form.password),
type: "password",
placeholder: "至少 8 位",
showPassword: true,
}, ...__VLS_functionalComponentArgsRest(__VLS_13));
var __VLS_11;
const __VLS_16 = {}.ElFormItem;
/** @type {[typeof __VLS_components.ElFormItem, typeof __VLS_components.elFormItem, typeof __VLS_components.ElFormItem, typeof __VLS_components.elFormItem, ]} */ ;
// @ts-ignore
const __VLS_17 = __VLS_asFunctionalComponent(__VLS_16, new __VLS_16({
label: "确认新密码",
}));
const __VLS_18 = __VLS_17({
label: "确认新密码",
}, ...__VLS_functionalComponentArgsRest(__VLS_17));
__VLS_19.slots.default;
const __VLS_20 = {}.ElInput;
/** @type {[typeof __VLS_components.ElInput, typeof __VLS_components.elInput, ]} */ ;
// @ts-ignore
const __VLS_21 = __VLS_asFunctionalComponent(__VLS_20, new __VLS_20({
modelValue: (__VLS_ctx.form.confirmPassword),
type: "password",
placeholder: "再次输入新密码",
showPassword: true,
}));
const __VLS_22 = __VLS_21({
modelValue: (__VLS_ctx.form.confirmPassword),
type: "password",
placeholder: "再次输入新密码",
showPassword: true,
}, ...__VLS_functionalComponentArgsRest(__VLS_21));
var __VLS_19;
const __VLS_24 = {}.ElFormItem;
/** @type {[typeof __VLS_components.ElFormItem, typeof __VLS_components.elFormItem, typeof __VLS_components.ElFormItem, typeof __VLS_components.elFormItem, ]} */ ;
// @ts-ignore
const __VLS_25 = __VLS_asFunctionalComponent(__VLS_24, new __VLS_24({}));
const __VLS_26 = __VLS_25({}, ...__VLS_functionalComponentArgsRest(__VLS_25));
__VLS_27.slots.default;
const __VLS_28 = {}.ElButton;
/** @type {[typeof __VLS_components.ElButton, typeof __VLS_components.elButton, typeof __VLS_components.ElButton, typeof __VLS_components.elButton, ]} */ ;
// @ts-ignore
const __VLS_29 = __VLS_asFunctionalComponent(__VLS_28, new __VLS_28({
...{ 'onClick': {} },
type: "primary",
loading: (__VLS_ctx.loading),
}));
const __VLS_30 = __VLS_29({
...{ 'onClick': {} },
type: "primary",
loading: (__VLS_ctx.loading),
}, ...__VLS_functionalComponentArgsRest(__VLS_29));
let __VLS_32;
let __VLS_33;
let __VLS_34;
const __VLS_35 = {
onClick: (__VLS_ctx.handleChangePassword)
};
__VLS_31.slots.default;
var __VLS_31;
var __VLS_27;
var __VLS_7;
var __VLS_3;
var __VLS_dollars; var __VLS_dollars;
const __VLS_self = (await import('vue')).defineComponent({ const __VLS_self = (await import('vue')).defineComponent({
setup() { setup() {
return {}; return {
form: form,
loading: loading,
handleChangePassword: handleChangePassword,
};
}, },
}); });
export default (await import('vue')).defineComponent({ export default (await import('vue')).defineComponent({
+1 -1
View File
@@ -1 +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"} {"root":["./src/env.d.ts","./src/main.ts","./src/api/client.ts","./src/router/index.ts","./src/stores/user.ts","./src/app.vue","./src/layouts/defaultlayout.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"}