refactor event

This commit is contained in:
Timi
2026-08-05 22:50:22 +08:00
parent 22ff13b1fe
commit 1243068fc3
46 changed files with 961 additions and 789 deletions
+66 -65
View File
@@ -1,5 +1,5 @@
<template>
<div v-can="gaoPermissionCodes.stat.read" class="customer-data-charts-page">
<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">
@@ -39,9 +39,9 @@
@change="selectRankDateFilter"
/>
<t-dropdown-item
v-model="rankRegistrationEventIdList"
:label="rankRegistrationEventFilterLabel"
:options="rankRegistrationEventOptions"
v-model="rankEventIdList"
:label="rankEventFilterLabel"
:options="rankEventOptions"
options-columns="2"
multiple
@change="reloadCustomerRegistrationRank"
@@ -56,12 +56,12 @@
<strong v-text="item.value" />
</div>
</section>
<registration-event-record-calendar
<event-record-calendar
v-if="selectedCustomer"
v-model="monthCursor"
v-model:selected-registration-event-id-list="selectedRegistrationEventIdList"
v-model:selected-event-id-list="selectedEventIdList"
class="calendar-section"
:registration-event-list="registrationEventList"
:event-list="eventList"
:customer-name="selectedCustomer.name"
:loading="calendarLoading"
:records="monthRecords"
@@ -115,11 +115,11 @@
</template>
<template #description>
<div>
<span v-text="`累计登记 ${item.totalRegistrationCount || 0} 次,`" />
<span v-text="`累计登记 ${item.totalRegistrationDays || 0} 天`" />
<span v-text="`累计登记 ${item.totalEventCount || 0} 次,`" />
<span v-text="`累计登记 ${item.totalEventDays || 0} 天`" />
</div>
<div>
<span v-text="`最近 ${item.recentContinuousRegistrationDays || 0} 天连续登记`" />
<span v-text="`最近 ${item.recentContinuousEventDays || 0} 天连续登记`" />
</div>
</template>
</t-cell>
@@ -165,16 +165,16 @@ import {init, use} from "echarts/core";
import {CanvasRenderer} from "echarts/renderers";
import {AttachmentAPI, Time} from "timi-web";
import CustomerAPI from "@/api/CustomerAPI";
import RegistrationEventAPI from "@/api/RegistrationEventAPI";
import RegistrationEventRecordAPI from "@/api/RegistrationEventRecordAPI";
import EventAPI from "@/api/EventAPI";
import EventRecordAPI from "@/api/EventRecordAPI";
import type {Customer} from "@/types/Customer";
import type {RegistrationEvent} from "@/types/RegistrationEvent";
import type {CustomerRegistrationRankView, RegistrationEventRecord, RegistrationEventRecordRangeReq} from "@/types/RegistrationEventRecord";
import type {EventItem} from "@/types/EventItem";
import type {CustomerEventRankView, EventRecord, EventRecordPage} from "@/types/EventRecord";
import CodeScanner from "@/components/CodeScanner.vue";
import CustomerSelector from "@/components/CustomerSelector.vue";
import RegistrationEventRecordCalendar from "@/components/RegistrationEventRecordCalendar.vue";
import EventRecordCalendar from "@/components/EventRecordCalendar.vue";
import {useSettingStore} from "@/store/settingStore";
import {gaoPermissionCodes} from "@/utils/gaoPermission";
import {permissionCodes} from "@/utils/permission";
type RankDateFilterType = "all" | "today" | "thisMonth" | "custom";
@@ -221,43 +221,43 @@ const selectorVisible = ref(false);
const scannerVisible = ref(false);
const scanStatus = ref("点击扫码开始识别。");
const selectedCustomer = ref<Customer>();
const registrationEventList = ref<RegistrationEvent[]>([]);
const selectedRegistrationEventIdList = ref<string[]>([]);
const eventList = ref<EventItem[]>([]);
const selectedEventIdList = ref<string[]>([]);
const monthCursor = ref(getMonthStart(new Date()));
const monthRecords = ref<RegistrationEventRecord[]>([]);
const monthRecords = ref<EventRecord[]>([]);
const calendarLoading = ref(false);
const customerRegistrationRankList = ref<CustomerRegistrationRankView[]>([]);
const customerRegistrationRankList = ref<CustomerEventRankView[]>([]);
const rankPageSize = 10;
const rankPageIndex = ref(1);
const rankDateFilterType = ref<RankDateFilterType>("all");
const rankDateRange = ref(buildRankPresetRange("all"));
const rankRegistrationEventIdList = ref<string[]>([]);
const rankEventIdList = ref<string[]>([]);
const rankLoading = ref(false);
const rankRefreshing = ref(false);
const rankStartPickerVisible = ref(false);
const rankEndPickerVisible = ref(false);
const rankCache = new Map<string, CustomerRegistrationRankView[]>();
const rankPendingRequestMap = new Map<string, Promise<CustomerRegistrationRankView[]>>();
const rankCache = new Map<string, CustomerEventRankView[]>();
const rankPendingRequestMap = new Map<string, Promise<CustomerEventRankView[]>>();
let rankRequestToken = 0;
let latestDecodedText = "";
const registrationEventNameMap = computed(() => {
const eventNameMap = computed(() => {
const map = new Map<string, string>();
for (const item of registrationEventList.value) {
for (const item of eventList.value) {
if (item.id) {
map.set(item.id, item.name || "未命名事件");
}
}
return map;
});
const selectedRegistrationEventIdSet = computed(() => new Set(selectedRegistrationEventIdList.value));
const selectedEventIdSet = computed(() => new Set(selectedEventIdList.value));
const filteredMonthRecords = computed(() => {
if (!selectedRegistrationEventIdList.value.length) {
if (!selectedEventIdList.value.length) {
return [];
}
return monthRecords.value.filter(item => {
if (!item.registrationEventId || !selectedRegistrationEventIdSet.value.has(item.registrationEventId)) {
if (!item.eventId || !selectedEventIdSet.value.has(item.eventId)) {
return false;
}
const recordDate = Time.toDateObject(item.registeredAt);
@@ -276,7 +276,7 @@ const summaryList = computed<SummaryItem[]>(() => [
},
{
label: "最近登记",
value: getRecordRegistrationEventName(filteredMonthRecords.value[0]) || "暂无"
value: getRecordEventName(filteredMonthRecords.value[0]) || "暂无"
}
]);
const chartList = computed<ChartProfile[]>(() => {
@@ -303,25 +303,25 @@ const chartList = computed<ChartProfile[]>(() => {
];
});
const rankDateFilterLabel = computed(() => rankDateOptions.find(item => item.value === rankDateFilterType.value)?.label || "全部");
const rankRegistrationEventOptions = computed(() => registrationEventList.value.map(item => ({
const rankEventOptions = computed(() => eventList.value.map(item => ({
label: item.name || "未命名事件",
value: item.id || ""
})).filter(item => item.value));
const rankRegistrationEventFilterLabel = computed(() => {
if (!rankRegistrationEventIdList.value.length || rankRegistrationEventIdList.value.length === registrationEventList.value.length) {
const rankEventFilterLabel = computed(() => {
if (!rankEventIdList.value.length || rankEventIdList.value.length === eventList.value.length) {
return "全部事件";
}
return registrationEventList.value
.filter(item => item.id && rankRegistrationEventIdList.value.includes(item.id))
return eventList.value
.filter(item => item.id && rankEventIdList.value.includes(item.id))
.map(item => item.name || "未命名事件")
.join("、");
});
const rankSummaryText = computed(() => {
if (rankDateFilterType.value === "custom") {
return `${rankDateRange.value[0] || "-"} ~ ${rankDateRange.value[1] || "-"}|${rankRegistrationEventFilterLabel.value}`;
return `${rankDateRange.value[0] || "-"} ~ ${rankDateRange.value[1] || "-"}|${rankEventFilterLabel.value}`;
}
return `范围:${rankDateFilterLabel.value}|${rankRegistrationEventFilterLabel.value}`;
return `范围:${rankDateFilterLabel.value}|${rankEventFilterLabel.value}`;
});
const visibleCustomerRegistrationRankList = computed(() => {
return customerRegistrationRankList.value.slice(0, rankPageIndex.value * rankPageSize);
@@ -409,9 +409,9 @@ function resizeCharts(): void {
// ---------- 数据 ----------
async function loadActions(): Promise<void> {
registrationEventList.value = await RegistrationEventAPI.enabledList();
selectedRegistrationEventIdList.value = registrationEventList.value.map(item => item.id).filter((item): item is string => Boolean(item));
rankRegistrationEventIdList.value = selectedRegistrationEventIdList.value;
eventList.value = await EventAPI.enabledList();
selectedEventIdList.value = eventList.value.map(item => item.id).filter((item): item is string => Boolean(item));
rankEventIdList.value = selectedEventIdList.value;
}
async function loadCustomer(code: string): Promise<void> {
@@ -485,10 +485,10 @@ async function loadMonthRecords(): Promise<void> {
const endDate = getDateEnd(addDays(beginDate, 41));
calendarLoading.value = true;
try {
monthRecords.value = await RegistrationEventRecordAPI.rangeList({
monthRecords.value = await EventRecordAPI.rangeList({
customerId,
beginRegisteredAt: Time.toTimestamp(beginDate),
endRegisteredAt: Time.toTimestamp(endDate)
beginAt: Time.toTimestamp(beginDate),
endAt: Time.toTimestamp(endDate)
});
} finally {
calendarLoading.value = false;
@@ -514,7 +514,7 @@ async function loadCustomerRegistrationRank(force = false): Promise<void> {
rankLoading.value = true;
let request = force ? undefined : rankPendingRequestMap.get(cacheKey);
if (!request) {
request = RegistrationEventRecordAPI.customerRegistrationRank(buildRankStatReq());
request = EventRecordAPI.customerRegistrationRank(buildRankStatReq());
rankPendingRequestMap.set(cacheKey, request);
}
try {
@@ -619,27 +619,27 @@ function handleScanStatusChange(value: string): void {
scanStatus.value = value;
}
function getRecordDateText(record: RegistrationEventRecord): string {
function getRecordDateText(record: EventRecord): string {
const date = Time.toDateObject(record.registeredAt);
return date ? Time.formatDate(date) : "";
}
function getRecordRegistrationEventName(record?: RegistrationEventRecord): string {
function getRecordEventName(record?: EventRecord): string {
if (!record) {
return "";
}
return record.registrationEvent?.name || (record.registrationEventId ? registrationEventNameMap.value.get(record.registrationEventId) : "") || "未知事件";
return record.event?.name || (record.eventId ? eventNameMap.value.get(record.eventId) : "") || "未知事件";
}
function getRankCustomerPhotoURL(item: CustomerRegistrationRankView): string {
function getRankCustomerPhotoURL(item: CustomerEventRankView): string {
if (!item.customerPhotoAttachmentId) {
return "";
}
return AttachmentAPI.getReadURL(item.customerPhotoAttachmentId);
}
function getRankCustomerInitial(item: CustomerRegistrationRankView): string {
function getRankCustomerInitial(item: CustomerEventRankView): string {
return (item.customerName || "客").slice(0, 1);
}
@@ -708,7 +708,7 @@ function buildBehaviorStructureOption(): EChartsOption {
...buildBaseOption(),
xAxis: {
type: "category",
data: registrationEventList.value.filter(item => item.id && selectedRegistrationEventIdSet.value.has(item.id)).map(item => item.name || "未命名事件"),
data: eventList.value.filter(item => item.id && selectedEventIdSet.value.has(item.id)).map(item => item.name || "未命名事件"),
axisTick: {
show: false
},
@@ -720,12 +720,12 @@ function buildBehaviorStructureOption(): EChartsOption {
{
name: "登记次数",
type: "bar",
data: registrationEventList.value
.filter(item => item.id && selectedRegistrationEventIdSet.value.has(item.id))
data: eventList.value
.filter(item => item.id && selectedEventIdSet.value.has(item.id))
.map(item => ({
value: filteredMonthRecords.value.filter(record => record.registrationEventId === item.id).length,
itemStyle: getRegistrationEventColor(item) ? {
color: getRegistrationEventColor(item)
value: filteredMonthRecords.value.filter(record => record.eventId === item.id).length,
itemStyle: getEventColor(item) ? {
color: getEventColor(item)
} : undefined
}))
}
@@ -733,8 +733,8 @@ function buildBehaviorStructureOption(): EChartsOption {
};
}
function getRegistrationEventColor(registrationEvent: RegistrationEvent): string | undefined {
return registrationEvent.color || undefined;
function getEventColor(event: EventItem): string | undefined {
return event.color || undefined;
}
function buildRelationOption(): EChartsOption {
@@ -795,27 +795,27 @@ function getDateEnd(date: Date): Date {
return new Date(date.getFullYear(), date.getMonth(), date.getDate(), 23, 59, 59, 999);
}
function buildRankStatReq(): Partial<RegistrationEventRecordRangeReq> | undefined {
const selectedAllEvent = !rankRegistrationEventIdList.value.length || rankRegistrationEventIdList.value.length === registrationEventList.value.length;
function buildRankStatReq(): Partial<EventRecordPage> | undefined {
const selectedAllEvent = !rankEventIdList.value.length || rankEventIdList.value.length === eventList.value.length;
if (rankDateFilterType.value === "all") {
return selectedAllEvent ? undefined : {
registrationEventIdList: rankRegistrationEventIdList.value
eventIdList: rankEventIdList.value
};
}
return {
beginRegisteredAt: Time.toTimestamp(parseRankDateTime(rankDateRange.value[0], false)),
endRegisteredAt: Time.toTimestamp(parseRankDateTime(rankDateRange.value[1], true)),
registrationEventIdList: selectedAllEvent ? undefined : rankRegistrationEventIdList.value
beginAt: Time.toTimestamp(parseRankDateTime(rankDateRange.value[0], false)),
endAt: Time.toTimestamp(parseRankDateTime(rankDateRange.value[1], true)),
eventIdList: selectedAllEvent ? undefined : rankEventIdList.value
};
}
function buildRankCacheKey(): string {
const req = buildRankStatReq();
return JSON.stringify({
beginRegisteredAt: req?.beginRegisteredAt,
endRegisteredAt: req?.endRegisteredAt,
registrationEventIdList: req?.registrationEventIdList?.slice().sort()
beginAt: req?.beginAt,
endAt: req?.endAt,
eventIdList: req?.eventIdList?.slice().sort()
});
}
@@ -1135,3 +1135,4 @@ function parseRankDateTime(value: string, isEnd: boolean): Date {
}
}
</style>
+8 -7
View File
@@ -1,5 +1,5 @@
<template>
<div v-can="gaoPermissionCodes.customer.read" class="customer-detail">
<div v-can="permissionCodes.customer.read" class="customer-detail">
<customer-profile-card
class="customer"
:customer="customer"
@@ -46,7 +46,7 @@
<div class="invite">
<div class="header">
<div class="title">邀请客户</div>
<div v-can="gaoPermissionCodes.customer.update" class="actions">
<div v-can="permissionCodes.customer.update" class="actions">
<t-button
size="extra-small"
content="选择添加"
@@ -91,7 +91,7 @@
</template>
</t-cell>
<template #right>
<div v-can="gaoPermissionCodes.customer.update" class="remove bg-red white" @click="removeInvitee(item)">删除</div>
<div v-can="permissionCodes.customer.update" class="remove bg-red white" @click="removeInvitee(item)">删除</div>
</template>
</t-swipe-cell>
<div v-if="!invitees.length" class="empty">暂无邀请客户</div>
@@ -99,7 +99,7 @@
</div>
<div class="bottom-actions">
<t-button
v-can="gaoPermissionCodes.customer.delete"
v-can="permissionCodes.customer.delete"
class="delete bg-white"
theme="danger"
variant="outline"
@@ -108,7 +108,7 @@
>
删除客户
</t-button>
<t-button v-can="gaoPermissionCodes.customer.update" theme="primary" class="edit" @click="toEdit">编辑客户</t-button>
<t-button v-can="permissionCodes.customer.update" theme="primary" class="edit" @click="toEdit">编辑客户</t-button>
</div>
<customer-selector
v-model="selectedInviteeId"
@@ -159,7 +159,7 @@ import CustomerSelector from "@/components/CustomerSelector.vue";
import { useSettingStore } from "@/store/settingStore";
import type { BodyAreaValue, Customer } from "@/types/Customer";
import { getCustomerPhotoURL, normalizeBodyAreaValue } from "@/utils/customer";
import { gaoPermissionCodes } from "@/utils/gaoPermission";
import { permissionCodes } from "@/utils/permission";
import { LunarCalendar } from "@/utils/LunarCalendar";
defineOptions({
@@ -183,7 +183,7 @@ const showSolarBirthdate = ref(false);
let latestDecodedText = "";
const inviter = computed(() => customer.value?.introducerCustomer);
const canReadStat = computed(() => userStore.hasPermissions(gaoPermissionCodes.stat.read));
const canReadStat = computed(() => userStore.hasPermissions(permissionCodes.stat.read));
const creatorName = computed(() => {
const creatorUser = customer.value?.creatorUser;
return creatorUser?.nick || creatorUser?.name || customer.value?.creatorUserId || "无";
@@ -627,3 +627,4 @@ watch(() => route.params.id, async () => {
}
}
</style>
+3 -2
View File
@@ -166,7 +166,7 @@ import PhotoField from "@/components/PhotoField.vue";
import { useSettingStore } from "@/store/settingStore";
import type { BodyAreaValue, Customer, CustomerBirthdateCalendar, CustomerGender } from "@/types/Customer";
import { createBodyAreaValue, getCustomerPhotoURL, normalizeBodyAreaValue } from "@/utils/customer";
import { gaoPermissionCodes } from "@/utils/gaoPermission";
import { permissionCodes } from "@/utils/permission";
import { compressUploadImage } from "@/utils/imageCompression";
defineOptions({
@@ -179,7 +179,7 @@ const route = useRoute();
const router = useRouter();
const settingStore = useSettingStore();
const isCreateMode = computed(() => route.name === "CustomerCreate");
const savePermissionCode = computed(() => isCreateMode.value ? gaoPermissionCodes.customer.create : gaoPermissionCodes.customer.update);
const savePermissionCode = computed(() => isCreateMode.value ? permissionCodes.customer.create : permissionCodes.customer.update);
const selectedAreas = ref<BodyAreaValue>(createBodyAreaValue());
const selectorVisible = ref(false);
const referrerCustomerId = ref("");
@@ -583,3 +583,4 @@ watch(() => route.fullPath, async () => {
}
}
</style>
+5 -4
View File
@@ -1,5 +1,5 @@
<template>
<div ref="pageRef" v-can="gaoPermissionCodes.customer.read" class="customer-list">
<div ref="pageRef" v-can="permissionCodes.customer.read" class="customer-list">
<div class="search-bar">
<t-input
v-model.trim="keyword"
@@ -51,7 +51,7 @@
</section>
<div class="bottom-actions">
<t-button
v-can="gaoPermissionCodes.customer.export"
v-can="permissionCodes.customer.export"
theme="primary"
variant="outline"
class="export bg-white"
@@ -65,7 +65,7 @@
导出
</t-button>
<t-button
v-can="gaoPermissionCodes.customer.create"
v-can="permissionCodes.customer.create"
theme="primary"
class="create"
@click="router.push('/management/customers/create')"
@@ -86,7 +86,7 @@ import CustomerAPI from "@/api/CustomerAPI";
import GenderIcon from "@/components/GenderIcon.vue";
import type { Customer, CustomerSearchReq } from "@/types/Customer";
import { getCustomerPhotoURL } from "@/utils/customer";
import { gaoPermissionCodes } from "@/utils/gaoPermission";
import { permissionCodes } from "@/utils/permission";
defineOptions({
name: "CustomerList"
@@ -372,3 +372,4 @@ onUnmounted(() => {
}
}
</style>
@@ -97,7 +97,7 @@
variant="outline"
:loading="removing"
:disabled="saving || removing"
@click="confirmRemoveRegistrationEvent"
@click="confirmRemoveEventItem"
>
删除事件
</t-button>
@@ -107,7 +107,7 @@
theme="primary"
:loading="saving"
:disabled="removing"
@click="saveRegistrationEvent"
@click="saveEventItem"
>
保存
</t-button>
@@ -184,28 +184,28 @@ import { Time } from "timi-web";
import { DialogPlugin, Toast } from "tdesign-mobile-vue";
import type { ColorObject } from "tdesign-mobile-vue";
import type { Role } from "timi-web";
import RegistrationEventAPI from "@/api/RegistrationEventAPI";
import GaoUserAPI from "@/api/GaoUserAPI";
import type { RegistrationEvent, RegistrationEventStatus, RegistrationLimitType } from "@/types/RegistrationEvent";
import { gaoPermissionCodes } from "@/utils/gaoPermission";
import EventAPI from "@/api/EventAPI";
import ManagedUserAPI from "@/api/ManagedUserAPI";
import type { EventItem, EventStatus, EventLimitType } from "@/types/EventItem";
import { permissionCodes } from "@/utils/permission";
type LimitOption = {
label: string;
value: RegistrationLimitType;
value: EventLimitType;
};
defineOptions({
name: "RegistrationEventEdit"
name: "EventEdit"
});
const route = useRoute();
const router = useRouter();
const routeId = computed(() => String(route.params.id || ""));
const isCreateMode = computed(() => !routeId.value);
const savePermissionCode = computed(() => isCreateMode.value ? gaoPermissionCodes.registrationEvent.create : gaoPermissionCodes.registrationEvent.update);
const deletePermissionCode = gaoPermissionCodes.registrationEvent.delete;
const savePermissionCode = computed(() => isCreateMode.value ? permissionCodes.event.create : permissionCodes.event.update);
const deletePermissionCode = permissionCodes.event.delete;
const pageAccess = computed(() => ({
value: [gaoPermissionCodes.registrationEvent.read, gaoPermissionCodes.user.read, savePermissionCode.value],
value: [permissionCodes.event.read, permissionCodes.user.read, savePermissionCode.value],
mode: "and" as const
}));
const roleOptions = ref<Role[]>([]);
@@ -239,7 +239,7 @@ const limitOptions: LimitOption[] = [
{ label: "事件总次数", value: "TOTAL_LIMIT" },
{ label: "单客户总次数", value: "CUSTOMER_TOTAL_LIMIT" }
];
const statusOptions: Array<{ label: string; value: RegistrationEventStatus }> = [
const statusOptions: Array<{ label: string; value: EventStatus }> = [
{ label: "草稿", value: "DRAFT" },
{ label: "可登记", value: "ACTIVE" },
{ label: "不可登记", value: "INACTIVE" }
@@ -251,9 +251,9 @@ const formModel = reactive({
name: "",
description: "",
color: "#0f766e",
status: "ACTIVE" as RegistrationEventStatus,
status: "ACTIVE" as EventStatus,
requirePhoto: false,
limitType: "NONE" as RegistrationLimitType,
limitType: "NONE" as EventLimitType,
limitValueText: "",
beginAtText: "",
endAtText: "",
@@ -262,7 +262,7 @@ const formModel = reactive({
const limitPickerVisible = ref(false);
const limitPickerDraft = ref<string[]>([formModel.limitType]);
const statusPickerVisible = ref(false);
const statusPickerDraft = ref<RegistrationEventStatus[]>([formModel.status]);
const statusPickerDraft = ref<EventStatus[]>([formModel.status]);
const minimumDateTimeText = "1970-01-01 00:00";
const nowDateTimeText = computed(() => Time.formatDateTime(new Date()));
const endAtMinimumDateTimeText = computed(() => formModel.beginAtText || minimumDateTimeText);
@@ -304,11 +304,11 @@ function updateColorDraftFromPalette(context: { color: ColorObject }): void {
colorDraft.value = context.color.hex8;
}
function getLimitText(value: RegistrationLimitType): string {
function getLimitText(value: EventLimitType): string {
return limitOptions.find(option => option.value === value)?.label || "不限";
}
function getStatusText(value: RegistrationEventStatus): string {
function getStatusText(value: EventStatus): string {
return statusOptions.find(option => option.value === value)?.label || "可登记";
}
@@ -320,7 +320,7 @@ function openLimitPicker(): void {
function confirmLimitPicker(value: Array<string | number>): void {
const limitType = value[0];
if (typeof limitType === "string") {
formModel.limitType = limitType as RegistrationLimitType;
formModel.limitType = limitType as EventLimitType;
}
limitPickerVisible.value = false;
}
@@ -338,7 +338,7 @@ function confirmStatusPicker(value: Array<string | number>): void {
statusPickerVisible.value = false;
}
function isValueLimitType(value: RegistrationLimitType): boolean {
function isValueLimitType(value: EventLimitType): boolean {
return value === "INTERVAL_HOUR" || value === "TOTAL_LIMIT" || value === "CUSTOMER_TOTAL_LIMIT";
}
@@ -383,7 +383,7 @@ function validateForm(): boolean {
return true;
}
function buildPayload(): RegistrationEvent {
function buildPayload(): EventItem {
return {
id: formModel.id || undefined,
name: formModel.name.trim(),
@@ -431,7 +431,7 @@ function clearEndAt(): void {
endAtPickerVisible.value = false;
}
async function saveRegistrationEvent(): Promise<void> {
async function saveEventItem(): Promise<void> {
if (!validateForm()) {
return;
}
@@ -440,9 +440,9 @@ async function saveRegistrationEvent(): Promise<void> {
try {
const payload = buildPayload();
if (isCreateMode.value) {
await RegistrationEventAPI.create(payload);
await EventAPI.create(payload);
} else {
await RegistrationEventAPI.update(payload);
await EventAPI.update(payload);
}
showToast("登记事件已保存");
await router.replace("/management/registration-events");
@@ -453,7 +453,7 @@ async function saveRegistrationEvent(): Promise<void> {
}
}
async function confirmRemoveRegistrationEvent(): Promise<void> {
async function confirmRemoveEventItem(): Promise<void> {
const id = formModel.id || routeId.value;
if (!id || removing.value) {
return;
@@ -470,19 +470,19 @@ async function confirmRemoveRegistrationEvent(): Promise<void> {
},
async onConfirm() {
dialog.destroy();
await removeRegistrationEvent(id);
await removeEventItem(id);
}
});
}
async function removeRegistrationEvent(id: string): Promise<void> {
async function removeEventItem(id: string): Promise<void> {
if (removing.value) {
return;
}
removing.value = true;
try {
await RegistrationEventAPI.remove(id);
await EventAPI.remove(id);
showToast("登记事件已删除");
await router.replace("/management/registration-events");
} catch {
@@ -501,12 +501,12 @@ function showToast(message: string, theme: "success" | "error" | "warning" = "su
}
async function fillForm(): Promise<void> {
roleOptions.value = await GaoUserAPI.roleList();
roleOptions.value = await ManagedUserAPI.roleList();
if (isCreateMode.value) {
return;
}
const current = await RegistrationEventAPI.detail(routeId.value);
const current = await EventAPI.detail(routeId.value);
Object.assign(formModel, {
id: current.id || "",
name: current.name || "",
@@ -696,3 +696,4 @@ onMounted(async () => {
}
}
</style>
@@ -1,13 +1,13 @@
<template>
<div v-can="{ value: [gaoPermissionCodes.registrationEvent.read, gaoPermissionCodes.user.read], mode: 'and' }" class="registration-event-list">
<div v-can="{ value: [permissionCodes.event.read, permissionCodes.user.read], mode: 'and' }" class="registration-event-list">
<t-cell-group theme="card">
<t-cell
class="item"
v-for="item in registrationEventList"
v-for="item in eventList"
:key="item.id"
:arrow="canUpdateRegistrationEvent"
:hover="canUpdateRegistrationEvent"
@click="handleRegistrationEventClick(item)"
:arrow="canUpdateEventItem"
:hover="canUpdateEventItem"
@click="handleEventClick(item)"
>
<template #title>
<div class="header">
@@ -40,8 +40,8 @@
</template>
</t-cell>
</t-cell-group>
<div v-if="!registrationEventList.length" class="empty-block">暂无登记事件</div>
<t-button v-can="gaoPermissionCodes.registrationEvent.create" class="create" theme="primary" @click="createRegistrationEvent">
<div v-if="!eventList.length" class="empty-block">暂无登记事件</div>
<t-button v-can="permissionCodes.event.create" class="create" theme="primary" @click="createEventItem">
<template #icon>
<t-icon name="add" />
</template>
@@ -53,30 +53,30 @@
<script setup lang="ts">
import { Toast } from "tdesign-mobile-vue";
import { useUserStore, type Role } from "timi-web";
import RegistrationEventAPI from "@/api/RegistrationEventAPI";
import GaoUserAPI from "@/api/GaoUserAPI";
import EventAPI from "@/api/EventAPI";
import ManagedUserAPI from "@/api/ManagedUserAPI";
import { useNavBarStore } from "@/store/navBarStore";
import type { RegistrationEvent, RegistrationEventStatus, RegistrationLimitType } from "@/types/RegistrationEvent";
import { gaoPermissionCodes } from "@/utils/gaoPermission";
import type { EventItem, EventStatus, EventLimitType } from "@/types/EventItem";
import { permissionCodes } from "@/utils/permission";
defineOptions({
name: "RegistrationEventList"
name: "eventList"
});
const router = useRouter();
const navBarStore = useNavBarStore();
const userStore = useUserStore();
const defaultColor = "#2f88ff";
const registrationEventList = ref<RegistrationEvent[]>([]);
const eventList = ref<EventItem[]>([]);
const roleOptions = ref<Role[]>([]);
const canUpdateRegistrationEvent = computed(() => userStore.hasPermissions(gaoPermissionCodes.registrationEvent.update));
const canUpdateEventItem = computed(() => userStore.hasPermissions(permissionCodes.event.update));
type StatusTag = {
text: string;
theme: "default" | "primary" | "danger";
};
const fixedStatusTagMap: Partial<Record<RegistrationEventStatus, StatusTag>> = {
const fixedStatusTagMap: Partial<Record<EventStatus, StatusTag>> = {
DRAFT: { text: "草稿", theme: "default" },
INACTIVE: { text: "不可登记", theme: "default" },
DELETED: { text: "已删除", theme: "default" }
@@ -85,7 +85,7 @@ const activeStatusTag: StatusTag = { text: "可登记", theme: "primary" };
const notEffectiveStatusTag: StatusTag = { text: "未生效", theme: "default" };
const expiredStatusTag: StatusTag = { text: "已过期", theme: "danger" };
function createRegistrationEvent(): void {
function createEventItem(): void {
router.push("/management/registration-events/create");
}
@@ -93,7 +93,7 @@ async function goSort(): Promise<void> {
await router.push("/management/registration-events/sort");
}
function editRegistrationEvent(item: RegistrationEvent): void {
function editEventItem(item: EventItem): void {
if (!item.id) {
return;
}
@@ -101,15 +101,15 @@ function editRegistrationEvent(item: RegistrationEvent): void {
router.push(`/management/registration-events/${item.id}/edit`);
}
function handleRegistrationEventClick(item: RegistrationEvent): void {
if (!canUpdateRegistrationEvent.value) {
function handleEventClick(item: EventItem): void {
if (!canUpdateEventItem.value) {
return;
}
editRegistrationEvent(item);
editEventItem(item);
}
function getStatusTag(item: RegistrationEvent): StatusTag {
function getStatusTag(item: EventItem): StatusTag {
const status = item.status || "ACTIVE";
const fixedStatusTag = fixedStatusTagMap[status];
if (fixedStatusTag) {
@@ -125,8 +125,8 @@ function getStatusTag(item: RegistrationEvent): StatusTag {
return activeStatusTag;
}
function getLimitText(item: RegistrationEvent): string {
const limitTextMap: Record<RegistrationLimitType, string> = {
function getLimitText(item: EventItem): string {
const limitTextMap: Record<EventLimitType, string> = {
NONE: "不限次数",
DAILY_HALF_DAY: "上午/下午各一次",
INTERVAL_HOUR: `每隔 ${item.limitValue || 0} 小时`,
@@ -145,19 +145,19 @@ function getRoleName(code: string): string {
async function loadData(): Promise<void> {
const [eventResult, roles] = await Promise.all([
RegistrationEventAPI.list({
EventAPI.list({
index: 0,
size: 100
}),
GaoUserAPI.roleList()
ManagedUserAPI.roleList()
]);
roleOptions.value = roles || [];
registrationEventList.value = eventResult.list || [];
eventList.value = eventResult.list || [];
}
onMounted(async () => {
navBarStore.clearRight();
if (canUpdateRegistrationEvent.value) {
if (canUpdateEventItem.value) {
navBarStore.setRightText("排序", "var(--td-brand-color)", goSort);
}
try {
@@ -245,3 +245,4 @@ onUnmounted(() => {
}
}
</style>
@@ -1,8 +1,8 @@
<template>
<div v-can="gaoPermissionCodes.registrationEventRecord.read" class="registration-record-detail">
<div v-can="permissionCodes.eventRecord.read" class="registration-record-detail">
<customer-profile-card v-if="record?.customer" class="customer-profile" :customer="record.customer" />
<t-cell-group v-if="record" title="登记信息" theme="card">
<t-cell title="登记事件" :note="record.registrationEvent?.name || '未知事件'" />
<t-cell title="登记事件" :note="record.event?.name || '未知事件'" />
<t-cell title="登记时间" :note="formatTime(record.registeredAt)" />
<t-cell title="登记人" :note="Text.display(record.operator?.nick || record.operator?.name)" />
<t-cell title="客户编码" :note="Text.display(record.customer?.code)" />
@@ -22,28 +22,28 @@
<script setup lang="ts">
import { Text, Time, useUserStore } from "timi-web";
import RegistrationEventRecordAPI from "@/api/RegistrationEventRecordAPI";
import EventRecordAPI from "@/api/EventRecordAPI";
import CustomerProfileCard from "@/components/CustomerProfileCard.vue";
import PhotoScroller from "@/components/PhotoScroller.vue";
import { useNavBarStore } from "@/store/navBarStore";
import type { RegistrationEventRecord } from "@/types/RegistrationEventRecord";
import { getRegistrationEventRecordPhotoURL } from "@/utils/registrationEventRecord";
import { gaoPermissionCodes } from "@/utils/gaoPermission";
import type { EventRecord } from "@/types/EventRecord";
import { getEventRecordPhotoURL } from "@/utils/eventRecord";
import { permissionCodes } from "@/utils/permission";
defineOptions({
name: "RegistrationEventRecordDetail"
name: "EventRecordDetail"
});
const route = useRoute();
const router = useRouter();
const navBarStore = useNavBarStore();
const userStore = useUserStore();
const record = ref<RegistrationEventRecord>();
const record = ref<EventRecord>();
const loading = ref(false);
const canUpdateRecord = computed(() => userStore.hasPermissions(gaoPermissionCodes.registrationEventRecord.update));
const canUpdateRecord = computed(() => userStore.hasPermissions(permissionCodes.eventRecord.update));
const recordPhotoList = computed(() => {
const url = getRegistrationEventRecordPhotoURL(record.value);
const url = getEventRecordPhotoURL(record.value);
return url ? [url] : [];
});
@@ -56,7 +56,7 @@ async function loadRecord(): Promise<void> {
loading.value = true;
try {
record.value = await RegistrationEventRecordAPI.detail(recordId);
record.value = await EventRecordAPI.detail(recordId);
} catch (err) {
record.value = undefined;
console.warn("登记事件记录加载失败", err);
@@ -132,3 +132,4 @@ onUnmounted(() => {
}
}
</style>
@@ -43,10 +43,10 @@
class="customer-profile"
:customer="selectedCustomer"
/>
<registration-event-selector
v-model="selectedRegistrationEventIdList"
<event-selector
v-model="selectedEventIdList"
class="registration-event-selector"
:registration-event-list="registrationEventList"
:event-list="eventList"
required-mark
/>
<t-form-item label="登记时间" content-align="right">
@@ -90,7 +90,7 @@
<t-cell
v-for="item in recentRecordList"
:key="item.id"
:title="getRecordRegistrationEventName(item)"
:title="getRecordEventName(item)"
:description="formatTime(item.registeredAt)"
arrow
hover
@@ -154,24 +154,24 @@
import { AttachmentAPI, Time, type Attachment } from "timi-web";
import { Toast } from "tdesign-mobile-vue";
import CustomerAPI from "@/api/CustomerAPI";
import RegistrationEventAPI from "@/api/RegistrationEventAPI";
import RegistrationEventRecordAPI from "@/api/RegistrationEventRecordAPI";
import EventAPI from "@/api/EventAPI";
import EventRecordAPI from "@/api/EventRecordAPI";
import CodeScanner from "@/components/CodeScanner.vue";
import CustomerList from "@/components/CustomerList.vue";
import CustomerProfileCard from "@/components/CustomerProfileCard.vue";
import CustomerSelector from "@/components/CustomerSelector.vue";
import PhotoScroller from "@/components/PhotoScroller.vue";
import RegistrationEventSelector from "@/components/RegistrationEventSelector.vue";
import EventSelector from "@/components/EventSelector.vue";
import { useSettingStore } from "@/store/settingStore";
import type { Customer } from "@/types/Customer";
import type { RegistrationEvent } from "@/types/RegistrationEvent";
import type { RegistrationEventRecord } from "@/types/RegistrationEventRecord";
import { getRegistrationEventRecordPhotoURL } from "@/utils/registrationEventRecord";
import { gaoPermissionCodes } from "@/utils/gaoPermission";
import type { EventItem } from "@/types/EventItem";
import type { EventRecord } from "@/types/EventRecord";
import { getEventRecordPhotoURL } from "@/utils/eventRecord";
import { permissionCodes } from "@/utils/permission";
import { compressUploadImageList } from "@/utils/imageCompression";
defineOptions({
name: "RegistrationEventRecordEdit"
name: "EventRecordEdit"
});
const router = useRouter();
@@ -183,9 +183,9 @@ const selectorVisible = ref(false);
const selectedCustomerListVisible = ref(false);
const selectedCustomerIdList = ref<string[]>([]);
const selectedCustomerList = ref<Customer[]>([]);
const registrationEventList = ref<RegistrationEvent[]>([]);
const selectedRegistrationEventIdList = ref<string[]>([]);
const recentRecordList = ref<RegistrationEventRecord[]>([]);
const eventList = ref<EventItem[]>([]);
const selectedEventIdList = ref<string[]>([]);
const recentRecordList = ref<EventRecord[]>([]);
const minimumDateTimeText = "1970-01-01 00:00";
const registeredAtText = ref("");
const registeredAtDraftText = ref("");
@@ -197,7 +197,7 @@ const photoUploading = ref(false);
const scannerVisible = ref(false);
const scanStatus = ref("点击扫码开始识别。");
const saving = ref(false);
const editingRecord = ref<RegistrationEventRecord>();
const editingRecord = ref<EventRecord>();
let latestDecodedText = "";
type EditablePhoto = {
@@ -208,11 +208,11 @@ type EditablePhoto = {
const routeId = computed(() => String(route.params.id || ""));
const isEditMode = computed(() => !!routeId.value);
const pageAccess = computed(() => isEditMode.value ? gaoPermissionCodes.registrationEventRecord.update : gaoPermissionCodes.registrationEventRecord.create);
const pageAccess = computed(() => isEditMode.value ? permissionCodes.eventRecord.update : permissionCodes.eventRecord.create);
const saveButtonText = computed(() => isEditMode.value ? "保存记录" : "提交");
const selectedCustomer = computed(() => selectedCustomerList.value[0]);
const showSelectedCustomerProfile = computed(() => selectedCustomerList.value.length === 1 && !!selectedCustomer.value?.id);
const canSubmit = computed(() => selectedCustomerList.value.some(item => !!item.id) && 0 < selectedRegistrationEventIdList.value.length);
const canSubmit = computed(() => selectedCustomerList.value.some(item => !!item.id) && 0 < selectedEventIdList.value.length);
const customerDisplayText = computed(() => {
if (1 < selectedCustomerList.value.length) {
return `已选择 ${selectedCustomerList.value.length} 位客户`;
@@ -229,9 +229,9 @@ const photoList = computed<string[]>({
photoItemList.value = photoItemList.value.filter(item => urlSet.has(item.url));
}
});
const registrationEventNameMap = computed(() => {
const eventNameMap = computed(() => {
const map = new Map<string, string>();
for (const item of registrationEventList.value) {
for (const item of eventList.value) {
if (item.id) {
map.set(item.id, item.name || "未命名事件");
}
@@ -242,23 +242,23 @@ const registrationEventNameMap = computed(() => {
// ---------- 页面数据 ----------
async function loadPageData(): Promise<void> {
registrationEventList.value = await RegistrationEventAPI.enabledList();
eventList.value = await EventAPI.enabledList();
if (isEditMode.value) {
await loadRecordForEdit();
return;
}
selectedRegistrationEventIdList.value = registrationEventList.value.slice(0, 1).map(item => item.id).filter((item): item is string => Boolean(item));
selectedEventIdList.value = eventList.value.slice(0, 1).map(item => item.id).filter((item): item is string => Boolean(item));
}
async function loadRecordForEdit(): Promise<void> {
const record = await RegistrationEventRecordAPI.detail(routeId.value);
const record = await EventRecordAPI.detail(routeId.value);
editingRecord.value = record;
appendRecordRegistrationEvent(record);
appendRecordEvent(record);
selectedCustomerList.value = record.customer ? [record.customer] : [];
customerCode.value = record.customer?.code || record.customerId || "";
selectedCustomerIdList.value = record.customer?.id ? [record.customer.id] : [];
selectedRegistrationEventIdList.value = record.registrationEventId ? [record.registrationEventId] : [];
selectedEventIdList.value = record.eventId ? [record.eventId] : [];
registeredAtText.value = record.registeredAt ? Time.formatDateTime(new Date(record.registeredAt)) : "";
note.value = record.remark || "";
photoItemList.value = (record.attachmentList || [])
@@ -271,12 +271,12 @@ async function loadRecordForEdit(): Promise<void> {
await loadRecentRecordList();
}
function appendRecordRegistrationEvent(record: RegistrationEventRecord): void {
if (!record.registrationEvent?.id || registrationEventList.value.some(item => item.id === record.registrationEvent?.id)) {
function appendRecordEvent(record: EventRecord): void {
if (!record.event?.id || eventList.value.some(item => item.id === record.event?.id)) {
return;
}
registrationEventList.value = [record.registrationEvent, ...registrationEventList.value];
eventList.value = [record.event, ...eventList.value];
}
// ---------- 扫码 ----------
@@ -394,22 +394,22 @@ async function loadRecentRecordList(): Promise<void> {
return;
}
const endRegisteredAt = Time.toTimestamp(new Date());
recentRecordList.value = (await RegistrationEventRecordAPI.rangeList({
const endAt = Time.toTimestamp(new Date());
recentRecordList.value = (await EventRecordAPI.rangeList({
customerId,
beginRegisteredAt: 0,
endRegisteredAt
beginAt: 0,
endAt
}))
.sort((left, right) => (right.registeredAt || 0) - (left.registeredAt || 0))
.slice(0, 5);
}
function getRecordRegistrationEventName(record: RegistrationEventRecord): string {
return record.registrationEvent?.name || (record.registrationEventId ? registrationEventNameMap.value.get(record.registrationEventId) : "") || "未知事件";
function getRecordEventName(record: EventRecord): string {
return record.event?.name || (record.eventId ? eventNameMap.value.get(record.eventId) : "") || "未知事件";
}
function getRecordPhotoURL(record: RegistrationEventRecord): string {
return getRegistrationEventRecordPhotoURL(record);
function getRecordPhotoURL(record: EventRecord): string {
return getEventRecordPhotoURL(record);
}
// ---------- 照片 ----------
@@ -473,9 +473,9 @@ async function submitRecord(): Promise<void> {
}
const customerIdList = selectedCustomerList.value.map(customer => customer.id).filter((item): item is string => !!item);
for (const registrationEventId of selectedRegistrationEventIdList.value) {
await RegistrationEventRecordAPI.create({
registrationEventId,
for (const eventId of selectedEventIdList.value) {
await EventRecordAPI.create({
eventId,
customerIdList,
remark: note.value || "客户现场登记",
registeredAt: buildRegisteredAt(),
@@ -500,7 +500,7 @@ function validateForm(): boolean {
showToast("请先选择或扫码客户", "warning");
return false;
}
if (!selectedRegistrationEventIdList.value.length) {
if (!selectedEventIdList.value.length) {
showToast("请至少选择一个登记事件", "warning");
return false;
}
@@ -515,10 +515,10 @@ async function updateRecord(): Promise<void> {
return;
}
await RegistrationEventRecordAPI.update({
await EventRecordAPI.update({
...editingRecord.value,
customerId: submitCustomer.id,
registrationEventId: selectedRegistrationEventIdList.value[0],
eventId: selectedEventIdList.value[0],
remark: note.value,
registeredAt: buildRegisteredAt(),
attachmentList: buildPhotoAttachmentList()
@@ -728,3 +728,4 @@ watch(customerCode, async (value) => {
}
}
</style>
@@ -1,5 +1,5 @@
<template>
<div ref="pageRef" v-can="gaoPermissionCodes.registrationEventRecord.read" class="registration-record-list">
<div ref="pageRef" v-can="permissionCodes.eventRecord.read" class="registration-record-list">
<div class="search-bar">
<t-input
v-model.trim="keyword"
@@ -55,7 +55,7 @@
>
<t-cell
class="cell"
:title="getRecordRegistrationEventName(item)"
:title="getRecordEventName(item)"
:description="getRecordCustomerName(item)"
:note="formatTime(item.registeredAt)"
arrow
@@ -66,7 +66,7 @@
<t-image
v-if="getRecordPhotoURL(item)"
:src="getRecordPhotoURL(item)"
:alt="getRecordRegistrationEventName(item)"
:alt="getRecordEventName(item)"
fit="cover"
class="photo"
/>
@@ -89,7 +89,7 @@
<div v-if="recordList.length" class="load-state" v-text="loadStateText" />
</div>
<t-button
v-can="gaoPermissionCodes.registrationEventRecord.export"
v-can="permissionCodes.eventRecord.export"
class="export"
theme="primary"
:loading="exporting"
@@ -125,21 +125,21 @@
<script setup lang="ts">
import { DialogPlugin, Toast, type DropdownValue } from "tdesign-mobile-vue";
import { AttachmentAPI, Scroller, Time, Toolkit, useUserStore } from "timi-web";
import RegistrationEventAPI from "@/api/RegistrationEventAPI";
import RegistrationEventRecordAPI from "@/api/RegistrationEventRecordAPI";
import EventAPI from "@/api/EventAPI";
import EventRecordAPI from "@/api/EventRecordAPI";
import { useNavBarStore } from "@/store/navBarStore";
import type { RegistrationEvent } from "@/types/RegistrationEvent";
import type { RegistrationEventRecordListItemView, RegistrationEventRecordRangeReq } from "@/types/RegistrationEventRecord";
import { gaoPermissionCodes } from "@/utils/gaoPermission";
import type { EventItem } from "@/types/EventItem";
import type { EventRecordListItemView, EventRecordPage } from "@/types/EventRecord";
import { permissionCodes } from "@/utils/permission";
type DateFilterType = "today" | "yesterday" | "beforeYesterday" | "lastWeek" | "all" | "custom";
defineOptions({
name: "RegistrationEventRecordList"
name: "EventRecordList"
});
const pageSize = 20;
const scrollListenerName = "RegistrationEventRecordList";
const scrollListenerName = "EventRecordList";
const router = useRouter();
const navBarStore = useNavBarStore();
const userStore = useUserStore();
@@ -158,7 +158,7 @@ const keyword = ref("");
const dateFilterType = ref<DateFilterType>("today");
const dateRange = ref(buildPresetRange("today"));
const actionFilter = ref<string[]>([]);
const registrationEventList = ref<RegistrationEvent[]>([]);
const eventList = ref<EventItem[]>([]);
const startVisible = ref(false);
const endVisible = ref(false);
const pageRef = ref<HTMLElement>();
@@ -169,25 +169,25 @@ function getPageScrollElement(): HTMLElement | null {
return pageRef.value?.closest(".pages") || null;
}
const canCreateRecord = computed(() => userStore.hasPermissions(gaoPermissionCodes.registrationEventRecord.create));
const canCreateRecord = computed(() => userStore.hasPermissions(permissionCodes.eventRecord.create));
const isCustomRange = computed(() => dateFilterType.value === "custom");
const dateFilterLabel = computed(() => dateOptions.find(item => item.value === dateFilterType.value)?.label || "今天");
const actionOptions = computed(() => registrationEventList.value.map(item => ({
const actionOptions = computed(() => eventList.value.map(item => ({
label: item.name || "未命名事件",
value: item.id || ""
})).filter(item => item.value));
const eventFilterLabel = computed(() => {
if (!actionFilter.value.length || actionFilter.value.length === registrationEventList.value.length) {
if (!actionFilter.value.length || actionFilter.value.length === eventList.value.length) {
return "全部事件";
}
return registrationEventList.value
return eventList.value
.filter(item => item.id && actionFilter.value.includes(item.id))
.map(item => item.name || "未命名事件")
.join("、");
});
const summaryBehaviorText = computed(() => `事件:${eventFilterLabel.value}`);
const canDeleteRecord = computed(() => userStore.hasPermissions(gaoPermissionCodes.registrationEventRecord.delete));
const canDeleteRecord = computed(() => userStore.hasPermissions(permissionCodes.eventRecord.delete));
const summaryText = computed(() => {
if (!isCustomRange.value) {
const current = dateOptions.find(item => item.value === dateFilterType.value);
@@ -231,7 +231,7 @@ watch(dateRange, async (value) => {
const pageIndex = ref(0);
const totalCount = ref(0);
const recordList = ref<RegistrationEventRecordListItemView[]>([]);
const recordList = ref<EventRecordListItemView[]>([]);
const loading = ref(false);
const noMore = ref(false);
let loadToken = 0;
@@ -260,7 +260,7 @@ async function loadRecords(): Promise<void> {
loadToken = token;
loading.value = true;
try {
const result = await RegistrationEventRecordAPI.rangePage({
const result = await EventRecordAPI.rangePage({
index: pageIndex.value,
size: pageSize,
equalsExample: buildRangeReq()
@@ -285,8 +285,8 @@ const exporting = ref(false);
const deletingId = ref("");
async function loadActions(): Promise<void> {
registrationEventList.value = await RegistrationEventAPI.enabledList();
actionFilter.value = registrationEventList.value.map(item => item.id).filter((item): item is string => Boolean(item));
eventList.value = await EventAPI.enabledList();
actionFilter.value = eventList.value.map(item => item.id).filter((item): item is string => Boolean(item));
}
async function selectDateFilter(type: DropdownValue): Promise<void> {
@@ -307,7 +307,7 @@ async function exportExcel(): Promise<void> {
}
exporting.value = true;
try {
const blob = await RegistrationEventRecordAPI.rangeExport(buildRangeReq());
const blob = await EventRecordAPI.rangeExport(buildRangeReq());
Toolkit.downloadBlob(blob, `登记事件记录-${Time.formatDate(new Date())}.xlsx`);
showToast("导出已开始");
} finally {
@@ -326,7 +326,7 @@ async function goCreate(): Promise<void> {
await router.push("/management/registration-event-records/create");
}
async function confirmRemoveRecord(record: RegistrationEventRecordListItemView): Promise<void> {
async function confirmRemoveRecord(record: EventRecordListItemView): Promise<void> {
if (!record.id) {
return;
}
@@ -334,7 +334,7 @@ async function confirmRemoveRecord(record: RegistrationEventRecordListItemView):
const recordId = record.id;
const dialog = DialogPlugin.confirm({
title: "删除登记记录",
content: `确定删除 ${getRecordCustomerName(record)} 的 ${getRecordRegistrationEventName(record)} 记录吗?删除后无法恢复。`,
content: `确定删除 ${getRecordCustomerName(record)} 的 ${getRecordEventName(record)} 记录吗?删除后无法恢复。`,
cancelBtn: "取消",
confirmBtn: "删除",
onCancel() {
@@ -354,7 +354,7 @@ async function removeRecord(id: string): Promise<void> {
deletingId.value = id;
try {
await RegistrationEventRecordAPI.remove(id);
await EventRecordAPI.remove(id);
recordList.value = recordList.value.filter(item => item.id !== id);
totalCount.value = Math.max(0, totalCount.value - 1);
showToast("登记记录已删除");
@@ -365,22 +365,22 @@ async function removeRecord(id: string): Promise<void> {
// ---------- 格式化 ----------
function getRecordRegistrationEventName(record: RegistrationEventRecordListItemView): string {
return record.registrationEventName || "未知事件";
function getRecordEventName(record: EventRecordListItemView): string {
return record.eventName || "未知事件";
}
function getRecordCustomerName(record: RegistrationEventRecordListItemView): string {
function getRecordCustomerName(record: EventRecordListItemView): string {
return record.customerName || "未知客户";
}
function getRecordPhotoURL(record: RegistrationEventRecordListItemView): string {
function getRecordPhotoURL(record: EventRecordListItemView): string {
if (!record.customerPhotoAttachmentId) {
return "";
}
return AttachmentAPI.getReadURL(record.customerPhotoAttachmentId);
}
function getRecordCustomerInitial(record: RegistrationEventRecordListItemView): string {
function getRecordCustomerInitial(record: EventRecordListItemView): string {
return (record.customerName || "客").slice(0, 1);
}
@@ -396,14 +396,14 @@ function formatTime(value?: number): string {
return Time.formatDateTime(date);
}
function buildRangeReq(): RegistrationEventRecordRangeReq {
const [beginRegisteredAt, endRegisteredAt] = buildUnixRange();
const selectedAllEvent = !actionFilter.value.length || actionFilter.value.length === registrationEventList.value.length;
function buildRangeReq(): EventRecordPage {
const [beginAt, endAt] = buildUnixRange();
const selectedAllEvent = !actionFilter.value.length || actionFilter.value.length === eventList.value.length;
return {
beginRegisteredAt,
endRegisteredAt,
beginAt,
endAt,
keyword: keyword.value.trim() || undefined,
registrationEventIdList: selectedAllEvent ? undefined : actionFilter.value
eventIdList: selectedAllEvent ? undefined : actionFilter.value
};
}
@@ -648,3 +648,4 @@ onUnmounted(() => {
}
}
</style>
@@ -1,12 +1,12 @@
<template>
<div v-can="gaoPermissionCodes.registrationEvent.update" class="registration-event-sort">
<div v-can="permissionCodes.event.update" class="registration-event-sort">
<div class="tip" v-text="'按住右侧拖动手柄调整事件顺序,保存后生效'" />
<div ref="sortListRef" class="items">
<t-cell-group v-if="registrationEventList.length" theme="card">
<t-cell-group v-if="eventList.length" theme="card">
<t-cell
class="cell"
:title="item.name || '未命名事件'"
v-for="(item, index) in registrationEventList"
v-for="(item, index) in eventList"
:key="item.id"
:class="{ dragging: draggingIndex === index }"
>
@@ -55,18 +55,18 @@
<script setup lang="ts">
import { Toast, type TagProps } from "tdesign-mobile-vue";
import RegistrationEventAPI from "@/api/RegistrationEventAPI";
import EventAPI from "@/api/EventAPI";
import { useNavBarStore } from "@/store/navBarStore";
import type { RegistrationEvent, RegistrationEventStatus } from "@/types/RegistrationEvent";
import { gaoPermissionCodes } from "@/utils/gaoPermission";
import type { EventItem, EventStatus } from "@/types/EventItem";
import { permissionCodes } from "@/utils/permission";
defineOptions({
name: "RegistrationEventSort"
name: "EventSort"
});
const navBarStore = useNavBarStore();
const defaultColor = "#2f88ff";
const registrationEventList = ref<RegistrationEvent[]>([]);
const eventList = ref<EventItem[]>([]);
const sortListRef = ref<HTMLElement>();
const loading = ref(false);
const saving = ref(false);
@@ -78,7 +78,7 @@ let pointerMoved = false;
let pointerTarget: HTMLElement | undefined;
let dragInputType: "pointer" | "touch" | undefined;
function getStatusText(status?: RegistrationEventStatus): string {
function getStatusText(status?: EventStatus): string {
if (status === "DRAFT") {
return "草稿";
}
@@ -88,7 +88,7 @@ function getStatusText(status?: RegistrationEventStatus): string {
return "可登记";
}
function getStatusTheme(status?: RegistrationEventStatus): TagProps["theme"] {
function getStatusTheme(status?: EventStatus): TagProps["theme"] {
if (status === "DRAFT") {
return "warning";
}
@@ -135,8 +135,8 @@ function moveDragAt(clientY: number): void {
return;
}
const [item] = registrationEventList.value.splice(draggingIndex.value, 1);
registrationEventList.value.splice(targetIndex, 0, item);
const [item] = eventList.value.splice(draggingIndex.value, 1);
eventList.value.splice(targetIndex, 0, item);
draggingIndex.value = targetIndex;
sortDirty.value = true;
}
@@ -223,11 +223,11 @@ async function saveSort(): Promise<void> {
saving.value = true;
try {
const idList = registrationEventList.value
const idList = eventList.value
.map(item => item.id)
.filter((id): id is string => Boolean(id));
await RegistrationEventAPI.sort(idList);
registrationEventList.value.forEach((item, index) => {
await EventAPI.sort(idList);
eventList.value.forEach((item, index) => {
item.sort = index;
});
sortDirty.value = false;
@@ -250,11 +250,11 @@ function showToast(message: string, theme: "success" | "error" = "success"): voi
async function loadData(): Promise<void> {
loading.value = true;
try {
const result = await RegistrationEventAPI.list({
const result = await EventAPI.list({
index: 0,
size: 100
});
registrationEventList.value = result.list || [];
eventList.value = result.list || [];
sortDirty.value = false;
} finally {
loading.value = false;
@@ -351,3 +351,4 @@ onUnmounted(() => {
}
}
</style>
+50 -49
View File
@@ -1,5 +1,5 @@
<template>
<div v-can="gaoPermissionCodes.stat.read" class="data-charts-page">
<div v-can="permissionCodes.stat.read" class="data-charts-page">
<section class="summary">
<div v-for="item in summaryList" :key="item.label" class="summary-item">
<span v-text="item.label" />
@@ -7,10 +7,10 @@
</div>
</section>
<registration-event-record-calendar
<event-record-calendar
v-model="monthCursor"
v-model:selected-registration-event-id-list="selectedRegistrationEventIdList"
:registration-event-list="registrationEventList"
v-model:selected-event-id-list="selectedEventIdList"
:event-list="eventList"
empty-text="当前月份暂无登记事件记录"
:loading="calendarLoading"
:records="monthRecords"
@@ -34,14 +34,14 @@ import { GridComponent, LegendComponent, TooltipComponent } from "echarts/compon
import { init, use } from "echarts/core";
import { CanvasRenderer } from "echarts/renderers";
import CustomerAPI from "@/api/CustomerAPI";
import RegistrationEventAPI from "@/api/RegistrationEventAPI";
import RegistrationEventRecordAPI from "@/api/RegistrationEventRecordAPI";
import RegistrationEventRecordCalendar from "@/components/RegistrationEventRecordCalendar.vue";
import EventAPI from "@/api/EventAPI";
import EventRecordAPI from "@/api/EventRecordAPI";
import EventRecordCalendar from "@/components/EventRecordCalendar.vue";
import type { CustomerDailyStatView, CustomerGenderStatView } from "@/types/Customer";
import type { RegistrationEvent } from "@/types/RegistrationEvent";
import type { RegistrationEventRecord, RegistrationEventRecordCalendarView, RegistrationEventRecordDailyStatView } from "@/types/RegistrationEventRecord";
import type { EventItem } from "@/types/EventItem";
import type { EventRecord, EventRecordCalendarView, EventRecordDailyStatView } from "@/types/EventRecord";
import { Time } from "timi-web";
import { gaoPermissionCodes } from "@/utils/gaoPermission";
import { permissionCodes } from "@/utils/permission";
interface SummaryItem {
label: string;
@@ -69,12 +69,12 @@ use([
const chartRefs = new Map<string, HTMLElement>();
const chartInstances = new Map<string, ECharts>();
const registrationEventList = ref<RegistrationEvent[]>([]);
const selectedRegistrationEventIdList = ref<string[]>([]);
const eventList = ref<EventItem[]>([]);
const selectedEventIdList = ref<string[]>([]);
const customerDailyStatList = ref<CustomerDailyStatView[]>([]);
const customerGenderStatList = ref<CustomerGenderStatView[]>([]);
const recordDailyStatMap = ref<Record<string, RegistrationEventRecordDailyStatView[]>>({});
const monthRecords = ref<RegistrationEventRecord[]>([]);
const recordDailyStatMap = ref<Record<string, EventRecordDailyStatView[]>>({});
const monthRecords = ref<EventRecord[]>([]);
const loading = ref(false);
const calendarLoading = ref(false);
const monthCursor = ref(getMonthStart(new Date()));
@@ -87,8 +87,8 @@ const beginDate = getDateStart(addDays(new Date(), -6));
const endDate = getDateEnd(new Date());
const dateTextList = buildDateTextList(beginDate, endDate);
const timestampRange = {
beginRegisteredAt: Time.toTimestamp(beginDate),
endRegisteredAt: Time.toTimestamp(endDate)
beginAt: Time.toTimestamp(beginDate),
endAt: Time.toTimestamp(endDate)
};
const summaryList = computed<SummaryItem[]>(() => [
@@ -188,27 +188,27 @@ function resizeCharts(): void {
async function loadChartsData(): Promise<void> {
loading.value = true;
try {
registrationEventList.value = await RegistrationEventAPI.enabledList();
const validRegistrationEventList = registrationEventList.value.filter((item): item is RegistrationEvent & { id: string } => Boolean(item.id));
selectedRegistrationEventIdList.value = validRegistrationEventList.map(item => item.id);
eventList.value = await EventAPI.enabledList();
const valideventList = eventList.value.filter((item): item is EventItem & { id: string } => Boolean(item.id));
selectedEventIdList.value = valideventList.map(item => item.id);
const [customerStatList, genderStatList, recordDailyStatList] = await Promise.all([
CustomerAPI.dailyStat({
beginCreatedAt: timestampRange.beginRegisteredAt,
endCreatedAt: timestampRange.endRegisteredAt
beginCreatedAt: timestampRange.beginAt,
endCreatedAt: timestampRange.endAt
}),
CustomerAPI.genderStat(),
Promise.all(validRegistrationEventList.map(async item => ({
registrationEventId: item.id,
stats: await RegistrationEventRecordAPI.allCustomerRegistrationEventDailyStat({
registrationEventId: item.id,
beginRegisteredAt: timestampRange.beginRegisteredAt,
endRegisteredAt: timestampRange.endRegisteredAt
Promise.all(valideventList.map(async item => ({
eventId: item.id,
stats: await EventRecordAPI.allCustomerEventDailyStat({
eventId: item.id,
beginAt: timestampRange.beginAt,
endAt: timestampRange.endAt
})
})))
]);
customerDailyStatList.value = customerStatList;
customerGenderStatList.value = genderStatList;
recordDailyStatMap.value = Object.fromEntries(recordDailyStatList.map(item => [item.registrationEventId, item.stats]));
recordDailyStatMap.value = Object.fromEntries(recordDailyStatList.map(item => [item.eventId, item.stats]));
} finally {
loading.value = false;
}
@@ -219,9 +219,9 @@ async function loadMonthRecords(): Promise<void> {
const end = getDateEnd(addDays(begin, 41));
calendarLoading.value = true;
try {
const calendarRecordList = await RegistrationEventRecordAPI.calendarRangeList({
beginRegisteredAt: Time.toTimestamp(begin),
endRegisteredAt: Time.toTimestamp(end)
const calendarRecordList = await EventRecordAPI.calendarRangeList({
beginAt: Time.toTimestamp(begin),
endAt: Time.toTimestamp(end)
});
monthRecords.value = calendarRecordList.map(buildCalendarRecord);
} finally {
@@ -295,14 +295,14 @@ function buildCustomerGrowthOption(): EChartsOption {
function buildRecordTrendOption(): EChartsOption {
return {
...buildBaseOption(),
series: registrationEventList.value.map(action => ({
series: eventList.value.map(action => ({
name: action.name || "未命名事件",
type: "bar",
stack: "record",
itemStyle: getRegistrationEventColor(action) ? {
color: getRegistrationEventColor(action)
itemStyle: getEventColor(action) ? {
color: getEventColor(action)
} : undefined,
data: dateTextList.map(dateText => countRecordByRegistrationEventAndDate(action.id || "", dateText))
data: dateTextList.map(dateText => countRecordByEventAndDate(action.id || "", dateText))
}))
};
}
@@ -315,7 +315,7 @@ function buildEventStructureOption(): EChartsOption {
},
xAxis: {
type: "category",
data: registrationEventList.value.map(item => item.name || "未命名事件"),
data: eventList.value.map(item => item.name || "未命名事件"),
axisTick: {
show: false
},
@@ -326,10 +326,10 @@ function buildEventStructureOption(): EChartsOption {
series: [{
name: "登记次数",
type: "bar",
data: registrationEventList.value.map(item => ({
value: countRecordByRegistrationEvent(item.id || ""),
itemStyle: getRegistrationEventColor(item) ? {
color: getRegistrationEventColor(item)
data: eventList.value.map(item => ({
value: countRecordByEvent(item.id || ""),
itemStyle: getEventColor(item) ? {
color: getEventColor(item)
} : undefined
}))
}]
@@ -391,7 +391,7 @@ function sumCustomerNewCount(): number {
}
function sumActionDailyCount(): number {
return registrationEventList.value.reduce((sum, item) => sum + countRecordByRegistrationEvent(item.id || ""), 0);
return eventList.value.reduce((sum, item) => sum + countRecordByEvent(item.id || ""), 0);
}
function getCustomerDailyStat(dateText: string): CustomerDailyStatView | undefined {
@@ -399,28 +399,28 @@ function getCustomerDailyStat(dateText: string): CustomerDailyStatView | undefin
return customerDailyStatList.value.find(item => item.dateValue === dateValue);
}
function countRecordByRegistrationEventAndDate(registrationEventId: string, dateText: string): number {
function countRecordByEventAndDate(eventId: string, dateText: string): number {
const dateValue = Number(dateText.replaceAll("-", ""));
return recordDailyStatMap.value[registrationEventId]?.find(item => item.dateValue === dateValue)?.dailyCount || 0;
return recordDailyStatMap.value[eventId]?.find(item => item.dateValue === dateValue)?.dailyCount || 0;
}
function countRecordByRegistrationEvent(registrationEventId: string): number {
return (recordDailyStatMap.value[registrationEventId] || []).reduce((sum, item) => sum + (item.dailyCount || 0), 0);
function countRecordByEvent(eventId: string): number {
return (recordDailyStatMap.value[eventId] || []).reduce((sum, item) => sum + (item.dailyCount || 0), 0);
}
function getRegistrationEventColor(registrationEvent: RegistrationEvent): string | undefined {
return registrationEvent.color || undefined;
function getEventColor(event: EventItem): string | undefined {
return event.color || undefined;
}
function getGenderCount(genderCode: "MALE" | "FEMALE" | "UNKNOWN"): number {
return customerGenderStatList.value.find(item => item.genderCode === genderCode)?.count || 0;
}
function buildCalendarRecord(record: RegistrationEventRecordCalendarView): RegistrationEventRecord {
function buildCalendarRecord(record: EventRecordCalendarView): EventRecord {
return {
id: record.id,
customerId: record.customerId,
registrationEventId: record.registrationEventId,
eventId: record.eventId,
registeredAt: record.registeredAt,
customer: record.customerId || record.customerCode || record.customerName ? {
id: record.customerId,
@@ -547,3 +547,4 @@ function buildDateTextList(begin: Date, end: Date): string[] {
}
}
</style>
+3 -2
View File
@@ -1,5 +1,5 @@
<template>
<div v-can="gaoPermissionCodes.qrCode.create" class="qr-page">
<div v-can="permissionCodes.qrCode.create" class="qr-page">
<t-form class="form" label-align="left">
<t-form-item label="编码前缀">
<t-input v-model.trim="formModel.prefix" placeholder="如 GAO" borderless />
@@ -70,7 +70,7 @@ import {
printManagementQrCodeImages,
splitManagementQrCodePages
} from "@/utils/managementQrCode";
import { gaoPermissionCodes } from "@/utils/gaoPermission";
import { permissionCodes } from "@/utils/permission";
defineOptions({
name: "ManagementQrCode"
@@ -358,3 +358,4 @@ async function printQrCode(): Promise<void> {
}
}
</style>
+6 -5
View File
@@ -20,8 +20,8 @@
<script setup lang="ts">
import { Toast } from "tdesign-mobile-vue";
import { gaoModuleCode, usePermissionSettingsStore } from "@/store/permissionSettingsStore";
import { corePermissionCodes, permissionManagementRoleAccess } from "@/utils/gaoPermission";
import { moduleCode, usePermissionSettingsStore } from "@/store/permissionSettingsStore";
import { corePermissionCodes, permissionManagementRoleAccess } from "@/utils/permission";
defineOptions({
name: "PermissionEdit"
@@ -39,7 +39,7 @@ const pageAccess = computed(() => ({
}));
const formModel = reactive({
id: "",
moduleCode: gaoModuleCode,
moduleCode: moduleCode,
code: "",
name: "",
description: ""
@@ -50,7 +50,7 @@ async function fillForm(): Promise<void> {
const current = await permissionStore.loadPermissionDetail(routeId.value);
Object.assign(formModel, {
id: current.id,
moduleCode: current.moduleCode || gaoModuleCode,
moduleCode: current.moduleCode || moduleCode,
code: current.code,
name: current.name,
description: current.description
@@ -60,7 +60,7 @@ async function fillForm(): Promise<void> {
async function savePermission(): Promise<void> {
const permission = await permissionStore.upsertPermission({
id: formModel.id,
moduleCode: gaoModuleCode,
moduleCode: moduleCode,
code: formModel.code,
name: formModel.name,
description: formModel.description
@@ -98,3 +98,4 @@ onMounted(async () => {
}
}
</style>
+2 -1
View File
@@ -31,7 +31,7 @@
<script setup lang="ts">
import type { Permission } from "@/store/permissionSettingsStore";
import { usePermissionSettingsStore } from "@/store/permissionSettingsStore";
import { corePermissionCodes, permissionManagementRoleAccess } from "@/utils/gaoPermission";
import { corePermissionCodes, permissionManagementRoleAccess } from "@/utils/permission";
defineOptions({
name: "PermissionList"
@@ -113,3 +113,4 @@ onMounted(async () => {
}
</style>
+2 -1
View File
@@ -51,7 +51,7 @@
<script setup lang="ts">
import { usePermissionSettingsStore } from "@/store/permissionSettingsStore";
import { corePermissionCodes } from "@/utils/gaoPermission";
import { corePermissionCodes } from "@/utils/permission";
defineOptions({
name: "PermissionSettings"
@@ -112,3 +112,4 @@ onMounted(async () => {
}
}
</style>
+6 -5
View File
@@ -38,8 +38,8 @@
<script setup lang="ts">
import { Toast } from "tdesign-mobile-vue";
import { gaoModuleCode, usePermissionSettingsStore } from "@/store/permissionSettingsStore";
import { corePermissionCodes, permissionManagementRoleAccess } from "@/utils/gaoPermission";
import { moduleCode, usePermissionSettingsStore } from "@/store/permissionSettingsStore";
import { corePermissionCodes, permissionManagementRoleAccess } from "@/utils/permission";
defineOptions({
name: "RoleEdit"
@@ -57,7 +57,7 @@ const pageAccess = computed(() => ({
}));
const formModel = reactive({
id: "",
moduleCode: gaoModuleCode,
moduleCode: moduleCode,
code: "",
name: "",
description: "",
@@ -84,7 +84,7 @@ async function fillForm(): Promise<void> {
const current = await permissionStore.loadRoleDetail(routeId.value);
Object.assign(formModel, {
id: current.id,
moduleCode: current.moduleCode || gaoModuleCode,
moduleCode: current.moduleCode || moduleCode,
code: current.code,
name: current.name,
description: current.description,
@@ -95,7 +95,7 @@ async function fillForm(): Promise<void> {
async function saveRole(): Promise<void> {
const role = await permissionStore.upsertRole({
id: formModel.id,
moduleCode: gaoModuleCode,
moduleCode: moduleCode,
code: formModel.code,
name: formModel.name,
description: formModel.description,
@@ -192,3 +192,4 @@ onMounted(async () => {
}
}
</style>
+2 -1
View File
@@ -31,7 +31,7 @@
<script setup lang="ts">
import type { Role } from "@/store/permissionSettingsStore";
import { usePermissionSettingsStore } from "@/store/permissionSettingsStore";
import { corePermissionCodes, permissionManagementRoleAccess } from "@/utils/gaoPermission";
import { corePermissionCodes, permissionManagementRoleAccess } from "@/utils/permission";
defineOptions({
name: "RoleList"
@@ -87,3 +87,4 @@ onMounted(async () => {
}
</style>
@@ -51,7 +51,7 @@ import type { DropdownOption, DropdownValue } from "tdesign-mobile-vue";
import { Toast } from "tdesign-mobile-vue";
import { useUserStore } from "timi-web";
import { usePermissionSettingsStore } from "@/store/permissionSettingsStore";
import { corePermissionCodes, permissionManagementRoleAccess } from "@/utils/gaoPermission";
import { corePermissionCodes, permissionManagementRoleAccess } from "@/utils/permission";
defineOptions({
name: "RolePermissionAssign"
@@ -288,3 +288,4 @@ onMounted(async () => {
}
}
</style>
+22 -21
View File
@@ -1,5 +1,5 @@
<template>
<div v-can="gaoPermissionCodes.user.read" class="user-edit">
<div v-can="permissionCodes.user.read" class="user-edit">
<t-cell-group v-if="currentUser" theme="card">
<t-cell
:title="currentUser.name"
@@ -27,7 +27,7 @@
<t-cell-group v-if="currentUser" theme="card">
<t-cell
v-can="gaoPermissionCodes.user.update"
v-can="permissionCodes.user.update"
title="配置角色"
note="为用户授权业务角色"
arrow
@@ -55,20 +55,20 @@
<div v-if="!scopeList.length" class="empty-card">未配置</div>
</div>
</section>
<t-button v-can="gaoPermissionCodes.user.update" class="edit" theme="primary" @click="goEdit">编辑用户</t-button>
<t-button v-can="permissionCodes.user.update" class="edit" theme="primary" @click="goEdit">编辑用户</t-button>
</div>
</template>
<script setup lang="ts">
import { Toast } from "tdesign-mobile-vue";
import { UserAPI, type User } from "timi-web";
import RegistrationEventAPI from "@/api/RegistrationEventAPI";
import GaoUserAPI from "@/api/GaoUserAPI";
import type { GaoUser } from "@/types/GaoUser";
import type { RegistrationEvent } from "@/types/RegistrationEvent";
import EventAPI from "@/api/EventAPI";
import ManagedUserAPI from "@/api/ManagedUserAPI";
import type { ManagedUser } from "@/types/ManagedUser";
import type { EventItem } from "@/types/EventItem";
import { usePermissionSettingsStore } from "@/store/permissionSettingsStore";
import { buildEffectiveRoleCodeSet, getGaoRoleName } from "@/utils/gaoRole";
import { gaoPermissionCodes } from "@/utils/gaoPermission";
import { buildEffectiveRoleCodeSet, getRoleDisplayName } from "@/utils/role";
import { permissionCodes } from "@/utils/permission";
defineOptions({
name: "UserDetail"
@@ -77,11 +77,11 @@ defineOptions({
const route = useRoute();
const router = useRouter();
const permissionStore = usePermissionSettingsStore();
const gaoUser = ref<GaoUser>();
const registrationEventList = ref<RegistrationEvent[]>([]);
const managedUser = ref<ManagedUser>();
const eventList = ref<EventItem[]>([]);
const currentUser = computed(() => {
const item = gaoUser.value;
const item = managedUser.value;
if (!item) {
return null;
}
@@ -89,19 +89,19 @@ const currentUser = computed(() => {
return {
id: item.id || "",
name: user?.nick || user?.name || "未命名用户",
role: getGaoRoleName(item.roleList?.[0], permissionStore.roleList),
role: getRoleDisplayName(item.roleList?.[0], permissionStore.roleList),
avatar: buildUserAvatar(user),
mobile: user?.phoneNo || "",
lastLoginAt: user?.lastLoginAt ? new Date(user.lastLoginAt).toLocaleString() : ""
};
});
const scopeList = computed(() => {
const item = gaoUser.value;
const item = managedUser.value;
if (!item) {
return [];
}
return registrationEventList.value
.filter(action => canRegistrationEvent(action, item))
return eventList.value
.filter(action => canEventItem(action, item))
.map(item => item.name);
});
@@ -144,7 +144,7 @@ function isUser(user?: Partial<User>): user is User {
return typeof user?.name === "string";
}
function canRegistrationEvent(action: RegistrationEvent, item: GaoUser): boolean {
function canEventItem(action: EventItem, item: ManagedUser): boolean {
const userRoleCodeSet = buildEffectiveRoleCodeSet(item, permissionStore.roleList);
const roleCodeList = action.roleCodeList || [];
if (!roleCodeList.length) {
@@ -156,12 +156,12 @@ function canRegistrationEvent(action: RegistrationEvent, item: GaoUser): boolean
async function loadData(): Promise<void> {
const routeId = String(route.params.id || "");
const [detail, actions] = await Promise.all([
GaoUserAPI.detail(routeId),
RegistrationEventAPI.enabledList(),
ManagedUserAPI.detail(routeId),
EventAPI.enabledList(),
permissionStore.loadAll()
]);
gaoUser.value = detail;
registrationEventList.value = actions || [];
managedUser.value = detail;
eventList.value = actions || [];
}
onMounted(async () => {
@@ -242,3 +242,4 @@ onMounted(async () => {
}
}
</style>
+12 -11
View File
@@ -17,9 +17,9 @@
<script setup lang="ts">
import { Toast } from "tdesign-mobile-vue";
import GaoUserAPI from "@/api/GaoUserAPI";
import type { GaoUser } from "@/types/GaoUser";
import { gaoPermissionCodes } from "@/utils/gaoPermission";
import ManagedUserAPI from "@/api/ManagedUserAPI";
import type { ManagedUser } from "@/types/ManagedUser";
import { permissionCodes } from "@/utils/permission";
defineOptions({
name: "UserEdit"
@@ -27,7 +27,7 @@ defineOptions({
const route = useRoute();
const router = useRouter();
const currentGaoUser = ref<GaoUser>();
const currentManagedUser = ref<ManagedUser>();
const isSaving = ref(false);
const formModel = reactive({
id: "",
@@ -37,13 +37,13 @@ const formModel = reactive({
password: ""
});
const isCreateMode = computed(() => !String(route.params.id || ""));
const savePermissionCode = computed(() => isCreateMode.value ? gaoPermissionCodes.user.create : gaoPermissionCodes.user.update);
const savePermissionCode = computed(() => isCreateMode.value ? permissionCodes.user.create : permissionCodes.user.update);
async function fillForm(): Promise<void> {
const routeId = String(route.params.id || "");
if (routeId) {
const current = await GaoUserAPI.detail(routeId);
currentGaoUser.value = current;
const current = await ManagedUserAPI.detail(routeId);
currentManagedUser.value = current;
Object.assign(formModel, {
id: current.id,
userId: current.userId || "",
@@ -83,11 +83,11 @@ async function saveUser(): Promise<void> {
isSaving.value = true;
try {
const payload: GaoUser = {
const payload: ManagedUser = {
id: formModel.id || undefined,
userId: formModel.userId || undefined,
enabled: currentGaoUser.value?.enabled ?? true,
remark: currentGaoUser.value?.remark || "",
enabled: currentManagedUser.value?.enabled ?? true,
remark: currentManagedUser.value?.remark || "",
user: {
id: formModel.userId || undefined,
name: formModel.name,
@@ -96,7 +96,7 @@ async function saveUser(): Promise<void> {
password: isCreateMode.value ? formModel.password : undefined
}
};
const saved = isCreateMode.value ? await GaoUserAPI.create(payload) : await GaoUserAPI.update(payload);
const saved = isCreateMode.value ? await ManagedUserAPI.create(payload) : await ManagedUserAPI.update(payload);
Toast({
theme: "success",
direction: "row",
@@ -145,3 +145,4 @@ onMounted(async () => {
}
}
</style>
+23 -22
View File
@@ -1,5 +1,5 @@
<template>
<div v-can="gaoPermissionCodes.user.read" class="user-list">
<div v-can="permissionCodes.user.read" class="user-list">
<div v-for="item in userList" :key="item.id" class="item" @click="goDetail(item)">
<t-image :src="buildUserAvatar(item)" :alt="getUserName(item)" fit="cover" class="avatar" />
<div class="content">
@@ -10,7 +10,7 @@
<span class="events gray" v-text="`可登记事件:${getActionScope(item)}`" />
</div>
</div>
<t-button v-can="gaoPermissionCodes.user.create" class="add" theme="primary" type="button" @click="goCreate">
<t-button v-can="permissionCodes.user.create" class="add" theme="primary" type="button" @click="goCreate">
<template #icon>
<t-icon name="add" />
</template>
@@ -22,13 +22,13 @@
<script setup lang="ts">
import { Toast } from "tdesign-mobile-vue";
import { UserAPI, type User } from "timi-web";
import RegistrationEventAPI from "@/api/RegistrationEventAPI";
import GaoUserAPI from "@/api/GaoUserAPI";
import type { GaoUser } from "@/types/GaoUser";
import type { RegistrationEvent } from "@/types/RegistrationEvent";
import EventAPI from "@/api/EventAPI";
import ManagedUserAPI from "@/api/ManagedUserAPI";
import type { ManagedUser } from "@/types/ManagedUser";
import type { EventItem } from "@/types/EventItem";
import { usePermissionSettingsStore } from "@/store/permissionSettingsStore";
import { buildEffectiveRoleCodeSet, getGaoRoleName } from "@/utils/gaoRole";
import { gaoPermissionCodes } from "@/utils/gaoPermission";
import { buildEffectiveRoleCodeSet, getRoleDisplayName } from "@/utils/role";
import { permissionCodes } from "@/utils/permission";
defineOptions({
name: "UserList"
@@ -36,25 +36,25 @@ defineOptions({
const router = useRouter();
const permissionStore = usePermissionSettingsStore();
const userList = ref<GaoUser[]>([]);
const registrationEventList = ref<RegistrationEvent[]>([]);
const userList = ref<ManagedUser[]>([]);
const eventList = ref<EventItem[]>([]);
function getUserName(item: GaoUser): string {
function getUserName(item: ManagedUser): string {
return item.user?.nick || item.user?.name || "未命名用户";
}
function getRoleName(item: GaoUser): string {
return getGaoRoleName(item.roleList?.[0], permissionStore.roleList);
function getRoleName(item: ManagedUser): string {
return getRoleDisplayName(item.roleList?.[0], permissionStore.roleList);
}
function getActionScope(item: GaoUser): string {
const nameList = registrationEventList.value
.filter(action => canRegistrationEvent(action, item))
function getActionScope(item: ManagedUser): string {
const nameList = eventList.value
.filter(action => canEventItem(action, item))
.map(action => action.name);
return nameList.join("、") || "未配置";
}
function canRegistrationEvent(action: RegistrationEvent, item: GaoUser): boolean {
function canEventItem(action: EventItem, item: ManagedUser): boolean {
const userRoleCodeSet = buildEffectiveRoleCodeSet(item, permissionStore.roleList);
const roleCodeList = action.roleCodeList || [];
if (!roleCodeList.length) {
@@ -63,7 +63,7 @@ function canRegistrationEvent(action: RegistrationEvent, item: GaoUser): boolean
return roleCodeList.some(code => userRoleCodeSet.has(code));
}
function buildUserAvatar(item: GaoUser): string {
function buildUserAvatar(item: ManagedUser): string {
return isUser(item.user) ? UserAPI.getAvatarURL(item.user) || buildFallbackAvatar(getUserName(item)) : buildFallbackAvatar(getUserName(item));
}
@@ -85,22 +85,22 @@ function buildFallbackAvatar(name: string): string {
async function loadData(): Promise<void> {
const [userResult, actions] = await Promise.all([
GaoUserAPI.list({
ManagedUserAPI.list({
index: 0,
size: 100
}),
RegistrationEventAPI.enabledList(),
EventAPI.enabledList(),
permissionStore.loadAll()
]);
userList.value = userResult.list || [];
registrationEventList.value = actions || [];
eventList.value = actions || [];
}
async function goCreate(): Promise<void> {
await router.push("/management/users/create");
}
async function goDetail(item: GaoUser): Promise<void> {
async function goDetail(item: ManagedUser): Promise<void> {
if (!item.id) {
return;
}
@@ -193,3 +193,4 @@ onMounted(async () => {
}
}
</style>
+24 -23
View File
@@ -5,7 +5,7 @@
<t-dropdown-item
:label="activeUserLabel"
:options="userOptions"
:value="activeGaoUserId"
:value="activeManagedUserId"
@change="handleUserChange"
/>
</t-dropdown-menu>
@@ -43,21 +43,21 @@
import type { DropdownOption, DropdownValue } from "tdesign-mobile-vue";
import { Toast } from "tdesign-mobile-vue";
import { OrderType, RoleAPI, type ModuleCode, type Role } from "timi-web";
import GaoUserAPI from "@/api/GaoUserAPI";
import type { GaoUser } from "@/types/GaoUser";
import { corePermissionCodes } from "@/utils/gaoPermission";
import ManagedUserAPI from "@/api/ManagedUserAPI";
import type { ManagedUser } from "@/types/ManagedUser";
import { corePermissionCodes } from "@/utils/permission";
defineOptions({
name: "UserRoleAssign"
});
const route = useRoute();
const gaoModuleCode: ModuleCode = "GAO";
const moduleCode: ModuleCode = "GAO";
const pageSize = 1000;
const userList = ref<GaoUser[]>([]);
const userList = ref<ManagedUser[]>([]);
const roleList = ref<Role[]>([]);
const routeGaoUserId = computed(() => String(route.params.id || route.query.gaoUserId || ""));
const activeGaoUserId = ref(routeGaoUserId.value);
const routeManagedUserId = computed(() => String(route.params.id || route.query.managedUserId || ""));
const activeManagedUserId = ref(routeManagedUserId.value);
const checkedRoleIds = ref<string[]>([]);
const loading = ref(false);
const saving = ref(false);
@@ -70,7 +70,7 @@ const userRoleWriteAccess = {
],
mode: "and"
} as const;
const activeUser = computed(() => userList.value.find(item => item.id === activeGaoUserId.value));
const activeUser = computed(() => userList.value.find(item => item.id === activeManagedUserId.value));
const activeUserLabel = computed(() => activeUser.value ? `当前用户:${getUserName(activeUser.value)}` : "选择用户");
const userOptions = computed<DropdownOption[]>(() => userList.value
.filter(item => !!item.id)
@@ -81,7 +81,7 @@ const userOptions = computed<DropdownOption[]>(() => userList.value
})));
async function handleUserChange(value: DropdownValue): Promise<void> {
activeGaoUserId.value = Array.isArray(value) ? String(value[0] || "") : String(value);
activeManagedUserId.value = Array.isArray(value) ? String(value[0] || "") : String(value);
await loadActiveUserRoles();
}
@@ -89,16 +89,16 @@ async function loadData(): Promise<void> {
loading.value = true;
try {
const [userResult, roles] = await Promise.all([
GaoUserAPI.list({
ManagedUserAPI.list({
index: 0,
size: 100
}),
loadGaoRoleList()
loadRoleList()
]);
userList.value = userResult.list || [];
roleList.value = roles || [];
if (!activeGaoUserId.value) {
activeGaoUserId.value = userList.value[0]?.id || "";
if (!activeManagedUserId.value) {
activeManagedUserId.value = userList.value[0]?.id || "";
}
await loadActiveUserRoles();
} finally {
@@ -107,17 +107,17 @@ async function loadData(): Promise<void> {
}
async function loadActiveUserRoles(): Promise<void> {
const gaoUserId = activeGaoUserId.value;
const gaoUser = activeUser.value;
if (!gaoUserId || !gaoUser?.userId) {
const managedUserId = activeManagedUserId.value;
const managedUser = activeUser.value;
if (!managedUserId || !managedUser?.userId) {
checkedRoleIds.value = [];
return;
}
const currentSeq = roleSyncSeq + 1;
roleSyncSeq = currentSeq;
const roles = await RoleAPI.authorizedList(gaoUser.userId, gaoModuleCode);
if (currentSeq !== roleSyncSeq || activeGaoUserId.value !== gaoUserId) {
const roles = await RoleAPI.authorizedList(managedUser.userId, moduleCode);
if (currentSeq !== roleSyncSeq || activeManagedUserId.value !== managedUserId) {
return;
}
@@ -139,7 +139,7 @@ async function saveRoleList(): Promise<void> {
try {
await RoleAPI.authorize({
userId: current.userId,
moduleCode: gaoModuleCode,
moduleCode: moduleCode,
roleIdList: [...checkedRoleIds.value]
});
Toast({
@@ -152,16 +152,16 @@ async function saveRoleList(): Promise<void> {
}
}
function getUserName(item: GaoUser): string {
function getUserName(item: ManagedUser): string {
return item.user?.nick || item.user?.name || item.user?.phoneNo || "未命名用户";
}
async function loadGaoRoleList(): Promise<Role[]> {
async function loadRoleList(): Promise<Role[]> {
const result = await RoleAPI.list({
index: 0,
size: pageSize,
equalsExample: {
moduleCode: gaoModuleCode
moduleCode: moduleCode
},
orderMap: {
code: OrderType.ASC
@@ -239,3 +239,4 @@ onMounted(async () => {
}
}
</style>