support consume point

This commit is contained in:
Timi
2026-08-18 16:46:13 +08:00
parent eaee1ae49b
commit eb6b8beb6b
14 changed files with 281 additions and 99 deletions
+1 -1
View File
@@ -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]) => {
+36 -3
View File
@@ -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> {
+16 -6
View File
@@ -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(";") || "无额外条件";
}
+10 -16
View File
@@ -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;
}
}
+70 -17
View File
@@ -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
+10 -3
View File
@@ -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"
};
}