提交
This commit is contained in:
1 parent
9345cf4ad8
commit
c3bd2c00ae
51 files changed
+14486
-15765
No files matched your search
@@ -184,33 +184,47 @@
|
||||
"182": "Community 182",
|
||||
"183": "Ha",
|
||||
"184": "highlight.ts",
|
||||
"185": "handleConfirmApproval",
|
||||
"186": "StorageKeyManager",
|
||||
"187": "Community 187",
|
||||
"188": "Community 188",
|
||||
"189": "Community 189",
|
||||
"190": "error-handle.ts",
|
||||
"191": "fetchDesignerData",
|
||||
"193": "loadDefaultCustomerOptions",
|
||||
"191": "formatYmd",
|
||||
"192": "loadDefaultCustomerOptions",
|
||||
"193": "fetchData",
|
||||
"194": "fetchData",
|
||||
"195": "Community 195",
|
||||
"196": "Community 196",
|
||||
"197": "handleEdit",
|
||||
"198": "fetchData",
|
||||
"199": "fetchData",
|
||||
"200": "index.js",
|
||||
"201": "fetchCustomerDetail",
|
||||
"202": "Community 202",
|
||||
"203": "menuFavorite.ts",
|
||||
"204": "fetchData",
|
||||
"205": "index.vue",
|
||||
"206": "price-letter.ts",
|
||||
"207": "emit",
|
||||
"208": "handleSearch",
|
||||
"209": "Community 209",
|
||||
"210": "Community 210",
|
||||
"211": "loadUserOptions",
|
||||
"212": "index.js",
|
||||
"213": "Community 213",
|
||||
"214": "Community 214",
|
||||
"215": "handleDelete",
|
||||
"216": "handleSearch",
|
||||
"217": "index.js",
|
||||
"218": "index.js",
|
||||
"219": "handleAdd",
|
||||
"220": "index.vue",
|
||||
"221": "zh_CN.js",
|
||||
"221": "getInventoryStatCellValue",
|
||||
"222": "handleEdit",
|
||||
"223": "Community 223",
|
||||
"224": "Community 224",
|
||||
"225": "getPlMaintenanceApplyStatusLabel",
|
||||
"225": "zh_CN.js",
|
||||
"226": "form.vue",
|
||||
"227": "Community 227",
|
||||
"228": "Community 228",
|
||||
@@ -297,7 +311,6 @@
|
||||
"405": "zh_CN.js",
|
||||
"406": "Community 406",
|
||||
"408": "Community 408",
|
||||
"409": "DbCrmCustomerSelect.vue",
|
||||
"410": "Community 410",
|
||||
"412": "Community 412",
|
||||
"416": "Community 416",
|
||||
|
||||
@@ -1 +1 @@
|
||||
/Users/wuzilong/work/00code/shanxia2.0/web-admin
|
||||
.
|
||||
@@ -184,39 +184,35 @@
|
||||
"182": "Community 182",
|
||||
"183": "Ha",
|
||||
"184": "highlight.ts",
|
||||
"185": "initLineBrandMetaFromProduct",
|
||||
"185": "handleConfirmApproval",
|
||||
"186": "StorageKeyManager",
|
||||
"187": "Community 187",
|
||||
"188": "Community 188",
|
||||
"189": "Community 189",
|
||||
"190": "error-handle.ts",
|
||||
"191": "fetchDesignerData",
|
||||
"192": "handleOpen",
|
||||
"193": "loadDefaultCustomerOptions",
|
||||
"192": "loadDefaultCustomerOptions",
|
||||
"194": "fetchData",
|
||||
"195": "Community 195",
|
||||
"196": "Community 196",
|
||||
"197": "handleEdit",
|
||||
"198": "fetchCustomerDetail",
|
||||
"199": "fetchData",
|
||||
"200": "index.js",
|
||||
"201": "fetchData",
|
||||
"201": "fetchCustomerDetail",
|
||||
"202": "Community 202",
|
||||
"208": "menuFavorite.ts",
|
||||
"204": "fetchData",
|
||||
"206": "price-letter.ts",
|
||||
"208": "handleSearch",
|
||||
"209": "Community 209",
|
||||
"210": "Community 210",
|
||||
"211": "loadUserOptions",
|
||||
"212": "index.js",
|
||||
"213": "Community 213",
|
||||
"214": "Community 214",
|
||||
"216": "loadUserOptions",
|
||||
"217": "index.js",
|
||||
"218": "index.js",
|
||||
"220": "index.vue",
|
||||
"221": "zh_CN.js",
|
||||
"223": "Community 223",
|
||||
"224": "Community 224",
|
||||
"225": "getPlMaintenanceApplyStatusLabel",
|
||||
"226": "form.vue",
|
||||
"227": "Community 227",
|
||||
"228": "Community 228",
|
||||
@@ -303,7 +299,6 @@
|
||||
"405": "zh_CN.js",
|
||||
"406": "Community 406",
|
||||
"408": "Community 408",
|
||||
"409": "DbCrmCustomerSelect.vue",
|
||||
"410": "Community 410",
|
||||
"412": "Community 412",
|
||||
"416": "Community 416",
|
||||
@@ -505,7 +500,6 @@
|
||||
"1223": "emojiimages.min.js",
|
||||
"1224": "emojis.js",
|
||||
"1225": "emojis.min.js",
|
||||
"1226": "index.js",
|
||||
"1228": "ar.js",
|
||||
"1229": "bg_BG.js",
|
||||
"1230": "ca.js",
|
||||
|
||||
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
File diff suppressed because it is too large.
Load diff
+2410
-2155
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -115,7 +115,9 @@ export interface PlanListParams {
|
||||
executionStatus?: string
|
||||
createdFrom?: string
|
||||
createdTo?: string
|
||||
/** 筛选区间开始:与计划有效期(起始~截止)有交集 */
|
||||
startDateFrom?: string
|
||||
/** 筛选区间结束:与计划有效期(起始~截止)有交集 */
|
||||
startDateTo?: string
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,8 @@ export interface DutySlotItem {
|
||||
shiftLabel?: string
|
||||
capacity: number
|
||||
taken?: number
|
||||
/** 班次工作安排 */
|
||||
remark?: string | null
|
||||
assignments: DutyAssignmentItem[]
|
||||
}
|
||||
|
||||
@@ -47,6 +49,7 @@ export function saveDutyDay(data: {
|
||||
slots: Array<{
|
||||
shiftType: ShiftType
|
||||
capacity: number
|
||||
remark?: string | null
|
||||
assignments: DutyAssignmentItem[]
|
||||
}>
|
||||
linkStatus?: LinkStatus
|
||||
|
||||
@@ -12,6 +12,7 @@ export interface PublicDutySlot {
|
||||
shiftLabel?: string
|
||||
capacity: number
|
||||
taken: number
|
||||
remark?: string | null
|
||||
assignments: Array<{ id?: string; source: string; personName: string; userId?: string }>
|
||||
}
|
||||
|
||||
|
||||
@@ -405,10 +405,17 @@
|
||||
loadBxAttachments
|
||||
} from '@/utils/bx/attachment'
|
||||
import { invoicesToLinkedAttachmentFiles } from '@/utils/bx/invoice-link-files'
|
||||
import { matchesApplyDateRange, type ApplyDateRange } from '@/utils/bx/apply-date-filter'
|
||||
import { buildApplyDateRangeQuery, type ApplyDateRange } from '@/utils/bx/apply-date-filter'
|
||||
import { usePagedTable } from '@/hooks/bx/usePagedTable'
|
||||
import { useApplicantOptions } from '@/hooks/bx/useApplicantOptions'
|
||||
|
||||
const STATUS_LABEL_TO_CODE: Record<string, string> = {
|
||||
待提交: 'draft',
|
||||
审批中: 'pending',
|
||||
审批通过: 'approved',
|
||||
审批驳回: 'rejected'
|
||||
}
|
||||
|
||||
type DialogKey = 'viewDetail'
|
||||
|
||||
const loading = ref(false)
|
||||
@@ -464,7 +471,7 @@
|
||||
const filterForm = reactive({
|
||||
code: '',
|
||||
bizType: '' as string,
|
||||
applicantId: undefined as number | undefined,
|
||||
applicantId: undefined as string | number | undefined,
|
||||
status: '' as string,
|
||||
applyDateRange: null as ApplyDateRange,
|
||||
amountRange: '',
|
||||
@@ -474,7 +481,7 @@
|
||||
const appliedFilter = reactive({
|
||||
code: '',
|
||||
bizType: '' as string,
|
||||
applicantId: undefined as number | undefined,
|
||||
applicantId: undefined as string | number | undefined,
|
||||
status: '' as string,
|
||||
applyDateRange: null as ApplyDateRange,
|
||||
amountRange: '',
|
||||
@@ -485,6 +492,7 @@
|
||||
Object.assign(appliedFilter, { ...filterForm })
|
||||
}
|
||||
|
||||
/** 接口已支持的条件在 loadData 中下发;此处仅处理合并列表后的本地条件 */
|
||||
function filterMergedRows(rows: any[]) {
|
||||
let list = [...rows]
|
||||
const quickKw = appliedQuickSearch.value.trim().toLowerCase()
|
||||
@@ -502,32 +510,11 @@
|
||||
.includes(quickKw)
|
||||
)
|
||||
}
|
||||
const code = appliedFilter.code.trim()
|
||||
if (code) list = list.filter((r: any) => String(r.applyCode || '').includes(code))
|
||||
if (appliedFilter.bizType && appliedFilter.bizType !== '全部') {
|
||||
list = list.filter((r: any) => r.bizType === appliedFilter.bizType)
|
||||
}
|
||||
if (appliedFilter.applicantId != null) {
|
||||
list = list.filter((r: any) => Number(r.applicantId) === Number(appliedFilter.applicantId))
|
||||
}
|
||||
if (appliedFilter.applyDateRange?.[0] || appliedFilter.applyDateRange?.[1]) {
|
||||
list = list.filter((r: any) =>
|
||||
matchesApplyDateRange(r.applyTime, appliedFilter.applyDateRange)
|
||||
)
|
||||
}
|
||||
if (appliedFilter.status && appliedFilter.status !== '全部') {
|
||||
if (appliedFilter.status === '已发放') {
|
||||
list = list.filter((r: any) => ['issued', 'paid', 'offset'].includes(r.status))
|
||||
} else {
|
||||
const map: Record<string, string> = {
|
||||
待提交: 'draft',
|
||||
审批中: 'pending',
|
||||
审批通过: 'approved',
|
||||
审批驳回: 'rejected'
|
||||
}
|
||||
const want = map[appliedFilter.status]
|
||||
if (want) list = list.filter((r: any) => r.status === want)
|
||||
}
|
||||
if (appliedFilter.status === '已发放') {
|
||||
list = list.filter((r: any) => ['issued', 'paid', 'offset'].includes(r.status))
|
||||
}
|
||||
if (appliedFilter.currentNode && appliedFilter.currentNode !== '全部') {
|
||||
list = list.filter((r: any) => (r.currentNode || '') === appliedFilter.currentNode)
|
||||
@@ -548,6 +535,21 @@
|
||||
return list
|
||||
}
|
||||
|
||||
function buildApiQuery() {
|
||||
const q: Record<string, string | number> = {}
|
||||
const code = appliedFilter.code.trim()
|
||||
if (code) q.keyword = code
|
||||
if (appliedFilter.applicantId != null && appliedFilter.applicantId !== '') {
|
||||
q.applicantId = String(appliedFilter.applicantId)
|
||||
}
|
||||
if (appliedFilter.status && appliedFilter.status !== '全部' && appliedFilter.status !== '已发放') {
|
||||
const statusCode = STATUS_LABEL_TO_CODE[appliedFilter.status]
|
||||
if (statusCode) q.status = statusCode
|
||||
}
|
||||
buildApplyDateRangeQuery(appliedFilter.applyDateRange, q)
|
||||
return q
|
||||
}
|
||||
|
||||
const filteredMergedRows = computed(() => filterMergedRows(mergedRows.value))
|
||||
|
||||
const pager = usePagedTable(() => filteredMergedRows.value, { initialPageSize: 20 })
|
||||
@@ -641,29 +643,44 @@
|
||||
'已发放'
|
||||
]
|
||||
|
||||
function mapAdvanceRow(r: any) {
|
||||
return {
|
||||
...r,
|
||||
bizType: '备用金',
|
||||
title: r.reason,
|
||||
applyCode: r.applyCode || r.code,
|
||||
applicantName: r.applicantName || r.user,
|
||||
applicantId: r.applicantId
|
||||
}
|
||||
}
|
||||
|
||||
function mapReimburseRow(r: any) {
|
||||
return {
|
||||
...r,
|
||||
bizType: '报销单',
|
||||
title: r.title,
|
||||
applyCode: r.applyCode || r.code,
|
||||
applicantName: r.applicantName || r.user,
|
||||
applicantId: r.applicantId
|
||||
}
|
||||
}
|
||||
|
||||
async function loadData() {
|
||||
loading.value = true
|
||||
try {
|
||||
const [advRes, reimRes]: any[] = await Promise.all([
|
||||
advanceApi.list({ page: 1, pageSize: 999 }),
|
||||
reimburseApi.list({ page: 1, pageSize: 999 })
|
||||
const listParams = { page: 1, pageSize: 9999, ...buildApiQuery() }
|
||||
const [advRes, reimRes] = await Promise.allSettled([
|
||||
advanceApi.list(listParams),
|
||||
reimburseApi.list(listParams)
|
||||
])
|
||||
const advRows = (advRes.data?.rows || []).map((r: any) => ({
|
||||
...r,
|
||||
bizType: '备用金',
|
||||
title: r.reason,
|
||||
applyCode: r.applyCode || r.code,
|
||||
applicantName: r.applicantName || r.user,
|
||||
applicantId: r.applicantId
|
||||
}))
|
||||
const reimRows = (reimRes.data?.rows || []).map((r: any) => ({
|
||||
...r,
|
||||
bizType: '报销单',
|
||||
title: r.title,
|
||||
applyCode: r.applyCode || r.code,
|
||||
applicantName: r.applicantName || r.user,
|
||||
applicantId: r.applicantId
|
||||
}))
|
||||
const advRows =
|
||||
advRes.status === 'fulfilled'
|
||||
? ((advRes.value as any).data?.rows || []).map(mapAdvanceRow)
|
||||
: []
|
||||
const reimRows =
|
||||
reimRes.status === 'fulfilled'
|
||||
? ((reimRes.value as any).data?.rows || []).map(mapReimburseRow)
|
||||
: []
|
||||
mergedRows.value = [...advRows, ...reimRows]
|
||||
} catch {
|
||||
// error handled by interceptor
|
||||
@@ -677,6 +694,7 @@
|
||||
appliedQuickSearch.value = ''
|
||||
syncAppliedFilter()
|
||||
pager.currentPage.value = 1
|
||||
loadData()
|
||||
}
|
||||
|
||||
useDbInstantSearch(
|
||||
@@ -714,6 +732,7 @@
|
||||
currentNode: ''
|
||||
})
|
||||
pager.currentPage.value = 1
|
||||
loadData()
|
||||
}
|
||||
|
||||
function handleQuickSearch() {
|
||||
|
||||
@@ -758,7 +758,7 @@
|
||||
|
||||
const handleDateRangeChange = () => {
|
||||
activeDateShortcut.value = ''
|
||||
currentPage.value = 1
|
||||
handleSearch()
|
||||
}
|
||||
|
||||
const resetFormData = () => {
|
||||
|
||||
@@ -110,6 +110,7 @@
|
||||
:key="ci"
|
||||
class="chip"
|
||||
:class="chip.cls"
|
||||
:title="chip.title || chip.text"
|
||||
>
|
||||
{{ chip.text }}
|
||||
</span>
|
||||
@@ -179,7 +180,7 @@
|
||||
v-model="dialogVisible"
|
||||
class="dialog-form-layout duty-day-dialog"
|
||||
:title="`配置 · ${editingDate}`"
|
||||
width="640px"
|
||||
width="880px"
|
||||
align-center
|
||||
destroy-on-close
|
||||
:close-on-click-modal="false"
|
||||
@@ -198,23 +199,35 @@
|
||||
</div>
|
||||
</div>
|
||||
<template v-if="slot.enabled">
|
||||
<el-form-item label="直接安排(值班人员)">
|
||||
<el-select
|
||||
v-model="slot.pendingUserId"
|
||||
filterable
|
||||
clearable
|
||||
placeholder="选择员工后添加"
|
||||
style="width: 100%"
|
||||
@change="(v) => addAssign(slot, String(v || ''))"
|
||||
>
|
||||
<el-option
|
||||
v-for="u in userOptions"
|
||||
:key="u.value"
|
||||
:label="u.label"
|
||||
:value="u.value"
|
||||
<div class="form-grid-2 slot-assign-row">
|
||||
<el-form-item label="值班安排">
|
||||
<el-select
|
||||
v-model="slot.pendingUserId"
|
||||
filterable
|
||||
clearable
|
||||
placeholder="选择员工后添加"
|
||||
style="width: 100%"
|
||||
@change="(v) => addAssign(slot, String(v || ''))"
|
||||
>
|
||||
<el-option
|
||||
v-for="u in userOptions"
|
||||
:key="u.value"
|
||||
:label="u.label"
|
||||
:value="u.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="工作安排">
|
||||
<el-input
|
||||
v-model="slot.remark"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
maxlength="500"
|
||||
show-word-limit
|
||||
placeholder="可选,填写本班次工作安排"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div class="person-tags">
|
||||
<el-tag
|
||||
v-for="(a, ai) in slot.assignments"
|
||||
@@ -320,6 +333,7 @@
|
||||
shiftType: ShiftType
|
||||
enabled: boolean
|
||||
capacity: number
|
||||
remark: string
|
||||
pendingUserId: string
|
||||
assignments: DutySlotItem['assignments']
|
||||
}
|
||||
@@ -329,6 +343,7 @@
|
||||
shiftType: t,
|
||||
enabled: false,
|
||||
capacity: 1,
|
||||
remark: '',
|
||||
pendingUserId: '',
|
||||
assignments: []
|
||||
}))
|
||||
@@ -473,7 +488,7 @@
|
||||
dateStr: string
|
||||
isToday: boolean
|
||||
summary?: DutyDayItem
|
||||
chips: Array<{ text: string; cls: string }>
|
||||
chips: Array<{ text: string; cls: string; title?: string }>
|
||||
}> = []
|
||||
|
||||
const prevDays = new Date(y, m - 1, 0).getDate()
|
||||
@@ -484,7 +499,7 @@
|
||||
for (let d = 1; d <= daysInMonth; d++) {
|
||||
const dateStr = toDateStr(y, m, d)
|
||||
const summary = dayMap.value.get(dateStr)
|
||||
const chips: Array<{ text: string; cls: string }> = []
|
||||
const chips: Array<{ text: string; cls: string; title?: string }> = []
|
||||
if (summary?.slots?.length) {
|
||||
for (const s of summary.slots) {
|
||||
if (shiftFilter && s.shiftType !== shiftFilter) continue
|
||||
@@ -494,6 +509,7 @@
|
||||
if (filterKey && !matched.length) continue
|
||||
const names = matched.map((a) => a.personName).filter(Boolean)
|
||||
const taken = s.taken ?? (s.assignments || []).length
|
||||
const remark = (s.remark || '').trim()
|
||||
let text: string
|
||||
if (filterKey) {
|
||||
text = `${s.shiftLabel || SHIFT_LABELS[s.shiftType]} ${names.join('、')}`
|
||||
@@ -502,7 +518,14 @@
|
||||
} else {
|
||||
text = `${s.shiftLabel || SHIFT_LABELS[s.shiftType]} ${taken}/${s.capacity}`
|
||||
}
|
||||
chips.push({ text, cls: SHIFT_CLS[s.shiftType] || 'am' })
|
||||
if (remark) {
|
||||
text = `${text} · ${remark}`
|
||||
}
|
||||
chips.push({
|
||||
text,
|
||||
cls: SHIFT_CLS[s.shiftType] || 'am',
|
||||
title: remark || undefined
|
||||
})
|
||||
}
|
||||
if (!hasFilter && !chips.length) {
|
||||
chips.push({ text: '未排班 · 点击配置', cls: 'empty' })
|
||||
@@ -598,6 +621,7 @@
|
||||
const found = detail?.slots?.find((s) => s.shiftType === fs.shiftType)
|
||||
fs.enabled = !!found
|
||||
fs.capacity = found?.capacity || 1
|
||||
fs.remark = (found?.remark || '').trim()
|
||||
fs.pendingUserId = ''
|
||||
fs.assignments = found ? [...(found.assignments || [])] : []
|
||||
}
|
||||
@@ -665,6 +689,7 @@
|
||||
.map((s) => ({
|
||||
shiftType: s.shiftType,
|
||||
capacity: s.capacity,
|
||||
remark: s.remark.trim() || null,
|
||||
assignments: s.assignments
|
||||
.filter((a) => a.source === 'assign')
|
||||
.map((a) => ({
|
||||
@@ -1147,6 +1172,12 @@
|
||||
justify-content: space-between;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.slot-assign-row {
|
||||
margin-bottom: 4px;
|
||||
:deep(.el-form-item) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
.cap-field {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
|
||||
@@ -11,6 +11,15 @@
|
||||
<span>收藏</span>
|
||||
<span class="n">{{ favoriteList.length }}</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="portal-home-aside__item"
|
||||
:class="{ 'is-on': isTodoView }"
|
||||
@click="activeModule = TODO_KEY"
|
||||
>
|
||||
<span>待办列表</span>
|
||||
<span class="n">{{ formatPendingCount(todoTotalSum) }}</span>
|
||||
</button>
|
||||
<button
|
||||
v-for="mod in modules"
|
||||
:key="mod.modulePath"
|
||||
@@ -77,6 +86,44 @@
|
||||
<p v-else class="portal-home-empty">暂无收藏,可在各模块菜单卡片上点星标添加</p>
|
||||
</section>
|
||||
|
||||
<section v-else-if="isTodoView" class="portal-home-block portal-home-block--flat">
|
||||
<div v-if="todoList.length" class="portal-home-grid">
|
||||
<button
|
||||
v-for="card in todoList"
|
||||
:key="'todo-' + card.path"
|
||||
type="button"
|
||||
class="portal-home-card"
|
||||
@click="openCard(card.path)"
|
||||
>
|
||||
<span
|
||||
class="portal-home-card__pending"
|
||||
:aria-label="`${pendingCountFor(card)} 条待审批`"
|
||||
>
|
||||
{{ formatPendingCount(pendingCountFor(card)) }}
|
||||
</span>
|
||||
<span class="portal-home-card__badge" aria-hidden="true">
|
||||
{{ titleAbbr(card.title) }}
|
||||
</span>
|
||||
<span class="portal-home-card__body">
|
||||
<span class="portal-home-card__title">{{ card.title }}</span>
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
class="portal-home-card__star"
|
||||
:class="{ 'is-on': favoritePathSet.has(card.path) }"
|
||||
:aria-label="favoritePathSet.has(card.path) ? '取消收藏' : '加入收藏'"
|
||||
@click.stop="onToggleStar(card.path)"
|
||||
>
|
||||
<ElIcon>
|
||||
<StarFilled v-if="favoritePathSet.has(card.path)" />
|
||||
<Star v-else />
|
||||
</ElIcon>
|
||||
</button>
|
||||
</button>
|
||||
</div>
|
||||
<p v-else class="portal-home-empty">暂无待办</p>
|
||||
</section>
|
||||
|
||||
<template v-else>
|
||||
<section
|
||||
v-for="(group, gIdx) in activeGroups"
|
||||
@@ -156,6 +203,7 @@
|
||||
defineOptions({ name: 'PortalHome' })
|
||||
|
||||
const FAV_KEY = '__favorites__'
|
||||
const TODO_KEY = '__todos__'
|
||||
/** CRM 报价/合同审批:侧栏角标体系尚未覆盖,首页单独拉取 */
|
||||
const PENDING_CARD_RULES: Array<{
|
||||
key: 'quote' | 'contract'
|
||||
@@ -194,9 +242,10 @@
|
||||
const favoritePathSet = computed(() => new Set(favItems.value.map((i) => i.path)))
|
||||
|
||||
const isFavView = computed(() => activeModule.value === FAV_KEY)
|
||||
const isTodoView = computed(() => activeModule.value === TODO_KEY)
|
||||
|
||||
const activeSection = computed(() => {
|
||||
if (isFavView.value) return undefined
|
||||
if (isFavView.value || isTodoView.value) return undefined
|
||||
return modules.value.find((m) => m.modulePath === activeModule.value)
|
||||
})
|
||||
|
||||
@@ -204,48 +253,6 @@
|
||||
|
||||
const activeCards = computed(() => activeSection.value?.cards || [])
|
||||
|
||||
const panelTitle = computed(() => {
|
||||
if (isFavView.value) return '收藏'
|
||||
return activeSection.value?.moduleTitle || ''
|
||||
})
|
||||
|
||||
const panelDesc = computed(() =>
|
||||
isFavView.value ? '可拖拽调整顺序,星标取消收藏' : '按二级菜单分组展示,点击卡片进入功能'
|
||||
)
|
||||
|
||||
const panelCount = computed(() =>
|
||||
isFavView.value ? favoriteList.value.length : activeCards.value.length
|
||||
)
|
||||
|
||||
const titleAbbr = (title: string) => {
|
||||
const t = String(title || '').trim()
|
||||
if (!t) return '?'
|
||||
return t.slice(0, 1)
|
||||
}
|
||||
|
||||
const ensureActiveModule = () => {
|
||||
if (activeModule.value === FAV_KEY) return
|
||||
const list = modules.value
|
||||
if (!list.length) {
|
||||
activeModule.value = FAV_KEY
|
||||
return
|
||||
}
|
||||
if (!list.some((m) => m.modulePath === activeModule.value)) {
|
||||
activeModule.value = FAV_KEY
|
||||
}
|
||||
}
|
||||
|
||||
const syncFavoriteList = () => {
|
||||
favoriteList.value = favItems.value.map(
|
||||
(i) => findHomeCardByPath(modules.value, i.path) || { path: i.path, title: i.path }
|
||||
)
|
||||
}
|
||||
|
||||
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
|
||||
@@ -276,6 +283,84 @@
|
||||
|
||||
const formatPendingCount = (n: number) => (n > 99 ? '99+' : String(n))
|
||||
|
||||
const allHomeCards = computed(() => {
|
||||
const seen = new Set<string>()
|
||||
const out: PortalHomeCardItem[] = []
|
||||
for (const sec of modules.value) {
|
||||
for (const card of sec.cards) {
|
||||
if (seen.has(card.path)) continue
|
||||
seen.add(card.path)
|
||||
out.push(card)
|
||||
}
|
||||
}
|
||||
return out
|
||||
})
|
||||
|
||||
const todoEntries = computed(() => {
|
||||
void badgeCounts.value
|
||||
void badgeLoaded.value
|
||||
void pendingCounts.value
|
||||
return allHomeCards.value
|
||||
.map((card) => ({ card, count: pendingCountFor(card) }))
|
||||
.filter((x) => x.count > 0)
|
||||
.sort(
|
||||
(a, b) => b.count - a.count || String(a.card.title).localeCompare(String(b.card.title), 'zh')
|
||||
)
|
||||
})
|
||||
|
||||
const todoList = computed(() => todoEntries.value.map((x) => x.card))
|
||||
|
||||
const todoTotalSum = computed(() =>
|
||||
todoEntries.value.reduce((sum, x) => sum + x.count, 0)
|
||||
)
|
||||
|
||||
const panelTitle = computed(() => {
|
||||
if (isFavView.value) return '收藏'
|
||||
if (isTodoView.value) return '待办列表'
|
||||
return activeSection.value?.moduleTitle || ''
|
||||
})
|
||||
|
||||
const panelDesc = computed(() => {
|
||||
if (isFavView.value) return '可拖拽调整顺序,星标取消收藏'
|
||||
if (isTodoView.value) return '汇总当前有待办的菜单,按数量从多到少排列'
|
||||
return '按二级菜单分组展示,点击卡片进入功能'
|
||||
})
|
||||
|
||||
const panelCount = computed(() => {
|
||||
if (isFavView.value) return favoriteList.value.length
|
||||
if (isTodoView.value) return todoList.value.length
|
||||
return activeCards.value.length
|
||||
})
|
||||
|
||||
const titleAbbr = (title: string) => {
|
||||
const t = String(title || '').trim()
|
||||
if (!t) return '?'
|
||||
return t.slice(0, 1)
|
||||
}
|
||||
|
||||
const ensureActiveModule = () => {
|
||||
if (activeModule.value === FAV_KEY || activeModule.value === TODO_KEY) return
|
||||
const list = modules.value
|
||||
if (!list.length) {
|
||||
activeModule.value = FAV_KEY
|
||||
return
|
||||
}
|
||||
if (!list.some((m) => m.modulePath === activeModule.value)) {
|
||||
activeModule.value = FAV_KEY
|
||||
}
|
||||
}
|
||||
|
||||
const syncFavoriteList = () => {
|
||||
favoriteList.value = favItems.value.map(
|
||||
(i) => findHomeCardByPath(modules.value, i.path) || { path: i.path, title: i.path }
|
||||
)
|
||||
}
|
||||
|
||||
const resolveCurrentUserId = (): string => {
|
||||
const info = (userStore.info || {}) as { id?: string | number; userId?: string | number }
|
||||
return String(info.id || info.userId || '')
|
||||
}
|
||||
|
||||
const loadCrmPendingCounts = async () => {
|
||||
const userId = resolveCurrentUserId()
|
||||
if (!userId) {
|
||||
|
||||
@@ -59,6 +59,9 @@
|
||||
<span>名额</span>
|
||||
<strong>{{ slot.taken }} / {{ slot.capacity }}</strong>
|
||||
</div>
|
||||
<p v-if="slot.remark" class="slot-remark">
|
||||
工作安排:<strong>{{ slot.remark }}</strong>
|
||||
</p>
|
||||
<div class="bar"><i :style="{ width: barWidth(slot) }" :class="slot.shiftType"></i></div>
|
||||
<div class="people">
|
||||
<span v-for="(a, i) in slot.assignments" :key="i" class="person">{{ a.personName }}</span>
|
||||
@@ -376,6 +379,18 @@
|
||||
font-size: 15px;
|
||||
color: #1f2a26;
|
||||
}
|
||||
.slot-remark {
|
||||
margin: 8px 0 0;
|
||||
font-size: 13px;
|
||||
line-height: 1.45;
|
||||
color: #6b7b74;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
.slot-remark strong {
|
||||
font-weight: 600;
|
||||
color: #1f2a26;
|
||||
}
|
||||
.bar {
|
||||
height: 7px;
|
||||
margin-top: 8px;
|
||||
|
||||
Reference in new issue
Block a user