From 8a0f5b6775cec3a9cec4fc96b66772f3491d57bd Mon Sep 17 00:00:00 2001 From: Timi Date: Tue, 15 Sep 2026 10:44:29 +0800 Subject: [PATCH] add point redemption --- src/api/PointAPI.ts | 68 ++- src/pages/point/PointRedeemSkuEdit.vue | 225 +++++++++ src/pages/point/PointRedeemSkuList.vue | 134 +++++ src/pages/point/PointRedemptionCreate.vue | 363 ++++++++++++++ src/pages/point/PointRedemptionDetail.vue | 240 +++++++++ src/pages/point/PointRedemptionList.vue | 574 ++++++++++++++++++++++ src/pages/tabs/HomeTab.vue | 13 +- src/pages/tabs/ProductTab.vue | 17 + src/router/routes/pointRoutes.ts | 24 + src/types/Point.ts | 91 ++++ src/utils/permission.ts | 12 + 11 files changed, 1752 insertions(+), 9 deletions(-) create mode 100644 src/pages/point/PointRedeemSkuEdit.vue create mode 100644 src/pages/point/PointRedeemSkuList.vue create mode 100644 src/pages/point/PointRedemptionCreate.vue create mode 100644 src/pages/point/PointRedemptionDetail.vue create mode 100644 src/pages/point/PointRedemptionList.vue diff --git a/src/api/PointAPI.ts b/src/api/PointAPI.ts index 1738d38..496126a 100644 --- a/src/api/PointAPI.ts +++ b/src/api/PointAPI.ts @@ -7,11 +7,20 @@ import type { PointLedger, PointLedgerPage, PointRule, - GroovyScript + GroovyScript, + PointRedeemSku, + PointRedeemSkuPage, + PointRedemption, + PointRedemptionCreateRequest, + PointRedemptionPage, + PointRedemptionReturn, + PointRedemptionReturnRequest } from "@/types/Point"; import { getCurrentStoreId, withCurrentStoreId, withCurrentStorePage } from "@/utils/store"; const BASE_URI = "/gao/customer/point"; +const REDEEM_SKU_URI = "/gao/point/redeem-sku"; +const REDEMPTION_URI = "/gao/point/redemption"; type ListOptions = { allStores?: boolean; @@ -118,6 +127,50 @@ async function ruleRemove(id: string): Promise { }); } +async function redeemSkuList(page: PointRedeemSkuPage): Promise> { + return await axios.post(`${REDEEM_SKU_URI}/list`, { ...page, storeId: page.storeId || getCurrentStoreId() }); +} + +async function redeemSkuDetail(id: string, storeId?: string): Promise { + return await axios.post(`${REDEEM_SKU_URI}/detail`, undefined, { params: { id, storeId: storeId || getCurrentStoreId() || undefined } }); +} + +async function redeemSkuCreate(item: PointRedeemSku): Promise { + return await axios.post(`${REDEEM_SKU_URI}/create`, withCurrentStoreId(item)); +} + +async function redeemSkuUpdate(item: PointRedeemSku): Promise { + return await axios.post(`${REDEEM_SKU_URI}/update`, item); +} + +async function redeemSkuDisable(id: string): Promise { + await axios.post(`${REDEEM_SKU_URI}/disable`, undefined, { params: { id, storeId: getCurrentStoreId() || undefined } }); +} + +async function redeemSkuRemove(id: string): Promise { + await axios.post(`${REDEEM_SKU_URI}/delete`, undefined, { params: { id, storeId: getCurrentStoreId() || undefined } }); +} + +async function redemptionList(page: PointRedemptionPage): Promise> { + return await axios.post(`${REDEMPTION_URI}/list`, { ...page, storeId: page.storeId || getCurrentStoreId() }); +} + +async function redemptionDetail(id: string, storeId?: string): Promise { + return await axios.post(`${REDEMPTION_URI}/detail`, undefined, { params: { id, storeId: storeId || getCurrentStoreId() || undefined } }); +} + +async function redemptionCreate(request: PointRedemptionCreateRequest): Promise { + return await axios.post(`${REDEMPTION_URI}/create`, withCurrentStoreId(request)); +} + +async function redemptionReturn(request: PointRedemptionReturnRequest): Promise { + return await axios.post(`${REDEMPTION_URI}/return`, request); +} + +async function redemptionReturnList(redemptionId: string): Promise { + return await axios.post(`${REDEMPTION_URI}/return/list`, undefined, { params: { redemptionId, storeId: getCurrentStoreId() || undefined } }); +} + export default { list, detailByCustomerId, @@ -130,5 +183,16 @@ export default { ruleCreate, ruleUpdate, ruleSort, - ruleRemove + ruleRemove, + redeemSkuList, + redeemSkuDetail, + redeemSkuCreate, + redeemSkuUpdate, + redeemSkuDisable, + redeemSkuRemove, + redemptionList, + redemptionDetail, + redemptionCreate, + redemptionReturn, + redemptionReturnList }; diff --git a/src/pages/point/PointRedeemSkuEdit.vue b/src/pages/point/PointRedeemSkuEdit.vue new file mode 100644 index 0000000..0920b35 --- /dev/null +++ b/src/pages/point/PointRedeemSkuEdit.vue @@ -0,0 +1,225 @@ + + + + + diff --git a/src/pages/point/PointRedeemSkuList.vue b/src/pages/point/PointRedeemSkuList.vue new file mode 100644 index 0000000..38c94b8 --- /dev/null +++ b/src/pages/point/PointRedeemSkuList.vue @@ -0,0 +1,134 @@ + + + + + diff --git a/src/pages/point/PointRedemptionCreate.vue b/src/pages/point/PointRedemptionCreate.vue new file mode 100644 index 0000000..6fb27bc --- /dev/null +++ b/src/pages/point/PointRedemptionCreate.vue @@ -0,0 +1,363 @@ + + + + + diff --git a/src/pages/point/PointRedemptionDetail.vue b/src/pages/point/PointRedemptionDetail.vue new file mode 100644 index 0000000..2257312 --- /dev/null +++ b/src/pages/point/PointRedemptionDetail.vue @@ -0,0 +1,240 @@ + + + + + diff --git a/src/pages/point/PointRedemptionList.vue b/src/pages/point/PointRedemptionList.vue new file mode 100644 index 0000000..ede86d5 --- /dev/null +++ b/src/pages/point/PointRedemptionList.vue @@ -0,0 +1,574 @@ + + + + + diff --git a/src/pages/tabs/HomeTab.vue b/src/pages/tabs/HomeTab.vue index a942783..079d204 100644 --- a/src/pages/tabs/HomeTab.vue +++ b/src/pages/tabs/HomeTab.vue @@ -22,7 +22,6 @@ -
微信内打开本站可能会经常在扫码时需要授权,建议使用浏览器打开(右上角菜单,使用默认浏览器打开),并添加快捷入口到桌面。
@@ -99,6 +98,12 @@ const entryGroupList: HomeEntryGroup[] = [ permission: permissionCodes.customerPackage.create, icon: "layers", title: "创建套餐订单" + }, + { + path: "/management/points/redemptions", + permission: permissionCodes.pointRedemption.create, + icon: "gift", + title: "积分兑换" } ] } @@ -110,7 +115,6 @@ const visibleEntryGroupList = computed(() => entryGroupList entryList: group.entryList.filter(item => userStore.hasPermissions(item.permission)) })) .filter(group => group.entryList.length)); -const visibleEntryCount = computed(() => visibleEntryGroupList.value.reduce((count, group) => count + group.entryList.length, 0)); const canReadNotify = computed(() => userStore.hasPermissions(corePermissionCodes.notify.read)); async function go(path: string): Promise { @@ -214,11 +218,6 @@ onUnmounted(() => { } } } - - .tips { - font-size: .875rem; - text-indent: 2em; - } } :global(html.theme-dark) { diff --git a/src/pages/tabs/ProductTab.vue b/src/pages/tabs/ProductTab.vue index 5162b25..d19a39c 100644 --- a/src/pages/tabs/ProductTab.vue +++ b/src/pages/tabs/ProductTab.vue @@ -119,6 +119,23 @@ const menuGroupList = computed(() => [ } ] }, + { + title: "积分兑换", + items: [ + { + icon: "cart", + path: "/management/points/redeem-skus", + title: "兑换商品", + visible: userStore.hasPermissions(permissionCodes.pointRedeemSku.manage) + }, + { + icon: "gift", + path: "/management/points/redemptions", + title: "积分兑换", + visible: userStore.hasPermissions(permissionCodes.pointRedemption.create) + } + ] + }, { title: "经营分析", items: [ diff --git a/src/router/routes/pointRoutes.ts b/src/router/routes/pointRoutes.ts index 74171d2..82a8128 100644 --- a/src/router/routes/pointRoutes.ts +++ b/src/router/routes/pointRoutes.ts @@ -1,6 +1,30 @@ import type { RouteRecordRaw } from "vue-router"; const pointRoutes: RouteRecordRaw[] = [ + { + path: "/management/points/redeem-skus", name: "PointRedeemSkuList", component: () => import("@/pages/point/PointRedeemSkuList.vue"), + meta: { depth: 3, navBarVisible: true, navBarCanBack: true, navBarTitle: "兑换商品", tabBarVisible: false, bodyBackground: "#F5F5F8", darkBodyBackground: "#10161f" } + }, + { + path: "/management/points/redeem-skus/create", name: "PointRedeemSkuCreate", component: () => import("@/pages/point/PointRedeemSkuEdit.vue"), + meta: { depth: 4, navBarVisible: true, navBarCanBack: true, navBarTitle: "新增兑换商品", tabBarVisible: false, bodyBackground: "#F5F5F8", darkBodyBackground: "#10161f" } + }, + { + path: "/management/points/redeem-skus/:id/edit", name: "PointRedeemSkuEdit", component: () => import("@/pages/point/PointRedeemSkuEdit.vue"), + meta: { depth: 4, navBarVisible: true, navBarCanBack: true, navBarTitle: "编辑兑换商品", tabBarVisible: false, bodyBackground: "#F5F5F8", darkBodyBackground: "#10161f" } + }, + { + path: "/management/points/redemptions", name: "PointRedemptionList", component: () => import("@/pages/point/PointRedemptionList.vue"), + meta: { depth: 3, navBarVisible: true, navBarCanBack: true, navBarTitle: "积分兑换", tabBarVisible: false, bodyBackground: "#F5F5F8", darkBodyBackground: "#10161f" } + }, + { + path: "/management/points/redemptions/create", name: "PointRedemptionCreate", component: () => import("@/pages/point/PointRedemptionCreate.vue"), + meta: { depth: 4, navBarVisible: true, navBarCanBack: true, navBarTitle: "客户兑换", tabBarVisible: false, bodyBackground: "#F5F5F8", darkBodyBackground: "#10161f" } + }, + { + path: "/management/points/redemptions/:id", name: "PointRedemptionDetail", component: () => import("@/pages/point/PointRedemptionDetail.vue"), + meta: { depth: 4, navBarVisible: true, navBarCanBack: true, navBarTitle: "兑换详情", tabBarVisible: false, bodyBackground: "#F5F5F8", darkBodyBackground: "#10161f" } + }, { path: "/management/points/account", name: "PointAccountList", diff --git a/src/types/Point.ts b/src/types/Point.ts index 7c79779..75b9db8 100644 --- a/src/types/Point.ts +++ b/src/types/Point.ts @@ -108,3 +108,94 @@ export type PointAdjustRequest = { pointDelta: number; remark: string; }; + +export type PointRedeemSkuStatus = "DRAFT" | "ACTIVE" | "INACTIVE" | "DELETED"; +export type PointRedemptionStatus = "COMPLETED" | "PARTIALLY_RETURNED" | "FULLY_RETURNED"; + +export type PointRedeemSku = { + storeId?: string; + skuId?: string; + skuUnitId?: string; + status?: PointRedeemSkuStatus; + defaultPointCost?: number; + beginAt?: number | null; + endAt?: number | null; + maxTotalQuantity?: number | string | null; + maxDailyQuantity?: number | string | null; + maxMonthlyQuantity?: number | string | null; + remark?: string | null; + product?: import("@/types/Product").Product; + sku?: import("@/types/Product").ProductSku; + unit?: import("@/types/Product").ProductSkuUnit; +} & Model; + +export type PointRedeemSkuPage = { + index: number; + size: number; + storeId?: string; + status?: PointRedeemSkuStatus; + keyword?: string; +}; + +export type PointRedemption = { + storeId?: string; + customerId?: string; + requestNo?: string; + redeemSkuIdSnapshot?: string; + productNameSnapshot?: string; + productTypeSnapshot?: string; + skuCodeSnapshot?: string; + skuNameSnapshot?: string; + unitSnapshot?: string; + quantity?: number | string; + defaultPointCost?: number; + actualPointCost?: number; + returnedQuantity?: number | string; + returnedPointCost?: number; + status?: PointRedemptionStatus; + redeemedAt?: number; + remark?: string | null; + customer?: Customer; + attachmentList?: import("timi-web-api").Attachment[]; + returnList?: PointRedemptionReturn[]; +} & Model; + +export type PointRedemptionReturn = { + redemptionId?: string; + requestNo?: string; + quantity?: number | string; + pointCost?: number; + returnedAt?: number; + remark?: string | null; + attachmentList?: import("timi-web-api").Attachment[]; +} & Model; + +export type PointRedemptionPage = { + index: number; + size: number; + storeId?: string; + customerId?: string; + keyword?: string; + status?: PointRedemptionStatus; + beginAt?: number; + endAt?: number; +}; + +export type PointRedemptionCreateRequest = { + requestNo: string; + storeId?: string; + customerId: string; + redeemSkuId: string; + quantity: string; + actualPointCost?: number; + attachmentList: Array<{ tempFileId: string; attachType: "PHOTO"; name?: string }>; + remark?: string | null; +}; + +export type PointRedemptionReturnRequest = { + redemptionId: string; + requestNo: string; + quantity: string; + attachmentList: Array<{ tempFileId: string; attachType: "PHOTO"; name?: string }>; + remark?: string | null; +}; diff --git a/src/utils/permission.ts b/src/utils/permission.ts index 18523af..b131c2a 100644 --- a/src/utils/permission.ts +++ b/src/utils/permission.ts @@ -39,6 +39,14 @@ export const permissionCodes = { adjust: "GAO:POINT:ADJUST", sourceValue: "GAO:POINT:SOURCE_VALUE" }, + pointRedeemSku: { + manage: "GAO:POINT_REDEEM_SKU:MANAGE" + }, + pointRedemption: { + create: "GAO:POINT_REDEMPTION:CREATE", + pointAdjust: "GAO:POINT_REDEMPTION:POINT_ADJUST", + return: "GAO:POINT_REDEMPTION:RETURN" + }, pointRule: { create: "GAO:POINT_RULE:CREATE", read: "GAO:POINT_RULE:READ", @@ -188,6 +196,10 @@ export const gaoStoreScopePermissionCodes = [ permissionCodes.point.ledgerRead, permissionCodes.point.adjust, permissionCodes.point.sourceValue, + permissionCodes.pointRedeemSku.manage, + permissionCodes.pointRedemption.create, + permissionCodes.pointRedemption.pointAdjust, + permissionCodes.pointRedemption.return, permissionCodes.pointRule.create, permissionCodes.pointRule.read, permissionCodes.pointRule.update,