250 lines
6.2 KiB
Vue
250 lines
6.2 KiB
Vue
<template>
|
|
<div v-can="permissionCodes.point.accountRead" class="point-account-detail">
|
|
<card-container v-if="customer" v-entrance-transition>
|
|
<customer-profile-card :customer="customer" />
|
|
</card-container>
|
|
<section v-if="account" class="account-card" v-entrance-transition="70">
|
|
<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" v-entrance-transition="140">
|
|
<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" v-entrance-transition="210">
|
|
<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 1rem;
|
|
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 {
|
|
gap: .75rem;
|
|
margin: 0 1rem;
|
|
display: flex;
|
|
|
|
.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>
|