update point account style

This commit is contained in:
Timi
2026-08-13 00:04:57 +08:00
parent b4ba0062a9
commit 206d2cbc81
3 changed files with 172 additions and 67 deletions
+1 -1
View File
@@ -52,9 +52,9 @@ defineOptions({
.tips { .tips {
color: #52607a; color: #52607a;
padding: .25rem 1rem;
font-size: .8125rem; font-size: .8125rem;
line-height: 1.5; line-height: 1.5;
padding: .25rem 1rem;
} }
} }
-1
View File
@@ -35,7 +35,6 @@ defineOptions({
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
.card-title { .card-title {
margin: 1rem;
.title { .title {
margin: 0; margin: 0;
+160 -54
View File
@@ -1,33 +1,61 @@
<template> <template>
<div v-can="permissionCodes.point.accountRead" class="point-account"> <div v-can="permissionCodes.point.accountRead" class="point-account">
<section class="selector-panel"> <div class="header bg-white">
<div class="section-title">选择客户</div> <div class="buttons">
<t-button <t-button
v-if="isGlobalManager" v-if="isGlobalManager"
class="customer-trigger" class="selector"
size="small"
theme="primary"
variant="outline" variant="outline"
:content="selectedStoreName" :content="selectedStoreName"
@click="openStorePicker" @click="openStorePicker"
/> />
<t-button <t-button
class="customer-trigger" class="selector"
size="small"
theme="primary"
variant="outline" variant="outline"
:content="customerText"
@click="selectorVisible = true" @click="selectorVisible = true"
/> >
<customer-profile-card v-if="customer" :customer="customer" /> <template #icon>
</section> <t-icon name="user-search" />
</template>
<section v-if="customer && account" class="account-card"> <span>选择客户</span>
<div class="account-caption">当前可用积分</div> </t-button>
<div class="balance" v-text="balanceText" /> <t-button size="small" theme="primary" @click="toggleScanner">
<div class="account-meta"> <template #icon>
<span v-text="`账户版本 ${account.version ?? '-'}`" /> <t-icon name="scan" />
<span v-text="account.storeId || '当前门店'" /> </template>
<span v-text="scannerVisible ? '关闭扫码' : '扫码'" />
</t-button>
</div> </div>
<div v-if="scannerVisible" class="line scanner-wrapper">
<code-scanner
visible
:status-text="scanStatus"
active-text="正在识别客户编码,请将二维码移入取景框"
@decode="handleDecodedText"
@status-change="handleScanStatusChange"
/>
</div>
</div>
<card-container>
<customer-profile-card :customer="customer" />
</card-container>
<section v-if="customer && 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> </section>
<t-cell-group v-if="customer && account" class="statistics" title="积分统计" theme="card"> <t-cell-group v-if="customer && account" title="积分统计" theme="card">
<t-cell title="累计获得" :note="formatPoints(account.totalEarned)" /> <t-cell title="累计获得" :note="formatPoints(account.totalEarned)" />
<t-cell title="累计撤销" :note="formatPoints(account.totalRevoked)" /> <t-cell title="累计撤销" :note="formatPoints(account.totalRevoked)" />
<t-cell title="人工调整" :note="formatSignedPoints(account.totalAdjusted)" /> <t-cell title="人工调整" :note="formatSignedPoints(account.totalAdjusted)" />
@@ -36,6 +64,8 @@
<section v-if="customer" class="actions"> <section v-if="customer" class="actions">
<t-button <t-button
v-can="permissionCodes.point.ledgerRead" v-can="permissionCodes.point.ledgerRead"
class="bg-white"
theme="primary"
variant="outline" variant="outline"
@click="toLedger" @click="toLedger"
> >
@@ -78,8 +108,10 @@ import { Toast } from "tdesign-mobile-vue";
import { useUserStore } from "timi-web"; import { useUserStore } from "timi-web";
import CustomerAPI from "@/api/CustomerAPI"; import CustomerAPI from "@/api/CustomerAPI";
import PointAPI from "@/api/PointAPI"; import PointAPI from "@/api/PointAPI";
import CodeScanner from "@/components/CodeScanner.vue";
import CustomerProfileCard from "@/components/CustomerProfileCard.vue"; import CustomerProfileCard from "@/components/CustomerProfileCard.vue";
import CustomerSelector from "@/components/CustomerSelector.vue"; import CustomerSelector from "@/components/CustomerSelector.vue";
import { useSettingStore } from "@/store/settingStore";
import { useCurrentStoreStore } from "@/store/currentStoreStore"; import { useCurrentStoreStore } from "@/store/currentStoreStore";
import type { Customer } from "@/types/Customer"; import type { Customer } from "@/types/Customer";
import type { PointAccount as PointAccountData } from "@/types/Point"; import type { PointAccount as PointAccountData } from "@/types/Point";
@@ -95,6 +127,7 @@ const route = useRoute();
const router = useRouter(); const router = useRouter();
const userStore = useUserStore(); const userStore = useUserStore();
const currentStoreStore = useCurrentStoreStore(); const currentStoreStore = useCurrentStoreStore();
const settingStore = useSettingStore();
// ---------- 门店 ---------- // ---------- 门店 ----------
@@ -155,7 +188,6 @@ const account = ref<PointAccountData>();
const selectorVisible = ref(false); const selectorVisible = ref(false);
const isLoading = ref(false); const isLoading = ref(false);
const customerText = computed(() => customer.value?.name || customer.value?.code || "请选择客户");
const balanceText = computed(() => formatPoints(account.value?.balance)); const balanceText = computed(() => formatPoints(account.value?.balance));
async function handleCustomerChange(value: string | string[]): Promise<void> { async function handleCustomerChange(value: string | string[]): Promise<void> {
@@ -167,6 +199,33 @@ async function handleCustomerChange(value: string | string[]): Promise<void> {
await loadAccount(); await loadAccount();
} }
async function loadCustomerByCode(code: string): Promise<void> {
const normalizedCode = code.trim();
if (!normalizedCode) {
return;
}
try {
const matched = await CustomerAPI.findByCode(normalizedCode);
if (!matched?.id) {
Toast.warning("未找到客户");
return;
}
selectedCustomerId.value = matched.id;
customer.value = matched;
await router.replace({
name: "PointAccount",
query: {
customerId: selectedCustomerId.value,
storeId: selectedStoreId.value
}
});
await loadAccount();
} catch {
Toast.error("客户信息加载失败");
}
}
async function loadAccount(): Promise<void> { async function loadAccount(): Promise<void> {
const customerId = selectedCustomerId.value; const customerId = selectedCustomerId.value;
if (!customerId) { if (!customerId) {
@@ -192,6 +251,36 @@ async function loadAccount(): Promise<void> {
} }
} }
// ---------- 扫码 ----------
const scannerVisible = ref(false);
const scanStatus = ref("点击扫码开始识别");
let latestDecodedText = "";
async function toggleScanner(): Promise<void> {
scannerVisible.value = !scannerVisible.value;
if (scannerVisible.value) {
latestDecodedText = "";
scanStatus.value = "正在启动扫码预览";
}
}
async function handleDecodedText(rawText: string): Promise<void> {
const normalizedText = rawText.trim();
if (!normalizedText || normalizedText === latestDecodedText) {
return;
}
latestDecodedText = normalizedText;
await settingStore.playScanSuccessSound();
scanStatus.value = `已识别 ${normalizedText},正在加载客户档案`;
scannerVisible.value = false;
await loadCustomerByCode(normalizedText);
}
function handleScanStatusChange(value: string): void {
scanStatus.value = value;
}
// ---------- 导航 ---------- // ---------- 导航 ----------
async function toLedger(): Promise<void> { async function toLedger(): Promise<void> {
@@ -224,6 +313,14 @@ function formatPoints(value?: number): string {
return `${(value ?? 0).toLocaleString("zh-CN")} 分`; return `${(value ?? 0).toLocaleString("zh-CN")} 分`;
} }
function formatTimestamp(timestamp: number) {
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 { function formatSignedPoints(value?: number): string {
const pointValue = value ?? 0; const pointValue = value ?? 0;
return `${0 < pointValue ? "+" : ""}${pointValue.toLocaleString("zh-CN")} 分`; return `${0 < pointValue ? "+" : ""}${pointValue.toLocaleString("zh-CN")} 分`;
@@ -249,10 +346,30 @@ onMounted(async () => {
.point-account { .point-account {
gap: 1rem; gap: 1rem;
display: flex; display: flex;
padding: calc(var(--app-nav-height) + 1rem) 0 2rem; padding: var(--app-nav-height) 0 calc(6rem + env(safe-area-inset-bottom, 0));
flex-direction: column; flex-direction: column;
.selector-panel, > .header {
top: var(--app-nav-height);
z-index: 10;
padding: .5rem 1rem;
position: sticky;
.buttons {
gap: 1rem;
display: flex;
.selector {
flex: 1;
min-width: 0;
}
}
.scanner-wrapper {
margin-top: 1rem;
}
}
.account-card { .account-card {
margin: 0 1rem; margin: 0 1rem;
padding: 1rem; padding: 1rem;
@@ -261,58 +378,48 @@ onMounted(async () => {
border-radius: 1.4rem; border-radius: 1.4rem;
} }
.selector-panel {
gap: .75rem;
display: flex;
flex-direction: column;
.section-title {
font-size: 1rem;
font-weight: 700;
}
.customer-trigger {
width: 100%;
}
:deep(.customer-profile-card) {
margin: 0;
}
}
.account-card { .account-card {
color: #fff; color: #FFF;
background: linear-gradient(135deg, #0f766e, #0f4c5c); background: linear-gradient(135deg, #0f766e, #0f4c5c);
.account-caption { .header {
opacity: .78; display: flex;
font-size: .875rem; justify-content: space-between;
} }
.balance { .balance {
gap: 1rem;
margin: .5rem 0; margin: .5rem 0;
font-size: 2.5rem; display: flex;
align-items: center;
justify-content: end;
.value {
font-size: 2rem;
text-align: right;
font-weight: 700; font-weight: 700;
letter-spacing: .03em; letter-spacing: .03em;
} }
}
.account-meta { .id {
gap: 1rem;
display: flex;
color: rgba(255, 255, 255, .72); color: rgba(255, 255, 255, .72);
font-size: .75rem; font-size: .75rem;
justify-content: space-between;
}
} }
.statistics { .time {
margin: 0 1rem; text-align: right;
}
} }
.actions { .actions {
left: 1rem;
right: 1rem;
gap: .75rem; gap: .75rem;
z-index: 20;
bottom: calc(.75rem + env(safe-area-inset-bottom, 0));
display: flex; display: flex;
margin: 0 1rem; position: fixed;
.t-button { .t-button {
flex: 1; flex: 1;
@@ -326,9 +433,8 @@ onMounted(async () => {
:global(html.theme-dark) { :global(html.theme-dark) {
.point-account { .point-account {
.selector-panel { .selector {
background: rgba(15, 23, 42, .88); background: #10161f;
box-shadow: 0 .75rem 2rem rgba(2, 6, 23, .32);
} }
.account-card { .account-card {