| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267 |
- <template>
- <div class="page-fill approvals-view">
- <PagePanel :title="pageTitle">
- <p class="page-tip">
- <template v-if="isHistoryTab && isTownReview">
- 已审记录按月名单表展示。通过为「新增」(等 15 号纳入月发),无资格为异常待处理,补材料退回「待提交资格」。可导出纸质新增名单。
- </template>
- <template v-else-if="isHistoryTab && isLeaderReview">
- 已办结的本镇月度清单与特殊业务审批。清单通过后社区事务可出盘;驳回后退回社区事务继续办理。
- </template>
- <template v-else-if="isHistoryTab">
- 已办结审批,可按条件筛选查看,不可再次审批。
- </template>
- <template v-else-if="isTownReview">
- 按月名单表审批本次新增人员。村级随时加入,镇经发随时通过或驳回。资格以村级勾选为准,不改已有在册名单。通过后为「新增」,等 15 号纳入月发。
- </template>
- <template v-else-if="isLeaderReview">
- 审本镇月度清单(镇统计)与特殊业务。通过后社区事务可出盘。提交到本节点前,本镇当月特殊业务须全部通过。
- </template>
- <template v-else>
- 管理员可按街镇协查:镇经发审退养资格;镇社区受理中心分管领导审本镇月度清单与特殊业务。日常审批由对应镇办理。
- </template>
- </p>
- <ListPageToolbar
- v-model:keyword="filterBatchNo"
- keyword-placeholder="请输入批次号查询"
- :show-keyword="!isTownReview"
- :actions="toolbarActions"
- @search="applyFilter"
- @refresh="resetFilter"
- >
- <template #filters>
- <el-form label-position="top" size="default">
- <el-form-item v-if="isAdmin" label="街镇">
- <el-select
- v-model="filterTown"
- placeholder="全部"
- clearable
- style="width: 100%"
- @change="onTownChange"
- >
- <el-option v-for="town in townOptions" :key="town" :label="town" :value="town" />
- </el-select>
- </el-form-item>
- <el-form-item label="村居">
- <el-select v-model="filterVillage" placeholder="全部" clearable style="width: 100%">
- <el-option v-for="v in villageOptions" :key="v" :label="v" :value="v" />
- </el-select>
- </el-form-item>
- <el-form-item v-if="isTownReview" label="名单月份">
- <el-date-picker
- v-model="filterMonth"
- type="month"
- value-format="YYYY-MM"
- format="YYYY年M月"
- style="width: 100%"
- />
- </el-form-item>
- <el-form-item v-if="isHistoryTab" label="状态">
- <el-select v-model="filterStatus" placeholder="全部" clearable style="width: 100%">
- <el-option label="已通过" value="已通过" />
- <el-option label="已驳回" value="已驳回" />
- </el-select>
- </el-form-item>
- </el-form>
- </template>
- </ListPageToolbar>
- <div class="style-a-tabs" role="tablist">
- <button
- type="button"
- class="style-a-tab"
- :class="{ 'is-active': activeTab === 'pending' }"
- @click="switchTab('pending')"
- >
- {{ isTownReview ? '待资格审批' : '待审批' }}
- </button>
- <button
- type="button"
- class="style-a-tab"
- :class="{ 'is-active': activeTab === 'history' }"
- @click="switchTab('history')"
- >
- {{ isTownReview ? '已审记录' : '历史记录' }}
- </button>
- </div>
- <div v-if="isTownReview" class="approvals-view__paper">
- <div class="approvals-view__paper-title">{{ paperFormTitle }}</div>
- <div class="approvals-view__paper-meta">
- <span class="approvals-view__paper-month">{{ paperMonthText }}</span>
- <span class="approvals-view__paper-unit">
- <SensitiveRevealSwitch v-model="revealSensitive" />
- <span>单位:元</span>
- </span>
- </div>
- <el-table
- v-loading="loading"
- :data="pagedList"
- border
- stripe
- size="small"
- @selection-change="onSelectionChange"
- >
- <el-table-column v-if="!isHistoryTab" type="selection" width="36" align="center" />
- <el-table-column label="序号" width="48" align="center">
- <template #default="{ $index }">{{ (page - 1) * pageSize + $index + 1 }}</template>
- </el-table-column>
- <el-table-column
- label="姓名"
- width="96"
- class-name="approvals-view__col-name"
- label-class-name="approvals-view__col-name"
- >
- <template #default="{ row }">
- <PersonNameCell
- :name="(row as Approval).personName"
- :death-warning="(row as Approval).deathWarning"
- :death-status="(row as Approval).deathStatus"
- />
- </template>
- </el-table-column>
- <el-table-column label="身份证号码"
- width="196"
- class-name="approvals-view__col-id"
- label-class-name="approvals-view__col-id"
- >
- <template #default="{ row }">
- <SensitiveValue
- :key="`q-id-${(row as Approval).id}`"
- :row-key="(row as Approval).id"
- :value="(row as Approval).idCard"
- kind="idCard"
- :revealed="revealSensitive"
- />
- </template>
- </el-table-column>
- <el-table-column label="户籍性质(农/非)" width="96" align="center">
- <template #default="{ row }">{{ paperHousehold((row as Approval).householdType) }}</template>
- </el-table-column>
- <el-table-column
- label="村集体经济合作社"
- width="136"
- show-overflow-tooltip
- class-name="approvals-view__col-coop"
- label-class-name="approvals-view__col-coop"
- >
- <template #default="{ row }">{{ (row as Approval).village || '' }}</template>
- </el-table-column>
- <el-table-column label="享受起始年月" width="108" align="center">
- <template #default="{ row }">{{ paperStartMonth((row as Approval).startMonth) }}</template>
- </el-table-column>
- <el-table-column label="享受标准" width="80" align="center">
- <template #default="{ row }">{{ paperStandard((row as Approval).standard) }}</template>
- </el-table-column>
- <el-table-column label="居保匹配" width="108" show-overflow-tooltip>
- <template #default="{ row }">
- <span :class="insuranceFail(row as Approval) ? 'approvals-view__ins-fail' : 'approvals-view__ins-ok'">
- {{ (row as Approval).insuranceStatus || '未匹配' }}
- </span>
- </template>
- </el-table-column>
- <el-table-column
- label="备注"
- min-width="120"
- class-name="approvals-view__col-remark"
- label-class-name="approvals-view__col-remark"
- >
- <template #default="{ row }">{{ (row as Approval).remark || '' }}</template>
- </el-table-column>
- <el-table-column label="新增时间" width="150" align="center" show-overflow-tooltip>
- <template #default="{ row }">{{ (row as Approval).addedAt || '' }}</template>
- </el-table-column>
- <el-table-column v-if="isHistoryTab" label="审批结果" width="88" align="center">
- <template #default="{ row }">
- <StatusTag :status="row.status" />
- </template>
- </el-table-column>
- <el-table-column label="操作" width="168" align="center" fixed="right" class-name="approvals-view__ops">
- <template #default="{ row }">
- <el-button link type="primary" size="small" @click="openDetail(row as Approval)">
- 详情
- </el-button>
- <el-button
- v-if="row.status === '待审批'"
- link
- type="success"
- size="small"
- @click="onDecide(row as Approval, true)"
- >
- 通过
- </el-button>
- <el-button
- v-if="row.status === '待审批'"
- link
- type="danger"
- size="small"
- @click="onDecide(row as Approval, false)"
- >
- 驳回
- </el-button>
- </template>
- </el-table-column>
- </el-table>
- <div class="approvals-view__paper-sign">
- <div>
- <p>经办单位(盖章):(镇经济发展服务中心)</p>
- <p>负责人签名:</p>
- <p>日期:</p>
- </div>
- <div>
- <p>审核单位(盖章):(镇社区事务受理服务中心)</p>
- <p>负责人签名:</p>
- <p>日期:</p>
- </div>
- </div>
- </div>
- <div v-else class="page-fill__table">
- <el-table
- v-loading="loading"
- :data="pagedList"
- border
- stripe
- size="small"
- height="100%"
- @selection-change="onSelectionChange"
- >
- <el-table-column v-if="!isHistoryTab" type="selection" width="42" align="center" />
- <el-table-column label="业务类型" width="130">
- <template #default="{ row }">{{ displayBizText((row as Approval).bizType) }}</template>
- </el-table-column>
- <el-table-column prop="title" label="标题" min-width="200" show-overflow-tooltip />
- <el-table-column v-if="!isTownReview" prop="batchNo" label="批次号" width="88" align="center" />
- <el-table-column v-if="!isTownReview || isAdmin" prop="town" label="街镇" width="90" />
- <el-table-column prop="village" label="村居" width="90" />
- <el-table-column label="人数" width="64" align="center">
- <template #default="{ row }">
- {{
- isSpecialApproval(row as Approval)
- ? (row as Approval).peopleCount ?? 1
- : (row as Approval).peopleCount != null
- ? (row as Approval).peopleCount
- : '—'
- }}
- </template>
- </el-table-column>
- <el-table-column v-if="!isTownReview" label="总发放金额" width="120" align="right">
- <template #default="{ row }">
- {{
- (row as Approval).totalAmount != null
- ? formatAmount((row as Approval).totalAmount)
- : '—'
- }}
- </template>
- </el-table-column>
- <el-table-column prop="applicant" label="申请人" width="140" show-overflow-tooltip />
- <el-table-column prop="node" label="审批节点" width="140" />
- <el-table-column label="状态" width="88" align="center">
- <template #default="{ row }">
- <StatusTag :status="row.status" />
- </template>
- </el-table-column>
- <el-table-column label="提交时间" width="160">
- <template #default="{ row }">
- {{ (row as Approval).createdAt || '—' }}
- </template>
- </el-table-column>
- <el-table-column label="操作" width="260" align="center" fixed="right">
- <template #default="{ row }">
- <el-button link type="primary" size="small" @click="openDetail(row as Approval)">
- 详情
- </el-button>
- <el-button
- v-if="batchIdOfApproval(row as Approval)"
- link
- type="primary"
- size="small"
- @click="openBatchFlow(row as Approval)"
- >
- 流程记录
- </el-button>
- <el-button
- v-if="row.status === '待审批'"
- link
- type="success"
- size="small"
- :disabled="isRowPassBlocked(row as Approval)"
- :title="rowPassBlockReason(row as Approval) || undefined"
- @click="onDecide(row as Approval, true)"
- >
- 通过
- </el-button>
- <el-button
- v-if="row.status === '待审批'"
- link
- type="danger"
- size="small"
- @click="onDecide(row as Approval, false)"
- >
- 驳回
- </el-button>
- </template>
- </el-table-column>
- </el-table>
- </div>
- <TablePagination v-model:page="page" v-model:page-size="pageSize" :total="total" />
- </PagePanel>
- <BatchDetailDrawer
- v-model="batchDrawerVisible"
- :batch-id="batchDrawerId"
- initial-tab="flow"
- />
- <!-- 审批详情:含本村人员列表 -->
- <el-dialog
- v-model="detailVisible"
- :title="detailDialogTitle"
- :width="isTownReview ? '1180px' : '1120px'"
- destroy-on-close
- @closed="onDetailClosed"
- >
- <el-descriptions v-if="currentRow" :column="2" border size="small">
- <el-descriptions-item label="业务类型">{{ displayBizText(currentRow.bizType) }}</el-descriptions-item>
- <el-descriptions-item label="审批节点">{{ currentRow.node }}</el-descriptions-item>
- <el-descriptions-item label="标题" :span="2">{{ currentRow.title }}</el-descriptions-item>
- <el-descriptions-item v-if="!isQualifyApproval(currentRow)" label="批次号">{{ currentRow.batchNo }}</el-descriptions-item>
- <el-descriptions-item label="状态">
- <StatusTag :status="currentRow.status" />
- </el-descriptions-item>
- <el-descriptions-item label="街镇">{{ currentRow.town }}</el-descriptions-item>
- <el-descriptions-item label="村居">{{ currentRow.village || '-' }}</el-descriptions-item>
- <el-descriptions-item
- v-if="!isQualifyApproval(currentRow) || (currentRow.applicant && currentRow.applicant !== currentRow.personName)"
- label="申请人"
- >
- {{ currentRow.applicant }}
- </el-descriptions-item>
- <el-descriptions-item label="提交时间">{{ currentRow.createdAt }}</el-descriptions-item>
- <el-descriptions-item v-if="!isSpecialApproval(currentRow) && !isQualifyApproval(currentRow)" label="总发放金额" :span="2">
- <strong>{{ formatAmount(detailBatchAmountSummary.payable) }}</strong>
- <span class="muted" style="margin-left: 8px">
- <template v-if="isAdjustDetailBatch">(调标差额批:应发=补发 {{ formatAmount(detailBatchAmountSummary.supplement) }})</template>
- <template v-else>
- (标准 {{ formatAmount(detailBatchAmountSummary.monthly) }}
- + 补发 {{ formatAmount(detailBatchAmountSummary.supplement) }}
- + 丧葬 {{ formatAmount(detailBatchAmountSummary.funeral) }}
- - 扣减 {{ formatAmount(detailBatchAmountSummary.deduct) }})
- </template>
- </span>
- </el-descriptions-item>
- <el-descriptions-item v-else-if="currentRow.amount != null" label="涉及金额" :span="2">
- {{ formatAmount(currentRow.amount) }}
- </el-descriptions-item>
- <el-descriptions-item label="审批意见" :span="2">
- {{ currentRow.status === '待审批' ? '-' : (currentRow.opinion || '-') }}
- </el-descriptions-item>
- <template v-if="isSpecialApproval(currentRow) || isQualifyApproval(currentRow)">
- <el-descriptions-item
- v-if="isAdjustSpecialApproval(currentRow)"
- label="调标金额变化"
- :span="2"
- >
- {{ adjustSpecialSummary(currentRow) }}
- </el-descriptions-item>
- <el-descriptions-item v-if="currentRow.personName && !isQualifyApproval(currentRow)" label="人员">
- {{ currentRow.personName }}
- </el-descriptions-item>
- <el-descriptions-item v-if="currentRow.deathDate" label="死亡日期">
- {{ currentRow.deathDate }}
- </el-descriptions-item>
- <el-descriptions-item label="申请说明" :span="2">
- {{ currentRow.reason || '-' }}
- </el-descriptions-item>
- <el-descriptions-item label="证明材料" :span="2">
- <FlowAttachments :file-urls="currentRow.fileUrls" show-empty />
- </el-descriptions-item>
- </template>
- </el-descriptions>
- <p v-if="isQualifyApproval(currentRow)" class="approvals-view__reject-tip" style="margin-top: 12px">
- 退养资格:只审本次新增。通过后人员状态为「新增」,等每月 15 号纳入全区月发;驳回补材料退回村合作社。
- </p>
- <p v-else-if="isSpecialApproval(currentRow)" class="approvals-view__reject-tip" style="margin-top: 12px">
- 特殊业务:通过后系统按业务类型回写人员状态(死亡登记通过后改为「死亡丧葬」并停止续发);驳回后退回业务受理补充材料。
- </p>
- <!-- 特殊业务:涉及人员(通常 1 人) -->
- <div v-if="isSpecialApproval(currentRow) || isQualifyApproval(currentRow)" class="approvals-view__people">
- <div class="approvals-view__people-toolbar">
- <div class="approvals-view__people-title">
- {{ isQualifyApproval(currentRow) ? '申请人员' : '涉及人员' }}
- <em>共 {{ detailPeople.length || (currentRow?.peopleCount ?? 0) }} 人</em>
- </div>
- <SensitiveRevealSwitch v-model="revealSensitive" />
- </div>
- <el-table
- v-loading="peopleLoading"
- :data="detailPeople"
- border
- stripe
- size="small"
- max-height="240"
- >
- <el-table-column label="姓名" width="140">
- <template #default="{ row }">
- <PersonNameCell :person="row" />
- </template>
- </el-table-column>
- <el-table-column label="身份证" min-width="168">
- <template #default="{ row }">
- <SensitiveValue
- :key="`sp-id-${row.id}`"
- :row-key="row.id"
- :value="row.idCard"
- kind="idCard"
- :revealed="revealSensitive"
- />
- </template>
- </el-table-column>
- <el-table-column prop="town" label="街镇" width="90" />
- <el-table-column prop="village" label="村居" width="90" />
- <el-table-column label="标准(元/月)" width="100" align="right">
- <template #default="{ row }">{{ formatAmount(row.standard) }}</template>
- </el-table-column>
- <el-table-column label="人员状态" width="110" align="center">
- <template #default="{ row }">
- <StatusTag :status="row.status" />
- </template>
- </el-table-column>
- <el-table-column label="操作" width="72" align="center" fixed="right">
- <template #default="{ row }">
- <el-button
- link
- type="primary"
- size="small"
- @click="openPersonDetail(row as Person)"
- >
- 详情
- </el-button>
- </template>
- </el-table-column>
- </el-table>
- <el-empty
- v-if="!peopleLoading && !detailPeople.length"
- description="未关联到在册人员,请核对申请说明与证明材料"
- :image-size="64"
- />
- </div>
- <div v-else class="approvals-view__people">
- <el-alert
- v-if="detailPassBlockAlert"
- :title="detailPassBlockAlert"
- type="warning"
- show-icon
- :closable="false"
- class="approvals-view__block-alert"
- />
- <div class="approvals-view__people-toolbar">
- <div class="approvals-view__people-title">
- {{ isTownReview ? '村合作社提交名单' : '本镇人员列表' }}
- <span v-if="currentRow?.village">
- ({{ currentRow.town }}{{ currentRow.village }} · 批次 {{ currentRow.batchNo }})
- </span>
- <em>共 {{ detailPeopleTotal || detailPeople.length }} 人</em>
- <em class="approvals-view__people-amount">
- ,总发放 {{ formatAmount(detailBatchAmountSummary.payable) }} 元
- </em>
- <em v-if="detailStatusFilter || detailSearchName || detailSearchIdCard">
- ,当前筛选 {{ detailPeopleTotal }} 人
- </em>
- </div>
- <div class="approvals-view__people-search">
- <el-input
- v-model="detailSearchName"
- placeholder="姓名"
- clearable
- size="small"
- style="width: 120px"
- @keyup.enter="onDetailSearch"
- />
- <el-input
- v-model="detailSearchIdCard"
- placeholder="身份证(可后几位)"
- clearable
- size="small"
- style="width: 160px"
- @keyup.enter="onDetailSearch"
- />
- <el-select
- v-model="detailAmountSort"
- clearable
- placeholder="金额排序"
- size="small"
- style="width: 140px"
- @change="onDetailSearch"
- >
- <el-option label="总计金额↓" value="totalAmount:desc" />
- <el-option label="总计金额↑" value="totalAmount:asc" />
- <el-option label="补发金额↓" value="supplementAmount:desc" />
- <el-option label="丧葬金额↓" value="funeralAmount:desc" />
- </el-select>
- <el-select
- v-model="detailAmountFilter"
- clearable
- placeholder="金额筛选"
- size="small"
- style="width: 120px"
- @change="onDetailSearch"
- >
- <el-option label="仅有补发" value="supplement" />
- <el-option label="仅有丧葬" value="funeral" />
- </el-select>
- <el-button size="small" type="primary" @click="onDetailSearch">查询</el-button>
- <el-button size="small" @click="resetDetailSearch">重置</el-button>
- <SensitiveRevealSwitch v-model="revealSensitive" />
- </div>
- <el-select
- v-model="detailStatusFilter"
- clearable
- filterable
- placeholder="人员状态"
- size="small"
- class="approvals-view__status-filter"
- @change="resetDetailPeoplePage"
- >
- <el-option
- v-for="s in detailStatusOptions"
- :key="s"
- :label="s"
- :value="s"
- />
- </el-select>
- </div>
- <el-table
- v-loading="peopleLoading"
- :data="pagedDetailPeople"
- border
- stripe
- size="small"
- max-height="320"
- show-summary
- :summary-method="detailTableSummary"
- :row-class-name="detailPeopleRowClass"
- >
- <el-table-column prop="name" label="姓名" width="168">
- <template #default="{ row }">
- <PersonNameCell :person="row">
- <el-tag
- v-if="(row as BatchMemberPerson).changeFlag === 'NEW'"
- size="small"
- type="success"
- effect="plain"
- >
- 新增
- </el-tag>
- <el-tag
- v-else-if="(row as BatchMemberPerson).changeFlag === 'REMOVE'"
- size="small"
- type="danger"
- effect="plain"
- >
- 减少
- </el-tag>
- </PersonNameCell>
- </template>
- </el-table-column>
- <el-table-column label="身份证" min-width="168">
- <template #default="{ row }">
- <SensitiveValue
- :key="`id-${row.id}`"
- :row-key="row.id"
- :value="row.idCard"
- kind="idCard"
- :revealed="revealSensitive"
- />
- </template>
- </el-table-column>
- <el-table-column label="银行卡" min-width="160">
- <template #default="{ row }">
- <SensitiveValue
- :key="`bank-${row.id}`"
- :row-key="row.id"
- :value="row.bankCard"
- kind="bankCard"
- :revealed="revealSensitive"
- />
- </template>
- </el-table-column>
- <el-table-column v-if="!isAdjustDetailBatch" label="标准金额" width="100" align="right">
- <template #default="{ row }">
- {{ formatAmount((row as BatchMemberPerson).monthlyAmount ?? row.standard) }}
- </template>
- </el-table-column>
- <el-table-column label="补发金额" width="96" align="right">
- <template #default="{ row }">
- {{ formatAmount((row as BatchMemberPerson).supplementAmount) }}
- </template>
- </el-table-column>
- <el-table-column v-if="!isAdjustDetailBatch" label="丧葬金额" width="96" align="right">
- <template #default="{ row }">
- {{ formatAmount((row as BatchMemberPerson).funeralAmount) }}
- </template>
- </el-table-column>
- <el-table-column v-if="!isAdjustDetailBatch" label="扣减" width="88" align="right">
- <template #default="{ row }">
- {{ formatAmount((row as BatchMemberPerson).deductAmount) }}
- </template>
- </el-table-column>
- <el-table-column label="总计金额" width="104" align="right">
- <template #default="{ row }">
- <strong>{{ formatAmount((row as BatchMemberPerson).payableAmount) }}</strong>
- </template>
- </el-table-column>
- <el-table-column v-if="isTownReview" label="退养资格" width="110" align="center">
- <template #default="{ row }">
- <el-tag
- v-if="row.retireQualifyType"
- size="small"
- :type="qualifyTagType(row.retireQualifyType)"
- effect="plain"
- >
- {{ retireQualifyLabel(row.retireQualifyType) }}
- </el-tag>
- <span v-else class="muted">待核验</span>
- </template>
- </el-table-column>
- <el-table-column label="人员状态" width="100" align="center">
- <template #default="{ row }">
- <StatusTag :status="row.status" />
- </template>
- </el-table-column>
- <el-table-column
- label="操作"
- :width="isTownReview ? 168 : 72"
- align="center"
- fixed="right"
- >
- <template #default="{ row }">
- <el-button
- link
- type="primary"
- size="small"
- @click="openPersonDetail(row as Person)"
- >
- 详情
- </el-button>
- <template v-if="isTownReview">
- <span
- v-if="row.retireQualifyType === '无资格' || row.status === '无资格'"
- class="muted"
- >
- 无资格
- </span>
- <span
- v-else-if="row.retireQualifyType"
- class="muted"
- >
- 村级已填
- </span>
- </template>
- </template>
- </el-table-column>
- </el-table>
- <TablePagination
- v-model:page="detailPeoplePage"
- v-model:page-size="detailPeoplePageSize"
- :total="detailPeopleTotal"
- />
- <el-empty
- v-if="!peopleLoading && !detailPeople.length"
- description="暂无待审批人员"
- :image-size="64"
- />
- <el-empty
- v-else-if="!peopleLoading && detailPeople.length && !filteredDetailPeople.length"
- description="当前筛选条件下无人员"
- :image-size="64"
- />
- </div>
- <template v-if="currentRow?.status === '待审批'" #footer>
- <el-button @click="detailVisible = false">关闭</el-button>
- <el-button type="danger" :loading="deciding" @click="onDecide(currentRow!, false)">
- 驳回
- </el-button>
- <el-button
- type="primary"
- :loading="deciding"
- :disabled="detailPassBlocked"
- :title="detailPassBlockedReason || undefined"
- @click="onDecide(currentRow!, true)"
- >
- 通过
- </el-button>
- </template>
- <template v-else #footer>
- <el-button type="primary" @click="detailVisible = false">关闭</el-button>
- </template>
- </el-dialog>
- <!-- 通过确认(可附意见与附件) -->
- <el-dialog
- v-model="passVisible"
- title="确认通过"
- width="520px"
- destroy-on-close
- :close-on-click-modal="!deciding"
- :close-on-press-escape="!deciding"
- @closed="onPassDialogClosed"
- >
- <p class="approvals-view__reject-tip" style="color: var(--el-text-color-secondary)">
- {{ passTip }}
- </p>
- <el-form label-width="90px" size="small">
- <el-form-item label="审批意见">
- <el-input
- v-model="passOpinion"
- type="textarea"
- :rows="3"
- maxlength="200"
- show-word-limit
- placeholder="可选,默认「同意」"
- class="is-editable"
- />
- </el-form-item>
- <el-form-item label="审批附件">
- <el-upload
- v-model:file-list="passFileList"
- :auto-upload="false"
- :limit="5"
- accept="image/*,.pdf,.doc,.docx,.xls,.xlsx"
- multiple
- @change="syncPassFiles"
- @remove="syncPassFiles"
- >
- <el-button type="primary">选择文件</el-button>
- <template #tip>
- <div class="el-upload__tip">可上传审批依据图片或文件,将写入流程留痕</div>
- </template>
- </el-upload>
- </el-form-item>
- </el-form>
- <template #footer>
- <el-button :disabled="deciding" @click="passVisible = false">取消</el-button>
- <el-button type="primary" :loading="deciding" :disabled="deciding" @click="confirmPass">确认通过</el-button>
- </template>
- </el-dialog>
- <!-- 驳回理由(必填,可附附件) -->
- <el-dialog
- v-model="rejectVisible"
- title="填写驳回理由"
- width="520px"
- destroy-on-close
- :close-on-click-modal="!deciding"
- :close-on-press-escape="!deciding"
- @closed="onRejectDialogClosed"
- >
- <p class="approvals-view__reject-tip">
- <template v-if="isQualifyRejectTarget">
- 驳回后退回村合作社「待提交资格」,补充材料后可重新提交。
- </template>
- <template v-else-if="isTownReview && ineligibleMembers.length">
- 本批有 {{ ineligibleMembers.length }} 人已认定「无资格」({{
- ineligibleMembers.map((p) => p.name).join('、')
- }})。驳回后将退回村合作社:请取消该人员或流程备注说明后重新提交。
- </template>
- <template v-else-if="isTownReview">
- 驳回后资格申请退回村合作社补材料或认定无资格。
- </template>
- <template v-else-if="isLeaderReview">
- 月度清单驳回后退回社区事务继续办理;特殊业务驳回后退回业务受理补充/修改。请写明原因。
- </template>
- <template v-else>
- 驳回后将退回修改,请明确说明退回原因。
- </template>
- </p>
- <el-form label-width="90px" size="small">
- <el-form-item label="驳回理由" required>
- <el-input
- v-model="rejectOpinion"
- type="textarea"
- :rows="4"
- maxlength="200"
- show-word-limit
- placeholder="例如:材料不完整 / 人员信息有误 / 不符合政策规定…"
- class="is-editable"
- />
- </el-form-item>
- <el-form-item label="审批附件">
- <el-upload
- v-model:file-list="rejectFileList"
- :auto-upload="false"
- :limit="5"
- accept="image/*,.pdf,.doc,.docx,.xls,.xlsx"
- multiple
- @change="syncRejectFiles"
- @remove="syncRejectFiles"
- >
- <el-button type="primary">选择文件</el-button>
- <template #tip>
- <div class="el-upload__tip">可上传驳回依据图片或文件,将写入流程留痕</div>
- </template>
- </el-upload>
- </el-form-item>
- </el-form>
- <template #footer>
- <el-button :disabled="deciding" @click="rejectVisible = false">取消</el-button>
- <el-button type="danger" :loading="deciding" :disabled="deciding" @click="confirmReject">确认驳回</el-button>
- </template>
- </el-dialog>
- <!-- 镇经发:核验退养资格 -->
- <el-dialog v-model="qualifyVisible" title="核验退养资格" width="520px" destroy-on-close>
- <p class="approvals-view__qualify-tip">
- 请确认该人员是否具备土地退养资格。选择「有土地退养」或「无土地有资格」后通过,人员变为「新增」,等每月 15 号纳入全区月发。认定「无资格」则进入异常待处理。
- </p>
- <el-descriptions v-if="qualifyTarget" :column="1" border size="small" class="approvals-view__qualify-info">
- <el-descriptions-item label="姓名">{{ qualifyTarget.name }}</el-descriptions-item>
- <el-descriptions-item label="身份证">
- <SensitiveValue :value="qualifyTarget.idCard" kind="idCard" />
- </el-descriptions-item>
- <el-descriptions-item label="村居">
- {{ qualifyTarget.town }}{{ qualifyTarget.village }}
- </el-descriptions-item>
- </el-descriptions>
- <el-form label-width="100px" size="small" class="approvals-view__qualify-form">
- <el-form-item label="资格认定" required>
- <el-radio-group v-model="qualifyType">
- <el-radio value="有土地退养">有土地退养</el-radio>
- <el-radio value="无土地有资格">无土地有资格</el-radio>
- <el-radio value="无资格">无资格</el-radio>
- </el-radio-group>
- </el-form-item>
- </el-form>
- <p v-if="qualifyType === '无资格'" class="approvals-view__qualify-warn">
- 认定无资格后,人员进入异常待处理,不再纳入每月 15 号全区月发。
- </p>
- <template #footer>
- <el-button :disabled="reviewingId !== null" @click="qualifyVisible = false">取消</el-button>
- <el-button type="primary" :loading="reviewingId !== null" :disabled="reviewingId !== null" @click="confirmQualify">
- 确认核验
- </el-button>
- </template>
- </el-dialog>
- <el-dialog
- v-model="personDetailVisible"
- :title="personDetail ? `人员详情 · ${personDetail.name}` : '人员详情'"
- width="920px"
- append-to-body
- destroy-on-close
- @closed="onPersonDetailClosed"
- >
- <div v-loading="personDetailLoading">
- <PersonnelDetailDescriptions v-if="personDetail" :person="personDetail" />
- </div>
- <template #footer>
- <el-button type="primary" @click="personDetailVisible = false">关闭</el-button>
- </template>
- </el-dialog>
- </div>
- </template>
- <script setup lang="ts">
- import { computed, onMounted, ref, watch } from 'vue'
- import { useRoute } from 'vue-router'
- import { ElMessage } from 'element-plus'
- import type { UploadFile, UploadUserFile } from 'element-plus'
- import PagePanel from '@/components/common/PagePanel.vue'
- import ListPageToolbar, {
- type ListToolbarAction,
- } from '@/components/common/ListPageToolbar.vue'
- import StatusTag from '@/components/common/StatusTag.vue'
- import TablePagination from '@/components/common/TablePagination.vue'
- import BatchDetailDrawer from '@/components/biz/BatchDetailDrawer.vue'
- import FlowAttachments from '@/components/biz/FlowAttachments.vue'
- import PersonnelDetailDescriptions from '@/components/biz/PersonnelDetailDescriptions.vue'
- import {
- decideApproval,
- decideApprovals,
- exportPersonnelNewMonth,
- fetchApprovals,
- fetchBatchMemberPeoplePage,
- fetchPersonDetail,
- fetchSpecialBizPerson,
- fetchUnresolvedInsuranceCount,
- reviewPersonQualify,
- uploadBizFile,
- } from '@/api/biz'
- import { parseApprovalId } from '@/api/sjnmtybt/mappers'
- import { useServerPagination } from '@/composables/useServerPagination'
- import { useTownVillageOptions } from '@/composables/useTownVillageOptions'
- import { guardBusy } from '@/composables/useSubmitLock'
- import { clearGetCache } from '@/utils/request'
- import { useUserStore } from '@/stores/user'
- import {
- paymentSummaryFromApi,
- sumBatchMemberAmounts,
- type BatchAmountSummary,
- } from '@/utils/batchAmounts'
- import { isYearlyAdjustBatchLike } from '@/utils/batchAdjust'
- import { formatAmount } from '@/utils/mask'
- import { displayBizText, retireQualifyLabel } from '@/utils/statusLabels'
- import type { Approval, BatchMemberPerson, Person } from '@/types/biz'
- import SensitiveValue from '@/components/common/SensitiveValue.vue'
- import SensitiveRevealSwitch from '@/components/common/SensitiveRevealSwitch.vue'
- import PersonNameCell from '@/components/common/PersonNameCell.vue'
- const userStore = useUserStore()
- const route = useRoute()
- const isAdmin = computed(() => userStore.roleInfo?.key === 'admin')
- /** 审批详情证件/卡号脱敏开关 */
- const revealSensitive = ref(false)
- const newMonthExporting = ref(false)
- const {
- filterTown,
- filterVillage,
- townOptions,
- villageOptions,
- onTownChange,
- resetTownVillage,
- loadVillages,
- } = useTownVillageOptions()
- const activeTab = ref('pending')
- const isHistoryTab = computed(() => activeTab.value === 'history')
- const toolbarActions = computed<ListToolbarAction[]>(() => {
- const list: ListToolbarAction[] = []
- if (!isHistoryTab.value) {
- list.push({
- key: 'batch-pass',
- label: '批量通过',
- type: 'primary',
- disabled: !selectedIds.value.length,
- onClick: () => onBatchDecide(true),
- })
- list.push({
- key: 'batch-reject',
- label: '批量驳回',
- disabled: !selectedIds.value.length,
- onClick: () => onBatchDecide(false),
- })
- }
- if (isTownReview.value) {
- list.push({
- key: 'export-new',
- label: '导出新增名单',
- loading: newMonthExporting.value,
- onClick: () => void onExportNewMonth(),
- })
- }
- return list
- })
- function switchTab(name: string) {
- if (activeTab.value === name) return
- activeTab.value = name
- onTabChange()
- }
- const filterBatchNo = ref('')
- const filterStatus = ref('')
- const filterMonth = ref(userStore.currentMonth || '')
- const deciding = ref(false)
- const selectedIds = ref<string[]>([])
- const detailVisible = ref(false)
- const currentRow = ref<Approval | null>(null)
- const detailPeople = ref<BatchMemberPerson[]>([])
- const peopleLoading = ref(false)
- /** 发放明细分页接口回填的全量合计(不是本页合计) */
- const detailPaymentSummary = ref<BatchAmountSummary | null>(null)
- /** 分管领导:批次居保未闭环人数缓存(approvalId → count) */
- const unresolvedInsuranceByApproval = ref<Record<string, number>>({})
- const isAdjustDetailBatch = computed(() =>
- isYearlyAdjustBatchLike({
- batchNo: currentRow.value?.batchNo,
- remarks: (currentRow.value as any)?.remarks || (currentRow.value as any)?.remark,
- }),
- )
- /** 表格底部:仅本页合计 */
- const detailPageAmountSummary = computed(() =>
- sumBatchMemberAmounts(detailPeople.value, { adjustOnly: isAdjustDetailBatch.value }),
- )
- /** 详情头/工具栏:全量合计(接口 summary → 列表行金额 → 本页兜底) */
- const detailBatchAmountSummary = computed((): BatchAmountSummary => {
- if (detailPaymentSummary.value) return detailPaymentSummary.value
- const row = currentRow.value
- if (row && (row.totalAmount != null || row.peopleCount != null)) {
- return {
- count: Number(row.peopleCount || 0),
- monthly: 0,
- oneTime: 0,
- supplement: 0,
- funeral: 0,
- deduct: 0,
- payable: Number(row.totalAmount || 0),
- }
- }
- return detailPageAmountSummary.value
- })
- function detailTableSummary(param: {
- columns: Array<{ label?: string; property?: string }>
- }) {
- const sum = detailPageAmountSummary.value
- return param.columns.map((col, index) => {
- if (index === 0) return '本页合计'
- const label = String(col.label || col.property || '')
- if (label.includes('标准')) return formatAmount(sum.monthly)
- if (label.includes('补发')) return formatAmount(sum.supplement)
- if (label.includes('丧葬')) return formatAmount(sum.funeral)
- if (label.includes('扣减')) return formatAmount(sum.deduct)
- if (label.includes('总计')) return formatAmount(sum.payable)
- return ''
- })
- }
- const rejectVisible = ref(false)
- const rejectOpinion = ref('')
- const rejectTarget = ref<Approval | null>(null)
- const rejectBatchMode = ref(false)
- const rejectFileList = ref<UploadUserFile[]>([])
- const rejectRawFiles = ref<File[]>([])
- const passVisible = ref(false)
- const passOpinion = ref('同意')
- const passTip = ref('')
- const passTarget = ref<Approval | null>(null)
- const passBatchMode = ref(false)
- const passFileList = ref<UploadUserFile[]>([])
- const passRawFiles = ref<File[]>([])
- const passQualifyType = ref<'有土地退养' | '无土地有资格'>('有土地退养')
- const rejectQualifyMode = ref<'supplement' | 'ineligible'>('supplement')
- const batchDrawerVisible = ref(false)
- const batchDrawerId = ref('')
- function batchIdOfApproval(row: Approval): string {
- const parsed = parseApprovalId(String(row.id || ''))
- if (parsed?.kind === 'batch') return parsed.bizId
- return ''
- }
- function isSpecialApproval(row: Approval | null | undefined): boolean {
- if (!row) return false
- const parsed = parseApprovalId(String(row.id || ''))
- return parsed?.kind === 'special'
- }
- function isQualifyApproval(row: Approval | null | undefined): boolean {
- if (!row) return false
- const parsed = parseApprovalId(String(row.id || ''))
- return parsed?.kind === 'qualify' || row.bizType === '退养资格审批'
- }
- function isAdjustSpecialApproval(row: Approval | null | undefined): boolean {
- if (!row) return false
- return row.bizType.includes('调标')
- }
- function adjustSpecialSummary(row: Approval): string {
- const reason = String(row.reason || '')
- if (reason.includes('旧标') && reason.includes('新标')) {
- return reason
- }
- if (row.totalAmount != null) {
- return `补发合计 ${formatAmount(row.totalAmount)} 元(详见申请说明)`
- }
- return reason || '—'
- }
- function syncRejectFiles(_file: UploadFile, fileList: UploadFile[]) {
- rejectFileList.value = fileList
- rejectRawFiles.value = fileList.map((f) => f.raw).filter((f): f is File => !!f)
- }
- function syncPassFiles(_file: UploadFile, fileList: UploadFile[]) {
- passFileList.value = fileList
- passRawFiles.value = fileList.map((f) => f.raw).filter((f): f is File => !!f)
- }
- function onRejectDialogClosed() {
- rejectOpinion.value = ''
- rejectFileList.value = []
- rejectRawFiles.value = []
- rejectTarget.value = null
- rejectBatchMode.value = false
- rejectQualifyMode.value = 'supplement'
- }
- function onPassDialogClosed() {
- passOpinion.value = '同意'
- passTip.value = ''
- passFileList.value = []
- passRawFiles.value = []
- passTarget.value = null
- passBatchMode.value = false
- }
- const isQualifyPassTarget = computed(
- () =>
- isQualifyApproval(passTarget.value) ||
- (passBatchMode.value && isTownReview.value),
- )
- const isQualifyRejectTarget = computed(
- () =>
- isQualifyApproval(rejectTarget.value) ||
- (rejectBatchMode.value && isTownReview.value),
- )
- async function uploadApproveFiles(files: File[]): Promise<string | undefined> {
- if (!files.length) return undefined
- const uploaded: string[] = []
- for (const f of files) {
- const r = await uploadBizFile(f)
- if (r?.url) uploaded.push(r.url)
- }
- return uploaded.length ? uploaded.join(',') : undefined
- }
- function openBatchFlow(row: Approval) {
- const id = batchIdOfApproval(row)
- if (!id) {
- ElMessage.info('该审批单无关联批次流程')
- return
- }
- batchDrawerId.value = id
- batchDrawerVisible.value = true
- }
- const qualifyVisible = ref(false)
- const qualifyTarget = ref<Person | null>(null)
- const qualifyType = ref<'有土地退养' | '无土地有资格' | '无资格'>('有土地退养')
- const reviewingId = ref<number | string | null>(null)
- const personDetailVisible = ref(false)
- const personDetailLoading = ref(false)
- const personDetail = ref<Person | null>(null)
- function qualifyTagType(t?: string): 'success' | 'warning' | 'danger' | 'info' {
- if (t === '无资格') return 'danger'
- if (t === '无土地有资格') return 'warning'
- return 'success'
- }
- function applyFilter() {
- void loadApprovals()
- }
- function resetFilter() {
- filterBatchNo.value = ''
- filterStatus.value = ''
- filterMonth.value = userStore.currentMonth || ''
- if (isAdmin.value) {
- resetTownVillage()
- } else {
- filterVillage.value = ''
- }
- void loadApprovals()
- }
- async function onExportNewMonth() {
- if (guardBusy(newMonthExporting)) return
- newMonthExporting.value = true
- try {
- await exportPersonnelNewMonth({
- town: filterTown.value || userStore.roleInfo?.town || undefined,
- village: filterVillage.value || undefined,
- payMonth: (filterMonth.value || userStore.currentMonth || '').replace('-', ''),
- })
- ElMessage.success('已导出新增人员名单')
- } catch (e) {
- ElMessage.error(e instanceof Error ? e.message : '导出失败')
- } finally {
- newMonthExporting.value = false
- }
- }
- async function syncScopeTown() {
- const town = userStore.roleInfo?.town
- if (!isAdmin.value && town) {
- filterTown.value = town
- await loadVillages(town)
- }
- }
- const detailStatusFilter = ref('')
- const detailSearchName = ref('')
- const detailSearchIdCard = ref('')
- /** totalAmount:desc | supplementAmount:desc | funeralAmount:desc ... */
- const detailAmountSort = ref('')
- /** supplement | funeral */
- const detailAmountFilter = ref('')
- const detailStatusOptions = computed(() => {
- const set = new Set<string>()
- for (const p of detailPeople.value) {
- if (p.status) set.add(p.status)
- }
- return Array.from(set).sort((a, b) => a.localeCompare(b, 'zh-CN'))
- })
- const filteredDetailPeople = computed(() => {
- if (!detailStatusFilter.value) return detailPeople.value
- return detailPeople.value.filter((p) => p.status === detailStatusFilter.value)
- })
- const detailPeoplePage = ref(1)
- const detailPeoplePageSize = ref(10)
- const detailPeopleTotal = ref(0)
- const pagedDetailPeople = filteredDetailPeople
- function resetDetailPeoplePage() {
- detailPeoplePage.value = 1
- }
- function onDetailSearch() {
- detailPeoplePage.value = 1
- void reloadDetailPeople()
- }
- function resetDetailSearch() {
- detailSearchName.value = ''
- detailSearchIdCard.value = ''
- detailAmountSort.value = ''
- detailAmountFilter.value = ''
- detailPeoplePage.value = 1
- void reloadDetailPeople()
- }
- /** 按角色限定审批节点 */
- const approvalNode = computed(() => {
- if (userStore.currentRole === 'town') return '镇经发中心初审'
- if (userStore.currentRole === 'leader') return '镇社区受理中心分管领导线上审批'
- return undefined
- })
- const isTownReview = computed(() => userStore.currentRole === 'town')
- const isLeaderReview = computed(() => userStore.currentRole === 'leader')
- /** 刚审完的单:本会话按角色记住,离开再进仍过滤;接口若因缓存仍返回待审,列表先剔除 */
- function decidedStorageKey(role?: string) {
- return `sj_tdtybz_decided_approvals_${role || userStore.currentRole || 'default'}`
- }
- function readDecidedIds(role?: string): Set<string> {
- try {
- const raw = sessionStorage.getItem(decidedStorageKey(role))
- const arr = raw ? JSON.parse(raw) : []
- return new Set(Array.isArray(arr) ? arr.filter((id) => typeof id === 'string' && id) : [])
- } catch {
- return new Set()
- }
- }
- function persistDecidedIds(ids: Set<string>, role?: string) {
- try {
- sessionStorage.setItem(decidedStorageKey(role), JSON.stringify([...ids]))
- } catch {
- /* ignore quota */
- }
- }
- const decidedIds = ref<Set<string>>(readDecidedIds())
- const {
- page,
- pageSize,
- total,
- list: pagedList,
- loading,
- load: reloadApprovals,
- resetAndLoad: loadApprovals,
- } = useServerPagination<Approval>(async (pageNum, pageSize) => {
- selectedIds.value = []
- const role = userStore.roleInfo
- const isPending = activeTab.value === 'pending'
- const page = await fetchApprovals({
- tab: isPending ? 'pending' : 'history',
- node: approvalNode.value,
- town: isTownReview.value || isLeaderReview.value ? role?.town : filterTown.value || undefined,
- village: filterVillage.value || undefined,
- batchNo: filterBatchNo.value.trim() || undefined,
- enjoyStartMonth: isTownReview.value
- ? (filterMonth.value || userStore.currentMonth || '').replace('-', '')
- : undefined,
- status: isHistoryTab.value ? filterStatus.value || undefined : undefined,
- pageNum,
- pageSize,
- })
- const hidden = decidedIds.value
- const raw = page.list || []
- const list = isPending && hidden.size ? raw.filter((r) => !hidden.has(r.id)) : raw
- void refreshUnresolvedInsuranceForList(list)
- return {
- ...page,
- list,
- total: Math.max(0, Number(page.total || 0) - (raw.length - list.length)),
- }
- })
- const list = pagedList
- function markDecided(ids: string[]) {
- if (!ids.length) return
- const next = new Set(decidedIds.value)
- for (const id of ids) {
- if (id) next.add(id)
- }
- decidedIds.value = next
- persistDecidedIds(next)
- pagedList.value = pagedList.value.filter((r) => !next.has(r.id))
- total.value = Math.max(0, total.value - ids.length)
- }
- const paperTownName = computed(() => filterTown.value || userStore.roleInfo?.town || '')
- const paperFormTitle = computed(
- () => `松江区${paperTownName.value}老年农民享受土地退养补助金新增名单`,
- )
- const paperMonthText = computed(() => {
- const m = String(filterMonth.value || userStore.currentMonth || '').replace('-', '')
- if (m.length >= 6) return `${m.slice(0, 4)}年${Number(m.slice(4, 6))}月`
- return ''
- })
- function paperHousehold(v?: string) {
- const s = String(v || '').trim()
- if (!s) return ''
- if (s.includes('非')) return '非农'
- if (s.includes('农')) return '农业'
- return s
- }
- function paperStartMonth(v?: string) {
- const s = String(v || '').replace(/-/g, '')
- if (s.length >= 6) return `${s.slice(0, 4)}年${Number(s.slice(4, 6))}月`
- return v || ''
- }
- function insuranceFail(row: Approval) {
- const t = String(row.insuranceStatus || '')
- return t !== '' && t !== '未匹配' && t !== '匹配通过' && t !== '人工确认匹配成功'
- }
- function paperStandard(n?: number | null) {
- if (n == null || Number.isNaN(Number(n))) return ''
- const v = Number(n)
- return Number.isInteger(v) ? String(v) : String(v)
- }
- const pageTitle = computed(() => {
- if (isTownReview.value) return '退养资格审批'
- if (isLeaderReview.value) return '审批管理(镇社区受理中心分管领导)'
- if (isAdmin.value) return '审批管理(全区 · 按镇+村)'
- return '审批管理'
- })
- const detailDialogTitle = computed(() => {
- const row = currentRow.value
- if (!row) return '审批详情'
- if (isQualifyApproval(row)) return '退养资格审批'
- if (isSpecialApproval(row)) return '特殊业务审批'
- if (isLeaderReview.value) return '月补贴清单审批'
- if (isTownReview.value) return '退养资格审批'
- return '审批详情'
- })
- /** 当前发放周期对应批次号 */
- const currentBatchNo = computed(() => userStore.currentMonth.replace('-', ''))
- /** 详情中仍需资格核验的人数(仅镇经发场景使用) */
- const pendingQualifyCount = computed(
- () => detailPeople.value.filter((p) => needsQualifyReview(p)).length,
- )
- /** 已认定「无资格」的人员:镇经发批次只能驳回 */
- const ineligibleMembers = computed(() =>
- detailPeople.value.filter((p) => p.retireQualifyType === '无资格' || p.status === '无资格'),
- )
- const hasIneligibleMembers = computed(() => ineligibleMembers.value.length > 0)
- /**
- * 详情弹窗「通过」禁用原因:
- * - 镇经发:无资格 / 未核验资格(资格审批本身除外)
- * - 分管领导:居保未闭环(资格核验与领导无关,绝不拦)
- */
- const detailPassBlockedReason = computed(() => {
- const row = currentRow.value
- if (!row || isQualifyApproval(row) || isSpecialApproval(row)) return ''
- if (isTownReview.value) {
- if (hasIneligibleMembers.value) {
- return `存在「无资格」人员 ${ineligibleMembers.value.length} 人,只能驳回由村处理`
- }
- if (pendingQualifyCount.value > 0) {
- return `有 ${pendingQualifyCount.value} 人退养资格尚未核验,请先在名单中点「核验资格」`
- }
- return ''
- }
- if (isLeaderReview.value) {
- const n = unresolvedInsuranceByApproval.value[String(row.id)] || 0
- if (n > 0) {
- return `仍有 ${n} 人居保未匹配/未通过,请先完成居保匹配或人工确认后再通过`
- }
- }
- return ''
- })
- const detailPassBlocked = computed(() => Boolean(detailPassBlockedReason.value))
- const detailPassBlockAlert = computed(() => {
- if (!detailPassBlockedReason.value) return ''
- if (isTownReview.value && pendingQualifyCount.value > 0) {
- return `${detailPassBlockedReason.value}(当前页可见待核验人员已标红,请先处理)`
- }
- return detailPassBlockedReason.value
- })
- function rowPassBlockReason(row: Approval): string {
- if (row.status !== '待审批') return ''
- if (isQualifyApproval(row) || isSpecialApproval(row)) return ''
- if (isTownReview.value) {
- // 详情已打开且是同一条:与弹窗共用资格拦截
- if (detailVisible.value && currentRow.value?.id === row.id) {
- return detailPassBlockedReason.value
- }
- return ''
- }
- if (isLeaderReview.value) {
- const n = unresolvedInsuranceByApproval.value[String(row.id)] || 0
- if (n > 0) {
- return `仍有 ${n} 人居保未匹配/未通过,不可通过`
- }
- }
- return ''
- }
- function isRowPassBlocked(row: Approval) {
- return Boolean(rowPassBlockReason(row))
- }
- function detailPeopleRowClass({ row }: { row: BatchMemberPerson }) {
- if (isTownReview.value && needsQualifyReview(row)) {
- return 'approvals-view__row--qualify-pending'
- }
- if (isTownReview.value && (row.retireQualifyType === '无资格' || row.status === '无资格')) {
- return 'approvals-view__row--ineligible'
- }
- return ''
- }
- async function refreshUnresolvedInsuranceForList(rows: Approval[]) {
- if (!isLeaderReview.value) return
- const next: Record<string, number> = { ...unresolvedInsuranceByApproval.value }
- await Promise.all(
- rows.map(async (row) => {
- if (isQualifyApproval(row) || isSpecialApproval(row)) return
- const batchId = row.batchId || batchIdOfApproval(row)
- if (!batchId) return
- try {
- next[String(row.id)] = await fetchUnresolvedInsuranceCount(batchId)
- } catch {
- // 忽略单条失败,不挡列表
- }
- }),
- )
- unresolvedInsuranceByApproval.value = next
- }
- function onSelectionChange(rows: Approval[]) {
- selectedIds.value = rows.map((r) => r.id)
- }
- function hasQualifyResult(row: Person) {
- return Boolean(row.retireQualifyType)
- }
- /** 退养资格未填即待核验(不再用「正常发放」推断为往期已核验) */
- function needsQualifyReview(row: Person) {
- if ((row as BatchMemberPerson).changeFlag === 'REMOVE') return false
- if (hasQualifyResult(row)) return false
- if (row.status === '无资格') return false
- return true
- }
- /** 待审批名单中资格未填的人员可核验 */
- function canReviewQualify(row: Person) {
- if (!isTownReview.value) return false
- if (currentRow.value?.status !== '待审批') return false
- return needsQualifyReview(row)
- }
- function openQualifyDialog(row: Person) {
- qualifyTarget.value = row
- if (row.retireQualifyType === '无资格') qualifyType.value = '无资格'
- else if (row.retireQualifyType === '无土地有资格') qualifyType.value = '无土地有资格'
- else qualifyType.value = '有土地退养'
- qualifyVisible.value = true
- }
- async function openPersonDetail(row: Person) {
- const id = String(row.id || currentRow.value?.personnelId || '')
- if (!id || id === 'undefined') {
- ElMessage.warning('未关联到在册人员,无法查看详情')
- return
- }
- personDetail.value = row
- personDetailVisible.value = true
- personDetailLoading.value = true
- try {
- const full = await fetchPersonDetail(id)
- if (full) personDetail.value = full
- } finally {
- personDetailLoading.value = false
- }
- }
- function onPersonDetailClosed() {
- personDetail.value = null
- }
- async function confirmQualify() {
- if (!qualifyTarget.value) return
- if (guardBusy(reviewingId)) return
- reviewingId.value = qualifyTarget.value.id
- try {
- await reviewPersonQualify({
- personId: qualifyTarget.value.id,
- retireQualifyType: qualifyType.value,
- })
- // 同步刷新详情列表中的该行
- const idx = detailPeople.value.findIndex((p) => p.id === qualifyTarget.value!.id)
- if (idx >= 0) {
- const prev = detailPeople.value[idx]
- let remark = prev.remark
- if (qualifyType.value === '无资格') {
- remark = [prev.remark, '无资格;不具备土地退养补助资格'].filter(Boolean).join(';')
- } else if (qualifyType.value === '无土地有资格' && !prev.remark?.includes('无土地')) {
- remark = [prev.remark, '无承包地,按政策认定具备退养补助资格'].filter(Boolean).join(';')
- }
- detailPeople.value[idx] = {
- ...prev,
- // 人员状态跟业务底数;有资格不改写状态文案,无资格展示「无资格」
- status: qualifyType.value === '无资格' ? '无资格' : prev.status,
- retireQualifyType: qualifyType.value,
- remark,
- }
- }
- if (qualifyType.value === '无资格') {
- ElMessage.warning(
- `已认定「无资格」:${qualifyTarget.value.name}。本批只能驳回,由村取消该人员或补充说明。`,
- )
- } else {
- ElMessage.success(`已核验:${qualifyTarget.value.name}(${qualifyType.value})`)
- }
- qualifyVisible.value = false
- } finally {
- reviewingId.value = null
- }
- }
- /** 打开详情:批次拉发放明细;特殊业务拉关联人员 */
- async function openDetail(row: Approval) {
- currentRow.value = { ...row }
- detailVisible.value = true
- detailPeople.value = []
- detailPaymentSummary.value = null
- detailStatusFilter.value = ''
- detailSearchName.value = ''
- detailSearchIdCard.value = ''
- peopleLoading.value = true
- try {
- if (isQualifyApproval(row) || isSpecialApproval(row)) {
- const pid = row.personnelId || (isQualifyApproval(row) ? parseApprovalId(String(row.id || ''))?.bizId : '')
- let person = pid
- ? (isQualifyApproval(row) ? await fetchPersonDetail(pid) : await fetchSpecialBizPerson(pid))
- : null
- if (!person && row.personName) {
- // 兜底:列表已有姓名时先展示一行,避免详情空白
- person = {
- id: row.personnelId || row.id,
- batchNo: '',
- name: row.personName,
- idCard: row.idCard || '',
- town: row.town,
- village: row.village && row.village !== '—' ? row.village : '',
- bankCard: '',
- bankName: '',
- standard: row.amount || 0,
- startMonth: '',
- status: '—',
- source: '特殊业务申请',
- insuranceStatus: '',
- }
- }
- if (person) {
- const amt = Number(row.amount ?? person.standard ?? 0) || 0
- detailPeople.value = [
- {
- ...person,
- batchNo: person.batchNo || '',
- monthlyAmount: amt,
- supplementAmount: 0,
- funeralAmount: row.bizType?.includes('丧葬') || row.bizType?.includes('死亡') ? amt : 0,
- deductAmount: 0,
- payableAmount: amt,
- standard: amt || person.standard,
- },
- ]
- currentRow.value = {
- ...currentRow.value!,
- village: person.village || currentRow.value?.village,
- town: person.town || currentRow.value?.town,
- personName: person.name || currentRow.value?.personName,
- idCard: person.idCard || currentRow.value?.idCard,
- peopleCount: 1,
- totalAmount: amt,
- }
- }
- return
- }
- const batchId = row.batchId || batchIdOfApproval(row)
- if (!batchId) {
- detailPeople.value = []
- return
- }
- if (isLeaderReview.value) {
- try {
- const n = await fetchUnresolvedInsuranceCount(batchId)
- unresolvedInsuranceByApproval.value = {
- ...unresolvedInsuranceByApproval.value,
- [String(row.id)]: n,
- }
- } catch {
- // ignore
- }
- }
- const peoplePage = await fetchBatchMemberPeoplePage(batchId, {
- batchNo: row.batchNo,
- town: row.town,
- village: row.village,
- pruneDefaults: false,
- pageNum: 1,
- pageSize: detailPeoplePageSize.value,
- name: detailSearchName.value.trim() || undefined,
- idCard: detailSearchIdCard.value.trim() || undefined,
- })
- detailPeople.value = peoplePage.list
- detailPeopleTotal.value = peoplePage.total
- detailPeoplePage.value = 1
- const apiSum = paymentSummaryFromApi(peoplePage.summary)
- detailPaymentSummary.value = apiSum
- if (currentRow.value) {
- currentRow.value = {
- ...currentRow.value,
- peopleCount: apiSum?.count || peoplePage.total || currentRow.value.peopleCount,
- totalAmount:
- apiSum?.payable ??
- currentRow.value.totalAmount ??
- 0,
- }
- }
- } finally {
- peopleLoading.value = false
- }
- }
- function onDetailClosed() {
- currentRow.value = null
- detailPeople.value = []
- detailPaymentSummary.value = null
- detailStatusFilter.value = ''
- detailSearchName.value = ''
- detailSearchIdCard.value = ''
- }
- async function doDecide(
- row: Approval,
- pass: boolean,
- opinion?: string,
- fileUrls?: string,
- ) {
- const qualifyType = isQualifyApproval(row)
- ? pass
- ? undefined
- : rejectQualifyMode.value === 'ineligible'
- ? '无资格'
- : undefined
- : undefined
- await decideApproval({
- id: row.id,
- pass,
- opinion,
- fileUrls,
- retireQualifyType: qualifyType,
- })
- const isTown = row.node.includes('镇经发') || isQualifyApproval(row)
- const isMonthly = row.bizType.includes('月') || row.bizType.includes('清单')
- if (pass) {
- const isDeath = row.bizType.includes('死亡')
- ElMessage.success(
- isTown
- ? '已通过,人员状态已更新为「新增」'
- : isMonthly
- ? '已通过,批次进入「已审核」,可由业务部门出盘'
- : isDeath
- ? '已通过,人员状态已更新为「死亡丧葬」'
- : '已通过',
- )
- } else {
- ElMessage.success(
- isQualifyApproval(row)
- ? rejectQualifyMode.value === 'ineligible'
- ? '已认定无资格,已写入村告警,请村合作社处理'
- : '已驳回补材料,已写入村告警,请村合作社补充后重新提交'
- : isTown
- ? '已驳回(补充/修改),批次已退回村合作社'
- : isMonthly
- ? '已驳回(补充/修改),批次已退回居保匹配环节'
- : '已驳回(补充/修改),已退回业务受理',
- )
- }
- detailVisible.value = false
- markDecided([row.id])
- clearGetCache()
- await reloadApprovals()
- }
- async function townPassBlockReason(row: Approval, people?: Person[]): Promise<string | null> {
- if (isSpecialApproval(row) || isQualifyApproval(row)) return null
- // 分管领导:只拦居保未闭环;资格核验不属于领导职责
- if (isLeaderReview.value || row.node.includes('分管领导')) {
- const cached = unresolvedInsuranceByApproval.value[String(row.id)]
- if (cached != null && cached > 0) {
- return `仍有 ${cached} 人居保未匹配/未通过,请先完成居保匹配或人工确认后再通过`
- }
- const batchId = row.batchId || batchIdOfApproval(row)
- if (!batchId) return null
- try {
- const n = await fetchUnresolvedInsuranceCount(batchId)
- unresolvedInsuranceByApproval.value = {
- ...unresolvedInsuranceByApproval.value,
- [String(row.id)]: n,
- }
- if (n > 0) {
- return `仍有 ${n} 人居保未匹配/未通过,请先完成居保匹配或人工确认后再通过`
- }
- } catch {
- // ignore
- }
- return null
- }
- if (!(isTownReview.value || row.node.includes('镇经发'))) return null
- let list = people
- if (!list) {
- const batchId = row.batchId || batchIdOfApproval(row)
- if (!batchId) return null
- list = (
- await fetchBatchMemberPeoplePage(batchId, {
- batchNo: row.batchNo,
- town: row.town,
- village: row.village,
- pruneDefaults: false,
- pageNum: 1,
- pageSize: 10,
- })
- ).list
- }
- const ineligible = list.filter((p) => p.retireQualifyType === '无资格' || p.status === '无资格')
- if (ineligible.length) {
- return `「${row.village || row.title}」有 ${ineligible.length} 人已认定「无资格」,不能通过;请驳回由村取消该人员或补充说明。`
- }
- const pending = list.filter((p) => needsQualifyReview(p))
- if (pending.length) {
- const names = pending
- .slice(0, 8)
- .map((p) => p.name || p.id)
- .join('、')
- const extra = pending.length > 8 ? ` 等 ${pending.length} 人` : ''
- return `「${row.village || row.title}」有 ${pending.length} 人退养资格尚未核验(${names}${extra}),请先在详情中点「核验资格」后再通过。`
- }
- return null
- }
- async function onDecide(row: Approval, pass: boolean) {
- if (pass) {
- const people =
- detailVisible.value && currentRow.value?.id === row.id ? detailPeople.value : undefined
- const block = await townPassBlockReason(row, people)
- if (block) {
- ElMessage.warning(block)
- return
- }
- passTarget.value = row
- passBatchMode.value = false
- passOpinion.value = '同意'
- passFileList.value = []
- passRawFiles.value = []
- if (isQualifyApproval(row) || isTownReview.value || row.node.includes('镇经发')) {
- passTip.value = `确认通过「${row.title}」?仅本次新增人员,资格以村级勾选为准,通过后变为「新增」。`
- } else if (isSpecialApproval(row)) {
- passTip.value = `确认通过「${row.title}」?特殊业务通过后将按类型回写人员状态(死亡登记通过后变为「死亡丧葬」)。`
- } else {
- passTip.value = `确认通过「${row.title}」?通过后对应批次状态将更新。`
- }
- passVisible.value = true
- return
- }
- rejectTarget.value = row
- rejectBatchMode.value = false
- rejectQualifyMode.value = 'supplement'
- rejectFileList.value = []
- rejectRawFiles.value = []
- rejectOpinion.value = hasIneligibleMembers.value
- ? `存在无资格人员(${ineligibleMembers.value.map((p) => p.name).join('、')}),退回村合作社取消该人员或补充说明后重提。`
- : ''
- rejectVisible.value = true
- }
- async function confirmPass() {
- if (guardBusy(deciding)) return
- const opinion = passOpinion.value.trim() || '同意'
- deciding.value = true
- try {
- const fileUrls = await uploadApproveFiles(passRawFiles.value)
- if (passBatchMode.value) {
- await decideApprovals({
- ids: selectedIds.value,
- pass: true,
- opinion,
- fileUrls,
- retireQualifyType: undefined,
- })
- ElMessage.success(isTownReview.value ? '批量资格审批通过' : '批量通过成功')
- const ids = [...selectedIds.value]
- selectedIds.value = []
- passVisible.value = false
- detailVisible.value = false
- markDecided(ids)
- clearGetCache()
- await reloadApprovals()
- return
- }
- if (passTarget.value) {
- await doDecide(passTarget.value, true, opinion, fileUrls)
- passVisible.value = false
- }
- } catch (e) {
- ElMessage.error(e instanceof Error ? e.message : '上传或审批失败')
- } finally {
- deciding.value = false
- }
- }
- async function confirmReject() {
- const reason = rejectOpinion.value.trim()
- if (!reason) {
- ElMessage.warning('请填写驳回理由')
- return
- }
- if (guardBusy(deciding)) return
- deciding.value = true
- try {
- const fileUrls = await uploadApproveFiles(rejectRawFiles.value)
- if (rejectBatchMode.value) {
- await decideApprovals({
- ids: selectedIds.value,
- pass: false,
- opinion: reason,
- fileUrls,
- retireQualifyType:
- isTownReview.value
- ? rejectQualifyMode.value === 'ineligible'
- ? '无资格'
- : '有土地退养'
- : undefined,
- })
- ElMessage.success('批量驳回成功')
- const ids = [...selectedIds.value]
- selectedIds.value = []
- rejectVisible.value = false
- detailVisible.value = false
- markDecided(ids)
- clearGetCache()
- await reloadApprovals()
- return
- }
- if (rejectTarget.value) {
- await doDecide(rejectTarget.value, false, reason, fileUrls)
- rejectVisible.value = false
- }
- } catch (e) {
- ElMessage.error(e instanceof Error ? e.message : '上传或驳回失败')
- } finally {
- deciding.value = false
- }
- }
- async function onBatchDecide(pass: boolean) {
- if (!pass) {
- rejectBatchMode.value = true
- rejectTarget.value = null
- rejectQualifyMode.value = 'supplement'
- rejectOpinion.value = ''
- rejectFileList.value = []
- rejectRawFiles.value = []
- rejectVisible.value = true
- return
- }
- if (isTownReview.value) {
- for (const id of selectedIds.value) {
- const row = list.value.find((r) => r.id === id)
- if (!row) continue
- const block = await townPassBlockReason(row)
- if (block) {
- ElMessage.warning(block)
- return
- }
- }
- }
- passBatchMode.value = true
- passTarget.value = null
- passOpinion.value = '同意'
- passTip.value = `确认批量通过选中的 ${selectedIds.value.length} 条审批?通过后对应批次状态将更新。`
- passFileList.value = []
- passRawFiles.value = []
- passVisible.value = true
- }
- async function reloadDetailPeople() {
- const row = currentRow.value
- if (!row || isQualifyApproval(row) || isSpecialApproval(row)) return
- const batchId = row.batchId || batchIdOfApproval(row)
- if (!batchId) return
- peopleLoading.value = true
- try {
- const peoplePage = await fetchBatchMemberPeoplePage(batchId, {
- batchNo: row.batchNo,
- town: row.town,
- village: row.village,
- pruneDefaults: false,
- pageNum: detailPeoplePage.value,
- pageSize: detailPeoplePageSize.value,
- name: detailSearchName.value.trim() || undefined,
- idCard: detailSearchIdCard.value.trim() || undefined,
- sortBy: detailAmountSort.value ? detailAmountSort.value.split(':')[0] : undefined,
- sortOrder: detailAmountSort.value ? detailAmountSort.value.split(':')[1] : undefined,
- onlyFuneral: detailAmountFilter.value === 'funeral' || undefined,
- onlySupplement: detailAmountFilter.value === 'supplement' || undefined,
- })
- detailPeople.value = peoplePage.list
- detailPeopleTotal.value = peoplePage.total
- const apiSum = paymentSummaryFromApi(peoplePage.summary)
- if (apiSum) {
- detailPaymentSummary.value = apiSum
- if (currentRow.value) {
- currentRow.value = {
- ...currentRow.value,
- peopleCount: apiSum.count || peoplePage.total || currentRow.value.peopleCount,
- totalAmount: apiSum.payable,
- }
- }
- }
- } finally {
- peopleLoading.value = false
- }
- }
- function onTabChange() {
- filterStatus.value = ''
- loadApprovals()
- }
- watch([detailPeoplePage, detailPeoplePageSize], () => {
- if (!detailVisible.value) return
- const row = currentRow.value
- if (!row || isQualifyApproval(row) || isSpecialApproval(row)) return
- void reloadDetailPeople()
- })
- /** 从审批超时告警带批次号进来:筛出这一单并打开审批详情。 */
- async function openBatchFromAlert() {
- const batchNo = String(route.query.batchNo || '').trim()
- if (!batchNo || isTownReview.value) return
- filterBatchNo.value = batchNo
- activeTab.value = 'pending'
- await loadApprovals()
- const hit = pagedList.value.find((row) => {
- const no = String(row.batchNo || '')
- return no === batchNo || no.replace(/-/g, '') === batchNo.replace(/-/g, '')
- })
- if (hit && hit.status === '待审批') {
- await openDetail(hit)
- return
- }
- ElMessage.warning(`批次 ${batchNo} 不在待审批列表,可能已审完,或不是当前角色能审的单`)
- }
- onMounted(async () => {
- await syncScopeTown()
- if (String(route.query.batchNo || '').trim() && !isTownReview.value) {
- await openBatchFromAlert()
- } else {
- await loadApprovals()
- }
- })
- watch(
- () => route.query.batchNo,
- (batchNo) => {
- if (!String(batchNo || '').trim() || isTownReview.value) return
- void openBatchFromAlert()
- },
- )
- watch(
- () => [userStore.currentRole, userStore.currentMonth],
- async () => {
- filterBatchNo.value = ''
- filterStatus.value = ''
- if (isAdmin.value) resetTownVillage()
- else {
- filterVillage.value = ''
- await syncScopeTown()
- }
- decidedIds.value = readDecidedIds()
- await loadApprovals()
- },
- )
- </script>
- <style scoped lang="less">
- .approvals-view__tip {
- margin: 0 0 10px;
- font-size: 12px;
- color: @text-secondary;
- line-height: 1.5;
- }
- .approvals-view__filter {
- /* ????? .content-panel .query-panel */
- }
- .approvals-view__people {
- margin-top: 16px;
- .approvals-view__block-alert {
- margin-bottom: 10px;
- }
- :deep(.approvals-view__row--qualify-pending) {
- --el-table-tr-bg-color: #fff7e6;
- }
- :deep(.approvals-view__row--ineligible) {
- --el-table-tr-bg-color: #fff1f0;
- }
- &-toolbar {
- display: flex;
- flex-wrap: wrap;
- align-items: center;
- justify-content: space-between;
- gap: 12px;
- min-height: 36px;
- margin-bottom: 8px;
- }
- &-search {
- display: flex;
- flex-wrap: wrap;
- align-items: center;
- gap: 8px;
- width: 100%;
- min-height: 36px;
- :deep(.el-input__wrapper),
- :deep(.el-select__wrapper) {
- min-height: 36px !important;
- height: 36px !important;
- }
- :deep(.el-button) {
- min-height: 36px;
- height: 36px;
- padding: 0 16px;
- }
- }
- &-title {
- flex: 1;
- min-width: 0;
- font-size: 13px;
- font-weight: 600;
- color: @text-primary;
- span {
- font-weight: 400;
- color: @text-secondary;
- }
- em {
- margin-left: 8px;
- font-style: normal;
- font-weight: 400;
- font-size: 12px;
- color: @primary-dark;
- }
- }
- &-warn {
- color: #fa8c16 !important;
- }
- &-amount {
- color: @primary-dark !important;
- font-weight: 600 !important;
- }
- }
- .approvals-view__status-filter {
- width: 140px;
- flex-shrink: 0;
- }
- .approvals-view__reject-tip,
- .approvals-view__qualify-tip {
- margin: 0 0 12px;
- font-size: 12px;
- color: @warning-color;
- line-height: 1.5;
- }
- .approvals-view__qualify-tip {
- color: @text-secondary;
- }
- .approvals-view__qualify-info {
- margin-bottom: 12px;
- }
- .approvals-view__qualify-form {
- margin-top: 4px;
- }
- .approvals-view__qualify-warn {
- margin: 0 0 4px;
- padding: 8px 10px;
- font-size: 12px;
- line-height: 1.5;
- color: #a63d2c;
- background: rgba(196, 92, 38, 0.08);
- border-radius: 4px;
- }
- .muted {
- color: @text-secondary;
- font-size: 12px;
- }
- .approvals-view__paper {
- min-height: 0;
- overflow-x: auto;
- /* ????????????? */
- :deep(.approvals-view__col-name .cell) {
- white-space: nowrap;
- }
- :deep(.approvals-view__col-name .person-name-cell) {
- flex-wrap: nowrap;
- }
- :deep(.approvals-view__col-id .cell) {
- white-space: nowrap;
- }
- :deep(.approvals-view__col-id .sensitive-value) {
- max-width: none;
- }
- :deep(.approvals-view__col-id .sensitive-value__text) {
- overflow: visible;
- text-overflow: clip;
- }
- /* ???????????/?????? */
- :deep(.approvals-view__col-coop .cell) {
- white-space: nowrap;
- }
- /* ??????? */
- :deep(.approvals-view__col-remark .cell) {
- white-space: normal;
- line-height: 1.45;
- word-break: break-word;
- }
- }
- .approvals-view__paper-title {
- text-align: center;
- font-size: 18px;
- font-weight: 600;
- letter-spacing: 1px;
- margin: 4px 0 8px;
- }
- .approvals-view__paper-meta {
- position: relative;
- display: flex;
- justify-content: flex-end;
- align-items: center;
- min-height: 28px;
- padding: 0 4px 10px;
- font-size: 13px;
- }
- .approvals-view__paper-month {
- position: absolute;
- left: 0;
- right: 0;
- text-align: center;
- pointer-events: none;
- }
- .approvals-view__paper-unit {
- position: relative;
- z-index: 1;
- display: flex;
- align-items: center;
- gap: 12px;
- background: #fff;
- }
- .approvals-view__paper-sign {
- display: grid;
- grid-template-columns: 1fr 1fr;
- gap: 24px;
- margin-top: 28px;
- padding: 0 8px 12px;
- font-size: 13px;
- line-height: 2.2;
- }
- .approvals-view__paper-sign p {
- margin: 0;
- }
- .approvals-view__ins-ok {
- color: #1a7f37;
- }
- .approvals-view__ins-fail {
- color: #c45656;
- }
- :deep(.approvals-view__ops) .cell {
- white-space: nowrap;
- }
- :deep(.approvals-view__ops) .el-button {
- margin-left: 0;
- padding: 0 2px;
- }
- :deep(.approvals-view__ops) .el-button + .el-button {
- margin-left: 6px;
- }
- </style>
|