index.vue 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. <template>
  2. <el-table
  3. ref="ftable"
  4. :show-summary="sumcol && sumcol.length > 0"
  5. :summary-method="sumComputed"
  6. :row-key="rowKey"
  7. :data="data"
  8. border
  9. stripe
  10. :max-height="height"
  11. @select="selectChange"
  12. @select-all="selectAll"
  13. >
  14. <el-table-column type="selection" align="center" width="60" v-if="useSelect" />
  15. <template v-for="i in fields" :key="i.model">
  16. <template v-if="!i.custom">
  17. <el-table-column sortable align="center" :label="i.label" :prop="i.model" :formatter="toFormatter"></el-table-column>
  18. </template>
  19. <slot :name="i.model"></slot>
  20. </template>
  21. <el-table-column align="center" label="操作" v-if="opera">
  22. <template #default="{ row, $index }">
  23. <template v-for="i in opera" :key="i.method">
  24. <el-link :type="i.type ? i.type : 'primary'" :underline="false" @click="toOpera(row, $index, i)" class="opera__btn">{{ i.label }}</el-link>
  25. </template>
  26. </template>
  27. </el-table-column>
  28. </el-table>
  29. <el-row class="page__row" v-if="total">
  30. <el-col :span="24">
  31. <el-pagination small :page-size="limit" background layout="->, total, prev, pager, next " :total="total" @current-change="pageChange" />
  32. </el-col>
  33. </el-row>
  34. </template>
  35. <script lang="ts">
  36. import { defineComponent } from 'vue';
  37. export default defineComponent({
  38. name: 'FTable',
  39. });
  40. </script>
  41. <script setup lang="ts">
  42. import _ from 'lodash';
  43. import { ElMessageBox } from 'element-plus';
  44. import type { Action } from 'element-plus';
  45. import { ElTable } from 'element-plus';
  46. import type { TableColumnCtx } from 'element-plus/es/components/table/src/table-column/defaults';
  47. import { IOpera, IField, OperaType, rowKeyType } from '@/types/FTable';
  48. import { ref, defineProps, defineEmits, withDefaults, watch, nextTick } from 'vue';
  49. interface IProps {
  50. /** 数据 */
  51. data: Array<any>;
  52. /** 显示设置 */
  53. fields: Array<IField>;
  54. /** 操作设置 */
  55. opera?: Array<IOpera>;
  56. // limit: number;
  57. /** 总数 */
  58. total?: number;
  59. /** 最大高度 */
  60. height?: number;
  61. /**显示格式化函数 */
  62. toFormat?: (obj: any) => string;
  63. /**行数据的key */
  64. rowKey?: rowKeyType | string;
  65. /**合计字段 */
  66. sumcol?: Array<string>;
  67. /**使用多选 */
  68. useSelect?: boolean;
  69. /**选择的数据 */
  70. selected?: Array<any>;
  71. }
  72. const props = withDefaults(defineProps<IProps>(), {
  73. rowKey: '_id',
  74. useSelect: true,
  75. });
  76. let limit = 10;
  77. const emits = defineEmits<{
  78. (e: 'query', p: any): void;
  79. (e: 'update:selected', p: any): void;
  80. (e: string, p: any): void;
  81. }>();
  82. /**
  83. * 运行操作
  84. * @prop {Record<string,any>} row 行数据
  85. * @prop {number} index 行数据
  86. * @prop {Record<string,any>} opera 行数据
  87. */
  88. let toOpera = (row: Record<string, any>, index: number, opera: IOpera) => {
  89. const { confirm, method, methodZh } = opera;
  90. if (!confirm) {
  91. emits(method, { row, index });
  92. } else {
  93. let word;
  94. if (methodZh) word = methodZh;
  95. else word = _.get(OperaType, method);
  96. ElMessageBox.confirm(`您确定要${word}该数据吗?`, '提示', {
  97. confirmButtonText: '确定',
  98. cancelButtonText: '取消',
  99. type: 'warning',
  100. callback: (res: Action) => {
  101. if (res === 'confirm') {
  102. // 执行函数
  103. emits(method, { row, index });
  104. } else {
  105. // 取消
  106. console.log('cancel');
  107. }
  108. },
  109. });
  110. }
  111. };
  112. /**
  113. * 表格显示格式化
  114. * @prop {Record<string,any>} row 行数据
  115. * @prop {Record<string,any>} column 列设置数据
  116. * @prop {any} cellValue 表格数据
  117. * @prop {number} index 索引
  118. * @return string
  119. */
  120. let toFormatter = (row: Record<string, any>, column: Record<string, any>, cellValue: any, index: number): string => {
  121. let this_fields = props.fields.filter((fil) => fil.model === column.property);
  122. if (this_fields.length > 0) {
  123. let format = _.get(this_fields[0], `format`, false);
  124. if (format) {
  125. let res;
  126. if (_.isFunction(format)) {
  127. res = format(cellValue);
  128. } else if (_.isFunction(props.toFormat)) {
  129. res = props.toFormat({
  130. model: this_fields[0].model,
  131. value: cellValue,
  132. row,
  133. });
  134. }
  135. return res;
  136. } else return cellValue;
  137. }
  138. return cellValue;
  139. };
  140. // #region 计算部分
  141. interface SummaryMethodProps<T> {
  142. columns: TableColumnCtx<T>[];
  143. data: T[];
  144. }
  145. /**
  146. * 计算函数
  147. * @prop {Array} columns 列数组
  148. * @prop {Array} data 表格中所有的数据
  149. * @return Array
  150. * */
  151. let sumComputed = (param: SummaryMethodProps<any>) => {
  152. const { columns, data } = param;
  153. let result: string[] = [];
  154. if (columns.length <= 0 || data.length <= 0) return [];
  155. const reg = new RegExp(/^\d+$/);
  156. for (const column of columns) {
  157. const prop = _.get(column, 'property');
  158. if (!prop) {
  159. result.push('');
  160. continue;
  161. }
  162. // 判断是否需要计算
  163. if (!props.sumcol) return result;
  164. const inlist = props.sumcol.find((f) => f == prop);
  165. if (!inlist) {
  166. result.push('');
  167. continue;
  168. }
  169. let res;
  170. // 整理出要计算的属性(只取出数字或者可以为数字的值)
  171. const resetList = data.map((i) => {
  172. const d = _.get(i, prop);
  173. const res = reg.test(d);
  174. if (res) return d * 1;
  175. else return 0;
  176. });
  177. res = `${resetList.reduce((p, n) => p + n, 0)}`;
  178. result.push(res);
  179. }
  180. result[0] = '合计';
  181. return result;
  182. };
  183. // #endregion
  184. // #region 选择部分
  185. const ftable = ref<InstanceType<typeof ElTable>>();
  186. /**选择/反选 */
  187. let selectChange = (selection: any, row: any) => {
  188. if (!props.selected) return;
  189. let selected = _.cloneDeep(props.selected);
  190. let has = props.selected.find((i) => i[props.rowKey] === row[props.rowKey]);
  191. if (has) {
  192. selected = props.selected.filter((f) => f[props.rowKey] !== row[props.rowKey]);
  193. } else {
  194. selected.push(row);
  195. }
  196. emits('update:selected', selected);
  197. };
  198. /**全选/反选 */
  199. let selectAll = (selection: any) => {
  200. if (!props.selected) return;
  201. let selected = _.cloneDeep(props.selected);
  202. let res = [];
  203. if (selection.length > 0) {
  204. res = _.uniqBy(selected.concat(selection), props.rowKey);
  205. } else {
  206. res = _.differenceBy(selected, props.data, props.rowKey);
  207. }
  208. emits('update:selected', res);
  209. };
  210. /**渲染选项 */
  211. let initSelection = async () => {
  212. // 清除已选择的选项样式
  213. if (!props.selected) return;
  214. await nextTick();
  215. let selected = props.selected;
  216. ftable.value?.clearSelection();
  217. selected.forEach((info) => {
  218. let d = props.data.filter((p) => p[props.rowKey] === info[props.rowKey]);
  219. if (d.length > 0) ftable.value?.toggleRowSelection(d[0], true);
  220. });
  221. };
  222. watch(
  223. () => props.data,
  224. (val) => initSelection(),
  225. { deep: true, immediate: true }
  226. );
  227. // #endregion
  228. /**
  229. * 换页
  230. * @prop {number} page 当前页
  231. */
  232. let pageChange = (page: number) => {
  233. const query = { skip: (page - 1) * limit, limit };
  234. emits('query', query);
  235. };
  236. </script>
  237. <style lang="scss" scoped>
  238. .opera__btn {
  239. padding-right: 10px;
  240. }
  241. .page__row {
  242. padding: 15px 0;
  243. }
  244. </style>