feat(establish): 添加标签创建功能及模态框组件

- 在establish组件中新增标签创建功能
- 添加标签创建模态框及相关样式
- 实现分类选择下拉框和标签名称输入
- 完善模态框的显示/隐藏逻辑
- 调整部分样式以优化用户体验
This commit is contained in:
qingfeng1121
2025-11-09 16:27:34 +08:00
parent 309aeaedc1
commit 4ae0ff7c2a
4 changed files with 200 additions and 46 deletions

View File

@@ -65,6 +65,7 @@
</div>
</el-tab-pane>
<el-tab-pane label="功能" name="second">
<div>还在开发中.....</div>
</el-tab-pane>
</el-tabs>
</div>
@@ -519,6 +520,9 @@ onUnmounted(() => {
justify-content: center;
align-items: center;
z-index: 1000;
/* 确保在任何情况下都能居中显示 */
margin: 0;
padding: 0;
}
.category-modal-content {
@@ -529,6 +533,7 @@ onUnmounted(() => {
max-height: 80vh;
overflow: hidden;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
/* 确保内容块在父容器中完美居中 */
}
.category-modal-header {

View File

@@ -36,7 +36,7 @@
<div class="search-box-container" :class="{ 'open': isSearchBoxOpen }">
<el-input
v-model="searchKeyword"
placeholder="搜索文章..."
placeholder="回车搜索文章..."
class="search-input"
@keyup.enter="performSearch"
@blur="closeSearchBoxWithDelay"
@@ -402,7 +402,9 @@ onUnmounted(() => {
width: 100%;
opacity: 1;
}
.el-input__wrapper{
width: 80px;
}
.search-input {
border: none;
outline: none;
@@ -430,7 +432,7 @@ onUnmounted(() => {
/* 防止搜索框在小屏幕上重叠 */
@media screen and (max-width: 1200px) {
.search-box-container.open {
width: 250px;
width: 150px;
}
}
</style>

View File

@@ -2,12 +2,8 @@
<div class="establish-container">
<!-- 弹出的按钮容器 -->
<!-- <div class="expanded-buttons" :class="{ 'show': isExpanded }"> -->
<button
v-for="(btn, index) in isbuttonsave()"
:class="['action-button', { 'show': isExpanded }]"
:style="getButtonStyle(index)"
:key="btn.id"
@click="handleButtonClick(btn)">
<button v-for="(btn, index) in isbuttonsave()" :class="['action-button', { 'show': isExpanded }]"
:style="getButtonStyle(index)" :key="btn.id" @click="handleButtonClick(btn)">
<!-- <i :class="btn.icon"></i> -->
<span>{{ btn.label }}</span>
</button>
@@ -18,6 +14,28 @@
<i class="icon">+</i>
</button>
</div>
<!-- 标签蒙板组件 -->
<Transition name="modal">
<div v-if="showAttributeModal" class="category-modal" @click.self="closeAttributeModal">
<div class="category-modal-content">
<div class="category-modal-header">
<h3>新建标签</h3>
<button class="category-modal-close" @click="closeAttributeModal">×</button>
</div>
<div class="category-modal-body">
<el-select v-model="CategoryAttributeDto.categoryid" placeholder="请选择">
<el-option v-for="item in categories" :key="item.value" :label="item.label" :value="item.value">
</el-option>
</el-select>
<el-input v-model="CategoryAttributeDto.name" placeholder="输入新标签" />
<el-button type="primary"
@click="saveAttribute(CategoryAttributeDto)">保存</el-button>
</div>
</div>
</div>
</Transition>
</template>
<script setup>
@@ -30,13 +48,29 @@ import { useGlobalStore } from '@/store/globalStore'
const globalStore = useGlobalStore()
// 路由参数
const router = useRouter()
const route = useRoute()
// 分类列表
const categories = ref([])
//蒙版
const showAttributeModal = ref(false)
// 选中的分类和新标签
const CategoryAttributeDto = ref({
categoryid: '',
name: ''
})
// 定义响应式状态
const isExpanded = ref(false)
// 疯言疯语模态框状态
const isNonsenseModalVisible = ref(false)
const nonsenseContent = ref('')
// 显示标签蒙板
const showAttributes = () => {
showAttributeModal.value = true
}
const closeAttributeModal = () => {
showAttributeModal.value = false
}
// 基础按钮配置
const baseButtons = [
{ id: 'logout', label: '登出', icon: 'icon-logout' },
@@ -52,7 +86,7 @@ const pageButtons = {
{ id: 'delete-article', label: '删除', icon: 'icon-create-category' },
...baseButtons
],
// 首页按钮
home: [
{ id: 'create-article', label: '新建', icon: 'icon-add-article' },
@@ -61,31 +95,32 @@ const pageButtons = {
{ id: 'published-articles', label: '已发表', icon: 'icon-new-tag' },
...baseButtons
],
// 疯言疯语页面按钮
nonsense: [
{ id: 'create-nonsense', label: '说说', icon: 'icon-upload-file' },
...baseButtons
],
// 分类页面按钮
articlelist: [
{ id: 'create-category', label: '新建', icon: 'icon-create-category' },
{ id: 'create-category', label: '分类', icon: 'icon-create-category' },
{ id: 'create-tag', label: '标签', icon: 'icon-create-tag' },
...baseButtons
],
// 标签页面按钮
tag: [
{ id: 'create-tag', label: '新建', icon: 'icon-new-tag' },
...baseButtons
],
// 文章保存页面按钮
articlesave: [
{ id: 'view-articles', label: '查看文章列表', icon: 'icon-new-tag' },
...baseButtons
],
// 默认按钮
default: baseButtons
}
@@ -129,7 +164,7 @@ const toggleExpand = (event) => {
const showNonsenseModal = () => {
nonsenseContent.value = '' // 清空输入框
isNonsenseModalVisible.value = true
ElMessageBox.prompt('请输入您的疯言疯语:', '发表疯言疯语', {
confirmButtonText: '保存',
cancelButtonText: '取消',
@@ -151,7 +186,7 @@ const saveNonsense = (content) => {
ElMessage.warning('内容不能为空')
return
}
// 调用服务保存疯言疯语
nonsenseService.saveNonsense({
content: content.trim(),
@@ -169,7 +204,7 @@ const handleErrorResponse = (error, defaultMessage = '操作失败') => {
console.error('操作失败:', error)
ElMessage.error(error.message || defaultMessage)
}
// articlelist新建
// 新建分类
const createCategory = () => {
ElMessageBox.prompt('请输入分类名称:', '新建分类', {
confirmButtonText: '保存',
@@ -210,6 +245,44 @@ const saveCategory = (typename) => {
}
}).catch(err => handleErrorResponse(err, '创建分类失败'));
};
// createAttribute新建标签
const createAttribute = async () => {
showAttributes()
try {
const response = await categoryService.getAllCategories();
if (response.code === 200) {
categories.value = response.data.map(item => ({
value: item.typeid,
label: item.typename
}))
}
} catch (error) {
handleErrorResponse(error, '获取分类失败');
} finally {
console.log(categories.value)
}
};
const saveAttribute = (CategoryAttributeDto) => {
if (!CategoryAttributeDto.categoryid || !CategoryAttributeDto.name) {
ElMessage.warning('请选择分类和输入标签名称')
return
}
categoryAttributeService.createAttribute({
categoryid: Number(CategoryAttributeDto.categoryid),
attributename: CategoryAttributeDto.name
}).then(response => {
if (response.code === 200) {
closeAttributeModal()
ElMessage.success('标签创建成功');
// 刷新页面以显示新标签
router.push({ path: `/home/aericlelist`});
} else {
ElMessage.error(response.message || '创建标签失败');
}
}).catch(err => handleErrorResponse(err, '创建标签失败'));
};
// 删除文章方法
const deleteArticle = () => {
const articleId = globalStore.getValue('articleInfo')?.id
@@ -252,8 +325,8 @@ const updateArticle = () => {
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
globalStore.setValue('updatearticle', articleId)
router.push({ path: '/articlesave' })
globalStore.setValue('updatearticle', articleId)
router.push({ path: '/articlesave' })
}).catch(() => {
// 取消修改,静默处理
})
@@ -286,7 +359,7 @@ const reloadPage = () => {
// 处理按钮点击事件
const handleButtonClick = (button) => {
console.log('点击了按钮:', button.id, button.label)
// 使用按钮ID进行处理更可靠且易于维护
switch (button.id) {
// 新增操作
@@ -294,62 +367,62 @@ const handleButtonClick = (button) => {
// 清除更新文章状态
router.push({ path: '/articlesave' })
break
case 'create-category':
createCategory()
break
case 'create-tag':
// router.push({ path: '/tagsave' })
createAttribute()
break
case 'create-nonsense':
// 显示疯言疯语模态框
showNonsenseModal()
break
// 修改操作
case 'edit-article':
updateArticle()
break
// 删除操作
case 'delete-article':
deleteArticle();
break
// 查看操作
case 'del-articles':
getButtonsByStatus(2)
break
case 'unpublished-articles':
getButtonsByStatus(0)
break
case 'published-articles':
getButtonsByStatus(1)
break
case 'view-articles':
router.push({ path: '/home' })
break
// 登出操作
case 'logout':
logout();
break
case 'reload':
reloadPage()
break
default:
console.warn('未处理的按钮类型:', button.id, button.label)
ElMessage.info(`功能 ${button.label} 暂未实现`)
}
}
// 点击后收起按钮菜单
isExpanded.value = false
}
@@ -361,7 +434,7 @@ const getButtonStyle = (index) => {
// 隐藏时间0.3s + index * 0.2s (递增)
const showDelay = Math.max(0.1, 0.2 + index * 0.2);
const hideDelay = 0.3 + index * 0.2;
// 根据是否展开返回不同的过渡样式
if (isExpanded.value) {
return {
@@ -494,6 +567,78 @@ onBeforeUnmount(() => {
margin-left: 150px;
opacity: 1;
}
/* 分类蒙板样式 */
.category-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
/* 确保在任何情况下都能居中显示 */
margin: 0;
padding: 0;
}
.category-modal-content {
background-color: white;
border-radius: 10px;
width: 90%;
max-width: 500px;
max-height: 80vh;
overflow: hidden;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
/* 确保内容块在父容器中完美居中 */
}
.category-modal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px 20px;
border-bottom: 1px solid #eee;
}
.category-modal-header h3 {
margin: 0;
font-size: 18px;
color: #333;
}
.category-modal-close {
background: none;
border: none;
font-size: 24px;
cursor: pointer;
color: #999;
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
transition: all 0.3s;
}
.category-modal-close:hover {
background-color: #f5f5f5;
color: #333;
}
.category-modal-body {
padding: 20px;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
gap: 12px;
max-height: 60vh;
overflow-y: auto;
}
/* 响应式调整 */
@media (max-width: 768px) {
.establish-container {

View File

@@ -24,11 +24,12 @@
<!-- 计算该分类组中实际有文章的属性数量 -->
<span class="badge badge-primary"> {{ categoryGroup.attributes.reduce((total, cat) => total + (cat.articles && cat.articles.length ? cat.articles.length : 0), 0) }} </span>
<ul class="category-item-list">
<li v-for="category in categoryGroup.attributes" :key="category.attributeid" >
&nbsp;&nbsp;<a class="category-link" @click="handleCategoryClick(category)"><kbd>{{ category.attributename
}}</kbd></a>
&nbsp; ({{ category.articles.length }})
</li>
<div v-for="category in categoryGroup.attributes" :key="category.attributeid">
<li v-if="category.articles && category.articles.length > 0">
&nbsp;&nbsp;<a class="category-link" @click="handleCategoryClick(category)"><kbd>{{ category.attributename}}</kbd></a>
&nbsp; ({{ category.articles.length }})
</li>
</div>
</ul>
</div>
@@ -39,6 +40,7 @@
<div v-else class="empty-state-container">
<el-empty description="暂无分类" />
</div>
</div>
</template>