| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912 |
- <template>
- <div :class="prefixCls" class="case-data-page">
- <div class="page-main">
- <div class="header-bar">
- <div class="import-tabs" role="tablist" aria-label="导入数据页签">
- <button
- type="button"
- class="import-tabs__item"
- :class="{ 'is-active': activeTab === 'import' }"
- role="tab"
- :aria-selected="activeTab === 'import'"
- @click="activeTab = 'import'"
- >
- 导入数据
- </button>
- <button
- type="button"
- class="import-tabs__item"
- :class="{ 'is-active': activeTab === 'files' }"
- role="tab"
- :aria-selected="activeTab === 'files'"
- @click="activeTab = 'files'"
- >
- 导入文件
- </button>
- </div>
- <div class="header-actions">
- <Button v-if="cleanMode === 'smart'" type="primary" class="header-actions__smart" @click="goSmartAnalysis">
- 进行智能分析
- </Button>
- <Button v-else-if="cleanMode === 'manual'" class="header-actions__result" @click="activeTab = 'import'">
- 查看结果
- </Button>
- <Button type="primary" class="header-actions__upload" @click="handleUploadNavigate('single')"
- >清洗数据</Button
- >
- <Button type="primary" class="header-actions__govern" @click="startCalcTask">数据治理</Button>
- <span class="header-actions__divider" aria-hidden="true"></span>
- <Button class="header-actions__config" @click="handleOpenGovernConfig">治理设置</Button>
- </div>
- </div>
- <div v-show="activeTab === 'import'" class="page-layout">
- <aside class="left-panel">
- <div class="sidebar-content tree-scroll-shell">
- <BasicTree
- class="case-tree"
- :tree-data="treeData"
- :tree-data-simple-mode="false"
- :field-names="{ key: 'key', title: 'title', children: 'children' }"
- :search="true"
- :toolbar="true"
- :showIcon="true"
- expandAction="doubleclick"
- :default-expand-all="true"
- v-model:selectedKeys="selectedKeys"
- @select="handleTreeSelect"
- />
- </div>
- </aside>
- <section ref="rightPanelRef" class="right-panel">
- <div class="panel-header">
- <div>
- <div class="panel-title">数据结果列表</div>
- </div>
- <div class="table-actions">
- <TableSetting
- :key="resultTableSettingKey"
- class="table-setting-trigger"
- :setting="{ setting: true, size: true, redo: false, fullScreen: false }"
- />
- <Popover
- v-if="showCustomQueryButton"
- v-model:open="customQueryPopoverOpen"
- trigger="click"
- placement="bottomRight"
- :overlay-style="{ padding: '12px' }"
- >
- <template #content>
- <CustomSearch ref="customSearchRef" :table-column="columns" @search="handleCustomSearch" />
- </template>
- <Button type="primary">自定义查询</Button>
- </Popover>
- </div>
- </div>
- <div class="table-wrapper">
- <BasicTable
- :key="resultTableRenderKey"
- :columns="columns"
- :data-source="tableData"
- :pagination="false"
- :loading="loading"
- :show-table-setting="false"
- :table-setting="{ setting: true, size: true, redo: false, fullScreen: false }"
- :can-resize="true"
- :is-can-resize-parent="true"
- :resize-height-offset="32"
- :immediate="false"
- row-key="id"
- @change="handleResultTableChange"
- @register="handleResultTableRegister"
- />
- </div>
- <div class="table-footer">
- <div class="footer-right">
- <div class="total">共 {{ totalCount }} 条</div>
- <Select
- v-model:value="pageSize"
- class="page-size"
- :options="pageSizeOptions"
- :getPopupContainer="getPagePopupContainer"
- @change="handlePageSizeChange"
- />
- <Pagination
- v-model:current="currentPage"
- :total="totalCount"
- :page-size="pageSize"
- :show-size-changer="false"
- @change="handlePageChange"
- />
- <div class="jump">
- <span>前往</span>
- <Input v-model:value="jumpPage" class="jump-input" @pressEnter="handleJump" />
- <span>页</span>
- </div>
- </div>
- </div>
- </section>
- </div>
- <div v-show="activeTab === 'files'" class="file-panel">
- <div class="panel-header file-panel-header">
- <div>
- <div class="panel-title">导入文件管理</div>
- </div>
- </div>
- <div class="file-panel__table">
- <ImportList :active="activeTab === 'files'" />
- </div>
- </div>
- </div>
- <Modal
- v-model:open="governConfigVisible"
- title="治理配置"
- :centered="true"
- width="480px"
- :footer="null"
- wrapClassName="govern-config-modal"
- @cancel="governConfigVisible = false"
- >
- <div class="govern-config-panel">
- <Form layout="vertical" class="govern-config-form">
- <div class="govern-config-item">
- <Form.Item label="治理去掉重复数据">
- <RadioGroup v-model:value="governConfig.removeDuplicate" @change="scheduleGovernConfigSave">
- <Radio value="是">是</Radio>
- <Radio value="否">否</Radio>
- </RadioGroup>
- </Form.Item>
- </div>
- <div class="govern-config-item">
- <Form.Item>
- <template #label>
- <span class="govern-config-label">
- <span>通信关联开户销户时间</span>
- <Tooltip
- title="选择是后,则只分析对象开户时间之后的通信记录"
- :getPopupContainer="getModalPopupContainer"
- >
- <span class="config-tip-icon">?</span>
- </Tooltip>
- </span>
- </template>
- <RadioGroup v-model:value="governConfig.associateAccountTime" @change="scheduleGovernConfigSave">
- <Radio value="是">是</Radio>
- <Radio value="否">否</Radio>
- </RadioGroup>
- </Form.Item>
- </div>
- <div class="govern-config-item">
- <Form.Item>
- <template #label>
- <span class="govern-config-label">
- <span>导入数据立即治理</span>
- <Tooltip title="选择是后,导入数据后立即治理" :getPopupContainer="getModalPopupContainer">
- <span class="config-tip-icon">?</span>
- </Tooltip>
- </span>
- </template>
- <RadioGroup v-model:value="governConfig.skipGovernOnImport" @change="scheduleGovernConfigSave">
- <Radio value="是">是</Radio>
- <Radio value="否">否</Radio>
- </RadioGroup>
- </Form.Item>
- </div>
- </Form>
- </div>
- </Modal>
- <GovernProgressModal
- :open="governProgressVisible"
- @update:open="handleGovernProgressOpenChange"
- @finished="handleGovernTaskFinished"
- />
- <TransRecordModal v-model:open="transRecordModalOpen" title="交易记录" :base-query="transRecordBaseQuery" />
- <CallRecordModal v-model:open="callRecordModalOpen" title="通信记录" :base-query="callRecordBaseQuery" />
- </div>
- </template>
- <script lang="ts" setup name="ViewsDataIndex">
- import { computed, h, ref, nextTick, onActivated, onBeforeUnmount, onDeactivated, onMounted } from 'vue';
- import { Button, Select, Pagination, Input, Modal, Radio, Tooltip, Popover, Form, RadioGroup } from 'ant-design-vue';
- import { BasicTable, type BasicColumn } from '@/core/components/Table';
- import TableSetting from '@/core/components/Table/src/components/settings/index.vue';
- import { createTableContext } from '@/core/components/Table/src/hooks/useTableContext';
- import { BasicTree } from '@/core/components/Tree';
- import { useDesign } from '@/core/hooks/web/useDesign';
- import { useGo } from '@/core/hooks/web/usePage';
- import { PageEnum } from '@/core/enums/pageEnum';
- import { getModalPopupContainer as resolveModalPopupContainer } from '@/core/utils';
- import { useMessage } from '@/core/hooks/web/useMessage';
- import { useEmitter, useUserStore } from '@/core/store/modules/case';
- import { markAnalysisDataChanged } from '@/core/logics/analysisRefreshPrompt';
- import CustomSearch from './components/CustomSearch.vue';
- import GovernProgressModal from './components/GovernProgressModal.vue';
- import { getActiveImportProgress } from './importProgressSession';
- import {
- downloadOfficeFileById,
- getOpenOfficeFileErrorMessage,
- resolveOfficeFileId,
- } from '@/core/utils/openOfficeFile';
- import CallRecordModal from '@/call/views/components/CallRecordModal.vue';
- import TransRecordModal from '@/trans/views/components/TransRecordModal.vue';
- import {
- aiCleanDataPage,
- getGovernConfList,
- getTree,
- treeTablePage,
- getTreeTransPersonPage,
- getTreeTransBankPage,
- getTreeCallPersonPage,
- getTreeCallOperPage,
- saveGovernConf,
- } from '@/case/api/govern/governApi';
- import type { GovernConfItem } from '@/case/api/govern/governApi';
- import componentSetting from '@/core/settings/componentSetting';
- import ImportList from './importList.vue';
- import { DataTableDTO, GovernTree } from '@/case/api/govern/types/dto';
- import { GovernTreeQuery } from '@/case/api/govern/types/query';
- import type { CallRecordQuery } from '@/call/api/types/query';
- import type { TransRecordQuery } from '@/trans/api/types/query';
- const { prefixCls } = useDesign('case-data');
- const go = useGo();
- const emitter = useEmitter();
- const userStore = useUserStore();
- const CASE_DATA_REFRESH_EVENT = 'case-data:refresh';
- const CASE_IMPORT_FILE_REFRESH_EVENT = 'case-import-file:refresh';
- /**
- * 记录里「下载文件」需要的字段:fileId 回查后端取文件流,fileName 只用于展示与兜底命名。
- * 其余列由后端表头决定(同一行既可能是话单字段也可能是勘察字段),不参与判断。
- */
- interface DownloadableFileRecord {
- fileId?: ApiId | number | null;
- fileName?: ApiText | null;
- [column: string]: unknown;
- }
- interface TableItem {
- id: number;
- ownNumber: string;
- otherNumber: string;
- contactTime: string;
- otherName: string;
- customerName: string;
- lac: string;
- cellId: string;
- baseId: string;
- carrier: string;
- }
- interface GovernTreeNodeOption {
- title: string;
- key: string;
- level: number;
- dataRef: GovernTree;
- children: GovernTreeNodeOption[];
- }
- type GovernTreeBranchKind = 'call' | 'trans' | 'default';
- type GovernConfigValue = '是' | '否';
- interface GovernConfigFormState {
- removeDuplicate: GovernConfigValue;
- associateAccountTime: GovernConfigValue;
- skipGovernOnImport: GovernConfigValue;
- }
- type DataPageTabKey = 'import' | 'files';
- // 支持 ?tab=files 直达「导入文件」页签(仅作 URL 直连兜底;正常流程由清洗进度页经 sessionStorage 切换)
- const initialTab = new URLSearchParams(window.location.search).get('tab') === 'files' ? 'files' : 'import';
- const activeTab = ref<DataPageTabKey>(initialTab as DataPageTabKey);
- /** 清洗来源模式(?mode=smart|manual,仅作 URL 直连兜底):决定展示「进行智能分析」还是「查看结果」 */
- const readCleanMode = (): '' | 'smart' | 'manual' => {
- const mode = new URLSearchParams(window.location.search).get('mode');
- return mode === 'smart' || mode === 'manual' ? mode : '';
- };
- const cleanMode = ref<'' | 'smart' | 'manual'>(readCleanMode());
- /** 智能模式 CTA:去工作台新建对话,绑定默认研判方案进行分析 */
- const goSmartAnalysis = () => {
- go({ path: PageEnum.BASE_WORKBENCH, query: { newChat: '1' } });
- };
- const governConfigVisible = ref(false);
- const governProgressVisible = ref(false);
- const customQueryPopoverOpen = ref(false);
- const customSearchRef = ref<InstanceType<typeof CustomSearch> | null>(null);
- const customQueryParams = ref<any>(null);
- const saving = ref(false);
- const governConfigLoaded = ref(false);
- const createDefaultGovernConfig = (): GovernConfigFormState => ({
- removeDuplicate: '否',
- associateAccountTime: '否',
- skipGovernOnImport: '否',
- });
- const governConfig = ref<GovernConfigFormState>(createDefaultGovernConfig());
- const governConfigIdMap = ref<Record<string, string | number | undefined>>({});
- let governConfigSaveTimer: ReturnType<typeof setTimeout> | undefined;
- const { createMessage } = useMessage();
- const getPagePopupContainer = (triggerNode?: HTMLElement) =>
- (triggerNode?.closest('.case-data-page') as HTMLElement | null) || document.body;
- const getModalPopupContainer = (triggerNode?: HTMLElement) => resolveModalPopupContainer(triggerNode);
- const getTemplateCreatePopupContainer = () => document.body;
- let restoringImportProgress = false;
- const restoreActiveImportProgress = async () => {
- if (restoringImportProgress || !getActiveImportProgress(userStore.getCaseInfo?.id)) return;
- restoringImportProgress = true;
- try {
- await go(PageEnum.BASE_CLEAN_PROGRESS, true);
- } finally {
- restoringImportProgress = false;
- }
- };
- onActivated(() => {
- void restoreActiveImportProgress();
- // 页面被 keep-alive 缓存,重新激活时要重选页签。
- // 清洗进度页「查看结果」跳转不带任何 URL 参数,页签(导入文件)与清洗来源全走 sessionStorage
- // 一次性上下文;URL 参数只在直连(手输/收藏)时作兜底。
- let ctx: { tab?: string; mode?: string } | null = null;
- try {
- const raw = sessionStorage.getItem('case_clean_result_context');
- if (raw) {
- ctx = JSON.parse(raw);
- sessionStorage.removeItem('case_clean_result_context');
- }
- } catch (e) {
- ctx = null;
- }
- const tabQuery = (ctx && ctx.tab) || new URLSearchParams(window.location.search).get('tab');
- if (tabQuery === 'files' || tabQuery === 'import') {
- activeTab.value = tabQuery;
- }
- const modeQuery = (ctx && ctx.mode) || new URLSearchParams(window.location.search).get('mode');
- cleanMode.value = modeQuery === 'smart' || modeQuery === 'manual' ? modeQuery : '';
- });
- onDeactivated(() => {
- restoringImportProgress = false;
- });
- const handleUploadNavigate = (mode?: 'single' | 'folder') => {
- go({ path: PageEnum.BASE_UPLOAD, query: { mode: mode || 'single' } });
- };
- const getRequestErrorMessage = (error: any, fallback = '操作失败') =>
- error?.response?.data?.message || error?.message || fallback;
- const normalizeGovernConfValue = (value?: string | null): GovernConfigValue =>
- String(value ?? '')
- .trim()
- .toUpperCase() === 'Y'
- ? '是'
- : '否';
- const normalizeHasGovernToSkipValue = (value?: string | null): GovernConfigValue =>
- String(value ?? '')
- .trim()
- .toUpperCase() === 'Y'
- ? '是'
- : '否';
- const toGovernConfFlag = (value?: GovernConfigValue) => (value === '是' ? 'Y' : 'N');
- const toHasGovernFlag = (value?: GovernConfigValue) => (value === '是' ? 'Y' : 'N');
- const buildGovernConfPayloadItem = (confType: string, confStr: string): GovernConfItem => {
- const id = governConfigIdMap.value[confType];
- return id === undefined || id === null || String(id).trim() === ''
- ? { confType, confStr }
- : { id, confType, confStr };
- };
- const loadGovernConfig = async () => {
- const nextConfig = createDefaultGovernConfig();
- const nextIdMap: Record<string, string | number | undefined> = {};
- const list = await getGovernConfList();
- (Array.isArray(list) ? list : []).forEach((item) => {
- const confType = String(item?.confType ?? '').trim();
- if (!confType) return;
- nextIdMap[confType] = item?.id;
- if (confType === 'duplicate') {
- nextConfig.removeDuplicate = normalizeGovernConfValue(item?.confStr);
- } else if (confType === 'useOpenInfo') {
- nextConfig.associateAccountTime = normalizeGovernConfValue(item?.confStr);
- } else if (confType === 'hasGovern') {
- nextConfig.skipGovernOnImport = normalizeHasGovernToSkipValue(item?.confStr);
- }
- });
- governConfigIdMap.value = nextIdMap;
- governConfig.value = nextConfig;
- };
- const handleOpenGovernConfig = async () => {
- governConfigLoaded.value = false;
- governConfig.value = createDefaultGovernConfig();
- governConfigIdMap.value = {};
- governConfigVisible.value = true;
- try {
- await loadGovernConfig();
- } catch (error) {
- createMessage.error(getRequestErrorMessage(error, '获取治理配置失败'));
- } finally {
- governConfigLoaded.value = true;
- }
- };
- const handleSaveGovernConfig = async (options?: { closeAfterSave?: boolean; silentSuccess?: boolean }) => {
- const { closeAfterSave = true, silentSuccess = false } = options || {};
- saving.value = true;
- try {
- const payload: GovernConfItem[] = [
- buildGovernConfPayloadItem('duplicate', toGovernConfFlag(governConfig.value.removeDuplicate)),
- buildGovernConfPayloadItem('useOpenInfo', toGovernConfFlag(governConfig.value.associateAccountTime)),
- buildGovernConfPayloadItem('hasGovern', toHasGovernFlag(governConfig.value.skipGovernOnImport)),
- ];
- await saveGovernConf(payload);
- if (!silentSuccess) {
- createMessage.success('保存成功');
- }
- if (closeAfterSave) {
- governConfigVisible.value = false;
- }
- } catch (error) {
- createMessage.error(getRequestErrorMessage(error, '保存失败'));
- } finally {
- saving.value = false;
- }
- };
- const scheduleGovernConfigSave = () => {
- if (!governConfigLoaded.value) return;
- if (governConfigSaveTimer) {
- clearTimeout(governConfigSaveTimer);
- }
- governConfigSaveTimer = setTimeout(() => {
- governConfigSaveTimer = undefined;
- void handleSaveGovernConfig({ closeAfterSave: false, silentSuccess: true });
- }, 120);
- };
- const startCalcTask = () => {
- governProgressVisible.value = true;
- };
- const handleGovernTaskFinished = async () => {
- markAnalysisDataChanged();
- await fetchTreeData({ preserveActive: true });
- emitter.emit(CASE_IMPORT_FILE_REFRESH_EVENT);
- };
- const handleGovernProgressOpenChange = async (open: boolean) => {
- const wasOpen = governProgressVisible.value;
- governProgressVisible.value = open;
- if (!open && wasOpen) {
- await fetchTreeData({ preserveActive: true });
- }
- };
- const treeData = ref<GovernTreeNodeOption[]>([]);
- const columns = ref<BasicColumn[]>([]);
- const selectedKeys = ref<string[]>([]);
- const tableData = ref<TableItem[]>([]);
- const rightPanelRef = ref<HTMLElement | null>(null);
- const resultTableAction = ref<any>(null);
- const totalCount = ref(0);
- const loading = ref(false);
- const pageSize = ref(componentSetting.table.defaultPageSize);
- const lastPageSize = ref(pageSize.value);
- const currentPage = ref(1);
- const jumpPage = ref('1');
- const activeNode = ref<GovernTree | null>(null);
- const activeNodeType = ref<string | undefined>(undefined);
- const activeParentName = ref<string | undefined>(undefined);
- const activeNodeLevel = ref<number>(0);
- const activeBranchKind = ref<GovernTreeBranchKind>('default');
- const resultSortOrderKey = ref<string>('');
- const resultSortDirection = ref<'ASC' | 'DESC' | ''>('');
- const rawTableRecords = ref<Recordable[]>([]);
- const callRecordModalOpen = ref(false);
- const callRecordBaseQuery = ref<Partial<CallRecordQuery> & Record<string, any>>({});
- const transRecordModalOpen = ref(false);
- const transRecordBaseQuery = ref<Partial<TransRecordQuery> & Record<string, any>>({});
- const handleResultTableRegister = (action: any) => {
- resultTableAction.value = action;
- };
- const getResultTableColumns = (...args: any[]) => resultTableAction.value?.getColumns?.(...args) || [];
- const resultTableSettingKey = computed(() =>
- columns.value
- .map((column, index) => {
- const dataIndex = Array.isArray(column?.dataIndex)
- ? column.dataIndex.join('.')
- : String(column?.dataIndex ?? '').trim();
- const key = String(column?.key ?? '').trim();
- const title = String(column?.title ?? '').trim();
- return `${index}:${key || dataIndex || title}`;
- })
- .join('|'),
- );
- const resultTableRenderKey = computed(() =>
- [
- activeNodeLevel.value,
- activeNodeType.value || '',
- activeNode.value?.tableNameEn || activeNode.value?.tableNameCn || '',
- columns.value
- .map((column, index) => {
- const dataIndex = Array.isArray(column?.dataIndex)
- ? column.dataIndex.join('.')
- : String(column?.dataIndex ?? '').trim();
- const key = String(column?.key ?? '').trim();
- return `${index}:${key || dataIndex}:${column?.sorter ? 'S' : 'N'}:${column?.sortOrder || ''}`;
- })
- .join('|'),
- ].join('::'),
- );
- createTableContext({
- wrapRef: rightPanelRef,
- getBindValues: computed(() => ({
- columns: columns.value,
- showIndexColumn: true,
- rowSelection: undefined,
- isTreeTable: false,
- })),
- getDefaultRowSelection: () => resultTableAction.value?.getDefaultRowSelection?.(),
- getColumns: getResultTableColumns,
- getCacheColumns: () => resultTableAction.value?.getCacheColumns?.() || [],
- setColumns: (nextColumns: any) => resultTableAction.value?.setColumns?.(nextColumns),
- setProps: (props: any) => resultTableAction.value?.setProps?.(props),
- } as any);
- const clearTableState = () => {
- activeNode.value = null;
- activeNodeType.value = undefined;
- activeParentName.value = undefined;
- activeNodeLevel.value = 0;
- activeBranchKind.value = 'default';
- resultSortOrderKey.value = '';
- resultSortDirection.value = '';
- selectedKeys.value = [];
- rawTableRecords.value = [];
- tableData.value = [];
- totalCount.value = 0;
- currentPage.value = 1;
- jumpPage.value = '1';
- columns.value = defaultColumns.map((column) => applyResultSortProps(column));
- };
- const formatTreeTitle = (node: GovernTree) => {
- const name = node.tableNameCn ?? node.tableNameEn ?? '';
- const countText = typeof node.dataCount === 'number' ? ` (${node.dataCount})` : '';
- return `${name}${countText}` || `节点-${node.id ?? ''}`;
- };
- const normalizeGovernTreeKeyPart = (value: unknown) => {
- const text = String(value ?? '').trim();
- return (text || 'na').replace(/[|/]/g, '_');
- };
- const isSameGovernNode = (left?: GovernTree | null, right?: GovernTree | null) => {
- if (!left || !right) return false;
- return (
- String(left.id ?? '') === String(right.id ?? '') &&
- String(left.pid ?? '') === String(right.pid ?? '') &&
- String(left.type ?? '') === String(right.type ?? '') &&
- String(left.tableNameEn ?? '') === String(right.tableNameEn ?? '') &&
- String(left.tableNameCn ?? '') === String(right.tableNameCn ?? '')
- );
- };
- const buildGovernTreeNodeKey = (node: GovernTree, parentKey = 'root', index = 0) =>
- [
- parentKey,
- normalizeGovernTreeKeyPart(node.type),
- normalizeGovernTreeKeyPart(node.id),
- normalizeGovernTreeKeyPart(node.pid),
- normalizeGovernTreeKeyPart(node.tableNameEn ?? node.tableNameCn),
- String(index),
- ].join('/');
- const mapGovernTree = (nodes: GovernTree[] = [], level = 1, parentKey = 'root'): GovernTreeNodeOption[] =>
- nodes.map((node, index) => {
- const nodeKey = buildGovernTreeNodeKey(node, parentKey, index);
- return {
- title: formatTreeTitle(node),
- key: nodeKey,
- level,
- dataRef: node,
- children: mapGovernTree(node.children ?? [], level + 1, nodeKey),
- };
- });
- const flattenGovernNodes = (nodes: GovernTree[] = []): GovernTree[] =>
- nodes.reduce<GovernTree[]>((result, node) => {
- result.push(node);
- if (node.children?.length) {
- result.push(...flattenGovernNodes(node.children));
- }
- return result;
- }, []);
- const findGovernTreeSelectionByKey = (
- nodes: GovernTreeNodeOption[],
- key: string,
- parent?: GovernTreeNodeOption,
- ancestors: GovernTreeNodeOption[] = [],
- ): { node: GovernTreeNodeOption; parent?: GovernTreeNodeOption; ancestors: GovernTreeNodeOption[] } | undefined => {
- for (const item of nodes) {
- if (item.key === key) return { node: item, parent, ancestors };
- if (item.children?.length) {
- const matched = findGovernTreeSelectionByKey(item.children, key, item, [...ancestors, item]);
- if (matched) return matched;
- }
- }
- return undefined;
- };
- const resolveGovernTreeBranchKind = (node?: GovernTree | null): GovernTreeBranchKind => {
- const branchText = `${String(node?.tableNameCn ?? '')} ${String(node?.tableNameEn ?? '')}`.toLowerCase();
- if (
- branchText.includes('通讯') ||
- branchText.includes('通信') ||
- branchText.includes('电话') ||
- branchText.includes('通话') ||
- branchText.includes('短信') ||
- branchText.includes('运营商') ||
- branchText.includes('call') ||
- branchText.includes('sms') ||
- branchText.includes('oper')
- ) {
- return 'call';
- }
- if (
- branchText.includes('银行') ||
- branchText.includes('交易') ||
- branchText.includes('资金') ||
- branchText.includes('trans') ||
- branchText.includes('bank')
- ) {
- return 'trans';
- }
- return 'default';
- };
- const resolveGovernTreeKey = (target?: GovernTree | null, nodes: GovernTreeNodeOption[] = treeData.value): string => {
- if (!target) return '';
- for (const item of nodes) {
- if (isSameGovernNode(item.dataRef, target)) return item.key;
- if (item.children?.length) {
- const childKey = resolveGovernTreeKey(target, item.children);
- if (childKey) return childKey;
- }
- }
- return '';
- };
- const initTreeSelection = (nodes: GovernTreeNodeOption[]) => {
- if (!nodes.length) return;
- const firstKey = nodes[0]?.key;
- if (firstKey) {
- selectedKeys.value = [String(firstKey)];
- }
- };
- const templateCategoryOptions = ref([
- { label: '全部', value: '全部' },
- { label: '金融类', value: '金融类' },
- { label: '工商类', value: '工商类' },
- ]);
- const updateTemplateCategories = (nodes: GovernTree[] = []) => {
- const categories = new Set<string>();
- flattenGovernNodes(nodes).forEach((node) => {
- if (node.type) {
- categories.add(node.type);
- }
- });
- const dynamic = Array.from(categories).map((item) => ({ label: item, value: item }));
- templateCategoryOptions.value = [{ label: '全部', value: '全部' }, ...dynamic];
- };
- const toTableRows = (records: Recordable[]): TableItem[] =>
- records.map((item, index) => ({
- id: (item as any).id ?? index + 1,
- ownNumber: (item as any).personPhone ?? (item as any).phoneNumber ?? (item as any).ownNumber ?? '-',
- otherNumber: (item as any).otherPhone ?? (item as any).otherCardNo ?? (item as any).otherNumber ?? '-',
- contactTime: (item as any).contactTime ?? (item as any).callDate ?? (item as any).transDate ?? '-',
- otherName: (item as any).otherName ?? '-',
- customerName: (item as any).customerName ?? (item as any).personName ?? '-',
- lac: (item as any).lac ?? (item as any).lacCode ?? '-',
- cellId: (item as any).cellId ?? (item as any).cellid ?? '-',
- baseId: (item as any).baseId ?? (item as any).stationId ?? '-',
- carrier: (item as any).carrier ?? (item as any).operatorName ?? '-',
- }));
- const extractTablePayload = (payload: DataTableDTO) => {
- if (payload.pages) {
- const pages = payload.pages as any;
- const total = Number(pages.total ?? pages.count ?? 0);
- return {
- list: pages.records ?? [],
- total,
- head: payload.head,
- };
- }
- if (Array.isArray(payload.list)) {
- return {
- list: payload.list ?? [],
- total: Number((payload as any).total ?? (payload as any).count ?? payload.list?.length ?? 0),
- head: payload.head,
- };
- }
- return {
- list: [],
- total: 0,
- head: payload.head,
- };
- };
- const normalizeDetailQueryText = (value?: string | number | null) => {
- const text = String(value ?? '').trim();
- if (!text || text === '-' || text === 'null' || text === 'undefined') return undefined;
- return text;
- };
- const toSingleQueryArray = (value?: string | number | null) => {
- const text = normalizeDetailQueryText(value);
- return text ? [text] : undefined;
- };
- const isTradeCountColumn = (column: BasicColumn) => {
- const title = String(column?.title ?? '').trim();
- const key = String(column?.key ?? '').trim();
- const dataIndex = String(column?.dataIndex ?? '').trim();
- return title === '交易总次数' || key === 'transCount' || dataIndex === 'transCount';
- };
- const resolveCallCountColumnDirection = (column: BasicColumn) => {
- const title = String(column?.title ?? '').trim();
- const key = String(column?.key ?? '')
- .trim()
- .toLowerCase();
- const dataIndex = String(column?.dataIndex ?? '')
- .trim()
- .toLowerCase();
- if (
- title === '主叫总次数' ||
- key.includes('maincall') ||
- key.includes('outcall') ||
- dataIndex.includes('maincall')
- ) {
- return 'CALL_OUT';
- }
- if (
- title === '被叫总次数' ||
- key.includes('calledcall') ||
- key.includes('incall') ||
- dataIndex.includes('calledcall')
- ) {
- return 'CALL_IN';
- }
- return undefined;
- };
- const getCallCountColumnDrilldown = (column: BasicColumn) => {
- const title = String(column?.title ?? '').trim();
- const key = String(column?.key ?? '')
- .trim()
- .toLowerCase();
- const dataIndex = String(column?.dataIndex ?? '')
- .trim()
- .toLowerCase();
- const isCallCountColumn =
- title === '通话总次数' ||
- key === 'callcount' ||
- key === 'totalcallcount' ||
- dataIndex === 'callcount' ||
- dataIndex === 'totalcallcount';
- if (isCallCountColumn) {
- return {
- direction: resolveCallCountColumnDirection(column),
- };
- }
- const currentNodeLabel = `${activeParentName.value ?? ''} ${activeNode.value?.tableNameCn ?? ''} ${
- activeNode.value?.tableNameEn ?? ''
- }`
- .toLowerCase()
- .trim();
- const isSmsContext = currentNodeLabel.includes('短信') || currentNodeLabel.includes('sms');
- const isSmsContactCountColumn =
- title === '联系总次数' ||
- key === 'contactcount' ||
- key === 'smscount' ||
- key === 'totalsmscount' ||
- dataIndex === 'contactcount' ||
- dataIndex === 'smscount' ||
- dataIndex === 'totalsmscount';
- if (!isSmsContext || !isSmsContactCountColumn) return undefined;
- return {
- smsOnly: true,
- };
- };
- const canOpenTradeRecordModal = () => ['trans_person', 'bank'].includes(String(activeNodeType.value ?? ''));
- const canOpenCallRecordModal = () => ['call_person', 'yys'].includes(String(activeNodeType.value ?? ''));
- const shouldAppendOpenFileActionColumn = () => activeNodeLevel.value === 1 || activeNodeLevel.value === 2;
- const openTradeRecordModal = (record: Recordable) => {
- if (!record || !canOpenTradeRecordModal()) return;
- const personName = record.personName ?? activeParentName.value ?? activeNode.value?.tableNameCn;
- const personCardNo = record.personCardNo ?? record.personAccount ?? record.cardNo;
- const bankName = activeNodeType.value === 'bank' ? activeNode.value?.tableNameCn : record.bankName;
- const transCount = Number(record.transCount ?? record['交易总次数']);
- transRecordBaseQuery.value = {
- personName: personName || undefined,
- personCardNo: personCardNo || undefined,
- bankName: bankName || undefined,
- otherName: record.otherName || undefined,
- otherCardNo: record.otherCardNo || undefined,
- transCount: Number.isFinite(transCount) ? transCount : undefined,
- };
- transRecordModalOpen.value = true;
- };
- const openCallRecordModalByColumn = (record: Recordable, column: BasicColumn) => {
- if (!record || !canOpenCallRecordModal()) return;
- const personName =
- normalizeDetailQueryText(record.personName ?? record.customerName) ||
- normalizeDetailQueryText(activeParentName.value) ||
- normalizeDetailQueryText(activeNode.value?.tableNameCn);
- const personPhone = normalizeDetailQueryText(record.personPhone ?? record.phoneNumber ?? record.ownNumber);
- const otherName = normalizeDetailQueryText(record.otherName);
- const otherPhone = normalizeDetailQueryText(record.otherPhone ?? record.otherNumber);
- const operatorId =
- normalizeDetailQueryText(record.operatorId ?? record.operName ?? record.operatorName) ||
- (activeNodeType.value === 'yys' ? normalizeDetailQueryText(activeNode.value?.tableNameCn) : undefined);
- const drilldown = getCallCountColumnDrilldown(column);
- const nodeTableName = normalizeDetailQueryText(activeNode.value?.tableNameEn);
- const nodeTableNameCn = normalizeDetailQueryText(activeNode.value?.tableNameCn);
- const nodeType = normalizeDetailQueryText(activeNodeType.value);
- callRecordBaseQuery.value = {
- personName,
- personPhones: toSingleQueryArray(personPhone),
- otherName,
- otherPhones: toSingleQueryArray(otherPhone),
- operatorId,
- callDirection: drilldown?.direction,
- tableName: nodeTableName,
- tableNameCn: nodeTableNameCn,
- type: nodeType,
- personType: activeNodeType.value === 'call_person' ? nodeType : undefined,
- operName: activeNodeType.value === 'yys' ? nodeTableNameCn : undefined,
- smsOnly: drilldown?.smsOnly,
- };
- callRecordModalOpen.value = true;
- };
- /**
- * 下载记录关联的源文件(Web 化:只传文件ID,服务端查库定位文件流,浏览器另存)。
- *
- * 下载落盘走统一出口(桌面端由下载管理器写到你配置的目录,见 core/utils/file/download.ts)。
- */
- const handleDownloadRecordFile = async (record: DownloadableFileRecord) => {
- const fileId = resolveOfficeFileId(record);
- if (!fileId) {
- createMessage.warning('当前记录没有关联的源文件,无法下载');
- return;
- }
- try {
- await downloadOfficeFileById(fileId, String(record.fileName ?? '').trim() || '文件');
- } catch (error) {
- createMessage.error(getOpenOfficeFileErrorMessage(error, '文件下载失败'));
- }
- };
- const createOpenFileActionColumn = (): BasicColumn => ({
- title: '操作',
- dataIndex: 'openFileAction',
- key: 'openFileAction',
- width: 120,
- fixed: 'right',
- align: 'center',
- customRender: ({ record }: { record: DownloadableFileRecord }) => {
- // 不按 fileId 置灰:汇总行没有来源文件时由 handleDownloadRecordFile 明确提示原因
- return h(
- Button,
- {
- type: 'link',
- size: 'small',
- onClick: (event: MouseEvent) => {
- event.stopPropagation();
- void handleDownloadRecordFile(record);
- },
- },
- { default: () => '下载文件' },
- );
- },
- });
- const getDrilldownDisplayText = (record: Recordable, column: BasicColumn, text: unknown) => {
- const dataIndex = typeof column?.dataIndex === 'string' ? column.dataIndex : '';
- const key = typeof column?.key === 'string' ? column.key : '';
- const title = String(column?.title ?? '').trim();
- const candidateValues = [
- text,
- dataIndex ? (record as any)?.[dataIndex] : undefined,
- key ? (record as any)?.[key] : undefined,
- title ? (record as any)?.[title] : undefined,
- ];
- for (const item of candidateValues) {
- if (item === 0) return '0';
- if (item === null || item === undefined) continue;
- if (typeof item === 'object') continue;
- const normalized = String(item).trim();
- if (normalized) return normalized;
- }
- return '-';
- };
- const normalizeResultColumnDataIndex = (column: BasicColumn, fallbackIndex: number) => {
- if (Array.isArray(column?.dataIndex) && column.dataIndex.length) {
- return column.dataIndex;
- }
- const dataIndex = typeof column?.dataIndex === 'string' ? column.dataIndex.trim() : '';
- if (dataIndex) return dataIndex;
- const titleText = typeof column?.title === 'string' ? column.title.trim() : '';
- if (titleText) return titleText;
- return `column_${fallbackIndex + 1}`;
- };
- const normalizeResultColumns = (head: BasicColumn[] = [], parentKey = 'column') =>
- head.map((column, index) => {
- const resolvedDataIndex = normalizeResultColumnDataIndex(column, index);
- const fallbackKey =
- `${parentKey}_${index + 1}_` +
- String(Array.isArray(resolvedDataIndex) ? resolvedDataIndex.join('_') : resolvedDataIndex)
- .trim()
- .replace(/\s+/g, '_');
- const resolvedKey = (typeof column?.key === 'string' && column.key.trim()) || fallbackKey;
- return {
- ...column,
- dataIndex: resolvedDataIndex,
- key: resolvedKey,
- children: Array.isArray(column?.children)
- ? normalizeResultColumns(column.children as BasicColumn[], resolvedKey)
- : column.children,
- };
- });
- const resolveSortableColumnField = (column: BasicColumn) => {
- if (typeof column?.dataIndex === 'string' && column.dataIndex.trim()) return column.dataIndex.trim();
- if (Array.isArray(column?.dataIndex) && column.dataIndex.length) return String(column.dataIndex[0] ?? '').trim();
- return String(column?.key ?? '').trim();
- };
- const getColumnSearchText = (column: BasicColumn) => {
- const title = String(column?.title ?? '')
- .trim()
- .toLowerCase();
- const key = String(column?.key ?? '')
- .trim()
- .toLowerCase();
- const dataIndex = Array.isArray(column?.dataIndex)
- ? column.dataIndex.join('_').trim().toLowerCase()
- : String(column?.dataIndex ?? '')
- .trim()
- .toLowerCase();
- return `${title}|${key}|${dataIndex}`;
- };
- const isSecondLevelCallNodeSortable = () =>
- activeNodeLevel.value === 2 && String(activeNodeType.value ?? '') === 'call_person';
- const isSecondLevelTradeNodeSortable = () =>
- activeNodeLevel.value === 2 && String(activeNodeType.value ?? '') === 'trans_person';
- const isTimeSortColumn = (column: BasicColumn) => {
- const text = getColumnSearchText(column);
- return (
- text.includes('时间') ||
- text.includes('日期') ||
- text.includes('datetime') ||
- text.includes('date') ||
- text.includes('time')
- );
- };
- const isAmountSortColumn = (column: BasicColumn) => {
- const text = getColumnSearchText(column);
- return (
- text.includes('金额') ||
- text.includes('余额') ||
- text.includes('amount') ||
- text.includes('money') ||
- text.includes('balance')
- );
- };
- const shouldEnableResultSorter = (column: BasicColumn) => {
- if (isSecondLevelCallNodeSortable()) {
- return isTimeSortColumn(column);
- }
- if (isSecondLevelTradeNodeSortable()) {
- return isAmountSortColumn(column) || isTimeSortColumn(column);
- }
- return false;
- };
- const toggleResultSort = (field: string) => {
- if (!field) return;
- if (resultSortOrderKey.value !== field) {
- resultSortOrderKey.value = field;
- resultSortDirection.value = 'DESC';
- } else if (resultSortDirection.value === 'DESC') {
- resultSortDirection.value = 'ASC';
- } else if (resultSortDirection.value === 'ASC') {
- resultSortOrderKey.value = '';
- resultSortDirection.value = '';
- } else {
- resultSortDirection.value = 'DESC';
- }
- currentPage.value = 1;
- jumpPage.value = '1';
- fetchByActiveNode();
- };
- const applyResultSortProps = (column: BasicColumn): BasicColumn => {
- const nextColumn: BasicColumn = {
- ...column,
- children: Array.isArray(column?.children)
- ? (column.children as BasicColumn[]).map((child) => applyResultSortProps(child))
- : column.children,
- };
- if (nextColumn.children?.length) {
- return nextColumn;
- }
- if (!shouldEnableResultSorter(nextColumn)) {
- return nextColumn;
- }
- const sortField = resolveSortableColumnField(nextColumn);
- const currentSortOrder =
- sortField && sortField === resultSortOrderKey.value
- ? resultSortDirection.value === 'ASC'
- ? 'ascend'
- : resultSortDirection.value === 'DESC'
- ? 'descend'
- : undefined
- : undefined;
- const customHeaderCell = nextColumn.customHeaderCell;
- return {
- ...nextColumn,
- ellipsis: false,
- sorter: true,
- sortOrder: currentSortOrder,
- customHeaderCell: (col: any) => {
- const baseCell = typeof customHeaderCell === 'function' ? customHeaderCell(col) : {};
- return {
- ...(baseCell || {}),
- class: [baseCell?.class, 'case-data-sortable-header'].filter(Boolean).join(' '),
- onClick: (event: any) => {
- if (typeof baseCell?.onClick === 'function') {
- baseCell.onClick(event);
- }
- event.stopPropagation();
- toggleResultSort(sortField);
- },
- };
- },
- };
- };
- const enhanceColumns = (head: BasicColumn[]) => {
- const normalizedColumns = normalizeResultColumns(head ?? []).map((column) => {
- const callDrilldown = getCallCountColumnDrilldown(column);
- const sortableColumn = applyResultSortProps(column);
- const isActionColumn = String(sortableColumn?.title ?? '').trim() === '操作';
- if (isActionColumn) {
- return {
- ...sortableColumn,
- width: Math.max(Number(sortableColumn.width ?? 0) || 0, 180),
- customRender: ({ record, text }: { record: DownloadableFileRecord; text?: unknown }) => {
- const rawDisplayText = text && typeof text !== 'object' ? String(text).trim() : text === 0 ? '0' : '';
- return h(
- 'div',
- {
- style: {
- display: 'inline-flex',
- alignItems: 'center',
- gap: '8px',
- justifyContent: 'center',
- flexWrap: 'wrap',
- },
- },
- [
- rawDisplayText ? h('span', rawDisplayText) : null,
- // 不按 fileId 置灰:汇总行没有来源文件时由 handleDownloadRecordFile 明确提示原因
- h(
- Button,
- {
- type: 'link',
- size: 'small',
- onClick: (event: MouseEvent) => {
- event.stopPropagation();
- void handleDownloadRecordFile(record);
- },
- },
- { default: () => '下载文件' },
- ),
- ],
- );
- },
- };
- }
- if (!isTradeCountColumn(column) && !callDrilldown) return sortableColumn;
- return {
- ...sortableColumn,
- customRender: ({ record, text }: any) => {
- const canOpenTrade = isTradeCountColumn(column) && canOpenTradeRecordModal();
- const canOpenCall = !!callDrilldown && canOpenCallRecordModal();
- const displayText = getDrilldownDisplayText(record, column, text);
- if (!canOpenTrade && !canOpenCall) {
- return displayText;
- }
- return h(
- Button,
- {
- type: 'link',
- size: 'small',
- onClick: (event: MouseEvent) => {
- event.stopPropagation();
- if (isTradeCountColumn(column)) {
- openTradeRecordModal(record);
- return;
- }
- openCallRecordModalByColumn(record, column);
- },
- },
- { default: () => displayText },
- );
- },
- };
- });
- if (!shouldAppendOpenFileActionColumn()) {
- return normalizedColumns;
- }
- const hasActionColumn = normalizedColumns.some((column) => String(column?.title ?? '').trim() === '操作');
- return hasActionColumn ? normalizedColumns : [...normalizedColumns, createOpenFileActionColumn()];
- };
- const applyTableData = (payload: DataTableDTO) => {
- const { list, total, head } = extractTablePayload(payload);
- rawTableRecords.value = Array.isArray(list) ? list : [];
- // 优先使用API返回的表头数据
- if (head?.length) {
- columns.value = enhanceColumns(head);
- // 如果有表头数据,直接使用原始记录
- tableData.value = rawTableRecords.value as TableItem[];
- } else {
- // 如果没有表头数据,使用默认列并转换数据格式
- const baseColumns = defaultColumns.map((column) => applyResultSortProps(column));
- columns.value = shouldAppendOpenFileActionColumn() ? [...baseColumns, createOpenFileActionColumn()] : baseColumns;
- tableData.value = toTableRows(rawTableRecords.value);
- }
- // 设置总条数
- totalCount.value = total ?? 0;
- // 确保jumpPage与currentPage同步
- jumpPage.value = currentPage.value.toString();
- void nextTick(() => {
- resultTableAction.value?.setColumns?.(columns.value);
- });
- };
- const handleResultTableChange = () => {};
- const buildResultSortQuery = () =>
- resultSortOrderKey.value && resultSortDirection.value
- ? {
- orderKey: resultSortOrderKey.value,
- sort: resultSortDirection.value,
- }
- : {};
- const fetchByNode = (node?: GovernTree, resetPage = true) => {
- if (!node) return;
- activeNode.value = node;
- activeNodeType.value = node.type;
- activeParentName.value = undefined;
- activeNodeLevel.value = 1;
- const selectedKey = resolveGovernTreeKey(node);
- selectedKeys.value = selectedKey ? [selectedKey] : [];
- if (resetPage) {
- currentPage.value = 1;
- }
- fetchTableData({
- page: currentPage.value,
- limit: pageSize.value,
- tableName: node.tableNameEn,
- pid: node.pid,
- type: node.type,
- personName: node.tableNameCn,
- conditionSql: customQueryParams.value ?? undefined,
- ...buildResultSortQuery(),
- });
- };
- /** 治理树上 id<0 的节点是 AI 动态表(真实 id 取负),既有 /gt/treeTablePage 走实体注册查不到它 */
- const isAiTableNode = (node?: GovernTree | null) => Number(node?.id ?? 0) < 0;
- /**
- * AI 动态表对应的 ai_clean_template.id(字符串)。
- * 不能用 -Number(node.id):雪花 id 是 19 位,超过 JS 安全整数,JSON number 解析时就已经丢精度
- * (实测 ...0003968 会变成 ...0004000),回传后端就查不到表。后端已额外下发字符串版 aiTemplateId。
- */
- const resolveAiTemplateId = (node?: GovernTree | null): string => {
- const precise = String(node?.aiTemplateId ?? '').trim();
- if (precise) {
- return precise;
- }
- // 兜底:从物理表名反解(ai_clean_{id} / ai_t{id})
- const matched = /^ai_(?:clean_|t)(\d+)$/.exec(String(node?.tableNameEn ?? '').trim());
- if (matched?.[1]) {
- return matched[1];
- }
- return String(node?.id ?? '').replace(/^-/, '');
- };
- const fetchTableData = async (params: GovernTreeQuery = {}) => {
- loading.value = true;
- try {
- // 翻页/排序/搜索都从这一个漏斗走,所以分流也放在这里:只换请求函数,
- // 表头与分页仍由 applyTableData 统一处理(/aiClean/data/page 的返回结构与 treeTablePage 一致)
- const aiParams: {
- aiTemplateId: string;
- orderKey?: string;
- asc?: boolean;
- page?: number;
- size?: number;
- } = {
- aiTemplateId: resolveAiTemplateId(activeNode.value),
- page: params.page,
- size: params.limit,
- };
- // 没排序时不要给 orderKey 占位:请求层会把空值序列化成字符串 "null",
- // 后端拼成 ORDER BY null 会让整页查询直接失败(返空)
- if (params.orderKey && params.sort) {
- aiParams.orderKey = params.orderKey;
- aiParams.asc = params.sort !== 'DESC';
- }
- const res = isAiTableNode(activeNode.value) ? await aiCleanDataPage(aiParams) : await treeTablePage(params);
- applyTableData(res);
- } catch (error) {
- console.error('获取治理树表格数据失败', error);
- tableData.value = [];
- totalCount.value = 0;
- } finally {
- loading.value = false;
- }
- };
- const fetchSecondNodeTrans = async (node?: GovernTree, resetPage = true) => {
- if (!node) return;
- activeNode.value = node;
- activeNodeType.value = node.type;
- activeParentName.value = undefined;
- activeNodeLevel.value = 2;
- const selectedKey = resolveGovernTreeKey(node);
- selectedKeys.value = selectedKey ? [selectedKey] : [];
- if (resetPage) {
- currentPage.value = 1;
- }
- loading.value = true;
- const params = {
- page: currentPage.value,
- limit: pageSize.value,
- tableName: node.tableNameEn,
- pid: node.pid,
- type: node.type,
- personName: node.tableNameCn,
- conditionSql: customQueryParams.value ?? undefined,
- ...buildResultSortQuery(),
- };
- try {
- const res = await getTreeTransPersonPage(params);
- applyTableData(res);
- } catch (error) {
- console.error('获取治理树表格数据失败', error);
- tableData.value = [];
- totalCount.value = tableData.value.length;
- } finally {
- loading.value = false;
- }
- };
- const fetchTrirdNodeTrans = async (node?: GovernTree, parentName?: string, resetPage = true) => {
- if (!node) return;
- activeNode.value = node;
- activeNodeType.value = node.type;
- activeParentName.value = parentName;
- activeNodeLevel.value = 3;
- const selectedKey = resolveGovernTreeKey(node);
- selectedKeys.value = selectedKey ? [selectedKey] : [];
- if (resetPage) {
- currentPage.value = 1;
- }
- loading.value = true;
- const params = {
- page: currentPage.value,
- limit: pageSize.value,
- tableName: node.tableNameEn,
- pid: node.pid,
- type: node.type,
- personName: parentName,
- bankName: node.tableNameCn,
- conditionSql: customQueryParams.value ?? undefined,
- ...buildResultSortQuery(),
- };
- try {
- const res = await getTreeTransBankPage(params);
- applyTableData(res);
- } catch (error) {
- console.error('获取治理树表格数据失败', error);
- tableData.value = [];
- totalCount.value = 0;
- } finally {
- loading.value = false;
- }
- };
- const fetchSecondNodeCall = async (node?: GovernTree, resetPage = true) => {
- if (!node) return;
- activeNode.value = node;
- activeNodeType.value = node.type;
- activeParentName.value = undefined;
- activeNodeLevel.value = 2;
- const selectedKey = resolveGovernTreeKey(node);
- selectedKeys.value = selectedKey ? [selectedKey] : [];
- if (resetPage) {
- currentPage.value = 1;
- }
- loading.value = true;
- const params = {
- page: currentPage.value,
- limit: pageSize.value,
- tableName: node.tableNameEn,
- pid: node.pid,
- type: node.type,
- personName: node.tableNameCn,
- personType: node.type,
- conditionSql: customQueryParams.value ?? undefined,
- ...buildResultSortQuery(),
- };
- try {
- const res = await getTreeCallPersonPage(params);
- applyTableData(res);
- } catch (error) {
- console.error('获取治理树表格数据失败', error);
- tableData.value = [];
- totalCount.value = 0;
- } finally {
- loading.value = false;
- }
- };
- const fetchTrirdNodeCall = async (node?: GovernTree, parentName?: string, resetPage = true) => {
- if (!node) return;
- activeNode.value = node;
- activeNodeType.value = node.type;
- activeParentName.value = parentName;
- activeNodeLevel.value = 3;
- const selectedKey = resolveGovernTreeKey(node);
- selectedKeys.value = selectedKey ? [selectedKey] : [];
- if (resetPage) {
- currentPage.value = 1;
- }
- loading.value = true;
- const params = {
- page: currentPage.value,
- limit: pageSize.value,
- tableName: node.tableNameEn,
- pid: node.pid,
- type: node.type,
- personName: parentName,
- operName: node.tableNameCn,
- conditionSql: customQueryParams.value ?? undefined,
- ...buildResultSortQuery(),
- };
- try {
- const res = await getTreeCallOperPage(params);
- applyTableData(res);
- } catch (error) {
- console.error('获取治理树表格数据失败', error);
- tableData.value = [];
- totalCount.value = 0;
- } finally {
- loading.value = false;
- }
- };
- const fetchByTreeSelection = (
- node?: GovernTree,
- level = 1,
- parentName?: string,
- branchKind: GovernTreeBranchKind = 'default',
- resetPage = true,
- ) => {
- if (!node) return;
- activeBranchKind.value = branchKind;
- if (level <= 1) {
- fetchByNode(node, resetPage);
- return;
- }
- if (level === 2) {
- if (branchKind === 'call') {
- fetchSecondNodeCall(node, resetPage);
- } else if (branchKind === 'trans') {
- fetchSecondNodeTrans(node, resetPage);
- } else {
- fetchByNode(node, resetPage);
- }
- return;
- }
- if (branchKind === 'call') {
- fetchTrirdNodeCall(node, parentName, resetPage);
- } else if (branchKind === 'trans') {
- fetchTrirdNodeTrans(node, parentName, resetPage);
- } else {
- fetchByNode(node, resetPage);
- }
- };
- const handleTreeSelect = (keys: Array<string | number>) => {
- if (!keys.length) return;
- // 切换节点时清空自定义查询条件
- customQueryParams.value = null;
- customSearchRef.value?.handleReset({ silent: true });
- resultSortOrderKey.value = '';
- resultSortDirection.value = '';
- const selectedKey = String(keys[0] ?? '');
- const matchedSelection = findGovernTreeSelectionByKey(treeData.value, selectedKey);
- const node = matchedSelection?.node?.dataRef;
- const level = matchedSelection?.node?.level ?? 1;
- const parentName = matchedSelection?.parent?.dataRef?.tableNameCn as string | undefined;
- const rootNode = matchedSelection?.ancestors?.[0]?.dataRef ?? node;
- const branchKind = resolveGovernTreeBranchKind(rootNode);
- fetchByTreeSelection(node, level, parentName, branchKind);
- };
- const fetchByActiveNode = () => {
- if (!activeNode.value) return;
- fetchByTreeSelection(
- activeNode.value,
- activeNodeLevel.value || 1,
- activeParentName.value,
- activeBranchKind.value,
- false,
- );
- };
- const handlePageChange = (page: number) => {
- currentPage.value = page;
- jumpPage.value = page.toString(); // 同步跳转页码输入框
- fetchByActiveNode();
- };
- const handleJump = () => {
- let target = Math.max(1, Number(jumpPage.value) || 1);
- // 计算最大页数
- const maxPage = Math.max(1, Math.ceil(totalCount.value / pageSize.value));
- if (target > maxPage) {
- target = maxPage;
- jumpPage.value = target.toString();
- }
- if (target !== currentPage.value) {
- currentPage.value = target;
- fetchByActiveNode();
- }
- };
- const handlePageSizeChange = (value: any) => {
- const nextValue = Number(value ?? lastPageSize.value);
- if (!Number.isFinite(nextValue) || nextValue === lastPageSize.value) return;
- lastPageSize.value = nextValue;
- pageSize.value = nextValue;
- currentPage.value = 1;
- jumpPage.value = '1'; // 重置跳转页码
- // 如果总条数小于等于每页条数,重置到第一页
- if (totalCount.value <= nextValue) {
- currentPage.value = 1;
- }
- fetchByActiveNode();
- };
- const showCustomQueryButton = computed(() => {
- return columns.value.length > 0 && activeNodeLevel.value > 0 && activeNodeLevel.value <= 2;
- });
- const handleCustomSearch = (val: any) => {
- customQueryParams.value = val;
- currentPage.value = 1;
- jumpPage.value = '1';
- fetchByActiveNode();
- };
- const findGovernNodeByCurrent = (nodes: GovernTree[]) => {
- const current = activeNode.value;
- if (!current?.id) return undefined;
- return flattenGovernNodes(nodes).find((node) => isSameGovernNode(node, current));
- };
- const fetchTreeData = async (options: { preserveActive?: boolean } = {}) => {
- try {
- const data = await getTree();
- const source = data;
- const mappedTree = mapGovernTree(source);
- treeData.value = mappedTree;
- updateTemplateCategories(source);
- if (!mappedTree.length) {
- clearTableState();
- return;
- }
- const matchedNode = options.preserveActive ? findGovernNodeByCurrent(source) : undefined;
- if (matchedNode) {
- activeNode.value = matchedNode;
- activeNodeLevel.value = 0;
- const matchedKey = resolveGovernTreeKey(matchedNode, mappedTree);
- selectedKeys.value = matchedKey ? [matchedKey] : [];
- fetchByActiveNode();
- return;
- }
- initTreeSelection(mappedTree);
- const firstNode = mappedTree[0]?.dataRef as GovernTree | undefined;
- if (firstNode) {
- fetchByNode(firstNode);
- }
- } catch (error) {
- console.error('获取治理树失败', error);
- treeData.value = [];
- clearTableState();
- }
- };
- const handleCaseDataRefresh = () => {
- // 从清洗完成跳转而来时(cleanMode 非空)保留跳转指定的页签(导入文件统计),不强切回导入数据
- if (!cleanMode.value) {
- activeTab.value = 'import';
- }
- customQueryParams.value = null;
- clearTableState();
- void fetchTreeData();
- };
- onMounted(() => {
- emitter.on(CASE_DATA_REFRESH_EVENT, handleCaseDataRefresh);
- jumpPage.value = currentPage.value.toString();
- fetchTreeData();
- void restoreActiveImportProgress();
- });
- onBeforeUnmount(() => {
- emitter.off(CASE_DATA_REFRESH_EVENT, handleCaseDataRefresh, true);
- });
- const defaultColumns: BasicColumn[] = [
- { title: '本方号码', dataIndex: 'ownNumber', key: 'ownNumber', width: 120 },
- { title: '对方号码', dataIndex: 'otherNumber', key: 'otherNumber', width: 120 },
- { title: '通信类型/联系日期', dataIndex: 'contactTime', key: 'contactTime', width: 170 },
- { title: '对方姓名', dataIndex: 'otherName', key: 'otherName', width: 120 },
- { title: '客户名称', dataIndex: 'customerName', key: 'customerName', width: 120 },
- { title: '本机LAC号', dataIndex: 'lac', key: 'lac', width: 120 },
- { title: '本机cellid', dataIndex: 'cellId', key: 'cellId', width: 120 },
- { title: '本机基站ID', dataIndex: 'baseId', key: 'baseId', width: 140 },
- { title: '本机归属运营商', dataIndex: 'carrier', key: 'carrier', width: 160 },
- ];
- columns.value = defaultColumns.map((column) => applyResultSortProps(column));
- const pageSizeOptions = componentSetting.table.pageSizeOptions.map((value) => ({
- label: value + '条/页',
- value: Number(value),
- }));
- const tableScrollY = ref(340);
- </script>
- <style lang="less">
- @prefix-cls: ~'jeesite-case-data';
- .@{prefix-cls}.case-data-page {
- padding: 0;
- width: calc(100% + 24px);
- margin: 0 -12px;
- background: #f0f2f5;
- display: flex;
- flex-direction: column;
- min-height: 0;
- height: 100%;
- box-sizing: border-box;
- overflow: hidden;
- .page-main {
- flex: 1 1 auto;
- display: flex;
- flex-direction: column;
- gap: 2px;
- min-width: 0;
- min-height: 0;
- height: 100%;
- padding: 2px;
- background: #f0f2f5;
- box-sizing: border-box;
- overflow: hidden;
- }
- .header-bar {
- flex-shrink: 0;
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 12px;
- padding: 5px 12px;
- border: 1px solid #e2e8f0;
- border-radius: 12px;
- background: #fff;
- box-shadow: none;
- }
- .header-actions {
- display: flex;
- gap: 8px;
- align-items: center;
- flex-wrap: wrap;
- justify-content: flex-end;
- &__upload,
- &__govern {
- min-width: 92px;
- height: 32px;
- padding: 0 16px;
- border-radius: 16px;
- font-size: 14px;
- box-shadow: none;
- }
- &__divider {
- width: 1px;
- height: 24px;
- background: #d9dee8;
- margin: 0 4px;
- flex: 0 0 auto;
- }
- &__config {
- min-width: 92px;
- height: 32px;
- padding: 0 16px;
- border-radius: 16px;
- font-size: 14px;
- }
- }
- .import-tabs {
- position: relative;
- display: inline-flex;
- align-items: center;
- gap: 28px;
- flex: 0 0 auto;
- min-width: max-content;
- padding: 0 2px;
- &::after {
- position: absolute;
- right: 0;
- bottom: -5px;
- left: 0;
- height: 1px;
- background: #e5e7eb;
- content: '';
- }
- &__item {
- position: relative;
- z-index: 1;
- display: inline-flex;
- align-items: center;
- justify-content: center;
- height: 32px;
- padding: 0;
- border: 0;
- background: transparent;
- color: #111827;
- font-size: 14px;
- line-height: 32px;
- cursor: pointer;
- &::after {
- position: absolute;
- right: 0;
- bottom: -5px;
- left: 0;
- z-index: 1;
- height: 3px;
- border-radius: 3px 3px 0 0;
- background: transparent;
- content: '';
- }
- &.is-active {
- color: #2f54eb;
- font-weight: 600;
- &::after {
- background: #2f54eb;
- }
- }
- }
- }
- .page-layout {
- display: grid;
- grid-template-columns: 200px minmax(0, 1fr);
- gap: 2px;
- align-items: stretch;
- min-height: 0;
- flex: 1 1 auto;
- overflow: hidden;
- }
- .left-panel {
- display: flex;
- flex-direction: column;
- gap: 8px;
- min-height: 0;
- height: 100%;
- padding: 8px;
- border: 1px solid #e2e8f0;
- border-radius: 12px;
- background: #fff;
- box-shadow: none;
- box-sizing: border-box;
- overflow: hidden;
- .sidebar-content {
- width: 100%;
- display: flex;
- flex-direction: column;
- min-height: 0;
- }
- .tree-scroll-shell {
- flex: 1 1 auto;
- min-height: 0;
- width: 100%;
- }
- .case-tree {
- width: 100%;
- height: 100%;
- .jeesite-basic-tree-header {
- padding: 6px 0;
- border-bottom: 1px solid #eef2f7;
- gap: 8px;
- }
- .jeesite-basic-tree-header > .flex {
- gap: 8px;
- min-width: 0;
- }
- .jeesite-basic-title {
- font-size: 14px;
- font-weight: 600;
- color: #111827;
- }
- .jeesite-basic-tree-header .w-full {
- flex: 1 1 auto;
- margin-right: 0;
- min-width: 0;
- }
- .jeesite-basic-tree-header .ant-input-affix-wrapper {
- height: 26px;
- border-radius: 6px;
- font-size: 12px;
- padding-inline: 8px;
- width: 100%;
- }
- .jeesite-basic-tree-header .anticon {
- font-size: 14px;
- color: #6b7280;
- }
- .jeesite-basic-tree-header .ant-dropdown-trigger {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- flex: 0 0 auto;
- height: 26px;
- width: 26px;
- min-width: 26px;
- padding: 0;
- border-radius: 6px;
- color: #6b7280;
- line-height: 1;
- cursor: pointer;
- transition:
- color 0.2s ease,
- background-color 0.2s ease;
- &:hover {
- color: #2563eb;
- background: #eff6ff;
- }
- .jeesite-icon,
- .anticon {
- display: inline-flex !important;
- align-items: center;
- justify-content: center;
- font-size: 14px;
- }
- &::after {
- content: none;
- }
- }
- .ant-tree {
- margin: 8px 0 0;
- }
- .ant-tree-indent-unit {
- width: 16px;
- }
- .ant-tree-node-content-wrapper {
- padding: 4px 6px !important;
- border-radius: 4px;
- font-size: 12px;
- color: #1f2937;
- }
- .ant-tree-switcher {
- display: flex;
- align-items: center;
- width: 16px;
- }
- .ant-tree-iconEle {
- width: 16px;
- color: #94a3b8;
- }
- .ant-tree-treenode {
- padding: 2px 0;
- }
- .ant-tree-indent-unit::before {
- border-color: #e5e7eb;
- }
- .ant-tree-switcher-icon {
- font-size: 10px;
- color: #64748b;
- }
- .ant-tree-treenode:hover .ant-tree-node-content-wrapper {
- background: #f5f7fb;
- }
- .ant-tree-treenode-selected .ant-tree-node-content-wrapper,
- .ant-tree-node-content-wrapper.ant-tree-node-selected {
- background: #eaf2ff;
- color: #2563eb;
- font-weight: 600;
- }
- .ant-tree-title {
- display: flex;
- align-items: center;
- gap: 6px;
- font-size: 12px;
- }
- }
- }
- .right-panel {
- background: #fff;
- border: 1px solid #e2e8f0;
- border-radius: 12px;
- padding: 8px;
- display: flex;
- flex-direction: column;
- gap: 8px;
- min-width: 0;
- min-height: 0;
- height: 100%;
- overflow: hidden;
- box-sizing: border-box;
- box-shadow: none;
- .panel-header {
- flex: 0 0 auto;
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 8px;
- min-height: 32px;
- padding: 0 0 8px;
- }
- .panel-title {
- font-size: 15px;
- font-weight: 600;
- color: #111827;
- }
- .panel-desc {
- margin-top: 4px;
- font-size: 12px;
- line-height: 1.6;
- color: #6b7280;
- }
- .table-actions {
- flex-shrink: 0;
- display: flex;
- align-items: center;
- justify-content: flex-end;
- margin-left: auto;
- gap: 8px;
- flex-wrap: wrap;
- min-height: 32px;
- }
- .table-setting-trigger {
- display: inline-flex;
- align-items: center;
- min-height: 32px;
- }
- .table-wrapper {
- flex: 1 1 auto;
- display: flex;
- flex-direction: column;
- min-height: 0;
- width: 100%;
- background: transparent;
- border: 0;
- border-radius: 0;
- overflow: hidden;
- }
- .table-wrapper .jeesite-basic-table {
- flex: 1 1 auto;
- height: 100%;
- display: flex;
- flex-direction: column;
- }
- .table-wrapper .ant-table-wrapper {
- flex: 1 1 auto;
- height: 100%;
- min-height: 0;
- padding: 0 !important;
- background: transparent;
- border-radius: 0;
- }
- .table-wrapper .ant-spin-nested-loading,
- .table-wrapper .ant-spin-container,
- .table-wrapper .ant-table,
- .table-wrapper .ant-table-container {
- height: 100%;
- min-height: 0;
- }
- .table-wrapper .ant-spin-nested-loading,
- .table-wrapper .ant-spin-container,
- .table-wrapper .ant-table,
- .table-wrapper .ant-table-container {
- display: flex;
- flex-direction: column;
- }
- .table-wrapper .ant-table-body {
- flex: 1 1 auto;
- max-height: 100% !important;
- }
- .table-wrapper .ant-table-thead > tr > th {
- background: #f8fafc;
- font-weight: 600;
- color: #374151;
- }
- .table-wrapper .ant-table-thead > tr > th.case-data-sortable-header {
- cursor: pointer;
- user-select: none;
- }
- .table-wrapper .ant-table-tbody > tr > td {
- color: #374151;
- }
- .table-wrapper .ant-table-tbody > tr:hover > td {
- background: #f5f7ff;
- }
- .table-wrapper .ant-table-body::-webkit-scrollbar {
- width: 8px;
- height: 8px;
- }
- .table-wrapper .ant-table-body::-webkit-scrollbar-thumb {
- background: rgba(148, 163, 184, 0.6);
- border-radius: 999px;
- }
- .table-wrapper .ant-table-body::-webkit-scrollbar-track {
- background: transparent;
- }
- .jeesite-basic-table-header__toolbar {
- gap: 6px;
- }
- .table-footer {
- flex-shrink: 0;
- display: flex;
- justify-content: space-between;
- align-items: center;
- gap: 16px;
- min-height: 40px;
- padding-top: 0;
- color: #6b7280;
- .total {
- white-space: nowrap;
- font-size: 12px;
- line-height: 1.5;
- }
- .footer-right {
- display: flex;
- align-items: center;
- margin-left: auto;
- gap: 12px;
- flex-wrap: nowrap;
- min-width: 0;
- }
- .page-size {
- width: 112px;
- flex: 0 0 auto;
- }
- .jump {
- display: flex;
- align-items: center;
- gap: 8px;
- white-space: nowrap;
- }
- .jump-input {
- width: 64px;
- flex: 0 0 auto;
- }
- }
- .page-tip {
- color: #6b7280;
- font-size: 12px;
- }
- }
- .template-modal {
- .ant-modal-content {
- border-radius: 10px;
- padding: 0;
- overflow: hidden;
- }
- .ant-modal-close {
- top: 12px;
- right: 12px;
- }
- .ant-modal-header {
- display: none;
- }
- .ant-modal-body {
- padding: 18px 20px 14px;
- }
- .template-modal__line {
- height: 1px;
- background: #eef2f7;
- margin: 4px 0 12px;
- }
- .template-modal__header {
- display: flex;
- align-items: center;
- justify-content: space-between;
- margin-bottom: 12px;
- }
- .template-modal__title {
- display: flex;
- align-items: center;
- gap: 8px;
- font-size: 16px;
- font-weight: 600;
- color: #1f2937;
- }
- .template-modal__header .template-modal__title {
- margin-bottom: 2px;
- }
- .template-modal__icon {
- width: 22px;
- height: 22px;
- border-radius: 6px;
- background: #ffe2a8;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 14px;
- }
- .template-modal__header .template-modal__icon {
- transform: translateY(-1px);
- }
- .template-modal__tabs {
- display: flex !important;
- align-items: center !important;
- gap: 10px;
- padding-bottom: 6px;
- flex-wrap: nowrap !important;
- white-space: nowrap;
- width: 200px;
- }
- .template-modal__tab {
- display: inline-flex !important;
- align-items: center !important;
- padding: 6px 16px;
- border-radius: 999px;
- background: #eef2ff;
- color: #1d4ed8;
- font-size: 13px;
- font-weight: 600;
- cursor: pointer;
- transition: all 0.2s ease;
- width: 500px;
- &.active {
- background: #2f54eb;
- color: #fff;
- box-shadow: 0 4px 10px rgba(47, 84, 235, 0.35);
- }
- }
- .template-modal__body {
- display: flex;
- flex-direction: column;
- gap: 12px;
- }
- .template-modal__filters {
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 12px;
- padding: 10px 0 16px;
- flex-wrap: wrap;
- }
- .template-modal__filters-left .ant-input,
- .template-modal__filters-left .ant-select {
- flex: 0 0 auto !important;
- }
- .template-modal__filters-left {
- display: flex !important;
- align-items: center !important;
- flex-wrap: nowrap !important;
- gap: 10px;
- min-width: 0;
- max-width: 620px;
- }
- .template-modal__search {
- width: 220px !important;
- max-width: 220px !important;
- height: 32px;
- }
- .template-modal__filter {
- display: flex;
- align-items: center;
- gap: 8px;
- color: #4b5563;
- font-size: 13px;
- }
- .template-modal__select {
- width: 120px;
- }
- .template-modal__btn {
- height: 32px;
- padding: 0 18px;
- border-radius: 6px;
- &.secondary {
- color: #334155;
- }
- }
- .template-modal__actions {
- display: flex;
- align-items: center;
- gap: 8px;
- flex-wrap: wrap;
- .ant-btn {
- height: 32px;
- padding: 0 14px;
- border-radius: 6px;
- }
- }
- .ant-table-wrapper {
- border: 1px solid #e5e7eb;
- border-radius: 8px;
- overflow: hidden;
- }
- .ant-table-tbody > tr > td {
- padding: 10px 12px;
- color: #374151;
- }
- .jeesite-table-action {
- display: flex;
- gap: 12px;
- flex-wrap: nowrap;
- }
- .ant-table-thead > tr > th {
- background: #f5f7fb;
- font-weight: 600;
- color: #374151;
- }
- .template-modal__footer {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 12px 0 4px;
- color: #6b7280;
- font-size: 12px;
- flex-wrap: wrap;
- gap: 10px;
- }
- .disposition-modal,
- .import-modal,
- .download-modal {
- display: flex;
- flex-direction: column;
- gap: 16px;
- }
- .disposition-form {
- display: flex;
- flex-direction: column;
- gap: 12px;
- }
- .disposition-form .form-row {
- display: flex;
- flex-direction: column;
- gap: 6px;
- }
- .disposition-form .form-label {
- color: #4b5563;
- font-size: 13px;
- }
- .disposition-actions,
- .import-actions,
- .download-actions {
- display: flex;
- justify-content: flex-end;
- gap: 10px;
- }
- .import-desc,
- .download-tip {
- color: #6b7280;
- font-size: 13px;
- }
- .template-modal__pagination {
- display: flex;
- align-items: center;
- gap: 10px;
- }
- .template-modal__page-size {
- width: 110px;
- }
- .template-modal__jump {
- display: flex;
- align-items: center;
- gap: 6px;
- }
- .template-modal__jump-input {
- width: 56px;
- }
- }
- .file-panel {
- background: #fff;
- border: 1px solid #e2e8f0;
- border-radius: 12px;
- padding: 8px;
- flex: 1 1 auto;
- display: flex;
- flex-direction: column;
- gap: 8px;
- min-width: 0;
- min-height: 0;
- height: 100%;
- margin-top: 0;
- overflow: hidden;
- box-sizing: border-box;
- box-shadow: none;
- }
- .file-panel-header {
- flex-shrink: 0;
- padding-bottom: 0;
- }
- .file-panel__table {
- flex: 1 1 auto;
- display: flex;
- flex-direction: column;
- min-height: 0;
- width: 100%;
- overflow: hidden;
- > * {
- flex: 1 1 auto;
- height: 100%;
- min-height: 0;
- }
- }
- .file-filters {
- display: flex;
- align-items: center;
- gap: 12px;
- padding: 8px 0 4px;
- border-bottom: 1px solid #e5e7eb;
- flex-wrap: wrap;
- }
- .filter-item {
- display: flex;
- align-items: center;
- gap: 6px;
- }
- .filter-label {
- color: #6b7280;
- font-size: 12px;
- white-space: nowrap;
- }
- .filter-input {
- width: 160px;
- }
- .filter-actions {
- display: flex;
- gap: 8px;
- }
- .filter-btn {
- height: 28px;
- padding: 0 16px;
- }
- .batch-btn {
- margin-left: auto;
- }
- .file-header {
- display: flex;
- align-items: center;
- justify-content: space-between;
- }
- .file-header .ant-btn {
- height: 28px;
- padding: 0 14px;
- }
- .file-title {
- font-size: 16px;
- font-weight: 600;
- color: #1f2937;
- }
- .file-actions {
- display: flex;
- gap: 12px;
- }
- .file-body {
- border: 1px dashed #e5e7eb;
- border-radius: 12px;
- padding: 24px;
- color: #94a3b8;
- text-align: center;
- }
- .file-panel .ant-table-wrapper {
- border: 1px solid #e5e7eb;
- border-radius: 8px;
- }
- .file-panel .ant-table-thead > tr > th {
- background: #f9fafb;
- font-weight: 600;
- }
- .case-template-modal {
- .ant-modal {
- width: 980px !important;
- max-width: calc(100vw - 80px);
- top: 40px;
- }
- .ant-modal-content {
- border-radius: 12px;
- overflow: hidden;
- }
- .ant-modal-header {
- padding: 12px 18px;
- border-bottom: 1px solid #eef2f7;
- background: @modal-header-bg-color;
- }
- .ant-modal-body {
- padding: 16px 18px 18px;
- }
- }
- .template-modal-body {
- display: flex;
- flex-direction: column;
- gap: 16px;
- }
- .template-modal-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding-bottom: 8px;
- border-bottom: 1px solid #eef2f7;
- }
- .template-modal-title {
- font-size: 16px;
- font-weight: 700;
- color: #0f172a;
- }
- .template-modal-subtitle {
- font-size: 12px;
- color: #64748b;
- margin-top: 4px;
- }
- .template-modal-form {
- display: flex;
- align-items: flex-end;
- gap: 12px;
- }
- .template-modal-form .jeesite-basic-form {
- flex: 1;
- }
- .template-form-actions {
- display: flex;
- gap: 8px;
- justify-content: flex-end;
- padding-bottom: 4px;
- }
- .template-actions {
- display: flex;
- gap: 10px;
- flex-wrap: nowrap;
- align-items: center;
- .ant-btn-link {
- padding: 0 4px;
- height: 24px;
- display: inline-flex;
- align-items: center;
- gap: 4px;
- }
- }
- .template-modal-footer {
- display: flex;
- justify-content: space-between;
- align-items: center;
- color: #6b7280;
- font-size: 12px;
- margin-top: 6px;
- }
- .template-pagination {
- display: flex;
- align-items: center;
- gap: 12px;
- }
- .template-page-size {
- width: 110px;
- }
- .template-table {
- .ant-table {
- border: 1px solid #eef2f7;
- border-radius: 10px;
- overflow: hidden;
- }
- .ant-table-thead > tr > th {
- background: #f8fafc;
- font-weight: 600;
- color: #374151;
- }
- .ant-table-tbody > tr > td {
- color: #374151;
- }
- .ant-table-tbody > tr:hover > td {
- background: #f3f6ff;
- }
- }
- }
- .govern-config-modal {
- .ant-modal-content {
- border-radius: 14px;
- overflow: hidden;
- }
- .ant-modal-header {
- padding: 12px 16px;
- border-bottom: 1px solid #e5e7eb;
- background: @modal-header-bg-color;
- }
- .ant-modal-title {
- color: #111827;
- font-size: 16px;
- font-weight: 600;
- line-height: 24px;
- }
- .ant-modal-body {
- padding: 16px;
- }
- .ant-modal-footer {
- padding: 8px 16px 10px;
- border-top: 1px solid #e5e7eb;
- background: #fff;
- }
- .govern-config-panel {
- display: flex;
- flex-direction: column;
- gap: 12px;
- }
- .govern-config-form {
- display: flex;
- flex-direction: column;
- gap: 12px;
- margin-bottom: 0;
- .ant-form-item {
- margin-bottom: 0;
- }
- .ant-form-item-label {
- padding-bottom: 8px;
- }
- .ant-form-item-label > label {
- color: #334155;
- font-size: 14px;
- font-weight: 600;
- }
- .ant-radio-group {
- display: flex;
- align-items: center;
- gap: 24px;
- }
- .ant-radio-wrapper {
- margin-right: 0;
- color: #475569;
- font-size: 13px;
- }
- .ant-radio-wrapper .ant-radio {
- top: 0;
- }
- }
- .govern-config-item {
- padding: 14px 16px;
- border: 1px solid #e6edf6;
- border-radius: 12px;
- background: #fff;
- box-shadow: 0 8px 24px rgba(15, 23, 42, 0.04);
- }
- .govern-config-label {
- display: inline-flex;
- align-items: center;
- gap: 6px;
- }
- .config-tip-icon {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- width: 18px;
- height: 18px;
- border-radius: 50%;
- background: #3150e6;
- color: #fff;
- font-size: 12px;
- font-weight: 700;
- line-height: 1;
- cursor: pointer;
- }
- .govern-config-footer {
- display: flex;
- justify-content: flex-end;
- gap: 10px;
- .ant-btn {
- min-width: 96px;
- height: 40px;
- border-radius: 10px;
- }
- }
- }
- @media (max-width: 960px) {
- .case-data-header-teleport {
- display: none !important;
- }
- .@{prefix-cls}.case-data-page {
- .header-bar,
- .right-panel .panel-header {
- flex-direction: column;
- align-items: flex-start;
- }
- .header-actions,
- .right-panel .table-actions {
- width: 100%;
- justify-content: flex-start;
- }
- .page-layout {
- grid-template-columns: 1fr;
- }
- .table-footer {
- flex-direction: column;
- align-items: flex-start;
- gap: 12px;
- }
- .table-footer .footer-right {
- flex-wrap: wrap;
- }
- }
- }
- html[data-theme='dark'] {
- .@{prefix-cls}.case-data-page {
- background: #0f172a;
- .panel-title,
- .file-title,
- .template-modal__title {
- color: #e5e7eb;
- }
- .panel-desc {
- color: #9ca3af;
- }
- .left-panel,
- .right-panel,
- .file-panel,
- .template-modal .ant-modal-content {
- background: #111827;
- }
- .template-modal {
- .ant-table-wrapper {
- border-color: #1f2937;
- }
- .ant-table-thead > tr > th {
- background: #0f172a;
- color: #cbd5f5;
- }
- }
- .tree-item {
- background: #1f2937;
- color: #e5e7eb;
- &.active {
- background: #1e40af;
- color: #fff;
- }
- }
- .tree-child {
- background: #0f172a;
- border-color: #1f2937;
- }
- .tree-title {
- color: #e5e7eb;
- }
- .case-tree {
- background: #111827;
- height: 100%;
- .jeesite-basic-tree-header {
- border-bottom-color: #1f2937;
- }
- .jeesite-basic-tree-header .ant-input-affix-wrapper {
- background: #0f172a;
- border-color: #1f2937;
- }
- .jeesite-basic-tree-header .anticon {
- color: #94a3b8;
- }
- .jeesite-basic-tree-header .ant-dropdown-trigger {
- color: #cbd5e1;
- &:hover {
- color: #93c5fd;
- background: rgba(30, 64, 175, 0.24);
- }
- }
- .ant-tree-node-content-wrapper {
- color: #e5e7eb;
- }
- .ant-tree-treenode:hover .ant-tree-node-content-wrapper {
- background: #1f2937;
- }
- .ant-tree-treenode-selected .ant-tree-node-content-wrapper,
- .ant-tree-node-content-wrapper.ant-tree-node-selected {
- background: #1e3a8a;
- color: #e0e7ff;
- }
- }
- .tree-sub,
- .tree-count,
- .table-footer,
- .page-tip,
- .file-body,
- .template-modal__footer {
- color: #9ca3af;
- }
- }
- }
- </style>
|