el-crud-page 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/table.vue ADDED
@@ -0,0 +1,159 @@
1
+ <!-- crud table 组件 -->
2
+ <template>
3
+ <el-table :data="data" class="crud-table" ref="table" v-bind="combinedProps" v-on="combinedEvents">
4
+ <crud-table-column v-for="column in columns" :indexMethod="(index)=>( baseIndex + 1 + index )"
5
+ v-if="!column[`v-hasPermi`] || !column[`v-hasPermi`].length || $auth.hasPermiOr(column[`v-hasPermi`] || [])"
6
+ :column="column" @action="onRowAction">
7
+ </crud-table-column>
8
+ </el-table>
9
+ </template>
10
+ <script>
11
+
12
+
13
+ /**
14
+ * CRUD table组件
15
+ */
16
+
17
+ import CrudTableColumn from "./tableColumn.js";
18
+
19
+ export default {
20
+ name: "CrudTable",
21
+ components: {
22
+ CrudTableColumn
23
+ },
24
+ inject: ["crud"],
25
+ props: {
26
+ columns: {
27
+ type: Array,
28
+ default: () => []
29
+ },
30
+ on: {
31
+ type: Object,
32
+ default: () => {
33
+ return {};
34
+ }
35
+ },
36
+ data: {
37
+ type: Array,
38
+ default: () => {
39
+ return [];
40
+ }
41
+ }
42
+ },
43
+ data() {
44
+ return {
45
+ emit: {},
46
+ defaultProps: {
47
+ stripe: true,
48
+ size: "mini",
49
+ }
50
+ };
51
+ },
52
+ computed: {
53
+ // 将外部传入的 props 与 crud 内部样式合并
54
+ combinedProps() {
55
+ return Object.assign({}, this.defaultProps, this.$attrs);
56
+ },
57
+ // 将各事件处理函数合并,包括 emit 与 on 上的回调
58
+ combinedEvents() {
59
+ return Object.assign(
60
+ {},
61
+ {
62
+ "selection-change": this.onSelectionChange,
63
+ "sort-change": this.onSortChange,
64
+ "row-contextmenu": this.onRowContextMenu
65
+ },
66
+ this.emit,
67
+ this.on
68
+ );
69
+ },
70
+ baseIndex(){
71
+ if( this.crud?.pagination ){
72
+ return (this.crud?.pagination?.pageNum-1) * this.crud?.pagination?.pageSize;
73
+ }else{
74
+ return 0;
75
+ }
76
+ }
77
+ },
78
+ mounted() {
79
+ this.bindEmits();
80
+ this.bindMethods();
81
+ },
82
+ methods: {
83
+ // 绑定 el-table 回调
84
+ bindEmits() {
85
+ const emits = [
86
+ "select",
87
+ "select-all",
88
+ "cell-mouse-enter",
89
+ "cell-mouse-leave",
90
+ "cell-click",
91
+ "cell-dblclick",
92
+ "row-click",
93
+ "row-contextmenu",
94
+ "row-dblclick",
95
+ "header-click",
96
+ "header-contextmenu",
97
+ "filter-change",
98
+ "current-change",
99
+ "header-dragend",
100
+ "expand-change"
101
+ ];
102
+
103
+ emits.forEach((name) => {
104
+ this.emit[name] = (...args) => {
105
+ this.$emit(name, ...args);
106
+ };
107
+ });
108
+ },
109
+
110
+ // 绑定 el-table 事件
111
+ bindMethods() {
112
+ const methods = [
113
+ "clearSelection",
114
+ "toggleRowSelection",
115
+ "toggleAllSelection",
116
+ "toggleRowExpansion",
117
+ "setCurrentRow",
118
+ "clearSort",
119
+ "clearFilter",
120
+ "doLayout",
121
+ "sort"
122
+ ];
123
+
124
+ methods.forEach((n) => {
125
+ this[n] = this.$refs["table"][n];
126
+ });
127
+ },
128
+ onSelectionChange(selection) {
129
+ this.$emit("selection-change", selection);
130
+ },
131
+ onSortChange({ prop, order }) {
132
+ this.$emit("sort-change", { prop, order });
133
+ },
134
+ onRowContextMenu(row, column, event) {
135
+ this.$emit("row-contextmenu", row, column, event);
136
+ },
137
+ onRowAction(action, scope) {
138
+ this.$emit("row-action", action, scope);
139
+ this.$emit(`row-${action}`, scope);
140
+ }
141
+ }
142
+
143
+ }
144
+ </script>
145
+
146
+ <style scoped lang="scss">
147
+ .crud-table {
148
+ &::v-deep {
149
+ .el-table__body-wrapper {
150
+ .el-table__cell {
151
+ .cell:empty::after {
152
+ content: '/';
153
+ color: #c0c4cc;
154
+ }
155
+ }
156
+ }
157
+ }
158
+ }
159
+ </style>
@@ -0,0 +1,330 @@
1
+ // 组件说明:TableColumn 用于在 CRUD 表格中动态渲染列,支持嵌套子列,处理字典与格式化展示。
2
+ import { renderNode } from "./utils/vnode.jsx";
3
+ import { isFunction, isString, isArray, cloneDeep, isObject,isPromise } from "./utils";
4
+
5
+
6
+ const TableColumn = {
7
+ // 组件名称
8
+ name: "TableColumn",
9
+ data() {
10
+ return {
11
+ // emit:{},
12
+ actionMap: {
13
+ info: {
14
+ label: "详情",
15
+ type: "text",
16
+ action: "info",
17
+ icon: "el-icon-document"
18
+ },
19
+ update: {
20
+ label: "编辑",
21
+ type: "text",
22
+ action: "update",
23
+ icon: "el-icon-edit"
24
+ },
25
+ delete: {
26
+ label: "删除",
27
+ type: "text",
28
+ action: "delete",
29
+ icon: "el-icon-delete"
30
+ },
31
+
32
+ }
33
+ }
34
+ },
35
+ props: {
36
+ // 列配置对象,包含列属性、子列、格式化函数等
37
+ column: {
38
+ type: Object,
39
+ required: true
40
+ },
41
+ // 行索引生成方法,用于动态生成行号
42
+ indexMethod: {
43
+ type: Function,
44
+ required: false
45
+ },
46
+ },
47
+ computed: {
48
+ // 判断当前列是否包含子列
49
+ hasChildren() {
50
+ return this.column.children && this.column.children.length > 0;
51
+ },
52
+ // 生成并返回列的属性集合,默认居中对齐
53
+ columnProps() {
54
+ return Object.assign({}, this.column, {
55
+ align: this.column.align || "center"
56
+ });
57
+ },
58
+ // 生成唯一的列 key,用于 Vue 渲染优化
59
+ columnKey() {
60
+ return "crud-table-column-" + (this.column.prop || Math.random());
61
+ },
62
+
63
+ },
64
+ mounted() {
65
+ },
66
+ methods: {
67
+ // 根据传入值查找并返回字典中对应的对象
68
+ getDict(value) {
69
+ if( isPromise(this.column.dict)){
70
+ this.column.dict.then((dict)=>{
71
+ this.column.dict = dict;
72
+ });
73
+ return null
74
+ }
75
+ // this.column.dict 如果 this.column.dict 是个 function
76
+ // 则调用 this.column.dict(value) 返回字典对象
77
+ if (typeof this.column.dict === "function") {
78
+ return this.column.dict(value).find(d => d.value == value);
79
+ }
80
+
81
+ if (this.column.dict) {
82
+ return this.column.dict?.find(d => d.value == value);
83
+ }
84
+ return null;
85
+ },
86
+ // 检查传入的值是否严格为 null
87
+ isNull(val) {
88
+ return val === null;
89
+ },
90
+ renderNode(vnode, options) {
91
+ return renderNode.call(this, vnode, options);
92
+ },
93
+ renderSelection(h) {
94
+ return h(
95
+ "el-table-column",
96
+ {
97
+ key: this.columnKey,
98
+ props: Object.assign({}, this.columnProps, { index: this.indexMethod })
99
+ },
100
+ []
101
+ );
102
+
103
+ },
104
+ // 渲染操作列
105
+ renderActions(h) {
106
+ return h(
107
+ "el-table-column",
108
+ {
109
+ key: this.columnKey,
110
+ props: Object.assign({}, this.columnProps, { index: this.indexMethod }),
111
+ scopedSlots: {
112
+ default: scope => {
113
+ let actions = this.column.actions;
114
+ if (isFunction(this.column.actions)) {
115
+ actions = this.column.actions({ scope, h });
116
+ }
117
+
118
+ return actions?.map(action => {
119
+
120
+ // 自定义渲染函数
121
+ if (isFunction(action)) {
122
+ return action({ scope, h });
123
+ }
124
+ // 预设的操作映射
125
+ if (isString(action)) {
126
+ action = this.actionMap[action];
127
+ if( !action){
128
+ return null;
129
+ }
130
+ }
131
+ // action 如果是数组 创建 更多 按钮列表
132
+ if (isArray(action)) {
133
+ return h(
134
+ "el-dropdown",
135
+ {
136
+ props: {
137
+ trigger: "hover",
138
+ // size: "mini"
139
+ },
140
+ on: {
141
+ command: (e) => {
142
+ this.$emit("action", e, scope);
143
+ }
144
+ },
145
+ style: {
146
+ right: "auto",
147
+ "margin-left":"10px"
148
+ }
149
+ },
150
+ [
151
+ h(
152
+ "span",
153
+ {
154
+ class: "el-dropdown-link",
155
+ style: "margin-left: 0",
156
+ },
157
+ [
158
+ "更多",
159
+ h("i", {
160
+ class: "el-icon-arrow-down el-icon--right"
161
+ })
162
+ ]
163
+ ),
164
+ h(
165
+ "el-dropdown-menu",
166
+ {
167
+ slot: "dropdown"
168
+ },
169
+ action.map(item => {
170
+ let _item = item
171
+ // 预设的操作映射
172
+ if (isString(_item)) {
173
+ _item = this.actionMap[_item];
174
+ if( !_item){
175
+ return null;
176
+ }
177
+ }
178
+ return h(
179
+ "el-dropdown-item",
180
+ {
181
+ props: {
182
+ command: _item.action,
183
+ icon: _item.icon,
184
+ disabled: _item.disabled,
185
+ },
186
+ },
187
+ _item.label
188
+ );
189
+ })
190
+ )
191
+ ]
192
+ );
193
+ }
194
+
195
+
196
+
197
+ // 渲染 action 按钮
198
+ return h(
199
+ "el-button",
200
+ {
201
+ props: {
202
+ type: action.type || "text",
203
+ size: action.size || "mini",
204
+ icon: action.icon || "",
205
+ disabled: action.disabled || false
206
+ },
207
+ on: {
208
+ click:(e)=> {
209
+ this.$emit("action", action.action, scope);
210
+ }
211
+ }
212
+ },
213
+ action.label
214
+ );
215
+ }).filter(item=>item);
216
+ }
217
+ }
218
+ },
219
+ []
220
+ );
221
+ },
222
+
223
+ },
224
+ // 动态渲染函数,根据列配置渲染 el-table-column
225
+ render(h) {
226
+ if ( this.column.hidden ){
227
+ if( typeof this.column.hidden === "function" ){
228
+ // 如果 hidden 是函数,则调用函数判断是否隐藏
229
+ if (this.column.hidden(this.column)) {
230
+ return null;
231
+ }
232
+ }
233
+ }
234
+
235
+ if (this.column.visible === false) {
236
+ return null;
237
+ }
238
+ if (this.column.type === "selection" || this.column.type === "index") {
239
+ // 对于 selection 类型,直接传递 props 让 Element 内部处理选中状态
240
+ return this.renderSelection(h);
241
+ } else if (this.column.type === "action") {
242
+ // 对于 action 类型,渲染操作列
243
+ return this.renderActions(h);
244
+ }
245
+ return h(
246
+ "el-table-column",
247
+ {
248
+ key: this.columnKey,
249
+ props: Object.assign({}, this.columnProps, { index: this.indexMethod }),
250
+ scopedSlots: {
251
+ // 自定义表头渲染:支持 slot 覆盖默认表头
252
+ header: scope => {
253
+ return this.$scopedSlots["header-" + this.column.prop]
254
+ ? this.$scopedSlots["header-" + this.column.prop](scope)
255
+ : scope.column.label;
256
+ },
257
+ // 自定义单元格渲染:支持 slot、组件、格式化函数或字典
258
+ default: (scope) => {
259
+ let content;
260
+ // 检查是否有自定义 slot 渲染
261
+ if (this.$scopedSlots["column-" + this.column.prop]) {
262
+ content = this.$scopedSlots["column-" + this.column.prop]({
263
+ scope: scope,
264
+ column: this.column
265
+ });
266
+ } else if (this.column.component) {
267
+ // 通过 renderNode 调用渲染组件,确保 this 指向当前实例
268
+ content = this.renderNode(this.column.component, { prop: this.column.prop, scope: scope });
269
+ } else if (this.column.formatter) {
270
+ content = this.column.formatter(
271
+ scope.row,
272
+ scope.column,
273
+ scope.row[this.column.prop],
274
+ scope.$index
275
+ );
276
+ } else if (this.column.dict) {
277
+ const dictItem = this.getDict(scope.row[this.column.prop]);
278
+ if (dictItem?.label) {
279
+ content = h(
280
+ "el-tag",
281
+ {
282
+ props: {
283
+ size: "mini",
284
+ disableTransitions: true,
285
+ type: dictItem.type
286
+ // effect: "dark"
287
+ }
288
+ },
289
+ dictItem.label
290
+ );
291
+ } else {
292
+ content = scope.row[this.column.prop];
293
+ }
294
+ } else if (this.isNull(scope.row[this.column.prop])) {
295
+ content = scope.emptyText;
296
+ } else {
297
+ content = scope.row[this.column.prop];
298
+ }
299
+ return content;
300
+ }
301
+ }
302
+ },
303
+ // 若有子列则递归渲染
304
+ this.hasChildren
305
+ ? this.column.children.map((child, idx) =>
306
+ h(TableColumn, {
307
+ key: idx,
308
+ props: {
309
+ column: child,
310
+ indexMethod: this.indexMethod,
311
+
312
+ },
313
+ on: this.$listeners,
314
+
315
+
316
+
317
+ })
318
+ )
319
+ : []
320
+ );
321
+ },
322
+
323
+ };
324
+
325
+ // 支持自身递归调用(递归组件)
326
+ TableColumn.components = {
327
+ TableColumn
328
+ };
329
+
330
+ export default TableColumn;
@@ -0,0 +1,42 @@
1
+ 'use strict';
2
+
3
+ import clone from './shallow-clone';
4
+ import typeOf from './kind-of';
5
+ import {isPlainObject} from './is-plain-object';
6
+
7
+ function cloneDeep(val, instanceClone) {
8
+ switch (typeOf(val)) {
9
+ case 'object':
10
+ return cloneObjectDeep(val, instanceClone);
11
+ case 'array':
12
+ return cloneArrayDeep(val, instanceClone);
13
+ default: {
14
+ return clone(val);
15
+ }
16
+ }
17
+ }
18
+
19
+ function cloneObjectDeep(val, instanceClone) {
20
+ if (typeof instanceClone === 'function') {
21
+ return instanceClone(val);
22
+ }
23
+ if (instanceClone || isPlainObject(val)) {
24
+ const res = (val.constructor !== undefined) ? new val.constructor() : Object.create(null);
25
+ for (let key in val) {
26
+ res[key] = cloneDeep(val[key], instanceClone);
27
+ }
28
+ return res;
29
+ }
30
+ return val;
31
+ }
32
+
33
+ function cloneArrayDeep(val, instanceClone) {
34
+ const res = new val.constructor(val.length);
35
+ for (let i = 0; i < val.length; i++) {
36
+ res[i] = cloneDeep(val[i], instanceClone);
37
+ }
38
+ return res;
39
+ }
40
+
41
+
42
+ export default cloneDeep;
@@ -0,0 +1,135 @@
1
+ import cloneDeep from './clone-deep';
2
+ import { scrollTo } from './scroll-to';
3
+
4
+
5
+ export function isArray(value) {
6
+ if (typeof Array.isArray === "function") {
7
+ return Array.isArray(value);
8
+ } else {
9
+ return Object.prototype.toString.call(value) === "[object Array]";
10
+ }
11
+ }
12
+
13
+ export function isObject(value) {
14
+ return Object.prototype.toString.call(value) === "[object Object]";
15
+ }
16
+
17
+ export function isNumber(value) {
18
+ return !isNaN(Number(value));
19
+ }
20
+
21
+ export function isFunction(value) {
22
+ return typeof value === "function";
23
+ }
24
+
25
+ export function isString(value) {
26
+ return typeof value === "string";
27
+ }
28
+
29
+ export function isNull(value) {
30
+ return !value && value !== 0;
31
+ }
32
+
33
+ export function isBoolean(value) {
34
+ return typeof value === "boolean";
35
+ }
36
+ export function isPromise(value) {
37
+ return value && typeof value === 'object' && typeof value.then === 'function' && typeof value.catch === 'function';
38
+ }
39
+
40
+ export function isEmpty(value) {
41
+ if (isArray(value)) {
42
+ return value.length === 0;
43
+ }
44
+
45
+ if (isObject(value)) {
46
+ return Object.keys(value).length === 0;
47
+ }
48
+
49
+ return value === "" || value === undefined || value === null;
50
+ }
51
+
52
+ export function clone(obj) {
53
+ return Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj));
54
+ }
55
+
56
+ export function getParent(name) {
57
+ let parent = this.$parent;
58
+
59
+ while (parent) {
60
+ if (parent.$options.componentName !== name) {
61
+ parent = parent.$parent;
62
+ } else {
63
+ return parent;
64
+ }
65
+ }
66
+
67
+ return null;
68
+ }
69
+
70
+
71
+
72
+ export function deepMerge(a, b) {
73
+ let k;
74
+ for (k in b) {
75
+ a[k] =
76
+ a[k] && a[k].toString() === "[object Object]" ? deepMerge(a[k], b[k]) : (a[k] = b[k]);
77
+ }
78
+ return a;
79
+ }
80
+
81
+ export function contains(parent, node) {
82
+ if (document.documentElement.contains) {
83
+ return parent !== node && parent.contains(node);
84
+ } else {
85
+ while (node && (node = node.parentNode)) if (node === parent) return true;
86
+ return false;
87
+ }
88
+ }
89
+
90
+ /**
91
+ * @param {Function} func
92
+ * @param {number} wait
93
+ * @param {boolean} immediate
94
+ * @return {*}
95
+ */
96
+ export function debounce(func, wait, immediate) {
97
+ let timeout, args, context, timestamp, result
98
+
99
+ const later = function() {
100
+ // 据上一次触发时间间隔
101
+ const last = +new Date() - timestamp
102
+
103
+ // 上次被包装函数被调用时间间隔 last 小于设定时间间隔 wait
104
+ if (last < wait && last > 0) {
105
+ timeout = setTimeout(later, wait - last)
106
+ } else {
107
+ timeout = null
108
+ // 如果设定为immediate===true,因为开始边界已经调用过了此处无需调用
109
+ if (!immediate) {
110
+ result = func.apply(context, args)
111
+ if (!timeout) context = args = null
112
+ }
113
+ }
114
+ }
115
+
116
+ return function(...args) {
117
+ context = this
118
+ timestamp = +new Date()
119
+ const callNow = immediate && !timeout
120
+ // 如果延时不存在,重新设定延时
121
+ if (!timeout) timeout = setTimeout(later, wait)
122
+ if (callNow) {
123
+ result = func.apply(context, args)
124
+ context = args = null
125
+ }
126
+
127
+ return result
128
+ }
129
+ }
130
+
131
+ export function toLine(name) {
132
+ return name.replace(/([A-Z])/g, "_$1").toLowerCase();
133
+ }
134
+
135
+ export { cloneDeep,scrollTo };
@@ -0,0 +1,32 @@
1
+ /*!
2
+ * is-plain-object <https://github.com/jonschlinkert/is-plain-object>
3
+ *
4
+ * Copyright (c) 2014-2017, Jon Schlinkert.
5
+ * Released under the MIT License.
6
+ */
7
+
8
+ function isObject(o) {
9
+ return Object.prototype.toString.call(o) === '[object Object]';
10
+ }
11
+
12
+ export function isPlainObject(o) {
13
+ var ctor,prot;
14
+
15
+ if (isObject(o) === false) return false;
16
+
17
+ // If has modified constructor
18
+ ctor = o.constructor;
19
+ if (ctor === undefined) return true;
20
+
21
+ // If has modified prototype
22
+ prot = ctor.prototype;
23
+ if (isObject(prot) === false) return false;
24
+
25
+ // If constructor does not have an Object-specific method
26
+ if (prot.hasOwnProperty('isPrototypeOf') === false) {
27
+ return false;
28
+ }
29
+
30
+ // Most likely a plain Object
31
+ return true;
32
+ };