Files
cz-web/src/views/home/cpns/HomeQuickEntries.vue
2026-10-08 17:26:33 +08:00

251 lines
5.4 KiB
Vue

<script setup lang="ts">
import { computed, ref } from 'vue'
import { storeToRefs } from 'pinia'
import { useRouter } from 'vue-router'
import { useHomeStore } from '@/stores/modules/home'
import type { PortalEntryCard } from '@/types/business/home'
import { toPortalEntryCards } from '@/utils/entryIcon'
import HomeAppDialog from './HomeAppDialog.vue'
import tileDemand from '@/assets/home/entry-demand.png'
import tileCases from '@/assets/home/entry-cases.png'
import tileService from '@/assets/home/entry-service.png'
import tileContract from '@/assets/home/entry-contract.png'
import tileFinance from '@/assets/home/entry-finance.png'
const homeStore = useHomeStore()
const { quickEntries } = storeToRefs(homeStore)
const router = useRouter()
const dialogVisible = ref(false)
const DESIGN_NAMES = [
'工单需求池',
'案例库管理',
'客服工作台',
'合同台账',
'财务台账',
]
const FALLBACK_QUICK: PortalEntryCard[] = [
{
id: 0,
label: '工单需求池',
route: '/demand',
groupType: '1',
icon: '',
isImage: false,
tone: 'cyan',
},
{
id: 0,
label: '案例库管理',
route: '/cases',
groupType: '1',
icon: '',
isImage: false,
tone: 'purple',
},
{
id: 0,
label: '客服工作台',
route: '/service',
groupType: '1',
icon: '',
isImage: false,
tone: 'green',
},
{
id: 0,
label: '合同台账',
route: '/contract',
groupType: '1',
icon: '',
isImage: false,
tone: 'blue',
},
{
id: 0,
label: '财务台账',
route: '/finance/bills',
groupType: '1',
icon: '',
isImage: false,
tone: 'amber',
},
]
const DESIGN_TILES: Record<string, string> = {
工单需求池: tileDemand,
案例库管理: tileCases,
客服工作台: tileService,
合同台账: tileContract,
财务台账: tileFinance,
}
const knownPaths = new Set(router.getRoutes().map((item) => item.path))
const entries = computed<PortalEntryCard[]>(() => {
const list = toPortalEntryCards(quickEntries.value).filter((card) =>
DESIGN_NAMES.includes(card.label),
)
return (list.length > 0 ? list : FALLBACK_QUICK).slice(0, 5)
})
function tileSrc(card: PortalEntryCard): string {
return card.isImage && card.icon
? card.icon
: (DESIGN_TILES[card.label] ?? '')
}
function resolvePath(card: PortalEntryCard): string {
if (card.route && knownPaths.has(card.route)) return card.route
const fallback = FALLBACK_QUICK.find((item) => item.label === card.label)
if (fallback && knownPaths.has(fallback.route)) return fallback.route
return '/'
}
function onMore(): void {
void homeStore.fetchPortalEntries()
dialogVisible.value = true
}
</script>
<template>
<section class="home-quick">
<div class="home-quick__head">
<h3 class="home-quick__title">快捷入口</h3>
</div>
<div class="home-quick__list">
<RouterLink
v-for="entry in entries"
:key="`${entry.label}-${entry.id}`"
:to="resolvePath(entry)"
class="home-quick__item"
>
<img
class="home-quick__tile"
:src="tileSrc(entry)"
:alt="entry.label"
/>
<span class="home-quick__label">{{ entry.label }}</span>
</RouterLink>
<button
class="home-quick__more"
type="button"
aria-label="更多应用"
@click="onMore"
>
<svg
width="32"
height="32"
viewBox="0 0 32 32"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M8 6 L18 16 L8 26"
stroke="currentColor"
stroke-width="3.5"
stroke-linecap="round"
stroke-linejoin="round"
fill="none"
/>
<path
d="M17 6 L27 16 L17 26"
stroke="currentColor"
stroke-width="3.5"
stroke-linecap="round"
stroke-linejoin="round"
fill="none"
/>
</svg>
</button>
</div>
<HomeAppDialog v-model:visible="dialogVisible" />
</section>
</template>
<style lang="less" scoped>
.home-quick {
position: relative;
.flex(column, flex-start, stretch);
flex-shrink: 0;
flex: 0 0 529px;
width: 529px;
height: 245px;
box-sizing: border-box;
min-width: 0;
gap: 0;
padding: 18px 40px 28px;
border: 1px solid @home-card-border;
border-radius: @radius-lg;
background: @white;
overflow: hidden;
&__head {
.flex(row, flex-start);
}
&__title {
font-size: @font-size-xxl;
font-weight: 700;
line-height: 52px;
color: @home-ink;
}
&__list {
.flex(row, flex-start, flex-start);
gap: 30px;
margin-top: 17px;
flex-wrap: wrap;
}
&__item {
.flex(column, flex-start, center);
gap: 8px;
flex: none;
transition: transform 0.2s;
&:hover {
transform: translateY(-2px);
}
}
&__tile {
display: block;
width: 97px;
height: 97px;
border-radius: @radius-base;
object-fit: cover;
}
&__label {
font-size: @font-size-lg;
font-weight: 500;
color: @home-ink;
text-align: center;
}
&__more {
position: absolute;
top: 120px;
right: 25px;
width: 32px;
height: 32px;
padding: 0;
border: 0;
background: transparent;
color: #c9cdd4;
cursor: pointer;
transition: color 0.2s;
&:hover {
color: @primary-color;
}
}
}
</style>