251 lines
5.4 KiB
Vue
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>
|