update point rule groovy script

This commit is contained in:
Timi
2026-09-09 01:24:42 +08:00
parent 74127fc20f
commit 07205f869b
8 changed files with 281 additions and 138 deletions
+8 -1
View File
@@ -6,7 +6,8 @@ import type {
PointAdjustRequest, PointAdjustRequest,
PointLedger, PointLedger,
PointLedgerPage, PointLedgerPage,
PointRule PointRule,
GroovyScript
} from "@/types/Point"; } from "@/types/Point";
import { getCurrentStoreId, withCurrentStoreId, withCurrentStorePage } from "@/utils/store"; import { getCurrentStoreId, withCurrentStoreId, withCurrentStorePage } from "@/utils/store";
@@ -94,6 +95,11 @@ async function ruleDetail(id: string): Promise<PointRule> {
}); });
} }
/** 查询积分规则可选的 Groovy 脚本。 */
async function ruleScriptList(): Promise<GroovyScript[]> {
return await axios.post(`${BASE_URI}/rule/script/list`);
}
async function ruleCreate(rule: PointRule): Promise<string> { async function ruleCreate(rule: PointRule): Promise<string> {
return await axios.post(`${BASE_URI}/rule/create`, withCurrentStoreId(rule)); return await axios.post(`${BASE_URI}/rule/create`, withCurrentStoreId(rule));
} }
@@ -120,6 +126,7 @@ export default {
adjust, adjust,
ruleList, ruleList,
ruleDetail, ruleDetail,
ruleScriptList,
ruleCreate, ruleCreate,
ruleUpdate, ruleUpdate,
ruleSort, ruleSort,
+4 -61
View File
@@ -22,9 +22,6 @@
borderless borderless
/> />
</t-form-item> </t-form-item>
<t-form-item v-if="isGlobalManager" label="门店" :arrow="isCreateMode" @click="openStorePicker">
<div class="picker-text" v-text="selectedStoreName" />
</t-form-item>
<t-form-item label="业务类型" :arrow="false"> <t-form-item label="业务类型" :arrow="false">
<div class="picker-text" v-text="getBizTypeText(formModel.bizType)" /> <div class="picker-text" v-text="getBizTypeText(formModel.bizType)" />
</t-form-item> </t-form-item>
@@ -171,15 +168,6 @@
@confirm="confirmLimitPicker" @confirm="confirmLimitPicker"
/> />
</t-popup> </t-popup>
<t-popup v-model="storePickerVisible" placement="bottom">
<t-picker
v-model="storePickerDraft"
:columns="storePickerColumns"
title="选择门店"
@cancel="storePickerVisible = false"
@confirm="confirmStorePicker"
/>
</t-popup>
<t-popup v-model="statusPickerVisible" placement="bottom"> <t-popup v-model="statusPickerVisible" placement="bottom">
<t-picker <t-picker
v-model="statusPickerDraft" v-model="statusPickerDraft"
@@ -311,46 +299,6 @@ function getBizTypeText(value: EventBizType): string {
return getEventBizTypeText(value); return getEventBizTypeText(value);
} }
// ---------- 门店选择 ----------
const storePickerVisible = ref(false);
const storePickerDraft = ref<PickerValue[]>([]);
const storePickerColumns = computed(() => [
currentStoreStore.storeList
.map(item => ({
label: item.name || item.code || "未命名门店",
value: item.id || ""
}))
.filter(item => item.value)
]);
const selectedStoreName = computed(() => {
const store = currentStoreStore.storeList.find(item => item.id === formModel.storeId);
return store?.name || store?.code || "请选择门店";
});
function openStorePicker(): void {
if (!isCreateMode.value) {
return;
}
if (!currentStoreStore.storeList.length) {
Message.warning("暂无可选门店");
return;
}
storePickerDraft.value = [formModel.storeId];
storePickerVisible.value = true;
}
async function confirmStorePicker(): Promise<void> {
const storeId = String(storePickerDraft.value[0] || "");
if (!storeId) {
return;
}
formModel.storeId = storeId;
storePickerVisible.value = false;
}
// ---------- 颜色 ---------- // ---------- 颜色 ----------
const colorPickerVisible = ref(false); const colorPickerVisible = ref(false);
@@ -600,7 +548,7 @@ function resolveTimestamp(valueText: string): number | undefined {
function validateForm(): boolean { function validateForm(): boolean {
if (!formModel.storeId) { if (!formModel.storeId) {
Message.warning("请选择门店"); Message.warning("当前门店不可用");
return false; return false;
} }
if (!formModel.name.trim()) { if (!formModel.name.trim()) {
@@ -729,15 +677,10 @@ async function saveEventItem(): Promise<void> {
async function prepareStoreScope(): Promise<void> { async function prepareStoreScope(): Promise<void> {
if (isGlobalManager.value) { if (isGlobalManager.value) {
await currentStoreStore.loadStoreList(); await currentStoreStore.loadStoreList();
const queryStoreId = String(route.query.storeId || ""); } else {
formModel.storeId = currentStoreStore.storeList.some(item => item.id === queryStoreId) await currentStoreStore.loadLoginStore();
? queryStoreId
: currentStoreStore.currentStoreId;
return;
} }
formModel.storeId = currentStoreStore.currentStoreId;
const store = await currentStoreStore.loadLoginStore();
formModel.storeId = store?.id || "";
} }
async function fillForm(): Promise<void> { async function fillForm(): Promise<void> {
+1 -4
View File
@@ -328,10 +328,7 @@ async function loadData(): Promise<void> {
} }
async function toCreate(): Promise<void> { async function toCreate(): Promise<void> {
await router.push({ await router.push("/management/registration-events/create");
path: "/management/registration-events/create",
query: currentStoreStore.currentStoreId ? { storeId: currentStoreStore.currentStoreId } : undefined
});
} }
async function toSort(): Promise<void> { async function toSort(): Promise<void> {
+10
View File
@@ -64,6 +64,7 @@
<t-cell title="规则名称" :note="hitRule.name || '未命名规则'" /> <t-cell title="规则名称" :note="hitRule.name || '未命名规则'" />
<t-cell title="规则模式" :note="hitRule.ruleMode === 'GROOVY' ? '脚本规则' : '简单规则'" /> <t-cell title="规则模式" :note="hitRule.ruleMode === 'GROOVY' ? '脚本规则' : '简单规则'" />
<t-cell title="规则积分" :note="getRulePointText(hitRule)" /> <t-cell title="规则积分" :note="getRulePointText(hitRule)" />
<t-cell v-if="hitRule.ruleMode === 'GROOVY'" title="执行脚本" :note="getRuleScriptText(hitRule)" />
<t-cell title="规则状态" :note="getRuleStatusText(hitRule.status)" /> <t-cell title="规则状态" :note="getRuleStatusText(hitRule.status)" />
<t-cell title="规则优先级" :note="getPointText(hitRule.priority)" /> <t-cell title="规则优先级" :note="getPointText(hitRule.priority)" />
<t-cell title="命中后停止匹配" :note="hitRule.exclusive ? '是' : '否'" /> <t-cell title="命中后停止匹配" :note="hitRule.exclusive ? '是' : '否'" />
@@ -254,6 +255,15 @@ function getRulePointText(rule: PointRule): string {
return `${0 < rule.pointValue ? "发放" : "消耗"} ${Math.abs(rule.pointValue)} 分`; return `${0 < rule.pointValue ? "发放" : "消耗"} ${Math.abs(rule.pointValue)} 分`;
} }
function getRuleScriptText(rule: PointRule): string {
const script = rule.groovyScript;
if (!script) {
return rule.groovyScriptId || "未绑定脚本";
}
const version = script.latest?.version;
return `${script.name || script.code || "未命名脚本"}${version === undefined ? "" : `(v${version})`}`;
}
function getRuleStatusText(status?: PointRuleStatus): string { function getRuleStatusText(status?: PointRuleStatus): string {
return ({ return ({
DRAFT: "草稿", DRAFT: "草稿",
+56 -2
View File
@@ -34,7 +34,27 @@
<t-cell-group v-if="rule" title="积分配置" theme="card" v-entrance-transition="140"> <t-cell-group v-if="rule" title="积分配置" theme="card" v-entrance-transition="140">
<t-cell title="规则模式" :note="rule.ruleMode === 'GROOVY' ? '脚本规则' : '简单规则'" /> <t-cell title="规则模式" :note="rule.ruleMode === 'GROOVY' ? '脚本规则' : '简单规则'" />
<t-cell v-if="rule.ruleMode !== 'GROOVY'" title="固定积分" :note="formatPointValue(rule.pointValue)" /> <t-cell v-if="rule.ruleMode !== 'GROOVY'" title="固定积分" :note="formatPointValue(rule.pointValue)" />
<t-cell v-else title="执行脚本" :note="rule.groovyScriptId || '未绑定脚本'" /> <template v-if="rule?.ruleMode === 'GROOVY'">
<t-cell title="执行脚本" :note="scriptName" />
<t-cell class="empty-cell-title-margin" title="脚本说明">
<template #description>
<markdown-view :content="rule.groovyScript?.description || '暂无脚本说明'" />
</template>
</t-cell>
<t-cell class="empty-cell-title-margin" title="脚本代码">
<template #description>
<code-editor
v-if="scriptContent"
:value="scriptContent"
language="groovy"
readonly
line-numbers
class="script-editor"
/>
<div v-else class="empty-script" v-text="'暂无脚本代码'" />
</template>
</t-cell>
</template>
</t-cell-group> </t-cell-group>
<t-cell-group v-if="rule" title="其他配置" theme="card" v-entrance-transition="280"> <t-cell-group v-if="rule" title="其他配置" theme="card" v-entrance-transition="280">
<t-cell title="规则优先级" :note="formatNumber(rule.priority)" /> <t-cell title="规则优先级" :note="formatNumber(rule.priority)" />
@@ -60,7 +80,8 @@
<script setup lang="ts"> <script setup lang="ts">
import { DialogPlugin } from "tdesign-mobile-vue"; import { DialogPlugin } from "tdesign-mobile-vue";
import { Text, Time } from "timi-web"; import { CodeEditor } from "timi-tdesign-mobile";
import { MarkdownView, Text, Time } from "timi-web";
import { useUserStore } from "timi-web-api"; import { useUserStore } from "timi-web-api";
import EventAPI from "@/api/EventAPI"; import EventAPI from "@/api/EventAPI";
import PointAPI from "@/api/PointAPI"; import PointAPI from "@/api/PointAPI";
@@ -112,6 +133,22 @@ const rulePointText = computed(() => {
} }
return formatPointValue(rule.value?.pointValue); return formatPointValue(rule.value?.pointValue);
}); });
const scriptName = computed(() => rule.value?.groovyScript?.name || rule.value?.groovyScriptId || "未绑定脚本");
const scriptStatusText = computed(() => {
const script = rule.value?.groovyScript;
if (!script) {
return "脚本不存在";
}
if (script.enabled && script.publishedVersionId) {
return "已启用并已发布";
}
return script.enabled ? "已启用,未发布" : "已停用";
});
const scriptVersionText = computed(() => {
const version = rule.value?.groovyScript?.latest?.version;
return version === undefined || version === null ? "未创建版本" : String(version);
});
const scriptContent = computed(() => rule.value?.groovyScript?.latest?.content || "");
async function loadStoreScope(): Promise<void> { async function loadStoreScope(): Promise<void> {
if (isGlobalManager.value) { if (isGlobalManager.value) {
@@ -349,5 +386,22 @@ onUnmounted(() => {
color: var(--td-text-color-secondary); color: var(--td-text-color-secondary);
} }
} }
.script-section {
.script-editor {
height: 24rem;
min-height: 16rem;
background: var(--td-bg-color-page);
border-radius: .5rem;
}
.empty-script {
padding: 1rem;
color: var(--td-text-color-placeholder);
background: var(--td-bg-color-page);
border-radius: .5rem;
}
}
} }
</style> </style>
+172 -55
View File
@@ -2,9 +2,6 @@
<div v-can="pageAccess" class="point-rule-edit"> <div v-can="pageAccess" class="point-rule-edit">
<t-form class="form" label-align="left" label-width="7rem"> <t-form class="form" label-align="left" label-width="7rem">
<card-container v-entrance-transition> <card-container v-entrance-transition>
<t-form-item v-if="isGlobalManager" label="门店" required-mark :arrow="isCreateMode" @click="openStorePicker">
<span class="picker-text" v-text="selectedStoreName" />
</t-form-item>
<t-form-item label="触发来源" required-mark arrow @click="openTriggerSourcePicker"> <t-form-item label="触发来源" required-mark arrow @click="openTriggerSourcePicker">
<span class="picker-text" v-text="getTriggerSourceText(formModel.triggerSourceType)" /> <span class="picker-text" v-text="getTriggerSourceText(formModel.triggerSourceType)" />
</t-form-item> </t-form-item>
@@ -23,6 +20,15 @@
</t-form-item> </t-form-item>
</card-container> </card-container>
<card-container v-entrance-transition="70"> <card-container v-entrance-transition="70">
<t-form-item label="规则模式" arrow @click="openRuleModePicker">
<span class="picker-text" v-text="getRuleModeText(formModel.ruleMode)" />
</t-form-item>
<template v-if="formModel.ruleMode === 'GROOVY'">
<t-form-item label="执行脚本" required-mark arrow @click="openScriptPicker">
<span class="picker-text" v-text="selectedScriptText" />
</t-form-item>
</template>
<template v-else>
<t-form-item label="积分动作" arrow @click="openPointActionPicker"> <t-form-item label="积分动作" arrow @click="openPointActionPicker">
<span class="picker-text" v-text="getPointActionText(formModel.pointAction)" /> <span class="picker-text" v-text="getPointActionText(formModel.pointAction)" />
</t-form-item> </t-form-item>
@@ -35,6 +41,7 @@
placeholder="请输入数量" placeholder="请输入数量"
/> />
</t-form-item> </t-form-item>
</template>
</card-container> </card-container>
<card-container v-entrance-transition="140"> <card-container v-entrance-transition="140">
<t-form-item label="生效开始" arrow @click="openBeginAtPicker"> <t-form-item label="生效开始" arrow @click="openBeginAtPicker">
@@ -57,15 +64,6 @@
</card-container> </card-container>
</t-form> </t-form>
</div> </div>
<t-popup v-model="storePickerVisible" placement="bottom">
<t-picker
v-model="storePickerDraft"
:columns="storePickerColumns"
title="选择门店"
@cancel="storePickerVisible = false"
@confirm="confirmStorePicker"
/>
</t-popup>
<t-popup v-model="eventPickerVisible" placement="bottom"> <t-popup v-model="eventPickerVisible" placement="bottom">
<t-picker <t-picker
v-model="eventPickerDraft" v-model="eventPickerDraft"
@@ -102,6 +100,51 @@
@confirm="confirmPointActionPicker" @confirm="confirmPointActionPicker"
/> />
</t-popup> </t-popup>
<t-popup v-model="ruleModePickerVisible" placement="bottom">
<t-picker
v-model="ruleModePickerDraft"
:columns="ruleModePickerColumns"
title="规则模式"
@cancel="ruleModePickerVisible = false"
@confirm="confirmRuleModePicker"
/>
</t-popup>
<bottom-popup
v-model="scriptPickerVisible"
title="选择执行脚本"
height="min(80vh, 48rem)"
@left-button-click="cancelScriptPicker"
@right-button-click="confirmScriptPicker"
>
<t-collapse
v-if="availableScriptList.length"
v-model="expandedScriptIdList"
class="script-collapse"
:expand-mutex="true"
>
<t-collapse-panel
v-for="script in availableScriptList"
:key="getScriptId(script)"
:value="getScriptId(script)"
class="script-panel"
placement="bottom"
>
<template #header>
<t-checkbox
:model-value="scriptPickerDraftId === getScriptId(script)"
:label="getScriptName(script)"
borderless
@click.stop
@change="selectScript(getScriptId(script))"
/>
</template>
<div class="script-description">
<markdown-view :content="script.description || '暂无脚本说明'" />
</div>
</t-collapse-panel>
</t-collapse>
<t-empty v-else description="暂无可选积分脚本" />
</bottom-popup>
<t-popup v-model="beginAtPickerVisible" placement="bottom"> <t-popup v-model="beginAtPickerVisible" placement="bottom">
<t-date-time-picker <t-date-time-picker
v-model="beginAtDraftText" v-model="beginAtDraftText"
@@ -127,7 +170,8 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { Time } from "timi-web"; import { BottomPopup } from "timi-tdesign-mobile";
import { MarkdownView, Time } from "timi-web";
import { useUserStore } from "timi-web-api"; import { useUserStore } from "timi-web-api";
import EventAPI from "@/api/EventAPI"; import EventAPI from "@/api/EventAPI";
import PointAPI from "@/api/PointAPI"; import PointAPI from "@/api/PointAPI";
@@ -135,8 +179,10 @@ import { useCurrentStoreStore } from "@/store/currentStoreStore";
import { useNavBarStore } from "@/store/navBarStore"; import { useNavBarStore } from "@/store/navBarStore";
import type { EventItem } from "@/types/EventItem"; import type { EventItem } from "@/types/EventItem";
import type { import type {
GroovyScript,
PointAction, PointAction,
PointRule, PointRule,
PointRuleMode,
PointRuleStatus, PointRuleStatus,
PointRuleTriggerSourceType PointRuleTriggerSourceType
} from "@/types/Point"; } from "@/types/Point";
@@ -170,30 +216,24 @@ const isSaving = ref(false);
const formModel = reactive({ const formModel = reactive({
id: "", id: "",
storeId: "", storeId: "",
ruleMode: "SIMPLE" as PointRuleMode,
triggerSourceType: "EVENT_RECORD" as PointRuleTriggerSourceType, triggerSourceType: "EVENT_RECORD" as PointRuleTriggerSourceType,
triggerRefId: "", triggerRefId: "",
name: "", name: "",
status: "DRAFT" as PointRuleStatus, status: "DRAFT" as PointRuleStatus,
pointAction: "EARN" as PointAction, pointAction: "EARN" as PointAction,
pointValueText: "", pointValueText: "",
groovyScriptId: "",
beginAtText: "", beginAtText: "",
endAtText: "", endAtText: "",
exclusive: false, exclusive: false,
remark: "" remark: ""
}); });
// ---------- 门店与事件 ---------- // ---------- 事件与脚本 ----------
const eventList = ref<EventItem[]>([]); const eventList = ref<EventItem[]>([]);
const selectedStoreName = computed(() => currentStoreStore.storeList.find(item => item.id === formModel.storeId)?.name || "请选择门店");
const selectedEventName = computed(() => eventList.value.find(item => item.id === formModel.triggerRefId)?.name || "请选择触发事件"); const selectedEventName = computed(() => eventList.value.find(item => item.id === formModel.triggerRefId)?.name || "请选择触发事件");
const storePickerVisible = ref(false);
const storePickerDraft = ref<PickerValue[]>([]);
const storePickerColumns = computed(() => [
currentStoreStore.storeList
.map(item => ({ label: item.name || item.code || "未命名门店", value: item.id || "" }))
.filter(item => item.value)
]);
const eventPickerVisible = ref(false); const eventPickerVisible = ref(false);
const eventPickerDraft = ref<PickerValue[]>([]); const eventPickerDraft = ref<PickerValue[]>([]);
const eventPickerColumns = computed(() => [ const eventPickerColumns = computed(() => [
@@ -202,28 +242,15 @@ const eventPickerColumns = computed(() => [
.filter(item => item.value) .filter(item => item.value)
]); ]);
function openStorePicker(): void { const scriptList = ref<GroovyScript[]>([]);
if (!isCreateMode.value || !storePickerColumns.value[0]?.length) { const selectedScript = computed(() => scriptList.value.find(item => item.id === formModel.groovyScriptId));
return; const availableScriptList = computed(() => scriptList.value.filter(item => item.id));
const selectedScriptText = computed(() => {
if (!selectedScript.value) {
return "请选择执行脚本";
} }
storePickerDraft.value = [formModel.storeId]; return selectedScript.value.name || selectedScript.value.code || "未命名脚本";
storePickerVisible.value = true; });
}
async function confirmStorePicker(value: Array<string | number>): Promise<void> {
const storeId = String(value[0] || "");
if (storeId && storeId !== formModel.storeId) {
formModel.storeId = storeId;
formModel.triggerRefId = "";
try {
await loadEventList();
} catch {
eventList.value = [];
Message.error("触发事件加载失败");
}
}
storePickerVisible.value = false;
}
function openEventPicker(): void { function openEventPicker(): void {
if (!eventPickerColumns.value[0]?.length) { if (!eventPickerColumns.value[0]?.length) {
@@ -245,14 +272,10 @@ function confirmEventPicker(value: Array<string | number>): void {
async function prepareStoreScope(): Promise<void> { async function prepareStoreScope(): Promise<void> {
if (isGlobalManager.value) { if (isGlobalManager.value) {
await currentStoreStore.loadStoreList(); await currentStoreStore.loadStoreList();
const queryStoreId = String(route.query.storeId || ""); } else {
formModel.storeId = currentStoreStore.storeList.some(item => item.id === queryStoreId) await currentStoreStore.loadLoginStore();
? queryStoreId
: currentStoreStore.currentStoreId;
return;
} }
const store = await currentStoreStore.loadLoginStore(); formModel.storeId = currentStoreStore.currentStoreId;
formModel.storeId = store?.id || "";
} }
async function loadEventList(): Promise<void> { async function loadEventList(): Promise<void> {
@@ -265,6 +288,10 @@ async function loadEventList(): Promise<void> {
} }
} }
async function loadScriptList(): Promise<void> {
scriptList.value = await PointAPI.ruleScriptList();
}
// ---------- 规则选项 ---------- // ---------- 规则选项 ----------
const statusOptions: Array<{ label: string; value: PointRuleStatus }> = [ const statusOptions: Array<{ label: string; value: PointRuleStatus }> = [
@@ -286,9 +313,27 @@ const pointActionOptions: Array<{ label: string; value: PointAction }> = [
{ label: "发放积分", value: "EARN" }, { label: "发放积分", value: "EARN" },
{ label: "消耗积分", value: "CONSUME" } { label: "消耗积分", value: "CONSUME" }
]; ];
const ruleModeOptions: Array<{ label: string; value: PointRuleMode }> = [
{ label: "简单规则", value: "SIMPLE" },
{ label: "脚本规则", value: "GROOVY" }
];
const pointActionPickerVisible = ref(false); const pointActionPickerVisible = ref(false);
const pointActionPickerDraft = ref<PickerValue[]>([]); const pointActionPickerDraft = ref<PickerValue[]>([]);
const pointActionPickerColumns = [pointActionOptions]; const pointActionPickerColumns = [pointActionOptions];
const ruleModePickerVisible = ref(false);
const ruleModePickerDraft = ref<PickerValue[]>([]);
const ruleModePickerColumns = [ruleModeOptions];
const scriptPickerVisible = ref(false);
const scriptPickerDraftId = ref("");
const expandedScriptIdList = ref<Array<string | number>>([]);
function getScriptId(script: GroovyScript): string {
return script.id || "";
}
function getScriptName(script: GroovyScript): string {
return script.name || script.code || "未命名脚本";
}
function openStatusPicker(): void { function openStatusPicker(): void {
statusPickerDraft.value = [formModel.status]; statusPickerDraft.value = [formModel.status];
@@ -332,6 +377,52 @@ function confirmPointActionPicker(value: Array<string | number>): void {
pointActionPickerVisible.value = false; pointActionPickerVisible.value = false;
} }
function openRuleModePicker(): void {
ruleModePickerDraft.value = [formModel.ruleMode];
ruleModePickerVisible.value = true;
}
function confirmRuleModePicker(value: Array<string | number>): void {
const ruleMode = value[0];
if (ruleMode !== "SIMPLE" && ruleMode !== "GROOVY") {
return;
}
formModel.ruleMode = ruleMode;
if (ruleMode === "SIMPLE") {
formModel.groovyScriptId = "";
} else {
formModel.pointValueText = "";
}
ruleModePickerVisible.value = false;
}
function openScriptPicker(): void {
if (!availableScriptList.value.length) {
Message.warning("暂无可选积分脚本");
return;
}
scriptPickerDraftId.value = formModel.groovyScriptId;
expandedScriptIdList.value = formModel.groovyScriptId ? [formModel.groovyScriptId] : [];
scriptPickerVisible.value = true;
}
function selectScript(scriptId: string): void {
if (scriptId && availableScriptList.value.some(item => item.id === scriptId)) {
scriptPickerDraftId.value = scriptId;
}
}
function cancelScriptPicker(): void {
scriptPickerVisible.value = false;
}
function confirmScriptPicker(): void {
if (scriptPickerDraftId.value && availableScriptList.value.some(item => item.id === scriptPickerDraftId.value)) {
formModel.groovyScriptId = scriptPickerDraftId.value;
}
scriptPickerVisible.value = false;
}
function getStatusText(status: PointRuleStatus): string { function getStatusText(status: PointRuleStatus): string {
return statusOptions.find(item => item.value === status)?.label || "草稿"; return statusOptions.find(item => item.value === status)?.label || "草稿";
} }
@@ -344,6 +435,10 @@ function getPointActionText(action: PointAction): string {
return action === "CONSUME" ? "消耗积分" : "发放积分"; return action === "CONSUME" ? "消耗积分" : "发放积分";
} }
function getRuleModeText(ruleMode: PointRuleMode): string {
return ruleMode === "GROOVY" ? "脚本规则" : "简单规则";
}
// ---------- 生效时间 ---------- // ---------- 生效时间 ----------
const minimumDateTimeText = "1970-01-01 00:00"; const minimumDateTimeText = "1970-01-01 00:00";
@@ -405,7 +500,7 @@ function resolveTimestamp(value: string): number | undefined {
function validateForm(): boolean { function validateForm(): boolean {
if (!formModel.storeId) { if (!formModel.storeId) {
Message.warning("请选择门店"); Message.warning("当前门店不可用");
return false; return false;
} }
if (!formModel.name.trim()) { if (!formModel.name.trim()) {
@@ -416,7 +511,12 @@ function validateForm(): boolean {
Message.warning("请选择触发事件"); Message.warning("请选择触发事件");
return false; return false;
} }
if (!resolvePositiveInteger(formModel.pointValueText)) { if (formModel.ruleMode === "GROOVY") {
if (!formModel.groovyScriptId) {
Message.warning("请选择执行脚本");
return false;
}
} else if (!resolvePositiveInteger(formModel.pointValueText)) {
Message.warning("积分数量必须是正整数"); Message.warning("积分数量必须是正整数");
return false; return false;
} }
@@ -438,14 +538,14 @@ async function saveRule(): Promise<void> {
const payload: PointRule = { const payload: PointRule = {
id: formModel.id || undefined, id: formModel.id || undefined,
storeId: formModel.storeId, storeId: formModel.storeId,
ruleMode: "SIMPLE", ruleMode: formModel.ruleMode,
triggerSourceType: formModel.triggerSourceType, triggerSourceType: formModel.triggerSourceType,
triggerRefType: formModel.triggerSourceType === "EVENT_RECORD" ? "EVENT" : undefined, triggerRefType: formModel.triggerSourceType === "EVENT_RECORD" ? "EVENT" : undefined,
triggerRefId: formModel.triggerSourceType === "EVENT_RECORD" ? formModel.triggerRefId : undefined, triggerRefId: formModel.triggerSourceType === "EVENT_RECORD" ? formModel.triggerRefId : undefined,
name: formModel.name.trim(), name: formModel.name.trim(),
status: formModel.status, status: formModel.status,
pointValue: resolvePointValue() ?? null, pointValue: formModel.ruleMode === "SIMPLE" ? resolvePointValue() ?? null : null,
groovyScriptId: undefined, groovyScriptId: formModel.ruleMode === "GROOVY" ? formModel.groovyScriptId : undefined,
beginAt: resolveTimestamp(formModel.beginAtText), beginAt: resolveTimestamp(formModel.beginAtText),
endAt: resolveTimestamp(formModel.endAtText), endAt: resolveTimestamp(formModel.endAtText),
exclusive: formModel.exclusive, exclusive: formModel.exclusive,
@@ -469,17 +569,23 @@ async function saveRule(): Promise<void> {
async function fillForm(): Promise<void> { async function fillForm(): Promise<void> {
await prepareStoreScope(); await prepareStoreScope();
await loadScriptList();
if (!isCreateMode.value) { if (!isCreateMode.value) {
const current = await PointAPI.ruleDetail(routeId.value); const current = await PointAPI.ruleDetail(routeId.value);
if (current.groovyScript?.id && !scriptList.value.some(item => item.id === current.groovyScript?.id)) {
scriptList.value.push(current.groovyScript);
}
Object.assign(formModel, { Object.assign(formModel, {
id: current.id || "", id: current.id || "",
storeId: current.storeId || formModel.storeId, storeId: current.storeId || formModel.storeId,
ruleMode: current.ruleMode || "SIMPLE",
triggerSourceType: current.triggerSourceType || "EVENT_RECORD", triggerSourceType: current.triggerSourceType || "EVENT_RECORD",
triggerRefId: current.triggerRefId || "", triggerRefId: current.triggerRefId || "",
name: current.name || "", name: current.name || "",
status: current.status === "ACTIVE" ? "ACTIVE" : current.status === "INACTIVE" ? "INACTIVE" : "DRAFT", status: current.status === "ACTIVE" ? "ACTIVE" : current.status === "INACTIVE" ? "INACTIVE" : "DRAFT",
pointAction: current.pointValue !== undefined && current.pointValue !== null && current.pointValue < 0 ? "CONSUME" : "EARN", pointAction: current.pointValue !== undefined && current.pointValue !== null && current.pointValue < 0 ? "CONSUME" : "EARN",
pointValueText: current.pointValue === undefined || current.pointValue === null ? "" : String(Math.abs(current.pointValue)), pointValueText: current.pointValue === undefined || current.pointValue === null ? "" : String(Math.abs(current.pointValue)),
groovyScriptId: current.groovyScriptId || "",
beginAtText: current.beginAt ? Time.toDateTime(current.beginAt) : "", beginAtText: current.beginAt ? Time.toDateTime(current.beginAt) : "",
endAtText: current.endAt ? Time.toDateTime(current.endAt) : "", endAtText: current.endAt ? Time.toDateTime(current.endAt) : "",
exclusive: current.exclusive ?? false, exclusive: current.exclusive ?? false,
@@ -543,6 +649,13 @@ onUnmounted(() => navBarStore.clearRight());
} }
} }
.script-collapse {
:deep(.t-cell) {
padding: 0 10px;
}
}
:global(html.theme-dark) { :global(html.theme-dark) {
.point-rule-edit { .point-rule-edit {
.form { .form {
@@ -554,6 +667,10 @@ onUnmounted(() => navBarStore.clearRight());
color: #94a3b8; color: #94a3b8;
} }
} }
.script-description {
color: #94a3b8;
}
} }
} }
</style> </style>
+1 -4
View File
@@ -264,10 +264,7 @@ function getStatusTheme(status?: PointRuleStatus): "default" | "success" | "warn
} }
async function toCreate(): Promise<void> { async function toCreate(): Promise<void> {
await router.push({ await router.push({ name: "PointRuleCreate" });
name: "PointRuleCreate",
query: currentStoreStore.currentStoreId ? { storeId: currentStoreStore.currentStoreId } : undefined
});
} }
async function toSort(): Promise<void> { async function toSort(): Promise<void> {
+18
View File
@@ -12,6 +12,23 @@ export type PointRuleTriggerSourceType = "EVENT_RECORD" | "SALES_ORDER_PAID";
/** 触发对象筛选类型 */ /** 触发对象筛选类型 */
export type PointRuleTriggerRefType = "EVENT"; export type PointRuleTriggerRefType = "EVENT";
export type GroovyScript = {
bizType?: "GAO_POINT_RULE";
code?: string;
name?: string;
description?: string;
enabled?: boolean;
publishedVersionId?: string;
latest?: GroovyScriptVersion | null;
} & Model;
export type GroovyScriptVersion = {
scriptId?: string;
version?: number;
content?: string;
checksum?: string;
} & Model;
export type PointLedgerChangeType = "EARN" | "CONSUME" | "REVOKE" | "ADJUST"; export type PointLedgerChangeType = "EARN" | "CONSUME" | "REVOKE" | "ADJUST";
export type PointLedgerDirection = "INCREASE" | "DECREASE"; export type PointLedgerDirection = "INCREASE" | "DECREASE";
export type PointAction = Extract<PointLedgerChangeType, "EARN" | "CONSUME">; export type PointAction = Extract<PointLedgerChangeType, "EARN" | "CONSUME">;
@@ -81,6 +98,7 @@ export type PointRule = {
priority?: number; priority?: number;
exclusive?: boolean; exclusive?: boolean;
remark?: string; remark?: string;
groovyScript?: GroovyScript | null;
} & Model; } & Model;
export type PointAdjustRequest = { export type PointAdjustRequest = {