添加自动推荐标签功能

This commit is contained in:
nojava
2026-05-23 03:17:46 +08:00
parent 6e145d0980
commit 20b7a57fb9
16 changed files with 1182 additions and 9 deletions
+142 -4
View File
@@ -11,9 +11,32 @@
</el-select>
</el-form-item>
<el-form-item label="标签">
<el-select v-model="form.tagIds" multiple placeholder="选择标签(最多5个)" :max-collapse-tags="5">
<el-option v-for="tag in tags" :key="tag.id" :label="tag.name" :value="tag.id" />
</el-select>
<div class="tag-wrapper">
<div class="tag-select-row">
<el-select v-model="form.tagIds" multiple placeholder="选择标签(最多5个)" :max-collapse-tags="5" class="tag-select">
<el-option v-for="tag in tags" :key="tag.id" :label="tag.name" :value="tag.id" />
</el-select>
<el-button
type="primary"
size="small"
:loading="recommending"
@click="handleRecommendTags"
class="recommend-btn"
>
智能推荐
</el-button>
</div>
<div v-if="recommendedTags.length" class="tag-recommend-result">
<span class="recommend-label">推荐标签</span>
<el-checkbox-group v-model="selectedRecommendedTags" class="recommend-checkboxes">
<el-checkbox v-for="tag in recommendedTags" :key="tag" :label="tag" />
</el-checkbox-group>
<el-button type="success" size="small" @click="confirmAddTags" :disabled="!selectedRecommendedTags.length">
确认添加
</el-button>
<el-button size="small" @click="clearRecommendedTags">取消</el-button>
</div>
</div>
</el-form-item>
<el-form-item label="内容">
<div class="editor-wrapper">
@@ -70,7 +93,7 @@ import { useRoute, useRouter } from 'vue-router'
import { useEditor, EditorContent } from '@tiptap/vue-3'
import StarterKit from '@tiptap/starter-kit'
import Placeholder from '@tiptap/extension-placeholder'
import { createArticle, updateArticle, getArticleDetail, getCategories, getTags, generateArticleSummary } from '@/api/article'
import { createArticle, updateArticle, getArticleDetail, getCategories, getTags, generateArticleSummary, recommendTags, createAppTag } from '@/api/article'
import { ElMessage, ElMessageBox } from 'element-plus'
const route = useRoute()
@@ -79,6 +102,9 @@ const isEdit = computed(() => !!route.params.id)
const saving = ref(false)
const summarizing = ref(false)
const lastSummaryMeta = ref('')
const recommending = ref(false)
const recommendedTags = ref([])
const selectedRecommendedTags = ref([])
const categories = ref([])
const tags = ref([])
const form = ref({
@@ -137,6 +163,83 @@ async function handleGenerateSummary() {
}
}
async function handleRecommendTags() {
const plain = getPlainTextContent()
if (plain.length < 10) {
ElMessage.warning('正文过短,无法推荐标签')
return
}
recommending.value = true
recommendedTags.value = []
selectedRecommendedTags.value = []
try {
const res = await recommendTags({
title: form.value.title || '',
content: plain,
})
const allTags = res.data.tags || []
// 过滤已选中的标签
const existingNames = tags.value
.filter(t => form.value.tagIds.includes(t.id))
.map(t => t.name)
recommendedTags.value = allTags.filter(t => !existingNames.includes(t))
if (!recommendedTags.value.length) {
ElMessage.info('没有新的推荐标签')
} else {
ElMessage.success(`推荐了 ${recommendedTags.value.length} 个标签`)
}
} catch (err) {
if (err?.code === 'ECONNABORTED') {
ElMessage.error('AI 服务响应超时,请稍后再试')
}
} finally {
recommending.value = false
}
}
async function resolveTagId(tagName) {
const name = (tagName || '').trim()
if (!name) return null
const existing = tags.value.find(t => t.name === name)
if (existing) return existing.id
try {
const res = await createAppTag(name)
return res.data?.id ?? null
} catch {
ElMessage.warning(`标签「${name}」创建失败,已跳过`)
return null
}
}
async function confirmAddTags() {
if (!selectedRecommendedTags.value.length) return
const idsToAdd = []
for (const tagName of selectedRecommendedTags.value) {
const tagId = await resolveTagId(tagName)
if (tagId != null) idsToAdd.push(tagId)
}
if (!idsToAdd.length) return
await loadTags()
const merged = [...form.value.tagIds]
for (const tagId of idsToAdd) {
if (merged.includes(tagId)) continue
if (merged.length >= 5) {
ElMessage.warning('最多选择 5 个标签')
break
}
merged.push(tagId)
}
form.value.tagIds = merged
recommendedTags.value = []
selectedRecommendedTags.value = []
ElMessage.success('标签添加完成')
}
function clearRecommendedTags() {
recommendedTags.value = []
selectedRecommendedTags.value = []
}
const editor = useEditor({
extensions: [
StarterKit,
@@ -270,4 +373,39 @@ onBeforeUnmount(() => {
font-size: var(--font-size-sm);
color: var(--text-secondary);
}
.tag-wrapper {
width: 100%;
}
.tag-select-row {
display: flex;
align-items: center;
gap: var(--spacing-sm);
}
.tag-select {
flex: 1;
}
.recommend-btn {
flex-shrink: 0;
}
.tag-recommend-result {
margin-top: var(--spacing-sm);
padding: var(--spacing-sm) var(--spacing-md);
background: var(--bg-soft);
border: 1px solid var(--border-color-light);
border-radius: var(--radius-sm);
display: flex;
align-items: center;
gap: var(--spacing-sm);
flex-wrap: wrap;
}
.recommend-label {
font-size: var(--font-size-sm);
color: var(--text-secondary);
flex-shrink: 0;
}
.recommend-checkboxes {
display: flex;
gap: var(--spacing-sm);
flex-wrap: wrap;
}
</style>