Skip to content
On this page

一些特例汇总

调试本地业务模板

TIP

业务模块 url 地址拼接时,域名默认取登录接口 "/els/account/login" 返回的 userInfo 中的 serivceUrl 值, 支持自定义配置;

ts
// 直接修改系统全局缓存的 userInfo
// 文件路径 "src/stores/user.ts"
// 此方法需要重新登录

// setUserInfo(result.userInfo) 修改为
export const useUserStore = defineStore('user', {
  actions: {
    setUserInfo<T extends object>(obj: T): void {
      this.userInfo = {
        ...obj,
        serivceUrl: 'http://127.0.0.1:8081', // 配置自定义业务模板地址
      }
    }
  }
}
vue
<script setup lang="ts">
// 当前行 currentRow, 当前用户信息 userInfo
import { useGlobalStoreWithDefaultValue } from "@/use/useGlobalStore";
// 全局数据缓存
const { userInfo } = useGlobalStoreWithDefaultValue();

// 配置
const options = reactive<Partial<GlobalPageLayoutTypes.EditPageLayoutProps>>({
  // 当前登录租户信息
  userInfo: {
    ...userInfo.value,
    serivceUrl: "http://127.0.0.1:8081", // 配置自定义业务模板地址
  } as GlobalPageLayoutTypes.UserInfo,
});
</script>

获取模板配置

TIP

某些业务场景下,需要先获取业务模板的所有配置

🙌 🌰 采购协同 > 寻源协同 > 询价管理 成本报价

vue
<script setup lang="tsx">
import { loadJS, getRemoteJsFilePath } from "@qqt-product/ui";

const filePath: string = getRemoteJsFilePath(
  {
    currentRow: priceRow.value, // 当前行缓存 templateName, templateAccount, templateNumber, templateVersion, elsAccount, busAccount
    userInfo: userInfo.value as GlobalPageLayoutTypes.UserInfo,
    businessType: "costForm",
    role: "purchase",
  },
  false
);

const config = await loadJS(`${filePath}?t=${+new Date()}`);
</script>

附件操作列判断是否可删除

vue
<script setup lang="tsx">
import type { GlobalPageLayoutTypes } from "@qqt-product/ui";
import type { VxeColumnSlotTypes, VxeTableDataRow } from "vxe-table";

import { useOperationColumnButtonHook } from "@qqt-product/ui";
const { handleDelete, handleDownload, handlePreview } =
  useOperationColumnButtonHook();

const operationColumn: GlobalPageLayoutTypes.ColumnItem = {
  groupCode: "purchaseAttachmentList",
  field: "grid_operation",
  title: "操作",
  width: 220,
  fieldLabelI18nKey: "i18n_title_operation",
  align: "center",
  fixed: "right",
  slots: {
    default({
      row,
      column,
      $rowIndex,
    }: VxeColumnSlotTypes.DefaultSlotParams<VxeTableDataRow>) {
      return [
        <a-space>
          <a-button
            type="link"
            onClick={() =>
              handleDownload({
                groupCode: "purchaseAttachmentList",
                row,
                rowIndex: $rowIndex,
              })
            }
          >
            {srmI18n("i18n_title_colunmDownload", "下载")}
          </a-button>
          <a-button
            type="link"
            onClick={() =>
              handlePreview({
                groupCode: "purchaseAttachmentList",
                row,
                rowIndex: $rowIndex,
              })
            }
          >
            {srmI18n("i18n_title_preview", "预览")}
          </a-button>
          <a-button
            type="link"
            onClick={() =>
              handleDelete({
                groupCode: "saleAttachmentList",
                row,
                rowIndex: $rowIndex,
              })
            }
            disabled={row.uploadElsAccount !== userInfo.value.elsAccount}
          >
            {srmI18n("i18n_title_delete", "删除")}
          </a-button>
        </a-space>,
      ];
    },
  },
};

// 配置
const options = reactive<Partial<GlobalPageLayoutTypes.EditPageLayoutProps>>({
  itemColumns: [operationColumn],
});
</script>

整单确认二次弹窗确认、整单拒绝二次弹窗且拒绝理由必填

TIP

"确认操作时需要二次弹窗确认, 拒绝操作时需要二次弹窗, 且拒绝理由 Textarea 输入框必填", 这类业务逻辑使用函数式方法组装更简洁

vue
<template>
  <div class="detail-page">
    <q-detail-page-layout
      ref="layoutRef"
      v-bind="options"
      @customAccept="handleCustomAccept"
      @customReject="handleCustomReject"
    ></q-detail-page-layout>
  </div>
</template>

<script setup lang="ts">
import { Modal, message as Message, Textarea } from "ant-design-vue";
import type { ComponentPublicInstance } from "vue";
import { useRefInstanceHook } from "@qqt-product/ui";

// 组件 ref
const layoutRef = ref<ComponentPublicInstance | null>(null);

const remark = ref<string>(""); // 意见
const { pageData } = useRefInstanceHook(layoutRef);

const acceptOrRejectAction = (type: string) => {
  const isReject = type === "reject";
  const url: string = isReject ? "/xxx/reject" : "/xxx/pass";

  const data = { ...pageData.value, remark: remark.value };
  Request.post({ url, data }).then((res) => {
    if (res.success) {
      Message.success(res.message);
      backToList(true);
    } else {
      Message.error(res.message);
    }
  });
};

const showPropsConfirm = (type: "confirm" | "reject") => {
  const isReject = type === "reject";

  const title = srmI18n("i18n_title_tip", "提示");
  const subTitle = isReject
    ? h(
        "span",
        { style: "color:red; margin-top: 10px;" },
        srmI18n("i18n_title_isSureReviewNotPassed", "是否确认审查拒绝?")
      )
    : srmI18n("i18n_title_isSureReviewPassed", "是否确认审查通过?");

  const textareaNode = h(Textarea, {
    style: {
      marginTop: "12px",
    },
    onChange(e) {
      rejectReason.value = e.target.value as string;
    },
    defaultValue: (pageData.value.remark as string) || "",
    placeholder: "请输入备注内容",
    maxlength: 400,
    autosize: { minRows: 3, maxRows: 6 },
  });

  const contentNode = h("div", [subTitle, textareaNode]);
  const content = isReject
    ? contentNode
    : srmI18n(`i18n_title_isSureReviewPassed`, "是否确认审查通过?");

  Modal.confirm({
    width: isReject ? 520 : 416,
    closable: true,
    title,
    content,
    onOk() {
      if (isReject && !rejectReason.value) {
        Message.error("请输入拒绝理由");
        return Promise.reject("error"); // 阻止弹窗关闭
      }

      const url: string = isReject
        ? "/qualification/purchaseQualificationReview/reject"
        : "/qualification/purchaseQualificationReview/pass";

      const data = { ...pageData.value, remark: remark.value };
      Request.post({ url, data })
        .then((res) => {
          if (res.success) {
            Message.success(res.message);
            backToList(true);
          } else {
            Message.error(res.message);
          }
        })
        .finally(() => {
          return Promise.resolve("success");
        });
    },
  });
};

const handleCustomAccept = () => {
  console.log("handleCustomAccept :>> ");
  showPropsConfirm("confirm");
};

const handleCustomReject = () => {
  console.log("handleCustomReject :>> ");
  showPropsConfirm("reject");
};

// 配置
const options = reactive<Partial<GlobalPageLayoutTypes.EditPageLayoutProps>>({
  pageButtons: [
    {
      ...BUTTON_CONFIRM,
      emit: true,
      emitKey: "customAccept",
      args: {
        url: "/price/saleInformationRecordsRequest/confirmed",
      },
      authorityCode:
        "informationRecordsRequest#SaleInformationRecordsRequest:confirmed",
      disabled(pageData) {
        return pageData.status !== "2";
      },
    },
    {
      ...BUTTON_REJECT,
      emit: true,
      emitKey: "customReject",
      args: {
        url: "/price/saleInformationRecordsRequest/rejected",
      },
      authorityCode:
        "informationRecordsRequest#SaleInformationRecordsRequest:rejected",
      disabled(pageData) {
        return pageData.status !== "2";
      },
    },
  ],
});
</script>

表行功能按钮批量操作与操作列渲染单行操作

🙌 🌰 采购协同 > 质量协同 > 样品申请 详情页(收货、关闭、转需求池)

vue
<template>
  <div class="detail-page">
    <q-detail-page-layout
      ref="layoutRef"
      v-bind="options"
      @batchReceive="handleBatchReceive"
      @batchClose="handleBatchClose"
      @batchTransform="handleBatchTransform"
    ></q-detail-page-layout>
  </div>
</template>

<script setup lang="tsx">
// ...

// 校验是否已勾选
const checkoutSelectedRows = (
  btn: GlobalPageLayoutTypes.PageButtonWithGroupCode
) => {
  console.log("click checkoutIsSelected :>> ", btn);

  const { groupCode } = btn;

  const tableData = pageData.value[
    groupCode
  ] as GlobalPageLayoutTypes.RecordString[];
  const selectedRows = tableData.filter((n) => !!n._checked);

  if (!selectedRows.length) {
    Message.warning(srmI18n(`i18n_title_selectDataMsg`, "请选择数据"));
    return [];
  }

  console.log("selectedRows :>> ", selectedRows);
  return selectedRows;
};

// 收货
const handleReceive = (rows: GlobalPageLayoutTypes.RecordString[]) => {
  console.log("handleReceive rows :>> ", rows);

  for (const [idx, item] of rows.entries()) {
    if (item.itemStatus !== "8") {
      const errorMsg = ["已选行", `${idx + 1}`, "非送样中状态,无法收货"];
      Message.error(errorMsg.join(""));
      return;
    }
  }

  const callback = () => {
    const url = "/sample/purchaseSampleHead/signSample";
    const data = rows.map((n) => n);
    Request.post({ url, data }).then((res) => {
      if (res.success) {
        Message.success(res.message);
        handleRefresh();
      } else {
        Message.error(res.message);
      }
    });
  };

  Modal.confirm({
    title: srmI18n("i18n_alert_RLlS_38d78a27", "确认收货"),
    content: srmI18n("i18n_field_KQRLlSW_ebe6d581", "是否确认收货?"),
    onOk: callback,
  });
};

// 批量收货操作
const handleBatchReceive: (
  btn: GlobalPageLayoutTypes.PageButtonWithGroupCode
) => void = (btn) => {
  const selectedRows = checkoutSelectedRows(btn);

  if (!selectedRows.length) {
    return;
  }
  handleReceive(selectedRows);
};

// 配置
const options = reactive<Partial<GlobalPageLayoutTypes.EditPageLayoutProps>>({
  groups: [
    {
      groupName: "样品行信息",
      groupNameI18nKey: "i18n_field_VNcVH_e5240e50",
      groupCode: "purchaseSampleItemList",
      groupType: "item",
      sortOrder: "2",
      buttons: [
        {
          ...BUTTON_ADD_ONE_ROW,
          authorityCode: "sample#purchaseSampleHead:signSample",
          key: "_receive",
          title: "批量收货",
          emit: true,
          emitKey: "batchReceive",
        },
        {
          ...BUTTON_ADD_ONE_ROW,
          authorityCode: "sample#purchaseSampleHead:colseByItems",
          key: "_close",
          title: "批量关闭",
          emit: true,
          emitKey: "batchClose",
        },
        {
          ...BUTTON_DELETE_ROW,
          authorityCode: "sample#purchaseSampleHead:transformRequest",
          key: "_transform",
          title: "批量转需求池",
          emit: true,
          emitKey: "batchTransform",
        },
      ],
    },
  ],
});
</script>

表行列配置自定义渲染 tsx

TIP

需求:确认项 表行 预制选项 字段,需要根据 填写类型 字段值,动态渲染为 input 或 select(单选或多选)

🙌 🌰 采购协同 > 寻源协同 > 竞价管理 供应方

vue
<script setup lang="tsx">
// ui组件库 types
import type { GlobalPageLayoutTypes } from "@qqt-product/ui";
import type { VxeColumnSlotTypes, VxeTableDataRow } from "vxe-table";

// 确认项 列配置
const purchaseEbiddingConfirmList = [
  {
    field: "writeType",
    title: srmI18n("i18n_field_SMAc_2973057e", "填写类型"),
    width: 120,
  },
  {
    field: "content",
    title: srmI18n("i18n_field_URid_469b0f42", "预制选项"),
    width: 200,
    editRender: {
      enabled: true,
    },
    slots: {
      default({
        row,
        column,
      }: VxeColumnSlotTypes.DefaultSlotParams<VxeTableDataRow>) {
        return [<span>{row[column.field]}</span>];
      },
      edit({
        row,
        column,
      }: VxeColumnSlotTypes.EditSlotParams<VxeTableDataRow>) {
        let elem;
        let options: GlobalPageLayoutTypes.RecordString[] = [];
        // 填写类型 writeType
        // 数据字典 inspection_item_write_type (0: 单选, 1: 多选, 2: 手输)
        if (row.writeType === "0" || row.writeType === "1") {
          options = row.confirmItemList.map(
            (n: GlobalPageLayoutTypes.RecordString) => ({
              label: n.optionsName,
              value: n.optionsCode,
            })
          );
        }
        const props = {
          options,
          transfer: true,
          filterable: true,
          clearable: true,
        };

        // 处理单选
        if (row.writeType === "0") {
          elem = (
            <q-vxe-select v-model={row[column.field]} {...props}></q-vxe-select>
          );
        }
        // 处理多选
        if (row.writeType === "1") {
          elem = (
            <q-vxe-select
              v-model={row[column.field]}
              {...props}
              multiple={true}
            ></q-vxe-select>
          );
        }
        // 处理手输
        if (row.writeType === "2") {
          elem = <vxe-input v-model={row[column.field]}></vxe-input>;
        }

        return [elem];
      },
    },
  },
];
const purchaseEbiddingConfirmListColumns = purchaseEbiddingConfirmList.map(
  (n) => ({ ...n, groupCode: "purchaseEbiddingConfirmList" })
) as GlobalPageLayoutTypes.ColumnItem[];

// 配置
const options = reactive<Partial<GlobalPageLayoutTypes.EditPageLayoutProps>>({
  itemColumns: [...purchaseEbiddingConfirmListColumns],
});
</script>
vue
<script setup lang="tsx">
// ui组件库 types
import type { GlobalPageLayoutTypes } from "@qqt-product/ui";
import type { VxeColumnSlotTypes, VxeTableDataRow } from "vxe-table";

// 确认项 列配置
const purchaseEbiddingConfirmList = [
  {
    field: "writeType",
    title: srmI18n("i18n_field_SMAc_2973057e", "填写类型"),
    width: 120,
  },
  {
    field: "content",
    title: srmI18n("i18n_field_URid_469b0f42", "预制选项"),
    width: 200,
    slots: {
      default({
        row,
        column,
      }: VxeColumnSlotTypes.DefaultSlotParams<VxeTableDataRow>) {
        const val = row[column.field];
        // 处理单选
        if (row.writeType === "0") {
          const confirmItemList: GlobalPageLayoutTypes.RecordString[] =
            row.confirmItemList || [];
          const item: GlobalPageLayoutTypes.RecordString = confirmItemList.find(
            (n) => n.optionsCode === val
          ) || { optionsName: "" };

          return [<span>{item.optionsName}</span>];
        }
        // 处理多选
        if (row.writeType === "1") {
          const confirmItemList: GlobalPageLayoutTypes.RecordString[] =
            row.confirmItemList || [];
          let str = "";
          if (val) {
            let result = val.split(",");
            str = confirmItemList.reduce((acc, obj, i) => {
              if (result.includes(obj.optionsCode)) {
                acc += obj.optionsName;
                if (i !== confirmItemList.length - 1) {
                  acc += ", ";
                }
              }
              return acc;
            }, "");
          }

          return [<span>{str}</span>];
        }
        // 处理手输
        return [<span>{row[column.field]}</span>];
      },
    },
  },
];
const purchaseEbiddingConfirmListColumns = purchaseEbiddingConfirmList.map(
  (n) => ({ ...n, groupCode: "purchaseEbiddingConfirmList" })
) as GlobalPageLayoutTypes.ColumnItem[];

// 配置
const options = reactive<Partial<GlobalPageLayoutTypes.EditPageLayoutProps>>({
  itemColumns: [...purchaseEbiddingConfirmListColumns],
});
</script>

动态设置表行分组数据

TIP

需求:考察项目分项权重表行数据 需要通过 考察项目 表行动态计算获取

🙌 🌰 采购协同 > 质量协同 > 现场考察 > 考察标准

1.监听模板表行添加、删除事件

2.配合绑定函数联动操作

vue
<template>
  <div class="edit-page">
    <q-edit-page-layout
      ref="layoutRef"
      v-bind="options"
      @addRow="handleAddRow"
      @deleteRow="handleDeleteRow"
    ></q-edit-page-layout>
  </div>
</template>

<script setup lang="ts">
// ...
const { pageData } = useRefInstanceHook(layoutRef);

const handleAddRow = (btn: GlobalPageLayoutTypes.PageButtonWithGroupCode) => {
  console.log("btn :>> ", btn);
  setItemWeightListData(btn.groupCode);
};

const handleDeleteRow = (
  btn: GlobalPageLayoutTypes.PageButtonWithGroupCode
) => {
  console.log("btn :>> ", btn);
  setItemWeightListData(btn.groupCode);
};

const cacheClassify = ref<string>();

// 过滤 考察项目 表行数据中的 ‘考察项目分类’
// 用于设置考察项目分项权重表行数据
const setItemWeightListData = (groupCode: string) => {
  if (groupCode !== "itemList") {
    return;
  }
  // 当前考察项目分类
  let itemList = pageData.value[
    groupCode
  ] as GlobalPageLayoutTypes.RecordString[];

  let classify = itemList
    .filter((n) => !!n.inspectionItemClassify)
    .map((n) => {
      return n.inspectionItemClassify as string;
    });

  let currentClassify = classify.join("");
  console.log("currentClassify :>> ", currentClassify);

  // 如果考察项目未修改,则跳过
  if (cacheClassify.value === currentClassify) {
    return;
  }

  cacheClassify.value = currentClassify;

  classify = [...new Set(classify)];
  let length = classify.length;
  let total = 100;

  const rows = classify.map((n, idx) => {
    // 计算各行项分类权重, 总和为100
    let classifyWeight: number;

    if (length === 1) {
      classifyWeight = 100;
    } else if (length > 1 && idx !== length - 1) {
      classifyWeight = Math.floor(100 / length);
      total -= classifyWeight;
    } else {
      classifyWeight = total;
    }
    // classifyWeight = String(classifyWeight)
    console.log("classifyWeight :>> ", classifyWeight);

    return {
      inspectionItemClassify: n,
      classifyWeight,
      remark: "",
    };
  });

  pageData.value.itemWeightList = rows;
};
</script>
tex
groupCode: 'itemList',
title: '考察项目分类',
fieldLabelI18nKey: 'i18n_dict_BmdIzA_c727a5c6',
fieldType: 'select',
field: 'inspectionItemClassify',
align: '',
headerAlign: 'center',
dataFormat: '',
defaultValue: '',
dictCode: 'inspection_item_classify',
alertMsg: '',
required:'1',
js
/**
 * @param {Object} ctx 组件实例
 * @param {String} value 当前所选值
 * @param {Array} data selectModal, remoteSelect 已选行数据 (如有)
 * @param {Object} pageData 页面所有数据
 * @param {Object} layoutConfig 模板配置
 * @param {Object} row 表行数据 (如有)
 * @param {number} idx 表行索引值 (如有)
 * @param {(groupCode: string, fieldName: string, fn: (item: FormFieldsItem | ColumnItem) => void) => void}
 * customFormatItem 遍历模板分组配置,自定义格式化查询到的字段
 * @param {(groupCode: string, fieldName: string, flag: boolean) => void}
 * setItemRequired 自定义设置字段必填
 * @param {(groupCode: string, fieldName: string, flag: boolean) => void}
 * setItemDisabled 自定义设置字段置灰
 * @param {(groupCode: string, fieldName: string, flag: boolean) => void}
 * setItemRequiredOrDisabled 自定义设置字段必填/置灰
 */
function callback(
  ctx,
  {
    value,
    data,
    row,
    idx,
    pageData,
    layoutConfig,
    customFormatItem,
    setItemRequired,
    setItemDisabled,
    setItemRequiredOrDisabled,
  }
) {
  // 当前考察项目分类 inspectionItemClassify
  let itemList = pageData["itemList"];
  let itemWeightList = pageData["itemWeightList"];

  let classify = itemList
    .filter((n) => !!n.inspectionItemClassify)
    .map((n) => n.inspectionItemClassify);

  let currentClassify = classify.join("");
  console.log("currentClassify :>> ", currentClassify);

  let cacheClassify = itemWeightList
    .filter((n) => !!n.inspectionItemClassify)
    .map((n) => n.inspectionItemClassify)
    .join("");

  // 如果考察项目未修改,则跳过
  if (cacheClassify === currentClassify) {
    return;
  }

  classify = [...new Set(classify)];
  let length = classify.length;
  let total = 100;

  const rows = classify.map((n, idx) => {
    // 计算各行项分类权重, 总和为100
    let classifyWeight = 0;

    if (length === 1) {
      classifyWeight = 100;
    } else if (length > 1 && idx !== length - 1) {
      classifyWeight = Math.floor(100 / length);
      total -= classifyWeight;
    } else {
      classifyWeight = total;
    }
    // classifyWeight = String(classifyWeight)
    console.log("classifyWeight :>> ", classifyWeight);

    return {
      inspectionItemClassify: n,
      classifyWeight,
      remark: "",
    };
  });

  pageData.itemWeightList = rows;
}

表头字段自定义弹窗选择渲染

TIP

业务场景中,表头某字段需要配置为自定义的弹窗,默认传参、接口查询、行勾选并点击确定后需要赋值到字段上

vue
<template>
  <div class="edit-page">
    <a-spin :spinning="loading || customLoading">
      <q-edit-page-layout ref="layoutRef" v-bind="options">
        <!-- 关联项目编码 渲染为只读输入框 -->
        <template
          #vertical_baseForm_customSlot_projectNumber="{ field, pageData }"
        >
          <a-input
            v-model:value="pageData[field.fieldName]"
            size="large"
            readOnly
            allowClear
            style="cursor: pointer"
            @click="handleFieldClick"
          />
        </template>
      </q-edit-page-layout>
    </a-spin>
    <!-- 关联项目编码 弹窗选择 -->
    <project-number-modal
      v-model="visible"
      @success="handleSuccess"
    ></project-number-modal>
  </div>
</template>

<script lang="ts" setup>
import ProjectNumberModal from "./component/projectNumberModal.vue";
import { useRefInstanceHook } from "@qqt-product/ui";

const { pageData, layoutConfig, defaultValues } = useRefInstanceHook(layoutRef);

const visible = ref<boolean>(false);

const handleFieldClick = () => {
  visible.value = true;
};

const handleSuccess = ({ data }: Payload) => {
  const projectNumber = (data[0].projectNumber as string) || "";
  pageData.value.projectNumber = projectNumber;
};

// 过滤业务模板相同字段
const handleAfterRemoteConfig = (config) => {
  const formFields = config.formFields || [];
  config.formFields = formFields.filter((n) => n.fieldName !== "projectNumber");

  return config; // 返回处理后的配置数据, 可以是 promise 对象
};

// 配置
const options = reactive<Partial<GlobalPageLayoutTypes.EditPageLayoutProps>>({
  handleAfterRemoteConfig,
  // 2. 配置字段为自定义插槽类型
  localConfig: {
    formFields: [
      {
        groupCode: "baseForm",
        sortOrder: "30",
        fieldType: "customSlot",
        fieldLabel: "关联项目编码",
        fieldLabelI18nKey: "i18n_field_RKdIAo_54458cc1",
        fieldName: "projectNumber",
      },
    ],
  },
});
</script>
vue
<template>
  <div class="checkPrice">
    <a-modal
      :visible="modelValue"
      title="查询关联项目编码"
      width="1000px"
      @cancel="handleCancel"
      @ok="handleOk"
    >
      <div class="form">
        <a-form
          style="display: flex; flex-flow: row wrap; margin: 4px"
          layout="inline"
        >
          <a-form-item
            style="margin-right: 12px"
            :label="srmI18n('i18n_field_availableQty', '可用数量')"
          >
            <q-select
              style="width: 200px"
              v-model:value="form.projectType"
              :placeholder="
                srmI18n('i18n_field_ViFqjWR_6f4361ef', '请选择可用数量')
              "
              allowClear
              dict-code="projectType"
            />
          </a-form-item>
          <a-form-item
            style="margin-right: 12px"
            :label="srmI18n('i18n_title_keyword', '关键字')"
          >
            <a-input
              style="width: 200px"
              :placeholder="
                srmI18n('i18n_title_pleaseInputThekeyword', '请输入关键字')
              "
              v-model:value="form.keyWord"
              allowClear
            />
          </a-form-item>
          <a-form-item style="margin-right: 12px">
            <a-button type="primary" @click="getData">{{
              srmI18n("i18n_title_Query", "查询")
            }}</a-button>
          </a-form-item>
        </a-form>
      </div>
      <div class="grid" style="margin-top: 12px">
        <vxe-grid
          ref="xGrid"
          v-bind="gridConfig"
          :pager-config="pagerConfig"
          :loading="loading"
          :columns="columns"
          :data="tableData"
          @page-change="handlePageChange"
        >
          <template #empty>
            <q-empty></q-empty>
          </template>
        </vxe-grid>
      </div>
    </a-modal>
  </div>
</template>

<script lang="ts" setup>
import { ref, toRefs, watch } from "vue";
import { srmI18n } from "@/utils/srmI18n";
// ui组件库 types
import type { GlobalPageLayoutTypes } from "@qqt-product/ui";
import type {
  VxeGridInstance,
  VxeGridProps,
  VxeGridPropTypes,
  VxePagerEvents,
} from "vxe-table";

import { editVxeTableConfig } from "@qqt-product/ui";

import qqtApi from "@qqt-product/api";
import { message as Message } from "ant-design-vue";

const Request: qqtApi.Request = qqtApi.useHttp();

interface Props {
  modelValue: boolean;
}

interface Payload {
  data: GlobalPageLayoutTypes.RecordString[];
}

const props = withDefaults(defineProps<Props>(), {
  modelValue: false,
});
const { modelValue } = toRefs(props);

const emit = defineEmits<{
  (e: "update:modelValue", modelValue: boolean): void;
  (e: "success", payload: Payload): void;
}>();

const xGrid = ref<VxeGridInstance>();

const rawState = {
  projectType: "0", // 默认询价管理
  keyWord: "",
};

const form = ref<GlobalPageLayoutTypes.RecordString>(rawState);

const loading = ref<boolean>(false);
const tableData = ref<GlobalPageLayoutTypes.RecordString[]>([]);
const pagerConfig = ref({
  total: 0,
  currentPage: 1,
  pageSize: 20,
});

const gridConfig: VxeGridProps = {
  ...editVxeTableConfig,
  rowConfig: {
    keyField: "id",
    isHover: true,
    isCurrent: true,
  },
  radioConfig: {
    trigger: "row",
  },
  height: 400,
  keyboardConfig: {},
  menuConfig: {},
  mouseConfig: {}, // 鼠标配置项
  areaConfig: {}, // 按键配置项
  seqConfig: {
    startIndex:
      ((pagerConfig.value.currentPage as number) - 1) *
      (pagerConfig.value.pageSize as number),
  },
};

const columns: VxeGridPropTypes.Columns = [
  { type: "radio", width: 50, fixed: "left" },
  { type: "seq", width: 60, fixed: "left" },
  { field: "projectNumber", title: "项目编号", minWidth: 150 },
  { field: "projectName", title: "项目名称", minWidth: 150 },
  { field: "projectStatus_dictText", title: "项目状态", minWidth: 150 },
];

const resetData = () => {
  pagerConfig.value = {
    total: 0,
    currentPage: 1,
    pageSize: 20,
  };
  tableData.value = [];
  form.value = { ...rawState };
};

watch(modelValue, (bool) => {
  if (bool) {
    getData();
  } else {
    resetData();
  }
});

const handleCancel = () => {
  emit("update:modelValue", false);
};

const handleOk = () => {
  const $grid = xGrid.value;
  if ($grid) {
    let row = $grid.getRadioRecord();
    if (!row) {
      Message.error(srmI18n(`i18n_title_selectDataMsg`, "请选择数据"));
      return;
    }

    const payload: Payload = {
      data: [row],
    };

    emit("success", payload);
    handleCancel();
  }
};

const getData = () => {
  loading.value = true;
  const url = "/enquiry/purchaseEnquiryHead/listRelationProject";
  const params = {
    pageSize: pagerConfig.value.pageSize,
    pageNo: pagerConfig.value.currentPage,
    column: "id",
    order: "asc",
    ...form.value,
  };
  loading.value = true;
  Request.get({ url, params })
    .then((res) => {
      if (!res.success) {
        Message.error(res.message);
        return;
      }
      const records = res.result.records || [];
      tableData.value = records;
      pagerConfig.value.total = res.result.total;
    })
    .finally(() => {
      loading.value = false;
    });
};

const handlePageChange: VxePagerEvents.PageChange = ({
  currentPage,
  pageSize,
}) => {
  pagerConfig.value.currentPage = currentPage;
  pagerConfig.value.pageSize = pageSize;
  getData();
};
</script>

缓存字段值,字段当前值改变后,需要根据字段值请求接口重新赋值表行数据,另一个表行下拉列选项需要根据接口值重新过滤渲染

TIP

需求:表头 考察步骤考察标准名称 需要缓存,任一值改变后需要请求接口,根据接口返回数据重新赋值考察项目内容,同时考察小组表行的“负责的考察分类”列下拉选项内容需要根据接口数据动态过滤生成

🙌 🌰 采购协同 > 质量协同 > 现场考察 > 现场考察

1.handleAfterDetailApiResponse 缓存当前字段值

2.编辑模板监听绑定函数事件,处理请求等复杂逻辑

3.jsx 实现动态列配置需求

vue
<script setup lang="tsx">
// ui组件库 types
import type { GlobalPageLayoutTypes } from "@qqt-product/ui";
// 工具函数及types
import qqtUtils from "@qqt-product/utils";
const { isEqual } = qqtUtils;

// 缓存
let cacheObj: GlobalPageLayoutTypes.RecordString = {};

const handleAfterDetailApiResponse = ({
  pageData,
}: GlobalPageLayoutTypes.Expose) => {
  console.log("pageData :>> ", pageData);

  // 考察步骤 inspectionStep
  const inspectionStep = pageData.inspectionStep as string;
  // 考察标准名称 inspectionStandardName
  const inspectionStandardName = pageData.inspectionStandardName as string;

  // 缓存当前考察步骤 + 考察标准名称
  cacheObj = {
    inspectionStep,
    inspectionStandardName,
  };
};
</script>
vue
<template>
  <div class="edit-page">
    <a-spin :spinning="loading">
      <q-edit-page-layout
        ref="layoutRef"
        v-bind="options"
        @scoreGroupListAdd="handleScoreGroupListAdd"
        @inspectionStep_callback="handleInspectionStepCallback"
        @inspectionStandardName_callback="handleInspectionStandardNameCallback"
      ></q-edit-page-layout>
    </a-spin>
  </div>
</template>

<script setup lang="tsx">
// 工具函数及types
import qqtUtils from "@qqt-product/utils";
const { isEqual } = qqtUtils;

const isEdit = computed(() => {
  // 单据状态 documentsStatus_dictText
  // 数据字典 site_inspection_status (0: 新建, 1: 待自评, 2: 待评分, 3: 部分评分, 4: 评分完成, 5: 考察完成, 6: 结案, 7: 作废)
  const documentsStatus = pageData.value.documentsStatus as string;
  return !documentsStatus || documentsStatus === "0";
});

// 考察步骤 inspectionStep
// 复杂绑定函数
const handleInspectionStepCallback: (
  config: GlobalPageLayoutTypes.BindFunctionEvent
) => void = () => {
  handleFieldCallback();
};

// 考察标准名称 inspectionStandardName
// 复杂绑定函数
const handleInspectionStandardNameCallback: (
  config: GlobalPageLayoutTypes.BindFunctionEvent
) => void = () => {
  handleFieldCallback();
};

const handleFieldCallback = () => {
  if (!isEdit.value) {
    return;
  }
  // 考察步骤 inspectionStep
  const inspectionStep = pageData.value.inspectionStep as string;
  // 考察标准名称 inspectionStandardName
  const inspectionStandardName = pageData.value
    .inspectionStandardName as string;
  const inspectionStandardVersion = pageData.value
    .inspectionStandardVersion as string;

  const cur = {
    inspectionStep,
    inspectionStandardName,
  };
  // 缓存数据未改变
  if (!inspectionStep && !inspectionStandardName) {
    return;
  }
  if (isEqual(cacheObj, cur)) {
    return;
  } else {
    Modal.confirm({
      title: srmI18n("i18n_alert_SMBmdI_9c235a50", "获取考察项目"),
      content: srmI18n(
        "i18n_alert_BmxsSBmBrRLrASPVVSMBmdIeKVVBmXVWFKQtT_5a374964",
        "考察步骤或考察标准名称改变后将重新获取考察项目, 同时清空考察小组数据,是否继续"
      ),
      onOk() {
        // 缓存当前考察步骤 + 考察标准名称
        cacheObj = {
          inspectionStep,
          inspectionStandardName,
        };

        const url = "/other/purchaseInspectionHead/getItems";
        const data = {
          inspectionStep,
          name: inspectionStandardName,
          versionNumber: inspectionStandardVersion,
        };
        loading.value = true;
        Request.post({ url, data })
          .then((res) => {
            if (res.success) {
              Message.success(res.message);
              pageData.value.scoreGroupList = [];
              // 考察项目表行赋值
              pageData.value.inspectionItemList = res.result || [];

              Notification["warning"]({
                message: srmI18n("i18n_alert_DK_c8f6a", "提示"),
                description: srmI18n(
                  "i18n_alert_BmXVWFIVV_68557a98",
                  "考察小组数据已清空"
                ),
              });
            } else {
              Message.error(res.message);
            }
          })
          .finally(() => {
            loading.value = false;
          });
      },
      onCancel() {
        pageData.value.inspectionStep = cacheObj.inspectionStep;
        pageData.value.inspectionStandardName = cacheObj.inspectionStandardName;
      },
    });
  }
};
</script>
vue
<script setup lang="tsx">
// ...
let inspectionClassifyColumn: GlobalPageLayoutTypes.ColumnItem = {
  groupCode: "scoreGroupList",
  field: "inspectionClassify",
  title: srmI18n("i18n_field_BFjBmzA_cf3ae791", "负责的考察分类"),
  width: 200,
  editRender: {
    enabled: true, // 允许使用作用域插槽
  },
  required: "1",
  slots: {
    default({
      row,
      column,
    }: VxeColumnSlotTypes.DefaultSlotParams<VxeTableDataRow>) {
      return [
        <span>{row[`${column.field}_dictText`] || row[column.field]}</span>,
      ];
    },
    edit({ row, column }: VxeColumnSlotTypes.EditSlotParams<VxeTableDataRow>) {
      let elem;
      const props = {
        options: inspectionClassifyOptions.value,
        transfer: true,
        filterable: true,
        clearable: true,
        multiple: true,
      };

      const onVxeSelectChange = ({
        value,
        label,
      }: {
        value: string;
        label: string;
      }) => {
        row[`${column.field}`] = value;
        row[`${column.field}_dictText`] = label;
      };

      elem = (
        <q-vxe-select
          modelValue={row[column.field]}
          {...props}
          onChange={onVxeSelectChange}
        ></q-vxe-select>
      );
      return [elem];
    },
  },
};

scoreGroupListColumns.splice(3, 1, inspectionClassifyColumn);
</script>

表行数据查询入参为表头某字段, 更新关联关系后弹窗二次确认清空关系表行数据需求

🙌 🌰 采购协同 > 质量协同 > 供应商整改

- 修改业务模板,触发字段绑定函数的 topEmit 方法

- 代码实现

  1. 声明缓存变量、连接符、占位符
  2. handleAfterDetailApiResponse 缓存关联的字段值
  3. 监听字段变化,弹窗二次确认
  4. 确认后执行清除表行回调、更新缓存变量值
  5. 拒绝后从缓存值中恢复原数据
tex
groupCode: 'baseForm',
sortOrder: '2',
fieldType: 'remoteSelect',
fieldLabel: '对方ELS账号',
fieldLabelI18nKey: 'i18n_field_toElsAccount',
fieldName: 'toElsAccount',
js
/**
 * @param {Object} ctx 组件实例
 * @param {String} value 当前所选值
 * @param {Array} data selectModal, remoteSelect 已选行数据 (如有)
 * @param {Object} pageData 页面所有数据
 * @param {Object} layoutConfig 模板配置
 * @param {Object} userInfo 当前登录人信息
 * @param {Object} row 表行数据 (如有)
 * @param {number} idx 表行索引值 (如有)
 * @param {(groupCode: string, fieldName: string, fn: (item: FormFieldsItem | ColumnItem) => void) => void}
 * customFormatItem 遍历模板分组配置,自定义格式化查询到的字段
 * @param {(groupCode: string, fieldName: string, flag: boolean) => void}
 * setItemRequired 自定义设置字段必填
 * @param {(groupCode: string, fieldName: string, flag: boolean) => void}
 * setItemDisabled 自定义设置字段置灰
 * @param {(groupCode: string, fieldName: string, flag: boolean) => void}
 * setItemRequiredOrDisabled 自定义设置字段必填/置灰
 * @param {() => void}
 * topEmit 用于处理复杂绑定函数需求
 */
function callback(
  ctx,
  {
    value,
    data,
    pageData,
    layoutConfig,
    customFormatItem,
    setItemRequired,
    setItemDisabled,
    setItemRequiredOrDisabled,
    topEmit,
  }
) {
  const {
    toElsAccount = "",
    supplierCode = "",
    supplierName = "",
  } = data[0] || {};
  pageData.toElsAccount = toElsAccount;
  pageData.supplierCode = supplierCode;
  pageData.supplierName = supplierName;

  // 本地页面处理复杂绑定函数需求
  topEmit && topEmit();
}
vue
<template>
  <q-edit-page-layout
    @toElsAccount_callback="handleToElsAccountCallback"
  ></q-edit-page-layout>
</template>

<script setup lang="ts">
//...

// 缓存
const cacheObj = ref<GlobalPageLayoutTypes.RecordString>({});

const handleAfterDetailApiResponse = ({
  pageData,
}: GlobalPageLayoutTypes.Expose) => {
  console.log("pageData :>> ", pageData);

  // 缓存需要关联的字段值
  // 供应商ELS账号 toElsAccount
  // 供应商名称 supplierName
  // 供应商编码 supplierCode
  const { toElsAccount, supplierName, supplierCode } = pageData;
  cacheObj.value = { toElsAccount, supplierName, supplierCode };
};

// 供应商ELS账号 toElsAccount
// 复杂绑定函数
const handleToElsAccountCallback: (
  config: GlobalPageLayoutTypes.BindFunctionEvent
) => void = () => {
  handleFieldCallback();
};

const handleFieldCallback = () => {
  const { toElsAccount, supplierName, supplierCode } = pageData.value;

  // 缓存数据未改变
  if (cacheObj.value.toElsAccount === toElsAccount) {
    return;
  }

  const content = [
    srmI18n("i18n_title_supplierELSAccount", "供应商ELS账号"),
    srmI18n("i18n_alert_RrAS_25ff6c73", "值改变后"),
    ",",
    srmI18n("i18n_alert_PVG_1675b85", "将清除"),
    srmI18n("i18n_title_supplierAllChageLineInfo", "供应商整改行信息"),
    srmI18n("i18n_alert_BcWF_400e0fe2", "表行数据"),
    ",",
    srmI18n("i18n_alert_KQtT_2fbef6fd", "是否继续"),
    "?",
  ];

  Modal.confirm({
    title: "操作确认",
    content: content.join(""),
    onOk() {
      // 清空行数据
      pageData.value.purchaseSupplierRectificationReportItemList = [];

      const description = [
        srmI18n("i18n_title_supplierAllChageLineInfo", "供应商整改行信息"),
        "表行数据",
        "已清空",
      ];

      Notification["warning"]({
        message: srmI18n("i18n_alert_DK_c8f6a", "提示"),
        description: description.join(""),
      });

      // 重置缓存数据
      cacheObj.value = { toElsAccount, supplierName, supplierCode };
    },
    onCancel() {
      const { toElsAccount, supplierName, supplierCode } = cacheObj.value;
      pageData.value.toElsAccount = toElsAccount;
      pageData.value.supplierName = supplierName;
      pageData.value.supplierCode = supplierCode;
    },
  });
};

// 配置
const options = reactive<Partial<GlobalPageLayoutTypes.EditPageLayoutProps>>({
  handleAfterDetailApiResponse,
});
</script>

阶梯报价重构-允许输入小于 1 的价格基数

TIP

把阶梯报价拆分为显示、输入两部分,表行插槽动态渲染实现

vue
<template>
  <div class="edit-page">
    <q-edit-page-layout ref="layoutRef" v-bind="options"></q-edit-page-layout>

    <!-- 阶梯报价 -->
    <ladder-price-modal
      v-model="ladderPriceVisible"
      :value="ladderPriceValue"
      :cachePayload="ladderPriceCachePayload"
      @success="handleLadderPriceSuccess"
    ></ladder-price-modal>
  </div>
</template>

<script setup lang="tsx">
import { ref, reactive, onBeforeMount } from "vue";
import type { ComponentPublicInstance } from "vue";

import LadderPriceDisplay from "./components/ladderPriceDisplay.vue";
import LadderPriceModal from "./components/ladderPriceModal.vue";

// 缓存行信息
const cachePayload =
  ref<VxeColumnSlotTypes.DefaultSlotParams<VxeTableDataRow>>();

const ladderPriceVisible = ref<boolean>(false);
const ladderPriceValue = ref<string>("");
const ladderPriceCachePayload =
  ref<VxeColumnSlotTypes.DefaultSlotParams<VxeTableDataRow>>();

const handleLadderPriceSuccess = (str) => {
  if (ladderPriceCachePayload.value) {
    ladderPriceCachePayload.value.row.ladderPriceJson = str;
  }
};

const handleAfterRemoteConfig = (config) => {
  const itemColumns = config.itemColumns || [];
  itemColumns.forEach((n) => {
    // 阶梯报价JSON
    if (n.field === "ladderPriceJson") {
      n.fieldType = "";
      n.slots = {
        default(
          payload: VxeColumnSlotTypes.DefaultSlotParams<VxeTableDataRow>
        ) {
          const { row, column } = payload;
          const val = row[column.field] || "";
          const handleLadderPriceClick = () => {
            ladderPriceCachePayload.value = payload;
            ladderPriceValue.value = val;
            ladderPriceVisible.value = true;
          };
          const handleLadderPriceClear = () => {
            row.ladderPriceJson = "";
          };

          return [
            <LadderPriceDisplay
              value={val}
              onClick={handleLadderPriceClick}
              onClear={handleLadderPriceClear}
            ></LadderPriceDisplay>,
          ];
        },
      };
    }
  });
  return config;
};

// 配置
const options = reactive<Partial<GlobalPageLayoutTypes.EditPageLayoutProps>>({
  handleAfterRemoteConfig,
});
</script>
vue
<template>
  <div class="ladder-price-display">
    <a-tooltip placement="topLeft" overlayClassName="tip-overlay-class">
      <template #title>
        <div class="grid">
          <vxe-table
            ref="xGrid"
            auto-resize
            border
            row-id="id"
            size="mini"
            :data="tableData"
          >
            <vxe-table-column
              type="seq"
              :title="srmI18n('i18n_alert_seq', '序号')"
              width="80"
            ></vxe-table-column>
            <vxe-table-column
              field="ladder"
              :title="srmI18n('i18n_title_ladderLeve', '阶梯级')"
              width="140"
            ></vxe-table-column>
            <vxe-table-column
              field="price"
              :title="srmI18n('i18n_title_price', '含税价')"
              width="140"
            ></vxe-table-column>
            <vxe-table-column
              field="netPrice"
              :title="srmI18n('i18n_title_netPrice', '不含税价')"
              width="140"
            ></vxe-table-column>
          </vxe-table>
        </div>
      </template>
      {{ str }}
    </a-tooltip>
    <a class="icons">
      <StockOutlined @click="handleClick" />
      <CloseCircleOutlined v-show="str" @click="handleClear" />
    </a>
  </div>
</template>

<script lang="ts" setup>
import { computed } from "vue";
import { StockOutlined, CloseCircleOutlined } from "@ant-design/icons-vue";
// 国际化
import { srmI18n } from "@/utils/srmI18n";
// ui组件库 types
import type { GlobalPageLayoutTypes } from "@qqt-product/ui";

type Props = {
  value: string;
};
const props = withDefaults(defineProps<Props>(), {
  value: "",
});
const emit = defineEmits<{
  (e: "click"): void;
  (e: "clear"): void;
}>();

const str = computed<string>(() => {
  if (props.value) {
    try {
      const arr = JSON.parse(props.value);
      if (Array.isArray(arr)) {
        return arr.map((n) => `${n.ladder}:${n.price || ""}`).join(";");
      }
    } catch (err) {
      console.log("parse ladderPriceJSON error :>> ", err);
    }
  }
  return "";
});

const tableData = computed<GlobalPageLayoutTypes.RecordString[]>(() => {
  if (props.value) {
    try {
      const arr = JSON.parse(props.value);
      if (Array.isArray(arr)) {
        return arr;
      }
    } catch (err) {
      console.log("parse ladderPriceJSON error :>> ", err);
    }
  }
  return [];
});

const handleClick = () => emit("click");
const handleClear = () => emit("clear");
</script>

<style lang="less">
.ladder-price-display {
  position: relative;
  min-height: 30px;
  padding-right: 20px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  .icons {
    position: absolute;
    // display: inline-block
    font-size: 16px;
    right: 0px;
    top: 3px;
    z-index: 10;
  }
}
</style>
vue
<template>
  <div class="checkPrice">
    <a-modal
      :visible="modelValue"
      width="580px"
      :title="srmI18n('i18n_title_ladderSetting', '阶梯设置')"
      :maskClosable="false"
      :keyboard="false"
      @cancel="handleCancel"
      @ok="handleOk"
    >
      <div class="form">
        <a-form
          style="display: flex; flex-flow: row wrap; margin: 4px"
          layout="inline"
        >
          <a-form-item
            style="margin-right: 12px"
            :label="srmI18n('i18n_title_ladderCount', '阶梯数量')"
          >
            <a-input-number
              style="width: 200px"
              placeholder="请输入大于0的阶梯数量"
              v-model:value="form.ladderQuantity"
              :min="0"
              @pressEnter="setData"
            />
          </a-form-item>
          <a-form-item style="margin-right: 12px">
            <a-button type="primary" @click="setData">{{
              srmI18n("i18n_title_add", "添加")
            }}</a-button>
          </a-form-item>
        </a-form>
      </div>
      <div class="grid" style="margin-top: 12px">
        <vxe-grid
          ref="xGrid"
          v-bind="gridConfig"
          :columns="columns"
          :data="tableData"
        >
          <template #empty>
            <q-empty></q-empty>
          </template>
        </vxe-grid>
      </div>
    </a-modal>
  </div>
</template>

<script lang="tsx" setup>
import { ref, toRefs, watch } from "vue";
import { srmI18n } from "@/utils/srmI18n";
// ui组件库 types
import type { GlobalPageLayoutTypes } from "@qqt-product/ui";
import type {
  VxeGridInstance,
  VxeGridProps,
  VxeGridPropTypes,
  VxeColumnSlotTypes,
  VxeTableDataRow,
} from "vxe-table";

import { editVxeTableConfig } from "@qqt-product/ui";
import { message as Message } from "ant-design-vue";
import qqtUtils from "@qqt-product/utils";
const { cloneDeep } = qqtUtils;

interface Props {
  modelValue: boolean;
  value: string;
  cachePayload:
    | VxeColumnSlotTypes.DefaultSlotParams<VxeTableDataRow>
    | undefined;
}

const props = withDefaults(defineProps<Props>(), {
  modelValue: false,
  value: "",
});
const { modelValue } = toRefs(props);

const emit = defineEmits<{
  (e: "update:modelValue", modelValue: boolean): void;
  (e: "success", val: string): void;
}>();

const LT = "< x <";
const ELT = "<= x <";
const GT = "<= x";

const xGrid = ref<VxeGridInstance>();

const rawState = {
  ladderQuantity: "",
};

const form = ref<GlobalPageLayoutTypes.RecordString>(rawState);

const tableData = ref<GlobalPageLayoutTypes.RecordString[]>([]);

const gridConfig: VxeGridProps = {
  ...editVxeTableConfig,
  rowConfig: {
    keyField: "id",
    isHover: true,
    isCurrent: true,
  },
  radioConfig: {
    trigger: "row",
  },
  height: 340,
  keyboardConfig: {},
  menuConfig: {},
  mouseConfig: {}, // 鼠标配置项
  areaConfig: {}, // 按键配置项
};

const columns: VxeGridPropTypes.Columns = [
  { type: "seq", width: 60, fixed: "left" },
  {
    field: "ladder",
    title: srmI18n("i18n_title_ladderLeve", "阶梯级"),
    width: 150,
  },
  {
    field: "ladderQuantity",
    title: srmI18n("i18n_title_ladderCount", "阶梯数量"),
    minWidth: 150,
  },
  {
    field: "grid_operation",
    // fixed: 'right',
    title: "操作",
    width: 140,
    slots: {
      default({
        $rowIndex,
      }: VxeColumnSlotTypes.DefaultSlotParams<VxeTableDataRow>) {
        const handleDelete = () => {
          tableData.value.splice($rowIndex, 1);
        };

        return [
          <a-button
            type="text"
            danger
            disabled={$rowIndex !== tableData.value.length - 1}
            onClick={handleDelete}
          >
            删除
          </a-button>,
        ];
      },
    },
  },
];

const setNltRow = () => {
  if (tableData.value.length <= 1) return;
  if (tableData.value.length > 2) {
    let last = tableData.value[tableData.value.length - 1];
    let prev = tableData.value[tableData.value.length - 2];
    prev.ladder = `${prev.ladderQuantity} ${ELT} ${last.ladderQuantity}`;
  } else {
    let last = tableData.value[tableData.value.length - 1];
    last.ladder = `${last.ladderQuantity} ${GT}`;
  }
};

const setData = () => {
  const val = form.value.ladderQuantity;
  if (!val) {
    Message.error(srmI18n("title_pleaseEnterladderCount", "请输入阶梯数量!"));
    return;
  }
  let taxCode = "";
  let taxRate = "";
  if (props.cachePayload) {
    taxCode = props.cachePayload.row.taxCode || "";
    taxRate = props.cachePayload.row.taxRate || "";
  }

  if (tableData.value.length === 0) {
    let row = {
      ladderQuantity: 0,
      ladder: `0 ${LT} ${val}`,
      price: "",
      netPrice: "",
      taxCode,
      taxRate,
    };
    tableData.value.push(row);
  } else {
    let flag = tableData.value.every((n) => n.ladderQuantity < val);
    if (!flag) {
      Message.error(
        srmI18n(
          "i18n_title_ladderMostLastLadderCount",
          "阶梯数量必须大于上一阶梯数量"
        )
      );
      return;
    }
  }
  let row = {
    ladderQuantity: val,
    ladder: `${val} ${GT}`,
    price: "",
    netPrice: "",
    taxCode,
    taxRate,
  };
  tableData.value.push(row);
  setNltRow();

  form.value.ladderQuantity = "";
};

const resetData = () => {
  tableData.value = [];
  form.value = { ...rawState };
};

watch(modelValue, (bool) => {
  if (bool) {
    init();
  } else {
    resetData();
  }
});

const handleCancel = () => {
  emit("update:modelValue", false);
};

const handleOk = () => {
  const arr = cloneDeep(tableData.value);
  emit("success", JSON.stringify(arr));
  handleCancel();
};

const init = () => {
  if (props.value) {
    try {
      const arr = JSON.parse(props.value);
      if (Array.isArray(arr)) {
        tableData.value = arr;
      }
    } catch (err) {
      console.log("parse ladderPriceJSON error :>> ", err);
    }
  }
};
</script>