feat:新增标签分类。

This commit is contained in:
tianyongbao
2025-12-13 15:46:46 +08:00
parent 667b06da37
commit 9256053ae7
4 changed files with 673 additions and 103 deletions

View File

@@ -6,14 +6,44 @@
</div>
<div class="manage-container">
<!-- 分类管理 -->
<div class="manage-section">
<!-- 分类 Tab 标签页固定在顶部 -->
<div class="manage-section fixed-section">
<div class="section-header">
<h2>书签分组管理</h2>
<h2>书签分</h2>
<div class="header-actions">
<button class="add-btn" @click="showAddSuperCategoryDialog">+ 添加分类</button>
</div>
</div>
<!-- Tab 标签 -->
<div class="tab-container">
<div class="tab-list">
<div
v-for="superCategory in superCategories"
:key="superCategory.id"
:class="['tab-item', { active: activeSuperCategoryId === superCategory.id }]"
@click="switchTab(superCategory.id)"
>
<span class="tab-sort">{{ superCategory.sortOrder }}</span>
<span class="tab-title">{{ superCategory.title }}</span>
<span class="tab-count">{{ getCategoryCountInSuper(superCategory.id) }} 个分组</span>
<div class="tab-actions" @click.stop>
<button class="tab-edit-btn" @click="editSuperCategory(superCategory)" title="编辑分类"></button>
<button class="tab-delete-btn" @click="deleteSuperCategory(superCategory)" title="删除分类">×</button>
</div>
</div>
</div>
</div>
</div>
<!-- 当前分类下的书签分组管理 -->
<div v-if="activeSuperCategoryId" class="manage-section">
<div class="section-header">
<h2>书签分组</h2>
<button class="add-btn" @click="showAddCategoryDialog">+ 添加书签分组</button>
</div>
<div class="category-list">
<div v-for="category in categories" :key="category.id" class="category-item">
<div v-for="category in currentCategories" :key="category.id" class="category-item">
<div class="category-info">
<span class="category-key">{{ category.key }}</span>
<span class="category-name">{{ category.title }}</span>
@@ -31,11 +61,11 @@
</div>
<!-- 网址管理 -->
<div class="manage-section">
<div v-if="activeSuperCategoryId" class="manage-section">
<div class="section-header">
<h2>网址管理</h2>
</div>
<div v-for="category in categories" :key="category.id" class="tool-group">
<div v-for="category in currentCategories" :key="category.id" class="tool-group">
<div class="tool-group-header">
<h3>{{ category.title }}</h3>
<button class="add-btn small" @click="showAddToolDialog(category)">+ 添加网址</button>
@@ -65,6 +95,29 @@
</div>
</div>
<!-- 分类编辑对话框 -->
<div v-if="showSuperCategoryDialog" class="dialog-overlay" @click="closeSuperCategoryDialog">
<div class="dialog" @click.stop>
<h3>{{ superCategoryForm.id ? '编辑分类' : '添加分类' }}</h3>
<div class="form-group">
<label><span class="required">*</span>分类标识</label>
<input v-model="superCategoryForm.key" placeholder="例如work" :disabled="!!superCategoryForm.id" />
</div>
<div class="form-group">
<label><span class="required">*</span>分类名称</label>
<input v-model="superCategoryForm.title" placeholder="例如:工作" />
</div>
<div class="form-group">
<label>排序序号</label>
<input v-model.number="superCategoryForm.sortOrder" type="number" placeholder="数字越小越靠前例如0" />
</div>
<div class="dialog-actions">
<button class="cancel-btn" @click="closeSuperCategoryDialog">取消</button>
<button class="confirm-btn" @click="saveSuperCategory">保存</button>
</div>
</div>
</div>
<!-- 分类编辑对话框 -->
<div v-if="showCategoryDialog" class="dialog-overlay" @click="closeCategoryDialog">
<div class="dialog" @click.stop>
@@ -174,7 +227,10 @@ export default {
data() {
return {
loading: false,
superCategories: [],
categories: [],
activeSuperCategoryId: null, // 当前Tab的分类ID
showSuperCategoryDialog: false,
showCategoryDialog: false,
showToolDialog: false,
showConfirmDialog: false,
@@ -213,10 +269,17 @@ export default {
{ name: '淡黄', value: 'linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%)' },
{ name: '淡蓝', value: 'linear-gradient(135deg, #accbee 0%, #e7f0fd 100%)' }
],
superCategoryForm: {
id: null,
key: '',
title: '',
sortOrder: 0
},
categoryForm: {
id: null,
key: '',
title: '',
superCategoryId: null,
sortOrder: 0
},
toolForm: {
@@ -242,6 +305,12 @@ export default {
warning: '⚠'
}
return icons[this.message.type] || '✓'
},
// 当前激活的分类下的分类列表
currentCategories() {
if (!this.activeSuperCategoryId) return []
return this.categories.filter(c => c.superCategoryId === this.activeSuperCategoryId)
}
},
@@ -284,20 +353,45 @@ export default {
this.$router.push('/')
},
// 辅助方法
getSuperCategoryName(superCategoryId) {
const superCat = this.superCategories.find(s => s.id === superCategoryId)
return superCat ? superCat.title : '未分类'
},
getCategoryCountInSuper(superCategoryId) {
return this.categories.filter(c => c.superCategoryId === superCategoryId).length
},
// 切换Tab
switchTab(superCategoryId) {
this.activeSuperCategoryId = superCategoryId
},
async loadData() {
try {
this.loading = true
const [categoriesRes, toolsRes] = await Promise.all([
const [superCategoriesRes, categoriesRes, toolsRes] = await Promise.all([
ToolService.getSuperCategories(),
ToolService.getCategories(),
ToolService.getTools()
])
if (categoriesRes && categoriesRes.code === 200 && toolsRes && toolsRes.code === 200) {
if (superCategoriesRes && superCategoriesRes.code === 200 && categoriesRes && categoriesRes.code === 200 && toolsRes && toolsRes.code === 200) {
// 处理分类数据
const superCategories = (superCategoriesRes.rows || []).map(superCat => ({
id: superCat.id,
key: superCat.superCategoryKey,
title: superCat.superCategoryTitle,
sortOrder: superCat.sortOrder || 0
}))
const categories = (categoriesRes.rows || []).map(cat => ({
id: cat.id,
key: cat.categoryKey,
title: cat.categoryTitle,
superCategoryId: cat.superCategoryId,
sortOrder: cat.sortOrder || 0,
tools: []
}))
@@ -322,8 +416,15 @@ export default {
})
categories.sort((a, b) => a.sortOrder - b.sortOrder)
superCategories.sort((a, b) => a.sortOrder - b.sortOrder)
this.superCategories = superCategories
this.categories = categories
// 默认激活第一个分类
if (superCategories.length > 0 && !this.activeSuperCategoryId) {
this.activeSuperCategoryId = superCategories[0].id
}
}
} catch (error) {
console.error('加载数据异常:', error)
@@ -333,13 +434,138 @@ export default {
}
},
// 分类管理
showAddSuperCategoryDialog() {
this.superCategoryForm = {
id: null,
key: '',
title: '',
sortOrder: this.superCategories.length
}
this.showSuperCategoryDialog = true
},
editSuperCategory(superCategory) {
this.superCategoryForm = {
id: superCategory.id,
key: superCategory.key,
title: superCategory.title,
sortOrder: superCategory.sortOrder
}
this.showSuperCategoryDialog = true
},
closeSuperCategoryDialog() {
this.showSuperCategoryDialog = false
},
saveSuperCategory() {
if (!this.superCategoryForm.key) {
this.showMessage('请填写分类标识', 'warning')
return
}
if (!this.superCategoryForm.title) {
this.showMessage('请填写分类名称', 'warning')
return
}
if (this.superCategoryForm.id) {
this.updateSuperCategoryToServer()
} else {
if (this.superCategories.find(c => c.key === this.superCategoryForm.key)) {
this.showMessage('分类标识已存在,请使用其他标识', 'warning')
return
}
this.addSuperCategoryToServer()
}
},
async addSuperCategoryToServer() {
try {
const data = {
superCategoryKey: this.superCategoryForm.key,
superCategoryTitle: this.superCategoryForm.title,
sortOrder: this.superCategoryForm.sortOrder
}
const response = await ToolService.createSuperCategory(data)
if (response && response.code === 200) {
this.showMessage('添加成功')
this.closeSuperCategoryDialog()
this.loadData()
} else {
this.showMessage('添加失败:' + (response.msg || '未知错误'), 'error')
}
} catch (error) {
console.error('添加分类失败:', error)
this.showMessage('添加失败,请检查网络连接', 'error')
}
},
async updateSuperCategoryToServer() {
try {
const response = await ToolService.updateSuperCategory({
id: this.superCategoryForm.id,
superCategoryKey: this.superCategoryForm.key,
superCategoryTitle: this.superCategoryForm.title,
sortOrder: this.superCategoryForm.sortOrder
})
if (response && response.code === 200) {
this.showMessage('修改成功')
this.closeSuperCategoryDialog()
this.loadData()
} else {
this.showMessage('修改失败:' + (response.msg || '未知错误'), 'error')
}
} catch (error) {
console.error('修改分类失败:', error)
this.showMessage('修改失败,请检查网络连接', 'error')
}
},
deleteSuperCategory(superCategory) {
const categoryCount = this.getCategoryCountInSuper(superCategory.id)
const message = categoryCount > 0
? `该分类下还有 ${categoryCount} 个分组,确定要删除“${superCategory.title}”吗?`
: `确定要删除分类“${superCategory.title}”吗?`
this.showConfirm(message, () => {
this.deleteSuperCategoryFromServer(superCategory.id)
})
},
async deleteSuperCategoryFromServer(id) {
try {
const response = await ToolService.deleteSuperCategory([id])
if (response && response.code === 200) {
this.showMessage('删除成功')
this.loadData()
} else {
this.showMessage('删除失败:' + (response.msg || '未知错误'), 'error')
}
} catch (error) {
console.error('删除分类失败:', error)
this.showMessage('删除失败,请检查网络连接', 'error')
}
},
// 分类管理
showAddCategoryDialog() {
if (!this.activeSuperCategoryId) {
this.showMessage('请先选择一个分类', 'warning')
return
}
this.categoryForm = {
id: null,
key: '',
title: '',
sortOrder: this.categories.length
superCategoryId: this.activeSuperCategoryId,
sortOrder: this.currentCategories.length
}
this.showCategoryDialog = true
},
@@ -349,6 +575,7 @@ export default {
id: category.id,
key: category.key,
title: category.title,
superCategoryId: category.superCategoryId,
sortOrder: category.sortOrder
}
this.showCategoryDialog = true
@@ -385,6 +612,7 @@ export default {
const data = {
categoryKey: this.categoryForm.key,
categoryTitle: this.categoryForm.title,
superCategoryId: this.categoryForm.superCategoryId,
sortOrder: this.categoryForm.sortOrder
}
@@ -409,6 +637,7 @@ export default {
id: this.categoryForm.id,
categoryKey: this.categoryForm.key,
categoryTitle: this.categoryForm.title,
superCategoryId: this.categoryForm.superCategoryId,
sortOrder: this.categoryForm.sortOrder
})
@@ -679,35 +908,195 @@ export default {
}
.manage-container {
padding: 20px 8px;
padding: 10px 8px;
padding-top: 180px; // 减小与固定区域的间距
}
// 固定的书签分类区域
.fixed-section {
position: fixed;
top:70px;
left: 0;
right: 0;
z-index: 999;
margin: 0;
border-radius: 0;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
padding: 28px 20px 20px 20px !important; // 顶部增加更多内边距
}
.manage-section {
background: white;
border-radius: 12px;
padding: 20px 12px;
margin-bottom: 24px;
padding: 10px 12px;
margin-bottom: 10px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
padding-bottom: 16px;
margin-bottom: 10px;
padding-bottom: 10px;
border-bottom: 2px solid #f0f0f0;
h2 {
margin: 0;
font-size: 20px;
color: @text-primary;
flex: 1; // 让标题占据剩余空间
min-width: 0; // 允许文本截断
}
.header-actions {
display: flex;
gap: 12px;
align-items: center;
flex-shrink: 0; // 防止按钮被压缩
}
}
}
// Tab 标签样式
.tab-container {
margin-top: 10px;
.tab-list {
display: flex;
flex-wrap: wrap;
gap: 12px;
padding: 0;
.tab-item {
position: relative;
display: flex;
align-items: center;
gap: 8px;
padding: 10px 10px;
background: #f5f7fa;
border: 2px solid transparent;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s ease;
user-select: none;
.tab-title {
font-size: 15px;
font-weight: 600;
color: @text-secondary;
transition: color 0.3s ease;
}
.tab-sort {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 28px;
height: 28px;
padding: 0 8px;
background: rgba(102, 126, 234, 0.1);
color: @primary-color;
font-size: 13px;
font-weight: 700;
border-radius: 6px;
transition: all 0.3s ease;
}
.tab-count {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 4px 10px;
background: #e5e7eb;
color: @text-secondary;
font-size: 12px;
font-weight: 600;
border-radius: 12px;
transition: background 0.3s ease;
}
.tab-actions {
display: none;
gap: 4px;
margin-left: 8px;
button {
width: 24px;
height: 24px;
padding: 0;
background: transparent;
border: none;
border-radius: 4px;
color: @text-tertiary;
font-size: 16px;
cursor: pointer;
transition: all 0.2s ease;
display: flex;
align-items: center;
justify-content: center;
&.tab-edit-btn:hover {
background: #409eff;
color: white;
}
&.tab-delete-btn:hover {
background: #f56c6c;
color: white;
}
}
}
&:hover {
background: #e5e7eb;
.tab-actions {
display: flex;
}
}
&.active {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-color: #667eea;
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
.tab-title {
color: white;
}
.tab-sort {
background: rgba(255, 255, 255, 0.3);
color: white;
}
.tab-count {
background: rgba(255, 255, 255, 0.25);
color: white;
}
.tab-actions {
display: flex;
button {
color: white;
&.tab-edit-btn:hover {
background: rgba(255, 255, 255, 0.2);
}
&.tab-delete-btn:hover {
background: rgba(255, 255, 255, 0.2);
}
}
}
}
}
}
}
// 统一的添加按钮样式
.add-btn {
padding: 8px 20px;
padding: 10px 24px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border: none;
border-radius: 8px;
@@ -779,6 +1168,16 @@ export default {
white-space: nowrap;
}
.category-super {
font-size: 12px;
color: #67c23a;
background: rgba(103, 194, 58, 0.1);
padding: 2px 8px;
border-radius: 4px;
white-space: nowrap;
font-weight: 500;
}
.category-sort {
font-size: 12px;
color: @primary-color;
@@ -1271,6 +1670,11 @@ button {
.manage-container {
padding: 16px 8px;
padding-top: 200px; // 手机端为固定区域预留更多空间
}
.fixed-section {
padding: 12px 8px;
}
.category-list {