| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858485948604861486248634864486548664867486848694870487148724873487448754876487748784879488048814882488348844885488648874888488948904891489248934894489548964897489848994900490149024903490449054906490749084909491049114912491349144915491649174918491949204921492249234924492549264927492849294930493149324933493449354936493749384939494049414942494349444945494649474948494949504951495249534954495549564957495849594960496149624963496449654966496749684969497049714972497349744975497649774978497949804981498249834984498549864987498849894990499149924993499449954996499749984999500050015002500350045005500650075008500950105011501250135014501550165017501850195020502150225023502450255026502750285029503050315032503350345035503650375038503950405041504250435044504550465047504850495050505150525053505450555056505750585059506050615062506350645065506650675068506950705071507250735074507550765077507850795080508150825083508450855086508750885089509050915092509350945095509650975098509951005101510251035104510551065107510851095110511151125113511451155116511751185119512051215122512351245125512651275128512951305131513251335134513551365137513851395140514151425143514451455146514751485149515051515152515351545155515651575158515951605161516251635164516551665167516851695170517151725173517451755176517751785179518051815182518351845185518651875188518951905191519251935194519551965197519851995200520152025203520452055206520752085209521052115212521352145215521652175218521952205221522252235224522552265227522852295230523152325233523452355236523752385239524052415242524352445245524652475248524952505251525252535254525552565257525852595260526152625263526452655266526752685269527052715272527352745275527652775278527952805281528252835284528552865287528852895290529152925293529452955296529752985299530053015302530353045305530653075308530953105311531253135314531553165317531853195320532153225323532453255326532753285329533053315332533353345335533653375338533953405341534253435344534553465347534853495350535153525353535453555356535753585359536053615362536353645365536653675368536953705371537253735374537553765377537853795380538153825383538453855386538753885389539053915392539353945395539653975398539954005401540254035404540554065407540854095410541154125413541454155416541754185419542054215422542354245425542654275428542954305431543254335434543554365437543854395440544154425443544454455446544754485449545054515452545354545455545654575458545954605461546254635464546554665467546854695470547154725473547454755476547754785479548054815482548354845485548654875488548954905491549254935494549554965497549854995500550155025503550455055506550755085509551055115512551355145515551655175518551955205521552255235524552555265527552855295530553155325533553455355536553755385539554055415542554355445545554655475548554955505551555255535554555555565557555855595560556155625563556455655566556755685569557055715572557355745575557655775578557955805581558255835584558555865587558855895590559155925593559455955596559755985599560056015602560356045605560656075608560956105611561256135614561556165617561856195620562156225623562456255626562756285629563056315632563356345635563656375638563956405641564256435644564556465647564856495650565156525653565456555656565756585659566056615662566356645665566656675668566956705671567256735674567556765677567856795680568156825683568456855686568756885689569056915692569356945695569656975698569957005701570257035704570557065707570857095710571157125713571457155716571757185719572057215722572357245725572657275728572957305731573257335734573557365737573857395740574157425743574457455746574757485749575057515752575357545755575657575758575957605761576257635764576557665767576857695770577157725773577457755776577757785779578057815782578357845785578657875788578957905791579257935794579557965797579857995800580158025803580458055806580758085809581058115812581358145815581658175818581958205821582258235824582558265827582858295830583158325833583458355836583758385839584058415842584358445845584658475848584958505851585258535854585558565857585858595860586158625863586458655866586758685869587058715872587358745875587658775878587958805881588258835884588558865887588858895890589158925893589458955896589758985899590059015902590359045905590659075908590959105911591259135914591559165917591859195920592159225923592459255926592759285929593059315932593359345935593659375938593959405941 |
- <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 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>
- <BasicModal
- v-model:open="uploadVisible"
- :title="uploadMode === 'single' ? '选择文件' : '选择文件夹'"
- width="760px"
- :show-cancel-btn="false"
- :show-ok-btn="false"
- wrapClassName="case-upload-modal"
- @register="registerUploadModal"
- @ok="handleUploadOk"
- @cancel="handleUploadCancel"
- >
- <div class="upload-modal-body">
- <div class="upload-modal-header">
- <div class="upload-mode-switch" role="tablist" aria-label="上传类型切换">
- <button
- type="button"
- :class="['upload-mode-switch__item', { 'is-active': uploadMode === 'single' }]"
- :disabled="uploadProcessing"
- @click="handleUploadModeChange('single')"
- >
- 选择文件
- </button>
- <button
- type="button"
- :class="['upload-mode-switch__item', { 'is-active': uploadMode === 'folder' }]"
- :disabled="uploadProcessing"
- @click="handleUploadModeChange('folder')"
- >
- 选择文件夹
- </button>
- </div>
- <!-- <div class="upload-modal-title">{{ uploadMode === 'single' ? '选择文件' : '选择文件夹' }}</div> -->
- <!-- <div class="upload-modal-subtitle">支持拖拽文件到这里,或点击选择文件或选择文件夹,系统会自动识别处理</div> -->
- </div>
- <div class="upload-dropzone">
- <Spin :spinning="uploadProcessing" tip="解析中">
- <Upload.Dragger
- :directory="uploadMode === 'folder'"
- :multiple="true"
- :disabled="uploadProcessing"
- :before-upload="uploadMode === 'folder' ? beforeUploadFolder : beforeUpload"
- :show-upload-list="false"
- >
- <div class="upload-dragger-content">
- <div class="upload-dragger-icon">⬆</div>
- <div class="upload-dragger-title">
- {{
- uploadMode === 'single' ? '拖拽文件到这里,或点击选择文件' : '拖拽文件夹到这里,或点击选择文件夹'
- }}
- </div>
- <div class="upload-dragger-desc">
- {{ uploadMode === 'single' ? '支持批量选择文件' : '支持选择整个文件夹,系统会自动读取文件' }}
- </div>
- <Button
- type="primary"
- class="upload-action-btn"
- :loading="selecting"
- :disabled="uploadProcessing"
- @click="handleSelectFile"
- >
- {{ uploadMode === 'single' ? '选择文件' : '选择文件夹' }}
- </Button>
- </div>
- </Upload.Dragger>
- </Spin>
- </div>
- </div>
- </BasicModal>
- <QueryTableModal
- v-model:open="templateVisible"
- title="模板库"
- wrap-class-name="case-template-library-modal"
- :loading="templateLoading"
- :filters="templateQuery"
- :filter-schema="templateFilterSchema"
- :columns="templateColumns"
- :action-column="templateActionColumn"
- :data-source="templateRows"
- :pagination="templatePagination"
- :total-pages="templateTotalPages"
- :page-size-options="templatePageSizeOptions"
- :show-table-setting="false"
- :table-setting="{ size: true, setting: true, redo: false, fullScreen: false }"
- @search="handleTemplateSearch"
- @reset="handleTemplateReset"
- @prev-page="handleTemplatePrevPage"
- @next-page="handleTemplateNextPage"
- @page-size-change="handleTemplatePageSizeChange"
- @page-change="handleTemplatePageChange"
- >
- <template #searchActions>
- <Button type="primary" @click="openTemplateCreateModal">
- <Icon icon="i-ant-design:plus-outlined" />
- 新增模板
- </Button>
- </template>
- </QueryTableModal>
- <Modal
- v-model:open="templateCreateVisible"
- :confirmLoading="templateCreateSaving"
- :closable="true"
- :centered="true"
- width="720px"
- wrapClassName="case-template-create-modal"
- @ok="handleTemplateCreateSave"
- @cancel="handleTemplateCreateCancel"
- >
- <template #title>
- <div class="template-create-title">
- <span class="template-create-title-icon">
- <Icon :icon="templateFormMode === 'create' ? 'i-ant-design:plus-outlined' : 'i-ant-design:edit-outlined'" />
- </span>
- <span>{{ templateFormMode === 'create' ? '新增模板' : '编辑模板' }}</span>
- </div>
- </template>
- <div class="template-create-body">
- <div class="template-create-intro">
- <div class="template-create-intro__title">
- {{ templateFormMode === 'create' ? '基于标准模板快速新增业务模板' : '调整当前模板的识别规则与配置' }}
- </div>
- <div class="template-create-intro__desc">
- {{
- templateFormMode === 'create'
- ? '选择标准模板后,补充当前业务的原始字段映射,后续可直接用于数据清洗匹配。'
- : '支持更新模板名称、表头识别行与原始字段映射配置,保存后会同步刷新模板库列表。'
- }}
- </div>
- </div>
- <div class="template-create-form-panel">
- <div class="template-create-row">
- <div :class="['template-create-label', { required: templateRequireMainTemplate }]">关联标准模板</div>
- <div class="template-create-field">
- <Select
- v-model:value="templateCreateForm.mainId"
- showSearch
- :loading="templateCatalogLoading"
- placeholder="请选择关联标准模板"
- :options="templateBaseTemplateOptions"
- :filter-option="filterTemplateOption"
- popupClassName="template-create-select-popup"
- :getPopupContainer="getTemplateCreatePopupContainer"
- @change="handleTemplateBaseChange"
- />
- </div>
- </div>
- <div class="template-create-row">
- <div class="template-create-label required">模板名称</div>
- <div class="template-create-field">
- <Input
- v-model:value="templateCreateForm.tableNameCn"
- placeholder="请输入模板名称"
- @blur="handleTemplateCreateNameBlur"
- />
- </div>
- </div>
- <div class="template-create-row">
- <div class="template-create-label required">表头识别行</div>
- <div class="template-create-field">
- <Input v-model:value="templateCreateForm.lineNo" placeholder="请输入表头识别行" />
- </div>
- </div>
- <div class="template-create-row template-create-row--textarea">
- <div class="template-create-label required">表头识别项</div>
- <div class="template-create-field">
- <div class="template-create-field-head">
- <span class="template-create-field-mode">{{
- templateHeaderEditorMode === 'text' ? '文本录入' : '配置模式'
- }}</span>
- <Button type="link" class="template-create-transform-btn" @click="toggleTemplateHeaderEditorMode">
- <Icon
- :icon="
- templateHeaderEditorMode === 'text'
- ? 'i-ant-design:swap-outlined'
- : 'i-ant-design:font-colors-outlined'
- "
- />
- {{ templateHeaderEditorMode === 'text' ? '转换' : '文本模式' }}
- </Button>
- </div>
- <Input.TextArea
- v-if="templateHeaderEditorMode === 'text'"
- v-model:value="templateCreateForm.headers"
- :auto-size="{ minRows: 5, maxRows: 7 }"
- placeholder="请输入原始字段,使用逗号或制表符分隔"
- />
- <div v-else class="template-header-builder">
- <div class="template-header-builder__head">
- <span>目标字段</span>
- <span>原始字段</span>
- </div>
- <div v-if="templateHeaderItems.length" class="template-header-builder__list">
- <div
- v-for="(item, index) in templateHeaderItems"
- :key="item.key"
- :class="[
- 'template-header-builder__row',
- { 'is-merge-disabled': isTemplateHeaderItemDisabled(item) },
- ]"
- >
- <div
- :class="[
- 'template-header-builder__preview',
- { 'is-required': item.required, 'is-optional': !item.required },
- ]"
- >
- <div class="template-header-builder__preview-label">
- <span v-if="item.required" class="template-header-builder__required-mark">*</span>
- <span class="template-header-builder__preview-text">
- {{ item.targetLabel || `目标字段 ${index + 1}` }}
- </span>
- </div>
- <span v-if="item.required" class="template-header-builder__preview-badge">必填</span>
- </div>
- <div
- v-if="!item.directionEntries.length && isTemplateHeaderItemTransAmount(item)"
- class="template-header-builder__source-config"
- >
- <div class="template-header-builder__source-config-select">
- <Tooltip
- :title="getTemplateHeaderDisabledText(item) || item.value || undefined"
- placement="topLeft"
- :getPopupContainer="getModalPopupContainer"
- >
- <Select
- :value="getTemplateHeaderSelectValue(item) || undefined"
- showSearch
- allowClear
- :options="getTemplateHeaderSourceOptions(item)"
- :filter-option="filterTemplateOption"
- popupClassName="template-create-select-popup"
- :getPopupContainer="getTemplateCreatePopupContainer"
- :disabled="isTemplateHeaderItemDisabled(item)"
- :placeholder="getTemplateHeaderDisabledText(item) || '请选择原始字段'"
- @change="handleTemplateHeaderItemValueChange(item, $event)"
- @clear="clearTemplateHeaderItemValue(item)"
- />
- </Tooltip>
- </div>
- <div class="template-header-builder__switch-field">
- <span class="template-header-builder__switch-label">正负识别</span>
- <Switch :checked="item.zf === 1" @change="handleTemplateHeaderItemZfChange(item, $event)" />
- </div>
- </div>
- <Tooltip
- v-else-if="!item.directionEntries.length"
- :title="getTemplateHeaderDisabledText(item) || item.value || undefined"
- placement="topLeft"
- :getPopupContainer="getModalPopupContainer"
- >
- <Select
- :value="getTemplateHeaderSelectValue(item) || undefined"
- showSearch
- allowClear
- :options="getTemplateHeaderSourceOptions(item)"
- :filter-option="filterTemplateOption"
- popupClassName="template-create-select-popup"
- :getPopupContainer="getTemplateCreatePopupContainer"
- :disabled="isTemplateHeaderItemDisabled(item)"
- :placeholder="getTemplateHeaderDisabledText(item) || '请选择原始字段'"
- @change="handleTemplateHeaderItemValueChange(item, $event)"
- @clear="clearTemplateHeaderItemValue(item)"
- />
- </Tooltip>
- <div
- v-else
- :class="[
- 'template-header-builder__direction-config',
- { 'has-switch-field': isTemplateHeaderItemTransAmount(item) },
- ]"
- >
- <div
- :class="[
- 'template-header-builder__direction-main',
- { 'has-switch-field': isTemplateHeaderItemTransAmount(item) },
- ]"
- >
- <div class="template-header-builder__source-field">
- <Tooltip
- :title="getTemplateHeaderDisabledText(item) || item.value || undefined"
- placement="topLeft"
- :getPopupContainer="getModalPopupContainer"
- >
- <Select
- :value="getTemplateHeaderSelectValue(item) || undefined"
- showSearch
- allowClear
- :options="getTemplateHeaderSourceOptions(item)"
- :filter-option="filterTemplateOption"
- popupClassName="template-create-select-popup"
- :getPopupContainer="getTemplateCreatePopupContainer"
- :disabled="isTemplateHeaderItemDisabled(item)"
- :placeholder="getTemplateHeaderDisabledText(item) || '请选择原始字段'"
- @change="handleTemplateHeaderItemValueChange(item, $event)"
- @clear="clearTemplateHeaderItemValue(item)"
- />
- </Tooltip>
- </div>
- <div v-if="isTemplateHeaderItemTransAmount(item)" class="template-header-builder__switch-field">
- <span class="template-header-builder__switch-label">正负识别</span>
- <Switch :checked="item.zf === 1" @change="handleTemplateHeaderItemZfChange(item, $event)" />
- </div>
- </div>
- <div class="template-header-builder__direction-list">
- <div
- v-for="directionItem in item.directionEntries"
- :key="directionItem.key"
- class="template-header-builder__direction-row"
- >
- <span class="template-header-builder__direction-label">{{ directionItem.label }}</span>
- <div class="template-header-builder__direction-value">
- <Tooltip
- :title="directionItem.value || undefined"
- placement="topLeft"
- :getPopupContainer="getModalPopupContainer"
- >
- <Input
- v-model:value="directionItem.value"
- :disabled="isTemplateHeaderItemDisabled(item)"
- placeholder="请输入"
- />
- </Tooltip>
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- <div v-else class="template-header-builder__empty">当前模板暂无目标字段,请切换文本模式录入</div>
- </div>
- <div class="template-create-tip">
- {{
- templateHeaderEditorMode === 'text'
- ? '按原始字段顺序录入,字段之间支持使用逗号(英文状态)或制表符分隔'
- : '左侧标记 * / 必填 的目标字段为必填项,右侧支持从拆分出的原始表头中下拉选择'
- }}
- </div>
- </div>
- </div>
- <div v-if="showTemplateMergePanel" class="template-create-merge-panel">
- <div class="template-create-merge-head" @click="templateMergeConfigExpanded = !templateMergeConfigExpanded">
- <div class="template-create-merge-summary">
- <div class="template-create-merge-title-wrap">
- <span class="template-create-merge-title">合并表头配置内容</span>
- <span class="template-create-merge-count">{{ templateMergeConfigRows.length }} 项</span>
- </div>
- <div class="template-create-merge-desc">{{ templateMergePanelDesc }}</div>
- </div>
- <div class="template-create-merge-actions">
- <Button
- type="link"
- class="template-create-merge-toggle"
- @click.stop="templateMergeConfigExpanded = !templateMergeConfigExpanded"
- >
- {{ templateMergeConfigExpanded ? '收起' : '展开' }}
- <Icon
- :icon="templateMergeConfigExpanded ? 'i-ant-design:up-outlined' : 'i-ant-design:down-outlined'"
- />
- </Button>
- </div>
- </div>
- <template v-if="templateMergeConfigExpanded">
- <div v-if="templateMergeConfigRows.length" class="template-create-merge-list">
- <div
- v-for="(item, index) in templateMergeConfigRows"
- :key="item.key"
- class="template-create-merge-item"
- >
- <div class="template-create-merge-item__index">{{ String(index + 1).padStart(2, '0') }}</div>
- <div class="template-create-merge-item__content">
- <div class="template-create-merge-field">
- <span class="template-create-merge-field__label">原始表头匹配项</span>
- <Input v-model:value="item.headerMatch" placeholder="请输入匹配项" />
- </div>
- <div class="template-create-merge-field">
- <span class="template-create-merge-field__label">目标字段</span>
- <Tooltip
- :title="item.targetField || undefined"
- placement="topLeft"
- :getPopupContainer="getModalPopupContainer"
- >
- <div class="template-create-merge-field__readonly" :class="{ 'is-empty': !item.targetField }">
- {{ item.targetField || '未配置目标字段' }}
- </div>
- </Tooltip>
- </div>
- <div class="template-create-merge-field">
- <span class="template-create-merge-field__label">内容获取截止标志</span>
- <Input v-model:value="item.endFlag" placeholder="请输入截止标志" />
- </div>
- </div>
- </div>
- </div>
- <div v-else class="template-create-merge-empty">
- <div class="template-create-merge-empty__title">暂无合并配置</div>
- <div class="template-create-merge-empty__desc"
- >当前模板未解析到可展示的合并规则,请检查默认提取规则配置。</div
- >
- </div>
- </template>
- </div>
- </div>
- </div>
- <template #footer>
- <div class="template-create-footer">
- <Button class="template-create-cancel-btn" @click="handleTemplateCreateCancel">取消</Button>
- <Button
- type="primary"
- class="template-create-submit-btn"
- :loading="templateCreateSaving"
- @click="handleTemplateCreateSave"
- >
- {{ templateFormMode === 'create' ? '保存' : '更新' }}
- </Button>
- </div>
- </template>
- </Modal>
- <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, watch } from 'vue';
- import {
- Button,
- Select,
- type SelectProps,
- Pagination,
- Input,
- Switch,
- Upload,
- Modal,
- Radio,
- Tooltip,
- Popover,
- Form,
- RadioGroup,
- Spin,
- } from 'ant-design-vue';
- import { BasicTable, type BasicColumn } from '@jeesite/core/components/Table';
- import TableSetting from '@jeesite/core/components/Table/src/components/settings/index.vue';
- import { createTableContext } from '@jeesite/core/components/Table/src/hooks/useTableContext';
- import { BasicTree } from '@jeesite/core/components/Tree';
- import { BasicModal, useModal } from '@jeesite/core/components/Modal';
- import { Icon } from '@jeesite/core/components/Icon';
- import { useDesign } from '@jeesite/core/hooks/web/useDesign';
- import { useGo } from '@jeesite/core/hooks/web/usePage';
- import { PageEnum } from '@jeesite/core/enums/pageEnum';
- import { getModalPopupContainer as resolveModalPopupContainer } from '@jeesite/core/utils';
- import { buildUUID } from '@jeesite/core/utils/uuid';
- import { useMessage } from '@jeesite/core/hooks/web/useMessage';
- import { useEmitter, useUserStore } from '@jeesite/core/store/modules/case';
- import { markAnalysisDataChanged } from '@jeesite/core/logics/analysisRefreshPrompt';
- import QueryTableModal from '../dm/components/QueryTableModal.vue';
- import CustomSearch from './components/CustomSearch.vue';
- import GovernProgressModal from './components/GovernProgressModal.vue';
- import { getActiveImportProgress } from './importProgressSession';
- import CallRecordModal from '@jeesite/call/views/components/CallRecordModal.vue';
- import TransRecordModal from '@jeesite/trans/views/components/TransRecordModal.vue';
- import {
- getOpenOfficeFileErrorMessage,
- isOpenOfficeFileSuccess,
- openOfficeFileById,
- resolveOfficeFileId,
- } from '@jeesite/core/utils/openOfficeFile';
- import {
- tplCategoryList,
- tplCheckTableName,
- tplCreate,
- tplDelete,
- tplDetail,
- tplPage,
- tplUpdate,
- } from '@jeesite/plat/api/tpl/tplApi';
- import type { TableField, TableInfo } from '@jeesite/plat/api/tpl/types/dto';
- import {
- getGovernConfList,
- getTree,
- treeTablePage,
- getTreeTransPersonPage,
- getTreeTransBankPage,
- getTreeCallPersonPage,
- getTreeCallOperPage,
- preFile,
- saveGovernConf,
- } from '@jeesite/case/api/govern/governApi';
- import type { GovernConfItem } from '@jeesite/case/api/govern/governApi';
- import componentSetting from '@jeesite/core/settings/componentSetting';
- import ImportList from './importList.vue';
- import { DataTableDTO, GovernTree } from '@jeesite/case/api/govern/types/dto';
- import { GovernTreeQuery } from '@jeesite/case/api/govern/types/query';
- import type { CallRecordQuery } from '@jeesite/call/api/types/query';
- import type { TransRecordQuery } from '@jeesite/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';
- const HEADER_TEMPLATE_LIBRARY_OPEN_EVENT = 'layout-header:open-template-library';
- 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';
- interface TemplateTargetField {
- key: string;
- label: string;
- fieldNameCn: string;
- fieldNameEn: string;
- required: boolean;
- zf: number;
- directionEntries: TemplateDirectionItem[];
- }
- interface TemplateDirectionItem {
- key: string;
- label: string;
- value: string;
- }
- interface TemplateHeaderItem {
- key: string;
- targetKey: string;
- targetLabel: string;
- value: string;
- required: boolean;
- zf: number;
- directionEntries: TemplateDirectionItem[];
- }
- interface TemplateMergeConfigItem {
- key: string;
- headerMatch: string;
- targetField: string;
- endFlag: string;
- }
- type GovernConfigValue = '是' | '否';
- interface GovernConfigFormState {
- removeDuplicate: GovernConfigValue;
- associateAccountTime: GovernConfigValue;
- skipGovernOnImport: GovernConfigValue;
- }
- type DataPageTabKey = 'import' | 'files';
- const activeTab = ref<DataPageTabKey>('import');
- const uploadVisible = ref(false);
- const templateVisible = ref(false);
- const templateCatalogLoading = ref(false);
- const templateCreateVisible = ref(false);
- const templateCreateSaving = ref(false);
- const templateEditOpeningId = ref<number | null>(null);
- const templateFormMode = ref<'create' | 'edit'>('create');
- const templateEditingId = ref<number | undefined>(undefined);
- const templateEditingHasInner = ref<'0' | '1' | undefined>(undefined);
- const templateEditingClassify = ref<string | undefined>(undefined);
- 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>>({});
- const uploadMode = ref<'single' | 'folder'>('single');
- const uploadBizKey = ref(buildUUID());
- const uploadLoadTime = ref(Date.now());
- const pendingUploadPaths = ref<string[]>([]);
- let uploadBatchTimer: ReturnType<typeof setTimeout> | null = null;
- let governConfigSaveTimer: ReturnType<typeof setTimeout> | undefined;
- const { createMessage } = useMessage();
- const [registerUploadModal, { closeModal: closeUploadModal }] = useModal();
- 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();
- });
- onDeactivated(() => {
- restoringImportProgress = false;
- });
- const openUpload = (mode: 'single' | 'folder') => {
- uploadMode.value = mode;
- uploadVisible.value = true;
- uploadBizKey.value = buildUUID();
- uploadLoadTime.value = Date.now();
- };
- const handleUploadNavigate = (mode?: 'single' | 'folder') => {
- if (mode) {
- openUpload(mode);
- } else {
- go(PageEnum.BASE_MATCH);
- }
- };
- const handleUploadModeChange = (mode: 'single' | 'folder') => {
- if (uploadMode.value === mode) return;
- uploadMode.value = mode;
- uploadBizKey.value = buildUUID();
- uploadLoadTime.value = Date.now();
- };
- const handleUploadChange = () => {
- uploadLoadTime.value = Date.now();
- };
- void handleUploadChange;
- const handleUploadOk = () => {
- uploadVisible.value = false;
- closeUploadModal();
- };
- const handleUploadCancel = () => {
- uploadVisible.value = false;
- };
- const handleTemplateOpen = async () => {
- templateVisible.value = true;
- templatePagination.value.page = 1;
- await loadTemplateClassifyOptions();
- await loadTemplateList();
- };
- const getRequestErrorMessage = (error: any, fallback = '操作失败') =>
- error?.response?.data?.message || error?.message || fallback;
- const normalizeTemplateNameText = (value?: unknown) => String(value ?? '').trim();
- const resolveTemplateNameBooleanFlag = (payload: unknown): boolean | null => {
- if (typeof payload === 'boolean') return payload;
- if (typeof payload === 'number') {
- if (payload === 1) return true;
- if (payload === 0) return false;
- return null;
- }
- if (typeof payload === 'string') {
- const normalized = payload.trim().toLowerCase();
- if (
- ['true', '1', 'yes', 'y', 'ok', 'success', 'exist', 'exists', 'duplicate', 'duplicated'].includes(normalized)
- ) {
- return true;
- }
- if (['false', '0', 'no', 'n', 'fail', 'available', 'notexist', 'not-exist'].includes(normalized)) {
- return false;
- }
- return null;
- }
- return null;
- };
- const resolveTemplateNameCheckResult = (payload: unknown): boolean | null => {
- if (!payload || typeof payload !== 'object') {
- const exists = resolveTemplateNameBooleanFlag(payload);
- return exists === null ? null : !exists;
- }
- const record = payload as Record<string, unknown>;
- for (const key of ['valid', 'available', 'pass']) {
- const resolved = resolveTemplateNameBooleanFlag(record[key]);
- if (resolved !== null) return resolved;
- }
- for (const key of ['exists', 'exist', 'duplicate', 'duplicated']) {
- const resolved = resolveTemplateNameBooleanFlag(record[key]);
- if (resolved !== null) return !resolved;
- }
- for (const key of ['result', 'data']) {
- const value = record[key];
- if (value && typeof value === 'object') {
- const resolved = resolveTemplateNameCheckResult(value);
- if (resolved !== null) return resolved;
- continue;
- }
- const resolved = resolveTemplateNameBooleanFlag(value);
- if (resolved !== null) return !resolved;
- }
- return null;
- };
- 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 handleTemplateClose = () => {
- templateVisible.value = false;
- };
- const templateLoading = ref(false);
- const defaultTemplateClassifyOptions = [{ label: '全部', value: '' }];
- const templateClassifyOptions = ref<Array<{ label: string; value: string }>>([...defaultTemplateClassifyOptions]);
- const templateQuery = ref({
- tableNameCn: '',
- classify: '',
- });
- const normalizeTemplateClassifyText = (value?: unknown) => String(value ?? '').trim();
- const templateFilterSchema = computed(() => [
- { field: 'tableNameCn', label: '模板名称', type: 'input' as const, placeholder: '请输入' },
- {
- field: 'classify',
- label: '表类别',
- type: 'select' as const,
- options: templateClassifyOptions.value,
- },
- ]);
- const templateColumns = [
- { title: '表类别', dataIndex: 'classify', key: 'classify', width: 140, ellipsis: true },
- { title: '表中文名称', dataIndex: 'tableNameCn', key: 'tableNameCn', width: 220, ellipsis: true },
- { title: '检测表头', dataIndex: 'headers', key: 'headers', ellipsis: true },
- ];
- const getTemplateDisplayName = (record: any) => record?.tableNameCn || '未命名模板';
- const isSystemTemplateRecord = (record: any) => {
- const normalizedValue = String(record?.hasInner ?? '')
- .trim()
- .toUpperCase();
- return ['YES', '1', 'TRUE'].includes(normalizedValue);
- };
- const templateCatalogRows = ref<any[]>([]);
- const templateHeaderEditorMode = ref<'text' | 'list'>('text');
- const templateTargetTemplate = ref<Partial<TableInfo> | null>(null);
- const templateCurrentDetail = ref<Partial<TableInfo> | null>(null);
- const templateDetailRequestCache = new Map<number, Promise<Partial<TableInfo> | null>>();
- const templateHeaderItems = ref<TemplateHeaderItem[]>([]);
- const templateDirectionDraftMap = ref<Record<string, TemplateDirectionItem[]>>({});
- const templateZfDraftMap = ref<Record<string, number>>({});
- const templateHeaderOverflowValues = ref<string[]>([]);
- const templateMergeConfigExpanded = ref(true);
- const templateMergeConfigRows = ref<TemplateMergeConfigItem[]>([]);
- const templateOriginalName = ref('');
- const templateCreateForm = ref<{
- mainId: number | undefined;
- tableNameCn: string;
- lineNo: string;
- headers: string;
- }>({
- mainId: undefined,
- tableNameCn: '',
- lineNo: '',
- headers: '',
- });
- const resetTemplateCreateForm = () => {
- templateFormMode.value = 'create';
- templateEditingId.value = undefined;
- templateEditingHasInner.value = undefined;
- templateEditingClassify.value = undefined;
- templateTargetTemplate.value = null;
- templateCurrentDetail.value = null;
- templateHeaderEditorMode.value = 'text';
- templateHeaderItems.value = [];
- templateDirectionDraftMap.value = {};
- templateZfDraftMap.value = {};
- templateHeaderOverflowValues.value = [];
- templateMergeConfigExpanded.value = true;
- templateMergeConfigRows.value = [];
- templateOriginalName.value = '';
- templateCreateForm.value = {
- mainId: undefined,
- tableNameCn: '',
- lineNo: '',
- headers: '',
- };
- };
- const normalizeHeaders = (headers: any) => {
- if (Array.isArray(headers)) return headers.join(',');
- if (typeof headers === 'string') return headers;
- return '';
- };
- const normalizeTemplateHeaderTextForForm = (headers: any) =>
- parseTemplateHeaderValues(normalizeHeaders(headers), true).join(', ');
- const normalizeTemplateRecord = (item: any, index: number) => ({
- ...item,
- id: item.id,
- rowKey: item.id ?? `${item.tableNameCn || ''}-${index}`,
- classify: item.classify || '-',
- tableNameCn: item.tableNameCn || '-',
- headers: normalizeHeaders(item.headers) || '-',
- });
- const getTemplateOptionLabel = (record: any) => {
- const name = record?.tableNameCn && record.tableNameCn !== '-' ? record.tableNameCn : '未命名模板';
- const classify = record?.classify && record.classify !== '-' ? ` / ${record.classify}` : '';
- return `${name}${classify}`;
- };
- const createTemplateFieldKey = (label: string, index: number) =>
- `field_${index + 1}_${String(label || '')
- .trim()
- .replace(/\s+/g, '_')}`;
- const parseTemplateHeaderValues = (value: string, preserveEmpty = false) => {
- const items = String(value || '')
- .split(/[,\n\r\t,]/)
- .map((item) => item.trim());
- return preserveEmpty ? items : items.filter(Boolean);
- };
- const normalizeRuleText = (value?: string | number | null) => String(value ?? '').trim();
- const normalizeFieldMatchText = (value?: string | number | null) =>
- normalizeRuleText(value)
- .replace(/[\s_\-—()()[\]【】{}<>《》"'`‘’“”、,,.。::;;/\\|]/g, '')
- .toLowerCase();
- const getTemplateFieldRequired = (value: unknown) => {
- if (value === null || value === undefined || value === '') return false;
- if (typeof value === 'boolean') return value;
- const parsed = Number(value);
- return !Number.isNaN(parsed) && parsed === 1;
- };
- const getTemplateFieldRecord = (item: unknown) => (item || {}) as Record<string, any>;
- const getTemplateFieldRequiredFlag = (item: unknown, fallback = false) => {
- const field = getTemplateFieldRecord(item);
- if (Object.prototype.hasOwnProperty.call(field, 'required')) {
- return getTemplateFieldRequired(field.required);
- }
- return fallback;
- };
- const hasTemplateOwnFieldProp = (item: unknown, key: string) =>
- !!item && Object.prototype.hasOwnProperty.call(item as Record<string, any>, key);
- const getOrderedTemplateFields = (fields?: TableField[] | null) =>
- (Array.isArray(fields) ? fields : [])
- .filter((item) => {
- const field = getTemplateFieldRecord(item);
- return !!(field.fieldNameCn || field.fieldNameEn || field.fieldName);
- })
- .map((item) => item);
- const sortTemplateHeaderItemsByRequired = (items: TemplateHeaderItem[]) =>
- [...items].sort((left, right) => {
- if (left.required === right.required) return 0;
- return left.required ? -1 : 1;
- });
- const getTemplateFieldTargetLabel = (item: unknown, index: number) => {
- const field = getTemplateFieldRecord(item);
- return String(field.fieldNameCn || field.fieldName || `目标字段 ${index + 1}`);
- };
- const getTemplateFieldTargetKey = (item: unknown, index: number) => {
- const field = getTemplateFieldRecord(item);
- return String(
- field.fieldNameEn || field.fieldName || createTemplateFieldKey(String(field.fieldNameCn || ''), index),
- );
- };
- const parseTemplateDirectionEntries = (value: unknown): TemplateDirectionItem[] => {
- const rawText = value === null || value === undefined || value === '-' ? '' : String(value).trim();
- if (!rawText) return [];
- try {
- let parsedValue: Record<string, any> | null = null;
- try {
- parsedValue = JSON.parse(rawText);
- } catch (jsonError) {
- void jsonError;
- const normalizedText = rawText
- .replace(/([{,]\s*)'([^']+?)'\s*:/g, '$1"$2":')
- .replace(/:\s*'([^']*?)'(\s*[,}])/g, ': "$1"$2');
- parsedValue = JSON.parse(normalizedText);
- }
- if (!parsedValue || typeof parsedValue !== 'object' || Array.isArray(parsedValue)) return [];
- return Object.entries(parsedValue).reduce((acc, [label, itemValue], index) => {
- const resolvedLabel = String(label || '').trim();
- if (!resolvedLabel) return acc;
- acc.push({
- key: `${resolvedLabel}_${index}`,
- label: resolvedLabel,
- value: itemValue === null || itemValue === undefined ? '' : String(itemValue),
- });
- return acc;
- }, [] as TemplateDirectionItem[]);
- } catch (error) {
- console.error('解析 directionConf 失败', error);
- return [];
- }
- };
- const buildTemplateDirectionConf = (entries?: TemplateDirectionItem[]) => {
- if (!entries?.length) return '';
- const payload = entries.reduce(
- (acc, item) => {
- const label = String(item.label || '').trim();
- if (!label) return acc;
- acc[label] = String(item.value ?? '').trim();
- return acc;
- },
- {} as Record<string, string>,
- );
- return Object.keys(payload).length ? JSON.stringify(payload) : '';
- };
- const findTemplateFieldByTarget = (
- fields: TableField[] | null | undefined,
- targetField: Pick<TemplateTargetField, 'key' | 'label'>,
- fallbackIndex?: number,
- ) => {
- const orderedFields = getOrderedTemplateFields(fields);
- return (
- orderedFields.find((item, index) => {
- const currentKey = getTemplateFieldTargetKey(item, index).trim();
- const currentLabel = getTemplateFieldTargetLabel(item, index).trim();
- return currentKey === targetField.key || currentLabel === targetField.label;
- }) || (fallbackIndex === undefined ? null : orderedFields[fallbackIndex] || null)
- );
- };
- const buildTemplateTargetFields = (template?: Partial<TableInfo> | null): TemplateTargetField[] => {
- const tableFields = getOrderedTemplateFields(template?.tableFields);
- if (tableFields.length) {
- return tableFields.map((item, index) => {
- const field = getTemplateFieldRecord(item);
- return {
- key: getTemplateFieldTargetKey(item, index),
- label: getTemplateFieldTargetLabel(item, index),
- fieldNameCn: normalizeTemplateFormText(field.fieldNameCn).trim(),
- fieldNameEn: normalizeTemplateFormText(field.fieldNameEn || field.fieldName).trim(),
- required: getTemplateFieldRequiredFlag(item),
- zf: Number(field.zf) === 1 ? 1 : 0,
- directionEntries: parseTemplateDirectionEntries(field.directionConf),
- };
- });
- }
- return [];
- };
- const templateTargetFields = computed(() => buildTemplateTargetFields(templateTargetTemplate.value));
- const templateHeaderSourceValues = computed(() => {
- const rawHeaders = parseTemplateHeaderValues(templateCreateForm.value.headers);
- return rawHeaders.reduce((acc, item) => {
- if (!item || acc.includes(item)) return acc;
- acc.push(item);
- return acc;
- }, [] as string[]);
- });
- const templateHeaderSourceOptions = computed(() =>
- templateHeaderSourceValues.value.map((item) => ({
- label: item,
- value: item,
- })),
- );
- const fixedTemplateMergeFieldBindings = {
- personCertNo: '客户证件号码',
- personName: '客户名称',
- personCardNo: '本方卡号',
- } as const;
- const fixedTemplateMergeFields = Object.values(fixedTemplateMergeFieldBindings);
- const normalizedFixedTemplateMergeFieldMap = Object.entries(fixedTemplateMergeFieldBindings).reduce(
- (acc, [fieldKey, targetField]) => {
- const normalizedFieldKey = normalizeFieldMatchText(fieldKey);
- const normalizedTargetField = normalizeFieldMatchText(targetField);
- if (normalizedFieldKey) {
- acc[normalizedFieldKey] = targetField;
- }
- if (normalizedTargetField) {
- acc[normalizedTargetField] = targetField;
- }
- return acc;
- },
- {} as Record<string, string>,
- );
- const normalizedFixedTemplateMergeFieldKeyMap = Object.entries(fixedTemplateMergeFieldBindings).reduce(
- (acc, [fieldKey, targetField]) => {
- const normalizedFieldKey = normalizeFieldMatchText(fieldKey);
- const normalizedTargetField = normalizeFieldMatchText(targetField);
- if (normalizedFieldKey) {
- acc[normalizedFieldKey] = fieldKey;
- }
- if (normalizedTargetField) {
- acc[normalizedTargetField] = fieldKey;
- }
- return acc;
- },
- {} as Record<string, string>,
- );
- const resolveTemplateMergeTargetFieldKey = (value: unknown) => {
- const normalizedValue = normalizeTemplateMergeStorageText(value);
- if (!normalizedValue) return '';
- return normalizedFixedTemplateMergeFieldKeyMap[normalizeFieldMatchText(normalizedValue)] || normalizedValue;
- };
- const templateMergeFilledTargetMap = computed(() =>
- templateMergeConfigRows.value.reduce(
- (acc, item) => {
- const headerMatch = normalizeTemplateFormText(item.headerMatch).trim();
- const targetField = normalizeTemplateFormText(item.targetField).trim();
- const targetKey = resolveTemplateMergeTargetFieldKey(targetField);
- const normalizedTargetKey = normalizeFieldMatchText(targetKey);
- if (headerMatch && normalizedTargetKey) {
- acc[normalizedTargetKey] = targetField;
- }
- return acc;
- },
- {} as Record<string, string>,
- ),
- );
- const getTemplateHeaderMergeTarget = (item: Pick<TemplateHeaderItem, 'targetKey'>) =>
- templateMergeFilledTargetMap.value[normalizeFieldMatchText(item.targetKey)] || '';
- const isTemplateHeaderItemMergeDisabled = (item: TemplateHeaderItem) => !!getTemplateHeaderMergeTarget(item);
- const isLoanDirectionLabel = (value: unknown) => {
- const text = normalizeTemplateFormText(value).trim();
- return !!text && (text.includes('借贷') || /(收入|支出|借方|贷方|借|贷)/.test(text));
- };
- const isTemplateHeaderItemLoanFlag = (
- item: Pick<TemplateHeaderItem, 'targetKey' | 'targetLabel' | 'directionEntries'>,
- ) =>
- !!(
- normalizeTemplateFormText(item.targetKey).trim() === 'debitCreditFlag' ||
- normalizeTemplateFormText(item.targetKey).trim() === 'loanFlag' ||
- normalizeTemplateFormText(item.targetLabel).trim().includes('借贷') ||
- (item.directionEntries || []).some((directionItem) => isLoanDirectionLabel(directionItem.label))
- );
- const isTemplateHeaderItemDisabledByZf = (item: TemplateHeaderItem) => {
- if (!isTemplateHeaderItemLoanFlag(item)) return false;
- return templateHeaderItems.value.some(
- (headerItem) => isTemplateHeaderItemTransAmount(headerItem) && headerItem.zf === 1,
- );
- };
- const shouldSkipTemplateHeaderSaveValidation = (item: TemplateHeaderItem) => isTemplateHeaderItemDisabledByZf(item);
- const isTemplateHeaderItemDisabled = (item: TemplateHeaderItem) =>
- isTemplateHeaderItemMergeDisabled(item) || isTemplateHeaderItemDisabledByZf(item);
- const getTemplateHeaderSelectValue = (item: TemplateHeaderItem) =>
- isTemplateHeaderItemDisabled(item) ? '' : item.value;
- const getTemplateHeaderMergeDisabledText = (item: TemplateHeaderItem) => {
- const targetField = getTemplateHeaderMergeTarget(item);
- return targetField ? `已由合并表头“${targetField}”配置,当前字段无需选择原始字段` : '';
- };
- const getTemplateHeaderDisabledText = (item: TemplateHeaderItem) => {
- const mergeDisabledText = getTemplateHeaderMergeDisabledText(item);
- if (mergeDisabledText) return mergeDisabledText;
- return isTemplateHeaderItemDisabledByZf(item) ? '已开启交易金额正负识别,当前借贷标志无需填写' : '';
- };
- const getTemplateHeaderSourceOptions = (currentItem: TemplateHeaderItem) => {
- const selectedValues = new Set(
- templateHeaderItems.value
- .filter((item) => item.key !== currentItem.key && !isTemplateHeaderItemDisabled(item))
- .map((item) => item.value.trim())
- .filter(Boolean),
- );
- const currentValue = currentItem.value.trim();
- const optionValues = [...templateHeaderSourceValues.value];
- if (currentValue && !optionValues.includes(currentValue)) {
- optionValues.unshift(currentValue);
- }
- return optionValues.map((item) => ({
- label: item,
- value: item,
- disabled: item !== currentValue && selectedValues.has(item),
- }));
- };
- const isTransAmountTemplateFieldKey = (value: unknown) => normalizeTemplateFormText(value).trim() === 'transAmount';
- const isTemplateHeaderItemTransAmount = (item: Pick<TemplateHeaderItem, 'targetKey'>) =>
- isTransAmountTemplateFieldKey(item.targetKey);
- const handleTemplateHeaderItemValueChange = (item: TemplateHeaderItem, value: unknown) => {
- if (isTemplateHeaderItemDisabled(item)) return;
- item.value = typeof value === 'string' ? value : '';
- };
- const handleTemplateHeaderItemZfChange = (item: TemplateHeaderItem, checked: boolean) => {
- item.zf = checked ? 1 : 0;
- };
- const clearTemplateHeaderItemValue = (item: TemplateHeaderItem) => {
- if (isTemplateHeaderItemDisabled(item)) return;
- item.value = '';
- };
- const cloneTemplateDirectionEntries = (entries?: TemplateDirectionItem[]) =>
- (entries || []).map((item, index) => ({
- key: item.key || `${item.label}_${index}`,
- label: item.label,
- value: item.value,
- }));
- const showTemplateMergePanel = computed(
- () => !!templateResolvedFuncRegx.value || templateMergeConfigRows.value.length > 0,
- );
- const templateMergePanelDesc = computed(() =>
- templateFormMode.value === 'create'
- ? '若标准模板配置了提取规则,这里仅展示配置表结构,原始表头匹配项与内容获取截止标志需按当前业务补充。'
- : '当模板配置了提取规则时,自动回填原始表头匹配项、目标字段与内容获取截止标志,目标字段保持模板默认映射。',
- );
- const templateResolvedFuncRegx = computed(() => {
- if (templateFormMode.value === 'edit' && templateCurrentDetail.value) {
- return normalizeTemplateFormText(templateCurrentDetail.value.funcRegx);
- }
- return normalizeTemplateFormText(templateTargetTemplate.value?.funcRegx);
- });
- const normalizeTemplateMergeText = (value: unknown) => normalizeTemplateFormText(value).trim();
- const stripTemplateMergeTrailingColon = (value: string) => value.replace(/[::]\s*$/g, '').trim();
- const normalizeTemplateMergeStorageText = (value: unknown) =>
- stripTemplateMergeTrailingColon(normalizeTemplateMergeText(value));
- const resolveTemplateMergeTargetField = (value: unknown) => {
- const normalizedValue = normalizeTemplateMergeStorageText(value);
- if (!normalizedValue) return '';
- return normalizedFixedTemplateMergeFieldMap[normalizeFieldMatchText(normalizedValue)] || normalizedValue;
- };
- const useFixedTemplateMergeSchema = computed(() => {
- const normalizedTargetKeys = new Set(
- templateTargetFields.value.map((item) => normalizeFieldMatchText(item.key)).filter(Boolean),
- );
- return Object.keys(fixedTemplateMergeFieldBindings).every((fieldKey) =>
- normalizedTargetKeys.has(normalizeFieldMatchText(fieldKey)),
- );
- });
- const templateMergeRegex = /^\{(.*?)\}$/s;
- const parseTemplateMergePayload = (funcRegx: unknown) => {
- const rawText = normalizeTemplateMergeText(funcRegx);
- if (!rawText) return null;
- const matchedText = rawText.match(templateMergeRegex)?.[0];
- if (!matchedText) return null;
- try {
- const parsedValue = JSON.parse(matchedText) as Record<string, any>;
- if (!parsedValue || typeof parsedValue !== 'object' || Array.isArray(parsedValue)) {
- return null;
- }
- return parsedValue;
- } catch (error) {
- console.error('解析 funcRegx 失败', error);
- return null;
- }
- };
- const buildTemplateMergeTargetFieldByHeaderMatchMap = (funcRegx: unknown) => {
- const parsedValue = parseTemplateMergePayload(funcRegx);
- if (!parsedValue) return {} as Record<string, string>;
- return Object.entries(parsedValue).reduce(
- (acc, [targetField, rawValue]) => {
- const rawList = Array.isArray(rawValue) ? rawValue : [];
- const headerMatch = normalizeTemplateMergeText(rawList[0] ?? '');
- const normalizedHeaderMatch = normalizeTemplateMergeStorageText(headerMatch);
- if (normalizedHeaderMatch) {
- acc[normalizedHeaderMatch] = resolveTemplateMergeTargetField(targetField);
- }
- return acc;
- },
- {} as Record<string, string>,
- );
- };
- const parseTemplateMergeConfigRows = (funcRegx: unknown): TemplateMergeConfigItem[] => {
- const parsedValue = parseTemplateMergePayload(funcRegx);
- if (!parsedValue) return [];
- const parentTargetFieldMap = buildTemplateMergeTargetFieldByHeaderMatchMap(templateTargetTemplate.value?.funcRegx);
- return Object.entries(parsedValue).reduce((acc, [storedKey, rawValue]) => {
- const rawList = Array.isArray(rawValue) ? rawValue : [];
- const headerMatch = normalizeTemplateMergeText(rawList[0] ?? '');
- const normalizedHeaderMatch = normalizeTemplateMergeStorageText(headerMatch);
- const endFlag = normalizeTemplateMergeText(rawList[1] ?? '');
- const normalizedStoredKey = normalizeTemplateMergeStorageText(storedKey);
- const resolvedStoredTargetField = resolveTemplateMergeTargetField(normalizedStoredKey);
- const mappedParentTargetField =
- normalizedHeaderMatch && normalizedStoredKey === normalizedHeaderMatch
- ? parentTargetFieldMap[normalizedHeaderMatch]
- : '';
- const resolvedTargetField = mappedParentTargetField || resolvedStoredTargetField;
- acc.push({
- key: buildUUID(),
- headerMatch,
- targetField: resolvedTargetField,
- endFlag,
- });
- return acc;
- }, [] as TemplateMergeConfigItem[]);
- };
- const clearTemplateMergeConfigFilledContent = (rows: TemplateMergeConfigItem[]) =>
- rows.map((item) => ({
- ...item,
- headerMatch: '',
- endFlag: '',
- }));
- const buildTemplateFuncRegx = () => {
- if (!templateMergeConfigRows.value.length) {
- return templateResolvedFuncRegx.value || '';
- }
- const hasFilledMergeContent = templateMergeConfigRows.value.some(
- (item) => !!normalizeTemplateMergeText(item.headerMatch) || !!normalizeTemplateMergeText(item.endFlag),
- );
- if (!hasFilledMergeContent) {
- return '';
- }
- const initialPayload = (
- useFixedTemplateMergeSchema.value
- ? fixedTemplateMergeFields.reduce(
- (acc, field) => {
- acc[field] = ['', ''];
- return acc;
- },
- {} as Record<string, string[]>,
- )
- : {}
- ) as Record<string, string[]>;
- const payload = templateMergeConfigRows.value.reduce((acc, item) => {
- const targetField = resolveTemplateMergeTargetField(item.targetField);
- const headerMatch = normalizeTemplateMergeText(item.headerMatch);
- const endFlag = normalizeTemplateMergeText(item.endFlag);
- if (!targetField) return acc;
- if (useFixedTemplateMergeSchema.value && !fixedTemplateMergeFields.includes(targetField as any)) {
- return acc;
- }
- acc[targetField] = [headerMatch, endFlag];
- return acc;
- }, initialPayload);
- return Object.keys(payload).length ? JSON.stringify(payload) : '';
- };
- const syncTemplateMergeConfigRows = () => {
- const parsedRows = parseTemplateMergeConfigRows(templateResolvedFuncRegx.value);
- templateMergeConfigRows.value =
- templateFormMode.value === 'edit' ? parsedRows : clearTemplateMergeConfigFilledContent(parsedRows);
- templateMergeConfigExpanded.value = !!templateMergeConfigRows.value.length;
- };
- const createTemplateHeaderItem = (
- targetLabel = '',
- value = '',
- targetKey?: string,
- required = true,
- zf = 0,
- directionEntries: TemplateDirectionItem[] = [],
- ): TemplateHeaderItem => ({
- key: buildUUID(),
- targetKey: targetKey || createTemplateFieldKey(targetLabel || 'field', templateHeaderItems.value.length),
- targetLabel: targetLabel || `目标字段 ${templateHeaderItems.value.length + 1}`,
- value,
- required,
- zf: Number(zf) === 1 ? 1 : 0,
- directionEntries: cloneTemplateDirectionEntries(directionEntries),
- });
- const normalizeTemplateFormText = (value: unknown) => {
- if (value === null || value === undefined || value === '-') return '';
- return String(value);
- };
- const buildTemplateZfDraftMap = (fields?: TableField[] | null) =>
- getOrderedTemplateFields(fields).reduce(
- (acc, item, index) => {
- const field = getTemplateFieldRecord(item);
- const targetKey = getTemplateFieldTargetKey(item, index);
- if (
- isTransAmountTemplateFieldKey(targetKey) ||
- isTransAmountTemplateFieldKey(field.fieldNameEn || field.fieldName)
- ) {
- acc[targetKey] = Number(field.zf) === 1 ? 1 : 0;
- }
- return acc;
- },
- {} as Record<string, number>,
- );
- const populateTemplateCreateForm = (record: Partial<TableInfo> & Recordable = {}) => {
- templateOriginalName.value = templateFormMode.value === 'edit' ? normalizeTemplateNameText(record.tableNameCn) : '';
- templateCreateForm.value = {
- mainId:
- record.mainId === null || record.mainId === undefined || record.mainId === 0
- ? undefined
- : Number(record.mainId),
- tableNameCn: normalizeTemplateFormText(record.tableNameCn),
- lineNo: record.lineNo ? String(record.lineNo) : '',
- headers: normalizeTemplateHeaderTextForForm(record.headers),
- };
- templateHeaderItems.value = [];
- templateDirectionDraftMap.value = {};
- templateZfDraftMap.value =
- templateFormMode.value === 'edit' ? buildTemplateZfDraftMap((record as Partial<TableInfo>).tableFields) : {};
- templateHeaderOverflowValues.value = [];
- if (templateFormMode.value === 'edit' && templateTargetFields.value.length) {
- syncTemplateHeaderItemsFromText(templateCreateForm.value.headers);
- templateHeaderEditorMode.value = 'list';
- return;
- }
- templateHeaderEditorMode.value = 'text';
- };
- const syncTemplateDirectionDraftsFromItems = () => {
- templateDirectionDraftMap.value = templateHeaderItems.value.reduce(
- (acc, item) => {
- if (item.directionEntries.length) {
- acc[item.targetKey] = cloneTemplateDirectionEntries(item.directionEntries);
- }
- return acc;
- },
- {} as Record<string, TemplateDirectionItem[]>,
- );
- templateZfDraftMap.value = templateHeaderItems.value.reduce(
- (acc, item) => {
- if (isTemplateHeaderItemTransAmount(item)) {
- acc[item.targetKey] = Number(item.zf) === 1 ? 1 : 0;
- }
- return acc;
- },
- {} as Record<string, number>,
- );
- };
- const findTemplateHeaderByFieldNameCn = (fieldNameCn: string, headers: string[]) => {
- const normalizedTarget = normalizeFieldMatchText(fieldNameCn);
- if (!normalizedTarget) return '';
- return headers.find((header) => normalizeFieldMatchText(header) === normalizedTarget) || '';
- };
- const isCreateTemplateFormMode = () => templateFormMode.value === 'create';
- const syncTemplateHeaderItemsFromText = (value?: string) => {
- const inputValues = parseTemplateHeaderValues(value ?? templateCreateForm.value.headers, true);
- const availableHeaders = inputValues.filter(Boolean);
- const targetFields = templateTargetFields.value;
- templateHeaderOverflowValues.value = [];
- if (targetFields.length) {
- const nextHeaderItems = targetFields.map((field, index) => {
- const currentFieldMeta = isCreateTemplateFormMode()
- ? null
- : findTemplateFieldByTarget(templateCurrentDetail.value?.tableFields, field, index);
- const currentField = getTemplateFieldRecord(currentFieldMeta);
- const targetSourceField = getTemplateFieldRecord(
- findTemplateFieldByTarget(templateTargetTemplate.value?.tableFields, field, index),
- );
- const targetSourceFieldMeta = findTemplateFieldByTarget(
- templateTargetTemplate.value?.tableFields,
- field,
- index,
- );
- const draftDirectionEntries = cloneTemplateDirectionEntries(templateDirectionDraftMap.value[field.key]);
- const currentDirectionEntries = parseTemplateDirectionEntries(currentField.directionConf);
- const sourceDirectionEntries = parseTemplateDirectionEntries(targetSourceField.directionConf);
- const resolvedDirectionEntries = draftDirectionEntries.length
- ? draftDirectionEntries
- : hasTemplateOwnFieldProp(currentFieldMeta, 'directionConf')
- ? currentDirectionEntries
- : hasTemplateOwnFieldProp(targetSourceFieldMeta, 'directionConf')
- ? sourceDirectionEntries
- : field.directionEntries;
- const resolvedValue = findTemplateHeaderByFieldNameCn(field.fieldNameCn, availableHeaders);
- const draftZf = templateZfDraftMap.value[field.key];
- const resolvedZf =
- draftZf !== undefined
- ? Number(draftZf) === 1
- ? 1
- : 0
- : hasTemplateOwnFieldProp(currentFieldMeta, 'zf')
- ? Number(currentField.zf) === 1
- ? 1
- : 0
- : field.zf;
- return createTemplateHeaderItem(
- field.label,
- resolvedValue,
- field.key,
- field.required,
- resolvedZf,
- resolvedDirectionEntries,
- );
- });
- templateHeaderItems.value = sortTemplateHeaderItemsByRequired(nextHeaderItems);
- templateHeaderOverflowValues.value = availableHeaders.filter(
- (header) =>
- !targetFields.some((field) => normalizeFieldMatchText(field.fieldNameCn) === normalizeFieldMatchText(header)),
- );
- return;
- }
- templateHeaderItems.value = [];
- };
- const isTemplateHeaderItemFilled = (item: TemplateHeaderItem) => {
- if (shouldSkipTemplateHeaderSaveValidation(item)) return true;
- if (isTemplateHeaderItemDisabled(item)) return true;
- if (!item.value.trim()) return false;
- if (!item.directionEntries.length) return true;
- return item.directionEntries.every((directionItem) => {
- if (isTemplateHeaderItemDisabled(item)) return true;
- return !!normalizeTemplateFormText(directionItem.value).trim();
- });
- };
- const resolveTemplateHeaderItemValue = (item: TemplateHeaderItem) => normalizeTemplateFormText(item.value).trim();
- const getTemplateHeaderTextValues = () => {
- if (templateHeaderEditorMode.value === 'list') {
- return templateHeaderItems.value.map((item) => resolveTemplateHeaderItemValue(item)).filter(Boolean);
- }
- return normalizeTemplateHeadersForSave(templateCreateForm.value.headers).filter(Boolean);
- };
- const buildTemplateTableFields = (headerValues: string[]): TableField[] => {
- if (!templateRequireMainTemplate.value) return [];
- const sourceFields = getOrderedTemplateFields(templateTargetTemplate.value?.tableFields);
- const headerItemMap = templateHeaderItems.value.reduce(
- (acc, item) => {
- if (item.targetKey) {
- acc[`key:${item.targetKey}`] = item;
- }
- if (item.targetLabel) {
- acc[`label:${item.targetLabel}`] = item;
- }
- return acc;
- },
- {} as Record<string, TemplateHeaderItem>,
- );
- return sourceFields.map((item, index) => {
- const targetField = {
- key: getTemplateFieldTargetKey(item, index),
- label: getTemplateFieldTargetLabel(item, index),
- };
- const sourceField = getTemplateFieldRecord(item);
- const currentField = getTemplateFieldRecord(
- findTemplateFieldByTarget(templateCurrentDetail.value?.tableFields, targetField, index),
- );
- const currentFieldMeta = findTemplateFieldByTarget(templateCurrentDetail.value?.tableFields, targetField, index);
- const mergedField = {
- ...sourceField,
- ...currentField,
- };
- const currentHeaderItem = headerItemMap[`key:${targetField.key}`] || headerItemMap[`label:${targetField.label}`];
- const resolvedFieldNameCn =
- templateHeaderEditorMode.value === 'list'
- ? currentHeaderItem && !isTemplateHeaderItemMergeDisabled(currentHeaderItem)
- ? resolveTemplateHeaderItemValue(currentHeaderItem)
- : ''
- : headerValues[index]?.trim() || '';
- const requiredFlag =
- currentHeaderItem?.required ??
- getTemplateFieldRequiredFlag(currentFieldMeta, getTemplateFieldRequiredFlag(item));
- const draftZf = templateZfDraftMap.value[targetField.key];
- const zf = isTransAmountTemplateFieldKey(currentHeaderItem?.targetKey || targetField.key)
- ? currentHeaderItem.zf === 1
- ? 1
- : 0
- : draftZf !== undefined
- ? Number(draftZf) === 1
- ? 1
- : 0
- : Number(currentField.zf ?? sourceField.zf) === 1
- ? 1
- : 0;
- const directionConf =
- templateHeaderEditorMode.value === 'list' && currentHeaderItem?.directionEntries.length
- ? buildTemplateDirectionConf(currentHeaderItem.directionEntries)
- : hasTemplateOwnFieldProp(currentFieldMeta, 'directionConf')
- ? normalizeTemplateFormText(currentField.directionConf).trim()
- : normalizeTemplateFormText(sourceField.directionConf).trim();
- const { id, ...restField } = mergedField;
- void id;
- return {
- ...restField,
- fieldNameCn: resolvedFieldNameCn,
- fieldNameEn: sourceField.fieldNameEn || sourceField.fieldName || undefined,
- required: requiredFlag ? 1 : 0,
- matched: requiredFlag ? 1 : 0,
- zf: isTransAmountTemplateFieldKey(sourceField.fieldNameEn || sourceField.fieldName) ? zf : undefined,
- directionConf: directionConf || undefined,
- fieldSort: sourceField.fieldSort ?? index + 1,
- } as TableField & { zf?: number };
- });
- };
- const requestTemplateDetail = async (id?: number | string) => {
- const detailId = Number(id);
- if (!detailId) return null;
- const cachedRequest = templateDetailRequestCache.get(detailId);
- if (cachedRequest) return cachedRequest;
- const request = tplDetail({ id: detailId })
- .then((detail) => ((detail as any) || null) as Partial<TableInfo> | null)
- .finally(() => {
- templateDetailRequestCache.delete(detailId);
- });
- templateDetailRequestCache.set(detailId, request);
- return request;
- };
- const ensureTemplateTargetTemplate = async () => {
- if (templateFormMode.value === 'edit' && templateCurrentDetail.value) {
- templateTargetTemplate.value = templateCurrentDetail.value;
- return true;
- }
- const currentMainId = Number(templateCreateForm.value.mainId);
- if (currentMainId) {
- if (Number(templateTargetTemplate.value?.id) === currentMainId) return true;
- try {
- templateTargetTemplate.value = await requestTemplateDetail(currentMainId);
- return true;
- } catch (error) {
- console.error('加载标准模板详情失败', error);
- createMessage.error('加载标准模板详情失败');
- return false;
- }
- }
- if (templateCurrentDetail.value) {
- templateTargetTemplate.value = templateCurrentDetail.value;
- return true;
- }
- return false;
- };
- const toggleTemplateHeaderEditorMode = async () => {
- if (templateHeaderEditorMode.value === 'text') {
- const loaded = await ensureTemplateTargetTemplate();
- if (!loaded) {
- createMessage.warning('请先选择关联标准模板');
- return;
- }
- if (!templateTargetFields.value.length) {
- createMessage.warning('当前模板未配置目标字段,请使用文本模式录入');
- return;
- }
- syncTemplateHeaderItemsFromText();
- templateHeaderEditorMode.value = 'list';
- return;
- }
- syncTemplateDirectionDraftsFromItems();
- templateHeaderEditorMode.value = 'text';
- };
- const templateBaseTemplateOptions = computed(() =>
- templateCatalogRows.value
- .filter((item) => !!item?.id && isSystemTemplateRecord(item))
- .map((item) => ({
- label: getTemplateOptionLabel(item),
- value: Number(item.id),
- })),
- );
- const templateRequireMainTemplate = computed(
- () =>
- templateFormMode.value === 'create' || templateEditingHasInner.value === '1' || !!templateCreateForm.value.mainId,
- );
- const filterTemplateOption: SelectProps['filterOption'] = (input, option) =>
- String(option?.label || '')
- .toLowerCase()
- .includes(
- String(input || '')
- .trim()
- .toLowerCase(),
- );
- const handleTemplateEdit = async (record: any) => {
- const recordId = Number(record?.id);
- if (!recordId) {
- createMessage.warning('缺少模板ID,暂时无法编辑');
- return;
- }
- if (templateEditOpeningId.value !== null) return;
- templateEditOpeningId.value = recordId;
- templateCreateSaving.value = true;
- try {
- if (!templateCatalogRows.value.length) await loadTemplateCatalog();
- const detail = await requestTemplateDetail(recordId);
- if (!detail) throw new Error('模板详情为空');
- templateCurrentDetail.value = detail;
- templateFormMode.value = 'edit';
- templateEditingId.value = recordId;
- templateEditingHasInner.value = ((detail as any)?.hasInner ?? record?.hasInner) as '0' | '1' | undefined;
- templateEditingClassify.value =
- normalizeTemplateFormText((detail as any)?.classify ?? record?.classify) || undefined;
- templateTargetTemplate.value = detail;
- populateTemplateCreateForm({
- ...(record || {}),
- ...(detail || {}),
- });
- templateCreateVisible.value = true;
- } catch (error) {
- console.error('加载模板详情失败', error);
- createMessage.error('加载模板详情失败');
- } finally {
- templateCreateSaving.value = false;
- templateEditOpeningId.value = null;
- }
- };
- const handleTemplateBaseChange: SelectProps['onChange'] = async (value) => {
- if (Array.isArray(value) || value === undefined || value === null) return;
- templateCreateForm.value.mainId = Number(value);
- if (templateFormMode.value === 'edit') return;
- templateDirectionDraftMap.value = {};
- const selected = templateCatalogRows.value.find((item) => Number(item.id) === Number(value));
- if (!selected) return;
- await ensureTemplateTargetTemplate();
- if (!templateCreateForm.value.lineNo && selected.lineNo) {
- templateCreateForm.value.lineNo = String(selected.lineNo);
- }
- if (templateHeaderEditorMode.value === 'list') {
- syncTemplateHeaderItemsFromText(templateCreateForm.value.headers);
- }
- syncTemplateMergeConfigRows();
- };
- const loadTemplateCatalog = async () => {
- templateCatalogLoading.value = true;
- try {
- const resp = await tplPage({
- page: 1,
- limit: 9999,
- } as any);
- const records = (resp as any)?.list || (resp as any)?.records || [];
- templateCatalogRows.value = (Array.isArray(records) ? records : []).map((item: any, index: number) =>
- normalizeTemplateRecord(item, index),
- );
- } catch (error) {
- console.error('加载模板目录失败', error);
- templateCatalogRows.value = [];
- } finally {
- templateCatalogLoading.value = false;
- }
- };
- const resolveTemplateClassifyRecords = (payload: unknown): unknown[] => {
- if (Array.isArray(payload)) return payload;
- if (!payload || typeof payload !== 'object') return [];
- const record = payload as Record<string, unknown>;
- for (const key of ['data', 'list', 'records', 'result']) {
- const value = record[key];
- if (Array.isArray(value)) return value;
- }
- return [];
- };
- const normalizeTemplateClassifyOption = (item: unknown) => {
- if (item === null || item === undefined) return null;
- if (typeof item === 'string' || typeof item === 'number') {
- const text = normalizeTemplateClassifyText(item);
- return text ? { label: text, value: text } : null;
- }
- if (typeof item !== 'object') return null;
- const record = item as Record<string, unknown>;
- const value = normalizeTemplateClassifyText(
- record.value ?? record.classify ?? record.name ?? record.label ?? record.dictValue ?? record.dictLabel,
- );
- const label = normalizeTemplateClassifyText(
- record.label ?? record.name ?? record.classify ?? record.value ?? record.dictLabel ?? record.dictValue,
- );
- if (!value && !label) return null;
- return {
- label: label || value,
- value: value || label,
- };
- };
- const loadTemplateClassifyOptions = async () => {
- try {
- const response = await tplCategoryList();
- const options = resolveTemplateClassifyRecords(response)
- .map((item) => normalizeTemplateClassifyOption(item))
- .filter((item): item is { label: string; value: string } => Boolean(item));
- const uniqueOptionMap = new Map<string, { label: string; value: string }>();
- options.forEach((item) => {
- if (!uniqueOptionMap.has(item.value)) {
- uniqueOptionMap.set(item.value, item);
- }
- });
- templateClassifyOptions.value = [...defaultTemplateClassifyOptions, ...Array.from(uniqueOptionMap.values())];
- } catch (error) {
- console.error('加载模板分类失败', error);
- templateClassifyOptions.value = [...defaultTemplateClassifyOptions];
- }
- };
- const openTemplateCreateModal = async () => {
- if (!templateCatalogRows.value.length) await loadTemplateCatalog();
- resetTemplateCreateForm();
- templateFormMode.value = 'create';
- templateCreateVisible.value = true;
- };
- const handleTemplateCreateCancel = () => {
- templateCreateVisible.value = false;
- resetTemplateCreateForm();
- };
- watch(
- [() => templateResolvedFuncRegx.value, () => templateTargetFields.value.map((item) => item.label).join('||')],
- () => {
- syncTemplateMergeConfigRows();
- },
- { immediate: true },
- );
- const normalizeTemplateHeadersForSave = (value: string) => parseTemplateHeaderValues(value);
- const showTemplateNameDuplicateWarning = (name: string) => {
- Modal.warning({
- title: '模板名称已存在',
- content: `模板名称“${name}”已存在,请重新输入。`,
- centered: true,
- });
- };
- const checkTemplateNameAvailable = async (tableNameCn: string, oldTableNameCn = '') => {
- const response = await tplCheckTableName(oldTableNameCn, tableNameCn);
- // const available = resolveTemplateNameCheckResult(response);
- return response === 'N';
- };
- const validateTemplateCreateName = async () => {
- const tableNameCn = normalizeTemplateNameText(templateCreateForm.value.tableNameCn);
- if (!tableNameCn) return true;
- const oldTableNameCn =
- templateFormMode.value === 'edit' ? normalizeTemplateNameText(templateOriginalName.value) : '';
- if (oldTableNameCn && tableNameCn === oldTableNameCn) {
- return true;
- }
- try {
- const available = await checkTemplateNameAvailable(tableNameCn, oldTableNameCn);
- if (!available) {
- showTemplateNameDuplicateWarning(tableNameCn);
- return false;
- }
- return true;
- } catch (error) {
- createMessage.error(getRequestErrorMessage(error, '模板名称校验失败'));
- return false;
- }
- };
- const handleTemplateCreateNameBlur = async () => {
- const tableNameCn = normalizeTemplateNameText(templateCreateForm.value.tableNameCn);
- if (!tableNameCn) return;
- await validateTemplateCreateName();
- };
- const handleTemplateCreateSave = async () => {
- const form = templateCreateForm.value;
- const tableNameCn = form.tableNameCn.trim();
- const lineNo = Number(form.lineNo);
- const headerValues = getTemplateHeaderTextValues();
- const selectedBaseTemplate = templateCatalogRows.value.find((item) => Number(item.id) === Number(form.mainId));
- const requiresMainTemplate = templateRequireMainTemplate.value;
- const isChildTemplate = Number(form.mainId) > 0;
- if (templateFormMode.value === 'edit' && isSystemTemplateRecord(templateCurrentDetail.value)) {
- createMessage.warning('内置模板不支持修改');
- return;
- }
- if (requiresMainTemplate && !form.mainId) {
- createMessage.warning('请选择关联标准模板');
- return;
- }
- if (requiresMainTemplate) {
- const loaded = await ensureTemplateTargetTemplate();
- if (!loaded) {
- createMessage.warning('加载关联标准模板失败,请重新选择');
- return;
- }
- }
- if (!tableNameCn) {
- createMessage.warning('请输入模板名称');
- return;
- }
- const templateNameValid = await validateTemplateCreateName();
- if (!templateNameValid) {
- return;
- }
- if (!Number.isInteger(lineNo) || lineNo <= 0) {
- createMessage.warning('表头识别行请输入大于 0 的整数');
- return;
- }
- if (templateFormMode.value === 'create' && templateHeaderEditorMode.value !== 'list') {
- createMessage.warning('新增模板前请先点击“转换”,完成表头识别项配置后再保存');
- return;
- }
- if (templateHeaderEditorMode.value === 'list') {
- if (!templateHeaderItems.value.length) {
- createMessage.warning('当前模板暂无可配置的目标字段');
- return;
- }
- const invalidItem = templateHeaderItems.value.find((item) => item.required && !isTemplateHeaderItemFilled(item));
- if (invalidItem) {
- const hasEmptyDirectionValue = invalidItem.directionEntries.some(
- (directionItem) => !normalizeTemplateFormText(directionItem.value).trim(),
- );
- createMessage.warning(
- invalidItem.directionEntries.length && !invalidItem.value.trim()
- ? `请选择“${invalidItem.targetLabel}”对应的原始字段`
- : hasEmptyDirectionValue
- ? `请填写“${invalidItem.targetLabel}”的方向配置`
- : `请填写“${invalidItem.targetLabel}”对应的原始字段`,
- );
- return;
- }
- }
- if (!headerValues.some((item) => !!item)) {
- createMessage.warning('请输入表头识别项');
- return;
- }
- templateCreateSaving.value = true;
- try {
- const headerText = headerValues.join(', ');
- templateCreateForm.value.headers = headerText;
- const selectedClassify =
- selectedBaseTemplate?.classify && selectedBaseTemplate.classify !== '-'
- ? selectedBaseTemplate.classify
- : undefined;
- const resolvedTableNameEn = normalizeTemplateFormText(
- templateTargetTemplate.value?.tableNameEn ||
- selectedBaseTemplate?.tableNameEn ||
- templateCurrentDetail.value?.tableNameEn,
- );
- const payload: TableInfo = {
- tableNameCn,
- tableNameEn: resolvedTableNameEn || undefined,
- hasNew: isChildTemplate,
- lineNo,
- headers: headerValues.join(','),
- classify: selectedClassify || templateEditingClassify.value,
- funcRegx: buildTemplateFuncRegx() || undefined,
- };
- const tableFields = buildTemplateTableFields(headerValues);
- if (tableFields.length) {
- payload.tableFields = tableFields;
- }
- if (requiresMainTemplate && form.mainId) {
- payload.mainId = Number(form.mainId);
- }
- if (templateFormMode.value === 'create') {
- payload.hasInner = '1';
- await tplCreate(undefined, payload);
- createMessage.success('新增模板成功');
- } else {
- if (!templateEditingId.value) {
- createMessage.warning('缺少模板ID,暂时无法更新');
- return;
- }
- payload.id = templateEditingId.value;
- if (templateEditingHasInner.value) {
- payload.hasInner = templateEditingHasInner.value;
- }
- await tplUpdate(undefined, payload);
- createMessage.success('模板更新成功');
- }
- templateCreateVisible.value = false;
- resetTemplateCreateForm();
- templatePagination.value.page = 1;
- templateCatalogRows.value = [];
- await loadTemplateList();
- } catch (error) {
- const actionLabel = templateFormMode.value === 'create' ? '新增模板' : '更新模板';
- console.error(`${actionLabel}失败`, error);
- if (!getRequestErrorMessage(error, '')) {
- createMessage.error(`${actionLabel}失败`);
- }
- } finally {
- templateCreateSaving.value = false;
- }
- };
- const handleTemplateDelete = async (record: any) => {
- if (isSystemTemplateRecord(record)) {
- createMessage.warning('系统内置模板不支持删除');
- return;
- }
- if (!record?.id) {
- createMessage.warning('缺少模板ID,暂时无法删除');
- return;
- }
- try {
- await tplDelete({ id: Number(record.id) });
- createMessage.success('模板删除成功');
- templateCatalogRows.value = templateCatalogRows.value.filter((item) => Number(item.id) !== Number(record.id));
- if (templateEditingId.value && Number(templateEditingId.value) === Number(record.id)) {
- templateCreateVisible.value = false;
- resetTemplateCreateForm();
- }
- const nextTotal = Math.max(0, Number(templatePagination.value.total || 0) - 1);
- const pageSize = Math.max(1, Number(templatePagination.value.limit) || componentSetting.table.defaultPageSize);
- const nextMaxPage = Math.max(1, Math.ceil(nextTotal / pageSize));
- templatePagination.value.page = Math.min(templatePagination.value.page, nextMaxPage);
- await loadTemplateList();
- } catch (error) {
- console.error('删除模板失败', error);
- createMessage.error('删除模板失败');
- }
- };
- const templateActionColumn: BasicColumn = {
- title: '操作',
- width: 120,
- fixed: 'right',
- align: 'center',
- actions: (record: any) => [
- {
- title: '编辑',
- tooltip: '编辑模板',
- icon: 'i-ant-design:edit-outlined',
- onClick: () => handleTemplateEdit(record),
- },
- {
- title: '删除',
- tooltip: isSystemTemplateRecord(record) ? '系统内置模板不支持删除' : '删除模板',
- icon: 'i-ant-design:delete-outlined',
- color: 'error',
- disabled: isSystemTemplateRecord(record),
- popConfirm: isSystemTemplateRecord(record)
- ? undefined
- : {
- title: `是否确认删除模板“${getTemplateDisplayName(record)}”?`,
- confirm: () => handleTemplateDelete(record),
- },
- },
- ],
- };
- const templateRows = ref<any[]>([]);
- const templatePageSizeOptions = componentSetting.table.pageSizeOptions.map((value) => ({ label: value, value }));
- const templatePagination = ref({
- page: 1,
- limit: componentSetting.table.defaultPageSize,
- total: 0,
- });
- const templateTotalPages = computed(() => {
- if (!templatePagination.value.total) return 1;
- return Math.max(1, Math.ceil(templatePagination.value.total / templatePagination.value.limit));
- });
- const loadTemplateList = async () => {
- templateLoading.value = true;
- try {
- const normalizeTemplateQueryValue = (value: unknown) => {
- if (value === null || value === undefined) return '';
- const text = String(value).trim();
- if (!text || text === 'null' || text === 'undefined' || text === '全部') return '';
- return text;
- };
- const tableNameCn = normalizeTemplateQueryValue(templateQuery.value.tableNameCn);
- const classify = normalizeTemplateQueryValue(templateQuery.value.classify);
- const params: Record<string, any> = {
- page: templatePagination.value.page,
- limit: templatePagination.value.limit,
- };
- if (tableNameCn) {
- params.tableNameCn = tableNameCn;
- }
- if (classify) {
- params.classify = classify;
- }
- const resp = await tplPage(params as any);
- const records = Array.isArray((resp as any)?.list || (resp as any)?.records)
- ? (resp as any)?.list || (resp as any)?.records
- : [];
- const total = Number((resp as any)?.count ?? (resp as any)?.total ?? records.length);
- const currentPage = Math.max(1, Number(templatePagination.value.page) || 1);
- const pageSize = Math.max(1, Number(templatePagination.value.limit) || componentSetting.table.defaultPageSize);
- const maxPage = Math.max(1, Math.ceil(total / pageSize));
- if (currentPage > maxPage) {
- templatePagination.value.page = maxPage;
- await loadTemplateList();
- return;
- }
- // 兼容后端返回全量列表但 total 正常的场景,前端按当前页进行一次兜底切片。
- const pageRecords =
- records.length > pageSize ? records.slice((currentPage - 1) * pageSize, currentPage * pageSize) : records;
- templateRows.value = pageRecords.map((item: any, index: number) => normalizeTemplateRecord(item, index));
- templatePagination.value.total = total;
- } catch (error) {
- console.error('加载模板库失败', error);
- templateRows.value = [];
- templatePagination.value.total = 0;
- } finally {
- templateLoading.value = false;
- }
- };
- const handleTemplateSearch = async () => {
- templatePagination.value.page = 1;
- await loadTemplateList();
- };
- const handleTemplateReset = async () => {
- templateQuery.value.tableNameCn = '';
- templateQuery.value.classify = '';
- templatePagination.value.page = 1;
- await loadTemplateList();
- };
- const handleTemplatePrevPage = async () => {
- if (templatePagination.value.page <= 1) return;
- templatePagination.value.page -= 1;
- await loadTemplateList();
- };
- const handleTemplateNextPage = async () => {
- if (templatePagination.value.page >= templateTotalPages.value) return;
- templatePagination.value.page += 1;
- await loadTemplateList();
- };
- const handleTemplatePageChange = async (page: number, pageSize: number) => {
- if (templateLoading.value) return;
- if (pageSize !== templatePagination.value.limit) {
- await handleTemplatePageSizeChange(pageSize);
- return;
- }
- if (page === templatePagination.value.page - 1) {
- await handleTemplatePrevPage();
- return;
- }
- if (page === templatePagination.value.page + 1) {
- await handleTemplateNextPage();
- return;
- }
- if (page === templatePagination.value.page) return;
- templatePagination.value.page = page;
- await loadTemplateList();
- };
- const handleTemplatePageSizeChange = async (size: number) => {
- templatePagination.value.limit = Number(size);
- templatePagination.value.page = 1;
- await loadTemplateList();
- };
- const selecting = ref(false);
- const uploadProcessing = ref(false);
- const PRE_FILE_EMPTY_MESSAGE = '未识别到可处理文件,可能是文件重复、所有文件不存在,或文件格式不正确';
- const normalizeUploadPaths = (filePaths: Array<string | undefined | null>) =>
- filePaths.map((item) => String(item ?? '').trim()).filter((item) => !!item);
- const extractUploadFilePath = (file: File) => {
- const electronApi = window.electron;
- const resolvedPath = electronApi?.getPathForFile?.(file) || (file as File & { path?: string })?.path;
- return normalizeUploadPaths([resolvedPath])[0];
- };
- const splitUploadPath = (value: string) => value.replace(/\\/g, '/').split('/').filter(Boolean);
- const extractUploadFolderPath = (file: File) => {
- const filePath = extractUploadFilePath(file);
- if (!filePath) return '';
- const relativePath = String((file as File & { webkitRelativePath?: string }).webkitRelativePath || '').trim();
- const relativeSegments = splitUploadPath(relativePath);
- if (relativeSegments.length <= 1) return '';
- const fileSegments = splitUploadPath(filePath);
- const rootSegmentCount = fileSegments.length - relativeSegments.length + 1;
- if (rootSegmentCount <= 0) return '';
- const separator = filePath.includes('\\') ? '\\' : '/';
- return fileSegments.slice(0, rootSegmentCount).join(separator);
- };
- const processUploadFilePaths = async (filePaths: string[]) => {
- const normalizedPaths = normalizeUploadPaths(filePaths);
- if (!normalizedPaths.length || uploadProcessing.value) return;
- uploadProcessing.value = true;
- selecting.value = true;
- try {
- const isDir = uploadMode.value === 'folder';
- const response = await preFile({ filePath: normalizedPaths, isDir });
- const fileInfos = Array.isArray(response) ? response : ((response as any)?.data ?? []);
- if (Array.isArray(fileInfos) && fileInfos.length) {
- sessionStorage.setItem('case_clean_file_infos', JSON.stringify(fileInfos));
- createMessage.success('文件预处理成功');
- handleUploadOk();
- emitter.emit(CASE_IMPORT_FILE_REFRESH_EVENT);
- go(PageEnum.BASE_MATCH);
- } else {
- createMessage.warning(PRE_FILE_EMPTY_MESSAGE);
- }
- } catch (error) {
- createMessage.error('文件预处理失败');
- } finally {
- uploadProcessing.value = false;
- selecting.value = false;
- }
- };
- const queueUploadFilePaths = (filePaths: string[]) => {
- const normalizedPaths = normalizeUploadPaths(filePaths);
- if (!normalizedPaths.length) return;
- pendingUploadPaths.value.push(...normalizedPaths);
- if (uploadBatchTimer) clearTimeout(uploadBatchTimer);
- uploadBatchTimer = setTimeout(() => {
- const batchPaths = Array.from(new Set(pendingUploadPaths.value));
- pendingUploadPaths.value = [];
- uploadBatchTimer = null;
- void processUploadFilePaths(batchPaths);
- }, 0);
- };
- const handleSelectFile = async (event: MouseEvent) => {
- event.stopPropagation();
- if (selecting.value || uploadProcessing.value) return;
- selecting.value = true;
- const isDir = uploadMode.value === 'folder';
- const ipcRenderer = (window as any)?.electron?.ipcRenderer;
- if (!ipcRenderer?.invoke) {
- createMessage.error('无法获取文件路径,请在 Electron 环境下选择文件');
- selecting.value = false;
- return;
- }
- try {
- const channel = isDir ? 'controller/os/selectFolder' : 'controller/effect/selectFile';
- const resolvedPath = await ipcRenderer.invoke(channel);
- const filePaths = Array.isArray(resolvedPath)
- ? resolvedPath.filter((item: unknown): item is string => typeof item === 'string' && !!item.trim())
- : typeof resolvedPath === 'string' && resolvedPath.trim()
- ? [resolvedPath]
- : [];
- if (!filePaths.length) {
- selecting.value = false;
- return;
- }
- await processUploadFilePaths(filePaths);
- } catch (error) {
- createMessage.error('文件预处理失败');
- } finally {
- selecting.value = false;
- }
- };
- const beforeUpload = async (file: File) => {
- const filePath = extractUploadFilePath(file);
- if (!filePath) {
- createMessage.warning('当前拖拽文件未获取到绝对路径,请重新拖拽或点击选择文件');
- return false;
- }
- queueUploadFilePaths([filePath]);
- return false;
- };
- const beforeUploadFolder = async (file: File) => {
- const folderPath = extractUploadFolderPath(file);
- if (!folderPath) {
- createMessage.warning('当前拖拽文件夹未获取到目录路径,请重新拖拽或点击选择文件夹');
- return false;
- }
- queueUploadFilePaths([folderPath]);
- return false;
- };
- 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;
- };
- const handleOpenOfficeFile = async (record: Recordable) => {
- const fileId = resolveOfficeFileId(record);
- if (!fileId) {
- createMessage.warning('未获取到文件ID,暂时无法打开文件');
- return;
- }
- try {
- const response = await openOfficeFileById(fileId);
- if (!isOpenOfficeFileSuccess(response)) {
- createMessage.warning((response as any)?.message || '打开文件失败');
- }
- } catch (error: any) {
- createMessage.error(getOpenOfficeFileErrorMessage(error, '打开文件失败'));
- }
- };
- const createOpenFileActionColumn = (): BasicColumn => ({
- title: '操作',
- dataIndex: 'openFileAction',
- key: 'openFileAction',
- width: 120,
- fixed: 'right',
- align: 'center',
- customRender: ({ record }: any) => {
- const fileId = resolveOfficeFileId(record);
- return h(
- Button,
- {
- type: 'link',
- size: 'small',
- disabled: !fileId,
- onClick: (event: MouseEvent) => {
- event.stopPropagation();
- void handleOpenOfficeFile(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 }: any) => {
- const fileId = resolveOfficeFileId(record);
- 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,
- h(
- Button,
- {
- type: 'link',
- size: 'small',
- disabled: !fileId,
- onClick: (event: MouseEvent) => {
- event.stopPropagation();
- void handleOpenOfficeFile(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(),
- });
- };
- const fetchTableData = async (params: GovernTreeQuery = {}) => {
- loading.value = true;
- try {
- const res = 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 = () => {
- activeTab.value = 'import';
- customQueryParams.value = null;
- clearTableState();
- void fetchTreeData();
- };
- onMounted(() => {
- emitter.on(CASE_DATA_REFRESH_EVENT, handleCaseDataRefresh);
- emitter.on(HEADER_TEMPLATE_LIBRARY_OPEN_EVENT, handleTemplateOpen);
- jumpPage.value = currentPage.value.toString();
- fetchTreeData();
- void restoreActiveImportProgress();
- });
- onBeforeUnmount(() => {
- if (uploadBatchTimer) clearTimeout(uploadBatchTimer);
- emitter.off(CASE_DATA_REFRESH_EVENT, handleCaseDataRefresh, true);
- emitter.off(HEADER_TEMPLATE_LIBRARY_OPEN_EVENT, handleTemplateOpen, 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: 10px 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: 34px;
- padding: 0 16px;
- border-radius: 17px;
- 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: 34px;
- padding: 0 16px;
- border-radius: 17px;
- 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: -10px;
- left: 0;
- height: 1px;
- background: #e5e7eb;
- content: '';
- }
- &__item {
- position: relative;
- z-index: 1;
- display: inline-flex;
- align-items: center;
- justify-content: center;
- height: 36px;
- padding: 0;
- border: 0;
- background: transparent;
- color: #111827;
- font-size: 14px;
- line-height: 36px;
- cursor: pointer;
- &::after {
- position: absolute;
- right: 0;
- bottom: -10px;
- 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;
- }
- }
- }
- .case-template-library-modal {
- .query-table-content {
- .ant-table-cell-fix-right {
- background: #fff;
- }
- .ant-table-tbody > tr:nth-child(even) > .ant-table-cell-fix-right {
- background: #fcfdff;
- }
- .ant-table-tbody > tr:hover > .ant-table-cell-fix-right {
- background: #eff6ff;
- }
- .jeesite-basic-table-action {
- justify-content: center;
- gap: 4px;
- flex-wrap: nowrap;
- .ant-btn-link,
- .ant-btn-link.ant-btn-sm {
- width: 28px;
- min-width: 28px;
- height: 28px;
- padding: 0;
- border-radius: 8px;
- color: #315efb;
- transition:
- color 0.2s ease,
- background-color 0.2s ease;
- &:hover {
- color: #1d4ed8;
- background: #eff6ff;
- }
- }
- .ant-btn-link.ant-btn-error,
- .ant-btn-link.ant-btn-error.ant-btn-sm {
- color: #ef4444;
- &:hover {
- color: #dc2626;
- background: #fef2f2;
- }
- }
- .ant-btn-link.is-disabled,
- .ant-btn-link.is-disabled.ant-btn-sm {
- color: rgb(0 0 0 / 25%);
- .jeesite-icon {
- color: rgb(0 0 0 / 25%) !important;
- }
- &:hover {
- color: rgb(0 0 0 / 25%);
- background: transparent;
- }
- }
- }
- }
- }
- .case-template-create-modal {
- .ant-modal {
- width: min(720px, calc(100vw - 48px)) !important;
- top: 56px;
- }
- .ant-modal-content {
- display: flex;
- flex-direction: column;
- max-height: calc(100vh - 88px);
- border: 1px solid #e7edf5;
- border-radius: 16px;
- overflow: hidden;
- box-shadow:
- 0 20px 52px rgba(15, 23, 42, 0.14),
- 0 2px 8px rgba(15, 23, 42, 0.04);
- }
- .ant-modal-header {
- flex: 0 0 auto;
- padding: 12px 16px;
- border-bottom: 1px solid #e5e7eb;
- background: @modal-header-bg-color;
- }
- .ant-modal-title {
- color: #111827;
- line-height: 24px;
- }
- .ant-modal-body {
- flex: 1 1 auto;
- min-height: 0;
- overflow-y: auto;
- padding: 16px;
- background: linear-gradient(180deg, #f8fbff 0%, #ffffff 112px);
- overscroll-behavior: contain;
- &::-webkit-scrollbar {
- width: 8px;
- }
- &::-webkit-scrollbar-thumb {
- background: rgba(148, 163, 184, 0.65);
- border-radius: 999px;
- }
- &::-webkit-scrollbar-track {
- background: transparent;
- }
- }
- .ant-modal-footer {
- flex: 0 0 auto;
- padding: 8px 16px 10px;
- border-top: 1px solid #e5e7eb;
- background: #fff;
- }
- .template-create-title {
- display: inline-flex;
- align-items: center;
- gap: 12px;
- color: #111827;
- font-size: 16px;
- font-weight: 600;
- }
- .template-create-title-icon {
- width: 28px;
- height: 28px;
- display: inline-flex;
- align-items: center;
- justify-content: center;
- border-radius: 9px;
- color: #fff;
- background: linear-gradient(135deg, #316dff 0%, #4f8bff 100%);
- box-shadow: 0 10px 20px rgba(49, 109, 255, 0.22);
- .jeesite-icon {
- font-size: 16px;
- }
- }
- .template-create-body {
- display: flex;
- flex-direction: column;
- gap: 16px;
- min-height: 0;
- }
- .template-create-intro {
- position: relative;
- padding: 14px 16px 14px 18px;
- border: 1px solid #deebff;
- border-radius: 14px;
- background: linear-gradient(135deg, #f5f9ff 0%, #ffffff 72%);
- box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.7);
- &::before {
- content: '';
- position: absolute;
- left: 0;
- top: 14px;
- bottom: 14px;
- width: 4px;
- border-radius: 999px;
- background: linear-gradient(180deg, #5b8cff 0%, #2f65f5 100%);
- }
- }
- .template-create-intro__title {
- color: #111827;
- font-size: 14px;
- font-weight: 600;
- }
- .template-create-intro__desc {
- margin-top: 4px;
- color: #6b7280;
- font-size: 12px;
- line-height: 1.7;
- }
- .template-create-form-panel {
- display: flex;
- flex-direction: column;
- gap: 14px;
- padding: 16px;
- border: 1px solid #e6edf6;
- border-radius: 14px;
- background: #fff;
- box-shadow: 0 8px 24px rgba(15, 23, 42, 0.04);
- }
- .template-create-row {
- display: grid;
- grid-template-columns: 116px minmax(0, 1fr);
- align-items: center;
- gap: 12px;
- &--textarea {
- align-items: start;
- }
- }
- .template-create-label {
- color: #475569;
- font-size: 14px;
- text-align: right;
- line-height: 40px;
- white-space: nowrap;
- font-weight: 500;
- &.required::before {
- content: '*';
- color: #ef4444;
- margin-right: 4px;
- }
- }
- .template-create-field {
- min-width: 0;
- .ant-select,
- .ant-input-affix-wrapper,
- .ant-input,
- .ant-input-textarea {
- width: 100%;
- }
- .ant-select-selector,
- .ant-input,
- .ant-input-affix-wrapper,
- .ant-input-textarea textarea.ant-input {
- border-color: #d7e1ee !important;
- background: #fbfcfe;
- transition:
- border-color 0.2s ease,
- box-shadow 0.2s ease,
- background-color 0.2s ease;
- }
- .ant-select-selector,
- .ant-input,
- .ant-input-affix-wrapper {
- min-height: 40px;
- border-radius: 10px !important;
- }
- .ant-select-single .ant-select-selector {
- display: flex;
- align-items: center;
- height: 40px;
- padding-top: 0;
- padding-bottom: 0;
- }
- .ant-select-single .ant-select-selector .ant-select-selection-item,
- .ant-select-single .ant-select-selector .ant-select-selection-placeholder {
- line-height: 38px;
- }
- .ant-select:hover .ant-select-selector,
- .ant-input:hover,
- .ant-input-affix-wrapper:hover {
- border-color: #9cbcff !important;
- background: #fff;
- }
- .ant-select-focused .ant-select-selector,
- .ant-input:focus,
- .ant-input-affix-wrapper-focused {
- background: #fff;
- box-shadow: 0 0 0 3px rgba(49, 109, 255, 0.12) !important;
- }
- .ant-input-textarea textarea.ant-input {
- min-height: 120px !important;
- border-radius: 10px !important;
- resize: none;
- }
- }
- .template-create-field-head {
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 12px;
- margin-bottom: 12px;
- }
- .template-create-field-mode {
- display: inline-flex;
- align-items: center;
- height: 28px;
- padding: 0 12px;
- border-radius: 999px;
- border: 1px solid #dbe8fb;
- background: #f5f9ff;
- color: #3157b8;
- font-size: 12px;
- font-weight: 600;
- letter-spacing: 0.2px;
- }
- .template-create-transform-btn {
- display: inline-flex;
- align-items: center;
- gap: 6px;
- height: 30px;
- padding: 0 12px;
- border-radius: 999px;
- background: #eef4ff;
- color: #315efb;
- font-weight: 600;
- .jeesite-icon {
- font-size: 14px;
- }
- &:hover {
- color: #1d4ed8;
- background: #e3edff;
- }
- }
- .template-header-builder {
- border: 1px solid #dce6f3;
- border-radius: 14px;
- background: linear-gradient(180deg, #f9fbff 0%, #ffffff 100%);
- padding: 8px 10px;
- display: flex;
- flex-direction: column;
- gap: 6px;
- }
- .template-header-builder__head {
- display: grid;
- grid-template-columns: minmax(0, 168px) minmax(0, 1fr);
- align-items: center;
- gap: 12px;
- padding: 0 2px;
- color: #64748b;
- font-size: 12px;
- font-weight: 600;
- letter-spacing: 0.2px;
- }
- .template-header-builder__list {
- max-height: 272px;
- display: flex;
- flex-direction: column;
- gap: 6px;
- overflow: auto;
- padding-right: 4px;
- }
- .template-header-builder__row {
- display: grid;
- grid-template-columns: minmax(0, 168px) minmax(0, 1fr);
- align-items: start;
- gap: 10px;
- &.is-merge-disabled {
- .template-header-builder__preview {
- border-color: #d7e4f3;
- background: linear-gradient(180deg, #f6f8fb 0%, #edf2f7 100%);
- color: #64748b;
- }
- }
- }
- .template-header-builder__preview {
- align-self: center;
- min-height: 32px;
- padding: 0 10px;
- border: 1px solid #dce5f1;
- border-radius: 8px;
- background: linear-gradient(180deg, #f6f9fd 0%, #eef3f8 100%);
- color: #334155;
- font-size: 13px;
- font-weight: 600;
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 8px;
- &.is-required {
- border-color: #f6c7c7;
- background: linear-gradient(180deg, #fff7f7 0%, #fff1f1 100%);
- }
- }
- .template-header-builder__preview-label {
- min-width: 0;
- display: inline-flex;
- align-items: center;
- gap: 4px;
- flex: 1 1 auto;
- }
- .template-header-builder__required-mark {
- flex: 0 0 auto;
- color: #ef4444;
- font-size: 14px;
- font-weight: 700;
- line-height: 1;
- }
- .template-header-builder__preview-text {
- min-width: 0;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- }
- .template-header-builder__preview-badge {
- flex: 0 0 auto;
- height: 20px;
- padding: 0 7px;
- border-radius: 999px;
- background: rgba(239, 68, 68, 0.1);
- color: #dc2626;
- font-size: 11px;
- font-weight: 700;
- line-height: 20px;
- }
- .template-header-builder__direction-config {
- display: grid;
- grid-template-columns: minmax(0, 1fr) minmax(0, 1.28fr);
- align-items: center;
- gap: 8px;
- min-height: 72px;
- min-width: 0;
- &.has-switch-field {
- grid-template-columns: minmax(0, 1.08fr) minmax(0, 1fr);
- }
- }
- .template-header-builder__source-config {
- display: grid;
- grid-template-columns: minmax(0, 1fr) minmax(0, 148px);
- align-items: center;
- gap: 8px;
- min-width: 0;
- }
- .template-header-builder__direction-main {
- display: grid;
- grid-template-columns: minmax(0, 1fr);
- align-items: center;
- gap: 8px;
- min-width: 0;
- &.has-switch-field {
- grid-template-columns: minmax(0, 1fr) minmax(0, 148px);
- }
- }
- .template-header-builder__source-config-select {
- min-width: 0;
- }
- .template-header-builder__source-field {
- align-self: center;
- min-width: 0;
- }
- .template-header-builder__switch-field {
- min-height: 32px;
- padding: 0 10px;
- border: 1px solid #dce5f1;
- border-radius: 8px;
- background: linear-gradient(180deg, #f8fbff 0%, #f2f7fd 100%);
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 8px;
- }
- .template-header-builder__switch-label {
- color: #475569;
- font-size: 12px;
- font-weight: 600;
- line-height: 1;
- white-space: nowrap;
- }
- .template-header-builder__direction-list {
- display: flex;
- flex-direction: column;
- gap: 8px;
- min-width: 0;
- }
- .template-header-builder__direction-row {
- display: flex;
- align-items: center;
- gap: 6px;
- min-height: 32px;
- min-width: 0;
- }
- .template-header-builder__direction-label {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- min-height: 26px;
- padding: 0 8px;
- border-radius: 7px;
- background: #f3f6fb;
- color: #475569;
- font-size: 12px;
- font-weight: 600;
- line-height: 1;
- white-space: nowrap;
- }
- .template-header-builder__direction-value {
- min-width: 0;
- flex: 1 1 auto;
- }
- .template-header-builder__direction-value .ant-input {
- width: 100%;
- }
- .template-header-builder__list {
- .ant-select-single .ant-select-selector,
- .ant-input {
- min-height: 32px;
- height: 32px;
- border-radius: 8px !important;
- }
- .ant-select-single .ant-select-selector {
- display: flex;
- align-items: center;
- }
- .ant-select-single .ant-select-selector .ant-select-selection-item,
- .ant-select-single .ant-select-selector .ant-select-selection-placeholder {
- line-height: 30px;
- }
- }
- .template-header-builder__empty {
- padding: 28px 16px;
- border: 1px dashed #d7e4f3;
- border-radius: 12px;
- background: #f8fbff;
- color: #94a3b8;
- font-size: 13px;
- text-align: center;
- line-height: 1.7;
- }
- .template-create-tip {
- margin-top: 10px;
- color: #64748b;
- font-size: 12px;
- line-height: 1.6;
- }
- .template-create-merge-panel {
- border: 1px solid #dce6f3;
- border-radius: 14px;
- background: linear-gradient(180deg, #f9fbff 0%, #ffffff 100%);
- padding: 12px;
- }
- .template-create-merge-head {
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 12px;
- margin-bottom: 10px;
- cursor: pointer;
- }
- .template-create-merge-summary {
- min-width: 0;
- display: flex;
- flex-direction: column;
- gap: 4px;
- }
- .template-create-merge-title-wrap {
- display: flex;
- align-items: center;
- gap: 10px;
- flex-wrap: wrap;
- }
- .template-create-merge-title {
- color: #111827;
- font-size: 15px;
- font-weight: 700;
- }
- .template-create-merge-count {
- display: inline-flex;
- align-items: center;
- height: 24px;
- padding: 0 10px;
- border-radius: 999px;
- background: #eef4ff;
- color: #315efb;
- font-size: 12px;
- font-weight: 700;
- line-height: 24px;
- }
- .template-create-merge-desc {
- color: #6b7280;
- font-size: 12px;
- line-height: 1.6;
- }
- .template-create-merge-actions {
- display: flex;
- align-items: center;
- gap: 0;
- flex-shrink: 0;
- }
- .template-create-merge-toggle {
- display: inline-flex;
- align-items: center;
- gap: 6px;
- height: 28px;
- padding: 0 10px;
- color: #315efb;
- font-weight: 600;
- .jeesite-icon {
- font-size: 12px;
- }
- }
- .template-create-merge-list {
- max-height: 220px;
- overflow: auto;
- display: flex;
- flex-direction: column;
- gap: 8px;
- padding-right: 2px;
- }
- .template-create-merge-item {
- position: relative;
- display: grid;
- grid-template-columns: 48px minmax(0, 1fr);
- align-items: center;
- gap: 10px;
- padding: 10px 12px;
- border: 1px solid #dce5f1;
- border-radius: 12px;
- background: #fff;
- box-shadow: 0 6px 18px rgba(15, 23, 42, 0.04);
- }
- .template-create-merge-item__index {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- width: 48px;
- height: 40px;
- border-radius: 10px;
- background: linear-gradient(180deg, #f6f9fd 0%, #eef4ff 100%);
- color: #315efb;
- font-size: 13px;
- font-weight: 700;
- line-height: 1;
- }
- .template-create-merge-item__content {
- display: grid;
- grid-template-columns: repeat(3, minmax(0, 1fr));
- align-items: center;
- gap: 10px;
- min-width: 0;
- }
- .template-create-merge-field {
- min-width: 0;
- display: flex;
- flex-direction: column;
- gap: 6px;
- }
- .template-create-merge-field__label {
- color: #64748b;
- font-size: 12px;
- font-weight: 600;
- line-height: 1.4;
- }
- .template-create-merge-field__readonly {
- display: flex;
- align-items: center;
- min-height: 32px;
- padding: 0 12px;
- border: 1px solid #d7e4f3;
- border-radius: 8px;
- background: linear-gradient(180deg, #f8fbff 0%, #f3f7ff 100%);
- color: #1f2937;
- font-size: 13px;
- font-weight: 500;
- line-height: 1.5;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- &.is-empty {
- color: #94a3b8;
- }
- }
- .template-create-merge-empty,
- .template-create-merge-entry {
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- gap: 8px;
- padding: 24px 16px;
- border: 1px dashed #d7e4f3;
- border-radius: 12px;
- background: #f8fbff;
- text-align: center;
- }
- .template-create-merge-empty__title {
- color: #111827;
- font-size: 14px;
- font-weight: 600;
- }
- .template-create-merge-empty__desc {
- color: #6b7280;
- font-size: 12px;
- line-height: 1.6;
- }
- .template-create-merge-entry-btn {
- display: inline-flex;
- align-items: center;
- gap: 6px;
- height: 36px;
- padding: 0 14px;
- border-radius: 10px;
- border-color: #cfe0ff;
- color: #315efb;
- background: #fff;
- font-weight: 600;
- &:hover {
- border-color: #9cbcff;
- color: #1d4ed8;
- }
- }
- .template-create-footer {
- display: flex;
- justify-content: flex-end;
- gap: 10px;
- }
- }
- .template-create-select-popup {
- z-index: 1200 !important;
- }
- .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;
- }
- }
- .case-template-create-modal {
- .template-create-intro {
- padding-left: 12px;
- padding-right: 12px;
- }
- .template-create-row {
- grid-template-columns: 1fr;
- gap: 6px;
- }
- .template-create-label {
- text-align: left;
- line-height: 1.6;
- }
- .template-create-field-head {
- align-items: flex-start;
- flex-wrap: wrap;
- }
- .template-header-builder__head {
- display: none;
- }
- .template-header-builder__row {
- grid-template-columns: 1fr;
- gap: 8px;
- }
- .template-header-builder__direction-config,
- .template-header-builder__direction-main,
- .template-header-builder__source-config {
- grid-template-columns: 1fr;
- }
- .template-create-merge-head {
- align-items: flex-start;
- flex-wrap: wrap;
- }
- .template-create-merge-actions {
- width: auto;
- justify-content: flex-end;
- }
- .template-create-merge-item {
- grid-template-columns: 1fr;
- gap: 10px;
- }
- .template-create-merge-item__index {
- width: 100%;
- justify-content: flex-start;
- padding: 0 12px;
- }
- .template-create-merge-item__content {
- grid-template-columns: 1fr;
- gap: 10px;
- }
- }
- }
- .case-upload-modal {
- .ant-modal {
- width: min(760px, calc(100vw - 48px)) !important;
- max-width: calc(100vw - 48px);
- top: 40px;
- padding-bottom: 20px;
- }
- .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-body {
- padding: 16px;
- }
- .ant-modal-footer {
- display: none;
- }
- .ant-btn {
- border-radius: 10px;
- }
- .ant-btn-primary {
- height: 34px;
- padding: 0 18px;
- }
- .upload-modal-body {
- display: flex;
- flex-direction: column;
- gap: 16px;
- padding: 0;
- min-height: 0;
- }
- .upload-modal-header {
- display: flex;
- flex-direction: column;
- gap: 12px;
- padding: 0;
- }
- .upload-mode-switch {
- display: inline-flex;
- align-items: center;
- gap: 6px;
- width: fit-content;
- padding: 4px;
- border-radius: 999px;
- background: #f1f5f9;
- border: 1px solid #dbe5f0;
- box-shadow: inset 0 1px 1px rgba(255, 255, 255, 0.7);
- }
- .upload-mode-switch__item {
- min-width: 108px;
- height: 34px;
- padding: 0 16px;
- border: none;
- border-radius: 999px;
- background: transparent;
- color: #64748b;
- font-size: 13px;
- font-weight: 600;
- cursor: pointer;
- transition:
- background-color 0.2s ease,
- color 0.2s ease,
- box-shadow 0.2s ease;
- &.is-active {
- background: linear-gradient(135deg, #316dff 0%, #4f8bff 100%);
- color: #fff;
- box-shadow: 0 8px 18px rgba(49, 109, 255, 0.22);
- }
- &:disabled {
- cursor: not-allowed;
- opacity: 0.6;
- }
- }
- .upload-modal-title {
- font-size: 14px;
- font-weight: 600;
- color: #0f172a;
- letter-spacing: 0.2px;
- }
- .upload-modal-subtitle {
- font-size: 12px;
- color: #64748b;
- }
- .upload-dropzone {
- flex: 0 0 auto;
- .ant-spin-nested-loading,
- .ant-spin-container {
- display: block;
- }
- .ant-upload-drag {
- min-height: 148px;
- border-radius: 14px;
- border: 1px dashed #c7d2fe;
- background: linear-gradient(180deg, #f8fafc 0%, #ffffff 100%);
- transition: all 0.2s ease;
- }
- .ant-upload-drag:hover {
- border-color: #3b82f6;
- box-shadow: 0 12px 30px rgba(15, 23, 42, 0.08);
- }
- .ant-upload.ant-upload-drag {
- padding: 0;
- }
- .ant-upload-drag-container {
- height: 100%;
- }
- }
- .upload-dragger-content {
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- gap: 8px;
- padding: 18px 0;
- color: #475569;
- }
- .upload-dragger-icon {
- width: 40px;
- height: 40px;
- border-radius: 12px;
- background: rgba(59, 130, 246, 0.12);
- color: #2563eb;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 18px;
- margin-bottom: 2px;
- }
- .upload-dragger-title {
- font-size: 14px;
- font-weight: 700;
- color: #0f172a;
- }
- .upload-dragger-desc {
- font-size: 12px;
- color: #64748b;
- max-width: 520px;
- }
- .upload-action-btn {
- margin-top: 4px;
- height: 32px;
- padding: 0 18px;
- border-radius: 10px;
- box-shadow: 0 8px 18px rgba(37, 99, 235, 0.22);
- }
- }
- @media (max-width: 960px) {
- .case-upload-modal {
- .upload-mode-switch {
- width: 100%;
- }
- .upload-mode-switch__item {
- flex: 1 1 0;
- min-width: 0;
- }
- }
- }
- 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>
|