index.vue 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  1. <template>
  2. <div class="table-container">
  3. <el-table
  4. :data="data"
  5. :border="setBorder"
  6. v-bind="$attrs"
  7. row-key="id"
  8. stripe
  9. style="width: 100%"
  10. v-loading="config.loading"
  11. @selection-change="onSelectionChange"
  12. >
  13. <el-table-column type="selection" :reserve-selection="true" width="30" v-if="config.isSelection" />
  14. <el-table-column type="index" label="序号" width="60" v-if="config.isSerialNo" />
  15. <el-table-column
  16. v-for="(item, index) in setHeader"
  17. :key="index"
  18. show-overflow-tooltip
  19. :prop="item.key"
  20. :width="item.colWidth"
  21. :label="item.title"
  22. >
  23. <template v-slot="scope">
  24. <template v-if="item.type === 'image'">
  25. <el-image
  26. :style="{ width: `${item.width}px`, height: `${item.height}px` }"
  27. :src="scope.row[item.key]"
  28. :zoom-rate="1.2"
  29. :preview-src-list="[scope.row[item.key]]"
  30. preview-teleported
  31. fit="cover"
  32. />
  33. </template>
  34. <template v-else>
  35. {{ scope.row[item.key] }}
  36. </template>
  37. </template>
  38. </el-table-column>
  39. <el-table-column label="操作" width="100" v-if="config.isOperate">
  40. <template v-slot="scope">
  41. <el-popconfirm title="确定删除吗?" @confirm="onDelRow(scope.row)">
  42. <template #reference>
  43. <el-button text type="primary">删除</el-button>
  44. </template>
  45. </el-popconfirm>
  46. </template>
  47. </el-table-column>
  48. <template #empty>
  49. <el-empty description="暂无数据" />
  50. </template>
  51. </el-table>
  52. <div class="table-footer mt15">
  53. <el-pagination
  54. v-model:current-page="state.page.pageNum"
  55. v-model:page-size="state.page.pageSize"
  56. :pager-count="5"
  57. :page-sizes="[10, 20, 30]"
  58. :total="config.total"
  59. layout="total, sizes, prev, pager, next, jumper"
  60. background
  61. @size-change="onHandleSizeChange"
  62. @current-change="onHandleCurrentChange"
  63. >
  64. </el-pagination>
  65. <div class="table-footer-tool">
  66. <SvgIcon name="iconfont icon-dayin" :size="19" title="打印" @click="onPrintTable" />
  67. <SvgIcon name="iconfont icon-yunxiazai_o" :size="22" title="导出" @click="onImportTable" />
  68. <SvgIcon name="iconfont icon-shuaxin" :size="22" title="刷新" @click="onRefreshTable" />
  69. <el-popover
  70. placement="top-end"
  71. trigger="click"
  72. transition="el-zoom-in-top"
  73. popper-class="table-tool-popper"
  74. :width="300"
  75. :persistent="false"
  76. @show="onSetTable"
  77. >
  78. <template #reference>
  79. <SvgIcon name="iconfont icon-quanjushezhi_o" :size="22" title="设置" />
  80. </template>
  81. <template #default>
  82. <div class="tool-box">
  83. <el-tooltip content="拖动进行排序" placement="top-start">
  84. <SvgIcon name="fa fa-question-circle-o" :size="17" class="ml11" color="#909399" />
  85. </el-tooltip>
  86. <el-checkbox
  87. v-model="state.checkListAll"
  88. :indeterminate="state.checkListIndeterminate"
  89. class="ml10 mr1"
  90. label="列显示"
  91. @change="onCheckAllChange"
  92. />
  93. <el-checkbox v-model="getConfig.isSerialNo" class="ml12 mr1" label="序号" />
  94. <el-checkbox v-model="getConfig.isSelection" class="ml12 mr1" label="多选" />
  95. </div>
  96. <el-scrollbar>
  97. <div ref="toolSetRef" class="tool-sortable">
  98. <div class="tool-sortable-item" v-for="v in header" :key="v.key" :data-key="v.key">
  99. <i class="fa fa-arrows-alt handle cursor-pointer"></i>
  100. <el-checkbox v-model="v.isCheck" size="default" class="ml12 mr8" :label="v.title" @change="onCheckChange" />
  101. </div>
  102. </div>
  103. </el-scrollbar>
  104. </template>
  105. </el-popover>
  106. </div>
  107. </div>
  108. </div>
  109. </template>
  110. <script setup lang="ts" name="netxTable">
  111. import { reactive, computed, nextTick, ref } from 'vue'
  112. import { ElMessage } from 'element-plus'
  113. import printJs from 'print-js'
  114. import table2excel from 'js-table2excel'
  115. import Sortable from 'sortablejs'
  116. import { storeToRefs } from 'pinia'
  117. import { useThemeConfig } from '/@/stores/themeConfig'
  118. import '/@/theme/tableTool.scss'
  119. // 定义父组件传过来的值
  120. const props = defineProps({
  121. // 列表内容
  122. data: {
  123. type: Array<EmptyObjectType>,
  124. default: () => [],
  125. },
  126. // 表头内容
  127. header: {
  128. type: Array<EmptyObjectType>,
  129. default: () => [],
  130. },
  131. // 配置项
  132. config: {
  133. type: Object,
  134. default: () => {},
  135. },
  136. // 打印标题
  137. printName: {
  138. type: String,
  139. default: () => '',
  140. },
  141. })
  142. // 定义子组件向父组件传值/事件
  143. const emit = defineEmits(['delRow', 'pageChange', 'sortHeader'])
  144. // 定义变量内容
  145. const toolSetRef = ref()
  146. const storesThemeConfig = useThemeConfig()
  147. const { themeConfig } = storeToRefs(storesThemeConfig)
  148. const state = reactive({
  149. page: {
  150. pageNum: 1,
  151. pageSize: 10,
  152. },
  153. selectlist: [] as EmptyObjectType[],
  154. checkListAll: true,
  155. checkListIndeterminate: false,
  156. })
  157. // 设置边框显示/隐藏
  158. const setBorder = computed(() => {
  159. return props.config.isBorder ? true : false
  160. })
  161. // 获取父组件 配置项(必传)
  162. const getConfig = computed(() => {
  163. return props.config
  164. })
  165. // 设置 tool header 数据
  166. const setHeader = computed(() => {
  167. return props.header.filter((v) => v.isCheck)
  168. })
  169. // tool 列显示全选改变时
  170. const onCheckAllChange = <T>(val: T) => {
  171. if (val) props.header.forEach((v) => (v.isCheck = true))
  172. else props.header.forEach((v) => (v.isCheck = false))
  173. state.checkListIndeterminate = false
  174. }
  175. // tool 列显示当前项改变时
  176. const onCheckChange = () => {
  177. const headers = props.header.filter((v) => v.isCheck).length
  178. state.checkListAll = headers === props.header.length
  179. state.checkListIndeterminate = headers > 0 && headers < props.header.length
  180. }
  181. // 表格多选改变时,用于导出
  182. const onSelectionChange = (val: EmptyObjectType[]) => {
  183. state.selectlist = val
  184. }
  185. // 删除当前项
  186. const onDelRow = (row: EmptyObjectType) => {
  187. emit('delRow', row)
  188. }
  189. // 分页改变
  190. const onHandleSizeChange = (val: number) => {
  191. state.page.pageSize = val
  192. emit('pageChange', state.page)
  193. }
  194. // 分页改变
  195. const onHandleCurrentChange = (val: number) => {
  196. state.page.pageNum = val
  197. emit('pageChange', state.page)
  198. }
  199. // 搜索时,分页还原成默认
  200. const pageReset = () => {
  201. state.page.pageNum = 1
  202. state.page.pageSize = 10
  203. emit('pageChange', state.page)
  204. }
  205. // 打印
  206. const onPrintTable = () => {
  207. // https://printjs.crabbly.com/#documentation
  208. // 自定义打印
  209. let tableTh = ''
  210. let tableTrTd = ''
  211. let tableTd: any = {}
  212. // 表头
  213. props.header.forEach((v) => {
  214. tableTh += `<th class="table-th">${v.title}</th>`
  215. })
  216. // 表格内容
  217. props.data.forEach((val, key) => {
  218. if (!tableTd[key]) tableTd[key] = []
  219. props.header.forEach((v) => {
  220. if (v.type === 'text') {
  221. tableTd[key].push(`<td class="table-th table-center">${val[v.key]}</td>`)
  222. } else if (v.type === 'image') {
  223. tableTd[key].push(`<td class="table-th table-center"><img src="${val[v.key]}" style="width:${v.width}px;height:${v.height}px;"/></td>`)
  224. }
  225. })
  226. tableTrTd += `<tr>${tableTd[key].join('')}</tr>`
  227. })
  228. // 打印
  229. printJs({
  230. printable: `<div style=display:flex;flex-direction:column;text-align:center><h3>${props.printName}</h3></div><table border=1 cellspacing=0><tr>${tableTh}${tableTrTd}</table>`,
  231. type: 'raw-html',
  232. css: ['//at.alicdn.com/t/c/font_2298093_rnp72ifj3ba.css', '//unpkg.com/element-plus/dist/index.css'],
  233. style: `@media print{.mb15{margin-bottom:15px;}.el-button--small i.iconfont{font-size: 12px !important;margin-right: 5px;}}; .table-th{word-break: break-all;white-space: pre-wrap;}.table-center{text-align: center;}`,
  234. })
  235. }
  236. // 导出
  237. const onImportTable = () => {
  238. if (state.selectlist.length <= 0) return ElMessage.warning('请先选择要导出的数据')
  239. table2excel(props.header, state.selectlist, `${themeConfig.value.globalTitle} ${new Date().toLocaleString()}`)
  240. }
  241. // 刷新
  242. const onRefreshTable = () => {
  243. emit('pageChange', state.page)
  244. }
  245. // 设置
  246. const onSetTable = () => {
  247. nextTick(() => {
  248. const sortable = Sortable.create(toolSetRef.value, {
  249. handle: '.handle',
  250. dataIdAttr: 'data-key',
  251. animation: 150,
  252. onEnd: () => {
  253. const headerList: EmptyObjectType[] = []
  254. sortable.toArray().forEach((val: string) => {
  255. props.header.forEach((v) => {
  256. if (v.key === val) headerList.push({ ...v })
  257. })
  258. })
  259. emit('sortHeader', headerList)
  260. },
  261. })
  262. })
  263. }
  264. // 暴露变量
  265. defineExpose({
  266. pageReset,
  267. })
  268. </script>
  269. <style scoped lang="scss">
  270. .table-container {
  271. display: flex;
  272. flex-direction: column;
  273. .el-table {
  274. flex: 1;
  275. }
  276. .table-footer {
  277. display: flex;
  278. .table-footer-tool {
  279. flex: 1;
  280. display: flex;
  281. align-items: center;
  282. justify-content: flex-end;
  283. i {
  284. margin-right: 10px;
  285. cursor: pointer;
  286. color: var(--el-text-color-regular);
  287. &:last-of-type {
  288. margin-right: 0;
  289. }
  290. }
  291. }
  292. }
  293. }
  294. </style>