add point
This commit is contained in:
@@ -0,0 +1,357 @@
|
||||
<template>
|
||||
<div v-can="permissionCodes.pointRule.read" class="point-rule-list">
|
||||
<div class="filter-menu">
|
||||
<t-dropdown-menu>
|
||||
<t-dropdown-item
|
||||
v-if="isGlobalManager"
|
||||
v-model="selectedStoreId"
|
||||
:label="selectedStoreName"
|
||||
:options="storeOptions"
|
||||
@change="selectStore"
|
||||
/>
|
||||
<t-dropdown-item
|
||||
v-model="statusFilter"
|
||||
label="规则状态"
|
||||
:options="statusOptions"
|
||||
@change="selectStatus"
|
||||
/>
|
||||
</t-dropdown-menu>
|
||||
</div>
|
||||
<t-pull-down-refresh
|
||||
v-model="isRefreshing"
|
||||
:refresh-timeout="30000"
|
||||
@refresh="reload"
|
||||
@scrolltolower="load"
|
||||
>
|
||||
<t-cell-group v-if="pageResult.list.length" class="rule-list" theme="card">
|
||||
<t-cell
|
||||
v-for="item in pageResult.list"
|
||||
:key="item.id"
|
||||
class="rule-item"
|
||||
:title="item.name || '未命名规则'"
|
||||
:description="getRuleDescription(item)"
|
||||
:note="getRuleNote(item)"
|
||||
:arrow="canUpdate"
|
||||
:hover="canUpdate"
|
||||
@click="toEdit(item)"
|
||||
>
|
||||
<template #leftIcon>
|
||||
<div class="priority" v-text="item.priority ?? 0" />
|
||||
</template>
|
||||
<template #extra>
|
||||
<t-tag
|
||||
:theme="getStatusTheme(item.status)"
|
||||
variant="light-outline"
|
||||
:content="getStatusText(item.status)"
|
||||
/>
|
||||
</template>
|
||||
</t-cell>
|
||||
</t-cell-group>
|
||||
<div v-else class="empty" v-text="isLoading ? '正在加载积分规则' : '暂无积分规则'" />
|
||||
<div class="load-state" v-text="loadStateText" />
|
||||
</t-pull-down-refresh>
|
||||
<t-button v-can="permissionCodes.pointRule.create" class="create" theme="primary" @click="toCreate">
|
||||
<template #icon>
|
||||
<t-icon name="add" />
|
||||
</template>
|
||||
新增积分规则
|
||||
</t-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Toast, type DropdownValue } from "tdesign-mobile-vue";
|
||||
import EventAPI from "@/api/EventAPI";
|
||||
import PointAPI from "@/api/PointAPI";
|
||||
import { useCurrentStoreStore } from "@/store/currentStoreStore";
|
||||
import type { EventItem } from "@/types/EventItem";
|
||||
import type { PointRule, PointRuleStatus } from "@/types/Point";
|
||||
import { gaoRoleCodes, permissionCodes } from "@/utils/permission";
|
||||
import { useUserStore, type Page, type PageResult } from "timi-web";
|
||||
|
||||
const allStoresValue = "__ALL_STORES__";
|
||||
|
||||
defineOptions({
|
||||
name: "PointRuleList"
|
||||
});
|
||||
|
||||
const router = useRouter();
|
||||
const userStore = useUserStore();
|
||||
const currentStoreStore = useCurrentStoreStore();
|
||||
|
||||
// ---------- 筛选与门店 ----------
|
||||
|
||||
const isGlobalManager = computed(() => userStore.hasRoles(gaoRoleCodes.globalManager));
|
||||
const canUpdate = computed(() => userStore.hasPermissions(permissionCodes.pointRule.update));
|
||||
const selectedStoreId = ref(allStoresValue);
|
||||
const statusFilter = ref("ALL");
|
||||
const statusOptions = [
|
||||
{ label: "全部状态", value: "ALL" },
|
||||
{ label: "草稿", value: "DRAFT" },
|
||||
{ label: "启用", value: "ACTIVE" },
|
||||
{ label: "停用", value: "INACTIVE" },
|
||||
{ label: "已删除", value: "DELETED" }
|
||||
];
|
||||
const storeOptions = computed(() => [
|
||||
{ label: "全部门店", value: allStoresValue },
|
||||
...currentStoreStore.storeList
|
||||
.map(item => ({
|
||||
label: item.name || item.code || "未命名门店",
|
||||
value: item.id || ""
|
||||
}))
|
||||
.filter(item => item.value)
|
||||
]);
|
||||
const selectedStoreName = computed(() => storeOptions.value.find(item => item.value === selectedStoreId.value)?.label || "全部门店");
|
||||
|
||||
async function selectStore(value: DropdownValue): Promise<void> {
|
||||
if (Array.isArray(value)) {
|
||||
return;
|
||||
}
|
||||
selectedStoreId.value = String(value);
|
||||
await loadEventList();
|
||||
await reload();
|
||||
}
|
||||
|
||||
async function selectStatus(value: DropdownValue): Promise<void> {
|
||||
if (Array.isArray(value)) {
|
||||
return;
|
||||
}
|
||||
statusFilter.value = String(value);
|
||||
await reload();
|
||||
}
|
||||
|
||||
// ---------- 规则列表 ----------
|
||||
|
||||
const page = reactive<Page<PointRule>>({
|
||||
index: 0,
|
||||
size: 20
|
||||
});
|
||||
const pageResult = reactive<PageResult<PointRule>>({
|
||||
total: 0,
|
||||
list: []
|
||||
});
|
||||
const isLoading = ref(false);
|
||||
const isRefreshing = ref(false);
|
||||
const isNoMore = ref(false);
|
||||
let loadToken = 0;
|
||||
|
||||
const loadStateText = computed(() => {
|
||||
if (isLoading.value) {
|
||||
return "正在加载更多";
|
||||
}
|
||||
if (isNoMore.value) {
|
||||
return pageResult.list.length ? `共 ${pageResult.total} 条规则` : "";
|
||||
}
|
||||
return "上拉加载更多";
|
||||
});
|
||||
|
||||
async function reload(): Promise<void> {
|
||||
isRefreshing.value = true;
|
||||
try {
|
||||
page.index = 0;
|
||||
pageResult.total = 0;
|
||||
pageResult.list = [];
|
||||
isNoMore.value = false;
|
||||
await load();
|
||||
} finally {
|
||||
isRefreshing.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function load(): Promise<void> {
|
||||
if (isLoading.value || isNoMore.value) {
|
||||
return;
|
||||
}
|
||||
const token = loadToken + 1;
|
||||
loadToken = token;
|
||||
isLoading.value = true;
|
||||
try {
|
||||
const result = await PointAPI.ruleList(buildRequest(), buildListOptions());
|
||||
if (token !== loadToken) {
|
||||
return;
|
||||
}
|
||||
const list = result.list || [];
|
||||
pageResult.list = pageResult.list.concat(list);
|
||||
pageResult.total = result.total || 0;
|
||||
page.index += 1;
|
||||
isNoMore.value = pageResult.list.length >= pageResult.total || list.length < page.size;
|
||||
} catch {
|
||||
Toast.error("积分规则加载失败");
|
||||
} finally {
|
||||
if (token === loadToken) {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function buildRequest(): Page<PointRule> {
|
||||
return {
|
||||
index: page.index,
|
||||
size: page.size,
|
||||
equalsExample: statusFilter.value === "ALL" ? undefined : {
|
||||
status: statusFilter.value as PointRuleStatus
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function buildListOptions(): { allStores?: boolean; storeId?: string } | undefined {
|
||||
if (!isGlobalManager.value) {
|
||||
return undefined;
|
||||
}
|
||||
return selectedStoreId.value === allStoresValue ? { allStores: true } : { storeId: selectedStoreId.value };
|
||||
}
|
||||
|
||||
// ---------- 事件名称 ----------
|
||||
|
||||
const eventList = ref<EventItem[]>([]);
|
||||
const eventNameMap = computed(() => new Map(eventList.value.filter(item => item.id).map(item => [item.id as string, item.name])));
|
||||
|
||||
async function loadEventList(): Promise<void> {
|
||||
try {
|
||||
const options = buildListOptions();
|
||||
const result = await EventAPI.list({ index: 0, size: 1000 }, options);
|
||||
eventList.value = result.list || [];
|
||||
} catch {
|
||||
eventList.value = [];
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- 展示与导航 ----------
|
||||
|
||||
function getRuleDescription(rule: PointRule): string {
|
||||
const eventName = rule.eventId ? eventNameMap.value.get(rule.eventId) || `事件 ${rule.eventId}` : "未绑定事件";
|
||||
const triggerText = getTriggerText(rule);
|
||||
const pointText = rule.pointMode === "SOURCE_VALUE" ? "使用登记时填写的积分" : `${rule.pointValue ?? 0} 分`;
|
||||
return `${eventName}|${triggerText}|${pointText}`;
|
||||
}
|
||||
|
||||
function getRuleNote(rule: PointRule): string {
|
||||
const priority = `优先级 ${rule.priority ?? 0}`;
|
||||
return rule.exclusive ? `${priority}|命中后停止匹配` : priority;
|
||||
}
|
||||
|
||||
function getTriggerText(rule: PointRule): string {
|
||||
const map: Record<string, string> = {
|
||||
EVERY_TIME: "每次登记",
|
||||
FIRST_TIME: "首次登记",
|
||||
AFTER_COUNT_EVERY_TIME: `达到 ${rule.conditionJson?.threshold ?? 0} 次后每次登记`,
|
||||
SOURCE_VALUE: "登记时自定义积分"
|
||||
};
|
||||
return map[rule.triggerType || ""] || "未知触发类型";
|
||||
}
|
||||
|
||||
function getStatusText(status?: PointRuleStatus): string {
|
||||
return ({
|
||||
DRAFT: "草稿",
|
||||
ACTIVE: "启用",
|
||||
INACTIVE: "停用",
|
||||
DELETED: "已删除"
|
||||
} as Record<string, string>)[status || ""] || "未知";
|
||||
}
|
||||
|
||||
function getStatusTheme(status?: PointRuleStatus): "default" | "success" | "warning" | "danger" {
|
||||
if (status === "ACTIVE") {
|
||||
return "success";
|
||||
}
|
||||
if (status === "INACTIVE") {
|
||||
return "warning";
|
||||
}
|
||||
if (status === "DELETED") {
|
||||
return "danger";
|
||||
}
|
||||
return "default";
|
||||
}
|
||||
|
||||
async function toCreate(): Promise<void> {
|
||||
await router.push({
|
||||
name: "PointRuleCreate",
|
||||
query: selectedStoreId.value === allStoresValue ? undefined : { storeId: selectedStoreId.value }
|
||||
});
|
||||
}
|
||||
|
||||
async function toEdit(rule: PointRule): Promise<void> {
|
||||
if (!canUpdate.value || !rule.id) {
|
||||
return;
|
||||
}
|
||||
await router.push(`/management/points/rules/${rule.id}/edit`);
|
||||
}
|
||||
|
||||
async function preparePage(): Promise<void> {
|
||||
if (isGlobalManager.value) {
|
||||
await currentStoreStore.loadStoreList();
|
||||
selectedStoreId.value = allStoresValue;
|
||||
} else {
|
||||
const store = await currentStoreStore.loadLoginStore();
|
||||
selectedStoreId.value = store?.id || "";
|
||||
}
|
||||
await loadEventList();
|
||||
await reload();
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
await preparePage();
|
||||
} catch {
|
||||
Toast.error("积分规则页面加载失败");
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.point-rule-list {
|
||||
padding: calc(var(--app-nav-height) + 4rem) 0 6rem;
|
||||
|
||||
.filter-menu {
|
||||
top: var(--app-nav-height);
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 20;
|
||||
position: fixed;
|
||||
background: var(--td-bg-color-container);
|
||||
}
|
||||
|
||||
.rule-list {
|
||||
margin: 0 1rem;
|
||||
|
||||
.rule-item {
|
||||
.priority {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
display: flex;
|
||||
color: #2563eb;
|
||||
background: #dbeafe;
|
||||
border-radius: .75rem;
|
||||
align-items: center;
|
||||
font-size: .875rem;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.empty {
|
||||
color: #52607a;
|
||||
padding: 2rem 1rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.create {
|
||||
left: 1rem;
|
||||
right: 1rem;
|
||||
bottom: calc(.75rem + env(safe-area-inset-bottom, 0));
|
||||
position: fixed;
|
||||
}
|
||||
}
|
||||
|
||||
:global(html.theme-dark) {
|
||||
.point-rule-list {
|
||||
.empty {
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
.rule-list .priority {
|
||||
color: #bfdbfe;
|
||||
background: rgba(30, 64, 175, .45);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user