index.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  1. <template>
  2. <div class="table-container">
  3. <div class="flex justify-between table-header">
  4. <div class="flex-center">
  5. <!-- 详情里面的表格左侧带有筛选区域的插槽 -->
  6. <slot name="left-search"></slot>
  7. <el-button v-if="handleBtnRole('add')" type="primary" @click="$emit('handleAdd')">
  8. + 添加
  9. </el-button>
  10. <el-button v-if="handleBtnRole('import')" @click="handleImport">
  11. 导入
  12. </el-button>
  13. <el-button v-if="handleBtnRole('export')" :loading="exportLoading" @click="handleExport">
  14. 导出
  15. </el-button>
  16. <!-- 左侧其他按钮插槽 -->
  17. <slot name="left"></slot>
  18. </div>
  19. <div class="flex-center">
  20. <!-- 右侧区域插槽 -->
  21. <slot name="right"></slot>
  22. <el-button link @click="openShowList" v-if="rightBtn.includes('list')">
  23. <SvgIcon iconClass="list-check" /> 列表
  24. </el-button>
  25. <!-- 如果是邮件的表格的话,分页组件放在这里 -->
  26. <pagination style="margin: 0;" v-if="isPagination && isEmail" layout="total, sizes, prev, pager, next"
  27. v-model:total="total" v-model:page="queryParams.pageIndex" v-model:limit="queryParams.pageSize"
  28. @pagination="getList" />
  29. </div>
  30. </div>
  31. <el-table
  32. ref="singleTableRef"
  33. v-loading="loading"
  34. :data="pageData"
  35. height="100%"
  36. @selection-change="selectionChange"
  37. @current-change="handleCurrentChange"
  38. :highlight-current-row="isCurrentChange"
  39. @sort-change="sortChange"
  40. >
  41. <el-table-column type="selection" width="40" align="center" v-if="isCheckbox" :selectable="selectable" />
  42. <template v-for="(item, index) in tableColumn">
  43. <el-table-column v-if="item.show" :key="index" :label="item.label" :prop="item.prop" :width="item.width"
  44. :sortable="item.sortable ? 'custom' : false" :show-overflow-tooltip="item.showOverflowTooltip" :sort-orders="['descending', 'ascending', null]">
  45. <!-- 自定义列模板 -->
  46. <template #default="scope" v-if="item.render || item.toFixed">
  47. <template v-if="item.toFixed">
  48. <span>{{ scope.row[item.prop] ? scope.row[item.prop].toFixed(2) : '' }}</span>
  49. </template>
  50. <component v-if="item.label != '操作'" :is="item.render" :column="scope.row" :index="scope.$index" />
  51. <template v-else>
  52. <component :is="item.render" :column="scope.row" :index="scope.$index" />
  53. <el-button link v-if="handleBtnRole('edit')" type="primary"
  54. @click="$emit('handleEdit', scope.row)">编辑</el-button>
  55. <el-button link v-if="handleBtnRole('remove')" type="danger"
  56. @click="handleDel(scope.row[tableParams.keyId])">删除</el-button>
  57. </template>
  58. </template>
  59. <template #header v-if="item.headerQuestionFilled">
  60. <div class="flex">
  61. <el-popover placement="top" :width="item.headerQuestionFilled.width"
  62. :content="item.headerQuestionFilled.content">
  63. <template #reference>
  64. <div>
  65. <span>{{ item.label }}</span>
  66. <i-ep-QuestionFilled style="margin-left: 5px;vertical-align: text-bottom;" />
  67. </div>
  68. </template>
  69. </el-popover>
  70. </div>
  71. </template>
  72. </el-table-column>
  73. </template>
  74. </el-table>
  75. <pagination v-if="isPagination && !isEmail" layout="total, sizes, prev, pager, next" v-model:total="total"
  76. v-model:page="queryParams.pageIndex" v-model:limit="queryParams.pageSize" @pagination="getList" />
  77. </div>
  78. <!-- 右侧列表弹窗 -->
  79. <listSort ref="listSortRef" :defaultColumn="defaultColumn" :cloumnCode="tableParams.cloumnCode"
  80. @changeList="changeList" />
  81. <!-- 导入弹窗 -->
  82. <fileImport ref="fileImportRef" @refresh="getData" />
  83. </template>
  84. <script setup lang="ts">
  85. import { tableTypes, columnTypes } from './types'
  86. import SvgIcon from '../SvgIcon/index.vue'
  87. import listSort from './components/listSort.vue'
  88. import fileImport from './components/fileImport.vue'
  89. import { listExport } from '@/api/public'
  90. import { AxiosResponse } from 'axios';
  91. import auth from '@/plugins/auth'
  92. import downloadPlugins from '@/plugins/download'
  93. import { userHobit } from '@/store/modules/userHobit'
  94. const userHobitStore = userHobit()
  95. const emits = defineEmits(['handleAdd', 'export', 'selectionChange', 'handleEdit', 'currentChange', 'sortChange'])
  96. const router = useRouter();
  97. const props = defineProps({
  98. // 表格参数,包括column、url跟查询参数,必传
  99. tableParams: {
  100. type: Object,
  101. required: true
  102. },
  103. // 是否有复选框
  104. isCheckbox: {
  105. type: Boolean,
  106. default: true
  107. },
  108. // 按钮权限,默认空数组,根据权限可传新增、导入、导出、编辑、删除按钮进来
  109. btnRole: {
  110. type: Array as () => { type: string, hasPermi: string }[] || [],
  111. default: () => { return [] }
  112. },
  113. // 右侧按钮区域,默认列表,没有时传空数组
  114. rightBtn: {
  115. type: Array as () => string[] || [],
  116. default: () => {
  117. return ['list']
  118. }
  119. },
  120. // 是否有操作栏
  121. isOperation: {
  122. type: Boolean,
  123. default: true
  124. },
  125. // 是否分页
  126. isPagination: {
  127. type: Boolean,
  128. default: true
  129. },
  130. // 是否是邮件里面使用,是的话分页组件位置有变化
  131. isEmail: {
  132. type: Boolean,
  133. default: false
  134. },
  135. // 是否有单选功能,有的话点击高亮
  136. isCurrentChange: {
  137. type: Boolean,
  138. default: false
  139. },
  140. // 表格数据
  141. tableData: {
  142. type: Array
  143. }
  144. })
  145. const loading = ref(false)
  146. const exportLoading = ref(false)
  147. const pageList = ref([])
  148. const pageData = computed(() => {
  149. return props.tableData || pageList.value
  150. })
  151. const queryParams = reactive<PageQuery>({
  152. pageIndex: 1,
  153. pageSize: 10,
  154. commonQueryOrderBy: undefined,
  155. sortMethod: undefined
  156. })
  157. const deepClone = (source: any) => {
  158. let targetObj: any = source.constructor === Array ? [] : {};
  159. for (let key in source) {
  160. if (source.hasOwnProperty(key)) {
  161. // 只拷贝自身的属性,不拷贝原型的属性
  162. if (source[key] && typeof source[key] === "object") {
  163. // 元素为引用数据类型
  164. targetObj[key] = source[key].constructor === Array ? [] : {};
  165. targetObj[key] = deepClone(source[key]);
  166. } else {
  167. // 元素为基本数据类型
  168. targetObj[key] = source[key];
  169. }
  170. }
  171. }
  172. return targetObj;
  173. }
  174. const total = ref(0)
  175. // const tableColumn = ref(props.tableParams.tableColumn)
  176. const tableColumn = ref()
  177. const { tableParams } = toRefs(props)
  178. const defaultColumn = deepClone(tableParams.value.tableColumn)
  179. userHobitStore.sortCloumn(tableParams.value.cloumnCode, props.tableParams.tableColumn).then(sortListColumn => {
  180. tableColumn.value = sortListColumn
  181. })
  182. // console.log(tableColumn.value, "sortListColumn")
  183. // 查询表格数据
  184. const getList = () => {
  185. let params = props.tableParams.queryParams
  186. if(props.isPagination) {
  187. params = Object.assign(queryParams, props.tableParams.queryParams)
  188. }
  189. params.scopeMenuId = props.tableParams.menuId
  190. loading.value = true
  191. props.tableParams.listFn(params).then((res: AxiosResponse) => {
  192. pageList.value = res.result.records;
  193. total.value = Number(res.result.total) || 0;
  194. }).finally(() => {
  195. loading.value = false;
  196. })
  197. }
  198. const selectable = (row) => {
  199. if(props.tableParams.selectableKey) {
  200. return row[props.tableParams.selectableKey] == props.tableParams.selectableValue
  201. }
  202. return true
  203. }
  204. // 按钮权限判断
  205. const handleBtnRole = (type: string) => {
  206. let detailRole = props.btnRole.find(a => a.type == type)
  207. if(detailRole){
  208. return auth.hasPermi(detailRole.hasPermi)
  209. }
  210. return false
  211. }
  212. // 复选框数据变化
  213. const selectionData = ref([])
  214. const selectionChange = (val: any) => {
  215. selectionData.value = val
  216. emits('selectionChange', selectionData.value)
  217. }
  218. const handleCurrentChange = (val) => {
  219. emits('currentChange', val)
  220. }
  221. // 右侧列表操作
  222. const listSortRef = ref()
  223. const openShowList = () => {
  224. listSortRef.value.open(tableColumn.value)
  225. }
  226. const changeList = (list: any) => {
  227. tableColumn.value = list
  228. }
  229. // 导入操作
  230. const fileImportRef = ref()
  231. const handleImport = () => {
  232. fileImportRef.value.open(props.tableParams.importCode, props.tableParams.importType)
  233. }
  234. // 导出操作
  235. const handleExport = () => {
  236. if(pageData.value.length == 0) {
  237. ElMessage.error('无数据可导出')
  238. return false
  239. }
  240. let params = JSON.parse(JSON.stringify(props.tableParams.queryParams))
  241. params.pageSize = 9999
  242. params.pageIndex = 1
  243. exportLoading.value = true
  244. listExport({ templateCode: props.tableParams.exportCode, ...params }).then(res => {
  245. try {
  246. downloadPlugins.download('export', res)
  247. }catch {
  248. ElMessage.error('导出失败,请稍后再试')
  249. }
  250. exportLoading.value = false
  251. })
  252. }
  253. // 删除操作
  254. const handleDel = (id: string | number) => {
  255. ElMessageBox.confirm(
  256. '是否确认删除当前数据?',
  257. '确认提示',
  258. { type: 'warning', confirmButtonText: '提交', }
  259. ).then(() => {
  260. props.tableParams.delFn!(id).then((res: AxiosResponse) => {
  261. if(res.success) {
  262. ElMessage.success('删除成功')
  263. getData()
  264. }else{
  265. ElMessage.error('删除失败')
  266. }
  267. })
  268. }).catch(() => {})
  269. }
  270. const getData = () => {
  271. queryParams.pageIndex = 1
  272. getList()
  273. }
  274. const singleTableRef = ref()
  275. const setCurrent = (row) => {
  276. singleTableRef.value?.setCurrentRow(row)
  277. }
  278. const sortChange = (val) => {
  279. let props = val.prop.indexOf('.') != -1 ? val.prop.split('.')[1] : val.prop
  280. queryParams.commonQueryOrderBy = val.order ? props : undefined
  281. queryParams.sortMethod = val.order ? (val.order == 'ascending' ? 'asc' : 'desc') : undefined
  282. if(!val.order && tableParams.value.commonQueryOrderBy){
  283. queryParams.commonQueryOrderBy = tableParams.value.commonQueryOrderBy
  284. queryParams.sortMethod = tableParams.value.sortMethod || 'desc'
  285. }
  286. getList()
  287. }
  288. onMounted(() => {
  289. if(!props.tableData) {
  290. nextTick(() => {
  291. if(tableParams.value.commonQueryOrderBy) {
  292. queryParams.commonQueryOrderBy = tableParams.value.commonQueryOrderBy
  293. queryParams.sortMethod = tableParams.value.sortMethod || 'desc'
  294. }
  295. getList()
  296. })
  297. }
  298. })
  299. defineExpose({
  300. getData,
  301. setCurrent,
  302. handleExport
  303. })
  304. </script>
  305. <style lang="scss" scoped>
  306. .table-container{
  307. display: flex;
  308. flex-direction: column;
  309. height: 100%;
  310. }
  311. .table-header{
  312. :deep(.el-button){
  313. padding: 5px 9px;
  314. min-width: 56px;
  315. border-radius: 2px;
  316. }
  317. }
  318. .el-table{
  319. border: var(--el-table-border);
  320. border-bottom: 0;
  321. margin-top: 15px;
  322. :deep(.el-table__header){
  323. th{
  324. background-color: #F8FAFC;
  325. color: #262626;
  326. }
  327. }
  328. }
  329. .pagination{
  330. padding: 30px 0 0;
  331. :deep(.el-pagination){
  332. justify-content: flex-end;
  333. }
  334. }
  335. </style>