add point rule sort
This commit is contained in:
@@ -78,6 +78,10 @@ async function ruleUpdate(rule: PointRule): Promise<void> {
|
|||||||
await axios.post(`${BASE_URI}/rule/update`, withCurrentStoreId(rule));
|
await axios.post(`${BASE_URI}/rule/update`, withCurrentStoreId(rule));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function ruleSort(idList: string[]): Promise<void> {
|
||||||
|
await axios.post(`${BASE_URI}/rule/sort`, idList);
|
||||||
|
}
|
||||||
|
|
||||||
async function ruleRemove(id: string): Promise<void> {
|
async function ruleRemove(id: string): Promise<void> {
|
||||||
await axios.post(`${BASE_URI}/rule/delete`, undefined, {
|
await axios.post(`${BASE_URI}/rule/delete`, undefined, {
|
||||||
params: { id }
|
params: { id }
|
||||||
@@ -92,5 +96,6 @@ export default {
|
|||||||
ruleDetail,
|
ruleDetail,
|
||||||
ruleCreate,
|
ruleCreate,
|
||||||
ruleUpdate,
|
ruleUpdate,
|
||||||
|
ruleSort,
|
||||||
ruleRemove
|
ruleRemove
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -44,15 +44,6 @@
|
|||||||
</t-cell-group>
|
</t-cell-group>
|
||||||
<div v-else class="empty-block" v-text="isLoading ? '正在加载登记事件' : '暂无登记事件'" />
|
<div v-else class="empty-block" v-text="isLoading ? '正在加载登记事件' : '暂无登记事件'" />
|
||||||
</div>
|
</div>
|
||||||
<t-button
|
|
||||||
class="save"
|
|
||||||
theme="primary"
|
|
||||||
:loading="isSaving"
|
|
||||||
:disabled="!sortDirty || isSaving || isLoading"
|
|
||||||
@click="saveSort"
|
|
||||||
>
|
|
||||||
保存排序
|
|
||||||
</t-button>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -151,6 +142,7 @@ async function loadData(): Promise<void> {
|
|||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
navBarStore.clearRight();
|
navBarStore.clearRight();
|
||||||
|
navBarStore.setRightText("保存", "var(--td-brand-color)", saveSort);
|
||||||
try {
|
try {
|
||||||
await loadData();
|
await loadData();
|
||||||
} catch {
|
} catch {
|
||||||
@@ -218,12 +210,6 @@ onUnmounted(() => {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.save {
|
|
||||||
left: 1rem;
|
|
||||||
right: 1rem;
|
|
||||||
bottom: calc(.75rem + env(safe-area-inset-bottom, 0));
|
|
||||||
position: fixed;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
:global(html.theme-dark) {
|
:global(html.theme-dark) {
|
||||||
|
|||||||
@@ -83,9 +83,6 @@
|
|||||||
</t-form-item>
|
</t-form-item>
|
||||||
</card-container>
|
</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>
|
|
||||||
<t-form-item label="独占匹配" content-align="right">
|
<t-form-item label="独占匹配" content-align="right">
|
||||||
<t-switch size="small" v-model="formModel.exclusive" />
|
<t-switch size="small" v-model="formModel.exclusive" />
|
||||||
</t-form-item>
|
</t-form-item>
|
||||||
@@ -248,7 +245,6 @@ const formModel = reactive({
|
|||||||
tiers: [] as PointRuleTierForm[],
|
tiers: [] as PointRuleTierForm[],
|
||||||
beginAtText: "",
|
beginAtText: "",
|
||||||
endAtText: "",
|
endAtText: "",
|
||||||
priorityText: "0",
|
|
||||||
exclusive: false,
|
exclusive: false,
|
||||||
remark: ""
|
remark: ""
|
||||||
});
|
});
|
||||||
@@ -696,11 +692,6 @@ function validateForm(): boolean {
|
|||||||
Toast.warning("生效结束时间不能早于开始时间");
|
Toast.warning("生效结束时间不能早于开始时间");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const priority = Number(formModel.priorityText);
|
|
||||||
if (!Number.isSafeInteger(priority) || 0 > priority) {
|
|
||||||
Toast.warning("优先级必须是大于等于 0 的整数");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -722,7 +713,6 @@ async function saveRule(): Promise<void> {
|
|||||||
conditionJson: buildCondition(),
|
conditionJson: buildCondition(),
|
||||||
beginAt: resolveTimestamp(formModel.beginAtText),
|
beginAt: resolveTimestamp(formModel.beginAtText),
|
||||||
endAt: resolveTimestamp(formModel.endAtText),
|
endAt: resolveTimestamp(formModel.endAtText),
|
||||||
priority: Number(formModel.priorityText),
|
|
||||||
exclusive: formModel.exclusive,
|
exclusive: formModel.exclusive,
|
||||||
remark: formModel.remark.trim()
|
remark: formModel.remark.trim()
|
||||||
};
|
};
|
||||||
@@ -803,7 +793,6 @@ async function fillForm(): Promise<void> {
|
|||||||
tiers: [],
|
tiers: [],
|
||||||
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) : "",
|
||||||
priorityText: String(current.priority ?? 0),
|
|
||||||
exclusive: current.exclusive ?? false,
|
exclusive: current.exclusive ?? false,
|
||||||
remark: current.remark || ""
|
remark: current.remark || ""
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ import { type DropdownValue, Toast } from "tdesign-mobile-vue";
|
|||||||
import EventAPI from "@/api/EventAPI";
|
import EventAPI from "@/api/EventAPI";
|
||||||
import PointAPI from "@/api/PointAPI";
|
import PointAPI from "@/api/PointAPI";
|
||||||
import { useCurrentStoreStore } from "@/store/currentStoreStore";
|
import { useCurrentStoreStore } from "@/store/currentStoreStore";
|
||||||
|
import { useNavBarStore } from "@/store/navBarStore";
|
||||||
import type { EventItem } from "@/types/EventItem";
|
import type { EventItem } from "@/types/EventItem";
|
||||||
import type { PointRule, PointRuleStatus } from "@/types/Point";
|
import type { PointRule, PointRuleStatus } from "@/types/Point";
|
||||||
import { gaoRoleCodes, permissionCodes } from "@/utils/permission";
|
import { gaoRoleCodes, permissionCodes } from "@/utils/permission";
|
||||||
@@ -85,6 +86,7 @@ defineOptions({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const navBarStore = useNavBarStore();
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
const currentStoreStore = useCurrentStoreStore();
|
const currentStoreStore = useCurrentStoreStore();
|
||||||
|
|
||||||
@@ -297,12 +299,20 @@ async function preparePage(): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
navBarStore.clearRight();
|
||||||
|
if (userStore.hasPermissions(permissionCodes.pointRule.update)) {
|
||||||
|
navBarStore.setRightText("排序", "var(--td-brand-color)", () => {
|
||||||
|
router.push("/management/points/rules/sort");
|
||||||
|
});
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
await preparePage();
|
await preparePage();
|
||||||
} catch {
|
} catch {
|
||||||
Toast.error("积分规则页面加载失败");
|
Toast.error("积分规则页面加载失败");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => navBarStore.clearRight());
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="less">
|
<style scoped lang="less">
|
||||||
|
|||||||
@@ -0,0 +1,161 @@
|
|||||||
|
<template>
|
||||||
|
<div v-can="permissionCodes.pointRule.update" class="point-rule-sort">
|
||||||
|
<div class="tip" v-text="'按住右侧拖动手柄调整规则顺序,保存后生效;仅显示启用规则'" />
|
||||||
|
<div class="items">
|
||||||
|
<t-cell-group v-if="ruleList.length" theme="card">
|
||||||
|
<vue-draggable-next
|
||||||
|
v-model="ruleList"
|
||||||
|
class="draggable"
|
||||||
|
item-key="id"
|
||||||
|
handle=".handle"
|
||||||
|
ghost-class="ghost"
|
||||||
|
chosen-class="chosen"
|
||||||
|
drag-class="dragging"
|
||||||
|
:animation="180"
|
||||||
|
:disabled="isLoading || isSaving"
|
||||||
|
@change="sortDirty = true"
|
||||||
|
>
|
||||||
|
<t-cell v-for="item in ruleList" :key="item.id" class="cell">
|
||||||
|
<template #title>
|
||||||
|
<div class="title" v-text="item.name || '未命名规则'" />
|
||||||
|
</template>
|
||||||
|
<template #description>
|
||||||
|
<div class="description">
|
||||||
|
<span v-text="'积分值:' + getPointText(item)" />
|
||||||
|
<span v-text="'登记事件:' + (eventNameMap.get(item.eventId || '') || '未绑定事件')" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #note>
|
||||||
|
<t-tag :theme="item.exclusive ? 'warning' : 'default'" variant="outline" :content="item.exclusive ? '独占匹配' : '继续匹配'" />
|
||||||
|
</template>
|
||||||
|
<template #rightIcon>
|
||||||
|
<t-icon class="handle" role="button" name="move" aria-label="拖动排序" />
|
||||||
|
</template>
|
||||||
|
</t-cell>
|
||||||
|
</vue-draggable-next>
|
||||||
|
</t-cell-group>
|
||||||
|
<div v-else class="empty-block" v-text="isLoading ? '正在加载启用规则' : '暂无启用规则'" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Toast } from "tdesign-mobile-vue";
|
||||||
|
import { VueDraggableNext } from "vue-draggable-next";
|
||||||
|
import EventAPI from "@/api/EventAPI";
|
||||||
|
import PointAPI from "@/api/PointAPI";
|
||||||
|
import { useNavBarStore } from "@/store/navBarStore";
|
||||||
|
import type { EventItem } from "@/types/EventItem";
|
||||||
|
import type { PointRule } from "@/types/Point";
|
||||||
|
import { permissionCodes } from "@/utils/permission";
|
||||||
|
|
||||||
|
defineOptions({ name: "PointRuleSort" });
|
||||||
|
|
||||||
|
const navBarStore = useNavBarStore();
|
||||||
|
const router = useRouter();
|
||||||
|
const ruleList = ref<PointRule[]>([]);
|
||||||
|
const eventList = ref<EventItem[]>([]);
|
||||||
|
const isLoading = ref(false);
|
||||||
|
const isSaving = ref(false);
|
||||||
|
const sortDirty = ref(false);
|
||||||
|
const eventNameMap = computed(() => new Map(eventList.value.filter(item => item.id).map(item => [item.id as string, item.name])));
|
||||||
|
|
||||||
|
function getPointText(rule: PointRule): string {
|
||||||
|
if (rule.pointMode === "TIER") {
|
||||||
|
return "分档";
|
||||||
|
}
|
||||||
|
if (rule.pointValue === undefined || rule.pointValue === null) {
|
||||||
|
return "按登记值";
|
||||||
|
}
|
||||||
|
return (0 < rule.pointValue ? "+" : "") + rule.pointValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadData(): Promise<void> {
|
||||||
|
isLoading.value = true;
|
||||||
|
try {
|
||||||
|
const [ruleResult, eventResult] = await Promise.all([
|
||||||
|
PointAPI.ruleList({ index: 0, size: 1000, equalsExample: { status: "ACTIVE" } }),
|
||||||
|
EventAPI.list({ index: 0, size: 1000 })
|
||||||
|
]);
|
||||||
|
ruleList.value = ruleResult.list || [];
|
||||||
|
eventList.value = eventResult.list || [];
|
||||||
|
sortDirty.value = false;
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveSort(): Promise<void> {
|
||||||
|
if (!sortDirty.value || isSaving.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
isSaving.value = true;
|
||||||
|
try {
|
||||||
|
const idList = ruleList.value.map(item => item.id).filter((id): id is string => Boolean(id));
|
||||||
|
await PointAPI.ruleSort(idList);
|
||||||
|
ruleList.value.forEach((item, index) => { item.priority = index; });
|
||||||
|
sortDirty.value = false;
|
||||||
|
Toast.success("积分规则排序已保存");
|
||||||
|
await router.back();
|
||||||
|
} catch {
|
||||||
|
Toast.error("积分规则排序保存失败");
|
||||||
|
} finally {
|
||||||
|
isSaving.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
navBarStore.clearRight();
|
||||||
|
navBarStore.setRightText("保存", "var(--td-brand-color)", saveSort);
|
||||||
|
try {
|
||||||
|
await loadData();
|
||||||
|
} catch {
|
||||||
|
Toast.error("启用积分规则加载失败");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => navBarStore.clearRight());
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.point-rule-sort {
|
||||||
|
padding: calc(var(--app-nav-height) + 1rem) 0 6rem;
|
||||||
|
|
||||||
|
.tip,
|
||||||
|
.empty-block {
|
||||||
|
color: #52607a;
|
||||||
|
padding: 0 1rem .75rem;
|
||||||
|
font-size: .8125rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-block {
|
||||||
|
padding-top: 2rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.draggable {
|
||||||
|
.cell {
|
||||||
|
.title { font-weight: 600; }
|
||||||
|
.description {
|
||||||
|
display: flex;
|
||||||
|
gap: .75rem;
|
||||||
|
font-size: .75rem;
|
||||||
|
}
|
||||||
|
.handle { color: #52607a; cursor: grab; touch-action: none; }
|
||||||
|
}
|
||||||
|
.ghost { opacity: .4; }
|
||||||
|
.chosen,
|
||||||
|
.dragging { z-index: 1; box-shadow: 0 .5rem 1rem rgba(15, 23, 42, .16); }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(html.theme-dark) {
|
||||||
|
.point-rule-sort {
|
||||||
|
.tip,
|
||||||
|
.empty-block,
|
||||||
|
.handle { color: #94a3b8; }
|
||||||
|
.draggable .cell { background: rgba(15, 23, 42, .88); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -42,15 +42,6 @@
|
|||||||
</t-cell-group>
|
</t-cell-group>
|
||||||
<div v-else class="empty-block" v-text="isLoading ? '正在加载门店' : '暂无门店'" />
|
<div v-else class="empty-block" v-text="isLoading ? '正在加载门店' : '暂无门店'" />
|
||||||
</div>
|
</div>
|
||||||
<t-button
|
|
||||||
class="save"
|
|
||||||
theme="primary"
|
|
||||||
:loading="isSaving"
|
|
||||||
:disabled="!sortDirty || isSaving || isLoading"
|
|
||||||
@click="saveSort"
|
|
||||||
>
|
|
||||||
保存排序
|
|
||||||
</t-button>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -140,6 +131,7 @@ async function loadData(): Promise<void> {
|
|||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
navBarStore.clearRight();
|
navBarStore.clearRight();
|
||||||
|
navBarStore.setRightText("保存", "var(--td-brand-color)", saveSort);
|
||||||
try {
|
try {
|
||||||
await loadData();
|
await loadData();
|
||||||
} catch {
|
} catch {
|
||||||
@@ -192,12 +184,6 @@ onUnmounted(() => {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.save {
|
|
||||||
left: 1rem;
|
|
||||||
right: 1rem;
|
|
||||||
bottom: calc(.75rem + env(safe-area-inset-bottom, 0));
|
|
||||||
position: fixed;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
:global(html.theme-dark) {
|
:global(html.theme-dark) {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import PointLedgerDetail from "@/pages/point/PointLedgerDetail.vue";
|
|||||||
import PointLedgerList from "@/pages/point/PointLedgerList.vue";
|
import PointLedgerList from "@/pages/point/PointLedgerList.vue";
|
||||||
import PointRuleEdit from "@/pages/point/PointRuleEdit.vue";
|
import PointRuleEdit from "@/pages/point/PointRuleEdit.vue";
|
||||||
import PointRuleList from "@/pages/point/PointRuleList.vue";
|
import PointRuleList from "@/pages/point/PointRuleList.vue";
|
||||||
|
import PointRuleSort from "@/pages/point/PointRuleSort.vue";
|
||||||
|
|
||||||
const pointRoutes: RouteRecordRaw[] = [
|
const pointRoutes: RouteRecordRaw[] = [
|
||||||
{
|
{
|
||||||
@@ -21,6 +22,20 @@ const pointRoutes: RouteRecordRaw[] = [
|
|||||||
darkBodyBackground: "#10161f"
|
darkBodyBackground: "#10161f"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "/management/points/rules/sort",
|
||||||
|
name: "PointRuleSort",
|
||||||
|
component: PointRuleSort,
|
||||||
|
meta: {
|
||||||
|
depth: 4,
|
||||||
|
navBarVisible: true,
|
||||||
|
navBarCanBack: true,
|
||||||
|
navBarTitle: "积分规则排序",
|
||||||
|
tabBarVisible: false,
|
||||||
|
bodyBackground: "#F5F5F8",
|
||||||
|
darkBodyBackground: "#10161f"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "/management/points/ledger",
|
path: "/management/points/ledger",
|
||||||
name: "PointLedgerList",
|
name: "PointLedgerList",
|
||||||
|
|||||||
Reference in New Issue
Block a user