From 84bc6d137d5f9f5b220233377f8869c3790b946b Mon Sep 17 00:00:00 2001 From: Timi Date: Wed, 19 Aug 2026 15:46:03 +0800 Subject: [PATCH] fix point rule unit --- src/pages/point/PointLedgerDetail.vue | 10 +- src/pages/point/PointRuleEdit.vue | 97 +++++++++++-------- .../point/component/PointRuleTierEditor.vue | 96 ++++++++---------- src/utils/eventValue.ts | 23 ++++- src/utils/pointRule.ts | 27 +++++- 5 files changed, 150 insertions(+), 103 deletions(-) diff --git a/src/pages/point/PointLedgerDetail.vue b/src/pages/point/PointLedgerDetail.vue index be66170..6b5919e 100644 --- a/src/pages/point/PointLedgerDetail.vue +++ b/src/pages/point/PointLedgerDetail.vue @@ -296,7 +296,15 @@ function getRuleConditionText(rule: PointRule): string { textList.push(`来源事件:${sourceEvent.value?.name || sourceEvent.value?.id || condition.sourceEventId}`); } if (condition.tiers?.length) { - textList.push(condition.tiers.map(tier => `${tier.min}~${tier.max ?? "以上"}:${0 < tier.pointValue ? "发放" : "消耗"} ${Math.abs(tier.pointValue)} 分`).join(",")); + const tierSourceEvent = sourceEvent.value || (condition.sourceEventId === triggerEvent.value?.id ? triggerEvent.value : undefined); + textList.push(condition.tiers.map(tier => { + const minText = formatEventNumberValue(tierSourceEvent, tier.min); + const maxText = tier.max === undefined || tier.max === null ? "以上" : formatEventNumberValue(tierSourceEvent, tier.max); + const pointText = tier.pointValue === 0 + ? "不产生积分流水" + : `${0 < tier.pointValue ? "发放" : "消耗"} ${Math.abs(tier.pointValue)} 分`; + return `${minText}~${maxText}:${pointText}`; + }).join(",")); } return textList.join(";") || "无额外条件"; } diff --git a/src/pages/point/PointRuleEdit.vue b/src/pages/point/PointRuleEdit.vue index e1c3618..21fd415 100644 --- a/src/pages/point/PointRuleEdit.vue +++ b/src/pages/point/PointRuleEdit.vue @@ -65,7 +65,7 @@ - + @@ -91,9 +91,8 @@ -
+
删除 - - 保存 -
@@ -208,11 +198,13 @@ import EventAPI from "@/api/EventAPI"; import PointAPI from "@/api/PointAPI"; import PointRuleTierEditor from "@/pages/point/component/PointRuleTierEditor.vue"; import { useCurrentStoreStore } from "@/store/currentStoreStore"; +import { useNavBarStore } from "@/store/navBarStore"; import type { EventItem } from "@/types/EventItem"; import type { PointAction, PointRule, PointRuleMode, PointRuleStatus, PointRuleTier, PointRuleTierForm, PointRuleTriggerType } from "@/types/Point"; +import { getEventValueUnitText } from "@/utils/eventValue"; import { getErrorMessage } from "@/utils/error"; import { gaoRoleCodes, permissionCodes } from "@/utils/permission"; -import { getTierMinValue, isLastTier, resolveNonNegativeInteger } from "@/utils/pointRule"; +import { formatTierBoundaryValue, getTierMinValue, isLastTier, resolveNonNegativeInteger, resolveTierBoundaryValue } from "@/utils/pointRule"; type PickerValue = string | number; @@ -222,6 +214,7 @@ defineOptions({ const route = useRoute(); const router = useRouter(); +const navBarStore = useNavBarStore(); const userStore = useUserStore(); const currentStoreStore = useCurrentStoreStore(); const routeId = computed(() => String(route.params.id || "")); @@ -282,20 +275,24 @@ const eventPickerColumns = computed(() => [ const sourceEventPickerVisible = ref(false); const sourceEventPickerDraft = ref([]); const sourceEventList = computed(() => eventList.value.filter(item => item.valueType === "NUMBER" && item.id)); +const selectedSourceEvent = computed(() => sourceEventList.value.find(item => item.id === formModel.sourceEventId)); +const selectedSourceValueUnit = computed(() => selectedSourceEvent.value?.valueUnit || null); const sourceEventPickerColumns = computed(() => [ sourceEventList.value - .map(item => ({ label: item.name || "未命名事件", value: item.id || "" })) + .map(item => ({ label: `${item.name || "未命名事件"}(${getEventValueUnitText(item.valueUnit)})`, value: item.id || "" })) .filter(item => item.value) ]); -const selectedSourceEventName = computed(() => sourceEventList.value.find(item => item.id === formModel.sourceEventId)?.name || (formModel.sourceEventId ? `事件 ${formModel.sourceEventId}` : "请选择来源事件")); +const selectedSourceEventName = computed(() => selectedSourceEvent.value + ? `${selectedSourceEvent.value.name || "未命名事件"}(${getEventValueUnitText(selectedSourceEvent.value.valueUnit)})` + : formModel.sourceEventId ? `事件 ${formModel.sourceEventId}` : "请选择来源事件"); let tierKey = 0; -function createTierForm(tier?: PointRuleTier, isLast = false): PointRuleTierForm { +function createTierForm(tier?: PointRuleTier, isLast = false, valueUnit?: EventItem["valueUnit"]): PointRuleTierForm { const pointValue = tier?.pointValue; return { key: tierKey++, - maxText: !isLast && tier?.max !== undefined && tier.max !== null ? String(tier.max) : "", + maxText: !isLast && tier?.max !== undefined && tier.max !== null ? formatTierBoundaryValue(tier.max, valueUnit) : "", pointAction: pointValue !== undefined && pointValue < 0 ? "CONSUME" : "EARN", pointValueText: pointValue === undefined ? "0" : String(Math.abs(pointValue)) }; @@ -360,6 +357,12 @@ function openSourceEventPicker(): void { function confirmSourceEventPicker(value: Array): void { const sourceEventId = String(value[0] || ""); if (sourceEventId) { + const previousSourceEvent = sourceEventList.value.find(item => item.id === formModel.sourceEventId); + const nextSourceEvent = sourceEventList.value.find(item => item.id === sourceEventId); + if (formModel.sourceEventId && previousSourceEvent?.valueUnit !== nextSourceEvent?.valueUnit) { + formModel.tiers = [createTierForm(undefined, true, nextSourceEvent?.valueUnit)]; + Toast.warning("来源事件单位已变化,原分档已清空,请重新配置"); + } formModel.sourceEventId = sourceEventId; } sourceEventPickerVisible.value = false; @@ -382,8 +385,11 @@ async function loadEventList(): Promise { const options = isGlobalManager.value ? { storeId: formModel.storeId } : undefined; const result = await EventAPI.list({ index: 0, size: 1000 }, options); eventList.value = result.list || []; - if (formModel.eventId && !eventList.value.some(item => item.id === formModel.eventId)) { - eventList.value.push(await EventAPI.detail(formModel.eventId)); + const requiredEventIdList = [formModel.eventId, formModel.sourceEventId].filter((id, index, list) => id && list.indexOf(id) === index); + for (const eventId of requiredEventIdList) { + if (!eventList.value.some(item => item.id === eventId)) { + eventList.value.push(await EventAPI.detail(eventId)); + } } } @@ -571,6 +577,7 @@ function resolveTimestamp(value: string): number | undefined { function buildCondition(): PointRule["conditionJson"] { if (formModel.triggerType === "CUSTOMER_EVENT_VALUE_TIER") { + const valueUnit = selectedSourceValueUnit.value; return { sourceEventId: formModel.sourceEventId, sourceValueField: "numberValue", @@ -580,12 +587,13 @@ function buildCondition(): PointRule["conditionJson"] { unit: "YEAR" }, tiers: formModel.tiers.map((tier, index) => { + const min = getTierMinValue(formModel.tiers, index, valueUnit); const result: PointRuleTier = { - min: getTierMinValue(formModel.tiers, index) || 0, + min: min ?? 0, pointValue: (tier.pointAction === "CONSUME" ? -1 : 1) * (resolveNonNegativeInteger(tier.pointValueText) || 0) }; if (!isLastTier(formModel.tiers, index)) { - result.max = resolveNonNegativeInteger(tier.maxText); + result.max = resolveTierBoundaryValue(tier.maxText, valueUnit); } return result; }) @@ -628,10 +636,14 @@ function validateForm(): boolean { Toast.warning("分档规则必须使用分档积分模式"); return false; } - if (!sourceEventList.value.some(item => item.id === formModel.sourceEventId)) { + if (!selectedSourceEvent.value) { Toast.warning("请选择 NUMBER 类型的来源事件"); return false; } + if (selectedSourceValueUnit.value !== "CENT" && selectedSourceValueUnit.value !== "COUNT") { + Toast.warning("来源事件必须配置数值单位"); + return false; + } if (!resolvePositiveInteger(formModel.windowAmountText)) { Toast.warning("累计窗口必须是正整数"); return false; @@ -642,7 +654,7 @@ function validateForm(): boolean { } for (let i = 0; i < formModel.tiers.length; i++) { const tier = formModel.tiers[i]; - const min = getTierMinValue(formModel.tiers, i); + const min = getTierMinValue(formModel.tiers, i, selectedSourceValueUnit.value); const pointValue = resolveNonNegativeInteger(tier.pointValueText); if (min === undefined) { Toast.warning(`第 ${i + 1} 档的上一档最大值无效`); @@ -653,9 +665,11 @@ function validateForm(): boolean { return false; } if (!isLastTier(formModel.tiers, i)) { - const max = resolveNonNegativeInteger(tier.maxText); + const max = resolveTierBoundaryValue(tier.maxText, selectedSourceValueUnit.value); if (max === undefined || max < min) { - Toast.warning(`第 ${i + 1} 档最大值必须是不小于最小值的整数`); + Toast.warning(selectedSourceValueUnit.value === "CENT" + ? `第 ${i + 1} 档最大值必须是不小于最小值且最多两位小数` + : `第 ${i + 1} 档最大值必须是不小于最小值的整数`); return false; } } @@ -786,31 +800,43 @@ async function fillForm(): Promise { maxText: current.conditionJson?.max === undefined ? "" : String(Math.abs(current.conditionJson.max)), sourceEventId: current.conditionJson?.sourceEventId || "", windowAmountText: current.conditionJson?.window?.amount === undefined ? "1" : String(current.conditionJson.window.amount), - tiers: current.conditionJson?.tiers?.length - ? current.conditionJson.tiers.map((tier, index, list) => createTierForm(tier, index === list.length - 1)) - : [createTierForm(undefined, true)], + tiers: [], beginAtText: current.beginAt ? Time.toDateTime(current.beginAt) : "", endAtText: current.endAt ? Time.toDateTime(current.endAt) : "", priorityText: String(current.priority ?? 0), exclusive: current.exclusive ?? false, remark: current.remark || "" }); + await loadEventList(); + const valueUnit = selectedSourceValueUnit.value; + formModel.tiers = current.conditionJson?.tiers?.length + ? current.conditionJson.tiers.map((tier, index, list) => createTierForm(tier, index === list.length - 1, valueUnit)) + : [createTierForm(undefined, true, valueUnit)]; + return; } await loadEventList(); + if (!formModel.tiers.length) { + formModel.tiers.push(createTierForm(undefined, true, selectedSourceValueUnit.value)); + } } onMounted(async () => { + if (userStore.hasPermissions(savePermissionCode.value)) { + navBarStore.setRightText("保存", "var(--td-brand-color)", saveRule); + } try { await fillForm(); } catch (error) { Toast.error(getErrorMessage(error, "积分规则加载失败")); } }); + +onUnmounted(() => navBarStore.clearRight());