add event record calendar to record list
This commit is contained in:
@@ -64,6 +64,7 @@ function openPhotoViewer(): void {
|
|||||||
gap: .875rem;
|
gap: .875rem;
|
||||||
padding: .5rem;
|
padding: .5rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
box-shadow: var(--td-shadow-1);
|
||||||
background: #e6eeff;
|
background: #e6eeff;
|
||||||
border-radius: 1.125rem;
|
border-radius: 1.125rem;
|
||||||
|
|
||||||
|
|||||||
@@ -47,6 +47,11 @@
|
|||||||
</div>
|
</div>
|
||||||
<div v-if="loading" class="empty-block">正在加载登记日历</div>
|
<div v-if="loading" class="empty-block">正在加载登记日历</div>
|
||||||
<div v-else-if="!currentMonthFilteredRecords.length" class="empty-block" v-text="emptyText" />
|
<div v-else-if="!currentMonthFilteredRecords.length" class="empty-block" v-text="emptyText" />
|
||||||
|
<t-cell title="登记事件记录列表" arrow @click="openRecordList">
|
||||||
|
<template #left-icon>
|
||||||
|
<t-icon name="catalog" />
|
||||||
|
</template>
|
||||||
|
</t-cell>
|
||||||
<t-popup v-model="dayRecordVisible" placement="bottom" destroy-on-close>
|
<t-popup v-model="dayRecordVisible" placement="bottom" destroy-on-close>
|
||||||
<div class="day-record-panel">
|
<div class="day-record-panel">
|
||||||
<div class="popup-head">
|
<div class="popup-head">
|
||||||
@@ -112,6 +117,11 @@ interface CalendarDay {
|
|||||||
records: EventRecord[];
|
records: EventRecord[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface EventRecordCalendarListPayload {
|
||||||
|
monthCursor: Date;
|
||||||
|
selectedEventIdList: string[];
|
||||||
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
eventList: EventItem[];
|
eventList: EventItem[];
|
||||||
customerName?: string;
|
customerName?: string;
|
||||||
@@ -131,6 +141,7 @@ const props = withDefaults(defineProps<{
|
|||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
"update:modelValue": [value: Date];
|
"update:modelValue": [value: Date];
|
||||||
"update:selectedEventIdList": [value: string[]];
|
"update:selectedEventIdList": [value: string[]];
|
||||||
|
list: [value: EventRecordCalendarListPayload];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
@@ -197,6 +208,13 @@ function openDayRecords(day: CalendarDay): void {
|
|||||||
dayRecordVisible.value = true;
|
dayRecordVisible.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openRecordList(): void {
|
||||||
|
emit("list", {
|
||||||
|
monthCursor: props.modelValue,
|
||||||
|
selectedEventIdList: props.selectedEventIdList
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async function toRecordDetail(record: EventRecord): Promise<void> {
|
async function toRecordDetail(record: EventRecord): Promise<void> {
|
||||||
if (!record.id) {
|
if (!record.id) {
|
||||||
return;
|
return;
|
||||||
@@ -272,9 +290,8 @@ function addMonths(date: Date, months: number): Date {
|
|||||||
|
|
||||||
<style scoped lang="less">
|
<style scoped lang="less">
|
||||||
.register-record-calendar {
|
.register-record-calendar {
|
||||||
gap: .875rem;
|
|
||||||
padding: 1rem;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
|
overflow: hidden;
|
||||||
background: rgba(255, 255, 255, .92);
|
background: rgba(255, 255, 255, .92);
|
||||||
box-shadow: var(--td-shadow-1);
|
box-shadow: var(--td-shadow-1);
|
||||||
border-radius: .75rem;
|
border-radius: .75rem;
|
||||||
@@ -282,6 +299,7 @@ function addMonths(date: Date, months: number): Date {
|
|||||||
|
|
||||||
.calendar-head {
|
.calendar-head {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
padding: .5rem 1rem;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|
||||||
@@ -312,12 +330,14 @@ function addMonths(date: Date, months: number): Date {
|
|||||||
|
|
||||||
.week-line {
|
.week-line {
|
||||||
color: #52607a;
|
color: #52607a;
|
||||||
|
padding: 0 1rem;
|
||||||
font-size: .75rem;
|
font-size: .75rem;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.calendar-grid {
|
.calendar-grid {
|
||||||
gap: .375rem;
|
gap: .375rem;
|
||||||
|
padding: 1rem 1rem .5rem 1rem;
|
||||||
|
|
||||||
.day-cell {
|
.day-cell {
|
||||||
padding: .25rem;
|
padding: .25rem;
|
||||||
|
|||||||
@@ -61,7 +61,12 @@ function parseTransitionDepth(value: unknown): number | undefined {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const depth = Number(value);
|
const text = value.trim();
|
||||||
|
if (!text) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const depth = Number(text);
|
||||||
if (!Number.isInteger(depth) || depth < 0 || 20 < depth) {
|
if (!Number.isInteger(depth) || depth < 0 || 20 < depth) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,6 +39,7 @@
|
|||||||
v-model:selected-event-id-list="selectedEventIdList"
|
v-model:selected-event-id-list="selectedEventIdList"
|
||||||
:customer-id="selectedCustomer.id || ''"
|
:customer-id="selectedCustomer.id || ''"
|
||||||
:customer-name="selectedCustomer.name"
|
:customer-name="selectedCustomer.name"
|
||||||
|
@list="toEventRecordList"
|
||||||
/>
|
/>
|
||||||
<customer-record-trend-chart
|
<customer-record-trend-chart
|
||||||
v-if="selectedCustomer"
|
v-if="selectedCustomer"
|
||||||
@@ -66,6 +67,7 @@ import { Time } from "timi-web";
|
|||||||
import CustomerAPI from "@/api/CustomerAPI";
|
import CustomerAPI from "@/api/CustomerAPI";
|
||||||
import CodeScanner from "@/components/CodeScanner.vue";
|
import CodeScanner from "@/components/CodeScanner.vue";
|
||||||
import CustomerSelector from "@/components/CustomerSelector.vue";
|
import CustomerSelector from "@/components/CustomerSelector.vue";
|
||||||
|
import type { EventRecordCalendarListPayload } from "@/components/EventRecordCalendar.vue";
|
||||||
import CustomerChartSummary from "@/pages/customer/component/CustomerChartSummary.vue";
|
import CustomerChartSummary from "@/pages/customer/component/CustomerChartSummary.vue";
|
||||||
import CustomerEventRecordCalendarSection from "@/pages/customer/component/CustomerEventRecordCalendarSection.vue";
|
import CustomerEventRecordCalendarSection from "@/pages/customer/component/CustomerEventRecordCalendarSection.vue";
|
||||||
import CustomerEventStructureChart from "@/pages/customer/component/CustomerEventStructureChart.vue";
|
import CustomerEventStructureChart from "@/pages/customer/component/CustomerEventStructureChart.vue";
|
||||||
@@ -79,6 +81,7 @@ defineOptions({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
const router = useRouter();
|
||||||
const settingStore = useSettingStore();
|
const settingStore = useSettingStore();
|
||||||
|
|
||||||
// ---------- 路由 ----------
|
// ---------- 路由 ----------
|
||||||
@@ -160,6 +163,35 @@ function clearLoadedCustomer(): void {
|
|||||||
selectedCustomerId.value = "";
|
selectedCustomerId.value = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function toEventRecordList(payload: EventRecordCalendarListPayload): Promise<void> {
|
||||||
|
if (!selectedCustomer.value?.id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await router.push({
|
||||||
|
name: "EventRecordList",
|
||||||
|
query: buildEventRecordListQuery(payload.monthCursor, payload.selectedEventIdList, selectedCustomer.value)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildEventRecordListQuery(monthCursor: Date, eventIdList: string[], customer: Customer): Record<string, string> {
|
||||||
|
const monthStart = getMonthStart(monthCursor);
|
||||||
|
const monthEnd = getDateEnd(addDays(addMonths(monthStart, 1), -1));
|
||||||
|
return {
|
||||||
|
customerId: customer.id || "",
|
||||||
|
customerName: customer.name || "",
|
||||||
|
dateType: "custom",
|
||||||
|
beginAt: `${Time.formatDate(monthStart)} 00:00`,
|
||||||
|
endAt: `${Time.formatDate(monthEnd)} 23:59`,
|
||||||
|
transitionDepth: String(getCurrentRouteDepth() + 1),
|
||||||
|
eventIds: eventIdList.join(",")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCurrentRouteDepth(): number {
|
||||||
|
return Number(route.meta.depth ?? 0);
|
||||||
|
}
|
||||||
|
|
||||||
// ---------- 扫码 ----------
|
// ---------- 扫码 ----------
|
||||||
|
|
||||||
const scannerVisible = ref(false);
|
const scannerVisible = ref(false);
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
:customer-name="customerName"
|
:customer-name="customerName"
|
||||||
:loading="isCalendarLoading"
|
:loading="isCalendarLoading"
|
||||||
:records="records"
|
:records="records"
|
||||||
|
@list="value => emit('list', value)"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -14,7 +15,7 @@
|
|||||||
import { Time } from "timi-web";
|
import { Time } from "timi-web";
|
||||||
import CustomerChartAPI from "@/api/CustomerChartAPI";
|
import CustomerChartAPI from "@/api/CustomerChartAPI";
|
||||||
import EventAPI from "@/api/EventAPI";
|
import EventAPI from "@/api/EventAPI";
|
||||||
import EventRecordCalendar from "@/components/EventRecordCalendar.vue";
|
import EventRecordCalendar, { type EventRecordCalendarListPayload } from "@/components/EventRecordCalendar.vue";
|
||||||
import type { EventItem } from "@/types/EventItem";
|
import type { EventItem } from "@/types/EventItem";
|
||||||
import type { EventRecord } from "@/types/EventRecord";
|
import type { EventRecord } from "@/types/EventRecord";
|
||||||
import type { StoreEventRecordCalendarView } from "@/types/Store";
|
import type { StoreEventRecordCalendarView } from "@/types/Store";
|
||||||
@@ -29,6 +30,7 @@ const props = defineProps<{
|
|||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
"update:modelValue": [value: Date];
|
"update:modelValue": [value: Date];
|
||||||
"update:selectedEventIdList": [value: string[]];
|
"update:selectedEventIdList": [value: string[]];
|
||||||
|
list: [value: EventRecordCalendarListPayload];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const monthCursorProxy = computed({
|
const monthCursorProxy = computed({
|
||||||
|
|||||||
@@ -163,7 +163,8 @@ const dateOptions: Array<{ label: string; value: DateFilterType }> = [
|
|||||||
|
|
||||||
// ---------- 筛选 ----------
|
// ---------- 筛选 ----------
|
||||||
|
|
||||||
const keyword = ref(getQueryText("keyword"));
|
const keyword = ref(getInitialKeyword());
|
||||||
|
const customerId = ref(getQueryText("customerId"));
|
||||||
const dateFilterType = ref<DateFilterType>(parseDateFilterType(route.query.dateType));
|
const dateFilterType = ref<DateFilterType>(parseDateFilterType(route.query.dateType));
|
||||||
const dateRange = ref(parseDateRange(dateFilterType.value));
|
const dateRange = ref(parseDateRange(dateFilterType.value));
|
||||||
const actionFilter = ref<string[]>([]);
|
const actionFilter = ref<string[]>([]);
|
||||||
@@ -350,12 +351,17 @@ async function toDetail(id: string): Promise<void> {
|
|||||||
}
|
}
|
||||||
await router.push({
|
await router.push({
|
||||||
path: `/management/registration-event-records/${id}`,
|
path: `/management/registration-event-records/${id}`,
|
||||||
query: buildFilterQuery()
|
query: buildFilterQuery(String(getCurrentRouteDepth() + 1))
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function toCreate(): Promise<void> {
|
async function toCreate(): Promise<void> {
|
||||||
await router.push("/management/registration-event-records/create");
|
await router.push({
|
||||||
|
path: "/management/registration-event-records/create",
|
||||||
|
query: {
|
||||||
|
transitionDepth: String(getCurrentRouteDepth() + 1)
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function confirmRemoveRecord(record: EventRecordListItemView): Promise<void> {
|
async function confirmRemoveRecord(record: EventRecordListItemView): Promise<void> {
|
||||||
@@ -428,6 +434,7 @@ function buildRangeReq(): Omit<EventRecordPage, "index" | "size"> {
|
|||||||
return {
|
return {
|
||||||
beginAt,
|
beginAt,
|
||||||
endAt,
|
endAt,
|
||||||
|
customerId: customerId.value || undefined,
|
||||||
keyword: keyword.value.trim() || undefined,
|
keyword: keyword.value.trim() || undefined,
|
||||||
eventIdList: selectedAllEvent ? undefined : actionFilter.value
|
eventIdList: selectedAllEvent ? undefined : actionFilter.value
|
||||||
};
|
};
|
||||||
@@ -458,14 +465,41 @@ function parseEventIdList(value: unknown): string[] {
|
|||||||
return text.split(",").map(item => item.trim()).filter(Boolean);
|
return text.split(",").map(item => item.trim()).filter(Boolean);
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildFilterQuery(): Record<string, string> {
|
function buildFilterQuery(transitionDepth = getQueryText("transitionDepth")): Record<string, string> {
|
||||||
return {
|
const query = {
|
||||||
keyword: keyword.value.trim(),
|
keyword: keyword.value.trim(),
|
||||||
dateType: dateFilterType.value,
|
dateType: dateFilterType.value,
|
||||||
beginAt: dateRange.value[0] || "",
|
beginAt: dateRange.value[0] || "",
|
||||||
endAt: dateRange.value[1] || "",
|
endAt: dateRange.value[1] || "",
|
||||||
|
customerId: customerId.value,
|
||||||
|
customerName: getQueryText("customerName"),
|
||||||
eventIds: actionFilter.value.join(",")
|
eventIds: actionFilter.value.join(",")
|
||||||
};
|
};
|
||||||
|
if (isValidTransitionDepth(transitionDepth)) {
|
||||||
|
return {
|
||||||
|
...query,
|
||||||
|
transitionDepth
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getInitialKeyword(): string {
|
||||||
|
return getQueryText("keyword") || getQueryText("customerName");
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCurrentRouteDepth(): number {
|
||||||
|
const transitionDepth = getQueryText("transitionDepth").trim();
|
||||||
|
if (isValidTransitionDepth(transitionDepth)) {
|
||||||
|
return Number(transitionDepth);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Number(route.meta.depth ?? 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isValidTransitionDepth(value: string): boolean {
|
||||||
|
const depth = Number(value);
|
||||||
|
return value !== "" && Number.isInteger(depth) && 0 <= depth && depth <= 20;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function replaceFilterQuery(): Promise<void> {
|
async function replaceFilterQuery(): Promise<void> {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<div v-can="permissionCodes.stat.read" class="data-charts-page">
|
<div v-can="permissionCodes.stat.read" class="data-charts-page">
|
||||||
<store-chart-summary :date-text-list="dateTextList" />
|
<store-chart-summary :date-text-list="dateTextList" />
|
||||||
|
|
||||||
<store-event-record-calendar-section />
|
<store-event-record-calendar-section @list="toEventRecordList" />
|
||||||
<store-customer-growth-chart :date-text-list="dateTextList" />
|
<store-customer-growth-chart :date-text-list="dateTextList" />
|
||||||
<store-event-record-trend-chart :date-text-list="dateTextList" />
|
<store-event-record-trend-chart :date-text-list="dateTextList" />
|
||||||
<store-event-structure-chart :date-text-list="dateTextList" />
|
<store-event-structure-chart :date-text-list="dateTextList" />
|
||||||
@@ -19,6 +19,7 @@ import StoreEventRecordCalendarSection from "@/pages/store/component/StoreEventR
|
|||||||
import StoreEventRecordTrendChart from "@/pages/store/component/StoreEventRecordTrendChart.vue";
|
import StoreEventRecordTrendChart from "@/pages/store/component/StoreEventRecordTrendChart.vue";
|
||||||
import StoreEventStructureChart from "@/pages/store/component/StoreEventStructureChart.vue";
|
import StoreEventStructureChart from "@/pages/store/component/StoreEventStructureChart.vue";
|
||||||
import { permissionCodes } from "@/utils/permission";
|
import { permissionCodes } from "@/utils/permission";
|
||||||
|
import type { EventRecordCalendarListPayload } from "@/components/EventRecordCalendar.vue";
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: "StoreDataCharts"
|
name: "StoreDataCharts"
|
||||||
@@ -29,9 +30,38 @@ defineOptions({
|
|||||||
const beginDate = getDateStart(addMonths(new Date(), -1));
|
const beginDate = getDateStart(addMonths(new Date(), -1));
|
||||||
const endDate = getDateEnd(new Date());
|
const endDate = getDateEnd(new Date());
|
||||||
const dateTextList = buildDateTextList(beginDate, endDate);
|
const dateTextList = buildDateTextList(beginDate, endDate);
|
||||||
|
const route = useRoute();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
async function toEventRecordList(payload: EventRecordCalendarListPayload): Promise<void> {
|
||||||
|
await router.push({
|
||||||
|
name: "EventRecordList",
|
||||||
|
query: buildEventRecordListQuery(payload.monthCursor, payload.selectedEventIdList)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildEventRecordListQuery(monthCursor: Date, eventIdList: string[]): Record<string, string> {
|
||||||
|
const monthStart = getMonthStart(monthCursor);
|
||||||
|
const monthEnd = getDateEnd(addDays(addMonths(monthStart, 1), -1));
|
||||||
|
return {
|
||||||
|
dateType: "custom",
|
||||||
|
beginAt: `${Time.formatDate(monthStart)} 00:00`,
|
||||||
|
endAt: `${Time.formatDate(monthEnd)} 23:59`,
|
||||||
|
transitionDepth: String(getCurrentRouteDepth() + 1),
|
||||||
|
eventIds: eventIdList.join(",")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCurrentRouteDepth(): number {
|
||||||
|
return Number(route.meta.depth ?? 0);
|
||||||
|
}
|
||||||
|
|
||||||
// ---------- 日期工具 ----------
|
// ---------- 日期工具 ----------
|
||||||
|
|
||||||
|
function getMonthStart(date: Date): Date {
|
||||||
|
return new Date(date.getFullYear(), date.getMonth(), 1, 0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
function getDateStart(date: Date): Date {
|
function getDateStart(date: Date): Date {
|
||||||
return new Date(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0, 0);
|
return new Date(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0, 0);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,18 +6,23 @@
|
|||||||
empty-text="当前月份暂无登记事件记录"
|
empty-text="当前月份暂无登记事件记录"
|
||||||
:loading="isCalendarLoading"
|
:loading="isCalendarLoading"
|
||||||
:records="monthRecords"
|
:records="monthRecords"
|
||||||
|
@list="value => emit('list', value)"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import EventAPI from "@/api/EventAPI";
|
import EventAPI from "@/api/EventAPI";
|
||||||
import StoreChartAPI from "@/api/StoreChartAPI";
|
import StoreChartAPI from "@/api/StoreChartAPI";
|
||||||
import EventRecordCalendar from "@/components/EventRecordCalendar.vue";
|
import EventRecordCalendar, { type EventRecordCalendarListPayload } from "@/components/EventRecordCalendar.vue";
|
||||||
import type { EventItem } from "@/types/EventItem";
|
import type { EventItem } from "@/types/EventItem";
|
||||||
import type { EventRecord } from "@/types/EventRecord";
|
import type { EventRecord } from "@/types/EventRecord";
|
||||||
import type { StoreEventRecordCalendarView } from "@/types/Store";
|
import type { StoreEventRecordCalendarView } from "@/types/Store";
|
||||||
import { Time } from "timi-web";
|
import { Time } from "timi-web";
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
list: [value: EventRecordCalendarListPayload];
|
||||||
|
}>();
|
||||||
|
|
||||||
const eventList = ref<EventItem[]>([]);
|
const eventList = ref<EventItem[]>([]);
|
||||||
const selectedEventIdList = ref<string[]>([]);
|
const selectedEventIdList = ref<string[]>([]);
|
||||||
const monthRecords = ref<EventRecord[]>([]);
|
const monthRecords = ref<EventRecord[]>([]);
|
||||||
|
|||||||
Reference in New Issue
Block a user