feat:新增标签分类。
This commit is contained in:
@@ -4,6 +4,71 @@ import request from './request'
|
||||
* 工具管理服务
|
||||
*/
|
||||
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}
|
||||
|
||||
@@ -2,8 +2,9 @@ import axios from 'axios'
|
||||
|
||||
// 创建 axios 实例
|
||||
const service = axios.create({
|
||||
baseURL: 'https://qdintc.com/prod-api',
|
||||
timeout: 20000
|
||||
// baseURL: 'https://qdintc.com/prod-api',
|
||||
baseURL: 'http://localhost:8080',
|
||||
timeout: 10000
|
||||
})
|
||||
|
||||
// 请求拦截器
|
||||
|
||||
@@ -13,10 +13,24 @@
|
||||
|
||||
<!-- 主内容区 -->
|
||||
<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
|
||||
v-for="category in categories"
|
||||
v-for="category in currentCategories"
|
||||
:key="category.key"
|
||||
v-show="category.tools && category.tools.length > 0"
|
||||
class="tool-category"
|
||||
@@ -50,12 +64,7 @@
|
||||
@click="openTool(tool.url)"
|
||||
>
|
||||
<span class="simple-tool-name">{{ tool.name }}</span>
|
||||
<div class="simple-tool-tooltip">
|
||||
<div class="tooltip-content">
|
||||
<p class="tooltip-desc">{{ tool.desc }}</p>
|
||||
<p class="tooltip-url">{{ tool.url }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -75,7 +84,9 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
superCategories: [],
|
||||
categories: [],
|
||||
activeSuperCategoryId: null, // 当前Tab的大分类ID
|
||||
viewMode: 'colorful' // 'colorful' 或 'simple'
|
||||
}
|
||||
},
|
||||
@@ -84,26 +95,59 @@ export default {
|
||||
this.loadData()
|
||||
},
|
||||
|
||||
computed: {
|
||||
// 当前激活的大分类下的分类列表
|
||||
currentCategories() {
|
||||
if (!this.activeSuperCategoryId) return []
|
||||
return this.categories.filter(c => c.superCategoryId === this.activeSuperCategoryId)
|
||||
}
|
||||
},
|
||||
|
||||
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() {
|
||||
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: []
|
||||
}))
|
||||
@@ -117,7 +161,8 @@ export default {
|
||||
url: tool.url,
|
||||
displayUrl: tool.displayUrl,
|
||||
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 排序
|
||||
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
|
||||
}
|
||||
} else {
|
||||
console.error('加载数据失败')
|
||||
this.loadDefaultData()
|
||||
@@ -274,7 +333,7 @@ export default {
|
||||
|
||||
// 间距变量
|
||||
@spacing-sm: 10px;
|
||||
@spacing-md: 20px;
|
||||
@spacing-md: 15px;
|
||||
@spacing-lg: 30px;
|
||||
|
||||
// 页面主体
|
||||
@@ -288,10 +347,87 @@ export default {
|
||||
.workbench-container {
|
||||
max-width: 1920px;
|
||||
margin: 0 auto;
|
||||
padding: 20px @spacing-md;
|
||||
padding: @spacing-md;
|
||||
padding-top: 89px; // 为固定的 Tab 区域预留空间 (76px标题栏 + 20px上边距 + 40px Tab高度)
|
||||
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 {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
@@ -399,23 +535,30 @@ export default {
|
||||
|
||||
.tools-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 15px @spacing-md;
|
||||
flex-direction: column;
|
||||
gap: 30px;
|
||||
align-items: flex-start;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.tool-category { flex: 0 1 auto;
|
||||
.tool-category {
|
||||
flex: 0 1 auto;
|
||||
overflow: visible;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
|
||||
.category-title {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
color: @white-color;
|
||||
margin-bottom: @spacing-sm;
|
||||
margin-bottom: 10px;
|
||||
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;
|
||||
text-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.category-items {
|
||||
@@ -458,7 +601,6 @@ export default {
|
||||
border: 1px solid rgba(255, 255, 255, 0.5);
|
||||
backdrop-filter: blur(10px);
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
|
||||
.simple-tool-name {
|
||||
font-size: 15px;
|
||||
@@ -466,7 +608,6 @@ export default {
|
||||
color: @text-primary;
|
||||
white-space: nowrap;
|
||||
letter-spacing: 0.3px;
|
||||
position: relative;
|
||||
|
||||
// 文字渐变效果
|
||||
background: linear-gradient(135deg, #333 0%, #555 100%);
|
||||
@@ -475,64 +616,11 @@ export default {
|
||||
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 {
|
||||
transform: translateY(-3px) scale(1.02);
|
||||
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.25);
|
||||
background: white;
|
||||
border-color: rgba(102, 126, 234, 0.3);
|
||||
z-index: 999;
|
||||
|
||||
.simple-tool-name {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
@@ -540,12 +628,6 @@ export default {
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
.simple-tool-tooltip {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
transform: translateX(-50%) translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
&:active {
|
||||
@@ -830,6 +912,27 @@ export default {
|
||||
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 {
|
||||
justify-content: flex-start;
|
||||
padding: @spacing-md 20px;
|
||||
@@ -865,10 +968,7 @@ export default {
|
||||
}
|
||||
|
||||
.tool-card-simple {
|
||||
// 手机端禁用提示框
|
||||
.simple-tool-tooltip {
|
||||
display: none !important;
|
||||
}
|
||||
// 手机端已移除提示框
|
||||
}
|
||||
|
||||
.tool-icon {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user