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

@@ -4,6 +4,71 @@ import request from './request'
* 工具管理服务 * 工具管理服务
*/ */
class ToolService { class ToolService {
/**
* 获取所有大分类列表(不分页)
* @returns {Promise}
*/
static getSuperCategories() {
return request({
url: '/invest/workToolSuperCategory/list',
method: 'get',
params: {
pageNum: 1,
pageSize: 1000
}
})
}
/**
* 获取大分类详情
* @param {number} id - 大分类ID
* @returns {Promise}
*/
static getSuperCategoryById(id) {
return request({
url: `/invest/workToolSuperCategory/${id}`,
method: 'get'
})
}
/**
* 创建大分类
* @param {Object} data - 大分类数据
* @returns {Promise}
*/
static createSuperCategory(data) {
return request({
url: '/invest/workToolSuperCategory',
method: 'post',
data
})
}
/**
* 更新大分类
* @param {Object} data - 大分类数据包含id
* @returns {Promise}
*/
static updateSuperCategory(data) {
return request({
url: '/invest/workToolSuperCategory',
method: 'put',
data
})
}
/**
* 删除大分类(逻辑删除)
* @param {Array} ids - 大分类ID数组
* @returns {Promise}
*/
static deleteSuperCategory(ids) {
return request({
url: `/invest/workToolSuperCategory/${ids.join(',')}`,
method: 'delete'
})
}
/** /**
* 获取所有分类列表(不分页) * 获取所有分类列表(不分页)
* @returns {Promise} * @returns {Promise}

View File

@@ -2,8 +2,9 @@ import axios from 'axios'
// 创建 axios 实例 // 创建 axios 实例
const service = axios.create({ const service = axios.create({
baseURL: 'https://qdintc.com/prod-api', // baseURL: 'https://qdintc.com/prod-api',
timeout: 20000 baseURL: 'http://localhost:8080',
timeout: 10000
}) })
// 请求拦截器 // 请求拦截器

View File

@@ -13,10 +13,24 @@
<!-- 主内容区 --> <!-- 主内容区 -->
<div class="workbench-container"> <div class="workbench-container">
<!-- 大分类 Tab 标签 -->
<div class="super-category-tabs" v-if="superCategories.length > 0">
<div
v-for="superCategory in superCategories"
:key="superCategory.id"
:class="['tab-item', { active: activeSuperCategoryId === superCategory.id }]"
@click="switchTab(superCategory.id)"
>
<span class="tab-title">{{ superCategory.title }}</span>
<span class="tab-count">{{ getCategoryCountInSuper(superCategory.id) }}</span>
</div>
</div>
<!-- Tab 内容区 -->
<div class="tools-container"> <div class="tools-container">
<!-- 工具分类循环渲染 --> <!-- 工具分类循环渲染 -->
<div <div
v-for="category in categories" v-for="category in currentCategories"
:key="category.key" :key="category.key"
v-show="category.tools && category.tools.length > 0" v-show="category.tools && category.tools.length > 0"
class="tool-category" class="tool-category"
@@ -50,12 +64,7 @@
@click="openTool(tool.url)" @click="openTool(tool.url)"
> >
<span class="simple-tool-name">{{ tool.name }}</span> <span class="simple-tool-name">{{ tool.name }}</span>
<div class="simple-tool-tooltip"> </div>
<div class="tooltip-content">
<p class="tooltip-desc">{{ tool.desc }}</p>
<p class="tooltip-url">{{ tool.url }}</p>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -75,7 +84,9 @@ export default {
data() { data() {
return { return {
loading: false, loading: false,
superCategories: [],
categories: [], categories: [],
activeSuperCategoryId: null, // 当前Tab的大分类ID
viewMode: 'colorful' // 'colorful' 或 'simple' viewMode: 'colorful' // 'colorful' 或 'simple'
} }
}, },
@@ -84,26 +95,59 @@ export default {
this.loadData() this.loadData()
}, },
computed: {
// 当前激活的大分类下的分类列表
currentCategories() {
if (!this.activeSuperCategoryId) return []
return this.categories.filter(c => c.superCategoryId === this.activeSuperCategoryId)
}
},
methods: { methods: {
// 辅助方法
getCategoriesInSuper(superCategoryId) {
return this.categories.filter(c => c.superCategoryId === superCategoryId)
},
// 获取大分类下的分组数量
getCategoryCountInSuper(superCategoryId) {
return this.categories.filter(c => c.superCategoryId === superCategoryId).length
},
// 切换Tab
switchTab(superCategoryId) {
this.activeSuperCategoryId = superCategoryId
},
/** /**
* 加载数据(分类和工具) * 加载数据(大分类、分类和工具)
*/ */
async loadData() { async loadData() {
try { try {
this.loading = true this.loading = true
// 并发加载分类和工具 // 并发加载大分类、分类和工具
const [categoriesRes, toolsRes] = await Promise.all([ const [superCategoriesRes, categoriesRes, toolsRes] = await Promise.all([
ToolService.getSuperCategories(),
ToolService.getCategories(), ToolService.getCategories(),
ToolService.getTools() 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 => ({ const categories = (categoriesRes.rows || []).map(cat => ({
id: cat.id, id: cat.id,
key: cat.categoryKey, key: cat.categoryKey,
title: cat.categoryTitle, title: cat.categoryTitle,
superCategoryId: cat.superCategoryId,
sortOrder: cat.sortOrder || 0, sortOrder: cat.sortOrder || 0,
tools: [] tools: []
})) }))
@@ -117,7 +161,8 @@ export default {
url: tool.url, url: tool.url,
displayUrl: tool.displayUrl, displayUrl: tool.displayUrl,
icon: tool.icon, icon: tool.icon,
color: tool.color color: tool.color,
sortOrder: tool.sortOrder || 0
})) }))
// 将工具分配到对应的分类 // 将工具分配到对应的分类
@@ -128,10 +173,24 @@ export default {
} }
}) })
// 对每个分类下的工具按 sortOrder 排序
categories.forEach(category => {
if (category.tools && category.tools.length > 0) {
category.tools.sort((a, b) => (a.sortOrder || 0) - (b.sortOrder || 0))
}
})
// 按 sortOrder 排序 // 按 sortOrder 排序
categories.sort((a, b) => a.sortOrder - b.sortOrder) categories.sort((a, b) => a.sortOrder - b.sortOrder)
superCategories.sort((a, b) => a.sortOrder - b.sortOrder)
this.superCategories = superCategories
this.categories = categories this.categories = categories
// 默认激活第一个大分类
if (superCategories.length > 0 && !this.activeSuperCategoryId) {
this.activeSuperCategoryId = superCategories[0].id
}
} else { } else {
console.error('加载数据失败') console.error('加载数据失败')
this.loadDefaultData() this.loadDefaultData()
@@ -274,7 +333,7 @@ export default {
// 间距变量 // 间距变量
@spacing-sm: 10px; @spacing-sm: 10px;
@spacing-md: 20px; @spacing-md: 15px;
@spacing-lg: 30px; @spacing-lg: 30px;
// 页面主体 // 页面主体
@@ -288,10 +347,87 @@ export default {
.workbench-container { .workbench-container {
max-width: 1920px; max-width: 1920px;
margin: 0 auto; margin: 0 auto;
padding: 20px @spacing-md; padding: @spacing-md;
padding-top: 89px; // 为固定的 Tab 区域预留空间 (76px标题栏 + 20px上边距 + 40px Tab高度)
overflow: visible; overflow: visible;
} }
// 大分类 Tab 标签样式
.super-category-tabs {
position: fixed;
top: @header-height;
left: 0;
right: 0;
z-index: 98;
display: flex;
flex-wrap: wrap;
gap: 12px;
padding: @spacing-md @spacing-md;
background: rgba(102, 126, 234, 0.95);
backdrop-filter: blur(10px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
.tab-item {
display: flex;
align-items: center;
gap: 8px;
padding: 5px 15px;
background: rgba(255, 255, 255, 0.2);
border: 2px solid transparent;
border-radius: 12px;
cursor: pointer;
transition: all 0.3s ease;
user-select: none;
.tab-title {
font-size: 16px;
font-weight: 700;
color: rgba(255, 255, 255, 0.9);
transition: color 0.3s ease;
letter-spacing: 0.5px;
}
.tab-count {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 24px;
height: 24px;
padding: 0 8px;
background: rgba(255, 255, 255, 0.25);
color: white;
font-size: 13px;
font-weight: 700;
border-radius: 12px;
transition: background 0.3s ease;
}
&:hover {
background: rgba(255, 255, 255, 0.25);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
&.active {
background: white;
border-color: white;
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.2);
.tab-title {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.tab-count {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
}
}
}
.workbench-header { .workbench-header {
position: fixed; position: fixed;
top: 0; top: 0;
@@ -399,23 +535,30 @@ export default {
.tools-container { .tools-container {
display: flex; display: flex;
flex-wrap: wrap; flex-direction: column;
gap: 15px @spacing-md; gap: 30px;
align-items: flex-start; align-items: flex-start;
overflow: visible; overflow: visible;
} }
.tool-category { flex: 0 1 auto; .tool-category {
flex: 0 1 auto;
overflow: visible; overflow: visible;
width: 100%;
position: relative;
z-index: 1;
.category-title { .category-title {
font-size: 20px; font-size: 22px;
font-weight: 600; font-weight: 700;
color: @white-color; color: @white-color;
margin-bottom: @spacing-sm; margin-bottom: 10px;
padding-left: 12px; padding-left: 12px;
border-left: 4px solid rgba(255, 255, 255, 0.8); border-left: 4px solid rgba(255, 255, 255, 0.9);
line-height: 1.5; line-height: 1.5;
text-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
position: relative;
z-index: 1;
} }
.category-items { .category-items {
@@ -458,7 +601,6 @@ export default {
border: 1px solid rgba(255, 255, 255, 0.5); border: 1px solid rgba(255, 255, 255, 0.5);
backdrop-filter: blur(10px); backdrop-filter: blur(10px);
position: relative; position: relative;
z-index: 1;
.simple-tool-name { .simple-tool-name {
font-size: 15px; font-size: 15px;
@@ -466,7 +608,6 @@ export default {
color: @text-primary; color: @text-primary;
white-space: nowrap; white-space: nowrap;
letter-spacing: 0.3px; letter-spacing: 0.3px;
position: relative;
// 文字渐变效果 // 文字渐变效果
background: linear-gradient(135deg, #333 0%, #555 100%); background: linear-gradient(135deg, #333 0%, #555 100%);
@@ -475,64 +616,11 @@ export default {
background-clip: text; background-clip: text;
} }
// 悬浮提示框
.simple-tool-tooltip {
position: absolute;
bottom: calc(100% + 10px);
left: 50%;
transform: translateX(-50%) translateY(5px);
opacity: 0;
visibility: hidden;
transition: all 0.15s ease-out;
pointer-events: none;
z-index: 1001;
.tooltip-content {
background: rgba(255, 255, 255, 0.98);
backdrop-filter: blur(10px);
color: @text-primary;
padding: 14px 18px;
border-radius: 8px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2), 0 0 0 1px rgba(102, 126, 234, 0.2);
white-space: nowrap;
position: relative;
// 小三角
&::after {
content: '';
position: absolute;
top: 100%;
left: 50%;
transform: translateX(-50%);
border: 6px solid transparent;
border-top-color: rgba(255, 255, 255, 0.98);
filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.1));
}
.tooltip-desc {
font-size: 14px;
font-weight: 600;
margin: 0 0 4px;
line-height: 1.4;
color: #333;
}
.tooltip-url {
font-size: 12px;
margin: 0;
font-family: 'Consolas', 'Monaco', monospace;
color: #667eea;
font-weight: 500;
}
}
}
&:hover { &:hover {
transform: translateY(-3px) scale(1.02); transform: translateY(-3px) scale(1.02);
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.25); box-shadow: 0 6px 20px rgba(102, 126, 234, 0.25);
background: white; background: white;
border-color: rgba(102, 126, 234, 0.3); border-color: rgba(102, 126, 234, 0.3);
z-index: 999;
.simple-tool-name { .simple-tool-name {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
@@ -540,12 +628,6 @@ export default {
-webkit-text-fill-color: transparent; -webkit-text-fill-color: transparent;
background-clip: text; background-clip: text;
} }
.simple-tool-tooltip {
opacity: 1;
visibility: visible;
transform: translateX(-50%) translateY(0);
}
} }
&:active { &:active {
@@ -830,6 +912,27 @@ export default {
padding: @spacing-lg @spacing-sm; padding: @spacing-lg @spacing-sm;
} }
.super-category-tabs {
padding: 12px;
gap: 8px;
margin-bottom: 20px;
.tab-item {
padding: 10px 16px;
.tab-title {
font-size: 14px;
}
.tab-count {
min-width: 20px;
height: 20px;
font-size: 12px;
padding: 0 6px;
}
}
}
.workbench-header { .workbench-header {
justify-content: flex-start; justify-content: flex-start;
padding: @spacing-md 20px; padding: @spacing-md 20px;
@@ -865,10 +968,7 @@ export default {
} }
.tool-card-simple { .tool-card-simple {
// 手机端禁用提示框 // 手机端已移除提示框
.simple-tool-tooltip {
display: none !important;
}
} }
.tool-icon { .tool-icon {

View File

@@ -6,14 +6,44 @@
</div> </div>
<div class="manage-container"> <div class="manage-container">
<!-- 分类管理 --> <!-- 分类 Tab 标签页固定在顶部 -->
<div class="manage-section"> <div class="manage-section fixed-section">
<div class="section-header"> <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> <button class="add-btn" @click="showAddCategoryDialog">+ 添加书签分组</button>
</div> </div>
<div class="category-list"> <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"> <div class="category-info">
<span class="category-key">{{ category.key }}</span> <span class="category-key">{{ category.key }}</span>
<span class="category-name">{{ category.title }}</span> <span class="category-name">{{ category.title }}</span>
@@ -31,11 +61,11 @@
</div> </div>
<!-- 网址管理 --> <!-- 网址管理 -->
<div class="manage-section"> <div v-if="activeSuperCategoryId" class="manage-section">
<div class="section-header"> <div class="section-header">
<h2>网址管理</h2> <h2>网址管理</h2>
</div> </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"> <div class="tool-group-header">
<h3>{{ category.title }}</h3> <h3>{{ category.title }}</h3>
<button class="add-btn small" @click="showAddToolDialog(category)">+ 添加网址</button> <button class="add-btn small" @click="showAddToolDialog(category)">+ 添加网址</button>
@@ -65,6 +95,29 @@
</div> </div>
</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 v-if="showCategoryDialog" class="dialog-overlay" @click="closeCategoryDialog">
<div class="dialog" @click.stop> <div class="dialog" @click.stop>
@@ -174,7 +227,10 @@ export default {
data() { data() {
return { return {
loading: false, loading: false,
superCategories: [],
categories: [], categories: [],
activeSuperCategoryId: null, // 当前Tab的分类ID
showSuperCategoryDialog: false,
showCategoryDialog: false, showCategoryDialog: false,
showToolDialog: false, showToolDialog: false,
showConfirmDialog: false, showConfirmDialog: false,
@@ -213,10 +269,17 @@ export default {
{ name: '淡黄', value: 'linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%)' }, { name: '淡黄', value: 'linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%)' },
{ name: '淡蓝', value: 'linear-gradient(135deg, #accbee 0%, #e7f0fd 100%)' } { name: '淡蓝', value: 'linear-gradient(135deg, #accbee 0%, #e7f0fd 100%)' }
], ],
superCategoryForm: {
id: null,
key: '',
title: '',
sortOrder: 0
},
categoryForm: { categoryForm: {
id: null, id: null,
key: '', key: '',
title: '', title: '',
superCategoryId: null,
sortOrder: 0 sortOrder: 0
}, },
toolForm: { toolForm: {
@@ -242,6 +305,12 @@ export default {
warning: '⚠' warning: '⚠'
} }
return icons[this.message.type] || '✓' 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('/') 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() { async loadData() {
try { try {
this.loading = true this.loading = true
const [categoriesRes, toolsRes] = await Promise.all([ const [superCategoriesRes, categoriesRes, toolsRes] = await Promise.all([
ToolService.getSuperCategories(),
ToolService.getCategories(), ToolService.getCategories(),
ToolService.getTools() 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 => ({ const categories = (categoriesRes.rows || []).map(cat => ({
id: cat.id, id: cat.id,
key: cat.categoryKey, key: cat.categoryKey,
title: cat.categoryTitle, title: cat.categoryTitle,
superCategoryId: cat.superCategoryId,
sortOrder: cat.sortOrder || 0, sortOrder: cat.sortOrder || 0,
tools: [] tools: []
})) }))
@@ -322,8 +416,15 @@ export default {
}) })
categories.sort((a, b) => a.sortOrder - b.sortOrder) categories.sort((a, b) => a.sortOrder - b.sortOrder)
superCategories.sort((a, b) => a.sortOrder - b.sortOrder)
this.superCategories = superCategories
this.categories = categories this.categories = categories
// 默认激活第一个分类
if (superCategories.length > 0 && !this.activeSuperCategoryId) {
this.activeSuperCategoryId = superCategories[0].id
}
} }
} catch (error) { } catch (error) {
console.error('加载数据异常:', 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() { showAddCategoryDialog() {
if (!this.activeSuperCategoryId) {
this.showMessage('请先选择一个分类', 'warning')
return
}
this.categoryForm = { this.categoryForm = {
id: null, id: null,
key: '', key: '',
title: '', title: '',
sortOrder: this.categories.length superCategoryId: this.activeSuperCategoryId,
sortOrder: this.currentCategories.length
} }
this.showCategoryDialog = true this.showCategoryDialog = true
}, },
@@ -349,6 +575,7 @@ export default {
id: category.id, id: category.id,
key: category.key, key: category.key,
title: category.title, title: category.title,
superCategoryId: category.superCategoryId,
sortOrder: category.sortOrder sortOrder: category.sortOrder
} }
this.showCategoryDialog = true this.showCategoryDialog = true
@@ -385,6 +612,7 @@ export default {
const data = { const data = {
categoryKey: this.categoryForm.key, categoryKey: this.categoryForm.key,
categoryTitle: this.categoryForm.title, categoryTitle: this.categoryForm.title,
superCategoryId: this.categoryForm.superCategoryId,
sortOrder: this.categoryForm.sortOrder sortOrder: this.categoryForm.sortOrder
} }
@@ -409,6 +637,7 @@ export default {
id: this.categoryForm.id, id: this.categoryForm.id,
categoryKey: this.categoryForm.key, categoryKey: this.categoryForm.key,
categoryTitle: this.categoryForm.title, categoryTitle: this.categoryForm.title,
superCategoryId: this.categoryForm.superCategoryId,
sortOrder: this.categoryForm.sortOrder sortOrder: this.categoryForm.sortOrder
}) })
@@ -679,35 +908,195 @@ export default {
} }
.manage-container { .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 { .manage-section {
background: white; background: white;
border-radius: 12px; border-radius: 12px;
padding: 20px 12px; padding: 10px 12px;
margin-bottom: 24px; margin-bottom: 10px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08); box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
.section-header { .section-header {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
margin-bottom: 20px; margin-bottom: 10px;
padding-bottom: 16px; padding-bottom: 10px;
border-bottom: 2px solid #f0f0f0; border-bottom: 2px solid #f0f0f0;
h2 { h2 {
margin: 0; margin: 0;
font-size: 20px; font-size: 20px;
color: @text-primary; 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 { .add-btn {
padding: 8px 20px; padding: 10px 24px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border: none; border: none;
border-radius: 8px; border-radius: 8px;
@@ -779,6 +1168,16 @@ export default {
white-space: nowrap; 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 { .category-sort {
font-size: 12px; font-size: 12px;
color: @primary-color; color: @primary-color;
@@ -1271,6 +1670,11 @@ button {
.manage-container { .manage-container {
padding: 16px 8px; padding: 16px 8px;
padding-top: 200px; // 手机端为固定区域预留更多空间
}
.fixed-section {
padding: 12px 8px;
} }
.category-list { .category-list {