objList.jsp 136 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250
  1. <%@ taglib prefix="ss" uri="/ssTag" %>
  2. <%@ page language="java" pageEncoding="UTF-8" isELIgnored="false" %>
  3. <%@ taglib uri="/ssTag" prefix="ss" %>
  4. <%pageContext.setAttribute("wdpageinformation", "{'hastab':'0'}");%>
  5. <!DOCTYPE html>
  6. <html>
  7. <head>
  8. <meta http-equiv="pragma" content="no-cache">
  9. <meta http-equiv="cache-control" content="no-cache">
  10. <meta http-equiv="expires" content="0">
  11. <script>window.loginStatus = "${empty sessionScope['ssUser']?'0':'1'}"</script>
  12. <ss:skin file='main.css'/>
  13. <script type="text/javascript" src="/ss/jquery/jquery.js"></script>
  14. <script type="text/javascript" src="/ss/artdialog/artDialogUtil.js"></script>
  15. <script type="text/javascript" src="/ss/js/base.js"></script>
  16. <script> if (!window.wd) var wd = {};
  17. if (!wd.display) wd.display = {};
  18. wd.display.wdDialogId = "objList";
  19. if (!wd.app) wd.app = {};
  20. wd.app.name = 'pms';</script>
  21. <script type="text/javascript" src="/ss/js/masklayer.js"></script>
  22. <script type="text/javascript" src="/ss/js/wdDialogInit.js"></script>
  23. <script type="text/javascript" src="/ss/js/common.js"></script>
  24. <script type="text/javascript" src="/ss/js/display.js"></script>
  25. <script type="text/javascript" src="/ss/js/edit.js"></script>
  26. <script type="text/javascript" src="/ss/nicescroll/jquery.nicescroll.js"></script>
  27. <script type="text/javascript" src="/ss/nicescroll/jquery.nicescroll.iframehelper.min.js"></script>
  28. <link rel="stylesheet" type="text/css" href="/ss/window/theme/dhtmlxwindows.css">
  29. <link rel="stylesheet" type="text/css" href="/ss/window/theme/dhx_blue/dhtmlxwindows_dhx_blue.css">
  30. <script type="text/javascript" src="/ss/window/dhtmlxcommon.js"></script>
  31. <script type="text/javascript" src="/ss/window/dhtmlxwindows.js"></script>
  32. <script type="text/javascript" src="/ss/window/dhtmlxcontainer.js"></script>
  33. <script type="text/javascript" src="/ss/js/common.js"></script>
  34. <script type="text/javascript" src="/ss/js/display.js"></script>
  35. <script type="text/javascript" src="/ss/js/edit.js"></script>
  36. <ssDlg setPval="true"/>
  37. <%-- setValue="true" close="true"。Lin --%>
  38. <script type="text/javascript" src="/ss/jquery/jquery.ellipsis.js"></script>
  39. <%-- 新UI引入的JS --%>
  40. <script src="/js/load.js"></script>
  41. <%-- ="/newUI/ss/js/base.js"。Lin(新UI) --%>
  42. <script src="/js/pageC.js"></script>
  43. <%-- 对象变动、修改、查看页,左则选项卡宽度 在里面定义 Ben(20251213) --%>
  44. <%-- 改为 <data@ss name="prt"/>。Lin
  45. <tab@ss name="print" enable="viewobject.funcMap.play.print"/> --%>
  46. <ss:data name='prt'/><%-- 初始化弹出窗口的打印按钮的后台数据 Ben--%>
  47. <%@include file="/ss/clip/prtIcon.jsp" %>
  48. <%-- 引入初始化弹出窗口的打印按钮的JS Ben--%>
  49. <script>
  50. window.ss.dom.formElemConfig = window.ss.dom.formElemConfig || {};
  51. </script>
  52. <style>
  53. /* 首页 loading:没加载完不显示页面内容 */
  54. .pobj-loading-home {
  55. position: fixed;
  56. left: 50%;
  57. top: 50%;
  58. transform: translate(-50%, -50%);
  59. z-index: 9999;
  60. pointer-events: none;
  61. }
  62. /* 列表 loading:区域性(搜索条件和分页之间) */
  63. .pobj-list-loading {
  64. width: 100%;
  65. min-height: 260px;
  66. display: flex;
  67. align-items: center;
  68. justify-content: center;
  69. }
  70. .pobj-spinner {
  71. width: 18px;
  72. height: 18px;
  73. border-radius: 50%;
  74. border: 2px solid rgba(0, 0, 0, 0.2);
  75. border-top-color: rgba(0, 0, 0, 0.65);
  76. animation: pobjSpin 0.8s linear infinite;
  77. }
  78. @keyframes pobjSpin {
  79. from {
  80. transform: rotate(0deg);
  81. }
  82. to {
  83. transform: rotate(360deg);
  84. }
  85. }
  86. </style>
  87. </head>
  88. <body>
  89. <div id="app">
  90. <div v-show="loadingHome" class="pobj-loading-home" style="display:none;">
  91. <div class="pobj-spinner"></div>
  92. </div>
  93. <%-- 功能说明:移除 thnType 直出调试内容,避免进入页面即输出 by xu 20260123 --%>
  94. <%-- 功能说明:主布局容器(左侧表单 + 右侧 ss-sidebar);为首屏 rowNumPer 自适应计算需要,避免用 homeReady 把容器 display:none(否则无法测量宽高) by xu 20260123 --%>
  95. <div style="display: flex; height: 100%; ">
  96. <%-- 功能说明:左侧表单区域在 flex 中占满剩余宽度(避免挤掉右侧 ss-sidebar) by xu 20260113 --%>
  97. <form class="page-container" id="myForm" style="flex: 1; min-width: 0; width: auto;"
  98. action="<ss:serv name='${currentService.servName}' dest='${currentService.dest}' parm='${currentService.parm}'/>"
  99. method="post">
  100. <input type="hidden" name="isAnd" v-model="form.isAnd"/>
  101. <input name="management" type="hidden" v-model="form.management"/>
  102. <input name="isFulltext" type="hidden" v-model="form.isFulltext"/>
  103. <%-- 功能说明:分页已完全走 Vue+Ajax(ssPaging),不再依赖旧表单提交隐藏域 by xu 20260123 --%>
  104. <%--搜索条件start--%>
  105. <div class="search-bar">
  106. <ss-breadcrumb></ss-breadcrumb>
  107. <div class="search-bar-contaienr">
  108. <template v-if="homeReady">
  109. <template v-for="field in searchFieldList" :key="field.name">
  110. <ss-objp
  111. v-if="field.cbName"
  112. v-model="form[field.name]"
  113. :name="field.name"
  114. :placeholder="field.desc"
  115. width="150px"
  116. :inp="true"
  117. url="/service?ssServ=loadObjpOpt&objectpickerdropdown1=1"
  118. :cb="field.cbName"
  119. ></ss-objp>
  120. <ss-search-date-picker
  121. v-else-if="field.type == 11"
  122. v-model="form[field.name]"
  123. :name="field.name"
  124. type="date"
  125. :placeholder="field.desc"
  126. width="140px"
  127. :fmt="field.fmt"
  128. ></ss-search-date-picker>
  129. <ss-search-date-picker
  130. v-else-if="field.type == 3"
  131. v-model="form[field.name]"
  132. :name="field.name"
  133. type="datetime"
  134. :placeholder="field.desc"
  135. width="200px"
  136. :fmt="field.fmt"
  137. ></ss-search-date-picker>
  138. <ss-search-input
  139. v-else
  140. :name="field.name"
  141. :placeholder="field.desc"
  142. v-model="form[field.name]"
  143. width="120px"
  144. @search="search"
  145. ></ss-search-input>
  146. </template>
  147. <ss-search-input
  148. v-if="hasKeyword"
  149. name="ssKeyword"
  150. placeholder="关键词"
  151. v-model="form.ssKeyword"
  152. width="120px"
  153. @search="search"
  154. ></ss-search-input>
  155. <ss-search-button
  156. v-if="hasScope"
  157. :key="'scope-' + String(searchButtonConfigCheckId)"
  158. text="所有"
  159. icon-class="nav-icon-search"
  160. :opt="searchButtonConfig"
  161. :check-id="searchButtonConfigCheckId"
  162. ></ss-search-button>
  163. <template v-for="func in rootFuncList" :key="func.id || func.servName">
  164. <ss-search-button
  165. :text="func.desc || func.title || ''"
  166. icon-class="nav-icon-add"
  167. :opt="[]"
  168. :check-id="0"
  169. @click="handleRootFuncClick(func)"
  170. ></ss-search-button>
  171. </template>
  172. </template>
  173. <template v-else>
  174. <%--新UI 单对象查询翻页end Ben --%>
  175. <%--关键词--%>
  176. <%-- 再去掉 -- 只支持一行。Lin
  177. <ss:rpt name='searchableFields' id='fieldsList'>
  178. --%>
  179. <ss:rpt name='fieldsList' id='searchItem'>
  180. <%
  181. ss.obj.ObjFieldB f = (ss.obj.ObjFieldB) pageContext.getAttribute("searchItem");
  182. pageContext.setAttribute("searchItemValue", request.getParameter(f.name));
  183. %>
  184. <%--codebook 使用select下拉--%>
  185. <ss:equal val='${empty searchItem.cbName}' val2='false'>
  186. <%-- 旧的objecPicker
  187. <input type="text" name="${searchItem.name}" value="${searchItemValue}"/>
  188. <input type="text" name="${searchItem.name}Name" placeholder="${searchItem.desc}" style="width:${searchItem.width};min-width: 108px;"/>
  189. <ss:equal val='${empty cadcadingInputs[searchItem.name]}' val2='true'>
  190. <ss:objp name='${searchItem.name}' cb='${searchItem.cbName}' inp='true' filterField='${searchItem.filterFieldStr}'/>
  191. </ss:equal> --%>
  192. <%-- 新UI --%>
  193. <!-- <ss-objp
  194. v-model="${searchItem.name}"
  195. name="${searchItem.name}"
  196. :options="${searchItem.name}Option"
  197. placeholder="${searchItem.desc}"
  198. width="120"
  199. input="true"
  200. ></ss-objp> -->
  201. <ss-objp
  202. v-model="${searchItem.name}"
  203. name="${searchItem.name}"
  204. :opt="${searchItem.name}Option"
  205. placeholder="${searchItem.desc}"
  206. width="150px"
  207. :inp="true"
  208. url="/service?ssServ=loadObjpOpt&objectpickerdropdown1=1"
  209. cb="${searchItem.cbName}"
  210. ></ss-objp>
  211. <script>
  212. /**
  213. * objectPicker(如:性别码)
  214. * optUrl:加载下拉菜单option选项的url
  215. */
  216. window.ss.dom.formElemConfig.${searchItem.name} = {
  217. desc: '${searchItem.desc}',
  218. value: '${searchItemValue}',
  219. optUrl: '123456',
  220. type: 2
  221. };//放当前页面表单元素配置的变量
  222. </script>
  223. </ss:equal>
  224. <%--codebook 使用input输入框--%>
  225. <ss:equal val='${empty searchItem.cbName}' val2='true'>
  226. <%--日期--%>
  227. <ss:equal val='${searchItem.type}'
  228. valList='3,11'> <%-- 改 equal:val1='true' val2='${searchItem.type == "time"}'。Lin --%>
  229. <!-- <div class="input-inside"> -->
  230. <%--年度--%>
  231. <%--
  232. <ss:equal val='${searchItem.enrolDate}' val2='true'>
  233. <input type='hidden' placeholder="${searchItem.desc}" name='${searchItem.name}'
  234. value="<ss:txt val='${searchItemValue}'/>"/>
  235. <div><input type="text" name="${searchItem.name}_year" autocomplete="off" />
  236. <input name="${searchItem.name}_month" type="button" value="春季" ssVal="3"/>
  237. <input name="${searchItem.name}_month" type="button" value="秋季" ssVal="9"/>
  238. </div><script>(function(){wd.edit.onoffInit('radio','${searchItem.name}_month','',false,null,null,null,'edit');})();</script>
  239. <script>(function(){
  240. wd.display.initEnrolDate('${searchItem.name}','edit');
  241. })()
  242. </script>
  243. </ss:equal>
  244. --%>
  245. <%--日期--%>
  246. <ss:equal val='${searchItem.type==11}' val2='true'>
  247. <%--<input type='text' autocomplete="off" placeholder="${searchItem.desc}" name='${searchItem.name}'
  248. value='<ss:txt val='${searchItemValue}'/>' format="${searchItem.fmt}"/>
  249. <input type="button" ssType="date" ssName="${searchItem.name}"/>--%>
  250. <!-- <ss-date-picker
  251. v-model="${searchItem.name}"
  252. name="${searchItem.name}"
  253. type="date"
  254. placeholder="${searchItem.desc}"
  255. width="120px"
  256. ></ss-date-picker> -->
  257. <ss-search-date-picker
  258. v-model="${searchItem.name}"
  259. name="${searchItem.name}"
  260. type="date"
  261. placeholder="${searchItem.desc}"
  262. width="100px"
  263. fmt="${searchItem.fmt}"
  264. ></ss-search-date-picker>
  265. <script>
  266. //日期类型(出生日期)
  267. window.ss.dom.formElemConfig.${searchItem.name} = {
  268. desc: '${searchItem.desc}',
  269. value: '${searchItemValue}',
  270. type: 3,
  271. name: '${searchItem.name}'
  272. };//放当前页面表单元素配置的变量
  273. </script>
  274. </ss:equal>
  275. <!-- </div> -->
  276. </ss:equal>
  277. <%--文本--%>
  278. <%-- 改 equal:val1='false' val2='${searchItem.type == "time"}'。Lin --%>
  279. <ss:notEqual val='${searchItem.type}' valList='3,11'>
  280. <%--<input name='${searchItem.name}' placeholder="${searchItem.desc}" type='text'
  281. value='<ss:txt val='${searchItemValue}'/>'/>
  282. <input type="hidden" ssType="and" ssName="${searchItem.name}"/>--%>
  283. <%--<script>wd.edit.addClearTextButton("${searchItem.name}");</script>--%>
  284. <ss-search-input
  285. name="${searchItem.name}"
  286. placeholder="${searchItem.desc}"
  287. v-model="${searchItem.name}"
  288. width="100px"
  289. >
  290. </ss-search-input>
  291. </ss:notEqual>
  292. </ss:equal>
  293. </ss:rpt>
  294. <%-- 再去掉 -- 只支持一行。Lin
  295. </ss:rpt>
  296. --%>
  297. <%--
  298. <ss:rpt name='cadcadingName' id='item'>
  299. <ss:ccp name='${item}'/>
  300. </ss:rpt>
  301. --%>
  302. <ss:equal val='${hasKeyWord}' val2='true'>
  303. <%--
  304. <input name="ssKeyword" value="${ssKeyword}" type="text" placeholder="关键词"/>
  305. <script>wd.edit.addClearTextButton("ssKeyword");</script>
  306. --%>
  307. <!-- <ss-search-input
  308. name="ssKeyword"
  309. placeholder="关键词"
  310. v-model="form.keyword"
  311. width="120"
  312. >
  313. </ss-search-input> -->
  314. <ss-search-input
  315. name="ssKeyword"
  316. placeholder="关键词"
  317. v-model="ssKeyword"
  318. width="100px"
  319. @search="search"
  320. >
  321. </ss-search-input>
  322. <script>
  323. //关键词
  324. window.ss.dom.formElemConfig.ssKeyword = {
  325. value: '${ssKeyword}',
  326. name: "ssKeyword",
  327. desc: "关键词",
  328. type: 31
  329. };//放当前页面表单元素配置的变量
  330. </script>
  331. </ss:equal>
  332. <ss-search-button
  333. text="所有"
  334. icon-class="nav-icon-search"
  335. :opt="searchButtonConfig"
  336. :check-id="searchButtonConfigCheckId"
  337. ></ss-search-button>
  338. <%-- 新UI 单对象查询 选项卡、翻页start Ben --%>
  339. <ss:equal val='${"1"==isReady && !isMultipleObject}' val2='true'>
  340. <%--
  341. <ss:equal val='${hasScope}' val2='true'>
  342. <ul style="list-style: none;display: inline-block;">
  343. <li ssType="searchScope" ssVal=99>所有</li>
  344. <li ssType="searchScope" ssVal=2>管理</li>
  345. <li ssType="searchScope" ssVal=1>创建</li>
  346. <li ssType="searchScope" ssVal=3>已办</li>
  347. <li ssType="searchScope" ssVal=55>停用</li>
  348. </ul>
  349. </ss:equal>
  350. --%>
  351. </ss:equal>
  352. <%--
  353. <input type="submit" name="ssSearch" value="搜索" class="content-invertButton"/>
  354. --%>
  355. <!-- <ss-search-button text="搜索" @click="console.log('这里改成提交表单')">
  356. </ss-search-button> -->
  357. <script>
  358. <%-- 根按钮(管理按钮) --%>
  359. window.ss.dom.btnElemConfig = window.ss.dom.btnElemConfig || {};
  360. </script>
  361. <%--多对像搜索隐藏全文按钮--%>
  362. <%--
  363. <ss:equal val='${isMultipleObject}' val2='false'>
  364. --%><%--
  365. <input type="hidden" ssType="fts" ssName="fts" value="全文"/>--%> <%-- wdType="isFulltext" wdName="isFulltext"。Lin --%>
  366. <%--</ss:equal>--%>
  367. <%--根按钮(管理按钮)start--%>
  368. <ss:rpt name='buttonList' id='button'>
  369. <ss:auth serv='${button.servName}'>
  370. <ss:equal val='${empty button.pluginList}' val2='true'>
  371. <%--
  372. <input type="button" name="${button.name}" value="${button.buttonName}" class="content-button"
  373. onclick='wd.display.showComponent({show:["wdDialog"],url:"<ss:serv name='${button.servName}' dest='${button.dest}' parm='${button.parm}'/>",title:"${button.title}",width:"${button.width}",height:"${button.height}",minHeight:"${button.minHeight}",maxHeight:"${button.maxHeight+100}",showTitle:"${button.showTitle}"});'/>
  374. --%>
  375. <!-- <ss-search-button
  376. text="${button.name}"
  377. icon-class="nav-icon-add"
  378. ></ss-search-button> -->
  379. <script>
  380. function ${button.id}handleClick() {
  381. wd.display.showComponent({
  382. show: ["wdDialog"],
  383. url: "<ss:serv name='${button.servName}' dest='${button.dest}' parm='${button.parm}'/>",
  384. title: "${button.title}",
  385. width: "${button.width}",
  386. height: "${button.height}",
  387. minHeight: "${button.minHeight}",
  388. maxHeight: "${button.maxHeight+100}",
  389. showTitle: "${button.showTitle}"
  390. });
  391. }
  392. <%-- 实际没用上,注释掉 Ben(20251225)
  393. window.ss.dom.btnElemConfig.${button.id}={
  394. desc:"${button.buttonName}",
  395. id:"${button.id}",
  396. dropOptions:[],
  397. onclick: () => {
  398. wd.display.showComponent({show:["wdDialog"],url:"<ss:serv name='${button.servName}' dest='${button.dest}' parm='${button.parm}'/>",title:"${button.title}",width:"${button.width}",height:"${button.height}",minHeight:"${button.minHeight}",maxHeight:"${button.maxHeight+100}",showTitle:"${button.showTitle}"});
  399. }
  400. };
  401. --%>
  402. </script>
  403. <ss-search-button
  404. text="${button.buttonName}"
  405. icon-class="nav-icon-add"
  406. :opt="[]"
  407. :check-id="0"
  408. onclick="${button.id}handleClick()"
  409. ></ss-search-button>
  410. </ss:equal>
  411. <ss:equal val='${empty button.pluginList}' val2='false'>
  412. <ss:rpt name='${button.pluginList}' id='plugin'>
  413. <span style="display:none;" class="${button.id}children" value="${plugin.plugin.desc}"
  414. onclick='wd.display.showComponent({show:["wdDialog"],url:"<ss:serv
  415. name='${plugin.servName}' dest='${plugin.dest}'
  416. parm='${plugin.parm}'/>",title:"${plugin.title}",width:"${plugin.width}",height:"${plugin.height}",minHeight:"${plugin.minHeight}",maxHeight:"${plugin.maxHeight}",showTitle:"${plugin.showTitle}"});'>
  417. </span>
  418. </ss:rpt>
  419. <%-- <input type="button" id="${button.id}" name="${button.name}" value="${button.buttonName}" class="content-button" onclick='void(0)'/>
  420. <span style=" display:inline-table; width: 60px; ">
  421. <input type="button" id="${button.id}" name="${button.name}" value="${button.buttonName}" class="content-button" onclick='void(0)'/>
  422. <span class="icon-dimPoint" style="margin-right: 7px;margin-top: -25px;position: relative;">
  423. </span>
  424. </span>
  425. <script>wd.display.attachButton("${button.id}","${button.id}children",null,false,true)</script>--%>
  426. <%-- 新UI start Ben --%>
  427. <!-- <ss-search-button
  428. text="${plugin.plugin.desc}"
  429. icon-class="nav-icon-add"
  430. :options="${button.id}DropOptions"
  431. ></ss-search-button> -->
  432. <%-- :opt改为取vue.data中的变量,而不取window.ss.dom.btnElemConfig(因为取不到),onclick改为:onclick xu(20251209) --%>
  433. <ss-search-button
  434. text="${button.buttonName}"
  435. icon-class="nav-icon-add"
  436. :opt="btnElemConfig.${button.id}.dropOptions"
  437. check-id="${management}"
  438. onclick="${button.id}handleClick()"
  439. ></ss-search-button>
  440. <script>
  441. function ${button.id}handleClick() {
  442. wd.display.showComponent({
  443. show: ["wdDialog"],
  444. url: "<ss:serv name='${plugin.servName}' dest='${plugin.dest}' parm='${plugin.parm}'/>",
  445. title: "${plugin.title}",
  446. width: "${plugin.width}",
  447. height: "${plugin.height}",
  448. minHeight: "${plugin.minHeight}",
  449. maxHeight: "${plugin.maxHeight}",
  450. showTitle: "${plugin.showTitle}"
  451. });
  452. }
  453. <%-- 实际没用上,注释掉 Ben(20251225)
  454. window.ss.dom.btnElemConfig.${button.id}={id:"${button.id}",dropOptions:[],
  455. onclick: () => {
  456. wd.display.showComponent({show:["wdDialog"],url:"<ss:serv name='${plugin.servName}' dest='${plugin.dest}' parm='${plugin.parm}'/>",title:"${plugin.title}",width:"${plugin.width}",height:"${plugin.height}",minHeight:"${plugin.minHeight}",maxHeight:"${plugin.maxHeight}",showTitle:"${plugin.showTitle}"});
  457. }
  458. };
  459. --%>
  460. <%-- 循环生成按钮数组 --%>
  461. <ss:rpt name='${button.pluginList}' id='plugin'>
  462. window.ss.dom.btnElemConfig
  463. .${button.id}.
  464. dropOptions.push(
  465. {
  466. desc: '${plugin.plugin.desc}', <%-- 按钮名 --%>
  467. callback: function () {
  468. wd.display.showComponent({
  469. show: ["wdDialog"],
  470. url: "<ss:serv name='${plugin.servName}' dest='${plugin.dest}' parm='${plugin.parm}'/>",
  471. title: "${plugin.title}",
  472. width: "${plugin.width}",
  473. height: "${plugin.height}",
  474. minHeight: "${plugin.minHeight}",
  475. maxHeight: "${plugin.maxHeight}",
  476. showTitle: "${plugin.showTitle}"
  477. });
  478. }
  479. }
  480. );
  481. </ss:rpt>
  482. </script>
  483. <%-- 新UI end Ben --%>
  484. </ss:equal>
  485. </ss:auth>
  486. </ss:rpt>
  487. <%--管理按钮end--%>
  488. </template>
  489. </div>
  490. </div>
  491. <%--搜索条件end--%>
  492. <script type="text/javascript" src="/ss/env/env_search.js"></script>
  493. <%-- 旧UI初始化查询列表的js,新UI做好后,要被删掉的 --%>
  494. <%-- 新UI初始化查询列表数据的JS --%>
  495. <script>
  496. window.ss.dom.listConfig = window.ss.dom.listConfig || {};
  497. //二级对象 草稿箱
  498. window.ss.dom.listConfig.draftbox = [];
  499. <%-- 草稿箱start --%>
  500. <ss:rpt name='cgxList' id='item'>
  501. {
  502. let item = {};
  503. window.ss.dom.listConfig.draftbox.push(item);
  504. <%-- 下面的titlexxx是原来td的一个属性,不知道有啥用 --%>
  505. <ss:equal val='${not empty item.service.play && empty item.service.update && empty item.service.change}' val2='true'>
  506. item.titlexxx = "${item.service.play.title}";
  507. item.onclick = function () {
  508. wd.display.showComponent({
  509. show: ["wdDialog"],
  510. url: "<ss:serv name='${item.service.play.servName}' dest='${item.service.play.dest}' parm='${item.service.play.parm}'/>",
  511. title: "${item.service.play.title}",
  512. width: "${item.service.play.width}",
  513. height: "${item.service.play.height}",
  514. minHeight: "${item.service.play.minHeight}",
  515. maxHeight: "${item.service.play.maxHeight}"
  516. });
  517. };
  518. </ss:equal>
  519. <ss:equal val='${item.service.lbm}' val2='1'>
  520. <ss:equal val='${empty item.service.update}' val2='false'>
  521. item.titlexxx = "${item.service.update.title}";
  522. item.onclick = function () {
  523. wd.display.showComponent({
  524. show: ["wdDialog"],
  525. url: "<ss:serv name='${item.service.update.servName}' dest='${item.service.update.dest}' parm='${item.service.update.parm}'/>",
  526. title: "${item.service.update.title}",
  527. width: "${item.service.update.width}",
  528. height: "${item.service.update.height}",
  529. minHeight: "${item.service.update.minHeight}",
  530. maxHeight: "${item.service.update.maxHeight}"
  531. });
  532. };
  533. </ss:equal>
  534. </ss:equal>
  535. <ss:equal val='${item.service.lbm}' val2='11'>
  536. <ss:equal val='${empty item.service.change}' val2='false'>
  537. item.titlexxx = "${item.service.change.title}";
  538. item.onclick = function () {
  539. wd.display.showComponent({
  540. show: ["wdDialog"],
  541. url: "<ss:serv name='${item.service.change.servName}' dest='${item.service.change.dest}' parm='${item.service.change.parm}'/>",
  542. title: "${item.service.change.title}",
  543. width: "${item.service.change.width}",
  544. height: "${item.service.change.height}",
  545. minHeight: "${item.service.change.minHeight}",
  546. maxHeight: "${item.service.change.maxHeight}"
  547. });
  548. };
  549. </ss:equal>
  550. </ss:equal>
  551. item.buttons = [];
  552. <ss:equal val='${item.service.lbm}' val2='1'>
  553. item.buttons.push(
  554. {
  555. class: "cart-list-setting",
  556. title: "增加",
  557. onclick: () => {
  558. console.log("点击了增加");
  559. }
  560. }
  561. );
  562. </ss:equal>
  563. <ss:equal val='${item.service.lbm}' val2='11'>
  564. item.buttons.push(
  565. {
  566. class: "cart-list-setting",
  567. title: "变动",
  568. onclick: () => {
  569. console.log("点击了变动");
  570. }
  571. }
  572. );
  573. </ss:equal>
  574. <ss:equal val='${item.service.lbm}' val2='51'>
  575. item.buttons.push(
  576. {
  577. class: "cart-list-setting",
  578. title: "停用",
  579. onclick: () => {
  580. console.log("点击了停用");
  581. }
  582. }
  583. );
  584. </ss:equal>
  585. <ss:equal val='${item.service.lbm}' val2='55'>
  586. item.buttons.push(
  587. {
  588. class: "cart-list-setting",
  589. title: "启用",
  590. onclick: () => {
  591. console.log("点击了启用");
  592. }
  593. }
  594. </ss:equal>
  595. <ss:equal val='${item.service.lbm}' valList='51,55'>
  596. item.buttons.push(
  597. {
  598. class: "cart-list-setting",
  599. title: "还原",
  600. onclick: () => {
  601. wd.display.showComponent({
  602. show: ["wdDialog"],
  603. url: "<ss:serv name='deleteSq' parm='{"sqid":"${item.sqid}"}' dest='info'/>", // {"wdConfirmationCaptchaService":"0",。Lin
  604. title: "提示信息",
  605. width: 715,
  606. height: 483
  607. });
  608. }
  609. }
  610. </ss:equal>
  611. console.log('###item.thumbnail:${item.thumbnail}');
  612. <%--缩略图--%>
  613. <ss:equal val='${empty item.thumbnail}' val2='false'>
  614. <ss:equal val='${item.service.state}' val2='0'>
  615. item.thumb = "${sessionScope['ssUser'].skinDir}image/object/default-${item.ssObjName}.png"
  616. onerror = "javascript:this.src='${sessionScope['ssUser'].skinDir}image/default-photo.png';this.onerror=null;";
  617. </ss:equal>
  618. <ss:notEqual val='${item.service.state}' val2='0'>
  619. item.thumb = "<ss:serv name='dlByHttp' parm='{"path":"${item.thumbnail.value}","type":"img"}'/>"; // {"wdConfirmationCaptchaService":"0",。Lin
  620. </ss:notEqual>
  621. </ss:equal>
  622. <%-- 列标题 --%>
  623. <ss:equal val='${empty item.first}' val2='false'> <%-- 改 equal:val1="true" val2="${not empty item.first}"。Lin --%>
  624. <%--不带codebook--%>
  625. <ss:equal val='${empty item.first.field.cbName}' val2='true'>
  626. item.title = "<ss:txt val='${item.first.value}' miniDate='false' fmt='${item.first.field.fmt}'/>";
  627. </ss:equal>
  628. <%--带codebook--%>
  629. <ss:equal val='${empty item.first.field.cbName}' val2='false'>
  630. item.title = "<ss:cbTrans cb='${item.first.field.cbName}' val='${item.first.value}'/>";
  631. </ss:equal>
  632. </ss:equal>
  633. <%--列标题:缺标题显示属性start--%>
  634. <ss:equal val='${empty item.first && not empty item.third}' val2='true'>
  635. item.title = '';
  636. <ss:rpt name='${item.third}' id='itemList'>
  637. <ss:rpt name='${itemList}' id='item2'>
  638. item.title += '${item2.field.desc}:';
  639. <%--不带codebook--%>
  640. <ss:equal val='${empty item2.field.cbName}' val2='true'>
  641. item.title += '<ss:txt val='${item2.value}' fmt='${item2.field.fmt}' miniDate='false'/>&nbsp;';
  642. </ss:equal>
  643. <%--带codebook--%>
  644. <ss:equal val='${empty item2.field.cbName}' val2='false'>
  645. item.title += '<ss:cbTrans cb='${item2.field.cbName}' val='${item2.value}'/>&nbsp;';
  646. </ss:equal>
  647. </ss:rpt>
  648. </ss:rpt>
  649. </ss:equal>
  650. <%--缺标题显示属性end--%>
  651. <%--正文或摘要--%>
  652. <ss:equal val='${empty item.second}' val2='false'>
  653. // 摘要字段统一映射为 desc(用于 ss-list-card 右侧文字区规则) by xu 20260113
  654. item.desc = '${item.second.value}';
  655. item.summary = '${item.second.value}'; // legacy 兜底 by xu 20260113
  656. </ss:equal>
  657. // 对象号字段映射(用于 ss-list-card 第三部分回显) by xu 20260113
  658. item.objNum = '${item.ssObjId}';
  659. <%-- 列表底部的对象属性(tags) --%>
  660. <ss:equal val='${empty item.first || empty item.third}' val2='false'>
  661. item.tags = [];
  662. <ss:rpt name='${item.third}' id='itemList'>
  663. <ss:rpt name='${itemList}' id='item2'>
  664. {
  665. let v;
  666. <%--不带codebook--%>
  667. <ss:equal val='${empty item2.field.cbName}' val2='true'>
  668. v = '<ss:txt val='${item2.value}' fmt='${item2.field.fmt}' miniDate='false'/>';
  669. </ss:equal>
  670. <%--带codebook--%>
  671. <ss:equal val='${empty item2.field.cbName}' val2='false'>
  672. v = '<ss:cbTrans cb='${item2.field.cbName}' val='${item2.value}'/>';
  673. </ss:equal>
  674. item.tags.push({'${item2.field.desc}': v});
  675. }
  676. </ss:rpt>
  677. </ss:rpt>
  678. </ss:equal>
  679. <%-- 对象变动前后属性列表 --%>
  680. item.changeItems = [];
  681. <ss:rpt name='${item.forth}' id='item3'>
  682. item.changeItems.push({
  683. name: '${item3.name}',
  684. oldValue: '${item3.oldValue}',
  685. newValue: '${item3.newValue}'
  686. });
  687. </ss:rpt>
  688. }
  689. </ss:rpt>
  690. <%-- 草稿箱end --%>
  691. //在用或停用的对象列表
  692. window.ss.dom.listConfig.list = [];
  693. <ss:rpt name='objectList' id='item'><%-- 循环一次生成一行列表 start --%>
  694. {
  695. let item = {};//列表的其中一行的属性
  696. window.ss.dom.listConfig.list.push(item);
  697. <ss:equal val='${item.service.state}' val2='0'><%-- 借阅 --%>
  698. item.onclick = function () {
  699. wd.display.showComponent({
  700. show: ["wdDialog"],
  701. url: "<ss:serv name='ydsq_tj' parm='{"ssObjId":"${item.ssObjId}","ssObjName":"${item.ssObjName}"}' dest='ydsq_tj'/>", // {"wdConfirmationCaptchaService":"0",。Lin
  702. title: "借阅",
  703. width: 613,
  704. height: 387
  705. });
  706. }
  707. </ss:equal>
  708. <ss:equal val='${empty item.service.play}' val2='false'><%-- 有权查看,不需要借阅的情况 --%>
  709. item.titlexxx = "${item.service.play.title}";
  710. item.onclick = function () {
  711. wd.display.showComponent({
  712. getSize: 1,
  713. width: ((${item.service.play.width}+DOM_SIZE_objInfoTabWidth) + ""),
  714. show: ["wdDialog"],
  715. url: "<ss:serv name='${item.service.play.servName}' dest='${item.service.play.dest}' parm='${item.service.play.parm}'/>",
  716. title: "${item.service.play.title}",
  717. height: "${item.service.play.height}",
  718. minHeight: "${item.service.play.minHeight}",
  719. maxHeight: "${item.service.play.maxHeight}"
  720. });
  721. }
  722. </ss:equal>
  723. <ss:equal val='${empty item.thumbnail}' val2='false'><%-- 缩略图 --%>
  724. <ss:equal val='${item.service.state}' val2='0'><%-- 借阅 --%>
  725. item.thumb = "${sessionScope['ssUser'].skinDir}image/object/default-${item.ssObjName}.png";
  726. </ss:equal>
  727. <ss:notEqual val='${item.service.state}' val2='0'>
  728. console.log('@@@图片:${item.thumbnail.value}');
  729. item.thumb = "<ss:serv name='dlByHttp' parm='{"path":"${item.thumbnail.value}","type":"img"}'/>"; // {"wdConfirmationCaptchaService":"0",。Lin
  730. </ss:notEqual>
  731. </ss:equal>
  732. <%-- 标题 --%>
  733. <ss:equal val='${empty item.first}' val2='false'> <%-- 改 equal:val1="true" val2="${not empty item.first}"。Lin --%>
  734. <%--不带codebook--%>
  735. <ss:equal val='${empty item.first.field.cbName}' val2='true'>
  736. item.title = "<ss:txt val='${item.first.value}' miniDate='false' fmt='${item.first.field.fmt}'/>";
  737. </ss:equal>
  738. <%--带codebook--%>
  739. <ss:equal val='${empty item.first.field.cbName}' val2='false'>
  740. item.title = "<ss:cbTrans cb='${item.first.field.cbName}' val='${item.first.value}'/>";
  741. </ss:equal>
  742. </ss:equal>
  743. <%--缺标题显示属性当标题start--%>
  744. <ss:equal val='${empty item.first && not empty item.third}' val2='true'>
  745. item.title = '';
  746. <ss:rpt name='${item.third}' id='itemList'>
  747. <ss:rpt name='${itemList}' id='item2'>
  748. item.title += '${item2.field.desc}:';
  749. <%--属性名--%>
  750. <%--不带codebook--%><%--属性值--%>
  751. <ss:equal val='${empty item2.field.cbName}' val2='true'>
  752. item.title += "<ss:txt val='${item2.value}' fmt='${item2.field.fmt}' miniDate='false'/>&nbsp;";
  753. </ss:equal>
  754. <%--带codebook--%><%--属性值--%>
  755. <ss:equal val='${empty item2.field.cbName}' val2='false'>
  756. item.title += "<ss:cbTrans cb='${item2.field.cbName}' val='${item2.value}'/>&nbsp;";
  757. </ss:equal>
  758. </ss:rpt>
  759. </ss:rpt>
  760. </ss:equal>
  761. <%--缺标题显示属性当标题end--%>
  762. <%-- 摘要 --%>
  763. <ss:equal val='${empty item.second}' val2='false'>
  764. // 摘要字段统一映射为 desc(用于 ss-list-card 右侧文字区规则) by xu 20260113
  765. item.desc = '${item.second.value}';
  766. item.summary = '${item.second.value}'; // legacy 兜底 by xu 20260113
  767. </ss:equal>
  768. // 对象号字段映射(用于 ss-list-card 第三部分回显) by xu 20260113
  769. item.objNum = '${item.ssObjId}';
  770. <%-- 列表底部的对象标签组 --%>
  771. item.tags = [];
  772. <%-- 提到条件外面 Ben(20251230) --%>
  773. <ss:equal val='${empty item.first || empty item.third}' val2='false'>
  774. <%--item.tags=[]; 提到条件外面,不然列表报 Mount failed:TypeError: Cannot read properties of undefined (reading 'map') Ben(20251230) --%>
  775. <ss:rpt name='${item.third}' id='itemList'>
  776. <ss:rpt name='${itemList}' id='item2'>
  777. {
  778. let v;
  779. <%--不带codebook--%>
  780. <ss:equal val='${empty item2.field.cbName}' val2='true'>
  781. v = '<ss:txt val='${item2.value}' miniDate='false' fmt='${item2.field.fmt}'/>';
  782. </ss:equal>
  783. <%--带codebook--%>
  784. <ss:equal val='${empty item2.field.cbName}' val2='false'>
  785. v = '<ss:cbTrans cb='${item2.field.cbName}' val='${item2.value}'/>';
  786. </ss:equal>
  787. item.tags.push({
  788. ${item2.field.desc}:
  789. v
  790. })
  791. ;
  792. }
  793. </ss:rpt>
  794. </ss:rpt>
  795. </ss:equal>
  796. <%--按钮--%>
  797. <ss:equal val='${empty item.service.btnList}' val2='false'>
  798. item.buttons = [];
  799. <ss:rpt name='${item.service.btnList}' id='btn'>
  800. item.buttons.push(
  801. {
  802. id: "${btn.btnID}",
  803. titlexxx: "${btn.title}",
  804. class: "cart-list-setting",
  805. title: "${btn.name}",
  806. onclick: () => {
  807. <%--列表的变动按钮就在这里,下面的width会根据不同业务对象而不同
  808. 弹窗参数加上getSize=1,使showComponent弹窗方法不再通过ajax从后台取宽高 --%>
  809. wd.display.showComponent({
  810. getSize: 1,
  811. width: ((${btn.width}+DOM_SIZE_objInfoTabWidth) + ""),
  812. show: ["wdDialog"],
  813. url: "<ss:serv name='${btn.servName}' dest='${btn.dest}' parm='${btn.parm}'/>",
  814. title: "${btn.title}",
  815. height: "${btn.height}",
  816. minHeight: "${btn.minHeight}",
  817. maxHeight: "${btn.maxHeight}"
  818. });
  819. }
  820. }
  821. );
  822. <%--
  823. <ss:equal val='${index}' val2='0'>
  824. </ss:equal>
  825. <ss:notEqual val='${index}' val2='0'>
  826. </ss:notEqual>
  827. 改为不区分是否第1个变动按钮,都输出相同json数据 --%>
  828. </ss:rpt>
  829. </ss:equal>
  830. }
  831. </ss:rpt><%-- 循环一次生成一行列表 end --%>
  832. </script>
  833. <%--搜索结果start--%>
  834. <%-- 功能说明:卡片区域使用 grid 自动分列,剩余宽度平分(不锁死 max-width) by xu 20260109 --%>
  835. <div v-show="userInteracted && loadingList" class="pobj-list-loading" style="display:none;">
  836. <div class="pobj-spinner"></div>
  837. </div>
  838. <div v-show="!(userInteracted && loadingList)"
  839. :class="['content-area','item-content-area', cardGridKind ? ('ss-card-grid--' + cardGridKind) : '']">
  840. <template v-for="(item, i) in listConfig.draftbox" :key="i">
  841. <ss-folder-card v-if="item.children" :item="item"></ss-folder-card>
  842. <!-- 功能说明:传入 ssObjName + cardClickAction(卡片主体点击 view/single;角标仍多选) by xu 20260109 -->
  843. <ss-list-card v-else :item="item" :ss-obj-name="ssObjName" :card-click-action="cardClickAction"
  844. @toggle-select="handleToggleSelect"></ss-list-card>
  845. </template>
  846. <template v-for="(item, i) in listConfig.list" :key="i">
  847. <ss-folder-card v-if="item.children" :item="item"></ss-folder-card>
  848. <!-- 功能说明:传入 ssObjName + cardClickAction(卡片主体点击 view/single;角标仍多选) by xu 20260109 -->
  849. <ss-list-card v-else :item="item" :ss-obj-name="ssObjName" :card-click-action="cardClickAction"
  850. @toggle-select="handleToggleSelect"></ss-list-card>
  851. </template>
  852. <ss-page
  853. :key="String(ssPaging?.pageNo || 1) + '-' + String(ssPaging?.rowNumPer || 0) + '-' + String(ssPaging?.rowNum || 0)" <%-- // 功能说明:分页 key 不再硬编码 12,仅用于触发分页组件重渲染 by xu 20260123 --%>
  854. :total="Number(ssPaging?.rowNum || 0)"
  855. :size="Number(ssPaging?.rowNumPer || 0)" <%-- // 功能说明:分页大小仅使用 ssPaging.rowNumPer(默认值由 data 初始化兜底一次) by xu 20260123 --%>
  856. :page="Number(ssPaging?.pageNo || 1)"
  857. @change="handlePageChange">
  858. </ss-page>
  859. </div>
  860. </form>
  861. <!-- 功能说明:右侧业务面板(ss-sidebar),用于已选/服务/预订/图表等;当无 rbarTabNameList/rbarFuncList/pstatList 时临时隐藏 by xu 20260122 -->
  862. <ss-sidebar v-if="showRightSidebar"
  863. :buttons="sidebarButtons"
  864. :panels="sidebarPanels"
  865. @remove="handleSidebarRemove"
  866. ></ss-sidebar>
  867. <!-- 功能说明:业务页编辑态右下角新增入口(+ hover 上拉:增加图/增加表) by xu 20260123 -->
  868. <div v-if="isSysEditMode && sysModeSource === 'biz'" class="ss-objlist-fab" @click.stop>
  869. <button type="button" class="ss-objlist-fab__btn" title="新增">
  870. <ss-icon class="menu-base-icon icon-add"></ss-icon> <%-- // 功能说明:新增按钮图标样式参考左侧菜单 by xu 20260123 --%>
  871. </button>
  872. <div class="ss-objlist-fab__menu">
  873. <div class="ss-objlist-fab__item" @click="openAddPcht">增加图</div>
  874. <div class="ss-objlist-fab__item" @click="openAddPform">增加表</div>
  875. </div>
  876. </div>
  877. </div>
  878. </div>
  879. </body>
  880. </html>
  881. <script type="module">
  882. <%-- 打印新UI相关json对象 --%>
  883. const data = {
  884. ssObjName: "${ssObjName}", // 对象名
  885. // 后端注入的 ssServ 接口名(用于 ajax 拉取查询首页/列表数据)
  886. ssSearchPobjHomeServName: "${ssSearchPobjHomeServName}", // <对象名>_cxsy
  887. ssSearchPobjListServName: "${ssSearchPobjListServName}", // <对象名>_cxlb
  888. // 接口返回(调试用,可删)
  889. ssHomeResp: null,
  890. ssListResp: null,
  891. // 页面状态
  892. homeReady: false,
  893. userInteracted: false, // 用户触发(搜索/翻页/切换范围)
  894. loadingHome: false,
  895. loadingList: false,
  896. isSysEditMode: false, // 功能说明:系统编辑态(业务页)控制右下角“新增”入口显隐 by xu 20260123
  897. sysModeSource: "unknown", // 功能说明:编辑态来源(desktop/biz/menu/unknown) by xu 20260123
  898. // 首页接口返回字段(按 page/objlist修改.txt)
  899. searchFieldList: [], // 查询条件字段
  900. rootFuncList: [], // 根功能
  901. hasKeyword: false, // 是否有关键词
  902. hasScope: false, // 是否带范围
  903. thnType: (() => { // 功能说明:首屏 thnType 优先从 JSP 注入(用于第一个接口前正确计算 rowNumPer);空/缺省回退 0 by xu 20260123
  904. try {
  905. const s = String("${thnType}" || "").trim();
  906. const n = parseInt(s, 10);
  907. return isNaN(n) ? 0 : n;
  908. } catch (_) {
  909. return 0;
  910. }
  911. })(), // 缩略图类型:1横向 2竖向 3方形 4圆形(目前仅用 1/2,其他先不处理)
  912. ssPaging: {pageNo: 1, rowNumPer: 12, rowNum: 0}, // 翻页信息 // 功能说明:分页默认每页12条(全文件仅此处兜底) by xu 20260123
  913. rbarFuncList: [], // 右侧栏功能
  914. rbarTabNameList: [], // 右侧栏选项卡
  915. pstatList: [], // 功能说明:个人统计图表配置(右侧图表区) by xu 20260114
  916. // 查询表单参数(用于接口入参)
  917. form: {management: "99", isFulltext: "0", isAnd: "", ssKeyword: ""},
  918. systemType: window.ss.dom.TYPE,
  919. listConfig: window.ss.dom.listConfig || {draftbox: [], list: []},
  920. formElemConfig: window.ss.dom.formElemConfig || {},
  921. // 卡片区域:自动分列/宽度平分 by xu 20260109
  922. cardGridKind: (() => { // 功能说明:首屏按 thnType 预设网格类型(决定 --ss-card-min),避免缩略图业务第一个接口仍按 300px 计算 by xu 20260123
  923. try {
  924. const t = (() => { const s = String("${thnType}" || "").trim(); const n = parseInt(s, 10); return isNaN(n) ? 0 : n; })();
  925. if (t === 1) return "thumbnail";
  926. if (t === 2) return "photo";
  927. return "none";
  928. } catch (_) {
  929. return "none";
  930. }
  931. })(), // none/photo/thumbnail by xu 20260109
  932. // 卡片主体点击动作:view=查看(调用 item.onclick);single=单选互斥;角标仍多选 by xu 20260109
  933. cardClickAction: "view", // 功能说明:默认先用 view,首页接口若返回预订面板则切到 single by xu 20260114
  934. hasBookPanel: false, // 功能说明:是否存在预订面板(驱动 cardClickAction + 选中后联动 cxycl) by xu 20260116
  935. hasServPanel: false, // 功能说明:是否存在服务面板(严格依赖首页 rbarTabNameList 是否返回 rbarServ) by xu 20260116
  936. hasObjPanel: false, // 功能说明:是否存在对象面板(严格依赖首页 rbarTabNameList 是否返回 rbarObj;无则不显示“已选”且禁止选中) by xu 20260122
  937. rbar: (String("${rbar}" || "").trim().toLowerCase() === "true") ? true : null, // 功能说明:JSP 注入 rbar:有字段且为 "true" 才展示;缺省/空串为 null,首页接口返回后会覆盖 by xu 20260123
  938. showRightSidebar: (String("${rbar}" || "").trim().toLowerCase() === "true"), // 功能说明:首屏右侧栏显隐以 JSP rbar 为准(用于首屏测量口径) by xu 20260123
  939. autoRowNumPer: 0, // 功能说明:动态计算的 rowNumPer(只记录,下次接口请求时生效;resize 不立即重拉) by xu 20260123
  940. yclLoading: false, // 功能说明:已选对象联动(cxycl)加载状态 by xu 20260114
  941. yclResp: null, // 功能说明:已选对象联动接口返回(调试/兜底) by xu 20260114
  942. // 右侧已选(用于未来接入 ss-sidebar 的已选面板) by xu 20260109
  943. selectedItems: [],
  944. // 右侧边栏:顶部按钮(由首页接口 rbarFuncList 初始化)
  945. sidebarButtons: [],
  946. // 右侧边栏:分区配置(由 initSidebarPanels 初始化) by xu 20260113
  947. sidebarPanels: [],
  948. <%-- 实际没用上,注释掉 Ben(20251225) btnElemConfig:window.ss.dom.btnElemConfig, --%>
  949. searchButtonConfigCheckId: "99",
  950. searchButtonConfig: [
  951. {
  952. id: "99",
  953. desc: "所有",
  954. callback: () => window.__objListVm && window.__objListVm.switchScope(99),
  955. },
  956. {
  957. id: "2",
  958. desc: "管理",
  959. callback: () => window.__objListVm && window.__objListVm.switchScope(2),
  960. },
  961. {
  962. id: "1",
  963. desc: "创建",
  964. callback: () => window.__objListVm && window.__objListVm.switchScope(1),
  965. },
  966. {
  967. id: "3",
  968. desc: "已办",
  969. callback: () => window.__objListVm && window.__objListVm.switchScope(3),
  970. },
  971. {
  972. id: "55",
  973. desc: "停用",
  974. callback: () => window.__objListVm && window.__objListVm.switchScope(55),
  975. }
  976. ],
  977. }
  978. if (window.ss.dom.formElemConfig) {
  979. Object.entries(window.ss.dom.formElemConfig).forEach(([key, config]) => {
  980. data[key] = config.value;
  981. // 处理 objPicker
  982. if (config.type === window.ss.dom.TYPE.OBJPICKER) {
  983. data[key + "ObjPicker"] = true;
  984. data[key + "Option"] = [];
  985. data[key + "Url"] = config.optUrl;
  986. }
  987. })
  988. }
  989. SS.ready(function () {
  990. // 功能说明:右侧 ss-sidebar__inner 统一增加 padding-top=10px(撤回外层 wrapper 方案) by xu 20260114
  991. try {
  992. if (!document.getElementById("ss-objlist-sidebar-inner-padding")) {
  993. var st = document.createElement("style");
  994. st.id = "ss-objlist-sidebar-inner-padding";
  995. st.type = "text/css";
  996. st.appendChild(document.createTextNode("#app .ss-sidebar__inner{padding-top:10px;}"));
  997. document.head.appendChild(st);
  998. }
  999. } catch (e) {
  1000. }
  1001. window.ss.dom.initializeFormApp({
  1002. el: "#app",
  1003. data() {
  1004. return data;
  1005. },
  1006. methods: {
  1007. openAddPform() {
  1008. // 功能说明:编辑态新增统计表(pformSet) by xu 20260123
  1009. try {
  1010. wd.display.showComponent({
  1011. show: ["wdDialog"],
  1012. url: "<ss:serv name='addPform' parm='{\"dxm\":\"${ssObjName}\"}' dest='pformSet'/>", // {\"wdConfirmationCaptchaService\":\"0\",。Lin
  1013. title: "设置统计表",
  1014. width: 800,
  1015. height: 580
  1016. });
  1017. } catch (e) {
  1018. console.error("openAddPform failed", e);
  1019. }
  1020. },
  1021. openAddPcht() {
  1022. // 功能说明:编辑态新增统计图(pchtSet) by xu 20260123
  1023. try {
  1024. wd.display.showComponent({
  1025. show: ["wdDialog"],
  1026. url: "<ss:serv name='addPcht' parm='{\"dxm\":\"${ssObjName}\",\"use_grtjmbid\":\"${use_grtjmbid}\"}' dest='pchtSet'/>", // {\"wdConfirmationCaptchaService\":\"0\",。Lin
  1027. title: "设置统计图",
  1028. width: 800,
  1029. height: 580
  1030. });
  1031. } catch (e) {
  1032. console.error("openAddPcht failed", e);
  1033. }
  1034. },
  1035. openServiceDialog(srv) {
  1036. // 功能说明:使用 tools.js 统一封装的 openServiceDialog,支持透传搜索条件 by xu 20260122
  1037. if (!srv) return;
  1038. const extraParams = window.ssTools.pickSearchParams(this.searchFieldList, this.form);
  1039. // 功能说明:已选对象 ID 拼接为 rbarObjId=111,222 by xu 20260424
  1040. const objIds = (this.selectedItems || [])
  1041. .map((it) => it?.objNum || it?._raw?.ssObjId)
  1042. .filter(Boolean)
  1043. .join(',');
  1044. if (objIds) extraParams.rbarObjId = objIds;
  1045. // 功能说明:已选人员 ID 拼接为 rbarRyId=xxx,xxx by xu 20260424
  1046. const ryPanel = (this.sidebarPanels || []).find((p) => p?._tabKey === 'rbarry');
  1047. const ryIds = (ryPanel?.items || [])
  1048. .map((it) => it?.id || it?.ryId || it?._raw?.ryId)
  1049. .filter(Boolean)
  1050. .join(',');
  1051. if (ryIds) extraParams.rbarRyId = ryIds;
  1052. window.ssTools.openServiceDialog(srv, { extraParams });
  1053. },
  1054. normalizeObjToCard(row) {
  1055. const card = {
  1056. _raw: row,
  1057. ssObjName: row?.ssObjName,
  1058. objNum: row?.ssObjId,
  1059. title: "",
  1060. desc: row?.abs || "",
  1061. tags: [],
  1062. buttons: [],
  1063. stateIconList: row?.stateIconList || [],
  1064. statusIcons: [],
  1065. bigState: row?.bigState,
  1066. bookState: row?.bookState,
  1067. };
  1068. // 功能说明:将后端 stateIconList 映射为卡片右上角 statusIcons(按 icon-state/icon-servType 规则转为 biz 图标组;无匹配则不展示;不使用 menu-*) by xu 20260123
  1069. try {
  1070. const rawList = Array.isArray(row?.stateIconList) ? row.stateIconList : [];
  1071. const icons = [];
  1072. const seen = new Set();
  1073. rawList.forEach((s) => {
  1074. const cls = String(s || "").trim();
  1075. if (!cls) return;
  1076. const m = cls.match(/-(\d+)$/);
  1077. const code = m ? Number(m[1]) : NaN;
  1078. if (code === 1) return; // 约定:后缀为 1 的不处理 by xu 20260116
  1079. // 功能说明:icon-state-<对象名>-<状态码>:15=清洁中(icon-obj-qjz),275=维修中(icon-obj-wxz) by xu 20260123
  1080. const stateMatch = cls.match(/^icon-state-[^-]+-(\d+)$/);
  1081. if (stateMatch) {
  1082. const stateCode = Number(stateMatch[1]);
  1083. if (stateCode === 15) {
  1084. const k = "ss-card-status-biz-icon icon-obj-qjz";
  1085. if (!seen.has(k)) { seen.add(k); icons.push({ class: k, title: "清洁中" }); }
  1086. return;
  1087. }
  1088. if (stateCode === 275) {
  1089. const k = "ss-card-status-biz-icon icon-obj-wxz";
  1090. if (!seen.has(k)) { seen.add(k); icons.push({ class: k, title: "维修中" }); }
  1091. return;
  1092. }
  1093. return;
  1094. }
  1095. // 功能说明:icon-servType-<对象名>-<服务类别码>:只要类别码!=1 就显示服务图标 icon-obj-fw by xu 20260123
  1096. const servTypeMatch = cls.match(/^icon-servType-[^-]+-(\d+)$/);
  1097. if (servTypeMatch) {
  1098. const servTypeCode = Number(servTypeMatch[1]);
  1099. if (servTypeCode !== 1) {
  1100. const k = "ss-card-status-biz-icon icon-obj-fw";
  1101. if (!seen.has(k)) { seen.add(k); icons.push({ class: k, title: "服务中" }); }
  1102. }
  1103. return;
  1104. }
  1105. // 功能说明:兜底支持后端直接下发 icon-base 字形类(例如 icon-fauth),不再拼 menu-base-icon;不再用 icon-sauth 兜底 by xu 20260123
  1106. try {
  1107. const tokens = cls.match(/\bicon-[a-zA-Z0-9_-]+\b/g) || [];
  1108. const glyph = tokens.find((t) => t && t.indexOf("icon-state-") !== 0 && t.indexOf("icon-servType-") !== 0);
  1109. if (glyph) {
  1110. if (glyph === "icon-sauth") return; // 功能说明:不再使用 icon-sauth 作为兜底/透传展示 by xu 20260123
  1111. const k = "ss-card-status-base-icon " + glyph;
  1112. if (!seen.has(k)) { seen.add(k); icons.push({ class: k, title: "使用中" }); }
  1113. }
  1114. } catch (_) {}
  1115. });
  1116. card.statusIcons = icons;
  1117. } catch (e) {
  1118. }
  1119. // 功能说明:title 可能是 {val, fmt}(日期/时间),此处按 fmt 做格式化(避免显示英文时间串) by xu 20260114
  1120. try {
  1121. const t = row?.title;
  1122. if (t && typeof t === "object" && t.val !== undefined) {
  1123. card.title = String(window.ssTools.formatValByFmt(t.val, t.fmt) ?? "");
  1124. } else {
  1125. card.title = String(row?.title?.val || row?.title || "");
  1126. }
  1127. } catch (_) {
  1128. card.title = String(row?.title?.val || row?.title || "");
  1129. }
  1130. // 功能说明:bookState 优先于 bigState(0绿空闲/1黄预订中/2红),映射到卡片 status by xu 20260116
  1131. const hasBookState = row?.bookState !== undefined && row?.bookState !== null && row?.bookState !== "";
  1132. const hasBigState = row?.bigState !== undefined && row?.bigState !== null && row?.bigState !== "";
  1133. if (hasBookState || hasBigState) {
  1134. const stateVal = hasBookState ? Number(row.bookState) : Number(row.bigState);
  1135. card.status = stateVal === 2 ? "disabled" : stateVal === 1 ? "unavailable" : stateVal === 0 ? "available" : "";
  1136. }
  1137. // thnType:缩略图类型(目前仅处理 1=横向、2=竖向;其余先按无缩略图处理)
  1138. // 注意:即使 thn 为空,只要 thnType 存在,也要保留缩略图区域,以显示“对象号_icon”占位
  1139. const thnType = Number(this.thnType || 0);
  1140. if (thnType === 1) card.thumbType = "thumbnail"; // 横向:180×100
  1141. else if (thnType === 2) card.thumbType = "photo"; // 竖向:73×100(默认)
  1142. // thn:缩略图不为空 => 有图片(否则 ss-list-card 会按 thumbType 显示占位 icon)
  1143. if (row?.thn) card.thumb = window.ssTools.buildThumbUrl(row.thn);
  1144. if (Array.isArray(row?.catList)) {
  1145. row.catList.forEach((c) => {
  1146. if (!c || !c.desc) return;
  1147. // 功能说明:catList 存在 fmt 时格式化时间(yyyy-MM-dd / yyyy-MM-dd HH:mm:ss),避免直接显示英文时间串 by xu 20260114
  1148. const val = window.ssTools.formatValByFmt(c.val, c.fmt);
  1149. card.tags.push({[c.desc]: val});
  1150. });
  1151. }
  1152. if (Array.isArray(row?.chgRootFuncList)) {
  1153. row.chgRootFuncList.forEach((btn) => {
  1154. card.buttons.push({
  1155. id: btn.id,
  1156. titlexxx: btn.title,
  1157. class: "cart-list-setting",
  1158. title: btn.desc || btn.title || "",
  1159. onclick: () => this.openServiceDialog(btn),
  1160. });
  1161. });
  1162. }
  1163. if (row?.play) {
  1164. card.onclick = () => this.openServiceDialog(row.play);
  1165. }
  1166. return card;
  1167. },
  1168. buildPstatChartPanels(bizIcon) {
  1169. // 功能说明:根据 pstatList 构建右侧图表面板(type=chart),用于 ss-sidebar 底部图表区 by xu 20260114
  1170. const list = Array.isArray(this.pstatList) ? this.pstatList : [];
  1171. return list
  1172. .filter((it) => Number(it?.grtjlbm) === 1)
  1173. .map((it) => ({
  1174. type: "chart",
  1175. _grtjid: it?.grtjid,
  1176. title: it?.mc || "",
  1177. iconClass: bizIcon,
  1178. // 功能说明:右侧面板图表高度做上限(避免后端 gd 过大导致 sidebar 竖向撑爆) by xu 20260114
  1179. height: (() => {
  1180. const gd = Number(it?.gd);
  1181. const h = (isFinite(gd) && gd > 0) ? Math.max(160, Math.min(260, gd)) : 220;
  1182. return String(h) + "px";
  1183. })(),
  1184. // 功能说明:统计图默认先用柱状图(后续可按后端类型再扩展) by xu 20260115
  1185. options: {
  1186. xAxis: {type: "category", data: []},
  1187. yAxis: {type: "value"},
  1188. series: [{type: "bar", data: []}]
  1189. },
  1190. }));
  1191. },
  1192. loadPstatChartData(panel) {
  1193. // 功能说明:根据 grtjid 调 wrPchtPlay 获取真实数据,并写回 panel.options 触发渲染 by xu 20260114
  1194. const grtjid = panel?._grtjid;
  1195. if (!grtjid) return Promise.resolve(null);
  1196. return this.callSsService("wrPchtPlay", {grtjid})
  1197. .then((res) => {
  1198. const payload = res && typeof res === "object" ? (res.ssData || res) : null;
  1199. // 功能说明:兼容 wrPchtPlay 返回 dataArray(name=mc,value=sl) by xu 20260114
  1200. let list =
  1201. (payload && (payload.dataArray || payload.dataList || payload.list || payload.data)) ||
  1202. payload;
  1203. if (!Array.isArray(list)) list = [];
  1204. const data = list
  1205. .map((it) => {
  1206. if (!it || typeof it !== "object") return null;
  1207. const name = it.name ?? it.mc ?? it.desc ?? it.title;
  1208. const value = it.value ?? it.sl ?? it.zrs ?? it.count ?? it.num;
  1209. if (name === undefined || name === null) return null;
  1210. const v = Number(value);
  1211. return {name: String(name), value: isNaN(v) ? 0 : v};
  1212. })
  1213. .filter(Boolean);
  1214. // 功能说明:统计图默认先用柱状图(tooltip/label 显示数值;全 0 也正常展示) by xu 20260115
  1215. const xData = data.map((it) => it.name);
  1216. const yData = data.map((it) => it.value);
  1217. panel.options = {
  1218. color: ["#5470C6", "#91CC75", "#FAC858", "#EE6666", "#73C0DE", "#3BA272", "#FC8452", "#9A60B4", "#EA7CCC"],
  1219. tooltip: {trigger: "axis", axisPointer: {type: "shadow"}},
  1220. grid: {left: "3%", right: "3%", top: 12, bottom: 24, containLabel: true},
  1221. xAxis: {
  1222. type: "category",
  1223. data: xData,
  1224. axisLabel: {interval: 0, rotate: xData.length > 6 ? 30 : 0}
  1225. },
  1226. yAxis: {type: "value"},
  1227. series: [{
  1228. type: "bar",
  1229. barMaxWidth: 36,
  1230. data: yData,
  1231. label: {show: true, position: "top"},
  1232. }],
  1233. };
  1234. // 功能说明:强制触发 sidebarPanels 更新(避免仅在选中卡片后才刷新图表) by xu 20260114
  1235. try {
  1236. this.sidebarPanels = (this.sidebarPanels || []).slice();
  1237. } catch (e) {
  1238. }
  1239. // 功能说明:图表初次渲染时容器尺寸可能未稳定,nextTick 后触发 resize,避免“未选中=灰色圆” by xu 20260114
  1240. try {
  1241. const self = this;
  1242. if (self && self.$nextTick) {
  1243. self.$nextTick(function () {
  1244. try {
  1245. window.dispatchEvent(new Event("resize"));
  1246. } catch (e) {
  1247. }
  1248. });
  1249. }
  1250. setTimeout(function () {
  1251. try {
  1252. window.dispatchEvent(new Event("resize"));
  1253. } catch (e) {
  1254. }
  1255. }, 120);
  1256. } catch (e) {
  1257. }
  1258. return res;
  1259. })
  1260. .catch((e) => {
  1261. console.error("loadPstatChartData failed", grtjid, e);
  1262. return null;
  1263. });
  1264. },
  1265. applyPobjHomeData(ssData) {
  1266. if (!ssData || typeof ssData !== "object") return;
  1267. this.ssHomeResp = ssData;
  1268. this.homeReady = true;
  1269. this.userInteracted = false;
  1270. this.ssObjName = ssData.ssObjName || this.ssObjName; // 对象名
  1271. this.searchFieldList = Array.isArray(ssData.searchFieldList) ? ssData.searchFieldList : []; // 查询条件字段
  1272. this.rootFuncList = Array.isArray(ssData.rootFuncList) ? ssData.rootFuncList : []; // 根功能
  1273. this.hasKeyword = !!ssData.hasKeyword; // 是否有关键词
  1274. this.hasScope = !!ssData.hasScope; // 是否带范围
  1275. this.thnType = Number(ssData.thnType || 0); // 缩略图类型
  1276. this.ssPaging = ssData.ssPaging || this.ssPaging; // 翻页信息
  1277. this.rbarFuncList = Array.isArray(ssData.rbarFuncList) ? ssData.rbarFuncList : []; // 右侧栏功能
  1278. this.rbarTabNameList = Array.isArray(ssData.rbarTabNameList) ? ssData.rbarTabNameList : []; // 右侧栏选项卡
  1279. this.pstatList = Array.isArray(ssData.pstatList) ? ssData.pstatList : []; // 功能说明:个人统计图表配置 by xu 20260114
  1280. // 功能说明:优先使用后端 0 服务字段 rbar 控制右侧栏显隐;若后端缺省未返回 rbar,则临时回退为 3 字段判空规则 by xu 20260123
  1281. if (Object.prototype.hasOwnProperty.call(ssData, "rbar")) {
  1282. this.rbar = !!ssData.rbar;
  1283. this.showRightSidebar = !!ssData.rbar;
  1284. } else {
  1285. this.rbar = null;
  1286. // 功能说明:临时规则——当 rbarTabNameList/rbarFuncList/pstatList 全为空时隐藏右侧栏,把宽度让给左侧列表;后续改为“无 rbarTabNameList && 无图表标记”再隐藏(等后端给图表标记) by xu 20260122
  1287. this.showRightSidebar = (this.rbarTabNameList?.length > 0) || (this.rbarFuncList?.length > 0) || (this.pstatList?.length > 0);
  1288. }
  1289. // 功能说明:如果首页返回预订/服务面板(rbarBook/rbarbook/rbarServ),则卡片主体点击切到 single(单选互斥),否则保持 view(查看) by xu 20260116
  1290. {
  1291. const tabs = Array.isArray(this.rbarTabNameList) ? this.rbarTabNameList : [];
  1292. const norm = (s) => String(s || "").trim().toLowerCase();
  1293. this.hasBookPanel = tabs.some((t) => norm(t) === "rbarbook"); // 功能说明:预订tab存在才回显bookList by xu 20260116
  1294. this.hasServPanel = tabs.some((t) => norm(t) === "rbarserv"); // 功能说明:服务tab存在才回显servList by xu 20260116
  1295. this.hasObjPanel = tabs.some((t) => norm(t) === "rbarobj"); // 功能说明:无 rbarObj 时禁用选中能力 by xu 20260122
  1296. this.cardClickAction = (this.hasObjPanel && (this.hasBookPanel || this.hasServPanel)) ? "single" : "view"; // 功能说明:仅允许选中时才启用单选联动 by xu 20260122
  1297. }
  1298. this.searchButtonConfigCheckId = String(this.form?.management || "99");
  1299. // 用当前表单参数初始化 form(确保 v-model 有值)
  1300. const curParams = this.getSearchFormParams();
  1301. const nextForm = {...this.form, ...curParams};
  1302. this.searchFieldList.forEach((f) => {
  1303. if (f && f.name && !(f.name in nextForm)) nextForm[f.name] = "";
  1304. });
  1305. this.form = nextForm;
  1306. this.searchButtonConfigCheckId = String(this.form?.management || "99");
  1307. // 初始化列表(objList/draftList)
  1308. const nextDraft = Array.isArray(ssData.draftList) ? ssData.draftList.map((r) => this.normalizeObjToCard(r)) : [];
  1309. const nextList = Array.isArray(ssData.objList) ? ssData.objList.map((r) => this.normalizeObjToCard(r)) : [];
  1310. if (this.listConfig) {
  1311. this.listConfig.draftbox = nextDraft;
  1312. this.listConfig.list = nextList;
  1313. }
  1314. // 列表数据更新后,重新探测卡片网格类型
  1315. this.cardGridKind = this.detectCardGridKind();
  1316. // 功能说明:首页列表/右侧栏落地后,按当前容器尺寸更新 autoRowNumPer(不立即重拉;下次列表请求时生效) by xu 20260123
  1317. if (this.$nextTick) this.$nextTick(() => this.scheduleAutoRowNumPer("home"));
  1318. else this.scheduleAutoRowNumPer("home");
  1319. // 初始化右侧栏(选项卡 + 功能)
  1320. const bizIcon = this.ssObjName ? ("ss-sidebar-biz-icon icon-obj-" + this.ssObjName) : "";
  1321. // 功能说明:右侧各 tab header 图标:预订/服务用固定业务图标,其余沿用当前对象图标 by xu 20260123
  1322. const tabIconClassOf = (tabKeyLower) => {
  1323. if (tabKeyLower === "rbarbook") return "ss-sidebar-biz-icon icon-obj-yd";
  1324. if (tabKeyLower === "rbarserv") return "ss-sidebar-biz-icon icon-obj-fw";
  1325. return bizIcon;
  1326. };
  1327. const pickedPanel = this.hasObjPanel ? { // 功能说明:仅 rbarObj 存在才显示“已选”tab by xu 20260122
  1328. type: "list",
  1329. title: "已选",
  1330. count: this.selectedItems.length,
  1331. iconClass: bizIcon,
  1332. closable: true,
  1333. itemLayout: "person",
  1334. items: this.selectedItems,
  1335. mode: "selected",
  1336. itemAction: true,
  1337. onClear: () => this.clearSelectedItems(),
  1338. } : null;
  1339. // 功能说明:面板 key 兼容后端大小写(rbarBook/rbarbook/rbarServ),并移除“对象”tab by xu 20260116
  1340. const titleMap = {rbarry: "人员", rbarbook: "预订", rbarserv: "服务"};
  1341. const tabs = Array.isArray(this.rbarTabNameList) ? this.rbarTabNameList : [];
  1342. const normKey = (s) => String(s || "").trim().toLowerCase();
  1343. const tabPanels = tabs.map((k) => {
  1344. const nk = normKey(k);
  1345. // 功能说明:右侧栏去掉“对象”tab(即使后端返回 rbarObj 也忽略) by xu 20260116
  1346. if (nk === "rbarobj") return null;
  1347. const p = {
  1348. type: "list",
  1349. _tabKey: nk, // 功能说明:保存原始 tab key(用于后续写回 items) by xu 20260114
  1350. title: titleMap[nk] || k,
  1351. iconClass: tabIconClassOf(nk),
  1352. items: [],
  1353. itemAction: false,
  1354. };
  1355. // 功能说明:右侧“预订/服务”面板复用“已选”tab 的 person 布局(title+meta),避免渲染 ss-sidebar-tag by xu 20260116
  1356. if (nk === "rbarbook") {
  1357. p.itemLayout = "person";
  1358. }
  1359. if (nk === "rbarserv") {
  1360. p.itemLayout = "simple"; // 功能说明:服务面板只回显 mc(名称),不需要 meta 槽位 by xu 20260116
  1361. }
  1362. // 功能说明:右侧“人员”面板先做前端 mock 搜索头(仅展示,不接后端) by xu 20260114
  1363. if (nk === "rbarry") {
  1364. p.iconClass = "ss-sidebar-biz-icon icon-obj-ry"; // 功能说明:右侧“人员”tab 使用右侧栏 biz 图标基类 by xu 20260123
  1365. p.count = 0;
  1366. p.headerFilters = [
  1367. {
  1368. key: "keyword",
  1369. component: "ss-search-input",
  1370. props: {name: "keyword", placeholder: "人员", width: "140px"},
  1371. value: "",
  1372. },
  1373. ];
  1374. p.headerSearchButton = true;
  1375. p.onSearch = function (payload) {
  1376. try {
  1377. console.log("人员搜索", payload);
  1378. } catch (_) {
  1379. }
  1380. };
  1381. p.mode = "selected";
  1382. p.items = [];
  1383. p.itemAction = false;
  1384. }
  1385. return p;
  1386. }).filter(Boolean);
  1387. this.sidebarPanels = pickedPanel ? [pickedPanel, ...tabPanels] : tabPanels; // 功能说明:无 rbarObj 时不注入“已选”tab by xu 20260122
  1388. try {
  1389. console.log("[pobj] sidebarPanels(init)", (this.sidebarPanels || []).map((p) => ({
  1390. title: p?.title,
  1391. type: p?.type,
  1392. tab: p?._tabKey
  1393. })));
  1394. } catch (_) {
  1395. } // 功能说明:打印初始化后的面板列表,排查缺少“对象/预订”tab by xu 20260116
  1396. // 功能说明:将 pstatList=51 聚拢为一个“报表”面板(type=report-table),用于右侧报表区 by xu 20260115
  1397. try {
  1398. const reportList = (Array.isArray(this.pstatList) ? this.pstatList : []).filter((it) => Number(it?.grtjlbm) === 51);
  1399. if (reportList.length) {
  1400. this.sidebarPanels = this.sidebarPanels.concat([{
  1401. type: "report-table",
  1402. title: "报表",
  1403. iconClass: bizIcon,
  1404. items: reportList,
  1405. // 功能说明:点击统计表单元格,优先按 fwm/bjm 组装后端 service 并弹窗 by xu 20260115
  1406. onOpen: (srv) => this.openServiceDialog(srv),
  1407. }]);
  1408. }
  1409. } catch (e) {
  1410. }
  1411. // 功能说明:将 pstatList=1 映射为右侧图表区(type=chart),并调 wrPchtPlay 拉取真实数据 by xu 20260114
  1412. const chartPanels = this.buildPstatChartPanels(bizIcon);
  1413. if (chartPanels.length) {
  1414. this.sidebarPanels = this.sidebarPanels.concat(chartPanels);
  1415. chartPanels.forEach((p) => this.loadPstatChartData(p));
  1416. // 功能说明:追加 chartPanels 后触发一次 resize,避免首次进入图表为灰色圆(布局未完成) by xu 20260114
  1417. try {
  1418. setTimeout(function () {
  1419. window.dispatchEvent(new Event("resize"));
  1420. }, 0);
  1421. } catch (_) {
  1422. }
  1423. }
  1424. this.sidebarButtons = this.rbarFuncList.map((f) => ({
  1425. id: f.id || f.servName,
  1426. text: f.desc || f.title || "",
  1427. onClick: () => this.openServiceDialog(f),
  1428. }));
  1429. if (!this.showRightSidebar) { // 功能说明:右侧栏隐藏时清空面板/按钮,避免残留旧数据误导(临时规则) by xu 20260122
  1430. this.sidebarPanels = [];
  1431. this.sidebarButtons = [];
  1432. }
  1433. },
  1434. handleRootFuncClick(func) {
  1435. this.openServiceDialog(func);
  1436. },
  1437. // 切换范围(所有/管理/创建/已办/停用)=> 刷新列表(不刷新右侧栏)
  1438. switchScope(management) {
  1439. if (this.loadingList) return;
  1440. this.form.management = String(management);
  1441. this.searchButtonConfigCheckId = String(management);
  1442. this.ssPaging.pageNo = 1;
  1443. this.userInteracted = true;
  1444. this.loadPobjList({ source: "scope" });
  1445. },
  1446. // 提取当前表单参数(用于 ajax 调用 /service?ssServ=...)
  1447. getSearchFormParams() {
  1448. const params = {};
  1449. try {
  1450. const arr = $("#myForm").serializeArray();
  1451. arr.forEach(({name, value}) => {
  1452. if (!name) return;
  1453. if (name === "pageNo" || name === "rowNumPer" || name === "rowNum") return; // 功能说明:分页参数不再从表单序列化读取(完全由 ssPaging 驱动) by xu 20260123
  1454. if (Object.prototype.hasOwnProperty.call(params, name)) {
  1455. const cur = params[name];
  1456. params[name] = Array.isArray(cur) ? cur.concat([value]) : [cur, value];
  1457. } else {
  1458. params[name] = value;
  1459. }
  1460. });
  1461. } catch (e) {
  1462. console.error("getSearchFormParams failed", e);
  1463. }
  1464. return params;
  1465. },
  1466. // 以 Vue data 为准,组装列表/搜索请求参数(避免依赖组件内部 input 的实现)
  1467. getPobjParams() {
  1468. const params = {};
  1469. const form = this.form || {};
  1470. Object.keys(form).forEach((k) => {
  1471. const v = form[k];
  1472. if (v === undefined || v === null) return;
  1473. if (typeof v === "object") return;
  1474. params[k] = v;
  1475. });
  1476. if (this.ssPaging) {
  1477. params.pageNo = this.ssPaging.pageNo;
  1478. params.rowNumPer = this.ssPaging.rowNumPer;
  1479. params.rowNum = this.ssPaging.rowNum;
  1480. }
  1481. return params;
  1482. },
  1483. // 通用:调用后端 service(统一返回原生 Promise,避免 jqXHR 不支持 finally/catch)
  1484. callSsService(ssServ, extraParams = {}) {
  1485. if (!ssServ) return Promise.resolve(null);
  1486. return new Promise((resolve) => {
  1487. $.ajax({
  1488. type: "get",
  1489. url: "/service",
  1490. data: {ssServ, ...extraParams},
  1491. dataType: "text",
  1492. })
  1493. .done((text) => {
  1494. if (typeof text !== "string") return resolve(text);
  1495. const t = text.trim();
  1496. if (!t) return resolve(null);
  1497. try {
  1498. return resolve(JSON.parse(t));
  1499. } catch (_) {
  1500. return resolve(text);
  1501. }
  1502. })
  1503. .fail((xhr, status, err) => {
  1504. console.error("callSsService failed", ssServ, status, err);
  1505. resolve(null);
  1506. });
  1507. });
  1508. },
  1509. // 初始化:拉取查询首页数据(搜索条件/初始列表/右侧面板等)
  1510. loadPobjHome() {
  1511. const ssServ = this.ssSearchPobjHomeServName;
  1512. if (!ssServ) return Promise.resolve(null);
  1513. if (this.loadingHome) return Promise.resolve(null);
  1514. const startedAt = Date.now();
  1515. this.loadingHome = true;
  1516. this.homeReady = false;
  1517. this.userInteracted = false;
  1518. const params = this.getPobjParams();
  1519. // 功能说明:打印首屏请求参数,确认 rowNumPer 是否在第一个接口前写入 by xu 20260123
  1520. try { console.log("[objList][自适应分页] 首页请求参数", { ssServ, pageNo: params?.pageNo, rowNumPer: params?.rowNumPer, rowNum: params?.rowNum, autoRowNumPer: this.autoRowNumPer }); } catch (_) {}
  1521. return this.callSsService(ssServ, params)
  1522. .then((res) => {
  1523. const payload = res && typeof res === "object" ? (res.ssData || res) : null;
  1524. this.applyPobjHomeData(payload);
  1525. console.log("[pobj] home", ssServ, res);
  1526. return res;
  1527. })
  1528. .finally(() => {
  1529. const delay = Math.max(0, 200 - (Date.now() - startedAt));
  1530. setTimeout(() => {
  1531. this.loadingHome = false;
  1532. }, delay);
  1533. });
  1534. },
  1535. applyPobjListData(ssData) {
  1536. if (!ssData || typeof ssData !== "object") return;
  1537. // thnType(若列表接口也返回,则更新;一般由首页接口返回)
  1538. if (ssData.thnType != null) this.thnType = Number(ssData.thnType || 0);
  1539. if (ssData.ssPaging) {
  1540. this.ssPaging = {
  1541. ...this.ssPaging,
  1542. ...ssData.ssPaging,
  1543. pageNo: Number(ssData.ssPaging.pageNo || 1),
  1544. rowNumPer: Number(this.ssPaging?.rowNumPer || ssData.ssPaging.rowNumPer || 0), // 功能说明:rowNumPer 由 loadPobjList 按 autoRowNumPer 统一生效;这里只在当前为空时回退后端值 by xu 20260123
  1545. rowNum: Number(ssData.ssPaging.rowNum || 0),
  1546. };
  1547. }
  1548. const nextList = Array.isArray(ssData.objList) ? ssData.objList.map((r) => this.normalizeObjToCard(r)) : [];
  1549. if (this.listConfig) this.listConfig.list = nextList;
  1550. this.cardGridKind = this.detectCardGridKind();
  1551. // 功能说明:列表接口返回后不再每次重算 autoRowNumPer,避免翻页过程中 rowNumPer 在 15/16 间抖动;仅在 autoRowNumPer 尚未得到时兜底计算一次 by xu 20260303
  1552. if (!Number(this.autoRowNumPer || 0)) {
  1553. if (this.$nextTick) this.$nextTick(() => this.scheduleAutoRowNumPer("list-init"));
  1554. else this.scheduleAutoRowNumPer("list-init");
  1555. }
  1556. },
  1557. // 功能说明:将 <对象名>_cxycl 返回的 bookList 写回右侧预订面板 by xu 20260114
  1558. applyPobjYclData(ssData) {
  1559. // 功能说明:兼容返回结构(ssData 可能是整包 {ssCode,ssData} 或直接 ssData),避免“预订tab不回显” by xu 20260116
  1560. if (!ssData) return;
  1561. const data = (ssData && typeof ssData === "object" && ssData.ssData && typeof ssData.ssData === "object") ? ssData.ssData : ssData;
  1562. if (!data || typeof data !== "object") return;
  1563. const ensurePanel = (tabKeyLower, title) => {
  1564. // 功能说明:严格依赖首页 rbarTabNameList 决定是否允许创建面板:未返回 rbarServ 时即使 cxycl 有 servList 也不处理 by xu 20260116
  1565. if (tabKeyLower === "rbarbook" && !this.hasBookPanel) return null;
  1566. if (tabKeyLower === "rbarserv" && !this.hasServPanel) return null;
  1567. const panels = Array.isArray(this.sidebarPanels) ? this.sidebarPanels : [];
  1568. let p = panels.find((it) => String(it?._tabKey || "").toLowerCase() === tabKeyLower);
  1569. if (p) return p;
  1570. const idx = panels.findIndex((it) => it?.type === "chart");
  1571. const insertAt = idx >= 0 ? idx : panels.length;
  1572. p = {
  1573. type: "list",
  1574. _tabKey: tabKeyLower,
  1575. title,
  1576. // 功能说明:预订/服务面板 iconClass 与首页初始化保持一致(避免 cxycl 兜底创建时图标错误) by xu 20260123
  1577. iconClass: (tabKeyLower === "rbarbook")
  1578. ? "ss-sidebar-biz-icon icon-obj-yd"
  1579. : (tabKeyLower === "rbarserv")
  1580. ? "ss-sidebar-biz-icon icon-obj-fw"
  1581. : (this.ssObjName ? ("ss-sidebar-biz-icon icon-obj-" + this.ssObjName) : ""),
  1582. items: [],
  1583. itemAction: false,
  1584. };
  1585. // 功能说明:预订面板默认使用 person 布局(title+meta) by xu 20260116
  1586. if (tabKeyLower === "rbarbook") p.itemLayout = "person";
  1587. if (tabKeyLower === "rbarserv") p.itemLayout = "simple"; // 功能说明:服务面板只回显 mc(名称) by xu 20260116
  1588. panels.splice(insertAt, 0, p);
  1589. this.sidebarPanels = panels;
  1590. return p;
  1591. };
  1592. const fmtDt = (v) => { // 功能说明:JSP 页面避免使用 JS 模板字符串插值(可能被 JSP EL 干扰),统一用字符串拼接 by xu 20260114
  1593. if (v === undefined || v === null || v === "") return "";
  1594. const d = new Date(v);
  1595. if (!isNaN(d.getTime())) {
  1596. const pad2 = (n) => String(n).padStart(2, "0");
  1597. return (
  1598. String(d.getFullYear()) +
  1599. "-" +
  1600. pad2(d.getMonth() + 1) +
  1601. "-" +
  1602. pad2(d.getDate()) +
  1603. " " +
  1604. pad2(d.getHours()) +
  1605. ":" +
  1606. pad2(d.getMinutes())
  1607. );
  1608. }
  1609. return String(v);
  1610. };
  1611. const bookList = Array.isArray(data.bookList) ? data.bookList : [];
  1612. const servList = Array.isArray(data.servList) ? data.servList : []; // 功能说明:将 <对象名>_cxycl 返回的 servList 写回右侧服务面板 by xu 20260116
  1613. try {
  1614. console.log("[pobj] cxycl bookList", {len: bookList.length, bookList: bookList});
  1615. } catch (_) {
  1616. } // 功能说明:打印 cxycl 返回,排查“预订tab不回显” by xu 20260116
  1617. // 功能说明:无论 bookList 是否为空,都要写回面板(为空则清空旧数据),避免“切换对象但仍显示上一次预订人” by xu 20260114
  1618. const bookPanel = ensurePanel("rbarbook", "预订");
  1619. if (bookPanel) {
  1620. bookPanel.itemLayout = "person"; // 功能说明:预订面板复用“已选”tab 的 person 布局(title+中间 meta 槽位),不走 tags by xu 20260114
  1621. bookPanel.items = bookList.map((b) => ({
  1622. _raw: b,
  1623. id: b?.id,
  1624. title: String(b?.ydr ?? ""), // 功能说明:预订面板 title=预订人 by xu 20260114
  1625. meta: String(b?.ydrdh ?? ""), // 功能说明:预订面板中间槽位 meta=电话(同“已选”tab) by xu 20260114
  1626. tags: [], // 功能说明:预订面板不使用 tags(避免自定义标签样式) by xu 20260114
  1627. }));
  1628. bookPanel.count = bookPanel.items.length;
  1629. }
  1630. // 功能说明:服务面板按预订(book)回显逻辑写回(title+meta),为空也要清空 by xu 20260116
  1631. const servPanel = ensurePanel("rbarserv", "服务");
  1632. if (servPanel) {
  1633. servPanel.itemLayout = "simple"; // 功能说明:服务面板字段只有 id/mc,仅回显 mc(名称) by xu 20260116
  1634. servPanel.items = servList.map((s) => ({
  1635. _raw: s,
  1636. id: s?.id, // 功能说明:服务面板 id=数据库ID by xu 20260116
  1637. title: String(s?.mc ?? ""), // 功能说明:服务面板 title=mc(名称) by xu 20260116
  1638. tags: [],
  1639. }));
  1640. servPanel.count = servPanel.items.length;
  1641. }
  1642. // 功能说明:强制触发 sidebarPanels 更新,避免仅修改子对象导致视图未刷新(预订tab回显不更新) by xu 20260116
  1643. try {
  1644. this.sidebarPanels = (this.sidebarPanels || []).slice();
  1645. } catch (e) {
  1646. }
  1647. try {
  1648. console.log("[pobj] cxycl bookPanel(after)", {
  1649. title: bookPanel.title,
  1650. tab: bookPanel._tabKey,
  1651. count: bookPanel.count,
  1652. first: bookPanel.items?.[0],
  1653. });
  1654. } catch (_) {
  1655. } // 功能说明:打印写回后的面板状态,排查“预订tab不回显” by xu 20260116
  1656. },
  1657. // 功能说明:选中对象后联动调用 <对象名>_cxycl,填充右侧预订/服务面板 by xu 20260114
  1658. loadPobjYclByItem(item) {
  1659. if (!(this.hasBookPanel || this.hasServPanel)) return Promise.resolve(null); // 功能说明:预订/服务任一存在才调用 cxycl;否则不联动 by xu 20260116
  1660. const objNum = item?.objNum || item?._raw?.ssObjId || item?._raw?.ssObjId;
  1661. if (!objNum) return Promise.resolve(null);
  1662. const ssServ = this.ssObjName ? (String(this.ssObjName) + "_cxycl") : "";
  1663. if (!ssServ) return Promise.resolve(null);
  1664. const reqId = (this.__yclReqId = (this.__yclReqId || 0) + 1);
  1665. this.yclLoading = true;
  1666. // 功能说明:cxycl 需要同时传入 ssObjIdName=<对象名>id,且把对象ID用该字段名提交(如 cdid=90052) by xu 20260114
  1667. const ssObjIdName = String(this.ssObjName || "") + "id";
  1668. const params = {ssObjName: this.ssObjName, ssObjIdName: ssObjIdName, ssObjId: objNum};
  1669. params[ssObjIdName] = objNum;
  1670. return this.callSsService(ssServ, params)
  1671. .then((res) => {
  1672. if (reqId !== this.__yclReqId) return null;
  1673. // 功能说明:兼容 $.ajax/axios 等不同返回结构,避免取错层级导致 bookList 为空 by xu 20260116
  1674. const payload = res && typeof res === "object" ? (res.ssData || res?.data?.ssData || res?.data || res) : null;
  1675. this.yclResp = payload;
  1676. this.applyPobjYclData(payload);
  1677. return res;
  1678. })
  1679. .finally(() => {
  1680. if (reqId !== this.__yclReqId) return;
  1681. this.yclLoading = false;
  1682. });
  1683. },
  1684. // 拉取列表数据(分页/搜索等)
  1685. loadPobjList(options = {}) {
  1686. const ssServ = this.ssSearchPobjListServName;
  1687. if (!ssServ) return Promise.resolve(null);
  1688. // 只有用户操作后才允许请求列表(避免初始化阶段出现第二个 loading)
  1689. if (!this.userInteracted) return Promise.resolve(null);
  1690. if (this.loadingList) return Promise.resolve(null);
  1691. const opts = (options && typeof options === "object") ? options : {};
  1692. const source = String(opts.source || "general");
  1693. const lockRowNumPer = !!opts.lockRowNumPer;
  1694. this.__lastListLoadSource = source;
  1695. // 功能说明:rowNumPer 按 autoRowNumPer 生效(resize 只记录,不立即请求;下次接口请求时才更新) by xu 20260123
  1696. // 功能说明:翻页/刷新请求锁定当前 rowNumPer,避免在翻页链路中被 autoRowNumPer 覆盖导致 pageNo 重置 by xu 20260303
  1697. if (!lockRowNumPer) {
  1698. try {
  1699. const next = Number(this.autoRowNumPer || 0);
  1700. const cur = Number(this.ssPaging?.rowNumPer || 0);
  1701. if (next > 0 && next !== cur) {
  1702. try { console.log("[objList][自适应分页] 列表请求前应用 rowNumPer", { source, cur, next, pageNo: this.ssPaging?.pageNo }); } catch (_) {}
  1703. this.ssPaging.rowNumPer = next;
  1704. // 功能说明:仅在非翻页来源下,页大小变化时回到第一页;翻页来源必须保持当前页码 by xu 20260303
  1705. if (source !== "page") {
  1706. this.ssPaging.pageNo = 1;
  1707. }
  1708. }
  1709. } catch (_) {
  1710. }
  1711. }
  1712. const startedAt = Date.now();
  1713. this.loadingList = true;
  1714. // 点击后立即清空列表区域,避免用户误以为没点到
  1715. if (this.listConfig) {
  1716. this.listConfig.draftbox = [];
  1717. this.listConfig.list = [];
  1718. }
  1719. const params = this.getPobjParams();
  1720. try { console.log("[objList][分页请求]", { source, lockRowNumPer, pageNo: params?.pageNo, rowNumPer: params?.rowNumPer, rowNum: params?.rowNum }); } catch (_) {}
  1721. return this.callSsService(ssServ, params)
  1722. .then((res) => {
  1723. const payload = res && typeof res === "object" ? (res.ssData || res) : null;
  1724. this.ssListResp = payload;
  1725. this.applyPobjListData(payload);
  1726. console.log("[pobj] list", ssServ, res);
  1727. return res;
  1728. })
  1729. .finally(() => {
  1730. const delay = Math.max(0, 200 - (Date.now() - startedAt));
  1731. setTimeout(() => {
  1732. this.loadingList = false;
  1733. }, delay);
  1734. });
  1735. },
  1736. // 初始化右侧边栏分区(已选 items 指向 selectedItems) by xu 20260113
  1737. initSidebarPanels() {
  1738. const bizIcon = this.ssObjName ? ('ss-sidebar-biz-icon icon-obj-' + this.ssObjName) : ''; // 功能说明:右侧栏 iconClass 使用右侧栏 biz 图标基类 by xu 20260123
  1739. // 初始只放“已选”,其他分区由首页接口(rbarTabNameList)初始化
  1740. this.sidebarPanels = [];
  1741. this.sidebarButtons = [];
  1742. },
  1743. // 更新已选分区计数 by xu 20260113
  1744. updatePickedPanelCount() {
  1745. const pickedPanel = this.sidebarPanels?.find?.((p) => p?.title === '已选');
  1746. if (pickedPanel) pickedPanel.count = this.selectedItems.length;
  1747. },
  1748. // 清空已选(右侧 header 清空按钮) by xu 20260113
  1749. clearSelectedItems() {
  1750. // 功能说明:按稳定 key 反向同步当前列表卡片选中态(避免列表刷新后对象引用变化导致取消选中失效) by xu 20260122
  1751. const keyOf = (it) => String(it?.objNum || it?.ssObjId || it?._raw?.ssObjId || it?._raw?.objNum || "");
  1752. const keys = new Set((this.selectedItems || []).map(keyOf).filter(Boolean));
  1753. const all = [].concat(this.listConfig?.draftbox || []).concat(this.listConfig?.list || []);
  1754. all.forEach((it) => {
  1755. const k = keyOf(it);
  1756. if (!k) return;
  1757. if (keys.has(k)) it._ssSelected = false;
  1758. });
  1759. this.selectedItems.forEach((it) => {
  1760. if (it) it._ssSelected = false;
  1761. });
  1762. this.selectedItems.splice(0, this.selectedItems.length);
  1763. this.updatePickedPanelCount();
  1764. // 功能说明:强制触发 sidebarPanels 更新,避免 count 未刷新 by xu 20260122
  1765. try { this.sidebarPanels = (this.sidebarPanels || []).slice(); } catch (_) {}
  1766. },
  1767. // 右侧边栏移除 -> 反向同步左侧卡片状态 by xu 20260113
  1768. handleSidebarRemove(item) {
  1769. if (!item) return;
  1770. // 功能说明:按稳定 key 移除,避免列表刷新后 selectedItems/indexOf 引用不一致导致 count 不更新/取消失败 by xu 20260122
  1771. const keyOf = (it) => String(it?.objNum || it?.ssObjId || it?._raw?.ssObjId || it?._raw?.objNum || "");
  1772. const key = keyOf(item);
  1773. item._ssSelected = false;
  1774. const idx = key ? this.selectedItems.findIndex((it) => keyOf(it) === key) : this.selectedItems.indexOf(item);
  1775. if (idx > -1) this.selectedItems.splice(idx, 1);
  1776. // 反向同步当前列表里同 key 的卡片状态
  1777. if (key) {
  1778. const all = [].concat(this.listConfig?.draftbox || []).concat(this.listConfig?.list || []);
  1779. const cur = all.find((it) => keyOf(it) === key);
  1780. if (cur) cur._ssSelected = false;
  1781. }
  1782. this.updatePickedPanelCount();
  1783. // 功能说明:强制触发 sidebarPanels 更新,避免 count 未刷新 by xu 20260122
  1784. try { this.sidebarPanels = (this.sidebarPanels || []).slice(); } catch (_) {}
  1785. },
  1786. // 功能说明:动态计算 rowNumPer(让分页视觉上在最后一行出现),以 DOM 实测为准;返回明细用于调试 by xu 20260123
  1787. calcAutoRowNumPerDetail() {
  1788. try {
  1789. // 功能说明:Vue3 根节点可能是 Fragment(this.$el 不是 Element),统一从 #app 下查找内容区 by xu 20260123
  1790. const host = (this.$el && this.$el.nodeType === 1 && this.$el.querySelector)
  1791. ? this.$el
  1792. : (document.getElementById && document.getElementById("app")) || document.body;
  1793. const area = host && host.querySelector ? host.querySelector(".item-content-area") : null;
  1794. if (!area) return { err: "no_area" };
  1795. const cs = window.getComputedStyle ? window.getComputedStyle(area) : null;
  1796. // 功能说明:内容区宽度以 item-content-area 为准;若右侧栏与内容区发生覆盖(非并排布局),按重叠宽度扣减 by xu 20260123
  1797. let W = Math.max(0, area.clientWidth || 0);
  1798. let overlap = 0;
  1799. try {
  1800. if (this.showRightSidebar) {
  1801. const sidebar = host && host.querySelector ? host.querySelector(".ss-sidebar") : null;
  1802. if (sidebar && area.getBoundingClientRect && sidebar.getBoundingClientRect) {
  1803. const ar = area.getBoundingClientRect();
  1804. const sr = sidebar.getBoundingClientRect();
  1805. overlap = Math.max(0, Math.min(ar.right, sr.right) - Math.max(ar.left, sr.left));
  1806. if (overlap > 0) W = Math.max(0, W - overlap);
  1807. }
  1808. }
  1809. } catch (_) {
  1810. }
  1811. const H = Math.max(0, area.clientHeight || 0);
  1812. if (!W || !H) {
  1813. // 功能说明:首屏 homeReady=false 时容器 display:none,W/H 为 0;此处回退用 viewport 估算 by xu 20260123
  1814. const vw = Math.max(0, document.documentElement?.clientWidth || window.innerWidth || 0);
  1815. const vhRaw = Math.max(0, document.documentElement?.clientHeight || window.innerHeight || 0);
  1816. // 功能说明:首屏高度按 URL 上的 removeHigh 扣减(弹窗/iframe 会预留顶部/底部区域),否则 rows 会偏大 by xu 20260123
  1817. let removeHigh = 0;
  1818. try {
  1819. const qs = window.location && window.location.search ? window.location.search : "";
  1820. const m = String(qs).match(/(?:\\?|&)removeHigh=([^&]+)/);
  1821. if (m && m[1] != null) {
  1822. const v = parseFloat(decodeURIComponent(m[1]));
  1823. if (!isNaN(v) && v > 0) removeHigh = v;
  1824. }
  1825. } catch (_) {
  1826. }
  1827. const vh = Math.max(0, vhRaw - removeHigh);
  1828. // 功能说明:右侧栏宽度用 rbar 判定(JSP 注入已写入 this.rbar;首页接口返回后会覆盖) by xu 20260123
  1829. const rbarFlag = (this.rbar === true);
  1830. const sidebarW = rbarFlag ? 380 : 0;
  1831. const minW = (Number(this.thnType || 0) === 1) ? 400 : (Number(this.thnType || 0) === 2) ? 320 : 300;
  1832. const gap = 20;
  1833. const padLeft = 20;
  1834. const padRight = 20;
  1835. const padTop = 0;
  1836. const padBottom = 100;
  1837. const pagerH = 32; // 功能说明:分页高度以 .pager-container .pager-content 的 32px 为准(首屏未渲染时用该值预留) by xu 20260123
  1838. let cardH = Number(this.__autoCardH || 0) || Number(this.measureAutoCardHeight?.() || 0);
  1839. if (cardH) this.__autoCardH = cardH;
  1840. if (!vw || !vh || !cardH) return { err: "zero_size", W, H, overlap, vw, vh, cardH, showRightSidebar: !!this.showRightSidebar };
  1841. const Wcontent = Math.max(0, (vw - sidebarW) - padLeft - padRight);
  1842. const cols = Math.max(1, Math.floor((Wcontent + gap) / (minW + gap)));
  1843. const availH = Math.max(0, (vh - 75) - padTop - padBottom - pagerH); // 75=search/预留(与 CSS 对齐) by xu 20260123
  1844. const rows = Math.max(1, Math.floor((availH + gap) / (cardH + gap)));
  1845. const n = Math.max(1, cols * rows);
  1846. return { n, cols, rows, W: 0, H: 0, overlap, gap, padTop, padBottom, padLeft, padRight, minW, pagerH, cardH, vw, vhRaw, vh, removeHigh, sidebarW, rbarFlag, fallback: "viewport" };
  1847. }
  1848. const gap = cs ? (parseFloat(cs.gap) || 0) : 0;
  1849. const padLeft = cs ? (parseFloat(cs.paddingLeft) || 0) : 0;
  1850. const padRight = cs ? (parseFloat(cs.paddingRight) || 0) : 0;
  1851. const padTop = cs ? (parseFloat(cs.paddingTop) || 0) : 0;
  1852. const padBottom = cs ? (parseFloat(cs.paddingBottom) || 0) : 0;
  1853. const minW = cs ? (parseFloat(cs.getPropertyValue("--ss-card-min")) || 300) : 300;
  1854. const Wcontent = Math.max(0, W - padLeft - padRight);
  1855. const cols = Math.max(1, Math.floor((Wcontent + gap) / (minW + gap)));
  1856. const pagerEl = area.querySelector ? (area.querySelector(".pager-bar, .pager-container") || null) : null;
  1857. const pagerH = pagerEl && pagerEl.getBoundingClientRect ? (pagerEl.getBoundingClientRect().height || 0) : 0;
  1858. const cardEl = area.querySelector ? (area.querySelector(".knowledge-item-container") || null) : null;
  1859. let cardH = cardEl && cardEl.getBoundingClientRect ? (cardEl.getBoundingClientRect().height || 0) : 0;
  1860. if (!cardH) {
  1861. // 功能说明:首屏/空列表时通过 probe 实测卡片高度并缓存,避免 rowNumPer 只能等列表接口返回后才可计算 by xu 20260123
  1862. cardH = Number(this.__autoCardH || 0) || Number(this.measureAutoCardHeight?.() || 0);
  1863. if (cardH) this.__autoCardH = cardH;
  1864. }
  1865. if (!cardH) return { err: "no_card_height", W, H, overlap, showRightSidebar: !!this.showRightSidebar };
  1866. const availH = Math.max(0, H - padTop - padBottom - pagerH);
  1867. const rows = Math.max(1, Math.floor((availH + gap) / (cardH + gap)));
  1868. const n = Math.max(1, cols * rows);
  1869. return { n, cols, rows, W, H, overlap, gap, padTop, padBottom, padLeft, padRight, minW, pagerH, cardH, showRightSidebar: !!this.showRightSidebar };
  1870. } catch (_) {
  1871. return { err: "exception" };
  1872. }
  1873. },
  1874. // 功能说明:仅返回 rowNumPer 数值(兼容旧调用点) by xu 20260123
  1875. calcAutoRowNumPer() {
  1876. const d = this.calcAutoRowNumPerDetail?.();
  1877. return d && d.n ? d.n : 0;
  1878. },
  1879. measureAutoCardHeight() { // 功能说明:用 DOM probe 实测 ss-list-card 的稳定高度(不依赖接口数据) by xu 20260123
  1880. try {
  1881. const probe = document.createElement("div");
  1882. const kind = Number(this.thnType || 0) === 1 ? "card-thumbnail" : "card-photo";
  1883. probe.className = "knowledge-item-container " + kind;
  1884. probe.style.position = "fixed";
  1885. probe.style.left = "-99999px";
  1886. probe.style.top = "0";
  1887. probe.style.visibility = "hidden";
  1888. probe.style.pointerEvents = "none";
  1889. probe.style.zIndex = "-1";
  1890. // 让 probe 按最小列宽测量(min-width 对高度几乎无影响,但保持一致) by xu 20260123
  1891. try {
  1892. // 功能说明:同 calcAutoRowNumPerDetail,优先从 #app 下取 item-content-area by xu 20260123
  1893. const host = (this.$el && this.$el.nodeType === 1 && this.$el.querySelector)
  1894. ? this.$el
  1895. : (document.getElementById && document.getElementById("app")) || document.body;
  1896. const area = host && host.querySelector ? host.querySelector(".item-content-area") : null;
  1897. const cs = area && window.getComputedStyle ? window.getComputedStyle(area) : null;
  1898. const minW = cs ? (parseFloat(cs.getPropertyValue("--ss-card-min")) || 300) : 300;
  1899. probe.style.width = String(minW) + "px";
  1900. } catch (_) {
  1901. }
  1902. probe.innerHTML =
  1903. '<div class="body">' +
  1904. ' <div class="box-header"><div>_</div></div>' +
  1905. ' <div class="box-body">' +
  1906. ' <div class="left ss-objlist-thumbPlaceholder"><div class="ss-objlist-thumbIcon">_</div></div>' +
  1907. ' <div class="right"></div>' +
  1908. " </div>" +
  1909. "</div>";
  1910. document.body.appendChild(probe);
  1911. const h = probe.getBoundingClientRect ? (probe.getBoundingClientRect().height || 0) : 0;
  1912. document.body.removeChild(probe);
  1913. return Math.max(0, h);
  1914. } catch (_) {
  1915. return 0;
  1916. }
  1917. },
  1918. // 功能说明:延迟/去抖触发自动 rowNumPer 计算(避免 resize 频繁触发请求) by xu 20260123
  1919. scheduleAutoRowNumPer(reason) {
  1920. if (this.__autoRowNumTimer) clearTimeout(this.__autoRowNumTimer);
  1921. this.__autoRowNumTimer = setTimeout(() => {
  1922. this.__autoRowNumTimer = null;
  1923. this.applyAutoRowNumPer(reason || "");
  1924. }, 150);
  1925. },
  1926. // 功能说明:应用自动 rowNumPer;只记录到 data(下次接口请求时生效),resize 不立即重拉 by xu 20260123
  1927. applyAutoRowNumPer(reason) {
  1928. const d = this.calcAutoRowNumPerDetail?.();
  1929. const next = Number(d?.n || 0);
  1930. if (!next) return;
  1931. const cur = Number(this.autoRowNumPer || 0);
  1932. if (cur === next) return;
  1933. this.autoRowNumPer = next;
  1934. // 功能说明:打印计算明细,便于排查“为什么还是12” by xu 20260123
  1935. try { console.log("[objList][自适应分页] 计算更新", { reason, cur, next, detail: d }); } catch (_) {}
  1936. },
  1937. // 功能:探测当前业务列表卡片类型,用于网格列最小宽度 by xu 20260109
  1938. detectCardGridKind() {
  1939. const all = [].concat(this.listConfig?.draftbox || []).concat(this.listConfig?.list || []);
  1940. if (all.some(it => String(it?.thumbType || '').trim() === 'thumbnail')) return 'thumbnail';
  1941. if (all.some(it => !!it?.thumb || !!it?.thumbType)) return 'photo';
  1942. return 'none';
  1943. },
  1944. // 左侧卡片角标选中(多选)+ 卡片主体单选互斥(exclusive) by xu 20260109
  1945. handleToggleSelect({item, selected, exclusive}) {
  1946. if (!item) return;
  1947. if (!this.hasObjPanel) return; // 功能说明:无 rbarObj 时禁止选中(已选tab也不显示) by xu 20260122
  1948. // 已选分区:中间槽位 meta(可按业务改为后端字段) by xu 20260113
  1949. if (!item.meta) item.meta = String(item.objNum || '');
  1950. // 功能说明:按稳定 key 管理 selectedItems,避免列表刷新后对象引用变化导致取消选中/计数异常 by xu 20260122
  1951. const keyOf = (it) => String(it?.objNum || it?.ssObjId || it?._raw?.ssObjId || it?._raw?.objNum || "");
  1952. const key = keyOf(item);
  1953. const idx = key ? this.selectedItems.findIndex((it) => keyOf(it) === key) : this.selectedItems.indexOf(item);
  1954. if (exclusive && selected && this.cardClickAction === 'single') {
  1955. this.selectedItems.forEach((it) => {
  1956. if (it && it !== item) it._ssSelected = false;
  1957. });
  1958. // 不要重新赋值数组,保留引用(供右侧面板使用) by xu 20260109
  1959. this.selectedItems.splice(0, this.selectedItems.length);
  1960. this.selectedItems.push(item);
  1961. this.updatePickedPanelCount();
  1962. // 功能说明:预订模式下,单选互斥选中后联动加载右侧预订/服务数据 by xu 20260114
  1963. this.loadPobjYclByItem(item);
  1964. // 功能说明:强制触发 sidebarPanels 更新,避免 count 未刷新 by xu 20260122
  1965. try { this.sidebarPanels = (this.sidebarPanels || []).slice(); } catch (_) {}
  1966. return;
  1967. }
  1968. if (selected) {
  1969. if (idx === -1) {
  1970. this.selectedItems.push(item);
  1971. } else if (idx > -1 && this.selectedItems[idx] !== item) {
  1972. // 功能说明:用最新的 item 对象替换旧引用,确保侧边栏操作能反向同步到当前卡片 by xu 20260122
  1973. this.selectedItems.splice(idx, 1, item);
  1974. }
  1975. // 功能说明:预订模式下,多选角标选中也可触发联动(以最近选中项为准) by xu 20260114
  1976. this.loadPobjYclByItem(item);
  1977. } else {
  1978. if (idx > -1) this.selectedItems.splice(idx, 1);
  1979. }
  1980. this.updatePickedPanelCount();
  1981. // 功能说明:强制触发 sidebarPanels 更新,避免 count 未刷新 by xu 20260122
  1982. try { this.sidebarPanels = (this.sidebarPanels || []).slice(); } catch (_) {}
  1983. },
  1984. handlePageChange({pageNo, rowNumPer, rowNum}) {
  1985. // 翻页:调用列表接口刷新(不刷新右侧栏)
  1986. if (this.loadingList) return;
  1987. this.ssPaging.pageNo = pageNo;
  1988. this.ssPaging.rowNumPer = rowNumPer;
  1989. this.ssPaging.rowNum = rowNum;
  1990. this.userInteracted = true;
  1991. this.loadPobjList({ source: "page", lockRowNumPer: true });
  1992. },
  1993. search() {
  1994. // 搜索:回到第一页,调用列表接口刷新(不刷新右侧栏)
  1995. if (this.loadingList) return;
  1996. this.ssPaging.pageNo = 1;
  1997. this.userInteracted = true;
  1998. this.loadPobjList({ source: "search" });
  1999. }
  2000. },
  2001. mounted() {
  2002. // 功能说明:临时将 vm 暴露到 window,供 searchButtonConfig.opt.callback 调用(后续再改为事件回传方式) by xu 20260123
  2003. try { window.__objListVm = this; } catch (_) {}
  2004. // 功能说明:弹窗保存后只刷新列表数据(走 Ajax),禁止走 form.submit/reload 以免跳到 /service 返回内容导致白屏 by xu 20260202
  2005. try {
  2006. const vm = this;
  2007. window.wdRefresh = function () {
  2008. try {
  2009. if (vm.loadingList) return;
  2010. // 保持当前筛选/页码,仅刷新列表数据
  2011. vm.userInteracted = true;
  2012. vm.loadPobjList({ source: "refresh", lockRowNumPer: true });
  2013. } catch (e) {
  2014. console.error("[objList] wdRefresh failed", e);
  2015. }
  2016. };
  2017. } catch (_) {}
  2018. // 初始化:把 URL/form 上已有的参数合并到 form/ssPaging(支持回显 + ajax)
  2019. const curParams = this.getSearchFormParams();
  2020. this.form = {...this.form, ...curParams};
  2021. // 功能说明:分页参数不再从表单读取/初始化(完全由 ssPaging 驱动,且页面不再输出分页隐藏域) by xu 20260123
  2022. this.searchButtonConfigCheckId = String(this.form?.management || "99");
  2023. // 功能说明:首屏在列表数据未到达前,按 thnType 预设 cardGridKind(决定 --ss-card-min),避免 detectCardGridKind 在空列表时把缩略图场景误判为 none by xu 20260123
  2024. try {
  2025. const t = Number(this.thnType || 0);
  2026. if (t === 1) this.cardGridKind = "thumbnail";
  2027. else if (t === 2) this.cardGridKind = "photo";
  2028. else this.cardGridKind = "none";
  2029. } catch (_) {
  2030. this.cardGridKind = "none";
  2031. }
  2032. // 初始化右侧边栏分区 by xu 20260113
  2033. this.initSidebarPanels();
  2034. // 功能说明:订阅系统编辑模式变化(来自 home 顶部按钮/菜单切换),用于业务页右下角“新增”入口显隐 by xu 20260123
  2035. try {
  2036. const bus = window.parent?.sharedEventBus;
  2037. if (bus?.subscribe) {
  2038. const applySysMode = (payload) => {
  2039. const mode = (payload && typeof payload === "object") ? payload.mode : payload;
  2040. const source = (payload && typeof payload === "object") ? payload.source : "unknown";
  2041. this.isSysEditMode = (mode === "edit");
  2042. this.sysModeSource = String(source || "unknown");
  2043. };
  2044. this.__sysEditSub = bus.subscribe("systemEditModelChange", applySysMode);
  2045. try { applySysMode(bus.getState?.("systemEditModelChange")); } catch (_) {}
  2046. }
  2047. } catch (_) {}
  2048. this.userInteracted = false;
  2049. // 功能说明:首屏在第一个接口调用前,按当前容器尺寸预先计算 rowNumPer,并写入 ssPaging(使首页接口直接返回“刚好铺满”的条数) by xu 20260123
  2050. const startHome = () => {
  2051. // 功能说明:首屏以 JSP 注入的 rbar 作为右侧栏存在与否的唯一依据 by xu 20260123
  2052. try { this.showRightSidebar = (this.rbar === true); } catch (_) {}
  2053. // 功能说明:打印首屏关键输入(thnType/cardGridKind/rbar),排查“有缩略图首屏 rowNumPer 偏大” by xu 20260123
  2054. try { console.log("[objList][自适应分页] 首屏输入", { thnType: this.thnType, cardGridKind: this.cardGridKind, rbar: this.rbar, showRightSidebar: this.showRightSidebar }); } catch (_) {}
  2055. // 功能说明:首屏首次渲染时容器可能尚未有尺寸(W/H=0),用 rAF 重试几帧再发第一个接口,确保 rowNumPer 能在首屏生效 by xu 20260123
  2056. let tries = 0;
  2057. const maxTries = 20;
  2058. const tick = () => {
  2059. let d = null;
  2060. try { d = this.calcAutoRowNumPerDetail?.(); } catch (_) {}
  2061. if (tries === 0 || tries === maxTries - 1) {
  2062. try { console.log("[objList][自适应分页] 首屏计算", { tries, detail: d }); } catch (_) {}
  2063. }
  2064. const n = Number(d?.n || 0);
  2065. if (n > 0) {
  2066. this.autoRowNumPer = n;
  2067. this.ssPaging.rowNumPer = n;
  2068. this.ssPaging.pageNo = 1;
  2069. try { console.log("[objList][自适应分页] 首屏应用 rowNumPer", { rowNumPer: n, ssPaging: this.ssPaging, fallback: d?.fallback }); } catch (_) {}
  2070. this.loadPobjHome();
  2071. return;
  2072. }
  2073. tries += 1;
  2074. if (tries >= maxTries) {
  2075. // 达到重试上限仍无法计算,回退按当前 ssPaging.rowNumPer 发请求 by xu 20260123
  2076. try { console.log("[objList][自适应分页] 首屏计算放弃", { ssPaging: this.ssPaging, autoRowNumPer: this.autoRowNumPer, last: d }); } catch (_) {}
  2077. this.loadPobjHome();
  2078. return;
  2079. }
  2080. requestAnimationFrame(tick);
  2081. };
  2082. requestAnimationFrame(tick);
  2083. };
  2084. if (this.$nextTick) this.$nextTick(() => startHome());
  2085. else startHome();
  2086. // 功能说明:监听窗口变化,触发 rowNumPer 自动计算(resize 只记录,下次接口请求时生效) by xu 20260123
  2087. this.__autoRowNumResizeHandler = () => this.scheduleAutoRowNumPer("resize");
  2088. window.addEventListener && window.addEventListener("resize", this.__autoRowNumResizeHandler);
  2089. }
  2090. ,beforeUnmount() {
  2091. // 功能说明:清理 resize 监听与 timer,避免页面切换残留 by xu 20260123
  2092. // 功能说明:清理 window.__objListVm(避免切页后回调指向旧 vm) by xu 20260123
  2093. try { if (window.__objListVm === this) window.__objListVm = null; } catch (_) {}
  2094. if (this.__autoRowNumResizeHandler && window.removeEventListener) {
  2095. window.removeEventListener("resize", this.__autoRowNumResizeHandler);
  2096. this.__autoRowNumResizeHandler = null;
  2097. }
  2098. if (this.__autoRowNumTimer) {
  2099. clearTimeout(this.__autoRowNumTimer);
  2100. this.__autoRowNumTimer = null;
  2101. }
  2102. // 功能说明:解绑系统编辑态订阅,避免多次进入页面累积回调 by xu 20260123
  2103. try { this.__sysEditSub?.unSubscribe?.(); } catch (_) {}
  2104. this.__sysEditSub = null;
  2105. }
  2106. });
  2107. });
  2108. <%-- 原有的清令牌 --%>
  2109. tokenCleanser("<ss:serv name='clearPageToken'/>", {tokenList: "<%= pageContext.getAttribute(ss.page.PageC.PAGE_tokenList)%>"});
  2110. </script>