UNPKG

z-crud-table

Version:

A powerful and flexible CRUD table component for Vue 3 and Element Plus.

488 lines (487 loc) 19.1 kB
import { defineComponent as W, ref as A, resolveComponent as f, createBlock as p, openBlock as n, mergeProps as v, withCtx as s, createElementBlock as h, Fragment as x, renderList as q, createCommentVNode as g, createTextVNode as _, toDisplayString as ae, computed as P, reactive as L, onMounted as re, resolveDirective as ne, normalizeClass as ie, renderSlot as y, createElementVNode as O, withDirectives as T, createVNode as b, createSlots as se } from "vue"; import { ElMessage as F } from "element-plus"; import ue from "axios"; const de = /* @__PURE__ */ W({ __name: "DynamicForm", props: { modelValue: {}, formConfig: {}, rules: {} }, setup(m, { expose: C }) { const z = A(null); return C({ validate: () => { var i; return (i = z.value) == null ? void 0 : i.validate(); } }), (i, t) => { const B = f("el-input"), j = f("el-option"), w = f("el-select"), d = f("el-radio"), D = f("el-radio-group"), k = f("el-form-item"), U = f("el-form"); return n(), p(U, v({ model: i.modelValue, rules: i.rules, ref_key: "formRef", ref: z }, i.$attrs), { default: s(() => [ (n(!0), h(x, null, q(i.formConfig, (a) => (n(), p(k, { key: a.prop, label: a.label, prop: a.prop }, { default: s(() => [ a.type === "input" ? (n(), p(B, v({ key: 0, modelValue: i.modelValue[a.prop], "onUpdate:modelValue": (o) => i.modelValue[a.prop] = o }, { ref_for: !0 }, a.componentProps), null, 16, ["modelValue", "onUpdate:modelValue"])) : g("", !0), a.type === "textarea" ? (n(), p(B, v({ key: 1, type: "textarea", modelValue: i.modelValue[a.prop], "onUpdate:modelValue": (o) => i.modelValue[a.prop] = o }, { ref_for: !0 }, a.componentProps), null, 16, ["modelValue", "onUpdate:modelValue"])) : g("", !0), a.type === "select" ? (n(), p(w, v({ key: 2, modelValue: i.modelValue[a.prop], "onUpdate:modelValue": (o) => i.modelValue[a.prop] = o }, { ref_for: !0 }, a.componentProps), { default: s(() => [ (n(!0), h(x, null, q(a.options, (o) => (n(), p(j, { key: o.value, label: o.label, value: o.value }, null, 8, ["label", "value"]))), 128)) ]), _: 2 }, 1040, ["modelValue", "onUpdate:modelValue"])) : g("", !0), a.type === "radio-group" ? (n(), p(D, v({ key: 3, modelValue: i.modelValue[a.prop], "onUpdate:modelValue": (o) => i.modelValue[a.prop] = o }, { ref_for: !0 }, a.componentProps), { default: s(() => [ (n(!0), h(x, null, q(a.options, (o) => (n(), p(d, { key: o.value, label: o.value }, { default: s(() => [ _(ae(o.label), 1) ]), _: 2 }, 1032, ["label"]))), 128)) ]), _: 2 }, 1040, ["modelValue", "onUpdate:modelValue"])) : g("", !0), a.type === "input-disabled" ? (n(), p(B, v({ key: 4, "model-value": i.modelValue[a.prop], disabled: "" }, { ref_for: !0 }, a.componentProps), null, 16, ["model-value"])) : g("", !0) ]), _: 2 }, 1032, ["label", "prop"]))), 128)) ]), _: 1 }, 16, ["model", "rules"]); }; } }), S = ue.create({ // VITE_APP_BASE_API 是在 .env 文件中定义的基础 URL // 您可以根据您的项目需求进行修改 baseURL: "", timeout: 1e4 // 请求超时时间 }); S.interceptors.request.use( (m) => { const C = localStorage.getItem("token"); return C && (m.headers.Authorization = "Bearer " + C), m; }, (m) => (console.log(m), Promise.reject(m)) ); S.interceptors.response.use( (m) => m.data, (m) => (console.log("err" + m), F({ message: "接口错误,请刷新接口", // 这里是您要求的统一错误提示 type: "error", duration: 5 * 1e3 }), Promise.reject(m)) ); const pe = { class: "flex flex-wrap items-center justify-between gap-4 mb-6" }, fe = { class: "flex items-center gap-x-2" }, me = { class: "flex items-center gap-x-3 action-buttons flex-shrink-0" }, ge = { key: 0, class: "flex items-center gap-x-2" }, ce = { key: 0, class: "flex justify-end" }, ye = { class: "dialog-footer" }, he = /* @__PURE__ */ W({ __name: "CrudTable", props: { /** * @description 组件的主题风格 * @type {'default' | 'large-screen'} */ theme: { type: String, default: "default" }, // API 相关配置 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 }, // 表格列定义,通过配置数组动态渲染,比 slot 更灵活。 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 }, // 功能开关,控制UI元素的显示/隐藏。 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 }, // UI 定制化配置 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: () => ({}) } }, emits: ["open-dialog", "submit", "delete"], setup(m, { expose: C, emit: z }) { const i = z, t = m, B = P(() => ["crud-table-wrapper", `theme-${t.theme}`]), j = P(() => t.theme === "large-screen" ? "large-screen-dialog" : ""), w = (e, l) => e ? !0 : (F.error(`${l} prop is required.`), !1), d = L({ pageNum: 1, pageSize: 10, ...t.initialSearchForm }), D = A([]), k = A(0), U = A(!1), a = A([]), o = L({ visible: !1, loading: !1, submitting: !1, mode: "add", data: {}, formRef: null }), H = P(() => o.mode === "add" ? "新增" : "编辑"), M = P(() => { if (o.mode === "add") return t.dialogFormConfig.filter((l) => l.prop !== "id"); const e = [...t.dialogFormConfig.filter((l) => l.prop !== "id")]; return e.some((l) => l.prop === "id") || e.unshift({ type: "input-disabled", prop: "id", label: "用户ID" }), e; }), V = async () => { if (w(t.apiUrlQuery, "apiUrlQuery")) { U.value = !0; try { let e = { ...d }; t.onBeforeQuery && (e = await t.onBeforeQuery(e)); const l = await S.get(t.apiUrlQuery, { params: e }); if (l && Array.isArray(l.data) && typeof l.total == "number") { let u = l.data; t.onAfterQuery && (u = await t.onAfterQuery(u)), D.value = u, k.value = l.total; } else console.warn("API response is not in the expected { data: [], total: 0 } format."), D.value = [], k.value = 0; } catch (e) { console.error("Fetch data failed:", e); } finally { U.value = !1; } } }, $ = () => { d.pageNum = 1, V(); }, G = () => { const { pageNum: e, pageSize: l, ...u } = t.initialSearchForm; Object.keys(d).forEach((c) => { c !== "pageNum" && c !== "pageSize" && delete d[c]; }), Object.assign(d, u), $(); }, J = (e) => { a.value = e; }, Q = async (e, l) => { let u = e === "add" ? { role: "user" } : { ...l }; if (t.onBeforeOpenDialog) { const c = await t.onBeforeOpenDialog(e, u); c && (u = c); } if (o.mode = e, o.visible = !0, e === "edit") { if (!w(t.apiUrlDetail, "apiUrlDetail")) return; o.loading = !0; try { const c = await S.get(t.apiUrlDetail + "/" + u.id); o.data = c.data; } finally { o.loading = !1, t.onAfterOpenDialog && t.onAfterOpenDialog(e, o.data), i("open-dialog", { mode: e, data: o.data }); } } else o.data = u, t.onAfterOpenDialog && t.onAfterOpenDialog(e, o.data), i("open-dialog", { mode: e, data: o.data }); }, K = async () => { try { o.formRef && await o.formRef.validate(); let e = { ...o.data }; if (t.onBeforeSubmit && (e = await t.onBeforeSubmit(e)), o.submitting = !0, o.mode === "add") { if (!w(t.apiUrlCreate, "apiUrlCreate")) return; await S.post(t.apiUrlCreate, e), F.success("新增成功"); } else { if (!w(t.apiUrlUpdate, "apiUrlUpdate")) return; await S.put(t.apiUrlUpdate, e), F.success("更新成功"); } t.onAfterSubmit && t.onAfterSubmit(o.mode, e), i("submit", { mode: o.mode, data: e }), o.visible = !1, V(); } catch (e) { console.log("Submit error or validation failed:", e); } finally { o.submitting = !1; } }, E = async (e) => { if (w(t.apiUrlDelete, "apiUrlDelete")) try { if (t.onBeforeDelete && await t.onBeforeDelete(e) === !1) return; const l = e.join(","); await S.delete(t.apiUrlDelete + "/" + l.toString()), F.success("删除成功"), t.onAfterDelete && t.onAfterDelete(e), i("delete", e), D.value.length === e.length && d.pageNum > 1 && d.pageNum--, V(); } catch (l) { console.error("Delete failed", l); } }, X = (e) => { d.pageSize = e, $(); }, Y = (e) => { d.pageNum = e, V(); }; return re(V), C({ refresh: V, // 刷新表格 search: $, // 按当前条件搜索 handleDelete: E, // 手动触发删除 openDialog: Q // 手动打开弹窗 }), (e, l) => { const u = f("el-button"), c = f("el-form-item"), Z = f("el-form"), N = f("el-table-column"), ee = f("el-popconfirm"), te = f("el-table"), oe = f("el-pagination"), le = f("el-dialog"), I = ne("loading"); return n(), h("div", { class: ie(B.value) }, [ y(e.$slots, "header"), O("div", pe, [ b(Z, { model: d, class: "query-form flex flex-nowrap items-center gap-x-4", style: { "overflow-x": "auto", "padding-bottom": "8px" } }, { default: s(() => [ y(e.$slots, "query-conditions", { searchForm: d }), b(c, { class: "!mr-0 flex-shrink-0" }, { default: s(() => [ O("div", fe, [ y(e.$slots, "query-left"), b(u, { style: { "margin-left": "16px" }, type: "primary", onClick: $, loading: U.value }, { default: s(() => l[6] || (l[6] = [ _("搜索 ") ])), _: 1, __: [6] }, 8, ["loading"]), b(u, { onClick: G }, { default: s(() => l[7] || (l[7] = [ _("清空") ])), _: 1, __: [7] }), y(e.$slots, "query-right") ]) ]), _: 3 }) ]), _: 3 }, 8, ["model"]), O("div", me, [ y(e.$slots, "action-left", { selections: a.value }), y(e.$slots, "add-button-content", {}, () => [ b(u, { type: "success", onClick: l[0] || (l[0] = (r) => Q("add")) }, { default: s(() => l[8] || (l[8] = [ _("新增") ])), _: 1, __: [8] }) ]), y(e.$slots, "action-right") ]) ]), T((n(), p(te, v({ data: D.value, onSelectionChange: J }, e.$attrs, { style: { width: "100%", "margin-bottom": "1.5rem" } }), { default: s(() => [ t.showSelectionColumn ? (n(), p(N, { key: 0, type: "selection", width: "55", fixed: "" })) : g("", !0), t.showIndexColumn ? (n(), p(N, { key: 1, type: "index", label: "序号", width: "70", fixed: "" })) : g("", !0), (n(!0), h(x, null, q(t.columns, (r) => (n(), p(N, v({ key: r.prop, prop: r.prop, label: r.label, width: r.width, sortable: r.sortable || !1 }, { ref_for: !0 }, r.attrs), se({ _: 2 }, [ r.slot ? { name: "default", fn: s((R) => [ y(e.$slots, r.slot, { row: R.row }) ]), key: "0" } : void 0 ]), 1040, ["prop", "label", "width", "sortable"]))), 128)), t.showActionsColumn ? (n(), p(N, { key: 2, label: "操作", width: m.actionsColumnWidth }, { default: s((r) => [ r.row ? (n(), h("div", ge, [ y(e.$slots, "actions", { row: r.row }, () => [ t.showEditButton ? (n(), p(u, { key: 0, size: "small", type: "primary", link: "", onClick: (R) => Q("edit", r.row) }, { default: s(() => l[9] || (l[9] = [ _("编辑 ") ])), _: 2, __: [9] }, 1032, ["onClick"])) : g("", !0), t.showDeleteButton ? (n(), p(ee, { key: 1, title: "确定要删除这条数据吗?", onConfirm: (R) => E([r.row.id]), "confirm-button-text": "确定", "cancel-button-text": "取消", width: "200" }, { reference: s(() => [ b(u, { size: "small", type: "danger", link: "" }, { default: s(() => l[10] || (l[10] = [ _("删除") ])), _: 1, __: [10] }) ]), _: 2 }, 1032, ["onConfirm"])) : g("", !0) ]) ])) : g("", !0) ]), _: 3 }, 8, ["width"])) : g("", !0) ]), _: 3 }, 16, ["data"])), [ [I, U.value] ]), t.showPagination && k.value > 0 ? (n(), h("div", ce, [ b(oe, { "current-page": d.pageNum, "onUpdate:currentPage": l[1] || (l[1] = (r) => d.pageNum = r), "page-size": d.pageSize, "onUpdate:pageSize": l[2] || (l[2] = (r) => d.pageSize = r), "page-sizes": t.pageSizes, layout: t.paginationLayout, total: k.value, background: t.paginationBackground, small: t.paginationSmall, "hide-on-single-page": t.paginationHideOnSinglePage, onSizeChange: X, onCurrentChange: Y }, null, 8, ["current-page", "page-size", "page-sizes", "layout", "total", "background", "small", "hide-on-single-page"]) ])) : g("", !0), b(le, { modelValue: o.visible, "onUpdate:modelValue": l[5] || (l[5] = (r) => o.visible = r), title: H.value, width: t.dialogWidth, "destroy-on-close": !0, "custom-class": j.value }, { footer: s(() => [ O("div", ye, [ b(u, { onClick: l[4] || (l[4] = (r) => o.visible = !1) }, { default: s(() => l[11] || (l[11] = [ _("取消") ])), _: 1, __: [11] }), b(u, { type: "primary", onClick: K, loading: o.submitting }, { default: s(() => l[12] || (l[12] = [ _("确定") ])), _: 1, __: [12] }, 8, ["loading"]) ]) ]), default: s(() => [ T((n(), h("div", null, [ y(e.$slots, "dialog-form-content", { formData: o.data, mode: o.mode }, () => [ t.dialogFormConfig.length > 0 ? (n(), p(de, { key: 0, "form-config": M.value, modelValue: o.data, "onUpdate:modelValue": l[3] || (l[3] = (r) => o.data = r), ref: (r) => o.formRef = r, rules: t.dialogFormRules, "label-width": "80px" }, null, 8, ["form-config", "modelValue", "rules"])) : g("", !0) ]) ])), [ [I, o.loading] ]) ]), _: 3 }, 8, ["modelValue", "title", "width", "custom-class"]) ], 2); }; } }); export { he as CrudTable };