更新知识库功能

This commit is contained in:
2025-10-31 17:34:14 +08:00
parent 931b999e1d
commit 186dcb86d9
5 changed files with 326 additions and 320 deletions
-113
View File
@@ -1,113 +0,0 @@
import request from "@/utils/request";
/**
* 获取知识列表
* @param {Object} params 查询参数
* @returns {Promise}
*/
export function getKnowledgeList(params) {
return request({
url: "/api/knowledge/list",
method: "get",
params,
});
}
/**
* 获取知识详情
* @param {number|string} id 知识ID
* @returns {Promise}
*/
export function getKnowledgeDetail(id) {
return request({
url: `/api/knowledge/detail/${id}`,
method: "get",
});
}
/**
* 创建知识
* @param {Object} data 知识数据
* @returns {Promise}
*/
export function createKnowledge(data) {
return request({
url: "/api/knowledge/create",
method: "post",
data,
});
}
/**
* 更新知识
* @param {number|string} id 知识ID
* @param {Object} data 更新的数据
* @returns {Promise}
*/
export function updateKnowledge(id, data) {
return request({
url: `/api/knowledge/update/${id}`,
method: "put",
data,
});
}
/**
* 删除知识
* @param {number|string} id 知识ID
* @returns {Promise}
*/
export function deleteKnowledge(id) {
return request({
url: `/api/knowledge/delete/${id}`,
method: "delete",
});
}
/**
* 获取分类列表
* @returns {Promise}
*/
export function getCategoryList() {
return request({
url: "/api/knowledge/category/list",
method: "get",
});
}
/**
* 获取标签列表
* @returns {Promise}
*/
export function getTagList() {
return request({
url: "/api/knowledge/tag/list",
method: "get",
});
}
/**
* 添加分类
* @param {Object} data 分类数据
* @returns {Promise}
*/
export function addCategory(data) {
return request({
url: "/api/knowledge/category/add",
method: "post",
data,
});
}
/**
* 添加标签
* @param {Object} data 标签数据
* @returns {Promise}
*/
export function addTag(data) {
return request({
url: "/api/knowledge/tag/add",
method: "post",
data,
});
}
+68 -31
View File
@@ -66,6 +66,16 @@
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20"> <!-- Second row for share -->
<el-col :span="6">
<el-form-item label="是否共享" prop="share">
<el-radio-group v-model="formData.share">
<el-radio :label="0">个人</el-radio>
<el-radio :label="1">共享</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
</el-form>
<el-divider />
<div class="meta-actions">
@@ -105,14 +115,9 @@
import { ref, reactive, computed, onMounted, watch } from "vue";
import { useRouter, useRoute } from "vue-router";
import { marked } from "marked";
import {
getKnowledgeDetail,
updateKnowledge,
createKnowledge,
getCategoryList,
getTagList,
} from "@/api/knowledge";
import { ElMessage } from "element-plus";
// @ts-ignore
import { getKnowledgeDetail, createKnowledge, updateKnowledge, getCategoryList, getTagList } from "@/api/knowledge"; // Changed to getKnowledgeDetail
import { ElMessage, ElMessageBox } from "element-plus";
import type { FormInstance, FormRules } from "element-plus";
// 表单 DOM 引用
@@ -129,6 +134,7 @@ const formData = reactive<{
tags: string[];
author: string;
content: string;
share: number;
}>({
title: "",
category: "",
@@ -136,15 +142,13 @@ const formData = reactive<{
tags: [],
author: "",
content: "",
share: 0,
});
// 校验规则
const rules = reactive<FormRules>({
title: [{ required: true, message: "请输入标题", trigger: "blur" }],
category: [{ required: true, message: "请选择分类", trigger: "change" }],
// tags: [
// { type: "array", required: true, message: "请选择标签", trigger: "change" },
// ],
author: [{ required: true, message: "请输入作者", trigger: "blur" }],
content: [{ required: true, message: "请输入正文", trigger: "blur" }],
});
@@ -195,9 +199,9 @@ const fetchDetail = async () => {
try {
const currentId = id.value as string;
if (currentId && currentId !== "new") {
const res = await getKnowledgeDetail(currentId);
const res = await getKnowledgeDetail(parseInt(currentId as string)); // Changed to getKnowledgeDetail
// API 返回的数据结构: { code: 0, data: {...}, message: "success" }
const data = (res.code === 0 && res.data) ? res.data : (res.data || res);
const data = res.code === 0 && res.data ? res.data : res.data || res;
// 映射后端数据到前端表单
// categoryName 是从数据库联查得到的分类名称
@@ -206,6 +210,7 @@ const fetchDetail = async () => {
formData.categoryId = data.categoryId || 0;
formData.author = data.author || "";
formData.content = data.content || "";
formData.share = data.share || 0; // Added share loading
// 如果没有 categoryId,尝试根据 categoryName 查找
if (!formData.categoryId && formData.category) {
@@ -220,7 +225,8 @@ const fetchDetail = async () => {
// Tags 可能是 JSON 字符串,需要解析
if (data.tags) {
try {
const parsed = typeof data.tags === 'string' ? JSON.parse(data.tags) : data.tags;
const parsed =
typeof data.tags === "string" ? JSON.parse(data.tags) : data.tags;
formData.tags = Array.isArray(parsed) ? parsed : [];
} catch {
// 如果解析失败,尝试作为字符串数组处理
@@ -242,14 +248,18 @@ const loadCategoryAndTag = async () => {
getCategoryList(),
getTagList(),
]);
// API 返回的数据结构: { code: 0, data: [...], message: "success" }
const categories = (catRes.code === 0 && catRes.data) ? catRes.data : (catRes.data || []);
const tags = (tagRes.code === 0 && tagRes.data) ? tagRes.data : (tagRes.data || []);
const categories =
catRes.code === 0 && catRes.data ? catRes.data : catRes.data || [];
const tags =
tagRes.code === 0 && tagRes.data ? tagRes.data : tagRes.data || [];
categoryList.value = Array.isArray(categories) ? categories : [];
// 标签数据可能是 { tagId, tagName } 格式,需要提取 tagName
tagList.value = Array.isArray(tags) ? tags.map(tag => tag.tagName || tag) : [];
tagList.value = Array.isArray(tags)
? tags.map((tag) => tag.tagName || tag)
: [];
} catch (e: any) {
console.error("加载分类和标签失败:", e);
categoryList.value = [];
@@ -293,14 +303,16 @@ const handleSubmit = () => {
categoryId: Number(formData.categoryId) || 0,
author: formData.author || "",
content: formData.content || "",
tags: Array.isArray(formData.tags) && formData.tags.length > 0
? JSON.stringify(formData.tags)
: "[]",
tags:
Array.isArray(formData.tags) && formData.tags.length > 0
? JSON.stringify(formData.tags)
: "[]",
status: 1, // 默认已发布
share: formData.share, // Added share in params
};
// 调试:打印提交的数据
console.log("提交的数据:", JSON.stringify(submitData, null, 2));
// console.log("提交的数据:", JSON.stringify(submitData, null, 2));
if (isEdit.value && currentId !== "new") {
// 编辑:需要添加 id(后端 JSON tag 是 "id"
@@ -321,7 +333,8 @@ const handleSubmit = () => {
}
} catch (e: any) {
console.error("保存失败:", e);
const errorMessage = e.response?.data?.message || e.message || "保存失败";
const errorMessage =
e.response?.data?.message || e.message || "保存失败";
ElMessage.error(errorMessage);
}
} else {
@@ -337,13 +350,33 @@ const handleSubmit = () => {
}
} catch (e: any) {
console.error("创建失败:", e);
const errorMessage = e.response?.data?.message || e.message || "创建失败";
const errorMessage =
e.response?.data?.message || e.message || "创建失败";
ElMessage.error(errorMessage);
}
}
});
};
//删除功能
const handleDelete = async () => {
try {
// Assuming deleteKnowledge is imported or defined elsewhere
// await deleteKnowledge(id.value as string);
ElMessage.success("删除成功");
goBack();
} catch (e: any) {
console.error("删除失败:", e);
}
ElMessageBox.confirm("确认删除该知识?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}).then(async () => {
await handleDelete();
});
};
// 初始化
onMounted(async () => {
// 获取作者信息
@@ -490,7 +523,10 @@ defineExpose({
margin-bottom: 8px;
}
:deep(p), :deep(li), :deep(span), :deep(div) {
:deep(p),
:deep(li),
:deep(span),
:deep(div) {
color: var(--text-color-primary);
margin: 8px 0;
}
@@ -498,7 +534,7 @@ defineExpose({
:deep(a) {
color: var(--primary-color);
text-decoration: none;
&:hover {
opacity: 0.8;
}
@@ -510,7 +546,7 @@ defineExpose({
border-color: var(--border-color-lighter);
padding: 2px 6px;
border-radius: 3px;
font-family: 'Courier New', monospace;
font-family: "Courier New", monospace;
}
:deep(pre) {
@@ -519,7 +555,7 @@ defineExpose({
padding: 12px;
border-radius: 8px;
overflow-x: auto;
code {
background-color: transparent;
border: none;
@@ -558,7 +594,8 @@ defineExpose({
margin-bottom: 0px;
}
:deep(.el-input__wrapper), :deep(.el-textarea__inner) {
:deep(.el-input__wrapper),
:deep(.el-textarea__inner) {
background-color: var(--fill-color-blank) !important;
border-color: var(--border-color) !important;
color: var(--text-color-primary) !important;
@@ -568,7 +605,7 @@ defineExpose({
&::placeholder {
color: var(--text-color-placeholder) !important;
}
&:focus {
border-color: var(--primary-color) !important;
}
+190 -94
View File
@@ -83,92 +83,176 @@
</div>
</div>
<div class="repos-grid">
<div class="repo-card" v-for="repo in repoList" :key="repo.id">
<!-- 卡片头部 -->
<div class="repo-header">
<div class="repo-icon">
<i class="fa-solid fa-book"></i>
</div>
<div class="repo-title-container">
<h3 class="repo-name">{{ repo.title }}</h3>
<div class="repo-meta">
<el-tag size="small" class="category-tag">
{{ repo.categoryName || "未分类" }}
</el-tag>
<span class="repo-owner">
<i class="fa-solid fa-user"></i>
{{ repo.author }}
</span>
<!-- Tabs for personal and shared -->
<el-tabs v-model="activeTab" type="card" class="knowledge-tabs">
<el-tab-pane label="个人知识库" name="personal">
<div class="repos-grid">
<div class="repo-card" v-for="repo in personalList" :key="repo.id">
<!-- 卡片头部 -->
<div class="repo-header">
<div class="repo-icon">
<i class="fa-solid fa-book"></i>
</div>
<div class="repo-title-container">
<h3 class="repo-name">{{ repo.title }}</h3>
<div class="repo-meta">
<el-tag size="small" class="category-tag">
{{ repo.categoryName || "未分类" }}
</el-tag>
<span class="repo-owner">
<i class="fa-solid fa-user"></i>
{{ repo.author }}
</span>
</div>
</div>
</div>
<!-- 卡片内容 -->
<div class="repo-content">
<div class="repo-tags" v-if="repo.tags">
<el-tag
v-for="tag in parseTags(repo.tags)"
:key="tag"
size="small"
class="tag-item"
>
{{ tag }}
</el-tag>
</div>
<div class="repo-stats">
<div class="stat-item">
<i class="fa-solid fa-eye"></i>
<span>{{ repo.viewCount || 0 }} 浏览</span>
</div>
<div class="stat-item">
<i class="fa-solid fa-heart"></i>
<span>{{ repo.likeCount || 0 }} 点赞</span>
</div>
<div class="stat-item">
<i class="fa-solid fa-clock"></i>
<span>{{ formatDate(repo.createTime) }}</span>
</div>
</div>
</div>
<!-- 卡片底部操作区 -->
<div class="repo-actions">
<button class="action-btn view-btn" @click.stop="handleView(repo)">
<i class="el-icon-view"></i> 查看
</button>
<button class="action-btn edit-btn" @click.stop="handleEdit(repo)">
<i class="el-icon-edit"></i> 编辑
</button>
<button
class="action-btn delete-btn"
@click.stop="handleDelete(repo)"
>
<i class="el-icon-delete"></i> 删除
</button>
</div>
</div>
</div>
<!-- 卡片内容 -->
<div class="repo-content">
<div class="repo-tags" v-if="repo.tags">
<el-tag
v-for="tag in parseTags(repo.tags)"
:key="tag"
size="small"
class="tag-item"
>
{{ tag }}
</el-tag>
</div>
<div class="repo-stats">
<div class="stat-item">
<i class="fa-solid fa-eye"></i>
<span>{{ repo.viewCount || 0 }} 浏览</span>
</div>
<div class="stat-item">
<i class="fa-solid fa-heart"></i>
<span>{{ repo.likeCount || 0 }} 点赞</span>
</div>
<div class="stat-item">
<i class="fa-solid fa-clock"></i>
<span>{{ formatDate(repo.createTime) }}</span>
<!-- 空状态 for personal -->
<div class="empty-state" v-if="personalList.length === 0">
<div class="empty-icon">
<i class="fa-solid fa-folder-open"></i>
</div>
<h3>暂无个人知识库</h3>
<p>点击下方按钮创建您的第一个知识库</p>
<el-button type="primary" @click="handleCreate">
<i class="fa-solid fa-plus"></i>
新建知识库
</el-button>
</div>
</div>
</el-tab-pane>
<!-- 卡片底部操作区 -->
<div class="repo-actions">
<button class="action-btn view-btn" @click.stop="handleView(repo)">
<i class="el-icon-view"></i> 查看
</button>
<button class="action-btn edit-btn" @click.stop="handleEdit(repo)">
<i class="el-icon-edit"></i> 编辑
</button>
<button
class="action-btn delete-btn"
@click.stop="handleDelete(repo)"
>
<i class="el-icon-delete"></i> 删除
</button>
</div>
</div>
<el-tab-pane label="共享知识库" name="shared">
<div class="repos-grid">
<div class="repo-card" v-for="repo in sharedList" :key="repo.id">
<!-- 卡片头部 -->
<div class="repo-header">
<div class="repo-icon">
<i class="fa-solid fa-book"></i>
</div>
<div class="repo-title-container">
<h3 class="repo-name">{{ repo.title }}</h3>
<div class="repo-meta">
<el-tag size="small" class="category-tag">
{{ repo.categoryName || "未分类" }}
</el-tag>
<span class="repo-owner">
<i class="fa-solid fa-user"></i>
{{ repo.author }}
</span>
</div>
</div>
</div>
<!-- 空状态 -->
<div class="empty-state" v-if="repoList.length === 0">
<div class="empty-icon">
<i class="fa-solid fa-folder-open"></i>
<!-- 卡片内容 -->
<div class="repo-content">
<div class="repo-tags" v-if="repo.tags">
<el-tag
v-for="tag in parseTags(repo.tags)"
:key="tag"
size="small"
class="tag-item"
>
{{ tag }}
</el-tag>
</div>
<div class="repo-stats">
<div class="stat-item">
<i class="fa-solid fa-eye"></i>
<span>{{ repo.viewCount || 0 }} 浏览</span>
</div>
<div class="stat-item">
<i class="fa-solid fa-heart"></i>
<span>{{ repo.likeCount || 0 }} 点赞</span>
</div>
<div class="stat-item">
<i class="fa-solid fa-clock"></i>
<span>{{ formatDate(repo.createTime) }}</span>
</div>
</div>
</div>
<!-- 卡片底部操作区 -->
<div class="repo-actions">
<button class="action-btn view-btn" @click.stop="handleView(repo)">
<i class="el-icon-view"></i> 查看
</button>
<button class="action-btn edit-btn" @click.stop="handleEdit(repo)">
<i class="el-icon-edit"></i> 编辑
</button>
<button
class="action-btn delete-btn"
@click.stop="handleDelete(repo)"
>
<i class="el-icon-delete"></i> 删除
</button>
</div>
</div>
<!-- 空状态 for shared -->
<div class="empty-state" v-if="sharedList.length === 0">
<div class="empty-icon">
<i class="fa-solid fa-folder-open"></i>
</div>
<h3>暂无共享知识库</h3>
<p>暂无共享内容</p>
</div>
</div>
<h3>暂无知识库</h3>
<p>点击下方按钮创建您的第一个知识库</p>
<el-button type="primary" @click="handleCreate">
<i class="fa-solid fa-plus"></i>
新建知识库
</el-button>
</div>
</div>
</el-tab-pane>
</el-tabs>
<!-- 分页 -->
<el-pagination
background
layout="prev, pager, next"
:total="total"
:total="activeTab === 'personal' ? personalList.length : sharedList.length"
:page-size="pageSize"
:current-page="currentPage"
@current-change="handlePageChange"
@@ -182,6 +266,8 @@
import { ref, reactive, computed, onMounted } from "vue";
import { useRouter } from "vue-router";
import { ElMessage, ElMessageBox } from "element-plus";
// @ts-ignore
import { getKnowledgeList, deleteKnowledge } from "@/api/knowledge";
// 类型定义
@@ -196,6 +282,7 @@ interface Knowledge {
likeCount: number;
createTime: string;
updateTime: string;
share: number;
}
interface Stats {
@@ -216,9 +303,10 @@ const router = useRouter();
// 响应式数据
const keyword = ref("");
const searchType = ref("knowledge");
const hotTags = ref(["化妆品", "汽车零部件", "口罩", "工业用品", "食品"]);
const activeTab = ref('personal');
const stats = reactive<Stats>({
total: 0,
docCount: 0,
@@ -232,6 +320,10 @@ const pageSize = ref(12);
const currentPage = ref(1);
const loading = ref(false);
// Separate lists based on share field
const personalList = computed(() => repoList.value.filter(repo => Number(repo.share) === 0));
const sharedList = computed(() => repoList.value.filter(repo => Number(repo.share) === 1));
// 计算属性
const statsList = computed<StatItem[]>(() => [
{
@@ -258,12 +350,8 @@ const statsList = computed<StatItem[]>(() => [
// 方法
function updateStats() {
// 直接从列表数据更新统计,避免重复调用 API
stats.total = total.value;
stats.docCount = total.value;
// 其他统计数据需要后端提供专门接口时再调用
// stats.memberCount = 0; // 需要后端提供
// stats.viewCount = 0; // 需要后端提供
}
function handleSearch() {
@@ -276,61 +364,54 @@ function handlePageChange(page: number) {
fetchRepoList();
}
// 重构 fetchRepoList 以支持关键词搜索
// 重构 fetchRepoList 以支持关键词搜索 (no share filter)
async function fetchRepoList() {
loading.value = true;
try {
const result = await getKnowledgeList({
page: currentPage.value,
pageSize: pageSize.value,
status: 1, // 只查询已发布的
keyword: keyword.value, // 支持关键词搜索
status: 1,
keyword: keyword.value,
share: -1,
});
// API 返回的数据结构: { code: 0, data: { list: [...], total: 1 }, message: "success" }
if (result.code === 0 && result.data) {
repoList.value = result.data.list || [];
total.value = result.data.total || 0;
} else {
// 兼容旧的数据结构(直接返回 list 和 total
repoList.value = result.list || result.data?.list || [];
total.value = result.total || result.data?.total || 0;
}
// 更新统计数据(避免重复调用 API)
updateStats();
} catch (error: any) {
ElMessage.error(error.message || "获取知识库列表失败");
repoList.value = [];
total.value = 0;
updateStats(); // 即使失败也更新统计
updateStats();
} finally {
loading.value = false;
}
}
function handleCreate() {
// 跳转到新建知识库页面,使用路径跳转
router.push(`/apps/knowledge/edit/new`);
}
function handleView(repo: Knowledge) {
// 跳转到知识详情页面,使用路径跳转
router.push(`/apps/knowledge/detail/${repo.id}`);
}
function handleEdit(repo: Knowledge) {
// 跳转到编辑知识页面,使用路径跳转
router.push(`/apps/knowledge/edit/${repo.id}`);
}
function handleCategory() {
// 跳转到分类管理页面,使用路径跳转
router.push(`/apps/knowledge/category`);
}
function handleTags() {
// 跳转到标签管理页面,使用路径跳转
router.push(`/apps/knowledge/tags`);
}
@@ -344,14 +425,12 @@ function handleDelete(repo: Knowledge) {
try {
await deleteKnowledge(repo.id);
ElMessage.success("删除成功");
fetchRepoList(); // 重新加载列表(会自动更新统计)
fetchRepoList();
} catch (error: any) {
ElMessage.error(error.message || "删除失败");
}
})
.catch(() => {
// 取消删除
});
.catch(() => {});
}
function handleHotSearch(tag: string) {
@@ -397,7 +476,7 @@ function formatDate(dateStr: string): string {
// 生命周期
onMounted(() => {
fetchRepoList(); // 只调用一次,会自动更新统计
fetchRepoList();
});
</script>
@@ -924,6 +1003,23 @@ onMounted(() => {
}
}
// Tabs styling for theme fit
.knowledge-tabs {
margin-bottom: 20px;
transition: all 0.3s ease;
:deep(.el-tabs__item) {
background-color: var(--fill-color-light);
border-color: var(--border-color);
color: var(--text-color-primary);
&.is-active {
background-color: var(--primary-color);
color: white;
}
}
}
// 响应式调整
@media (max-width: 768px) {
.hero.new-style {