update customer charts style

This commit is contained in:
Timi
2026-08-12 16:47:19 +08:00
parent 826f1b2dd0
commit 0f7871250e
9 changed files with 158 additions and 180 deletions
+92 -157
View File
@@ -1,68 +1,63 @@
<template>
<div v-can="permissionCodes.stat.read" class="customer-data-charts-page">
<div class="header">
<div class="selector bg-white" :class="{ 'selected' : selectedCustomer }">
<div class="line buttons">
<t-button
v-if="selectedCustomer"
theme="primary"
@click="clearCustomerSelection"
>清除</t-button>
<t-button class="customer" block theme="primary" variant="outline" @click="selectorVisible = true">
<template #icon>
<t-icon name="user-search" />
</template>
<span v-text="selectedCustomer ? selectedCustomer.name : '选择客户'" />
</t-button>
<t-button theme="primary" @click="toggleScanner">
<template #icon>
<t-icon name="scan" />
</template>
<span>扫码</span>
</t-button>
</div>
<div v-if="scannerVisible" class="line scanner-wrapper">
<code-scanner
visible
:status-text="scanStatus"
active-text="正在识别客户编码,请将二维码移入取景框"
@decode="handleDecodedText"
@status-change="handleScanStatusChange"
/>
</div>
<div class="header bg-white">
<div class="buttons">
<t-button class="selector" size="small" theme="primary" variant="outline" @click="selectorVisible = true">
<template #icon>
<t-icon name="user-search" />
</template>
<span>选择客户</span>
</t-button>
<t-button size="small" theme="primary" @click="toggleScanner">
<template #icon>
<t-icon name="scan" />
</template>
<span v-text="scannerVisible ? '关闭扫码' : '扫码'" />
</t-button>
</div>
<div v-if="scannerVisible" class="line scanner-wrapper">
<code-scanner
visible
:status-text="scanStatus"
active-text="正在识别客户编码,请将二维码移入取景框"
@decode="handleDecodedText"
@status-change="handleScanStatusChange"
/>
</div>
</div>
<section v-if="selectedCustomer" class="summary">
<customer-profile-card class="customer" :customer="selectedCustomer" />
<div class="charts">
<customer-chart-summary
v-if="selectedCustomer"
:customer="selectedCustomer"
:month-cursor="monthCursor"
:selected-event-id-list="selectedEventIdList"
/>
</section>
<customer-event-record-calendar-section
v-if="selectedCustomer"
v-model="monthCursor"
v-model:selected-event-id-list="selectedEventIdList"
:customer-id="selectedCustomer.id || ''"
:customer-name="selectedCustomer.name"
/>
<customer-record-trend-chart
v-if="selectedCustomer"
:customer-id="selectedCustomer.id || ''"
:date-text-list="chartDateTextList"
:selected-event-id-list="selectedEventIdList"
/>
<customer-event-structure-chart
v-if="selectedCustomer"
:customer-id="selectedCustomer.id || ''"
:date-text-list="monthDateTextList"
:selected-event-id-list="selectedEventIdList"
/>
<customer-selector
v-model="selectedCustomerId"
v-model:visible="selectorVisible"
@change="handleCustomerSelected"
/>
<customer-event-record-calendar-section
v-if="selectedCustomer"
v-model="monthCursor"
v-model:selected-event-id-list="selectedEventIdList"
:customer-id="selectedCustomer.id || ''"
:customer-name="selectedCustomer.name"
/>
<customer-record-trend-chart
v-if="selectedCustomer"
:customer-id="selectedCustomer.id || ''"
:date-text-list="chartDateTextList"
:selected-event-id-list="selectedEventIdList"
/>
<customer-event-structure-chart
v-if="selectedCustomer"
:customer-id="selectedCustomer.id || ''"
:date-text-list="monthDateTextList"
:selected-event-id-list="selectedEventIdList"
/>
<customer-selector
v-model="selectedCustomerId"
v-model:visible="selectorVisible"
@change="handleCustomerSelected"
/>
</div>
</div>
</template>
@@ -83,25 +78,15 @@ defineOptions({
name: "CustomerDataCharts"
});
const settingStore = useSettingStore();
const router = useRouter();
const route = useRoute();
const settingStore = useSettingStore();
// ---------- 路由 ----------
const routeCustomerId = computed(() => String(route.params.id || ""));
const routeCustomerCode = computed(() => String(route.query.customerCode || ""));
const customerCode = ref(routeCustomerCode.value);
const selectedCustomerId = ref(routeCustomerId.value);
const selectorVisible = ref(false);
const scannerVisible = ref(false);
const scanStatus = ref("点击扫码开始识别。");
const selectedCustomer = ref<Customer>();
const selectedEventIdList = ref<string[]>([]);
const monthCursor = ref(getMonthStart(new Date()));
const chartBeginDate = getDateStart(addMonths(new Date(), -1));
const chartEndDate = getDateEnd(new Date());
const chartDateTextList = buildDateTextList(chartBeginDate, chartEndDate);
let latestDecodedText = "";
const monthDateTextList = computed(() => buildMonthDateTextList(monthCursor.value));
watch(routeCustomerCode, (value) => {
if (value) {
@@ -115,10 +100,6 @@ watch(routeCustomerId, async (value) => {
}
});
watch(customerCode, async (value) => {
await loadCustomer(value);
});
onMounted(async () => {
if (routeCustomerId.value) {
await loadCustomerById(routeCustomerId.value);
@@ -129,6 +110,14 @@ onMounted(async () => {
// ---------- 数据 ----------
const selectorVisible = ref(false);
const selectedCustomer = ref<Customer>();
const selectedEventIdList = ref<string[]>([]);
watch(customerCode, async (value) => {
await loadCustomer(value);
});
async function loadCustomer(code: string): Promise<void> {
const normalizedCode = code.trim();
if (!normalizedCode) {
@@ -166,15 +155,6 @@ async function handleCustomerSelected(id: string): Promise<void> {
await loadCustomerById(id);
}
async function clearCustomerSelection(): Promise<void> {
customerCode.value = "";
clearLoadedCustomer();
if (route.name !== "CustomerDataCharts" || Object.keys(route.query).length) {
await router.replace({ name: "CustomerDataCharts" });
}
}
function clearLoadedCustomer(): void {
selectedCustomer.value = undefined;
selectedCustomerId.value = "";
@@ -182,11 +162,15 @@ function clearLoadedCustomer(): 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 = "正在启动扫码预览。";
scanStatus.value = "正在启动扫码预览";
}
}
@@ -195,11 +179,10 @@ async function handleDecodedText(rawText: string): Promise<void> {
if (!normalizedText || normalizedText === latestDecodedText) {
return;
}
latestDecodedText = normalizedText;
await settingStore.playScanSuccessSound();
customerCode.value = normalizedText;
scanStatus.value = `已识别 ${normalizedText},正在加载客户档案。`;
scanStatus.value = `已识别 ${normalizedText},正在加载客户档案`;
scannerVisible.value = false;
}
@@ -207,7 +190,13 @@ function handleScanStatusChange(value: string): void {
scanStatus.value = value;
}
// ---------- 日期 ----------
// ---------- 图表日期 ----------
const monthCursor = ref(getMonthStart(new Date()));
const chartBeginDate = getDateStart(addMonths(new Date(), -1));
const chartEndDate = getDateEnd(new Date());
const chartDateTextList = buildDateTextList(chartBeginDate, chartEndDate);
const monthDateTextList = computed(() => buildMonthDateTextList(monthCursor.value));
function getMonthStart(date: Date): Date {
return new Date(date.getFullYear(), date.getMonth(), 1, 0, 0, 0, 0);
@@ -261,72 +250,33 @@ function getDateEnd(date: Date): Date {
.header {
top: var(--app-nav-height);
z-index: 10;
padding: 1rem 1rem .875rem 1rem;
padding: .5rem 1rem;
position: sticky;
box-shadow: var(--td-shadow-1);
background: #F5F5F8;
.selector {
border: 1px solid rgba(148, 163, 184, .18);
.buttons {
gap: 1rem;
display: flex;
overflow: hidden;
border-radius: .35rem .35rem 1rem 1rem;
flex-direction: column;
&.selected {
border: 1px solid transparent;
background: transparent;
border-radius: 0;
.selector {
flex: 1;
min-width: 0;
}
}
.line {
gap: .75rem;
display: flex;
&.buttons {
z-index: 1;
.customer {
flex: 1;
min-width: 0;
}
}
&.scanner-wrapper {
margin: 1rem .5rem;
}
}
.scanner-wrapper {
margin-top: 1rem;
}
}
.summary {
gap: .75rem;
.customer {
margin: 0 1rem;
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.summary-item {
padding: .875rem .625rem;
display: flex;
min-width: 0;
background: rgba(255, 255, 255, .92);
box-shadow: var(--td-shadow-1);
align-items: center;
border-radius: .75rem;
flex-direction: column;
span {
color: #52607a;
font-size: .75rem;
}
strong {
margin-top: .375rem;
color: #0f172a;
font-size: 1rem;
font-weight: 700;
}
}
.charts {
gap: 1rem;
padding: 0 1rem;
display: flex;
flex-direction: column;
}
}
@@ -335,21 +285,6 @@ function getDateEnd(date: Date): Date {
.selector {
background: #10161f;
}
.summary {
.summary-item {
background: rgba(15, 23, 42, .88);
box-shadow: 0 .75rem 2rem rgba(2, 6, 23, .32);
span {
color: #94a3b8;
}
strong {
color: #e2e8f0;
}
}
}
}
}
</style>
@@ -1,8 +1,14 @@
<template>
<div v-for="item in summaryList" :key="item.label" class="summary-item">
<span v-text="item.label" />
<strong v-text="item.value" />
</div>
<section class="summary">
<div
v-for="item in summaryList"
:key="item.label"
class="item"
>
<span v-text="item.label" />
<strong v-text="item.value" />
</div>
</section>
</template>
<script setup lang="ts">
@@ -44,10 +50,6 @@ const summaryList = computed<SummaryItem[]>(() => [
label: "本月登记",
value: `${monthRecordList.value.length} 次`
},
{
label: "转介绍",
value: `${props.customer.invitedCount || 0} 人`
},
{
label: "最近登记",
value: getRecordEventName(monthRecordList.value[0]) || "暂无"
@@ -98,7 +100,6 @@ function getRecordEventName(record?: StoreEventRecordCalendarView): string {
if (!record) {
return "";
}
return record.eventId ? eventNameMap.value.get(record.eventId) || "未知事件" : "未知事件";
}
@@ -122,3 +123,51 @@ function addDays(date: Date, days: number): Date {
return value;
}
</script>
<style scoped lang="less">
.summary {
gap: .75rem;
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
.item {
padding: .875rem .625rem;
display: flex;
min-width: 0;
background: rgba(255, 255, 255, .92);
box-shadow: var(--td-shadow-1);
align-items: center;
border-radius: 1rem;
flex-direction: column;
span {
color: #52607a;
font-size: .75rem;
}
strong {
margin-top: .375rem;
color: #0f172a;
font-size: 1.125rem;
font-weight: 700;
}
}
}
:global(html.theme-dark) {
.summary {
.item {
background: rgba(15, 23, 42, .88);
box-shadow: 0 .75rem 2rem rgba(2, 6, 23, .32);
span {
color: #94a3b8;
}
strong {
color: #e2e8f0;
}
}
}
}
</style>
@@ -137,10 +137,6 @@ function addDays(date: Date, days: number): Date {
</script>
<style scoped lang="less">
.calendar-section {
margin: 0 1rem;
}
:global(html.theme-dark) {
.calendar-section {
background: rgba(15, 23, 42, .88);
@@ -130,7 +130,6 @@ function getDateEnd(dateText: string): Date {
<style scoped lang="less">
.chart-section {
margin: 0 1rem;
padding: 1rem;
background: rgba(255, 255, 255, .92);
box-shadow: var(--td-shadow-1);
@@ -120,7 +120,6 @@ function getDateEnd(dateText: string): Date {
<style scoped lang="less">
.chart-section {
margin: 0 1rem;
padding: 1rem;
background: rgba(255, 255, 255, .92);
box-shadow: var(--td-shadow-1);
+2 -2
View File
@@ -72,7 +72,7 @@ function buildDateTextList(begin: Date, end: Date): string[] {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
.summary-item {
.item {
padding: .875rem .625rem;
display: flex;
min-width: 0;
@@ -100,7 +100,7 @@ function buildDateTextList(begin: Date, end: Date): string[] {
:global(html.theme-dark) {
.data-charts-page {
.summary {
.summary-item {
.item {
background: rgba(15, 23, 42, .88);
box-shadow: 0 .75rem 2rem rgba(2, 6, 23, .32);
@@ -1,6 +1,6 @@
<template>
<section class="summary">
<div v-for="item in summaryList" :key="item.label" class="summary-item">
<div v-for="item in summaryList" :key="item.label" class="item">
<span v-text="item.label" />
<strong v-text="item.value" />
</div>
@@ -159,7 +159,7 @@ function toDateValue(date: Date): number {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
.summary-item {
.item {
padding: .875rem .625rem;
display: flex;
min-width: 0;
@@ -185,7 +185,7 @@ function toDateValue(date: Date): number {
:global(html.theme-dark) {
.summary {
.summary-item {
.item {
background: rgba(15, 23, 42, .88);
box-shadow: 0 .75rem 2rem rgba(2, 6, 23, .32);