support consume point
This commit is contained in:
@@ -171,7 +171,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { DialogPlugin, Toast } from "tdesign-mobile-vue";
|
||||
import { PassTimeLabel, Text, Time, useUserStore } from "timi-web";
|
||||
import { PassTimeLabel, Text, useUserStore } from "timi-web";
|
||||
import CustomerAPI from "@/api/CustomerAPI";
|
||||
import PointAPI from "@/api/PointAPI";
|
||||
import TagAPI from "@/api/TagAPI";
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
<t-cell-group v-if="record" title="登记信息" theme="card">
|
||||
<t-cell title="登记事件" :note="record.event?.name || '未知事件'" />
|
||||
<t-cell v-if="record.numberValue !== undefined && record.numberValue !== null" title="事件数值" :note="getNumberValueText(record)" />
|
||||
<t-cell v-if="record.pointValue !== undefined && record.pointValue !== null" title="提交积分值" :note="`${record.pointValue} 分`" />
|
||||
<t-cell v-if="record.earnedPointValue !== undefined && record.earnedPointValue !== null" title="本次获得积分" :note="`${record.earnedPointValue} 分`" />
|
||||
<t-cell v-if="record.pointValue !== undefined && record.pointValue !== null" title="提交积分值" :note="getPointValueText(record.pointValue)" />
|
||||
<t-cell v-if="record.earnedPointValue !== undefined && record.earnedPointValue !== null" title="本次积分变化" :note="getPointValueText(record.earnedPointValue)" />
|
||||
<t-cell title="登记时间" :note="record.registeredAt ? Time.toPassedDateTime(record.registeredAt) : '-'" />
|
||||
<t-cell title="登记人" :note="Text.display(record.operator?.nick || record.operator?.name)" />
|
||||
<t-cell title="客户编码" :note="Text.display(record.customer?.code)" />
|
||||
@@ -63,6 +63,13 @@ function getNumberValueText(value: EventRecord): string {
|
||||
return formatEventNumberValue(value.event, value.numberValue);
|
||||
}
|
||||
|
||||
function getPointValueText(value?: number | null): string {
|
||||
if (value === undefined || value === null) {
|
||||
return "-";
|
||||
}
|
||||
return `${0 < value ? "获得" : "消耗"} ${Math.abs(value)} 分`;
|
||||
}
|
||||
|
||||
// ---------- 页面数据 ----------
|
||||
|
||||
async function loadRecord(): Promise<void> {
|
||||
|
||||
@@ -68,7 +68,10 @@
|
||||
</t-form-item>
|
||||
</div>
|
||||
<div v-if="isEditMode" class="edit-point-tip">已产生积分的登记记录不会因修改客户、事件或时间而重算;如需修改这些事实,请删除后重新创建。</div>
|
||||
<t-form-item v-if="showSourceValueInput" label="自定义积分" required-mark>
|
||||
<t-form-item v-if="showSourceValueInput" label="积分动作">
|
||||
<span class="picker-text" v-text="getPointActionText(sourceValueAction)" />
|
||||
</t-form-item>
|
||||
<t-form-item v-if="showSourceValueInput" label="积分数量" required-mark>
|
||||
<t-input
|
||||
v-model.trim="pointValueText"
|
||||
type="number"
|
||||
@@ -198,10 +201,11 @@ import { useSettingStore } from "@/store/settingStore";
|
||||
import type { Customer } from "@/types/Customer";
|
||||
import type { EventItem } from "@/types/EventItem";
|
||||
import type { EventRecord } from "@/types/EventRecord";
|
||||
import type { PointAction } from "@/types/Point";
|
||||
import { getEventRecordPhotoURL } from "@/utils/eventRecord";
|
||||
import { getEventNumberValuePlaceholder, resolveEventNumberValue } from "@/utils/eventValue";
|
||||
import { permissionCodes } from "@/utils/permission";
|
||||
import { loadSourceValueRange } from "@/utils/point";
|
||||
import { loadSourceValueConfig, type SourceValueConfig } from "@/utils/point";
|
||||
import { compressUploadImageList } from "@/utils/imageCompression";
|
||||
|
||||
defineOptions({
|
||||
@@ -456,40 +460,46 @@ function validateNumberValueList(): boolean {
|
||||
// ---------- 积分规则联动 ----------
|
||||
|
||||
const pointValueText = ref("");
|
||||
const sourceValueRange = ref<[number, number]>();
|
||||
const sourceValueConfig = ref<SourceValueConfig>();
|
||||
const sourceValueAction = ref<PointAction>("EARN");
|
||||
let sourceValueLoadToken = 0;
|
||||
|
||||
const showSourceValueInput = computed(() => (
|
||||
!isEditMode.value
|
||||
&& userStore.hasPermissions(permissionCodes.point.sourceValue)
|
||||
&& !!sourceValueRange.value
|
||||
&& !!sourceValueConfig.value
|
||||
));
|
||||
const sourceValuePlaceholder = computed(() => {
|
||||
const range = sourceValueRange.value;
|
||||
return range ? `请输入 ${range[0]}-${range[1]} 的正整数` : "请输入积分";
|
||||
const range = sourceValueConfig.value?.range;
|
||||
return range ? `请输入 ${range[0]}-${range[1]} 的数量` : "请输入数量";
|
||||
});
|
||||
|
||||
async function loadSourceValueRule(): Promise<void> {
|
||||
const token = sourceValueLoadToken + 1;
|
||||
sourceValueLoadToken = token;
|
||||
sourceValueRange.value = undefined;
|
||||
sourceValueConfig.value = undefined;
|
||||
if (isEditMode.value || !userStore.hasPermissions(permissionCodes.point.sourceValue) || !selectedEventIdList.value.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
sourceValueRange.value = await loadSourceValueRange(selectedEventIdList.value);
|
||||
sourceValueConfig.value = await loadSourceValueConfig(selectedEventIdList.value);
|
||||
sourceValueAction.value = sourceValueConfig.value?.action || "EARN";
|
||||
if (token !== sourceValueLoadToken) {
|
||||
return;
|
||||
}
|
||||
} catch {
|
||||
sourceValueRange.value = undefined;
|
||||
sourceValueConfig.value = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function resolveSourceValue(): number | undefined {
|
||||
const value = Number(pointValueText.value);
|
||||
return Number.isSafeInteger(value) && 0 < value ? value : undefined;
|
||||
return Number.isSafeInteger(value) && 0 < value ? sourceValueAction.value === "CONSUME" ? -value : value : undefined;
|
||||
}
|
||||
|
||||
function getPointActionText(action: PointAction): string {
|
||||
return action === "CONSUME" ? "消耗积分" : "发放积分";
|
||||
}
|
||||
|
||||
// ---------- 照片 ----------
|
||||
@@ -641,9 +651,9 @@ function validateForm(): boolean {
|
||||
}
|
||||
if (showSourceValueInput.value) {
|
||||
const pointValue = resolveSourceValue();
|
||||
const range = sourceValueRange.value;
|
||||
if (!pointValue || !range || pointValue < range[0] || range[1] < pointValue) {
|
||||
Toast.warning(`自定义积分必须是 ${range?.[0] || 1}-${range?.[1] || 0} 的正整数`);
|
||||
const range = sourceValueConfig.value?.range;
|
||||
if (!pointValue || !range || Math.abs(pointValue) < range[0] || range[1] < Math.abs(pointValue)) {
|
||||
Toast.warning(`积分数量必须是 ${range?.[0] || 1}-${range?.[1] || 0} 的正整数`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@
|
||||
</t-form-item>
|
||||
</div>
|
||||
<div v-if="showSourceValueInput" class="source-value-panel">
|
||||
<div class="source-value-title">自定义积分</div>
|
||||
<div class="source-value-title" v-text="sourceValueAction === 'CONSUME' ? '消耗积分' : '发放积分'" />
|
||||
<t-input
|
||||
v-model.trim="pointValueText"
|
||||
type="number"
|
||||
@@ -146,10 +146,11 @@ import PhotoScroller from "@/components/PhotoScroller.vue";
|
||||
import { useSettingStore } from "@/store/settingStore";
|
||||
import type { Customer } from "@/types/Customer";
|
||||
import type { EventItem } from "@/types/EventItem";
|
||||
import type { PointAction } from "@/types/Point";
|
||||
import { getCustomerPhotoURL } from "@/utils/customer";
|
||||
import { getEventNumberValuePlaceholder, resolveEventNumberValue } from "@/utils/eventValue";
|
||||
import { permissionCodes } from "@/utils/permission";
|
||||
import { loadSourceValueRange } from "@/utils/point";
|
||||
import { loadSourceValueConfig, type SourceValueConfig } from "@/utils/point";
|
||||
import { compressUploadImage, compressUploadImageList } from "@/utils/imageCompression";
|
||||
import EventSelector from "@/components/EventSelector.vue";
|
||||
import EventRecordAPI from "@/api/EventRecordAPI.ts";
|
||||
@@ -171,31 +172,33 @@ const selectedNumberEventList = computed(() => selectedEventIdList.value
|
||||
.map(eventId => eventList.value.find(item => item.id === eventId))
|
||||
.filter((item): item is EventItem => item?.valueType === "NUMBER"));
|
||||
const pointValueText = ref("");
|
||||
const sourceValueRange = ref<[number, number]>();
|
||||
const sourceValueConfig = ref<SourceValueConfig>();
|
||||
const sourceValueAction = ref<PointAction>("EARN");
|
||||
const showSourceValueInput = computed(() => (
|
||||
userStore.hasPermissions(permissionCodes.point.sourceValue)
|
||||
&& !!sourceValueRange.value
|
||||
&& !!sourceValueConfig.value
|
||||
));
|
||||
const sourceValuePlaceholder = computed(() => {
|
||||
const range = sourceValueRange.value;
|
||||
return range ? `请输入 ${range[0]}-${range[1]} 的正整数` : "请输入积分";
|
||||
const range = sourceValueConfig.value?.range;
|
||||
return range ? `请输入 ${range[0]}-${range[1]} 的数量` : "请输入数量";
|
||||
});
|
||||
|
||||
async function loadSourceValueRule(): Promise<void> {
|
||||
if (!selectedEventIdList.value.length || !userStore.hasPermissions(permissionCodes.point.sourceValue)) {
|
||||
sourceValueRange.value = undefined;
|
||||
sourceValueConfig.value = undefined;
|
||||
return;
|
||||
}
|
||||
try {
|
||||
sourceValueRange.value = await loadSourceValueRange(selectedEventIdList.value);
|
||||
sourceValueConfig.value = await loadSourceValueConfig(selectedEventIdList.value);
|
||||
sourceValueAction.value = sourceValueConfig.value?.action || "EARN";
|
||||
} catch {
|
||||
sourceValueRange.value = undefined;
|
||||
sourceValueConfig.value = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function resolveSourceValue(): number | undefined {
|
||||
const value = Number(pointValueText.value);
|
||||
return Number.isSafeInteger(value) && 0 < value ? value : undefined;
|
||||
return Number.isSafeInteger(value) && 0 < value ? sourceValueAction.value === "CONSUME" ? -value : value : undefined;
|
||||
}
|
||||
|
||||
function getNumberValuePlaceholder(event: EventItem): string {
|
||||
@@ -640,9 +643,9 @@ function validateForm(): string {
|
||||
}
|
||||
if (showSourceValueInput.value) {
|
||||
const pointValue = resolveSourceValue();
|
||||
const range = sourceValueRange.value;
|
||||
if (!pointValue || !range || pointValue < range[0] || range[1] < pointValue) {
|
||||
showToast(`自定义积分必须是 ${range?.[0] || 1}-${range?.[1] || 0} 的正整数`, "warning");
|
||||
const range = sourceValueConfig.value?.range;
|
||||
if (!pointValue || !range || Math.abs(pointValue) < range[0] || range[1] < Math.abs(pointValue)) {
|
||||
showToast(`积分数量必须是 ${range?.[0] || 1}-${range?.[1] || 0} 的正整数`, "warning");
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
/>
|
||||
</div>
|
||||
<div v-if="showSourceValueInput" class="source-value-panel">
|
||||
<div class="source-value-title">自定义积分</div>
|
||||
<div class="source-value-title" v-text="sourceValueAction === 'CONSUME' ? '消耗积分' : '发放积分'" />
|
||||
<t-input
|
||||
v-model.trim="pointValueText"
|
||||
type="number"
|
||||
@@ -99,11 +99,12 @@ import { useSettingStore } from "@/store/settingStore";
|
||||
import type { Customer } from "@/types/Customer";
|
||||
import type { EventItem } from "@/types/EventItem";
|
||||
import type { EventRecord } from "@/types/EventRecord";
|
||||
import type { PointAction } from "@/types/Point";
|
||||
import { getEventNumberValuePlaceholder, resolveEventNumberValue } from "@/utils/eventValue";
|
||||
import { permissionCodes } from "@/utils/permission";
|
||||
import { getEventRecordPhotoURL } from "@/utils/eventRecord";
|
||||
import { Storage, Time, useUserStore } from "timi-web";
|
||||
import { loadSourceValueRange } from "@/utils/point";
|
||||
import { loadSourceValueConfig, type SourceValueConfig } from "@/utils/point";
|
||||
|
||||
defineOptions({
|
||||
name: "QuickSignInPage"
|
||||
@@ -206,34 +207,36 @@ async function toRecordDetail(id: string): Promise<void> {
|
||||
const submitting = ref(false);
|
||||
const pointValueText = ref("");
|
||||
const numberValueText = ref("");
|
||||
const sourceValueRange = ref<[number, number]>();
|
||||
const sourceValueConfig = ref<SourceValueConfig>();
|
||||
const sourceValueAction = ref<PointAction>("EARN");
|
||||
const canSubmitRegister = computed(() => !!matchedCustomer.value && !!selectedEventId.value);
|
||||
const showNumberValueInput = computed(() => selectedEvent.value?.valueType === "NUMBER");
|
||||
const numberValuePlaceholder = computed(() => selectedEvent.value ? getEventNumberValuePlaceholder(selectedEvent.value) : "请输入事件数值");
|
||||
const showSourceValueInput = computed(() => (
|
||||
userStore.hasPermissions(permissionCodes.point.sourceValue)
|
||||
&& !!sourceValueRange.value
|
||||
&& !!sourceValueConfig.value
|
||||
));
|
||||
const sourceValuePlaceholder = computed(() => {
|
||||
const range = sourceValueRange.value;
|
||||
return range ? `请输入 ${range[0]}-${range[1]} 的正整数` : "请输入积分";
|
||||
const range = sourceValueConfig.value?.range;
|
||||
return range ? `请输入 ${range[0]}-${range[1]} 的数量` : "请输入数量";
|
||||
});
|
||||
|
||||
async function loadSourceValueRule(): Promise<void> {
|
||||
if (!selectedEventId.value || !userStore.hasPermissions(permissionCodes.point.sourceValue)) {
|
||||
sourceValueRange.value = undefined;
|
||||
sourceValueConfig.value = undefined;
|
||||
return;
|
||||
}
|
||||
try {
|
||||
sourceValueRange.value = await loadSourceValueRange([selectedEventId.value]);
|
||||
sourceValueConfig.value = await loadSourceValueConfig([selectedEventId.value]);
|
||||
sourceValueAction.value = sourceValueConfig.value?.action || "EARN";
|
||||
} catch {
|
||||
sourceValueRange.value = undefined;
|
||||
sourceValueConfig.value = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function resolveSourceValue(): number | undefined {
|
||||
const value = Number(pointValueText.value);
|
||||
return Number.isSafeInteger(value) && 0 < value ? value : undefined;
|
||||
return Number.isSafeInteger(value) && 0 < value ? sourceValueAction.value === "CONSUME" ? -value : value : undefined;
|
||||
}
|
||||
|
||||
function resolveNumberValue(): number | undefined {
|
||||
@@ -255,9 +258,9 @@ async function doSubmit(): Promise<void> {
|
||||
Toast.warning("请填写事件数值");
|
||||
return;
|
||||
}
|
||||
const range = sourceValueRange.value;
|
||||
if (showSourceValueInput.value && (!pointValue || !range || pointValue < range[0] || range[1] < pointValue)) {
|
||||
Toast.warning(`自定义积分必须是 ${range?.[0] || 1}-${range?.[1] || 0} 的正整数`);
|
||||
const range = sourceValueConfig.value?.range;
|
||||
if (showSourceValueInput.value && (!pointValue || !range || Math.abs(pointValue) < range[0] || range[1] < Math.abs(pointValue))) {
|
||||
Toast.warning(`积分数量必须是 ${range?.[0] || 1}-${range?.[1] || 0} 的正整数`);
|
||||
return;
|
||||
}
|
||||
submitting.value = true;
|
||||
|
||||
@@ -335,7 +335,7 @@ function formatTimestamp(timestamp: number) {
|
||||
|
||||
function formatSignedPoints(value?: number): string {
|
||||
const pointValue = value ?? 0;
|
||||
return `${0 < pointValue ? "+" : ""}${pointValue.toLocaleString("zh-CN")} 分`;
|
||||
return pointValue < 0 ? `扣除 ${Math.abs(pointValue).toLocaleString("zh-CN")} 分` : `增加 ${pointValue.toLocaleString("zh-CN")} 分`;
|
||||
}
|
||||
|
||||
watch([() => route.query.customerId, () => route.query.storeId], async ([customerIdValue, storeIdValue]) => {
|
||||
|
||||
@@ -43,12 +43,15 @@
|
||||
<customer-profile-card :customer="selectedCustomer" />
|
||||
<div class="balance" v-text="balanceText" />
|
||||
</section>
|
||||
<t-form-item label="调整积分" required-mark>
|
||||
<t-form-item label="调整动作" arrow @click="openPointActionPicker">
|
||||
<span class="picker-text" v-text="pointAction === 'CONSUME' ? '扣除积分' : '补发积分'" />
|
||||
</t-form-item>
|
||||
<t-form-item label="调整数量" required-mark>
|
||||
<t-input
|
||||
v-model.trim="pointDeltaText"
|
||||
type="number"
|
||||
align="right"
|
||||
placeholder="正数加分,负数扣分"
|
||||
placeholder="请输入数量"
|
||||
borderless
|
||||
/>
|
||||
</t-form-item>
|
||||
@@ -82,12 +85,22 @@
|
||||
@confirm="confirmStorePicker"
|
||||
/>
|
||||
</t-popup>
|
||||
<t-popup v-model="pointActionPickerVisible" placement="bottom">
|
||||
<t-picker
|
||||
v-model="pointActionPickerDraft"
|
||||
:columns="pointActionPickerColumns"
|
||||
title="调整动作"
|
||||
@cancel="pointActionPickerVisible = false"
|
||||
@confirm="confirmPointActionPicker"
|
||||
/>
|
||||
</t-popup>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Toast } from "tdesign-mobile-vue";
|
||||
import { useUserStore } from "timi-web";
|
||||
import type { PointAction } from "@/types/Point";
|
||||
import CustomerAPI from "@/api/CustomerAPI";
|
||||
import PointAPI from "@/api/PointAPI";
|
||||
import CodeScanner from "@/components/CodeScanner.vue";
|
||||
@@ -254,6 +267,13 @@ function handleScanStatusChange(value: string): void {
|
||||
// ---------- 调整表单 ----------
|
||||
|
||||
const pointDeltaText = ref("");
|
||||
const pointAction = ref<PointAction>("EARN");
|
||||
const pointActionPickerVisible = ref(false);
|
||||
const pointActionPickerDraft = ref<Array<string | number>>([]);
|
||||
const pointActionPickerColumns = [[
|
||||
{ label: "补发积分", value: "EARN" },
|
||||
{ label: "扣除积分", value: "CONSUME" }
|
||||
]];
|
||||
const remark = ref("");
|
||||
const adjustRequestId = ref(generateRequestId());
|
||||
const isSaving = ref(false);
|
||||
@@ -261,7 +281,20 @@ const canSubmit = computed(() => !!selectedCustomerId.value && !!selectedStoreId
|
||||
|
||||
function parsePointDelta(): number {
|
||||
const value = Number(pointDeltaText.value);
|
||||
return Number.isSafeInteger(value) ? value : 0;
|
||||
return Number.isSafeInteger(value) && 0 < value ? pointAction.value === "CONSUME" ? -value : value : 0;
|
||||
}
|
||||
|
||||
function openPointActionPicker(): void {
|
||||
pointActionPickerDraft.value = [pointAction.value];
|
||||
pointActionPickerVisible.value = true;
|
||||
}
|
||||
|
||||
function confirmPointActionPicker(value: Array<string | number>): void {
|
||||
const action = value[0];
|
||||
if (action === "EARN" || action === "CONSUME") {
|
||||
pointAction.value = action;
|
||||
}
|
||||
pointActionPickerVisible.value = false;
|
||||
}
|
||||
|
||||
async function submitAdjust(): Promise<void> {
|
||||
|
||||
@@ -45,8 +45,8 @@
|
||||
</template>
|
||||
</t-cell>
|
||||
<t-cell v-if="hasEventNumberValue" title="事件数值" :note="getEventNumberValueText()" />
|
||||
<t-cell v-if="hasSubmittedPointValue" title="提交积分值" :note="getPointText(eventRecord.pointValue)" />
|
||||
<t-cell v-if="hasEarnedPointValue" title="记录获得积分" :note="getPointText(eventRecord.earnedPointValue)" />
|
||||
<t-cell v-if="hasSubmittedPointValue" title="提交积分值" :note="getSignedPointText(eventRecord.pointValue)" />
|
||||
<t-cell v-if="hasEarnedPointValue" title="本次积分变化" :note="getSignedPointText(eventRecord.earnedPointValue)" />
|
||||
<t-cell title="登记人" :note="Text.display(eventRecord.operator?.nick || eventRecord.operator?.name)" />
|
||||
<t-cell title="记录备注" :note="Text.display(eventRecord.remark)" />
|
||||
</template>
|
||||
@@ -198,7 +198,7 @@ async function loadPageData(): Promise<void> {
|
||||
|
||||
function getChangeTypeText(changeType?: PointLedger["changeType"]): string {
|
||||
return ({
|
||||
EARN: "获得积分",
|
||||
EARN: "积分变化",
|
||||
REVOKE: "撤销积分",
|
||||
ADJUST: "人工调整"
|
||||
} as Record<string, string>)[changeType || ""] || "未知流水";
|
||||
@@ -212,13 +212,20 @@ function getPointDeltaText(pointDelta?: number): string {
|
||||
if (pointDelta === undefined || pointDelta === null) {
|
||||
return "-";
|
||||
}
|
||||
return `${0 < pointDelta ? "+" : ""}${pointDelta} 分`;
|
||||
return `${0 < pointDelta ? "获得" : "消耗"} ${Math.abs(pointDelta)} 分`;
|
||||
}
|
||||
|
||||
function getPointText(value?: number | null): string {
|
||||
return value === undefined || value === null ? "-" : `${value} 分`;
|
||||
}
|
||||
|
||||
function getSignedPointText(value?: number | null): string {
|
||||
if (value === undefined || value === null) {
|
||||
return "-";
|
||||
}
|
||||
return `${0 < value ? "获得" : "消耗"} ${Math.abs(value)} 分`;
|
||||
}
|
||||
|
||||
function getEventValueTypeText(event: EventItem): string {
|
||||
if (event.valueType === "NUMBER") {
|
||||
return `数值事件(${getEventValueUnitText(event.valueUnit)})`;
|
||||
@@ -252,7 +259,10 @@ function getRulePointText(rule: PointRule): string {
|
||||
if (rule.pointMode === "TIER") {
|
||||
return rule.conditionJson?.tiers?.length ? `${rule.conditionJson.tiers.length} 档` : "分档积分";
|
||||
}
|
||||
return getPointText(rule.pointValue);
|
||||
if (rule.pointValue === undefined || rule.pointValue === null) {
|
||||
return "-";
|
||||
}
|
||||
return `${0 < rule.pointValue ? "发放" : "消耗"} ${Math.abs(rule.pointValue)} 分`;
|
||||
}
|
||||
|
||||
function getRuleConditionText(rule: PointRule): string {
|
||||
@@ -278,7 +288,7 @@ 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 ?? "以上"}:${tier.pointValue} 分`).join(","));
|
||||
textList.push(condition.tiers.map(tier => `${tier.min}~${tier.max ?? "以上"}:${0 < tier.pointValue ? "发放" : "消耗"} ${Math.abs(tier.pointValue)} 分`).join(","));
|
||||
}
|
||||
return textList.join(";") || "无额外条件";
|
||||
}
|
||||
|
||||
@@ -102,18 +102,20 @@
|
||||
class="type-icon"
|
||||
:theme="item.pointDelta && 0 < item.pointDelta ? 'success' : 'danger'"
|
||||
:content="getLedgerShortType(item)"
|
||||
variant="outline"
|
||||
/>
|
||||
<div class="name" v-text="item.customer?.name" />
|
||||
</div>
|
||||
</template>
|
||||
<template #description>
|
||||
<div class="description">
|
||||
<div v-text="`交易时间:${Time.toPassedDateTime(item.occurredAt)}`" />
|
||||
<div class="remark" v-text="getLedgerDescription(item)" />
|
||||
<div v-text="`时间:${Time.toPassedDateTime(item.occurredAt)}`" />
|
||||
<div v-text="`余额:${item.balanceBefore ?? 0} → ${item.balanceAfter ?? 0}`" />
|
||||
<div v-text="item.remark" />
|
||||
</div>
|
||||
</template>
|
||||
<template #note>
|
||||
<div class="delta" :class="getLedgerTheme(item)" v-text="getLedgerDelta(item)" />
|
||||
<div class="delta keep-text" :class="getLedgerTheme(item)" v-text="getLedgerDelta(item)" />
|
||||
</template>
|
||||
</t-cell>
|
||||
</t-cell-group>
|
||||
@@ -410,21 +412,16 @@ function buildListOptions(): { allStores?: boolean; storeId?: string } | undefin
|
||||
|
||||
// ---------- 展示与导航 ----------
|
||||
|
||||
function getLedgerDescription(item: PointLedger): string {
|
||||
const parts = [`余额 ${item.balanceBefore ?? 0} → ${item.balanceAfter ?? 0}`];
|
||||
if (item.remark) {
|
||||
parts.push(item.remark);
|
||||
}
|
||||
return parts.join("|");
|
||||
}
|
||||
|
||||
function getLedgerDelta(item: PointLedger): string {
|
||||
const value = item.pointDelta ?? 0;
|
||||
return `${0 < value ? "+" : ""}${value}`;
|
||||
return `${0 < value ? "+" : "-"}${Math.abs(value)} 分`;
|
||||
}
|
||||
|
||||
function getLedgerShortType(item: PointLedger): string {
|
||||
return item.changeType === "EARN" ? "得" : item.changeType === "REVOKE" ? "撤" : "调";
|
||||
if (item.changeType === "EARN") {
|
||||
return 0 < (item.pointDelta || 0) ? "获得" : "消耗";
|
||||
}
|
||||
return item.changeType === "REVOKE" ? "撤销" : "调整";
|
||||
}
|
||||
|
||||
function getLedgerTheme(item: PointLedger): string {
|
||||
@@ -604,9 +601,6 @@ onMounted(async () => {
|
||||
|
||||
.type-icon {
|
||||
display: flex;
|
||||
font-size: 1rem;
|
||||
font-weight: 700;
|
||||
border-radius: .5rem;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,13 +25,16 @@
|
||||
<t-form-item label="积分值模式" :arrow="formModel.triggerType !== 'CUSTOMER_EVENT_VALUE_TIER'" @click="openModePicker">
|
||||
<span class="picker-text" v-text="getModeText(formModel.pointMode)" />
|
||||
</t-form-item>
|
||||
<t-form-item v-if="formModel.pointMode === 'FIXED'" label="固定积分" required-mark>
|
||||
<t-form-item v-if="formModel.pointMode === 'FIXED'" label="积分动作" arrow @click="openPointActionPicker">
|
||||
<span class="picker-text" v-text="getPointActionText(formModel.pointAction)" />
|
||||
</t-form-item>
|
||||
<t-form-item v-if="formModel.pointMode === 'FIXED'" label="积分数量" required-mark>
|
||||
<t-input
|
||||
v-model.trim="formModel.pointValueText"
|
||||
type="number"
|
||||
align="right"
|
||||
borderless
|
||||
placeholder="请输入正整数"
|
||||
placeholder="请输入数量"
|
||||
/>
|
||||
</t-form-item>
|
||||
<t-form-item v-if="formModel.triggerType === 'AFTER_COUNT_EVERY_TIME'" label="触发阈值" required-mark>
|
||||
@@ -45,14 +48,17 @@
|
||||
</t-form-item>
|
||||
<div v-if="formModel.pointMode === 'SOURCE_VALUE'" class="source-condition">
|
||||
<div class="source-title">自定义积分范围</div>
|
||||
<t-form-item label="积分动作" arrow @click="openPointActionPicker">
|
||||
<span class="picker-text" v-text="getPointActionText(formModel.pointAction)" />
|
||||
</t-form-item>
|
||||
<div class="source-inputs">
|
||||
<t-input v-model.trim="formModel.minText" type="number" align="right" borderless placeholder="最小值" />
|
||||
<span>至</span>
|
||||
<t-input v-model.trim="formModel.maxText" type="number" align="right" borderless placeholder="最大值" />
|
||||
</div>
|
||||
<span class="tips">登记时只有输入在此范围内的正整数,系统才会按本规则发放积分</span>
|
||||
<span class="tips">登记时输入数量,系统会按选定动作执行积分变化</span>
|
||||
</div>
|
||||
<template v-if="formModel.triggerType === 'CUSTOMER_EVENT_VALUE_TIER'" class="tier-condition">
|
||||
<template v-if="formModel.triggerType === 'CUSTOMER_EVENT_VALUE_TIER'">
|
||||
<t-form-item label="来源事件" required-mark arrow @click="openSourceEventPicker">
|
||||
<span class="picker-text" v-text="selectedSourceEventName" />
|
||||
</t-form-item>
|
||||
@@ -76,7 +82,7 @@
|
||||
</div>
|
||||
</t-form-item>
|
||||
</card-container>
|
||||
<card-container tips="命中规则并发放积分后不再匹配其他规则">
|
||||
<card-container tips="命中规则并执行积分动作后不再匹配后续规则">
|
||||
<t-form-item label="优先级" required-mark>
|
||||
<t-input v-model.trim="formModel.priorityText" type="number" align="right" borderless placeholder="数值越小越优先" />
|
||||
</t-form-item>
|
||||
@@ -162,6 +168,15 @@
|
||||
@confirm="confirmModePicker"
|
||||
/>
|
||||
</t-popup>
|
||||
<t-popup v-model="pointActionPickerVisible" placement="bottom">
|
||||
<t-picker
|
||||
v-model="pointActionPickerDraft"
|
||||
:columns="pointActionPickerColumns"
|
||||
title="积分动作"
|
||||
@cancel="pointActionPickerVisible = false"
|
||||
@confirm="confirmPointActionPicker"
|
||||
/>
|
||||
</t-popup>
|
||||
<t-popup v-model="beginAtPickerVisible" placement="bottom">
|
||||
<t-date-time-picker
|
||||
v-model="beginAtDraftText"
|
||||
@@ -194,7 +209,7 @@ import PointAPI from "@/api/PointAPI";
|
||||
import PointRuleTierEditor from "@/pages/point/component/PointRuleTierEditor.vue";
|
||||
import { useCurrentStoreStore } from "@/store/currentStoreStore";
|
||||
import type { EventItem } from "@/types/EventItem";
|
||||
import type { PointRule, PointRuleMode, PointRuleStatus, PointRuleTier, PointRuleTierForm, PointRuleTriggerType } from "@/types/Point";
|
||||
import type { PointAction, PointRule, PointRuleMode, PointRuleStatus, PointRuleTier, PointRuleTierForm, PointRuleTriggerType } from "@/types/Point";
|
||||
import { getErrorMessage } from "@/utils/error";
|
||||
import { gaoRoleCodes, permissionCodes } from "@/utils/permission";
|
||||
import { getTierMinValue, isLastTier, resolveNonNegativeInteger } from "@/utils/pointRule";
|
||||
@@ -230,6 +245,7 @@ const formModel = reactive({
|
||||
status: "DRAFT" as PointRuleStatus,
|
||||
triggerType: "EVERY_TIME" as PointRuleTriggerType,
|
||||
pointMode: "FIXED" as PointRuleMode,
|
||||
pointAction: "EARN" as PointAction,
|
||||
pointValueText: "",
|
||||
thresholdText: "",
|
||||
minText: "",
|
||||
@@ -276,10 +292,12 @@ const selectedSourceEventName = computed(() => sourceEventList.value.find(item =
|
||||
let tierKey = 0;
|
||||
|
||||
function createTierForm(tier?: PointRuleTier, isLast = false): PointRuleTierForm {
|
||||
const pointValue = tier?.pointValue;
|
||||
return {
|
||||
key: tierKey++,
|
||||
maxText: !isLast && tier?.max !== undefined && tier.max !== null ? String(tier.max) : "",
|
||||
pointValueText: tier ? String(tier.pointValue) : "0"
|
||||
pointAction: pointValue !== undefined && pointValue < 0 ? "CONSUME" : "EARN",
|
||||
pointValueText: pointValue === undefined ? "0" : String(Math.abs(pointValue))
|
||||
};
|
||||
}
|
||||
|
||||
@@ -401,6 +419,13 @@ const triggerPickerColumns = [triggerOptions];
|
||||
const modePickerVisible = ref(false);
|
||||
const modePickerDraft = ref<PickerValue[]>([]);
|
||||
const modePickerColumns = computed(() => [modeOptions.value]);
|
||||
const pointActionOptions: Array<{ label: string; value: PointAction }> = [
|
||||
{ label: "发放积分", value: "EARN" },
|
||||
{ label: "消耗积分", value: "CONSUME" }
|
||||
];
|
||||
const pointActionPickerVisible = ref(false);
|
||||
const pointActionPickerDraft = ref<PickerValue[]>([]);
|
||||
const pointActionPickerColumns = [pointActionOptions];
|
||||
|
||||
function openStatusPicker(): void {
|
||||
statusPickerDraft.value = [formModel.status];
|
||||
@@ -453,6 +478,22 @@ function confirmModePicker(value: Array<string | number>): void {
|
||||
modePickerVisible.value = false;
|
||||
}
|
||||
|
||||
function openPointActionPicker(): void {
|
||||
if (formModel.pointMode === "TIER") {
|
||||
return;
|
||||
}
|
||||
pointActionPickerDraft.value = [formModel.pointAction];
|
||||
pointActionPickerVisible.value = true;
|
||||
}
|
||||
|
||||
function confirmPointActionPicker(value: Array<string | number>): void {
|
||||
const action = value[0];
|
||||
if (action === "EARN" || action === "CONSUME") {
|
||||
formModel.pointAction = action;
|
||||
}
|
||||
pointActionPickerVisible.value = false;
|
||||
}
|
||||
|
||||
function getStatusText(status: PointRuleStatus): string {
|
||||
return statusOptions.find(item => item.value === status)?.label || "草稿";
|
||||
}
|
||||
@@ -465,6 +506,10 @@ function getModeText(pointMode: PointRuleMode): string {
|
||||
return modeOptions.value.find(item => item.value === pointMode)?.label || "固定积分";
|
||||
}
|
||||
|
||||
function getPointActionText(action: PointAction): string {
|
||||
return action === "CONSUME" ? "消耗积分" : "发放积分";
|
||||
}
|
||||
|
||||
// ---------- 生效时间 ----------
|
||||
|
||||
const minimumDateTimeText = "1970-01-01 00:00";
|
||||
@@ -511,6 +556,11 @@ function resolvePositiveInteger(value: string): number | undefined {
|
||||
return Number.isSafeInteger(numberValue) && 0 < numberValue ? numberValue : undefined;
|
||||
}
|
||||
|
||||
function resolveSignedPointValue(value: string, action: PointAction): number | undefined {
|
||||
const pointValue = resolvePositiveInteger(value);
|
||||
return pointValue === undefined ? undefined : action === "CONSUME" ? -pointValue : pointValue;
|
||||
}
|
||||
|
||||
function resolveTimestamp(value: string): number | undefined {
|
||||
if (!value.trim()) {
|
||||
return undefined;
|
||||
@@ -532,7 +582,7 @@ function buildCondition(): PointRule["conditionJson"] {
|
||||
tiers: formModel.tiers.map((tier, index) => {
|
||||
const result: PointRuleTier = {
|
||||
min: getTierMinValue(formModel.tiers, index) || 0,
|
||||
pointValue: resolveNonNegativeInteger(tier.pointValueText) || 0
|
||||
pointValue: (tier.pointAction === "CONSUME" ? -1 : 1) * (resolveNonNegativeInteger(tier.pointValueText) || 0)
|
||||
};
|
||||
if (!isLastTier(formModel.tiers, index)) {
|
||||
result.max = resolveNonNegativeInteger(tier.maxText);
|
||||
@@ -543,8 +593,8 @@ function buildCondition(): PointRule["conditionJson"] {
|
||||
}
|
||||
if (formModel.pointMode === "SOURCE_VALUE") {
|
||||
return {
|
||||
min: resolvePositiveInteger(formModel.minText),
|
||||
max: resolvePositiveInteger(formModel.maxText)
|
||||
min: resolveSignedPointValue(formModel.minText, formModel.pointAction),
|
||||
max: resolveSignedPointValue(formModel.maxText, formModel.pointAction)
|
||||
};
|
||||
}
|
||||
if (formModel.triggerType === "FIRST_TIME") {
|
||||
@@ -599,7 +649,7 @@ function validateForm(): boolean {
|
||||
return false;
|
||||
}
|
||||
if (pointValue === undefined) {
|
||||
Toast.warning(`第 ${i + 1} 档积分必须是大于等于 0 的整数`);
|
||||
Toast.warning(`第 ${i + 1} 档积分数量必须是大于等于 0 的整数`);
|
||||
return false;
|
||||
}
|
||||
if (!isLastTier(formModel.tiers, i)) {
|
||||
@@ -611,7 +661,7 @@ function validateForm(): boolean {
|
||||
}
|
||||
}
|
||||
} else if (formModel.pointMode === "FIXED" && !resolvePositiveInteger(formModel.pointValueText)) {
|
||||
Toast.warning("固定积分必须是正整数");
|
||||
Toast.warning("积分数量必须是正整数");
|
||||
return false;
|
||||
}
|
||||
if (formModel.triggerType === "AFTER_COUNT_EVERY_TIME" && !resolvePositiveInteger(formModel.thresholdText)) {
|
||||
@@ -622,7 +672,7 @@ function validateForm(): boolean {
|
||||
const min = resolvePositiveInteger(formModel.minText);
|
||||
const max = resolvePositiveInteger(formModel.maxText);
|
||||
if (!min || !max || max < min) {
|
||||
Toast.warning("自定义积分范围必须是正整数,且最大值不能小于最小值");
|
||||
Toast.warning("积分数量范围必须是正整数,且最大值不能小于最小值");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -654,7 +704,7 @@ async function saveRule(): Promise<void> {
|
||||
status: formModel.status,
|
||||
triggerType: formModel.triggerType,
|
||||
pointMode: formModel.pointMode,
|
||||
pointValue: formModel.pointMode === "FIXED" ? resolvePositiveInteger(formModel.pointValueText) : null,
|
||||
pointValue: formModel.pointMode === "FIXED" ? resolveSignedPointValue(formModel.pointValueText, formModel.pointAction) : null,
|
||||
conditionJson: buildCondition(),
|
||||
beginAt: resolveTimestamp(formModel.beginAtText),
|
||||
endAt: resolveTimestamp(formModel.endAtText),
|
||||
@@ -727,10 +777,13 @@ async function fillForm(): Promise<void> {
|
||||
status: current.status === "ACTIVE" ? "ACTIVE" : current.status === "INACTIVE" ? "INACTIVE" : "DRAFT",
|
||||
triggerType: current.triggerType || "EVERY_TIME",
|
||||
pointMode: current.pointMode || "FIXED",
|
||||
pointValueText: current.pointValue === undefined || current.pointValue === null ? "" : String(current.pointValue),
|
||||
pointAction: current.pointValue !== undefined && current.pointValue !== null
|
||||
? current.pointValue < 0 ? "CONSUME" : "EARN"
|
||||
: current.conditionJson?.min !== undefined && current.conditionJson.min < 0 ? "CONSUME" : "EARN",
|
||||
pointValueText: current.pointValue === undefined || current.pointValue === null ? "" : String(Math.abs(current.pointValue)),
|
||||
thresholdText: current.conditionJson?.threshold === undefined ? "" : String(current.conditionJson.threshold),
|
||||
minText: current.conditionJson?.min === undefined ? "" : String(current.conditionJson.min),
|
||||
maxText: current.conditionJson?.max === undefined ? "" : String(current.conditionJson.max),
|
||||
minText: current.conditionJson?.min === undefined ? "" : String(Math.abs(current.conditionJson.min)),
|
||||
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
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
@click="toEdit(item)"
|
||||
>
|
||||
<template #left-icon>
|
||||
<div class="point">
|
||||
<div class="point" :class="{ 'consume': item.pointValue && item.pointValue < 0 }">
|
||||
<t-icon name="money" />
|
||||
<span v-text="getPointText(item)" />
|
||||
</div>
|
||||
@@ -242,7 +242,10 @@ function getPointText(rule: PointRule): string {
|
||||
if (rule.pointMode === "TIER") {
|
||||
return "分档";
|
||||
}
|
||||
return rule.pointValue === undefined || rule.pointValue === null ? "?" : String(rule.pointValue);
|
||||
if (rule.pointValue === undefined || rule.pointValue === null) {
|
||||
return "?";
|
||||
}
|
||||
return `${0 < rule.pointValue ? "+" : "-"}${Math.abs(rule.pointValue)}`;
|
||||
}
|
||||
|
||||
function getStatusText(status?: PointRuleStatus): string {
|
||||
@@ -323,11 +326,15 @@ onMounted(async () => {
|
||||
.item {
|
||||
|
||||
.point {
|
||||
width: 3rem;
|
||||
width: 4rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
|
||||
&.consume {
|
||||
color: var(--td-error-color);
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
|
||||
@@ -50,23 +50,35 @@
|
||||
placeholder="请输入最大值"
|
||||
/>
|
||||
</t-form-item>
|
||||
<t-form-item label="积分" required-mark>
|
||||
<t-form-item label="积分动作" arrow @click="openActionPicker">
|
||||
<span class="picker-text" v-text="getPointActionText(tierEditorDraft.pointAction)" />
|
||||
</t-form-item>
|
||||
<t-form-item label="积分数量" required-mark>
|
||||
<t-input
|
||||
v-model.trim="tierEditorDraft.pointValueText"
|
||||
type="number"
|
||||
align="right"
|
||||
borderless
|
||||
placeholder="请输入积分,可填写 0"
|
||||
placeholder="请输入数量,可填写 0"
|
||||
/>
|
||||
</t-form-item>
|
||||
<div class="tips">积分为 0 时规则仍会命中,但系统不会创建积分流水。</div>
|
||||
</t-form>
|
||||
</div>
|
||||
</bottom-popup>
|
||||
<t-popup v-model="actionPickerVisible" placement="bottom">
|
||||
<t-picker
|
||||
v-model="actionPickerDraft"
|
||||
:columns="actionPickerColumns"
|
||||
title="积分动作"
|
||||
@cancel="actionPickerVisible = false"
|
||||
@confirm="confirmActionPicker"
|
||||
/>
|
||||
</t-popup>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { PointRuleTierForm } from "@/types/Point";
|
||||
import type { PointAction, PointRuleTierForm } from "@/types/Point";
|
||||
import { getTierMinText, getTierMinValue, isLastTier, resolveNonNegativeInteger } from "@/utils/pointRule";
|
||||
import { Toast } from "tdesign-mobile-vue";
|
||||
|
||||
@@ -80,8 +92,16 @@ const tierEditorIndex = ref(-1);
|
||||
const tierEditorAdding = ref(false);
|
||||
const tierEditorDraft = reactive({
|
||||
maxText: "",
|
||||
pointAction: "EARN" as PointAction,
|
||||
pointValueText: ""
|
||||
});
|
||||
const actionOptions: Array<{ label: string; value: PointAction }> = [
|
||||
{ label: "发放积分", value: "EARN" },
|
||||
{ label: "消耗积分", value: "CONSUME" }
|
||||
];
|
||||
const actionPickerVisible = ref(false);
|
||||
const actionPickerDraft = ref<Array<string | number>>([]);
|
||||
const actionPickerColumns = [actionOptions];
|
||||
const tierEditorTitle = computed(() => {
|
||||
if (tierEditorIndex.value < 0) {
|
||||
return "编辑分档";
|
||||
@@ -107,7 +127,7 @@ function getTierPointText(tier: PointRuleTierForm): string {
|
||||
if (pointValue === undefined) {
|
||||
return "待填写";
|
||||
}
|
||||
return `+${pointValue} 积分`;
|
||||
return `${tier.pointAction === "CONSUME" ? "消耗" : "发放"} ${pointValue} 分`;
|
||||
}
|
||||
|
||||
function openTierEditor(index: number, adding = false): void {
|
||||
@@ -118,6 +138,7 @@ function openTierEditor(index: number, adding = false): void {
|
||||
tierEditorIndex.value = index;
|
||||
tierEditorAdding.value = adding;
|
||||
tierEditorDraft.maxText = adding ? "" : tier.maxText;
|
||||
tierEditorDraft.pointAction = tier.pointAction;
|
||||
tierEditorDraft.pointValueText = tier.pointValueText;
|
||||
tierEditorVisible.value = true;
|
||||
}
|
||||
@@ -140,7 +161,7 @@ function confirmTierEditor(): void {
|
||||
return;
|
||||
}
|
||||
if (pointValue === undefined) {
|
||||
Toast.warning("积分必须是大于等于 0 的整数,允许填写 0");
|
||||
Toast.warning("积分数量必须是大于等于 0 的整数,允许填写 0");
|
||||
return;
|
||||
}
|
||||
const shouldAddTier = tierEditorAdding.value && isLastTier(tiers.value, index);
|
||||
@@ -155,6 +176,7 @@ function confirmTierEditor(): void {
|
||||
tier.maxText = "";
|
||||
}
|
||||
tier.pointValueText = String(pointValue);
|
||||
tier.pointAction = tierEditorDraft.pointAction;
|
||||
if (shouldAddTier) {
|
||||
tiers.value.push(createTierForm());
|
||||
}
|
||||
@@ -181,11 +203,29 @@ function removeTier(index: number): void {
|
||||
tiers.value.splice(index, 1);
|
||||
}
|
||||
|
||||
function openActionPicker(): void {
|
||||
actionPickerDraft.value = [tierEditorDraft.pointAction];
|
||||
actionPickerVisible.value = true;
|
||||
}
|
||||
|
||||
function confirmActionPicker(value: Array<string | number>): void {
|
||||
const action = value[0];
|
||||
if (action === "EARN" || action === "CONSUME") {
|
||||
tierEditorDraft.pointAction = action;
|
||||
}
|
||||
actionPickerVisible.value = false;
|
||||
}
|
||||
|
||||
function getPointActionText(action: PointAction): string {
|
||||
return action === "CONSUME" ? "消耗积分" : "发放积分";
|
||||
}
|
||||
|
||||
function createTierForm(): PointRuleTierForm {
|
||||
const key = tiers.value.reduce((max, tier) => Math.max(max, tier.key), -1) + 1;
|
||||
return {
|
||||
key,
|
||||
maxText: "",
|
||||
pointAction: "EARN",
|
||||
pointValueText: "0"
|
||||
};
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ export type PointRuleTriggerType = "EVERY_TIME" | "FIRST_TIME" | "AFTER_COUNT_EV
|
||||
export type PointRuleMode = "FIXED" | "SOURCE_VALUE" | "TIER";
|
||||
export type PointLedgerChangeType = "EARN" | "REVOKE" | "ADJUST";
|
||||
export type PointLedgerDirection = "INCREASE" | "DECREASE";
|
||||
export type PointAction = "EARN" | "CONSUME";
|
||||
|
||||
export type PointRuleCondition = {
|
||||
min?: number;
|
||||
@@ -32,6 +33,7 @@ export type PointRuleTier = {
|
||||
export type PointRuleTierForm = {
|
||||
key: number;
|
||||
maxText: string;
|
||||
pointAction: PointAction;
|
||||
pointValueText: string;
|
||||
};
|
||||
|
||||
|
||||
+26
-6
@@ -1,5 +1,10 @@
|
||||
import PointAPI from "@/api/PointAPI";
|
||||
import type { PointRule } from "@/types/Point";
|
||||
import type { PointAction, PointRule } from "@/types/Point";
|
||||
|
||||
export type SourceValueConfig = {
|
||||
range: [number, number];
|
||||
action: PointAction;
|
||||
};
|
||||
|
||||
/**
|
||||
* 查询一组事件可用的自定义积分范围,并返回所有规则范围的交集。
|
||||
@@ -8,6 +13,16 @@ import type { PointRule } from "@/types/Point";
|
||||
* @returns 可用范围;没有完整有效的 SOURCE_VALUE 规则时返回 undefined
|
||||
*/
|
||||
export async function loadSourceValueRange(eventIdList: string[]): Promise<[number, number] | undefined> {
|
||||
return (await loadSourceValueConfig(eventIdList))?.range;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询登记时自定义积分的数量范围和积分动作。
|
||||
*
|
||||
* @param eventIdList 事件 ID 列表
|
||||
* @returns 所有事件规则的共同配置
|
||||
*/
|
||||
export async function loadSourceValueConfig(eventIdList: string[]): Promise<SourceValueConfig | undefined> {
|
||||
if (!eventIdList.length) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -34,16 +49,21 @@ export async function loadSourceValueRange(eventIdList: string[]): Promise<[numb
|
||||
&& range.max !== undefined
|
||||
&& Number.isSafeInteger(range.min)
|
||||
&& Number.isSafeInteger(range.max)
|
||||
&& 0 < range.min
|
||||
&& 0 < range.max
|
||||
&& range.min !== 0
|
||||
&& range.max !== 0
|
||||
&& (range.min < 0) === (range.max < 0)
|
||||
));
|
||||
if (rangeList.length !== ruleList.length) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const min = Math.max(...rangeList.map(range => range.min), 1);
|
||||
const max = Math.min(...rangeList.map(range => range.max));
|
||||
return min <= max ? [min, max] : undefined;
|
||||
const action = rangeList[0].min < 0 ? "CONSUME" : "EARN";
|
||||
if (rangeList.some(range => (range.min < 0 ? "CONSUME" : "EARN") !== action)) {
|
||||
return undefined;
|
||||
}
|
||||
const min = Math.max(...rangeList.map(range => action === "CONSUME" ? Math.abs(range.max) : range.min), 1);
|
||||
const max = Math.min(...rangeList.map(range => action === "CONSUME" ? Math.abs(range.min) : range.max));
|
||||
return min <= max ? { range: [min, max], action } : undefined;
|
||||
}
|
||||
|
||||
function isSourceValueRule(rule: PointRule): boolean {
|
||||
|
||||
Reference in New Issue
Block a user