z-crud-table 0.0.35 → 0.0.37

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.
@@ -1,3 +1,4 @@
1
+ (function(){"use strict";try{if(typeof document<"u"){var e=document.createElement("style");e.appendChild(document.createTextNode('.header-cell-content[data-v-ed80fae4]{display:inline-block;width:100%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.crud-table-container[data-v-b3e9c997]{display:flex;flex-direction:column;height:100%;width:100%;min-width:0;min-height:0;overflow:hidden}.search-section-wrapper[data-v-b3e9c997]{flex-shrink:0}.table-content-wrapper[data-v-b3e9c997]{flex:1;min-height:0;overflow:hidden}.pagination-wrapper[data-v-b3e9c997]{flex-shrink:0;padding-top:1rem;display:flex;justify-content:flex-end}*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.visible{visibility:visible}.\\!mr-0{margin-right:0!important}.mb-6{margin-bottom:1.5rem}.mt-2{margin-top:.5rem}.flex{display:flex}.hidden{display:none}.flex-shrink{flex-shrink:1}.flex-shrink-0{flex-shrink:0}.flex-wrap{flex-wrap:wrap}.flex-nowrap{flex-wrap:nowrap}.items-center{align-items:center}.justify-end{justify-content:flex-end}.justify-between{justify-content:space-between}.gap-4{gap:1rem}.gap-x-2{-moz-column-gap:.5rem;column-gap:.5rem}.gap-x-3{-moz-column-gap:.75rem;column-gap:.75rem}.gap-x-4{-moz-column-gap:1rem;column-gap:1rem}.rounded-xl{border-radius:.75rem}.border{border-width:1px}.blur{--tw-blur: blur(8px);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}body{background-color:#f0f2f5;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif;margin:0}#app{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;color:#2c3e50;padding:1rem}@media (min-width: 768px){#app{padding:1.5rem}}.crud-container{transition:box-shadow .3s ease-in-out}.crud-container:hover{box-shadow:0 20px 25px -5px #0000001a,0 8px 10px -6px #0000001a}.query-form .el-form-item{margin-bottom:0!important}.el-table__expanded-cell{padding:1rem 3.5rem!important;background-color:#f8fafc}.action-buttons>*+*{margin-left:.75rem}.el-dialog__header{border-bottom:none;padding:24px 24px 10px}.el-dialog__title{font-size:1.25rem;font-weight:600}.el-dialog__footer{border-top:none;padding:10px 24px 24px}.el-dialog__body{padding:10px 24px}:root{--large-screen-border-color: rgba(120, 153, 199, 1);--large-screen-text-color: rgba(227, 246, 255, 1);--large-screen-search-bg: rgba(210, 224, 243, .1);--large-screen-button-bg: rgba(38, 120, 255, 1);--large-screen-action-link-color: rgba(39, 233, 255, 1);--large-screen-dialog-bg: #1e293b;--large-screen-hover-bg: rgba(120, 153, 199, .15)}.theme-large-screen{color:var(--large-screen-text-color)}.theme-large-screen .crud-container{background-color:transparent!important;border:none}.theme-large-screen .el-table,.theme-large-screen .el-table__expanded-cell,.theme-large-screen .el-table th.el-table__cell,.theme-large-screen .el-table tr,.theme-large-screen .el-table .el-table__row{background-color:transparent!important;color:var(--large-screen-text-color)}.theme-large-screen .el-table th.el-table__cell{font-weight:600}.theme-large-screen .el-table td.el-table__cell,.theme-large-screen .el-table th.el-table__cell.is-leaf{border-bottom:1px solid var(--large-screen-border-color)}.theme-large-screen .el-table--enable-row-hover .el-table__body tr:hover>td.el-table__cell{background-color:var(--large-screen-hover-bg)!important}.theme-large-screen .el-table--border .el-table__inner-wrapper:after,.theme-large-screen .el-table--border:after,.theme-large-screen .el-table--border:before,.theme-large-screen .el-table__inner-wrapper:before{background-color:var(--large-screen-border-color)}.theme-large-screen .el-form-item__label{color:var(--large-screen-text-color)}.theme-large-screen .query-form .el-input__wrapper,.theme-large-screen .query-form .el-select__wrapper{background-color:var(--large-screen-search-bg);box-shadow:none!important;border:1px solid var(--large-screen-border-color)}.theme-large-screen .el-input__inner{color:var(--large-screen-text-color)}.theme-large-screen .el-button--primary,.theme-large-screen .el-button--success{background-color:var(--large-screen-button-bg);border-color:var(--large-screen-button-bg);color:#fff}.theme-large-screen .el-button--primary:hover,.theme-large-screen .el-button--success:hover{filter:brightness(1.2);background-color:var(--large-screen-button-bg);border-color:var(--large-screen-button-bg)}.theme-large-screen .el-button--danger:not(.is-link){background-color:#f56c6c;border-color:#f56c6c;color:#fff}.theme-large-screen .el-table .el-button.is-link{color:var(--large-screen-action-link-color)}.theme-large-screen .el-table .el-button.is-link:hover{filter:brightness(1.2)}.theme-large-screen .el-pagination{--el-pagination-text-color: var(--large-screen-text-color);--el-pagination-bg-color: transparent;--el-pagination-button-disabled-bg-color: rgba(120, 153, 199, .2);--el-pagination-button-bg-color: rgba(120, 153, 199, .3)}.large-screen-dialog .el-dialog{background-color:var(--el-dialog-bg-color)!important;border-radius:8px}.large-screen-dialog .el-dialog__title,.large-screen-dialog .el-dialog__body{color:var(--large-screen-text-color)}.large-screen-dialog .el-form-item__label{color:var(--large-screen-text-color)!important}.large-screen-dialog .el-input__wrapper,.large-screen-dialog .el-select__wrapper{background-color:#7899c71a!important;border:1px solid var(--large-screen-border-color)!important;box-shadow:none!important}.large-screen-dialog .el-input__inner,.large-screen-dialog .el-radio__label{color:var(--large-screen-text-color)!important}.large-screen-dialog .el-button--primary{background-color:var(--large-screen-button-bg);border-color:var(--large-screen-button-bg);color:#fff}')),document.head.appendChild(e)}}catch(t){console.error("vite-plugin-css-injected-by-js",t)}})();
1
2
  import { getCurrentScope as le, onScopeDispose as ie, computed as x, toValue as G, watch as se, shallowRef as ue, getCurrentInstance as de, onMounted as R, defineComponent as J, ref as z, resolveComponent as b, createElementBlock as v, createBlock as h, openBlock as i, toDisplayString as Q, withCtx as f, createElementVNode as $, reactive as M, resolveDirective as pe, normalizeClass as ce, createCommentVNode as y, createVNode as B, renderSlot as g, Fragment as q, createTextVNode as F, withDirectives as fe, mergeProps as H, renderList as ge, createSlots as me } from "vue";
2
3
  import { ElMessage as O, ElMessageBox as ye } from "element-plus";
3
4
  import he from "axios";
@@ -1 +1,2 @@
1
+ (function(){"use strict";try{if(typeof document<"u"){var e=document.createElement("style");e.appendChild(document.createTextNode('.header-cell-content[data-v-ed80fae4]{display:inline-block;width:100%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.crud-table-container[data-v-b3e9c997]{display:flex;flex-direction:column;height:100%;width:100%;min-width:0;min-height:0;overflow:hidden}.search-section-wrapper[data-v-b3e9c997]{flex-shrink:0}.table-content-wrapper[data-v-b3e9c997]{flex:1;min-height:0;overflow:hidden}.pagination-wrapper[data-v-b3e9c997]{flex-shrink:0;padding-top:1rem;display:flex;justify-content:flex-end}*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.visible{visibility:visible}.\\!mr-0{margin-right:0!important}.mb-6{margin-bottom:1.5rem}.mt-2{margin-top:.5rem}.flex{display:flex}.hidden{display:none}.flex-shrink{flex-shrink:1}.flex-shrink-0{flex-shrink:0}.flex-wrap{flex-wrap:wrap}.flex-nowrap{flex-wrap:nowrap}.items-center{align-items:center}.justify-end{justify-content:flex-end}.justify-between{justify-content:space-between}.gap-4{gap:1rem}.gap-x-2{-moz-column-gap:.5rem;column-gap:.5rem}.gap-x-3{-moz-column-gap:.75rem;column-gap:.75rem}.gap-x-4{-moz-column-gap:1rem;column-gap:1rem}.rounded-xl{border-radius:.75rem}.border{border-width:1px}.blur{--tw-blur: blur(8px);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}body{background-color:#f0f2f5;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif;margin:0}#app{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;color:#2c3e50;padding:1rem}@media (min-width: 768px){#app{padding:1.5rem}}.crud-container{transition:box-shadow .3s ease-in-out}.crud-container:hover{box-shadow:0 20px 25px -5px #0000001a,0 8px 10px -6px #0000001a}.query-form .el-form-item{margin-bottom:0!important}.el-table__expanded-cell{padding:1rem 3.5rem!important;background-color:#f8fafc}.action-buttons>*+*{margin-left:.75rem}.el-dialog__header{border-bottom:none;padding:24px 24px 10px}.el-dialog__title{font-size:1.25rem;font-weight:600}.el-dialog__footer{border-top:none;padding:10px 24px 24px}.el-dialog__body{padding:10px 24px}:root{--large-screen-border-color: rgba(120, 153, 199, 1);--large-screen-text-color: rgba(227, 246, 255, 1);--large-screen-search-bg: rgba(210, 224, 243, .1);--large-screen-button-bg: rgba(38, 120, 255, 1);--large-screen-action-link-color: rgba(39, 233, 255, 1);--large-screen-dialog-bg: #1e293b;--large-screen-hover-bg: rgba(120, 153, 199, .15)}.theme-large-screen{color:var(--large-screen-text-color)}.theme-large-screen .crud-container{background-color:transparent!important;border:none}.theme-large-screen .el-table,.theme-large-screen .el-table__expanded-cell,.theme-large-screen .el-table th.el-table__cell,.theme-large-screen .el-table tr,.theme-large-screen .el-table .el-table__row{background-color:transparent!important;color:var(--large-screen-text-color)}.theme-large-screen .el-table th.el-table__cell{font-weight:600}.theme-large-screen .el-table td.el-table__cell,.theme-large-screen .el-table th.el-table__cell.is-leaf{border-bottom:1px solid var(--large-screen-border-color)}.theme-large-screen .el-table--enable-row-hover .el-table__body tr:hover>td.el-table__cell{background-color:var(--large-screen-hover-bg)!important}.theme-large-screen .el-table--border .el-table__inner-wrapper:after,.theme-large-screen .el-table--border:after,.theme-large-screen .el-table--border:before,.theme-large-screen .el-table__inner-wrapper:before{background-color:var(--large-screen-border-color)}.theme-large-screen .el-form-item__label{color:var(--large-screen-text-color)}.theme-large-screen .query-form .el-input__wrapper,.theme-large-screen .query-form .el-select__wrapper{background-color:var(--large-screen-search-bg);box-shadow:none!important;border:1px solid var(--large-screen-border-color)}.theme-large-screen .el-input__inner{color:var(--large-screen-text-color)}.theme-large-screen .el-button--primary,.theme-large-screen .el-button--success{background-color:var(--large-screen-button-bg);border-color:var(--large-screen-button-bg);color:#fff}.theme-large-screen .el-button--primary:hover,.theme-large-screen .el-button--success:hover{filter:brightness(1.2);background-color:var(--large-screen-button-bg);border-color:var(--large-screen-button-bg)}.theme-large-screen .el-button--danger:not(.is-link){background-color:#f56c6c;border-color:#f56c6c;color:#fff}.theme-large-screen .el-table .el-button.is-link{color:var(--large-screen-action-link-color)}.theme-large-screen .el-table .el-button.is-link:hover{filter:brightness(1.2)}.theme-large-screen .el-pagination{--el-pagination-text-color: var(--large-screen-text-color);--el-pagination-bg-color: transparent;--el-pagination-button-disabled-bg-color: rgba(120, 153, 199, .2);--el-pagination-button-bg-color: rgba(120, 153, 199, .3)}.large-screen-dialog .el-dialog{background-color:var(--el-dialog-bg-color)!important;border-radius:8px}.large-screen-dialog .el-dialog__title,.large-screen-dialog .el-dialog__body{color:var(--large-screen-text-color)}.large-screen-dialog .el-form-item__label{color:var(--large-screen-text-color)!important}.large-screen-dialog .el-input__wrapper,.large-screen-dialog .el-select__wrapper{background-color:#7899c71a!important;border:1px solid var(--large-screen-border-color)!important;box-shadow:none!important}.large-screen-dialog .el-input__inner,.large-screen-dialog .el-radio__label{color:var(--large-screen-text-color)!important}.large-screen-dialog .el-button--primary{background-color:var(--large-screen-button-bg);border-color:var(--large-screen-button-bg);color:#fff}')),document.head.appendChild(e)}}catch(t){console.error("vite-plugin-css-injected-by-js",t)}})();
1
2
  (function(u,e){typeof exports=="object"&&typeof module<"u"?e(exports,require("vue"),require("element-plus"),require("axios")):typeof define=="function"&&define.amd?define(["exports","vue","element-plus","axios"],e):(u=typeof globalThis<"u"?globalThis:u||self,e(u.ZCrudTable={},u.Vue,u.ElementPlus,u.axios))})(this,function(u,e,b,z){"use strict";const k=z.create({baseURL:"",timeout:1e4});k.interceptors.request.use(l=>{const a=localStorage.getItem("token");return a&&(l.headers.Authorization="Bearer "+a),l},l=>(console.log(l),Promise.reject(l))),k.interceptors.response.use(l=>{const a=l.data;return a.code!==200?(b.ElMessage({message:a.msg||"Error",type:"error",duration:5*1e3}),(a.code===401||a.code===403)&&b.ElMessageBox.confirm("您的登录状态已失效,请重新登录","确认登出",{confirmButtonText:"重新登录",cancelButtonText:"取消",type:"warning"}).then(()=>{localStorage.removeItem("token"),location.reload()}),Promise.reject(new Error(a.msg||"Error"))):a},l=>(console.log("HTTP Error: "+l),b.ElMessage({message:"网络错误,请检查您的网络连接或联系管理员",type:"error",duration:5*1e3}),Promise.reject(l)));function F(l){return e.getCurrentScope()?(e.onScopeDispose(l),!0):!1}const $=typeof window<"u"&&typeof document<"u";typeof WorkerGlobalScope<"u"&&globalThis instanceof WorkerGlobalScope;const j=$?window:void 0;function U(l){var a;const p=e.toValue(l);return(a=p==null?void 0:p.$el)!=null?a:p}function q(){const l=e.shallowRef(!1),a=e.getCurrentInstance();return a&&e.onMounted(()=>{l.value=!0},a),l}function M(l){const a=q();return e.computed(()=>(a.value,!!l()))}function W(l,a,p={}){const{window:f=j,...t}=p;let y;const S=M(()=>f&&"ResizeObserver"in f),h=()=>{y&&(y.disconnect(),y=void 0)},x=e.computed(()=>{const m=e.toValue(l);return Array.isArray(m)?m.map(g=>U(g)):[U(m)]}),d=e.watch(x,m=>{if(h(),S.value&&f){y=new ResizeObserver(a);for(const g of m)g&&y.observe(g,t)}},{immediate:!0,flush:"post"}),B=()=>{h(),d()};return F(B),{isSupported:S,stop:B}}const P=e.defineComponent({__name:"TableHeaderWithTooltip",props:{label:{}},setup(l){const a=e.ref(null),p=e.ref(!1),f=()=>{a.value&&(p.value=a.value.scrollWidth>a.value.clientWidth)};return e.onMounted(()=>{f()}),W(a,()=>{f()}),(t,y)=>{const S=e.resolveComponent("el-tooltip");return p.value?(e.openBlock(),e.createBlock(S,{key:1,content:t.label,placement:"top"},{default:e.withCtx(()=>[e.createElementVNode("span",{ref_key:"textRef",ref:a,class:"header-cell-content"},e.toDisplayString(t.label),513)]),_:1},8,["content"])):(e.openBlock(),e.createElementBlock("span",{key:0,ref_key:"textRef",ref:a,class:"header-cell-content"},e.toDisplayString(t.label),513))}}}),A=(l,a)=>{const p=l.__vccOpts||l;for(const[f,t]of a)p[f]=t;return p},Q=A(P,[["__scopeId","data-v-ed80fae4"]]),R={key:0,class:"search-section-wrapper"},I={class:"flex flex-wrap items-center justify-between gap-4 mb-6"},H={class:"flex items-center gap-x-2"},L={class:"flex items-center gap-x-3 action-buttons flex-shrink-0"},G={class:"table-content-wrapper"},Z={key:1},J={key:0,class:"flex items-center gap-x-2"},K={key:1,class:"pagination-wrapper flex justify-end mt-2"},X=A(e.defineComponent({__name:"CrudTable",props:{theme:{type:String,default:"default"},customClass:{type:String,default:""},apiUrlQuery:{type:String,required:!0},apiUrlDetail:{type:String,required:!0},apiUrlCreate:{type:String,required:!0},apiUrlUpdate:{type:String,required:!0},apiUrlDelete:{type:String,required:!0},loadingText:{type:String,default:"加载中…"},loadingBackground:{type:String,default:"rgba(0, 0, 0, 0.3)"},showSearchSection:{type:Boolean,default:!0},showSearchActionButtons:{type:Boolean,default:!0},showNewBtn:{type:Boolean,default:!0},columns:{type:Array,default:()=>[]},onBeforeQuery:{type:Function},onAfterQuery:{type:Function},onBeforeOpenDialog:{type:Function},onAfterOpenDialog:{type:Function},onBeforeSubmit:{type:Function},onAfterSubmit:{type:Function},onBeforeDelete:{type:Function},onAfterDelete:{type:Function},showSelectionColumn:{type:Boolean,default:!0},showIndexColumn:{type:Boolean,default:!0},showActionsColumn:{type:Boolean,default:!0},showEditButton:{type:Boolean,default:!0},showDeleteButton:{type:Boolean,default:!0},actionsColumnWidth:{type:[String,Number],default:120},dialogWidth:{type:String,default:"50%"},initialSearchForm:{type:Object,default:()=>({pageNum:1,pageSize:10})},showPagination:{type:Boolean,default:!0},pageSizes:{type:Array,default:()=>[10,20,50,100]},paginationLayout:{type:String,default:"total, sizes, prev, pager, next, jumper"},paginationBackground:{type:Boolean,default:!0},paginationSmall:{type:Boolean,default:!1},paginationHideOnSinglePage:{type:Boolean,default:!1},dialogFormConfig:{type:Array,default:()=>[]},dialogFormRules:{type:Object,default:()=>({})},submitAsFormData:{type:Boolean,default:!1}},emits:["open-dialog","submit","delete"],setup(l,{expose:a,emit:p}){const f=p,t=l,y=e.computed(()=>["crud-table-wrapper",`theme-${t.theme}`,t.customClass]),S=e.computed(()=>t.theme==="large-screen"?"large-screen-dialog":""),h=(o,n)=>o?!0:(b.ElMessage.error(`${n} prop is required.`),!1),x=async(o,n)=>{try{let r={...n};t.onBeforeSubmit&&(r=await t.onBeforeSubmit(r));let c=r;if(t.submitAsFormData){const D=new FormData;for(const w in r)if(Object.prototype.hasOwnProperty.call(r,w)){const V=r[w];D.append(w,V??"")}c=D}if(s.submitting=!0,o==="add"){if(!h(t.apiUrlCreate,"apiUrlCreate"))throw new Error("apiUrlCreate is not configured.");await k.post(t.apiUrlCreate,c),b.ElMessage.success("新增成功")}else{if(!h(t.apiUrlUpdate,"apiUrlUpdate"))throw new Error("apiUrlUpdate is not configured.");await k.put(t.apiUrlUpdate,c),b.ElMessage.success("更新成功")}return t.onAfterSubmit&&t.onAfterSubmit(o,r),f("submit",{mode:o,data:r}),s.visible&&(s.visible=!1),C(),Promise.resolve()}catch(r){return console.error("Submit failed:",r),Promise.reject(r)}finally{s.submitting=!1}},d=e.reactive({pageNum:1,pageSize:10,...t.initialSearchForm}),B=e.ref([]),m=e.ref(0),g=e.ref(!1),T=e.ref([]),s=e.reactive({visible:!1,loading:!1,submitting:!1,mode:"add",data:{},formRef:null}),Y=e.computed(()=>s.mode==="add"?"新增":"编辑");e.computed(()=>{if(s.mode==="add")return t.dialogFormConfig.filter(n=>n.prop!=="id");const o=[...t.dialogFormConfig.filter(n=>n.prop!=="id")];return o.some(n=>n.prop==="id")||o.unshift({type:"input-disabled",prop:"id",label:"用户ID"}),o});const C=async()=>{if(h(t.apiUrlQuery,"apiUrlQuery")){g.value=!0;try{let o={...d};t.onBeforeQuery&&(o=await t.onBeforeQuery(o));const n=await k.get(t.apiUrlQuery,{params:o});if(n&&n.data&&Array.isArray(n.data.rows)&&typeof n.data.total=="number"){let r=n.data.rows;t.onAfterQuery&&(r=await t.onAfterQuery(r)),B.value=r,m.value=n.data.total}else console.warn("API response is not in the expected { data: { rows: [], total: 0 } } format."),B.value=[],m.value=0}catch(o){console.error("Fetch data failed:",o)}finally{g.value=!1}}},_=()=>{d.pageNum=1,C()},v=()=>{const{pageNum:o,pageSize:n,...r}=t.initialSearchForm;Object.keys(d).forEach(c=>{c!=="pageNum"&&c!=="pageSize"&&delete d[c]}),Object.assign(d,r),_()},ee=o=>{T.value=o},N=async(o,n)=>{let r;if(o==="add"?r=n?{...n}:{role:"user"}:r={...n},t.onBeforeOpenDialog){const c=await t.onBeforeOpenDialog(o,r);c&&(r=c)}if(s.mode=o,s.visible=!0,o==="edit"){if(!h(t.apiUrlDetail,"apiUrlDetail"))return;s.loading=!0;try{const c=await k.get(t.apiUrlDetail+"/"+r.id.toString());s.data=c.data.data}finally{s.loading=!1,t.onAfterOpenDialog&&t.onAfterOpenDialog(o,s.data),f("open-dialog",{mode:o,data:s.data})}}else s.data=r,t.onAfterOpenDialog&&t.onAfterOpenDialog(o,s.data),f("open-dialog",{mode:o,data:s.data})},O=async o=>{if(h(t.apiUrlDelete,"apiUrlDelete"))try{if(t.onBeforeDelete&&await t.onBeforeDelete(o)===!1)return;const n=o.join(",");await k.delete(t.apiUrlDelete+"/"+n.toString()),b.ElMessage.success("删除成功"),t.onAfterDelete&&t.onAfterDelete(o),f("delete",o),B.value.length===o.length&&d.pageNum>1&&d.pageNum--,C()}catch(n){console.error("Delete failed",n)}},te=o=>{d.pageSize=o,_()},oe=o=>{d.pageNum=o,C()};return e.onMounted(C),a({refresh:C,search:_,handleDelete:O,openDialog:N,submit:x}),(o,n)=>{const r=e.resolveComponent("el-button"),c=e.resolveComponent("el-form-item"),D=e.resolveComponent("el-form"),w=e.resolveComponent("el-table-column"),V=e.resolveComponent("el-popconfirm"),ne=e.resolveComponent("el-table"),re=e.resolveComponent("el-pagination"),ae=e.resolveComponent("el-dialog"),le=e.resolveDirective("loading");return e.openBlock(),e.createElementBlock("div",{class:e.normalizeClass([y.value,"crud-table-container"])},[t.showSearchSection?(e.openBlock(),e.createElementBlock("div",R,[e.renderSlot(o.$slots,"header",{},void 0,!0),e.createElementVNode("div",I,[e.createVNode(D,{model:d,class:"query-form flex flex-nowrap items-center gap-x-4",style:{"overflow-x":"auto","padding-bottom":"8px"}},{default:e.withCtx(()=>[e.renderSlot(o.$slots,"query-conditions",{searchForm:d},void 0,!0),e.createVNode(c,{class:"!mr-0 flex-shrink-0"},{default:e.withCtx(()=>[e.createElementVNode("div",H,[e.renderSlot(o.$slots,"query-left",{},void 0,!0),t.showSearchActionButtons?(e.openBlock(),e.createElementBlock(e.Fragment,{key:0},[e.createVNode(r,{type:"primary",onClick:_,loading:g.value},{default:e.withCtx(()=>n[4]||(n[4]=[e.createTextVNode("搜索")])),_:1,__:[4]},8,["loading"]),e.createVNode(r,{onClick:v},{default:e.withCtx(()=>n[5]||(n[5]=[e.createTextVNode("清空")])),_:1,__:[5]})],64)):e.createCommentVNode("",!0),e.renderSlot(o.$slots,"query-right",{},void 0,!0)])]),_:3})]),_:3},8,["model"]),e.createElementVNode("div",L,[e.renderSlot(o.$slots,"action-left",{selections:T.value},void 0,!0),e.renderSlot(o.$slots,"add-button-content",{},()=>[t.showNewBtn?(e.openBlock(),e.createBlock(r,{key:0,type:"success",onClick:n[0]||(n[0]=i=>N("add"))},{default:e.withCtx(()=>n[6]||(n[6]=[e.createTextVNode("新增")])),_:1,__:[6]})):e.createCommentVNode("",!0)],!0),e.renderSlot(o.$slots,"action-right",{},void 0,!0)])])])):e.createCommentVNode("",!0),e.createElementVNode("div",G,[e.withDirectives((e.openBlock(),e.createBlock(ne,e.mergeProps({data:B.value,"element-loading-text":t.loadingText,"element-loading-background":t.loadingBackground,onSelectionChange:ee},o.$attrs,{height:"100%",style:{width:"100%"}}),{default:e.withCtx(()=>[t.showSelectionColumn?(e.openBlock(),e.createBlock(w,{key:0,type:"selection",width:"55",fixed:""})):e.createCommentVNode("",!0),t.showIndexColumn?(e.openBlock(),e.createBlock(w,{key:1,type:"index",label:"序号",width:"70",fixed:""})):e.createCommentVNode("",!0),(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(t.columns,i=>(e.openBlock(),e.createBlock(w,e.mergeProps({key:i.prop,prop:i.prop,label:i.label,width:i.width,sortable:i.sortable||!1},{ref_for:!0},i.attrs),e.createSlots({header:e.withCtx(()=>[i.headerTooltip?(e.openBlock(),e.createBlock(Q,{key:0,label:i.label},null,8,["label"])):(e.openBlock(),e.createElementBlock("span",Z,e.toDisplayString(i.label),1))]),_:2},[i.slot?{name:"default",fn:e.withCtx(E=>[e.renderSlot(o.$slots,i.slot,{row:E.row},void 0,!0)]),key:"0"}:void 0]),1040,["prop","label","width","sortable"]))),128)),t.showActionsColumn?(e.openBlock(),e.createBlock(w,{key:2,label:"操作",width:l.actionsColumnWidth},{default:e.withCtx(i=>[i.row?(e.openBlock(),e.createElementBlock("div",J,[o.$slots.actions?e.renderSlot(o.$slots,"actions",{key:0,row:i.row},void 0,!0):(e.openBlock(),e.createElementBlock(e.Fragment,{key:1},[e.renderSlot(o.$slots,"action-before-edit",{row:i.row},void 0,!0),t.showEditButton?(e.openBlock(),e.createBlock(r,{key:0,size:"small",type:"primary",link:"",onClick:E=>N("edit",i.row)},{default:e.withCtx(()=>n[7]||(n[7]=[e.createTextVNode("编辑 ")])),_:2,__:[7]},1032,["onClick"])):e.createCommentVNode("",!0),t.showDeleteButton?(e.openBlock(),e.createBlock(V,{key:1,title:"确定要删除这条数据吗?",onConfirm:E=>O([i.row.id]),"confirm-button-text":"确定","cancel-button-text":"取消",width:"200"},{reference:e.withCtx(()=>[e.createVNode(r,{size:"small",type:"danger",link:""},{default:e.withCtx(()=>n[8]||(n[8]=[e.createTextVNode("删除")])),_:1,__:[8]})]),_:2},1032,["onConfirm"])):e.createCommentVNode("",!0),e.renderSlot(o.$slots,"action-after-delete",{row:i.row},void 0,!0)],64))])):e.createCommentVNode("",!0)]),_:3},8,["width"])):e.createCommentVNode("",!0)]),_:3},16,["data","element-loading-text","element-loading-background"])),[[le,g.value]])]),t.showPagination&&m.value>0?(e.openBlock(),e.createElementBlock("div",K,[e.createVNode(re,{"current-page":d.pageNum,"onUpdate:currentPage":n[1]||(n[1]=i=>d.pageNum=i),"page-size":d.pageSize,"onUpdate:pageSize":n[2]||(n[2]=i=>d.pageSize=i),"page-sizes":t.pageSizes,layout:t.paginationLayout,total:m.value,background:t.paginationBackground,small:t.paginationSmall,"hide-on-single-page":t.paginationHideOnSinglePage,onSizeChange:te,onCurrentChange:oe},null,8,["current-page","page-size","page-sizes","layout","total","background","small","hide-on-single-page"])])):e.createCommentVNode("",!0),e.createVNode(ae,{modelValue:s.visible,"onUpdate:modelValue":n[3]||(n[3]=i=>s.visible=i),title:Y.value,width:t.dialogWidth,"destroy-on-close":!0,"custom-class":S.value},null,8,["modelValue","title","width","custom-class"])],2)}}}),[["__scopeId","data-v-b3e9c997"]]);u.CrudTable=X,Object.defineProperty(u,Symbol.toStringTag,{value:"Module"})});
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "z-crud-table",
3
3
  "private": false,
4
- "version": "0.0.35",
4
+ "version": "0.0.37",
5
5
  "type": "module",
6
6
  "description": "A powerful and flexible CRUD table component for Vue 3 and Element Plus.",
7
7
  "keywords": [
@@ -29,8 +29,7 @@
29
29
  "./dist/style.css": "./dist/style.css"
30
30
  },
31
31
  "files": [
32
- "dist",
33
- "src/components"
32
+ "dist"
34
33
  ],
35
34
  "scripts": {
36
35
  "dev": "vite",
@@ -57,6 +56,7 @@
57
56
  "tailwindcss": "^3.4.4",
58
57
  "typescript": "~5.4.5",
59
58
  "vite": "^6.2.4",
59
+ "vite-plugin-css-injected-by-js": "^3.5.2",
60
60
  "vite-plugin-dts": "^3.9.1",
61
61
  "vite-plugin-vue-devtools": "^7.7.2",
62
62
  "vue": "^3.5.13",
@@ -1 +0,0 @@
1
- .header-cell-content[data-v-ed80fae4]{display:inline-block;width:100%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.crud-table-container[data-v-b3e9c997]{display:flex;flex-direction:column;height:100%;width:100%;min-width:0;min-height:0;overflow:hidden}.search-section-wrapper[data-v-b3e9c997]{flex-shrink:0}.table-content-wrapper[data-v-b3e9c997]{flex:1;min-height:0;overflow:hidden}.pagination-wrapper[data-v-b3e9c997]{flex-shrink:0;padding-top:1rem;display:flex;justify-content:flex-end}*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.visible{visibility:visible}.\!mr-0{margin-right:0!important}.mb-6{margin-bottom:1.5rem}.mt-2{margin-top:.5rem}.flex{display:flex}.hidden{display:none}.flex-shrink{flex-shrink:1}.flex-shrink-0{flex-shrink:0}.flex-wrap{flex-wrap:wrap}.flex-nowrap{flex-wrap:nowrap}.items-center{align-items:center}.justify-end{justify-content:flex-end}.justify-between{justify-content:space-between}.gap-4{gap:1rem}.gap-x-2{-moz-column-gap:.5rem;column-gap:.5rem}.gap-x-3{-moz-column-gap:.75rem;column-gap:.75rem}.gap-x-4{-moz-column-gap:1rem;column-gap:1rem}.rounded-xl{border-radius:.75rem}.border{border-width:1px}.blur{--tw-blur: blur(8px);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}body{background-color:#f0f2f5;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif;margin:0}#app{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;color:#2c3e50;padding:1rem}@media (min-width: 768px){#app{padding:1.5rem}}.crud-container{transition:box-shadow .3s ease-in-out}.crud-container:hover{box-shadow:0 20px 25px -5px #0000001a,0 8px 10px -6px #0000001a}.query-form .el-form-item{margin-bottom:0!important}.el-table__expanded-cell{padding:1rem 3.5rem!important;background-color:#f8fafc}.action-buttons>*+*{margin-left:.75rem}.el-dialog__header{border-bottom:none;padding:24px 24px 10px}.el-dialog__title{font-size:1.25rem;font-weight:600}.el-dialog__footer{border-top:none;padding:10px 24px 24px}.el-dialog__body{padding:10px 24px}:root{--large-screen-border-color: rgba(120, 153, 199, 1);--large-screen-text-color: rgba(227, 246, 255, 1);--large-screen-search-bg: rgba(210, 224, 243, .1);--large-screen-button-bg: rgba(38, 120, 255, 1);--large-screen-action-link-color: rgba(39, 233, 255, 1);--large-screen-dialog-bg: #1e293b;--large-screen-hover-bg: rgba(120, 153, 199, .15)}.theme-large-screen{color:var(--large-screen-text-color)}.theme-large-screen .crud-container{background-color:transparent!important;border:none}.theme-large-screen .el-table,.theme-large-screen .el-table__expanded-cell,.theme-large-screen .el-table th.el-table__cell,.theme-large-screen .el-table tr,.theme-large-screen .el-table .el-table__row{background-color:transparent!important;color:var(--large-screen-text-color)}.theme-large-screen .el-table th.el-table__cell{font-weight:600}.theme-large-screen .el-table td.el-table__cell,.theme-large-screen .el-table th.el-table__cell.is-leaf{border-bottom:1px solid var(--large-screen-border-color)}.theme-large-screen .el-table--enable-row-hover .el-table__body tr:hover>td.el-table__cell{background-color:var(--large-screen-hover-bg)!important}.theme-large-screen .el-table--border .el-table__inner-wrapper:after,.theme-large-screen .el-table--border:after,.theme-large-screen .el-table--border:before,.theme-large-screen .el-table__inner-wrapper:before{background-color:var(--large-screen-border-color)}.theme-large-screen .el-form-item__label{color:var(--large-screen-text-color)}.theme-large-screen .query-form .el-input__wrapper,.theme-large-screen .query-form .el-select__wrapper{background-color:var(--large-screen-search-bg);box-shadow:none!important;border:1px solid var(--large-screen-border-color)}.theme-large-screen .el-input__inner{color:var(--large-screen-text-color)}.theme-large-screen .el-button--primary,.theme-large-screen .el-button--success{background-color:var(--large-screen-button-bg);border-color:var(--large-screen-button-bg);color:#fff}.theme-large-screen .el-button--primary:hover,.theme-large-screen .el-button--success:hover{filter:brightness(1.2);background-color:var(--large-screen-button-bg);border-color:var(--large-screen-button-bg)}.theme-large-screen .el-button--danger:not(.is-link){background-color:#f56c6c;border-color:#f56c6c;color:#fff}.theme-large-screen .el-table .el-button.is-link{color:var(--large-screen-action-link-color)}.theme-large-screen .el-table .el-button.is-link:hover{filter:brightness(1.2)}.theme-large-screen .el-pagination{--el-pagination-text-color: var(--large-screen-text-color);--el-pagination-bg-color: transparent;--el-pagination-button-disabled-bg-color: rgba(120, 153, 199, .2);--el-pagination-button-bg-color: rgba(120, 153, 199, .3)}.large-screen-dialog .el-dialog{background-color:var(--el-dialog-bg-color)!important;border-radius:8px}.large-screen-dialog .el-dialog__title,.large-screen-dialog .el-dialog__body{color:var(--large-screen-text-color)}.large-screen-dialog .el-form-item__label{color:var(--large-screen-text-color)!important}.large-screen-dialog .el-input__wrapper,.large-screen-dialog .el-select__wrapper{background-color:#7899c71a!important;border:1px solid var(--large-screen-border-color)!important;box-shadow:none!important}.large-screen-dialog .el-input__inner,.large-screen-dialog .el-radio__label{color:var(--large-screen-text-color)!important}.large-screen-dialog .el-button--primary{background-color:var(--large-screen-button-bg);border-color:var(--large-screen-button-bg);color:#fff}
@@ -1,623 +0,0 @@
1
- <template>
2
- <div :class="wrapperClass" class="crud-table-container">
3
-
4
- <div v-if="props.showSearchSection" class="search-section-wrapper">
5
- <slot name="header"></slot>
6
- <div class="flex flex-wrap items-center justify-between gap-4 mb-6">
7
- <el-form :model="searchForm" class="query-form flex flex-nowrap items-center gap-x-4"
8
- style="overflow-x: auto; padding-bottom: 8px;">
9
- <slot name="query-conditions" :search-form="searchForm"></slot>
10
- <el-form-item class="!mr-0 flex-shrink-0">
11
- <div class="flex items-center gap-x-2">
12
- <slot name="query-left"></slot>
13
- <template v-if="props.showSearchActionButtons">
14
- <el-button type="primary" @click="handleSearch" :loading="loading">搜索</el-button>
15
- <el-button @click="handleClearSearch">清空</el-button>
16
- </template>
17
- <slot name="query-right"></slot>
18
- </div>
19
- </el-form-item>
20
- </el-form>
21
- <div class="flex items-center gap-x-3 action-buttons flex-shrink-0">
22
- <slot name="action-left" :selections="selections"></slot>
23
- <slot name="add-button-content">
24
- <el-button v-if="props.showNewBtn" type="success" @click="openDialog('add')">新增</el-button>
25
- </slot>
26
- <slot name="action-right"></slot>
27
- </div>
28
- </div>
29
- </div>
30
-
31
- <div class="table-content-wrapper">
32
- <el-table
33
- :data="tableData"
34
- v-loading="loading"
35
- :element-loading-text="props.loadingText"
36
- :element-loading-background="props.loadingBackground"
37
- @selection-change="handleSelectionChange"
38
- v-bind="$attrs"
39
-
40
- height="100%"
41
-
42
- style="width: 100%;"
43
- >
44
- <el-table-column v-if="props.showSelectionColumn" type="selection" width="55" fixed/>
45
- <el-table-column v-if="props.showIndexColumn" type="index" label="序号" width="70" fixed/>
46
- <template v-for="column in props.columns" :key="column.prop">
47
- <el-table-column
48
- :prop="column.prop"
49
- :label="column.label"
50
- :width="column.width"
51
- :sortable="column.sortable || false"
52
- v-bind="column.attrs"
53
- >
54
- <template #header>
55
- <TableHeaderWithTooltip v-if="column.headerTooltip" :label="column.label"/>
56
- <span v-else>{{ column.label }}</span>
57
- </template>
58
-
59
- <template v-if="column.slot" #default="scope">
60
- <slot :name="column.slot" :row="scope.row"></slot>
61
- </template>
62
- </el-table-column>
63
- </template>
64
- <el-table-column v-if="props.showActionsColumn" label="操作" :width="actionsColumnWidth">
65
- <template #default="scope">
66
- <div v-if="scope.row" class="flex items-center gap-x-2">
67
- <slot v-if="$slots.actions" name="actions" :row="scope.row"></slot>
68
- <template v-else>
69
- <slot name="action-before-edit" :row="scope.row"></slot>
70
- <el-button v-if="props.showEditButton" size="small" type="primary" link
71
- @click="openDialog('edit', scope.row)">编辑
72
- </el-button>
73
- <el-popconfirm v-if="props.showDeleteButton" title="确定要删除这条数据吗?"
74
- @confirm="handleDelete([scope.row.id])" confirm-button-text="确定"
75
- cancel-button-text="取消" width="200">
76
- <template #reference>
77
- <el-button size="small" type="danger" link>删除</el-button>
78
- </template>
79
- </el-popconfirm>
80
- <slot name="action-after-delete" :row="scope.row"></slot>
81
- </template>
82
- </div>
83
- </template>
84
- </el-table-column>
85
- </el-table>
86
- </div>
87
-
88
- <div v-if="props.showPagination && total > 0" class="pagination-wrapper flex justify-end mt-2">
89
- <el-pagination
90
- v-model:current-page="searchForm.pageNum"
91
- v-model:page-size="searchForm.pageSize"
92
- :page-sizes="props.pageSizes"
93
- :layout="props.paginationLayout"
94
- :total="total"
95
- :background="props.paginationBackground"
96
- :small="props.paginationSmall"
97
- :hide-on-single-page="props.paginationHideOnSinglePage"
98
- @size-change="handleSizeChange"
99
- @current-change="handleCurrentChange"
100
- />
101
- </div>
102
-
103
- <el-dialog v-model="dialog.visible" :title="dialogTitle" :width="props.dialogWidth" :destroy-on-close="true"
104
- :custom-class="dialogClass">
105
- </el-dialog>
106
- </div>
107
- </template>
108
-
109
- <script setup lang="ts">
110
- import {computed, onMounted, PropType, reactive, ref} from 'vue';
111
- import {ElMessage} from 'element-plus';
112
- import DynamicForm from './DynamicForm.vue';
113
- import request from '@/utils/request';
114
- import TableHeaderWithTooltip from './TableHeaderWithTooltip.vue';
115
-
116
- // --- 1. 组件事件定义 ---
117
- // 定义组件向外触发的自定义事件,允许父组件监听这些关键操作的完成时机。
118
- const emit = defineEmits(['open-dialog', 'submit', 'delete']);
119
-
120
- // --- 2. 组件属性 (Props) 定义 ---
121
- // 定义组件接收的所有外部参数,这是组件配置和行为定制的核心。
122
- const props = defineProps({
123
- /**
124
- * @description 组件的主题风格
125
- * @type {'default' | 'large-screen'}
126
- */
127
- theme: {type: String as PropType<'default' | 'large-screen'>, default: 'default'},
128
- customClass: {type: String, default: ''},
129
- // API 相关配置
130
- apiUrlQuery: {type: String, required: true},
131
- apiUrlDetail: {type: String, required: true},
132
- apiUrlCreate: {type: String, required: true},
133
- apiUrlUpdate: {type: String, required: true},
134
- apiUrlDelete: {type: String, required: true},
135
-
136
- // ✨ [新增] Loading 状态相关配置
137
- /**
138
- * @description 加载数据时显示的提示文字
139
- * @type {String}
140
- */
141
- loadingText: {
142
- type: String,
143
- default: '加载中…' // 默认为‘加载中…’
144
- },
145
- /**
146
- * @description 遮罩层的背景色 (CSS color)
147
- * @type {String}
148
- */
149
- loadingBackground: {
150
- type: String,
151
- default: 'rgba(0, 0, 0, 0.3)' // 默认为 rgba(0, 0, 0, 0.3)
152
- },
153
-
154
- showSearchSection: {type: Boolean, default: true},
155
-
156
- // ✨ [新增] 控制搜索/清空按钮的显示
157
- showSearchActionButtons: {type: Boolean, default: true},
158
-
159
- // 控制新增按钮
160
- showNewBtn: {type: Boolean, default: true},
161
-
162
- // 表格列定义,通过配置数组动态渲染,比 slot 更灵活。
163
- columns: {
164
- type: Array as PropType<any[]>,
165
- default: () => []
166
- },
167
-
168
- // 生命周期钩子函数,允许在特定操作前后注入自定义逻辑。
169
- onBeforeQuery: {type: Function as PropType<(params: any) => Promise<any> | any>},
170
- onAfterQuery: {type: Function as PropType<(data: any[]) => Promise<any[]> | any[]>},
171
- onBeforeOpenDialog: {type: Function as PropType<(mode: string, data?: any) => Promise<any> | any>},
172
- onAfterOpenDialog: {type: Function as PropType<(mode: string, data: any) => void>},
173
- onBeforeSubmit: {type: Function as PropType<(data: any) => Promise<any> | any>},
174
- onAfterSubmit: {type: Function as PropType<(mode: string, data: any) => void>},
175
- onBeforeDelete: {type: Function as PropType<(ids: number[]) => Promise<boolean> | boolean>},
176
- onAfterDelete: {type: Function as PropType<(ids: number[]) => void>},
177
-
178
- // 功能开关,控制UI元素的显示/隐藏。
179
- showSelectionColumn: {type: Boolean, default: true},
180
- showIndexColumn: {type: Boolean, default: true},
181
- showActionsColumn: {type: Boolean, default: true},
182
- showEditButton: {type: Boolean, default: true},
183
- showDeleteButton: {type: Boolean, default: true},
184
-
185
- // UI 定制化配置
186
- actionsColumnWidth: {type: [String, Number], default: 120},
187
- dialogWidth: {type: String, default: '50%'},
188
- initialSearchForm: {type: Object, default: () => ({pageNum: 1, pageSize: 10})},
189
-
190
- // 分页相关配置
191
- showPagination: {type: Boolean, default: true},
192
- pageSizes: {type: Array, default: () => [10, 20, 50, 100]},
193
- paginationLayout: {type: String, default: 'total, sizes, prev, pager, next, jumper'},
194
- paginationBackground: {type: Boolean, default: true},
195
- paginationSmall: {type: Boolean, default: false},
196
- paginationHideOnSinglePage: {type: Boolean, default: false},
197
-
198
- // 弹窗表单配置
199
- dialogFormConfig: {type: Array as () => any[], default: () => []},
200
- dialogFormRules: {type: Object, default: () => ({})},
201
- /**
202
- * @description 是否以 multipart/form-data 格式提交表单。
203
- * 适用于需要上传文件的场景。
204
- * @type {Boolean}
205
- */
206
- submitAsFormData: {type: Boolean, default: false},
207
- });
208
-
209
- // --- 3. 动态计算属性 (Computed) ---
210
-
211
- // 根据 `theme` prop 动态计算组件根元素的 CSS 类名。
212
- const wrapperClass = computed(() => ['crud-table-wrapper', `theme-${props.theme}`, props.customClass]);
213
- // 为大屏主题的弹窗动态添加特定的 CSS 类名。
214
- const dialogClass = computed(() => props.theme === 'large-screen' ? 'large-screen-dialog' : '');
215
-
216
- // --- 4. 内部工具函数 ---
217
-
218
- /**
219
- * @description 验证传入的 URL prop 是否有效,无效则提示错误。
220
- * @param {string | undefined} url - 待验证的 URL 字符串。
221
- * @param {string} propName - prop 的名称,用于错误提示。
222
- * @returns {boolean} - URL 是否有效。
223
- */
224
- const validateUrl = (url: string | undefined, propName: string): boolean => {
225
- if (!url) {
226
- ElMessage.error(`${propName} prop is required.`);
227
- return false;
228
- }
229
- return true;
230
- };
231
-
232
- // [新增] 抽离出核心提交逻辑,并设置为 async
233
- const submit = async (mode: 'add' | 'edit', data: Record<string, any>) => {
234
- try {
235
- // 步骤 1: 运行 onBeforeSubmit 钩子
236
- let finalData = {...data};
237
- if (props.onBeforeSubmit) {
238
- finalData = await props.onBeforeSubmit(finalData);
239
- }
240
-
241
- // 步骤 2: 根据 submitAsFormData Prop 决定是否转换数据
242
- let dataToSend: any = finalData;
243
-
244
- if (props.submitAsFormData) {
245
- const formData = new FormData();
246
- for (const key in finalData) {
247
- if (Object.prototype.hasOwnProperty.call(finalData, key)) {
248
- const value = finalData[key];
249
- formData.append(key, value ?? '');
250
- }
251
- }
252
- dataToSend = formData;
253
- }
254
-
255
- // 步骤 3: 设置提交状态并发起请求
256
- dialog.submitting = true;
257
- if (mode === 'add') {
258
- if (!validateUrl(props.apiUrlCreate, 'apiUrlCreate')) throw new Error('apiUrlCreate is not configured.');
259
- await request.post(props.apiUrlCreate, dataToSend);
260
- ElMessage.success('新增成功');
261
- } else { // edit mode
262
- if (!validateUrl(props.apiUrlUpdate, 'apiUrlUpdate')) throw new Error('apiUrlUpdate is not configured.');
263
- await request.put(props.apiUrlUpdate, dataToSend);
264
- ElMessage.success('更新成功');
265
- }
266
-
267
- // 步骤 4: 运行 onAfterSubmit 钩子并触发事件
268
- if (props.onAfterSubmit) {
269
- props.onAfterSubmit(mode, finalData);
270
- }
271
- emit('submit', {mode, data: finalData});
272
-
273
- // 步骤 5: 清理并刷新
274
- if (dialog.visible) {
275
- dialog.visible = false;
276
- }
277
- fetchData(); // 刷新表格
278
- return Promise.resolve(); // 表示成功
279
- } catch (error) {
280
- console.error('Submit failed:', error);
281
- return Promise.reject(error); // 表示失败
282
- } finally {
283
- dialog.submitting = false; // 无论成功失败,都重置提交状态
284
- }
285
- };
286
-
287
- // --- 5. 核心响应式状态 ---
288
-
289
- // 搜索表单的数据模型,使用 `initialSearchForm` 进行初始化,并包含分页参数。
290
- const searchForm = reactive({pageNum: 1, pageSize: 10, ...props.initialSearchForm});
291
- // 存储从 API 获取的表格数据。
292
- const tableData = ref([]);
293
- // 总记录数,用于分页组件。
294
- const total = ref(0);
295
- // 控制表格加载状态的 loading 指示器。
296
- const loading = ref(false);
297
- // 存储用户在表格中勾选的行数据。
298
- const selections = ref<any[]>([]);
299
- // 统一管理新增/编辑弹窗的状态,包括可见性、加载状态、提交状态、模式及表单数据。
300
- const dialog = reactive<{
301
- visible: boolean;
302
- loading: boolean;
303
- submitting: boolean;
304
- mode: 'add' | 'edit';
305
- data: Record<string, any>;
306
- formRef: any; // 存储 DynamicForm 组件的实例,用于调用其 validate 方法。
307
- }>({visible: false, loading: false, submitting: false, mode: 'add', data: {}, formRef: null});
308
-
309
- // --- 6. 衍生的计算属性 ---
310
-
311
- // 根据 `dialog.mode` 动态计算弹窗的标题。
312
- const dialogTitle = computed(() => (dialog.mode === 'add' ? '新增' : '编辑'));
313
-
314
- // 根据弹窗模式动态计算最终传递给 DynamicForm 的配置。
315
- const finalDialogFormConfig = computed(() => {
316
- // 新增模式下,过滤掉 'id' 字段。
317
- if (dialog.mode === 'add') return props.dialogFormConfig.filter(item => item.prop !== 'id');
318
- // 编辑模式下,确保包含一个禁用的 'id' 输入框以便展示。
319
- const editConfig = [...props.dialogFormConfig.filter(item => item.prop !== 'id')];
320
- if (!editConfig.some(i => i.prop === 'id')) {
321
- editConfig.unshift({type: 'input-disabled', prop: 'id', label: '用户ID'});
322
- }
323
- return editConfig;
324
- });
325
-
326
- // --- 7.核心业务方法 ---
327
-
328
- /**
329
- * @description 核心数据获取方法,负责请求列表数据并更新表格。
330
- */
331
- const fetchData = async () => {
332
- // 校验查询 API 是否已配置
333
- if (!validateUrl(props.apiUrlQuery, 'apiUrlQuery')) return;
334
- loading.value = true;
335
- try {
336
- // 允许在请求前通过 onBeforeQuery 钩子修改参数
337
- let finalParams = {...searchForm};
338
- if (props.onBeforeQuery) {
339
- finalParams = await props.onBeforeQuery(finalParams);
340
- }
341
-
342
- // 发起 GET 请求
343
- const res: any = await request.get(props.apiUrlQuery, {params: finalParams});
344
-
345
- // 校验返回数据格式是否符合预期 { data: { rows: [], total: number } }
346
- if (res && res.data && Array.isArray(res.data.rows) && typeof res.data.total === 'number') {
347
- // 允许在数据渲染前通过 onAfterQuery 钩子格式化数据
348
- let processedData = res.data.rows;
349
- if (props.onAfterQuery) {
350
- processedData = await props.onAfterQuery(processedData);
351
- }
352
- tableData.value = processedData;
353
- total.value = res.data.total;
354
- } else {
355
- console.warn('API response is not in the expected { data: { rows: [], total: 0 } } format.');
356
- tableData.value = [];
357
- total.value = 0;
358
- }
359
- } catch (error) {
360
- console.error("Fetch data failed:", error);
361
- } finally {
362
- // 无论成功或失败,都关闭 loading 状态
363
- loading.value = false;
364
- }
365
- };
366
-
367
- /**
368
- * @description 处理“搜索”按钮点击事件,重置到第一页并重新获取数据。
369
- */
370
- const handleSearch = () => {
371
- searchForm.pageNum = 1;
372
- fetchData();
373
- };
374
-
375
- /**
376
- * @description 处理“清空”按钮点击事件,将搜索条件重置为初始状态。
377
- */
378
- const handleClearSearch = () => {
379
- const {pageNum, pageSize, ...initialFilters} = props.initialSearchForm;
380
- // 清空 searchForm 中除分页外的所有字段
381
- Object.keys(searchForm).forEach(key => {
382
- if (key !== 'pageNum' && key !== 'pageSize') delete (searchForm as any)[key];
383
- });
384
- // 合并初始搜索条件
385
- Object.assign(searchForm, initialFilters);
386
- handleSearch();
387
- };
388
-
389
- /**
390
- * @description 监听表格的多选框变化,更新 `selections` 状态。
391
- */
392
- const handleSelectionChange = (val: any[]) => {
393
- selections.value = val;
394
- };
395
-
396
- /**
397
- * @description 打开新增或编辑弹窗的核心逻辑。
398
- * @param {'add' | 'edit'} mode - 弹窗模式。
399
- * @param {object} [rowData] - (编辑模式下) 当前行的数据。
400
- */
401
- const openDialog = async (mode: 'add' | 'edit', dataPayload?: any) => {
402
- // 1. 定义初始数据变量
403
- let initialData;
404
- if (mode === 'add') {
405
- // 如果是新增模式,且外部传入了数据对象,则使用它;否则使用默认值
406
- initialData = dataPayload ? {...dataPayload} : {role: 'user'};
407
- } else {
408
- // 编辑模式下,dataPayload 就是行数据
409
- initialData = {...dataPayload};
410
- }
411
-
412
- // 2. onBeforeOpenDialog 钩子仍然可以对 initialData 进行最后修改
413
- if (props.onBeforeOpenDialog) {
414
- const processedData = await props.onBeforeOpenDialog(mode, initialData);
415
- if (processedData) initialData = processedData;
416
- }
417
-
418
- dialog.mode = mode;
419
- dialog.visible = true;
420
-
421
- // 3. 后续逻辑基本不变
422
- if (mode === 'edit') {
423
- if (!validateUrl(props.apiUrlDetail, 'apiUrlDetail')) return;
424
- dialog.loading = true;
425
- try {
426
- const res: any = await request.get(props.apiUrlDetail + "/" + initialData.id.toString());
427
- dialog.data = res.data.data;
428
- } finally {
429
- dialog.loading = false;
430
- if (props.onAfterOpenDialog) {
431
- props.onAfterOpenDialog(mode, dialog.data);
432
- }
433
- emit('open-dialog', {mode, data: dialog.data});
434
- }
435
- } else { // 新增模式
436
- dialog.data = initialData; // 直接使用处理后的 initialData
437
- if (props.onAfterOpenDialog) {
438
- props.onAfterOpenDialog(mode, dialog.data);
439
- }
440
- emit('open-dialog', {mode, data: dialog.data});
441
- }
442
- };
443
-
444
- /**
445
- * @description 处理弹窗中“确定”按钮的点击事件,负责表单校验和数据提交。
446
- */
447
- const handleDialogSubmit = async () => {
448
- try {
449
- if (dialog.formRef) await dialog.formRef.validate();
450
-
451
- let finalData = {...dialog.data};
452
- if (props.onBeforeSubmit) {
453
- finalData = await props.onBeforeSubmit(finalData);
454
- }
455
-
456
- // --- 修改开始 ---
457
-
458
- // 声明一个变量来存储最终要发送的数据
459
- let dataToSend: any = finalData;
460
-
461
- // 如果 submitAsFormData prop 为 true,则将数据转换为 FormData
462
- if (props.submitAsFormData) {
463
-
464
- const formData = new FormData();
465
- for (const key in finalData) {
466
- // 确保只添加对象自身的属性
467
- if (Object.prototype.hasOwnProperty.call(finalData, key)) {
468
- const value = finalData[key];
469
- // FormData 可以处理 null, undefined, File 对象等
470
- // 这里我们做个简单处理,如果值为 null 或 undefined,转换为空字符串
471
- formData.append(key, value ?? '');
472
- }
473
- }
474
- dataToSend = formData;
475
- debugger
476
-
477
- }
478
-
479
- // --- 修改结束 ---
480
-
481
- dialog.submitting = true;
482
- if (dialog.mode === 'add') {
483
- if (!validateUrl(props.apiUrlCreate, 'apiUrlCreate')) return;
484
- // 使用 dataToSend 变量进行提交
485
- await request.post(props.apiUrlCreate, dataToSend);
486
- ElMessage.success('新增成功');
487
- } else {
488
- if (!validateUrl(props.apiUrlUpdate, 'apiUrlUpdate')) return;
489
- // 使用 dataToSend 变量进行提交
490
- await request.put(props.apiUrlUpdate, dataToSend);
491
- ElMessage.success('更新成功');
492
- }
493
-
494
- if (props.onAfterSubmit) {
495
- props.onAfterSubmit(dialog.mode, finalData); // 钩子函数仍然传递原始对象
496
- }
497
- emit('submit', {mode: dialog.mode, data: finalData}); // 事件仍然传递原始对象
498
-
499
- dialog.visible = false;
500
- fetchData();
501
- } catch (error) {
502
- console.log('Submit error or validation failed:', error);
503
- } finally {
504
- dialog.submitting = false;
505
- }
506
- };
507
-
508
- /**
509
- * @description 处理删除操作,支持单条和批量删除。
510
- * @param {number[]} ids - 待删除记录的 ID 数组。
511
- */
512
- const handleDelete = async (ids: number[]) => {
513
- if (!validateUrl(props.apiUrlDelete, 'apiUrlDelete')) return;
514
- try {
515
- // 允许通过 onBeforeDelete 钩子进行删除前的二次确认,返回 false 可中止删除。
516
- if (props.onBeforeDelete) {
517
- const canDelete = await props.onBeforeDelete(ids);
518
- if (canDelete === false) return;
519
- }
520
-
521
- // 发起删除请求
522
- const idsString = ids.join(',');
523
- await request.delete(props.apiUrlDelete + "/" + idsString.toString());
524
- ElMessage.success('删除成功');
525
-
526
- // 触发删除后的钩子和事件
527
- if (props.onAfterDelete) {
528
- props.onAfterDelete(ids);
529
- }
530
- emit('delete', ids);
531
-
532
- // 智能分页:如果删除的是当前页的全部数据且不是第一页,则自动跳转到前一页。
533
- if (tableData.value.length === ids.length && searchForm.pageNum > 1) {
534
- searchForm.pageNum--;
535
- }
536
- fetchData();
537
- } catch (error) {
538
- console.error('Delete failed', error);
539
- }
540
- };
541
-
542
- // --- 8. 分页相关方法 ---
543
-
544
- /**
545
- * @description 处理每页显示条数(pageSize)变化的事件。
546
- */
547
- const handleSizeChange = (val: number) => {
548
- searchForm.pageSize = val;
549
- handleSearch();
550
- };
551
- /**
552
- * @description 处理当前页码(pageNum)变化的事件。
553
- */
554
- const handleCurrentChange = (val: number) => {
555
- searchForm.pageNum = val;
556
- fetchData();
557
- };
558
-
559
- // --- 9. Vue 生命周期钩子 ---
560
-
561
- // 组件挂载完成后,立即执行一次数据获取。
562
- onMounted(fetchData);
563
-
564
- // --- 10. 暴露给父组件的方法 ---
565
- // 使用 defineExpose 使父组件可以通过 ref 调用这些内部方法,实现更灵活的交互。
566
- defineExpose({
567
- refresh: fetchData, // 刷新表格
568
- search: handleSearch, // 按当前条件搜索
569
- handleDelete, // 手动触发删除
570
- openDialog, // 手动打开弹窗
571
- submit // 手动提交
572
- });
573
- </script>
574
-
575
- <style scoped>
576
- .crud-table-container {
577
- min-width: 0;
578
- width: 100%;
579
- display: flex;
580
- flex-direction: column;
581
- }
582
-
583
- .crud-table-container {
584
- /* 1. 设置为 Flexbox 容器,并指定垂直方向 */
585
- display: flex;
586
- flex-direction: column;
587
-
588
- /* 2. 关键:让组件自身高度占满其父容器 */
589
- height: 100%;
590
- width: 100%;
591
-
592
- /* 3. 防止 flex 子项在内容溢出时撑开自身 (处理横向和纵向溢出) */
593
- min-width: 0;
594
- min-height: 0;
595
-
596
- /* 4. 确保所有内容都在这个容器内 */
597
- overflow: hidden;
598
- }
599
-
600
- .search-section-wrapper {
601
- /* 搜索区高度由内容决定,不允许被压缩 */
602
- flex-shrink: 0;
603
- }
604
-
605
- .table-content-wrapper {
606
- /* 关键:让表格包装器占据所有剩余的垂直空间 */
607
- flex: 1;
608
-
609
- /* 关键:同样需要这个来防止子元素(el-table)在某些情况下溢出 */
610
- min-height: 0;
611
- overflow: hidden; /* 确保 el-table 不会溢出这个容器 */
612
- }
613
-
614
- .pagination-wrapper {
615
- /* 分页区高度由内容决定,不允许被压缩 */
616
- flex-shrink: 0;
617
-
618
- /* 为分页器添加一些内边距,使其与表格有间距 */
619
- padding-top: 1rem;
620
- display: flex;
621
- justify-content: flex-end;
622
- }
623
- </style>
@@ -1,65 +0,0 @@
1
- <template>
2
- <el-form :model="modelValue" :rules="rules" ref="formRef" v-bind="$attrs">
3
- <template v-for="item in formConfig" :key="item.prop">
4
- <el-form-item :label="item.label" :prop="item.prop">
5
- <el-input
6
- v-if="item.type === 'input'"
7
- v-model="modelValue[item.prop]"
8
- v-bind="item.componentProps"
9
- />
10
- <el-input
11
- v-if="item.type === 'textarea'"
12
- type="textarea"
13
- v-model="modelValue[item.prop]"
14
- v-bind="item.componentProps"
15
- />
16
- <el-select
17
- v-if="item.type === 'select'"
18
- v-model="modelValue[item.prop]"
19
- v-bind="item.componentProps"
20
- >
21
- <el-option
22
- v-for="option in item.options"
23
- :key="option.value"
24
- :label="option.label"
25
- :value="option.value"
26
- />
27
- </el-select>
28
- <el-radio-group
29
- v-if="item.type === 'radio-group'"
30
- v-model="modelValue[item.prop]"
31
- v-bind="item.componentProps"
32
- >
33
- <el-radio
34
- v-for="option in item.options"
35
- :key="option.value"
36
- :label="option.value"
37
- >{{ option.label }}</el-radio
38
- >
39
- </el-radio-group>
40
- <el-input
41
- v-if="item.type === 'input-disabled'"
42
- :model-value="modelValue[item.prop]"
43
- disabled
44
- v-bind="item.componentProps"
45
- />
46
- </el-form-item>
47
- </template>
48
- </el-form>
49
- </template>
50
-
51
- <script setup lang="ts">
52
- import { ref } from 'vue';
53
-
54
- defineProps<{
55
- modelValue: Record<string, any>;
56
- formConfig: any[];
57
- rules?: Record<string, any>;
58
- }>();
59
-
60
- const formRef = ref<any>(null);
61
-
62
- defineExpose({
63
- validate: () => formRef.value?.validate(),
64
- });
65
- </script>
@@ -1,56 +0,0 @@
1
- <template>
2
- <span v-if="!isOverflow" ref="textRef" class="header-cell-content">
3
- {{ label }}
4
- </span>
5
- <el-tooltip v-else :content="label" placement="top">
6
- <span ref="textRef" class="header-cell-content">
7
- {{ label }}
8
- </span>
9
- </el-tooltip>
10
- </template>
11
-
12
- <script setup lang="ts">
13
- import { ref, onMounted } from 'vue';
14
- // 引入 @vueuse/core 的 useResizeObserver 来监听元素尺寸变化
15
- import { useResizeObserver } from '@vueuse/core';
16
-
17
- // 接收父组件传来的表头标题
18
- const props = defineProps<{
19
- label: string;
20
- }>();
21
-
22
- // 创建一个 ref 来引用 DOM 元素
23
- const textRef = ref<HTMLElement | null>(null);
24
- // 创建一个 ref 来存储是否溢出的状态
25
- const isOverflow = ref(false);
26
-
27
- // 定义一个函数来检查是否溢出
28
- const checkOverflow = () => {
29
- if (textRef.value) {
30
- // 关键逻辑:当元素的滚动宽度 > 元素的可见宽度时,即为溢出
31
- isOverflow.value = textRef.value.scrollWidth > textRef.value.clientWidth;
32
- }
33
- };
34
-
35
- // 在组件挂载后,立即执行一次检查
36
- onMounted(() => {
37
- checkOverflow();
38
- });
39
-
40
- // 使用 useResizeObserver 监听元素的尺寸变化
41
- // 当浏览器窗口大小改变或列宽被拖动时,会自动重新检查
42
- useResizeObserver(textRef, () => {
43
- checkOverflow();
44
- });
45
- </script>
46
-
47
- <style scoped>
48
- /* 确保 span 元素应用了文本溢出的基础样式 */
49
- .header-cell-content {
50
- display: inline-block;
51
- width: 100%;
52
- white-space: nowrap;
53
- overflow: hidden;
54
- text-overflow: ellipsis;
55
- }
56
- </style>