抽奖相关
This commit is contained in:
parent
3b21bd1133
commit
0789e02c6a
@ -2881,6 +2881,18 @@
|
|||||||
"x-permissions": ["resource-shop:update"]
|
"x-permissions": ["resource-shop:update"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/admin/resource-shop/purchase-orders": {
|
||||||
|
"get": {
|
||||||
|
"operationId": "listResourceShopPurchaseOrders",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"$ref": "#/components/responses/EmptyResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-permission": "resource-shop:view",
|
||||||
|
"x-permissions": ["resource-shop:view"]
|
||||||
|
}
|
||||||
|
},
|
||||||
"/admin/resource-shop/items/{shop_item_id}/disable": {
|
"/admin/resource-shop/items/{shop_item_id}/disable": {
|
||||||
"post": {
|
"post": {
|
||||||
"operationId": "disableResourceShopItem",
|
"operationId": "disableResourceShopItem",
|
||||||
|
|||||||
@ -356,7 +356,7 @@ function poolLabel(wheelId) {
|
|||||||
return (wheelPools.find((pool) => pool.key === wheelId) || wheelPools[0]).label;
|
return (wheelPools.find((pool) => pool.key === wheelId) || wheelPools[0]).label;
|
||||||
}
|
}
|
||||||
|
|
||||||
function groupItemToTier(item, wheelId, index) {
|
export function groupItemToTier(item, wheelId, index) {
|
||||||
const resource = item.resource || {};
|
const resource = item.resource || {};
|
||||||
const isCoin = item.itemType === "wallet_asset" && item.walletAssetType === "COIN";
|
const isCoin = item.itemType === "wallet_asset" && item.walletAssetType === "COIN";
|
||||||
const rewardType = isCoin ? "coin" : resourceTypeToRewardType(resource.resourceType);
|
const rewardType = isCoin ? "coin" : resourceTypeToRewardType(resource.resourceType);
|
||||||
@ -364,10 +364,15 @@ function groupItemToTier(item, wheelId, index) {
|
|||||||
const metadata = {
|
const metadata = {
|
||||||
animation_url: isCoin ? "" : resource.animationUrl || "",
|
animation_url: isCoin ? "" : resource.animationUrl || "",
|
||||||
asset_url: resource.assetUrl || "",
|
asset_url: resource.assetUrl || "",
|
||||||
|
coinPrice: coinValue,
|
||||||
|
coin_price: coinValue,
|
||||||
|
displayValue: coinValue,
|
||||||
|
display_value: coinValue,
|
||||||
preview_url: resource.previewUrl || resource.assetUrl || "",
|
preview_url: resource.previewUrl || resource.assetUrl || "",
|
||||||
resource_code: resource.resourceCode || "",
|
resource_code: resource.resourceCode || "",
|
||||||
resource_id: resource.resourceId || 0,
|
resource_id: resource.resourceId || 0,
|
||||||
resource_type: resource.resourceType || "",
|
resource_type: resource.resourceType || "",
|
||||||
|
value: coinValue,
|
||||||
};
|
};
|
||||||
return {
|
return {
|
||||||
displayName: isCoin
|
displayName: isCoin
|
||||||
|
|||||||
@ -1,6 +1,10 @@
|
|||||||
import { describe, expect, test } from "vitest";
|
import { describe, expect, test } from "vitest";
|
||||||
import { wheelConfigFormSchema } from "@/features/operations/schema";
|
import { wheelConfigFormSchema } from "@/features/operations/schema";
|
||||||
import { generateWheelTierProbabilities, wheelPools } from "@/features/operations/hooks/useWheelPage.js";
|
import {
|
||||||
|
generateWheelTierProbabilities,
|
||||||
|
groupItemToTier,
|
||||||
|
wheelPools,
|
||||||
|
} from "@/features/operations/hooks/useWheelPage.js";
|
||||||
|
|
||||||
function tier(overrides) {
|
function tier(overrides) {
|
||||||
return {
|
return {
|
||||||
@ -35,6 +39,33 @@ function wheelExpectedRTP(tiers, drawPriceCoins) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
describe("wheel prize probability generation", () => {
|
describe("wheel prize probability generation", () => {
|
||||||
|
test("stores resource coin price in metadata for prop and dress display", () => {
|
||||||
|
const result = groupItemToTier(
|
||||||
|
{
|
||||||
|
itemType: "resource",
|
||||||
|
quantity: 1,
|
||||||
|
resource: {
|
||||||
|
assetUrl: "https://example.com/a.png",
|
||||||
|
coinPrice: 1314,
|
||||||
|
name: "道具",
|
||||||
|
previewUrl: "https://example.com/p.png",
|
||||||
|
resourceCode: "prop_a",
|
||||||
|
resourceId: 326,
|
||||||
|
resourceType: "prop",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"classic",
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
const metadata = JSON.parse(result.metadataJson);
|
||||||
|
|
||||||
|
expect(result.rewardType).toBe("prop");
|
||||||
|
expect(result.rtpValueCoins).toBe(0);
|
||||||
|
expect(metadata.coin_price).toBe(1314);
|
||||||
|
expect(metadata.display_value).toBe(1314);
|
||||||
|
expect(metadata.value).toBe(1314);
|
||||||
|
});
|
||||||
|
|
||||||
test("uses eight prize slots for the classic wheel", () => {
|
test("uses eight prize slots for the classic wheel", () => {
|
||||||
const classicPool = wheelPools.find((pool) => pool.key === "classic");
|
const classicPool = wheelPools.find((pool) => pool.key === "classic");
|
||||||
const tiers = Array.from({ length: 8 }, (_, index) =>
|
const tiers = Array.from({ length: 8 }, (_, index) =>
|
||||||
|
|||||||
@ -22,6 +22,7 @@ import {
|
|||||||
listResourceGrants,
|
listResourceGrants,
|
||||||
listGifts,
|
listGifts,
|
||||||
listResourceShopItems,
|
listResourceShopItems,
|
||||||
|
listResourceShopPurchaseOrders,
|
||||||
listResources,
|
listResources,
|
||||||
lookupResourceGrantTarget,
|
lookupResourceGrantTarget,
|
||||||
updateGift,
|
updateGift,
|
||||||
@ -327,6 +328,12 @@ test("resource shop APIs use generated admin paths", async () => {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await listResourceShopPurchaseOrders({
|
||||||
|
page: 1,
|
||||||
|
page_size: 10,
|
||||||
|
resource_type: "avatar_frame",
|
||||||
|
user_keyword: "163000",
|
||||||
|
});
|
||||||
await listResourceShopItems({ page: 1, page_size: 10, resource_type: "avatar_frame", status: "active" });
|
await listResourceShopItems({ page: 1, page_size: 10, resource_type: "avatar_frame", status: "active" });
|
||||||
await upsertResourceShopItems({
|
await upsertResourceShopItems({
|
||||||
items: [
|
items: [
|
||||||
@ -343,11 +350,16 @@ test("resource shop APIs use generated admin paths", async () => {
|
|||||||
await enableResourceShopItem(99);
|
await enableResourceShopItem(99);
|
||||||
await disableResourceShopItem(99);
|
await disableResourceShopItem(99);
|
||||||
|
|
||||||
const [listUrl, listInit] = vi.mocked(fetch).mock.calls[0];
|
const [purchaseUrl, purchaseInit] = vi.mocked(fetch).mock.calls[0];
|
||||||
const [upsertUrl, upsertInit] = vi.mocked(fetch).mock.calls[1];
|
const [listUrl, listInit] = vi.mocked(fetch).mock.calls[1];
|
||||||
const [enableUrl, enableInit] = vi.mocked(fetch).mock.calls[2];
|
const [upsertUrl, upsertInit] = vi.mocked(fetch).mock.calls[2];
|
||||||
const [disableUrl, disableInit] = vi.mocked(fetch).mock.calls[3];
|
const [enableUrl, enableInit] = vi.mocked(fetch).mock.calls[3];
|
||||||
|
const [disableUrl, disableInit] = vi.mocked(fetch).mock.calls[4];
|
||||||
|
|
||||||
|
expect(String(purchaseUrl)).toContain("/api/v1/admin/resource-shop/purchase-orders?");
|
||||||
|
expect(String(purchaseUrl)).toContain("resource_type=avatar_frame");
|
||||||
|
expect(String(purchaseUrl)).toContain("user_keyword=163000");
|
||||||
|
expect(purchaseInit?.method).toBe("GET");
|
||||||
expect(String(listUrl)).toContain("/api/v1/admin/resource-shop/items?");
|
expect(String(listUrl)).toContain("/api/v1/admin/resource-shop/items?");
|
||||||
expect(String(listUrl)).toContain("resource_type=avatar_frame");
|
expect(String(listUrl)).toContain("resource_type=avatar_frame");
|
||||||
expect(String(listUrl)).toContain("status=active");
|
expect(String(listUrl)).toContain("status=active");
|
||||||
|
|||||||
@ -124,6 +124,25 @@ export interface ResourceShopItemDto {
|
|||||||
updatedAtMs?: number;
|
updatedAtMs?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ResourceShopPurchaseOrderDto {
|
||||||
|
appCode?: string;
|
||||||
|
orderId: string;
|
||||||
|
commandId?: string;
|
||||||
|
userId?: string;
|
||||||
|
user?: ResourceGrantUserDto;
|
||||||
|
shopItemId?: number;
|
||||||
|
resourceId: number;
|
||||||
|
resource?: ResourceDto;
|
||||||
|
durationDays?: number;
|
||||||
|
priceCoin?: number;
|
||||||
|
status?: string;
|
||||||
|
walletTransactionId?: string;
|
||||||
|
resourceGrantId?: string;
|
||||||
|
entitlementId?: string;
|
||||||
|
createdAtMs?: number;
|
||||||
|
updatedAtMs?: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface GiftDto {
|
export interface GiftDto {
|
||||||
appCode?: string;
|
appCode?: string;
|
||||||
giftId: string;
|
giftId: string;
|
||||||
@ -352,6 +371,17 @@ export function listResourceShopItems(query: PageQuery = {}): Promise<ApiPage<Re
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function listResourceShopPurchaseOrders(query: PageQuery = {}): Promise<ApiPage<ResourceShopPurchaseOrderDto>> {
|
||||||
|
const endpoint = API_ENDPOINTS.listResourceShopPurchaseOrders;
|
||||||
|
return apiRequest<ApiPage<ResourceShopPurchaseOrderDto>>(
|
||||||
|
apiEndpointPath(API_OPERATIONS.listResourceShopPurchaseOrders),
|
||||||
|
{
|
||||||
|
method: endpoint.method,
|
||||||
|
query,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function upsertResourceShopItems(payload: ResourceShopItemsPayload): Promise<ResourceShopItemDto[]> {
|
export function upsertResourceShopItems(payload: ResourceShopItemsPayload): Promise<ResourceShopItemDto[]> {
|
||||||
const endpoint = API_ENDPOINTS.upsertResourceShopItems;
|
const endpoint = API_ENDPOINTS.upsertResourceShopItems;
|
||||||
return apiRequest<ResourceShopItemDto[], ResourceShopItemsPayload>(
|
return apiRequest<ResourceShopItemDto[], ResourceShopItemsPayload>(
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useMemo, useState } from "react";
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
import { parseForm } from "@/shared/forms/validation";
|
import { parseForm } from "@/shared/forms/validation";
|
||||||
import { useRegionOptions } from "@/shared/hooks/useRegionOptions.js";
|
import { useRegionOptions } from "@/shared/hooks/useRegionOptions.js";
|
||||||
import { usePaginatedQuery } from "@/shared/hooks/usePaginatedQuery.js";
|
import { usePaginatedQuery } from "@/shared/hooks/usePaginatedQuery.js";
|
||||||
@ -27,6 +27,7 @@ import {
|
|||||||
listResourceGrants,
|
listResourceGrants,
|
||||||
listResourceGroups,
|
listResourceGroups,
|
||||||
listResourceShopItems,
|
listResourceShopItems,
|
||||||
|
listResourceShopPurchaseOrders,
|
||||||
listResources,
|
listResources,
|
||||||
lookupResourceGrantTarget,
|
lookupResourceGrantTarget,
|
||||||
updateGift,
|
updateGift,
|
||||||
@ -482,6 +483,11 @@ export function useResourceShopPage() {
|
|||||||
const [resourceType, setResourceType] = useState("");
|
const [resourceType, setResourceType] = useState("");
|
||||||
const [page, setPage] = useState(1);
|
const [page, setPage] = useState(1);
|
||||||
const [drawerOpen, setDrawerOpen] = useState(false);
|
const [drawerOpen, setDrawerOpen] = useState(false);
|
||||||
|
const [purchaseDrawerOpen, setPurchaseDrawerOpen] = useState(false);
|
||||||
|
const [purchasePage, setPurchasePage] = useState(1);
|
||||||
|
const [purchaseUserKeyword, setPurchaseUserKeyword] = useState("");
|
||||||
|
const [purchaseResourceType, setPurchaseResourceType] = useState("");
|
||||||
|
const [purchaseKeyword, setPurchaseKeyword] = useState("");
|
||||||
const [drawerQuery, setDrawerQuery] = useState("");
|
const [drawerQuery, setDrawerQuery] = useState("");
|
||||||
const [drawerResourceType, setDrawerResourceType] = useState("");
|
const [drawerResourceType, setDrawerResourceType] = useState("");
|
||||||
const [batchDurationDays, setBatchDurationDays] = useState("1");
|
const [batchDurationDays, setBatchDurationDays] = useState("1");
|
||||||
@ -505,6 +511,31 @@ export function useResourceShopPage() {
|
|||||||
pageSize,
|
pageSize,
|
||||||
queryKey: ["resource-shop", filters, page],
|
queryKey: ["resource-shop", filters, page],
|
||||||
});
|
});
|
||||||
|
const purchaseFilters = useMemo(
|
||||||
|
() => ({
|
||||||
|
keyword: purchaseKeyword,
|
||||||
|
resource_type: purchaseResourceType,
|
||||||
|
user_keyword: purchaseUserKeyword,
|
||||||
|
}),
|
||||||
|
[purchaseKeyword, purchaseResourceType, purchaseUserKeyword],
|
||||||
|
);
|
||||||
|
const fetchPurchaseOrders = useCallback(
|
||||||
|
(requestQuery) => {
|
||||||
|
if (!purchaseDrawerOpen) {
|
||||||
|
return Promise.resolve(emptyData);
|
||||||
|
}
|
||||||
|
return listResourceShopPurchaseOrders(requestQuery);
|
||||||
|
},
|
||||||
|
[purchaseDrawerOpen],
|
||||||
|
);
|
||||||
|
const purchaseResult = usePaginatedQuery({
|
||||||
|
errorMessage: "加载售卖记录失败",
|
||||||
|
fetcher: fetchPurchaseOrders,
|
||||||
|
filters: purchaseFilters,
|
||||||
|
page: purchasePage,
|
||||||
|
pageSize,
|
||||||
|
queryKey: ["resource-shop-purchase-orders", purchaseDrawerOpen, purchaseFilters, purchasePage],
|
||||||
|
});
|
||||||
const shopItemsByResourceId = useMemo(() => {
|
const shopItemsByResourceId = useMemo(() => {
|
||||||
const items = new Map();
|
const items = new Map();
|
||||||
(result.data?.items || []).forEach((item) => {
|
(result.data?.items || []).forEach((item) => {
|
||||||
@ -579,6 +610,15 @@ export function useResourceShopPage() {
|
|||||||
setDraftByResourceId({});
|
setDraftByResourceId({});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const openPurchaseDrawer = () => {
|
||||||
|
setPurchaseDrawerOpen(true);
|
||||||
|
setPurchasePage(1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const closePurchaseDrawer = () => {
|
||||||
|
setPurchaseDrawerOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
const toggleDraftResource = (resource) => {
|
const toggleDraftResource = (resource) => {
|
||||||
if (!resource?.resourceId) {
|
if (!resource?.resourceId) {
|
||||||
return;
|
return;
|
||||||
@ -692,12 +732,24 @@ export function useResourceShopPage() {
|
|||||||
changeResourceType: resetSetter(setResourceType, setPage),
|
changeResourceType: resetSetter(setResourceType, setPage),
|
||||||
changeStatus: resetSetter(setStatus, setPage),
|
changeStatus: resetSetter(setStatus, setPage),
|
||||||
closeShopDrawer,
|
closeShopDrawer,
|
||||||
|
closePurchaseDrawer,
|
||||||
drawerOpen,
|
drawerOpen,
|
||||||
drawerQuery,
|
drawerQuery,
|
||||||
drawerResourceType,
|
drawerResourceType,
|
||||||
filteredResourceOptions,
|
filteredResourceOptions,
|
||||||
loadingAction,
|
loadingAction,
|
||||||
openShopDrawer,
|
openShopDrawer,
|
||||||
|
openPurchaseDrawer,
|
||||||
|
purchaseData: purchaseResult.data,
|
||||||
|
purchaseDrawerOpen,
|
||||||
|
purchaseError: purchaseResult.error,
|
||||||
|
purchaseKeyword,
|
||||||
|
purchaseLoading: purchaseResult.loading,
|
||||||
|
purchasePage,
|
||||||
|
purchaseResourceType,
|
||||||
|
purchaseUserKeyword,
|
||||||
|
reloadPurchaseOrders: purchaseResult.reload,
|
||||||
|
setPurchasePage,
|
||||||
resourceOptionsLoading,
|
resourceOptionsLoading,
|
||||||
resourceType,
|
resourceType,
|
||||||
resetFilters,
|
resetFilters,
|
||||||
@ -707,6 +759,9 @@ export function useResourceShopPage() {
|
|||||||
submitShopItems,
|
submitShopItems,
|
||||||
toggleDraftResource,
|
toggleDraftResource,
|
||||||
toggleShopItem,
|
toggleShopItem,
|
||||||
|
changePurchaseKeyword: resetSetter(setPurchaseKeyword, setPurchasePage),
|
||||||
|
changePurchaseResourceType: resetSetter(setPurchaseResourceType, setPurchasePage),
|
||||||
|
changePurchaseUserKeyword: resetSetter(setPurchaseUserKeyword, setPurchasePage),
|
||||||
updateDraftItem,
|
updateDraftItem,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,10 @@
|
|||||||
import Add from "@mui/icons-material/Add";
|
import Add from "@mui/icons-material/Add";
|
||||||
|
import ReceiptLongOutlined from "@mui/icons-material/ReceiptLongOutlined";
|
||||||
import StorefrontOutlined from "@mui/icons-material/StorefrontOutlined";
|
import StorefrontOutlined from "@mui/icons-material/StorefrontOutlined";
|
||||||
import Checkbox from "@mui/material/Checkbox";
|
import Checkbox from "@mui/material/Checkbox";
|
||||||
import MenuItem from "@mui/material/MenuItem";
|
import MenuItem from "@mui/material/MenuItem";
|
||||||
import TextField from "@mui/material/TextField";
|
import TextField from "@mui/material/TextField";
|
||||||
|
import { AdminUserIdentity } from "@/shared/ui/AdminUserIdentity.jsx";
|
||||||
import { AdminSwitch } from "@/shared/ui/AdminSwitch.jsx";
|
import { AdminSwitch } from "@/shared/ui/AdminSwitch.jsx";
|
||||||
import { Button } from "@/shared/ui/Button.jsx";
|
import { Button } from "@/shared/ui/Button.jsx";
|
||||||
import { DataState } from "@/shared/ui/DataState.jsx";
|
import { DataState } from "@/shared/ui/DataState.jsx";
|
||||||
@ -70,6 +72,39 @@ const shopColumns = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const purchaseColumns = [
|
||||||
|
{
|
||||||
|
key: "user",
|
||||||
|
label: "用户信息",
|
||||||
|
width: "minmax(210px, 1fr)",
|
||||||
|
render: (item) => <PurchaseUser order={item} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "resourceType",
|
||||||
|
label: "道具类型",
|
||||||
|
width: "minmax(120px, 0.55fr)",
|
||||||
|
render: (item) => resourceTypeLabels[item.resource?.resourceType] || item.resource?.resourceType || "-",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "resource",
|
||||||
|
label: "购买素材",
|
||||||
|
width: "minmax(260px, 1.1fr)",
|
||||||
|
render: (item) => <ResourceIdentity resource={item.resource} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "price",
|
||||||
|
label: "购买价格",
|
||||||
|
width: "minmax(130px, 0.55fr)",
|
||||||
|
render: (item) => coinPriceLabel(item.priceCoin),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "time",
|
||||||
|
label: "购买时间",
|
||||||
|
width: "minmax(170px, 0.7fr)",
|
||||||
|
render: (item) => formatMillis(item.createdAtMs),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
export function ResourceShopPage() {
|
export function ResourceShopPage() {
|
||||||
const page = useResourceShopPage();
|
const page = useResourceShopPage();
|
||||||
const items = page.data.items || [];
|
const items = page.data.items || [];
|
||||||
@ -115,11 +150,18 @@ export function ResourceShopPage() {
|
|||||||
<AdminListPage>
|
<AdminListPage>
|
||||||
<AdminListToolbar
|
<AdminListToolbar
|
||||||
actions={
|
actions={
|
||||||
page.abilities.canUpdateShop ? (
|
<>
|
||||||
<AdminActionIconButton label="添加售卖资源" primary onClick={page.openShopDrawer}>
|
{page.abilities.canViewShop ? (
|
||||||
<Add fontSize="small" />
|
<AdminActionIconButton label="售卖记录" onClick={page.openPurchaseDrawer}>
|
||||||
</AdminActionIconButton>
|
<ReceiptLongOutlined fontSize="small" />
|
||||||
) : null
|
</AdminActionIconButton>
|
||||||
|
) : null}
|
||||||
|
{page.abilities.canUpdateShop ? (
|
||||||
|
<AdminActionIconButton label="添加售卖资源" primary onClick={page.openShopDrawer}>
|
||||||
|
<Add fontSize="small" />
|
||||||
|
</AdminActionIconButton>
|
||||||
|
) : null}
|
||||||
|
</>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<DataState error={page.error} loading={page.loading} onRetry={page.reload}>
|
<DataState error={page.error} loading={page.loading} onRetry={page.reload}>
|
||||||
@ -143,6 +185,7 @@ export function ResourceShopPage() {
|
|||||||
</AdminListBody>
|
</AdminListBody>
|
||||||
</DataState>
|
</DataState>
|
||||||
<ResourceShopDrawer page={page} />
|
<ResourceShopDrawer page={page} />
|
||||||
|
<ResourceShopPurchaseDrawer page={page} />
|
||||||
</AdminListPage>
|
</AdminListPage>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -227,6 +270,69 @@ function ResourceShopDrawer({ page }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ResourceShopPurchaseDrawer({ page }) {
|
||||||
|
const data = page.purchaseData || {};
|
||||||
|
const items = data.items || [];
|
||||||
|
const total = data.total || 0;
|
||||||
|
const columns = purchaseColumns.map((column) => {
|
||||||
|
if (column.key === "user") {
|
||||||
|
return {
|
||||||
|
...column,
|
||||||
|
filter: createTextColumnFilter({
|
||||||
|
placeholder: "搜索用户ID、短号、靓号、昵称",
|
||||||
|
value: page.purchaseUserKeyword,
|
||||||
|
onChange: page.changePurchaseUserKeyword,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (column.key === "resourceType") {
|
||||||
|
return {
|
||||||
|
...column,
|
||||||
|
filter: createOptionsColumnFilter({
|
||||||
|
options: resourceShopTypeFilters,
|
||||||
|
placeholder: "搜索道具类型",
|
||||||
|
value: page.purchaseResourceType,
|
||||||
|
onChange: page.changePurchaseResourceType,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (column.key === "resource") {
|
||||||
|
return {
|
||||||
|
...column,
|
||||||
|
filter: createTextColumnFilter({
|
||||||
|
placeholder: "搜索素材名称、素材编码、订单号",
|
||||||
|
value: page.purchaseKeyword,
|
||||||
|
onChange: page.changePurchaseKeyword,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return column;
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SideDrawer open={page.purchaseDrawerOpen} title="售卖记录" width="wide" onClose={page.closePurchaseDrawer}>
|
||||||
|
<DataState error={page.purchaseError} loading={page.purchaseLoading} onRetry={page.reloadPurchaseOrders}>
|
||||||
|
<DataTable
|
||||||
|
columns={columns}
|
||||||
|
items={items}
|
||||||
|
minWidth="980px"
|
||||||
|
pagination={
|
||||||
|
total > 0
|
||||||
|
? {
|
||||||
|
page: page.purchasePage,
|
||||||
|
pageSize: data.pageSize || 50,
|
||||||
|
total,
|
||||||
|
onPageChange: page.setPurchasePage,
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
rowKey={(item) => item.orderId}
|
||||||
|
/>
|
||||||
|
</DataState>
|
||||||
|
</SideDrawer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function ResourceSelectionRow({ disabled, page, resource }) {
|
function ResourceSelectionRow({ disabled, page, resource }) {
|
||||||
const resourceId = String(resource.resourceId);
|
const resourceId = String(resource.resourceId);
|
||||||
const selected = page.selectedResourceIds.has(resourceId);
|
const selected = page.selectedResourceIds.has(resourceId);
|
||||||
@ -316,6 +422,14 @@ function ResourceIdentity({ resource = {} }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function PurchaseUser({ order }) {
|
||||||
|
const user = order.user || { userId: order.userId };
|
||||||
|
if (!user?.userId && !user?.displayUserId && !user?.username) {
|
||||||
|
return "-";
|
||||||
|
}
|
||||||
|
return <AdminUserIdentity openInAppUserDetail user={user} />;
|
||||||
|
}
|
||||||
|
|
||||||
function SaleSpec({ item }) {
|
function SaleSpec({ item }) {
|
||||||
return (
|
return (
|
||||||
<div className={styles.stack}>
|
<div className={styles.stack}>
|
||||||
@ -342,3 +456,10 @@ function resourcePriceLabel(item = {}) {
|
|||||||
}
|
}
|
||||||
return resourcePriceTypeLabels[item.priceType] || "-";
|
return resourcePriceTypeLabels[item.priceType] || "-";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function coinPriceLabel(value) {
|
||||||
|
if (value === 0 || value) {
|
||||||
|
return `${Number(value).toLocaleString("zh-CN")}金币`;
|
||||||
|
}
|
||||||
|
return "-";
|
||||||
|
}
|
||||||
|
|||||||
@ -182,6 +182,7 @@ export const API_OPERATIONS = {
|
|||||||
listResourceGroups: "listResourceGroups",
|
listResourceGroups: "listResourceGroups",
|
||||||
listResources: "listResources",
|
listResources: "listResources",
|
||||||
listResourceShopItems: "listResourceShopItems",
|
listResourceShopItems: "listResourceShopItems",
|
||||||
|
listResourceShopPurchaseOrders: "listResourceShopPurchaseOrders",
|
||||||
listRobotRooms: "listRobotRooms",
|
listRobotRooms: "listRobotRooms",
|
||||||
listRoles: "listRoles",
|
listRoles: "listRoles",
|
||||||
listRoomPins: "listRoomPins",
|
listRoomPins: "listRoomPins",
|
||||||
@ -1472,6 +1473,13 @@ export const API_ENDPOINTS: Record<ApiOperationId, ApiEndpoint> = {
|
|||||||
permission: "resource-shop:view",
|
permission: "resource-shop:view",
|
||||||
permissions: ["resource-shop:view"]
|
permissions: ["resource-shop:view"]
|
||||||
},
|
},
|
||||||
|
listResourceShopPurchaseOrders: {
|
||||||
|
method: "GET",
|
||||||
|
operationId: API_OPERATIONS.listResourceShopPurchaseOrders,
|
||||||
|
path: "/v1/admin/resource-shop/purchase-orders",
|
||||||
|
permission: "resource-shop:view",
|
||||||
|
permissions: ["resource-shop:view"]
|
||||||
|
},
|
||||||
listRobotRooms: {
|
listRobotRooms: {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
operationId: API_OPERATIONS.listRobotRooms,
|
operationId: API_OPERATIONS.listRobotRooms,
|
||||||
|
|||||||
28
src/shared/api/generated/schema.d.ts
vendored
28
src/shared/api/generated/schema.d.ts
vendored
@ -2020,6 +2020,22 @@ export interface paths {
|
|||||||
patch?: never;
|
patch?: never;
|
||||||
trace?: never;
|
trace?: never;
|
||||||
};
|
};
|
||||||
|
"/admin/resource-shop/purchase-orders": {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
get: operations["listResourceShopPurchaseOrders"];
|
||||||
|
put?: never;
|
||||||
|
post?: never;
|
||||||
|
delete?: never;
|
||||||
|
options?: never;
|
||||||
|
head?: never;
|
||||||
|
patch?: never;
|
||||||
|
trace?: never;
|
||||||
|
};
|
||||||
"/admin/resource-shop/items/{shop_item_id}/disable": {
|
"/admin/resource-shop/items/{shop_item_id}/disable": {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
@ -6064,6 +6080,18 @@ export interface operations {
|
|||||||
200: components["responses"]["EmptyResponse"];
|
200: components["responses"]["EmptyResponse"];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
listResourceShopPurchaseOrders: {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody?: never;
|
||||||
|
responses: {
|
||||||
|
200: components["responses"]["EmptyResponse"];
|
||||||
|
};
|
||||||
|
};
|
||||||
disableResourceShopItem: {
|
disableResourceShopItem: {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user