123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607 |
- 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<typeof ElForm> | 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)
- }
- }
|