index.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
  1. <template>
  2. <div class="table-container">
  3. <div class="table-header mb15">
  4. <div>
  5. <slot name="command"></slot>
  6. </div>
  7. <div v-loading="state.importLoading" class="table-footer-tool">
  8. <SvgIcon name="iconfont icon-shuaxin" :size="22" title="刷新" @click="onRefreshTable" />
  9. <el-dropdown v-if="!config.hideExport" trigger="click">
  10. <SvgIcon name="iconfont icon-yunxiazai_o" :size="22" title="导出" />
  11. <template #dropdown>
  12. <el-dropdown-menu>
  13. <el-dropdown-item @click="onImportTable">导出本页数据</el-dropdown-item>
  14. <el-dropdown-item @click="onImportTableAll">导出全部数据</el-dropdown-item>
  15. </el-dropdown-menu>
  16. </template>
  17. </el-dropdown>
  18. <el-popover placement="top-end" trigger="click" transition="el-zoom-in-top" popper-class="table-tool-popper" :width="300" :persistent="false" @show="onSetTable">
  19. <template #reference>
  20. <SvgIcon name="iconfont icon-quanjushezhi_o" :size="22" title="设置" />
  21. </template>
  22. <template #default>
  23. <div class="tool-box">
  24. <el-tooltip content="拖动进行排序" placement="top-start">
  25. <SvgIcon name="fa fa-question-circle-o" :size="17" class="ml11" color="#909399" />
  26. </el-tooltip>
  27. <el-checkbox v-model="state.checkListAll" :indeterminate="state.checkListIndeterminate" class="ml10 mr1" label="列显示" @change="onCheckAllChange" />
  28. <el-checkbox v-model="getConfig.isSerialNo" class="ml12 mr1" label="序号" />
  29. <el-checkbox v-if="getConfig.showSelection" v-model="getConfig.isSelection" class="ml12 mr1" label="多选" />
  30. </div>
  31. <el-scrollbar>
  32. <div ref="toolSetRef" class="tool-sortable">
  33. <div class="tool-sortable-item" v-for="v in columns" :key="v.prop" v-show="!v.hideCheck && !v.fixed" :data-key="v.prop">
  34. <i class="fa fa-arrows-alt handle cursor-pointer"></i>
  35. <el-checkbox v-model="v.isCheck" size="default" class="ml12 mr8" :label="v.label" @change="onCheckChange" />
  36. </div>
  37. </div>
  38. </el-scrollbar>
  39. </template>
  40. </el-popover>
  41. </div>
  42. </div>
  43. <el-table
  44. ref="tableRef"
  45. :data="state.data"
  46. :border="setBorder"
  47. :stripe="setStripe"
  48. v-bind="$attrs"
  49. row-key="id"
  50. default-expand-all
  51. style="width: 100%"
  52. v-loading="state.loading"
  53. :default-sort="defaultSort"
  54. @selection-change="onSelectionChange"
  55. @sort-change="sortChange"
  56. >
  57. <el-table-column type="selection" :reserve-selection="true" :width="30" v-if="config.isSelection && config.showSelection" />
  58. <el-table-column type="index" label="序号" align="center" :width="60" v-if="config.isSerialNo" />
  59. <el-table-column v-for="(item, index) in setHeader" :key="index" v-bind="item">
  60. <!-- 自定义列插槽,插槽名为columns属性的prop -->
  61. <template #default="scope" v-if="$slots[item.prop]">
  62. <slot :name="item.prop" v-bind="scope"></slot>
  63. </template>
  64. <template v-else v-slot="scope">
  65. <template v-if="item.type === 'image'">
  66. <img :src="scope.row[item.prop]" class="w100" />
  67. </template>
  68. <template v-else>
  69. {{ scope.row[item.prop] }}
  70. </template>
  71. </template>
  72. </el-table-column>
  73. <template #empty>
  74. <el-empty description="暂无数据" />
  75. </template>
  76. </el-table>
  77. <div v-if="state.showPagination" class="table-footer mt15">
  78. <el-pagination
  79. v-model:current-page="state.page.page"
  80. v-model:page-size="state.page.pageSize"
  81. :pager-count="5"
  82. :page-sizes="[10, 30, 50, 100]"
  83. :total="state.total"
  84. layout="total, sizes, prev, pager, next, jumper"
  85. background
  86. @size-change="onHandleSizeChange"
  87. @current-change="onHandleCurrentChange"
  88. >
  89. </el-pagination>
  90. </div>
  91. </div>
  92. </template>
  93. <script setup lang="ts" name="netxTable">
  94. import { reactive, computed, nextTick, ref, onMounted } from 'vue';
  95. import { ElMessage } from 'element-plus';
  96. import Sortable from 'sortablejs';
  97. import { storeToRefs } from 'pinia';
  98. import { useThemeConfig } from '/@/stores/themeConfig';
  99. import { exportExcel } from '/@/utils/exportExcel';
  100. // import '/@/theme/tableTool.scss';
  101. // 定义父组件传过来的值
  102. const props = defineProps({
  103. //获取数据的方法,由父组件传递
  104. getData: {
  105. type: Function,
  106. required: true,
  107. },
  108. //列属性,和elementUI的Table-column 属性相同,附加属性:isCheck-是否默认勾选展示,hideCheck-是否隐藏该列的可勾选和拖拽
  109. columns: {
  110. type: Array<any>,
  111. default: () => [],
  112. },
  113. // 配置项:isBorder-是否显示表格边框,isSerialNo-是否显示表格序号,showSelection-是否显示表格可多选,isSelection-是否默认选中表格多选,pageSize-每页条数,hideExport-是否隐藏导出按钮,exportFileName-导出表格的文件名,空值默认用应用名称作为文件名
  114. config: {
  115. type: Object,
  116. default: () => {},
  117. },
  118. // 筛选参数
  119. param: {
  120. type: Object,
  121. default: () => {},
  122. },
  123. // 默认排序方式,{prop:"排序字段",order:"ascending or descending"}
  124. defaultSort: {
  125. type: Object,
  126. default: () => {},
  127. },
  128. // 导出报表自定义数据转换方法,不传按字段值导出
  129. exportChangeData: {
  130. type: Function,
  131. },
  132. });
  133. // 定义子组件向父组件传值/事件,pageChange-翻页事件,selectionChange-表格多选事件,可以在父组件处理批量删除/修改等功能,sortHeader-拖拽列顺序事件
  134. const emit = defineEmits(['pageChange', 'selectionChange', 'sortHeader']);
  135. // 定义变量内容
  136. const toolSetRef = ref();
  137. const tableRef = ref();
  138. const storesThemeConfig = useThemeConfig();
  139. const { themeConfig } = storeToRefs(storesThemeConfig);
  140. const state = reactive({
  141. data: [] as Array<EmptyObjectType>,
  142. loading: false,
  143. importLoading: false,
  144. total: 0,
  145. page: {
  146. page: 1,
  147. pageSize: 10,
  148. field: '',
  149. order: '',
  150. },
  151. showPagination: true,
  152. selectlist: [] as EmptyObjectType[],
  153. checkListAll: true,
  154. checkListIndeterminate: false,
  155. });
  156. // 设置边框显示/隐藏
  157. const setBorder = computed(() => {
  158. return props.config.isBorder ? true : false;
  159. });
  160. // 设置斑马纹显示/隐藏
  161. const setStripe = computed(() => {
  162. return props.config.isStripe ? true : false;
  163. });
  164. // 获取父组件 配置项(必传)
  165. const getConfig = computed(() => {
  166. return props.config;
  167. });
  168. // 设置 tool header 数据
  169. const setHeader = computed(() => {
  170. return props.columns.filter((v) => v.isCheck);
  171. });
  172. // tool 列显示全选改变时
  173. const onCheckAllChange = <T>(val: T) => {
  174. if (val) props.columns.forEach((v) => (v.isCheck = true));
  175. else props.columns.forEach((v) => (v.isCheck = false));
  176. state.checkListIndeterminate = false;
  177. };
  178. // tool 列显示当前项改变时
  179. const onCheckChange = () => {
  180. const headers = props.columns.filter((v) => v.isCheck).length;
  181. state.checkListAll = headers === props.columns.length;
  182. state.checkListIndeterminate = headers > 0 && headers < props.columns.length;
  183. };
  184. // 表格多选改变时
  185. const onSelectionChange = (val: EmptyObjectType[]) => {
  186. state.selectlist = val;
  187. emit('selectionChange', state.selectlist);
  188. };
  189. // 分页改变
  190. const onHandleSizeChange = (val: number) => {
  191. state.page.pageSize = val;
  192. handleList();
  193. emit('pageChange', state.page);
  194. };
  195. // 改变当前页
  196. const onHandleCurrentChange = (val: number) => {
  197. state.page.page = val;
  198. handleList();
  199. emit('pageChange', state.page);
  200. };
  201. // 列排序
  202. const sortChange = (column: any) => {
  203. state.page.field = column.prop;
  204. state.page.order = column.order;
  205. handleList();
  206. };
  207. // 重置列表
  208. const pageReset = () => {
  209. tableRef.value.clearSelection();
  210. state.page.page = 1;
  211. handleList();
  212. };
  213. // 导出当前页
  214. const onImportTable = () => {
  215. if (setHeader.value.length <= 0) return ElMessage.error('没有勾选要导出的列');
  216. importData(state.data);
  217. };
  218. // 全部导出
  219. const onImportTableAll = async () => {
  220. if (setHeader.value.length <= 0) return ElMessage.error('没有勾选要导出的列');
  221. state.importLoading = true;
  222. const param = Object.assign({}, props.param, { page: 1, pageSize: 99999 });
  223. const res = await props.getData(param);
  224. state.importLoading = false;
  225. const data = res.result?.items ?? [];
  226. importData(data);
  227. };
  228. // 导出方法
  229. const importData = (data: Array<EmptyObjectType>) => {
  230. if (data.length <= 0) return ElMessage.error('没有数据可以导出');
  231. state.importLoading = true;
  232. let exportData = JSON.parse(JSON.stringify(data));
  233. if (props.exportChangeData) {
  234. exportData = props.exportChangeData(exportData);
  235. }
  236. exportExcel(
  237. exportData,
  238. `${props.config.exportFileName ? props.config.exportFileName : themeConfig.value.globalTitle}_${new Date().toLocaleString()}.xlsx`,
  239. setHeader.value.filter((item) => {
  240. return item.type != 'action';
  241. }),
  242. '导出数据'
  243. );
  244. state.importLoading = false;
  245. };
  246. // 刷新
  247. const onRefreshTable = () => {
  248. handleList();
  249. // emit('pageChange', state.page);
  250. };
  251. // 拖拽设置
  252. const onSetTable = () => {
  253. nextTick(() => {
  254. const sortable = Sortable.create(toolSetRef.value, {
  255. handle: '.handle',
  256. dataIdAttr: 'data-key',
  257. animation: 150,
  258. onEnd: () => {
  259. const headerList: EmptyObjectType[] = [];
  260. sortable.toArray().forEach((val: any) => {
  261. props.columns.forEach((v) => {
  262. if (v.prop === val) headerList.push({ ...v });
  263. });
  264. });
  265. console.log(headerList);
  266. emit('sortHeader', headerList);
  267. },
  268. });
  269. });
  270. };
  271. const handleList = async () => {
  272. state.loading = true;
  273. let param = Object.assign({}, props.param, { ...state.page });
  274. Object.keys(param).forEach((key) => !param[key] && delete param[key]);
  275. const res = await props.getData(param);
  276. state.loading = false;
  277. if (res.result.items) {
  278. state.showPagination = true;
  279. state.data = res.result?.items ?? [];
  280. state.total = res.result?.total ?? 0;
  281. } else {
  282. state.showPagination = false;
  283. state.data = res.result ?? [];
  284. }
  285. };
  286. const toggleSelection = (row: any, statu?: boolean) => {
  287. tableRef.value!.toggleRowSelection(row, statu);
  288. };
  289. onMounted(() => {
  290. if (props.defaultSort) {
  291. state.page.field = props.defaultSort.prop;
  292. state.page.order = props.defaultSort.order;
  293. }
  294. state.page.pageSize = props.config.pageSize;
  295. handleList();
  296. });
  297. // 暴露变量
  298. defineExpose({
  299. pageReset,
  300. handleList,
  301. toggleSelection,
  302. });
  303. </script>
  304. <style scoped lang="scss">
  305. .table-container {
  306. display: flex;
  307. flex-direction: column;
  308. height: 100%;
  309. .el-table {
  310. flex: 1;
  311. }
  312. .table-footer {
  313. display: flex;
  314. justify-content: flex-end;
  315. }
  316. .table-header {
  317. display: flex;
  318. .table-footer-tool {
  319. flex: 1;
  320. display: flex;
  321. align-items: center;
  322. justify-content: flex-end;
  323. i {
  324. margin-right: 10px;
  325. cursor: pointer;
  326. color: var(--el-text-color-regular);
  327. &:last-of-type {
  328. margin-right: 0;
  329. }
  330. }
  331. .el-dropdown {
  332. i {
  333. margin-right: 10px;
  334. color: var(--el-text-color-regular);
  335. }
  336. }
  337. }
  338. }
  339. }
  340. </style>