diff --git a/src/services/ToolService.js b/src/services/ToolService.js index 59f9965..c03b8ef 100644 --- a/src/services/ToolService.js +++ b/src/services/ToolService.js @@ -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} diff --git a/src/services/request.js b/src/services/request.js index 69f2aa5..1f70a95 100644 --- a/src/services/request.js +++ b/src/services/request.js @@ -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 }) // 请求拦截器 diff --git a/src/views/HomePage.vue b/src/views/HomePage.vue index e11ab2c..e63aa4c 100644 --- a/src/views/HomePage.vue +++ b/src/views/HomePage.vue @@ -13,10 +13,24 @@
+ +
+
+ {{ superCategory.title }} + {{ getCategoryCountInSuper(superCategory.id) }} +
+
+ +
{{ tool.name }} -
-
-

{{ tool.desc }}

-

{{ tool.url }}

-
-
+
@@ -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 { diff --git a/src/views/ManagePage.vue b/src/views/ManagePage.vue index 64e01b0..5211ed8 100644 --- a/src/views/ManagePage.vue +++ b/src/views/ManagePage.vue @@ -6,14 +6,44 @@
- -
+ +
-

书签分组管理

+

书签分类

+
+ +
+
+ + +
+
+
+ {{ superCategory.sortOrder }} + {{ superCategory.title }} + {{ getCategoryCountInSuper(superCategory.id) }} 个分组 +
+ + +
+
+
+
+
+ + +
+
+

书签分组

-
+
{{ category.key }} {{ category.title }} @@ -31,11 +61,11 @@
-
+

网址管理

-
+

{{ category.title }}

@@ -65,6 +95,29 @@
+ +
+
+

{{ superCategoryForm.id ? '编辑分类' : '添加分类' }}

+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
@@ -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 {