This commit is contained in:
SunCheng
2026-02-02 11:07:34 +08:00
parent 61916dc6da
commit 460dcd17ef
9 changed files with 9695 additions and 4967 deletions

View File

@@ -56,11 +56,33 @@
v-for="category in categories"
:key="category.id"
>
<van-cell
:title="category.name"
is-link
@click="handleEdit(category)"
/>
<van-cell :title="category.name">
<template #icon>
<div
v-if="category.icon"
class="category-icon"
v-html="parseIcon(category.icon)"
/>
</template>
<template #default>
<div class="category-actions">
<van-button
size="small"
type="primary"
plain
@click="handleIconSelect(category)"
>
选择图标
</van-button>
<van-button
size="small"
@click="handleEditOld(category)"
>
编辑
</van-button>
</div>
</template>
</van-cell>
<template #right>
<van-button
square
@@ -131,6 +153,52 @@
message="删除后无法恢复,确定要删除吗?"
@confirm="handleConfirmDelete"
/>
<!-- 图标选择对话框 -->
<van-dialog
v-model:show="showIconDialog"
title="选择图标"
show-cancel-button
@confirm="handleConfirmIconSelect"
>
<div class="icon-selector">
<div
v-if="currentCategory && currentCategory.icon"
class="icon-list"
>
<div
v-for="(icon, index) in parseIconArray(currentCategory.icon)"
:key="index"
class="icon-item"
:class="{ active: selectedIconIndex === index }"
@click="selectedIconIndex = index"
>
<div
class="icon-preview"
v-html="icon"
/>
</div>
</div>
<div
v-else
class="empty-icons"
>
<van-empty description="暂无图标" />
</div>
<div class="icon-actions">
<van-button
type="primary"
size="small"
:loading="isGeneratingIcon"
:disabled="isGeneratingIcon"
@click="handleGenerateIcon"
>
{{ isGeneratingIcon ? 'AI生成中...' : '生成新图标' }}
</van-button>
</div>
</div>
</van-dialog>
</div>
</div>
</template>
@@ -143,7 +211,9 @@ import {
getCategoryList,
createCategory,
deleteCategory,
updateCategory
updateCategory,
generateIcon,
updateSelectedIcon
} from '@/api/transactionCategory'
const router = useRouter()
@@ -185,6 +255,12 @@ const editForm = ref({
name: ''
})
// 图标选择对话框
const showIconDialog = ref(false)
const currentCategory = ref(null) // 当前正在编辑图标的分类
const selectedIconIndex = ref(0)
const isGeneratingIcon = ref(false)
// 计算导航栏标题
const navTitle = computed(() => {
if (currentLevel.value === 0) {
@@ -309,6 +385,97 @@ const handleEdit = (category) => {
showEditDialog.value = true
}
/**
* 打开图标选择器
*/
const handleIconSelect = (category) => {
currentCategory.value = category
selectedIconIndex.value = 0
showIconDialog.value = true
}
/**
* 生成新图标
*/
const handleGenerateIcon = async () => {
if (!currentCategory.value) return
try {
isGeneratingIcon.value = true
showLoadingToast({
message: 'AI正在生成图标...',
forbidClick: true,
duration: 0
})
const { success, data, message } = await generateIcon(currentCategory.value.id)
if (success) {
showSuccessToast('图标生成成功')
// 重新加载分类列表以获取最新的图标
await loadCategories()
// 更新当前分类引用
const updated = categories.value.find((c) => c.id === currentCategory.value.id)
if (updated) {
currentCategory.value = updated
}
} else {
showToast(message || '生成图标失败')
}
} catch (error) {
console.error('生成图标失败:', error)
showToast('生成图标失败: ' + (error.message || '未知错误'))
} finally {
isGeneratingIcon.value = false
closeToast()
}
}
/**
* 确认选择图标
*/
const handleConfirmIconSelect = async () => {
if (!currentCategory.value) return
try {
showLoadingToast({
message: '保存中...',
forbidClick: true,
duration: 0
})
const { success, message } = await updateSelectedIcon(
currentCategory.value.id,
selectedIconIndex.value
)
if (success) {
showSuccessToast('图标保存成功')
showIconDialog.value = false
await loadCategories()
} else {
showToast(message || '保存失败')
}
} catch (error) {
console.error('保存图标失败:', error)
showToast('保存图标失败: ' + (error.message || '未知错误'))
} finally {
closeToast()
}
}
/**
* 编辑分类
*/
const handleEditOld = (category) => {
editForm.value = {
id: category.id,
name: category.name
}
showEditDialog.value = true
}
/**
* 确认编辑
*/
@@ -392,6 +559,32 @@ const resetAddForm = () => {
}
}
/**
* 解析图标数组(第一个图标为当前选中的)
*/
const parseIcon = (iconJson) => {
if (!iconJson) return ''
try {
const icons = JSON.parse(iconJson)
return Array.isArray(icons) && icons.length > 0 ? icons[0] : ''
} catch {
return ''
}
}
/**
* 解析图标数组为完整数组
*/
const parseIconArray = (iconJson) => {
if (!iconJson) return []
try {
const icons = JSON.parse(iconJson)
return Array.isArray(icons) ? icons : []
} catch {
return []
}
}
onMounted(() => {
// 初始化时显示类型选择
currentLevel.value = 0
@@ -412,6 +605,85 @@ onMounted(() => {
cursor: pointer;
}
.category-icon {
width: 24px;
height: 24px;
margin-right: 12px;
display: inline-flex;
align-items: center;
justify-content: center;
}
.category-icon :deep(svg) {
width: 100%;
height: 100%;
fill: currentColor;
}
.category-actions {
display: flex;
gap: 8px;
}
.icon-selector {
padding: 16px;
}
.icon-list {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(60px, 1fr));
gap: 12px;
margin-bottom: 16px;
}
.icon-item {
width: 60px;
height: 60px;
border: 2px solid var(--van-border-color);
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s;
}
.icon-item:hover {
border-color: var(--van-primary-color);
background-color: var(--van-primary-color-light);
}
.icon-item.active {
border-color: var(--van-primary-color);
background-color: var(--van-primary-color-light);
box-shadow: 0 2px 8px rgba(25, 137, 250, 0.3);
}
.icon-preview {
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
}
.icon-preview :deep(svg) {
width: 100%;
height: 100%;
fill: currentColor;
}
.empty-icons {
padding: 20px 0;
}
.icon-actions {
padding-top: 16px;
border-top: 1px solid var(--van-border-color);
display: flex;
justify-content: center;
}
/* 深色模式 */
/* @media (prefers-color-scheme: dark) {
.level-container {