import { initData, download } from '@/api/data' import { parseTime, downloadFile } from '@/utils/index' import Vue from 'vue' /** * CRUD配置 * @author moxun * @param {*} options
* @return crud instance. * @example * 要使用多crud时,请在关联crud的组件处使用crud-tag进行标记,如: */ function CRUD(options) { const defaultOptions = { tag: 'default', // id字段名 idField: 'id', // 标题 title: '', // 请求数据的url url: '', // 表格数据 data: [], // 选择项 selections: [], // 待查询的对象 query: {}, // 查询数据的参数 params: {}, // Form 表单 form: {}, // 重置表单 defaultForm: () => {}, // 排序规则,默认 id 降序, 支持多字段排序 ['id,desc', 'createTime,asc'] sort: ['id,desc'], // 等待时间 time: 50, // CRUD Method crudMethod: { add: (form) => {}, del: (id) => {}, edit: (form) => {}, get: (id) => {} }, // 主页操作栏显示哪些按钮 optShow: { add: true, edit: true, del: true, download: true, reset: true }, // 自定义一些扩展属性 props: {}, // 在主页准备 queryOnPresenterCreated: true, // 调试开关 debug: false } options = mergeOptions(defaultOptions, options) const data = { ...options, // 记录数据状态 dataStatus: {}, status: { add: CRUD.STATUS.NORMAL, edit: CRUD.STATUS.NORMAL, // 添加或编辑状态 get cu() { if (this.add === CRUD.STATUS.NORMAL && this.edit === CRUD.STATUS.NORMAL) { return CRUD.STATUS.NORMAL } else if (this.add === CRUD.STATUS.PREPARED || this.edit === CRUD.STATUS.PREPARED) { return CRUD.STATUS.PREPARED } else if (this.add === CRUD.STATUS.PROCESSING || this.edit === CRUD.STATUS.PROCESSING) { return CRUD.STATUS.PROCESSING } throw new Error('wrong crud\'s cu status') }, // 标题 get title() { return this.add > CRUD.STATUS.NORMAL ? `新增${crud.title}` : this.edit > CRUD.STATUS.NORMAL ? `编辑${crud.title}` : crud.title } }, msg: { submit: '提交成功', add: '新增成功', edit: '编辑成功', del: '删除成功' }, page: { // 页码 page: 0, // 每页数据条数 size: 10, // 总数据条数 total: 0 }, // 整体loading loading: false, // 导出的 Loading downloadLoading: false, // 删除的 Loading delAllLoading: false } const methods = { /** * 通用的提示 */ submitSuccessNotify() { crud.notify(crud.msg.submit, CRUD.NOTIFICATION_TYPE.SUCCESS) }, addSuccessNotify() { crud.notify(crud.msg.add, CRUD.NOTIFICATION_TYPE.SUCCESS) }, editSuccessNotify() { crud.notify(crud.msg.edit, CRUD.NOTIFICATION_TYPE.SUCCESS) }, delSuccessNotify() { crud.notify(crud.msg.del, CRUD.NOTIFICATION_TYPE.SUCCESS) }, // 搜索 toQuery() { crud.page.page = 1 crud.refresh() }, // 刷新 refresh() { if (!callVmHook(crud, CRUD.HOOK.beforeRefresh)) { return } return new Promise((resolve, reject) => { crud.loading = true // 请求数据 initData(crud.url, crud.getQueryParams()).then(data => { const table = crud.getTable() if (table && table.lazy) { // 懒加载子节点数据,清掉已加载的数据 table.store.states.treeData = {} table.store.states.lazyTreeNodeMap = {} } crud.page.total = data.totalElements crud.data = data.content crud.resetDataStatus() // time 毫秒后显示表格 setTimeout(() => { crud.loading = false callVmHook(crud, CRUD.HOOK.afterRefresh) }, crud.time) resolve(data) }).catch(err => { crud.loading = false reject(err) }) }) }, /** * 启动添加 */ toAdd() { crud.resetForm() if (!(callVmHook(crud, CRUD.HOOK.beforeToAdd, crud.form) && callVmHook(crud, CRUD.HOOK.beforeToCU, crud.form))) { return } crud.status.add = CRUD.STATUS.PREPARED callVmHook(crud, CRUD.HOOK.afterToAdd, crud.form) callVmHook(crud, CRUD.HOOK.afterToCU, crud.form) }, /** * 启动编辑 * @param {*} data 数据项 */ toEdit(data) { crud.resetForm(JSON.parse(JSON.stringify(data))) if (!(callVmHook(crud, CRUD.HOOK.beforeToEdit, crud.form) && callVmHook(crud, CRUD.HOOK.beforeToCU, crud.form))) { return } crud.status.edit = CRUD.STATUS.PREPARED crud.getDataStatus(crud.getDataId(data)).edit = CRUD.STATUS.PREPARED callVmHook(crud, CRUD.HOOK.afterToEdit, crud.form) callVmHook(crud, CRUD.HOOK.afterToCU, crud.form) }, /** * 启动删除 * @param {*} data 数据项 */ toDelete(data) { crud.getDataStatus(crud.getDataId(data)).delete = CRUD.STATUS.PREPARED }, /** * 取消删除 * @param {*} data 数据项 */ cancelDelete(data) { if (!callVmHook(crud, CRUD.HOOK.beforeDeleteCancel, data)) { return } crud.getDataStatus(crud.getDataId(data)).delete = CRUD.STATUS.NORMAL callVmHook(crud, CRUD.HOOK.afterDeleteCancel, data) }, /** * 取消新增/编辑 */ cancelCU() { const addStatus = crud.status.add const editStatus = crud.status.edit if (addStatus === CRUD.STATUS.PREPARED) { if (!callVmHook(crud, CRUD.HOOK.beforeAddCancel, crud.form)) { return } crud.status.add = CRUD.STATUS.NORMAL } if (editStatus === CRUD.STATUS.PREPARED) { if (!callVmHook(crud, CRUD.HOOK.beforeEditCancel, crud.form)) { return } crud.status.edit = CRUD.STATUS.NORMAL crud.getDataStatus(crud.getDataId(crud.form)).edit = CRUD.STATUS.NORMAL } crud.resetForm() if (addStatus === CRUD.STATUS.PREPARED) { callVmHook(crud, CRUD.HOOK.afterAddCancel, crud.form) } if (editStatus === CRUD.STATUS.PREPARED) { callVmHook(crud, CRUD.HOOK.afterEditCancel, crud.form) } // 清除表单验证 if (crud.findVM('form').$refs['form']) { crud.findVM('form').$refs['form'].clearValidate() } }, /** * 提交新增/编辑 */ submitCU() { if (!callVmHook(crud, CRUD.HOOK.beforeValidateCU)) { return } crud.findVM('form').$refs['form'].validate(valid => { if (!valid) { return } if (!callVmHook(crud, CRUD.HOOK.afterValidateCU)) { return } if (crud.status.add === CRUD.STATUS.PREPARED) { crud.doAdd() } else if (crud.status.edit === CRUD.STATUS.PREPARED) { crud.doEdit() } }) }, /** * 执行添加 */ doAdd() { if (!callVmHook(crud, CRUD.HOOK.beforeSubmit)) { return } crud.status.add = CRUD.STATUS.PROCESSING crud.crudMethod.add(crud.form).then(() => { crud.status.add = CRUD.STATUS.NORMAL crud.resetForm() crud.addSuccessNotify() callVmHook(crud, CRUD.HOOK.afterSubmit) crud.toQuery() }).catch(() => { crud.status.add = CRUD.STATUS.PREPARED callVmHook(crud, CRUD.HOOK.afterAddError) }) }, /** * 执行编辑 */ doEdit() { if (!callVmHook(crud, CRUD.HOOK.beforeSubmit)) { return } crud.status.edit = CRUD.STATUS.PROCESSING crud.crudMethod.edit(crud.form).then(() => { crud.status.edit = CRUD.STATUS.NORMAL crud.getDataStatus(crud.getDataId(crud.form)).edit = CRUD.STATUS.NORMAL crud.editSuccessNotify() crud.resetForm() callVmHook(crud, CRUD.HOOK.afterSubmit) crud.refresh() }).catch(() => { crud.status.edit = CRUD.STATUS.PREPARED callVmHook(crud, CRUD.HOOK.afterEditError) }) }, /** * 执行删除 * @param {*} data 数据项 */ doDelete(data) { let delAll = false let dataStatus const ids = [] if (data instanceof Array) { delAll = true data.forEach(val => { ids.push(this.getDataId(val)) }) } else { ids.push(this.getDataId(data)) dataStatus = crud.getDataStatus(this.getDataId(data)) } if (!callVmHook(crud, CRUD.HOOK.beforeDelete, data)) { return } if (!delAll) { dataStatus.delete = CRUD.STATUS.PROCESSING } return crud.crudMethod.del(ids).then(() => { if (delAll) { crud.delAllLoading = false } else dataStatus.delete = CRUD.STATUS.PREPARED crud.dleChangePage(1) crud.delSuccessNotify() callVmHook(crud, CRUD.HOOK.afterDelete, data) crud.refresh() }).catch(() => { if (delAll) { crud.delAllLoading = false } else dataStatus.delete = CRUD.STATUS.PREPARED }) }, /** * 通用导出 */ doExport() { crud.downloadLoading = true download(crud.url + '/download', crud.getQueryParams()).then(result => { downloadFile(result, crud.title + '数据', 'xlsx') crud.downloadLoading = false }).catch(() => { crud.downloadLoading = false }) }, /** * 获取查询参数 */ getQueryParams: function() { // 清除参数无值的情况 Object.keys(crud.query).length !== 0 && Object.keys(crud.query).forEach(item => { if (crud.query[item] === null || crud.query[item] === '') crud.query[item] = undefined }) Object.keys(crud.params).length !== 0 && Object.keys(crud.params).forEach(item => { if (crud.params[item] === null || crud.params[item] === '') crud.params[item] = undefined }) return { page: crud.page.page - 1, size: crud.page.size, sort: crud.sort, ...crud.query, ...crud.params } }, // 当前页改变 pageChangeHandler(e) { crud.page.page = e crud.refresh() }, // 每页条数改变 sizeChangeHandler(e) { crud.page.size = e crud.page.page = 1 crud.refresh() }, // 预防删除第二页最后一条数据时,或者多选删除第二页的数据时,页码错误导致请求无数据 dleChangePage(size) { if (crud.data.length === size && crud.page.page !== 1) { crud.page.page -= 1 } }, // 选择改变 selectionChangeHandler(val) { crud.selections = val }, /** * 重置查询参数 * @param {Boolean} toQuery 重置后进行查询操作 */ resetQuery(toQuery = true) { const defaultQuery = JSON.parse(JSON.stringify(crud.defaultQuery)) const query = crud.query Object.keys(query).forEach(key => { query[key] = defaultQuery[key] }) // 重置参数 this.params = {} if (toQuery) { crud.toQuery() } }, /** * 重置表单 * @param {Array} data 数据 */ resetForm(data) { const form = data || (typeof crud.defaultForm === 'object' ? JSON.parse(JSON.stringify(crud.defaultForm)) : crud.defaultForm.apply(crud.findVM('form'))) const crudFrom = crud.form for (const key in form) { if (crudFrom.hasOwnProperty(key)) { crudFrom[key] = form[key] } else { Vue.set(crudFrom, key, form[key]) } } // add by ghl 2020-10-04 页面重复添加信息时,下拉框的校验会存在,需要找工取消 if (crud.findVM('form').$refs['form']) { crud.findVM('form').$refs['form'].clearValidate() } }, /** * 重置数据状态 */ resetDataStatus() { const dataStatus = {} function resetStatus(datas) { datas.forEach(e => { dataStatus[crud.getDataId(e)] = { delete: 0, edit: 0 } if (e.children) { resetStatus(e.children) } }) } resetStatus(crud.data) crud.dataStatus = dataStatus }, /** * 获取数据状态 * @param {Number | String} id 数据项id */ getDataStatus(id) { return crud.dataStatus[id] }, /** * 用于树形表格多选, 选中所有 * @param selection */ selectAllChange(selection) { // 如果选中的数目与请求到的数目相同就选中子节点,否则就清空选中 if (selection && selection.length === crud.data.length) { selection.forEach(val => { crud.selectChange(selection, val) }) } else { crud.getTable().clearSelection() } }, /** * 用于树形表格多选,单选的封装 * @param selection * @param row */ selectChange(selection, row) { // 如果selection中存在row代表是选中,否则是取消选中 if (selection.find(val => { return crud.getDataId(val) === crud.getDataId(row) })) { if (row.children) { row.children.forEach(val => { crud.getTable().toggleRowSelection(val, true) selection.push(val) if (val.children) { crud.selectChange(selection, val) } }) } } else { crud.toggleRowSelection(selection, row) } }, /** * 切换选中状态 * @param selection * @param data */ toggleRowSelection(selection, data) { if (data.children) { data.children.forEach(val => { crud.getTable().toggleRowSelection(val, false) if (val.children) { crud.toggleRowSelection(selection, val) } }) } }, findVM(type) { return crud.vms.find(vm => vm && vm.type === type).vm }, notify(title, type = CRUD.NOTIFICATION_TYPE.INFO) { crud.vms[0].vm.$notify({ title, type, duration: 2500 }) }, updateProp(name, value) { Vue.set(crud.props, name, value) }, getDataId(data) { return data[this.idField] }, getTable() { return this.findVM('presenter').$refs.table }, attchTable() { const table = this.getTable() this.updateProp('table', table) const that = this table.$on('expand-change', (row, expanded) => { if (!expanded) { return } const lazyTreeNodeMap = table.store.states.lazyTreeNodeMap row.children = lazyTreeNodeMap[crud.getDataId(row)] if (row.children) { row.children.forEach(ele => { const id = crud.getDataId(ele) if (that.dataStatus[id] === undefined) { that.dataStatus[id] = { delete: 0, edit: 0 } } }) } }) } } const crud = Object.assign({}, data) // 可观测化 Vue.observable(crud) // 附加方法 Object.assign(crud, methods) // 记录初始默认的查询参数,后续重置查询时使用 Object.assign(crud, { defaultQuery: JSON.parse(JSON.stringify(data.query)), // 预留4位存储:组件 主页、头部、分页、表单,调试查看也方便找 vms: Array(4), /** * 注册组件实例 * @param {String} type 类型 * @param {*} vm 组件实例 * @param {Number} index 该参数内部使用 */ registerVM(type, vm, index = -1) { const vmObj = { type, vm: vm } if (index < 0) { this.vms.push(vmObj) return } if (index < 4) { // 内置预留vm数 this.vms[index] = vmObj return } this.vms.length = Math.max(this.vms.length, index) this.vms.splice(index, 1, vmObj) }, /** * 取消注册组件实例 * @param {*} vm 组件实例 */ unregisterVM(type, vm) { for (let i = this.vms.length - 1; i >= 0; i--) { if (this.vms[i] === undefined) { continue } if (this.vms[i].type === type && this.vms[i].vm === vm) { if (i < 4) { // 内置预留vm数 this.vms[i] = undefined } else { this.vms.splice(i, 1) } break } } } }) // 冻结处理,需要扩展数据的话,使用crud.updateProp(name, value),以crud.props.name形式访问,这个是响应式的,可以做数据绑定 Object.freeze(crud) return crud } // hook VM function callVmHook(crud, hook) { if (crud.debug) { console.log('callVmHook: ' + hook) } const tagHook = crud.tag ? hook + '$' + crud.tag : null let ret = true const nargs = [crud] for (let i = 2; i < arguments.length; ++i) { nargs.push(arguments[i]) } // 有些组件扮演了多个角色,调用钩子时,需要去重 const vmSet = new Set() crud.vms.forEach(vm => vm && vmSet.add(vm.vm)) vmSet.forEach(vm => { if (vm[hook]) { ret = vm[hook].apply(vm, nargs) !== false && ret } if (tagHook && vm[tagHook]) { ret = vm[tagHook].apply(vm, nargs) !== false && ret } }) return ret } function mergeOptions(src, opts) { const optsRet = { ...src } for (const key in src) { if (opts.hasOwnProperty(key)) { optsRet[key] = opts[key] } } return optsRet } /** * 查找crud * @param {*} vm * @param {string} tag */ function lookupCrud(vm, tag) { tag = tag || vm.$attrs['crud-tag'] || 'default' // function lookupCrud(vm, tag) { if (vm.$crud) { const ret = vm.$crud[tag] if (ret) { return ret } } return vm.$parent ? lookupCrud(vm.$parent, tag) : undefined } /** * crud主页 */ function presenter(crud) { if (crud) { console.warn('[CRUD warn]: ' + 'please use $options.cruds() { return CRUD(...) or [CRUD(...), ...] }') } return { data() { // 在data中返回crud,是为了将crud与当前实例关联,组件观测crud相关属性变化 return { crud: this.crud } }, beforeCreate() { this.$crud = this.$crud || {} let cruds = this.$options.cruds instanceof Function ? this.$options.cruds() : crud if (!(cruds instanceof Array)) { cruds = [cruds] } cruds.forEach(ele => { if (this.$crud[ele.tag]) { console.error('[CRUD error]: ' + 'crud with tag [' + ele.tag + ' is already exist') } this.$crud[ele.tag] = ele ele.registerVM('presenter', this, 0) }) this.crud = this.$crud['defalut'] || cruds[0] }, methods: { parseTime }, created() { for (const k in this.$crud) { if (this.$crud[k].queryOnPresenterCreated) { this.$crud[k].toQuery() } } }, destroyed() { for (const k in this.$crud) { this.$crud[k].unregisterVM('presenter', this) } }, mounted() { // 如果table未实例化(例如使用了v-if),请稍后在适当时机crud.attchTable刷新table信息 if (this.$refs.table !== undefined) { this.crud.attchTable() } } } } /** * 头部 */ function header() { return { data() { return { crud: this.crud, query: this.crud.query } }, beforeCreate() { this.crud = lookupCrud(this) this.crud.registerVM('header', this, 1) }, destroyed() { this.crud.unregisterVM('header', this) } } } /** * 分页 */ function pagination() { return { data() { return { crud: this.crud, page: this.crud.page } }, beforeCreate() { this.crud = lookupCrud(this) this.crud.registerVM('pagination', this, 2) }, destroyed() { this.crud.unregisterVM('pagination', this) } } } /** * 表单 */ function form(defaultForm) { return { data() { return { crud: this.crud, form: this.crud.form } }, beforeCreate() { this.crud = lookupCrud(this) this.crud.registerVM('form', this, 3) }, created() { this.crud.defaultForm = defaultForm this.crud.resetForm() }, destroyed() { this.crud.unregisterVM('form', this) } } } /** * crud */ function crud(options = {}) { const defaultOptions = { type: undefined } options = mergeOptions(defaultOptions, options) return { data() { return { crud: this.crud } }, beforeCreate() { this.crud = lookupCrud(this) this.crud.registerVM(options.type, this) }, destroyed() { this.crud.unregisterVM(options.type, this) } } } /** * CRUD钩子 */ CRUD.HOOK = { /** 刷新 - 之前 */ beforeRefresh: 'beforeCrudRefresh', /** 刷新 - 之后 */ afterRefresh: 'afterCrudRefresh', /** 删除 - 之前 */ beforeDelete: 'beforeCrudDelete', /** 删除 - 之后 */ afterDelete: 'afterCrudDelete', /** 删除取消 - 之前 */ beforeDeleteCancel: 'beforeCrudDeleteCancel', /** 删除取消 - 之后 */ afterDeleteCancel: 'afterCrudDeleteCancel', /** 新建 - 之前 */ beforeToAdd: 'beforeCrudToAdd', /** 新建 - 之后 */ afterToAdd: 'afterCrudToAdd', /** 编辑 - 之前 */ beforeToEdit: 'beforeCrudToEdit', /** 编辑 - 之后 */ afterToEdit: 'afterCrudToEdit', /** 开始 "新建/编辑" - 之前 */ beforeToCU: 'beforeCrudToCU', /** 开始 "新建/编辑" - 之后 */ afterToCU: 'afterCrudToCU', /** "新建/编辑" 验证 - 之前 */ beforeValidateCU: 'beforeCrudValidateCU', /** "新建/编辑" 验证 - 之后 */ afterValidateCU: 'afterCrudValidateCU', /** 添加取消 - 之前 */ beforeAddCancel: 'beforeCrudAddCancel', /** 添加取消 - 之后 */ afterAddCancel: 'afterCrudAddCancel', /** 编辑取消 - 之前 */ beforeEditCancel: 'beforeCrudEditCancel', /** 编辑取消 - 之后 */ afterEditCancel: 'afterCrudEditCancel', /** 提交 - 之前 */ beforeSubmit: 'beforeCrudSubmitCU', /** 提交 - 之后 */ afterSubmit: 'afterCrudSubmitCU', afterAddError: 'afterCrudAddError', afterEditError: 'afterCrudEditError' } /** * CRUD状态 */ CRUD.STATUS = { NORMAL: 0, PREPARED: 1, PROCESSING: 2 } /** * CRUD通知类型 */ CRUD.NOTIFICATION_TYPE = { SUCCESS: 'success', WARNING: 'warning', INFO: 'info', ERROR: 'error' } export default CRUD export { presenter, header, form, pagination, crud }