update customer charts style
This commit is contained in:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user