Files
CN_Tool_client/frontend/src/views/home/components/FavoriteMenuCard.vue
2026-04-13 17:32:58 +08:00

283 lines
6.9 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<el-card shadow="hover" class="panel-card">
<template #header>
<div class="panel-header">
<div>
<h3 class="panel-title">收藏菜单</h3>
<p class="panel-subtitle">手动固定常用入口登录后可以直接从这里进入业务页面</p>
</div>
<el-tag round type="primary">{{ favoriteCount }}/{{ favoriteLimit }}</el-tag>
</div>
</template>
<div class="panel-body">
<div class="panel-toolbar">
<el-input
v-model.trim="searchKeyword"
clearable
class="search-input"
placeholder="搜索收藏菜单"
:disabled="!favorites.length"
/>
<div class="favorite-adder">
<el-select
v-model="selectedPath"
class="menu-select"
filterable
clearable
placeholder="添加快捷入口"
:disabled="favoriteCount >= favoriteLimit || !availableMenus.length"
>
<el-option v-for="item in availableMenus" :key="item.path" :label="item.title" :value="item.path" />
</el-select>
<el-button type="primary" :disabled="!selectedPath || favoriteCount >= favoriteLimit" @click="handleAdd">
添加
</el-button>
</div>
</div>
<div v-if="filteredFavorites.length" class="list-scroll">
<div class="menu-list">
<div v-for="item in filteredFavorites" :key="item.path" class="menu-item" @click="$emit('navigate', item.path)">
<div class="menu-main">
<span class="menu-icon">
<el-icon>
<component :is="item.icon"></component>
</el-icon>
</span>
<strong class="menu-title">{{ item.title }}</strong>
</div>
<el-button text type="danger" class="menu-action" @click.stop="$emit('remove', item.path)">移除</el-button>
</div>
</div>
</div>
<div v-else class="empty-box">
<el-empty :description="emptyDescription" :image-size="88" />
</div>
</div>
</el-card>
</template>
<script setup lang="ts">
import { computed, ref, toRefs } from 'vue'
import type { HomeMenuItem } from '@/utils/home'
const props = defineProps<{
favorites: HomeMenuItem[]
availableMenus: HomeMenuItem[]
favoriteCount: number
favoriteLimit: number
}>()
const { favorites, availableMenus, favoriteCount, favoriteLimit } = toRefs(props)
const emit = defineEmits<{
navigate: [path: string]
add: [path: string]
remove: [path: string]
}>()
const selectedPath = ref('')
const searchKeyword = ref('')
const filteredFavorites = computed(() => {
const keyword = searchKeyword.value.trim().toLowerCase()
if (!keyword) return props.favorites
return props.favorites.filter(item => {
const searchFields = [item.title, item.name]
return searchFields.some(field => field?.toLowerCase().includes(keyword))
})
})
const emptyDescription = computed(() => {
return searchKeyword.value.trim() ? '未找到匹配的收藏菜单' : '暂无收藏菜单'
})
const handleAdd = () => {
if (!selectedPath.value) return
const path = selectedPath.value
emit('add', path)
selectedPath.value = ''
}
</script>
<style scoped lang="scss">
.panel-card {
display: flex;
flex-direction: column;
height: 100%;
border: 0;
border-radius: 22px;
box-shadow: 0 18px 44px rgba(15, 23, 42, 0.08);
}
.panel-card :deep(.el-card__header) {
flex-shrink: 0;
}
.panel-card :deep(.el-card__body) {
display: flex;
flex: 1;
min-height: 0;
flex-direction: column;
}
.panel-header {
display: flex;
gap: 16px;
align-items: flex-start;
justify-content: space-between;
}
.panel-title {
margin: 0;
font-size: 20px;
color: #172033;
}
.panel-subtitle {
margin: 6px 0 0;
font-size: 13px;
color: #6b7280;
}
.panel-body {
display: flex;
flex: 1;
flex-direction: column;
gap: 16px;
min-height: 0;
}
.panel-toolbar {
display: grid;
grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr);
gap: 12px;
align-items: center;
}
.search-input {
min-width: 0;
}
.favorite-adder {
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
gap: 12px;
align-items: center;
min-width: 0;
}
.menu-select {
width: 100%;
}
.list-scroll {
--list-item-min-height: 74px;
flex: 1;
min-height: calc(var(--list-item-min-height) * 3 + 24px);
overflow-y: auto;
padding-right: 6px;
}
.menu-list {
display: grid;
align-content: start;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 12px;
}
.menu-item {
display: flex;
gap: 12px;
align-items: center;
justify-content: space-between;
min-height: var(--list-item-min-height);
padding: 12px 16px;
border: 1px solid #e7edf5;
border-radius: 16px;
background: linear-gradient(180deg, #ffffff 0%, #f8fbff 100%);
cursor: pointer;
transition:
transform 0.2s ease,
box-shadow 0.2s ease,
border-color 0.2s ease;
}
.menu-item:hover {
transform: translateY(-2px);
border-color: rgba(82, 106, 222, 0.35);
box-shadow: 0 14px 28px rgba(82, 106, 222, 0.12);
}
.menu-main {
display: flex;
gap: 12px;
align-items: center;
min-width: 0;
flex: 1;
}
.menu-icon {
display: inline-flex;
align-items: center;
justify-content: center;
width: 40px;
height: 40px;
font-size: 18px;
color: var(--el-color-primary);
border-radius: 14px;
background: rgba(82, 106, 222, 0.12);
flex-shrink: 0;
}
.menu-title {
overflow: hidden;
margin: 0;
color: #172033;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
line-height: 1.45;
}
.menu-action {
flex-shrink: 0;
padding-right: 0;
padding-left: 0;
}
.empty-box {
display: flex;
flex: 1;
align-items: center;
justify-content: center;
min-height: calc(74px * 3 + 24px);
border: 1px dashed #d8e0eb;
border-radius: 16px;
background: #fbfcfe;
}
@media (max-width: 1600px) {
.panel-toolbar {
grid-template-columns: 1fr;
}
}
@media (max-width: 768px) {
.favorite-adder,
.menu-list {
grid-template-columns: 1fr;
}
.list-scroll,
.empty-box {
min-height: auto;
}
}
</style>