Browse Source

feat:add pageModal pageSearch pageContent

snow 2 năm trước cách đây
mục cha
commit
49d62453e0
63 tập tin đã thay đổi với 1682 bổ sung1397 xóa
  1. 5 0
      src/components/BasicForm/index.ts
  2. 84 0
      src/components/BasicForm/src/basic-form.vue
  3. 47 0
      src/components/BasicForm/src/types.ts
  4. 0 5
      src/components/Form/index.ts
  5. 0 56
      src/components/Form/src/basic-form.vue
  6. 0 13
      src/components/Form/src/hooks/use-vmodel.ts
  7. 0 31
      src/components/Form/src/types.ts
  8. 5 0
      src/components/PageContent/index.ts
  9. 20 0
      src/components/PageContent/src/actions/action-create.tsx
  10. 52 0
      src/components/PageContent/src/actions/action-delete.tsx
  11. 20 0
      src/components/PageContent/src/actions/action-preview.tsx
  12. 58 0
      src/components/PageContent/src/actions/action-status.tsx
  13. 22 0
      src/components/PageContent/src/actions/action-update.tsx
  14. 15 0
      src/components/PageContent/src/actions/index.ts
  15. 50 0
      src/components/PageContent/src/hooks/use-pagination.ts
  16. 19 0
      src/components/PageContent/src/hooks/use-params.ts
  17. 108 0
      src/components/PageContent/src/hooks/use-request.ts
  18. 120 0
      src/components/PageContent/src/page-content.tsx
  19. 65 0
      src/components/PageContent/src/types.ts
  20. 33 0
      src/components/PageContent/src/utils/create-operation.tsx
  21. 19 0
      src/components/PageContent/src/utils/merge-params.ts
  22. 5 0
      src/components/PageModal/index.ts
  23. 15 0
      src/components/PageModal/src/description.vue
  24. 70 0
      src/components/PageModal/src/hooks/use-page-modal.ts
  25. 81 0
      src/components/PageModal/src/page-modal.vue
  26. 42 0
      src/components/PageModal/src/types.ts
  27. 11 0
      src/components/PageModal/src/utils/create-basic-title.ts
  28. 6 0
      src/components/PageSearch/index.ts
  29. 20 0
      src/components/PageSearch/src/hooks/use-page-search.ts
  30. 46 0
      src/components/PageSearch/src/page-search.vue
  31. 45 0
      src/components/PageSearch/src/types.ts
  32. 11 0
      src/components/PageSearch/src/utils/create-form-data.ts
  33. 0 50
      src/views/parameter/clients/columns.tsx
  34. 0 32
      src/views/parameter/clients/components/detail-dialog.vue
  35. 56 0
      src/views/parameter/clients/config/content.config.ts
  36. 40 0
      src/views/parameter/clients/config/modal.config.ts
  37. 18 0
      src/views/parameter/clients/config/search.config.ts
  38. 27 154
      src/views/parameter/clients/index.vue
  39. 0 19
      src/views/parameter/clients/utils/dialog-helper.ts
  40. 0 67
      src/views/parameter/invoiceheader/columns.tsx
  41. 0 159
      src/views/parameter/invoiceheader/components/action-table.vue
  42. 0 93
      src/views/parameter/invoiceheader/components/edit-dialog.vue
  43. 0 60
      src/views/parameter/invoiceheader/components/search-form.vue
  44. 81 0
      src/views/parameter/invoiceheader/config/content.config.ts
  45. 65 0
      src/views/parameter/invoiceheader/config/modal.config.ts
  46. 18 0
      src/views/parameter/invoiceheader/config/search.config.ts
  47. 32 24
      src/views/parameter/invoiceheader/index.vue
  48. 0 17
      src/views/parameter/invoiceheader/utils/create-rules.ts
  49. 0 18
      src/views/parameter/invoiceheader/utils/dialog-handler.ts
  50. 0 25
      src/views/parameter/invoiceheader/utils/dialog-helper.ts
  51. 0 49
      src/views/parameter/supplierPay/columns.tsx
  52. 55 0
      src/views/parameter/supplierPay/config/content.config.ts
  53. 13 0
      src/views/parameter/supplierPay/config/search.config.ts
  54. 12 130
      src/views/parameter/supplierPay/index.vue
  55. 0 57
      src/views/system/setBtn/columns.tsx
  56. 0 154
      src/views/system/setBtn/components/action-table.vue
  57. 0 138
      src/views/system/setBtn/components/edit-dialog.vue
  58. 2 7
      src/views/system/setBtn/components/menu-tree.vue
  59. 78 0
      src/views/system/setBtn/config/content.config.ts
  60. 37 0
      src/views/system/setBtn/config/modal.config.ts
  61. 41 39
      src/views/system/setBtn/index.vue
  62. 5 0
      src/views/system/setBtn/types.ts
  63. 8 0
      src/views/system/setBtn/utils/create-options.ts

+ 5 - 0
src/components/BasicForm/index.ts

@@ -0,0 +1,5 @@
+import BasicForm from "./src/basic-form.vue";
+
+export * from "./src/types";
+
+export { BasicForm };

+ 84 - 0
src/components/BasicForm/src/basic-form.vue

@@ -0,0 +1,84 @@
+<script setup lang="ts">
+import { ref } from "vue";
+import { ElForm } from "element-plus";
+import { useVModel } from "@vueuse/core";
+import { basicFormProps } from "./types";
+
+const props = defineProps(basicFormProps);
+const formRef = ref<InstanceType<typeof ElForm>>(null);
+const formData = useVModel(props, "formData");
+
+//校验方法
+function vaildate(callback?: (vaild: boolean) => void) {
+  formRef.value?.validate(vaild => {
+    callback && callback(vaild);
+  });
+}
+
+defineExpose({
+  vaildate
+});
+</script>
+
+<template>
+  <el-form
+    ref="formRef"
+    v-model="formData"
+    :model="formData"
+    :disabled="disabled"
+  >
+    <el-row>
+      <template v-for="(item, index) in formItems" :key="index">
+        <el-col v-bind="colLayout">
+          <el-form-item
+            :label="item.label"
+            :prop="item.field"
+            :rules="item.rules"
+            :label-width="item.labelWidth"
+            :style="itemStyle"
+          >
+            <!-- 输入框 -->
+            <template v-if="item.type === 'input'">
+              <el-input
+                v-model="formData[item.field]"
+                :placeholder="item.placeholder"
+              />
+            </template>
+
+            <!-- 选择框 -->
+            <template v-if="item.type === 'select'">
+              <el-select
+                v-model="formData[item.field]"
+                :placeholder="item.placeholder"
+              >
+                <el-option
+                  v-for="(opt, index) in item.options"
+                  :key="index"
+                  :label="opt.label"
+                  :value="opt.value"
+                />
+              </el-select>
+            </template>
+
+            <template v-if="item.type === 'radio'">
+              <el-radio-group v-model="formData[item.field]">
+                <el-radio
+                  v-for="(opt, index) in item.options"
+                  :key="index"
+                  :label="opt.value"
+                  >{{ opt.label }}</el-radio
+                >
+              </el-radio-group>
+            </template>
+
+            <!-- TODO: 日期选择 -->
+          </el-form-item>
+        </el-col>
+      </template>
+
+      <el-form-item>
+        <slot name="action" />
+      </el-form-item>
+    </el-row>
+  </el-form>
+</template>

+ 47 - 0
src/components/BasicForm/src/types.ts

@@ -0,0 +1,47 @@
+import { FormItemRule } from "element-plus";
+import { PropType } from "vue";
+
+export type FormItemType = "input" | "select" | "radio";
+
+export type FormItem = {
+  field: string;
+  label?: string;
+  placeholder?: any;
+  rules?: Array<FormItemRule> | FormItemRule;
+  type?: FormItemType;
+  options?: Array<any>;
+  isHidden?: boolean;
+  otherOptions?: any;
+  labelWidth?: string;
+};
+
+export const basicFormProps = {
+  formItems: {
+    type: Array as PropType<Array<FormItem>>,
+    default: () => []
+  },
+  itemStyle: {
+    type: Object,
+    default: () => ({
+      paddingRight: "20px"
+    })
+  },
+  disabled: {
+    type: Boolean,
+    default: false
+  },
+  colLayout: {
+    type: Object,
+    default: () => ({
+      xl: 6,
+      lg: 8,
+      md: 12,
+      sm: 24,
+      xs: 24
+    })
+  },
+  formData: {
+    type: Object,
+    required: true
+  }
+} as const;

+ 0 - 5
src/components/Form/index.ts

@@ -1,5 +0,0 @@
-import BaiscFrom from "./src/basic-form.vue";
-
-export * from "./src/types";
-
-export default BaiscFrom;

+ 0 - 56
src/components/Form/src/basic-form.vue

@@ -1,56 +0,0 @@
-<script setup lang="ts">
-import { useVModel } from "@vueuse/core";
-import { ElForm } from "element-plus";
-import { ref } from "vue";
-import { basicFormProps } from "./types";
-const props = defineProps(basicFormProps);
-const formRef = ref<InstanceType<typeof ElForm>>(null);
-const formData = useVModel(props, "formData");
-
-//校验方法
-function vaildate(callback?: (vaild: boolean) => void) {
-  formRef.value?.validate(vaild => {
-    callback && callback(vaild);
-  });
-}
-
-defineExpose({
-  vaildate
-});
-</script>
-
-<template>
-  <div class="form__content">
-    <el-form>
-      <template v-for="(item, index) in formItems" :key="index">
-        <el-form-item
-          ref="formRef"
-          :label="item.label"
-          :prop="item.field"
-          :rules="item.rules"
-          :label-width="labelWidth + 'px'"
-        >
-          <template v-if="item.type === 'input'">
-            <el-input
-              v-model="formData[item.field]"
-              :placeholder="item.placeholder"
-            />
-          </template>
-          <template v-if="item.type === 'select'">
-            <el-select
-              v-model="formData[item.field]"
-              :placeholder="item.placeholder"
-            >
-              <el-option
-                v-for="(opt, index) in item.options"
-                :key="index"
-                :label="opt.label"
-                :value="opt.value"
-              />
-            </el-select>
-          </template>
-        </el-form-item>
-      </template>
-    </el-form>
-  </div>
-</template>

+ 0 - 13
src/components/Form/src/hooks/use-vmodel.ts

@@ -1,13 +0,0 @@
-import { computed, getCurrentInstance, Ref } from "vue";
-
-export function useVModel<T = any>(props: unknown, key: string): Ref<T> {
-  const emit = getCurrentInstance().emit;
-  return computed({
-    get() {
-      return props[key];
-    },
-    set(newVal) {
-      emit(`update:${key}`, newVal);
-    }
-  });
-}

+ 0 - 31
src/components/Form/src/types.ts

@@ -1,31 +0,0 @@
-import { FormItemRule, SelectOptionProxy } from "element-plus";
-
-export type FormItemType = "input" | "datepicker" | "select";
-export type FormItem = {
-  label: string;
-  field: string;
-  placeholder?: any;
-  rules?: Array<FormItemRule> | FormItemRule;
-  type?: FormItemType;
-  options?: Array<SelectOptionProxy>;
-  isHidden?: boolean;
-  otherOptions?: any;
-};
-
-export const basicFormProps = {
-  formItems: {
-    type: Array as PropType<Array<FormItem>>,
-    default: () => []
-  },
-  labelWidth: {
-    type: Number,
-    default: 0
-  },
-  colLayout: {
-    type: String
-  },
-  formData: {
-    type: Object,
-    default: () => ({})
-  }
-} as const;

+ 5 - 0
src/components/PageContent/index.ts

@@ -0,0 +1,5 @@
+import PageContent from "./src/page-content";
+
+export { PageContent };
+
+export * from "./src/types";

+ 20 - 0
src/components/PageContent/src/actions/action-create.tsx

@@ -0,0 +1,20 @@
+import { defineComponent } from "vue";
+import { useRenderIcon } from "/@/components/ReIcon/src/hooks";
+
+const ActionCreate = defineComponent({
+  name: "ActionCreate",
+  emits: ["create"],
+  setup(_, { emit }) {
+    return () => (
+      <el-button
+        onClick={() => emit("create")}
+        icon={useRenderIcon("add")}
+        type="primary"
+      >
+        新增
+      </el-button>
+    );
+  }
+});
+
+export default ActionCreate;

+ 52 - 0
src/components/PageContent/src/actions/action-delete.tsx

@@ -0,0 +1,52 @@
+import { defineComponent } from "vue";
+import { actionProps } from "../types";
+import { useRenderIcon } from "/@/components/ReIcon/src/hooks";
+import { useNav } from "/@/layout/hooks/nav";
+import { responseHandle } from "/@/utils/responseHandle";
+
+const ActionDelete = defineComponent({
+  name: "ActionDelete",
+  props: {
+    ...actionProps,
+    row: {
+      type: Object as PropType<any>,
+      required: true
+    }
+  },
+  emits: ["reload"],
+  setup(props, { emit }) {
+    const { logout } = useNav();
+
+    async function handleDelete() {
+      const { config, row } = props;
+      const { code, message } = await config.api({ id: row.id });
+
+      responseHandle({
+        code,
+        message,
+        logout,
+        handler: () => emit("reload")
+      });
+    }
+
+    return () => (
+      <el-popconfirm
+        title="是否确认删除"
+        placement="top"
+        onConfirm={handleDelete}
+        v-slots={{
+          reference: () => (
+            <el-button
+              class="reset-margin"
+              link
+              type="primary"
+              icon={useRenderIcon("delete")}
+            />
+          )
+        }}
+      />
+    );
+  }
+});
+
+export default ActionDelete;

+ 20 - 0
src/components/PageContent/src/actions/action-preview.tsx

@@ -0,0 +1,20 @@
+import { defineComponent } from "vue";
+import { useRenderIcon } from "/@/components/ReIcon/src/hooks";
+
+const ActionPreview = defineComponent({
+  name: "ActionCreate",
+  emits: ["preview"],
+  setup(_, { emit }) {
+    return () => (
+      <el-button
+        class="reset-margin"
+        link
+        type="primary"
+        onClick={() => emit("preview")}
+        icon={useRenderIcon("eye-view")}
+      />
+    );
+  }
+});
+
+export default ActionPreview;

+ 58 - 0
src/components/PageContent/src/actions/action-status.tsx

@@ -0,0 +1,58 @@
+import { defineComponent } from "vue";
+import { actionProps } from "../types";
+import { useRenderIcon } from "/@/components/ReIcon/src/hooks";
+import { useNav } from "/@/layout/hooks/nav";
+import { responseHandle } from "/@/utils/responseHandle";
+
+const ActionStatus = defineComponent({
+  name: "ActionDelete",
+  props: {
+    ...actionProps,
+    row: {
+      type: Object as PropType<any>,
+      required: true
+    }
+  },
+  emits: ["reload"],
+  setup(props, { emit }) {
+    const { logout } = useNav();
+
+    async function handleStatus() {
+      const { config, row } = props;
+      const { status: rawStatus, id } = row;
+
+      const status = String(rawStatus) === "1" ? "0" : "1";
+      const { code, message } = await config.api({ id, status });
+
+      responseHandle({
+        code,
+        message,
+        logout,
+        handler: () => emit("reload")
+      });
+    }
+
+    return () => (
+      <el-popconfirm
+        title={String(props.row.status) === "1" ? "改为禁用?" : "改为启用?"}
+        onConfirm={handleStatus}
+        v-slots={{
+          reference: () => (
+            <el-button
+              class="reset-margin"
+              link
+              type="primary"
+              icon={useRenderIcon(
+                props.row.status === "1"
+                  ? "close-circle-line"
+                  : "checkbox-circle-line"
+              )}
+            />
+          )
+        }}
+      ></el-popconfirm>
+    );
+  }
+});
+
+export default ActionStatus;

+ 22 - 0
src/components/PageContent/src/actions/action-update.tsx

@@ -0,0 +1,22 @@
+import { defineComponent } from "vue";
+import { actionProps } from "../types";
+import { useRenderIcon } from "/@/components/ReIcon/src/hooks";
+
+const ActionUpdate = defineComponent({
+  name: "ActionUpdate",
+  props: actionProps,
+  emits: ["update"],
+  setup(_, { emit }) {
+    return () => (
+      <el-button
+        class="reset-margin"
+        onClick={() => emit("update")}
+        link
+        type="primary"
+        icon={useRenderIcon("edits")}
+      />
+    );
+  }
+});
+
+export default ActionUpdate;

+ 15 - 0
src/components/PageContent/src/actions/index.ts

@@ -0,0 +1,15 @@
+import Create from "./action-create";
+import Delete from "./action-delete";
+import Update from "./action-update";
+import Preview from "./action-preview";
+import Status from "./action-status";
+
+const Operation = {
+  Create,
+  Update,
+  Delete,
+  Preview,
+  Status
+};
+
+export { Operation };

+ 50 - 0
src/components/PageContent/src/hooks/use-pagination.ts

@@ -0,0 +1,50 @@
+import { PaginationProps } from "@pureadmin/table";
+import { reactive } from "vue";
+import { PageContentProps } from "../types";
+
+export function usePagination(props: PageContentProps, { onSearch }: any) {
+  const { pageSize, contentConfig } = props;
+
+  if (contentConfig.notPagination) return {};
+
+  //分页参数
+  const pagination = reactive<PaginationProps>({
+    total: 0,
+    pageSize,
+    currentPage: 1,
+    background: true
+  });
+
+  /**
+   * 设置当前页
+   */
+  async function onCurrentChange(currentPage: number) {
+    pagination.currentPage = currentPage;
+    await onSearch();
+  }
+
+  /**
+   * 设置分页器
+   */
+  async function onSizeChange(pageSize: number) {
+    pagination.pageSize = pageSize;
+    pagination.currentPage = 1;
+    await onSearch();
+  }
+
+  function getPagination() {
+    return pagination;
+  }
+
+  function changePagination(key: string, newVal) {
+    pagination[key] = newVal;
+  }
+
+  return {
+    onSizeChange,
+    onCurrentChange,
+    pagination,
+    getPagination,
+    changePagination
+  };
+}

+ 19 - 0
src/components/PageContent/src/hooks/use-params.ts

@@ -0,0 +1,19 @@
+import { ref } from "vue";
+
+export function useParams() {
+  //查询参数
+  const basicParams = ref<Record<string, any>>({});
+
+  function changeBasicParams(newParams: any = {}) {
+    basicParams.value = newParams;
+  }
+
+  function getBasicParams() {
+    return basicParams.value;
+  }
+
+  return {
+    changeBasicParams,
+    getBasicParams
+  };
+}

+ 108 - 0
src/components/PageContent/src/hooks/use-request.ts

@@ -0,0 +1,108 @@
+import { onMounted, ref } from "vue";
+import { useNav } from "/@/layout/hooks/nav";
+import { ActionType, PageContentProps } from "../types";
+import { mapActionToApi } from "../utils/create-operation";
+import { mergeParams } from "../utils/merge-params";
+import { responseHandle } from "/@/utils/responseHandle";
+import { handleTree } from "@pureadmin/utils";
+import { usePagination } from "./use-pagination";
+import { useParams } from "./use-params";
+
+/**
+ * 请求方法
+ */
+export function useRequeset(props: PageContentProps) {
+  const { logout } = useNav();
+
+  const loading = ref(false);
+  const dataList = ref([]);
+  const initRequest = ref(!props.contentConfig.notReuqiredInit);
+
+  const { getBasicParams, changeBasicParams } = useParams();
+  const paginationConfig = usePagination(props, {
+    onSearch
+  });
+
+  const { changePagination, getPagination } = paginationConfig;
+
+  /**
+   * 搜索
+   */
+  async function onSearch() {
+    const { contentConfig } = props;
+    const { apis, isTree } = contentConfig;
+
+    if (!initRequest.value) return (initRequest.value = true);
+
+    loading.value = true;
+
+    const { code, data, message } = await apis.httpList(
+      mergeParams({
+        pagination: getPagination ? getPagination() : {},
+        basicParams: getBasicParams()
+      })
+    );
+
+    responseHandle({
+      code,
+      message,
+      logout,
+      handler: () => {
+        const list = Array.isArray(data) ? data : data.list;
+        dataList.value = isTree ? handleTree(list ?? []) : list;
+        changePagination("total", data.count);
+      }
+    });
+
+    loading.value = false;
+  }
+
+  /**
+   * 外部使用的请求方法
+   */
+  function getPageData(parmas: any = {}) {
+    changePagination && changePagination("currentPage", 1);
+    changeBasicParams && changeBasicParams(parmas);
+    onSearch();
+  }
+
+  /**
+   * 外部action执行传入数据发起请求
+   */
+  async function onBeforeAction(
+    action: ActionType,
+    data: any = {},
+    responseCallback?: () => void
+  ) {
+    const { contentConfig } = props;
+    const { apis } = contentConfig;
+    const key = mapActionToApi[action];
+
+    loading.value = true;
+
+    const { code, message } = await apis[key](data);
+
+    responseCallback && responseCallback();
+
+    responseHandle({
+      code,
+      message,
+      logout,
+      handler: () => onSearch()
+    });
+
+    loading.value = false;
+  }
+
+  //初始化数据
+  onMounted(() => onSearch());
+
+  return {
+    loading,
+    dataList,
+    onBeforeAction,
+    paginationConfig,
+    getPageData,
+    onSearch
+  };
+}

+ 120 - 0
src/components/PageContent/src/page-content.tsx

@@ -0,0 +1,120 @@
+import { defineComponent } from "vue";
+import PureTable from "@pureadmin/table";
+import { pageContentProps } from "./types";
+import { TableProBar } from "../../ReTable";
+import { createActionProps, createOperation } from "./utils/create-operation";
+import { useRequeset } from "./hooks/use-request";
+import { Operation } from "./actions";
+
+const PageConent = defineComponent({
+  name: "PageContent",
+  props: pageContentProps,
+  emits: [
+    "createBtnClick",
+    "updateBtnClick",
+    "previewBtnClick",
+    "statusBtnClick"
+  ],
+  setup(props, { expose, emit }) {
+    //根据传入的api决定表格需要的操作
+    const action = createOperation(props.contentConfig.apis);
+
+    const {
+      loading,
+      dataList,
+      onSearch,
+      getPageData,
+      onBeforeAction,
+      paginationConfig
+    } = useRequeset(props);
+
+    /**
+     * 渲染需要的操作按钮
+     */
+    function renderOperation(row) {
+      const { contentConfig } = props;
+      const { apis } = contentConfig;
+
+      return (
+        <>
+          <Operation.Preview onPreview={() => emit("previewBtnClick", row)} />
+          {action.status && (
+            <Operation.Status
+              row={row}
+              onReload={() => onSearch()}
+              {...createActionProps("status", apis)}
+            />
+          )}
+          {action.update && (
+            <Operation.Update
+              onUpdate={() => emit("updateBtnClick", row)}
+              {...createActionProps("update", apis)}
+            />
+          )}
+          {action.delete && (
+            <Operation.Delete
+              row={row}
+              onReload={() => onSearch()}
+              {...createActionProps("delete", apis)}
+            />
+          )}
+        </>
+      );
+    }
+
+    function renderPureTable(size, checkList) {
+      const { contentConfig } = props;
+      const { columns } = contentConfig;
+
+      return (
+        <PureTable
+          border
+          align="left"
+          showOverflowTooltip
+          table-layout="auto"
+          size={size}
+          data={dataList.value}
+          columns={columns}
+          checkList={checkList}
+          paginationSmall={size === "small" ? true : false}
+          headerCellStyle={{ background: "#fafafa", color: "#606266" }}
+          {...paginationConfig}
+          v-slots={{
+            operation: ({ row }) => renderOperation(row)
+          }}
+        />
+      );
+    }
+
+    expose({
+      getPageData,
+      onBeforeAction
+    });
+
+    return () => {
+      const { contentConfig } = props;
+      const { title } = contentConfig;
+
+      return (
+        <TableProBar
+          title={title}
+          dataList={dataList.value}
+          onRefresh={onSearch}
+          loading={loading.value}
+          v-slots={{
+            buttons: () =>
+              action.create && (
+                <Operation.Create
+                  {...createActionProps("create", contentConfig.apis)}
+                  onCreate={() => emit("createBtnClick")}
+                />
+              ),
+            default: ({ size, checkList }) => renderPureTable(size, checkList)
+          }}
+        />
+      );
+    };
+  }
+});
+
+export default PageConent;

+ 65 - 0
src/components/PageContent/src/types.ts

@@ -0,0 +1,65 @@
+import { PropType, ExtractPropTypes } from "vue";
+import PageContent from "./page-content";
+
+/***
+ * @param {title} 标题
+ * @param {apis} 请求
+ * @param {columns} 列数据
+ * @param {notReuqiredInit} 首次加载是否需要请求列表
+ * @param {notPagination} 不需要分页器
+ * @param {isTree} 是否为tree
+ */
+export interface ContentConfig {
+  title: string;
+  apis: ContentApis;
+  columns: any;
+  notReuqiredInit?: boolean;
+  notPagination?: boolean;
+  isTree?: boolean;
+}
+
+interface ContentApiResponse {
+  code: number;
+  message: string;
+  data: any;
+}
+
+type API = (data: any) => Promise<ContentApiResponse>;
+
+export type ActionType = "update" | "delete" | "create" | "preview" | "status";
+
+export interface ContentApis {
+  httpAdd?: API;
+  httpList: API;
+  httpDelete?: API;
+  httpUpdate?: API;
+  httpStatus?: API;
+}
+
+export type PageContentInstance = typeof PageContent & {
+  getPageData: (params?: any) => void;
+  onBeforeAction: (
+    type: ActionType,
+    data?: any,
+    responseCallback?: () => void
+  ) => void;
+};
+
+export const pageContentProps = {
+  contentConfig: {
+    type: Object as PropType<ContentConfig>,
+    required: true
+  },
+  pageSize: {
+    type: Number,
+    default: 15
+  }
+} as const;
+
+export const actionProps = {
+  config: {
+    type: Object as PropType<{ api: API }>
+  }
+};
+
+export type PageContentProps = ExtractPropTypes<typeof pageContentProps>;

+ 33 - 0
src/components/PageContent/src/utils/create-operation.tsx

@@ -0,0 +1,33 @@
+import type { ContentApis, ActionType } from "./../types";
+
+export const mapActionToApi = {
+  update: "httpUpdate",
+  create: "httpAdd",
+  delete: "httpDelete",
+  status: "httpStatus"
+};
+
+/**
+ * 生成表格包含的操作
+ */
+export function createOperation(apis: ContentApis) {
+  const keys = Object.keys(mapActionToApi);
+  const actions: Record<string, boolean> = {};
+
+  keys.forEach(key => {
+    actions[key] = !!apis[mapActionToApi[key]];
+  });
+
+  return actions;
+}
+
+/**
+ * 生成操作需要的参数
+ */
+export function createActionProps(type: ActionType, apis: ContentApis) {
+  return {
+    config: {
+      api: apis[mapActionToApi[type]]
+    }
+  };
+}

+ 19 - 0
src/components/PageContent/src/utils/merge-params.ts

@@ -0,0 +1,19 @@
+type Params = {
+  pagination: any;
+  basicParams: any;
+};
+
+/**
+ * 合并查询参数
+ */
+export function mergeParams({ pagination, basicParams }: Params) {
+  if (!pagination) return basicParams;
+
+  const { currentPage: page, pageSize: size } = pagination;
+
+  return {
+    page,
+    size,
+    ...basicParams
+  };
+}

+ 5 - 0
src/components/PageModal/index.ts

@@ -0,0 +1,5 @@
+import PageModal from "./src/page-modal.vue";
+
+export { usePageModal } from "./src/hooks/use-page-modal";
+
+export { PageModal };

+ 15 - 0
src/components/PageModal/src/description.vue

@@ -0,0 +1,15 @@
+<script setup lang="ts">
+import { descritonProps } from "./types";
+
+defineProps(descritonProps);
+</script>
+
+<template>
+  <el-descriptions :column="1" border>
+    <template v-for="(item, index) in config.formItems" :key="index">
+      <el-descriptions-item :label="item.label" align="center">{{
+        defaultInfo[item.field]
+      }}</el-descriptions-item>
+    </template>
+  </el-descriptions>
+</template>

+ 70 - 0
src/components/PageModal/src/hooks/use-page-modal.ts

@@ -0,0 +1,70 @@
+import { Ref, ref } from "vue";
+import { PageModalInstance } from "../types";
+import {
+  ActionType,
+  PageContentInstance
+} from "/@/components/PageContent/src/types";
+
+type CallbackType = () => any;
+
+type Config = {
+  createCallback?: CallbackType;
+  updateCallback?: CallbackType;
+  confirmCallback?: (data: any) => any;
+  pageContentRef: Ref<PageContentInstance>;
+};
+
+export function usePageModal({
+  createCallback,
+  updateCallback,
+  confirmCallback,
+  pageContentRef
+}: Config) {
+  const pageModalRef = ref<PageModalInstance>(null);
+  const defaultInfo = ref<Record<string, string>>({});
+
+  function handleCreateData() {
+    defaultInfo.value = {};
+    pageModalRef.value.type = "create";
+    pageModalRef.value.dialogVisible = true;
+    createCallback && createCallback();
+  }
+
+  function handleUpdateData(item: any) {
+    defaultInfo.value = { ...item };
+    pageModalRef.value.type = "update";
+    pageModalRef.value.dialogVisible = true;
+    updateCallback && updateCallback();
+  }
+
+  function handlePreviewData(item: any) {
+    defaultInfo.value = { ...item };
+    pageModalRef.value.type = "preview";
+    pageModalRef.value.dialogVisible = true;
+  }
+
+  function handleConfrim(type: ActionType, data: any) {
+    const result = confirmCallback ? confirmCallback(data) : {};
+    pageModalRef.value.confirmLoading = true;
+
+    const params = {
+      ...(type === "update" ? { id: defaultInfo.value.id } : {}),
+      ...(result ? result : {}),
+      ...data
+    };
+
+    pageContentRef.value.onBeforeAction(type, params, () => {
+      pageModalRef.value.confirmLoading = false;
+      pageModalRef.value.dialogVisible = false;
+    });
+  }
+
+  return {
+    handleCreateData,
+    handleUpdateData,
+    handlePreviewData,
+    handleConfrim,
+    defaultInfo,
+    pageModalRef
+  };
+}

+ 81 - 0
src/components/PageModal/src/page-modal.vue

@@ -0,0 +1,81 @@
+<script setup lang="ts">
+import { computed, ref, unref, watch } from "vue";
+import Description from "./description.vue";
+import { BasicForm } from "/@/components/BasicForm";
+import { ActionType } from "/@/components/PageContent";
+import { createBasicTitle } from "./utils/create-basic-title";
+import { modalProps } from "./types";
+
+const props = defineProps(modalProps);
+const emit = defineEmits(["confirmBtnClick"]);
+
+const formData = ref({});
+
+const type = ref<ActionType>("preview");
+const formRef = ref<InstanceType<typeof BasicForm>>(null);
+const dialogVisible = ref(false);
+const confirmLoading = ref(false);
+
+//状态是否处于预览详情
+const isPreview = computed(() => type.value === "preview");
+
+const title = computed(() => {
+  const basicTitle = createBasicTitle(type.value);
+  return type.value === "preview"
+    ? props.modalConfig.title + basicTitle
+    : basicTitle + props.modalConfig.title;
+});
+
+watch(
+  () => props.defaultInfo,
+  newVal => {
+    for (const item of props.modalConfig.formItems) {
+      formData.value[item.field] = newVal[item.field];
+    }
+  }
+);
+
+function handleConfirmClick() {
+  formRef.value.vaildate(isVaild => {
+    //表单校验通过派发事件
+    isVaild && emit("confirmBtnClick", unref(type), unref(formData));
+  });
+}
+
+defineExpose({
+  dialogVisible,
+  confirmLoading,
+  type
+});
+</script>
+
+<template>
+  <el-dialog :title="title" v-model="dialogVisible" center destroy-on-close>
+    <!-- 表单 -->
+    <BasicForm
+      ref="formRef"
+      v-if="!prviewDescription"
+      v-bind="modalConfig"
+      v-model:form-data="formData"
+      :disabled="isPreview"
+    />
+
+    <!-- 预览详情为描述列表 -->
+    <Description
+      v-if="prviewDescription"
+      :config="modalConfig"
+      :default-info="defaultInfo"
+    />
+
+    <!-- 保存/重置 -->
+    <div class="flex justify-end" v-if="!isPreview">
+      <el-button
+        type="primary"
+        :loading="confirmLoading"
+        @click="handleConfirmClick"
+        >保存</el-button
+      >
+      <el-button @click="dialogVisible = false">关闭</el-button>
+    </div>
+  </el-dialog>
+</template>

+ 42 - 0
src/components/PageModal/src/types.ts

@@ -0,0 +1,42 @@
+import { ActionType } from "../../PageContent";
+import { FormItem } from "../../BasicForm";
+import pageModal from "./page-modal.vue";
+
+export type PageModalInstance = typeof pageModal & {
+  dialogVisible: boolean;
+  confirmLoading: boolean;
+  type: ActionType;
+};
+
+export interface ModalConfig {
+  title: string;
+  formItems: Array<FormItem>;
+  colLayout?: any;
+  itemStyle?: Record<string, string>;
+}
+
+export const modalProps = {
+  modalConfig: {
+    type: Object as PropType<ModalConfig>,
+    required: true
+  },
+  prviewDescription: {
+    type: Boolean,
+    default: false
+  },
+  defaultInfo: {
+    type: Object,
+    required: true
+  }
+};
+
+export const descritonProps = {
+  config: {
+    type: Object as PropType<ModalConfig>,
+    required: true
+  },
+  defaultInfo: {
+    type: Object,
+    required: true
+  }
+};

+ 11 - 0
src/components/PageModal/src/utils/create-basic-title.ts

@@ -0,0 +1,11 @@
+import { ActionType } from "/@/components/PageContent";
+
+const mapActionToBasicTitle = {
+  update: "编辑",
+  create: "新增",
+  preview: "详情"
+};
+
+export function createBasicTitle(type: ActionType) {
+  return mapActionToBasicTitle[type];
+}

+ 6 - 0
src/components/PageSearch/index.ts

@@ -0,0 +1,6 @@
+import PageSearch from "./src/page-search.vue";
+import { usePageSearch } from "./src/hooks/use-page-search";
+
+export * from "./src/types";
+
+export { PageSearch, usePageSearch };

+ 20 - 0
src/components/PageSearch/src/hooks/use-page-search.ts

@@ -0,0 +1,20 @@
+import { ref } from "vue";
+import { PageContentInstance } from "/@/components/PageContent";
+
+export function usePageSearch() {
+  const pageContentRef = ref<PageContentInstance>(null);
+
+  function handleSearchClick(params: any) {
+    pageContentRef.value.getPageData(params);
+  }
+
+  function handleResetClick() {
+    pageContentRef.value.getPageData();
+  }
+
+  return {
+    pageContentRef,
+    handleSearchClick,
+    handleResetClick
+  };
+}

+ 46 - 0
src/components/PageSearch/src/page-search.vue

@@ -0,0 +1,46 @@
+<script setup lang="ts">
+import { ref } from "vue";
+import { BasicForm } from "/@/components/BasicForm";
+import { useRenderIcon } from "../../ReIcon/src/hooks";
+import { createFormData } from "./utils/create-form-data";
+import { searchFormProps } from "./types";
+
+const props = defineProps(searchFormProps);
+const emit = defineEmits(["searchBtnClick", "resetBtnClick"]);
+
+//初始表格数据
+const defaultFormData = createFormData(props.formConfig.formItems);
+
+const formData = ref({ ...defaultFormData });
+
+//重置搜索
+function handleResetClick() {
+  formData.value = { ...defaultFormData };
+  emit("resetBtnClick");
+}
+
+//搜索
+function handleSearchClick() {
+  emit("searchBtnClick", formData.value);
+}
+</script>
+
+<template>
+  <div class="bg-white w-99/100 pl-8 pt-4">
+    <BasicForm v-bind="formConfig" v-model:form-data="formData">
+      <template #action>
+        <el-button
+          type="primary"
+          :icon="useRenderIcon('search')"
+          @click="handleSearchClick"
+        >
+          搜索
+        </el-button>
+
+        <el-button :icon="useRenderIcon('refresh')" @click="handleResetClick">
+          重置
+        </el-button>
+      </template>
+    </BasicForm>
+  </div>
+</template>

+ 45 - 0
src/components/PageSearch/src/types.ts

@@ -0,0 +1,45 @@
+import { FormItem } from "/@/components/BasicForm";
+
+export interface FormConfig {
+  formItems: Array<FormItem>;
+  colLayout?: any;
+  itemStyle?: Record<string, string>;
+}
+
+export const basicFormProps = {
+  formItems: {
+    type: Array as PropType<Array<FormItem>>,
+    default: () => []
+  },
+  itemStyle: {
+    type: Object,
+    default: () => ({
+      paddingRight: "20px"
+    })
+  },
+  disabled: {
+    type: Boolean,
+    default: false
+  },
+  colLayout: {
+    type: Object,
+    default: () => ({
+      xl: 6,
+      lg: 8,
+      md: 12,
+      sm: 24,
+      xs: 24
+    })
+  },
+  formData: {
+    type: Object,
+    required: true
+  }
+} as const;
+
+export const searchFormProps = {
+  formConfig: {
+    type: Object as PropType<FormConfig>,
+    required: true
+  }
+} as const;

+ 11 - 0
src/components/PageSearch/src/utils/create-form-data.ts

@@ -0,0 +1,11 @@
+import { FormItem } from "/@/components/BasicForm";
+
+export function createFormData(formItems: Array<FormItem>) {
+  const formData: Record<string, any> = {};
+
+  for (const item of formItems) {
+    formData[item.field] = "";
+  }
+
+  return formData;
+}

+ 0 - 50
src/views/parameter/clients/columns.tsx

@@ -1,50 +0,0 @@
-import { ref } from "vue";
-import dayjs from "dayjs";
-export function useColumns() {
-  const columns = ref([
-    {
-      type: "selection",
-      width: 55,
-      hide: ({ checkList }) => !checkList.includes("勾选列")
-    },
-    {
-      label: "序号",
-      type: "index",
-      width: 70,
-      hide: ({ checkList }) => !checkList.includes("序号列")
-    },
-
-    {
-      label: "客户编号",
-      prop: "companyNo"
-    },
-    {
-      label: "客户名称",
-      prop: "companyName"
-    },
-    {
-      label: "归属集团",
-      prop: "parent"
-    },
-    {
-      label: "联系人",
-      prop: "contactor"
-    },
-    {
-      label: "创建时间",
-      prop: "createTime",
-      formatter: ({ createTime }) =>
-        dayjs(createTime).format("YYYY-MM-DD HH:mm:ss")
-    },
-    {
-      label: "操作",
-      fixed: "right",
-      width: 60,
-      slot: "operation"
-    }
-  ]);
-
-  return {
-    columns
-  };
-}

+ 0 - 32
src/views/parameter/clients/components/detail-dialog.vue

@@ -1,32 +0,0 @@
-<script setup lang="ts">
-import { ref } from "vue";
-import { mapKeyToLabel, helper } from "./../utils/dialog-helper";
-import type { IClinetsDetail } from "../types";
-
-const isShow = ref(false);
-const detail = ref<Partial<IClinetsDetail>>({});
-const { getLabel, getDetail } = helper();
-
-//显示
-function show(clinetsDetail: IClinetsDetail) {
-  isShow.value = true;
-  detail.value = clinetsDetail;
-}
-
-defineExpose({
-  show
-});
-</script>
-
-<template>
-  <el-dialog v-model="isShow">
-    <el-descriptions>
-      <el-descriptions-item
-        v-for="(key, index) in Object.keys(mapKeyToLabel)"
-        :label="getLabel(key)"
-        :key="index"
-        >{{ getDetail(key, detail) }}</el-descriptions-item
-      >
-    </el-descriptions>
-  </el-dialog>
-</template>

+ 56 - 0
src/views/parameter/clients/config/content.config.ts

@@ -0,0 +1,56 @@
+import { ContentConfig } from "/@/components/PageContent";
+import { httpList } from "/@/api/parameter/clients";
+
+import dayjs from "dayjs";
+
+const columns = [
+  {
+    type: "selection",
+    width: 55,
+    hide: ({ checkList }) => !checkList.includes("勾选列")
+  },
+  {
+    label: "序号",
+    type: "index",
+    width: 70,
+    hide: ({ checkList }) => !checkList.includes("序号列")
+  },
+  {
+    label: "客户编号",
+    prop: "companyNo"
+  },
+  {
+    label: "客户名称",
+    prop: "companyName"
+  },
+  {
+    label: "归属集团",
+    prop: "parent"
+  },
+  {
+    label: "联系人",
+    prop: "contactor"
+  },
+  {
+    label: "创建时间",
+    prop: "createTime",
+    formatter: ({ createTime }) =>
+      dayjs(createTime).format("YYYY-MM-DD HH:mm:ss")
+  },
+  {
+    label: "操作",
+    fixed: "right",
+    width: 80,
+    slot: "operation"
+  }
+];
+
+const contentConfig: ContentConfig = {
+  title: "企业客户",
+  columns,
+  apis: {
+    httpList
+  }
+};
+
+export default contentConfig;

+ 40 - 0
src/views/parameter/clients/config/modal.config.ts

@@ -0,0 +1,40 @@
+import { ModalConfig } from "../../../../components/PageModal/src/types";
+
+const modalConfig: ModalConfig = {
+  title: "客户",
+  itemStyle: {},
+  formItems: [
+    {
+      field: "companyNo",
+      type: "input",
+      label: "客户编号",
+      labelWidth: "120px"
+    },
+    {
+      field: "companyName",
+      type: "input",
+      labelWidth: "120px",
+      label: "客户名称"
+    },
+    {
+      field: "parent",
+      type: "input",
+      labelWidth: "120px",
+      label: "归属集团"
+    },
+    {
+      field: "contactor",
+      type: "input",
+      labelWidth: "120px",
+      label: "联系人"
+    },
+    {
+      field: "createTime",
+      type: "input",
+      labelWidth: "120px",
+      label: "创建时间"
+    }
+  ]
+};
+
+export default modalConfig;

+ 18 - 0
src/views/parameter/clients/config/search.config.ts

@@ -0,0 +1,18 @@
+import { FormConfig } from "/@/components/PageSearch";
+
+const searchFormConfig: FormConfig = {
+  formItems: [
+    {
+      field: "company",
+      type: "input",
+      placeholder: "客户公司名称"
+    },
+    {
+      field: "contactor",
+      type: "input",
+      placeholder: "联系人"
+    }
+  ]
+};
+
+export default searchFormConfig;

+ 27 - 154
src/views/parameter/clients/index.vue

@@ -1,167 +1,40 @@
 <script setup lang="ts">
-import { useColumns } from "./columns";
-import { httpList } from "/@/api/parameter/clients";
-import { reactive, ref, onMounted } from "vue";
-import { type FormInstance } from "element-plus";
-import { TableProBar } from "/@/components/ReTable";
-import { type PaginationProps } from "@pureadmin/table";
-import { useRenderIcon } from "/@/components/ReIcon/src/hooks";
-import { useNav } from "/@/layout/hooks/nav";
-import DetailDialog from "./components/detail-dialog.vue";
-import { responseHandle } from "/@/utils/responseHandle";
-const { logout } = useNav();
-defineOptions({
-  name: "clients"
-});
+import { PageSearch, usePageSearch } from "/@/components/PageSearch";
+import { PageModal, usePageModal } from "/@/components/PageModal";
+import { PageContent } from "/@/components/PageContent";
+import searchFormConfig from "./config/search.config";
+import contentConfig from "./config/content.config";
+import modalConfig from "./config/modal.config";
 
-const form = reactive({
-  page: 1,
-  size: 15,
-  company: "",
-  contactor: ""
+defineOptions({
+  name: "invoiceheader"
 });
 
-const dataList = ref([]);
-const loading = ref(true);
-const { columns } = useColumns();
-const formRef = ref<FormInstance>();
-const dialogRef = ref<InstanceType<typeof DetailDialog>>(null);
+const { pageContentRef, handleResetClick, handleSearchClick } = usePageSearch();
 
-const pagination = reactive<PaginationProps>({
-  total: 0,
-  pageSize: 15,
-  currentPage: 1,
-  background: true
-});
-
-async function handleCurrentChange(val: number) {
-  form.page = val;
-  await onSearch();
-}
-
-async function handleSizeChange(val: number) {
-  form.size = val;
-  form.page = 1;
-  await onSearch();
-}
-
-function handleSelectionChange(val) {
-  console.log("handleSelectionChange", val);
-}
-
-async function onSearch() {
-  loading.value = true;
-  const { code, data, message } = await httpList(form);
-
-  responseHandle({
-    code,
-    message,
-    logout,
-    handler: () => {
-      const { list, count } = data;
-      dataList.value = list ?? [];
-      pagination.total = count ?? 0;
-      pagination.pageSize = form.size;
-      pagination.currentPage = form.page;
-    }
-  });
-
-  loading.value = false;
-}
-
-async function resetSearch() {
-  form.page = 1;
-  await onSearch();
-}
-//详情弹窗
-function showDetail(row) {
-  dialogRef.value.show(row);
-}
-
-const resetForm = (formEl: FormInstance | undefined) => {
-  if (!formEl) return;
-  formEl.resetFields();
-  form.page = 1;
-  onSearch();
-};
-
-onMounted(() => {
-  onSearch();
+const { pageModalRef, handlePreviewData, defaultInfo } = usePageModal({
+  pageContentRef
 });
 </script>
 
 <template>
   <div class="main role">
-    <el-form
-      ref="formRef"
-      :inline="true"
-      :model="form"
-      :label-width="0"
-      class="bg-white w-99/100 pl-8 pt-4"
-    >
-      <el-form-item prop="company">
-        <el-input placeholder="客户公司名称" v-model="form.company" clearable />
-      </el-form-item>
-      <el-form-item prop="contactor">
-        <el-input placeholder="联系人" v-model="form.contactor" clearable />
-      </el-form-item>
-      <el-form-item>
-        <el-button
-          type="primary"
-          :icon="useRenderIcon('search')"
-          :loading="loading"
-          @click="resetSearch"
-        >
-          搜索
-        </el-button>
-        <el-button :icon="useRenderIcon('refresh')" @click="resetForm(formRef)">
-          重置
-        </el-button>
-      </el-form-item>
-    </el-form>
-
-    <TableProBar
-      title="企业客户"
-      :loading="loading"
-      :dataList="dataList"
-      @refresh="onSearch"
-    >
-      <template #buttons />
-      <template v-slot="{ size, checkList }">
-        <PureTable
-          border
-          align="left"
-          showOverflowTooltip
-          table-layout="auto"
-          :size="size"
-          :data="dataList"
-          :columns="columns"
-          :checkList="checkList"
-          :pagination="pagination"
-          :paginationSmall="size === 'small' ? true : false"
-          :header-cell-style="{ background: '#fafafa', color: '#606266' }"
-          @selection-change="handleSelectionChange"
-          @size-change="handleSizeChange"
-          @current-change="handleCurrentChange"
-        >
-          <template #operation="{ row }">
-            <el-tooltip content="查看详情" placement="top">
-              <el-button
-                class="reset-margin"
-                link
-                type="primary"
-                :size="size"
-                @click="showDetail(row)"
-                :icon="useRenderIcon('eye-view')"
-              />
-            </el-tooltip>
-          </template>
-        </PureTable>
-      </template>
-    </TableProBar>
-
-    <!-- 详情框 -->
-    <DetailDialog ref="dialogRef" />
+    <PageSearch
+      :form-config="searchFormConfig"
+      @search-btn-click="handleSearchClick"
+      @reset-btn-click="handleResetClick"
+    />
+    <PageContent
+      ref="pageContentRef"
+      :content-config="contentConfig"
+      @preview-btn-click="handlePreviewData"
+    />
+    <PageModal
+      ref="pageModalRef"
+      prview-description
+      :modal-config="modalConfig"
+      :default-info="defaultInfo"
+    />
   </div>
 </template>
 

+ 0 - 19
src/views/parameter/clients/utils/dialog-helper.ts

@@ -1,19 +0,0 @@
-export const mapKeyToLabel: Record<string, string> = {
-  companyName: "客户名称",
-  companyNo: "客户编号",
-  addtime: "创建时间",
-  contactor: "联系人",
-  mobile: "联系方式",
-  parent: "归属集团"
-};
-
-export function helper() {
-  return {
-    getLabel(key: string) {
-      return mapKeyToLabel[key];
-    },
-    getDetail(key: string, detail: any) {
-      return detail[key];
-    }
-  };
-}

+ 0 - 67
src/views/parameter/invoiceheader/columns.tsx

@@ -1,67 +0,0 @@
-import { ref } from "vue";
-import dayjs from "dayjs";
-export function useColumns() {
-  const columns = ref([
-    {
-      type: "selection",
-      width: 55,
-      hide: ({ checkList }) => !checkList.includes("勾选列")
-    },
-    {
-      label: "序号",
-      type: "index",
-      width: 70,
-      hide: ({ checkList }) => !checkList.includes("序号列")
-    },
-
-    {
-      label: "业务公司",
-      
-      prop: "invoice_title"
-    },
-    {
-      label: "企业法人",
-      prop: "invoice_people"
-    },
-    {
-      label: "企业注册地址",
-      prop: "invoice_addr"
-    },
-    {
-      label: "企业联系方式",
-      prop: "invoice_mobile"
-    },
-    {
-      label: "企业纳税识别号",
-      prop: "invoice_code"
-    },
-    {
-      label: "对公银行",
-      prop: "invoice_bank"
-    },
-    {
-      label: "对公银行账户",
-      prop: "invoice_bankNo"
-    },
-    {
-      label: "创建人",
-      prop: "apply_name"
-    },
-    {
-      label: "创建时间",
-      prop: "addTime",
-      formatter: ({ createTime }) =>
-        dayjs(createTime).format("YYYY-MM-DD HH:mm:ss")
-    },
-    {
-      label: "操作",
-      fixed: "right",
-      width: 80,
-      slot: "operation"
-    }
-  ]);
-
-  return {
-    columns
-  };
-}

+ 0 - 159
src/views/parameter/invoiceheader/components/action-table.vue

@@ -1,159 +0,0 @@
-<script setup lang="ts">
-import { onMounted, reactive, ref } from "vue";
-import { useColumns } from "./../columns";
-import { handleTree } from "@pureadmin/utils";
-import { httpList, httpDelete } from "/@/api/parameter/invoiceheader";
-import { TableProBar } from "/@/components/ReTable";
-import { useRenderIcon } from "/@/components/ReIcon/src/hooks";
-import { useNav } from "/@/layout/hooks/nav";
-import { responseHandle } from "/@/utils/responseHandle";
-import { PaginationProps } from "@pureadmin/table";
-
-const dataList = ref([]);
-const loading = ref(false);
-const tableRef = ref();
-const { columns } = useColumns();
-const { logout } = useNav();
-
-const emit = defineEmits(["edit", "create"]);
-
-const pagination = reactive<PaginationProps>({
-  total: 0,
-  pageSize: 15,
-  currentPage: 1,
-  background: true
-});
-
-async function handleCurrentChange(val: number) {
-  pagination.currentPage = val;
-  await onSearch();
-}
-
-async function handleSizeChange(val: number) {
-  pagination.pageSize = val;
-  pagination.currentPage = 1;
-  await onSearch();
-}
-
-function handleSelectionChange(val) {
-  console.log("handleSelectionChange", val);
-}
-
-//删除按钮
-const handleDelete = async row => {
-  const { id } = row;
-  const { code, message } = await httpDelete(id);
-
-  responseHandle({
-    code,
-    message,
-    logout,
-    handler: () => onSearch()
-  });
-};
-
-async function onSearch(form: any = {}) {
-  loading.value = true;
-  const { pageSize, currentPage } = pagination;
-
-  const { code, data, message } = await httpList({
-    ...form,
-    page: currentPage,
-    size: pageSize
-  });
-
-  responseHandle({
-    code,
-    message,
-    logout,
-    handler: () => {
-      dataList.value = handleTree(data.list ?? []);
-      pagination.pageCount = data.count;
-    }
-  });
-
-  loading.value = false;
-}
-
-//编辑/新增 操作
-function editItem(type: "create" | "edit", item) {
-  emit(type, item);
-}
-
-onMounted(() => {
-  onSearch();
-});
-
-defineExpose({
-  onSearch,
-  resetPagination: () => (pagination.currentPage = 1)
-});
-</script>
-<template>
-  <TableProBar
-    :title="'按钮列表'"
-    :loading="loading"
-    :tableRef="tableRef?.getTableRef()"
-    :dataList="dataList"
-    @refresh="onSearch()"
-  >
-    <template #buttons>
-      <el-button
-        type="primary"
-        :icon="useRenderIcon('add')"
-        @click="editItem('create', {})"
-      >
-        新增
-      </el-button>
-    </template>
-    <template v-slot="{ size, checkList }">
-      <PureTable
-        ref="tableRef"
-        border
-        align="left"
-        row-key="id"
-        table-layout="auto"
-        default-expand-all
-        :size="size"
-        :data="dataList"
-        :columns="columns"
-        :checkList="checkList"
-        :header-cell-style="{ background: '#fafafa', color: '#606266' }"
-        :pagination="pagination"
-        @selection-change="handleSelectionChange"
-        @size-change="handleSizeChange"
-        @current-change="handleCurrentChange"
-      >
-        <template #operation="{ row }">
-          <el-button
-            class="reset-margin"
-            link
-            type="primary"
-            :size="size"
-            :icon="useRenderIcon('edits')"
-            @click="editItem('edit', row)"
-          />
-          <el-popconfirm title="是否确认删除?" @confirm="handleDelete(row)">
-            <template #reference>
-              <el-button
-                class="reset-margin"
-                link
-                type="primary"
-                :size="size"
-                :icon="useRenderIcon('delete')"
-              />
-            </template>
-          </el-popconfirm>
-          <el-button
-            v-if="row.menu_type + '' === '1'"
-            class="reset-margin"
-            link
-            type="primary"
-            :size="size"
-            :icon="useRenderIcon('add')"
-          />
-        </template>
-      </PureTable>
-    </template>
-  </TableProBar>
-</template>

+ 0 - 93
src/views/parameter/invoiceheader/components/edit-dialog.vue

@@ -1,93 +0,0 @@
-<script setup lang="ts">
-import { ElForm } from "element-plus";
-import { ref } from "vue";
-import { responseHandle } from "/@/utils/responseHandle";
-import { useNav } from "/@/layout/hooks/nav";
-import { create, edit } from "../utils/dialog-handler";
-import { createRules } from "../utils/create-rules";
-import {
-  mapPropToLabel,
-  defaultFormData,
-  helper
-} from "../utils/dialog-helper";
-
-enum DIALOG_TYPE {
-  create = "create",
-  edit = "edit"
-}
-
-const emit = defineEmits(["reload"]);
-
-const visible = ref(false);
-const editId = ref("");
-const formData = ref({ ...defaultFormData });
-const formRef = ref<InstanceType<typeof ElForm>>(null);
-const FROM_TYPE = ref<DIALOG_TYPE>(DIALOG_TYPE.create);
-const loading = ref(false);
-
-const rules = createRules();
-const { logout } = useNav();
-
-//显示dialog
-function show(item: any) {
-  visible.value = true;
-  const keys = Object.keys(formData.value);
-  const isCreate = Object.keys(item).length === 0;
-
-  editId.value = isCreate ? "" : item.id;
-
-  keys.forEach(key => {
-    formData.value[key] = isCreate ? "" : item[key];
-  });
-
-  FROM_TYPE.value = isCreate ? DIALOG_TYPE.create : DIALOG_TYPE.edit;
-}
-
-//创建/编辑 请求
-function handleSave() {
-  formRef.value.validate(async valid => {
-    if (!valid) return;
-    const handler = FROM_TYPE.value === DIALOG_TYPE.create ? create : edit;
-    const { data, api } = handler(formData.value, editId.value);
-    loading.value = true;
-    const { code, message } = await api(data);
-
-    responseHandle({
-      code,
-      message,
-      logout,
-      handler: () => emit("reload")
-    });
-
-    visible.value = false;
-    loading.value = false;
-  });
-}
-
-defineExpose({
-  show
-});
-</script>
-
-<template>
-  <el-dialog v-model="visible" destroy-on-close @close="loading = false">
-    <el-form ref="formRef" :model="formData" :rules="rules">
-      <el-form-item
-        v-for="(key, index) in Object.keys(mapPropToLabel)"
-        :label="helper.getLabel(key) + ':'"
-        label-width="150px"
-        :prop="key"
-        :key="index"
-      >
-        <el-input v-model="formData[key]" />
-      </el-form-item>
-
-      <div class="flex justify-end">
-        <el-button @click="visible = false">取消</el-button>
-        <el-button :loading="loading" type="primary" @click="handleSave"
-          >保存</el-button
-        >
-      </div>
-    </el-form>
-  </el-dialog>
-</template>

+ 0 - 60
src/views/parameter/invoiceheader/components/search-form.vue

@@ -1,60 +0,0 @@
-<script setup lang="ts">
-import { reactive, ref } from "vue";
-import { FormInstance } from "element-plus";
-import { useRenderIcon } from "/@/components/ReIcon/src/hooks";
-
-const emit = defineEmits(["search"]);
-const formRef = ref<FormInstance>(null);
-const loading = ref(false);
-const form = reactive({
-  invoice_title: "",
-  invoice_code: "",
-  page: 1,
-  size: 15
-});
-
-function reset() {
-  form.invoice_code = "";
-  form.invoice_title = "";
-  form.page = 1;
-  emit("search", form);
-}
-
-function search() {
-  emit("search", form);
-}
-</script>
-
-<template>
-  <el-form
-    ref="formRef"
-    :inline="true"
-    :model="form"
-    :label-width="0"
-    class="bg-white w-99/100 pl-8 pt-4"
-  >
-    <el-form-item prop="invoice_title">
-      <el-input
-        v-model="form.invoice_title"
-        placeholder="发票抬头名称"
-        clearable
-      />
-    </el-form-item>
-    <el-form-item prop="invoice_code">
-      <el-input v-model="form.invoice_code" placeholder="发票编号" clearable />
-    </el-form-item>
-    <el-form-item>
-      <el-button
-        type="primary"
-        :icon="useRenderIcon('search')"
-        :loading="loading"
-        @click="search"
-      >
-        搜索
-      </el-button>
-      <el-button :icon="useRenderIcon('refresh')" @click="reset()">
-        重置
-      </el-button>
-    </el-form-item>
-  </el-form>
-</template>

+ 81 - 0
src/views/parameter/invoiceheader/config/content.config.ts

@@ -0,0 +1,81 @@
+import { ContentConfig } from "/@/components/PageContent";
+import {
+  httpList,
+  httpAdd,
+  httpDelete,
+  httpUpdate
+} from "/@/api/parameter/invoiceheader";
+
+import dayjs from "dayjs";
+
+const columns = [
+  {
+    type: "selection",
+    width: 55,
+    hide: ({ checkList }) => !checkList.includes("勾选列")
+  },
+  {
+    label: "序号",
+    type: "index",
+    width: 70,
+    hide: ({ checkList }) => !checkList.includes("序号列")
+  },
+
+  {
+    label: "业务公司",
+    prop: "invoice_title"
+  },
+  {
+    label: "企业法人",
+    prop: "invoice_people"
+  },
+  {
+    label: "企业注册地址",
+    prop: "invoice_addr"
+  },
+  {
+    label: "企业联系方式",
+    prop: "invoice_mobile"
+  },
+  {
+    label: "企业纳税识别号",
+    prop: "invoice_code"
+  },
+  {
+    label: "对公银行",
+    prop: "invoice_bank"
+  },
+  {
+    label: "对公银行账户",
+    prop: "invoice_bankNo"
+  },
+  {
+    label: "创建人",
+    prop: "apply_name"
+  },
+  {
+    label: "创建时间",
+    prop: "addTime",
+    formatter: ({ createTime }) =>
+      dayjs(createTime).format("YYYY-MM-DD HH:mm:ss")
+  },
+  {
+    label: "操作",
+    fixed: "right",
+    width: 120,
+    slot: "operation"
+  }
+];
+
+const contentConfig: ContentConfig = {
+  title: "企业客户发票",
+  columns,
+  apis: {
+    httpList,
+    httpAdd,
+    httpDelete,
+    httpUpdate
+  }
+};
+
+export default contentConfig;

+ 65 - 0
src/views/parameter/invoiceheader/config/modal.config.ts

@@ -0,0 +1,65 @@
+import { ModalConfig } from "../../../../components/PageModal/src/types";
+
+const modalConfig: ModalConfig = {
+  title: "发票",
+  colLayout: { span: 24 },
+  itemStyle: {},
+  formItems: [
+    {
+      field: "invoice_title",
+      type: "input",
+      label: "发票抬头名称",
+      labelWidth: "120px",
+      placeholder: "发票抬头名称",
+      rules: [
+        { required: true, trigger: "change", message: "请输入发票抬头名称" }
+      ]
+    },
+    {
+      field: "invoice_people",
+      type: "input",
+      labelWidth: "120px",
+      label: "法人",
+      placeholder: "法人",
+      rules: [{ required: true, trigger: "change", message: "请输入法人" }]
+    },
+    {
+      field: "invoice_addr",
+      type: "input",
+      labelWidth: "120px",
+      label: "企业注册地址",
+      placeholder: "企业注册地址",
+      rules: [
+        { required: true, trigger: "change", message: "请输入企业注册地址" }
+      ]
+    },
+    {
+      field: "invoice_code",
+      type: "input",
+      labelWidth: "120px",
+      label: "发票编号",
+      placeholder: "发票编号",
+      rules: [{ required: true, trigger: "change", message: "请输入发票编号" }]
+    },
+    {
+      field: "invoice_bank",
+      type: "input",
+      labelWidth: "120px",
+      label: "发票编号",
+      placeholder: "发票编号",
+      rules: [
+        { required: true, trigger: "change", message: "请输入企业开户银行" }
+      ]
+    },
+    {
+      field: "invoice_bankNo",
+      type: "input",
+      labelWidth: "120px",
+      label: "发票编号",
+      placeholder: "发票编号",
+      rules: [{ required: true, trigger: "change", message: "请输入银行账户" }]
+    }
+  ]
+};
+
+export default modalConfig;

+ 18 - 0
src/views/parameter/invoiceheader/config/search.config.ts

@@ -0,0 +1,18 @@
+import { FormConfig } from "/@/components/PageSearch";
+
+const searchFormConfig: FormConfig = {
+  formItems: [
+    {
+      field: "invoice_title",
+      type: "input",
+      placeholder: "发票抬头名称"
+    },
+    {
+      field: "invoice_code",
+      type: "input",
+      placeholder: "发票编号"
+    }
+  ]
+};
+
+export default searchFormConfig;

+ 32 - 24
src/views/parameter/invoiceheader/index.vue

@@ -1,39 +1,47 @@
 <script setup lang="ts">
-import { ref } from "vue";
-import SearchForm from "./components/search-form.vue";
-import ActionTable from "./components/action-table.vue";
-import EditDialog from "./components/edit-dialog.vue";
+import { PageSearch, usePageSearch } from "/@/components/PageSearch";
+import { PageModal, usePageModal } from "/@/components/PageModal";
+import { PageContent } from "/@/components/PageContent";
+import searchFormConfig from "./config/search.config";
+import contentConfig from "./config/content.config";
+import modalConfig from "./config/modal.config";
 
 defineOptions({
   name: "invoiceheader"
 });
 
-const tableRef = ref<InstanceType<typeof ActionTable>>(null);
-const dialogRef = ref<InstanceType<typeof EditDialog>>(null);
+const { pageContentRef, handleResetClick, handleSearchClick } = usePageSearch();
 
-//搜索
-function handleSearch(data: any) {
-  const { onSearch, resetPagination } = tableRef.value;
-  resetPagination();
-  onSearch(data);
-}
-
-function handleDialogVisible(item: any) {
-  dialogRef.value.show(item);
-}
+const {
+  pageModalRef,
+  handleUpdateData,
+  handleCreateData,
+  handlePreviewData,
+  handleConfrim,
+  defaultInfo
+} = usePageModal({ pageContentRef });
 </script>
 
 <template>
   <div class="main role">
-    <SearchForm @search="handleSearch" />
-
-    <ActionTable
-      ref="tableRef"
-      @create="handleDialogVisible"
-      @edit="handleDialogVisible"
+    <PageSearch
+      :form-config="searchFormConfig"
+      @search-btn-click="handleSearchClick"
+      @reset-btn-click="handleResetClick"
+    />
+    <PageContent
+      ref="pageContentRef"
+      :content-config="contentConfig"
+      @create-btn-click="handleCreateData"
+      @update-btn-click="handleUpdateData"
+      @preview-btn-click="handlePreviewData"
+    />
+    <PageModal
+      ref="pageModalRef"
+      :modal-config="modalConfig"
+      :default-info="defaultInfo"
+      @confirm-btn-click="handleConfrim"
     />
-
-    <EditDialog ref="dialogRef" @reload="tableRef.onSearch()" />
   </div>
 </template>
 

+ 0 - 17
src/views/parameter/invoiceheader/utils/create-rules.ts

@@ -1,17 +0,0 @@
-import { FormRules } from "element-plus";
-import { mapPropToLabel, helper } from "./dialog-helper";
-
-export function createRules() {
-  const rules: FormRules = {};
-  Object.keys(mapPropToLabel).forEach(key => {
-    rules[key] = [
-      {
-        required: true,
-        trigger: "blur",
-        message: `请输入${helper.getLabel(key)}`
-      }
-    ];
-  });
-
-  return rules;
-}

+ 0 - 18
src/views/parameter/invoiceheader/utils/dialog-handler.ts

@@ -1,18 +0,0 @@
-import { httpAdd, httpUpdate } from "/@/api/parameter/invoiceheader";
-
-export function create(form: Record<string, any>) {
-  return {
-    data: form,
-    api: httpAdd
-  };
-}
-
-export function edit(form: Record<string, any>, id: string) {
-  return {
-    data: {
-      id: id,
-      ...form
-    },
-    api: httpUpdate
-  };
-}

+ 0 - 25
src/views/parameter/invoiceheader/utils/dialog-helper.ts

@@ -1,25 +0,0 @@
-export const mapPropToLabel = {
-  invoice_title: "企业纳税名称",
-  invoice_people: "法人",
-  invoice_addr: "企业注册地址",
-  invoice_mobile: "企业联系方式",
-  invoice_code: "纳税识别号",
-  invoice_bank: "企业开户银行",
-  invoice_bankNo: "银行账户"
-};
-
-export const defaultFormData = {
-  invoice_title: "",
-  invoice_people: "",
-  invoice_addr: "",
-  invoice_mobile: "",
-  invoice_code: "",
-  invoice_bank: "",
-  invoice_bankNo: ""
-};
-
-export const helper = {
-  getLabel(key: string) {
-    return mapPropToLabel[key];
-  }
-};

+ 0 - 49
src/views/parameter/supplierPay/columns.tsx

@@ -1,49 +0,0 @@
-import { ref } from "vue";
-import dayjs from "dayjs";
-
-export function useColumns() {
-  const columns = ref([
-    {
-      type: "selection",
-      width: 55,
-      hide: ({ checkList }) => !checkList.includes("勾选列")
-    },
-    {
-      label: "序号",
-      type: "index",
-      width: 70,
-      hide: ({ checkList }) => !checkList.includes("序号列")
-    },
-
-    {
-      label: "供应商名称",
-      prop: "name"
-    },
-    {
-      label: "联系人",
-      prop: "contector"
-    },
-    {
-      label: "公司类型",
-      prop: "nature"
-    },
-    {
-      label: "联系方式",
-      prop: "mobile"
-    },
-    {
-      label: "地址",
-      prop: "address"
-    },
-    {
-      label: "创建时间",
-      prop: "createTime",
-      formatter: ({ createTime }) =>
-        dayjs(createTime).format("YYYY-MM-DD HH:mm:ss")
-    }
-  ]);
-
-  return {
-    columns
-  };
-}

+ 55 - 0
src/views/parameter/supplierPay/config/content.config.ts

@@ -0,0 +1,55 @@
+import { ContentConfig } from "/@/components/PageContent";
+import { httpList } from "/@/api/parameter/supplierPay";
+
+import dayjs from "dayjs";
+
+const columns = [
+  {
+    type: "selection",
+    width: 55,
+    hide: ({ checkList }) => !checkList.includes("勾选列")
+  },
+  {
+    label: "序号",
+    type: "index",
+    width: 70,
+    hide: ({ checkList }) => !checkList.includes("序号列")
+  },
+
+  {
+    label: "供应商名称",
+    prop: "name"
+  },
+  {
+    label: "联系人",
+    prop: "contector"
+  },
+  {
+    label: "公司类型",
+    prop: "nature"
+  },
+  {
+    label: "联系方式",
+    prop: "mobile"
+  },
+  {
+    label: "地址",
+    prop: "address"
+  },
+  {
+    label: "创建时间",
+    prop: "createTime",
+    formatter: ({ createTime }) =>
+      dayjs(createTime).format("YYYY-MM-DD HH:mm:ss")
+  }
+];
+
+const contentConfig: ContentConfig = {
+  title: "企业客户发票",
+  columns,
+  apis: {
+    httpList
+  }
+};
+
+export default contentConfig;

+ 13 - 0
src/views/parameter/supplierPay/config/search.config.ts

@@ -0,0 +1,13 @@
+import { FormConfig } from "/@/components/PageSearch";
+
+const searchFormConfig: FormConfig = {
+  formItems: [
+    {
+      field: "name",
+      type: "input",
+      placeholder: "供应商名称"
+    }
+  ]
+};
+
+export default searchFormConfig;

+ 12 - 130
src/views/parameter/supplierPay/index.vue

@@ -1,142 +1,24 @@
 <script setup lang="ts">
-import { reactive, ref, onMounted } from "vue";
-import { useColumns } from "./columns";
-import { httpList } from "/@/api/system/updates";
-import { type FormInstance } from "element-plus";
-import { TableProBar } from "/@/components/ReTable";
-import { type PaginationProps } from "@pureadmin/table";
-import { useRenderIcon } from "/@/components/ReIcon/src/hooks";
-import { useNav } from "/@/layout/hooks/nav";
-import { responseHandle } from "/@/utils/responseHandle";
-const { logout } = useNav();
+import { PageSearch, usePageSearch } from "/@/components/PageSearch";
+import { PageContent } from "/@/components/PageContent";
+import searchFormConfig from "./config/search.config";
+import contentConfig from "./config/content.config";
+
 defineOptions({
   name: "supplierPay"
 });
 
-const form = reactive({
-  name: "",
-  page: 1,
-  size: 15
-});
-
-const dataList = ref([]);
-const loading = ref(true);
-const { columns } = useColumns();
-const formRef = ref<FormInstance>();
-
-const pagination = reactive<PaginationProps>({
-  total: 0,
-  pageSize: 15,
-  currentPage: 1,
-  background: true
-});
-
-async function handleCurrentChange(val: number) {
-  form.page = val;
-  await onSearch();
-}
-
-async function handleSizeChange(val: number) {
-  form.size = val;
-  form.page = 1;
-  await onSearch();
-}
-
-function handleSelectionChange(val) {
-  console.log("handleSelectionChange", val);
-}
-
-async function onSearch() {
-  loading.value = true;
-  const { code, data, message } = await httpList(form);
-
-  responseHandle({
-    code,
-    message,
-    logout,
-    handler: () => {
-      const { list, count } = data;
-      dataList.value = list ?? [];
-      pagination.total = count ?? 0;
-      pagination.pageSize = form.size;
-      pagination.currentPage = form.page;
-    }
-  });
-  loading.value = false;
-}
-
-async function resetSearch() {
-  form.page = 1;
-  await onSearch();
-}
-
-const resetForm = (formEl: FormInstance | undefined) => {
-  if (!formEl) return;
-  formEl.resetFields();
-  form.page = 1;
-  onSearch();
-};
-
-onMounted(() => {
-  onSearch();
-});
+const { pageContentRef, handleResetClick, handleSearchClick } = usePageSearch();
 </script>
 
 <template>
   <div class="main role">
-    <el-form
-      ref="formRef"
-      :inline="true"
-      :model="form"
-      :label-width="0"
-      class="bg-white w-99/100 pl-8 pt-4"
-    >
-      <el-form-item prop="name">
-        <el-input v-model="form.name" placeholder="供应商名称" clearable />
-      </el-form-item>
-      <el-form-item>
-        <el-button
-          type="primary"
-          :icon="useRenderIcon('search')"
-          :loading="loading"
-          @click="resetSearch"
-        >
-          搜索
-        </el-button>
-        <el-button :icon="useRenderIcon('refresh')" @click="resetForm(formRef)">
-          重置
-        </el-button>
-      </el-form-item>
-    </el-form>
-
-    <TableProBar
-      title="角色管理"
-      :loading="loading"
-      :dataList="dataList"
-      @refresh="onSearch"
-    >
-      <template #buttons />
-      <template v-slot="{ size, checkList }">
-        <PureTable
-          border
-          align="left"
-          showOverflowTooltip
-          table-layout="auto"
-          :size="size"
-          :data="dataList"
-          :columns="columns"
-          :checkList="checkList"
-          :pagination="pagination"
-          :paginationSmall="size === 'small' ? true : false"
-          :header-cell-style="{ background: '#fafafa', color: '#606266' }"
-          @selection-change="handleSelectionChange"
-          @size-change="handleSizeChange"
-          @current-change="handleCurrentChange"
-        >
-          <template #operation />
-        </PureTable>
-      </template>
-    </TableProBar>
+    <PageSearch
+      :form-config="searchFormConfig"
+      @search-btn-click="handleSearchClick"
+      @reset-btn-click="handleResetClick"
+    />
+    <PageContent ref="pageContentRef" :content-config="contentConfig" />
   </div>
 </template>
 

+ 0 - 57
src/views/system/setBtn/columns.tsx

@@ -1,57 +0,0 @@
-import { ref } from "vue";
-import dayjs from "dayjs";
-
-export function useColumns() {
-  const columns = ref([
-    {
-      type: "selection",
-      minWidth: 55,
-      align: "left",
-      hide: ({ checkList }) => !checkList.includes("勾选列")
-    },
-    {
-      label: "序号",
-      type: "index",
-      minWidth: 60,
-      align: "left",
-      hide: ({ checkList }) => !checkList.includes("序号列")
-    },
-    {
-      label: "按钮名称",
-      prop: "action_name",
-      minWidth: 180,
-      align: "left"
-    },
-    {
-      label: "状态",
-      prop: "status",
-      minWidth: 80,
-      cellRenderer: ({ row, props }) => (
-        <el-tag
-          size={props.size}
-          type={row.status + "" === "1" ? "success" : "danger"}
-          effect="plain"
-        >
-          {row.status + "" === "0" ? "禁用" : "启用"}
-        </el-tag>
-      )
-    },
-    {
-      label: "创建时间",
-      minWidth: 180,
-      prop: "createTime",
-      formatter: ({ createTime }) =>
-        dayjs(createTime).format("YYYY-MM-DD HH:mm:ss")
-    },
-    {
-      label: "操作",
-      fixed: "right",
-      minWidth: 140,
-      slot: "operation"
-    }
-  ]);
-
-  return {
-    columns
-  };
-}

+ 0 - 154
src/views/system/setBtn/components/action-table.vue

@@ -1,154 +0,0 @@
-<script setup lang="ts">
-import { ref } from "vue";
-import { useColumns } from "./../columns";
-import { handleTree } from "@pureadmin/utils";
-import { httpList, httpStatus, httpDelete } from "/@/api/system/buttonOperator";
-import { TableProBar } from "/@/components/ReTable";
-import { useRenderIcon } from "/@/components/ReIcon/src/hooks";
-import { useNav } from "/@/layout/hooks/nav";
-import { responseHandle } from "/@/utils/responseHandle";
-
-const dataList = ref([]);
-const loading = ref(false);
-const tableRef = ref();
-const currentActionId = ref("");
-const { columns } = useColumns();
-const { logout } = useNav();
-
-const emit = defineEmits(["edit", "create"]);
-
-const handleUpdate = async row => {
-  const { id, status } = row;
-  const { code, message } = await httpStatus({
-    id,
-    status: status + "" === "1" ? "0" : "1"
-  });
-
-  responseHandle({
-    code,
-    message,
-    logout,
-    handler: () => onSearch(currentActionId.value)
-  });
-};
-
-//删除按钮
-const handleDelete = async row => {
-  const { id } = row;
-  const { code, message } = await httpDelete({ id });
-
-  responseHandle({
-    code,
-    message,
-    logout,
-    handler: () => onSearch(currentActionId.value)
-  });
-};
-
-async function onSearch(id: string) {
-  loading.value = true;
-  const { code, data, message } = await httpList(id);
-
-  responseHandle({
-    code,
-    message,
-    logout,
-    handler: () => (dataList.value = handleTree(data ?? []))
-  });
-  loading.value = false;
-  currentActionId.value = id;
-}
-
-//编辑
-function editItem(type: "create" | "edit", item) {
-  emit(type, item);
-}
-
-defineExpose({
-  onSearch
-});
-</script>
-<template>
-  <TableProBar
-    :title="'按钮列表'"
-    :loading="loading"
-    :tableRef="tableRef?.getTableRef()"
-    :dataList="dataList"
-    @refresh="onSearch(currentActionId)"
-  >
-    <template #buttons>
-      <el-button
-        type="primary"
-        :icon="useRenderIcon('add')"
-        @click="editItem('create', {})"
-      >
-        新增
-      </el-button>
-    </template>
-    <template v-slot="{ size, checkList }">
-      <PureTable
-        ref="tableRef"
-        border
-        align="left"
-        row-key="id"
-        table-layout="auto"
-        default-expand-all
-        :size="size"
-        :data="dataList"
-        :columns="columns"
-        :checkList="checkList"
-        :header-cell-style="{ background: '#fafafa', color: '#606266' }"
-      >
-        <template #operation="{ row }">
-          <el-button
-            class="reset-margin"
-            link
-            type="primary"
-            :size="size"
-            :icon="useRenderIcon('edits')"
-            @click="editItem('edit', row)"
-          />
-          <el-popconfirm
-            :title="row.status === '1' ? '改为禁用?' : '改为启用?'"
-            @confirm="handleUpdate(row)"
-          >
-            <template #reference>
-              <el-button
-                class="reset-margin"
-                link
-                type="primary"
-                :size="size"
-                :icon="
-                  useRenderIcon(
-                    row.status === '1'
-                      ? 'close-circle-line'
-                      : 'checkbox-circle-line'
-                  )
-                "
-              />
-            </template>
-          </el-popconfirm>
-          <el-popconfirm title="是否确认删除?" @confirm="handleDelete(row)">
-            <template #reference>
-              <el-button
-                class="reset-margin"
-                link
-                type="primary"
-                :size="size"
-                :icon="useRenderIcon('delete')"
-              />
-            </template>
-          </el-popconfirm>
-          <el-button
-            v-if="row.menu_type + '' === '1'"
-            class="reset-margin"
-            link
-            type="primary"
-            :size="size"
-            :icon="useRenderIcon('add')"
-          />
-        </template>
-      </PureTable>
-    </template>
-  </TableProBar>
-</template>

+ 0 - 138
src/views/system/setBtn/components/edit-dialog.vue

@@ -1,138 +0,0 @@
-<script setup lang="ts">
-import { FormRules, ElForm } from "element-plus";
-import { computed, reactive, ref } from "vue";
-import btnList from "/@/utils/btnList";
-import { httpUpdate, httpAdd } from "/@/api/system/buttonOperator";
-import { useNav } from "/@/layout/hooks/nav";
-import { IMenuTree } from "../types";
-import { responseHandle } from "/@/utils/responseHandle";
-
-enum FROM_TYPE {
-  create = "create",
-  edit = "edit"
-}
-
-const showModel = ref(false);
-const currentMenuId = ref("");
-const TYPE = ref<FROM_TYPE>(FROM_TYPE.create);
-const formRef = ref<InstanceType<typeof ElForm>>(null);
-const currentNode = ref<IMenuTree | null>(null);
-const loading = ref(false);
-const { logout } = useNav();
-
-const emit = defineEmits(["reload"]);
-
-const formModel = ref({
-  action_code: "",
-  status: ""
-});
-
-const action_name = computed(() => {
-  return btnList.filter(b => b.code === formModel.value.action_code)[0].name;
-});
-
-const rules = reactive<FormRules>({
-  action_code: [{ required: true, message: "请选择按钮名称", trigger: "blur" }],
-  status: [{ required: true, message: "请选择按钮状态", trigger: "blur" }]
-});
-
-function show(node: any, isCreate = false) {
-  showModel.value = true;
-  currentNode.value = node;
-  Object.keys(formModel.value).forEach(key => {
-    formModel.value[key] = isCreate ? "" : node[key];
-  });
-
-  TYPE.value = isCreate ? FROM_TYPE.create : FROM_TYPE.edit;
-}
-
-function handleUpdate() {
-  const data = {
-    menuid: currentMenuId.value,
-    id: currentNode.value.id,
-    action_name: action_name.value,
-    ...formModel.value
-  };
-
-  return {
-    data,
-    api: httpUpdate
-  };
-}
-
-function handleCreate() {
-  const data = {
-    menuid: currentMenuId.value,
-    action_name: action_name.value,
-    ...formModel.value
-  };
-
-  return {
-    data,
-    api: httpAdd
-  };
-}
-
-function handleSave() {
-  formRef.value.validate(async vaild => {
-    if (vaild) {
-      const handler =
-        TYPE.value === FROM_TYPE.create ? handleCreate : handleUpdate;
-
-      loading.value = true;
-      const { api, data } = handler();
-
-      const { message, code } = await api(data);
-
-      responseHandle({
-        code,
-        message,
-        logout,
-        handler: () => {
-          emit("reload");
-        }
-      });
-
-      showModel.value = false;
-      loading.value = false;
-    }
-  });
-}
-
-defineExpose({
-  show,
-  changeCurrentMenuId: (id: string) => (currentMenuId.value = id)
-});
-</script>
-
-<template>
-  <div>
-    <el-dialog v-model="showModel">
-      <el-form :model="formModel" :rules="rules" ref="formRef">
-        <el-form-item label="按钮标识" prop="action_code">
-          <el-select v-model="formModel.action_code">
-            <el-option
-              v-for="(btn, index) in btnList"
-              :label="btn.name"
-              :value="btn.code"
-              :key="index"
-            />
-          </el-select>
-        </el-form-item>
-        <el-form-item label="按钮状态" prop="status">
-          <el-radio-group v-model="formModel.status">
-            <el-radio label="1">启用</el-radio>
-            <el-radio label="0">禁用</el-radio>
-          </el-radio-group>
-        </el-form-item>
-
-        <div class="flex justify-end">
-          <el-button :loading="loading" type="primary" @click="handleSave"
-            >保存</el-button
-          >
-          <el-button @click="showModel = false">取消</el-button>
-        </div>
-      </el-form>
-    </el-dialog>
-  </div>
-</template>

+ 2 - 7
src/views/system/setBtn/components/menu-tree.vue

@@ -23,9 +23,8 @@ async function initMenuList() {
   const page = menu.child[0];
   defaultExpandedKeys.value = [menu.id];
 
-  nextTick(() => {
-    treeRef.value.setCurrentKey(page.id);
-  });
+  //默认选中节点
+  nextTick(() => treeRef.value.setCurrentKey(page.id));
 
   emit("initTableData", page.id);
 }
@@ -35,10 +34,6 @@ function handleSelect(node) {
   emit("treeSelectChange", node);
 }
 
-function handleTreeExpaned() {
-  // treeRef.value
-}
-
 initMenuList();
 </script>
 

+ 78 - 0
src/views/system/setBtn/config/content.config.ts

@@ -0,0 +1,78 @@
+import { ContentConfig } from "/@/components/PageContent";
+import {
+  httpList,
+  httpDelete,
+  httpStatus,
+  httpAdd,
+  httpEdit as httpUpdate
+} from "/@/api/system/setBtn";
+
+import dayjs from "dayjs";
+import { h } from "vue";
+import { ElTag } from "element-plus";
+
+const columns = [
+  {
+    type: "selection",
+    minWidth: 55,
+    align: "left",
+    hide: ({ checkList }) => !checkList.includes("勾选列")
+  },
+  {
+    label: "序号",
+    type: "index",
+    minWidth: 60,
+    align: "left",
+    hide: ({ checkList }) => !checkList.includes("序号列")
+  },
+  {
+    label: "按钮名称",
+    prop: "action_name",
+    minWidth: 180,
+    align: "left"
+  },
+  {
+    label: "状态",
+    prop: "status",
+    minWidth: 80,
+    cellRenderer: ({ row, props }) =>
+      h(
+        ElTag,
+        {
+          size: props.size,
+          type: String(row.status) === "1" ? "success" : "danger"
+        },
+        {
+          default: () => (row.status + "" === "0" ? "禁用" : "启用")
+        }
+      )
+  },
+  {
+    label: "创建时间",
+    minWidth: 180,
+    prop: "createTime",
+    formatter: ({ createTime }) =>
+      dayjs(createTime).format("YYYY-MM-DD HH:mm:ss")
+  },
+  {
+    label: "操作",
+    fixed: "right",
+    minWidth: 140,
+    slot: "operation"
+  }
+];
+
+const contentConfig: ContentConfig = {
+  title: "按钮设置",
+  columns,
+  notReuqiredInit: true,
+  apis: {
+    httpList,
+    httpDelete,
+    httpAdd,
+    httpStatus,
+    httpUpdate
+  }
+};
+
+export default contentConfig;

+ 37 - 0
src/views/system/setBtn/config/modal.config.ts

@@ -0,0 +1,37 @@
+import { createOptions } from "../utils/create-options";
+import { ModalConfig } from "/@/components/PageModal/src/types";
+const modalConfig: ModalConfig = {
+  title: "发票",
+  colLayout: { span: 24 },
+  itemStyle: {},
+  formItems: [
+    {
+      field: "action_code",
+      type: "select",
+      label: "按钮名称",
+      labelWidth: "120px",
+      placeholder: "按钮名称",
+      options: createOptions(),
+      rules: [{ required: true, trigger: "change", message: "请选择按钮标识" }]
+    },
+    {
+      field: "status",
+      type: "radio",
+      labelWidth: "120px",
+      label: "按钮状态",
+      options: [
+        {
+          label: "启用",
+          value: "1"
+        },
+        {
+          label: "禁用",
+          value: "0"
+        }
+      ],
+      rules: [{ required: true, trigger: "change", message: "请选择按钮状态" }]
+    }
+  ]
+};
+
+export default modalConfig;

+ 41 - 39
src/views/system/setBtn/index.vue

@@ -1,63 +1,65 @@
 <script setup lang="ts">
-import { ref } from "vue";
-import MenuTree from "./components/menu-tree.vue";
-import ActionTable from "./components/action-table.vue";
-import EditModel from "./components/edit-dialog.vue";
-import { IMenuTree } from "./types";
+import { ref, unref } from "vue";
+import TreeMenu from "./components/menu-tree.vue";
+import { PageModal, usePageModal } from "/@/components/PageModal";
+import { PageContent, PageContentInstance } from "/@/components/PageContent";
+import contentConfig from "./config/content.config";
+import modalConfig from "./config/modal.config";
+import { IMenuTree, MENU_TYPE } from "./types";
+import btnList from "/@/utils/btnList";
 
-const actionTableRef = ref<InstanceType<typeof ActionTable>>(null);
-const modelRef = ref<InstanceType<typeof EditModel>>(null);
-const currentMenuId = ref("");
+const menuid = ref("");
+const pageContentRef = ref<PageContentInstance>(null);
 
-enum MENU_TYPE {
-  MENU = "1",
-  PAGE = "2"
-}
+const {
+  pageModalRef,
+  handleCreateData,
+  handlePreviewData,
+  handleUpdateData,
+  handleConfrim,
+  defaultInfo
+} = usePageModal({
+  pageContentRef,
+  confirmCallback: ({ action_code }) => ({
+    menuid: unref(menuid),
+    action_name: btnList.find(b => b.code === action_code)?.name
+  })
+});
 
 //节点选择
 function handleTreeNodeSelect(node: IMenuTree) {
   const { menu_type, id } = node;
   const isPage = menu_type === MENU_TYPE.PAGE;
-
   if (!isPage) return;
-  //选择页面进行搜索
-  currentMenuId.value = id;
-  actionTableRef.value.onSearch(id);
-  modelRef.value.changeCurrentMenuId(id);
-}
-
-//编辑
-function handleEdit(item: any) {
-  modelRef.value.show(item);
-}
-
-//新建
-function handleCreate() {
-  modelRef.value.show(null, true);
+  menuid.value = id;
+  pageContentRef.value.getPageData({ id });
 }
 
 //初始化table
-function initTableData(menuId: string) {
-  actionTableRef.value.onSearch(menuId);
-  currentMenuId.value = menuId;
-  modelRef.value.changeCurrentMenuId(menuId);
+function initTableData(id: string) {
+  menuid.value = id;
+  pageContentRef.value.getPageData({ id });
 }
 </script>
 
 <template>
   <div class="main flex">
-    <MenuTree
+    <TreeMenu
       @tree-select-change="handleTreeNodeSelect"
       @init-table-data="initTableData"
     />
-    <ActionTable
-      ref="actionTableRef"
-      @edit="handleEdit"
-      @create="handleCreate"
+    <PageContent
+      ref="pageContentRef"
+      :content-config="contentConfig"
+      @create-btn-click="handleCreateData"
+      @preview-btn-click="handlePreviewData"
+      @update-btn-click="handleUpdateData"
     />
-    <EditModel
-      ref="modelRef"
-      @reload="actionTableRef.onSearch(currentMenuId)"
+    <PageModal
+      ref="pageModalRef"
+      :modal-config="modalConfig"
+      :default-info="defaultInfo"
+      @confirm-btn-click="handleConfrim"
     />
   </div>
 </template>

+ 5 - 0
src/views/system/setBtn/types.ts

@@ -12,3 +12,8 @@ export interface IMenuTree {
   weight: string;
   children: Array<IMenuTree>;
 }
+
+export enum MENU_TYPE {
+  MENU = "1",
+  PAGE = "2"
+}

+ 8 - 0
src/views/system/setBtn/utils/create-options.ts

@@ -0,0 +1,8 @@
+import btnList from "/@/utils/btnList";
+
+export function createOptions() {
+  return btnList.map(btn => ({
+    label: btn.name,
+    value: btn.code
+  }));
+}