filesjs.js 121 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676
  1. $(document).ready(function() {
  2. let fileSearch, fileReference;
  3. autoFlashHeight();
  4. /**
  5. * FilingSjs - 资料管理 SpreadJS 版本
  6. * 使用 SpreadJS 替代 zTree 展示文件分类树,保留完整的文件管理功能
  7. */
  8. class FilingSjs {
  9. constructor() {
  10. // 初始化 SpreadJS
  11. this.spread = SpreadJsObj.createNewSpread($('#filing-spread')[0]);
  12. this.spread.options.showHorizontalScrollbar = false; // 隐藏水平滚动条
  13. this.spread.options.allowUndo = false; // 名称逐次保存到服务端,不使用仅修改单元格的撤销。
  14. this.sheet = this.spread.getActiveSheet();
  15. this.treeIcons = {
  16. closed: new Image(),
  17. open: new Image(),
  18. leaf: new Image(),
  19. };
  20. const refreshTreeIcon = () => { this.spread.refresh(); };
  21. this.treeIcons.closed.onload = refreshTreeIcon;
  22. this.treeIcons.open.onload = refreshTreeIcon;
  23. this.treeIcons.leaf.onload = refreshTreeIcon;
  24. this.treeIcons.closed.src = '/public/images/filesjs-tree-closed.png';
  25. this.treeIcons.open.src = '/public/images/filesjs-tree-open.png';
  26. this.treeIcons.leaf.src = '/public/images/filesjs-tree-leaf.png';
  27. const treeIcons = this.treeIcons;
  28. // 初始化 PathTree,使用 zh_filing 原有字段映射
  29. this.filingTree = createNewPathTree('base', {
  30. id: 'tree_id',
  31. pid: 'tree_pid',
  32. order: 'order',
  33. level: 'level',
  34. isLeaf: 'is_leaf',
  35. fullPath: 'full_path',
  36. rootId: -1,
  37. });
  38. // SpreadJS 表格配置
  39. this.spreadSetting = {
  40. cols: [
  41. {
  42. title: '分类名称', colSpan: '1', rowSpan: '1', field: 'name', hAlign: 0, width: 220, formatter: '@', cellType: 'tree', showTips: true,
  43. getValue: function(data) {
  44. return data.name;
  45. },
  46. getIcon: function(data) {
  47. const fallback = data.is_leaf ? 'file' : 'folder';
  48. return {
  49. image: data.is_leaf ? treeIcons.leaf : (data.expanded ? treeIcons.open : treeIcons.closed),
  50. fallback,
  51. size: 18,
  52. };
  53. },
  54. getTip: function(data) { return _.escape(data.name || ''); },
  55. readOnly: data => !this._canEditFilingName(data),
  56. },
  57. { title: '文件数', colSpan: '1', rowSpan: '1', field: 'total_file_count', hAlign: 1, width: 70, formatter: '@', readOnly: true },
  58. ],
  59. emptyRows: 0,
  60. headRows: 1,
  61. headRowHeight: [32],
  62. defaultRowHeight: 21,
  63. headColWidth: [25],
  64. headerFont: '12px 微软雅黑',
  65. font: '12px 微软雅黑',
  66. readOnly: false,
  67. selectedBackColor: '#e8f4fd',
  68. };
  69. SpreadJsObj.initSheet(this.sheet, this.spreadSetting);
  70. // 文件管理相关属性
  71. this.pageCount = 15;
  72. this.curPage = 1;
  73. this.curTotalPage = 1;
  74. this.curFiling = null;
  75. this.selectRemoveNodes = [];
  76. this._directorySaving = false;
  77. this._reloadingFiling = false;
  78. this.fileOrderKey = 'fileOrderSjs';
  79. this.fileOrder = getLocalCache(this.fileOrderKey) || 'create_time|desc';
  80. this.curFilingKey = 'cur-filing-sjs-' + window.location.pathname.split('/')[2];
  81. this._isRenaming = false;
  82. this._nameEdit = null;
  83. this._nameSaving = false;
  84. this._bindEvents();
  85. this._initNameClipboard();
  86. this._loadFilingData();
  87. this.refreshFileOrderButton();
  88. }
  89. /**
  90. * 递归累加文件数
  91. */
  92. _calcTotalFileCount(data) {
  93. for (const node of data) {
  94. node.total_file_count = 0;
  95. node._children = [];
  96. }
  97. for (const node of data) {
  98. if (node.tree_pid !== -1) {
  99. const parent = data.find(x => x.tree_id === node.tree_pid);
  100. if (parent) {
  101. parent._children.push(node);
  102. }
  103. }
  104. }
  105. const calcNode = (node) => {
  106. if (node._children && node._children.length > 0) {
  107. for (const child of node._children) {
  108. calcNode(child);
  109. }
  110. node.total_file_count = node._children.reduce((sum, child) => {
  111. return sum + child.total_file_count;
  112. }, 0);
  113. } else {
  114. node.total_file_count = node.file_count || 0;
  115. }
  116. };
  117. const roots = data.filter(x => x.tree_pid === -1);
  118. for (const root of roots) {
  119. calcNode(root);
  120. }
  121. for (const node of data) {
  122. delete node._children;
  123. }
  124. }
  125. /**
  126. * 转换数据格式
  127. */
  128. _convertData(sourceData) {
  129. const data = [];
  130. const skippedIds = new Set();
  131. const sortedData = [...sourceData].sort((a, b) => {
  132. return Number(a.tree_level) - Number(b.tree_level);
  133. });
  134. for (const node of sortedData) {
  135. if (skippedIds.has(node.tree_pid)) {
  136. skippedIds.add(node.id);
  137. continue;
  138. }
  139. const parent = (node.tree_pid === '-1' || node.tree_pid === -1)
  140. ? undefined
  141. : data.find(x => x.id === node.tree_pid);
  142. const hasChild = sourceData.some(x => x.tree_pid === node.id);
  143. if (!parent && node.tree_pid !== '-1' && node.tree_pid !== -1) {
  144. skippedIds.add(node.id);
  145. continue;
  146. }
  147. const convertedNode = {
  148. id: node.id,
  149. tree_id: data.length + 1,
  150. tree_pid: parent ? parent.tree_id : -1,
  151. order: Number(node.tree_order) + 1,
  152. level: Number(node.tree_level),
  153. is_leaf: !hasChild,
  154. full_path: '',
  155. name: node.name,
  156. filing_type: Number(node.filing_type),
  157. file_count: Number(node.file_count) || 0,
  158. is_fixed: node.is_fixed,
  159. tips: node.tips || '',
  160. upload_tips: node.upload_tips || '',
  161. spid: node.spid,
  162. };
  163. data.push(convertedNode);
  164. }
  165. this._calcTotalFileCount(data);
  166. return data;
  167. }
  168. /**
  169. * 加载 filing 数据到 SpreadJS
  170. */
  171. _loadFilingData() {
  172. const convertedData = this._convertData(filing);
  173. this.filingTree.loadDatas(convertedData);
  174. SpreadJsObj.loadSheetData(this.sheet, SpreadJsObj.DataType.Tree, this.filingTree);
  175. this.resizeSpreadColumns();
  176. // 查询助手指定的目录优先于本地缓存。
  177. const queryFilingId = new URLSearchParams(window.location.search).get('filing_id');
  178. const curFilingId = queryFilingId || getLocalCache(this.curFilingKey);
  179. if (curFilingId) {
  180. const curNode = this.filingTree.nodes.find(x => String(x.id) === String(curFilingId));
  181. if (curNode) {
  182. this.setCurFiling(curNode);
  183. SpreadJsObj.locateTreeNode(this.sheet, curNode.tree_id, true);
  184. }
  185. }
  186. this.refreshFileCountHint();
  187. }
  188. /**
  189. * 根据目录区域宽度调整树表列宽,拖动分隔条时保持 SpreadJS 填满容器。
  190. */
  191. resizeSpreadColumns() {
  192. const containerWidth = Math.floor($('#filing-spread').innerWidth() || 0);
  193. if (containerWidth <= 0) return;
  194. const rowHeaderWidth = 25;
  195. const fileCountWidth = 70;
  196. const scrollbarWidth = 18;
  197. const borderWidth = 2;
  198. const nameWidth = Math.max(
  199. containerWidth - rowHeaderWidth - fileCountWidth - scrollbarWidth - borderWidth,
  200. 60
  201. );
  202. this.sheet.setColumnWidth(0, nameWidth);
  203. this.sheet.setColumnWidth(1, fileCountWidth);
  204. this.spread.refresh();
  205. }
  206. /**
  207. * 绑定事件
  208. */
  209. _bindEvents() {
  210. const self = this;
  211. // 选中行变化事件
  212. this.spread.bind(spreadNS.Events.SelectionChanged, async function(e, info) {
  213. if (self._isRenaming || self._reloadingFiling) return;
  214. const node = SpreadJsObj.getSelectObject(self.sheet);
  215. if (node && (!self.curFiling || self.curFiling.id !== node.id)) {
  216. await self.setCurFiling(node);
  217. }
  218. });
  219. // 显示层级按钮点击
  220. $('[name=showLevel]').click(function() {
  221. const tag = $(this).attr('tag');
  222. self._showLevel(tag);
  223. });
  224. // 添加同级
  225. $('#add-slibing').click(function() {
  226. if (!self.curFiling) return;
  227. if (self.curFiling.is_fixed) {
  228. toastr.error('固定分类不可添加同级');
  229. return;
  230. }
  231. self.addSiblingFiling(self.curFiling);
  232. });
  233. // 添加子级
  234. $('#add-child').click(function() {
  235. if (!self.curFiling) return;
  236. const children = self.filingTree.getChildren(self.curFiling);
  237. const fixedChild = children.find(x => x.is_fixed);
  238. if (fixedChild) {
  239. toastr.error('该分类下存在固定分类,不可添加子级');
  240. return;
  241. }
  242. if (self.curFiling.file_count > 0) {
  243. toastr.error('该分类下已导入文件,不可添加子级');
  244. return;
  245. }
  246. self.addChildFiling(self.curFiling);
  247. });
  248. // 文件排序
  249. $('[name=file-sort]').click(function() {
  250. const tag = $(this).attr('tag');
  251. self.changeFileOrder(tag);
  252. });
  253. // 分页
  254. $('.page-select[content=pre]').click(function() {
  255. self.prePage();
  256. });
  257. $('.page-select[content=next]').click(function() {
  258. self.nextPage();
  259. });
  260. this._bindFilingNameEditing();
  261. // 分类右键菜单
  262. $.contextMenu({
  263. selector: '#filing-spread',
  264. build: function ($trigger, e) {
  265. const hit = SpreadJsObj.getHitTest($trigger, e, self.sheet);
  266. if (!hit || hit.row < 0 || hit.row >= self.filingTree.nodes.length) return false;
  267. const target = SpreadJsObj.safeRightClickSelection($trigger, e, self.spread);
  268. return target.hitTestType === spreadNS.SheetArea.viewport || target.hitTestType === spreadNS.SheetArea.rowHeader;
  269. },
  270. items: {
  271. 'paste-name': {
  272. name: '粘贴名称',
  273. icon: 'fa-paste',
  274. callback: function() { self._showNamePastePreview(); },
  275. disabled: function() { return !self._referenceNameClipboard || self._namePasteSaving || self._nameSaving; },
  276. },
  277. 'delete': {
  278. name: '删除',
  279. icon: 'fa-trash-o',
  280. callback: function () {
  281. self._showDeletePreview();
  282. },
  283. disabled: function () {
  284. const nodes = self._getSelectedFilings();
  285. return self._directorySaving || self._nameSaving || projectFileLocked || !nodes.length || nodes.some(node => {
  286. const permission = self._getFilingPermission(node.id);
  287. return Number(node.is_fixed) || !permission.can_view || !permission.can_edit_dir;
  288. });
  289. },
  290. },
  291. },
  292. });
  293. }
  294. /**
  295. * 设置当前选中的分类
  296. */
  297. async setCurFiling(node) {
  298. this.curFiling = node;
  299. this._applyCurrentPermission(node);
  300. this.curPage = 1;
  301. this.refreshPages();
  302. const permission = this._getFilingPermission(node.id);
  303. if (!permission.can_view) {
  304. $('#file-view').hide();
  305. setLocalCache(this.curFilingKey, node.id);
  306. return;
  307. }
  308. // 判断是否为叶子节点:无子节点或有文件都视为可展示文件的节点
  309. const hasChildren = this.filingTree.getChildren(node).length > 0;
  310. const isLeafNode = !hasChildren || node.file_count > 0;
  311. if (isLeafNode) {
  312. $('#file-view').show();
  313. this.refreshUploadTips(node);
  314. await this.loadFiles(node, 1);
  315. this.refreshFilesTable();
  316. } else {
  317. $('#file-view').hide();
  318. }
  319. setLocalCache(this.curFilingKey, node.id);
  320. }
  321. /**
  322. * 刷新当前目录的上报说明
  323. */
  324. refreshUploadTips(node) {
  325. const uploadTips = node ? String(node.upload_tips || '').trim() : '';
  326. $('#filing-upload-tips')
  327. .text(uploadTips || '暂无上报说明')
  328. .toggleClass('text-muted', !uploadTips);
  329. }
  330. _getFilingPermission(filingId) {
  331. return filingPermissionMap[String(filingId)] || {
  332. can_view: 0,
  333. can_upload: 0,
  334. can_edit_file: 0,
  335. can_edit_dir: 0,
  336. can_lock_file: 0,
  337. };
  338. }
  339. _applyCurrentPermission(node) {
  340. const permission = this._getFilingPermission(node.id);
  341. canUpload = Boolean(permission.can_view) && !projectFileLocked && Boolean(permission.can_upload);
  342. canEdit = Boolean(permission.can_view) && !projectFileLocked && Boolean(permission.can_edit_file);
  343. canFiling = Boolean(permission.can_view) && !projectFileLocked && Boolean(permission.can_edit_dir);
  344. canLockFile = Boolean(permission.can_view) && !projectFileLocked && Boolean(permission.can_lock_file);
  345. $('.js-can-upload').toggle(canUpload);
  346. $('.js-can-edit-dir').toggle(canFiling);
  347. }
  348. canUploadFiling(filingId) {
  349. const permission = this._getFilingPermission(filingId);
  350. return Boolean(permission.can_view) && !projectFileLocked && Boolean(permission.can_upload);
  351. }
  352. canEditFiling(filingId) {
  353. const permission = this._getFilingPermission(filingId);
  354. return Boolean(permission.can_view) && !projectFileLocked && Boolean(permission.can_edit_file);
  355. }
  356. isFileLocked(file) {
  357. return Boolean(file) && Number(file.is_locked) === 1;
  358. }
  359. /**
  360. * 刷新文件数量提示
  361. */
  362. refreshFileCountHint() {
  363. const sum = _.sum(this.filingTree.nodes.filter(x => x.level === 1).map(x => x.total_file_count));
  364. $('#file-count').html(`文件总数:${sum}`);
  365. }
  366. /**
  367. * 刷新文件排序按钮
  368. */
  369. refreshFileOrderButton() {
  370. const orderBy = this.fileOrder.split('|');
  371. const field = orderBy[0], sort = orderBy[1];
  372. const buttons = $('[name=file-sort]');
  373. for (const b of buttons) {
  374. const curField = b.getAttribute('field');
  375. if (curField === field) {
  376. $(b).html(`<i class="fa fa-sort-amount-${sort}" aria-hidden="true"></i>`).attr('tag', `${curField}|${sort === 'asc' ? 'desc' : 'asc'}`);
  377. } else {
  378. $(b).html('<i class="fa fa-sort" aria-hidden="true">').attr('tag', `${curField}|desc`);
  379. }
  380. }
  381. }
  382. /**
  383. * 刷新分页
  384. */
  385. refreshPages() {
  386. if (!this.curFiling) return;
  387. this.curTotalPage = Math.ceil(this.curFiling.file_count / this.pageCount);
  388. this.curPage = Math.min(this.curTotalPage, Math.max(this.curPage, 1));
  389. $('#curPage').html(this.curPage);
  390. $('#curTotalPage').html(this.curTotalPage);
  391. if (this.curTotalPage > 1) {
  392. $('#showPage').show();
  393. } else {
  394. $('#showPage').hide();
  395. }
  396. }
  397. /**
  398. * 加载文件列表
  399. */
  400. async loadFiles(node, page) {
  401. if (!node.files) node.files = [];
  402. if (!node.file_count) return;
  403. const needFiles = Math.min(page * this.pageCount, node.file_count);
  404. if (node.files.length >= needFiles) return;
  405. const files = await postDataAsync('file/load', {
  406. filing_id: node.id,
  407. page,
  408. count: this.pageCount,
  409. order: this.fileOrder
  410. });
  411. files.forEach(x => {
  412. const file = node.files.find(f => f.id === x.id);
  413. if (file) {
  414. Object.assign(file, x);
  415. } else {
  416. node.files.push(x);
  417. }
  418. });
  419. }
  420. /**
  421. * 刷新文件表格
  422. */
  423. refreshFilesTable() {
  424. const html = [];
  425. const files = this.curFiling.files;
  426. if (!files || files.length === 0) {
  427. $('#file-list').html('');
  428. return;
  429. }
  430. const startIndex = (this.curPage - 1) * this.pageCount;
  431. const endIndex = this.curPage * this.pageCount;
  432. for (let i = startIndex; i < endIndex && i < files.length; i++) {
  433. const file = files[i];
  434. html.push(this._getFileHtml(file));
  435. }
  436. $('#file-list').html(html.join(''));
  437. }
  438. /**
  439. * 生成文件HTML
  440. */
  441. _getFileHtml(file) {
  442. const isLocked = this.isFileLocked(file);
  443. const canOperateFile = !isLocked && canEdit;
  444. const moveHtml = canOperateFile ?
  445. `<a href="javascript: void(0);" class="dropdown-item text-primary" name="move-file" fid="${file.id}"><i class="fa fa-exchange fa-fw"></i> 移动</a>` : '';
  446. const editHtml = canOperateFile ?
  447. `<a href="javascript: void(0);" class="dropdown-item text-primary" name="edit-file" fid="${file.id}"><i class="fa fa-pencil fa-fw"></i> 重命名</a>` : '';
  448. const viewHtml = file.viewpath ?
  449. `<a href="${file.viewpath}" class="dropdown-item text-primary" target="_blank"><i class="fa fa-eye fa-fw"></i> 预览</a>` : '';
  450. const downHtml =
  451. `<a href="javascript: void(0);" onclick="AliOss.downloadFile('${file.filepath}', '${file.filename + file.fileext}')" class="dropdown-item text-primary"><i class="fa fa-download fa-fw"></i> 下载</a>`;
  452. const delHtml = canOperateFile ?
  453. `<div class="dropdown-divider"></div><a href="javascript: void(0);" class="dropdown-item text-danger" name="del-file" fid="${file.id}"><i class="fa fa-trash-o fa-fw"></i> 删除</a>` : '';
  454. const lockId = `file-lock-${file.id}`;
  455. const lockHtml = canLockFile ?
  456. `<div class="custom-control custom-switch d-inline-block">
  457. <input type="checkbox" class="custom-control-input" name="file-lock" id="${lockId}" fid="${file.id}" ${isLocked ? 'checked' : ''}>
  458. <label class="custom-control-label" for="${lockId}"></label>
  459. </div>` : (isLocked ? '<i class="fa fa-lock text-danger" title="文件已锁定"></i>' : '');
  460. const html = [];
  461. html.push(`<tr fid="${file.id}">`);
  462. html.push(`<td class="text-center"><input type="checkbox" name="bd-check" fid="${file.id}"></td>`);
  463. html.push(`<td fid="${file.id}"><div class="d-flex justify-content-between align-items-center table-file"><div name="filename" fid="${file.id}">${file.filename}${file.fileext}</div></div></td>`);
  464. html.push(`<td class="text-center">${lockHtml}</td>`);
  465. html.push(`<td class="text-center">${file.user_name}</td>`);
  466. html.push(`<td class="text-center">${moment(file.create_time).format('YYYY-MM-DD HH:mm:ss')}</td>`);
  467. html.push(`<td class="text-center">${file.fileext_str}</td>`);
  468. html.push(`<td class="text-center">
  469. <div class="dropdown">
  470. <a href="javascript: void(0);" class="dropdown-toggle text-primary" data-toggle="dropdown"><i class="fa fa-bars"></i></a>
  471. <div class="dropdown-menu">
  472. ${editHtml}${viewHtml}${moveHtml}${downHtml}${delHtml}
  473. </div>
  474. </div>
  475. </td>`);
  476. html.push('</tr>');
  477. return html.join('');
  478. }
  479. /**
  480. * 生成文件名显示HTML
  481. */
  482. _getFileNameHtml(file) {
  483. return `<div class="d-flex justify-content-between align-items-center table-file"><div name="filename" fid="${file.id}">${file.filename}${file.fileext}</div></div>`;
  484. }
  485. /**
  486. * 添加同级分类
  487. */
  488. addSiblingFiling(node) {
  489. const treePid = node.tree_pid === -1 ? '-1' : this._findOriginalId(node.tree_pid);
  490. this._addFiling({ tree_pid: treePid, tree_pre_id: node.id, name: '' });
  491. }
  492. /**
  493. * 添加子级分类
  494. */
  495. addChildFiling(node) {
  496. this._addFiling({ tree_pid: node.id, name: '' });
  497. }
  498. /**
  499. * 重新加载归档树
  500. */
  501. _reloadFilingTree(focusId) {
  502. const expanded = new Map(this.filingTree.nodes.map(node => [node.id, node.expanded]));
  503. const currentId = focusId || (this.curFiling && this.curFiling.id);
  504. const topRow = this.sheet.getViewportTopRow(1);
  505. const topNode = this.filingTree.nodes[topRow];
  506. this._reloadingFiling = true;
  507. let currentNode;
  508. try {
  509. this.filingTree.loadDatas(this._convertData(filing));
  510. this.filingTree.nodes.forEach(node => {
  511. if (expanded.has(node.id)) node.expanded = expanded.get(node.id);
  512. });
  513. currentNode = this.filingTree.nodes.find(node => node.id === currentId);
  514. if (focusId && currentNode) {
  515. let parent = this.filingTree.getParent(currentNode);
  516. while (parent) {
  517. parent.expanded = true;
  518. parent = this.filingTree.getParent(parent);
  519. }
  520. }
  521. this.filingTree.refreshNodeVisible();
  522. SpreadJsObj.loadSheetData(this.sheet, SpreadJsObj.DataType.Tree, this.filingTree);
  523. this.resizeSpreadColumns();
  524. const highlightedRows = this.sheet.getSelections().map(selection => selection.row)
  525. .filter(row => row >= 0 && row < this.sheet.getRowCount());
  526. this.sheet.clearSelection();
  527. if (currentNode) {
  528. const selectedRow = this.filingTree.nodes.indexOf(currentNode);
  529. this.sheet.setSelection(selectedRow, 0, 1, 1);
  530. // 程序定位选区后,单独刷新组件绘制的整行选中背景。
  531. SpreadJsObj.reloadRowsBackColor(this.sheet, [...new Set(highlightedRows.concat(selectedRow))]);
  532. } else {
  533. highlightedRows.forEach(row => {
  534. this.sheet.getRange(row, 0, 1, this.sheet.getColumnCount()).backColor(this.sheet.getDefaultStyle().backColor);
  535. });
  536. }
  537. const targetRow = focusId && currentNode ? this.filingTree.nodes.indexOf(currentNode)
  538. : this.filingTree.nodes.findIndex(node => topNode && node.id === topNode.id);
  539. if (targetRow >= 0) this.sheet.showRow(targetRow, focusId ? spreadNS.VerticalPosition.center : spreadNS.VerticalPosition.top);
  540. this.refreshFileCountHint();
  541. } finally {
  542. this._reloadingFiling = false;
  543. }
  544. this.curFiling = null;
  545. if (currentNode) {
  546. this.setCurFiling(currentNode).catch(() => { $('#file-view').hide(); });
  547. if (focusId) this.spread.focus();
  548. } else {
  549. canFiling = false;
  550. $('.js-can-edit-dir').hide();
  551. $('#file-view').hide();
  552. }
  553. }
  554. /**
  555. * 删除分类
  556. */
  557. delFiling(nodes, callback) {
  558. if (this._directorySaving || this._nameSaving || !nodes.length) return;
  559. const self = this;
  560. const modal = $('#filesjs-del-filing');
  561. this._directorySaving = true;
  562. $('button', modal).prop('disabled', true);
  563. $('#filesjs-del-filing-error').text('');
  564. postData('filing/del', { ids: nodes.map(node => node.id) }, function(result) {
  565. // 更新 filing 数组:移除已删除的节点
  566. if (result && result.delete) {
  567. for (const delId of result.delete) {
  568. const idx = filing.findIndex(x => x.id === delId);
  569. if (idx >= 0) filing.splice(idx, 1);
  570. delete filingPermissionMap[delId];
  571. }
  572. }
  573. // 更新兄弟节点的 tree_order
  574. if (result && result.update) {
  575. for (const u of result.update) {
  576. const srcNode = filing.find(x => x.id === u.id);
  577. if (srcNode) srcNode.tree_order = u.tree_order;
  578. }
  579. }
  580. // 重新加载树
  581. self._reloadFilingTree();
  582. self._directorySaving = false;
  583. $('button', modal).prop('disabled', false);
  584. if (callback) callback();
  585. toastr.success(`已删除 ${result.delete.length} 个分类`);
  586. }, function(error) {
  587. self._directorySaving = false;
  588. $('button', modal).prop('disabled', false);
  589. $('#filesjs-del-filing-error').text(error || '删除失败,请重试');
  590. });
  591. }
  592. /**
  593. * 重命名分类
  594. */
  595. renameFiling(node, newName) {
  596. this._nameSaving = true;
  597. const finishSave = () => {
  598. this._nameSaving = false;
  599. const row = this.filingTree.nodes.findIndex(item => item.id === node.id);
  600. if (row >= 0) SpreadJsObj.reLoadRowsData(this.sheet, [row]);
  601. };
  602. postData('filing/save', { id: node.id, name: newName }, () => {
  603. const source = filing.find(item => item.id === node.id);
  604. if (source) source.name = newName;
  605. const currentNode = this.filingTree.nodes.find(item => item.id === node.id);
  606. if (currentNode) currentNode.name = newName;
  607. finishSave();
  608. }, finishSave, false);
  609. }
  610. /**
  611. * 移动分类
  612. */
  613. moveFiling(node, newTreePid, newOrder, callback) {
  614. const self = this;
  615. const srcNode = filing.find(x => x.id === node.id || x.id == node.id);
  616. if (!srcNode) return;
  617. if (String(srcNode.tree_pid) === String(newTreePid) && Number(srcNode.tree_order) === newOrder) {
  618. if (callback) callback();
  619. return;
  620. }
  621. postData('filing/move', { id: node.id, tree_pid: newTreePid, tree_order: newOrder }, function(result) {
  622. srcNode.tree_pid = newTreePid;
  623. srcNode.tree_order = newOrder;
  624. self._reloadFilingTree();
  625. if (callback) callback();
  626. });
  627. }
  628. /**
  629. * 查找原始ID
  630. */
  631. _findOriginalId(treeId) {
  632. const node = this.filingTree.nodes.find(x => x.tree_id === treeId);
  633. return node ? node.id : null;
  634. }
  635. /**
  636. * 改变文件排序
  637. */
  638. changeFileOrder(fileOrder) {
  639. this.fileOrder = fileOrder;
  640. setLocalCache(this.fileOrderKey, fileOrder);
  641. this.refreshFileOrderButton();
  642. if (this.curFiling) {
  643. this.curFiling.files = [];
  644. this.setCurFiling(this.curFiling);
  645. }
  646. }
  647. /**
  648. * 上一页
  649. */
  650. prePage() {
  651. if (this.curPage === 1) return;
  652. this.curPage = this.curPage - 1;
  653. this.refreshPages();
  654. this.refreshFilesTable();
  655. }
  656. /**
  657. * 下一页
  658. */
  659. async nextPage() {
  660. if (this.curPage === this.curTotalPage) return;
  661. await this.loadFiles(this.curFiling, this.curPage + 1);
  662. this.curPage = this.curPage + 1;
  663. this.refreshPages();
  664. this.refreshFilesTable();
  665. }
  666. /**
  667. * 按层级显示
  668. */
  669. _showLevel(level) {
  670. if (level === 'last') {
  671. this.filingTree.expandByCustom(() => { return true; });
  672. } else {
  673. this.filingTree.expandByLevel(parseInt(level));
  674. }
  675. SpreadJsObj.refreshTreeRowVisible(this.sheet);
  676. }
  677. /**
  678. * 批量下载文件
  679. */
  680. batchDownloadFiles() {
  681. const self = this;
  682. const checkedFiles = $('input[name=bd-check]:checked');
  683. if (checkedFiles.length === 0) return;
  684. const files = [];
  685. checkedFiles.each(function() {
  686. const fid = $(this).attr('fid');
  687. const file = self.curFiling.files.find(f => f.id === fid);
  688. file && files.push(file);
  689. });
  690. if (files.length === 0) return;
  691. $('#batch-download').attr('disabled', 'true');
  692. AliOss.zipFiles(files, self.curFiling.name + '.zip', (fails) => {
  693. $('#batch-download').removeAttr('disabled');
  694. if (fails.length === 0) {
  695. toastr.success('下载成功');
  696. } else {
  697. toastr.warning(`下载成功(${fails.length}个文件下载失败)`);
  698. }
  699. }, () => {
  700. $('#batch-download').removeAttr('disabled');
  701. toastr.error('批量下载失败');
  702. });
  703. }
  704. /**
  705. * 根据id查找归档节点
  706. */
  707. findFiling(id) {
  708. return this.filingTree.nodes.find(x => x.id === id || x.id == id);
  709. }
  710. /**
  711. * 获取当前归档完整路径
  712. */
  713. getCurFilingFullPath() {
  714. let cur = this.curFiling;
  715. const result = [];
  716. while (cur) {
  717. result.unshift(cur.name);
  718. cur = this.filingTree.getParent(cur);
  719. }
  720. return result.join('/');
  721. }
  722. /**
  723. * 生成行内编辑HTML
  724. */
  725. _getEditFileNameHtml(file) {
  726. const inputHtml = `<input type="text" class="form-control form-control-sm form-control-width" maxlength="100" value="${file.filename + file.fileext}" fid="${file.id}">`;
  727. const btnHtml = `<div class="btn-group-table" style="display: none;"><a href="javascript: void(0)" class="mr-1" name="edit-file-ok"><i class="fa fa-check fa-fw"></i></a><a href="javascript: void(0)" class="mr-1" name="edit-file-cancel"><i class="fa fa-remove fa-fw"></i></a></div>`;
  728. return `<div class="d-flex justify-content-between align-items-center table-file"><div>${inputHtml}</div>${btnHtml}</div>`;
  729. }
  730. /**
  731. * 重命名文件
  732. */
  733. renameFile(file, filename) {
  734. const self = this;
  735. const td = $(`td[fid=${file.id}]`);
  736. postData('file/save', { id: file.id, filename }, function(data) {
  737. const relaFiling = self.findFiling(file.filing_id);
  738. const relaFile = relaFiling.files.find(x => x.id === file.id);
  739. if (relaFile) {
  740. relaFile.filename = data.filename;
  741. relaFile.fileext = data.fileext;
  742. }
  743. file.filename = data.filename;
  744. file.fileext = data.fileext;
  745. td.html(self._getFileNameHtml(file));
  746. if (fileSearch) fileSearch.renameSearchFile(file.id, data);
  747. }, function() {
  748. td.html(self._getFileNameHtml(file));
  749. if (fileSearch) fileSearch.renameSearchFile(file.id);
  750. });
  751. }
  752. /**
  753. * 删除文件
  754. */
  755. delFiles(files, callback, hint) {
  756. const self = this;
  757. const del = function() {
  758. postData('file/del', { del: files }, async function(data) {
  759. const relaFiling = data.filing.id === self.curFiling.id
  760. ? self.curFiling : self.findFiling(data.filing.id);
  761. if (relaFiling && relaFiling.files) {
  762. for (const id of data.del) {
  763. const fIndex = relaFiling.files.findIndex(x => x.id === id);
  764. if (fIndex >= 0) relaFiling.files.splice(fIndex, 1);
  765. if (fileSearch) fileSearch.removeSearchFile(id);
  766. }
  767. }
  768. self.updateFilingFileCount(relaFiling, data.filing.file_count);
  769. if (data.filing.id === self.curFiling.id) {
  770. await self.loadFiles(self.curFiling, self.curPage);
  771. self.refreshPages();
  772. self.refreshFilesTable();
  773. }
  774. self.refreshFileCountHint();
  775. if (callback) callback();
  776. });
  777. };
  778. if (hint) {
  779. deleteAfterHint(del, hint);
  780. } else {
  781. del();
  782. }
  783. }
  784. /**
  785. * 更新归档文件计数
  786. */
  787. updateFilingFileCount(node, count) {
  788. if (!node) return;
  789. const differ = count - (node.file_count || 0);
  790. node.file_count = count;
  791. node.total_file_count = count;
  792. // 分类增删后会从 filing 重建树,同步原始数据以保留最新文件数。
  793. const source = filing.find(item => item.id === node.id);
  794. if (source) source.file_count = count;
  795. // 更新 SpreadJS 显示
  796. const rowIndex = this.filingTree.nodes.indexOf(node);
  797. if (rowIndex >= 0) {
  798. SpreadJsObj.reLoadRowsData(this.sheet, [rowIndex]);
  799. }
  800. // 递归更新父节点
  801. let parent = this.filingTree.getParent(node);
  802. while (parent) {
  803. parent.total_file_count = parent.total_file_count ? parent.total_file_count + differ : differ;
  804. const pIndex = this.filingTree.nodes.indexOf(parent);
  805. if (pIndex >= 0) {
  806. SpreadJsObj.reLoadRowsData(this.sheet, [pIndex]);
  807. }
  808. parent = this.filingTree.getParent(parent);
  809. }
  810. }
  811. /**
  812. * 移动文件
  813. */
  814. moveFile(file, filingId, callback) {
  815. if (file.filing_id === filingId) {
  816. callback();
  817. return;
  818. }
  819. const self = this;
  820. const orgFilingId = file.filing_id;
  821. postData('file/move', { id: file.id, filingId: filingId }, function(data) {
  822. const orgFiling = self.findFiling(orgFilingId);
  823. if (orgFiling) {
  824. self.updateFilingFileCount(orgFiling, orgFiling.file_count - 1);
  825. const fIndex = orgFiling.files.findIndex(x => x.id === file.id);
  826. if (fIndex >= 0) orgFiling.files.splice(fIndex, 1);
  827. }
  828. const targetFiling = self.findFiling(filingId);
  829. if (targetFiling) {
  830. self.updateFilingFileCount(targetFiling, targetFiling.file_count + 1);
  831. delete targetFiling.files;
  832. }
  833. Object.assign(file, data.file);
  834. if (fileSearch) fileSearch.refreshSearchFile(data.file);
  835. callback();
  836. });
  837. }
  838. setFileLocked(file, isLocked, errorCallback) {
  839. const self = this;
  840. postData('file/lock', { id: file.id, is_locked: isLocked ? 1 : 0 }, function(data) {
  841. file.is_locked = Number(data.is_locked) === 1 ? 1 : 0;
  842. if (fileSearch) fileSearch.refreshSearchFile(data);
  843. self.refreshFilesTable();
  844. }, errorCallback);
  845. }
  846. /**
  847. * 引用文件
  848. */
  849. relaFiles(files, callback) {
  850. const self = this;
  851. postData('file/rela', { filing_id: this.curFiling.id, files: files }, function(data) {
  852. self.updateFilingFileCount(self.curFiling, data.filing.file_count);
  853. if (!self.curFiling.files) self.curFiling.files = [];
  854. self.curFiling.files.unshift(...data.files);
  855. self.refreshPages();
  856. self.refreshFilesTable();
  857. self.refreshFileCountHint();
  858. if (callback) callback();
  859. });
  860. }
  861. /**
  862. * 检查同名文件
  863. */
  864. checkFilesExist(files, callback) {
  865. const data = [];
  866. for (const file of files) {
  867. if (file === undefined) return;
  868. data.push(file.name);
  869. }
  870. postData('file/check', { filing_id: this.curFiling.id, files: data }, function(result) {
  871. callback(result);
  872. });
  873. }
  874. /**
  875. * 清除所有文件缓存
  876. */
  877. _clearAllFileCache() {
  878. for (const node of this.filingTree.nodes) {
  879. if (node.is_leaf && node.files) {
  880. node.files.length = 0;
  881. }
  882. }
  883. }
  884. /**
  885. * 获取所有有查看权限的资料目录ID。
  886. */
  887. getAllFilingId() {
  888. return this.filingTree.nodes.filter(node => {
  889. return Boolean(this._getFilingPermission(node.id).can_view);
  890. }).map(node => node.id);
  891. }
  892. /**
  893. * 获取当前节点及其子节点中有查看权限的资料目录ID。
  894. */
  895. getCurFilingId() {
  896. if (!this.curFiling) return [];
  897. const includedTreeIds = new Set([this.curFiling.tree_id]);
  898. const result = [];
  899. this.filingTree.nodes.forEach(node => {
  900. if (node.tree_id !== this.curFiling.tree_id && !includedTreeIds.has(node.tree_pid)) return;
  901. includedTreeIds.add(node.tree_id);
  902. if (this._getFilingPermission(node.id).can_view) result.push(node.id);
  903. });
  904. return result;
  905. }
  906. _initNameClipboard() {
  907. this._referenceNameClipboard = null;
  908. this._namePasteItems = [];
  909. this._namePasteSaving = false;
  910. const container = $('#filing-spread')[0];
  911. // 先于 SpreadJS 的只读单元格处理,保留浏览器原生粘贴事件以读取本次剪贴板。
  912. container.addEventListener('keydown', e => {
  913. if (!(e.ctrlKey || e.metaKey) || e.altKey || e.keyCode !== 86 || this._isRenaming) return;
  914. e.stopImmediatePropagation();
  915. if (!this._referenceNameClipboard || this._namePasteSaving) {
  916. e.preventDefault();
  917. if (!this._namePasteSaving) toastr.warning('请先从右侧参考文件复制名称');
  918. }
  919. }, true);
  920. container.addEventListener('paste', e => {
  921. if (this._isRenaming) return;
  922. e.preventDefault();
  923. e.stopImmediatePropagation();
  924. const text = e.clipboardData ? e.clipboardData.getData('text/plain') : '';
  925. this._pasteReferenceText(text);
  926. }, true);
  927. this.sheet.bind(spreadNS.Events.ClipboardPasting, (e, info) => {
  928. if (this._isRenaming || this.sheet.isEditing()) return;
  929. info.cancel = true;
  930. this._pasteReferenceText(info.pasteData ? info.pasteData.text : '');
  931. });
  932. $('#filing-name-paste-ok').on('click.filesjsNames', () => { this._savePastedNames(); });
  933. $('#filing-name-paste').on('hide.bs.modal.filesjsNames', e => {
  934. if (this._namePasteSaving) e.preventDefault();
  935. }).on('hidden.bs.modal.filesjsNames', () => {
  936. this._namePasteItems = [];
  937. this.spread.focus();
  938. });
  939. }
  940. initReferenceNameClipboard(spread) {
  941. const container = $('#std-reference-spread')[0];
  942. const sheet = spread.getActiveSheet();
  943. container.addEventListener('keydown', e => {
  944. if (!(e.ctrlKey || e.metaKey) || e.altKey || e.keyCode !== 67 || sheet.isEditing()) return;
  945. e.preventDefault();
  946. e.stopImmediatePropagation();
  947. this._copyReferenceNames(spread);
  948. }, true);
  949. container.addEventListener('copy', e => {
  950. if (sheet.isEditing()) return;
  951. e.preventDefault();
  952. e.stopImmediatePropagation();
  953. this._copyReferenceNames(spread, e.clipboardData);
  954. }, true);
  955. $.contextMenu({
  956. selector: '#std-reference-spread',
  957. build: function($trigger, e) {
  958. const hit = SpreadJsObj.getHitTest($trigger, e, sheet);
  959. if (!hit || hit.row < 0 || !sheet.zh_tree || hit.row >= sheet.zh_tree.nodes.length) return false;
  960. if (hit.hitTestType !== spreadNS.SheetArea.viewport && hit.hitTestType !== spreadNS.SheetArea.rowHeader) return false;
  961. SpreadJsObj.safeRightClickSelection($trigger, e, spread);
  962. return true;
  963. },
  964. items: {
  965. 'copy-name': {
  966. name: '复制名称',
  967. icon: 'fa-copy',
  968. callback: () => { this._copyReferenceNames(spread); },
  969. },
  970. },
  971. });
  972. }
  973. _copyReferenceNames(spread, clipboardData) {
  974. const sheet = spread.getActiveSheet();
  975. const tree = sheet.zh_tree;
  976. const rows = new Set();
  977. for (const selection of sheet.getSelections()) {
  978. if (selection.col > 0) continue;
  979. const first = Math.max(0, selection.row);
  980. const last = selection.row < 0 ? sheet.getRowCount() : first + selection.rowCount;
  981. for (let row = first; row < last; row++) {
  982. if (tree && tree.nodes[row] && sheet.getRowVisible(row)) rows.add(row);
  983. }
  984. }
  985. if (!rows.size) {
  986. this._referenceNameClipboard = null;
  987. toastr.warning('请先选中参考文件的名称列');
  988. return;
  989. }
  990. const names = [...rows].sort((a, b) => a - b).map(row => String(tree.nodes[row].name || ''));
  991. const text = names.join('\r\n');
  992. // 保留原始数组,名称内的换行仍属于同一个分类。
  993. this._referenceNameClipboard = { names, text };
  994. SpreadJsObj.Clipboard.setCopyData(text);
  995. if (clipboardData) {
  996. clipboardData.setData('text/plain', text);
  997. } else {
  998. SpreadJsObj.Clipboard.setSysClipboard(text);
  999. }
  1000. spread.focus();
  1001. toastr.success(`已复制 ${names.length} 条名称`);
  1002. }
  1003. _pasteReferenceText(text) {
  1004. const clipboard = this._referenceNameClipboard;
  1005. if (!clipboard || String(text || '').replace(/\r\n?/g, '\n') !== clipboard.text.replace(/\r\n?/g, '\n')) {
  1006. toastr.warning('请重新从右侧参考文件复制名称后粘贴');
  1007. return;
  1008. }
  1009. this._showNamePastePreview();
  1010. }
  1011. _buildNamePasteItems(names, startRow) {
  1012. const targets = [];
  1013. for (let row = startRow; row < this.filingTree.nodes.length && targets.length < names.length; row++) {
  1014. if (this.sheet.getRowVisible(row)) targets.push({ row, node: this.filingTree.nodes[row] });
  1015. }
  1016. return names.map((value, index) => {
  1017. const target = targets[index];
  1018. const node = target && target.node;
  1019. const name = String(value).trim();
  1020. const errors = [];
  1021. if (!node) {
  1022. errors.push('目标行数不足');
  1023. } else {
  1024. const permission = this._getFilingPermission(node.id);
  1025. if (projectFileLocked) errors.push('项目资料已锁定');
  1026. if (Number(node.is_fixed)) errors.push('固定分类不可改名');
  1027. if (!Number(permission.can_view) || !Number(permission.can_edit_dir)) errors.push('无编辑目录权限');
  1028. }
  1029. if (!name) errors.push('名称不能为空');
  1030. if (name.length > 100) errors.push('名称不能超过100个字符');
  1031. return {
  1032. id: node ? node.id : null,
  1033. row: target ? target.row : null,
  1034. old_name: node ? String(node.name || '') : '',
  1035. name,
  1036. errors,
  1037. };
  1038. });
  1039. }
  1040. _showNamePastePreview() {
  1041. if (this._namePasteSaving || this._nameSaving || $('#filing-name-paste').hasClass('show')) return;
  1042. const clipboard = this._referenceNameClipboard;
  1043. if (!clipboard) { toastr.warning('请先从右侧参考文件复制名称'); return; }
  1044. if (this._isRenaming) return;
  1045. const row = this.sheet.getActiveRowIndex();
  1046. if (row < 0 || !this.filingTree.nodes[row] || !this.sheet.getRowVisible(row)) {
  1047. toastr.warning('请选择粘贴的起始分类');
  1048. return;
  1049. }
  1050. if (this.sheet.getActiveColumnIndex() > 0) {
  1051. toastr.warning('请在分类名称列粘贴');
  1052. return;
  1053. }
  1054. this._namePasteItems = this._buildNamePasteItems(clipboard.names, row);
  1055. const tbody = $('#filing-name-paste-rows').empty();
  1056. for (const item of this._namePasteItems) {
  1057. const tr = $('<tr>').toggleClass('table-danger', item.errors.length > 0);
  1058. $('<td>').text(item.row === null ? '—' : item.row + 1).appendTo(tr);
  1059. $('<td>').text(item.old_name).appendTo(tr);
  1060. $('<td>').text(item.name).toggleClass('filesjs-name-paste-new', !item.errors.length && item.name !== item.old_name).appendTo(tr);
  1061. $('<td>').text(item.errors.length ? item.errors.join(';') : (item.name === item.old_name ? '名称相同,不修改' : '可替换')).appendTo(tr);
  1062. tbody.append(tr);
  1063. }
  1064. const invalid = this._namePasteItems.filter(item => item.errors.length).length;
  1065. const changed = this._namePasteItems.filter(item => item.name !== item.old_name).length;
  1066. $('#filing-name-paste-summary').text(`共 ${this._namePasteItems.length} 条,${changed} 条名称有变化,${invalid} 条校验未通过。`);
  1067. $('#filing-name-paste-error').text(invalid ? '请调整复制范围或起始分类后重新粘贴,校验全部通过后才能替换。' : '');
  1068. $('#filing-name-paste-ok').prop('disabled', invalid > 0 || changed === 0);
  1069. $('#filing-name-paste').modal('show');
  1070. }
  1071. _savePastedNames() {
  1072. if (this._namePasteSaving || this._nameSaving || !this._namePasteItems.length || this._namePasteItems.some(item => item.errors.length)) return;
  1073. const items = this._namePasteItems.map(item => ({ id: item.id, old_name: item.old_name, name: item.name }));
  1074. if (!items.some(item => item.name !== item.old_name)) return;
  1075. const modal = $('#filing-name-paste');
  1076. this._namePasteSaving = true;
  1077. $('button', modal).prop('disabled', true);
  1078. $('#filing-name-paste-error').text('');
  1079. postData('filing/names/save', { items }, result => {
  1080. const rows = [];
  1081. for (const update of result.update) {
  1082. const source = filing.find(node => node.id === update.id);
  1083. if (source) source.name = update.name;
  1084. const row = this.filingTree.nodes.findIndex(node => node.id === update.id);
  1085. if (row >= 0) {
  1086. this.filingTree.nodes[row].name = update.name;
  1087. rows.push(row);
  1088. }
  1089. }
  1090. if (rows.length) SpreadJsObj.reLoadRowsData(this.sheet, rows);
  1091. this._namePasteSaving = false;
  1092. $('button', modal).prop('disabled', false);
  1093. modal.modal('hide');
  1094. toastr.success(`已替换 ${result.update.length} 条分类名称`);
  1095. }, error => {
  1096. this._namePasteSaving = false;
  1097. $('button', modal).prop('disabled', false);
  1098. $('#filing-name-paste-error').text(error || '保存失败,请重试;若名称已变化,请刷新页面后重新粘贴。');
  1099. });
  1100. }
  1101. _addFiling(data) {
  1102. if (this._directorySaving || this._nameSaving || !canFiling || this._isRenaming) return;
  1103. this._directorySaving = true;
  1104. postData('filing/add', data, result => {
  1105. for (const update of result.update || []) {
  1106. const source = filing.find(node => node.id === update.id);
  1107. if (source) Object.assign(source, update);
  1108. }
  1109. for (const node of result.create || []) {
  1110. if (!filing.some(source => source.id === node.id)) filing.push(node);
  1111. }
  1112. Object.assign(filingPermissionMap, result.permissionMap || {});
  1113. this._directorySaving = false;
  1114. const created = (result.create || [])[0];
  1115. this._reloadFilingTree(created && created.id);
  1116. }, () => { this._directorySaving = false; });
  1117. }
  1118. _getSelectedFilings() {
  1119. const rows = new Set();
  1120. for (const selection of this.sheet.getSelections()) {
  1121. const first = Math.max(0, selection.row);
  1122. const last = selection.row < 0 ? this.filingTree.nodes.length
  1123. : Math.min(first + selection.rowCount, this.filingTree.nodes.length);
  1124. for (let row = first; row < last; row++) {
  1125. if (this.sheet.getRowVisible(row)) rows.add(row);
  1126. }
  1127. }
  1128. return [...rows].sort((a, b) => a - b).map(row => this.filingTree.nodes[row]);
  1129. }
  1130. _showDeletePreview() {
  1131. if (this._directorySaving || this._nameSaving || this._isRenaming || projectFileLocked) return;
  1132. const nodes = this._getSelectedFilings();
  1133. if (!nodes.length) return;
  1134. const included = new Set(nodes.map(node => node.id));
  1135. // 树节点按父子顺序排列,检查选中的分类以及折叠的子分类。
  1136. const scope = this.filingTree.nodes.filter(node => {
  1137. const parent = this.filingTree.getParent(node);
  1138. if (parent && included.has(parent.id)) included.add(node.id);
  1139. return included.has(node.id);
  1140. });
  1141. if (scope.some(node => Number(node.file_count || 0) !== 0 || Number(node.total_file_count || 0) !== 0)) {
  1142. toastr.error('此分类已存在文件,请先删除所有文件。');
  1143. return;
  1144. }
  1145. if (scope.some(node => Number(node.is_fixed))) {
  1146. toastr.error('固定分类不可删除');
  1147. return;
  1148. }
  1149. if (scope.some(node => {
  1150. const permission = this._getFilingPermission(node.id);
  1151. return !permission.can_view || !permission.can_edit_dir;
  1152. })) {
  1153. toastr.error('部分分类或子分类没有编辑目录权限,无法删除');
  1154. return;
  1155. }
  1156. this.selectRemoveNodes = nodes;
  1157. const list = $('#filesjs-del-filing-list').empty();
  1158. nodes.forEach(node => { $('<li>').text(node.name || '(未命名分类)').appendTo(list); });
  1159. $('#filesjs-del-filing-summary').text(`已选择 ${nodes.length} 个分类,确认删除?`);
  1160. $('#filesjs-del-filing-error').text('');
  1161. $('#filesjs-del-filing').modal('show');
  1162. }
  1163. _canEditFilingName(node) {
  1164. if (!node || projectFileLocked || Number(node.is_fixed)) return false;
  1165. const permission = this._getFilingPermission(node.id);
  1166. return Boolean(Number(permission.can_view) && Number(permission.can_edit_dir));
  1167. }
  1168. _bindFilingNameEditing() {
  1169. this.sheet.bind(spreadNS.Events.EditStarting, (e, info) => {
  1170. const node = this.filingTree.nodes[info.row];
  1171. if (info.col !== 0 || !this._canEditFilingName(node) || this._directorySaving
  1172. || this._nameSaving || this._namePasteSaving || this._reloadingFiling) {
  1173. info.cancel = true;
  1174. return;
  1175. }
  1176. this._isRenaming = true;
  1177. this._nameEdit = { node, row: info.row, oldName: String(node.name || '') };
  1178. });
  1179. this.sheet.bind(spreadNS.Events.EditEnded, (e, info) => { this._finishFilingNameEdit(info); });
  1180. $('#filing-spread')[0].addEventListener('keydown', e => {
  1181. if (e.isComposing || e.keyCode === 229) return;
  1182. // 不允许快捷键绕过单个名称保存,批量替换继续走粘贴确认弹窗。
  1183. if (!this.sheet.isEditing() && (e.keyCode === 46 || e.keyCode === 8 || ((e.ctrlKey || e.metaKey) && e.keyCode === 88))) {
  1184. e.preventDefault();
  1185. e.stopImmediatePropagation();
  1186. } else if ((e.ctrlKey || e.metaKey) && e.keyCode === 13) {
  1187. e.preventDefault();
  1188. e.stopImmediatePropagation();
  1189. if (this.sheet.isEditing()) this.sheet.endEdit();
  1190. }
  1191. }, true);
  1192. }
  1193. _finishFilingNameEdit(info) {
  1194. const editing = this._nameEdit;
  1195. this._nameEdit = null;
  1196. this._isRenaming = false;
  1197. if (!editing || info.col !== 0 || info.row !== editing.row) return;
  1198. // Esc 取消时 editingText 仍是输入内容;读取实际单元格值,避免误保存。
  1199. const value = String(this.sheet.getValue(info.row, 0) || '');
  1200. if (value === editing.oldName) return;
  1201. const newName = value.trim();
  1202. if (!newName || newName === editing.oldName) {
  1203. this.sheet.setValue(info.row, 0, editing.oldName);
  1204. return;
  1205. }
  1206. this.renameFiling(editing.node, newName);
  1207. }
  1208. }
  1209. // 初始化
  1210. const filingSjs = new FilingSjs();
  1211. // 首次进入空资料项目时,由拥有“管理目录”权限的用户选择初始化方式。
  1212. if (needFilingInitialization) {
  1213. const initializationDescription = {
  1214. blank: '从空白目录开始创建资料目录',
  1215. template: '从系统内置模板库中选取',
  1216. project: '从系统中其他有管理目录权限的项目资料中选取',
  1217. };
  1218. const refreshInitializationType = function() {
  1219. const initType = $('[name=filing-initialization-type]:checked').val();
  1220. $('#filing-initialization-description').text(initializationDescription[initType] || '');
  1221. $('#filing-initialization-template-wrap').toggle(initType === 'template');
  1222. $('#filing-initialization-project-wrap').toggle(initType === 'project');
  1223. };
  1224. $('[name=filing-initialization-type]').on('change', refreshInitializationType);
  1225. $('#filing-initialization-ok').on('click', function() {
  1226. const button = $(this);
  1227. const initType = $('[name=filing-initialization-type]:checked').val();
  1228. const data = { init_type: initType };
  1229. if (initType === 'template') {
  1230. data.template_id = $('#filing-initialization-template').val();
  1231. if (!data.template_id) {
  1232. toastr.warning('请选择系统模板库');
  1233. return;
  1234. }
  1235. }
  1236. if (initType === 'project') {
  1237. data.source_spid = $('#filing-initialization-project').val();
  1238. if (!data.source_spid) {
  1239. toastr.warning('请选择来源项目');
  1240. return;
  1241. }
  1242. }
  1243. button.prop('disabled', true);
  1244. postData('filing/init', data, function() {
  1245. window.location.reload();
  1246. }, function() {
  1247. button.prop('disabled', false);
  1248. });
  1249. });
  1250. refreshInitializationType();
  1251. $('#filing-initialization-modal').appendTo(document.body).modal('show');
  1252. }
  1253. /**
  1254. * MoveFileObj - 移动文件弹窗(SpreadJS树选择目标目录)
  1255. */
  1256. class MoveFileObj {
  1257. constructor() {
  1258. const self = this;
  1259. this.spread = null;
  1260. this.sheet = null;
  1261. this.file = null;
  1262. this.selectedNode = null;
  1263. this.adjustingSelection = false;
  1264. this.modal = $('#move-file2');
  1265. this.okButton = $('#move-file2-ok');
  1266. this.filingTree = createNewPathTree('base', {
  1267. id: 'tree_id', pid: 'tree_pid', order: 'order', level: 'level',
  1268. isLeaf: 'is_leaf', fullPath: 'full_path', rootId: -1,
  1269. });
  1270. this.spreadSetting = {
  1271. cols: [
  1272. {
  1273. title: '分类名称', colSpan: '1', rowSpan: '1', field: 'name', hAlign: 0, width: 280, formatter: '@', cellType: 'tree',
  1274. getValue: function(data) {
  1275. return data.name;
  1276. },
  1277. getIcon: function(data) {
  1278. const fallback = data.is_leaf ? 'file' : 'folder';
  1279. return {
  1280. image: data.is_leaf
  1281. ? filingSjs.treeIcons.leaf
  1282. : (data.expanded ? filingSjs.treeIcons.open : filingSjs.treeIcons.closed),
  1283. fallback,
  1284. size: 18,
  1285. };
  1286. },
  1287. foreColor: function(data) {
  1288. return data.is_leaf && !data.move_selectable ? '#999999' : '#333333';
  1289. },
  1290. },
  1291. ],
  1292. emptyRows: 0, headRows: 1, headRowHeight: [32], defaultRowHeight: 21,
  1293. headColWidth: [25], headerFont: '12px 微软雅黑', font: '12px 微软雅黑',
  1294. readOnly: true,
  1295. };
  1296. this.okButton.off('click.filesjsMove').on('click.filesjsMove', function() {
  1297. const node = self.selectedNode;
  1298. if (!node || !self._isSelectableNode(node)) {
  1299. toastr.warning('请选择目标分类');
  1300. return;
  1301. }
  1302. filingSjs.moveFile(self.file, node.id, function() {
  1303. self.modal.modal('hide');
  1304. filingSjs.refreshPages();
  1305. filingSjs.refreshFilesTable();
  1306. });
  1307. });
  1308. this.modal.off('shown.bs.modal.filesjsMove').on('shown.bs.modal.filesjsMove', function() {
  1309. self.initMoveTree();
  1310. });
  1311. $('a[name=mf2-showLevel]').click(function() {
  1312. const tag = $(this).attr('tag');
  1313. setTimeout(() => {
  1314. showWaitingView();
  1315. self._expandByLevel(tag);
  1316. closeWaitingView();
  1317. }, 100);
  1318. });
  1319. }
  1320. _isSelectableNode(node) {
  1321. return Boolean(node) && Boolean(node.is_leaf) && Boolean(node.move_selectable);
  1322. }
  1323. _setSelectionHint(text, warning) {
  1324. $('#move-file2-selection-hint')
  1325. .text(text)
  1326. .toggleClass('text-warning', Boolean(warning))
  1327. .toggleClass('text-muted', !warning);
  1328. }
  1329. _clearSelection() {
  1330. this.selectedNode = null;
  1331. this.okButton.prop('disabled', true);
  1332. if (!this.sheet || this.adjustingSelection) return;
  1333. this.adjustingSelection = true;
  1334. this.sheet.clearSelection();
  1335. this.adjustingSelection = false;
  1336. }
  1337. _handleSelectionChanged() {
  1338. if (this.adjustingSelection) return;
  1339. const node = SpreadJsObj.getSelectObject(this.sheet);
  1340. if (!node) {
  1341. this._clearSelection();
  1342. return;
  1343. }
  1344. if (!node.is_leaf) {
  1345. this._setSelectionHint('请展开目录并选择最底层节点', false);
  1346. this._clearSelection();
  1347. return;
  1348. }
  1349. if (String(node.id) === String(this.file.filing_id)) {
  1350. this._setSelectionHint('文件已在当前目录,请选择其他最底层节点', true);
  1351. this._clearSelection();
  1352. return;
  1353. }
  1354. if (!filingSjs.canUploadFiling(node.id)) {
  1355. this._setSelectionHint('您没有该目录的上传/引用权限', true);
  1356. this._clearSelection();
  1357. return;
  1358. }
  1359. this.selectedNode = node;
  1360. this.okButton.prop('disabled', false);
  1361. this._setSelectionHint(`已选择:${node.name}`, false);
  1362. }
  1363. _resizeColumn() {
  1364. if (!this.sheet) return;
  1365. const width = Math.floor($('#moveFileSjs').innerWidth() || 0);
  1366. if (width > 0) this.sheet.setColumnWidth(0, Math.max(width - 45, 220));
  1367. }
  1368. _expandByLevel(level) {
  1369. if (!this.sheet) return;
  1370. if (level === 'last') {
  1371. this.filingTree.expandByCustom(node => { return !node.is_leaf; });
  1372. } else {
  1373. const maxLevel = parseInt(level);
  1374. this.filingTree.expandByCustom(node => {
  1375. return !node.is_leaf && node.level < maxLevel;
  1376. });
  1377. }
  1378. SpreadJsObj.loadSheetData(this.sheet, SpreadJsObj.DataType.Tree, this.filingTree);
  1379. this._clearSelection();
  1380. this._setSelectionHint('请选择最底层目标目录', false);
  1381. }
  1382. initMoveTree() {
  1383. if (!this.spread) {
  1384. this.spread = SpreadJsObj.createNewSpread($('#moveFileSjs')[0]);
  1385. this.spread.options.showHorizontalScrollbar = false;
  1386. this.sheet = this.spread.getActiveSheet();
  1387. SpreadJsObj.initSheet(this.sheet, this.spreadSetting);
  1388. this.spread.bind(spreadNS.Events.SelectionChanged, () => {
  1389. this._handleSelectionChanged();
  1390. });
  1391. }
  1392. SpreadJsObj.loadSheetData(this.sheet, SpreadJsObj.DataType.Tree, this.filingTree);
  1393. this._resizeColumn();
  1394. this._clearSelection();
  1395. this.spread.refresh();
  1396. }
  1397. show(file) {
  1398. if (!file) return;
  1399. this.file = file;
  1400. this.selectedNode = null;
  1401. const convertedData = filingSjs._convertData(filing);
  1402. for (const node of convertedData) {
  1403. node.move_selectable = node.is_leaf &&
  1404. String(node.id) !== String(file.filing_id) &&
  1405. filingSjs.canUploadFiling(node.id);
  1406. }
  1407. this.filingTree.loadDatas(convertedData);
  1408. $('#move-file2-description').text(`将“${file.filename + file.fileext}”移动到:`);
  1409. this._setSelectionHint('请选择最底层目标目录', false);
  1410. this.okButton.prop('disabled', true);
  1411. this.modal.modal('show');
  1412. }
  1413. }
  1414. const moveFileObj = new MoveFileObj();
  1415. /**
  1416. * RelaFileLoader - 引用文件(标段树使用SpreadJS替代zTree)
  1417. */
  1418. class RelaFileLoader {
  1419. constructor() {
  1420. const self = this;
  1421. this.curTender = null;
  1422. this.curFiles = [];
  1423. this.tenderFileType = [];
  1424. this.loaded = false;
  1425. this.loadingPromise = null;
  1426. this.spread = null;
  1427. this.sheet = null;
  1428. this.tenderPathTree = createNewPathTree('base', {
  1429. id: 'tmt_id', pid: 'tmt_pid', order: 'sort', level: 'level',
  1430. isLeaf: 'is_leaf', fullPath: 'full_path', rootId: -1,
  1431. });
  1432. this.spreadSetting = {
  1433. cols: [{
  1434. title: '标段列表', colSpan: '1', rowSpan: '1', field: 'name', hAlign: 0, width: 280,
  1435. formatter: '@', cellType: 'tree', showTips: true,
  1436. getValue: function(data) {
  1437. return data.name;
  1438. },
  1439. getIcon: function(data) {
  1440. return data.source_node && data.source_node.tid ? 'file' : 'folder';
  1441. },
  1442. }],
  1443. emptyRows: 0, headRows: 1, headRowHeight: [32], defaultRowHeight: 21,
  1444. headColWidth: [25], headerFont: '12px 微软雅黑', font: '12px 微软雅黑',
  1445. readOnly: true, selectedBackColor: '#e8f4fd',
  1446. };
  1447. $('body').on('click', '[name=rf-check]', function() {
  1448. self.selectFile(this.getAttribute('rfid'), this.checked);
  1449. });
  1450. $('#tf-type').change(function() { self.selectTfType(this.value); });
  1451. $('#tf-sub-type').change(function() { self.selectTfSubType(this.value); });
  1452. $('#tf-stage').change(function() { self.selectTfStage(this.value); });
  1453. $('#tf-select').change(function() { self.selectTfId(this.value); });
  1454. $('#rela-file-ok').off('click.filesjsRela').on('click.filesjsRela', function() {
  1455. const selectFiles = self.getSelectRelaFile();
  1456. filingSjs.relaFiles(selectFiles, function() {
  1457. $('#rela-file').modal('hide');
  1458. });
  1459. });
  1460. }
  1461. _convertTenderData(data) {
  1462. const tenderPathTree = Tender2Tree.convert(data.category, data.tenders, null, null, function(node, source) {
  1463. _.assign(node, source);
  1464. }, data.selfCategoryLevel);
  1465. const sortedData = [...tenderPathTree.nodes].sort((a, b) => Number(a.level) - Number(b.level));
  1466. const result = [];
  1467. for (const node of sortedData) {
  1468. const parent = (node.tmt_pid === -1 || node.tmt_pid === '-1') ? undefined : result.find(x => x.tmt_id === node.tmt_pid);
  1469. if (!parent && node.tmt_pid !== -1 && node.tmt_pid !== '-1') continue;
  1470. const hasChild = tenderPathTree.nodes.some(x => x.tmt_pid === node.tmt_id);
  1471. result.push({
  1472. id: node.tmt_id, tmt_id: node.tmt_id, tmt_pid: parent ? parent.tmt_id : -1,
  1473. sort: node.sort || 0, level: node.level || 0,
  1474. is_leaf: !hasChild, full_path: '', name: node.name, source_node: node,
  1475. });
  1476. }
  1477. for (const node of result) { node._children = []; }
  1478. for (const node of result) {
  1479. if (node.tmt_pid !== -1) {
  1480. const parent = result.find(x => x.tmt_id === node.tmt_pid);
  1481. if (parent) parent._children.push(node);
  1482. }
  1483. }
  1484. const calcCount = (node) => {
  1485. if (node._children && node._children.length > 0) {
  1486. for (const child of node._children) calcCount(child);
  1487. node.total_file_count = node._children.reduce((sum, child) => sum + (child.total_file_count || 0), 0);
  1488. } else { node.total_file_count = 0; }
  1489. };
  1490. for (const root of result.filter(x => x.tmt_pid === -1)) calcCount(root);
  1491. for (const node of result) delete node._children;
  1492. return result;
  1493. }
  1494. async showRelaFile() {
  1495. $('#rela-filing-hint').html(`当前目录:${filingSjs.getCurFilingFullPath()}`);
  1496. this._initSpread();
  1497. if (!this.loaded) {
  1498. $('#rela-file-hint').text('正在加载标段数据...');
  1499. if (!this.loadingPromise) {
  1500. this.loadingPromise = postDataAsync('file/rela/tender', {}).then(data => {
  1501. this._tenderRawData = data;
  1502. const convertedData = this._convertTenderData(data);
  1503. this.tenderPathTree.loadDatas(convertedData);
  1504. SpreadJsObj.loadSheetData(this.sheet, SpreadJsObj.DataType.Tree, this.tenderPathTree);
  1505. this.loaded = true;
  1506. }).finally(() => {
  1507. this.loadingPromise = null;
  1508. });
  1509. }
  1510. await this.loadingPromise;
  1511. }
  1512. this.spread.refresh();
  1513. this.clearFileSelect();
  1514. this.refreshSelectHint();
  1515. const firstTender = this.tenderPathTree.nodes.find(node => node.source_node && node.source_node.tid);
  1516. if (firstTender) {
  1517. await this.setCurTender(firstTender);
  1518. this.sheet.setActiveCell(this.tenderPathTree.nodes.indexOf(firstTender), 0);
  1519. } else {
  1520. this.curTender = null;
  1521. this.curFiles = [];
  1522. $('#cur-tender-hint').text('');
  1523. $('#rf-files').html('<tr><td colspan="3" class="text-center text-muted">暂无可引用标段</td></tr>');
  1524. }
  1525. }
  1526. clearFileSelect() {
  1527. if (!this.tenderPathTree) return;
  1528. const recursiveFun = (nodes) => {
  1529. if (!nodes || nodes.length === 0) return;
  1530. for (const node of nodes) {
  1531. const x = node.source_node;
  1532. if (x.tid) {
  1533. x.selectFiles = [];
  1534. if (x.att) x.att.forEach(la => { la.checked = false; });
  1535. if (x.advance) x.advance.forEach(a => { if (a.files) a.files.forEach(aa => { aa.checked = false; }); });
  1536. if (x.stage) x.stage.forEach(s => { if (s.att) s.att.forEach(sa => { sa.checked = false; }); });
  1537. } else {
  1538. recursiveFun(this.tenderPathTree.getChildren(node));
  1539. }
  1540. }
  1541. };
  1542. const roots = this.tenderPathTree.nodes.filter(n => n.level === 1);
  1543. recursiveFun(roots);
  1544. }
  1545. refreshSelectHint() {
  1546. if (this.curTender && this.curTender.source_node.tid) {
  1547. $('#cur-tender-hint').html(`当前标段,已选${this.curTender.source_node.selectFiles.length}文件`);
  1548. } else {
  1549. $('#cur-tender-hint').html('');
  1550. }
  1551. const nodes = this.tenderPathTree.nodes;
  1552. const selectTenders = nodes.filter(x => x.source_node.tid && x.source_node.selectFiles && x.source_node.selectFiles.length > 0);
  1553. if (selectTenders.length > 0) {
  1554. const count = selectTenders.reduce((rst, x) => rst + x.source_node.selectFiles.length, 0);
  1555. $('#rela-file-hint').html(`已选择${selectTenders.length}个标段,共${count}个文件`);
  1556. } else {
  1557. $('#rela-file-hint').html('未选择标段、文件');
  1558. }
  1559. }
  1560. selectFile(fileId, isSelect) {
  1561. const file = this.curFiles.find(x => x.rf_id == fileId);
  1562. if (file) {
  1563. file.checked = isSelect;
  1564. if (isSelect) {
  1565. this.curTender.source_node.selectFiles.push(file);
  1566. } else {
  1567. const index = this.curTender.source_node.selectFiles.findIndex(x => x.rf_id === file.rf_id);
  1568. this.curTender.source_node.selectFiles.splice(index, 1);
  1569. }
  1570. this.refreshSelectHint();
  1571. }
  1572. }
  1573. refreshTenderFileSelectId() {
  1574. if (['change', 'change_apply', 'change_plan', 'change_project'].indexOf(this.rfType.type) >= 0) {
  1575. const type = this.tenderFileType.find(x => x.value === this.rfType.type);
  1576. const html = type.select.map(x => `<option value="${x.value}">${x.text}</option>`);
  1577. $('#tf-select').html(html.join('')).show();
  1578. this.rfType.selectId = type.select[0].value;
  1579. } else {
  1580. $('#tf-select').html('').hide();
  1581. }
  1582. }
  1583. refreshTenderFileStage() {
  1584. if (this.rfType.sub_type) {
  1585. const type = this.tenderFileType.find(x => x.value === this.rfType.type);
  1586. const subType = type.subType ? type.subType.find(x => x.value === this.rfType.sub_type) : null;
  1587. if (subType) {
  1588. this.rfType.stage = subType.stage[0].value;
  1589. const html = [];
  1590. for (const stage of subType.stage) html.push(`<option value="${stage.value}">${stage.text}</option>`);
  1591. $('#tf-stage').html(html.join('')).show();
  1592. } else { $('#tf-stage').html('').hide(); }
  1593. } else { $('#tf-stage').html('').hide(); }
  1594. }
  1595. refreshTenderFileSubType() {
  1596. const type = this.tenderFileType.find(x => x.value === this.rfType.type);
  1597. if (type && type.subType && type.subType.length > 0) {
  1598. this.rfType.sub_type = type.subType[0].value;
  1599. const html = [];
  1600. for (const tfst of type.subType) html.push(`<option value="${tfst.value}">${tfst.text}</option>`);
  1601. $('#tf-sub-type').html(html.join('')).show();
  1602. } else { $('#tf-sub-type').html('').hide(); }
  1603. }
  1604. refreshTenderFileType() {
  1605. const html = [];
  1606. for (const tft of this.tenderFileType) html.push(`<option value="${tft.value}">${_.escape(tft.text)}</option>`);
  1607. $('#tf-type').html(html.join(''));
  1608. }
  1609. refreshSelects(tender) {
  1610. this.tenderFileType = [];
  1611. if (tender.advance && tender.advance.length > 0) {
  1612. const advanceType = [];
  1613. tender.advance.forEach(x => {
  1614. let at = advanceType.find(y => y.value === x.type + '');
  1615. if (!at) { at = { value: x.type + '', text: x.type_str, stage: [] }; advanceType.push(at); }
  1616. at.stage.push({ value: x.id, text: `第${x.order}期` });
  1617. });
  1618. this.tenderFileType.push({ value: 'advance', text: '预付款', subType: advanceType });
  1619. }
  1620. if (tender.tid) this.tenderFileType.push({ value: 'ledger', text: '台账附件' });
  1621. if (tender.stage && tender.stage.length > 0) {
  1622. const stages = tender.stage.map(x => ({ value: x.id, text: `第${x.order}期` }));
  1623. this.tenderFileType.push({
  1624. value: 'stage', text: '计量期',
  1625. subType: [
  1626. { value: 'att', text: '计量附件', stage: JSON.parse(JSON.stringify(stages)) },
  1627. { value: 'dealPay', text: '合同支付', stage: JSON.parse(JSON.stringify(stages)) },
  1628. { value: 'stageIm', text: '中间计量', stage: JSON.parse(JSON.stringify(stages)) },
  1629. ],
  1630. });
  1631. }
  1632. if (tender.change && tender.change.length > 0) {
  1633. this.tenderFileType.push({ value: 'change', text: '变更令', select: tender.change.map(x => ({ value: x.cid, text: x.code })) });
  1634. }
  1635. if (tender.change_plan && tender.change_plan.length > 0) {
  1636. this.tenderFileType.push({ value: 'change_plan', text: planName, select: tender.change_plan.map(x => ({ value: x.id, text: x.code })) });
  1637. }
  1638. if (tender.change_apply && tender.change_apply.length > 0) {
  1639. this.tenderFileType.push({ value: 'change_apply', text: '变更申请', select: tender.change_apply.map(x => ({ value: x.id, text: x.code })) });
  1640. }
  1641. if (tender.change_project && tender.change_project.length > 0) {
  1642. this.tenderFileType.push({ value: 'change_project', text: '变更立项', select: tender.change_project.map(x => ({ value: x.id, text: x.code })) });
  1643. }
  1644. this.rfType = { type: tender.tid ? this.tenderFileType[0].value : '' };
  1645. this.refreshTenderFileType();
  1646. this.refreshTenderFileSubType();
  1647. this.refreshTenderFileStage();
  1648. this.refreshTenderFileSelectId();
  1649. }
  1650. async selectTfId(id) { this.rfType.selectId = id; await this.loadFiles(); this.refreshFileTable(); }
  1651. async selectTfStage(stage) { this.rfType.stage = stage; await this.loadFiles(); this.refreshFileTable(); }
  1652. async selectTfSubType(sub_type) { this.rfType.sub_type = sub_type; this.refreshTenderFileStage(); await this.loadFiles(); this.refreshFileTable(); }
  1653. async selectTfType(type) { this.rfType.type = type; this.refreshTenderFileSubType(); this.refreshTenderFileStage(); this.refreshTenderFileSelectId(); await this.loadFiles(); this.refreshFileTable(); }
  1654. refreshFileTable() {
  1655. const html = [];
  1656. const typeStr = [];
  1657. $('option:selected', '#rela-file').each((i, x) => { typeStr.push(x.innerText); });
  1658. for (const f of this.curFiles) {
  1659. html.push('<tr>');
  1660. const checked = f.checked ? 'checked' : '';
  1661. html.push(`<td><input type="checkbox" name="rf-check" rfid="${f.rf_id}" ${checked}></td>`);
  1662. html.push(`<td>${f.filename}${f.fileext}</td>`);
  1663. html.push(`<td>${_.escape(typeStr.join(' - '))}</td>`);
  1664. html.push('</tr>');
  1665. }
  1666. $('#rf-files').html(html.join(''));
  1667. }
  1668. initFilesId(files) {
  1669. const tender_id = this.curTender.id;
  1670. const rfType = this.rfType;
  1671. files.forEach((f, i) => {
  1672. f.rf_id = `${tender_id}-${rfType.type}-${rfType.sub_type}-${rfType.stage}-${i}`;
  1673. f.rf_key = { tender_id, ...rfType, id: f.id };
  1674. });
  1675. }
  1676. async _loadRelaFiles(rfType) {
  1677. return await postDataAsync('file/rela/files', { tender_id: this.curTender.source_node.tid, ...rfType });
  1678. }
  1679. async _loadLedgerFile() {
  1680. if (!this.curTender.source_node.att) this.curTender.source_node.att = await this._loadRelaFiles(this.rfType);
  1681. this.curFiles = this.curTender.source_node.att;
  1682. }
  1683. async _loadStageFile() {
  1684. const stage = this.curTender.source_node.stage.find(x => x.id == this.rfType.stage);
  1685. if (!stage) { this.curFiles = []; return; }
  1686. if (!stage[this.rfType.sub_type]) stage[this.rfType.sub_type] = await this._loadRelaFiles(this.rfType);
  1687. this.curFiles = stage[this.rfType.sub_type];
  1688. }
  1689. async _loadAdvanceFile() {
  1690. const advance = this.curTender.source_node.advance.find(x => x.id == this.rfType.stage);
  1691. if (!advance) { this.curFiles = []; return; }
  1692. if (!advance.files) advance.files = await this._loadRelaFiles(this.rfType);
  1693. this.curFiles = advance.files;
  1694. }
  1695. async _loadChangeFile() {
  1696. const change = this.curTender.source_node.change.find(x => x.cid === this.rfType.selectId);
  1697. if (!change) { this.curFiles = []; return; }
  1698. if (!change.files) change.files = await this._loadRelaFiles(this.rfType);
  1699. this.curFiles = change.files;
  1700. }
  1701. async _loadChangePlanFile() {
  1702. const change = this.curTender.source_node.change_plan.find(x => x.id == this.rfType.selectId);
  1703. if (!change) { this.curFiles = []; return; }
  1704. if (!change.files) change.files = await this._loadRelaFiles(this.rfType);
  1705. this.curFiles = change.files;
  1706. }
  1707. async _loadChangeProjectFile() {
  1708. const change = this.curTender.source_node.change_project.find(x => x.id == this.rfType.selectId);
  1709. if (!change) { this.curFiles = []; return; }
  1710. if (!change.files) change.files = await this._loadRelaFiles(this.rfType);
  1711. this.curFiles = change.files;
  1712. }
  1713. async _loadChangeApplyFile() {
  1714. const change = this.curTender.source_node.change_apply.find(x => x.id == this.rfType.selectId);
  1715. if (!change) { this.curFiles = []; return; }
  1716. if (!change.files) change.files = await this._loadRelaFiles(this.rfType);
  1717. this.curFiles = change.files;
  1718. }
  1719. async loadFiles() {
  1720. switch (this.rfType.type) {
  1721. case 'ledger': await this._loadLedgerFile(); break;
  1722. case 'stage': await this._loadStageFile(); break;
  1723. case 'advance': await this._loadAdvanceFile(); break;
  1724. case 'change': await this._loadChangeFile(); break;
  1725. case 'change_plan': await this._loadChangePlanFile(); break;
  1726. case 'change_project': await this._loadChangeProjectFile(); break;
  1727. case 'change_apply': await this._loadChangeApplyFile(); break;
  1728. default: this.curFiles = [];
  1729. }
  1730. this.initFilesId(this.curFiles);
  1731. }
  1732. async setCurTender(node) {
  1733. this.curTender = node;
  1734. this.refreshSelects(node.source_node);
  1735. await this.loadFiles();
  1736. this.refreshSelectHint();
  1737. this.refreshFileTable();
  1738. }
  1739. getSelectRelaFile() {
  1740. const data = [];
  1741. for (const node of this.tenderPathTree.nodes) {
  1742. if (!node.source_node.selectFiles || node.source_node.selectFiles.length === 0) continue;
  1743. node.source_node.selectFiles.forEach(x => {
  1744. data.push({ filename: x.filename, fileext: x.fileext, filepath: x.filepath, filesize: x.filesize, rela_info: x.rf_key });
  1745. });
  1746. }
  1747. return data;
  1748. }
  1749. _initSpread() {
  1750. if (this.spread) return;
  1751. const self = this;
  1752. const container = $('#rela-tender')[0];
  1753. if (!container) throw new Error('引用文件标段列表容器不存在');
  1754. this.spread = SpreadJsObj.createNewSpread(container);
  1755. this.sheet = this.spread.getActiveSheet();
  1756. SpreadJsObj.initSheet(this.sheet, this.spreadSetting);
  1757. this.spread.bind(spreadNS.Events.SelectionChanged, async function(e, info) {
  1758. const node = SpreadJsObj.getSelectObject(self.sheet);
  1759. if (node && (!self.curTender || self.curTender.tmt_id !== node.tmt_id)) {
  1760. await self.setCurTender(node);
  1761. }
  1762. });
  1763. }
  1764. }
  1765. /**
  1766. * FileSearch - 文件搜索类
  1767. */
  1768. class FileSearch {
  1769. constructor() {
  1770. this.searchResult = [];
  1771. this._initSearch();
  1772. }
  1773. getOperateHtml(file) {
  1774. const locateHtml = `<a href="javascript: void(0);" class="mr-1" name="locate-search-file" fid="${file.id}"><i class="fa fa-crosshairs"></i></a>`;
  1775. const canOperateFile = !filingSjs.isFileLocked(file) && filingSjs.canEditFiling(file.filing_id);
  1776. const editHtml = canOperateFile ? `<a href="javascript: void(0);" class="mr-1" name="edit-search-file" fid="${file.id}"><i class="fa fa-pencil fa-fw"></i></a>` : '';
  1777. const viewHtml = file.viewpath ? `<a href="${file.viewpath}" class="mr-1" target="_blank"><i class="fa fa-eye fa-fw"></i></a>` : '';
  1778. const downHtml = `<a href="javascript: void(0);" onclick="AliOss.downloadFile('${file.filepath}', '${file.filename + file.fileext}')" class="mr-1"><i class="fa fa-download fa-fw"></i></a>`;
  1779. const delHtml = canOperateFile ? `<a href="javascript: void(0);" class="mr-1 text-danger" name="del-search-file" fid="${file.id}"><i class="fa fa-trash-o fa-fw"></i></a>` : '';
  1780. return `<div class="d-flex justify-content-between align-items-center table-file">${locateHtml}${editHtml}${viewHtml}${downHtml}${delHtml}</div>`;
  1781. }
  1782. getFileHtml(file) {
  1783. const html = [];
  1784. html.push(`<tr fid="search_${file.id}">`);
  1785. html.push(`<td name="filename" fid="search_${file.id}">${file.filename}${file.fileext}</td>`);
  1786. html.push(`<td class="text-center">${file.user_name}</td>`);
  1787. html.push(`<td class="text-center">${this.getOperateHtml(file)}</td>`);
  1788. html.push('</tr>');
  1789. return html.join('');
  1790. }
  1791. getResultHtml(result) {
  1792. this.searchResult = result;
  1793. const html = [];
  1794. for (const r of result) {
  1795. html.push(this.getFileHtml(r));
  1796. }
  1797. return html.join('');
  1798. }
  1799. getSearchFilter() {
  1800. const filter = $('#search-filter').val();
  1801. const filing_id = filter === 'all' ? filingSjs.getAllFilingId() : filingSjs.getCurFilingId();
  1802. return { filing_id };
  1803. }
  1804. getEditFileNameHtml(file) {
  1805. const inputHtml = `<input type="text" class="form-control form-control-sm form-control-s-width" maxlength="100" value="${file.filename + file.fileext}" fid="${file.id}">`;
  1806. const btnHtml = `<div class="btn-group-table" style="display: none;"><a href="javascript: void(0)" class="mr-1" name="edit-search-file-ok"><i class="fa fa-check fa-fw"></i></a><a href="javascript: void(0)" class="mr-1" name="edit-search-file-cancel"><i class="fa fa-remove fa-fw"></i></a></div>`;
  1807. return `<div class="d-flex justify-content-between align-items-center table-file"><div>${inputHtml}</div>${btnHtml}</div>`;
  1808. }
  1809. search() {
  1810. const self = this;
  1811. const searchData = this.getSearchFilter();
  1812. searchData.keyword = $('#search-keyword', '#search').val();
  1813. if (!searchData.keyword) {
  1814. toastr.warning('请输入查询的文件名称');
  1815. this.getResultHtml([]);
  1816. return;
  1817. }
  1818. postData('file/search', searchData, function(result) {
  1819. if (result.list.length === result.limit) toastr.warning(`最多查询${result.limit}个结果,请给出更准确的文件名称`);
  1820. $('#search-list').html(self.getResultHtml(result.list));
  1821. });
  1822. }
  1823. refreshSearchFile(file) {
  1824. const sfile = this.searchResult.find(x => x.id === file.id);
  1825. if (sfile) {
  1826. for (const prop in file) {
  1827. if (prop === 'id') continue;
  1828. sfile[prop] = file[prop];
  1829. }
  1830. const row = $(`tr[fid=search_${sfile.id}]`);
  1831. if (row.length > 0) row.replaceWith(this.getFileHtml(sfile));
  1832. }
  1833. }
  1834. removeSearchFile(fid) {
  1835. $(`tr[fid=search_${fid}]`, '#search').remove();
  1836. const index = this.searchResult.findIndex(x => x.id === fid);
  1837. if (index >= 0) this.searchResult.splice(index, 1);
  1838. }
  1839. renameSearchFile(fid, data) {
  1840. const file = this.searchResult.find(x => x.id === fid);
  1841. if (file) {
  1842. file.filename = data.filename;
  1843. file.fileext = data.fileext;
  1844. const td = $(`td[fid=search_${file.id}]`);
  1845. td.html(`${file.filename}${file.fileext}`);
  1846. }
  1847. }
  1848. _initSearch() {
  1849. const self = this;
  1850. $('input', '#search').bind('keydown', function(e) {
  1851. if (e.keyCode === 13) self.search();
  1852. });
  1853. $('button', '#search').bind('click', () => { self.search(); });
  1854. }
  1855. }
  1856. fileSearch = new FileSearch();
  1857. /**
  1858. * FilingPermission - 授权用户管理
  1859. */
  1860. class FilingPermission {
  1861. constructor(setting) {
  1862. this.setting = setting;
  1863. const self = this;
  1864. this.spread = SpreadJsObj.createNewSpread($(`${setting.spread}`)[0]);
  1865. this.sheet = this.spread.getActiveSheet();
  1866. this.spreadSetting = {
  1867. cols: [
  1868. { title: '名称', colSpan: '1', rowSpan: '1', field: 'name', hAlign: 0, width: 200, formatter: '@', cellType: 'tree'},
  1869. { title: '选择', colSpan: '1', rowSpan: '1', field: 'check', hAlign: 1, width: 45, cellType: 'checkbox' },
  1870. { title: '归档单位', colSpan: '1', rowSpan: '1', field: 'file_company', hAlign: 0, width: 80, formatter: '@', cellType: 'autoTip' },
  1871. ],
  1872. emptyRows: 0, headRows: 1, headRowHeight: [32], defaultRowHeight: 21,
  1873. headColWidth: [25], headerFont: '12px 微软雅黑', font: '12px 微软雅黑',
  1874. readOnly: true, selectedBackColor: '#fffacd',
  1875. };
  1876. SpreadJsObj.initSheet(this.sheet, this.spreadSetting);
  1877. this.permissionTree = createNewPathTree('base', {
  1878. id: 'tree_id', pid: 'tree_pid', order: 'order', level: 'level',
  1879. isLeaf: 'is_leaf', fullPath: 'full_path', rootId: -1,
  1880. });
  1881. this.spread.bind(spreadNS.Events.ButtonClicked, function(e, info) {
  1882. if (!info.sheet.zh_setting) return;
  1883. const sheet = info.sheet, cellType = sheet.getCellType(info.row, info.col);
  1884. if (cellType instanceof spreadNS.CellTypes.CheckBox) {
  1885. if (sheet.isEditing()) sheet.endEdit(true);
  1886. }
  1887. const col = info.sheet.zh_setting.cols[info.col];
  1888. if (col.field !== 'check') return;
  1889. const tree = self.permissionTree;
  1890. const node = tree.nodes[info.row];
  1891. const row = [info.row];
  1892. node.check = !node.check;
  1893. if (node.children && node.children.length > 0) {
  1894. const posterity = tree.getPosterity(node);
  1895. for (const p of posterity) {
  1896. p.check = node.check;
  1897. row.push(tree.nodes.indexOf(p));
  1898. }
  1899. }
  1900. if (!node.check) {
  1901. const parent = tree.getAllParents(node);
  1902. for (const p of parent) {
  1903. p.check = node.check;
  1904. row.push(tree.nodes.indexOf(p));
  1905. }
  1906. }
  1907. SpreadJsObj.reLoadRowsData(info.sheet, row);
  1908. });
  1909. this.spread.bind(spreadNS.Events.SelectionChanged, function(e, info) {
  1910. if (info.newSelections[0].col === 1) {
  1911. info.sheet.setSelection(info.oldSelections[0].row, info.oldSelections[0].col, info.oldSelections[0].rowCount, info.oldSelections[0].colCount);
  1912. SpreadJsObj.reloadRowsBackColor(info.sheet, [info.newSelections[0].row, info.oldSelections[0].row]);
  1913. } else {
  1914. const node = SpreadJsObj.getSelectObject(info.sheet);
  1915. self.setCurFiling(node.filing_type);
  1916. }
  1917. });
  1918. $(setting.modal).on('show.bs.modal', () => { self.loadPermission(); });
  1919. $(setting.modal).on('shown.bs.modal', () => { self.spread.refresh(); });
  1920. $(`${setting.modal}-ok`).click(() => { self.savePermission(); });
  1921. $('.book-list').on('click', 'dt', function() {
  1922. const idx = $(this).find('.acc-btn').attr('data-groupid');
  1923. const type = $(this).find('.acc-btn').attr('data-type');
  1924. if (type === 'hide') {
  1925. $(this).parent().find(`div[data-toggleid="${idx}"]`).show(() => {
  1926. $(this).children().find('i').removeClass('fa-plus-square').addClass('fa-minus-square-o');
  1927. $(this).find('.acc-btn').attr('data-type', 'show');
  1928. });
  1929. } else {
  1930. $(this).parent().find(`div[data-toggleid="${idx}"]`).hide(() => {
  1931. $(this).children().find('i').removeClass('fa-minus-square-o').addClass('fa-plus-square');
  1932. $(this).find('.acc-btn').attr('data-type', 'hide');
  1933. });
  1934. }
  1935. return false;
  1936. });
  1937. $('dl').on('click', 'dd', function(e) {
  1938. const type = $(this).data('type');
  1939. if (type === 'all') {
  1940. const cid = parseInt($(this).data('id'));
  1941. const company = self.company.find(x => x.id === cid);
  1942. for (const u of company.users) {
  1943. if (u.filing_type.indexOf(self.curFiling) < 0) u.filing_type.push(self.curFiling);
  1944. }
  1945. } else {
  1946. const uid = $(this).data('id');
  1947. const pu = self.permissionUser.find(x => x.id === uid);
  1948. if (pu.filing_type.indexOf(self.curFiling) < 0) pu.filing_type.push(self.curFiling);
  1949. }
  1950. self.loadCurFiling();
  1951. e.stopPropagation();
  1952. });
  1953. $('#sync-filing').click(function() {
  1954. const selectFilingId = [];
  1955. self.permissionTree.nodes.forEach(x => {
  1956. if (x.check && (!x.children || x.children.length === 0)) selectFilingId.push(x.filing_type);
  1957. });
  1958. if (selectFilingId.length === 0) { toastr.warning('请先选择文档类别'); return; }
  1959. self.syncFiling(self.curFiling, selectFilingId);
  1960. toastr.success('同步成功');
  1961. self.permissionTree.nodes.forEach(x => { x.check = false; });
  1962. SpreadJsObj.reloadColData(self.sheet, 1);
  1963. $('#filing-select-all')[0].checked = false;
  1964. });
  1965. $('#batch-del-filing').click(() => {
  1966. const selectUser = $('[name=ftu-check]:checked');
  1967. if (selectUser.length === 0) { toastr.warning('请先选择用户'); return; }
  1968. const userId = [];
  1969. selectUser.each((i, x) => { userId.push(x.getAttribute('uid')); });
  1970. self.delFiling(self.curFiling, userId);
  1971. self.loadCurFiling();
  1972. });
  1973. $('#batch-add-filing').click(() => {
  1974. const selectFilingId = [];
  1975. self.permissionTree.nodes.forEach(x => {
  1976. if (x.check && (!x.children || x.children.length === 0)) selectFilingId.push(x.filing_type);
  1977. });
  1978. if (selectFilingId.length === 0) { toastr.warning('请先选择文档类别'); return; }
  1979. const selectUser = $('[name=ftu-check]:checked');
  1980. if (selectUser.length === 0) { toastr.warning('请先选择用户'); return; }
  1981. const userId = [];
  1982. selectUser.each((i, x) => { userId.push(x.getAttribute('uid')); });
  1983. self.batchAddFiling(selectFilingId, userId);
  1984. self.loadCurFiling();
  1985. toastr.success('批量添加成功');
  1986. self.permissionTree.nodes.forEach(x => { x.check = false; });
  1987. SpreadJsObj.reloadColData(self.sheet, 1);
  1988. $('#filing-select-all')[0].checked = false;
  1989. });
  1990. $('body').on('click', '[name=del-filing]', function() {
  1991. const id = this.getAttribute('uid');
  1992. self.delFiling(self.curFiling, id);
  1993. self.loadCurFiling();
  1994. this.syncPermissionUserSelectedMarks();
  1995. });
  1996. $('#user-select-all').click(function() { $('input[uid]').attr('checked', this.checked); });
  1997. $('#filing-select-all').click(function() {
  1998. self.permissionTree.nodes.forEach(x => { x.check = this.checked; });
  1999. SpreadJsObj.reloadColData(self.sheet, 1);
  2000. });
  2001. }
  2002. analysisFiling(data) {
  2003. this.permissionUser = data;
  2004. this.permissionUser.forEach(x => { x.filing_type = x.filing_type ? x.filing_type.split(',') : []; });
  2005. this.company = [];
  2006. for (const pu of this.permissionUser) {
  2007. let c = this.company.find(x => x.company === pu.company);
  2008. if (!c) {
  2009. c = { id: this.company.length + 1, company: pu.company, users: [] };
  2010. this.company.push(c);
  2011. }
  2012. c.users.push(pu);
  2013. }
  2014. }
  2015. loadCurFiling() {
  2016. const html = [];
  2017. for (const f of this.permissionUser) {
  2018. if (f.filing_type.indexOf(this.curFiling) < 0) continue;
  2019. html.push('<tr>');
  2020. html.push(`<td><input uid="${f.id}" type="checkbox" name="ftu-check"></td>`);
  2021. html.push(`<td>${f.name}</td>`);
  2022. html.push(`<td>${moment(f.create_time).format('YYYY-MM-DD HH:mm:ss')}</td>`);
  2023. html.push(`<td>${f.file_permission}</td>`);
  2024. html.push(`<td><button class="btn btn-sm btn-outline-danger" uid="${f.id}" name="del-filing">移除</button></td>`);
  2025. html.push('</tr>');
  2026. }
  2027. $(this.setting.list).html(html.join(''));
  2028. $('#user-select-all')[0].checked = false;
  2029. this.syncPermissionUserSelectedMarks();
  2030. }
  2031. refreshBtn() {
  2032. const self = this;
  2033. const filing = this.permissionTree.nodes.find(x => x.filing_type === self.curFiling);
  2034. if (!filing || (filing.children && filing.children.length > 0)) {
  2035. $('#filing-add-user').hide();
  2036. $('#batch-del-filing').hide();
  2037. $('#batch-add-filing').hide();
  2038. $('#sync-filing').hide();
  2039. } else {
  2040. $('#filing-add-user').show();
  2041. $('#batch-del-filing').show();
  2042. $('#batch-add-filing').show();
  2043. $('#sync-filing').show();
  2044. }
  2045. }
  2046. setCurFiling(filingType) {
  2047. this.curFiling = filingType;
  2048. this.loadCurFiling();
  2049. this.refreshBtn();
  2050. }
  2051. loadPermissionUser() {
  2052. const html = [];
  2053. const currentFilingUserIds = new Set();
  2054. if (this.permissionUser && Array.isArray(this.permissionUser)) {
  2055. for (const f of this.permissionUser) {
  2056. if (f.filing_type && f.filing_type.indexOf(this.curFiling) >= 0) {
  2057. currentFilingUserIds.add(f.id);
  2058. }
  2059. }
  2060. }
  2061. for (const c of this.company) {
  2062. html.push(`<dt><a href="javascript: void(0);" class="acc-btn" data-groupid="${c.id}" data-type="hide"><i class="fa fa-plus-square"></i></a> ${c.company}</dt>`);
  2063. html.push(`<div class="dd-content" data-toggleid="${c.id}">`);
  2064. html.push(`<dd class="border-bottom p-2 mb-0" data-id="${c.id}" data-type="all"><p class="mb-0 d-flex"><span class="text-primary">添加单位下全部用户</span></p></dd>`);
  2065. for (const u of c.users) {
  2066. const initialDisplayStyle = currentFilingUserIds.has(u.id) ? 'display:inline-block;' : 'display:none;';
  2067. html.push(`<dd class="border-bottom p-2 mb-0" data-id="${u.id}">`);
  2068. html.push(`<p class="mb-0 d-flex"><span class="text-primary">${u.name}</span><span class="ml-auto">${u.mobile}</span><span class="selected-mark text-success ml-2" style="${initialDisplayStyle}"><i class="fa fa-check"></i></span></p>`);
  2069. html.push(`<span class="text-muted">${u.role}</span>`);
  2070. html.push('</dd>');
  2071. }
  2072. html.push('</div>');
  2073. }
  2074. $('#puList').html(html.join(''));
  2075. }
  2076. syncPermissionUserSelectedMarks() {
  2077. const tableUserIds = new Set();
  2078. $(this.setting.list + ' tr').each(function() {
  2079. const $firstInput = $(this).find('input[name="ftu-check"]').first();
  2080. if ($firstInput.length) {
  2081. const uid = parseInt($firstInput.attr('uid'), 10);
  2082. if (!isNaN(uid)) tableUserIds.add(uid);
  2083. }
  2084. });
  2085. $('#puList dd[data-id]').each(function() {
  2086. const userId = parseInt($(this).data('id'), 10);
  2087. const $mark = $(this).find('.selected-mark');
  2088. if (!isNaN(userId) && $mark.length > 0 && $(this).data('type') !== 'all') {
  2089. if (tableUserIds.has(userId)) { $mark.show(); } else { $mark.hide(); }
  2090. }
  2091. });
  2092. }
  2093. _convertData(sourceTree) {
  2094. const data = [];
  2095. for (const node of sourceTree.nodes) {
  2096. if (!node.is_fixed) continue;
  2097. const parent = node.tree_pid === '-1' ? undefined : data.find(x => x.id === node.tree_pid);
  2098. const child = sourceTree.nodes.find(x => x.tree_pid === node.id);
  2099. data.push({
  2100. id: node.id,
  2101. tree_id: data.length + 1,
  2102. tree_pid: parent ? parent.tree_id : -1,
  2103. order: node.tree_order + 1,
  2104. level: node.tree_level,
  2105. is_leaf: !child || !child.is_fixed,
  2106. full_path: '',
  2107. name: node.name,
  2108. is_fixed: node.is_fixed,
  2109. filing_type: node.filing_type + '',
  2110. tips: node.tips,
  2111. file_company: node.file_company,
  2112. file_count: node.file_count,
  2113. });
  2114. }
  2115. return data;
  2116. }
  2117. loadPermission() {
  2118. this.permissionTree.loadDatas(this._convertData({nodes: filing}));
  2119. SpreadJsObj.loadSheetData(this.sheet, SpreadJsObj.DataType.Tree, this.permissionTree);
  2120. const self = this;
  2121. postData('permission', {}, function(result) {
  2122. self.analysisFiling(result);
  2123. if (!self.curFiling) {
  2124. const node = SpreadJsObj.getSelectObject(self.sheet);
  2125. self.setCurFiling(node.filing_type);
  2126. } else {
  2127. self.loadCurFiling();
  2128. }
  2129. self.loadPermissionUser();
  2130. });
  2131. }
  2132. syncFiling(sourceId, targetIds) {
  2133. for (const pu of this.permissionUser) {
  2134. if (pu.filing_type.indexOf(sourceId) >= 0) {
  2135. targetIds.forEach(id => {
  2136. if (pu.filing_type.indexOf(id) < 0) pu.filing_type.push(id);
  2137. });
  2138. } else {
  2139. targetIds.forEach(id => {
  2140. if (pu.filing_type.indexOf(id) >= 0) pu.filing_type.splice(pu.filing_type.indexOf(id), 1);
  2141. });
  2142. }
  2143. }
  2144. }
  2145. delFiling(filingId, userId) {
  2146. const userIds = userId instanceof Array ? userId : [userId];
  2147. for (const id of userIds) {
  2148. const pu = this.permissionUser.find(x => x.id === id);
  2149. if (!pu) continue;
  2150. if (pu.filing_type.indexOf(filingId) >= 0) pu.filing_type.splice(pu.filing_type.indexOf(filingId), 1);
  2151. }
  2152. }
  2153. batchAddFiling(filingIds, userId) {
  2154. for (const pu of this.permissionUser) {
  2155. if (userId.indexOf(pu.id) < 0) continue;
  2156. filingIds.forEach(fid => {
  2157. if (pu.filing_type.indexOf(fid) < 0) pu.filing_type.push(fid);
  2158. });
  2159. }
  2160. }
  2161. savePermission() {
  2162. const self = this;
  2163. const data = this.permissionUser.map(x => {
  2164. return { id: x.id, filing_type: x.filing_type.join(',') };
  2165. });
  2166. postData('permission/save', data, function(result) {
  2167. $(self.setting.modal).modal('hide');
  2168. });
  2169. }
  2170. }
  2171. /**
  2172. * 侧边工具栏展开/收起
  2173. */
  2174. const refreshSideLayout = function() {
  2175. filingSjs.resizeSpreadColumns();
  2176. if (fileReference) fileReference.spread.refresh();
  2177. };
  2178. const showSideTools = function(show) {
  2179. const left = $('#left-view'), right = $('#right-view'), parent = left.parent();
  2180. const uploadTipsPanel = $('#filing-upload-tips-panel');
  2181. if (show) {
  2182. uploadTipsPanel.hide();
  2183. right.show();
  2184. autoFlashHeight();
  2185. const percent = 100 - right.outerWidth() / parent.width() * 100;
  2186. left.css('width', percent + '%');
  2187. } else {
  2188. left.css('width', '100%');
  2189. right.hide();
  2190. uploadTipsPanel.show();
  2191. }
  2192. // 容器宽度变化后再刷新 SpreadJS,避免继续使用展开前的画布尺寸。
  2193. setTimeout(refreshSideLayout, 0);
  2194. };
  2195. // 展开收起侧边工具栏
  2196. $('a', '#side-menu').bind('click', function(e) {
  2197. e.preventDefault();
  2198. const tab = $(this), tabPanel = $(tab.attr('content'));
  2199. if (!tab.hasClass('active')) {
  2200. $('a', '#side-menu').removeClass('active');
  2201. $('.tab-content .tab-select-show.tab-pane.active').removeClass('active');
  2202. tab.addClass('active');
  2203. tabPanel.addClass('active');
  2204. showSideTools(tab.hasClass('active'));
  2205. if (tab.attr('content') === '#reference') {
  2206. if (!fileReference) {
  2207. fileReference = $.stdLib({
  2208. selector: '#reference',
  2209. stdType: 'reference',
  2210. libs: fileReferenceList,
  2211. treeSetting: {
  2212. id: 'template_id', pid: 'pid', order: 'order', level: 'level',
  2213. rootId: -1, keys: ['id', 'list_id', 'template_id'],
  2214. },
  2215. spreadSetting: {
  2216. cols: [
  2217. {title: '名称', field: 'name', hAlign: 0, width: 420, formatter: '@', cellType: 'tree', wordWrap: true},
  2218. {title: '备注', field: 'remark', hAlign: 1, width: 150, formatter: '@'}
  2219. ],
  2220. emptyRows: 0, headRows: 1, headRowHeight: [32], defaultRowHeight: 21,
  2221. headerFont: '12px 微软雅黑', font: '12px 微软雅黑',
  2222. headColWidth: [30], selectedBackColor: '#fffacd', readOnly: true,
  2223. localCache: { key: 'file_detail_reference', colWidth: true },
  2224. },
  2225. page: 'file',
  2226. });
  2227. filingSjs.initReferenceNameClipboard(fileReference.spread);
  2228. }
  2229. fileReference.spread.refresh();
  2230. }
  2231. } else {
  2232. tab.removeClass('active');
  2233. tabPanel.removeClass('active');
  2234. showSideTools(tab.hasClass('active'));
  2235. }
  2236. });
  2237. // 左右拖拽分隔条
  2238. $.divResizer({
  2239. select: '#file-right-spr',
  2240. callback: function() { filingSjs.resizeSpreadColumns(); },
  2241. });
  2242. $.divResizer({
  2243. select: '#right-spr',
  2244. callback: refreshSideLayout,
  2245. });
  2246. $(window).off('resize.filesjsFiling').on('resize.filesjsFiling', function() {
  2247. filingSjs.resizeSpreadColumns();
  2248. });
  2249. // ========== 事件委托绑定 ==========
  2250. // 文件行内操作:鼠标悬停显示按钮
  2251. $('body').on('mouseenter', '.table-file', function() {
  2252. $(this).children('.btn-group-table').css('display', 'block');
  2253. });
  2254. $('body').on('mouseleave', '.table-file', function() {
  2255. $(this).children('.btn-group-table').css('display', 'none');
  2256. });
  2257. // 锁定或解锁单个文件
  2258. $('body').on('change', 'input[name=file-lock]', function() {
  2259. const lockSwitch = $(this);
  2260. const fid = this.getAttribute('fid');
  2261. const file = filingSjs.curFiling.files.find(x => x.id === fid);
  2262. if (!file) {
  2263. lockSwitch.prop('checked', !this.checked);
  2264. return;
  2265. }
  2266. const isLocked = this.checked;
  2267. lockSwitch.prop('disabled', true);
  2268. filingSjs.setFileLocked(file, isLocked, function() {
  2269. lockSwitch.prop('checked', !isLocked).prop('disabled', false);
  2270. });
  2271. });
  2272. // 单个删除文件
  2273. $('body').on('click', 'a[name=del-file]', function() {
  2274. const fid = this.getAttribute('fid');
  2275. const filename = $(`div[name=filename][fid=${fid}]`)[0].innerHTML;
  2276. filingSjs.delFiles([fid], null, `确认删除「${filename}」?`);
  2277. });
  2278. // 重命名文件 - 进入编辑模式
  2279. $('body').on('click', 'a[name=edit-file]', function() {
  2280. const check = $('[name=filename] input[fid]');
  2281. if (check.length > 0 && check[0].getAttribute('fid') === this.getAttribute('fid')) return;
  2282. const id = this.getAttribute('fid');
  2283. const file = filingSjs.curFiling.files.find(x => x.id === id);
  2284. $(`td[fid=${id}]`).html(filingSjs._getEditFileNameHtml(file));
  2285. });
  2286. // 重命名文件 - 确认
  2287. $('body').on('click', 'a[name=edit-file-ok]', function() {
  2288. const td = $(this).parent().parent().parent();
  2289. const fid = td.attr('fid');
  2290. const file = filingSjs.curFiling.files.find(x => x.id === fid);
  2291. if (!file) return;
  2292. const filename = $('input', td).val();
  2293. if (filename === file.filename + file.fileext) {
  2294. td.html(filingSjs._getFileNameHtml(file));
  2295. return;
  2296. }
  2297. filingSjs.renameFile(file, filename);
  2298. });
  2299. // 重命名文件 - 取消
  2300. $('body').on('click', 'a[name=edit-file-cancel]', function() {
  2301. const td = $(this).parent().parent().parent();
  2302. const fid = td.attr('fid');
  2303. const file = filingSjs.curFiling.files.find(x => x.id === fid);
  2304. if (!file) return;
  2305. td.html(filingSjs._getFileNameHtml(file));
  2306. });
  2307. // 移动文件
  2308. $('body').on('click', 'a[name=move-file]', function() {
  2309. const id = this.getAttribute('fid');
  2310. const file = filingSjs.curFiling.files.find(x => x.id === id);
  2311. moveFileObj.show(file);
  2312. });
  2313. // 搜索结果中删除文件
  2314. $('body').on('click', 'a[name=del-search-file]', function() {
  2315. const del = [this.getAttribute('fid')];
  2316. const filename = $('[name=filename]', $(this).closest('tr'))[0].innerHTML;
  2317. filingSjs.delFiles(del, null, `确认删除「${filename}」?`);
  2318. });
  2319. // 搜索结果中定位文件
  2320. $('body').on('click', 'a[name=locate-search-file]', function() {
  2321. const fid = this.getAttribute('fid');
  2322. const file = fileSearch.searchResult.find(x => x.id === fid);
  2323. if (!file) return;
  2324. const filingNode = filingSjs.findFiling(file.filing_id);
  2325. if (filingNode) {
  2326. const rowIndex = filingSjs.filingTree.nodes.indexOf(filingNode);
  2327. if (rowIndex >= 0) {
  2328. filingSjs.sheet.setActiveCell(rowIndex, 0);
  2329. filingSjs.setCurFiling(filingNode);
  2330. }
  2331. }
  2332. });
  2333. // 搜索结果中重命名文件
  2334. $('body').on('click', 'a[name=edit-search-file]', function() {
  2335. const id = this.getAttribute('fid');
  2336. const file = fileSearch.searchResult.find(x => x.id === id);
  2337. $(`td[fid=search_${id}]`).html(fileSearch.getEditFileNameHtml(file));
  2338. });
  2339. $('body').on('click', 'a[name=edit-search-file-ok]', function() {
  2340. const td = $(this).parent().parent().parent();
  2341. const fid = td.attr('fid').split('_')[1];
  2342. const file = fileSearch.searchResult.find(x => x.id === fid);
  2343. if (!file) return;
  2344. filingSjs.renameFile(file, $('input', td).val());
  2345. });
  2346. $('body').on('click', 'a[name=edit-search-file-cancel]', function() {
  2347. const td = $(this).parent().parent().parent();
  2348. const fid = td.attr('fid').split('_')[1];
  2349. const file = fileSearch.searchResult.find(x => x.id === fid);
  2350. if (!file) return;
  2351. td.html(`${file.filename}${file.fileext}`);
  2352. });
  2353. // ========== Modal 事件 ==========
  2354. // 上传文件弹窗显示时重置
  2355. $('#add-file').on('show.bs.modal', function() {
  2356. $('#filter-same')[0].checked = false;
  2357. $('#filter-same').parent().hide();
  2358. $('#upload-file-hint').hide();
  2359. $('#upload-file')[0].value = '';
  2360. if ($('#add-file-ok').hasClass('btn-warning')) $('#add-file-ok').removeClass('btn-warning').addClass('btn-primary');
  2361. });
  2362. // 上传文件选择时检查同名
  2363. $('#upload-file').change(function() {
  2364. $('#add-file-ok').attr('disabled', true);
  2365. const input = $(this);
  2366. filingSjs.checkFilesExist(input[0].files, function(result) {
  2367. if (result.length === 0) {
  2368. $('#upload-file-hint').hide();
  2369. $('#add-file-ok').removeClass('btn-warning').addClass('btn-primary').attr('disabled', false);
  2370. $('#filter-same').parent().hide();
  2371. } else {
  2372. const msg = result[0] + (result.length > 1 ? `(等${result.length}个文件)`: '') + '</br>存在同名文件,请确认是否上传重复';
  2373. $('#upload-file-hint').html(msg).show();
  2374. $('#add-file-ok').removeClass('btn-primary').addClass('btn-warning').attr('disabled', false);
  2375. $('#filter-same').parent().show();
  2376. $('#filter-same').attr('filterFiles', result.join('$;$'));
  2377. }
  2378. });
  2379. });
  2380. // 覆盖上传文件确认(支持同名过滤)
  2381. $('#add-file-ok').off('click').on('click', function() {
  2382. const input = $('#upload-file');
  2383. const filter = $('#filter-same')[0].checked;
  2384. const filterFiles = filter ? $('#filter-same').attr('filterFiles').split('$;$') : [];
  2385. const files = input[0].files;
  2386. if (!files || files.length === 0) { toastr.error('未选择上传文件。'); return; }
  2387. const formData = new FormData();
  2388. formData.append('filing_id', filingSjs.curFiling.id);
  2389. let count = 0;
  2390. for (const file of files) {
  2391. if (filterFiles.indexOf(file.name) >= 0) continue;
  2392. if (file.size > 50 * 1024 * 1024) { toastr.error('上传文件大小超过50MB。'); return; }
  2393. const fileext = '.' + file.name.toLowerCase().split('.').splice(-1)[0];
  2394. if (whiteList.indexOf(fileext) === -1) { toastr.error('仅支持造价文件、Office文档、图片、压缩包格式,请勿上传' + fileext + '格式文件。'); return; }
  2395. formData.append('size', file.size);
  2396. formData.append('file[]', file);
  2397. count++;
  2398. }
  2399. if (count === 0) { toastr.warning('没有可上传的文件'); return; }
  2400. postDataWithFile('file/upload', formData, function(data) {
  2401. filingSjs.updateFilingFileCount(filingSjs.curFiling, data.filing.file_count);
  2402. if (!filingSjs.curFiling.files) filingSjs.curFiling.files = [];
  2403. filingSjs.curFiling.files.unshift(...data.files);
  2404. filingSjs.refreshPages();
  2405. filingSjs.refreshFilesTable();
  2406. filingSjs.refreshFileCountHint();
  2407. $(input).val('');
  2408. $('#add-file').modal('hide');
  2409. });
  2410. });
  2411. // 大文件上传弹窗显示时重置
  2412. $('#add-big-file').on('show.bs.modal', function() {
  2413. $('#upload-big-file-hint').hide();
  2414. $('#upload-big-file')[0].value = '';
  2415. if ($('#add-big-file-ok').hasClass('btn-warning')) $('#add-big-file-ok').removeClass('btn-warning').addClass('btn-primary');
  2416. });
  2417. // 大文件选择时检查同名
  2418. $('#upload-big-file').change(function() {
  2419. $('#add-big-file-ok').attr('disabled', true);
  2420. const input = $(this);
  2421. filingSjs.checkFilesExist(input[0].files, function(result) {
  2422. if (result.length === 0) {
  2423. $('#upload-big-file-hint').hide();
  2424. $('#add-big-file-ok').removeClass('btn-warning').addClass('btn-primary').attr('disabled', false);
  2425. } else {
  2426. $('#upload-big-file-hint').html('存在同名文件,请确认是否上传重复').show();
  2427. $('#add-big-file-ok').removeClass('btn-primary').addClass('btn-warning').attr('disabled', false);
  2428. }
  2429. });
  2430. });
  2431. // 覆盖大文件上传确认
  2432. $('#add-big-file-ok').off('click').on('click', function() {
  2433. const input = $('#upload-big-file');
  2434. const file = input[0].files[0];
  2435. if (!file) { toastr.error('未选择上传文件。'); return; }
  2436. if (file.size > 500 * 1024 * 1024) { toastr.error('上传文件大小超过500MB。'); return; }
  2437. const fileext = '.' + file.name.toLowerCase().split('.').splice(-1)[0];
  2438. if (whiteList.indexOf(fileext) === -1) { toastr.error('仅支持造价文件、Office文档、图片、压缩包格式,请勿上传' + fileext + '格式文件。'); return; }
  2439. AliOss.uploadBigFile(file, 'file/upload/big', { filing_id: filingSjs.curFiling.id },
  2440. { progressObj: $('#upload-big-file-progress'), resumeObj: $('#add-big-file-resume'), stopObj: $('#add-big-file-stop') },
  2441. function(result) {
  2442. if (!filingSjs.curFiling.files) filingSjs.curFiling.files = [];
  2443. filingSjs.curFiling.files.unshift(...result.files);
  2444. filingSjs.updateFilingFileCount(filingSjs.curFiling, result.filing.file_count);
  2445. filingSjs.refreshPages();
  2446. filingSjs.refreshFilesTable();
  2447. filingSjs.refreshFileCountHint();
  2448. $(input).val('');
  2449. $('#add-big-file').modal('hide');
  2450. }
  2451. );
  2452. });
  2453. // 删除分类确认弹窗
  2454. $('#filesjs-del-filing-ok').on('click', function() {
  2455. if (filingSjs.selectRemoveNodes.length) {
  2456. filingSjs.delFiling(filingSjs.selectRemoveNodes, function() {
  2457. $('#filesjs-del-filing').modal('hide');
  2458. });
  2459. }
  2460. });
  2461. $('#filesjs-del-filing').on('hide.bs.modal', function(e) {
  2462. if (filingSjs._directorySaving) e.preventDefault();
  2463. }).on('hidden.bs.modal', function() {
  2464. filingSjs.selectRemoveNodes = [];
  2465. filingSjs.spread.focus();
  2466. });
  2467. // 批量删除确认
  2468. $('#batch-del-file-ok').off('click').on('click', function() {
  2469. const del = [];
  2470. const checkes = $('[name=bd-check]:checked');
  2471. checkes.each(function() { del.push(this.getAttribute('fid')); });
  2472. filingSjs.delFiles(del, function() {
  2473. $('#batch-del-file').modal('hide');
  2474. });
  2475. });
  2476. // 覆盖批量删除按钮(增加权限检查)
  2477. $('#batch-del-file-btn').off('click').on('click', function() {
  2478. if (projectFileLocked) { toastr.error('管理员锁定中,暂无法删除文件'); return; }
  2479. if (!canEdit) { toastr.error('您没有当前目录的编辑文件权限'); return; }
  2480. const checkes = $('[name=bd-check]:checked');
  2481. if (checkes.length === 0) { toastr.warning('请选择要删除的文件'); return; }
  2482. for (const c of checkes) {
  2483. const fid = c.getAttribute('fid');
  2484. const file = filingSjs.curFiling.files.find(x => x.id === fid);
  2485. if (!file) continue;
  2486. if (filingSjs.isFileLocked(file)) {
  2487. toastr.error(`文件【${file.filename + file.fileext}】已锁定,无法删除`);
  2488. return;
  2489. }
  2490. }
  2491. $('#batch-del-file').modal('show');
  2492. });
  2493. // 覆盖批量下载(使用zip打包)
  2494. $('#batch-download').off('click').on('click', function() {
  2495. filingSjs.batchDownloadFiles();
  2496. });
  2497. // 引用文件
  2498. const relaFileLoader = new RelaFileLoader();
  2499. $('#rela-file').on('show.bs.modal', function() {
  2500. $('#rela-file-hint').text('正在加载标段数据...');
  2501. }).on('shown.bs.modal', async function() {
  2502. try {
  2503. await relaFileLoader.showRelaFile();
  2504. } catch (error) {
  2505. console.error(error);
  2506. $('#rela-file-hint').text('标段数据加载失败,请关闭后重试');
  2507. toastr.error('标段数据加载失败,请重试');
  2508. }
  2509. });
  2510. // 授权用户
  2511. const filingPermission = new FilingPermission({
  2512. modal: '#filing-permission',
  2513. list: '#filing-valid',
  2514. spread: '#permission-spread',
  2515. });
  2516. });