提交
This commit is contained in:
1 parent
47ef575524
commit
38383b35d9
92 files changed
+1051117
-37355
No files matched your search
@@ -191,9 +191,13 @@
|
||||
"189": "Community 189",
|
||||
"190": "ViewKvTableCell.vue",
|
||||
"191": "handleConfirmApproval",
|
||||
"192": "handleDisburse",
|
||||
"192": "index.vue",
|
||||
"193": "navigation.ts",
|
||||
"194": "canDeleteByCreator",
|
||||
"195": "Community 195",
|
||||
"196": "Community 196",
|
||||
"197": "v2.4.2.1 - 2025-06-16",
|
||||
"198": "index.vue",
|
||||
"200": "index.js",
|
||||
"201": "index.vue",
|
||||
"202": "Community 202",
|
||||
@@ -202,7 +206,6 @@
|
||||
"208": "Community 208",
|
||||
"209": "Community 209",
|
||||
"210": "Community 210",
|
||||
"211": "v2.4.2.1 - 2025-06-16",
|
||||
"212": "index.js",
|
||||
"213": "Community 213",
|
||||
"214": "Community 214",
|
||||
|
||||
@@ -191,11 +191,8 @@
|
||||
"189": "Community 189",
|
||||
"190": "ViewKvTableCell.vue",
|
||||
"191": "handleConfirmApproval",
|
||||
"192": "handleDisburse",
|
||||
"194": "handlePublish",
|
||||
"195": "Community 195",
|
||||
"196": "Community 196",
|
||||
"199": "index.vue",
|
||||
"200": "index.js",
|
||||
"201": "index.vue",
|
||||
"202": "Community 202",
|
||||
@@ -204,7 +201,6 @@
|
||||
"208": "Community 208",
|
||||
"209": "Community 209",
|
||||
"210": "Community 210",
|
||||
"211": "v2.4.2.1 - 2025-06-16",
|
||||
"212": "index.js",
|
||||
"213": "Community 213",
|
||||
"214": "Community 214",
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
+1717
-1293
File diff suppressed because it is too large.
Load diff
@@ -7,6 +7,8 @@ export interface PortalHomeCardItem {
|
||||
path: string
|
||||
title: string
|
||||
icon?: string
|
||||
/** 与侧栏 meta.menuCode 一致,用于待办数字角标 */
|
||||
menuCode?: string
|
||||
}
|
||||
|
||||
export interface PortalHomeSection {
|
||||
@@ -64,10 +66,12 @@ function buildPinyinMeta(title: string): { py: string; abbr: string; letter: str
|
||||
function toCard(item: AppRouteRecord): PortalHomeCardItem | null {
|
||||
const path = String(item.meta?.activePath || item.path || '')
|
||||
if (!path) return null
|
||||
const menuCode = String(item.meta?.menuCode || '').trim()
|
||||
return {
|
||||
path,
|
||||
title: routeTitle(item),
|
||||
icon: item.meta?.icon ? String(item.meta.icon) : undefined
|
||||
icon: item.meta?.icon ? String(item.meta.icon) : undefined,
|
||||
menuCode: menuCode || undefined
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -51,6 +51,13 @@
|
||||
class="portal-home-card portal-home-card--fav"
|
||||
@click="onFavCardClick(card.path)"
|
||||
>
|
||||
<span
|
||||
v-if="pendingCountFor(card) > 0"
|
||||
class="portal-home-card__pending"
|
||||
:aria-label="`${pendingCountFor(card)} 条待审批`"
|
||||
>
|
||||
{{ formatPendingCount(pendingCountFor(card)) }}
|
||||
</span>
|
||||
<span class="portal-home-card__badge portal-home-card__badge--fav" aria-hidden="true">
|
||||
{{ titleAbbr(card.title) }}
|
||||
</span>
|
||||
@@ -80,6 +87,13 @@
|
||||
class="portal-home-card"
|
||||
@click="openCard(card.path)"
|
||||
>
|
||||
<span
|
||||
v-if="pendingCountFor(card) > 0"
|
||||
class="portal-home-card__pending"
|
||||
:aria-label="`${pendingCountFor(card)} 条待审批`"
|
||||
>
|
||||
{{ formatPendingCount(pendingCountFor(card)) }}
|
||||
</span>
|
||||
<span
|
||||
class="portal-home-card__badge"
|
||||
:data-tone="toneIndex(activeModule)"
|
||||
@@ -115,13 +129,19 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
import { computed, onActivated, onMounted, ref, watch } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { Star, StarFilled } from '@element-plus/icons-vue'
|
||||
import { VueDraggable } from 'vue-draggable-plus'
|
||||
import { listQuote } from '@/api/crm/quote'
|
||||
import { listContract } from '@/api/crm/contract'
|
||||
import { MENU_BADGE_CODES, MENU_BADGE_CODE_SET } from '@/constants/menu-badge-codes'
|
||||
import { useMenuStore } from '@/store/modules/menu'
|
||||
import { useMenuBadgeStore } from '@/store/modules/menu-badge'
|
||||
import { useMenuFavoriteStore } from '@/store/modules/menuFavorite'
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
import { refreshMenuBadges } from '@/utils/menu-badge-registry'
|
||||
import {
|
||||
collectPortalHomeSections,
|
||||
findHomeCardByPath,
|
||||
@@ -131,17 +151,38 @@
|
||||
defineOptions({ name: 'PortalHome' })
|
||||
|
||||
const FAV_KEY = '__favorites__'
|
||||
/** CRM 报价/合同审批:侧栏角标体系尚未覆盖,首页单独拉取 */
|
||||
const PENDING_CARD_RULES: Array<{
|
||||
key: 'quote' | 'contract'
|
||||
pathIncludes: string
|
||||
titles: string[]
|
||||
}> = [
|
||||
{ key: 'quote', pathIncludes: 'quote-approval', titles: ['报价单审批'] },
|
||||
{ key: 'contract', pathIncludes: 'contract-approval', titles: ['合同审批'] }
|
||||
]
|
||||
/** 标题兜底 → 侧栏 menuCode(与 menu-badge-registry 一致) */
|
||||
const TITLE_BADGE_CODE: Record<string, string> = {
|
||||
备用金审批: MENU_BADGE_CODES.BX_ADVANCE_APPROVE,
|
||||
备用金发放: MENU_BADGE_CODES.BX_ADVANCE_ISSUE,
|
||||
报销审批: MENU_BADGE_CODES.BX_REIMBURSE_APPROVE,
|
||||
支付发放冲抵: MENU_BADGE_CODES.BX_PAYMENT,
|
||||
待我审批: MENU_BADGE_CODES.BX_VIEW_TODO
|
||||
}
|
||||
|
||||
const router = useRouter()
|
||||
const menuStore = useMenuStore()
|
||||
const badgeStore = useMenuBadgeStore()
|
||||
const favStore = useMenuFavoriteStore()
|
||||
const userStore = useUserStore()
|
||||
const { menuList } = storeToRefs(menuStore)
|
||||
const { items: favItems } = storeToRefs(favStore)
|
||||
const { counts: badgeCounts, loaded: badgeLoaded } = storeToRefs(badgeStore)
|
||||
|
||||
const activeModule = ref(FAV_KEY)
|
||||
const favoriteList = ref<PortalHomeCardItem[]>([])
|
||||
const favDragging = ref(false)
|
||||
const suppressFavClick = ref(false)
|
||||
const pendingCounts = ref({ quote: 0, contract: 0 })
|
||||
|
||||
const modules = computed(() => collectPortalHomeSections(menuList.value || []))
|
||||
|
||||
@@ -207,6 +248,71 @@
|
||||
)
|
||||
}
|
||||
|
||||
const resolveCurrentUserId = (): string => {
|
||||
const info = (userStore.info || {}) as { id?: string | number; userId?: string | number }
|
||||
return String(info.id || info.userId || '')
|
||||
}
|
||||
|
||||
const resolveBadgeMenuCode = (card: PortalHomeCardItem) => {
|
||||
const code = String(card.menuCode || '').trim()
|
||||
if (code && MENU_BADGE_CODE_SET.has(code)) return code
|
||||
const title = String(card.title || '').trim()
|
||||
const byTitle = TITLE_BADGE_CODE[title]
|
||||
if (byTitle) return byTitle
|
||||
return ''
|
||||
}
|
||||
|
||||
const matchPendingRule = (card: PortalHomeCardItem) => {
|
||||
const path = String(card.path || '')
|
||||
const title = String(card.title || '').trim()
|
||||
return PENDING_CARD_RULES.find(
|
||||
(r) => path.includes(r.pathIncludes) || r.titles.includes(title)
|
||||
)
|
||||
}
|
||||
|
||||
const pendingCountFor = (card: PortalHomeCardItem) => {
|
||||
// 依赖 badgeCounts / badgeLoaded,保证角标刷新后视图更新
|
||||
void badgeCounts.value
|
||||
void badgeLoaded.value
|
||||
const badgeCode = resolveBadgeMenuCode(card)
|
||||
if (badgeCode) return badgeStore.getCount(badgeCode)
|
||||
const rule = matchPendingRule(card)
|
||||
if (!rule) return 0
|
||||
return Number(pendingCounts.value[rule.key] || 0)
|
||||
}
|
||||
|
||||
const formatPendingCount = (n: number) => (n > 99 ? '99+' : String(n))
|
||||
|
||||
const loadCrmPendingCounts = async () => {
|
||||
const userId = resolveCurrentUserId()
|
||||
if (!userId) {
|
||||
pendingCounts.value = { quote: 0, contract: 0 }
|
||||
return
|
||||
}
|
||||
const [quoteRes, contractRes] = await Promise.all([
|
||||
listQuote({
|
||||
pageNum: 1,
|
||||
pageSize: 1,
|
||||
status: 'submitted',
|
||||
pendingApprovalUserId: userId
|
||||
}).catch(() => ({ total: 0 })),
|
||||
listContract({
|
||||
pageNum: 1,
|
||||
pageSize: 1,
|
||||
approvalStatus: 'pending',
|
||||
pendingApprovalUserId: userId
|
||||
}).catch(() => ({ total: 0 }))
|
||||
])
|
||||
pendingCounts.value = {
|
||||
quote: Number(quoteRes.total || 0),
|
||||
contract: Number(contractRes.total || 0)
|
||||
}
|
||||
}
|
||||
|
||||
const loadPendingCounts = async () => {
|
||||
await Promise.all([refreshMenuBadges(menuList.value), loadCrmPendingCounts()])
|
||||
}
|
||||
|
||||
watch(modules, ensureActiveModule, { immediate: true, deep: true })
|
||||
watch([favItems, modules], syncFavoriteList, { immediate: true, deep: true })
|
||||
|
||||
@@ -214,6 +320,11 @@
|
||||
if (!favStore.loaded) {
|
||||
await favStore.loadFavorites()
|
||||
}
|
||||
await loadPendingCounts()
|
||||
})
|
||||
|
||||
onActivated(() => {
|
||||
void loadPendingCounts()
|
||||
})
|
||||
|
||||
const openCard = (path: string) => {
|
||||
@@ -391,10 +502,6 @@
|
||||
|
||||
&--fav {
|
||||
margin-top: 14px;
|
||||
padding: 12px 12px 14px;
|
||||
background: linear-gradient(180deg, #ecfdf5 0%, #fff 72%);
|
||||
border: 1px solid #bbf7d0;
|
||||
border-radius: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -434,7 +541,7 @@
|
||||
.portal-home-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(168px, 1fr));
|
||||
gap: 10px;
|
||||
gap: 16px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
@@ -444,7 +551,7 @@
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
min-height: 64px;
|
||||
padding: 12px 34px 12px 12px;
|
||||
padding: 12px 34px 28px 12px;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
background: #fff;
|
||||
@@ -573,10 +680,32 @@
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.portal-home-card__star {
|
||||
.portal-home-card__pending {
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
right: 6px;
|
||||
z-index: 1;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 18px;
|
||||
height: 18px;
|
||||
padding: 0 5px;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
font-variant-numeric: tabular-nums;
|
||||
line-height: 1;
|
||||
color: #fff;
|
||||
pointer-events: none;
|
||||
background: #dc2626;
|
||||
border-radius: 9px;
|
||||
box-shadow: 0 1px 2px rgba(220, 38, 38, 0.25);
|
||||
}
|
||||
|
||||
.portal-home-card__star {
|
||||
position: absolute;
|
||||
right: 6px;
|
||||
bottom: 6px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
Reference in new issue
Block a user