| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322 |
- <template>
- <div class="app-page">
- <section class="glass-card section-card">
- <el-form :model="filters" inline class="filter-form">
- <el-form-item label="分类">
- <el-select v-model="filters.category" placeholder="全部分类" clearable style="width:150px">
- <el-option v-for="cat in categories" :key="cat.id" :label="cat.name" :value="cat.id" />
- </el-select>
- </el-form-item>
- <el-form-item label="关键词">
- <el-input v-model="filters.keyword" placeholder="搜索关键词" clearable style="width:160px" @keyup.enter="loadData" />
- </el-form-item>
- <el-form-item label="状态">
- <el-select v-model="filters.status" placeholder="全部" clearable style="width:120px">
- <el-option label="已启用" value="enabled" />
- <el-option label="已禁用" value="disabled" />
- </el-select>
- </el-form-item>
- <el-form-item>
- <el-button type="primary" @click="loadData">查询</el-button>
- <el-button @click="resetFilters">重置</el-button>
- </el-form-item>
- </el-form>
- </section>
- <section class="glass-card section-card" style="padding:12px 24px">
- <div class="table-toolbar" style="margin-bottom:0">
- <div class="chip-list">
- <el-button type="primary" @click="openCreateCategory">新建分类</el-button>
- <el-button type="success" @click="openCreate">新增知识</el-button>
- <el-button @click="batchImport">批量导入</el-button>
- <el-button @click="doExport">导出</el-button>
- </div>
- <el-button @click="loadData">刷新</el-button>
- </div>
- </section>
- <section class="glass-card section-card">
- <div class="knowledge-layout">
- <div class="category-tree">
- <div class="category-tree__header">
- <h3>知识分类</h3>
- </div>
- <el-tree
- :data="categoryTree"
- :props="{ children: 'children', label: 'name' }"
- node-key="id"
- @node-click="handleCategoryClick"
- default-expand-all
- >
- <template #default="{ node, data }">
- <span class="category-node">
- <span>{{ data.name }}</span>
- <span class="category-node__count">({{ data.count }})</span>
- </span>
- </template>
- </el-tree>
- </div>
- <div class="knowledge-list">
- <el-table :data="filteredItems" style="width:100%" v-loading="loading">
- <el-table-column prop="keywords" label="关键词" width="200">
- <template #default="{ row }">
- <el-tag v-for="kw in row.keywords.slice(0, 3)" :key="kw" size="small" style="margin-right:4px">{{ kw }}</el-tag>
- <span v-if="row.keywords.length > 3" style="color:#999;font-size:12px">+{{ row.keywords.length - 3 }}</span>
- </template>
- </el-table-column>
- <el-table-column prop="question" label="标准问题" min-width="200" show-overflow-tooltip />
- <el-table-column prop="answer" label="标准答案" min-width="250" show-overflow-tooltip />
- <el-table-column prop="clicks" label="点击量" width="90" align="center" />
- <el-table-column prop="aiScore" label="AI匹配度" width="100" align="center">
- <template #default="{ row }">
- <el-progress :percentage="row.aiScore" :status="aiScoreStatus(row.aiScore)" />
- </template>
- </el-table-column>
- <el-table-column prop="status" label="状态" width="90" align="center">
- <template #default="{ row }">
- <el-tag :type="getKnowledgeStatus(row.status).type" size="small">
- {{ getKnowledgeStatus(row.status).label }}
- </el-tag>
- </template>
- </el-table-column>
- <el-table-column prop="updatedAt" label="更新时间" width="120" />
- <el-table-column label="操作" width="150" fixed="right">
- <template #default="{ row }">
- <el-button link type="primary" @click="openEdit(row)">编辑</el-button>
- <el-button link :type="row.status === 'enabled' ? 'warning' : 'success'" @click="toggleStatus(row)">
- {{ row.status === 'enabled' ? '禁用' : '启用' }}
- </el-button>
- <el-button link type="danger" @click="handleDelete(row)">删除</el-button>
- </template>
- </el-table-column>
- </el-table>
- </div>
- </div>
- </section>
- <el-dialog v-model="dialogVisible" :title="dialogTitle" width="700px" @closed="resetForm">
- <el-form :model="form" label-width="100px">
- <el-form-item label="所属分类" required>
- <el-cascader v-model="form.categoryId" :options="categoryOptions" :props="{ checkStrictly: true, label: 'name', value: 'id' }" placeholder="选择分类" style="width:100%" />
- </el-form-item>
- <el-form-item label="关键词" required>
- <el-select v-model="form.keywords" multiple filterable allow-create default-first-option placeholder="输入关键词后回车" style="width:100%">
- <el-option v-for="kw in form.keywords" :key="kw" :label="kw" :value="kw" />
- </el-select>
- </el-form-item>
- <el-form-item label="标准问题" required>
- <el-input v-model="form.question" type="textarea" :rows="2" placeholder="请输入标准问题" />
- </el-form-item>
- <el-form-item label="标准答案" required>
- <el-input v-model="form.answer" type="textarea" :rows="4" placeholder="请输入标准答案" />
- </el-form-item>
- <el-form-item label="状态">
- <el-radio-group v-model="form.status">
- <el-radio value="enabled">启用</el-radio>
- <el-radio value="disabled">禁用</el-radio>
- </el-radio-group>
- </el-form-item>
- </el-form>
- <template #footer>
- <el-button @click="dialogVisible = false">取消</el-button>
- <el-button type="primary" @click="saveKnowledge">保存</el-button>
- </template>
- </el-dialog>
- <el-dialog v-model="categoryDialogVisible" title="新建分类" width="400px">
- <el-form :model="categoryForm" label-width="80px">
- <el-form-item label="上级分类">
- <el-tree-select v-model="categoryForm.parentId" :data="categoryTree" :props="{ label: 'name', value: 'id' }" placeholder="选择上级分类(可选)" clearable check-strictly style="width:100%" />
- </el-form-item>
- <el-form-item label="分类名称" required>
- <el-input v-model="categoryForm.name" placeholder="请输入分类名称" />
- </el-form-item>
- </el-form>
- <template #footer>
- <el-button @click="categoryDialogVisible = false">取消</el-button>
- <el-button type="primary" @click="saveCategory">保存</el-button>
- </template>
- </el-dialog>
- </div>
- </template>
- <script setup lang="ts">
- import { ref, computed, onMounted } from 'vue';
- import { ElMessage, ElMessageBox } from 'element-plus';
- import { api } from '@/api/services';
- import type { KnowledgeBaseItem, KnowledgeCategory } from '@/types/page';
- import { getKnowledgeStatus } from '@/utils/enumMappings';
- const loading = ref(false);
- const dialogVisible = ref(false);
- const categoryDialogVisible = ref(false);
- const isEdit = ref(false);
- const currentId = ref('');
- const filters = ref({ category: '', keyword: '', status: '' });
- const categories = ref<KnowledgeCategory[]>([
- { id: '1', name: '产品咨询', count: 45 },
- { id: '2', name: '物流问题', count: 32 },
- { id: '3', name: '支付问题', count: 18 },
- { id: '4', name: '退换货', count: 28 },
- { id: '5', name: '账户问题', count: 15 }
- ]);
- const categoryTree = ref<KnowledgeCategory[]>([
- { id: '1', name: '产品咨询', count: 45, children: [
- { id: '1-1', name: '产品规格', count: 20 },
- { id: '1-2', name: '产品材质', count: 15 },
- { id: '1-3', name: '使用方法', count: 10 }
- ]},
- { id: '2', name: '物流问题', count: 32, children: [
- { id: '2-1', name: '发货时间', count: 12 },
- { id: '2-2', name: '物流查询', count: 10 },
- { id: '2-3', name: '快递丢失', count: 10 }
- ]},
- { id: '3', name: '支付问题', count: 18 },
- { id: '4', name: '退换货', count: 28 },
- { id: '5', name: '账户问题', count: 15 }
- ]);
- const categoryOptions = computed(() => categoryTree.value);
- const items = ref<KnowledgeBaseItem[]>([]);
- const filteredItems = computed(() => {
- return items.value.filter(item => {
- if (filters.value.category && item.categoryId !== filters.value.category) return false;
- if (filters.value.keyword && !item.keywords.some(k => k.includes(filters.value.keyword)) && !item.question.includes(filters.value.keyword)) return false;
- if (filters.value.status && item.status !== filters.value.status) return false;
- return true;
- });
- });
- const form = ref<Partial<KnowledgeBaseItem>>({
- categoryId: '',
- keywords: [],
- question: '',
- answer: '',
- status: 'enabled'
- });
- const categoryForm = ref({ parentId: '', name: '' });
- const dialogTitle = computed(() => isEdit.value ? '编辑知识' : '新增知识');
- const loadData = async () => {
- loading.value = true;
- try {
- const res = await api.getKnowledgeBase();
- items.value = res.items ?? [];
- } finally {
- loading.value = false;
- }
- };
- const resetFilters = () => { filters.value = { category: '', keyword: '', status: '' }; };
- const handleCategoryClick = (data: KnowledgeCategory) => {
- filters.value.category = data.id;
- };
- const aiScoreStatus = (score: number) => {
- if (score >= 90) return 'success';
- if (score >= 70) return 'warning';
- return 'exception';
- };
- const openCreate = () => { isEdit.value = false; dialogVisible.value = true; };
- const openEdit = (row: KnowledgeBaseItem) => { isEdit.value = true; currentId.value = row.id; form.value = { ...row }; dialogVisible.value = true; };
- const openCreateCategory = () => { categoryForm.value = { parentId: '', name: '' }; categoryDialogVisible.value = true; };
- const saveKnowledge = () => {
- if (!form.value.categoryId || !form.value.question || !form.value.answer) {
- ElMessage.warning('请填写必填项');
- return;
- }
- if (isEdit.value) {
- const idx = items.value.findIndex(i => i.id === currentId.value);
- if (idx !== -1) items.value[idx] = { ...items.value[idx], ...form.value } as KnowledgeBaseItem;
- ElMessage.success('更新成功');
- } else {
- items.value.unshift({
- id: 'K' + Date.now(),
- category: categories.value.find(c => c.id === form.value.categoryId)?.name || '',
- ...form.value,
- clicks: 0,
- aiScore: 0,
- createdAt: new Date().toISOString().split('T')[0],
- updatedAt: new Date().toISOString().split('T')[0]
- } as KnowledgeBaseItem);
- ElMessage.success('创建成功');
- }
- dialogVisible.value = false;
- };
- const resetForm = () => { form.value = { categoryId: '', keywords: [], question: '', answer: '', status: 'enabled' }; };
- const saveCategory = () => {
- if (!categoryForm.value.name) {
- ElMessage.warning('请输入分类名称');
- return;
- }
- ElMessage.success('分类创建成功');
- categoryDialogVisible.value = false;
- };
- const toggleStatus = (row: KnowledgeBaseItem) => {
- row.status = row.status === 'enabled' ? 'disabled' : 'enabled';
- ElMessage.success(`已${row.status === 'enabled' ? '启用' : '禁用'}`);
- };
- const handleDelete = (row: KnowledgeBaseItem) => {
- ElMessageBox.confirm('确定删除该知识吗?', '提示', { type: 'warning' })
- .then(() => {
- items.value = items.value.filter(i => i.id !== row.id);
- ElMessage.success('删除成功');
- })
- .catch(() => {});
- };
- const batchImport = () => { ElMessage.info('批量导入功能开发中'); };
- const doExport = () => { ElMessage.info('导出开始'); };
- onMounted(loadData);
- </script>
- <style scoped>
- .filter-form :deep(.el-form-item) { margin-bottom: 0; }
- .knowledge-layout {
- display: grid;
- grid-template-columns: 240px 1fr;
- gap: 16px;
- min-height: 500px;
- }
- .category-tree {
- background: #fafafa;
- border-radius: 12px;
- padding: 16px;
- }
- .category-tree__header {
- margin-bottom: 16px;
- }
- .category-tree__header h3 {
- margin: 0;
- font-size: 15px;
- font-weight: 600;
- }
- .category-node {
- display: flex;
- align-items: center;
- gap: 4px;
- }
- .category-node__count {
- color: #999;
- font-size: 12px;
- }
- .knowledge-list {
- min-width: 0;
- }
- </style>
|