123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251 |
- <template>
- <el-table
- ref="ftable"
- :show-summary="sumcol && sumcol.length > 0"
- :summary-method="sumComputed"
- :row-key="rowKey"
- :data="data"
- border
- stripe
- :max-height="height"
- @select="selectChange"
- @select-all="selectAll"
- >
- <el-table-column type="selection" align="center" width="60" v-if="useSelect" />
- <template v-for="i in fields" :key="i.model">
- <template v-if="!i.custom">
- <el-table-column sortable align="center" :label="i.label" :prop="i.model" :formatter="toFormatter"></el-table-column>
- </template>
- <slot :name="i.model"></slot>
- </template>
- <el-table-column align="center" label="操作" v-if="opera">
- <template #default="{ row, $index }">
- <template v-for="i in opera" :key="i.method">
- <el-link :type="i.type ? i.type : 'primary'" :underline="false" @click="toOpera(row, $index, i)" class="opera__btn">{{ i.label }}</el-link>
- </template>
- </template>
- </el-table-column>
- </el-table>
- <el-row class="page__row" v-if="total">
- <el-col :span="24">
- <el-pagination small :page-size="limit" background layout="->, total, prev, pager, next " :total="total" @current-change="pageChange" />
- </el-col>
- </el-row>
- </template>
- <script lang="ts">
- import { defineComponent } from 'vue';
- export default defineComponent({
- name: 'FTable',
- });
- </script>
- <script setup lang="ts">
- import _ from 'lodash';
- import { ElMessageBox } from 'element-plus';
- import type { Action } from 'element-plus';
- import { ElTable } from 'element-plus';
- import type { TableColumnCtx } from 'element-plus/es/components/table/src/table-column/defaults';
- import { IOpera, IField, OperaType, rowKeyType } from '@/types/FTable';
- import { ref, defineProps, defineEmits, withDefaults, watch, nextTick } from 'vue';
- interface IProps {
- /** 数据 */
- data: Array<any>;
- /** 显示设置 */
- fields: Array<IField>;
- /** 操作设置 */
- opera?: Array<IOpera>;
- // limit: number;
- /** 总数 */
- total?: number;
- /** 最大高度 */
- height?: number;
- /**显示格式化函数 */
- toFormat?: (obj: any) => string;
- /**行数据的key */
- rowKey?: rowKeyType | string;
- /**合计字段 */
- sumcol?: Array<string>;
- /**使用多选 */
- useSelect?: boolean;
- /**选择的数据 */
- selected?: Array<any>;
- }
- const props = withDefaults(defineProps<IProps>(), {
- rowKey: '_id',
- useSelect: true,
- });
- let limit = 10;
- const emits = defineEmits<{
- (e: 'query', p: any): void;
- (e: 'update:selected', p: any): void;
- (e: string, p: any): void;
- }>();
- /**
- * 运行操作
- * @prop {Record<string,any>} row 行数据
- * @prop {number} index 行数据
- * @prop {Record<string,any>} opera 行数据
- */
- let toOpera = (row: Record<string, any>, index: number, opera: IOpera) => {
- const { confirm, method, methodZh } = opera;
- if (!confirm) {
- emits(method, { row, index });
- } else {
- let word;
- if (methodZh) word = methodZh;
- else word = _.get(OperaType, method);
- ElMessageBox.confirm(`您确定要${word}该数据吗?`, '提示', {
- confirmButtonText: '确定',
- cancelButtonText: '取消',
- type: 'warning',
- callback: (res: Action) => {
- if (res === 'confirm') {
- // 执行函数
- emits(method, { row, index });
- } else {
- // 取消
- console.log('cancel');
- }
- },
- });
- }
- };
- /**
- * 表格显示格式化
- * @prop {Record<string,any>} row 行数据
- * @prop {Record<string,any>} column 列设置数据
- * @prop {any} cellValue 表格数据
- * @prop {number} index 索引
- * @return string
- */
- let toFormatter = (row: Record<string, any>, column: Record<string, any>, cellValue: any, index: number): string => {
- let this_fields = props.fields.filter((fil) => fil.model === column.property);
- if (this_fields.length > 0) {
- let format = _.get(this_fields[0], `format`, false);
- if (format) {
- let res;
- if (_.isFunction(format)) {
- res = format(cellValue);
- } else if (_.isFunction(props.toFormat)) {
- res = props.toFormat({
- model: this_fields[0].model,
- value: cellValue,
- row,
- });
- }
- return res;
- } else return cellValue;
- }
- return cellValue;
- };
- // #region 计算部分
- interface SummaryMethodProps<T> {
- columns: TableColumnCtx<T>[];
- data: T[];
- }
- /**
- * 计算函数
- * @prop {Array} columns 列数组
- * @prop {Array} data 表格中所有的数据
- * @return Array
- * */
- let sumComputed = (param: SummaryMethodProps<any>) => {
- const { columns, data } = param;
- let result: string[] = [];
- if (columns.length <= 0 || data.length <= 0) return [];
- const reg = new RegExp(/^\d+$/);
- for (const column of columns) {
- const prop = _.get(column, 'property');
- if (!prop) {
- result.push('');
- continue;
- }
- // 判断是否需要计算
- if (!props.sumcol) return result;
- const inlist = props.sumcol.find((f) => f == prop);
- if (!inlist) {
- result.push('');
- continue;
- }
- let res;
- // 整理出要计算的属性(只取出数字或者可以为数字的值)
- const resetList = data.map((i) => {
- const d = _.get(i, prop);
- const res = reg.test(d);
- if (res) return d * 1;
- else return 0;
- });
- res = `${resetList.reduce((p, n) => p + n, 0)}`;
- result.push(res);
- }
- result[0] = '合计';
- return result;
- };
- // #endregion
- // #region 选择部分
- const ftable = ref<InstanceType<typeof ElTable>>();
- /**选择/反选 */
- let selectChange = (selection: any, row: any) => {
- if (!props.selected) return;
- let selected = _.cloneDeep(props.selected);
- let has = props.selected.find((i) => i[props.rowKey] === row[props.rowKey]);
- if (has) {
- selected = props.selected.filter((f) => f[props.rowKey] !== row[props.rowKey]);
- } else {
- selected.push(row);
- }
- emits('update:selected', selected);
- };
- /**全选/反选 */
- let selectAll = (selection: any) => {
- if (!props.selected) return;
- let selected = _.cloneDeep(props.selected);
- let res = [];
- if (selection.length > 0) {
- res = _.uniqBy(selected.concat(selection), props.rowKey);
- } else {
- res = _.differenceBy(selected, props.data, props.rowKey);
- }
- emits('update:selected', res);
- };
- /**渲染选项 */
- let initSelection = async () => {
- // 清除已选择的选项样式
- if (!props.selected) return;
- await nextTick();
- let selected = props.selected;
- ftable.value?.clearSelection();
- selected.forEach((info) => {
- let d = props.data.filter((p) => p[props.rowKey] === info[props.rowKey]);
- if (d.length > 0) ftable.value?.toggleRowSelection(d[0], true);
- });
- };
- watch(
- () => props.data,
- (val) => initSelection(),
- { deep: true, immediate: true }
- );
- // #endregion
- /**
- * 换页
- * @prop {number} page 当前页
- */
- let pageChange = (page: number) => {
- const query = { skip: (page - 1) * limit, limit };
- emits('query', query);
- };
- </script>
- <style lang="scss" scoped>
- .opera__btn {
- padding-right: 10px;
- }
- .page__row {
- padding: 15px 0;
- }
- </style>
|