add point account list
This commit is contained in:
@@ -0,0 +1,256 @@
|
||||
<template>
|
||||
<div v-can="permissionCodes.point.accountRead" class="point-account-detail">
|
||||
<card-container v-if="customer">
|
||||
<customer-profile-card :customer="customer" />
|
||||
</card-container>
|
||||
<section v-if="account" class="account-card">
|
||||
<div class="header">
|
||||
<div>积分卡</div>
|
||||
<div class="time dark-white" v-text="formatTimestamp(account.createdAt!)" />
|
||||
</div>
|
||||
<div class="balance">
|
||||
<t-icon name="money" size="2rem" />
|
||||
<div class="value" v-text="balanceText" />
|
||||
</div>
|
||||
<div class="id" v-text="account.id?.toUpperCase()" />
|
||||
</section>
|
||||
|
||||
<t-cell-group v-if="account" title="积分统计" theme="card">
|
||||
<t-cell title="累计获得" :note="formatPoints(account.totalEarned)" />
|
||||
<t-cell title="累计消耗" :note="formatPoints(account.totalConsumed)" />
|
||||
<t-cell title="累计撤销" :note="formatPoints(account.totalRevoked)" />
|
||||
<t-cell title="人工调整" :note="formatSignedPoints(account.totalAdjusted)" />
|
||||
</t-cell-group>
|
||||
|
||||
<section v-if="customer" class="actions">
|
||||
<t-button
|
||||
v-can="permissionCodes.point.ledgerRead"
|
||||
class="bg-white"
|
||||
theme="primary"
|
||||
variant="outline"
|
||||
@click="toLedger"
|
||||
>
|
||||
查看积分流水
|
||||
</t-button>
|
||||
<t-button
|
||||
v-can="permissionCodes.point.adjust"
|
||||
theme="primary"
|
||||
@click="toAdjust"
|
||||
>
|
||||
人工调整积分
|
||||
</t-button>
|
||||
</section>
|
||||
|
||||
<t-empty v-if="!account" :description="isLoading ? '正在加载积分账户' : '积分账户不存在或已被删除'" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Message } from "@/utils/message";
|
||||
import { useUserStore } from "timi-web";
|
||||
import CustomerAPI from "@/api/CustomerAPI";
|
||||
import PointAPI from "@/api/PointAPI";
|
||||
import CustomerProfileCard from "@/components/CustomerProfileCard.vue";
|
||||
import { useCurrentStoreStore } from "@/store/currentStoreStore";
|
||||
import type { Customer } from "@/types/Customer";
|
||||
import type { PointAccount as PointAccountData } from "@/types/Point";
|
||||
import { gaoRoleCodes, permissionCodes } from "@/utils/permission";
|
||||
|
||||
defineOptions({
|
||||
name: "PointAccountDetail"
|
||||
});
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const userStore = useUserStore();
|
||||
const currentStoreStore = useCurrentStoreStore();
|
||||
// ---------- 门店 ----------
|
||||
|
||||
const isGlobalManager = computed(() => userStore.hasRoles(gaoRoleCodes.globalManager));
|
||||
|
||||
async function prepareStoreScope(): Promise<void> {
|
||||
if (isGlobalManager.value) {
|
||||
await currentStoreStore.loadStoreList();
|
||||
return;
|
||||
}
|
||||
await currentStoreStore.loadLoginStore();
|
||||
}
|
||||
|
||||
// ---------- 账户 ----------
|
||||
|
||||
const accountId = String(route.params.id || "");
|
||||
const customer = ref<Customer>();
|
||||
const account = ref<PointAccountData>();
|
||||
const isLoading = ref(false);
|
||||
|
||||
const balanceText = computed(() => formatPoints(account.value?.balance));
|
||||
|
||||
async function loadAccount(): Promise<void> {
|
||||
if (!accountId) {
|
||||
customer.value = undefined;
|
||||
account.value = undefined;
|
||||
return;
|
||||
}
|
||||
|
||||
isLoading.value = true;
|
||||
try {
|
||||
account.value = await PointAPI.detail(
|
||||
accountId,
|
||||
String(route.query.storeId || currentStoreStore.currentStoreId || "")
|
||||
);
|
||||
if (!account.value) {
|
||||
customer.value = undefined;
|
||||
return;
|
||||
}
|
||||
customer.value = account.value.customer || (account.value.customerId ? await CustomerAPI.detail(account.value.customerId) : undefined);
|
||||
} catch {
|
||||
customer.value = undefined;
|
||||
account.value = undefined;
|
||||
Message.error("积分账户加载失败");
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- 导航 ----------
|
||||
|
||||
async function toLedger(): Promise<void> {
|
||||
if (!account.value?.customerId) {
|
||||
return;
|
||||
}
|
||||
await router.push({
|
||||
name: "PointLedgerList",
|
||||
query: {
|
||||
customerId: account.value.customerId,
|
||||
accountId: account.value.id || "",
|
||||
transitionDepth: String(getCurrentRouteDepth() + 1)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getCurrentRouteDepth(): number {
|
||||
const transitionDepth = Array.isArray(route.query.transitionDepth)
|
||||
? route.query.transitionDepth[0]
|
||||
: route.query.transitionDepth;
|
||||
const depth = Number(transitionDepth);
|
||||
if (transitionDepth && Number.isInteger(depth) && 0 <= depth && depth <= 20) {
|
||||
return depth;
|
||||
}
|
||||
return Number(route.meta.depth ?? 0);
|
||||
}
|
||||
|
||||
async function toAdjust(): Promise<void> {
|
||||
if (!account.value?.customerId) {
|
||||
return;
|
||||
}
|
||||
await router.push({
|
||||
name: "PointAdjust",
|
||||
query: { customerId: account.value.customerId }
|
||||
});
|
||||
}
|
||||
|
||||
// ---------- 展示 ----------
|
||||
|
||||
function formatPoints(value?: number): string {
|
||||
return `${(value ?? 0).toLocaleString("zh-CN")} 分`;
|
||||
}
|
||||
|
||||
function formatTimestamp(timestamp?: number): string {
|
||||
if (!timestamp) {
|
||||
return "未知时间";
|
||||
}
|
||||
const date = new Date(timestamp);
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, "0");
|
||||
const day = String(date.getDate()).padStart(2, "0");
|
||||
return `${year}${month}${day}`;
|
||||
}
|
||||
|
||||
function formatSignedPoints(value?: number): string {
|
||||
const pointValue = value ?? 0;
|
||||
return pointValue < 0 ? `扣除 ${Math.abs(pointValue).toLocaleString("zh-CN")} 分` : `增加 ${pointValue.toLocaleString("zh-CN")} 分`;
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
await prepareStoreScope();
|
||||
await loadAccount();
|
||||
} catch {
|
||||
Message.error("积分账户页面加载失败");
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.point-account-detail {
|
||||
gap: 1rem;
|
||||
display: flex;
|
||||
padding: calc(var(--app-nav-height) + 1rem) 0 calc(6rem + env(safe-area-inset-bottom, 0));
|
||||
flex-direction: column;
|
||||
|
||||
.account-card {
|
||||
color: #FFF;
|
||||
margin: 0 1rem;
|
||||
padding: 1rem;
|
||||
background: rgba(255, 255, 255, .92);
|
||||
box-shadow: var(--td-shadow-1);
|
||||
border-radius: 1.4rem;
|
||||
background: linear-gradient(135deg, #0f766e, #0f4c5c);
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.balance {
|
||||
gap: 1rem;
|
||||
margin: 2rem 0 1rem 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: end;
|
||||
|
||||
.value {
|
||||
font-size: 2rem;
|
||||
text-align: right;
|
||||
font-weight: 700;
|
||||
letter-spacing: .03em;
|
||||
}
|
||||
}
|
||||
|
||||
.id {
|
||||
color: rgba(255, 255, 255, .72);
|
||||
font-size: .75rem;
|
||||
}
|
||||
|
||||
.time {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
left: 1rem;
|
||||
right: 1rem;
|
||||
gap: .75rem;
|
||||
z-index: 20;
|
||||
bottom: calc(.75rem + env(safe-area-inset-bottom, 0));
|
||||
display: flex;
|
||||
position: fixed;
|
||||
|
||||
.t-button {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.t-empty {
|
||||
margin: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
:global(html.theme-dark) {
|
||||
.point-account-detail {
|
||||
.account-card {
|
||||
box-shadow: 0 .75rem 2rem rgba(2, 6, 23, .32);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user