fix: 功能优化完善。

This commit is contained in:
tianyongbao
2025-12-13 19:05:13 +08:00
parent bdf4035816
commit 4965e96556
5 changed files with 299 additions and 9 deletions

View File

@@ -2,9 +2,9 @@
<html lang="zh-Hans-CN">
<head>
<meta charset="utf-8"/>
<meta name="Keywords" content="记账平台,健康档案,茅台预约"/>
<meta name="Keywords" content="工作台"/>
<meta name="Description"
content="智聪工作台"/>
content="工作台"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"/>
<meta name="apple-mobile-web-app-capable" content="yes"/>
<meta name="apple-mobile-web-app-status-bar-style" content="black"/>
@@ -12,7 +12,7 @@
<meta http-equiv="x-dns-prefetch-control" content="on"/>
<link rel="icon" href="/static/favicon.ico" />
<link rel="dns-prefetch" href="http://www.qdintc.com"/>
<title>智聪工作台</title>
<title>工作台</title>
</head>
<body>
<div id="app"></div>

11
package-lock.json generated
View File

@@ -10,6 +10,7 @@
"dependencies": {
"axios": "^0.27.2",
"jsonp": "^0.2.1",
"sortablejs": "^1.15.6",
"vue": "^2.7.15",
"vue-carousel": "^0.18.0",
"vue-router": "^3.0.1",
@@ -13130,6 +13131,11 @@
"node": ">=0.10.0"
}
},
"node_modules/sortablejs": {
"version": "1.15.6",
"resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.15.6.tgz",
"integrity": "sha512-aNfiuwMEpfBM/CN6LY0ibyhxPfPbyFeBTYJKCvzkJ2GkUpazIt3H+QIPAMHwqQ7tMKaHz1Qj+rJJCqljnf4p3A=="
},
"node_modules/source-list-map": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz",
@@ -26431,6 +26437,11 @@
"is-plain-obj": "^1.0.0"
}
},
"sortablejs": {
"version": "1.15.6",
"resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.15.6.tgz",
"integrity": "sha512-aNfiuwMEpfBM/CN6LY0ibyhxPfPbyFeBTYJKCvzkJ2GkUpazIt3H+QIPAMHwqQ7tMKaHz1Qj+rJJCqljnf4p3A=="
},
"source-list-map": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz",

View File

@@ -12,6 +12,7 @@
"dependencies": {
"axios": "^0.27.2",
"jsonp": "^0.2.1",
"sortablejs": "^1.15.6",
"vue": "^2.7.15",
"vue-carousel": "^0.18.0",
"vue-router": "^3.0.1",

View File

@@ -64,7 +64,12 @@
@click="openTool(tool.url)"
>
<span class="simple-tool-name">{{ tool.name }}</span>
</div>
<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>
@@ -87,7 +92,7 @@ export default {
superCategories: [],
categories: [],
activeSuperCategoryId: null, // 当前Tab的大分类ID
viewMode: 'colorful' // 'colorful' 或 'simple'
viewMode: 'simple' // 'colorful' 或 'simple'
}
},
@@ -345,7 +350,7 @@ export default {
}
.workbench-container {
max-width: 1920px;
max-width: 1820px;
margin: 0 auto;
padding: @spacing-md;
padding-top: 89px; // 为固定的 Tab 区域预留空间 (76px标题栏 + 20px上边距 + 40px Tab高度)
@@ -601,6 +606,7 @@ 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;
@@ -608,6 +614,7 @@ export default {
color: @text-primary;
white-space: nowrap;
letter-spacing: 0.3px;
position: relative;
// 文字渐变效果
background: linear-gradient(135deg, #333 0%, #555 100%);
@@ -616,11 +623,64 @@ 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: 4px 8px;
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%);
@@ -628,6 +688,12 @@ export default {
-webkit-text-fill-color: transparent;
background-clip: text;
}
.simple-tool-tooltip {
opacity: 1;
visibility: visible;
transform: translateX(-50%) translateY(0);
}
}
&:active {
@@ -968,7 +1034,10 @@ export default {
}
.tool-card-simple {
// 手机端已移除提示框
// 手机端禁用提示框
.simple-tool-tooltip {
display: none !important;
}
}
.tool-icon {

View File

@@ -219,6 +219,7 @@
<script>
import Page from '../components/Page'
import ToolService from '../services/ToolService'
import Sortable from 'sortablejs'
export default {
name: 'ManagePage',
@@ -293,7 +294,10 @@ export default {
color: 'linear-gradient(135deg, #a1c4fd 0%, #c2e9fb 100%)',
sortOrder: 0
},
currentCategory: null
currentCategory: null,
categorySortable: null, // 分组拖拽实例
toolSortables: [], // 网址拖拽实例数组
superCategorySortable: null // 大分类拖拽实例
}
},
@@ -320,6 +324,18 @@ export default {
this.loadData()
},
mounted() {
// 初始化拖拽功能
this.initSortable()
},
updated() {
// 数据更新后重新初始化拖拽
this.$nextTick(() => {
this.initSortable()
})
},
methods: {
// 消息提示
showMessage(text, type = 'success') {
@@ -825,6 +841,179 @@ export default {
console.error('删除工具失败:', error)
this.showMessage('删除失败,请检查网络连接', 'error')
}
},
// 初始化拖拽排序
initSortable() {
// 销毁旧的 Sortable 实例
if (this.superCategorySortable) {
this.superCategorySortable.destroy()
}
if (this.categorySortable) {
this.categorySortable.destroy()
}
if (this.toolSortables) {
this.toolSortables.forEach(s => s.destroy())
}
this.toolSortables = []
// 大分类 Tab 拖拽
const tabListEl = document.querySelector('.tab-list')
if (tabListEl) {
this.superCategorySortable = Sortable.create(tabListEl, {
animation: 150,
handle: '.tab-item',
ghostClass: 'sortable-ghost',
chosenClass: 'sortable-chosen',
dragClass: 'sortable-drag',
onEnd: (evt) => {
this.handleSuperCategorySort(evt)
}
})
}
// 书签分组拖拽
const categoryListEl = document.querySelector('.category-list')
if (categoryListEl) {
this.categorySortable = Sortable.create(categoryListEl, {
animation: 150,
handle: '.category-item',
ghostClass: 'sortable-ghost',
chosenClass: 'sortable-chosen',
dragClass: 'sortable-drag',
onEnd: (evt) => {
this.handleCategorySort(evt)
}
})
}
// 网址列表拖拽(每个分组都需要初始化)
const toolLists = document.querySelectorAll('.tool-list')
toolLists.forEach((toolListEl, index) => {
const sortable = Sortable.create(toolListEl, {
animation: 150,
handle: '.tool-item',
ghostClass: 'sortable-ghost',
chosenClass: 'sortable-chosen',
dragClass: 'sortable-drag',
onEnd: (evt) => {
this.handleToolSort(evt, index)
}
})
this.toolSortables.push(sortable)
})
},
// 处理大分类排序
async handleSuperCategorySort(evt) {
const { oldIndex, newIndex } = evt
if (oldIndex === newIndex) return
const superCategories = [...this.superCategories]
const movedItem = superCategories[oldIndex]
superCategories.splice(oldIndex, 1)
superCategories.splice(newIndex, 0, movedItem)
// 更新排序号
const updates = superCategories.map((cat, index) => ({
id: cat.id,
sortOrder: index
}))
try {
// 批量更新排序
for (const update of updates) {
await ToolService.updateSuperCategory({
id: update.id,
superCategoryKey: superCategories.find(c => c.id === update.id).key,
superCategoryTitle: superCategories.find(c => c.id === update.id).title,
sortOrder: update.sortOrder
})
}
this.showMessage('排序保存成功')
await this.loadData()
} catch (error) {
console.error('排序失败:', error)
this.showMessage('排序保存失败', 'error')
await this.loadData()
}
},
// 处理书签分组排序
async handleCategorySort(evt) {
const { oldIndex, newIndex } = evt
if (oldIndex === newIndex) return
const categories = [...this.currentCategories]
const movedItem = categories[oldIndex]
categories.splice(oldIndex, 1)
categories.splice(newIndex, 0, movedItem)
// 更新排序号
const updates = categories.map((cat, index) => ({
id: cat.id,
sortOrder: index
}))
try {
// 批量更新排序
for (const update of updates) {
await ToolService.updateCategory({
id: update.id,
categoryKey: categories.find(c => c.id === update.id).key,
categoryTitle: categories.find(c => c.id === update.id).title,
superCategoryId: categories.find(c => c.id === update.id).superCategoryId,
sortOrder: update.sortOrder
})
}
this.showMessage('排序保存成功')
await this.loadData()
} catch (error) {
console.error('排序失败:', error)
this.showMessage('排序保存失败', 'error')
await this.loadData()
}
},
// 处理网址排序
async handleToolSort(evt, categoryIndex) {
const { oldIndex, newIndex } = evt
if (oldIndex === newIndex) return
const category = this.currentCategories[categoryIndex]
const tools = [...category.tools]
const movedItem = tools[oldIndex]
tools.splice(oldIndex, 1)
tools.splice(newIndex, 0, movedItem)
// 更新排序号
const updates = tools.map((tool, index) => ({
...tool,
sortOrder: index
}))
try {
// 批量更新排序
for (const tool of updates) {
await ToolService.updateTool({
id: tool.id,
categoryId: category.id,
name: tool.name,
description: tool.desc,
url: tool.url,
displayUrl: tool.displayUrl,
icon: tool.icon,
color: tool.color,
sortOrder: tool.sortOrder
})
}
this.showMessage('排序保存成功')
await this.loadData()
} catch (error) {
console.error('排序失败:', error)
this.showMessage('排序保存失败', 'error')
await this.loadData()
}
}
}
}
@@ -976,7 +1165,7 @@ export default {
background: #f5f7fa;
border: 2px solid transparent;
border-radius: 8px;
cursor: pointer;
cursor: move; // 显示可拖拽
transition: all 0.3s ease;
user-select: none;
@@ -1137,6 +1326,7 @@ export default {
border-radius: 6px;
border: 1px solid #e5e7eb;
transition: all 0.2s ease;
cursor: move; // 显示可拖拽
&:hover {
background: #f3f4f6;
@@ -1245,6 +1435,7 @@ export default {
border-radius: 6px;
border: 1px solid #e5e7eb;
transition: all 0.2s ease;
cursor: move; // 显示可拖拽
&:hover {
background: #f3f4f6;
@@ -1647,6 +1838,24 @@ button {
transform: translate(-50%, -20px);
}
// 拖拽样式
.sortable-ghost {
opacity: 0.4;
background: #e0e7ff !important;
border: 2px dashed @primary-color !important;
}
.sortable-chosen {
opacity: 0.8;
transform: scale(1.02);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.sortable-drag {
opacity: 0.8;
transform: rotate(2deg);
}
// 响应式布局 - 手机端
@media (max-width: 600px) {
.manage-header {