Compare commits
2 Commits
1697a71693
...
4547dc7777
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4547dc7777 | ||
|
|
f5ef075fb1 |
86
src/api/kb/favorite.ts
Normal file
86
src/api/kb/favorite.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import { request } from '@/api/request';
|
||||
|
||||
/** 资源类型 */
|
||||
export type ResourceType = 'document' | 'faq';
|
||||
|
||||
/** 收藏记录接口 */
|
||||
export interface Favorite {
|
||||
id: number;
|
||||
created_at: string;
|
||||
resource_type: ResourceType;
|
||||
resource_id: number;
|
||||
resource_name: string;
|
||||
remarks: string;
|
||||
is_deleted: boolean;
|
||||
resource_data?: any;
|
||||
}
|
||||
|
||||
/** 收藏列表响应 */
|
||||
export interface FavoriteListResponse {
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
data: Favorite[];
|
||||
}
|
||||
|
||||
/** 获取收藏列表参数 */
|
||||
export interface FetchFavoriteListParams {
|
||||
page?: number;
|
||||
page_size?: number;
|
||||
resource_type?: ResourceType;
|
||||
}
|
||||
|
||||
/** 收藏请求参数 */
|
||||
export interface CollectParams {
|
||||
resource_type: ResourceType;
|
||||
resource_id: number;
|
||||
remarks?: string;
|
||||
}
|
||||
|
||||
/** 取消收藏参数 */
|
||||
export interface UncollectParams {
|
||||
resource_type: ResourceType;
|
||||
resource_id: number;
|
||||
}
|
||||
|
||||
/** API响应包装类型 */
|
||||
export interface ApiResponse<T = any> {
|
||||
code: number;
|
||||
message: string;
|
||||
data: T;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取收藏列表
|
||||
*/
|
||||
export async function fetchFavoriteList(
|
||||
params: FetchFavoriteListParams = {}
|
||||
): Promise<ApiResponse<FavoriteListResponse>> {
|
||||
return request.get<ApiResponse<FavoriteListResponse>>('/Kb/v1/favorite/list', {
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 收藏资源
|
||||
*/
|
||||
export async function collectResource(
|
||||
data: CollectParams
|
||||
): Promise<ApiResponse<Favorite>> {
|
||||
return request.post<ApiResponse<Favorite>>('/Kb/v1/favorite/collect', data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消收藏
|
||||
*/
|
||||
export async function uncollectResource(
|
||||
data: UncollectParams
|
||||
): Promise<ApiResponse<string>> {
|
||||
return request.post<ApiResponse<string>>('/Kb/v1/favorite/uncollect', data);
|
||||
}
|
||||
|
||||
/** 资源类型选项 */
|
||||
export const resourceTypeOptions = [
|
||||
{ label: '文档', value: 'document' },
|
||||
{ label: 'FAQ', value: 'faq' },
|
||||
];
|
||||
@@ -12,6 +12,162 @@ export interface ReviewStatsPayload {
|
||||
need_my_review_unreviewed_total?: number;
|
||||
}
|
||||
|
||||
/** 文档资源类型 */
|
||||
export interface DocumentResource {
|
||||
id: number;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
doc_no: string;
|
||||
title: string;
|
||||
description: string;
|
||||
content: string;
|
||||
type: string;
|
||||
status: string;
|
||||
category_id: number;
|
||||
sub_category: string;
|
||||
author_id: number;
|
||||
author_name: string;
|
||||
reviewer_id: number;
|
||||
reviewer_name: string;
|
||||
reviewed_at: string | null;
|
||||
published_at: string | null;
|
||||
publisher_id: number;
|
||||
view_count: number;
|
||||
like_count: number;
|
||||
comment_count: number;
|
||||
download_count: number;
|
||||
version: string;
|
||||
version_notes: string;
|
||||
tags: string;
|
||||
attachments: string | null;
|
||||
related_docs: string | null;
|
||||
metadata: string | null;
|
||||
keywords: string;
|
||||
remarks: string;
|
||||
}
|
||||
|
||||
/** FAQ资源类型 */
|
||||
export interface FaqResource {
|
||||
id: number;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
faq_no: string;
|
||||
question: string;
|
||||
answer: string;
|
||||
status: string;
|
||||
priority: string;
|
||||
category_id: number;
|
||||
sub_category: string;
|
||||
problem_type: string;
|
||||
solution: string;
|
||||
process_steps: string;
|
||||
prerequisites: string;
|
||||
author_id: number;
|
||||
author_name: string;
|
||||
reviewer_id: number;
|
||||
reviewer_name: string;
|
||||
reviewed_at: string | null;
|
||||
published_at: string | null;
|
||||
view_count: number;
|
||||
use_count: number;
|
||||
helpful_count: number;
|
||||
useless_count: number;
|
||||
tags: string;
|
||||
related_faqs: string | null;
|
||||
related_docs: string | null;
|
||||
related_links: string | null;
|
||||
attachments: string | null;
|
||||
keywords: string;
|
||||
applicable_scope: string;
|
||||
remarks: string;
|
||||
}
|
||||
|
||||
/** 审核列表项(resource_type 为 all 时) */
|
||||
export interface ReviewListItem {
|
||||
type: "document" | "faq";
|
||||
resource: DocumentResource | FaqResource;
|
||||
}
|
||||
|
||||
/** 分页响应类型 */
|
||||
export interface PaginatedResponse<T> {
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
data: T[];
|
||||
}
|
||||
|
||||
/** API响应包装类型 */
|
||||
export interface ApiResponse<T = any> {
|
||||
code: number;
|
||||
message: string;
|
||||
data: T;
|
||||
}
|
||||
|
||||
/** 获取审核列表参数 */
|
||||
export interface FetchReviewListParams {
|
||||
page?: number;
|
||||
page_size?: number;
|
||||
resource_type?: ReviewStatsResourceType;
|
||||
}
|
||||
|
||||
/** 审核通过参数 */
|
||||
export interface ApproveParams {
|
||||
resource_type: "document" | "faq";
|
||||
id: number;
|
||||
}
|
||||
|
||||
/** 审核拒绝参数 */
|
||||
export interface RejectParams {
|
||||
resource_type: "document" | "faq";
|
||||
id: number;
|
||||
reason?: string;
|
||||
}
|
||||
|
||||
/** 按当前登录用户统计需要本人审核的数量(不含本人为作者的稿件) */
|
||||
export const fetchReviewStats = (params?: { resource_type?: ReviewStatsResourceType }) =>
|
||||
request.get("/Kb/v1/review/stats", params ? { params } : undefined);
|
||||
|
||||
/** 获取待审核列表 */
|
||||
export const fetchReviewList = (params?: FetchReviewListParams) =>
|
||||
request.get<ApiResponse<PaginatedResponse<ReviewListItem | DocumentResource | FaqResource>>>("/Kb/v1/review/list", { params });
|
||||
|
||||
/** 审核通过 */
|
||||
export const approveReview = (data: ApproveParams) =>
|
||||
request.post<ApiResponse<string>>("/Kb/v1/review/approve", data);
|
||||
|
||||
/** 审核拒绝 */
|
||||
export const rejectReview = (data: RejectParams) =>
|
||||
request.post<ApiResponse<string>>("/Kb/v1/review/reject", data);
|
||||
|
||||
/** 获取文档详情 */
|
||||
export const fetchDocumentDetail = (id: number) =>
|
||||
request.get<ApiResponse<DocumentResource>>(`/Kb/v1/document/${id}`);
|
||||
|
||||
/** 获取FAQ详情 */
|
||||
export const fetchFaqDetail = (id: number) =>
|
||||
request.get<ApiResponse<FaqResource>>(`/Kb/v1/faq/${id}`);
|
||||
|
||||
/** 资源类型选项 */
|
||||
export const resourceTypeOptions = [
|
||||
{ label: '全部', value: 'all' },
|
||||
{ label: '文档', value: 'document' },
|
||||
{ label: 'FAQ', value: 'faq' },
|
||||
];
|
||||
|
||||
/** 获取资源类型文本 */
|
||||
export const getResourceTypeText = (type: string): string => {
|
||||
const typeMap: Record<string, string> = {
|
||||
document: '文档',
|
||||
faq: 'FAQ',
|
||||
};
|
||||
return typeMap[type] || type;
|
||||
};
|
||||
|
||||
/** 获取资源类型颜色 */
|
||||
export const getResourceTypeColor = (type: string): string => {
|
||||
const colorMap: Record<string, string> = {
|
||||
document: 'arcoblue',
|
||||
faq: 'green',
|
||||
};
|
||||
return colorMap[type] || 'gray';
|
||||
};
|
||||
|
||||
@@ -133,6 +133,16 @@ const OPS: AppRouteRecordRaw = {
|
||||
roles: ['*'],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'favorite',
|
||||
name: 'Favorite',
|
||||
component: () => import('@/views/ops/pages/favorite/index.vue'),
|
||||
meta: {
|
||||
locale: '收藏管理',
|
||||
requiresAuth: true,
|
||||
roles: ['*'],
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
440
src/views/ops/pages/kb/favorite/index.vue
Normal file
440
src/views/ops/pages/kb/favorite/index.vue
Normal file
@@ -0,0 +1,440 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<a-card class="general-card" title="收藏管理">
|
||||
<!-- 数据表格 -->
|
||||
<a-table
|
||||
:data="tableData"
|
||||
:columns="columns"
|
||||
:loading="loading"
|
||||
:pagination="pagination"
|
||||
row-key="id"
|
||||
@page-change="handlePageChange"
|
||||
>
|
||||
<!-- 序号 -->
|
||||
<template #index="{ rowIndex }">
|
||||
{{ rowIndex + 1 + (pagination.current - 1) * pagination.pageSize }}
|
||||
</template>
|
||||
|
||||
<!-- 资源类型 -->
|
||||
<template #resource_type="{ record }">
|
||||
<a-tag :color="record.resource_type === 'document' ? 'arc-blue' : 'arc-green'">
|
||||
{{ record.resource_type === 'document' ? '文档' : 'FAQ' }}
|
||||
</a-tag>
|
||||
</template>
|
||||
|
||||
<!-- 收藏时间 -->
|
||||
<template #created_at="{ record }">
|
||||
{{ formatDateTime(record.created_at) }}
|
||||
</template>
|
||||
|
||||
<!-- 操作 -->
|
||||
<template #actions="{ record }">
|
||||
<a-space>
|
||||
<a-button
|
||||
type="text"
|
||||
size="small"
|
||||
:disabled="record.is_deleted"
|
||||
@click="handleView(record)"
|
||||
>
|
||||
查看
|
||||
</a-button>
|
||||
<a-button
|
||||
type="text"
|
||||
size="small"
|
||||
:disabled="record.is_deleted"
|
||||
@click="handleDownload(record)"
|
||||
>
|
||||
下载
|
||||
</a-button>
|
||||
<a-button
|
||||
type="text"
|
||||
size="small"
|
||||
status="danger"
|
||||
@click="handleUncollect(record)"
|
||||
>
|
||||
取消收藏
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
|
||||
<!-- 文档详情对话框 -->
|
||||
<a-modal
|
||||
v-model:visible="detailVisible"
|
||||
title="文档详情"
|
||||
:width="800"
|
||||
:footer="false"
|
||||
unmount-on-close
|
||||
>
|
||||
<div v-if="currentResource" class="detail-content">
|
||||
<a-descriptions :column="2" bordered>
|
||||
<a-descriptions-item label="文档编号">
|
||||
{{ currentResource.doc_no || '-' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="标题">
|
||||
{{ currentResource.title || '-' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="作者">
|
||||
{{ currentResource.author_name || '-' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="状态">
|
||||
<a-tag :color="getDocStatusColor(currentResource.status)">
|
||||
{{ getDocStatusText(currentResource.status) }}
|
||||
</a-tag>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="发布时间">
|
||||
{{ formatDateTime(currentResource.published_at) }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="浏览次数">
|
||||
{{ currentResource.view_count || 0 }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="描述" :span="2">
|
||||
{{ currentResource.description || '-' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="内容" :span="2">
|
||||
<div class="content-preview" v-html="currentResource.content || '-'"></div>
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</div>
|
||||
</a-modal>
|
||||
|
||||
<!-- FAQ详情对话框 -->
|
||||
<a-modal
|
||||
v-model:visible="faqDetailVisible"
|
||||
title="FAQ详情"
|
||||
:width="800"
|
||||
:footer="false"
|
||||
unmount-on-close
|
||||
>
|
||||
<div v-if="currentFaq" class="detail-content">
|
||||
<a-descriptions :column="2" bordered>
|
||||
<a-descriptions-item label="FAQ编号">
|
||||
{{ currentFaq.faq_no || '-' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="状态">
|
||||
<a-tag color="green">{{ currentFaq.status || '已发布' }}</a-tag>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="问题" :span="2">
|
||||
{{ currentFaq.question || '-' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="答案" :span="2">
|
||||
<div class="content-preview" v-html="currentFaq.answer || '-'"></div>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="浏览次数">
|
||||
{{ currentFaq.view_count || 0 }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="有用次数">
|
||||
{{ currentFaq.helpful_count || 0 }}
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</div>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, computed, onMounted } from 'vue';
|
||||
import { Message, Modal } from '@arco-design/web-vue';
|
||||
import type { TableColumnData } from '@arco-design/web-vue/es/table/interface';
|
||||
import {
|
||||
fetchFavoriteList,
|
||||
uncollectResource,
|
||||
type Favorite,
|
||||
type ResourceType,
|
||||
} from '@/api/kb/favorite';
|
||||
import { request } from '@/api/request';
|
||||
|
||||
// 状态管理
|
||||
const loading = ref(false);
|
||||
const tableData = ref<Favorite[]>([]);
|
||||
|
||||
const pagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 20,
|
||||
total: 0,
|
||||
});
|
||||
|
||||
// 表格列配置
|
||||
const columns = computed<TableColumnData[]>(() => [
|
||||
{
|
||||
title: '序号',
|
||||
dataIndex: 'index',
|
||||
slotName: 'index',
|
||||
width: 70,
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '资源名称',
|
||||
dataIndex: 'resource_name',
|
||||
ellipsis: true,
|
||||
tooltip: true,
|
||||
width: 250,
|
||||
},
|
||||
{
|
||||
title: '资源类型',
|
||||
dataIndex: 'resource_type',
|
||||
slotName: 'resource_type',
|
||||
width: 100,
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '备注',
|
||||
dataIndex: 'remarks',
|
||||
ellipsis: true,
|
||||
tooltip: true,
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: '收藏时间',
|
||||
dataIndex: 'created_at',
|
||||
slotName: 'created_at',
|
||||
width: 180,
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
slotName: 'actions',
|
||||
width: 200,
|
||||
fixed: 'right',
|
||||
},
|
||||
]);
|
||||
|
||||
// 当前选中的资源
|
||||
const currentResource = ref<any>(null);
|
||||
const currentFaq = ref<any>(null);
|
||||
|
||||
// 对话框可见性
|
||||
const detailVisible = ref(false);
|
||||
const faqDetailVisible = ref(false);
|
||||
|
||||
// 获取收藏列表
|
||||
const fetchFavorites = async () => {
|
||||
loading.value = true;
|
||||
|
||||
try {
|
||||
const params = {
|
||||
page: pagination.current,
|
||||
page_size: pagination.pageSize,
|
||||
};
|
||||
|
||||
const res: any = await fetchFavoriteList(params);
|
||||
|
||||
if (res.code === 0) {
|
||||
tableData.value = res.details?.data || [];
|
||||
pagination.total = res.details?.total || 0;
|
||||
} else {
|
||||
Message.error(res.message || '获取收藏列表失败');
|
||||
tableData.value = [];
|
||||
pagination.total = 0;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取收藏列表失败:', error);
|
||||
Message.error('获取收藏列表失败');
|
||||
tableData.value = [];
|
||||
pagination.total = 0;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 分页变化
|
||||
const handlePageChange = (current: number) => {
|
||||
pagination.current = current;
|
||||
fetchFavorites();
|
||||
};
|
||||
|
||||
// 查看详情
|
||||
const handleView = async (record: Favorite) => {
|
||||
if (record.is_deleted) {
|
||||
Message.warning('该资源已被删除,无法查看');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (record.resource_type === 'document') {
|
||||
// 如果有resource_data直接使用,否则请求详情
|
||||
if (record.resource_data) {
|
||||
currentResource.value = record.resource_data;
|
||||
detailVisible.value = true;
|
||||
} else {
|
||||
const res = await request.get<any>(`/Kb/v1/document/${record.resource_id}`);
|
||||
if (res.code === 0) {
|
||||
currentResource.value = res.details;
|
||||
detailVisible.value = true;
|
||||
} else {
|
||||
Message.error(res.message || '获取文档详情失败');
|
||||
}
|
||||
}
|
||||
} else if (record.resource_type === 'faq') {
|
||||
// FAQ详情
|
||||
if (record.resource_data) {
|
||||
currentFaq.value = record.resource_data;
|
||||
faqDetailVisible.value = true;
|
||||
} else {
|
||||
const res = await request.get<any>(`/Kb/v1/faq/${record.resource_id}`);
|
||||
if (res.code === 0) {
|
||||
currentFaq.value = res.details;
|
||||
faqDetailVisible.value = true;
|
||||
} else {
|
||||
Message.error(res.message || '获取FAQ详情失败');
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取详情失败:', error);
|
||||
Message.error('获取详情失败');
|
||||
}
|
||||
};
|
||||
|
||||
// 下载文档
|
||||
const handleDownload = async (record: Favorite) => {
|
||||
if (record.is_deleted) {
|
||||
Message.warning('该资源已被删除,无法下载');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (record.resource_type === 'document') {
|
||||
// 调用文档下载接口
|
||||
const res: any = await request.get<any>(`/Kb/v1/document/${record.resource_id}`);
|
||||
if (res.code === 0) {
|
||||
const doc = res.details;
|
||||
// 创建下载内容
|
||||
const content = `# ${doc.title || '无标题'}\n\n## 描述\n${doc.description || '无描述'}\n\n## 内容\n${doc.content || '无内容'}`;
|
||||
const blob = new Blob([content], { type: 'text/markdown;charset=utf-8' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = `${doc.title || 'document'}.md`;
|
||||
link.click();
|
||||
URL.revokeObjectURL(url);
|
||||
Message.success('下载成功');
|
||||
} else {
|
||||
Message.error(res.message || '获取文档失败');
|
||||
}
|
||||
} else if (record.resource_type === 'faq') {
|
||||
// FAQ下载
|
||||
const res = await request.get<any>(`/Kb/v1/faq/${record.resource_id}`);
|
||||
if (res.code === 0) {
|
||||
const faq = res.details;
|
||||
const content = `# ${faq.question || 'FAQ'}\n\n## 答案\n${faq.answer || '无答案'}`;
|
||||
const blob = new Blob([content], { type: 'text/markdown;charset=utf-8' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = `FAQ-${faq.faq_no || 'unknown'}.md`;
|
||||
link.click();
|
||||
URL.revokeObjectURL(url);
|
||||
Message.success('下载成功');
|
||||
} else {
|
||||
Message.error(res.message || '获取FAQ失败');
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('下载失败:', error);
|
||||
Message.error('下载失败');
|
||||
}
|
||||
};
|
||||
|
||||
// 取消收藏
|
||||
const handleUncollect = (record: Favorite) => {
|
||||
Modal.confirm({
|
||||
title: '确认取消收藏',
|
||||
content: `确认取消收藏「${record.resource_name}」吗?`,
|
||||
onOk: async () => {
|
||||
try {
|
||||
const res = await uncollectResource({
|
||||
resource_type: record.resource_type,
|
||||
resource_id: record.resource_id,
|
||||
});
|
||||
|
||||
if (res.code === 0) {
|
||||
Message.success('取消收藏成功');
|
||||
fetchFavorites();
|
||||
} else {
|
||||
Message.error(res.message || '取消收藏失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('取消收藏失败:', error);
|
||||
Message.error('取消收藏失败');
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// 格式化日期时间
|
||||
const formatDateTime = (dateStr?: string) => {
|
||||
if (!dateStr) return '-';
|
||||
// 处理多种日期格式
|
||||
let date: Date;
|
||||
if (dateStr.includes('T')) {
|
||||
date = new Date(dateStr);
|
||||
} else {
|
||||
// 格式: YYYY-MM-DD HH:mm:ss
|
||||
date = new Date(dateStr.replace(' ', 'T'));
|
||||
}
|
||||
|
||||
if (isNaN(date.getTime())) return dateStr;
|
||||
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
const hours = String(date.getHours()).padStart(2, '0');
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0');
|
||||
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}`;
|
||||
};
|
||||
|
||||
// 获取文档状态颜色
|
||||
const getDocStatusColor = (status?: string) => {
|
||||
const colorMap: Record<string, string> = {
|
||||
draft: 'gray',
|
||||
published: 'green',
|
||||
reviewed: 'blue',
|
||||
rejected: 'red',
|
||||
};
|
||||
return colorMap[status || ''] || 'gray';
|
||||
};
|
||||
|
||||
// 获取文档状态文本
|
||||
const getDocStatusText = (status?: string) => {
|
||||
const textMap: Record<string, string> = {
|
||||
draft: '草稿',
|
||||
published: '已发布',
|
||||
reviewed: '已审核',
|
||||
rejected: '已拒绝',
|
||||
};
|
||||
return textMap[status || ''] || '未知';
|
||||
};
|
||||
|
||||
// 初始化加载数据
|
||||
onMounted(() => {
|
||||
fetchFavorites();
|
||||
});
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'FavoriteManage',
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.detail-content {
|
||||
max-height: 60vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.content-preview {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
padding: 8px;
|
||||
background-color: var(--color-fill-1);
|
||||
border-radius: 4px;
|
||||
}
|
||||
</style>
|
||||
@@ -90,14 +90,14 @@
|
||||
</template>
|
||||
<template #extra>
|
||||
<a-space v-if="currentDocument || isEditing">
|
||||
<!-- 编辑模式切换 -->
|
||||
<a-button-group>
|
||||
<!-- 编辑模式切换(仅在编辑状态时显示) -->
|
||||
<a-button-group v-if="isEditing">
|
||||
<a-button
|
||||
:type="editorMode === 'edit' ? 'primary' : 'secondary'"
|
||||
size="small"
|
||||
@click="editorMode = 'edit'"
|
||||
>
|
||||
编辑
|
||||
源码
|
||||
</a-button>
|
||||
<a-button
|
||||
:type="editorMode === 'preview' ? 'primary' : 'secondary'"
|
||||
@@ -209,9 +209,9 @@
|
||||
</div>
|
||||
|
||||
<!-- Markdown编辑/预览区域 -->
|
||||
<div class="markdown-area" :class="editorMode">
|
||||
<!-- 编辑区 -->
|
||||
<div v-show="editorMode === 'edit' || editorMode === 'split'" class="editor-pane">
|
||||
<div class="markdown-area" :class="isEditing ? editorMode : 'preview'">
|
||||
<!-- 编辑区(仅在编辑状态时显示) -->
|
||||
<div v-show="isEditing && (editorMode === 'edit' || editorMode === 'split')" class="editor-pane">
|
||||
<!-- 工具栏 -->
|
||||
<div class="md-toolbar">
|
||||
<a-space>
|
||||
@@ -264,8 +264,8 @@
|
||||
class="md-editor"
|
||||
/>
|
||||
</div>
|
||||
<!-- 预览区 -->
|
||||
<div v-show="editorMode === 'preview' || editorMode === 'split'" class="preview-pane">
|
||||
<!-- 预览区(非编辑状态始终显示,编辑状态根据模式显示) -->
|
||||
<div v-show="!isEditing || editorMode === 'preview' || editorMode === 'split'" class="preview-pane">
|
||||
<div class="md-preview" v-html="renderedContent"></div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -410,30 +410,31 @@ const fetchData = async () => {
|
||||
|
||||
// 兼容多种API响应格式
|
||||
if (res?.code === 0) {
|
||||
let rawData: any[] = []
|
||||
|
||||
// 格式1: res.details.data
|
||||
if (res.details?.data) {
|
||||
documentList.value = res.details.data || []
|
||||
rawData = res.details.data || []
|
||||
total.value = res.details.total || 0
|
||||
}
|
||||
// 格式2: res.data.data
|
||||
else if (res.data?.data) {
|
||||
documentList.value = res.data.data || []
|
||||
rawData = res.data.data || []
|
||||
total.value = res.data.total || 0
|
||||
}
|
||||
// 格式3: res.data 是数组
|
||||
else if (Array.isArray(res.data)) {
|
||||
documentList.value = res.data
|
||||
rawData = res.data
|
||||
total.value = res.data.length
|
||||
}
|
||||
// 格式4: res.details 是数组
|
||||
else if (Array.isArray(res.details)) {
|
||||
documentList.value = res.details
|
||||
rawData = res.details
|
||||
total.value = res.details.length
|
||||
}
|
||||
else {
|
||||
documentList.value = []
|
||||
total.value = 0
|
||||
}
|
||||
|
||||
// 处理数据:提取 resource 字段(document 和 faq 都作为文档处理)
|
||||
documentList.value = rawData.map((item: any) => item.resource)
|
||||
} else {
|
||||
documentList.value = []
|
||||
total.value = 0
|
||||
@@ -496,10 +497,12 @@ const handleSelectDocument = async (doc: Document) => {
|
||||
loading.value = true
|
||||
const res: any = await fetchDocumentDetail(doc.id)
|
||||
if (res?.code === 0 && res.details) {
|
||||
currentDocument.value = res.details
|
||||
// 兼容两种响应格式:直接返回文档对象 或 { type, resource } 格式
|
||||
const docData = res.details.resource || res.details
|
||||
currentDocument.value = docData
|
||||
|
||||
// 检查收藏状态
|
||||
if (res.details.status === 'reviewed') {
|
||||
if (docData.status === 'reviewed') {
|
||||
try {
|
||||
const favRes: any = await checkFavorite(doc.id)
|
||||
isFavorited.value = favRes?.details?.is_favorited || false
|
||||
@@ -932,6 +935,7 @@ export default {
|
||||
}
|
||||
|
||||
.editor-header {
|
||||
max-width: 80%;
|
||||
.title-input {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
|
||||
@@ -1,73 +1,63 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<Breadcrumb :items="['知识管理', '回收站']" />
|
||||
<SearchTable
|
||||
:form-model="searchForm"
|
||||
:form-items="filters"
|
||||
:data="tableData"
|
||||
:columns="columns"
|
||||
:loading="loading"
|
||||
title="回收站"
|
||||
:pagination="{
|
||||
current: page,
|
||||
pageSize,
|
||||
total,
|
||||
}"
|
||||
:show-download="false"
|
||||
@update:form-model="handleFormModelUpdate"
|
||||
@search="handleSearch"
|
||||
@reset="handleReset"
|
||||
@page-change="handlePageChange"
|
||||
@refresh="fetchData"
|
||||
>
|
||||
<!-- 序号列 -->
|
||||
<template #index="{ rowIndex }">
|
||||
{{ rowIndex + 1 + (page - 1) * pageSize }}
|
||||
</template>
|
||||
|
||||
<a-card class="general-card">
|
||||
<!-- 搜索表单 -->
|
||||
<SearchForm
|
||||
:model-value="searchForm"
|
||||
:form-items="filters"
|
||||
@update:model-value="handleFormModelUpdate"
|
||||
@search="handleSearch"
|
||||
@reset="handleReset"
|
||||
/>
|
||||
<!-- 资源类型列 -->
|
||||
<template #resource_type="{ record }">
|
||||
<a-tag :color="getResourceTypeColor(record.resource_type)">
|
||||
{{ getResourceTypeText(record.resource_type) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
|
||||
<a-divider style="margin-top: 0" />
|
||||
<!-- 删除时间列 -->
|
||||
<template #deleted_time="{ record }">
|
||||
{{ formatTime(record.deleted_time) }}
|
||||
</template>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<DataTable
|
||||
:data="tableData"
|
||||
:columns="columns"
|
||||
:loading="loading"
|
||||
:pagination="{
|
||||
current: page,
|
||||
pageSize,
|
||||
total,
|
||||
}"
|
||||
:show-download="false"
|
||||
@page-change="handlePageChange"
|
||||
@refresh="fetchData"
|
||||
>
|
||||
<!-- 序号列 -->
|
||||
<template #index="{ rowIndex }">
|
||||
{{ rowIndex + 1 + (page - 1) * pageSize }}
|
||||
</template>
|
||||
<!-- 删除人列 -->
|
||||
<template #deleted_name="{ record }">
|
||||
{{ record.deleted_name || '-' }}
|
||||
</template>
|
||||
|
||||
<!-- 资源类型列 -->
|
||||
<template #resource_type="{ record }">
|
||||
<a-tag :color="getResourceTypeColor(record.resource_type)">
|
||||
{{ getResourceTypeText(record.resource_type) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
|
||||
<!-- 删除时间列 -->
|
||||
<template #deleted_time="{ record }">
|
||||
{{ formatTime(record.deleted_time) }}
|
||||
</template>
|
||||
|
||||
<!-- 删除人列 -->
|
||||
<template #deleted_name="{ record }">
|
||||
{{ record.deleted_name || '-' }}
|
||||
</template>
|
||||
|
||||
<!-- 操作列 -->
|
||||
<template #operation="{ record }">
|
||||
<a-space>
|
||||
<a-button type="text" size="small" @click="handleRestore(record)">
|
||||
恢复
|
||||
</a-button>
|
||||
<a-button
|
||||
type="text"
|
||||
size="small"
|
||||
status="danger"
|
||||
@click="handleDelete(record)"
|
||||
>
|
||||
彻底删除
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</DataTable>
|
||||
</a-card>
|
||||
<!-- 操作列 -->
|
||||
<template #operation="{ record }">
|
||||
<a-space>
|
||||
<a-button type="text" size="small" @click="handleRestore(record)">
|
||||
恢复
|
||||
</a-button>
|
||||
<a-button
|
||||
type="text"
|
||||
size="small"
|
||||
status="danger"
|
||||
@click="handleDelete(record)"
|
||||
>
|
||||
彻底删除
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</SearchTable>
|
||||
|
||||
<!-- 恢复确认对话框 -->
|
||||
<a-modal
|
||||
@@ -103,8 +93,7 @@
|
||||
import { ref, reactive, computed, onMounted } from 'vue'
|
||||
import { Message } from '@arco-design/web-vue'
|
||||
import dayjs from 'dayjs'
|
||||
import SearchForm from '@/components/search-form/index.vue'
|
||||
import DataTable from '@/components/data-table/index.vue'
|
||||
import SearchTable from '@/components/search-table/index.vue'
|
||||
import type { FormItem } from '@/components/search-form/types'
|
||||
import type { TableColumnData } from '@arco-design/web-vue/es/table/interface'
|
||||
import type { TrashRecord, FetchTrashListParams } from '@/api/kb/trash'
|
||||
@@ -181,7 +170,7 @@ const filters = computed((): FormItem[] => [
|
||||
type: 'select',
|
||||
placeholder: '请选择资源类型',
|
||||
options: resourceTypeOptions,
|
||||
span: 4,
|
||||
span: 6,
|
||||
},
|
||||
])
|
||||
|
||||
@@ -226,11 +215,11 @@ const fetchData = async () => {
|
||||
| undefined,
|
||||
}
|
||||
|
||||
const res = await fetchTrashList(params)
|
||||
|
||||
if (res?.code === 200 && res.data) {
|
||||
const res: any = await fetchTrashList(params)
|
||||
console.log('获取回收站列表成功:', res)
|
||||
if (res?.code === 0) {
|
||||
// 如果有关键词,在前端过滤
|
||||
let data = res.data.data || []
|
||||
let data = res.details?.data || []
|
||||
if (searchForm.keyword) {
|
||||
const keyword = searchForm.keyword.toLowerCase()
|
||||
data = data.filter(
|
||||
@@ -240,7 +229,7 @@ const fetchData = async () => {
|
||||
)
|
||||
}
|
||||
tableData.value = data
|
||||
total.value = res.data.total || 0
|
||||
total.value = res.details.total || 0
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取回收站列表失败:', error)
|
||||
@@ -288,7 +277,7 @@ const handleConfirmRestore = async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
const res = await restoreTrash({ id: recordToRestore.value.id })
|
||||
if (res?.code === 200) {
|
||||
if (res?.code === 0) {
|
||||
Message.success('恢复成功')
|
||||
restoreConfirmVisible.value = false
|
||||
recordToRestore.value = null
|
||||
@@ -317,7 +306,7 @@ const handleConfirmDelete = async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
const res = await deleteTrash({ id: recordToDelete.value.id })
|
||||
if (res?.code === 200) {
|
||||
if (res?.code === 0) {
|
||||
Message.success('彻底删除成功')
|
||||
deleteConfirmVisible.value = false
|
||||
recordToDelete.value = null
|
||||
@@ -339,12 +328,9 @@ onMounted(() => {
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
<style scoped lang="less">
|
||||
.container {
|
||||
padding: 0 20px 20px 20px;
|
||||
}
|
||||
|
||||
.general-card {
|
||||
margin-top: 16px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,474 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<SearchTable
|
||||
:form-model="searchForm"
|
||||
:form-items="filters"
|
||||
:data="tableData"
|
||||
:columns="columns"
|
||||
:loading="loading"
|
||||
title="待审核列表"
|
||||
:pagination="{
|
||||
current: page,
|
||||
pageSize,
|
||||
total,
|
||||
}"
|
||||
:show-download="false"
|
||||
@update:form-model="handleFormModelUpdate"
|
||||
@search="handleSearch"
|
||||
@reset="handleReset"
|
||||
@page-change="handlePageChange"
|
||||
@refresh="fetchData"
|
||||
>
|
||||
<!-- 序号列 -->
|
||||
<template #index="{ rowIndex }">
|
||||
{{ rowIndex + 1 + (page - 1) * pageSize }}
|
||||
</template>
|
||||
|
||||
<!-- 分类列 -->
|
||||
<template #category="{ record }">
|
||||
<a-tag :color="getResourceTypeColor(record.type)">
|
||||
{{ getResourceTypeText(record.type) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
|
||||
<!-- 作者列 -->
|
||||
<template #author="{ record }">
|
||||
{{ record.resource?.author_name || '-' }}
|
||||
</template>
|
||||
|
||||
<!-- 申请时间列 -->
|
||||
<template #created_at="{ record }">
|
||||
{{ formatTime(record.resource?.created_at) }}
|
||||
</template>
|
||||
|
||||
<!-- 描述列 -->
|
||||
<template #description="{ record }">
|
||||
<a-tooltip :content="record.resource?.description || record.resource?.question || '-'">
|
||||
<span class="description-text">
|
||||
{{ record.resource?.description || record.resource?.question || '-' }}
|
||||
</span>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
|
||||
<!-- 操作列 -->
|
||||
<template #operation="{ record }">
|
||||
<a-space>
|
||||
<a-button type="text" size="small" @click="handleView(record)">
|
||||
查看
|
||||
</a-button>
|
||||
<a-button type="text" size="small" status="success" @click="handleApprove(record)">
|
||||
审核通过
|
||||
</a-button>
|
||||
<a-button type="text" size="small" status="danger" @click="handleReject(record)">
|
||||
拒绝
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</SearchTable>
|
||||
|
||||
<!-- 详情弹窗 -->
|
||||
<a-modal
|
||||
v-model:visible="detailVisible"
|
||||
title="详情"
|
||||
:width="720"
|
||||
:footer="false"
|
||||
>
|
||||
<a-descriptions :column="2" bordered>
|
||||
<a-descriptions-item label="类型">
|
||||
<a-tag :color="getResourceTypeColor(currentRecord?.type || '')">
|
||||
{{ getResourceTypeText(currentRecord?.type || '') }}
|
||||
</a-tag>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="编号">
|
||||
{{ getResourceNo(currentRecord) }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="标题" :span="2">
|
||||
{{ getResourceTitle(currentRecord) }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="作者">
|
||||
{{ currentRecord?.resource?.author_name || '-' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="创建时间">
|
||||
{{ formatTime(currentRecord?.resource?.created_at) }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="分类">
|
||||
{{ currentRecord?.resource?.sub_category || '-' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="状态">
|
||||
<a-tag :color="getStatusColor(currentRecord?.resource?.status)">
|
||||
{{ getStatusText(currentRecord?.resource?.status) }}
|
||||
</a-tag>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="描述" :span="2">
|
||||
{{ getResourceDescription(currentRecord) }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="关键词" :span="2">
|
||||
{{ currentRecord?.resource?.keywords || '-' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="标签" :span="2">
|
||||
<a-space wrap>
|
||||
<a-tag v-for="tag in parseTags(currentRecord?.resource?.tags)" :key="tag">
|
||||
{{ tag }}
|
||||
</a-tag>
|
||||
</a-space>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item v-if="currentRecord?.type === 'faq'" label="答案" :span="2">
|
||||
<div class="content-preview">{{ getFaqAnswer(currentRecord) }}</div>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item v-if="currentRecord?.type === 'document'" label="内容" :span="2">
|
||||
<div class="content-preview">{{ getDocumentContent(currentRecord) }}</div>
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-modal>
|
||||
|
||||
<!-- 拒绝原因对话框 -->
|
||||
<a-modal
|
||||
v-model:visible="rejectVisible"
|
||||
title="拒绝原因"
|
||||
:ok-loading="rejectLoading"
|
||||
@ok="handleConfirmReject"
|
||||
@cancel="handleCancelReject"
|
||||
>
|
||||
<a-form :model="rejectForm" layout="vertical">
|
||||
<a-form-item label="拒绝原因" required>
|
||||
<a-textarea
|
||||
v-model="rejectForm.reason"
|
||||
placeholder="请输入拒绝原因"
|
||||
:max-length="500"
|
||||
:auto-size="{ minRows: 3, maxRows: 6 }"
|
||||
show-word-limit
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, computed, onMounted } from 'vue'
|
||||
import { Message } from '@arco-design/web-vue'
|
||||
import dayjs from 'dayjs'
|
||||
import SearchTable from '@/components/search-table/index.vue'
|
||||
import type { FormItem } from '@/components/search-form/types'
|
||||
import type { TableColumnData } from '@arco-design/web-vue/es/table/interface'
|
||||
import type { ReviewListItem, FetchReviewListParams } from '@/api/kb/review'
|
||||
import {
|
||||
fetchReviewList,
|
||||
approveReview,
|
||||
rejectReview,
|
||||
getResourceTypeText,
|
||||
getResourceTypeColor,
|
||||
resourceTypeOptions,
|
||||
} from '@/api/kb/review'
|
||||
|
||||
// 表格列配置
|
||||
const columns = computed((): TableColumnData[] => [
|
||||
{
|
||||
title: '序号',
|
||||
dataIndex: 'index',
|
||||
slotName: 'index',
|
||||
width: 80,
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '分类',
|
||||
dataIndex: 'category',
|
||||
slotName: 'category',
|
||||
width: 100,
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '作者',
|
||||
dataIndex: 'author',
|
||||
slotName: 'author',
|
||||
width: 120,
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '申请时间',
|
||||
dataIndex: 'created_at',
|
||||
slotName: 'created_at',
|
||||
width: 180,
|
||||
align: 'center',
|
||||
sortable: {
|
||||
sortDirections: ['ascend', 'descend'],
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '描述',
|
||||
dataIndex: 'description',
|
||||
slotName: 'description',
|
||||
ellipsis: true,
|
||||
tooltip: true,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'operation',
|
||||
slotName: 'operation',
|
||||
width: 280,
|
||||
align: 'center',
|
||||
fixed: 'right',
|
||||
},
|
||||
])
|
||||
|
||||
// 搜索表单配置
|
||||
const filters = computed((): FormItem[] => [
|
||||
{
|
||||
label: '资源类型',
|
||||
field: 'resource_type',
|
||||
type: 'select',
|
||||
placeholder: '请选择资源类型',
|
||||
options: resourceTypeOptions,
|
||||
span: 6,
|
||||
},
|
||||
])
|
||||
|
||||
// 搜索表单数据
|
||||
const searchForm = reactive({
|
||||
resource_type: 'all',
|
||||
})
|
||||
|
||||
// 处理表单模型更新
|
||||
const handleFormModelUpdate = (newFormModel: Record<string, any>) => {
|
||||
Object.assign(searchForm, newFormModel)
|
||||
}
|
||||
|
||||
// 表格数据
|
||||
const tableData = ref<ReviewListItem[]>([])
|
||||
const loading = ref(false)
|
||||
|
||||
// 分页
|
||||
const page = ref(1)
|
||||
const pageSize = ref(10)
|
||||
const total = ref(0)
|
||||
|
||||
// 详情弹窗
|
||||
const detailVisible = ref(false)
|
||||
const currentRecord = ref<ReviewListItem | null>(null)
|
||||
|
||||
// 拒绝对话框
|
||||
const rejectVisible = ref(false)
|
||||
const rejectLoading = ref(false)
|
||||
const recordToReject = ref<ReviewListItem | null>(null)
|
||||
const rejectForm = reactive({
|
||||
reason: '',
|
||||
})
|
||||
|
||||
// 获取数据
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
const params: FetchReviewListParams = {
|
||||
page: page.value,
|
||||
page_size: pageSize.value,
|
||||
resource_type: searchForm.resource_type as 'all' | 'document' | 'faq',
|
||||
}
|
||||
|
||||
const res: any = await fetchReviewList(params)
|
||||
|
||||
if (res?.code === 0) {
|
||||
tableData.value = res.details?.data || []
|
||||
total.value = res.details?.total || 0
|
||||
} else {
|
||||
Message.error(res?.message || '获取审核列表失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取审核列表失败:', error)
|
||||
Message.error('获取审核列表失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 搜索
|
||||
const handleSearch = () => {
|
||||
page.value = 1
|
||||
fetchData()
|
||||
}
|
||||
|
||||
// 重置
|
||||
const handleReset = () => {
|
||||
searchForm.resource_type = 'all'
|
||||
page.value = 1
|
||||
fetchData()
|
||||
}
|
||||
|
||||
// 页码变化
|
||||
const handlePageChange = (current: number) => {
|
||||
page.value = current
|
||||
fetchData()
|
||||
}
|
||||
|
||||
// 格式化时间
|
||||
const formatTime = (time: string | null | undefined): string => {
|
||||
return time ? dayjs(time).format('YYYY-MM-DD HH:mm') : '-'
|
||||
}
|
||||
|
||||
// 获取状态文本
|
||||
const getStatusText = (status: string | undefined): string => {
|
||||
const statusMap: Record<string, string> = {
|
||||
draft: '草稿',
|
||||
published: '待审核',
|
||||
reviewed: '已审核',
|
||||
rejected: '已拒绝',
|
||||
}
|
||||
return statusMap[status || ''] || status || '-'
|
||||
}
|
||||
|
||||
// 获取状态颜色
|
||||
const getStatusColor = (status: string | undefined): string => {
|
||||
const colorMap: Record<string, string> = {
|
||||
draft: 'gray',
|
||||
published: 'orange',
|
||||
reviewed: 'green',
|
||||
rejected: 'red',
|
||||
}
|
||||
return colorMap[status || ''] || 'gray'
|
||||
}
|
||||
|
||||
// 解析标签
|
||||
const parseTags = (tags: string | null | undefined): string[] => {
|
||||
if (!tags) return []
|
||||
try {
|
||||
return JSON.parse(tags)
|
||||
} catch {
|
||||
return tags.split(',').filter(Boolean)
|
||||
}
|
||||
}
|
||||
|
||||
// 获取资源编号
|
||||
const getResourceNo = (record: ReviewListItem | null): string => {
|
||||
if (!record?.resource) return '-'
|
||||
const resource = record.resource as any
|
||||
return resource.doc_no || resource.faq_no || '-'
|
||||
}
|
||||
|
||||
// 获取资源标题
|
||||
const getResourceTitle = (record: ReviewListItem | null): string => {
|
||||
if (!record?.resource) return '-'
|
||||
const resource = record.resource as any
|
||||
return resource.title || resource.question || '-'
|
||||
}
|
||||
|
||||
// 获取资源描述
|
||||
const getResourceDescription = (record: ReviewListItem | null): string => {
|
||||
if (!record?.resource) return '-'
|
||||
const resource = record.resource as any
|
||||
return resource.description || '-'
|
||||
}
|
||||
|
||||
// 获取FAQ答案
|
||||
const getFaqAnswer = (record: ReviewListItem | null): string => {
|
||||
if (!record?.resource) return '-'
|
||||
const resource = record.resource as any
|
||||
return resource.answer || '-'
|
||||
}
|
||||
|
||||
// 获取文档内容
|
||||
const getDocumentContent = (record: ReviewListItem | null): string => {
|
||||
if (!record?.resource) return '-'
|
||||
const resource = record.resource as any
|
||||
return resource.content || '-'
|
||||
}
|
||||
|
||||
// 查看详情
|
||||
const handleView = (record: ReviewListItem) => {
|
||||
currentRecord.value = record
|
||||
detailVisible.value = true
|
||||
}
|
||||
|
||||
// 审核通过
|
||||
const handleApprove = async (record: ReviewListItem) => {
|
||||
try {
|
||||
loading.value = true
|
||||
const res: any = await approveReview({
|
||||
resource_type: record.type,
|
||||
id: record.resource.id,
|
||||
})
|
||||
if (res?.code === 0) {
|
||||
Message.success('审核通过')
|
||||
await fetchData()
|
||||
} else {
|
||||
Message.error(res?.message || '审核失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('审核失败:', error)
|
||||
Message.error('审核失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 拒绝
|
||||
const handleReject = (record: ReviewListItem) => {
|
||||
recordToReject.value = record
|
||||
rejectForm.reason = ''
|
||||
rejectVisible.value = true
|
||||
}
|
||||
|
||||
// 确认拒绝
|
||||
const handleConfirmReject = async () => {
|
||||
if (!recordToReject.value) return
|
||||
|
||||
if (!rejectForm.reason.trim()) {
|
||||
Message.warning('请输入拒绝原因')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
rejectLoading.value = true
|
||||
const res: any = await rejectReview({
|
||||
resource_type: recordToReject.value.type,
|
||||
id: recordToReject.value.resource.id,
|
||||
reason: rejectForm.reason,
|
||||
})
|
||||
if (res?.code === 0) {
|
||||
Message.success('已拒绝')
|
||||
rejectVisible.value = false
|
||||
recordToReject.value = null
|
||||
rejectForm.reason = ''
|
||||
await fetchData()
|
||||
} else {
|
||||
Message.error(res?.message || '拒绝失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('拒绝失败:', error)
|
||||
Message.error('拒绝失败')
|
||||
} finally {
|
||||
rejectLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 取消拒绝
|
||||
const handleCancelReject = () => {
|
||||
rejectVisible.value = false
|
||||
recordToReject.value = null
|
||||
rejectForm.reason = ''
|
||||
}
|
||||
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
fetchData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.container {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.description-text {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.content-preview {
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user