index.vue 57 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744
  1. <template>
  2. <div v-if="showReportData" style="width: 100%; height: calc(100% - 70px);">
  3. <div class="cy-report-content">
  4. <div class="cy-div0">
  5. <div class="cy-report-ruler-unit">mm</div>
  6. </div>
  7. <div class="cy-div1">
  8. <div class="cy-ruler-top">
  9. <ruler-view ruler-position="horizontal"></ruler-view>
  10. </div>
  11. <div class="cy-box">
  12. <div class="cy-ruler-left">
  13. <ruler-view ruler-position="vertical"></ruler-view>
  14. </div>
  15. <div class="cy-box-main">
  16. <el-input type="text" placeholder="请输入报表模板标题" v-model="templateName" maxlength="20" size="mini" show-word-limit></el-input>
  17. <div id="luckysheet"
  18. style="margin:0px;padding:0px;position:relative;height:calc(100% - 40px);width:100%;left: 0px;top: 0px;bottom:0px;">
  19. </div>
  20. </div>
  21. </div>
  22. </div>
  23. <div class="cy-div2">
  24. <input ref="importFileNode" class="import-file-node" type="file" @change="loadExcel" style="display: none;"></input>
  25. <div v-if="templateId && templateId != 0 && templateId != -999" class="cy-chart-div" @click="saveReportTemplate('saveAs')">
  26. <svg-icon icon-class="report_save" style="width: 35px; height: 35px; margin-bottom: 6px;"/>
  27. <span>另存为</span>
  28. </div>
  29. <div class="cy-chart-div" @click="saveReportTemplate">
  30. <svg-icon icon-class="report_save" style="width: 35px; height: 35px; margin-bottom: 6px;"/>
  31. <span>保存</span>
  32. </div>
  33. <div class="cy-chart-div" @click="resetReport(false)">
  34. <svg-icon icon-class="report_cancel" style="width: 30px; height: 30px; margin-bottom: 8px;"/>
  35. <span>重置</span>
  36. </div>
  37. <el-divider></el-divider>
  38. <div class="cy-chart-div" @click="templateEvent()">
  39. <svg-icon icon-class="report_template" style="width: 35px;"/>
  40. <span>模板库</span>
  41. </div>
  42. <div class="cy-chart-div" @click="dataEvent(1)">
  43. <svg-icon icon-class="report_basedata" style="width: 35px;"/>
  44. <span>基础数据项</span>
  45. </div>
  46. <div class="cy-chart-div" @click="dataEvent()">
  47. <svg-icon icon-class="report_data" style="width: 35px;"/>
  48. <span>数据项</span>
  49. </div>
  50. <div class="cy-chart-div" @click="barEvent('bar')">
  51. <svg-icon icon-class="bar_chart"/>
  52. <span>柱状图</span>
  53. </div>
  54. <div class="cy-chart-div" @click="barEvent('line')">
  55. <svg-icon icon-class="line_chart"/>
  56. <span>折线图</span>
  57. </div>
  58. <div class="cy-chart-div" @click="barEvent('pie')">
  59. <svg-icon icon-class="pie_chart"/>
  60. <span>饼状图</span>
  61. </div>
  62. </div>
  63. </div>
  64. <!-- 图表右键操作 -->
  65. <ul v-show="visibleChartMenu"
  66. :style="{ left: menuLeft + 'px', top: menuTop + 'px' }"
  67. class="contextmenu contextmenu-black">
  68. <li @click="updateChartItem">修改</li>
  69. <li @click="delChartItem">删除</li>
  70. </ul>
  71. <!-- 弹出层:图配置 -->
  72. <el-drawer
  73. :title="chartConfigTitle"
  74. :visible.sync="dialogBarChartVisible"
  75. :show-close="false"
  76. :close-on-press-escape="false"
  77. :wrapperClosable="false"
  78. :before-close="dialogClose">
  79. <el-form ref="barChartForm" :model="barChartForm" label-position="top" size="mini" style="margin: 0 20px;">
  80. <el-form-item label="图表标题:" prop="title" class="label-title1">
  81. <el-input type="text" placeholder="请输入标题" v-model="barChartForm.title" maxlength="80" show-word-limit>
  82. </el-input>
  83. </el-form-item>
  84. <el-form-item label="图表副标题:" prop="subtitle" class="label-title1">
  85. <el-input type="text" placeholder="请输入副标题" v-model="barChartForm.subtitle" maxlength="60" show-word-limit>
  86. </el-input>
  87. </el-form-item>
  88. <el-form-item label="是否显示图表标题:" prop="showTitle">
  89. <el-radio v-model="barChartForm.showTitle" label="1">是</el-radio>
  90. <el-radio v-model="barChartForm.showTitle" label="0">否</el-radio>
  91. </el-form-item>
  92. <el-form-item label="是否显示图例:" prop="showLegend">
  93. <el-radio v-model="barChartForm.showLegend" label="1">是</el-radio>
  94. <el-radio v-model="barChartForm.showLegend" label="0">否</el-radio>
  95. </el-form-item>
  96. <!-- <el-form-item label="图例:" prop="legendType" v-if="barChartForm.type != 'pie'">-->
  97. <!-- <el-radio v-model="barChartForm.legendType" label="1">数据项</el-radio>-->
  98. <!-- <el-radio v-model="barChartForm.legendType" label="0">值</el-radio>-->
  99. <!-- </el-form-item>-->
  100. <el-form-item label="数据组:">
  101. <el-select filterable
  102. v-model="chooseDataGroup"
  103. placeholder="请选择数据组"
  104. @change="dataSourceChange"
  105. style="width: calc(100% - 110px);">
  106. <el-option
  107. v-for="dict in dataGroupList"
  108. :key="dict.id"
  109. :label="dict.groupName"
  110. :value="dict.id"
  111. ></el-option>
  112. </el-select>
  113. <el-button size="mini" @click="addDataItem" style="float: right;width: 100px;">选择数据项</el-button>
  114. <el-tag size="mini" v-if="chooseItemData.length > 0"
  115. style="margin-top: 10px;"
  116. class="cy-item-tag">已选择({{ chooseItemData.length }})项</el-tag>
  117. </el-form-item>
  118. <el-button type="warning" size="mini" @click="insertBarChartForm(0)">应用</el-button>
  119. <el-button type="primary" size="mini" @click="insertBarChartForm(1)">保存</el-button>
  120. <el-button size="mini" @click="cancelBarChartForm">取消</el-button>
  121. </el-form>
  122. </el-drawer>
  123. <!-- 数据组选择 -->
  124. <el-dialog
  125. title="选择数据项"
  126. width="600px"
  127. top="10vh"
  128. center
  129. v-dialog-drag
  130. v-if="dialogDataItemVisible"
  131. :before-close="dialogItemClose"
  132. :visible.sync="dialogDataItemVisible"
  133. :close-on-click-modal="false"
  134. :append-to-body="true">
  135. <div>
  136. <div style="margin-bottom: 10px;">
  137. <span>组名称:{{ itemDataListByGroupName }}(共 {{ itemDataListByTree.length }} 项)</span>
  138. <el-checkbox v-model="isSelectAllItem" @change="selectAllItem" style="float: right;">全选</el-checkbox>
  139. </div>
  140. <el-input placeholder="请输入关键字进行过滤" v-model="filterItemData"></el-input>
  141. <div style="height: 50vh; margin-top: 10px; overflow: auto;">
  142. <el-tree class="cy-item-tree"
  143. ref="itemTree"
  144. :data="itemDataListByTree"
  145. :indent="10"
  146. node-key="id"
  147. show-checkbox
  148. :check-on-click-node="true"
  149. :filter-node-method="filterItemDataNode"
  150. :highlight-current="true"
  151. :default-expand-all="true"
  152. @check-change="handleItemCheckChange">
  153. <span class="custom-tree-node" slot-scope="{ node, data }">
  154. <svg-icon v-if="!data.children || data.children.length == 0" icon-class="file"/>
  155. <svg-icon v-else-if="node.expanded" icon-class="folder-open"/>
  156. <svg-icon v-else icon-class="folder"/>
  157. <span :title='data.itemName || "-"' style="margin-left: 2px;">
  158. {{ data.describe
  159. ? ((data.itemName ? data.itemName : '') + '(' + data.describe + ')')
  160. : data.itemName ? data.itemName : '' }}
  161. </span>
  162. </span>
  163. </el-tree>
  164. </div>
  165. </div>
  166. <div style="width: 100%;text-align: center;">
  167. <el-button type="primary"
  168. @click="chooseItemTreeEvent"
  169. style="margin-top: 20px;">确定</el-button>
  170. </div>
  171. </el-dialog>
  172. <!-- 选择基础数据项 -->
  173. <el-dialog
  174. title="选择基础数据项"
  175. width="500px"
  176. center
  177. v-dialog-drag
  178. v-if="dialogBaseDataVisible"
  179. :before-close="dialogBaseDataClose"
  180. :visible.sync="dialogBaseDataVisible"
  181. :close-on-click-modal="false"
  182. :append-to-body="true">
  183. <el-select v-model="chooseBaseData" filterable placeholder="请选择数据项" style="width: 100%;">
  184. <el-option
  185. v-for="item in this.baseDataList"
  186. :key="item.id"
  187. :label="item.dictKey"
  188. :value="item.dictValue">
  189. </el-option>
  190. </el-select>
  191. <div style="width: 100%; text-align: center; margin-top: 20px;">
  192. <el-button type="warning" @click="baseItemEvent(0)">应用</el-button>
  193. <el-button type="primary" @click="baseItemEvent(1)">确定</el-button>
  194. <el-button @click="dialogBaseDataClose">取消</el-button>
  195. </div>
  196. </el-dialog>
  197. <el-dialog
  198. title="选择四则运算表达式"
  199. width="500px"
  200. top="10vh"
  201. center
  202. :before-close="dialogDataModelClose"
  203. :visible.sync="dialogDataModelVisible"
  204. :close-on-click-modal="false"
  205. :append-to-body="true">
  206. <el-select v-model="chooseDataModel" filterable placeholder="请选择表达式" style="width: 100%;">
  207. <el-option
  208. v-for="item in this.dataModelList"
  209. :key="item.id"
  210. :label="item.operationRule"
  211. :value="item.id">
  212. </el-option>
  213. </el-select>
  214. <div style="width: 100%; text-align: center; margin-top: 20px;">
  215. <el-button type="primary" @click="saveDataModelEvent">确定</el-button>
  216. <el-button @click="dialogDataModelClose">取消</el-button>
  217. </div>
  218. </el-dialog>
  219. <!-- 公共模板报表类型选择 -->
  220. <el-dialog
  221. title="选择模板报表"
  222. width="60vw"
  223. center
  224. v-dialog-drag
  225. v-if="dialogCommReportVisible"
  226. :before-close="dialogClose"
  227. :visible.sync="dialogCommReportVisible"
  228. :close-on-click-modal="false"
  229. :append-to-body="true">
  230. <div style="height: 60vh; overflow: auto;">
  231. <div style="height: 50px; display: flex;">
  232. <div style="width: 50%;">
  233. <label style="margin-right: 15px;">模板名称</label>
  234. <el-input placeholder="请输入报表模板名称进行过滤"
  235. v-model="searchReportTxt"
  236. @input="handleReportTypeInputChange"
  237. style="width: calc(100% - 100px); margin-bottom: 10px;">
  238. </el-input>
  239. </div>
  240. <div style="width: 50%;">
  241. <label style="margin-right: 15px;">所属行业</label>
  242. <el-input placeholder="请选择所属行业"
  243. @click.native="clickCascader"
  244. v-model="reportTypeInputName"
  245. readonly
  246. style="width: calc(100% - 100px); z-index: 1;">
  247. </el-input>
  248. <el-cascader v-model="reportType"
  249. ref="reportType"
  250. :options="reportTypes"
  251. style="width: calc(100% - 100px); width: 40px; margin-left: calc(-100% + 100px);"
  252. :props="{ value: 'id', label: 'dictKey', emitPath: true, multiple: false }"
  253. clearable
  254. @change="handleReportTypeChange"
  255. @expand-change="handleReportTypeNodeChange"></el-cascader>
  256. </div>
  257. </div>
  258. <div style="height: calc(100% - 50px); overflow: auto;">
  259. <el-empty v-if="commTemplateList.length == 0" description="暂无数据"></el-empty>
  260. <div v-else style="margin-top: 10px;">
  261. <div v-for="item in commTemplateList"
  262. style="float: left;width: 30%;display: flex;flex-direction: column;align-items: center;margin-top: 10px;">
  263. <el-image v-if="item.logo == 'zsxt.png'" :src="require('@/assets/images/template/zsxt.png')" fit="contain" style="height: 80px;"></el-image>
  264. <el-image v-else-if="item.logo == 'jcscs.png'" :src="require('@/assets/images/template/jcscs.png')" fit="contain" style="height: 80px;"></el-image>
  265. <el-image v-else-if="item.logo == 'ncs.png'" :src="require('@/assets/images/template/ncs.png')" fit="contain" style="height: 80px;"></el-image>
  266. <el-image v-else :src="require('@/assets/images/template/muban.png')" fit="contain" style="height: 80px;"></el-image>
  267. <el-radio v-model="commTemplate" :label="item"
  268. style="margin-top: 5px;width: 100%;text-overflow: ellipsis;white-space: nowrap;overflow: hidden;text-align: center;">
  269. {{ item.templateName }}
  270. </el-radio>
  271. </div>
  272. </div>
  273. </div>
  274. </div>
  275. <div style="text-align: center; margin-top: 40px;">
  276. <el-button type="warning" @click="commReportEvent(0)">应用</el-button>
  277. <el-button type="primary" @click="commReportEvent(1)">确定</el-button>
  278. <el-button @click="dialogClose">取消</el-button>
  279. </div>
  280. </el-dialog>
  281. <!-- 选择数据组数据 -->
  282. <el-dialog
  283. title="数据项配置"
  284. width="80%"
  285. top="10vh"
  286. center
  287. v-dialog-drag
  288. v-if="dialogGroupItemVisible"
  289. :before-close="dialogClose"
  290. :visible.sync="dialogGroupItemVisible"
  291. :close-on-click-modal="false"
  292. :append-to-body="true">
  293. <div>
  294. <div>
  295. <el-select filterable
  296. v-model="chooseDataGroup"
  297. placeholder="请选择数据组"
  298. style="width: calc(100% - 110px);">
  299. <el-option
  300. v-for="dict in dataGroupList"
  301. :key="dict.id"
  302. :label="dict.groupName"
  303. :value="dict.id"
  304. ></el-option>
  305. </el-select>
  306. <el-button size="mini" @click="addDataItem" style="float: right;width: 100px;margin-top: 4px;">选择数据项</el-button>
  307. </div>
  308. <div style="height: 30vh; margin-top: 10px; overflow: auto;">
  309. <el-tree class="cy-item-tree"
  310. ref="itemTree"
  311. :data="chooseGroupItemList"
  312. :indent="10"
  313. node-key="id"
  314. :check-on-click-node="true"
  315. :filter-node-method="filterItemDataNode"
  316. :highlight-current="true"
  317. :default-expand-all="true">
  318. <span class="custom-tree-node" slot-scope="{ node, data }">
  319. <svg-icon v-if="!data.children || data.children.length == 0" icon-class="file"/>
  320. <svg-icon v-else-if="node.expanded" icon-class="folder-open"/>
  321. <svg-icon v-else icon-class="folder"/>
  322. <span v-if="!data.children || data.children.length == 0" :title='data.itemName || "-"' style="margin-left: 2px;">
  323. <!-- {{ data.describe-->
  324. <!-- ? ((data.itemName ? data.itemName : '') + '【' + data.describe + '】')-->
  325. <!-- : data.itemName ? data.itemName : '' }}-->
  326. {{ '【' + data.groupName + '】' + data.itemName + (data.describe ? ('(' + data.describe + ')') : '') }}
  327. </span>
  328. <span v-else :title='data.groupName || "-"' style="margin-left: 2px;">
  329. {{ data.groupName }}
  330. </span>
  331. </span>
  332. </el-tree>
  333. </div>
  334. <div v-if="chooseGroupItemList.length > 0">
  335. <template>
  336. <el-divider content-position="left">数据项排列方式</el-divider>
  337. <div class="cy-line custom-tree">
  338. <el-radio-group v-model="itemShowParams.fieldType" @input="showDataTypeEvent">
  339. <el-radio label="1">横向</el-radio>
  340. <el-radio label="2">纵向</el-radio>
  341. </el-radio-group>
  342. </div>
  343. </template>
  344. <template>
  345. <el-divider content-position="left">值显示数量</el-divider>
  346. <div class="cy-line custom-tree" style="display: flex; align-items: center;">
  347. <div style="width: 50%;display: none;">
  348. <label>显示方式:</label>
  349. <el-radio v-model="itemShowParams.valType" label="1">横向</el-radio>
  350. <el-radio v-model="itemShowParams.valType" label="2">纵向</el-radio>
  351. </div>
  352. <div style="width: 50%;">
  353. <!-- <label>值显示数量:</label>-->
  354. <el-input-number label="请输入显示数量"
  355. v-model="itemShowParams.valLine"
  356. :min="1"
  357. :max="50"></el-input-number>
  358. </div>
  359. </div>
  360. </template>
  361. </div>
  362. </div>
  363. <div style="width: 100%;text-align: center;">
  364. <el-button type="warning" style="margin-top: 20px;" @click="itemEvent(0)">应用</el-button>
  365. <el-button type="primary" style="margin-top: 20px;" @click="itemEvent(1)">确定</el-button>
  366. <el-button style="margin-top: 20px;" @click="dialogClose">取消</el-button>
  367. </div>
  368. </el-dialog>
  369. </div>
  370. <div v-else></div>
  371. </template>
  372. <script>
  373. import RulerView from '@/components/RulerView'
  374. import LuckyExcel from 'luckyexcel'
  375. import {
  376. getAllDataModel,
  377. getAllItemGroup, getSysTableTemplate, getSysTableTemplateById,
  378. getTableItemGroupById,
  379. getTableTemplateById,
  380. saveReportTemplate,
  381. updateReportTemplate
  382. } from "@/api/datasource";
  383. import {getLuckysheetConfig, handleTree, showAlertWin, showLoading, traverseNode, traverseVisible} from "@/utils/cqcy";
  384. import {getDictByKey} from "@/api/basic";
  385. import {exportExcel} from "@/utils/export";
  386. import {insertLuckysheetEChart} from "@/utils/luckysheettool";
  387. export default {
  388. name: 'index',
  389. components: {
  390. RulerView
  391. },
  392. data() {
  393. return {
  394. showReportData: true,
  395. dialogBarChartVisible: false,
  396. dialogDataItemVisible: false,
  397. dialogDataModelVisible: false,
  398. dialogCommReportVisible: false,
  399. dialogGroupItemVisible: false,
  400. dialogBaseDataVisible: false,
  401. isSelectAllItem: false,
  402. menuLeft: 0,
  403. menuTop: 0,
  404. visibleChartMenu: false,
  405. chooseChartClz: '',
  406. chartConfigTitle: '',
  407. barChartForm: {
  408. title: '',
  409. type: '',
  410. subtitle: '',
  411. showTitle: '1',
  412. showLegend: '1',
  413. legendType: '1',
  414. legendData: ['模板数据1', '模板数据2'],
  415. xAxisData: ['数据1', '数据2', '数据3']
  416. },
  417. barChartOption: {
  418. },
  419. itemShowParams: {
  420. fieldType: '1',
  421. valType: '2',
  422. valLine: 1,
  423. dataId: new Date().getTime()
  424. },
  425. baseDataList: [],
  426. chooseBaseData: null,
  427. dataGroupList: [],
  428. itemDataListByTree: [],
  429. itemDataListByGroupName: '',
  430. itemDataListByCalc: [],
  431. chooseGroupItemList: [],
  432. dataModelList: [],
  433. chooseDataModel: null,
  434. chooseDataItemIndex: null,
  435. bomCheckKey: 0,
  436. chooseDataGroup: null,
  437. chooseItemData: [],
  438. filterItemData: '',
  439. searchReportTxt: '',
  440. keyType: 'report_template',
  441. reportTypes: [],
  442. reportType: null,
  443. reportTypeInputName: '所有',
  444. reportTypeInputId: [],
  445. commTemplate: null,
  446. commTemplateList: [],
  447. templateName: '',
  448. templateId: '',
  449. templateVersion: '',
  450. reportTemplateItem: this.$store.getters.sessionName.REPORT_TEMPLATE_ITEM,
  451. reportInterval: null,
  452. toolChart: [],
  453. toolTable: [],
  454. luckysheetOption: {
  455. container: 'luckysheet', // 设定 DOM 容器的 id
  456. title: '报表模板', // 设定表格名称
  457. lang: 'zh', // 设定表格语言
  458. showinfobar: false, // 是否显示顶部信息栏
  459. showtoolbar: false, // 是否显示工具栏
  460. showtoolbarConfig: {
  461. paintFormat: true, //格式刷
  462. moreFormats: true, // 单元格格式
  463. font: true, // 字体
  464. fontSize: true, // 字号大小
  465. bold: true, // 粗体 (Ctrl+B)
  466. italic: true, // 斜体 (Ctrl+I)
  467. strikethrough: true, // 删除线 (Alt+Shift+5)
  468. underline: true, // 下划线 (Alt+Shift+6)
  469. textColor: true, // 文本颜色
  470. fillColor: true, // 单元格颜色
  471. border: true, // 边框
  472. mergeCell: true, // 合并单元格
  473. horizontalAlignMode: true, // 水平对齐方式
  474. verticalAlignMode: true, // 垂直对齐方式
  475. function: true, // 公式
  476. // image: true
  477. // chart: true
  478. },
  479. showsheetbar: false, // 是否显示底部 sheet 页按钮
  480. sheetFormulaBar: false, // 是否显示公式
  481. row: 100, // 是否显示底部 sheet 页按钮
  482. data: [{
  483. "name": "统计报表", //工作表名称
  484. }],
  485. cellRightClickConfig: { // 自定义配置单元格右击菜单
  486. copy: true, // 复制
  487. copyAs: false, // 复制为
  488. paste: true, // 粘贴
  489. insertRow: true, // 插入行
  490. insertColumn: true, // 插入列
  491. deleteRow: true, // 删除选中行
  492. deleteColumn: true, // 删除选中列
  493. deleteCell: false, // 删除单元格
  494. hideRow: false, // 隐藏选中行和显示选中行
  495. hideColumn: false, // 隐藏选中列和显示选中列
  496. rowHeight: true, // 行高
  497. columnWidth: true, // 列宽
  498. clear: false, // 清除内容
  499. matrix: false, // 矩阵操作选区
  500. sort: false, // 排序选区
  501. filter: false, // 筛选选区
  502. chart: true, // 图表生成
  503. image: false, // 插入图片
  504. link: false, // 插入链接
  505. data: false, // 数据验证
  506. cellFormat: false // 设置单元格格式
  507. },
  508. plugins: ['chart']
  509. }
  510. }
  511. },
  512. watch: {
  513. '$route.query.t': {
  514. handler(now, old) {
  515. this.initLuckysheet(this.$route.query.id, this.$route.query.type)
  516. }
  517. },
  518. filterItemData(val) {
  519. this.$refs.itemTree.filter(val)
  520. },
  521. visibleChartMenu(value) {
  522. if (value) {
  523. document.body.addEventListener('click', this.closeMenu)
  524. } else {
  525. document.body.removeEventListener('click', this.closeMenu)
  526. }
  527. }
  528. },
  529. mounted() {
  530. let _this = this
  531. $(function () {
  532. if (_this.showReportData) {
  533. let tempId = _this.$route.query.id
  534. let type = _this.$route.query.type
  535. _this.initLuckysheet(tempId, type)
  536. }
  537. })
  538. document.onkeyup = function (event) {
  539. let e = event || window.event || arguments.callee.caller.arguments[0]
  540. if (!e) return
  541. const {key, keyCode} = e
  542. if (keyCode === 46 || keyCode === 8) {
  543. _this.forceRefreshLuckysheet()
  544. }
  545. }
  546. },
  547. created() {
  548. let _this = this
  549. // 强制关闭自定义图表右键菜单
  550. document.body.oncontextmenu = (e) => {
  551. setTimeout(() => {
  552. if ($('#luckysheet-rightclick-menu') && 'block' == $('#luckysheet-rightclick-menu').css('display')) {
  553. _this.closeMenu()
  554. }
  555. }, 500)
  556. }
  557. },
  558. destroyed() {
  559. luckysheet.destroy()
  560. if (this.reportInterval) {
  561. clearInterval(this.reportInterval)
  562. }
  563. },
  564. methods: {
  565. /** 初始化操作 */
  566. initLuckysheet(tempId, type) {
  567. let _this = this
  568. if (type && type != 'export') {
  569. this.withTypeReportTemplate(type)
  570. return
  571. }
  572. if (tempId && tempId != -999) {
  573. _this.getTableTemplate(tempId, type)
  574. return
  575. }
  576. this.templateId = null
  577. this.templateVersion = null
  578. this.templateName = ''
  579. this.toolChart = []
  580. this.toolTable = []
  581. luckysheet.destroy()
  582. let locItem = localStorage.getItem(_this.reportTemplateItem)
  583. if (locItem && JSON.parse(locItem).data && Array.isArray(JSON.parse(locItem).data)) {
  584. let option = JSON.parse(JSON.stringify(_this.luckysheetOption))
  585. option.data = JSON.parse(locItem).data
  586. _this.toolChart = JSON.parse(locItem).charts
  587. _this.toolTable = JSON.parse(locItem).tables
  588. option.hook = {
  589. workbookCreateAfter() {
  590. for (let i in _this.toolChart) {
  591. _this.insertEChartTool(_this.toolChart[i].info, false)
  592. }
  593. _this.autoSaveReportInfo()
  594. },
  595. cellUpdated(r, c, newV, oldV) {
  596. if (!(r === 49 && c === 0)) {
  597. _this.forceRefreshLuckysheet()
  598. }
  599. }
  600. }
  601. luckysheet.create(option)
  602. } else {
  603. let option = JSON.parse(JSON.stringify(_this.luckysheetOption))
  604. option.hook = {
  605. workbookCreateAfter() {
  606. _this.autoSaveReportInfo()
  607. },
  608. cellUpdated(r, c, newV, oldV) {
  609. if (!(r === 49 && c === 0)) {
  610. _this.forceRefreshLuckysheet()
  611. }
  612. }
  613. }
  614. luckysheet.create(option)
  615. }
  616. },
  617. /** 实现类似刷新效果 */
  618. forceRefreshLuckysheet() {
  619. if (luckysheet) {
  620. luckysheet.setCellValue(49, 0, luckysheet.getCellValue(49, 0))
  621. }
  622. },
  623. /** 数据项全选事件 */
  624. selectAllItem() {
  625. const data = traverseNode(this.$refs.itemTree.store.root.childNodes)
  626. if (this.isSelectAllItem) {
  627. // this.$refs.itemTree.setCheckedNodes(this.itemDataListByTree)
  628. let arr = traverseVisible(data)
  629. let ids = Array.from(arr, ({ id }) => id)
  630. this.$refs.itemTree.setCheckedKeys(ids)
  631. } else {
  632. this.$refs.itemTree.setCheckedNodes([])
  633. }
  634. },
  635. /**数据项单项选择判断 */
  636. handleItemCheckChange(data, checked, indeterminate) {
  637. const tdata = traverseNode(this.$refs.itemTree.store.root.childNodes)
  638. let arr = traverseVisible(tdata)
  639. if (arr.length == this.$refs.itemTree.getCheckedNodes().length) {
  640. this.isSelectAllItem = true
  641. } else {
  642. this.isSelectAllItem = false
  643. }
  644. },
  645. /** 处理日期显示数字问题 */
  646. withDateData(excelData) {
  647. if (!excelData) {
  648. return
  649. }
  650. excelData.map((item) => {
  651. if (item) {
  652. item.map((ll) => {
  653. if (ll && ll.ct && ll.ct.t && ll.ct.t === 'd') {
  654. ll.m = ll.m,
  655. ll.v = ll.m,
  656. ll.ct = {
  657. fa: "@",
  658. t: "s",
  659. }
  660. }
  661. })
  662. }
  663. })
  664. },
  665. /** 保存模版信息 */
  666. saveReportTemplate(type) {
  667. let _templateName = this.templateName
  668. let _title = '保存'
  669. if (type == 'saveAs') {
  670. this.templateName = '副本_' + _templateName
  671. _title = '另存为'
  672. }
  673. this.$prompt('请输入/确认报表模板名称', _title, {
  674. confirmButtonText: '确定',
  675. cancelButtonText: '取消',
  676. customClass: 'close_confirm',
  677. closeOnClickModal: false,
  678. inputValue: this.templateName,
  679. inputValidator: (val) => {
  680. if (!val) {
  681. return '模板名称不能为空'
  682. }
  683. if (val.length > 20) {
  684. return '模板名称必须在20字以内'
  685. }
  686. }
  687. }).then(({ value }) => {
  688. const loading = showLoading(this, '保存中,请稍候···')
  689. let option = JSON.parse(JSON.stringify(this.luckysheetOption))
  690. let _data = JSON.parse(getLuckysheetConfig())
  691. let excelData = _data[0].data
  692. this.withDateData(excelData)
  693. this.spliceToolTable()
  694. let result = {
  695. 'charts': this.toolChart,
  696. 'tables': this.toolTable,
  697. 'option': option,
  698. 'data': _data
  699. }
  700. let data = {
  701. 'templateName': value,
  702. 'templateData': JSON.stringify(result)
  703. }
  704. if (this.templateId && type != 'saveAs') {
  705. data.id = this.templateId
  706. data.version = this.templateVersion
  707. updateReportTemplate(data).then(res => {
  708. loading.close()
  709. let msg = res.data ? '修改成功!' : '修改失败!'
  710. let msgType = res.data ? 'success' : 'error'
  711. this.$message({
  712. message: msg,
  713. type: msgType
  714. })
  715. this.resetReport(true)
  716. this.getTableTemplate(this.$route.query.id, this.$route.query.type)
  717. this.$emit('refreshReportTemplateData', new Date().getTime())
  718. }).catch((e) => {
  719. loading.close()
  720. this.templateName = _templateName
  721. showAlertWin(this, e)
  722. })
  723. } else {
  724. saveReportTemplate(data).then(res => {
  725. loading.close()
  726. let msg = res.data ? '保存成功!' : '保存失败!'
  727. let msgType = res.data ? 'success' : 'error'
  728. this.$message({
  729. message: msg,
  730. type: msgType
  731. })
  732. this.resetReport(true)
  733. this.$emit('refreshReportTemplateData', new Date().getTime())
  734. }).catch((e) => {
  735. loading.close()
  736. this.templateName = _templateName
  737. showAlertWin(this, e)
  738. })
  739. }
  740. }).catch(() => {
  741. this.templateName = _templateName
  742. })
  743. },
  744. /** 重置报表信息 */
  745. resetReport(clearFlag) {
  746. let _this = this
  747. localStorage.removeItem(this.reportTemplateItem)
  748. if (clearFlag) {
  749. this.templateId = null
  750. this.templateVersion = null
  751. this.templateName = ''
  752. }
  753. this.toolChart = []
  754. this.toolTable = []
  755. luckysheet.destroy()
  756. let option = JSON.parse(JSON.stringify(this.luckysheetOption))
  757. option.hook = {
  758. workbookCreateAfter() {
  759. _this.autoSaveReportInfo()
  760. },
  761. cellUpdated(r, c, newV, oldV) {
  762. if (!(r === 49 && c === 0)) {
  763. _this.forceRefreshLuckysheet()
  764. }
  765. }
  766. }
  767. luckysheet.create(option)
  768. },
  769. /** 处理报表事件报表内容 */
  770. withTypeReportTemplate(type) {
  771. this.resetReport(true)
  772. if (type === 'import') { // 报表模板导入
  773. console.log('导入报表')
  774. this.$refs.importFileNode.dispatchEvent(new MouseEvent('click'))
  775. }
  776. },
  777. /** 导入报表 */
  778. loadExcel(evt) {
  779. console.log(evt)
  780. let _this = this
  781. const files = evt.target.files
  782. if (files == null || files.length == 0) {
  783. _this.$message({
  784. message: '请选择文件',
  785. type: 'warning'
  786. })
  787. return
  788. }
  789. console.log(files)
  790. // 获取文件名
  791. let name = files[0].name
  792. // 获取文件后缀
  793. let suffixArr = name.split("."),
  794. suffix = suffixArr[suffixArr.length - 1]
  795. if (suffix != 'xlsx') {
  796. _this.$message({
  797. message: '只能导入xlsx格式文件!',
  798. type: 'warning'
  799. })
  800. return
  801. }
  802. const loading = showLoading(this, '文件导入中,请稍候···')
  803. // 转换导入的excel
  804. LuckyExcel.transformExcelToLucky(files[0], function (exportJson, luckysheetfile) {
  805. if (exportJson.sheets == null || exportJson.sheets.length == 0) {
  806. loading.close()
  807. _this.$message({
  808. message: '无法读取excel文件的内容,当前不支持xls文件!',
  809. type: 'warning'
  810. })
  811. return
  812. }
  813. console.log('exportJson', exportJson)
  814. // 销毁之前的表格
  815. window.luckysheet.destroy()
  816. // 创建新的表格
  817. let option = JSON.parse(JSON.stringify(_this.luckysheetOption))
  818. option.data = exportJson.sheets
  819. option.title = exportJson.info.name
  820. option.data = exportJson.sheets
  821. option.hook = {
  822. workbookCreateAfter() {
  823. _this.autoSaveReportInfo()
  824. },
  825. cellUpdated(r, c, newV, oldV) {
  826. if (!(r === 49 && c === 0)) {
  827. _this.forceRefreshLuckysheet()
  828. }
  829. }
  830. }
  831. window.luckysheet.create(option)
  832. document.getElementsByClassName('import-file-node')[0].value = ''
  833. loading.close()
  834. })
  835. },
  836. /** 生成唯一 ID 字符串 */
  837. generateRandomKey(prefix) {
  838. if (prefix == null) {
  839. prefix = 'chart'
  840. }
  841. let userAgent = window.navigator.userAgent
  842. .replace(/[^a-zA-Z0-9]/g, '')
  843. .split('')
  844. let mid = ''
  845. for (let i = 0; i < 12; i++) {
  846. mid += userAgent[Math.round(Math.random() * (userAgent.length - 1))]
  847. }
  848. let time = new Date().getTime()
  849. return prefix + '_' + mid + '_' + time
  850. },
  851. /** 向 Excel 插入图表 */
  852. insertEChartTool(info, flag, item, legendType) {
  853. let _this = this
  854. let _self = this
  855. setTimeout(() => {
  856. const sheet = luckysheet.getLuckysheetfile()[0]
  857. let optionData = sheet.data
  858. try {
  859. insertLuckysheetEChart({
  860. selector: '#luckysheet',
  861. info,
  862. sheet,
  863. optionData,
  864. echarts,
  865. luckysheet,
  866. $,
  867. _self
  868. })
  869. } catch (e) {
  870. console.error(e)
  871. }
  872. // 保存图表信息
  873. if (flag) {
  874. let chart = {
  875. 'info': info,
  876. 'legendType': legendType,
  877. 'item': item
  878. }
  879. _this.toolChart.push(chart)
  880. }
  881. // 图表事件监听
  882. $('.' + info.className).mousedown(function(e) {
  883. if (3 == e.which) {
  884. _this.closeMenu()
  885. _this.menuTop = e.pageY
  886. _this.menuLeft = e.pageX / 2
  887. _this.visibleChartMenu = true
  888. _this.chooseChartClz = info.className
  889. setTimeout(() => {
  890. $('#luckysheet-rightclick-menu').css('display', 'none')
  891. }, 100)
  892. }
  893. })
  894. }, 200)
  895. },
  896. /** 获取右侧数据组列表 */
  897. getDataGroupList() {
  898. getAllItemGroup().then(res => {
  899. this.dataGroupList = res.data
  900. }).catch((e) => {
  901. showAlertWin(this, e)
  902. })
  903. },
  904. /** 选择数据源值改变事件 */
  905. dataSourceChange(val) {
  906. this.chooseDataGroup = val
  907. this.chooseItemData = []
  908. },
  909. /** 数据项搜索过滤 */
  910. filterItemDataNode(value, data) {
  911. if (!value) return true
  912. return data.itemName.indexOf(value) !== -1
  913. },
  914. /** 添加数据项 */
  915. addDataItem() {
  916. let id = this.chooseDataGroup
  917. if (!id) {
  918. this.$message({
  919. message: '请选择数据组!',
  920. type: 'warning'
  921. })
  922. return
  923. }
  924. this.isSelectAllItem = false
  925. this.itemDataListByTree = []
  926. const loading = showLoading(this, '加载中,请稍候···')
  927. getTableItemGroupById(id).then(res => {
  928. loading.close()
  929. this.itemDataListByGroupName = res.data.groupName
  930. this.itemDataListByTree = res.data.itemList
  931. let tempList = this.chooseGroupItemList.length == 0 ? this.chooseItemData : this.chooseGroupItemList
  932. if (this.$refs.itemTree) this.$refs.itemTree.setCheckedKeys([])
  933. if (tempList.length > 0) {
  934. let idList = Array.from(tempList, ({ id }) => id)
  935. setTimeout(() => {
  936. if (this.$refs.itemTree) this.$refs.itemTree.setCheckedKeys(idList)
  937. if (this.itemDataListByTree.length == idList.length) {
  938. this.isSelectAllItem = true
  939. } else {
  940. this.isSelectAllItem = false
  941. }
  942. },100)
  943. }
  944. this.dialogDataItemVisible = true
  945. }).catch((e) => {
  946. loading.close()
  947. showAlertWin(this, e)
  948. })
  949. },
  950. /** 选择的数据项:第一步 */
  951. chooseItemTreeEvent() {
  952. let checkedNodes = this.$refs.itemTree.getCheckedNodes(false, true)
  953. if (checkedNodes.length == 0) {
  954. this.$message({
  955. message: '请选择数据项!',
  956. type: 'warning'
  957. })
  958. return
  959. }
  960. let chooseList = []
  961. for (let i in checkedNodes) {
  962. let checkedNode = checkedNodes[i]
  963. if (checkedNode.children && checkedNode.children.length > 0) {
  964. continue
  965. }
  966. chooseList.push(checkedNode)
  967. }
  968. this.chooseItemData = chooseList
  969. this.dialogDataItemVisible = false
  970. if (this.dialogGroupItemVisible) {
  971. this.initGroupItem()
  972. }
  973. this.filterItemData = ''
  974. // this.getAllDataModel(chooseList)
  975. },
  976. initGroupItem() {
  977. let group = null
  978. for (let i = 0; i < this.dataGroupList.length; i ++) {
  979. if (this.dataGroupList[i].id == this.chooseDataGroup) {
  980. group = this.dataGroupList[i]
  981. break
  982. }
  983. }
  984. this.chooseGroupItemList = this.chooseGroupItemList.filter(function (item, index) {
  985. return item.itemGroupId != group.id;
  986. })
  987. for (let j = 0; j < this.chooseItemData.length; j ++) {
  988. this.chooseItemData[j].groupName = group.groupName
  989. this.chooseGroupItemList.push(this.chooseItemData[j])
  990. }
  991. let obj = {};
  992. this.chooseGroupItemList = this.chooseGroupItemList.reduce(function(item, next) {
  993. obj[next.id] ? '' : obj[next.id] = true && item.push(next)
  994. return item;
  995. }, [])
  996. },
  997. /** 查询所有数据模型 */
  998. getAllDataModel(itemList) {
  999. let loading = showLoading(this, '数据加载中,请稍候···')
  1000. let params = {
  1001. 'page': 1,
  1002. 'limit': 1000
  1003. }
  1004. getAllDataModel(params).then(res => {
  1005. loading.close()
  1006. if (!res.data) {
  1007. this.chooseItemData = itemList
  1008. return
  1009. }
  1010. this.dataModelList = res.data.dataModelList
  1011. this.itemDataListByCalc = JSON.parse(JSON.stringify(itemList))
  1012. for (let i in this.itemDataListByCalc) {
  1013. let temp = this.itemDataListByCalc[i]
  1014. temp.rule = {
  1015. 'operationRule': '默认值'
  1016. }
  1017. }
  1018. }).catch((e) => {
  1019. loading.close()
  1020. showAlertWin(this, e)
  1021. })
  1022. },
  1023. /** 选择数据模型 */
  1024. handleChoose(index, row) {
  1025. this.chooseDataItemIndex = index
  1026. this.chooseDataModel = null
  1027. this.dialogDataModelVisible = true
  1028. },
  1029. /** 保存数据模型事件 */
  1030. saveDataModelEvent() {
  1031. if (!this.chooseDataModel) {
  1032. this.$message({
  1033. message: '请选择表达式!',
  1034. type: 'warning'
  1035. })
  1036. return
  1037. }
  1038. let item = {}
  1039. for (let i in this.dataModelList) {
  1040. if (this.chooseDataModel == this.dataModelList[i].id) {
  1041. item = this.dataModelList[i]
  1042. }
  1043. }
  1044. this.$nextTick(() => {
  1045. this.itemDataListByCalc[this.chooseDataItemIndex].rule = item
  1046. this.bomCheckKey ++
  1047. })
  1048. this.dialogDataModelVisible = false
  1049. },
  1050. /** 报表模板库 */
  1051. templateEvent() {
  1052. this.commTemplateList = []
  1053. this.reportTypes = []
  1054. this.reportType = null
  1055. // 获取报表类别
  1056. getDictByKey({
  1057. 'keyType': 'report_template'
  1058. }).then(res => {
  1059. this.reportTypes.push({
  1060. 'id': -1,
  1061. 'dictKey': '所有',
  1062. 'dictValue': ''
  1063. })
  1064. let t = handleTree(res.data, 'id', 'parentId', 'children')
  1065. this.reportTypes = this.reportTypes.concat(t)
  1066. this.dialogCommReportVisible = true
  1067. this.handleReportTypeChange([-1])
  1068. }).catch((e) => {
  1069. showAlertWin(this, e)
  1070. })
  1071. },
  1072. clickCascader() {
  1073. this.$refs.reportType.$el.click()
  1074. this.reportType = null
  1075. },
  1076. searchDictTree(nodes, searchKey) {
  1077. for (let _i = 0; _i < nodes.length; _i++) {
  1078. if (nodes[_i].id === searchKey) {
  1079. return nodes[_i].dictKey
  1080. } else {
  1081. if (nodes[_i].children && nodes[_i].children.length > 0) {
  1082. let res = this.searchDictTree(nodes[_i].children, searchKey)
  1083. if (res) {
  1084. return res
  1085. }
  1086. }
  1087. }
  1088. }
  1089. return null
  1090. },
  1091. handleReportTypeInputChange() {
  1092. let arr = this.reportTypeInputId.length == 0 ? [-1] : this.reportTypeInputId
  1093. this.handleReportTypeChange(arr)
  1094. },
  1095. /** 报表行业选择 */
  1096. handleReportTypeNodeChange(val) {
  1097. this.handleReportTypeChange(val)
  1098. },
  1099. /** 报表行业选择 */
  1100. handleReportTypeChange(val) {
  1101. if (!val || val.length == 0) {
  1102. return
  1103. }
  1104. let nameArr = []
  1105. val.forEach(v => {
  1106. let name = this.searchDictTree(this.reportTypes, v)
  1107. nameArr.push(name)
  1108. })
  1109. this.reportTypeInputName = nameArr.join(' / ')
  1110. this.reportTypeInputId = val
  1111. let id = val[val.length - 1]
  1112. if (id === -1) {
  1113. this.reportTypeInputName = '所有'
  1114. this.reportTypeInputId = []
  1115. id = null
  1116. }
  1117. let params = {
  1118. 'keyType': this.keyType,
  1119. 'dictId': id
  1120. }
  1121. if (this.searchReportTxt && this.searchReportTxt.trim()) {
  1122. params.templateName = this.searchReportTxt.trim()
  1123. }
  1124. getSysTableTemplate(params).then(res => {
  1125. if (!res.data) {
  1126. return
  1127. }
  1128. this.commTemplateList = res.data
  1129. }).catch((e) => {
  1130. showAlertWin(this, e)
  1131. })
  1132. },
  1133. /** 选择模板报表 */
  1134. commReportEvent(flag) {
  1135. let _this = this
  1136. if (!_this.commTemplate) {
  1137. _this.$message({
  1138. message: '请选择模板!',
  1139. type: 'warning'
  1140. })
  1141. return
  1142. }
  1143. const loading = showLoading(_this, '加载中,请稍候···')
  1144. getSysTableTemplateById(_this.commTemplate.id).then(res => {
  1145. if (!res || !res.data) {
  1146. loading.close()
  1147. return
  1148. }
  1149. let locItem = res.data.templateData
  1150. luckysheet.destroy()
  1151. let option = JSON.parse(JSON.stringify(_this.luckysheetOption))
  1152. option.data = JSON.parse(locItem).data
  1153. this.toolChart = JSON.parse(locItem).charts
  1154. this.toolTable = JSON.parse(locItem).tables
  1155. option.hook = {
  1156. workbookCreateAfter() {
  1157. loading.close()
  1158. if (flag == 1) _this.dialogClose()
  1159. },
  1160. cellUpdated(r, c, newV, oldV) {
  1161. if (!(r === 49 && c === 0)) {
  1162. _this.forceRefreshLuckysheet()
  1163. }
  1164. }
  1165. }
  1166. luckysheet.create(option)
  1167. }).catch((e) => {
  1168. loading.close()
  1169. showAlertWin(_this, e)
  1170. })
  1171. },
  1172. /** 数据项 */
  1173. dataEvent(type) {
  1174. let rangeWithFlatten = luckysheet.getRangeWithFlatten()
  1175. if (!rangeWithFlatten || rangeWithFlatten.length != 1) {
  1176. this.$message({
  1177. message: '请选择一项单元格!',
  1178. type: 'warning'
  1179. })
  1180. return
  1181. }
  1182. // 基础数据项
  1183. if (type == 1) {
  1184. this.getBaseDataItem()
  1185. return
  1186. }
  1187. // 初始化数据显示方式
  1188. this.itemShowParams = {
  1189. fieldType: '1',
  1190. valType: '2',
  1191. valLine: 1,
  1192. dataId: new Date().getTime()
  1193. }
  1194. this.chooseDataGroup = null
  1195. this.getDataGroupList()
  1196. this.dialogGroupItemVisible = true
  1197. },
  1198. /** 查询字典表:基础数据项 */
  1199. getBaseDataItem() {
  1200. getDictByKey({
  1201. 'keyType': 'base_data_item'
  1202. }).then(res => {
  1203. this.dialogBaseDataVisible = true
  1204. this.baseDataList = res.data
  1205. }).catch((e) => {
  1206. showAlertWin(this, e)
  1207. })
  1208. },
  1209. /** 基础数据项插入 */
  1210. baseItemEvent(flag) {
  1211. if (!this.chooseBaseData) {
  1212. this.$message({
  1213. message: '请选择基础数据项!',
  1214. type: 'warning'
  1215. })
  1216. return
  1217. }
  1218. let rangeWithFlatten = luckysheet.getRangeWithFlatten()
  1219. let c = rangeWithFlatten[0].c
  1220. let r = rangeWithFlatten[0].r
  1221. luckysheet.setCellValue(r, c, this.chooseBaseData)
  1222. if (flag == 1) this.dialogBaseDataClose()
  1223. },
  1224. /** 动态数据项插入 */
  1225. itemEvent(flag) {
  1226. if (this.chooseGroupItemList.length == 0) {
  1227. this.$message({
  1228. message: '请选择数据组项!',
  1229. type: 'warning'
  1230. })
  1231. return
  1232. }
  1233. let rangeWithFlatten = luckysheet.getRangeWithFlatten()
  1234. let c = rangeWithFlatten[0].c
  1235. let r = rangeWithFlatten[0].r
  1236. let fieldList = []
  1237. for (let n = 0; n < this.itemShowParams.valLine; n ++) {
  1238. for (let i in this.chooseGroupItemList) {
  1239. let groupId = this.chooseGroupItemList[i].itemGroupId
  1240. let name = this.chooseGroupItemList[i].itemName
  1241. name = name.substring(name.lastIndexOf('.') + 1)
  1242. let isCopy = ''
  1243. if (n > 0) isCopy = '.copy'
  1244. let v = '${' + groupId + '.' + name + '.' + this.itemShowParams.dataId + isCopy + '}'
  1245. let p_c = parseInt(c)
  1246. let p_r = parseInt(r)
  1247. // fieldType:1 横向 2 纵向
  1248. if (this.itemShowParams.fieldType == '2') {
  1249. p_r += parseInt(i)
  1250. p_c += n
  1251. } else {
  1252. p_c += parseInt(i)
  1253. p_r += n
  1254. }
  1255. if (n === 0) {
  1256. fieldList.push({
  1257. 'name': v,
  1258. 'r': p_r,
  1259. 'c': p_c
  1260. })
  1261. }
  1262. luckysheet.setCellValue(p_r, p_c, v)
  1263. }
  1264. }
  1265. let tableTem = {
  1266. 'item': this.chooseGroupItemList,
  1267. 'field': fieldList,
  1268. 'showType': this.itemShowParams
  1269. }
  1270. this.toolTable.push(tableTem)
  1271. this.spliceToolTable()
  1272. if (flag == 1) {
  1273. this.chooseGroupItemList = []
  1274. this.chooseDataGroup = null
  1275. this.chooseItemData = []
  1276. this.dialogGroupItemVisible = false
  1277. }
  1278. },
  1279. spliceToolTable() {
  1280. for (let i = this.toolTable.length - 1; i >= 0; i --) {
  1281. let info = this.toolTable[i]
  1282. if (info.field && info.field[0]) {
  1283. let v = luckysheet.getCellValue(info.field[0].r, info.field[0].c)
  1284. if (v != info.field[0].name) {
  1285. this.toolTable.splice(i, 1)
  1286. }
  1287. } else {
  1288. this.toolTable.splice(i, 1)
  1289. }
  1290. }
  1291. // 去重
  1292. this.toolTable = this.uniqueFunc(this.toolTable, 'dataId')
  1293. },
  1294. uniqueFunc(arr, uniId){
  1295. try {
  1296. const res = new Map()
  1297. return arr.filter((item) => !res.has(item.showType[uniId]) && res.set(item.showType[uniId], 1))
  1298. } catch (e) {
  1299. return arr
  1300. }
  1301. },
  1302. /** 柱状图 */
  1303. barEvent(type) {
  1304. let _this = this
  1305. // 当前选择范围
  1306. let rangeWithFlatten = luckysheet.getRangeWithFlatten()
  1307. if (!rangeWithFlatten || rangeWithFlatten.length <= 1) {
  1308. _this.$message({
  1309. message: '请选择多项单元格!',
  1310. type: 'warning'
  1311. })
  1312. return
  1313. }
  1314. this.chooseDataGroup = null
  1315. this.getDataGroupList()
  1316. if (!type) {
  1317. type = 'bar'
  1318. }
  1319. if (type == 'bar') {
  1320. this.chartConfigTitle = '柱状图配置'
  1321. } else if (type == 'line') {
  1322. this.chartConfigTitle = '折线图配置'
  1323. } else if (type == 'pie') {
  1324. this.chartConfigTitle = '饼状图配置'
  1325. }
  1326. this.barChartForm.type = type
  1327. // 显示柱状图配置信息
  1328. this.dialogBarChartVisible = true
  1329. },
  1330. /** 柱状图图表插入 */
  1331. insertBarChartForm(flag) {
  1332. if (this.barChartForm.showTitle == '1' && !this.barChartForm.title) {
  1333. this.$message({
  1334. message: '标题不能为空!',
  1335. type: 'warning'
  1336. })
  1337. return
  1338. }
  1339. if (!this.chooseDataGroup) {
  1340. this.$message({
  1341. message: '请选择数据组!',
  1342. type: 'warning'
  1343. })
  1344. return
  1345. }
  1346. if (this.chooseItemData.length == 0) {
  1347. this.$message({
  1348. message: '请选择数据组项!',
  1349. type: 'warning'
  1350. })
  1351. return
  1352. }
  1353. if ((this.barChartForm.legendType == '0' && this.chooseItemData.length > 1)
  1354. || (this.barChartForm.type == 'pie' && this.chooseItemData.length > 1)) {
  1355. this.$message({
  1356. message: '请选择一项数据项!',
  1357. type: 'warning'
  1358. })
  1359. return
  1360. }
  1361. // 图表参数
  1362. this.barChartOption = {
  1363. title: {
  1364. show: this.barChartForm.showTitle == '1',
  1365. text: this.barChartForm.title,
  1366. subtext: this.barChartForm.subtitle,
  1367. left: 'center'
  1368. },
  1369. tooltip: {},
  1370. legend: {
  1371. show: this.barChartForm.showLegend == '1',
  1372. orient: 'vertical',
  1373. left: 'left',
  1374. data: []
  1375. },
  1376. series: []
  1377. }
  1378. if (this.barChartForm.type != 'pie') {
  1379. this.barChartOption.xAxis = {
  1380. data: this.barChartForm.xAxisData
  1381. }
  1382. this.barChartOption.yAxis = {
  1383. }
  1384. // 图表图例类型:1 数据项 0 值
  1385. if (this.barChartForm.legendType == '1') {
  1386. let legendNameList = []
  1387. for (let i in this.chooseItemData) {
  1388. let name = this.chooseItemData[i].describe
  1389. name = name ? name : this.chooseItemData[i].itemName
  1390. legendNameList.push(name)
  1391. }
  1392. this.barChartOption.legend.data = legendNameList
  1393. } else {
  1394. this.barChartOption.legend.data = ['值', '运算值']
  1395. }
  1396. // this.barChartOption.legend.data = this.barChartForm.legendData
  1397. for (let i in this.barChartOption.legend.data) {
  1398. let seriesName = this.barChartOption.legend.data[i]
  1399. let dataList = []
  1400. for (let j in this.barChartForm.xAxisData) {
  1401. dataList.push(Math.ceil(Math.random() * 10))
  1402. }
  1403. let series = {
  1404. name: seriesName,
  1405. type: this.barChartForm.type,
  1406. data: dataList
  1407. }
  1408. this.barChartOption.series.push(series)
  1409. }
  1410. } else {
  1411. this.barChartOption.legend.data = null
  1412. let seriesName = this.barChartForm.legendData[0]
  1413. let dataList = []
  1414. for (let i in this.barChartForm.xAxisData) {
  1415. dataList.push({
  1416. value: Math.ceil(Math.random() * 10),
  1417. name: this.barChartForm.xAxisData[i]
  1418. })
  1419. }
  1420. let series = {
  1421. name: seriesName,
  1422. type: this.barChartForm.type,
  1423. radius: '50%',
  1424. data: dataList
  1425. }
  1426. this.barChartOption.series.push(series)
  1427. }
  1428. if (this.barChartForm.uid) {
  1429. this.delChartItem()
  1430. }
  1431. // 合并当前选择范围
  1432. luckysheet.setRangeMerge('all')
  1433. // 图表插入的 x、y 轴坐标获取
  1434. let range = luckysheet.getRange()
  1435. let x = range[0].column[0]
  1436. let y = range[0].row[0]
  1437. // 随机类名称
  1438. let clz = this.generateRandomKey('chart_clz')
  1439. let info = {
  1440. id: this.generateRandomKey('chart'), // 唯一 id
  1441. pos: [y, x], // eCharts 插入的单元格横、纵坐标
  1442. className: clz, // 给定eCharts容器的 className,自定义
  1443. chart: null, // 创建的 eCharts 实例
  1444. option: this.barChartOption // eCharts 图形配置选项
  1445. }
  1446. this.insertEChartTool(info, true, this.chooseItemData, this.barChartForm.legendType)
  1447. this.chooseDataGroup = null
  1448. if (flag == 0) {
  1449. setTimeout(() => {
  1450. this.chooseChartClz = clz
  1451. this.updateChartItem()
  1452. }, 1000)
  1453. return
  1454. }
  1455. this.$refs.barChartForm.resetFields()
  1456. this.chooseItemData = []
  1457. this.dialogBarChartVisible = false
  1458. },
  1459. /** 取消图表插入 */
  1460. cancelBarChartForm() {
  1461. this.$refs.barChartForm.resetFields()
  1462. this.chooseDataGroup = null
  1463. this.chooseItemData = []
  1464. this.dialogBarChartVisible = false
  1465. },
  1466. /** 图表修改 */
  1467. updateChartItem() {
  1468. this.getDataGroupList()
  1469. let chartInfo = {}
  1470. for (let i in this.toolChart) {
  1471. if (this.toolChart[i].info.className == this.chooseChartClz) {
  1472. chartInfo = this.toolChart[i]
  1473. }
  1474. }
  1475. let temp = chartInfo.info.option
  1476. this.barChartForm.uid = new Date().getTime()
  1477. this.barChartForm.title = temp.title.text
  1478. this.barChartForm.subtitle = temp.title.subtext
  1479. this.barChartForm.showTitle = temp.title.show ? '1' : '0'
  1480. this.barChartForm.showLegend = temp.legend.show ? '1' : '0'
  1481. this.barChartForm.type = temp.series[0].type
  1482. this.barChartForm.legendType = chartInfo.legendType
  1483. this.chooseItemData = chartInfo.item
  1484. this.chooseDataGroup = chartInfo.item[0].itemGroupId
  1485. this.dialogBarChartVisible = true
  1486. },
  1487. /** 图表删除 */
  1488. delChartItem() {
  1489. if (!this.chooseChartClz) {
  1490. return
  1491. }
  1492. $('.' + this.chooseChartClz).remove()
  1493. luckysheet.cancelRangeMerge()
  1494. for (let i in this.toolChart) {
  1495. if (this.toolChart[i].info.className == this.chooseChartClz) {
  1496. this.toolChart.splice(i, 1)
  1497. }
  1498. }
  1499. },
  1500. /** 自动存储报表信息 */
  1501. autoSaveReportInfo() {
  1502. let _this = this
  1503. if (this.reportInterval) {
  1504. clearInterval(this.reportInterval)
  1505. }
  1506. let time = setInterval(() => {
  1507. if (!this.templateId) {
  1508. let result = {
  1509. 'charts': _this.toolChart,
  1510. 'tables': _this.toolTable,
  1511. 'data': JSON.parse(getLuckysheetConfig())
  1512. }
  1513. localStorage.setItem(_this.reportTemplateItem, JSON.stringify(result))
  1514. console.log(new Date().getTime(), 'auto save report template success.')
  1515. }
  1516. }, 5 * 1000)
  1517. this.reportInterval = time
  1518. },
  1519. /** 获取模板报表信息 */
  1520. getTableTemplate(id, type) {
  1521. let _this = this
  1522. if (!id || id == -999) {
  1523. this.resetReport(true)
  1524. return
  1525. }
  1526. getTableTemplateById(id).then(res => {
  1527. if (!res.data) {
  1528. return
  1529. }
  1530. luckysheet.destroy()
  1531. this.templateName = res.data.templateName
  1532. this.templateId = res.data.id
  1533. this.templateVersion = res.data.version
  1534. let templateData = res.data.templateData
  1535. let option = JSON.parse(JSON.stringify(this.luckysheetOption))
  1536. option.data = JSON.parse(templateData).data
  1537. this.toolChart = JSON.parse(templateData).charts
  1538. this.toolTable = JSON.parse(templateData).tables
  1539. option.hook = {
  1540. workbookCreateAfter() {
  1541. for (let i in _this.toolChart) {
  1542. _this.insertEChartTool(_this.toolChart[i].info, false)
  1543. }
  1544. // 报表模板导出
  1545. if (type === 'export' && type === localStorage.getItem('__export')) {
  1546. localStorage.setItem('__export', '')
  1547. exportExcel(luckysheet.getAllSheets(), _this.templateName)
  1548. }
  1549. },
  1550. cellUpdated(r, c, newV, oldV) {
  1551. if (!(r === 49 && c === 0)) {
  1552. _this.forceRefreshLuckysheet()
  1553. }
  1554. }
  1555. }
  1556. luckysheet.create(option)
  1557. }).catch((e) => {
  1558. showAlertWin(this, e)
  1559. })
  1560. },
  1561. /** 数据值显示类型事件 */
  1562. showDataTypeEvent(val) {
  1563. if (val == 1) {
  1564. this.itemShowParams.valType = '2'
  1565. } else {
  1566. this.itemShowParams.valType = '1'
  1567. }
  1568. },
  1569. clearRt() {
  1570. this.reportTypeInputName = '所有'
  1571. this.reportTypeInputId = []
  1572. this.reportType = null
  1573. this.searchReportTxt = ''
  1574. },
  1575. /** 弹出层关闭事件 */
  1576. dialogClose(done) {
  1577. this.chooseDataGroup = null
  1578. this.chooseItemData = []
  1579. this.chooseGroupItemList = []
  1580. this.filterItemData = ''
  1581. this.clearRt()
  1582. this.commTemplate = null
  1583. if (typeof(done) === 'function') {
  1584. done()
  1585. } else {
  1586. this.dialogBarChartVisible = false
  1587. this.dialogDataItemVisible = false
  1588. this.dialogDataModelVisible = false
  1589. this.dialogCommReportVisible = false
  1590. this.dialogGroupItemVisible = false
  1591. }
  1592. },
  1593. /** 弹出层关闭事件 */
  1594. dialogItemClose(done) {
  1595. this.itemDataListByTree = []
  1596. this.filterItemData = ''
  1597. if (typeof(done) === 'function') {
  1598. done()
  1599. } else {
  1600. this.dialogDataItemVisible = false
  1601. }
  1602. },
  1603. /** 弹出层关闭事件 */
  1604. dialogDataModelClose() {
  1605. if (typeof(done) === 'function') {
  1606. done()
  1607. } else {
  1608. this.dialogDataModelVisible = false
  1609. }
  1610. },
  1611. /** 弹出层关闭事件 */
  1612. dialogBaseDataClose() {
  1613. this.chooseBaseData = null
  1614. if (typeof(done) === 'function') {
  1615. done()
  1616. } else {
  1617. this.dialogBaseDataVisible = false
  1618. }
  1619. },
  1620. /** 关闭右键弹出层 */
  1621. closeMenu() {
  1622. this.visibleChartMenu = false
  1623. }
  1624. }
  1625. }
  1626. </script>
  1627. <style rel="stylesheet/scss" lang="scss">
  1628. .cy-report-content {
  1629. width: 100%;
  1630. height: 100%;
  1631. padding-left: 6px;
  1632. color: #FFFFFF;
  1633. font-size: 14px;
  1634. background-color: #2c3e50;
  1635. display: flex;
  1636. flex-direction: row;
  1637. flex-wrap: nowrap;
  1638. }
  1639. .cy-div0 {
  1640. width: 30px;
  1641. height: 30px;
  1642. background: #767676;
  1643. }
  1644. .cy-div1 {
  1645. width: calc(100% - 90px);
  1646. height: 100%;
  1647. }
  1648. .cy-div2 {
  1649. width: 60px;
  1650. height: 100%;
  1651. z-index: 1;
  1652. background: #2c3e50;
  1653. overflow: auto;
  1654. }
  1655. .cy-report-ruler-unit {
  1656. width: 30px;
  1657. height: 30px;
  1658. display: flex;
  1659. justify-content: center;
  1660. align-items: center;
  1661. float: left;
  1662. border-right: 1px solid #2c3e50;
  1663. border-bottom: 1px solid #2c3e50;
  1664. }
  1665. .cy-ruler-top {
  1666. background: #767676;
  1667. width: 100%;
  1668. height: 30px;
  1669. }
  1670. .cy-box {
  1671. height: calc(100% - 50px);
  1672. width: calc(100% + 30px);
  1673. display: flex;
  1674. margin-left: -30px;
  1675. overflow: hidden;
  1676. .cy-ruler-left {
  1677. background: #767676;
  1678. width: 30px;
  1679. height: 100%;
  1680. }
  1681. .cy-box-main {
  1682. width: 100%;
  1683. height: 100%;
  1684. margin: 10px;
  1685. background: #FFFFFF;
  1686. }
  1687. }
  1688. .cy-chart-div {
  1689. display: flex;
  1690. flex-direction: column;
  1691. align-items: center;
  1692. cursor: pointer;
  1693. margin-bottom: 15px;
  1694. .svg-icon {
  1695. width: 50px;
  1696. height: 50px;
  1697. }
  1698. span {
  1699. font-size: 12px;
  1700. margin-top: -5px;
  1701. }
  1702. }
  1703. .label-title {
  1704. display: block !important;
  1705. .el-form-item__content {
  1706. width: calc(100% - 100px);
  1707. }
  1708. }
  1709. .el-transfer-panel {
  1710. width: 300px !important;
  1711. }
  1712. </style>