update point tier rule
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div v-can="pageAccess" class="registration-event-edit">
|
||||
<t-form class="form" label-align="left">
|
||||
<t-form class="form" label-align="left" label-width="7rem">
|
||||
<t-form-item label="颜色">
|
||||
<t-button
|
||||
size="extra-small"
|
||||
@@ -33,6 +33,15 @@
|
||||
borderless
|
||||
/>
|
||||
</t-form-item>
|
||||
<t-form-item label="数值类型" arrow @click="openValueTypePicker">
|
||||
<div class="picker-text" v-text="getValueTypeText(formModel.valueType)" />
|
||||
</t-form-item>
|
||||
<t-form-item v-if="isNumberValueType(formModel.valueType)" label="数值单位" required-mark arrow @click="openValueUnitPicker">
|
||||
<div class="picker-text" v-text="getValueUnitText(formModel.valueUnit)" />
|
||||
</t-form-item>
|
||||
<t-form-item v-if="isNumberValueType(formModel.valueType)" label="登记必填数值" content-align="right">
|
||||
<t-switch v-model="formModel.requireValue" />
|
||||
</t-form-item>
|
||||
<t-form-item label="起始时间" arrow @click="openBeginAtPicker">
|
||||
<div class="begin-at">
|
||||
<div class="picker-text" v-text="formModel.beginAtText || '不限'" />
|
||||
@@ -161,6 +170,24 @@
|
||||
@confirm="confirmStatusPicker"
|
||||
/>
|
||||
</t-popup>
|
||||
<t-popup v-model="valueTypePickerVisible" placement="bottom">
|
||||
<t-picker
|
||||
v-model="valueTypePickerDraft"
|
||||
:columns="valueTypePickerColumns"
|
||||
title="数值类型"
|
||||
@cancel="valueTypePickerVisible = false"
|
||||
@confirm="confirmValueType"
|
||||
/>
|
||||
</t-popup>
|
||||
<t-popup v-model="valueUnitPickerVisible" placement="bottom">
|
||||
<t-picker
|
||||
v-model="valueUnitPickerDraft"
|
||||
:columns="valueUnitPickerColumns"
|
||||
title="数值单位"
|
||||
@cancel="valueUnitPickerVisible = false"
|
||||
@confirm="confirmValueUnit"
|
||||
/>
|
||||
</t-popup>
|
||||
<t-popup v-model="beginAtPickerVisible" placement="bottom">
|
||||
<div class="picker-popup">
|
||||
<t-date-time-picker
|
||||
@@ -198,7 +225,8 @@ import type { ColorObject } from "tdesign-mobile-vue";
|
||||
import { useUserStore, type Role } from "timi-web";
|
||||
import EventAPI from "@/api/EventAPI";
|
||||
import { useCurrentStoreStore } from "@/store/currentStoreStore";
|
||||
import type { EventItem, EventStatus, EventLimitType } from "@/types/EventItem";
|
||||
import type { EventItem, EventStatus, EventLimitType, EventValueType, EventValueUnit } from "@/types/EventItem";
|
||||
import { getErrorMessage } from "@/utils/error";
|
||||
import { gaoRoleCodes, permissionCodes } from "@/utils/permission";
|
||||
|
||||
type PickerValue = string | number;
|
||||
@@ -239,6 +267,9 @@ const formModel = reactive({
|
||||
name: "",
|
||||
description: "",
|
||||
color: "#0f766e",
|
||||
valueType: "NONE" as EventValueType,
|
||||
valueUnit: null as EventValueUnit | null,
|
||||
requireValue: false,
|
||||
status: "ACTIVE" as EventStatus,
|
||||
requirePhoto: false,
|
||||
limitType: "NONE" as EventLimitType,
|
||||
@@ -400,6 +431,71 @@ function getStatusText(value: EventStatus): string {
|
||||
return statusOptions.find(option => option.value === value)?.label || "可登记";
|
||||
}
|
||||
|
||||
// ---------- 数值定义 ----------
|
||||
|
||||
const valueTypeOptions: Array<{ label: string; value: EventValueType }> = [
|
||||
{ label: "不携带数值", value: "NONE" },
|
||||
{ label: "携带数值", value: "NUMBER" }
|
||||
];
|
||||
const valueUnitOptions: Array<{ label: string; value: EventValueUnit }> = [
|
||||
{ label: "金额(元)", value: "CENT" },
|
||||
{ label: "次数", value: "COUNT" }
|
||||
];
|
||||
const valueTypePickerVisible = ref(false);
|
||||
const valueTypePickerDraft = ref<PickerValue[]>([]);
|
||||
const valueTypePickerColumns = [valueTypeOptions];
|
||||
const valueUnitPickerVisible = ref(false);
|
||||
const valueUnitPickerDraft = ref<PickerValue[]>([]);
|
||||
const valueUnitPickerColumns = [valueUnitOptions];
|
||||
|
||||
function isNumberValueType(value: EventValueType): boolean {
|
||||
return value === "NUMBER";
|
||||
}
|
||||
|
||||
function getValueTypeText(value: EventValueType): string {
|
||||
return valueTypeOptions.find(option => option.value === value)?.label || "不携带数值";
|
||||
}
|
||||
|
||||
function getValueUnitText(value: EventValueUnit | null): string {
|
||||
return valueUnitOptions.find(option => option.value === value)?.label || "请选择单位";
|
||||
}
|
||||
|
||||
function openValueTypePicker(): void {
|
||||
valueTypePickerDraft.value = [formModel.valueType];
|
||||
valueTypePickerVisible.value = true;
|
||||
}
|
||||
|
||||
function confirmValueType(value: Array<string | number>): void {
|
||||
const valueType = value[0];
|
||||
if (valueType !== "NONE" && valueType !== "NUMBER") {
|
||||
return;
|
||||
}
|
||||
formModel.valueType = valueType;
|
||||
if (valueType === "NONE") {
|
||||
formModel.valueUnit = null;
|
||||
formModel.requireValue = false;
|
||||
} else if (!formModel.valueUnit) {
|
||||
formModel.valueUnit = "CENT";
|
||||
}
|
||||
valueTypePickerVisible.value = false;
|
||||
}
|
||||
|
||||
function openValueUnitPicker(): void {
|
||||
if (!isNumberValueType(formModel.valueType)) {
|
||||
return;
|
||||
}
|
||||
valueUnitPickerDraft.value = [formModel.valueUnit || "CENT"];
|
||||
valueUnitPickerVisible.value = true;
|
||||
}
|
||||
|
||||
function confirmValueUnit(value: Array<string | number>): void {
|
||||
const valueUnit = value[0];
|
||||
if (valueUnit === "CENT" || valueUnit === "COUNT") {
|
||||
formModel.valueUnit = valueUnit;
|
||||
}
|
||||
valueUnitPickerVisible.value = false;
|
||||
}
|
||||
|
||||
// ---------- 次数限制 ----------
|
||||
|
||||
function openLimitPicker(): void {
|
||||
@@ -539,6 +635,9 @@ async function saveEventItem(): Promise<void> {
|
||||
name: formModel.name.trim(),
|
||||
description: formModel.description,
|
||||
color: formModel.color,
|
||||
valueType: formModel.valueType,
|
||||
valueUnit: isNumberValueType(formModel.valueType) ? formModel.valueUnit : null,
|
||||
requireValue: isNumberValueType(formModel.valueType) && formModel.requireValue,
|
||||
status: formModel.status,
|
||||
requirePhoto: formModel.requirePhoto,
|
||||
limitType: formModel.limitType,
|
||||
@@ -554,8 +653,8 @@ async function saveEventItem(): Promise<void> {
|
||||
}
|
||||
Toast.success("登记事件已保存");
|
||||
await router.replace("/management/registration-events");
|
||||
} catch {
|
||||
Toast.error("登记事件保存失败");
|
||||
} catch (error) {
|
||||
Toast.error(getErrorMessage(error, "登记事件保存失败"));
|
||||
} finally {
|
||||
isSaving.value = false;
|
||||
}
|
||||
@@ -629,6 +728,9 @@ async function fillForm(): Promise<void> {
|
||||
name: current.name || "",
|
||||
description: current.description || "",
|
||||
color: current.color || "#2f88ff",
|
||||
valueType: current.valueType || "NONE",
|
||||
valueUnit: current.valueUnit || null,
|
||||
requireValue: current.requireValue ?? false,
|
||||
status: current.status === "DRAFT" ? "DRAFT" : current.status === "INACTIVE" ? "INACTIVE" : "ACTIVE",
|
||||
requirePhoto: current.requirePhoto ?? false,
|
||||
limitType: current.limitType || "NONE",
|
||||
|
||||
@@ -57,6 +57,7 @@
|
||||
<span v-if="!(item.roleCodeList || []).length" class="muted" v-text="'未配置'" />
|
||||
</div>
|
||||
<div class="line" v-text="getLimitText(item)" />
|
||||
<div class="line" v-text="getValueDefinitionText(item)" />
|
||||
</div>
|
||||
</template>
|
||||
</t-cell>
|
||||
@@ -81,6 +82,7 @@ import ManagedUserAPI from "@/api/ManagedUserAPI";
|
||||
import { useCurrentStoreStore } from "@/store/currentStoreStore";
|
||||
import { useNavBarStore } from "@/store/navBarStore";
|
||||
import type { EventItem, EventLimitType, EventStatus } from "@/types/EventItem";
|
||||
import { getEventValueUnitText } from "@/utils/eventValue";
|
||||
import { gaoRoleCodes, permissionCodes } from "@/utils/permission";
|
||||
|
||||
defineOptions({
|
||||
@@ -199,6 +201,14 @@ function getLimitText(item: EventItem): string {
|
||||
return limitTextMap[item.limitType || "NONE"];
|
||||
}
|
||||
|
||||
function getValueDefinitionText(item: EventItem): string {
|
||||
if (item.valueType !== "NUMBER") {
|
||||
return "不携带数值";
|
||||
}
|
||||
const unitText = getEventValueUnitText(item.valueUnit);
|
||||
return `整数数值|${unitText}${item.requireValue ? "|登记必填" : "|登记可选"}`;
|
||||
}
|
||||
|
||||
function resolveLimitValue(value?: number): number {
|
||||
if (value !== undefined && Number.isFinite(value) && 0 < value) {
|
||||
return Math.floor(value);
|
||||
|
||||
@@ -8,6 +8,9 @@
|
||||
/>
|
||||
<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 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)" />
|
||||
@@ -33,6 +36,7 @@ import PhotoScroller from "@/components/PhotoScroller.vue";
|
||||
import { useNavBarStore } from "@/store/navBarStore";
|
||||
import type { EventRecord } from "@/types/EventRecord";
|
||||
import { getEventRecordPhotoURL } from "@/utils/eventRecord";
|
||||
import { formatEventNumberValue } from "@/utils/eventValue";
|
||||
import { permissionCodes } from "@/utils/permission";
|
||||
|
||||
defineOptions({
|
||||
@@ -55,6 +59,10 @@ const recordPhotoList = computed(() => {
|
||||
return url ? [url] : [];
|
||||
});
|
||||
|
||||
function getNumberValueText(value: EventRecord): string {
|
||||
return formatEventNumberValue(value.event, value.numberValue);
|
||||
}
|
||||
|
||||
// ---------- 页面数据 ----------
|
||||
|
||||
async function loadRecord(): Promise<void> {
|
||||
|
||||
@@ -50,6 +50,23 @@
|
||||
:disabled="isEditMode"
|
||||
required-mark
|
||||
/>
|
||||
<div v-if="!isEditMode && selectedNumberEventList.length" class="event-value-list">
|
||||
<t-form-item
|
||||
v-for="item in selectedNumberEventList"
|
||||
:key="item.id"
|
||||
:label="`${item.name || '未命名事件'}数值`"
|
||||
:required-mark="item.requireValue === true"
|
||||
label-align="top"
|
||||
>
|
||||
<t-input
|
||||
v-model.trim="numberValueTextMap[item.id || '']"
|
||||
type="number"
|
||||
align="right"
|
||||
borderless
|
||||
:placeholder="getNumberValuePlaceholder(item)"
|
||||
/>
|
||||
</t-form-item>
|
||||
</div>
|
||||
<div v-if="isEditMode" class="edit-point-tip">已产生积分的登记记录不会因修改客户、事件或时间而重算;如需修改这些事实,请删除后重新创建。</div>
|
||||
<t-form-item v-if="showSourceValueInput" label="自定义积分" required-mark>
|
||||
<t-input
|
||||
@@ -182,6 +199,7 @@ import type { Customer } from "@/types/Customer";
|
||||
import type { EventItem } from "@/types/EventItem";
|
||||
import type { EventRecord } from "@/types/EventRecord";
|
||||
import { getEventRecordPhotoURL } from "@/utils/eventRecord";
|
||||
import { getEventNumberValuePlaceholder, resolveEventNumberValue } from "@/utils/eventValue";
|
||||
import { permissionCodes } from "@/utils/permission";
|
||||
import { loadSourceValueRange } from "@/utils/point";
|
||||
import { compressUploadImageList } from "@/utils/imageCompression";
|
||||
@@ -372,6 +390,10 @@ function handleScanStatusChange(value: string): void {
|
||||
|
||||
const eventList = ref<EventItem[]>([]);
|
||||
const selectedEventIdList = ref<string[]>([]);
|
||||
const numberValueTextMap = reactive<Record<string, string>>({});
|
||||
const selectedNumberEventList = computed(() => selectedEventIdList.value
|
||||
.map(eventId => eventList.value.find(item => item.id === eventId))
|
||||
.filter((item): item is EventItem => item?.valueType === "NUMBER"));
|
||||
|
||||
const eventNameMap = computed(() => {
|
||||
const map = new Map<string, string>();
|
||||
@@ -391,6 +413,46 @@ function appendRecordEvent(record: EventRecord): void {
|
||||
eventList.value = [record.event, ...eventList.value];
|
||||
}
|
||||
|
||||
function getNumberValuePlaceholder(event: EventItem): string {
|
||||
return getEventNumberValuePlaceholder(event);
|
||||
}
|
||||
|
||||
function resolveNumberValue(eventId: string): number | undefined {
|
||||
const valueText = numberValueTextMap[eventId] || "";
|
||||
if (!valueText.trim()) {
|
||||
return undefined;
|
||||
}
|
||||
const event = eventList.value.find(item => item.id === eventId);
|
||||
return event ? resolveEventNumberValue(event, valueText) : undefined;
|
||||
}
|
||||
|
||||
function validateNumberValueList(): boolean {
|
||||
for (const eventId of selectedEventIdList.value) {
|
||||
const event = eventList.value.find(item => item.id === eventId);
|
||||
if (!event) {
|
||||
continue;
|
||||
}
|
||||
const valueText = numberValueTextMap[eventId] || "";
|
||||
if (event.valueType !== "NUMBER") {
|
||||
if (valueText.trim()) {
|
||||
Toast.warning(`${event.name || "当前事件"}不能提交数值`);
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
const numberValue = resolveNumberValue(eventId);
|
||||
if (valueText.trim() && numberValue === undefined) {
|
||||
Toast.warning(`${event.name || "当前事件"}数值格式不正确,请检查单位和小数位`);
|
||||
return false;
|
||||
}
|
||||
if (event.requireValue && numberValue === undefined) {
|
||||
Toast.warning(`请填写${event.name || "当前事件"}数值`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---------- 积分规则联动 ----------
|
||||
|
||||
const pointValueText = ref("");
|
||||
@@ -533,6 +595,7 @@ async function submitRecord(): Promise<void> {
|
||||
}
|
||||
|
||||
const customerIdList = selectedCustomerList.value.map(customer => customer.id).filter((item): item is string => !!item);
|
||||
// 多个事件按选择顺序逐条提交,避免把有累计积分顺序依赖的事件混入同一批次。
|
||||
for (const eventId of selectedEventIdList.value) {
|
||||
await EventRecordAPI.create({
|
||||
eventId,
|
||||
@@ -540,6 +603,7 @@ async function submitRecord(): Promise<void> {
|
||||
remark: note.value || "客户现场登记",
|
||||
registeredAt: buildRegisteredAt(),
|
||||
pointValue: showSourceValueInput.value ? resolveSourceValue() : undefined,
|
||||
numberValue: resolveNumberValue(eventId),
|
||||
attachmentList: buildTempFileIdList().map(tempFileId => ({
|
||||
tempFileId,
|
||||
attachType: "PHOTO"
|
||||
@@ -551,6 +615,9 @@ async function submitRecord(): Promise<void> {
|
||||
note.value = "";
|
||||
pointValueText.value = "";
|
||||
photoItemList.value = [];
|
||||
for (const eventId of Object.keys(numberValueTextMap)) {
|
||||
delete numberValueTextMap[eventId];
|
||||
}
|
||||
await loadRecentRecordList();
|
||||
await settingStore.playRegisterSuccessSound();
|
||||
Toast.success("登记已提交");
|
||||
@@ -569,6 +636,9 @@ function validateForm(): boolean {
|
||||
Toast.warning("请至少选择一个登记事件");
|
||||
return false;
|
||||
}
|
||||
if (!isEditMode.value && !validateNumberValueList()) {
|
||||
return false;
|
||||
}
|
||||
if (showSourceValueInput.value) {
|
||||
const pointValue = resolveSourceValue();
|
||||
const range = sourceValueRange.value;
|
||||
@@ -710,6 +780,14 @@ watch(customerCode, async (value) => {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.event-value-list {
|
||||
padding: 0 1rem;
|
||||
|
||||
.t-form-item {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.time-actions {
|
||||
gap: .5rem;
|
||||
display: flex;
|
||||
|
||||
Reference in New Issue
Block a user