import {reactive, onUnmounted, onActivated, onDeactivated, defineEmits} from 'vue' import {getArrayKey} from '/@/utils/common' import useCurrentInstance from '/@/utils/useCurrentInstance' import type {baTableApi} from '/@/api/common' import Sortable from 'sortablejs' import {findIndexRow} from '/@/components/table' import {ElNotification, ElForm} from 'element-plus' import {onBeforeRouteUpdate, useRoute} from 'vue-router' import _, {assign} from 'lodash' export default class baTable { public api public activate /* 表格状态-s */ public table: BaTable = reactive({ ref: undefined, // 主键字段 pk: 'id', // 数据源 data: [], // 路由remark remark: null, // 表格加载状态 loading: false, // 是否展开所有子项 expandAll: false, // 选中项 selection: [], // 不需要'双击编辑'的字段 dblClickNotEditColumn: [undefined], // 列数据 column: [], // 数据总量 total: 0, // 字段搜索,快速搜索,分页等数据 filter: {}, // 拖动排序限位字段:例如拖动行pid=1,那么拖动目的行pid也需要为1 dragSortLimitField: 'pid', // 接受url的query参数并自动触发通用搜索 acceptQuery: true, // 扩展数据 extend: {}, actionStruct: {data: null, name: ''}, tableName: '', rowStyle: '', headerRowStyle: '', }) /* 表格状态-e */ /* 表单状态-s */ public form: BaTableForm = reactive({ // 表单ref,new时无需传递 ref: undefined, // 表单label宽度 labelWidth: 160, // 当前操作:add=添加,edit=编辑 operate: '', // 被操作数据ID,支持批量编辑:add=[0],edit=[1,2,n] operateIds: [], // 表单数据 items: {}, // 提交按钮状态 submitLoading: false, // 默认表单数据(添加) defaultItems: {}, // 表单字段加载状态 loading: false, // 扩展数据 extend: {}, }) /* 表单状态-e */ // BaTable前置处理函数列表(前置埋点) public before // BaTable后置处理函数列表(后置埋点) public after // 通用搜索数据-需要响应性 public comSearch: ComSearch = reactive({ form: {}, fieldData: new Map(), }) constructor(api: baTableApi, table: BaTable, form: BaTableForm = {}, before: BaTableBefore = {}, after: BaTableAfter = {}) { this.api = api this.form = Object.assign(this.form, form) this.table = Object.assign(this.table, table) this.before = before this.after = after this.activate = true const route = useRoute() this.initComSearch(!_.isUndefined(route) ? route.query : {}) } runBefore(funName: string, args: any = {}) { if (this.before && this.before[funName] && typeof this.before[funName] == 'function') { return this.before[funName]!({...args}) === false ? false : true } return true } runAfter(funName: string, args: any = {}) { if (this.after && this.after[funName] && typeof this.after[funName] == 'function') { return this.after[funName]!({...args}) === false ? false : true } return true } /* API请求方法-s */ // 查看 getIndex = () => { if (this.runBefore('getIndex') === false) return this.table.loading = true return this.api .index(this.table.filter) .then((res) => { this.table.data = res.data.list this.table.total = typeof res.data.total == 'string' ? parseInt(res.data.total) : res.data.total this.table.remark = res.data.remark this.table.loading = false this.runAfter('getIndex', {res}) }) .catch(() => { this.table.loading = false }) } // 删除 postDel = (ids: string[]) => { if (this.runBefore('postDel', {ids}) === false) return this.api.del(ids).then((res) => { this.onTableHeaderAction('refresh', {}) this.runAfter('postDel', {res}) this.table.actionStruct!.name = "del" this.table.actionStruct!.data = ids }) } // 编辑 requestEdit = (id: string) => { if (this.runBefore('requestEdit', {id}) === false) return this.form.loading = true this.form.items = {} return this.api .edit({ id: id, }) .then((res) => { this.form.loading = false // this.form.items = Object.assign(res.data.row, this.form.defaultItems) this.form.items = res.data.row if (res.data.row.hasOwnProperty('status')) { this.form.items!.status = res.data.row.status + '' } this.runAfter('requestEdit', {res}) }) } /* API请求方法-e */ /** * 双击表格 */ onTableDblclick = (row: TableRow, column: any) => { if (this.table.dblClickNotEditColumn!.indexOf('all') === -1 && this.table.dblClickNotEditColumn!.indexOf(column.property) === -1) { if (this.runBefore('onTableDblclick', {row, column}) === false) return this.toggleForm('edit', [row[this.table.pk!]]) this.runAfter('onTableDblclick', { row, column, }) } } /** * 打开表单 * @param operate 操作:add=添加,edit=编辑 * @param operateIds 被操作项的数组:add=[],edit=[1,2,...] */ toggleForm = (operate: string = '', operateIds: string[] = []) => { if (this.runBefore('toggleForm', {operate, operateIds}) === false) return if (this.form.ref) { this.form.ref.resetFields() } if (operate == 'edit') { if (!operateIds.length) { return false } this.requestEdit(operateIds[0]) } else if (operate == 'add') { this.form.items = Object.assign({}, this.form.defaultItems) } this.form.operate = operate this.form.operateIds = operateIds this.runAfter('toggleForm', {operate, operateIds}) } onSubmit = (formEl: InstanceType | undefined = undefined) => { if (this.runBefore('onSubmit', { formEl: formEl, operate: this.form.operate!, items: this.form.items! }) === false) return for (const key in this.form.items) { if (this.form.items[key] === null) { delete this.form.items[key] } } // 表单验证通过后执行的api请求操作 let submitCallback = () => { this.form.submitLoading = true this.api .postData(this.form.operate!, this.form.items!) .then((res) => { this.onTableHeaderAction('refresh', {}) this.form.submitLoading = false this.form.operateIds?.shift() if (this.form.operateIds?.length! > 0) { this.toggleForm('edit', this.form.operateIds) } else { this.toggleForm() } this.runAfter('onSubmit', {res}) }) .catch((err) => { this.form.submitLoading = false }) } if (formEl) { this.form.ref = formEl formEl.validate((valid) => { if (valid) { submitCallback() } }) } else { submitCallback() } } /* 获取表格选择项的id数组 */ getSelectionIds() { let ids: string[] = [] for (const key in this.table.selection) { ids.push(this.table.selection[key as any][this.table.pk!]) } return ids } /** * 表格内的事件响应 * @param event 事件:selection-change=选中项改变,page-size-change=每页数量改变,current-page-change=翻页,sort-change=排序 * @param data 携带数据 */ onTableAction = (event: string, data: anyObj) => { if (this.runBefore('onTableAction', {event, data}) === false) return const actionFun = new Map([ [ 'selection-change', () => { this.table.selection = data as TableRow[] }, ], [ 'page-size-change', () => { this.table.filter!.limit = data.size this.getIndex() }, ], [ 'current-page-change', () => { this.table.filter!.page = data.page this.getIndex() }, ], [ 'sort-change', () => { let newOrder = '' if (!data.prop) { newOrder = '' } else if (data.prop) { newOrder = data.prop + ',' + data.order } if (newOrder != this.table.filter!.order) { this.table.filter!.order = newOrder this.getIndex() } }, ], [ 'default', () => { console.warn('未定义操作') }, ], ]) let action = actionFun.get(event) || actionFun.get('default') action!.call(this) return this.runAfter('onTableAction', {event, data}) } /** * 表格顶栏按钮事件响应 * @param event 事件:refresh=刷新,edit=编辑,delete=删除,quick-search=快速查询 * @param data 携带数据 */ onTableHeaderAction = (event: string, data: anyObj) => { if (this.runBefore('onTableHeaderAction', {event, data}) === false) return const actionFun = new Map([ [ 'refresh', () => { this.table.data = [] this.getIndex() }, ], [ 'add', () => { this.toggleForm('add') }, ], [ 'edit', () => { this.toggleForm('edit', this.getSelectionIds()) }, ], [ 'delete', () => { this.postDel(this.getSelectionIds()) }, ], [ 'unfold', () => { if (!this.table.ref) { console.warn('折叠/展开失败,因为tableRef未定义,请在onMounted时赋值tableRef') return } this.table.expandAll = data.unfold this.table.ref.unFoldAll(data.unfold) }, ], [ 'quick-search', () => { this.table.filter!.quickSearch = data.keyword this.getIndex() }, ], [ 'change-show-column', () => { let columnKey = getArrayKey(this.table.column, 'prop', data.field) this.table.column[columnKey].show = data.value }, ], [ 'default', () => { console.warn('未定义操作') }, ], ]) let action = actionFun.get(event) || actionFun.get('default') action!.call(this) return this.runAfter('onTableHeaderAction', {event, data}) } /** * 初始化默认排序 * el表格的`default-sort`在自定义排序时无效 * 此方法只有在表格数据请求结束后执行有效 */ initSort = () => { if (this.table.defaultOrder && this.table.defaultOrder.prop) { if (!this.table.ref) { console.warn('初始化默认排序失败,因为tableRef未定义,请在onMounted时赋值tableRef') return } let defaultOrder = this.table.defaultOrder.prop + ',' + this.table.defaultOrder.order if (this.table.filter && this.table.filter.order != defaultOrder) { this.table.filter.order = defaultOrder this.table.ref.getRef()?.sort(this.table.defaultOrder.prop, this.table.defaultOrder.order == 'desc' ? 'descending' : 'ascending') } } } /** * 表格拖动排序 */ dragSort = () => { let buttonsKey = getArrayKey(this.table.column, 'render', 'buttons') let moveButton = getArrayKey(this.table.column[buttonsKey].buttons, 'render', 'moveButton') if (moveButton === false) { return } if (!this.table.ref) { console.warn('初始化拖拽排序失败,因为tableRef未定义,请在onMounted时赋值tableRef') return } let el = this.table.ref.getRef().$el.querySelector('.el-table__body-wrapper .el-table__body tbody') var sortable = Sortable.create(el, { animation: 200, handle: '.table-row-weigh-sort', ghostClass: 'ba-table-row', onStart: () => { for (const key in this.table.column[buttonsKey].buttons) { this.table.column[buttonsKey].buttons![key as any].disabledTip = true } }, onEnd: (evt: Sortable.SortableEvent) => { for (const key in this.table.column[buttonsKey].buttons) { this.table.column[buttonsKey].buttons![key as any].disabledTip = false } // 找到对应行id let moveRow = findIndexRow(this.table.data!, evt.oldIndex!) as TableRow let replaceRow = findIndexRow(this.table.data!, evt.newIndex!) as TableRow if (this.table.dragSortLimitField && moveRow[this.table.dragSortLimitField] != replaceRow[this.table.dragSortLimitField]) { this.onTableHeaderAction('refresh', {}) ElNotification({ type: 'error', message: '移动位置超出了可移动范围!', }) return } this.api.sortableApi(moveRow.id, replaceRow.id).then((res) => { this.onTableHeaderAction('refresh', {}) }) }, }) } mount = () => { if (this.runBefore('mount') === false) return this.activate = true const {proxy} = useCurrentInstance() /** * 表格内的按钮响应 * @param name 按钮name * @param row 被操作行数据 */ proxy.eventBus.on('onTableButtonClick', (data: { name: string; row: TableRow }) => { if (!this.activate) return if (data.name == 'edit') { this.toggleForm('edit', [data.row[this.table.pk!]]) } else if (data.name == 'delete') { this.postDel([data.row[this.table.pk!]]) } }) /** * 通用搜索响应 * @param comSearchData 通用搜索数据 */ proxy.eventBus.on('onTableComSearch', (data: comSearchData) => { if (!this.activate) return this.table.filter!.search = JSON.stringify(data) this.getIndex() }) /** * 表格内的字段操作响应 * @param value 修改后的值 * @param row 被操作行数据 * @param field 被操作字段名 */ proxy.eventBus.on('onTableFieldChange', (data: { value: any; row: TableRow; field: keyof TableRow; render: string }) => { if (!this.activate) return if (data.render == 'switch') { data.row.loading = true this.api .postData('edit', {[this.table.pk!]: data.row[this.table.pk!], [data.field]: data.value}) .then(() => { data.row.loading = false data.row[data.field] = data.value }) .catch(() => { data.row.loading = false }) } }) onUnmounted(() => { // 考虑到 keepalive 在 onMounted、onActivated 时注册事件;onUnmounted、onDeactivated 时注销事件,但并不是最方便的方案,且注销后在全局事件管理中本来就有留存 // 干脆,不off掉事件,改用 this.activate 来决定事件是否执行 this.activate = false this.runAfter('mount') }) onActivated(() => { this.activate = true }) onDeactivated(() => { this.activate = false }) // 监听路由变化,响应通用搜索更新 onBeforeRouteUpdate((to) => { this.initComSearch(to.query) this.getIndex() }) } /** * 通用搜索初始化 */ initComSearch = (query: anyObj = {}) => { let form: anyObj = {} if (this.table.column.length <= 0) { return } const field = this.table.column for (const key in field) { if (field[key].operator === false) { continue } let prop = field[key].prop if (typeof field[key].operator == 'undefined') { field[key].operator = '=' } if (prop) { if (field[key].operator == 'RANGE' || field[key].operator == 'NOT RANGE') { form[prop] = '' form[prop + '-start'] = '' form[prop + '-end'] = '' } else if (field[key].operator == 'NULL' || field[key].operator == 'NOT NULL') { form[prop] = false } else { form[prop] = '' } // 初始化来自query中的默认值 if (this.table.acceptQuery && typeof query[prop] != 'undefined') { let queryProp = (query[prop] as string) ?? '' if (field[key].operator == 'RANGE' || field[key].operator == 'NOT RANGE') { let range = queryProp.split(',') if (field[key].render == 'datetime') { if (range && range.length >= 2) { form[prop + '-default'] = [new Date(range[0]), new Date(range[1])] } } else { form[prop + '-start'] = range[0] ?? '' form[prop + '-end'] = range[1] ?? '' } } else if (field[key].operator == 'NULL' || field[key].operator == 'NOT NULL') { form[prop] = queryProp ? true : false } else if (field[key].render == 'datetime') { form[prop + '-default'] = new Date(queryProp) } else { form[prop] = queryProp } } this.comSearch.fieldData.set(prop, { operator: field[key].operator, render: field[key].render, }) } } // 接受query再搜索 if (this.table.acceptQuery) { let comSearchData: comSearchData[] = [] for (const key in query) { let fieldDataTemp = this.comSearch.fieldData.get(key) comSearchData.push({ field: key, val: query[key] as string, operator: fieldDataTemp.operator, render: fieldDataTemp.render, }) } this.table.filter!.search = JSON.stringify(comSearchData) } this.comSearch.form = Object.assign(this.comSearch.form, form) } }