Appearance
模板配置
基本配置
vue
<template>
<div class="edit-page">
<q-edit-page-layout
v-bind="options"
@pageBack="handlePageBack"
></q-edit-page-layout>
</div>
</template>
<script setup lang="ts">
import { ref, reactive } from "vue";
// 国际化
import { srmI18n } from "@/utils/srmI18n";
// ui组件库 types
import type { GlobalPageLayoutTypes } from "@qqt-product/ui";
// 按钮常量引用
import { BUTTON_SAVE } from "@qqt-product/ui";
import { message as Message } from "ant-design-vue";
// 全局数据缓存
import { useGlobalStoreWithDefaultValue } from "@/use/useGlobalStore";
import { useRouter } from "vue-router";
const router = useRouter();
// 配置页面缓存
defineOptions({
name: "srm-enquiry-ebiddingHead-edit",
});
const {
isCreate, // 布尔值, 是否为新增状态
getCurrentRow, // 获取父级当前行缓存方法
userInfo, // 当前登录人信息
} = useGlobalStoreWithDefaultValue();
const currentRow = getCurrentRow(); // 父级当前行缓存
// 配置
const options = reactive<Partial<GlobalPageLayoutTypes.EditPageLayoutProps>>({
/**
* 模块业务类型 string
* 取值根据各模块配置
* 当定义为"custom"时,使用本地配置
*/
businessType: "ebidding",
// 布局模式
pattern: "tab",
// 当前行缓存数据
currentRow: currentRow,
refreshMethods(row?: GlobalPageLayoutTypes.CurrentRow) {
if (options.currentRow) {
options.currentRow =
row ||
Object.assign({}, options.currentRow, {
_t: +new Date(),
});
}
},
// 当前登录人信息
userInfo: userInfo.value as GlobalPageLayoutTypes.UserInfo,
// 明细接口 -- 接口字符串
detailApi: "/ebidding/purchaseEbiddingHead/queryById",
// 本地页面数据配置
localConfig: {
// 本地分组配置
groups: [],
//
itemColumns: [],
},
// 页面按钮
pageButtons: [
{
// 保存按钮
...BUTTON_SAVE,
args: {
url: "/ebidding/purchaseEbiddingHead/edit",
},
},
],
});
// 回退操作,如果是新增单据, 列表页需要刷新
const backToList: (flag?: boolean) => void = (flag = false) => {
const path = "/srm/enquiry/ebiddingHead/list";
const query = flag ? { t: +new Date() } : {};
router.push({ path, query });
};
// 回退至列表页
const handlePageBack = () => {
backToList(isCreate.value);
};
</script>
queryById 接口自定义配置
🙌 🌰 采购协同 > 寻源协同 > 多标包招标管理
vue
<script setup lang="ts">
const options = reactive<Partial<GlobalPageLayoutTypes.EditPageLayoutProps>>({
// 方法配置自定义明细接口
detailApi(row) {
return {
url: "/bidding/purchaseBiddingProjectHead/queryById",
method: "get",
params: {
id: row.projectId || '', // 须返回id值
},
};
},
});
</script>
可选自定义属性
vue
<script setup lang="ts">
const options = reactive<Partial<GlobalPageLayoutTypes.EditPageLayoutProps>>({
pageTitle: "xxx", // 模板名称
showPageHeader: false, // 是否隐藏模板头部
showLayoutAnchor: false, // vertical布局模式,是否隐藏 anchor 锚点
isUseLocalPattern: false, // 是否只使用本地布局模式
isUseLocalData: false, // 是否只使用本地同步数据
localData: {}, // 本地同步数据
});
</script>
本地头分组字段配置、本地行分组字段配置
WARNING
本地的头分组、或行分组字段配置会与业务模板配置所获取的数据混合, 头分组字段按 sortOrder 属性排序, 字段 fieldName 即使相同也不会被覆盖, 可以使用模板预设的钩子方法过滤, 详见后述示例
🙌 🌰 采购协同 > 寻源协同 > 答疑
ts
// 国际化
import { srmI18n } from "@/utils/srmI18n";
const baseForm = [
{
fieldLabel: srmI18n("i18n_title_questionNotNo", "答疑编号"),
fieldName: "mentoringNumber",
},
{
fieldType: "select",
fieldLabel: srmI18n("i18n_field_businessType", "业务类型"),
fieldName: "businessType",
dictCode: "srmClarificationBusinessType",
disabled: true,
},
// ...
];
// 表头分组配置
export const formFields = baseForm.map((n) => ({
...n,
groupCode: "baseForm",
})) as GlobalPageLayoutTypes.FormFieldsItem[];
const purchaseMentoringItemList = [
{
field: "mentoringNumber",
title: srmI18n("i18n_field_mentoringNumber", "答疑单号"),
minWidth: 180,
},
{
field: "mentoringAnswer",
title: srmI18n("i18n_title_questionAnswer", "疑问答案"),
fieldType: "input",
minWidth: 180,
},
// ...
];
// 表行分组配置
export const purchaseMentoringItemListColumns = purchaseMentoringItemList.map(
(n) => ({ ...n, groupCode: "purchaseMentoringItemList" })
) as GlobalPageLayoutTypes.ColumnItem[];
vue
<script setup lang="ts">
import { formFields, purchaseMentoringItemListColumns } from "./hook/columns";
// 配置
const options = reactive<Partial<GlobalPageLayoutTypes.EditPageLayoutProps>>({
formFields,
itemColumns: [...purchaseMentoringItemListColumns, gridOperationColumn],
});
</script>
附件表行列配置
TIP
可以使用本地配置与钩子方法相结合的组装方式
ts
// 附件 列配置
const purchaseAttachmentList = [
{
title: srmI18n("i18n_field_fileType", "文件类型"),
field: "fileType_dictText",
minWidth: 120,
},
{
title: srmI18n("i18n_title_lineItem", "行项目"),
field: "itemNumber",
minWidth: 120,
},
{
title: srmI18n("i18n_field_cdIRL_c7c40624", "行项目名称"),
field: "materialName",
width: 120,
},
];
export const purchaseAttachmentListColumns = purchaseAttachmentList.map(
(n) => ({ ...n, groupCode: "purchaseAttachmentList" })
) as GlobalPageLayoutTypes.ColumnItem[];
vue
<script setup lang="tsx">
// 附件表行列配置预设方法
import { useFileColumnHook } from "@qqt-product/ui";
// 列配置
import { purchaseAttachmentListColumns } from "./hook/columns";
// 配置
const options = reactive<Partial<GlobalPageLayoutTypes.EditPageLayoutProps>>({
// 本地页面数据配置
localConfig: {
itemColumns: [
...purchaseAttachmentListColumns,
...useFileColumnHook({ groupCode: "purchaseAttachmentList" }),
],
},
});
</script>
handleAfterRemoteConfig, 自定义处理业务模板数据
TIP
允许自定义修改业务模板返回的数据内容
vue
<script setup lang="ts">
// ...
// 兼容处理业务模板中,原行信息各字段在设置了采购方的 groupCode 后
// 到供应方端字段无法显示bug
const handleAfterRemoteConfig = (config) => {
const itemColumns = config.itemColumns || [];
itemColumns.forEach((n) => {
if (n.groupCode === "purchaseEbiddingItemList") {
n.groupCode = "saleEbiddingItemList";
}
});
return config;
};
// 配置
const options = reactive<Partial<GlobalPageLayoutTypes.EditPageLayoutProps>>({
handleAfterRemoteConfig,
});
</script>
handleAfterDetailApiResponse, 自定义组装接口数据
TIP
允许自定义组装接口返回数据 钩子方法会触发两次, 分别为初始化赋值后触发以及 queryById 接口返回赋值后触发,可以加条件判断,通过 pageData.id 来限制是否只在编辑状态下触发
🙌 🌰 采购协同 > 寻源协同 > 物料主数据
vue
<script setup lang="ts">
// 自定义组装详情接口数据
const handleAfterDetailApiResponse = ({
pageData,
layoutConfig,
defaultValues,
}: GlobalPageLayoutTypes.Expose) => {
// console.log("pageData :>> ", pageData);
// console.log("layoutConfig :>> ", layoutConfig);
// console.log("defaultValues :>> ", defaultValues);
let basicUnit = pageData.basicUnit as string;
if (!pageData.id) {
const base = {
basicUnit,
basicAmount: 1,
changeUnit: "=",
objectAmount: 1,
objectUnit: "",
isDefault: "1",
};
const tableData = pageData[
"purchaseMaterialMeterUnitList"
] as GlobalPageLayoutTypes.RecordString[];
if (tableData) {
tableData.push({ ...base, type: "0", _row_id: uniqueId("_row_id_") });
tableData.push({ ...base, type: "1", _row_id: uniqueId("_row_id_") });
}
}
};
// 配置
const options = reactive<Partial<GlobalPageLayoutTypes.EditPageLayoutProps>>({
handleAfterDetailApiResponse,
});
</script>
按钮配置
WARNING
V6 产品的按钮规则为: 用户权限配置控制按钮的显示或隐藏;数据只控制按钮是否置灰;与 V5 产品不同,不允许随意设置按钮的显隐; 通常情况下,从 UI 包里获取预设的按钮常量
🙌 🌰 采购协同 > 寻源协同 > 竞价管理
vue
<script setup lang="ts">
// 按钮常量引用
import {
BUTTON_FILL_CELLS, // 向下填充
BUTTON_ADD_ONE_ROW, // 添加行
BUTTON_DELETE_ROW, // 删除行
BUTTON_SAVE, // 保存
BUTTON_PUBLISH, // 发布
BUTTON_SUBMIT, // 提交审批
BUTTON_CANCELAUDIT, // 撤销审批
BUTTON_FLOW_VIEW, // 查看流程
BUTTON_UPLOAD, // 上传附件
BUTTON_DOWNLOAD_ALL, // 批量下载
BUTTON_REMOVE_ALL, // 批量删除
BUTTON_IMPORT_ROW, // 导入Excel
BUTTON_CONFIRM, // 确认
BUTTON_REJECT, // 拒绝
BUTTON_CUSTOM, // 自定义按钮_1
BUTTON_CUSTOM_PRIMARY, // 自定义按钮_2
BUTTON_CUSTOM_DANGER, // 自定义按钮_3
} from "@qqt-product/ui";
// 配置
const options = reactive<Partial<GlobalPageLayoutTypes.EditPageLayoutProps>>({
localConfig: {
groups: [
{
groupName: "确认项",
groupNameI18nKey: "i18n_field_RLd_1d59643",
groupCode: "purchaseEbiddingConfirmList",
groupType: "item",
sortOrder: "4",
buttons: [BUTTON_ADD_ONE_ROW, BUTTON_DELETE_ROW], // 普通添加行、删除行, 无需额外配置
},
],
// 页面按钮
pageButtons: [
{
// 保存
...BUTTON_SAVE,
args: {
url: "编辑保存api接口地址",
addUrl: "新增保存 api 接口地址", // 可以不配置, 新增状态模板默认会把 'xxx/edit' 替换为 'xxx/add'
},
},
{
// 发布
...BUTTON_PUBLISH,
args: {
url: "发布api接口地址",
},
checkBefore: true, // 是否需要校验单据publishAudit字段值状态, 默认为true, 如果需要关闭校验,设置为 false
},
{
// 提交审批
...BUTTON_SUBMIT,
handleBefore: handleBeforeSubmit, // 提交审批前需要格式化审批流的数据, 在 handleBefore 钩子中处理
args: {
url: "提交审批api接口地址",
},
},
],
},
});
</script>
vue
<script setup lang="ts">
// 1. 引用按钮常量引用
import {
BUTTON_CUSTOM, // 自定义按钮_1 普通按钮
BUTTON_CUSTOM_PRIMARY, // 自定义按钮_2 蓝色按钮
BUTTON_CUSTOM_DANGER, // 自定义按钮_3 红色按钮
} from "@qqt-product/ui";
// 2. 按钮配置及自定义事件
const options = reactive<Partial<GlobalPageLayoutTypes.EditPageLayoutProps>>({
localConfig: {
// 页面按钮
pageButtons: [
{
...BUTTON_CUSTOM,
title: "自定义标题_1",
emit: true, // 触发自定义事件
emitKey: "customClick", // 自定义事件名
},
],
},
});
// 4. 自定义事件方法
const handleCustomClick = () => {
// 处理业务逻辑
};
</script>
<!-- 3 监听自定义事件 -->
<template>
<q-edit-page-layout @customClick="handleCustomClick"></q-edit-page-layout>
</template>
vue
<script setup lang="ts">
// 按钮常量引用
import {
BUTTON_SUBMIT, // 提交审批
} from "@qqt-product/ui";
// 配置
const options = reactive<Partial<GlobalPageLayoutTypes.EditPageLayoutProps>>({
// 页面按钮
pageButtons: [
{
...BUTTON_SUBMIT,
disabled(pageData) {
return !pageData.id || pageData.publishAudit !== "1";
},
args: {
url: "/elsUflo/audit/submit",
},
},
],
});
</script>
vue
<script setup lang="ts">
// 按钮常量引用
import {
BUTTON_PUBLISH, // 提交审批
} from "@qqt-product/ui";
// 配置
const options = reactive<Partial<GlobalPageLayoutTypes.EditPageLayoutProps>>({
// 页面按钮
pageButtons: [
{
...BUTTON_PUBLISH,
args: {
url: "/enquiry/purchaseEnquiryHead/publish",
},
authorityCode: "enquiry#purchaseEnquiryHead:publish",
},
],
});
</script>
vue
<template>
<q-edit-page-layout @customSubmit="handleCustomSubmit"></q-edit-page-layout>
</template>
<script setup lang="ts">
// 按钮常量引用
import {
BUTTON_PUBLISH, // 提交审批
} from "@qqt-product/ui";
// 配置
const options = reactive<Partial<GlobalPageLayoutTypes.EditPageLayoutProps>>({
// 页面按钮
pageButtons: [
{
...BUTTON_SUBMIT,
emit: true,
emitKey: "customSubmit",
args: {
url: "/elsUflo/audit/submit",
},
},
],
});
// 自定义处理提交审批业务逻辑
const handleCustomSubmit = () => {};
</script>
vue
<script setup lang="ts">
// 按钮常量引用
import { BUTTON_SAVE } from "@qqt-product/ui";
// 配置
const options = reactive<Partial<GlobalPageLayoutTypes.EditPageLayoutProps>>({
// 页面按钮
pageButtons: [
{
...BUTTON_SAVE,
emit: true,
emitKey: "customSave",
// args 可以配置为方法,须返回 PageButtonArgs 类型
args(pageData) {
let url: string;
url = pageData.supplierInfoChangeId
? "/supplier/supplierInfoChangeHead/edit"
: "/supplier/supplierInfoChangeHead/add";
return {
url,
};
},
authorityCode: "price#PurchaseInformationRecords:add",
},
],
});
</script>
向下填充功能按钮
TIP
表行可配置"向下填充"
按钮,满足用户快速填写单据需求,所有可编辑字段都允许填充,字段配置的绑定函数会在赋值完成后执行,弹窗、远程下拉搜索等类型字段的绑定函数需要做特殊处理,详见示例说明。
vue
<script setup lang="ts">
// 按钮常量引用
import {
BUTTON_FILL_CELLS, // 向下填充
} from "@qqt-product/ui";
// 配置
const options = reactive<Partial<GlobalPageLayoutTypes.EditPageLayoutProps>>({
localConfig: {
groups: [
{
groupName: "竞价行信息",
groupNameI18nKey: "i18n_title_ebiddingBankInfo",
groupCode: "purchaseEbiddingItemList",
groupType: "item",
sortOrder: "2",
buttons: [BUTTON_FILL_CELLS],
},
],
},
});
</script>
js
/**
* @param {Object} ctx 组件实例
* @param {String} value 当前所选值
* @param {Array} data selectModal, remoteSelect 已选行数据 (如有)
* @param {boolean} _isFill 向下填充或粘贴时为 true
* @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,
_isFill,
data,
row,
idx,
pageData,
layoutConfig,
userInfo,
customFormatItem,
setItemRequired,
setItemDisabled,
setItemRequiredOrDisabled,
topEmit,
Decimal,
}
) {
if (data && data.length) {
// 填充操作时 _isFill 判断标识值为 true
// 且 data 参数为当前所选行缓存数据
// 如果赋值操作中字段属性不同, 需要加条件判断区分取值
if (_isFill) {
const { materialId = "" } = data[0] || {};
row.materialId = materialId;
} else {
const { id = "" } = data[0] || {};
row.materialId = id;
}
}
}
发布前或提交审批前查询供应商是否为已冻结
INFO
竞价、询价、招投标、采购订单等模块,发布前需要校验所选供商是否为已冻结,正常状态的供应商才允许发布操作;
🙌 🌰 采购协同 > 寻源协同 > 多标包招标管理
vue
<script setup lang="tsx">
// 配置
const options = reactive<Partial<GlobalPageLayoutTypes.EditPageLayoutProps>>({
pageButtons: [
{
...BUTTON_PUBLISH,
checkBefore: false,
isExistFrozenSource: true, // 开关, 是否需要开启冻结校验
// isExistFrozenSourceUrl: '/supplier/supplierMaster/isExistFrozenStateData', // 使用默认值, 可以不配置
// 接口参数配置
isExistFrozenSourceParams: (pageData) => {
// 设置供应商信息和询价行信息
const purchaseBiddingItemList =
pageData.purchaseBiddingItemList as unknown[];
const biddingSupplierList = pageData.biddingSupplierList as unknown[];
// 返回
return Object.assign({}, pageData, {
purchaseOrgItemList: purchaseBiddingItemList,
supplierItemList: biddingSupplierList,
});
},
args: {
url: "/ebidding/purchaseEbiddingHead/publish",
},
},
],
});
</script>
handleBefore 或 handleAfter(AOP 切片编程, 异步校验)
vue
<script setup lang="ts">
import qqtUtils from "@qqt-product/utils";
const { createPromise } = qqtUtils;
import type { CreatePromise } from "@qqt-product/utils";
// 保存操作前混合当前按钮配置参数
const handleBeforeSave = (
args: GlobalPageLayoutTypes.ExposeWithPageButtons
) => {
const fn: CreatePromise<GlobalPageLayoutTypes.ExposeWithPageButtons> = (
resolve
) => {
let pageData = args.pageData;
const cacheButton = args.cacheButton as GlobalPageLayoutTypes.PageButton;
const btnArgs = cacheButton.args as GlobalPageLayoutTypes.RecordString;
let formatData = {
...pageData.value,
fbk1: btnArgs.fbk1 ?? "", // 混合按钮配置的某些属性
};
resolve({ ...args, pageData: ref(formatData) });
};
return createPromise(
fn
) as Promise<GlobalPageLayoutTypes.ExposeWithPageButtons>;
};
// 提交审批前格式化入参
const handleBeforeSubmit = (
args: GlobalPageLayoutTypes.ExposeWithPageButtons
) => {
const fn: CreatePromise<GlobalPageLayoutTypes.ExposeWithPageButtons> = (
resolve
) => {
let pageData = args.pageData;
let formatData = {
businessId: pageData.value.id || "",
businessType: "publishEnquiry",
auditSubject: `询价发布审批,单号:${
pageData.value.materialNumber || ""
}`,
params: JSON.stringify(pageData.value),
};
resolve({ ...args, pageData: ref(formatData) });
};
return createPromise(
fn
) as Promise<GlobalPageLayoutTypes.ExposeWithPageButtons>;
};
// 配置
const options = reactive<Partial<GlobalPageLayoutTypes.EditPageLayoutProps>>({
pageButtons: [
{
...BUTTON_SAVE,
handleBefore: handleBeforeSave,
args: {
url: "/enquiry/purchaseEnquiryHead/edit",
},
},
{
...BUTTON_SUBMIT,
emit: true,
emitKey: "customSubmit",
handleBefore: handleBeforeSubmit,
args: {
url: "/elsUflo/audit/submit",
},
},
],
});
</script>
模板数据获取
TIP
业务模板暴露的数据都是双向绑定的值,可以读取数据,也可以赋值操作
vue
<!-- 组件ref -->
<q-edit-page-layout ref="layoutRef" v-bind="options"></q-edit-page-layout>
<script setup lang="ts">
// 获取模板 ref 暴露值方法
import { useRefInstanceHook } from "@qqt-product/ui";
const {
pageData, // 模板所有数据
layoutConfig, // 模板配置
defaultValues, // 模板配置默认值
} = useRefInstanceHook(layoutRef);
</script>
获取表行勾选数据、行索引
vue
<q-edit-page-layout
ref="layoutRef"
@_checkboxChange="handleCheckboxChange"
></q-edit-page-layout>
<script setup lang="ts">
import {
BUTTON_CUSTOM, // 自定义按钮_1
} from "@qqt-product/ui";
// 组件 ref
const layoutRef = ref<ComponentPublicInstance | null>(null);
const { pageData } = useRefInstanceHook(layoutRef);
const handleCheckboxChange: (btn: GlobalPageLayoutTypes.PageButtonWithGroupCode) => void) = (btn) => {
const { groupCode } = btn
const tableData = pageData[groupCode] as GlobalPageLayoutTypes.RecordString[]
// 已勾选行数据
const selectedRows = tableData.filter((n) => !!n._checked)
console.log('selectedRows :>> ', selectedRows)
// 已勾行索引值
const rowIdxs = selectedRows.map(n => n._rowIndex)
console.log('rowIdxs :>> ', rowIdxs)
}
const options = reactive<Partial<GlobalPageLayoutTypes.EditPageLayoutProps>>({
localConfig: {
// 页面按钮
pageButtons: [
{
...BUTTON_CUSTOM,
emit: true,
emitKey: "_checkboxChange", // 自定义事件名
},
],
},
});
</script>
表行数据添加弹窗选择
TIP
许多模块各表行数据需要通过调用接口获取
🙌 🌰 采购协同 > 寻源协同 > 价格申请
vue
<!-- 自定义事件监听 -->
<q-edit-page-layout
ref="layoutRef"
@itemListAdd="handleItemListAdd"
@priceAdd="handlePriceAdd"
></q-edit-page-layout>
<!-- 弹窗组件 -->
<q-field-select-modal ref="fieldSelectModal" @ok="ok"></q-field-select-modal>
<script setup lang="ts">
// 按钮常量引用
import { BUTTON_CUSTOM } from "@qqt-product/ui";
// 组件 ref
const layoutRef = ref<ComponentPublicInstance | null>(null);
// 弹窗选择自定义方法配置 hook
import useFieldSelectModalHook from "./hook/use-field-select-modal-hook";
const { fieldSelectModal, handleItemListAdd, handlePriceAdd, ok } =
useFieldSelectModalHook(layoutRef);
// 配置
const options = reactive<Partial<GlobalPageLayoutTypes.EditPageLayoutProps>>({
// ...
// 本地页面数据配置
localConfig: {
groups: [
{
groupName: "价格申请行",
groupCode: "informationRecordsRequestItemList",
groupType: "item",
sortOrder: "2",
buttons: [
{
...BUTTON_CUSTOM,
title: srmI18n("i18n_alert_iFumdWF_af48dd34", "选择价格主数据"),
emit: true, // 抛出事件
emitKey: "priceAdd", // 自定义事件名称
},
{
...BUTTON_CUSTOM,
title: srmI18n("i18n_alert_iFSL_42fb1450", "选择物料"),
emit: true,
emitKey: "itemListAdd",
},
],
},
],
},
// ...
});
</script>
ts
import { ref, watchEffect } from "vue";
import type { Ref, ComponentPublicInstance } from "vue";
import type { GlobalPageLayoutTypes } from "@qqt-product/ui";
import { srmI18n } from "@/utils/srmI18n";
import { message as Message } from "ant-design-vue";
export default function useFieldSelectModal(
layoutRef: Ref<ComponentPublicInstance | null>
) {
const fieldSelectModal = ref();
const handleItemListAdd: Ref<
((btn: GlobalPageLayoutTypes.PageButtonWithGroupCode) => void) | null
> = ref(null);
const handlePriceAdd: Ref<
((btn: GlobalPageLayoutTypes.PageButtonWithGroupCode) => void) | null
> = ref(null);
const ok: Ref<((data: GlobalPageLayoutTypes.RecordString[]) => void) | null> =
ref(null);
const cache: Ref<GlobalPageLayoutTypes.PageButtonWithGroupCode | null> =
ref(null);
watchEffect(() => {
if (layoutRef.value) {
const instance = layoutRef.value.instance;
const { pageData, defaultValues } = instance;
// 选择物料
handleItemListAdd.value = (btn) => {
console.log("click handleItemListAdd :>> ", btn);
// 缓存当前按钮配置
cache.value = btn;
const url = "/material/purchaseMaterialHead/list-head-or-item";
const columns = [
{
field: "materialNumber",
title: srmI18n("i18n_title_materialCode", "物料编码"),
width: 150,
},
{
field: "materialName",
title: srmI18n("i18n_field_materialName", "物料名称"),
width: 150,
},
{
field: "materialDesc",
title: srmI18n("i18n_field_materialDesc", "物料描述"),
width: 150,
},
{
field: "materialSpec",
title: srmI18n("i18n_title_materialSpec", "物料规格"),
width: 150,
},
{
field: "cateCode",
title: srmI18n("i18n_title_classificationCode", "分类编码"),
width: 150,
},
{
field: "cateName",
title: srmI18n("i18n_title_classificationName", "分类名称"),
width: 150,
},
{
field: "materialGroup",
title: srmI18n("i18n_field_materialGroup", "物料组"),
width: 150,
},
{
field: "materialGroup_dictText",
title: srmI18n("i18n_field_materialGroupName", "物料组名称"),
width: 150,
},
{
field: "deliveryArrange_dictText",
title: srmI18n("i18n_field_dSpA_43933cef", "送货安排"),
width: 150,
},
{
field: "checkQuality_dictText",
title: srmI18n("i18n_field_qualityTest", "是否质检"),
width: 150,
},
{
field: "purchaseUnit_dictText",
title: srmI18n("i18n_field_purchaseUnit", "采购单位"),
width: 150,
},
{
field: "minOrderQuantity",
title: srmI18n("i18n_field_minOrderQuantity", "最小交货数量"),
width: 150,
},
{
field: "factory_dictText",
title: srmI18n("i18n_field_RH_bb23d", "工厂"),
width: 150,
},
];
const params = { blocDel: "0", freeze: "0", filterPurchaseType: "1" };
const $fieldSelectModal = fieldSelectModal.value;
if ($fieldSelectModal) {
$fieldSelectModal.open({ url, params, columns });
}
};
// 价格主数据
handlePriceAdd.value = (btn) => {
console.log("click handleItemListAdd :>> ", btn);
if (pageData.needSaleConfirm === "1" && !pageData.toElsAccount) {
Message.error("请选择对方ELS账号");
return;
}
// 缓存当前按钮配置
cache.value = btn;
const url = "/price/purchaseInformationRecords/itemList";
const columns = [
{
field: "templateNumber",
title: srmI18n("i18n_title_templateNumber", "模板编号"),
width: 150,
align: "center",
},
{
field: "infoRecordNumber",
title: srmI18n("i18n_field_infoRecordNumber", "价格记录号"),
width: 150,
align: "center",
},
{
field: "recordType_dictText",
title: srmI18n("i18n_field_recordType", "价格记录类型"),
width: 150,
align: "center",
},
{
field: "recordStatus_dictText",
title: srmI18n("i18n_baseForm01bf_ruleStatus", "状态"),
width: 150,
align: "center",
},
{
field: "auditStatus_dictText",
title: srmI18n("i18n_baseForm925b_auditStatus", "审批状态"),
width: 150,
align: "center",
},
{
field: "toElsAccount",
title: srmI18n("i18n_title_supplierElsNumber", "供应商ELS账号"),
width: 150,
align: "center",
},
{
field: "supplierName",
title: srmI18n("i18n_massProdHead5f0c_supplierName", "供应商名称"),
width: 150,
align: "center",
},
{
field: "company_dictText",
title: srmI18n("i18n_massProdHeadc8f5_companyCode", "公司"),
width: 150,
align: "center",
},
{
field: "purchaseOrg_dictText",
title: srmI18n("i18n_massProdHead1725_purchaseOrgCode", "采购组织"),
width: 150,
align: "center",
},
{
field: "purchaseGroup_dictText",
title: srmI18n("i18n_massProdHead1515_purchaseGroupCode", "采购组"),
width: 150,
align: "center",
},
{
field: "factory_dictText",
title: srmI18n("i18n_massProdHead3e34_factoryCode", "工厂"),
width: 150,
align: "center",
},
{
field: "materialNumber",
title: srmI18n("i18n_title_materialNumber", "物料编码"),
width: 150,
align: "center",
},
{
field: "materialName",
title: srmI18n("i18n_title_materialName", "物料名称"),
width: 150,
align: "center",
},
{
field: "materialSpec",
title: srmI18n("i18n_title_materialSpec", "物料规格"),
width: 150,
align: "center",
},
{
field: "price",
title: srmI18n("i18n_title_unitPriceIncludTax", "含税单价"),
width: 150,
align: "center",
},
{
field: "netPrice",
title: srmI18n("i18n_title_unitPriceExcludingTax", "未税单价"),
width: 150,
align: "center",
},
{
field: "ladderPriceJson",
title: srmI18n("i18n_field_ladderPriceJson", "阶梯价格"),
width: 150,
align: "center",
slots: { default: "ladder_price_json_render" },
},
{
field: "priceType",
title: srmI18n("i18n_field_priceType", "价格类型"),
width: 150,
align: "center",
},
{
field: "taxCode",
title: srmI18n("i18n_title_enterTaxCode", "税码"),
width: 150,
align: "center",
},
{
field: "taxRate",
title: srmI18n("i18n_title_taxRate", "税率"),
width: 150,
align: "center",
},
{
field: "currencyCode_dictText",
title: srmI18n("i18n_field_currency", "币别"),
width: 150,
align: "center",
},
{
field: "effectiveDate",
title: srmI18n(
"i18n_field_umtHjXAKBA_f67d10f7",
"价格记录有效起始日期"
),
width: 150,
align: "center",
},
{
field: "expiryDate",
title: srmI18n(
"i18n_field_umtHjXyRBA_e310233b",
"价格记录有效截止日期"
),
width: 150,
align: "center",
},
{
field: "purchaseUnit_dictText",
title: srmI18n("i18n_field_purchaseUnit", "采购单位"),
width: 150,
align: "center",
},
{
field: "sourceType_dictText",
title: srmI18n("i18n_field_wjAc_30ae977b", "来源类型"),
width: 150,
align: "center",
},
{
field: "payTermsCode",
title: srmI18n("i18n_field_paymentCondition", "付款条件"),
width: 150,
align: "center",
},
];
const params = { toElsAccount: pageData.toElsAccount };
const $fieldSelectModal = fieldSelectModal.value;
if ($fieldSelectModal) {
$fieldSelectModal.open({ url, params, columns });
}
};
ok.value = (data) => {
if (!data.length) {
return;
}
const emitKey = cache.value?.emitKey;
if (!emitKey) {
return;
}
// 价格申请行
// 物料选择弹窗赋值
if (emitKey === "itemListAdd") {
const inserData = data.map((n) => {
const { id, ...others } = n;
return {
...others,
materialId: id,
requestOptType: "0",
changeToPrice: "0",
toElsAccount: pageData.toElsAccount,
supplierCode: pageData.supplierCode,
supplierName: pageData.supplierName,
};
});
const groupCode = cache.value?.groupCode;
if (!groupCode) {
return;
}
const row = defaultValues[
groupCode
] as GlobalPageLayoutTypes.RecordString;
const tableData = pageData[
groupCode
] as GlobalPageLayoutTypes.RecordString[];
inserData.forEach((n) => {
tableData.push({ ...row, _row_id: uniqueId("_row_id_"), ...n });
});
}
// 价格申请行
// 选择价格主数据
if (emitKey === "priceAdd") {
const inserData = data.map((n) => {
const { id, ...others } = n;
return {
...others,
materialId: id,
infoRecordId: id,
requestOptType: "1",
changeToPrice: "0",
};
});
const groupCode = cache.value?.groupCode;
if (!groupCode) {
return;
}
const row = defaultValues[
groupCode
] as GlobalPageLayoutTypes.RecordString;
const tableData = pageData[
groupCode
] as GlobalPageLayoutTypes.RecordString[];
inserData.forEach((n) => {
tableData.push({ ...row, _row_id: uniqueId("_row_id_"), ...n });
});
}
};
}
});
return {
fieldSelectModal,
handleItemListAdd,
handlePriceAdd,
ok,
};
}
表头、表行分组显隐配置
vue
<script setup lang="ts">
// 配置
const options = reactive<Partial<GlobalPageLayoutTypes.EditPageLayoutProps>>({
localConfig: {
groups: [
{
groupName: "价格申请行",
groupCode: "informationRecordsRequestItemList", // 本地分组与业务模板分组会合并,以groupCode为唯一key
groupType: "item",
sortOrder: "2",
show: false, // 分组显隐
},
],
},
});
</script>
vue
<template>
<div class="edit-page">
<q-edit-page-layout
ref="layoutRef"
@toggleGroup="handleToggleGroup"
></q-edit-page-layout>
</div>
</template>
<script setup lang="ts">
import { useRefInstanceHook } from "@qqt-product/ui";
// 组件 ref
const layoutRef = ref<ComponentPublicInstance | null>(null);
const { layoutConfig } = useRefInstanceHook(layoutRef);
// 测试设置分组显隐
const handleToggleGroup = () => {
layoutConfig.value.groups.forEach((group) => {
// 供应商信息
if (group.groupCode === "purchaseEbiddingSupplierList") {
group.show = !group.show;
}
});
};
</script>
usePromiseStepHook 可解构的步骤方法
TIP
UI 库 提供与模板相配套的步骤方法,满足各种业务场景需求
vue
<script setup lang="ts">
import { usePromiseStepHook, useRefInstanceHook } from "@qqt-product/ui";
// 组件 ref
const layoutRef = ref<ComponentPublicInstance | null>(null);
const { pageData, layoutConfig, defaultValues } = useRefInstanceHook(layoutRef);
const {
localUid, // 组件唯一标识 uid
customLoading, // loading 只读状态
stepTriggerValidate, // 触发模板校验
composeSave, // 组合式保存
composePublish, // 组合式发布
composeSubmit, // 组合式提交审批
composeConfirm, // 组合式确认
composeReject, // 组合式拒绝
} = usePromiseStepHook({ pageData, layoutConfig, defaultValues, options });
</script>
撤销审批
INFO
撤销审批功能一般配置在详情页面,且成功后返回至模块的编辑页面, 通常与 查看流程 按钮配套使用
vue
<template>
<div class="detail-page">
<q-detail-page-layout @back="back"></q-detail-page-layout>
</div>
</template>
<script setup lang="ts">
// 按钮常量引用
import { BUTTON_CANCELAUDIT, BUTTON_FLOW_VIEW } from "@qqt-product/ui";
// 配置
const options = reactive<Partial<GlobalPageLayoutTypes.EditPageLayoutProps>>({
pageButtons: [
{
...BUTTON_CANCELAUDIT,
args: {
url: "/a1bpmn/audit/api/cancel",
auditSubject:
"评标模板审批:" +
currentRow.evaluationNumber +
" " +
currentRow.evaluationName || "",
},
},
BUTTON_FLOW_VIEW,
],
});
const back = (btn: GlobalPageLayoutTypes.PageButton) => {
// 撤销审批跳转至编辑页面
if (btn.key === BUTTON_CANCELAUDIT.key) {
router.replace({
path: `/srm/enquiry/enquiryHead/edit/${currentRow.id}`,
query: { t: +new Date() },
});
}
};
</script>
vue
<template>
<div class="detail-page">
<q-detail-page-layout></q-detail-page-layout>
</div>
</template>
<script setup lang="ts">
// 按钮常量引用
import { BUTTON_CANCELAUDIT, BUTTON_FLOW_VIEW } from "@qqt-product/ui";
// 配置
const options = reactive<Partial<GlobalPageLayoutTypes.EditPageLayoutProps>>({
pageButtons: [
{
...BUTTON_CANCELAUDIT,
args: (pageData) => {
const flowId =
pageData.rectificationStatus === "0" ||
pageData.rectificationStatus === "3"
? pageData.preFlowCode
: pageData.resultFlowCode;
const businessType =
pageData.rectificationStatus === "0" ||
pageData.rectificationStatus === "3"
? "supplierRetification"
: "supplierRetificationResult";
return {
url: "/a1bpmn/audit/api/cancel",
rootProcessInstanceId: flowId,
businessType,
auditSubject: "整改单号:" + currentRow.reportCode || "",
};
},
},
BUTTON_FLOW_VIEW,
],
});
</script>
自定义保存、自定义发布、自定义提交审批(同步校验)
TIP
现实情况中,各模块的业务需求可能比较复杂,需要灵活组合使用模板预设的方法,满足业务需求 如:自定义保存,需要在保存前,校验页面数据,校验通过后才允许保存 如:发布、或提交审批前,需要对页面某些字段做特殊校验,同步或异步操作,满足条件后才允许发布或提交审批
vue
<a-spin :spinning="customLoading">
<q-edit-page-layout
ref="layoutRef"
@customSave="handleCustomSave"
@customPublish="handleCustomPublish"
@customSubmit="handleCustomSubmit"
@validate-success="handleValidateSuccess"
></q-edit-page-layout>
</a-spin>
<script setup lang="ts">
// 配置
const options = reactive<Partial<GlobalPageLayoutTypes.EditPageLayoutProps>>({
// ...
pageButtons: [
{
...BUTTON_SAVE,
title: "保存",
emit: true,
emitKey: "customSave",
args: {
addUrl: "/archiver/archiverConfigHead/saveConfig",
url: "/archiver/archiverConfigHead/saveConfig",
},
},
{
...BUTTON_PUBLISH,
title: "custom Publish",
emit: true,
emitKey: "customPublish",
args: {
url: "/price/purchaseInformationRecordsRequest/publish",
},
},
{
...BUTTON_SUBMIT,
handleBefore: handleBeforeSubmit, // 调用 composeSubmit 方法后, 使有组合式提交审批功能, handleBefore 步骤方法会被调用
title: "custom Submit",
emit: true,
emitKey: "customSubmit",
args: {
url: "/a1bpmn/audit/api/submit",
},
},
],
});
// 组件 ref
const layoutRef = ref<ComponentPublicInstance | null>(null);
const { pageData, layoutConfig, defaultValues } = useRefInstanceHook(layoutRef);
const { customLoading, stepTriggerValidate, composePublish, composeSubmit } =
usePromiseStepHook({ pageData, layoutConfig, defaultValues, options });
// 自定义保存(页面校验 + 保存)
const handleCustomSave = () => {
// 页面校验
stepTriggerValidate();
};
// 页面校验成功后执行下一步回调步骤
const handleValidateSuccess = (button: GlobalPageLayoutTypes.PageButton) => {
if (button.key === BUTTON_SAVE.key) {
composeSave();
}
};
// 执行自定义校验
const customCheck: (pageData: GlobalPageLayoutTypes.RecordString) => boolean = (
pageData
) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const informationRecordsRequestItemList =
pageData.informationRecordsRequestItemList as any[];
if (!informationRecordsRequestItemList.length) {
Message.error(
srmI18n("i18n_alert_VWSuumUVcWF_1a6ec7eb", "请先添加价格申请行数据")
);
return false;
}
return true;
};
// 自定义发布
const handleCustomPublish = () => {
if (!customCheck(pageData.value)) {
return;
}
composePublish();
};
// 自定义提交审批
const handleCustomSubmit = () => {
if (!customCheck(pageData.value)) {
return;
}
composeSubmit();
};
</script>
back 事件监听组合式方法完成后回调
WARNING
模板提供的保存、发布、提交审批是多个异步方法的组合执行,可以通监听 back 回调事件(参数为当前按钮)执行后继的业务操作; 注:页面回退是监听 pageBack 事件, 两者不一样
vue
<template>
<div class="edit-page">
<q-edit-page-layout
ref="layoutRef"
@pageBack="handlePageBack"
@back="back"
></q-edit-page-layout>
</div>
</template>
<script setup lang="ts">
const backToList: (flag?: boolean) => void = (flag = false) => {
const path = "/srm/trial/trialProductionHead/list";
const query = flag ? { t: +new Date() } : {};
router.push({ path, query });
};
const handlePageBack = () => {
backToList(isCreate.value);
};
// 统一监听 back 事件处理具体业务逻辑
// 退出或自定义路由跳转
const back = (btn: GlobalPageLayoutTypes.PageButton) => {
beforeRouteLeaveStatus.value = true;
if (btn.key === BUTTON_SUBMIT.key) {
// 提交审批完成后执行业务逻辑
backToList(true);
}
};
</script>
审批通过、退回(及其他审批功能)操作成功后回调事件监听
WARNING
审批详情页, 通过监听 flowApprovalCallback
事件处理各审批功能成功后的业务操作,e.g 回写 OA 操作。
vue
<template>
<div class="detail-page">
<q-detail-page-layout
ref="layoutRef"
@flowApprovalCallback="handleFlowApprovalCallback"
></q-detail-page-layout>
</div>
</template>
<script setup lang="ts">
// ui组件库 types
import type { GlobalPageLayoutTypes } from "@qqt-product/ui";
const handleFlowApprovalCallback = ({
btn,
params,
}: GlobalPageLayoutTypes.FlowApprovalCallbackPayload) => {
// 缓存的审批按钮配置
console.log("btn :>> ", btn);
// 缓存的审批接口参数
console.log("params :>> ", params);
};
</script>
多分组表头配置
🙌 🌰 采购协同 > 质量协同 > 8D 改进
vue
<script setup lang="ts">
// 配置
const options = reactive<Partial<GlobalPageLayoutTypes.EditPageLayoutProps>>({
// 本地页面数据配置
localConfig: {
groups: [
{
groupName: "D2:问题界定",
groupNameI18nKey: "i18n_field_WWWQDyI_b3c4cc24",
groupCode: "eightDisciplinesTwo",
groupType: "head",
sortOrder: "3",
enableTile: "0", // 表头允许嵌套
},
{
groupName: "D4:原因分析",
groupNameI18nKey: "i18n_field_WWWjWzH_980f28d5",
groupCode: "eightDisciplinesFour",
groupType: "head",
sortOrder: "5",
enableTile: "0", // 表头允许嵌套
},
{
groupName: "D7:预防再发生",
groupNameI18nKey: "i18n_field_WWWUCKhb_f026e026",
groupCode: "eightDisciplinesSeven",
groupType: "head",
sortOrder: "8",
enableTile: "0", // 表头允许嵌套
},
{
groupName: "D8:结案评价",
groupNameI18nKey: "i18n_field_WWWypUu_b2ce606e",
groupCode: "eightDisciplinesEight",
groupType: "head",
sortOrder: "9",
enableTile: "0", // 表头允许嵌套
},
],
},
});
</script>
js
// ...
// 分组配置 enableTile 为 "0"时,多个表单以嵌套的格式获取和提交
// {
// "eightDisciplinesTwo": {
// // ...
// "id": "1720012953689456642"
// // ...
// },
// "eightDisciplinesFour": {
// // ...
// "id": "1720012953865617409",
// // ...
// },
// "eightDisciplinesSeven": {
// // ...
// "id": "1720012954029195266",
// // ...
// },
// "eightDisciplinesEight": {
// // ...
// "id": "1720012954188578817",
// // ...
// },
// // ...
// "id": "1720012952280170498",
// // ...
// }