From 9df6dfaf59ed6eccabd60f9a4a8e6c9387dc569c Mon Sep 17 00:00:00 2001 From: Timi Date: Thu, 6 Aug 2026 20:53:22 +0800 Subject: [PATCH] refactor event --- src/api/CustomerAPI.ts | 143 ++---- src/api/EventAPI.ts | 13 +- src/api/EventRecordAPI.ts | 138 +----- src/api/ManagedUserAPI.ts | 13 +- src/components/CustomerSelector.vue | 10 +- src/components/TelephoneLink.vue | 1 + src/pages/customer/QuickEntryPage.vue | 258 +++++----- src/pages/customer/QuickSignInPage.vue | 229 +++------ src/pages/management/CustomerDataCharts.vue | 468 +----------------- src/pages/management/CustomerDetail.vue | 196 +++----- src/pages/management/CustomerEdit.vue | 444 ++++++----------- src/pages/management/CustomerList.vue | 224 ++++----- src/pages/management/EventEdit.vue | 189 +++---- src/pages/management/EventList.vue | 183 ++++--- src/pages/management/EventRecordDetail.vue | 33 +- src/pages/management/EventRecordEdit.vue | 414 ++++++++-------- src/pages/management/EventRecordList.vue | 220 ++++---- src/pages/management/EventSort.vue | 51 +- src/pages/management/ManagementDataCharts.vue | 48 +- src/pages/management/ManagementPassword.vue | 23 +- src/pages/management/ManagementProfile.vue | 50 +- src/pages/management/ManagementSettings.vue | 5 + src/pages/management/PermissionEdit.vue | 16 +- src/pages/management/PermissionList.vue | 61 ++- src/pages/management/PermissionSettings.vue | 21 +- src/pages/management/RoleEdit.vue | 19 +- src/pages/management/RoleList.vue | 69 +-- src/pages/management/RolePermissionAssign.vue | 20 +- src/pages/management/UserDetail.vue | 28 +- src/pages/management/UserEdit.vue | 50 +- src/pages/management/UserList.vue | 105 +++- src/pages/management/UserRoleAssign.vue | 60 ++- src/store/permissionSettingsStore.ts | 5 +- src/types/Customer.ts | 26 +- src/types/EventRecord.ts | 62 +-- src/utils/customer.ts | 3 +- src/utils/eventRecord.ts | 2 - 37 files changed, 1584 insertions(+), 2316 deletions(-) diff --git a/src/api/CustomerAPI.ts b/src/api/CustomerAPI.ts index 7857992..55700a7 100644 --- a/src/api/CustomerAPI.ts +++ b/src/api/CustomerAPI.ts @@ -1,36 +1,10 @@ import { axios, type Page, type PageResult } from "timi-web"; -import type { Customer, CustomerDailyStatView, CustomerGenderStatView, CustomerInviteUpdateReq, CustomerSearchReq, CustomerTrendStatReq, QuickRegisterReq } from "@/types/Customer"; -import type { EventRecord } from "@/types/EventRecord"; +import type { Customer, CustomerDailyStatView, CustomerGenderStatView, CustomerTrendStatReq } from "@/types/Customer"; const BASE_URI = "/gao/customer"; -async function list(req: Page): Promise> { - return await axios.post(`${BASE_URI}/list`, req); -} - -async function search(req: CustomerSearchReq): Promise { - const [codeMatched, nameMatched] = await Promise.all([ - req.keyword ? nullableDetailByCode(req.keyword) : undefined, - req.keyword ? nullableDetailByName(req.keyword) : undefined - ]); - return [codeMatched, nameMatched].filter((item, index, list): item is Customer => !!item && index === list.findIndex(current => current?.id === item.id)); -} - -async function searchPage(req: Page): Promise> { - return await axios.post(`${BASE_URI}/list`, { - ...req, - equalsExample: buildCustomerListExample(req.equalsExample) - }); -} - -async function exportExcel(req: CustomerSearchReq): Promise { - return await axios.post(`${BASE_URI}/export`, { - index: 0, - size: 100000, - equalsExample: buildCustomerListExample(req) - }, { - responseType: "blob" - }); +async function list(page: Page): Promise> { + return await axios.post(`${BASE_URI}/list`, page); } async function detail(id: string): Promise { @@ -39,52 +13,12 @@ async function detail(id: string): Promise { }); } -async function nullableDetailByCode(code: string): Promise { - return await axios.post(`${BASE_URI}/find/code`, undefined, { - params: { code } - }); +async function create(customer: Customer): Promise { + return await axios.post(`${BASE_URI}/create`, customer); } -async function nullableDetailByName(name: string): Promise { - return await axios.post(`${BASE_URI}/find/name`, undefined, { - params: { name } - }); -} - -async function invitedList(introducerCustomerId: string): Promise { - return await axios.post(`${BASE_URI}/invited/list`, undefined, { - params: { introducerCustomerId } - }); -} - -async function create(req: Customer): Promise { - const id = await axios.post(`${BASE_URI}/create`, req) as unknown as string; - return { - ...req, - id - }; -} - -async function quickRegister(req: QuickRegisterReq): Promise { - const customer = await nullableDetailByCode(req.customerCode); - if (!customer?.id) { - throw new Error("客户不存在,无法登记"); - } - const record: EventRecord = { - customerId: customer.id, - eventId: req.eventId, - remark: req.remark, - attachmentIdList: req.attachmentIdList - }; - await axios.post("/gao/event/record/create", { - ...record, - eventId: req.eventId - }); - return record; -} - -async function update(req: Customer): Promise { - return await axios.post(`${BASE_URI}/update`, req); +async function update(page: Customer): Promise { + return await axios.post(`${BASE_URI}/update`, page); } async function remove(id: string): Promise { @@ -93,15 +27,41 @@ async function remove(id: string): Promise { }); } -async function updateInvite(req: CustomerInviteUpdateReq): Promise { - if (!req.introducerCustomerId) { - await deleteInvite(req.customerId); - return; - } +async function findById(id: string): Promise { + return await axios.post(`${BASE_URI}/find/id`, null, { + params: { id } + }); +} + +async function findByCode(code: string): Promise { + return await axios.post(`${BASE_URI}/find/code`, null, { + params: { code } + }); +} + +async function findByName(name: string): Promise { + return await axios.post(`${BASE_URI}/find/name`, null, { + params: { name } + }) +} + +async function exportXSSF(page: Page): Promise { + return await axios.post(`${BASE_URI}/export`, page, { + responseType: "blob" + }); +} + +async function listInvited(introducerCustomerId: string): Promise { + return await axios.post(`${BASE_URI}/invited/list`, undefined, { + params: { introducerCustomerId } + }); +} + +async function saveInvite(introducerCustomerId: string, customerId: string): Promise { return await axios.post(`${BASE_URI}/invite/save`, undefined, { params: { - customerId: req.customerId, - introducerCustomerId: req.introducerCustomerId + introducerCustomerId, + customerId } }); } @@ -120,29 +80,18 @@ async function genderStat(): Promise { return await axios.post(`${BASE_URI}/state/gender`); } -function buildCustomerListExample(req?: CustomerSearchReq): Partial { - if (!req?.introducerCustomerId) { - return {}; - } - return { - introducerCustomerId: req.introducerCustomerId - }; -} - export default { list, - search, - searchPage, - exportExcel, detail, - nullableDetailByCode, - nullableDetailByName, - invitedList, create, - quickRegister, update, remove, - updateInvite, + findById, + findByCode, + findByName, + exportXSSF, + listInvited, + saveInvite, deleteInvite, dailyStat, genderStat diff --git a/src/api/EventAPI.ts b/src/api/EventAPI.ts index f340410..a6b61e8 100644 --- a/src/api/EventAPI.ts +++ b/src/api/EventAPI.ts @@ -7,7 +7,7 @@ async function list(req: Page): Promise> { return await axios.post(`${BASE_URI}/list`, req); } -async function enabledList(): Promise { +async function listValid(): Promise { return await axios.post(`${BASE_URI}/list/valid`); } @@ -17,12 +17,12 @@ async function detail(id: string): Promise { }); } -async function create(req: EventItem): Promise { - return await axios.post(`${BASE_URI}/create`, req); +async function create(event: EventItem): Promise { + return await axios.post(`${BASE_URI}/create`, event); } -async function update(req: EventItem): Promise { - return await axios.post(`${BASE_URI}/update`, req); +async function update(event: EventItem): Promise { + return await axios.post(`${BASE_URI}/update`, event); } async function sort(idList: string[]): Promise { @@ -37,11 +37,10 @@ async function remove(id: string): Promise { export default { list, - enabledList, + listValid, detail, create, update, sort, remove }; - diff --git a/src/api/EventRecordAPI.ts b/src/api/EventRecordAPI.ts index b077d30..d641c17 100644 --- a/src/api/EventRecordAPI.ts +++ b/src/api/EventRecordAPI.ts @@ -1,92 +1,33 @@ -import { axios, type Page, type PageResult } from "timi-web"; -import type { - CustomerEventRankResponse, - CustomerEventRankView, - EventRecord, - EventRecordCalendarView, - EventRecordCreateReq, - EventRecordDailyStatView, - EventRecordListItemView, - EventRecordPage, - EventRecordPageReq, - EventRecordTrendStatReq -} from "@/types/EventRecord"; +import { axios, type PageResult } from "timi-web"; +import type { CustomerEventRankView, EventRecord, EventRecordPage, EventRecordRankPage } from "@/types/EventRecord"; +import type { Customer } from "@/types/Customer.ts"; const BASE_URI = "/gao/event/record"; -async function list(req: Page): Promise> { - return await axios.post(`${BASE_URI}/list`, buildRecordPage(req)); +async function list(page: EventRecordPage): Promise> { + return await axios.post(`${BASE_URI}/list`, page); } async function detail(id: string): Promise { - return await axios.post(`${BASE_URI}/detail`, undefined, { + return await axios.post(`${BASE_URI}/detail`, null, { params: { id } }); } -async function rangeList(req: EventRecordPage): Promise { - const result: PageResult = await axios.post(`${BASE_URI}/list`, buildRecordPage({ - index: 0, - size: 1000, - equalsExample: req - })); - return result.list; +async function create(record: EventRecord): Promise { + await axios.post(`${BASE_URI}/create`, record); } -async function calendarRangeList(req: EventRecordPage): Promise { - const result = await rangeList(req); - return result.map(item => ({ - id: item.id, - customerId: item.customerId, - customerCode: item.customer?.code || "", - customerName: item.customer?.name || "", - eventId: item.eventId, - registeredAt: item.registeredAt - })); +async function createBatch(recordList: EventRecord[]): Promise { + await axios.post(`${BASE_URI}/create/batch`, recordList); } -async function rangePage(req: Page): Promise> { - const result: PageResult = await axios.post(`${BASE_URI}/list`, buildRecordPage(req)); - return { - total: result.total, - list: result.list.map(item => ({ - id: item.id, - customerId: item.customerId, - customerName: item.customer?.name || "", - customerPhotoAttachmentId: item.customer?.attachmentList?.[0]?.id, - eventId: item.eventId, - eventName: item.event?.name || "", - registeredAt: item.registeredAt - })) - }; +async function createWithCustomer(customer: Customer): Promise { + await axios.post(`${BASE_URI}/create/customer`, customer); } -async function rangeExport(req: EventRecordPage): Promise { - return await axios.post(`${BASE_URI}/export`, buildRecordPage({ - index: 0, - size: 100000, - equalsExample: req - }), { - responseType: "blob" - }); -} - -async function create(req: EventRecordCreateReq): Promise { - const recordList = req.customerIdList.map(customerId => ({ - customerId, - eventId: req.eventId, - remark: req.remark, - registeredAt: req.registeredAt, - attachmentIdList: req.attachmentIdList - })); - for (const record of recordList) { - await axios.post(`${BASE_URI}/create`, record); - } - return recordList; -} - -async function update(req: EventRecord): Promise { - await axios.post(`${BASE_URI}/update`, req); +async function update(record: EventRecord): Promise { + await axios.post(`${BASE_URI}/update`, record); } async function remove(id: string): Promise { @@ -95,56 +36,25 @@ async function remove(id: string): Promise { }); } -async function customerRegistrationRank(req?: Partial): Promise { - const result: PageResult = await axios.post(`${BASE_URI}/rank`, buildRecordPage({ - index: 0, - size: 100, - equalsExample: req - })); - return result.list.map(item => ({ - rank: item.rank, - customerId: item.customerId, - customerCode: item.customer?.code || "", - customerName: item.customer?.name || "", - customerPhotoAttachmentId: item.customer?.attachmentList?.[0]?.id, - totalEventDays: item.totalEventDays, - totalEventCount: item.totalEventCount, - recentContinuousEventDays: item.recentContinuousEventDays, - latestRegisteredAt: item.latestRegisteredAt - })); +async function eventRank(page: EventRecordRankPage): Promise { + return await axios.post(`${BASE_URI}/rank`, page); } -async function allCustomerEventDailyStat(req: EventRecordTrendStatReq): Promise { - void req; - return []; -} - -function buildRecordPage(req: Page | Page>): EventRecordPageReq { - const equalsExample = req.equalsExample || {}; - const eventIdList = "eventIdList" in equalsExample ? equalsExample.eventIdList : undefined; - const singleEventId = "eventId" in equalsExample ? equalsExample.eventId : undefined; - return { - index: req.index, - size: req.size, - customerId: equalsExample.customerId, - eventIdList: eventIdList || (singleEventId ? [singleEventId] : undefined), - keyword: "keyword" in equalsExample ? equalsExample.keyword : undefined, - beginAt: "beginAt" in equalsExample ? equalsExample.beginAt : undefined, - endAt: "endAt" in equalsExample ? equalsExample.endAt : undefined - }; +async function exportXSSF(page: EventRecordPage): Promise { + return await axios.post(`${BASE_URI}/export`, page, { + responseType: "blob" + }); } export default { list, detail, - rangeList, - calendarRangeList, - rangePage, - rangeExport, + exportXSSF, create, + createBatch, + createWithCustomer, update, remove, - customerRegistrationRank, - allCustomerEventDailyStat + eventRank }; diff --git a/src/api/ManagedUserAPI.ts b/src/api/ManagedUserAPI.ts index f7525d7..c41cf60 100644 --- a/src/api/ManagedUserAPI.ts +++ b/src/api/ManagedUserAPI.ts @@ -3,8 +3,8 @@ import type { ManagedUser } from "@/types/ManagedUser"; const BASE_URI = "/gao/user"; -async function list(req: Page): Promise> { - return await axios.post(`${BASE_URI}/list`, req); +async function list(page: Page): Promise> { + return await axios.post(`${BASE_URI}/list`, page); } async function detail(id: string): Promise { @@ -13,12 +13,12 @@ async function detail(id: string): Promise { }); } -async function create(req: ManagedUser): Promise { - return await axios.post(`${BASE_URI}/create`, req); +async function create(user: ManagedUser): Promise { + return await axios.post(`${BASE_URI}/create`, user); } -async function update(req: ManagedUser): Promise { - return await axios.post(`${BASE_URI}/update`, req); +async function update(user: ManagedUser): Promise { + return await axios.post(`${BASE_URI}/update`, user); } async function remove(id: string): Promise { @@ -39,4 +39,3 @@ export default { remove, roleList }; - diff --git a/src/components/CustomerSelector.vue b/src/components/CustomerSelector.vue index 4fb1407..8d71a50 100644 --- a/src/components/CustomerSelector.vue +++ b/src/components/CustomerSelector.vue @@ -109,11 +109,13 @@ async function loadCustomers(): Promise { searchToken = token; loading.value = true; try { - const result = await CustomerAPI.searchPage({ + const result = await CustomerAPI.list({ index: pageIndex.value, size: pageSize, - equalsExample: { - keyword: keyword.value || undefined + likesExample: { + code: keyword.value || undefined, + name: keyword.value || undefined, + telephone: keyword.value || undefined } }); if (token !== searchToken) { @@ -253,7 +255,7 @@ onUnmounted(() => { .search-wrapper { z-index: 1; padding: 1rem 0 .5rem 0; - box-shadow: var(--td-shadow-1); + box-shadow: 0 4px 4px rgba(0, 0, 0, .05); .search { --td-input-vertical-padding: 8px; diff --git a/src/components/TelephoneLink.vue b/src/components/TelephoneLink.vue index 2f6b93f..d1e2831 100644 --- a/src/components/TelephoneLink.vue +++ b/src/components/TelephoneLink.vue @@ -7,6 +7,7 @@ :class="{ clickable: !!href }" @click="handleClick" > + diff --git a/src/pages/customer/QuickEntryPage.vue b/src/pages/customer/QuickEntryPage.vue index 19af752..aa613c6 100644 --- a/src/pages/customer/QuickEntryPage.vue +++ b/src/pages/customer/QuickEntryPage.vue @@ -50,12 +50,12 @@ @@ -76,18 +76,18 @@
- diff --git a/src/pages/management/CustomerDetail.vue b/src/pages/management/CustomerDetail.vue index d40a4ee..4dc1e31 100644 --- a/src/pages/management/CustomerDetail.vue +++ b/src/pages/management/CustomerDetail.vue @@ -4,7 +4,7 @@ class="customer" :customer="customer" /> - + - + @@ -36,18 +36,16 @@ - - + + -
+
邀请客户
- @@ -103,7 +98,7 @@ class="delete bg-white" theme="danger" variant="outline" - :loading="removing" + :loading="isRemoving" @click="confirmRemoveCustomer" > 删除客户 @@ -178,7 +173,7 @@ const selectorVisible = ref(false); const scanPopupVisible = ref(false); const pendingInvitee = ref(); const scanStatus = ref("点击扫码开始识别。"); -const removing = ref(false); +const isRemoving = ref(false); const showSolarBirthdate = ref(false); let latestDecodedText = ""; @@ -188,7 +183,7 @@ const creatorName = computed(() => { const creatorUser = customer.value?.creatorUser; return creatorUser?.nick || creatorUser?.name || customer.value?.creatorUserId || "无"; }); -const createdAtText = computed(() => formatTime(customer.value?.createdAt)); +const createdAtText = computed(() => customer.value?.createdAt ? Time.toPassedDateTime(customer.value.createdAt) : "无"); const isLunarBirthdate = computed(() => customer.value?.birthdateCalendar === "LUNAR" && !!customer.value?.birthdate); const solarBirthdateText = computed(() => formatSolarBirthdate(customer.value)); const birthdateText = computed(() => showSolarBirthdate.value && isLunarBirthdate.value ? solarBirthdateText.value : formatBirthdate(customer.value)); @@ -219,7 +214,7 @@ async function loadCustomer(): Promise { customer.value = undefined; invitees.value = []; console.warn("客户详情加载失败", err); - showToast("客户详情加载失败", "warning"); + Toast.warning("客户详情加载失败"); } } @@ -230,7 +225,7 @@ async function loadInvitees(): Promise { return; } - invitees.value = await CustomerAPI.invitedList(customerId); + invitees.value = await CustomerAPI.listInvited(customerId); } async function removeInvitee(item: Customer): Promise { @@ -240,7 +235,7 @@ async function removeInvitee(item: Customer): Promise { await CustomerAPI.deleteInvite(item.id); await loadCustomer(); - showToast("已删除邀请关系"); + Toast.success("已删除邀请关系"); } async function confirmRemoveCustomer(): Promise { @@ -267,13 +262,13 @@ async function confirmRemoveCustomer(): Promise { } async function removeCustomer(id: string): Promise { - removing.value = true; + isRemoving.value = true; try { await CustomerAPI.remove(id); - showToast("客户已删除"); + Toast.success("客户已删除"); await router.replace("/management/customers"); } finally { - removing.value = false; + isRemoving.value = false; } } @@ -284,25 +279,6 @@ async function toEdit(): Promise { await router.push(`/management/customers/${customer.value.id}/edit`); } -function showToast(message: string, theme: "success" | "warning" = "success"): void { - Toast({ - theme, - direction: "row", - message - }); -} - -function formatTime(value?: number): string { - if (!value) { - return "无"; - } - const date = Time.toDateObject(value); - if (!date) { - return "无"; - } - return Time.formatDateTime(date); -} - function formatBirthdate(value?: Customer): string { if (!value?.birthdate) { return "无"; @@ -345,20 +321,17 @@ async function addInvitee(invitee: Customer): Promise { return false; } if (invitee.id === customer.value.id) { - showToast("不能把客户本人添加为邀请客户", "warning"); + Toast.warning("不能把客户本人添加为邀请客户"); return false; } if (isCustomerAncestor(invitee)) { - showToast("不能把上级邀请人添加为邀请客户", "warning"); + Toast.warning("不能把上级邀请人添加为邀请客户"); return false; } - await CustomerAPI.updateInvite({ - customerId: invitee.id, - introducerCustomerId: customer.value.id - }); + await CustomerAPI.saveInvite(customer.value.id, invitee.id); await loadCustomer(); - showToast(`已添加 ${invitee.name || invitee.code}`); + Toast.success(`已添加 ${invitee.name || invitee.code}`); return true; } @@ -403,7 +376,7 @@ function handleScanStatusChange(value: string): void { async function confirmScannedInvitee(): Promise { if (!pendingInvitee.value) { - showToast("请先扫码识别客户", "warning"); + Toast.warning("请先扫码识别客户"); return; } @@ -413,17 +386,17 @@ async function confirmScannedInvitee(): Promise { } async function loadCustomerByCode(code: string): Promise { - return await CustomerAPI.nullableDetailByCode(code) || undefined; + return await CustomerAPI.findByCode(code) || undefined; } -async function goCustomerDetail(target: Customer): Promise { +async function toCustomerDetail(target: Customer): Promise { if (!target.id) { return; } await router.push(`/management/customers/${target.id}`); } -async function goCustomerDataCharts(): Promise { +async function toCustomerDataCharts(): Promise { if (!customer.value?.id) { return; } @@ -451,19 +424,18 @@ watch(() => route.params.id, async () => { margin: 0 1rem; } - :deep(.t-cell__title) { - flex: 0 0 auto; - } + .info { - :deep(.t-cell__title-text) { - white-space: nowrap; - } + :deep(.t-cell__title-text) { + white-space: nowrap; + } - :deep(.t-cell__note) { - flex: 1 1 auto; - min-width: 0; - white-space: normal; - overflow-wrap: anywhere; + :deep(.t-cell__note) { + flex: 1 1 auto; + min-width: 0; + white-space: normal; + overflow-wrap: anywhere; + } } .conditioning-part { @@ -473,59 +445,7 @@ watch(() => route.params.id, async () => { flex-direction: column; } - .invite { - - .inviter-profile { - gap: .5rem; - display: flex; - cursor: pointer; - padding: .75rem; - align-items: center; - - .profile-card { - flex: 1; - min-width: 0; - } - } - - .item { - - .title { - gap: .5rem; - display: flex; - align-items: center; - } - - &:last-child { - - :deep(.t-cell:after) { - display: none; - } - } - } - - .avatar { - width: 3rem; - height: 3rem; - border-radius: .875rem; - - &.fallback { - color: #2563eb; - display: flex; - font-size: 1.125rem; - background: #dbeafe; - align-items: center; - font-weight: 600; - justify-content: center; - } - } - - .remove { - width: fit-content; - padding: 0 1rem; - display: flex; - align-items: center; - } + .invite-list { .header { display: flex; @@ -542,11 +462,48 @@ watch(() => route.params.id, async () => { } .actions { - gap: 1rem; + gap: .875rem; display: flex; } } + .item { + + &:last-child { + + :deep(.t-cell:after) { + display: none; + } + } + + .cell { + padding: 6px 10px 6px 6px; + + .avatar { + width: 3.25rem; + height: 3.25rem; + border-radius: .875rem; + + &.fallback { + color: #2563eb; + display: flex; + font-size: 1.125rem; + background: #dbeafe; + align-items: center; + font-weight: 600; + justify-content: center; + } + } + } + } + + .remove { + width: fit-content; + padding: 0 1rem; + display: flex; + align-items: center; + } + .empty { color: #52607a; padding: 1rem; @@ -627,4 +584,3 @@ watch(() => route.params.id, async () => { } } - diff --git a/src/pages/management/CustomerEdit.vue b/src/pages/management/CustomerEdit.vue index 05637ab..b9bbfac 100644 --- a/src/pages/management/CustomerEdit.vue +++ b/src/pages/management/CustomerEdit.vue @@ -3,7 +3,7 @@ @@ -29,50 +29,50 @@
- + 男 女 未知 - +
选择 - - + +
-
+
@@ -102,7 +102,7 @@ @@ -110,7 +110,7 @@ @@ -122,7 +122,7 @@ @@ -139,8 +139,8 @@ theme="primary" class="save" :disabled="!canSubmit" - :loading="saving" - @click="saveCustomer" + :loading="isSubmitting" + @click="doSubmit" > @@ -164,8 +164,8 @@ import CodeScanner from "@/components/CodeScanner.vue"; import CustomerSelector from "@/components/CustomerSelector.vue"; 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 type { BodyAreaValue, Customer } from "@/types/Customer"; +import { createBodyAreaValue, normalizeBodyAreaValue } from "@/utils/customer"; import { permissionCodes } from "@/utils/permission"; import { compressUploadImage } from "@/utils/imageCompression"; @@ -173,126 +173,27 @@ defineOptions({ name: "CustomerEdit" }); -type ScanTarget = "code" | "referrer"; - const route = useRoute(); const router = useRouter(); const settingStore = useSettingStore(); const isCreateMode = computed(() => route.name === "CustomerCreate"); const savePermissionCode = computed(() => isCreateMode.value ? permissionCodes.customer.create : permissionCodes.customer.update); -const selectedAreas = ref(createBodyAreaValue()); -const selectorVisible = ref(false); -const referrerCustomerId = ref(""); -const photoInputRef = ref(); -const photoTempFileId = ref(""); -const photoUploading = ref(false); -const scannerVisible = ref(false); -const scannerTarget = ref("code"); -const scanStatus = ref("点击扫码开始识别。"); -const currentCustomer = ref(); -const saving = ref(false); -const canSubmit = computed(() => !!formModel.name.trim()); + let latestDecodedText = ""; -const formModel = reactive({ - code: "", - name: "", - gender: "" as CustomerGender | "", - photoURL: "", - telephone: "", - age: "", - birthdate: "", - birthdateCalendar: "SOLAR" as CustomerBirthdateCalendar, - address: "", - referrer: "", - disease: "", - inviterCustomerId: "", - note: "" -}); - -function resetFormModel(): void { - Object.assign(formModel, { - code: "", - name: "", - gender: "", - photoURL: "", - telephone: "", - age: "", - birthdate: "", - birthdateCalendar: "SOLAR", - address: "", - referrer: "", - disease: "", - inviterCustomerId: "", - note: "" - }); - currentCustomer.value = undefined; - selectedAreas.value = createBodyAreaValue(); - referrerCustomerId.value = ""; - photoTempFileId.value = ""; - scannerVisible.value = false; -} - -async function fillCreateInviter(): Promise { - const inviterCode = String(route.query.inviterCode || ""); - if (!inviterCode) { - return; - } - - const inviter = await findCustomer(inviterCode); - if (!inviter) { - return; - } - formModel.referrer = inviter.name || inviter.code || ""; - formModel.inviterCustomerId = inviter.id || ""; - referrerCustomerId.value = inviter.id || ""; -} - -async function fillForm(): Promise { - resetFormModel(); - if (isCreateMode.value) { - await fillCreateInviter(); - return; - } - - const id = String(route.params.id || ""); - if (!id) { - return; - } - try { - const customer = await CustomerAPI.detail(id); - currentCustomer.value = customer; - Object.assign(formModel, { - code: customer.code || "", - name: customer.name || "", - gender: customer.gender || "", - photoURL: getCustomerPhotoURL(customer), - telephone: customer.telephone || "", - age: customer.age === undefined || customer.age === null ? "" : String(customer.age), - birthdate: customer.birthdate || "", - birthdateCalendar: customer.birthdateCalendar || "SOLAR", - address: customer.address || "", - referrer: customer.introducerCustomer?.name || "", - disease: customer.disease || "", - inviterCustomerId: customer.introducerCustomerId || "", - note: customer.remark || "" - }); - selectedAreas.value = normalizeBodyAreaValue(customer.conditioningPart); - referrerCustomerId.value = customer.introducerCustomer?.id || ""; - } catch (err) { - console.warn("客户详情加载失败", err); - showToast("客户详情加载失败", "warning"); - } -} - // ---------- 扫码 ---------- +type ScanTarget = "code" | "introducer"; +const scannerVisible = ref(false); +const scannerTarget = ref("code"); +const scanStatus = ref("点击扫码开始识别"); +const introducerCustomerId = ref(""); + async function toggleScanner(target: ScanTarget): Promise { if (scannerVisible.value && scannerTarget.value === target) { scannerVisible.value = false; return; } - scannerTarget.value = target; scannerVisible.value = true; latestDecodedText = ""; @@ -303,210 +204,157 @@ async function handleDecodedText(rawText: string): Promise { if (!normalizedText || normalizedText === latestDecodedText) { return; } - latestDecodedText = normalizedText; await settingStore.playScanSuccessSound(); if (scannerTarget.value === "code") { - formModel.code = normalizedText; - scanStatus.value = `已识别客户编码 ${normalizedText}。`; + // 选择客户扫码 + formData.code = normalizedText; + scanStatus.value = `已识别客户编码 ${normalizedText}`; scannerVisible.value = false; return; + } else { + // 选择介绍人扫码 + await applyIntroducer(normalizedText); } - - await applyReferrer(normalizedText); } function handleScanStatusChange(value: string): void { scanStatus.value = value; } +// ---------- 介绍人 ---------- + +const selectorVisible = ref(false); + +async function applyIntroducer(code: string): Promise { + if (!code) { + formData.introducerCustomerId = ""; + introducerCustomerId.value = ""; + scannerVisible.value = false; + return; + } + const matched = await CustomerAPI.findByCode(code); + if (!matched?.id) { + formData.introducerCustomerId = ""; + introducerCustomerId.value = ""; + scannerVisible.value = false; + scannerVisible.value = false; + Toast.warning(`未匹配到 ${code} 编码介绍人,请选择或扫码已有客户`); + return; + } + formData.introducerCustomerId = matched.id; + formData.introducerCustomer = matched; + introducerCustomerId.value = matched.id; + scannerVisible.value = false; +} + +async function handleIntroducerChange(id: string): Promise { + const customer = await CustomerAPI.findById(id); + introducerCustomerId.value = customer!.id!; + formData.introducerCustomer = customer; +} + +// ---------- 表单 ---------- + +const photoInputRef = ref(); +const photoTempFileId = ref(""); +const isPhotoUploading = ref(false); +const photoURL = ref(); +const selectedAreas = ref(createBodyAreaValue()); +const formData = reactive({ +}); + +const canSubmit = computed(() => formData.name?.trim()); + +async function loadDetail() { + const id = String(route.params.id || ""); + if (!id) { + return; + } + try { + const customer = await CustomerAPI.detail(id); + Object.assign(formData, customer); + selectedAreas.value = normalizeBodyAreaValue(customer.conditioningPart); + introducerCustomerId.value = customer.introducerCustomer?.id || ""; + } catch (err) { + console.warn("客户详情加载失败", err); + Toast.error("客户详情加载失败"); + } +} +onMounted(loadDetail); +watch(() => route.fullPath, loadDetail); + +// ---------- 年龄 ---------- + +watch(() => formData.birthdate, (value) => { + if (value) { + const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value); + if (match) { + const birthYear = Number(match[1]); + const birthMonth = Number(match[2]); + const birthDay = Number(match[3]); + const today = new Date(); + let age = today.getFullYear() - birthYear; + if (today.getMonth() + 1 < birthMonth || (today.getMonth() + 1 === birthMonth && today.getDate() < birthDay)) { + age--; + } + formData.age = Math.max(age, 0); + } else { + formData.age = undefined; + } + } +}); + // ---------- 照片 ---------- -function selectPhoto(): void { - photoInputRef.value?.click(); -} - -function resetPhotoState(): void { - photoTempFileId.value = ""; -} - async function handlePhotoSelected(event: Event): Promise { const input = event.target as HTMLInputElement; const file = input.files?.[0]; if (!file) { return; } - photoUploading.value = true; + isPhotoUploading.value = true; try { const compressedFile = await compressUploadImage(file); - const [tempFile] = await AttachmentAPI.uploadTemp(compressedFile); - if (!tempFile?.id) { - showToast("客户照片上传失败", "warning"); - return; - } + const [tempFile] = await AttachmentAPI.upload(compressedFile); photoTempFileId.value = tempFile.id; - formModel.photoURL = AttachmentAPI.getTempReadURL(tempFile.id); + photoURL.value = AttachmentAPI.getTempReadURL(tempFile.id); } finally { - photoUploading.value = false; + isPhotoUploading.value = false; input.value = ""; } } -// ---------- 介绍人 ---------- - -async function findCustomer(rawValue: string): Promise { - const normalizedText = rawValue.trim(); - if (!normalizedText) { - return undefined; - } - - const customer = await CustomerAPI.nullableDetailByCode(normalizedText); - if (customer) { - return customer; - } - - const list = await CustomerAPI.search({ keyword: normalizedText }); - return list[0]; -} - -async function applyReferrer(rawValue: string): Promise { - const normalizedText = rawValue.trim(); - if (!normalizedText) { - formModel.referrer = ""; - formModel.inviterCustomerId = ""; - referrerCustomerId.value = ""; - scannerVisible.value = false; - return; - } - - const matched = await findCustomer(normalizedText); - if (!matched?.id) { - formModel.referrer = ""; - formModel.inviterCustomerId = ""; - referrerCustomerId.value = ""; - scanStatus.value = `未匹配到介绍人 ${normalizedText},请重新选择或扫码。`; - scannerVisible.value = false; - showToast("介绍人不存在,请选择或扫码已有客户", "warning"); - return; - } - - formModel.referrer = matched.name || matched.code || ""; - formModel.inviterCustomerId = matched.id; - referrerCustomerId.value = matched.id; - scanStatus.value = `已匹配介绍人 ${formModel.referrer}。`; - scannerVisible.value = false; -} - -async function handleReferrerChange(id: string): Promise { - const customer = await CustomerAPI.detail(id); - formModel.referrer = customer.name || customer.code || ""; - formModel.inviterCustomerId = customer.id || ""; - referrerCustomerId.value = customer.id || ""; - scanStatus.value = `已选择介绍人 ${formModel.referrer}。`; -} - // ---------- 保存 ---------- -async function saveCustomer(): Promise { - if (!validateForm()) { - return; - } +const isSubmitting = ref(false); - const customerCode = formModel.code.trim(); - const payload: Customer = { - ...(currentCustomer.value || {}), - code: customerCode || null, - name: formModel.name, - gender: formModel.gender || undefined, - age: Number(formModel.age) || undefined, - birthdate: formModel.birthdate || null, - birthdateCalendar: formModel.birthdate ? formModel.birthdateCalendar : undefined, - telephone: formModel.telephone, - address: formModel.address, - remark: formModel.note, - disease: formModel.disease, - conditioningPart: selectedAreas.value, - introducerCustomerId: formModel.inviterCustomerId || null, - attachmentIdList: photoTempFileId.value ? [photoTempFileId.value] : undefined - }; - - saving.value = true; +async function doSubmit(): Promise { + isSubmitting.value = true; try { - let savedCustomer = currentCustomer.value; + formData.conditioningPart = selectedAreas.value; + formData.attachmentList = photoTempFileId.value ? [{ + tempFileId: photoTempFileId.value + }] : undefined; + if (isCreateMode.value) { - savedCustomer = await CustomerAPI.create(payload); + const id = await CustomerAPI.create(formData); + Toast.success("创建成功"); + await router.replace(`/management/customers/${id}`); } else { - await CustomerAPI.update(payload); - } - showToast("客户已保存"); - const redirect = String(route.query.redirect || ""); - if (redirect) { - await router.replace(redirect); - return; - } - if (savedCustomer?.id) { - await router.replace(`/management/customers/${savedCustomer.id}`); - return; + await CustomerAPI.update(formData); + Toast.success("保存成功"); + const redirect = String(route.query.redirect || ""); + if (redirect) { + await router.replace(redirect); + return; + } } await router.replace("/management/customers"); } finally { - saving.value = false; + isSubmitting.value = false; } } - -function validateForm(): boolean { - if (!formModel.name.trim()) { - showToast("请填写客户姓名", "warning"); - return false; - } - - return true; -} - -function showToast(message: string, theme: "success" | "warning" = "success"): void { - Toast({ - theme, - direction: "row", - message - }); -} - -watch(() => formModel.referrer, (value) => { - if (!value.trim()) { - formModel.inviterCustomerId = ""; - referrerCustomerId.value = ""; - } -}); - -watch(() => formModel.birthdate, (value) => { - if (value) { - formModel.age = String(calculateAge(value)); - } -}); - -function calculateAge(value: string): number { - const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value); - if (!match) { - return 0; - } - const birthYear = Number(match[1]); - const birthMonth = Number(match[2]); - const birthDay = Number(match[3]); - const today = new Date(); - let age = today.getFullYear() - birthYear; - if (today.getMonth() + 1 < birthMonth || (today.getMonth() + 1 === birthMonth && today.getDate() < birthDay)) { - age--; - } - return Math.max(age, 0); -} - -onMounted(async () => { - await fillForm(); -}); - -watch(() => route.fullPath, async () => { - await fillForm(); -}); - diff --git a/src/pages/management/EventList.vue b/src/pages/management/EventList.vue index d1a42a6..578c4e7 100644 --- a/src/pages/management/EventList.vue +++ b/src/pages/management/EventList.vue @@ -1,47 +1,55 @@ -
+
保存排序 @@ -65,12 +65,18 @@ defineOptions({ }); const navBarStore = useNavBarStore(); + +// ---------- 列表数据 ---------- + const defaultColor = "#2f88ff"; const eventList = ref([]); const sortListRef = ref(); -const loading = ref(false); -const saving = ref(false); +const isLoading = ref(false); +const isSaving = ref(false); const sortDirty = ref(false); + +// ---------- 拖拽状态 ---------- + const draggingIndex = ref(-1); let pointerId: number | undefined; let pointerStartY = 0; @@ -78,6 +84,8 @@ let pointerMoved = false; let pointerTarget: HTMLElement | undefined; let dragInputType: "pointer" | "touch" | undefined; +// ---------- 展示文案 ---------- + function getStatusText(status?: EventStatus): string { if (status === "DRAFT") { return "草稿"; @@ -98,6 +106,8 @@ function getStatusTheme(status?: EventStatus): TagProps["theme"] { return "primary"; } +// ---------- 拖拽排序 ---------- + function resolveDropIndex(clientY: number): number { const elements = sortListRef.value?.querySelectorAll(".item"); if (!elements?.length) { @@ -152,7 +162,7 @@ function finishDragState(): void { } function startDrag(event: PointerEvent, index: number): void { - if (saving.value || loading.value || dragInputType || !event.isPrimary || event.button !== 0) { + if (isSaving.value || isLoading.value || dragInputType || !event.isPrimary || event.button !== 0) { return; } @@ -184,7 +194,7 @@ function finishDrag(event: PointerEvent): void { } function startTouchDrag(event: TouchEvent, index: number): void { - if (saving.value || loading.value || dragInputType || event.touches.length !== 1) { + if (isSaving.value || isLoading.value || dragInputType || event.touches.length !== 1) { return; } @@ -216,12 +226,14 @@ function finishTouchDrag(event: TouchEvent): void { finishDragState(); } +// ---------- 保存 ---------- + async function saveSort(): Promise { - if (!sortDirty.value || saving.value) { + if (!sortDirty.value || isSaving.value) { return; } - saving.value = true; + isSaving.value = true; try { const idList = eventList.value .map(item => item.id) @@ -231,24 +243,18 @@ async function saveSort(): Promise { item.sort = index; }); sortDirty.value = false; - showToast("登记事件排序已保存"); + Toast.success("登记事件排序已保存"); } catch { - showToast("登记事件排序保存失败", "error"); + Toast.error("登记事件排序保存失败"); } finally { - saving.value = false; + isSaving.value = false; } } -function showToast(message: string, theme: "success" | "error" = "success"): void { - Toast({ - theme, - direction: "row", - message - }); -} +// ---------- 页面数据 ---------- async function loadData(): Promise { - loading.value = true; + isLoading.value = true; try { const result = await EventAPI.list({ index: 0, @@ -257,7 +263,7 @@ async function loadData(): Promise { eventList.value = result.list || []; sortDirty.value = false; } finally { - loading.value = false; + isLoading.value = false; } } @@ -266,7 +272,7 @@ onMounted(async () => { try { await loadData(); } catch { - showToast("登记事件加载失败", "error"); + Toast.error("登记事件加载失败"); } }); @@ -351,4 +357,3 @@ onUnmounted(() => { } } - diff --git a/src/pages/management/ManagementDataCharts.vue b/src/pages/management/ManagementDataCharts.vue index 75aa833..4cf5bd3 100644 --- a/src/pages/management/ManagementDataCharts.vue +++ b/src/pages/management/ManagementDataCharts.vue @@ -12,7 +12,7 @@ v-model:selected-event-id-list="selectedEventIdList" :event-list="eventList" empty-text="当前月份暂无登记事件记录" - :loading="calendarLoading" + :loading="isCalendarLoading" :records="monthRecords" /> @@ -35,7 +35,6 @@ import { init, use } from "echarts/core"; import { CanvasRenderer } from "echarts/renderers"; import CustomerAPI from "@/api/CustomerAPI"; 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 { EventItem } from "@/types/EventItem"; @@ -69,14 +68,17 @@ use([ const chartRefs = new Map(); const chartInstances = new Map(); + +// ---------- 数据源 ---------- + const eventList = ref([]); const selectedEventIdList = ref([]); const customerDailyStatList = ref([]); const customerGenderStatList = ref([]); const recordDailyStatMap = ref>({}); const monthRecords = ref([]); -const loading = ref(false); -const calendarLoading = ref(false); +const isLoading = ref(false); +const isCalendarLoading = ref(false); const monthCursor = ref(getMonthStart(new Date())); const genderColorMap = { MALE: "#2563eb", @@ -91,6 +93,8 @@ const timestampRange = { endAt: Time.toTimestamp(endDate) }; +// ---------- 摘要 ---------- + const summaryList = computed(() => [ { label: "当前客户", @@ -106,6 +110,8 @@ const summaryList = computed(() => [ } ]); +// ---------- 图表配置 ---------- + const chartList = computed(() => [ { id: "customer-growth", @@ -134,6 +140,8 @@ watch(chartList, async () => { renderCharts(); }); +// ---------- 日历 ---------- + watch(monthCursor, async () => { await loadMonthRecords(); }); @@ -152,6 +160,8 @@ onBeforeUnmount(() => { chartInstances.clear(); }); +// ---------- 图表实例 ---------- + function setChartRef(id: string, element: Element | ComponentPublicInstance | null): void { if (element instanceof HTMLElement) { chartRefs.set(id, element); @@ -185,10 +195,12 @@ function resizeCharts(): void { chartInstances.forEach(item => item.resize()); } +// ---------- 页面数据 ---------- + async function loadChartsData(): Promise { - loading.value = true; + isLoading.value = true; try { - eventList.value = await EventAPI.enabledList(); + eventList.value = await EventAPI.listValid(); 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([ @@ -199,36 +211,35 @@ async function loadChartsData(): Promise { CustomerAPI.genderStat(), Promise.all(valideventList.map(async item => ({ eventId: item.id, - stats: await EventRecordAPI.allCustomerEventDailyStat({ - eventId: item.id, - beginAt: timestampRange.beginAt, - endAt: timestampRange.endAt - }) + stats: [] }))) ]); customerDailyStatList.value = customerStatList; customerGenderStatList.value = genderStatList; recordDailyStatMap.value = Object.fromEntries(recordDailyStatList.map(item => [item.eventId, item.stats])); } finally { - loading.value = false; + isLoading.value = false; } } async function loadMonthRecords(): Promise { const begin = getCalendarBeginDate(monthCursor.value); const end = getDateEnd(addDays(begin, 41)); - calendarLoading.value = true; + isCalendarLoading.value = true; try { - const calendarRecordList = await EventRecordAPI.calendarRangeList({ + await Promise.resolve({ beginAt: Time.toTimestamp(begin), endAt: Time.toTimestamp(end) }); + const calendarRecordList: EventRecordCalendarView[] = []; monthRecords.value = calendarRecordList.map(buildCalendarRecord); } finally { - calendarLoading.value = false; + isCalendarLoading.value = false; } } +// ---------- 图表构建 ---------- + function buildBaseOption(): EChartsOption { return { color: ["#2563eb", "#0f766e", "#d97706", "#db2777", "#7c3aed"], @@ -381,6 +392,8 @@ function buildGenderStatOption(): EChartsOption { }; } +// ---------- 统计计算 ---------- + function getLatestTotalCustomerCount(): number { const latest = customerDailyStatList.value[customerDailyStatList.value.length - 1]; return latest?.totalCustomerCount || 0; @@ -416,6 +429,8 @@ function getGenderCount(genderCode: "MALE" | "FEMALE" | "UNKNOWN"): number { return customerGenderStatList.value.find(item => item.genderCode === genderCode)?.count || 0; } +// ---------- 日历转换 ---------- + function buildCalendarRecord(record: EventRecordCalendarView): EventRecord { return { id: record.id, @@ -430,6 +445,8 @@ function buildCalendarRecord(record: EventRecordCalendarView): EventRecord { }; } +// ---------- 日期工具 ---------- + function getDateStart(date: Date): Date { return new Date(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0, 0); } @@ -547,4 +564,3 @@ function buildDateTextList(begin: Date, end: Date): string[] { } } - diff --git a/src/pages/management/ManagementPassword.vue b/src/pages/management/ManagementPassword.vue index a004f49..fc37d74 100644 --- a/src/pages/management/ManagementPassword.vue +++ b/src/pages/management/ManagementPassword.vue @@ -25,6 +25,9 @@ defineOptions({ const router = useRouter(); const userStore = useUserStore(); + +// ---------- 表单 ---------- + const isSaving = ref(false); const formModel = reactive({ oldPassword: "", @@ -32,21 +35,15 @@ const formModel = reactive({ confirmPassword: "" }); +// ---------- 保存 ---------- + async function savePassword(): Promise { if (!formModel.oldPassword || !formModel.newPassword) { - Toast({ - theme: "warning", - direction: "row", - message: "请填写旧密码和新密码" - }); + Toast.warning("请填写旧密码和新密码"); return; } if (formModel.newPassword !== formModel.confirmPassword) { - Toast({ - theme: "warning", - direction: "row", - message: "两次输入的新密码不一致" - }); + Toast.warning("两次输入的新密码不一致"); return; } isSaving.value = true; @@ -56,11 +53,7 @@ async function savePassword(): Promise { newValue: formModel.newPassword }); userStore.clearLoginUser(); - Toast({ - theme: "success", - direction: "row", - message: "密码已更新,请重新登录" - }); + Toast.success("密码已更新,请重新登录"); await router.replace("/login"); } finally { isSaving.value = false; diff --git a/src/pages/management/ManagementProfile.vue b/src/pages/management/ManagementProfile.vue index 161f253..458a424 100644 --- a/src/pages/management/ManagementProfile.vue +++ b/src/pages/management/ManagementProfile.vue @@ -7,7 +7,7 @@ add-text="更换" alt-text="用户头像" size="compact" - :loading="avatarUploading" + :loading="isAvatarUploading" :preview="false" :show-remove="false" @add="changeAvatar" @@ -41,7 +41,7 @@ @change="handleAvatarSelected" > - + 保存 退出登录 @@ -65,9 +65,15 @@ defineOptions({ const router = useRouter(); const userStore = useUserStore(); + +// ---------- 头像 ---------- + const avatarInputRef = ref(); const avatarTempFileId = ref(""); -const avatarUploading = ref(false); +const isAvatarUploading = ref(false); + +// ---------- 表单 ---------- + const isSaving = ref(false); const formModel = reactive({ @@ -85,6 +91,8 @@ const formModel = reactive({ lastLoginAt: "" }); +// ---------- 页面数据 ---------- + function fillForm(): void { const user = userStore.loginUser.user; Object.assign(formModel, { @@ -103,6 +111,8 @@ function fillForm(): void { }); } +// ---------- 头像 ---------- + function changeAvatar(): void { avatarInputRef.value?.click(); } @@ -113,37 +123,33 @@ async function handleAvatarSelected(event: Event): Promise { if (!file) { return; } - avatarUploading.value = true; + isAvatarUploading.value = true; try { const compressedFile = await compressUploadImage(file); - const [tempFile] = await AttachmentAPI.uploadTemp(compressedFile); + const [tempFile] = await AttachmentAPI.upload(compressedFile); if (!tempFile?.id) { - Toast({ - theme: "error", - direction: "row", - message: "头像上传失败" - }); + Toast.error("头像上传失败"); return; } avatarTempFileId.value = tempFile.id; formModel.avatar = AttachmentAPI.getTempReadURL(tempFile.id); } finally { - avatarUploading.value = false; + isAvatarUploading.value = false; input.value = ""; } } -async function goPassword(): Promise { +// ---------- 导航 ---------- + +async function toPassword(): Promise { await router.push("/management/profile/password"); } +// ---------- 保存 ---------- + async function saveProfile(): Promise { if (!formModel.name) { - Toast({ - theme: "warning", - direction: "row", - message: "请填写用户名" - }); + Toast.warning("请填写用户名"); return; } isSaving.value = true; @@ -156,17 +162,15 @@ async function saveProfile(): Promise { tempFileIdList: avatarTempFileId.value ? [avatarTempFileId.value] : undefined }); await userStore.updateLoginUser(result); - Toast({ - theme: "success", - direction: "row", - message: "资料已保存" - }); + Toast.success("资料已保存"); await router.back(); } finally { isSaving.value = false; } } +// ---------- 退出登录 ---------- + async function confirmLogout(): Promise { const dialog = DialogPlugin.confirm({ title: "退出登录", @@ -188,6 +192,8 @@ async function logout(): Promise { await router.replace("/login"); } +// ---------- 格式化 ---------- + function sexToText(sex?: number): "男" | "女" | "未知" { if (sex === 1) { return "男"; diff --git a/src/pages/management/ManagementSettings.vue b/src/pages/management/ManagementSettings.vue index 668e651..4d6c59d 100644 --- a/src/pages/management/ManagementSettings.vue +++ b/src/pages/management/ManagementSettings.vue @@ -53,6 +53,9 @@ defineOptions({ const globalUIStore = useGlobalUIStore(); const settingStore = useSettingStore(); + +// ---------- 主题 ---------- + const themeOptions: Array<{ label: string; value: ThemeMode }> = [ { label: "浅色", value: "light" }, { label: "深色", value: "dark" }, @@ -63,6 +66,8 @@ function handleThemeChange(value: string): void { globalUIStore.setThemeMode(value as ThemeMode); } +// ---------- 声音 ---------- + async function handleScanSuccessChange(value: boolean): Promise { settingStore.setSound({ scanSuccess: value diff --git a/src/pages/management/PermissionEdit.vue b/src/pages/management/PermissionEdit.vue index 9eb0e51..9b00beb 100644 --- a/src/pages/management/PermissionEdit.vue +++ b/src/pages/management/PermissionEdit.vue @@ -30,6 +30,9 @@ defineOptions({ const route = useRoute(); const router = useRouter(); const permissionStore = usePermissionSettingsStore(); + +// ---------- 路由权限 ---------- + const routeId = computed(() => String(route.params.id || "")); const isCreateMode = computed(() => !routeId.value); const savePermissionCode = computed(() => isCreateMode.value ? corePermissionCodes.permission.create : corePermissionCodes.permission.update); @@ -37,6 +40,9 @@ const pageAccess = computed(() => ({ value: [corePermissionCodes.role.read, corePermissionCodes.permission.read, savePermissionCode.value], mode: "and" as const })); + +// ---------- 表单 ---------- + const formModel = reactive({ id: "", moduleCode: moduleCode, @@ -45,6 +51,8 @@ const formModel = reactive({ description: "" }); +// ---------- 页面数据 ---------- + async function fillForm(): Promise { await permissionStore.loadAll(); const current = await permissionStore.loadPermissionDetail(routeId.value); @@ -57,6 +65,8 @@ async function fillForm(): Promise { }); } +// ---------- 保存 ---------- + async function savePermission(): Promise { const permission = await permissionStore.upsertPermission({ id: formModel.id, @@ -65,11 +75,7 @@ async function savePermission(): Promise { name: formModel.name, description: formModel.description }, isCreateMode.value); - Toast({ - theme: "success", - direction: "row", - message: "权限已保存" - }); + Toast.success("权限已保存"); await router.push(isCreateMode.value ? "/management/permissions/items" : `/management/permissions/items/${permission?.id || formModel.id}`); } diff --git a/src/pages/management/PermissionList.vue b/src/pages/management/PermissionList.vue index 361184d..b73e88b 100644 --- a/src/pages/management/PermissionList.vue +++ b/src/pages/management/PermissionList.vue @@ -1,25 +1,31 @@