index.vue 57 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701
  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-and-zoom
  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-and-zoom
  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-and-zoom
  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 {
  384. getLuckysheetConfig,
  385. handleTree, showAlertMsgWin,
  386. showAlertWin,
  387. showLoading, showPromptWin,
  388. traverseNode,
  389. traverseVisible
  390. } from "@/utils/cqcy";
  391. import {getDictByKey} from "@/api/basic";
  392. import {exportExcel} from "@/utils/export";
  393. import {insertLuckysheetEChart} from "@/utils/luckysheettool";
  394. export default {
  395. name: 'index',
  396. components: {
  397. RulerView
  398. },
  399. data() {
  400. return {
  401. showReportData: true,
  402. dialogBarChartVisible: false,
  403. dialogDataItemVisible: false,
  404. dialogDataModelVisible: false,
  405. dialogCommReportVisible: false,
  406. dialogGroupItemVisible: false,
  407. dialogBaseDataVisible: false,
  408. isSelectAllItem: false,
  409. menuLeft: 0,
  410. menuTop: 0,
  411. visibleChartMenu: false,
  412. chooseChartClz: '',
  413. chartConfigTitle: '',
  414. barChartForm: {
  415. title: '',
  416. type: '',
  417. subtitle: '',
  418. showTitle: '1',
  419. showLegend: '1',
  420. legendType: '1',
  421. legendData: ['模板数据1', '模板数据2'],
  422. xAxisData: ['数据1', '数据2', '数据3']
  423. },
  424. barChartOption: {
  425. },
  426. itemShowParams: {
  427. fieldType: '1',
  428. valType: '2',
  429. valLine: 1,
  430. dataId: new Date().getTime()
  431. },
  432. baseDataList: [],
  433. chooseBaseData: null,
  434. dataGroupList: [],
  435. itemDataListByTree: [],
  436. itemDataListByGroupName: '',
  437. itemDataListByCalc: [],
  438. chooseGroupItemList: [],
  439. dataModelList: [],
  440. chooseDataModel: null,
  441. chooseDataItemIndex: null,
  442. bomCheckKey: 0,
  443. chooseDataGroup: null,
  444. chooseItemData: [],
  445. filterItemData: '',
  446. searchReportTxt: '',
  447. keyType: 'report_template',
  448. reportTypes: [],
  449. reportType: null,
  450. reportTypeInputName: '所有',
  451. reportTypeInputId: [],
  452. commTemplate: null,
  453. commTemplateList: [],
  454. templateName: '',
  455. templateId: '',
  456. templateVersion: '',
  457. reportTemplateItem: this.$store.getters.sessionName.REPORT_TEMPLATE_ITEM,
  458. reportInterval: null,
  459. toolChart: [],
  460. toolTable: [],
  461. luckysheetOption: {
  462. container: 'luckysheet', // 设定 DOM 容器的 id
  463. title: '报表模板', // 设定表格名称
  464. lang: 'zh', // 设定表格语言
  465. showinfobar: false, // 是否显示顶部信息栏
  466. showtoolbar: false, // 是否显示工具栏
  467. showtoolbarConfig: {
  468. paintFormat: true, //格式刷
  469. moreFormats: true, // 单元格格式
  470. font: true, // 字体
  471. fontSize: true, // 字号大小
  472. bold: true, // 粗体 (Ctrl+B)
  473. italic: true, // 斜体 (Ctrl+I)
  474. strikethrough: true, // 删除线 (Alt+Shift+5)
  475. underline: true, // 下划线 (Alt+Shift+6)
  476. textColor: true, // 文本颜色
  477. fillColor: true, // 单元格颜色
  478. border: true, // 边框
  479. mergeCell: true, // 合并单元格
  480. horizontalAlignMode: true, // 水平对齐方式
  481. verticalAlignMode: true, // 垂直对齐方式
  482. function: true, // 公式
  483. // image: true
  484. // chart: true
  485. },
  486. showsheetbar: false, // 是否显示底部 sheet 页按钮
  487. sheetFormulaBar: false, // 是否显示公式
  488. row: 100, // 是否显示底部 sheet 页按钮
  489. data: [{
  490. "name": "统计报表", //工作表名称
  491. }],
  492. cellRightClickConfig: { // 自定义配置单元格右击菜单
  493. copy: true, // 复制
  494. copyAs: false, // 复制为
  495. paste: true, // 粘贴
  496. insertRow: true, // 插入行
  497. insertColumn: true, // 插入列
  498. deleteRow: true, // 删除选中行
  499. deleteColumn: true, // 删除选中列
  500. deleteCell: false, // 删除单元格
  501. hideRow: false, // 隐藏选中行和显示选中行
  502. hideColumn: false, // 隐藏选中列和显示选中列
  503. rowHeight: true, // 行高
  504. columnWidth: true, // 列宽
  505. clear: false, // 清除内容
  506. matrix: false, // 矩阵操作选区
  507. sort: false, // 排序选区
  508. filter: false, // 筛选选区
  509. chart: true, // 图表生成
  510. image: false, // 插入图片
  511. link: false, // 插入链接
  512. data: false, // 数据验证
  513. cellFormat: false // 设置单元格格式
  514. },
  515. plugins: ['chart']
  516. }
  517. }
  518. },
  519. watch: {
  520. '$route.query.t': {
  521. handler(now, old) {
  522. this.initLuckysheet(this.$route.query.id, this.$route.query.type)
  523. }
  524. },
  525. filterItemData(val) {
  526. this.$refs.itemTree.filter(val)
  527. },
  528. visibleChartMenu(value) {
  529. if (value) {
  530. document.body.addEventListener('click', this.closeMenu)
  531. } else {
  532. document.body.removeEventListener('click', this.closeMenu)
  533. }
  534. }
  535. },
  536. mounted() {
  537. let _this = this
  538. $(function () {
  539. if (_this.showReportData) {
  540. let tempId = _this.$route.query.id
  541. let type = _this.$route.query.type
  542. _this.initLuckysheet(tempId, type)
  543. }
  544. })
  545. document.onkeyup = function (event) {
  546. let e = event || window.event || arguments.callee.caller.arguments[0]
  547. if (!e) return
  548. const {key, keyCode} = e
  549. if (keyCode === 46 || keyCode === 8) {
  550. _this.forceRefreshLuckysheet()
  551. }
  552. }
  553. },
  554. created() {
  555. let _this = this
  556. // 强制关闭自定义图表右键菜单
  557. document.body.oncontextmenu = (e) => {
  558. setTimeout(() => {
  559. if ($('#luckysheet-rightclick-menu') && 'block' == $('#luckysheet-rightclick-menu').css('display')) {
  560. _this.closeMenu()
  561. }
  562. }, 500)
  563. }
  564. },
  565. destroyed() {
  566. luckysheet.destroy()
  567. if (this.reportInterval) {
  568. clearInterval(this.reportInterval)
  569. }
  570. },
  571. methods: {
  572. /** 初始化操作 */
  573. initLuckysheet(tempId, type) {
  574. let _this = this
  575. if (type && type != 'export') {
  576. this.withTypeReportTemplate(type)
  577. return
  578. }
  579. if (tempId && tempId != -999) {
  580. _this.getTableTemplate(tempId, type)
  581. return
  582. }
  583. this.templateId = null
  584. this.templateVersion = null
  585. this.templateName = ''
  586. this.toolChart = []
  587. this.toolTable = []
  588. luckysheet.destroy()
  589. let locItem = localStorage.getItem(_this.reportTemplateItem)
  590. if (locItem && JSON.parse(locItem).data && Array.isArray(JSON.parse(locItem).data)) {
  591. let option = JSON.parse(JSON.stringify(_this.luckysheetOption))
  592. option.data = JSON.parse(locItem).data
  593. _this.toolChart = JSON.parse(locItem).charts
  594. _this.toolTable = JSON.parse(locItem).tables
  595. option.hook = {
  596. workbookCreateAfter() {
  597. for (let i in _this.toolChart) {
  598. _this.insertEChartTool(_this.toolChart[i].info, false)
  599. }
  600. _this.autoSaveReportInfo()
  601. },
  602. cellUpdated(r, c, newV, oldV) {
  603. if (!(r === 49 && c === 0)) {
  604. _this.forceRefreshLuckysheet()
  605. }
  606. }
  607. }
  608. luckysheet.create(option)
  609. } else {
  610. let option = JSON.parse(JSON.stringify(_this.luckysheetOption))
  611. option.hook = {
  612. workbookCreateAfter() {
  613. _this.autoSaveReportInfo()
  614. },
  615. cellUpdated(r, c, newV, oldV) {
  616. if (!(r === 49 && c === 0)) {
  617. _this.forceRefreshLuckysheet()
  618. }
  619. }
  620. }
  621. luckysheet.create(option)
  622. }
  623. },
  624. /** 实现类似刷新效果 */
  625. forceRefreshLuckysheet() {
  626. if (luckysheet) {
  627. luckysheet.setCellValue(49, 0, luckysheet.getCellValue(49, 0))
  628. }
  629. },
  630. /** 数据项全选事件 */
  631. selectAllItem() {
  632. const data = traverseNode(this.$refs.itemTree.store.root.childNodes)
  633. if (this.isSelectAllItem) {
  634. // this.$refs.itemTree.setCheckedNodes(this.itemDataListByTree)
  635. let arr = traverseVisible(data)
  636. let ids = Array.from(arr, ({ id }) => id)
  637. this.$refs.itemTree.setCheckedKeys(ids)
  638. } else {
  639. this.$refs.itemTree.setCheckedNodes([])
  640. }
  641. },
  642. /**数据项单项选择判断 */
  643. handleItemCheckChange(data, checked, indeterminate) {
  644. const tdata = traverseNode(this.$refs.itemTree.store.root.childNodes)
  645. let arr = traverseVisible(tdata)
  646. if (arr.length == this.$refs.itemTree.getCheckedNodes().length) {
  647. this.isSelectAllItem = true
  648. } else {
  649. this.isSelectAllItem = false
  650. }
  651. },
  652. /** 处理日期显示数字问题 */
  653. withDateData(excelData) {
  654. if (!excelData) {
  655. return
  656. }
  657. excelData.map((item) => {
  658. if (item) {
  659. item.map((ll) => {
  660. if (ll && ll.ct && ll.ct.t && ll.ct.t === 'd') {
  661. ll.m = ll.m,
  662. ll.v = ll.m,
  663. ll.ct = {
  664. fa: "@",
  665. t: "s",
  666. }
  667. }
  668. })
  669. }
  670. })
  671. },
  672. /** 保存模版信息 */
  673. saveReportTemplate(type) {
  674. let _templateName = this.templateName
  675. let _title = '保存'
  676. if (type == 'saveAs') {
  677. this.templateName = '副本_' + _templateName
  678. _title = '另存为'
  679. showPromptWin(this, _title, '请输入/确认报表模板名称', this.templateName, (val) => {
  680. if (!val) {
  681. return '模板名称不能为空'
  682. }
  683. if (val.length > 20) {
  684. return '模板名称必须在20字以内'
  685. }
  686. }, (value) => {
  687. this.saveReportTemplateData(type, value, _templateName, _title)
  688. }, () => {
  689. this.templateName = _templateName
  690. })
  691. return
  692. }
  693. if (!this.templateName || this.templateName.length > 20) {
  694. showAlertMsgWin(this, null, '模板名称不能为空且必须在20字以内!')
  695. return
  696. }
  697. this.saveReportTemplateData(type, this.templateName, _templateName, _title)
  698. },
  699. saveReportTemplateData(type, templateName, _templateName, _title) {
  700. const loading = showLoading(this, '保存中,请稍候···')
  701. let option = JSON.parse(JSON.stringify(this.luckysheetOption))
  702. let _data = JSON.parse(getLuckysheetConfig())
  703. let excelData = _data[0].data
  704. this.withDateData(excelData)
  705. this.spliceToolTable()
  706. let result = {
  707. 'charts': this.toolChart,
  708. 'tables': this.toolTable,
  709. 'option': option,
  710. 'data': _data
  711. }
  712. let data = {
  713. 'templateName': templateName,
  714. 'templateData': JSON.stringify(result)
  715. }
  716. if (this.templateId && type != 'saveAs') {
  717. data.id = this.templateId
  718. data.version = this.templateVersion
  719. updateReportTemplate(data).then(res => {
  720. loading.close()
  721. let msg = res.data ? '修改成功!' : '修改失败!'
  722. showAlertMsgWin(this, null, msg, () => {
  723. this.resetReport(true)
  724. this.getTableTemplate(this.$route.query.id, this.$route.query.type)
  725. this.$emit('refreshReportTemplateData', new Date().getTime())
  726. })
  727. }).catch((e) => {
  728. loading.close()
  729. this.templateName = _templateName
  730. showAlertWin(this, null, e)
  731. })
  732. } else {
  733. saveReportTemplate(data).then(res => {
  734. loading.close()
  735. let msg = res.data ? _title + '成功!' : _title + '失败!'
  736. showAlertMsgWin(this, null, msg)
  737. this.resetReport(true)
  738. this.$emit('refreshReportTemplateData', new Date().getTime())
  739. }).catch((e) => {
  740. loading.close()
  741. this.templateName = _templateName
  742. showAlertWin(this, null, e)
  743. })
  744. }
  745. },
  746. /** 重置报表信息 */
  747. resetReport(clearFlag) {
  748. let _this = this
  749. localStorage.removeItem(this.reportTemplateItem)
  750. if (clearFlag) {
  751. this.templateId = null
  752. this.templateVersion = null
  753. this.templateName = ''
  754. }
  755. this.toolChart = []
  756. this.toolTable = []
  757. luckysheet.destroy()
  758. let option = JSON.parse(JSON.stringify(this.luckysheetOption))
  759. option.hook = {
  760. workbookCreateAfter() {
  761. _this.autoSaveReportInfo()
  762. },
  763. cellUpdated(r, c, newV, oldV) {
  764. if (!(r === 49 && c === 0)) {
  765. _this.forceRefreshLuckysheet()
  766. }
  767. }
  768. }
  769. luckysheet.create(option)
  770. },
  771. /** 处理报表事件报表内容 */
  772. withTypeReportTemplate(type) {
  773. this.resetReport(true)
  774. if (type === 'import') { // 报表模板导入
  775. console.log('导入报表')
  776. this.$refs.importFileNode.dispatchEvent(new MouseEvent('click'))
  777. }
  778. },
  779. /** 导入报表 */
  780. loadExcel(evt) {
  781. console.log(evt)
  782. let _this = this
  783. const files = evt.target.files
  784. if (files == null || files.length == 0) {
  785. showAlertMsgWin(_this, null, '请选择文件!')
  786. return
  787. }
  788. console.log(files)
  789. // 获取文件名
  790. let name = files[0].name
  791. // 获取文件后缀
  792. let suffixArr = name.split("."),
  793. suffix = suffixArr[suffixArr.length - 1]
  794. if (suffix != 'xlsx') {
  795. showAlertMsgWin(_this, null, '只能导入xlsx格式文件!')
  796. return
  797. }
  798. const loading = showLoading(this, '文件导入中,请稍候···')
  799. // 转换导入的excel
  800. LuckyExcel.transformExcelToLucky(files[0], function (exportJson, luckysheetfile) {
  801. if (exportJson.sheets == null || exportJson.sheets.length == 0) {
  802. loading.close()
  803. showAlertMsgWin(_this, null, '无法读取excel文件的内容,当前不支持xls文件!')
  804. return
  805. }
  806. console.log('exportJson', exportJson)
  807. // 销毁之前的表格
  808. window.luckysheet.destroy()
  809. // 创建新的表格
  810. let option = JSON.parse(JSON.stringify(_this.luckysheetOption))
  811. option.data = exportJson.sheets
  812. option.title = exportJson.info.name
  813. option.data = exportJson.sheets
  814. option.hook = {
  815. workbookCreateAfter() {
  816. _this.autoSaveReportInfo()
  817. },
  818. cellUpdated(r, c, newV, oldV) {
  819. if (!(r === 49 && c === 0)) {
  820. _this.forceRefreshLuckysheet()
  821. }
  822. }
  823. }
  824. window.luckysheet.create(option)
  825. document.getElementsByClassName('import-file-node')[0].value = ''
  826. loading.close()
  827. })
  828. },
  829. /** 生成唯一 ID 字符串 */
  830. generateRandomKey(prefix) {
  831. if (prefix == null) {
  832. prefix = 'chart'
  833. }
  834. let userAgent = window.navigator.userAgent
  835. .replace(/[^a-zA-Z0-9]/g, '')
  836. .split('')
  837. let mid = ''
  838. for (let i = 0; i < 12; i++) {
  839. mid += userAgent[Math.round(Math.random() * (userAgent.length - 1))]
  840. }
  841. let time = new Date().getTime()
  842. return prefix + '_' + mid + '_' + time
  843. },
  844. /** 向 Excel 插入图表 */
  845. insertEChartTool(info, flag, item, legendType) {
  846. let _this = this
  847. let _self = this
  848. setTimeout(() => {
  849. const sheet = luckysheet.getLuckysheetfile()[0]
  850. let optionData = sheet.data
  851. try {
  852. insertLuckysheetEChart({
  853. selector: '#luckysheet',
  854. info,
  855. sheet,
  856. optionData,
  857. echarts,
  858. luckysheet,
  859. $,
  860. _self
  861. })
  862. } catch (e) {
  863. console.error(e)
  864. }
  865. // 保存图表信息
  866. if (flag) {
  867. let chart = {
  868. 'info': info,
  869. 'legendType': legendType,
  870. 'item': item
  871. }
  872. _this.toolChart.push(chart)
  873. }
  874. // 图表事件监听
  875. $('.' + info.className).mousedown(function(e) {
  876. if (3 == e.which) {
  877. _this.closeMenu()
  878. _this.menuTop = e.pageY
  879. _this.menuLeft = e.pageX / 2
  880. _this.visibleChartMenu = true
  881. _this.chooseChartClz = info.className
  882. setTimeout(() => {
  883. $('#luckysheet-rightclick-menu').css('display', 'none')
  884. }, 100)
  885. }
  886. })
  887. }, 200)
  888. },
  889. /** 获取右侧数据组列表 */
  890. getDataGroupList() {
  891. getAllItemGroup().then(res => {
  892. this.dataGroupList = res.data
  893. }).catch((e) => {
  894. showAlertWin(this, null, e)
  895. })
  896. },
  897. /** 选择数据源值改变事件 */
  898. dataSourceChange(val) {
  899. this.chooseDataGroup = val
  900. this.chooseItemData = []
  901. },
  902. /** 数据项搜索过滤 */
  903. filterItemDataNode(value, data) {
  904. if (!value) return true
  905. return data.itemName.indexOf(value) !== -1
  906. },
  907. /** 添加数据项 */
  908. addDataItem() {
  909. let id = this.chooseDataGroup
  910. if (!id) {
  911. showAlertMsgWin(this, null, '请选择数据组!')
  912. return
  913. }
  914. this.isSelectAllItem = false
  915. this.itemDataListByTree = []
  916. const loading = showLoading(this, '加载中,请稍候···')
  917. getTableItemGroupById(id).then(res => {
  918. loading.close()
  919. this.itemDataListByGroupName = res.data.groupName
  920. this.itemDataListByTree = res.data.itemList
  921. let tempList = this.chooseGroupItemList.length == 0 ? this.chooseItemData : this.chooseGroupItemList
  922. if (this.$refs.itemTree) this.$refs.itemTree.setCheckedKeys([])
  923. if (tempList.length > 0) {
  924. let idList = Array.from(tempList, ({ id }) => id)
  925. setTimeout(() => {
  926. if (this.$refs.itemTree) this.$refs.itemTree.setCheckedKeys(idList)
  927. if (this.itemDataListByTree.length == idList.length) {
  928. this.isSelectAllItem = true
  929. } else {
  930. this.isSelectAllItem = false
  931. }
  932. },100)
  933. }
  934. this.dialogDataItemVisible = true
  935. }).catch((e) => {
  936. loading.close()
  937. showAlertWin(this, null, e)
  938. })
  939. },
  940. /** 选择的数据项:第一步 */
  941. chooseItemTreeEvent() {
  942. let checkedNodes = this.$refs.itemTree.getCheckedNodes(false, true)
  943. if (checkedNodes.length == 0) {
  944. showAlertMsgWin(this, null, '请选择数据项!')
  945. return
  946. }
  947. let chooseList = []
  948. for (let i in checkedNodes) {
  949. let checkedNode = checkedNodes[i]
  950. if (checkedNode.children && checkedNode.children.length > 0) {
  951. continue
  952. }
  953. chooseList.push(checkedNode)
  954. }
  955. this.chooseItemData = chooseList
  956. this.dialogDataItemVisible = false
  957. if (this.dialogGroupItemVisible) {
  958. this.initGroupItem()
  959. }
  960. this.filterItemData = ''
  961. // this.getAllDataModel(chooseList)
  962. },
  963. initGroupItem() {
  964. let group = null
  965. for (let i = 0; i < this.dataGroupList.length; i ++) {
  966. if (this.dataGroupList[i].id == this.chooseDataGroup) {
  967. group = this.dataGroupList[i]
  968. break
  969. }
  970. }
  971. this.chooseGroupItemList = this.chooseGroupItemList.filter(function (item, index) {
  972. return item.itemGroupId != group.id;
  973. })
  974. for (let j = 0; j < this.chooseItemData.length; j ++) {
  975. this.chooseItemData[j].groupName = group.groupName
  976. this.chooseGroupItemList.push(this.chooseItemData[j])
  977. }
  978. let obj = {};
  979. this.chooseGroupItemList = this.chooseGroupItemList.reduce(function(item, next) {
  980. obj[next.id] ? '' : obj[next.id] = true && item.push(next)
  981. return item;
  982. }, [])
  983. },
  984. /** 查询所有数据模型 */
  985. getAllDataModel(itemList) {
  986. let loading = showLoading(this, '数据加载中,请稍候···')
  987. let params = {
  988. 'page': 1,
  989. 'limit': 1000
  990. }
  991. getAllDataModel(params).then(res => {
  992. loading.close()
  993. if (!res.data) {
  994. this.chooseItemData = itemList
  995. return
  996. }
  997. this.dataModelList = res.data.dataModelList
  998. this.itemDataListByCalc = JSON.parse(JSON.stringify(itemList))
  999. for (let i in this.itemDataListByCalc) {
  1000. let temp = this.itemDataListByCalc[i]
  1001. temp.rule = {
  1002. 'operationRule': '默认值'
  1003. }
  1004. }
  1005. }).catch((e) => {
  1006. loading.close()
  1007. showAlertWin(this, null, e)
  1008. })
  1009. },
  1010. /** 选择数据模型 */
  1011. handleChoose(index, row) {
  1012. this.chooseDataItemIndex = index
  1013. this.chooseDataModel = null
  1014. this.dialogDataModelVisible = true
  1015. },
  1016. /** 保存数据模型事件 */
  1017. saveDataModelEvent() {
  1018. if (!this.chooseDataModel) {
  1019. showAlertMsgWin(this, null, '请选择表达式!')
  1020. return
  1021. }
  1022. let item = {}
  1023. for (let i in this.dataModelList) {
  1024. if (this.chooseDataModel == this.dataModelList[i].id) {
  1025. item = this.dataModelList[i]
  1026. }
  1027. }
  1028. this.$nextTick(() => {
  1029. this.itemDataListByCalc[this.chooseDataItemIndex].rule = item
  1030. this.bomCheckKey ++
  1031. })
  1032. this.dialogDataModelVisible = false
  1033. },
  1034. /** 报表模板库 */
  1035. templateEvent() {
  1036. this.commTemplateList = []
  1037. this.reportTypes = []
  1038. this.reportType = null
  1039. // 获取报表类别
  1040. getDictByKey({
  1041. 'keyType': 'report_template'
  1042. }).then(res => {
  1043. this.reportTypes.push({
  1044. 'id': -1,
  1045. 'dictKey': '所有',
  1046. 'dictValue': ''
  1047. })
  1048. let t = handleTree(res.data, 'id', 'parentId', 'children')
  1049. this.reportTypes = this.reportTypes.concat(t)
  1050. this.dialogCommReportVisible = true
  1051. this.handleReportTypeChange([-1])
  1052. }).catch((e) => {
  1053. showAlertWin(this, null, e)
  1054. })
  1055. },
  1056. clickCascader() {
  1057. this.$refs.reportType.$el.click()
  1058. this.reportType = null
  1059. },
  1060. searchDictTree(nodes, searchKey) {
  1061. for (let _i = 0; _i < nodes.length; _i++) {
  1062. if (nodes[_i].id === searchKey) {
  1063. return nodes[_i].dictKey
  1064. } else {
  1065. if (nodes[_i].children && nodes[_i].children.length > 0) {
  1066. let res = this.searchDictTree(nodes[_i].children, searchKey)
  1067. if (res) {
  1068. return res
  1069. }
  1070. }
  1071. }
  1072. }
  1073. return null
  1074. },
  1075. handleReportTypeInputChange() {
  1076. let arr = this.reportTypeInputId.length == 0 ? [-1] : this.reportTypeInputId
  1077. this.handleReportTypeChange(arr)
  1078. },
  1079. /** 报表行业选择 */
  1080. handleReportTypeNodeChange(val) {
  1081. this.handleReportTypeChange(val)
  1082. },
  1083. /** 报表行业选择 */
  1084. handleReportTypeChange(val) {
  1085. if (!val || val.length == 0) {
  1086. return
  1087. }
  1088. let nameArr = []
  1089. val.forEach(v => {
  1090. let name = this.searchDictTree(this.reportTypes, v)
  1091. nameArr.push(name)
  1092. })
  1093. this.reportTypeInputName = nameArr.join(' / ')
  1094. this.reportTypeInputId = val
  1095. let id = val[val.length - 1]
  1096. if (id === -1) {
  1097. this.reportTypeInputName = '所有'
  1098. this.reportTypeInputId = []
  1099. id = null
  1100. }
  1101. let params = {
  1102. 'keyType': this.keyType,
  1103. 'dictId': id
  1104. }
  1105. if (this.searchReportTxt && this.searchReportTxt.trim()) {
  1106. params.templateName = this.searchReportTxt.trim()
  1107. }
  1108. getSysTableTemplate(params).then(res => {
  1109. if (!res.data) {
  1110. return
  1111. }
  1112. this.commTemplateList = res.data
  1113. }).catch((e) => {
  1114. showAlertWin(this, null, e)
  1115. })
  1116. },
  1117. /** 选择模板报表 */
  1118. commReportEvent(flag) {
  1119. let _this = this
  1120. if (!_this.commTemplate) {
  1121. showAlertMsgWin(_this, null, '请选择模板!')
  1122. return
  1123. }
  1124. const loading = showLoading(_this, '加载中,请稍候···')
  1125. getSysTableTemplateById(_this.commTemplate.id).then(res => {
  1126. if (!res || !res.data) {
  1127. loading.close()
  1128. return
  1129. }
  1130. let locItem = res.data.templateData
  1131. luckysheet.destroy()
  1132. let option = JSON.parse(JSON.stringify(_this.luckysheetOption))
  1133. option.data = JSON.parse(locItem).data
  1134. this.toolChart = JSON.parse(locItem).charts
  1135. this.toolTable = JSON.parse(locItem).tables
  1136. option.hook = {
  1137. workbookCreateAfter() {
  1138. loading.close()
  1139. if (flag == 1) _this.dialogClose()
  1140. },
  1141. cellUpdated(r, c, newV, oldV) {
  1142. if (!(r === 49 && c === 0)) {
  1143. _this.forceRefreshLuckysheet()
  1144. }
  1145. }
  1146. }
  1147. luckysheet.create(option)
  1148. }).catch((e) => {
  1149. loading.close()
  1150. showAlertWin(_this, null, e)
  1151. })
  1152. },
  1153. /** 数据项 */
  1154. dataEvent(type) {
  1155. let rangeWithFlatten = luckysheet.getRangeWithFlatten()
  1156. if (!rangeWithFlatten || rangeWithFlatten.length != 1) {
  1157. showAlertMsgWin(this, null, '请选择一项单元格!')
  1158. return
  1159. }
  1160. // 基础数据项
  1161. if (type == 1) {
  1162. this.getBaseDataItem()
  1163. return
  1164. }
  1165. // 初始化数据显示方式
  1166. this.itemShowParams = {
  1167. fieldType: '1',
  1168. valType: '2',
  1169. valLine: 1,
  1170. dataId: new Date().getTime()
  1171. }
  1172. this.chooseDataGroup = null
  1173. this.getDataGroupList()
  1174. this.dialogGroupItemVisible = true
  1175. },
  1176. /** 查询字典表:基础数据项 */
  1177. getBaseDataItem() {
  1178. getDictByKey({
  1179. 'keyType': 'base_data_item'
  1180. }).then(res => {
  1181. this.dialogBaseDataVisible = true
  1182. this.baseDataList = res.data
  1183. }).catch((e) => {
  1184. showAlertWin(this, null, e)
  1185. })
  1186. },
  1187. /** 基础数据项插入 */
  1188. baseItemEvent(flag) {
  1189. if (!this.chooseBaseData) {
  1190. showAlertMsgWin(this, null, '请选择基础数据项!')
  1191. return
  1192. }
  1193. let rangeWithFlatten = luckysheet.getRangeWithFlatten()
  1194. let c = rangeWithFlatten[0].c
  1195. let r = rangeWithFlatten[0].r
  1196. luckysheet.setCellValue(r, c, this.chooseBaseData)
  1197. if (flag == 1) this.dialogBaseDataClose()
  1198. },
  1199. /** 动态数据项插入 */
  1200. itemEvent(flag) {
  1201. if (this.chooseGroupItemList.length == 0) {
  1202. showAlertMsgWin(this, null, '请选择数据组项!')
  1203. return
  1204. }
  1205. let rangeWithFlatten = luckysheet.getRangeWithFlatten()
  1206. let c = rangeWithFlatten[0].c
  1207. let r = rangeWithFlatten[0].r
  1208. let fieldList = []
  1209. for (let n = 0; n < this.itemShowParams.valLine; n ++) {
  1210. for (let i in this.chooseGroupItemList) {
  1211. let groupId = this.chooseGroupItemList[i].itemGroupId
  1212. let name = this.chooseGroupItemList[i].itemName
  1213. name = name.substring(name.lastIndexOf('.') + 1)
  1214. let isCopy = ''
  1215. if (n > 0) isCopy = '.copy'
  1216. let v = '${' + groupId + '.' + name + '.' + this.itemShowParams.dataId + isCopy + '}'
  1217. let p_c = parseInt(c)
  1218. let p_r = parseInt(r)
  1219. // fieldType:1 横向 2 纵向
  1220. if (this.itemShowParams.fieldType == '2') {
  1221. p_r += parseInt(i)
  1222. p_c += n
  1223. } else {
  1224. p_c += parseInt(i)
  1225. p_r += n
  1226. }
  1227. if (n === 0) {
  1228. fieldList.push({
  1229. 'name': v,
  1230. 'r': p_r,
  1231. 'c': p_c
  1232. })
  1233. }
  1234. luckysheet.setCellValue(p_r, p_c, v)
  1235. }
  1236. }
  1237. let tableTem = {
  1238. 'item': this.chooseGroupItemList,
  1239. 'field': fieldList,
  1240. 'showType': this.itemShowParams
  1241. }
  1242. this.toolTable.push(tableTem)
  1243. this.spliceToolTable()
  1244. if (flag == 1) {
  1245. this.chooseGroupItemList = []
  1246. this.chooseDataGroup = null
  1247. this.chooseItemData = []
  1248. this.dialogGroupItemVisible = false
  1249. }
  1250. },
  1251. spliceToolTable() {
  1252. for (let i = this.toolTable.length - 1; i >= 0; i --) {
  1253. let info = this.toolTable[i]
  1254. if (info.field && info.field[0]) {
  1255. let v = luckysheet.getCellValue(info.field[0].r, info.field[0].c)
  1256. if (v != info.field[0].name) {
  1257. this.toolTable.splice(i, 1)
  1258. }
  1259. } else {
  1260. this.toolTable.splice(i, 1)
  1261. }
  1262. }
  1263. // 去重
  1264. this.toolTable = this.uniqueFunc(this.toolTable, 'dataId')
  1265. },
  1266. uniqueFunc(arr, uniId){
  1267. try {
  1268. const res = new Map()
  1269. return arr.filter((item) => !res.has(item.showType[uniId]) && res.set(item.showType[uniId], 1))
  1270. } catch (e) {
  1271. return arr
  1272. }
  1273. },
  1274. /** 柱状图 */
  1275. barEvent(type) {
  1276. let _this = this
  1277. // 当前选择范围
  1278. let rangeWithFlatten = luckysheet.getRangeWithFlatten()
  1279. if (!rangeWithFlatten || rangeWithFlatten.length <= 1) {
  1280. showAlertMsgWin(_this, null, '请选择多项单元格!')
  1281. return
  1282. }
  1283. this.chooseDataGroup = null
  1284. this.getDataGroupList()
  1285. if (!type) {
  1286. type = 'bar'
  1287. }
  1288. if (type == 'bar') {
  1289. this.chartConfigTitle = '柱状图配置'
  1290. } else if (type == 'line') {
  1291. this.chartConfigTitle = '折线图配置'
  1292. } else if (type == 'pie') {
  1293. this.chartConfigTitle = '饼状图配置'
  1294. }
  1295. this.barChartForm.type = type
  1296. // 显示柱状图配置信息
  1297. this.dialogBarChartVisible = true
  1298. },
  1299. /** 柱状图图表插入 */
  1300. insertBarChartForm(flag) {
  1301. if (this.barChartForm.showTitle == '1' && !this.barChartForm.title) {
  1302. showAlertMsgWin(this, null, '标题不能为空!')
  1303. return
  1304. }
  1305. if (!this.chooseDataGroup) {
  1306. showAlertMsgWin(this, null, '请选择数据组!')
  1307. return
  1308. }
  1309. if (this.chooseItemData.length == 0) {
  1310. showAlertMsgWin(this, null, '请选择数据组项!')
  1311. return
  1312. }
  1313. if ((this.barChartForm.legendType == '0' && this.chooseItemData.length > 1)
  1314. || (this.barChartForm.type == 'pie' && this.chooseItemData.length > 1)) {
  1315. showAlertMsgWin(this, null, '请选择一项数据项!')
  1316. return
  1317. }
  1318. // 图表参数
  1319. this.barChartOption = {
  1320. title: {
  1321. show: this.barChartForm.showTitle == '1',
  1322. text: this.barChartForm.title,
  1323. subtext: this.barChartForm.subtitle,
  1324. left: 'center'
  1325. },
  1326. tooltip: {},
  1327. legend: {
  1328. show: this.barChartForm.showLegend == '1',
  1329. orient: 'vertical',
  1330. left: 'left',
  1331. data: []
  1332. },
  1333. series: []
  1334. }
  1335. if (this.barChartForm.type != 'pie') {
  1336. this.barChartOption.xAxis = {
  1337. data: this.barChartForm.xAxisData
  1338. }
  1339. this.barChartOption.yAxis = {
  1340. }
  1341. // 图表图例类型:1 数据项 0 值
  1342. if (this.barChartForm.legendType == '1') {
  1343. let legendNameList = []
  1344. for (let i in this.chooseItemData) {
  1345. let name = this.chooseItemData[i].describe
  1346. name = name ? name : this.chooseItemData[i].itemName
  1347. legendNameList.push(name)
  1348. }
  1349. this.barChartOption.legend.data = legendNameList
  1350. } else {
  1351. this.barChartOption.legend.data = ['值', '运算值']
  1352. }
  1353. // this.barChartOption.legend.data = this.barChartForm.legendData
  1354. for (let i in this.barChartOption.legend.data) {
  1355. let seriesName = this.barChartOption.legend.data[i]
  1356. let dataList = []
  1357. for (let j in this.barChartForm.xAxisData) {
  1358. dataList.push(Math.ceil(Math.random() * 10))
  1359. }
  1360. let series = {
  1361. name: seriesName,
  1362. type: this.barChartForm.type,
  1363. data: dataList
  1364. }
  1365. this.barChartOption.series.push(series)
  1366. }
  1367. } else {
  1368. this.barChartOption.legend.data = null
  1369. let seriesName = this.barChartForm.legendData[0]
  1370. let dataList = []
  1371. for (let i in this.barChartForm.xAxisData) {
  1372. dataList.push({
  1373. value: Math.ceil(Math.random() * 10),
  1374. name: this.barChartForm.xAxisData[i]
  1375. })
  1376. }
  1377. let series = {
  1378. name: seriesName,
  1379. type: this.barChartForm.type,
  1380. radius: '50%',
  1381. data: dataList
  1382. }
  1383. this.barChartOption.series.push(series)
  1384. }
  1385. if (this.barChartForm.uid) {
  1386. this.delChartItem()
  1387. }
  1388. // 合并当前选择范围
  1389. luckysheet.setRangeMerge('all')
  1390. // 图表插入的 x、y 轴坐标获取
  1391. let range = luckysheet.getRange()
  1392. let x = range[0].column[0]
  1393. let y = range[0].row[0]
  1394. // 随机类名称
  1395. let clz = this.generateRandomKey('chart_clz')
  1396. let info = {
  1397. id: this.generateRandomKey('chart'), // 唯一 id
  1398. pos: [y, x], // eCharts 插入的单元格横、纵坐标
  1399. className: clz, // 给定eCharts容器的 className,自定义
  1400. chart: null, // 创建的 eCharts 实例
  1401. option: this.barChartOption // eCharts 图形配置选项
  1402. }
  1403. this.insertEChartTool(info, true, this.chooseItemData, this.barChartForm.legendType)
  1404. this.chooseDataGroup = null
  1405. if (flag == 0) {
  1406. setTimeout(() => {
  1407. this.chooseChartClz = clz
  1408. this.updateChartItem()
  1409. }, 1000)
  1410. return
  1411. }
  1412. this.$refs.barChartForm.resetFields()
  1413. this.chooseItemData = []
  1414. this.dialogBarChartVisible = false
  1415. },
  1416. /** 取消图表插入 */
  1417. cancelBarChartForm() {
  1418. this.$refs.barChartForm.resetFields()
  1419. this.chooseDataGroup = null
  1420. this.chooseItemData = []
  1421. this.dialogBarChartVisible = false
  1422. },
  1423. /** 图表修改 */
  1424. updateChartItem() {
  1425. this.getDataGroupList()
  1426. let chartInfo = {}
  1427. for (let i in this.toolChart) {
  1428. if (this.toolChart[i].info.className == this.chooseChartClz) {
  1429. chartInfo = this.toolChart[i]
  1430. }
  1431. }
  1432. let temp = chartInfo.info.option
  1433. this.barChartForm.uid = new Date().getTime()
  1434. this.barChartForm.title = temp.title.text
  1435. this.barChartForm.subtitle = temp.title.subtext
  1436. this.barChartForm.showTitle = temp.title.show ? '1' : '0'
  1437. this.barChartForm.showLegend = temp.legend.show ? '1' : '0'
  1438. this.barChartForm.type = temp.series[0].type
  1439. this.barChartForm.legendType = chartInfo.legendType
  1440. this.chooseItemData = chartInfo.item
  1441. this.chooseDataGroup = chartInfo.item[0].itemGroupId
  1442. this.dialogBarChartVisible = true
  1443. },
  1444. /** 图表删除 */
  1445. delChartItem() {
  1446. if (!this.chooseChartClz) {
  1447. return
  1448. }
  1449. $('.' + this.chooseChartClz).remove()
  1450. luckysheet.cancelRangeMerge()
  1451. for (let i in this.toolChart) {
  1452. if (this.toolChart[i].info.className == this.chooseChartClz) {
  1453. this.toolChart.splice(i, 1)
  1454. }
  1455. }
  1456. },
  1457. /** 自动存储报表信息 */
  1458. autoSaveReportInfo() {
  1459. let _this = this
  1460. if (this.reportInterval) {
  1461. clearInterval(this.reportInterval)
  1462. }
  1463. let time = setInterval(() => {
  1464. if (!this.templateId) {
  1465. let result = {
  1466. 'charts': _this.toolChart,
  1467. 'tables': _this.toolTable,
  1468. 'data': JSON.parse(getLuckysheetConfig())
  1469. }
  1470. localStorage.setItem(_this.reportTemplateItem, JSON.stringify(result))
  1471. console.log(new Date().getTime(), 'auto save report template success.')
  1472. }
  1473. }, 5 * 1000)
  1474. this.reportInterval = time
  1475. },
  1476. /** 获取模板报表信息 */
  1477. getTableTemplate(id, type) {
  1478. let _this = this
  1479. if (!id || id == -999) {
  1480. this.resetReport(true)
  1481. return
  1482. }
  1483. getTableTemplateById(id).then(res => {
  1484. if (!res.data) {
  1485. return
  1486. }
  1487. luckysheet.destroy()
  1488. this.templateName = res.data.templateName
  1489. this.templateId = res.data.id
  1490. this.templateVersion = res.data.version
  1491. let templateData = res.data.templateData
  1492. let option = JSON.parse(JSON.stringify(this.luckysheetOption))
  1493. option.data = JSON.parse(templateData).data
  1494. this.toolChart = JSON.parse(templateData).charts
  1495. this.toolTable = JSON.parse(templateData).tables
  1496. option.hook = {
  1497. workbookCreateAfter() {
  1498. for (let i in _this.toolChart) {
  1499. _this.insertEChartTool(_this.toolChart[i].info, false)
  1500. }
  1501. // 报表模板导出
  1502. if (type === 'export' && type === localStorage.getItem('__export')) {
  1503. localStorage.setItem('__export', '')
  1504. exportExcel(luckysheet.getAllSheets(), _this.templateName)
  1505. }
  1506. },
  1507. cellUpdated(r, c, newV, oldV) {
  1508. if (!(r === 49 && c === 0)) {
  1509. _this.forceRefreshLuckysheet()
  1510. }
  1511. }
  1512. }
  1513. luckysheet.create(option)
  1514. }).catch((e) => {
  1515. showAlertWin(this, null, e)
  1516. })
  1517. },
  1518. /** 数据值显示类型事件 */
  1519. showDataTypeEvent(val) {
  1520. if (val == 1) {
  1521. this.itemShowParams.valType = '2'
  1522. } else {
  1523. this.itemShowParams.valType = '1'
  1524. }
  1525. },
  1526. clearRt() {
  1527. this.reportTypeInputName = '所有'
  1528. this.reportTypeInputId = []
  1529. this.reportType = null
  1530. this.searchReportTxt = ''
  1531. },
  1532. /** 弹出层关闭事件 */
  1533. dialogClose(done) {
  1534. this.chooseDataGroup = null
  1535. this.chooseItemData = []
  1536. this.chooseGroupItemList = []
  1537. this.filterItemData = ''
  1538. this.clearRt()
  1539. this.commTemplate = null
  1540. if (typeof(done) === 'function') {
  1541. done()
  1542. } else {
  1543. this.dialogBarChartVisible = false
  1544. this.dialogDataItemVisible = false
  1545. this.dialogDataModelVisible = false
  1546. this.dialogCommReportVisible = false
  1547. this.dialogGroupItemVisible = false
  1548. }
  1549. },
  1550. /** 弹出层关闭事件 */
  1551. dialogItemClose(done) {
  1552. this.itemDataListByTree = []
  1553. this.filterItemData = ''
  1554. if (typeof(done) === 'function') {
  1555. done()
  1556. } else {
  1557. this.dialogDataItemVisible = false
  1558. }
  1559. },
  1560. /** 弹出层关闭事件 */
  1561. dialogDataModelClose() {
  1562. if (typeof(done) === 'function') {
  1563. done()
  1564. } else {
  1565. this.dialogDataModelVisible = false
  1566. }
  1567. },
  1568. /** 弹出层关闭事件 */
  1569. dialogBaseDataClose() {
  1570. this.chooseBaseData = null
  1571. if (typeof(done) === 'function') {
  1572. done()
  1573. } else {
  1574. this.dialogBaseDataVisible = false
  1575. }
  1576. },
  1577. /** 关闭右键弹出层 */
  1578. closeMenu() {
  1579. this.visibleChartMenu = false
  1580. }
  1581. }
  1582. }
  1583. </script>
  1584. <style rel="stylesheet/scss" lang="scss">
  1585. .cy-report-content {
  1586. width: 100%;
  1587. height: 100%;
  1588. padding-left: 6px;
  1589. color: #FFFFFF;
  1590. font-size: 14px;
  1591. background-color: #2c3e50;
  1592. display: flex;
  1593. flex-direction: row;
  1594. flex-wrap: nowrap;
  1595. }
  1596. .cy-div0 {
  1597. width: 30px;
  1598. height: 30px;
  1599. background: #767676;
  1600. }
  1601. .cy-div1 {
  1602. width: calc(100% - 90px);
  1603. height: 100%;
  1604. }
  1605. .cy-div2 {
  1606. width: 60px;
  1607. height: 100%;
  1608. z-index: 1;
  1609. background: #2c3e50;
  1610. overflow: auto;
  1611. }
  1612. .cy-report-ruler-unit {
  1613. width: 30px;
  1614. height: 30px;
  1615. display: flex;
  1616. justify-content: center;
  1617. align-items: center;
  1618. float: left;
  1619. border-right: 1px solid #2c3e50;
  1620. border-bottom: 1px solid #2c3e50;
  1621. }
  1622. .cy-ruler-top {
  1623. background: #767676;
  1624. width: 100%;
  1625. height: 30px;
  1626. }
  1627. .cy-box {
  1628. height: calc(100% - 50px);
  1629. width: calc(100% + 30px);
  1630. display: flex;
  1631. margin-left: -30px;
  1632. overflow: hidden;
  1633. .cy-ruler-left {
  1634. background: #767676;
  1635. width: 30px;
  1636. height: 100%;
  1637. }
  1638. .cy-box-main {
  1639. width: 100%;
  1640. height: 100%;
  1641. margin: 10px;
  1642. background: #FFFFFF;
  1643. }
  1644. }
  1645. .cy-chart-div {
  1646. display: flex;
  1647. flex-direction: column;
  1648. align-items: center;
  1649. cursor: pointer;
  1650. margin-bottom: 15px;
  1651. .svg-icon {
  1652. width: 50px;
  1653. height: 50px;
  1654. }
  1655. span {
  1656. font-size: 12px;
  1657. margin-top: -5px;
  1658. }
  1659. }
  1660. .label-title {
  1661. display: block !important;
  1662. .el-form-item__content {
  1663. width: calc(100% - 100px);
  1664. }
  1665. }
  1666. .el-transfer-panel {
  1667. width: 300px !important;
  1668. }
  1669. </style>