mirror of
https://codeup.aliyun.com/67762337eccfc218f6110e0e/vue/learning-system-portal.git
synced 2025-12-12 04:16:45 +08:00
提示测试
This commit is contained in:
203
src/components/Course/GuideOverlay.vue
Normal file
203
src/components/Course/GuideOverlay.vue
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
<template>
|
||||||
|
<div v-if="visible" class="guide-overlay">
|
||||||
|
<!-- 高亮区域 -->
|
||||||
|
<div class="highlight-area" :style="highlightStyle"></div>
|
||||||
|
|
||||||
|
<!-- 蒙层 -->
|
||||||
|
<div class="overlay-mask"></div>
|
||||||
|
|
||||||
|
<!-- 提示内容区域 -->
|
||||||
|
<div class="tip-content" :style="tipStyle">
|
||||||
|
<div class="tip-header">
|
||||||
|
<h3>{{ currentStep.title }}</h3>
|
||||||
|
<div class="step-indicator">{{ currentStepIndex + 1 }}/{{ steps.length }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="tip-body">
|
||||||
|
<p>{{ currentStep.content }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="tip-footer">
|
||||||
|
<el-button size="small" @click="skipAll">跳过</el-button>
|
||||||
|
<el-button
|
||||||
|
size="small"
|
||||||
|
type="primary"
|
||||||
|
@click="nextStep">
|
||||||
|
{{ isLastStep ? '完成' : '下一步' }}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
visible: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
steps: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
currentStepIndex: 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
currentStep() {
|
||||||
|
return this.steps[this.currentStepIndex] || {}
|
||||||
|
},
|
||||||
|
isLastStep() {
|
||||||
|
return this.currentStepIndex === this.steps.length - 1
|
||||||
|
},
|
||||||
|
highlightStyle() {
|
||||||
|
const step = this.currentStep
|
||||||
|
if (!step.targetElement) return {}
|
||||||
|
|
||||||
|
const rect = this.getElementRect(step.targetElement)
|
||||||
|
return {
|
||||||
|
width: `${rect.width}px`,
|
||||||
|
height: `${rect.height}px`,
|
||||||
|
top: `${rect.top}px`,
|
||||||
|
left: `${rect.left}px`,
|
||||||
|
borderRadius: step.borderRadius || '4px'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tipStyle() {
|
||||||
|
const step = this.currentStep
|
||||||
|
const rect = this.getElementRect(step.targetElement)
|
||||||
|
|
||||||
|
// 根据高亮区域位置智能调整提示框位置
|
||||||
|
let top = rect.top + rect.height + 10
|
||||||
|
let left = rect.left
|
||||||
|
|
||||||
|
// 如果下方空间不足,显示在上方
|
||||||
|
if (top + 200 > window.innerHeight) {
|
||||||
|
top = rect.top - 210
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果右侧空间不足,左对齐
|
||||||
|
if (left + 300 > window.innerWidth) {
|
||||||
|
left = window.innerWidth - 320
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
top: `${top}px`,
|
||||||
|
left: `${left}px`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getElementRect(selector) {
|
||||||
|
const el = document.querySelector(selector)
|
||||||
|
if (!el) return { width: 0, height: 0, top: 0, left: 0 }
|
||||||
|
|
||||||
|
const rect = el.getBoundingClientRect()
|
||||||
|
return {
|
||||||
|
width: rect.width,
|
||||||
|
height: rect.height,
|
||||||
|
top: rect.top + window.pageYOffset,
|
||||||
|
left: rect.left + window.pageXOffset
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
nextStep() {
|
||||||
|
if (this.isLastStep) {
|
||||||
|
this.complete()
|
||||||
|
} else {
|
||||||
|
this.currentStepIndex++
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
skipAll() {
|
||||||
|
this.$emit('skip')
|
||||||
|
this.complete()
|
||||||
|
},
|
||||||
|
|
||||||
|
complete() {
|
||||||
|
this.$emit('complete')
|
||||||
|
this.currentStepIndex = 0
|
||||||
|
|
||||||
|
// 保存引导完成状态到本地存储
|
||||||
|
localStorage.setItem('courseTagGuideCompleted', 'true')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.guide-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
z-index: 9999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overlay-mask {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight-area {
|
||||||
|
position: absolute;
|
||||||
|
border: 2px solid #409EFF;
|
||||||
|
background: transparent;
|
||||||
|
box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.5),
|
||||||
|
0 0 20px rgba(64, 158, 255, 0.5);
|
||||||
|
z-index: 10000;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tip-content {
|
||||||
|
position: absolute;
|
||||||
|
width: 300px;
|
||||||
|
background: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||||
|
z-index: 10001;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tip-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tip-header h3 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #303133;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-indicator {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #909399;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tip-body {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tip-body p {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.5;
|
||||||
|
color: #606266;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tip-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,201 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="smart-tag-hint" v-if="showHint">
|
|
||||||
<div class="hint-content">
|
|
||||||
<div class="hint-header">
|
|
||||||
<i class="el-icon-light-rain"></i>
|
|
||||||
<span>智能标签建议</span>
|
|
||||||
<i class="el-icon-close" @click="closeHint"></i>
|
|
||||||
</div>
|
|
||||||
<div class="hint-body">
|
|
||||||
<!-- 基于分类的推荐 -->
|
|
||||||
<div v-if="categoryBasedTags.length > 0" class="tag-section">
|
|
||||||
<div class="section-title">基于内容分类的推荐</div>
|
|
||||||
<el-tag
|
|
||||||
v-for="tag in categoryBasedTags"
|
|
||||||
:key="tag.id"
|
|
||||||
class="suggest-tag"
|
|
||||||
@click="applySuggestedTag(tag)"
|
|
||||||
type="info">
|
|
||||||
{{ tag.tagName }}
|
|
||||||
</el-tag>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 基于课程名称的推荐 -->
|
|
||||||
<div v-if="nameBasedTags.length > 0" class="tag-section">
|
|
||||||
<div class="section-title">基于课程名称的推荐</div>
|
|
||||||
<el-tag
|
|
||||||
v-for="tag in nameBasedTags"
|
|
||||||
:key="tag.id"
|
|
||||||
class="suggest-tag"
|
|
||||||
@click="applySuggestedTag(tag)"
|
|
||||||
type="info">
|
|
||||||
{{ tag.tagName }}
|
|
||||||
</el-tag>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 热门标签 -->
|
|
||||||
<div v-if="hotTags.length > 0" class="tag-section">
|
|
||||||
<div class="section-title">热门标签</div>
|
|
||||||
<el-tag
|
|
||||||
v-for="tag in hotTags"
|
|
||||||
:key="tag.id"
|
|
||||||
class="suggest-tag"
|
|
||||||
@click="applySuggestedTag(tag)"
|
|
||||||
type="warning">
|
|
||||||
{{ tag.tagName }}
|
|
||||||
</el-tag>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="hint-footer">
|
|
||||||
<el-button size="mini" @click="dontShowAgain">不再显示</el-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
courseName: String,
|
|
||||||
sysTypeList: Array,
|
|
||||||
existingTags: Array,
|
|
||||||
maxTags: {
|
|
||||||
type: Number,
|
|
||||||
default: 5
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
showHint: false,
|
|
||||||
categoryBasedTags: [],
|
|
||||||
nameBasedTags: [],
|
|
||||||
hotTags: [],
|
|
||||||
hintDismissed: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
async showSmartHint() {
|
|
||||||
if (this.hintDismissed || this.existingTags.length >= this.maxTags) return;
|
|
||||||
|
|
||||||
// 获取智能推荐标签
|
|
||||||
await this.fetchSuggestedTags();
|
|
||||||
this.showHint = true;
|
|
||||||
},
|
|
||||||
|
|
||||||
async fetchSuggestedTags() {
|
|
||||||
try {
|
|
||||||
// 基于分类获取推荐标签
|
|
||||||
// if (this.sysTypeList.length > 0) {
|
|
||||||
// const typeId = this.sysTypeList[this.sysTypeList.length - 1];
|
|
||||||
// const { result } = await apiCourseTag.getTagsByCategory(typeId);
|
|
||||||
// this.categoryBasedTags = result.slice(0, 3);
|
|
||||||
// this.categoryBasedTags = ['园区物联','工业互联网','智能制造解决方案'];
|
|
||||||
// }
|
|
||||||
this.nameBasedTags = ['园区物联','工业互联网','智能制造解决方案'];
|
|
||||||
this.categoryBasedTags = ['工业互联网','中祥英工业互联网','智能制造解决方案'];
|
|
||||||
this.categoryBasedTags = ['工业互联网','中祥英工业互联网','智能制造解决方案'];
|
|
||||||
// 基于课程名称关键词获取推荐
|
|
||||||
// if (this.courseName) {
|
|
||||||
// const keywords = this.extractKeywords(this.courseName);
|
|
||||||
// if (keywords.length > 0) {
|
|
||||||
// const { result } = await apiCourseTag.getTagsByKeywords(keywords);
|
|
||||||
// this.nameBasedTags = result.slice(0, 3);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// 获取热门标签
|
|
||||||
// const { result } = await apiCourseTag.getHotTags();
|
|
||||||
// this.hotTags = result.slice(0, 3);
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取推荐标签失败:', error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
extractKeywords(name) {
|
|
||||||
// 简单的关键词提取逻辑
|
|
||||||
return name.replace(/[^\u4e00-\u9fa5a-zA-Z0-9]/g, ' ').split(' ')
|
|
||||||
.filter(word => word.length > 1).slice(0, 3);
|
|
||||||
},
|
|
||||||
|
|
||||||
applySuggestedTag(tag) {
|
|
||||||
this.$emit('tag-selected', tag);
|
|
||||||
this.showHint = false;
|
|
||||||
},
|
|
||||||
|
|
||||||
closeHint() {
|
|
||||||
this.showHint = false;
|
|
||||||
},
|
|
||||||
|
|
||||||
dontShowAgain() {
|
|
||||||
this.hintDismissed = true;
|
|
||||||
localStorage.setItem('tagHintDismissed', 'true');
|
|
||||||
this.closeHint();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.hintDismissed = localStorage.getItem('tagHintDismissed') === 'true';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.smart-tag-hint {
|
|
||||||
position: absolute;
|
|
||||||
top: 100%;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
z-index: 2000;
|
|
||||||
margin-top: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hint-content {
|
|
||||||
background: #fff;
|
|
||||||
border: 1px solid #e4e7ed;
|
|
||||||
border-radius: 4px;
|
|
||||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
|
||||||
padding: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hint-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #303133;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hint-body {
|
|
||||||
max-height: 200px;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tag-section {
|
|
||||||
margin-bottom: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-title {
|
|
||||||
font-size: 12px;
|
|
||||||
color: #909399;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.suggest-tag {
|
|
||||||
margin: 2px 4px 2px 0;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.3s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.suggest-tag:hover {
|
|
||||||
transform: scale(1.05);
|
|
||||||
}
|
|
||||||
|
|
||||||
.hint-footer {
|
|
||||||
text-align: right;
|
|
||||||
margin-top: 10px;
|
|
||||||
padding-top: 10px;
|
|
||||||
border-top: 1px solid #f0f0f0;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -74,7 +74,7 @@
|
|||||||
:options="sysTypeListMap"></el-cascader>
|
:options="sysTypeListMap"></el-cascader>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="标签" required>
|
<el-form-item label="标签" required>
|
||||||
<courseTag ref="courseTag" :courseId="curCourseId" :sysTypeList="sysTypeList" :initialTags="courseTags" :courseName="courseInfo.name" @change="handleTagsChange"></courseTag>
|
<courseTag ref="courseTag" :courseId="curCourseId" :sysTypeList="sysTypeList" :initialTags="courseTags" @change="handleTagsChange"></courseTag>
|
||||||
<!-- 标签提示对话框 -->
|
<!-- 标签提示对话框 -->
|
||||||
<div v-if="tagTipDialogVisible && isFirstTime"
|
<div v-if="tagTipDialogVisible && isFirstTime"
|
||||||
class="tag-tip-dialog"
|
class="tag-tip-dialog"
|
||||||
@@ -419,6 +419,14 @@
|
|||||||
<!--选择图片-->
|
<!--选择图片-->
|
||||||
<filecloud :show="dlgFileChoose.show" @choose="changeCourseImage" @close="choseChoose"></filecloud>
|
<filecloud :show="dlgFileChoose.show" @choose="changeCourseImage" @close="choseChoose"></filecloud>
|
||||||
<chooseOrg ref="refChooseOrg" @confirm="confirmChooseOrg"></chooseOrg>
|
<chooseOrg ref="refChooseOrg" @confirm="confirmChooseOrg"></chooseOrg>
|
||||||
|
|
||||||
|
<!-- 蒙层引导 -->
|
||||||
|
<guide-overlay
|
||||||
|
:visible="showGuide"
|
||||||
|
:steps="guideSteps"
|
||||||
|
@skip="handleSkipGuide"
|
||||||
|
@complete="handleCompleteGuide"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
@@ -446,11 +454,44 @@ import { courseType, getType } from '../../utils/tools.js';
|
|||||||
import { mapGetters, mapActions } from 'vuex';
|
import { mapGetters, mapActions } from 'vuex';
|
||||||
import filecloud from '@/components/FileCloud/index.vue';
|
import filecloud from '@/components/FileCloud/index.vue';
|
||||||
import chooseOrg from '@/components/System/chooseOrg.vue';
|
import chooseOrg from '@/components/System/chooseOrg.vue';
|
||||||
|
import GuideOverlay from '@/components/Course/GuideOverlay.vue';
|
||||||
export default {
|
export default {
|
||||||
props: {},
|
props: {},
|
||||||
components: { courseTag, weikeContent, catalogCourseware, imageUpload, WxEditor, catalogSort,agreement,filecloud,choice,chooseOrg},
|
components: { courseTag, weikeContent, catalogCourseware, imageUpload, WxEditor, catalogSort,agreement,filecloud,choice,chooseOrg,GuideOverlay},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
showGuide: false,
|
||||||
|
guideSteps: [
|
||||||
|
{
|
||||||
|
title: '课程名称',
|
||||||
|
content: '请输入课程名称,这将帮助系统智能推荐相关标签',
|
||||||
|
targetElement: '.el-input__inner', // 课程名称输入框
|
||||||
|
borderRadius: '4px'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '课程分类',
|
||||||
|
content: '选择课程分类,系统会根据分类自动推荐合适的标签',
|
||||||
|
targetElement: '.el-cascader__search-input', // 分类选择框
|
||||||
|
borderRadius: '4px'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '智能标签',
|
||||||
|
content: '系统会根据课程名称和分类智能推荐标签,点击即可添加',
|
||||||
|
targetElement: '.smart-tag-hint', // 智能提示区域
|
||||||
|
borderRadius: '4px'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '标签管理',
|
||||||
|
content: '您可以手动输入标签或选择推荐标签,最多可添加5个标签',
|
||||||
|
targetElement: '.el-select__tags', // 标签显示区域
|
||||||
|
borderRadius: '4px'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
courseForm: {
|
||||||
|
name: '',
|
||||||
|
categories: [],
|
||||||
|
tags: []
|
||||||
|
},
|
||||||
keywords:'',//关键字的定义
|
keywords:'',//关键字的定义
|
||||||
tips:[],
|
tips:[],
|
||||||
addOrder:1,
|
addOrder:1,
|
||||||
@@ -612,6 +653,7 @@ export default {
|
|||||||
this.$refs.courseTag.$el.addEventListener('focus', this.showTagTip, true);
|
this.$refs.courseTag.$el.addEventListener('focus', this.showTagTip, true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
this.checkAndShowGuide()
|
||||||
},
|
},
|
||||||
|
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
@@ -623,6 +665,37 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
checkAndShowGuide() {
|
||||||
|
// 检查是否为新用户或首次使用标签功能
|
||||||
|
const guideCompleted = localStorage.getItem('courseTagGuideCompleted')
|
||||||
|
if (!guideCompleted && this.isNewUser()) {
|
||||||
|
// 延迟显示引导,确保DOM已渲染
|
||||||
|
setTimeout(() => {
|
||||||
|
this.showGuide = true
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
isNewUser() {
|
||||||
|
// 根据业务逻辑判断是否为新用户
|
||||||
|
return !this.curCourseId || this.courseTags.length === 0
|
||||||
|
},
|
||||||
|
|
||||||
|
handleSkipGuide() {
|
||||||
|
this.showGuide = false
|
||||||
|
this.$message.info('已跳过引导,您可以在设置中重新查看')
|
||||||
|
},
|
||||||
|
|
||||||
|
handleCompleteGuide() {
|
||||||
|
this.showGuide = false
|
||||||
|
this.$message.success('引导完成,开始使用智能标签功能吧!')
|
||||||
|
},
|
||||||
|
|
||||||
|
// 手动触发引导(供用户重新查看)
|
||||||
|
showTagGuide() {
|
||||||
|
this.showGuide = true
|
||||||
|
},
|
||||||
|
|
||||||
// 显示标签提示对话框
|
// 显示标签提示对话框
|
||||||
showTagTip() {
|
showTagTip() {
|
||||||
if (this.isFirstTime) {
|
if (this.isFirstTime) {
|
||||||
|
|||||||
@@ -2,23 +2,22 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="tag-container">
|
<div class="tag-container">
|
||||||
<el-select style="width: 100%;"
|
<el-select style="width: 100%;"
|
||||||
v-model="selectedTags"
|
v-model="selectedTags"
|
||||||
multiple
|
multiple
|
||||||
filterable
|
filterable
|
||||||
value-key="id"
|
value-key="id"
|
||||||
remote
|
remote
|
||||||
reserve-keyword
|
reserve-keyword
|
||||||
:remote-method="debouncedSearch"
|
:remote-method="debouncedSearch"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
:placeholder="'回车创建新标签'"
|
:placeholder="'回车创建新标签'"
|
||||||
:no-data-text="'无此标签,按回车键创建'"
|
:no-data-text="'无此标签,按回车键创建'"
|
||||||
@remove-tag="handleTagRemove"
|
@remove-tag="handleTagRemove"
|
||||||
@change="handleSelectionChange"
|
@change="handleSelectionChange"
|
||||||
@keyup.enter.native="handleEnterKey"
|
@keyup.enter.native="handleEnterKey"
|
||||||
@keyup.delete.native="handleDeleteKey"
|
@keyup.delete.native="handleDeleteKey"
|
||||||
@focus="handleFocus"
|
@focus="handleFocus"
|
||||||
@blur="handleBlur"
|
ref="tagSelect"
|
||||||
ref="tagSelect"
|
|
||||||
>
|
>
|
||||||
<el-option
|
<el-option
|
||||||
v-for="item in searchResults"
|
v-for="item in searchResults"
|
||||||
@@ -29,16 +28,17 @@
|
|||||||
/>
|
/>
|
||||||
</el-select>
|
</el-select>
|
||||||
|
|
||||||
<!-- 智能提示组件 -->
|
<!-- 添加引导提示图标 -->
|
||||||
<smart-tag-hint
|
<el-tooltip content="点击查看标签使用引导" placement="top">
|
||||||
ref="smartHint"
|
<el-button
|
||||||
:course-name="courseName"
|
class="guide-trigger"
|
||||||
:sys-type-list="sysTypeList"
|
type="text"
|
||||||
:existing-tags="selectedTags"
|
icon="el-icon-question"
|
||||||
:max-tags="maxTags"
|
@click="showGuide"
|
||||||
@tag-selected="handleSmartTagSelect"
|
></el-button>
|
||||||
v-show="showSmartHint"
|
</el-tooltip>
|
||||||
/>
|
|
||||||
|
<smart-tag-hint ref="smartHint" />
|
||||||
|
|
||||||
<!-- 添加标签计数显示 -->
|
<!-- 添加标签计数显示 -->
|
||||||
<div class="tag-count">
|
<div class="tag-count">
|
||||||
@@ -51,17 +51,9 @@
|
|||||||
import { debounce } from 'lodash'
|
import { debounce } from 'lodash'
|
||||||
import apiCourseTag from '@/api/modules/courseTag.js'
|
import apiCourseTag from '@/api/modules/courseTag.js'
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import SmartTagHint from './SmartTagHint.vue';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
|
||||||
SmartTagHint
|
|
||||||
},
|
|
||||||
props: {
|
props: {
|
||||||
courseName: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
},
|
|
||||||
courseId:{
|
courseId:{
|
||||||
type:String,
|
type:String,
|
||||||
require:true,
|
require:true,
|
||||||
@@ -83,7 +75,6 @@ export default {
|
|||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
showSmartHint: false,
|
|
||||||
selectedTags: [],
|
selectedTags: [],
|
||||||
searchResults: [],
|
searchResults: [],
|
||||||
loading: false,
|
loading: false,
|
||||||
@@ -105,7 +96,8 @@ export default {
|
|||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.debouncedSearch = debounce(this.doSearch, 500)
|
this.debouncedSearch = debounce(this.doSearch, 500)
|
||||||
this.showSmartMatching();
|
console.log("----------sysTypeList.length---------->"+this.sysTypeList.length)
|
||||||
|
console.log("----------sysTypeList.length---------->"+(this.sysTypeList.length===0))
|
||||||
},
|
},
|
||||||
// 添加:挂载时初始化标签数据
|
// 添加:挂载时初始化标签数据
|
||||||
mounted() {
|
mounted() {
|
||||||
@@ -146,6 +138,9 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
showGuide() {
|
||||||
|
this.$emit('show-guide')
|
||||||
|
},
|
||||||
// 新增:检查标签是否应该被禁用
|
// 新增:检查标签是否应该被禁用
|
||||||
isTagDisabled(tag) {
|
isTagDisabled(tag) {
|
||||||
// 如果标签已经被选中,不应该禁用(允许取消选择)
|
// 如果标签已经被选中,不应该禁用(允许取消选择)
|
||||||
@@ -159,38 +154,11 @@ export default {
|
|||||||
// 新增:处理输入框获得焦点事件
|
// 新增:处理输入框获得焦点事件
|
||||||
async handleFocus() {
|
async handleFocus() {
|
||||||
this.previousTags = [...this.selectedTags];
|
this.previousTags = [...this.selectedTags];
|
||||||
this.showSmartHint = true;
|
|
||||||
setTimeout(() => {
|
|
||||||
if (this.$refs.smartHint) {
|
|
||||||
this.$refs.smartHint.showSmartHint();
|
|
||||||
}
|
|
||||||
}, 300);
|
|
||||||
|
|
||||||
// 当输入框获得焦点时,加载默认的搜索结果
|
// 当输入框获得焦点时,加载默认的搜索结果
|
||||||
if (this.sysTypeList.length > 0) {
|
if (this.sysTypeList.length > 0) {
|
||||||
await this.doSearch('');
|
await this.doSearch('');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
handleBlur() {
|
|
||||||
// 延迟隐藏以避免点击建议标签时立即隐藏
|
|
||||||
setTimeout(() => {
|
|
||||||
this.showSmartHint = false;
|
|
||||||
}, 200);
|
|
||||||
},
|
|
||||||
handleSmartTagSelect(tag) {
|
|
||||||
if (this.selectedTags.length >= this.maxTags) {
|
|
||||||
this.$message.warning(`最多只能添加${this.maxTags}个标签`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const isDuplicate = this.selectedTags.some(t => t.id === tag.id);
|
|
||||||
if (!isDuplicate) {
|
|
||||||
this.selectedTags = [...this.selectedTags, tag];
|
|
||||||
this.$emit('change', this.displayTags);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
// 新增:重置标签状态的方法
|
// 新增:重置标签状态的方法
|
||||||
resetTagState() {
|
resetTagState() {
|
||||||
this.selectedTags = [];
|
this.selectedTags = [];
|
||||||
@@ -367,17 +335,7 @@ export default {
|
|||||||
} finally {
|
} finally {
|
||||||
this.loading = false
|
this.loading = false
|
||||||
}
|
}
|
||||||
},
|
|
||||||
showSmartMatching(query) {
|
|
||||||
// 在搜索结果中高亮匹配项
|
|
||||||
this.searchResults = this.searchResults.map(tag => {
|
|
||||||
return {
|
|
||||||
...tag,
|
|
||||||
isSmartMatch: tag.tagName.toLowerCase().includes(query.toLowerCase())
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -433,4 +391,17 @@ export default {
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 60px;
|
min-width: 60px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tag-container {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guide-trigger {
|
||||||
|
position: absolute;
|
||||||
|
right: -30px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
color: #409EFF;
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user