diff --git a/src/api/PointAPI.ts b/src/api/PointAPI.ts index eff4f70..aaeacc7 100644 --- a/src/api/PointAPI.ts +++ b/src/api/PointAPI.ts @@ -1,6 +1,7 @@ import { axios, type Page, type PageResult } from "timi-web"; import type { PointAccount, + PointAccountPage, PointAdjustRequest, PointLedger, PointLedgerPage, @@ -15,9 +16,21 @@ type ListOptions = { storeId?: string; }; +/** 分页查询积分账户。账户列表由后端关联返回客户资料。 */ +async function list(page: PointAccountPage, options?: ListOptions): Promise> { + if (options?.allStores) { + return await axios.post(`${BASE_URI}/account/list`, page); + } + + return await axios.post(`${BASE_URI}/account/list`, { + ...page, + storeId: options?.storeId || getCurrentStoreId() + }); +} + /** 查询指定客户的积分账户。账户不存在表示客户尚未产生过积分流水。 */ -async function accountDetail(customerId: string, storeId?: string): Promise { - return await axios.post(`${BASE_URI}/account/detail`, undefined, { +async function detailByCustomerId(customerId: string, storeId?: string): Promise { + return await axios.post(`${BASE_URI}/account/detail/customer`, undefined, { params: { customerId, storeId: storeId || getCurrentStoreId() || undefined @@ -25,8 +38,18 @@ async function accountDetail(customerId: string, storeId?: string): Promise { + return await axios.post(`${BASE_URI}/account/detail`, undefined, { + params: { + id: accountId, + storeId: storeId || getCurrentStoreId() || undefined + } + }); +} + /** 分页查询积分流水。全局管理员不传门店时允许查询全部门店。 */ -async function ledgerList(page: PointLedgerPage, options?: ListOptions): Promise> { +async function listLedger(page: PointLedgerPage, options?: ListOptions): Promise> { if (options?.allStores) { return await axios.post(`${BASE_URI}/ledger/list`, page); } @@ -89,8 +112,10 @@ async function ruleRemove(id: string): Promise { } export default { - accountDetail, - ledgerList, + list, + detailByCustomerId, + detail, + listLedger, adjust, ruleList, ruleDetail, diff --git a/src/components/CustomerCell.vue b/src/components/CustomerCell.vue new file mode 100644 index 0000000..5ac2c40 --- /dev/null +++ b/src/components/CustomerCell.vue @@ -0,0 +1,114 @@ + + + + + diff --git a/src/pages/customer/CustomerDetail.vue b/src/pages/customer/CustomerDetail.vue index b8050be..effe499 100644 --- a/src/pages/customer/CustomerDetail.vue +++ b/src/pages/customer/CustomerDetail.vue @@ -378,7 +378,7 @@ async function loadPointAccount(): Promise { return; } try { - pointAccount.value = await PointAPI.accountDetail(customer.value.id, customer.value.storeId); + pointAccount.value = await PointAPI.detailByCustomerId(customer.value.id, customer.value.storeId); } catch { pointAccount.value = undefined; } @@ -388,10 +388,14 @@ async function toPointAccount(): Promise { if (!customer.value?.id) { return; } + if (!pointAccount.value?.id) { + await router.push({ name: "PointAccountList" }); + return; + } await router.push({ - name: "PointAccount", + name: "PointAccountDetail", + params: { id: pointAccount.value.id }, query: { - customerId: customer.value.id, storeId: customer.value.storeId || "", transitionDepth: String(Number(route.meta.depth ?? 0) + 1) } diff --git a/src/pages/customer/CustomerInviteeList.vue b/src/pages/customer/CustomerInviteeList.vue index d0ac587..73e0698 100644 --- a/src/pages/customer/CustomerInviteeList.vue +++ b/src/pages/customer/CustomerInviteeList.vue @@ -30,9 +30,9 @@ :key="item.id || item.code || index" class="item" > - - - + @@ -105,7 +95,6 @@ import CustomerProfileCard from "@/components/CustomerProfileCard.vue"; import CustomerSelector from "@/components/CustomerSelector.vue"; import { useSettingStore } from "@/store/settingStore"; import type { Customer } from "@/types/Customer"; -import { getCustomerPhotoURL } from "@/utils/customer"; import { permissionCodes } from "@/utils/permission"; defineOptions({ @@ -322,11 +311,6 @@ watch(() => route.params.id, loadCustomer); .cell { padding: 6px 10px 6px 6px; - - .avatar { - width: 3.25rem; - height: 3.25rem; - } } } diff --git a/src/pages/customer/CustomerList.vue b/src/pages/customer/CustomerList.vue index 494c441..7ce0306 100644 --- a/src/pages/customer/CustomerList.vue +++ b/src/pages/customer/CustomerList.vue @@ -38,36 +38,18 @@ @scrolltolower="fetch" > - - - - +
@@ -85,14 +67,12 @@ import { Message } from "@/utils/message"; import { type Page, type PageResult, Time, Toolkit, useUserStore } from "timi-web"; import CustomerAPI from "@/api/CustomerAPI"; -import GenderIcon from "@/components/GenderIcon.vue"; import TagSelector from "@/components/TagSelector.vue"; import { useCurrentStoreStore } from "@/store/currentStoreStore"; import { useNavBarStore } from "@/store/navBarStore"; import type { Customer } from "@/types/Customer"; import type { NavBarAction } from "@/types/NavBarAction"; import type { Tag } from "@/types/Tag"; -import { getCustomerPhotoURL } from "@/utils/customer"; import { corePermissionCodes, gaoRoleCodes, permissionCodes } from "@/utils/permission"; defineOptions({ @@ -304,26 +284,6 @@ async function doExport(): Promise { .item { padding: 10px; - - .avatar { - width: 3.5rem; - height: 3.5rem; - border-radius: .5rem; - } - - .header { - gap: .5rem; - width: 100%; - display: flex; - align-items: center; - justify-content: space-between; - - .left { - gap: .5rem; - display: flex; - align-items: center; - } - } } } } diff --git a/src/pages/event/EventRecordRank.vue b/src/pages/event/EventRecordRank.vue index 1d1e992..1784996 100644 --- a/src/pages/event/EventRecordRank.vue +++ b/src/pages/event/EventRecordRank.vue @@ -47,40 +47,25 @@ class="list" theme="card" > - - - - +
@@ -145,7 +130,6 @@ import EventAPI from "@/api/EventAPI"; import EventRecordAPI from "@/api/EventRecordAPI"; import type { CustomerEventRankView, EventRecordRankPage, EventRecordRankType } from "@/types/EventRecord"; import type { EventItem } from "@/types/EventItem"; -import { getCustomerPhotoURL } from "@/utils/customer"; import { getErrorMessage } from "@/utils/error"; import { formatEventNumberValue } from "@/utils/eventValue"; import { permissionCodes } from "@/utils/permission"; @@ -376,14 +360,6 @@ async function toCustomerDetail(customerId: string): Promise { // ---------- 格式化 ---------- -function getCustomerName(item: CustomerEventRankView): string { - return item.customer?.name || "未知客户"; -} - -function getCustomerInitial(item: CustomerEventRankView): string { - return getCustomerName(item).slice(0, 1); -} - function getTotalNumberValueText(item: CustomerEventRankView): string { return formatEventNumberValue(selectedNumberEvent.value, item.totalNumberValue); } @@ -543,73 +519,33 @@ onMounted(async () => { .list { .item { - --td-spacer-1: 8px; - --td-spacer-2: 4px; - padding: 10px; - &.in-top3 { - - .title { - - .name { - font-size: 1rem; - font-weight: 600; - } - } - } - - .avatar { - width: 3.5rem; - height: 3.5rem; - border-radius: .5rem; - } - - .title { - flex: 1 1 auto; + .rank { + flex: 0 0 1.5rem; + width: 1.5rem; + color: #52607a; + height: 1.5rem; display: flex; - min-width: 0; - justify-content: space-between; + font-size: .76rem; + background: #F0F0F0; + font-weight: 700; + align-items: center; + border-radius: .5rem; + justify-content: center; - .left { - gap: .5rem; - display: flex; + &.top1 { + color: #5C3A00; + background: #FFD700; + } - .rank { - flex: 0 0 1.5rem; - width: 1.5rem; - color: #52607a; - height: 1.5rem; - display: flex; - font-size: .76rem; - background: #F0F0F0; - font-weight: 700; - align-items: center; - border-radius: .5rem; - justify-content: center; + &.top2 { + color: #374151; + background: #C0C0C0; + } - &.top1 { - color: #5C3A00; - background: #FFD700; - } - - &.top2 { - color: #374151; - background: #C0C0C0; - } - - &.top3 { - color: #FFF8F0; - background: #CD7F32; - } - } - - .name { - color: #0F172A; - overflow: hidden; - font-size: .875rem; - white-space: nowrap; - text-overflow: ellipsis; - } + &.top3 { + color: #FFF8F0; + background: #CD7F32; } } diff --git a/src/pages/point/PointAccount.vue b/src/pages/point/PointAccountDetail.vue similarity index 51% rename from src/pages/point/PointAccount.vue rename to src/pages/point/PointAccountDetail.vue index 28bb69b..c08f7ee 100644 --- a/src/pages/point/PointAccount.vue +++ b/src/pages/point/PointAccountDetail.vue @@ -1,40 +1,9 @@ @@ -91,25 +50,20 @@ import { Message } from "@/utils/message"; import { useUserStore } from "timi-web"; import CustomerAPI from "@/api/CustomerAPI"; import PointAPI from "@/api/PointAPI"; -import CodeScanner from "@/components/CodeScanner.vue"; import CustomerProfileCard from "@/components/CustomerProfileCard.vue"; -import CustomerSelector from "@/components/CustomerSelector.vue"; -import { useSettingStore } from "@/store/settingStore"; import { useCurrentStoreStore } from "@/store/currentStoreStore"; import type { Customer } from "@/types/Customer"; import type { PointAccount as PointAccountData } from "@/types/Point"; import { gaoRoleCodes, permissionCodes } from "@/utils/permission"; defineOptions({ - name: "PointAccount" + name: "PointAccountDetail" }); const route = useRoute(); const router = useRouter(); const userStore = useUserStore(); const currentStoreStore = useCurrentStoreStore(); -const settingStore = useSettingStore(); - // ---------- 门店 ---------- const isGlobalManager = computed(() => userStore.hasRoles(gaoRoleCodes.globalManager)); @@ -122,52 +76,17 @@ async function prepareStoreScope(): Promise { await currentStoreStore.loadLoginStore(); } -// ---------- 客户与账户 ---------- +// ---------- 账户 ---------- -const selectedCustomerId = ref(String(route.query.customerId || "")); +const accountId = String(route.params.id || ""); const customer = ref(); const account = ref(); -const selectorVisible = ref(false); const isLoading = ref(false); const balanceText = computed(() => formatPoints(account.value?.balance)); -async function handleCustomerChange(value: string | string[]): Promise { - selectedCustomerId.value = Array.isArray(value) ? String(value[0] || "") : value; - await router.replace({ - name: "PointAccount", - query: selectedCustomerId.value ? { customerId: selectedCustomerId.value } : undefined - }); - await loadAccount(); -} - -async function loadCustomerByCode(code: string): Promise { - const normalizedCode = code.trim(); - if (!normalizedCode) { - return; - } - - try { - const matched = await CustomerAPI.findByCode(normalizedCode); - if (!matched?.id) { - Message.warning("未找到客户"); - return; - } - selectedCustomerId.value = matched.id; - customer.value = matched; - await router.replace({ - name: "PointAccount", - query: { customerId: selectedCustomerId.value } - }); - await loadAccount(); - } catch { - Message.error("客户信息加载失败"); - } -} - async function loadAccount(): Promise { - const customerId = selectedCustomerId.value; - if (!customerId) { + if (!accountId) { customer.value = undefined; account.value = undefined; return; @@ -175,62 +94,35 @@ async function loadAccount(): Promise { isLoading.value = true; try { - customer.value = await CustomerAPI.detail(customerId); - try { - account.value = await PointAPI.accountDetail(customerId, currentStoreStore.currentStoreId || undefined); - } catch { - account.value = undefined; + account.value = await PointAPI.detail( + accountId, + String(route.query.storeId || currentStoreStore.currentStoreId || "") + ); + if (!account.value) { + customer.value = undefined; + return; } + customer.value = account.value.customer || (account.value.customerId ? await CustomerAPI.detail(account.value.customerId) : undefined); } catch { customer.value = undefined; account.value = undefined; - Message.error("客户信息加载失败"); + Message.error("积分账户加载失败"); } finally { isLoading.value = false; } } -// ---------- 扫码 ---------- - -const scannerVisible = ref(false); -const scanStatus = ref("点击扫码开始识别"); -let latestDecodedText = ""; - -async function toggleScanner(): Promise { - scannerVisible.value = !scannerVisible.value; - if (scannerVisible.value) { - latestDecodedText = ""; - scanStatus.value = "正在启动扫码预览"; - } -} - -async function handleDecodedText(rawText: string): Promise { - const normalizedText = rawText.trim(); - if (!normalizedText || normalizedText === latestDecodedText) { - return; - } - latestDecodedText = normalizedText; - await settingStore.playScanSuccessSound(); - scanStatus.value = `已识别 ${normalizedText},正在加载客户档案`; - scannerVisible.value = false; - await loadCustomerByCode(normalizedText); -} - -function handleScanStatusChange(value: string): void { - scanStatus.value = value; -} - // ---------- 导航 ---------- async function toLedger(): Promise { - if (!selectedCustomerId.value) { + if (!account.value?.customerId) { return; } await router.push({ name: "PointLedgerList", query: { - customerId: selectedCustomerId.value, - accountId: account.value?.id || "", + customerId: account.value.customerId, + accountId: account.value.id || "", transitionDepth: String(getCurrentRouteDepth() + 1) } }); @@ -248,12 +140,12 @@ function getCurrentRouteDepth(): number { } async function toAdjust(): Promise { - if (!selectedCustomerId.value) { + if (!account.value?.customerId) { return; } await router.push({ name: "PointAdjust", - query: { customerId: selectedCustomerId.value } + query: { customerId: account.value.customerId } }); } @@ -263,7 +155,10 @@ function formatPoints(value?: number): string { return `${(value ?? 0).toLocaleString("zh-CN")} 分`; } -function formatTimestamp(timestamp: number) { +function formatTimestamp(timestamp?: number): string { + if (!timestamp) { + return "未知时间"; + } const date = new Date(timestamp); const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, "0"); @@ -276,50 +171,23 @@ function formatSignedPoints(value?: number): string { return pointValue < 0 ? `扣除 ${Math.abs(pointValue).toLocaleString("zh-CN")} 分` : `增加 ${pointValue.toLocaleString("zh-CN")} 分`; } -watch(() => route.query.customerId, async customerIdValue => { - const customerId = String(customerIdValue || ""); - if (customerId !== selectedCustomerId.value) { - selectedCustomerId.value = customerId; - await loadAccount(); - } -}); - onMounted(async () => { - await prepareStoreScope(); - await loadAccount(); + try { + await prepareStoreScope(); + await loadAccount(); + } catch { + Message.error("积分账户页面加载失败"); + } }); diff --git a/src/pages/point/PointAdjust.vue b/src/pages/point/PointAdjust.vue index 89ed42c..46a2224 100644 --- a/src/pages/point/PointAdjust.vue +++ b/src/pages/point/PointAdjust.vue @@ -165,7 +165,7 @@ async function loadCustomerAccount(): Promise { selectedCustomer.value = await CustomerAPI.detail(selectedCustomerId.value); if (userStore.hasPermissions(permissionCodes.point.accountRead)) { try { - account.value = await PointAPI.accountDetail(selectedCustomerId.value, currentStoreStore.currentStoreId || undefined); + account.value = await PointAPI.detailByCustomerId(selectedCustomerId.value, currentStoreStore.currentStoreId || undefined); } catch { account.value = undefined; } diff --git a/src/pages/point/PointLedgerList.vue b/src/pages/point/PointLedgerList.vue index 196c02a..b321481 100644 --- a/src/pages/point/PointLedgerList.vue +++ b/src/pages/point/PointLedgerList.vue @@ -344,7 +344,7 @@ async function load(): Promise { loadToken = token; isLoading.value = true; try { - const result = await PointAPI.ledgerList(buildRequest(), buildListOptions()); + const result = await PointAPI.listLedger(buildRequest(), buildListOptions()); if (token !== loadToken) { return; } diff --git a/src/router/routes/pointRoutes.ts b/src/router/routes/pointRoutes.ts index eb5a779..967c703 100644 --- a/src/router/routes/pointRoutes.ts +++ b/src/router/routes/pointRoutes.ts @@ -1,5 +1,6 @@ import type { RouteRecordRaw } from "vue-router"; -import PointAccount from "@/pages/point/PointAccount.vue"; +import PointAccountDetail from "@/pages/point/PointAccountDetail.vue"; +import PointAccountList from "@/pages/point/PointAccountList.vue"; import PointAdjust from "@/pages/point/PointAdjust.vue"; import PointLedgerDetail from "@/pages/point/PointLedgerDetail.vue"; import PointLedgerList from "@/pages/point/PointLedgerList.vue"; @@ -10,8 +11,8 @@ import PointRuleSort from "@/pages/point/PointRuleSort.vue"; const pointRoutes: RouteRecordRaw[] = [ { path: "/management/points/account", - name: "PointAccount", - component: PointAccount, + name: "PointAccountList", + component: PointAccountList, meta: { depth: 3, navBarVisible: true, @@ -22,6 +23,20 @@ const pointRoutes: RouteRecordRaw[] = [ darkBodyBackground: "#10161f" } }, + { + path: "/management/points/account/:id", + name: "PointAccountDetail", + component: PointAccountDetail, + meta: { + depth: 4, + navBarVisible: true, + navBarCanBack: true, + navBarTitle: "积分账户详情", + tabBarVisible: false, + bodyBackground: "#F5F5F8", + darkBodyBackground: "#10161f" + } + }, { path: "/management/points/rules/sort", name: "PointRuleSort", diff --git a/src/types/Point.ts b/src/types/Point.ts index 201a5f6..c8f483d 100644 --- a/src/types/Point.ts +++ b/src/types/Point.ts @@ -47,8 +47,14 @@ export type PointAccount = { totalRevoked?: number; totalAdjusted?: number; version?: number; + customer?: Customer; } & Model; +export type PointAccountPage = { + storeId?: string; + keyword?: string; +} & BasePage; + export type PointLedger = { accountId?: string; storeId?: string;