index.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431
  1. <script lang="ts" setup>
  2. import { ref, onMounted, reactive, watch, inject } from "vue";
  3. import { useRouter, useRoute } from 'vue-router'
  4. import { post, get } from "@/utils/request";
  5. import { GET_STRUCT_BY_TABLE_NAME, ADD_OR_UPDATE_REPORT_FORM } from "../api"
  6. import { generateSql, rangeGetSql } from "../function"
  7. import { ElMessage, ElMessageBox } from 'element-plus'
  8. import { VueDraggable } from 'vue-draggable-plus'
  9. import rangeFilter from "./rangeFilter.vue";
  10. import previewTable from "./previewTable.vue";
  11. import { confirmAction } from "@/utils/tools";
  12. interface collapseItemList {
  13. busObject: any,
  14. columnList: any[],
  15. }
  16. interface CollapseItem {
  17. data: {
  18. label: string;
  19. };
  20. list: collapseItemList;
  21. }
  22. const globalPopup = inject<GlobalPopup>('globalPopup')
  23. const router = useRouter()
  24. const route = useRoute()
  25. const reportName = ref('')
  26. const addEditData = JSON.parse(sessionStorage.getItem('reportJson') || '{}')
  27. const activeNames = ref([])
  28. const collapseList = ref<CollapseItem[]>([])
  29. // 拖拽表格参数
  30. const tableData = ref<any>([]);
  31. const tableColumns = ref<any>([{}, {}, {}]);
  32. // 全部Loading的Visable
  33. const allLoading = reactive({
  34. collapseListLoading: false,
  35. saveLoading: false
  36. })
  37. const allVisable = reactive({
  38. previewTableVisable: false
  39. })
  40. // ref
  41. const rangeFilterRef = ref<InstanceType<typeof rangeFilter> | null>(null);
  42. const previewTableRef = ref<InstanceType<typeof previewTable> | null>(null);
  43. // 保存预览的判断以及生成
  44. function preMethod(): any {
  45. const rangeFilterData = rangeFilterRef.value?.getRangeData()
  46. if(!tableData.value.length) {
  47. ElMessage.warning('请选择列')
  48. return
  49. }
  50. if(rangeFilterData.length) {
  51. for(let i in rangeFilterData) {
  52. if(!rangeFilterData[i].twoDisable && !rangeFilterData[i].filterValueTwo) {
  53. ElMessage.warning('请选择第二个条件')
  54. return
  55. }
  56. }
  57. }
  58. const tabeName = tableData.value.map((item: any) => item.tableName)
  59. const tableNames = addEditData.mindMapJSON.selectNodes[0].data.tblName
  60. if(!tabeName.includes(tableNames)) {
  61. ElMessage.warning('请选择主表数据')
  62. return
  63. }
  64. const list = rangeFilterData.filter((item: any) => item.filterValueThree)
  65. const SQL = generateSql(tableData.value, rangeFilterData, addEditData)
  66. const rangeSQL = rangeGetSql(rangeFilterData, addEditData)
  67. const finalSql = `${SQL} ${list.length ? rangeSQL : 'where 1=1'}`
  68. return {
  69. finalSql,
  70. formTransConditionJson: '',
  71. }
  72. }
  73. // 保存页面
  74. function saveReport() {
  75. const rangeFilterData = rangeFilterRef.value?.getRangeData()
  76. const { finalSql } = preMethod()
  77. // 需要转存的 JSON
  78. const formJson = {
  79. addEditData,
  80. tableData: tableData.value,
  81. rangeFilterData: rangeFilterData,
  82. }
  83. const formFieldHead = tableData.value.map((item: any) => {
  84. return {
  85. tableName: item.tableName,
  86. columnName: item.columnName,
  87. columnVal: item.columnComment
  88. }
  89. })
  90. const { reportFormName, description, userAccessList = [], visibleRangeData = [], parentStoreId, privilege, id } = addEditData.addFormVal
  91. const formVal: any = {
  92. reportFormName,
  93. description,
  94. parentStoreId: parentStoreId ? parentStoreId : 0,
  95. privilege: privilege ? 1 : 2,
  96. executeSql: finalSql,
  97. userIds: visibleRangeData.filter((item: any) => item.isUser).map((item: any) => item.id).join(','),
  98. departmentIds: visibleRangeData.filter((item: any) => !item.isUser).map((item: any) => item.id).join(','),
  99. formJson: JSON.stringify(formJson),
  100. formFieldHead: JSON.stringify(formFieldHead),
  101. }
  102. if(id) {
  103. formVal.id = id
  104. }
  105. allLoading.saveLoading = true
  106. post(ADD_OR_UPDATE_REPORT_FORM, { ...formVal }).then(_res => {
  107. globalPopup?.showSuccess('保存成功')
  108. sessionStorage.removeItem('reportJson');
  109. sessionStorage.removeItem('editReportJson');
  110. router.go(-2)
  111. }).finally(() => {
  112. allLoading.saveLoading = false
  113. })
  114. }
  115. // 预览页面
  116. function preview() {
  117. const rangeFilterData = rangeFilterRef.value?.getRangeData()
  118. const { finalSql } = preMethod()
  119. allVisable.previewTableVisable = true
  120. setTimeout(() => {
  121. previewTableRef.value?.initializedData(finalSql, rangeFilterData, tableData.value)
  122. }, 100)
  123. }
  124. // 关闭页面
  125. function closePage() {
  126. ElMessageBox.confirm('要保存当前修改吗', '', {
  127. distinguishCancelAndClose: true,
  128. confirmButtonText: '确定',
  129. cancelButtonText: '取消',
  130. type: 'warning',
  131. }).then(() => {
  132. saveReport()
  133. }).catch((action) => {
  134. if(action === 'cancel') {
  135. sessionStorage.removeItem('reportJson');
  136. sessionStorage.removeItem('editReportJson');
  137. router.go(-2)
  138. }
  139. })
  140. }
  141. // 分组
  142. function grouping(index: number) {
  143. const val = tableData.value[index]?.singleColumnOperation?.grouping
  144. if(!tableData.value[index].singleColumnOperation) {
  145. tableData.value[index].singleColumnOperation = {}
  146. tableData.value[index].singleColumnOperation.grouping = val ? false : true
  147. } else {
  148. tableData.value[index].singleColumnOperation.grouping = val ? false : true
  149. }
  150. }
  151. // 统计
  152. function statistics(index: number) {
  153. const val = tableData.value[index]?.singleColumnOperation?.statistics
  154. if(!tableData.value[index].singleColumnOperation) {
  155. tableData.value[index].singleColumnOperation = {}
  156. tableData.value[index].singleColumnOperation.statistics = val ? false : true
  157. } else {
  158. tableData.value[index].singleColumnOperation.statistics = val ? false : true
  159. }
  160. }
  161. // 修改名称
  162. function changeName(index: number) {
  163. ElMessageBox.prompt('修改名称', '', {
  164. confirmButtonText: '确定',
  165. cancelButtonText: '取消',
  166. inputPattern: /^.+$/,
  167. inputErrorMessage: '请输入',
  168. }).then(({ value }) => {
  169. tableData.value[index].columnComment = value
  170. })
  171. }
  172. // 删除列
  173. function deleteColumn(index: number) {
  174. tableData.value.splice(index, 1);
  175. }
  176. // 添加列
  177. function addTable(row: any) {
  178. const { tableName = '', columnName = '', dictCode = '', columnComment = '' } = row.data
  179. const index = row.newIndex;
  180. console.log(tableData.value, '<====== tableData.value')
  181. if (tableData.value.filter((item: any) => `${item.tableName}.${item.columnName}.${item.dictCode ? item.dictCode : ''}` === `${tableName}.${columnName}.${dictCode}`).length >= 2) {
  182. ElMessage.warning('该列已存在')
  183. tableData.value.splice(index, 1);
  184. return
  185. }
  186. if(tableData.value.filter((item: any) => item.columnComment === columnComment).length >= 2) {
  187. ElMessage.warning(`【${columnComment}】列名称已存在、请先修改列名称`)
  188. tableData.value.splice(index, 1);
  189. return
  190. }
  191. }
  192. function listOnClone(clonedItem: any) {
  193. clonedItem.prop = `col_${Date.now()}`; // 赋予唯一 key
  194. }
  195. async function getCollapseListData() {
  196. const { mindMapJSON = {} } = addEditData
  197. const selectNodes = mindMapJSON?.selectNodes
  198. allLoading.collapseListLoading = true
  199. for (let i in selectNodes) {
  200. console.log(selectNodes, '<===== selectNodes')
  201. const { dictCode = '', tblName = '' } = selectNodes[i].data
  202. const res = await post(GET_STRUCT_BY_TABLE_NAME, { tableName: tblName })
  203. res.data?.columnList.forEach((item: any) => {
  204. item.dictCode = dictCode
  205. });
  206. selectNodes[i].list = res.data
  207. }
  208. collapseList.value = selectNodes
  209. allLoading.collapseListLoading = false
  210. }
  211. function goBack() {
  212. router.go(-1)
  213. }
  214. function displayBackData() {
  215. const json = JSON.parse(sessionStorage.getItem('editReportJson') || '{}')
  216. const { rangeFilterData = [], tableData: tableDataList = [] } = json
  217. if(rangeFilterData.length) {
  218. setTimeout(() => {
  219. rangeFilterRef.value?.setRangeData(rangeFilterData, false)
  220. }, 300)
  221. }
  222. if(tableDataList.length) {
  223. setTimeout(() => {
  224. tableData.value = tableDataList
  225. }, 300)
  226. }
  227. }
  228. onMounted(() => {
  229. console.log(addEditData)
  230. const { reportFormName } = addEditData.addFormVal
  231. reportName.value = reportFormName
  232. getCollapseListData()
  233. displayBackData()
  234. setTimeout(() => {
  235. rangeFilterRef.value?.setIsItAnEditor()
  236. }, 100)
  237. })
  238. </script>
  239. <template>
  240. <div class="dragEdit flex flex-row h-full bg-white rounded-md w-full">
  241. <div class="w-[15%] h-full overflow-auto scroll-bar p-4" v-loading="allLoading.collapseListLoading">
  242. <el-collapse v-model="activeNames">
  243. <el-collapse-item :title="item.data.newLabel || item.data.label" :name="index" v-for="(item, index) in (collapseList as any)" :key="index">
  244. <VueDraggable v-model="item.list.columnList" :animation="150"
  245. :group="{ name: 'people', pull: 'clone', put: false }" :sort="false"
  246. class="flex flex-col gap-2 p-4 w-300px bg-gray-500/5 rounded" @clone="listOnClone">
  247. <div v-for="listItem in item.list.columnList" class="cursor-all-scroll px-3 py-2 bg-white">
  248. {{ listItem.columnComment }}
  249. </div>
  250. </VueDraggable>
  251. </el-collapse-item>
  252. </el-collapse>
  253. </div>
  254. <div class="w-[85%] h-full">
  255. <div class="h-full w-full flex flex-col p-3 box-border">
  256. <div class="dragEdit-header">
  257. <div class="flex justify-between">
  258. <div><el-button @click="goBack()">返回上一级</el-button></div>
  259. <div class="text-[20px] font-bold">{{ reportName }}</div>
  260. <div>
  261. <el-button @click="closePage()">关闭</el-button>
  262. <el-button @click="preview()">预览</el-button>
  263. <el-button type="primary" @click="saveReport()" :loading="allLoading.saveLoading">保存</el-button>
  264. </div>
  265. </div>
  266. <div class="range mt-3 mb-3">
  267. <div class="flex items-center pb-3 font-bold text-[16px]">
  268. 数据范围
  269. </div>
  270. <rangeFilter ref="rangeFilterRef" />
  271. </div>
  272. </div>
  273. <!-- 中间 -->
  274. <div class="flex justify-between items-center mb-3">
  275. <div class="flex items-center">
  276. <div class="font-bold text-[16px]">报表示例</div>
  277. </div>
  278. <div>点击顶部预览按钮可查看全部数据</div>
  279. </div>
  280. <!-- 表格 -->
  281. <div class="flex-1 relative border-2">
  282. <div class="table-container scroll-bar">
  283. <VueDraggable v-model="tableData" group="people" target=".sort-target" @add="addTable">
  284. <div class="absolute top-0 left-0 text-center text-[#999] w-full text-[20px] p-3" v-if="!tableData.length">
  285. <div class="border-2 border-dashed py-2">这里是表格头部区域,请将左侧字段拖入此区域生成报表</div>
  286. </div>
  287. <table class="table table-striped">
  288. <thead class="thead-dark">
  289. <tr class="sort-target">
  290. <th class="cursor-move" v-for="(header, headerIndex) in tableData" :key="header.value">
  291. <div class="w-full flex justify-between items-center">
  292. <div>{{ header.columnComment }}</div>
  293. <el-dropdown>
  294. <span class="el-dropdown-link">
  295. <el-icon color="#075985">
  296. <InfoFilled />
  297. </el-icon>
  298. </span>
  299. <template #dropdown>
  300. <el-dropdown-menu>
  301. <el-dropdown-item v-if="['varchar', 'text'].includes(header.dataType)">
  302. <el-text :underline="false" type="primary" @click="grouping(headerIndex)">
  303. {{ header?.singleColumnOperation?.grouping ? '取消分组' : '分组' }}
  304. </el-text>
  305. </el-dropdown-item>
  306. <el-dropdown-item v-if="['decimal'].includes(header.dataType)">
  307. <el-text :underline="false" type="primary" @click="statistics(headerIndex)">
  308. {{ header?.singleColumnOperation?.statistics ? '取消统计' : '统计' }}
  309. </el-text>
  310. </el-dropdown-item>
  311. <el-dropdown-item>
  312. <el-text :underline="false" type="primary" @click="changeName(headerIndex)">
  313. 修改名称
  314. </el-text>
  315. </el-dropdown-item>
  316. <el-dropdown-item>
  317. <el-text :underline="false" type="danger"
  318. @click="deleteColumn(headerIndex)">删除列</el-text>
  319. </el-dropdown-item>
  320. </el-dropdown-menu>
  321. </template>
  322. </el-dropdown>
  323. </div>
  324. </th>
  325. </tr>
  326. </thead>
  327. <tbody>
  328. <tr v-for="item in tableColumns" :key="item.name">
  329. <td v-for="header in tableData" :key="header.value">
  330. 示例数据
  331. </td>
  332. </tr>
  333. </tbody>
  334. </table>
  335. </VueDraggable>
  336. </div>
  337. </div>
  338. </div>
  339. </div>
  340. <!-- 全屏预览 -->
  341. <el-dialog v-model="allVisable.previewTableVisable" fullscreen top="40vh" width="70%" draggable>
  342. <previewTable ref="previewTableRef"></previewTable>
  343. </el-dialog>
  344. </div>
  345. </template>
  346. <style lang="scss" scoped>
  347. .dragEdit {
  348. .table {
  349. display: table;
  350. width: 100%;
  351. font-size: 15px;
  352. border-collapse: collapse;
  353. margin: 0 0;
  354. overflow-x: auto;
  355. }
  356. tr {
  357. background-color: #fff;
  358. border-top: 1px solid #e2e2e3;
  359. transition: background-color .5s;
  360. }
  361. th {
  362. text-align: left;
  363. font-size: 14px;
  364. font-weight: 600;
  365. color: rgba(60, 60, 67, .78);
  366. background-color: #f6f6f7;
  367. border: 1px solid #e2e2e3;
  368. padding: 8px 16px;
  369. min-width: 240px;
  370. }
  371. td {
  372. padding: 8px 16px;
  373. font-size: 14px;
  374. min-width: 200px;
  375. }
  376. .cursor-move {
  377. cursor: move;
  378. }
  379. .table-container {
  380. overflow-x: auto;
  381. white-space: nowrap;
  382. width: 100%;
  383. height: 100%;
  384. }
  385. }
  386. </style>