Compare commits
No commits in common. "efaaef7e8683bfcb3ac223e1cc41997acaa462cf" and "b96fdb4f6ac1671e92e968b6eb3176d6f24c9da2" have entirely different histories.
efaaef7e86
...
b96fdb4f6a
@ -132,7 +132,7 @@
|
|||||||
"x-permissions": ["user-leaderboard:view"]
|
"x-permissions": ["user-leaderboard:view"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/resident-activity/voice-room-red-packet/records": {
|
"/admin/activity/red-packets": {
|
||||||
"get": {
|
"get": {
|
||||||
"operationId": "listRedPackets",
|
"operationId": "listRedPackets",
|
||||||
"responses": {
|
"responses": {
|
||||||
@ -144,7 +144,7 @@
|
|||||||
"x-permissions": ["red-packet:view"]
|
"x-permissions": ["red-packet:view"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/resident-activity/voice-room-red-packet/records/{packet_id}": {
|
"/admin/activity/red-packets/{packet_id}": {
|
||||||
"get": {
|
"get": {
|
||||||
"operationId": "getRedPacket",
|
"operationId": "getRedPacket",
|
||||||
"responses": {
|
"responses": {
|
||||||
@ -166,7 +166,7 @@
|
|||||||
"x-permissions": ["red-packet:view"]
|
"x-permissions": ["red-packet:view"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/resident-activity/voice-room-red-packet/config": {
|
"/admin/activity/red-packets/config": {
|
||||||
"get": {
|
"get": {
|
||||||
"operationId": "getRedPacketConfig",
|
"operationId": "getRedPacketConfig",
|
||||||
"responses": {
|
"responses": {
|
||||||
@ -177,7 +177,7 @@
|
|||||||
"x-permission": "red-packet:view",
|
"x-permission": "red-packet:view",
|
||||||
"x-permissions": ["red-packet:view"]
|
"x-permissions": ["red-packet:view"]
|
||||||
},
|
},
|
||||||
"post": {
|
"put": {
|
||||||
"operationId": "updateRedPacketConfig",
|
"operationId": "updateRedPacketConfig",
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
@ -188,18 +188,6 @@
|
|||||||
"x-permissions": ["red-packet:update"]
|
"x-permissions": ["red-packet:update"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/resident-activity/voice-room-red-packet/refund/retry": {
|
|
||||||
"post": {
|
|
||||||
"operationId": "retryRedPacketRefund",
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"$ref": "#/components/responses/EmptyResponse"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"x-permission": "red-packet:update",
|
|
||||||
"x-permissions": ["red-packet:update"]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/admin/activity/lucky-gifts/v2/config": {
|
"/admin/activity/lucky-gifts/v2/config": {
|
||||||
"get": {
|
"get": {
|
||||||
"operationId": "getLuckyGiftConfig",
|
"operationId": "getLuckyGiftConfig",
|
||||||
@ -1483,18 +1471,6 @@
|
|||||||
"x-permissions": ["coin-ledger:view"]
|
"x-permissions": ["coin-ledger:view"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/admin/operations/reports": {
|
|
||||||
"get": {
|
|
||||||
"operationId": "listReports",
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"$ref": "#/components/responses/EmptyResponse"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"x-permission": "report:view",
|
|
||||||
"x-permissions": ["report:view"]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/admin/payment/recharge-bills": {
|
"/admin/payment/recharge-bills": {
|
||||||
"get": {
|
"get": {
|
||||||
"operationId": "listRechargeBills",
|
"operationId": "listRechargeBills",
|
||||||
|
|||||||
@ -6,7 +6,6 @@ import CardGiftcardOutlined from "@mui/icons-material/CardGiftcardOutlined";
|
|||||||
import CategoryOutlined from "@mui/icons-material/CategoryOutlined";
|
import CategoryOutlined from "@mui/icons-material/CategoryOutlined";
|
||||||
import DashboardOutlined from "@mui/icons-material/DashboardOutlined";
|
import DashboardOutlined from "@mui/icons-material/DashboardOutlined";
|
||||||
import EventAvailableOutlined from "@mui/icons-material/EventAvailableOutlined";
|
import EventAvailableOutlined from "@mui/icons-material/EventAvailableOutlined";
|
||||||
import FlagOutlined from "@mui/icons-material/FlagOutlined";
|
|
||||||
import GroupsOutlined from "@mui/icons-material/GroupsOutlined";
|
import GroupsOutlined from "@mui/icons-material/GroupsOutlined";
|
||||||
import HistoryOutlined from "@mui/icons-material/HistoryOutlined";
|
import HistoryOutlined from "@mui/icons-material/HistoryOutlined";
|
||||||
import HubOutlined from "@mui/icons-material/HubOutlined";
|
import HubOutlined from "@mui/icons-material/HubOutlined";
|
||||||
@ -45,7 +44,6 @@ const iconMap = {
|
|||||||
dashboard: DashboardOutlined,
|
dashboard: DashboardOutlined,
|
||||||
event_available: EventAvailableOutlined,
|
event_available: EventAvailableOutlined,
|
||||||
explore: MapOutlined,
|
explore: MapOutlined,
|
||||||
flag: FlagOutlined,
|
|
||||||
gift: CardGiftcardOutlined,
|
gift: CardGiftcardOutlined,
|
||||||
inventory: Inventory2Outlined,
|
inventory: Inventory2Outlined,
|
||||||
leaderboard: LeaderboardOutlined,
|
leaderboard: LeaderboardOutlined,
|
||||||
@ -146,7 +144,6 @@ export const fallbackNavigation = [
|
|||||||
children: [
|
children: [
|
||||||
routeNavItem("operation-coin-ledger", { icon: ReceiptLongOutlined }),
|
routeNavItem("operation-coin-ledger", { icon: ReceiptLongOutlined }),
|
||||||
routeNavItem("operation-coin-adjustment", { icon: WalletOutlined }),
|
routeNavItem("operation-coin-adjustment", { icon: WalletOutlined }),
|
||||||
routeNavItem("operation-reports", { icon: FlagOutlined }),
|
|
||||||
routeNavItem("lucky-gift", { icon: RedeemOutlined }),
|
routeNavItem("lucky-gift", { icon: RedeemOutlined }),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
@ -28,7 +28,6 @@ export const PERMISSIONS = {
|
|||||||
coinLedgerView: "coin-ledger:view",
|
coinLedgerView: "coin-ledger:view",
|
||||||
coinAdjustmentView: "coin-adjustment:view",
|
coinAdjustmentView: "coin-adjustment:view",
|
||||||
coinAdjustmentCreate: "coin-adjustment:create",
|
coinAdjustmentCreate: "coin-adjustment:create",
|
||||||
reportView: "report:view",
|
|
||||||
paymentBillView: "payment-bill:view",
|
paymentBillView: "payment-bill:view",
|
||||||
paymentProductView: "payment-product:view",
|
paymentProductView: "payment-product:view",
|
||||||
paymentProductCreate: "payment-product:create",
|
paymentProductCreate: "payment-product:create",
|
||||||
@ -161,7 +160,6 @@ export const MENU_CODES = {
|
|||||||
operations: "operations",
|
operations: "operations",
|
||||||
operationCoinLedger: "operation-coin-ledger",
|
operationCoinLedger: "operation-coin-ledger",
|
||||||
operationCoinAdjustment: "operation-coin-adjustment",
|
operationCoinAdjustment: "operation-coin-adjustment",
|
||||||
operationReports: "operation-reports",
|
|
||||||
luckyGift: "lucky-gift",
|
luckyGift: "lucky-gift",
|
||||||
activities: "activities",
|
activities: "activities",
|
||||||
dailyTaskList: "daily-task-list",
|
dailyTaskList: "daily-task-list",
|
||||||
|
|||||||
@ -8,7 +8,6 @@ import type {
|
|||||||
CoinLedgerEntryDto,
|
CoinLedgerEntryDto,
|
||||||
CoinLedgerUserDto,
|
CoinLedgerUserDto,
|
||||||
PageQuery,
|
PageQuery,
|
||||||
ReportDto,
|
|
||||||
} from "@/shared/api/types";
|
} from "@/shared/api/types";
|
||||||
|
|
||||||
export function listCoinLedger(query: PageQuery = {}): Promise<ApiPage<CoinLedgerEntryDto>> {
|
export function listCoinLedger(query: PageQuery = {}): Promise<ApiPage<CoinLedgerEntryDto>> {
|
||||||
@ -27,14 +26,6 @@ export function listCoinAdjustments(query: PageQuery = {}): Promise<ApiPage<Coin
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function listReports(query: PageQuery = {}): Promise<ApiPage<ReportDto>> {
|
|
||||||
const endpoint = API_ENDPOINTS.listReports;
|
|
||||||
return apiRequest<ApiPage<ReportDto>>(apiEndpointPath(API_OPERATIONS.listReports), {
|
|
||||||
method: endpoint.method,
|
|
||||||
query,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function lookupCoinAdjustmentTarget(userId: string): Promise<CoinLedgerUserDto> {
|
export function lookupCoinAdjustmentTarget(userId: string): Promise<CoinLedgerUserDto> {
|
||||||
const endpoint = API_ENDPOINTS.lookupCoinAdjustmentTarget;
|
const endpoint = API_ENDPOINTS.lookupCoinAdjustmentTarget;
|
||||||
return apiRequest<CoinLedgerUserDto>(apiEndpointPath(API_OPERATIONS.lookupCoinAdjustmentTarget), {
|
return apiRequest<CoinLedgerUserDto>(apiEndpointPath(API_OPERATIONS.lookupCoinAdjustmentTarget), {
|
||||||
|
|||||||
@ -185,56 +185,6 @@
|
|||||||
background: var(--bg-card-strong);
|
background: var(--bg-card-strong);
|
||||||
}
|
}
|
||||||
|
|
||||||
.reportTargetType {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: var(--space-2);
|
|
||||||
color: var(--text-primary);
|
|
||||||
font-weight: 700;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.reportReason {
|
|
||||||
display: -webkit-box;
|
|
||||||
overflow: hidden;
|
|
||||||
color: var(--text-primary);
|
|
||||||
line-height: 1.55;
|
|
||||||
overflow-wrap: anywhere;
|
|
||||||
-webkit-box-orient: vertical;
|
|
||||||
-webkit-line-clamp: 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
.reportImages {
|
|
||||||
display: flex;
|
|
||||||
min-width: 0;
|
|
||||||
align-items: center;
|
|
||||||
gap: var(--space-2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.reportImage,
|
|
||||||
.reportImageCount {
|
|
||||||
display: inline-flex;
|
|
||||||
flex: 0 0 auto;
|
|
||||||
width: 44px;
|
|
||||||
height: 44px;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
overflow: hidden;
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
border-radius: var(--radius-control);
|
|
||||||
background: var(--bg-card-strong);
|
|
||||||
color: var(--text-secondary);
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 700;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.reportImage img {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
object-fit: cover;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 640px) {
|
@media (max-width: 640px) {
|
||||||
.detailGrid {
|
.detailGrid {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
|
|||||||
@ -1,260 +0,0 @@
|
|||||||
import MeetingRoomOutlined from "@mui/icons-material/MeetingRoomOutlined";
|
|
||||||
import PersonOutlineOutlined from "@mui/icons-material/PersonOutlineOutlined";
|
|
||||||
import { useMemo, useState } from "react";
|
|
||||||
import { listReports } from "@/features/operations/api";
|
|
||||||
import styles from "@/features/operations/operations.module.css";
|
|
||||||
import { usePaginatedQuery } from "@/shared/hooks/usePaginatedQuery.js";
|
|
||||||
import {
|
|
||||||
AdminFilterResetButton,
|
|
||||||
AdminListBody,
|
|
||||||
AdminListPage,
|
|
||||||
AdminListToolbar,
|
|
||||||
} from "@/shared/ui/AdminListLayout.jsx";
|
|
||||||
import { DataState } from "@/shared/ui/DataState.jsx";
|
|
||||||
import { DataTable } from "@/shared/ui/DataTable.jsx";
|
|
||||||
import { TimeRangeFilter } from "@/shared/ui/TimeRangeFilter.jsx";
|
|
||||||
import { createOptionsColumnFilter, createTextColumnFilter } from "@/shared/ui/tableFilters.js";
|
|
||||||
import { formatMillis } from "@/shared/utils/time.js";
|
|
||||||
|
|
||||||
const pageSize = 50;
|
|
||||||
|
|
||||||
const targetTypeOptions = [
|
|
||||||
{ label: "全部目标", value: "" },
|
|
||||||
{ label: "用户", value: "user" },
|
|
||||||
{ label: "房间", value: "room" },
|
|
||||||
];
|
|
||||||
|
|
||||||
const reportTypeOptions = [
|
|
||||||
{ label: "全部类型", value: "" },
|
|
||||||
{ label: "政治", value: "politics" },
|
|
||||||
{ label: "色情", value: "pornography" },
|
|
||||||
{ label: "血腥暴力", value: "graphic_violence" },
|
|
||||||
{ label: "言语辱骂", value: "verbal_abuse" },
|
|
||||||
{ label: "欺诈", value: "fraud" },
|
|
||||||
{ label: "违法内容", value: "illegal_content" },
|
|
||||||
{ label: "未成年人", value: "minors" },
|
|
||||||
{ label: "线下交易", value: "in_person_transactions" },
|
|
||||||
{ label: "其他", value: "other" },
|
|
||||||
];
|
|
||||||
|
|
||||||
const columns = [
|
|
||||||
{
|
|
||||||
key: "targetType",
|
|
||||||
label: "举报目标",
|
|
||||||
render: (report) => <TargetTypeLabel report={report} />,
|
|
||||||
width: "minmax(120px, 0.55fr)",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "target",
|
|
||||||
label: "举报信息",
|
|
||||||
render: (report) => <TargetCell report={report} />,
|
|
||||||
width: "minmax(300px, 1.3fr)",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "reportType",
|
|
||||||
label: "举报类型",
|
|
||||||
render: (report) => reportTypeLabel(report.reportType),
|
|
||||||
width: "minmax(150px, 0.7fr)",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "reason",
|
|
||||||
label: "举报理由",
|
|
||||||
render: (report) => <span className={styles.reportReason}>{report.reason || "-"}</span>,
|
|
||||||
width: "minmax(220px, 1fr)",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "imageUrls",
|
|
||||||
label: "举报图片",
|
|
||||||
render: (report) => <EvidenceImages imageUrls={report.imageUrls} />,
|
|
||||||
width: "minmax(180px, 0.8fr)",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "createdAtMs",
|
|
||||||
label: "提交时间",
|
|
||||||
render: (report) => formatMillis(report.createdAtMs),
|
|
||||||
width: "minmax(170px, 0.75fr)",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export function ReportListPage() {
|
|
||||||
const [page, setPage] = useState(1);
|
|
||||||
const [keyword, setKeyword] = useState("");
|
|
||||||
const [targetType, setTargetType] = useState("");
|
|
||||||
const [reportType, setReportType] = useState("");
|
|
||||||
const [timeRange, setTimeRange] = useState({ endMs: "", startMs: "" });
|
|
||||||
|
|
||||||
const filters = useMemo(
|
|
||||||
() => ({
|
|
||||||
end_at_ms: timeRange.endMs || "",
|
|
||||||
keyword,
|
|
||||||
report_type: reportType,
|
|
||||||
start_at_ms: timeRange.startMs || "",
|
|
||||||
target_type: targetType,
|
|
||||||
}),
|
|
||||||
[keyword, reportType, targetType, timeRange.endMs, timeRange.startMs],
|
|
||||||
);
|
|
||||||
const query = usePaginatedQuery({
|
|
||||||
errorMessage: "获取举报列表失败",
|
|
||||||
fetcher: listReports,
|
|
||||||
filters,
|
|
||||||
page,
|
|
||||||
pageSize,
|
|
||||||
queryKey: ["reports", filters, page],
|
|
||||||
});
|
|
||||||
const data = query.data || { items: [], total: 0, page, pageSize };
|
|
||||||
const items = data.items || [];
|
|
||||||
const total = data.total || 0;
|
|
||||||
|
|
||||||
const changeFilter = (setter) => (value) => {
|
|
||||||
setter(value);
|
|
||||||
setPage(1);
|
|
||||||
};
|
|
||||||
const changeTimeRange = (nextRange) => {
|
|
||||||
setTimeRange(nextRange);
|
|
||||||
setPage(1);
|
|
||||||
};
|
|
||||||
const resetFilters = () => {
|
|
||||||
setKeyword("");
|
|
||||||
setTargetType("");
|
|
||||||
setReportType("");
|
|
||||||
setTimeRange({ endMs: "", startMs: "" });
|
|
||||||
setPage(1);
|
|
||||||
};
|
|
||||||
|
|
||||||
const tableColumns = columns.map((column) => {
|
|
||||||
if (column.key === "target") {
|
|
||||||
return {
|
|
||||||
...column,
|
|
||||||
filter: createTextColumnFilter({
|
|
||||||
placeholder: "长 ID / 短 ID / 昵称",
|
|
||||||
value: keyword,
|
|
||||||
onChange: changeFilter(setKeyword),
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (column.key === "targetType") {
|
|
||||||
return {
|
|
||||||
...column,
|
|
||||||
filter: createOptionsColumnFilter({
|
|
||||||
options: targetTypeOptions,
|
|
||||||
value: targetType,
|
|
||||||
onChange: changeFilter(setTargetType),
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (column.key === "reportType") {
|
|
||||||
return {
|
|
||||||
...column,
|
|
||||||
filter: createOptionsColumnFilter({
|
|
||||||
options: reportTypeOptions,
|
|
||||||
value: reportType,
|
|
||||||
onChange: changeFilter(setReportType),
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return column;
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<AdminListPage>
|
|
||||||
<AdminListToolbar
|
|
||||||
filters={
|
|
||||||
<>
|
|
||||||
<TimeRangeFilter value={timeRange} onChange={changeTimeRange} />
|
|
||||||
<AdminFilterResetButton
|
|
||||||
disabled={!keyword && !targetType && !reportType && !timeRange.startMs && !timeRange.endMs}
|
|
||||||
onClick={resetFilters}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<DataState error={query.error} loading={query.loading} onRetry={query.reload}>
|
|
||||||
<AdminListBody>
|
|
||||||
<DataTable
|
|
||||||
columns={tableColumns}
|
|
||||||
items={items}
|
|
||||||
minWidth="1140px"
|
|
||||||
pagination={
|
|
||||||
total > 0
|
|
||||||
? {
|
|
||||||
page,
|
|
||||||
pageSize: data.pageSize || pageSize,
|
|
||||||
total,
|
|
||||||
onPageChange: setPage,
|
|
||||||
}
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
rowKey={(report) => report.reportId}
|
|
||||||
/>
|
|
||||||
</AdminListBody>
|
|
||||||
</DataState>
|
|
||||||
</AdminListPage>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function TargetTypeLabel({ report }) {
|
|
||||||
const isRoom = report.targetType === "room";
|
|
||||||
return (
|
|
||||||
<span className={styles.reportTargetType}>
|
|
||||||
{isRoom ? <MeetingRoomOutlined fontSize="small" /> : <PersonOutlineOutlined fontSize="small" />}
|
|
||||||
{isRoom ? "房间" : "用户"}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function TargetCell({ report }) {
|
|
||||||
const target = report.target || {};
|
|
||||||
if (report.targetType === "room") {
|
|
||||||
const room = target.room || {};
|
|
||||||
return (
|
|
||||||
<div className={styles.identity}>
|
|
||||||
<span className={styles.avatar}>
|
|
||||||
{room.coverUrl ? <img alt="" src={room.coverUrl} /> : <MeetingRoomOutlined fontSize="small" />}
|
|
||||||
</span>
|
|
||||||
<span className={styles.identityText}>
|
|
||||||
<span className={styles.primaryText}>{room.title || "-"}</span>
|
|
||||||
<span className={styles.meta}>{targetMeta(room.roomShortId, room.roomId || report.roomId)}</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const user = target.user || {};
|
|
||||||
return (
|
|
||||||
<div className={styles.identity}>
|
|
||||||
<span className={styles.avatar}>
|
|
||||||
{user.avatar ? <img alt="" src={user.avatar} /> : <PersonOutlineOutlined fontSize="small" />}
|
|
||||||
</span>
|
|
||||||
<span className={styles.identityText}>
|
|
||||||
<span className={styles.primaryText}>{user.username || "-"}</span>
|
|
||||||
<span className={styles.meta}>
|
|
||||||
{targetMeta(user.displayUserId || user.defaultDisplayUserId, user.userId || report.userId)}
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function EvidenceImages({ imageUrls = [] }) {
|
|
||||||
const urls = Array.isArray(imageUrls) ? imageUrls.filter(Boolean) : [];
|
|
||||||
if (!urls.length) {
|
|
||||||
return "-";
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<div className={styles.reportImages}>
|
|
||||||
{urls.slice(0, 4).map((url) => (
|
|
||||||
<a className={styles.reportImage} href={url} key={url} rel="noreferrer" target="_blank">
|
|
||||||
<img alt="" src={url} />
|
|
||||||
</a>
|
|
||||||
))}
|
|
||||||
{urls.length > 4 ? <span className={styles.reportImageCount}>+{urls.length - 4}</span> : null}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function targetMeta(shortId, longId) {
|
|
||||||
return `短ID ${shortId || "-"} · 长ID ${longId || "-"}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function reportTypeLabel(value) {
|
|
||||||
return reportTypeOptions.find((item) => item.value === value)?.label || value || "-";
|
|
||||||
}
|
|
||||||
@ -17,12 +17,4 @@ export const operationsRoutes = [
|
|||||||
path: "/operations/coin-adjustments",
|
path: "/operations/coin-adjustments",
|
||||||
permission: PERMISSIONS.coinAdjustmentView,
|
permission: PERMISSIONS.coinAdjustmentView,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
label: "举报列表",
|
|
||||||
loader: () => import("./pages/ReportListPage.jsx").then((module) => module.ReportListPage),
|
|
||||||
menuCode: MENU_CODES.operationReports,
|
|
||||||
pageKey: "operation-reports",
|
|
||||||
path: "/operations/reports",
|
|
||||||
permission: PERMISSIONS.reportView,
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|||||||
@ -10,7 +10,6 @@ export interface RedPacketConfigDto {
|
|||||||
delayedOpenSeconds: number;
|
delayedOpenSeconds: number;
|
||||||
expireSeconds: number;
|
expireSeconds: number;
|
||||||
dailySendLimit: number;
|
dailySendLimit: number;
|
||||||
ruleUrl?: string;
|
|
||||||
updatedByAdminId?: number;
|
updatedByAdminId?: number;
|
||||||
createdAtMs?: number;
|
createdAtMs?: number;
|
||||||
updatedAtMs?: number;
|
updatedAtMs?: number;
|
||||||
@ -21,9 +20,8 @@ export interface RedPacketConfigPayload {
|
|||||||
count_tiers: number[];
|
count_tiers: number[];
|
||||||
amount_tiers: number[];
|
amount_tiers: number[];
|
||||||
delayed_open_seconds: number;
|
delayed_open_seconds: number;
|
||||||
expire_seconds?: number;
|
expire_seconds: number;
|
||||||
daily_send_limit: number;
|
daily_send_limit: number;
|
||||||
rule_url?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RedPacketClaimDto {
|
export interface RedPacketClaimDto {
|
||||||
@ -56,20 +54,15 @@ export interface RedPacketDto {
|
|||||||
expiresAtMs: number;
|
expiresAtMs: number;
|
||||||
createdAtMs: number;
|
createdAtMs: number;
|
||||||
updatedAtMs?: number;
|
updatedAtMs?: number;
|
||||||
refundAmount?: number;
|
|
||||||
refundStatus?: string;
|
|
||||||
refundedAtMs?: number;
|
|
||||||
claims?: RedPacketClaimDto[];
|
claims?: RedPacketClaimDto[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RedPacketQuery extends PageQuery {
|
export interface RedPacketQuery extends PageQuery {
|
||||||
roomId?: string;
|
room_id?: string;
|
||||||
senderUserId?: string;
|
sender_user_id?: string;
|
||||||
region_id?: string;
|
region_id?: string;
|
||||||
packetMode?: string;
|
packet_type?: string;
|
||||||
status?: string;
|
status?: string;
|
||||||
startTime?: string;
|
|
||||||
endTime?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type RawConfig = RedPacketConfigDto & Record<string, unknown>;
|
type RawConfig = RedPacketConfigDto & Record<string, unknown>;
|
||||||
@ -109,14 +102,6 @@ export function getRedPacket(packetId: string): Promise<RedPacketDto> {
|
|||||||
}).then(normalizePacket);
|
}).then(normalizePacket);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function retryRedPacketRefund(packetId: string): Promise<unknown> {
|
|
||||||
const endpoint = API_ENDPOINTS.retryRedPacketRefund;
|
|
||||||
return apiRequest(apiEndpointPath(API_OPERATIONS.retryRedPacketRefund), {
|
|
||||||
body: { packetNo: packetId },
|
|
||||||
method: endpoint.method,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function normalizeConfig(item: RawConfig): RedPacketConfigDto {
|
function normalizeConfig(item: RawConfig): RedPacketConfigDto {
|
||||||
return {
|
return {
|
||||||
appCode: stringValue(item.appCode ?? item.app_code),
|
appCode: stringValue(item.appCode ?? item.app_code),
|
||||||
@ -126,7 +111,6 @@ function normalizeConfig(item: RawConfig): RedPacketConfigDto {
|
|||||||
delayedOpenSeconds: numberValue(item.delayedOpenSeconds ?? item.delayed_open_seconds),
|
delayedOpenSeconds: numberValue(item.delayedOpenSeconds ?? item.delayed_open_seconds),
|
||||||
expireSeconds: numberValue(item.expireSeconds ?? item.expire_seconds),
|
expireSeconds: numberValue(item.expireSeconds ?? item.expire_seconds),
|
||||||
dailySendLimit: numberValue(item.dailySendLimit ?? item.daily_send_limit),
|
dailySendLimit: numberValue(item.dailySendLimit ?? item.daily_send_limit),
|
||||||
ruleUrl: stringValue(item.ruleUrl ?? item.rule_url),
|
|
||||||
updatedByAdminId: numberValue(item.updatedByAdminId ?? item.updated_by_admin_id),
|
updatedByAdminId: numberValue(item.updatedByAdminId ?? item.updated_by_admin_id),
|
||||||
createdAtMs: numberValue(item.createdAtMs ?? item.created_at_ms),
|
createdAtMs: numberValue(item.createdAtMs ?? item.created_at_ms),
|
||||||
updatedAtMs: numberValue(item.updatedAtMs ?? item.updated_at_ms),
|
updatedAtMs: numberValue(item.updatedAtMs ?? item.updated_at_ms),
|
||||||
@ -152,9 +136,6 @@ function normalizePacket(item: RawPacket): RedPacketDto {
|
|||||||
expiresAtMs: numberValue(item.expiresAtMs ?? item.expires_at_ms),
|
expiresAtMs: numberValue(item.expiresAtMs ?? item.expires_at_ms),
|
||||||
createdAtMs: numberValue(item.createdAtMs ?? item.created_at_ms),
|
createdAtMs: numberValue(item.createdAtMs ?? item.created_at_ms),
|
||||||
updatedAtMs: numberValue(item.updatedAtMs ?? item.updated_at_ms),
|
updatedAtMs: numberValue(item.updatedAtMs ?? item.updated_at_ms),
|
||||||
refundAmount: numberValue(item.refundAmount ?? item.refund_amount),
|
|
||||||
refundStatus: stringValue(item.refundStatus ?? item.refund_status),
|
|
||||||
refundedAtMs: numberValue(item.refundedAtMs ?? item.refunded_at_ms),
|
|
||||||
claims: rawClaims.map(normalizeClaim),
|
claims: rawClaims.map(normalizeClaim),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -61,38 +61,30 @@ export function RedPacketConfigDrawer({
|
|||||||
<div className="form-drawer__grid">
|
<div className="form-drawer__grid">
|
||||||
<TextField
|
<TextField
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
inputProps={{ min: 0 }}
|
||||||
label="延迟红包秒数"
|
label="延迟红包秒数"
|
||||||
slotProps={{ htmlInput: { min: 0 } }}
|
|
||||||
type="number"
|
type="number"
|
||||||
value={form.delayedOpenSeconds}
|
value={form.delayedOpenSeconds}
|
||||||
onChange={(event) => updateValue("delayedOpenSeconds", event.target.value)}
|
onChange={(event) => updateValue("delayedOpenSeconds", event.target.value)}
|
||||||
/>
|
/>
|
||||||
<TextField
|
<TextField
|
||||||
disabled
|
disabled={disabled}
|
||||||
label="过期退款秒数(固定)"
|
inputProps={{ min: 1 }}
|
||||||
slotProps={{ htmlInput: { min: 1 } }}
|
label="过期退款秒数"
|
||||||
type="number"
|
type="number"
|
||||||
value={form.expireSeconds}
|
value={form.expireSeconds}
|
||||||
|
onChange={(event) => updateValue("expireSeconds", event.target.value)}
|
||||||
/>
|
/>
|
||||||
<TextField
|
<TextField
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
inputProps={{ min: 1 }}
|
||||||
label="每用户每日最多发送"
|
label="每用户每日最多发送"
|
||||||
slotProps={{ htmlInput: { min: 1 } }}
|
|
||||||
type="number"
|
type="number"
|
||||||
value={form.dailySendLimit}
|
value={form.dailySendLimit}
|
||||||
onChange={(event) => updateValue("dailySendLimit", event.target.value)}
|
onChange={(event) => updateValue("dailySendLimit", event.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<section className="form-drawer__section">
|
|
||||||
<TextField
|
|
||||||
fullWidth
|
|
||||||
disabled={disabled}
|
|
||||||
label="规则说明 URL"
|
|
||||||
value={form.ruleUrl}
|
|
||||||
onChange={(event) => updateValue("ruleUrl", event.target.value)}
|
|
||||||
/>
|
|
||||||
</section>
|
|
||||||
<TierSection
|
<TierSection
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
label="红包数量档位"
|
label="红包数量档位"
|
||||||
@ -148,8 +140,8 @@ function TierSection({ disabled, label, onAdd, onRemove, onUpdate, tierKey, valu
|
|||||||
<div className={styles.tierGrid} key={`${tierKey}-${index}`}>
|
<div className={styles.tierGrid} key={`${tierKey}-${index}`}>
|
||||||
<TextField
|
<TextField
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
inputProps={{ min: 1 }}
|
||||||
label={`${label} ${index + 1}`}
|
label={`${label} ${index + 1}`}
|
||||||
slotProps={{ htmlInput: { min: 1 } }}
|
|
||||||
type="number"
|
type="number"
|
||||||
value={value}
|
value={value}
|
||||||
onChange={(event) => onUpdate(tierKey, index, event.target.value)}
|
onChange={(event) => onUpdate(tierKey, index, event.target.value)}
|
||||||
|
|||||||
@ -20,10 +20,9 @@ export function RedPacketConfigSummary({ canUpdate, config, configLoading, onEdi
|
|||||||
<SummaryItem label="数量档位">{formatTiers(config?.countTiers)}</SummaryItem>
|
<SummaryItem label="数量档位">{formatTiers(config?.countTiers)}</SummaryItem>
|
||||||
<SummaryItem label="金额档位">{formatTiers(config?.amountTiers)}</SummaryItem>
|
<SummaryItem label="金额档位">{formatTiers(config?.amountTiers)}</SummaryItem>
|
||||||
<SummaryItem label="延迟/过期">
|
<SummaryItem label="延迟/过期">
|
||||||
{Number(config?.delayedOpenSeconds || 0)}s / 24h
|
{Number(config?.delayedOpenSeconds || 0)}s / {Number(config?.expireSeconds || 0)}s
|
||||||
</SummaryItem>
|
</SummaryItem>
|
||||||
<SummaryItem label="每日上限">{Number(config?.dailySendLimit || 0)} 次</SummaryItem>
|
<SummaryItem label="每日上限">{Number(config?.dailySendLimit || 0)} 次</SummaryItem>
|
||||||
<SummaryItem label="规则 URL">{config?.ruleUrl || "-"}</SummaryItem>
|
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.summaryActions}>
|
<div className={styles.summaryActions}>
|
||||||
<Button disabled={configLoading} startIcon={<RefreshOutlined fontSize="small" />} onClick={onRefresh}>
|
<Button disabled={configLoading} startIcon={<RefreshOutlined fontSize="small" />} onClick={onRefresh}>
|
||||||
|
|||||||
@ -53,20 +53,6 @@ export function RedPacketDetailDrawer({ loading, onClose, open, packet }) {
|
|||||||
<InfoRow label="状态" value={packetStatusLabel(packet.status)} />
|
<InfoRow label="状态" value={packetStatusLabel(packet.status)} />
|
||||||
<InfoRow label="金额" value={`${formatNumber(packet.totalAmount)} 金币`} />
|
<InfoRow label="金额" value={`${formatNumber(packet.totalAmount)} 金币`} />
|
||||||
<InfoRow label="剩余" value={`${formatNumber(packet.remainingAmount)} / ${packet.remainingCount} 个`} />
|
<InfoRow label="剩余" value={`${formatNumber(packet.remainingAmount)} / ${packet.remainingCount} 个`} />
|
||||||
<InfoRow
|
|
||||||
label="已领"
|
|
||||||
value={`${formatNumber(packet.totalAmount - packet.remainingAmount)} / ${
|
|
||||||
packet.packetCount - packet.remainingCount
|
|
||||||
} 个`}
|
|
||||||
/>
|
|
||||||
<InfoRow
|
|
||||||
label="退款"
|
|
||||||
value={
|
|
||||||
packet.refundStatus
|
|
||||||
? `${packetStatusLabel(packet.refundStatus)} ${formatNumber(packet.refundAmount)} 金币`
|
|
||||||
: "-"
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<InfoRow label="开启时间" value={formatMillis(packet.openAtMs)} />
|
<InfoRow label="开启时间" value={formatMillis(packet.openAtMs)} />
|
||||||
<InfoRow label="过期时间" value={formatMillis(packet.expiresAtMs)} />
|
<InfoRow label="过期时间" value={formatMillis(packet.expiresAtMs)} />
|
||||||
<DataTable
|
<DataTable
|
||||||
@ -95,7 +81,7 @@ function InfoRow({ label, value }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function packetTypeLabel(type) {
|
function packetTypeLabel(type) {
|
||||||
return String(type || "").toLowerCase() === "delayed" ? "延时红包" : "即时红包";
|
return type === "delayed" ? "延迟红包" : "普通红包";
|
||||||
}
|
}
|
||||||
|
|
||||||
function packetStatusLabel(status) {
|
function packetStatusLabel(status) {
|
||||||
@ -105,7 +91,7 @@ function packetStatusLabel(status) {
|
|||||||
refunded: "已退款",
|
refunded: "已退款",
|
||||||
waiting_open: "待开启",
|
waiting_open: "待开启",
|
||||||
};
|
};
|
||||||
return labels[String(status || "").toLowerCase()] || status || "-";
|
return labels[status] || status || "-";
|
||||||
}
|
}
|
||||||
|
|
||||||
function claimStatusLabel(status) {
|
function claimStatusLabel(status) {
|
||||||
|
|||||||
@ -3,7 +3,6 @@ import {
|
|||||||
getRedPacket,
|
getRedPacket,
|
||||||
getRedPacketConfig,
|
getRedPacketConfig,
|
||||||
listRedPackets,
|
listRedPackets,
|
||||||
retryRedPacketRefund,
|
|
||||||
updateRedPacketConfig,
|
updateRedPacketConfig,
|
||||||
} from "@/features/red-packets/api";
|
} from "@/features/red-packets/api";
|
||||||
import { useRedPacketAbilities } from "@/features/red-packets/permissions.js";
|
import { useRedPacketAbilities } from "@/features/red-packets/permissions.js";
|
||||||
@ -18,9 +17,8 @@ const emptyForm = {
|
|||||||
countTiers: [""],
|
countTiers: [""],
|
||||||
amountTiers: [""],
|
amountTiers: [""],
|
||||||
delayedOpenSeconds: "0",
|
delayedOpenSeconds: "0",
|
||||||
expireSeconds: "86400",
|
expireSeconds: "300",
|
||||||
dailySendLimit: "5",
|
dailySendLimit: "5",
|
||||||
ruleUrl: "",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export function useRedPacketPage() {
|
export function useRedPacketPage() {
|
||||||
@ -42,9 +40,9 @@ export function useRedPacketPage() {
|
|||||||
|
|
||||||
const filters = useMemo(
|
const filters = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
roomId: roomId.trim() || undefined,
|
room_id: roomId.trim() || undefined,
|
||||||
senderUserId: senderUserId.trim() || undefined,
|
sender_user_id: senderUserId.trim() || undefined,
|
||||||
packetMode: packetType || undefined,
|
packet_type: packetType || undefined,
|
||||||
status: status || undefined,
|
status: status || undefined,
|
||||||
}),
|
}),
|
||||||
[packetType, roomId, senderUserId, status],
|
[packetType, roomId, senderUserId, status],
|
||||||
@ -126,22 +124,6 @@ export function useRedPacketPage() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const retryRefund = async (packetId) => {
|
|
||||||
if (!packetId || !abilities.canUpdate) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
await retryRedPacketRefund(packetId);
|
|
||||||
showToast("退款重试已提交", "success");
|
|
||||||
await reloadPackets();
|
|
||||||
if (selectedPacket?.packetId === packetId) {
|
|
||||||
await openPacketDetail(packetId);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
showToast(err.message || "退款重试失败", "error");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const closePacketDetail = () => {
|
const closePacketDetail = () => {
|
||||||
setDetailOpen(false);
|
setDetailOpen(false);
|
||||||
setSelectedPacket(null);
|
setSelectedPacket(null);
|
||||||
@ -181,7 +163,6 @@ export function useRedPacketPage() {
|
|||||||
reloadConfig,
|
reloadConfig,
|
||||||
reloadPackets,
|
reloadPackets,
|
||||||
resetFilters,
|
resetFilters,
|
||||||
retryRefund,
|
|
||||||
roomId,
|
roomId,
|
||||||
selectedPacket,
|
selectedPacket,
|
||||||
senderUserId,
|
senderUserId,
|
||||||
@ -202,9 +183,8 @@ function formFromConfig(config) {
|
|||||||
countTiers: valuesToForm(config.countTiers),
|
countTiers: valuesToForm(config.countTiers),
|
||||||
amountTiers: valuesToForm(config.amountTiers),
|
amountTiers: valuesToForm(config.amountTiers),
|
||||||
delayedOpenSeconds: String(config.delayedOpenSeconds || 0),
|
delayedOpenSeconds: String(config.delayedOpenSeconds || 0),
|
||||||
expireSeconds: "86400",
|
expireSeconds: String(config.expireSeconds || 300),
|
||||||
dailySendLimit: String(config.dailySendLimit || 0),
|
dailySendLimit: String(config.dailySendLimit || 0),
|
||||||
ruleUrl: config.ruleUrl || "",
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -216,7 +196,7 @@ function payloadFromForm(form) {
|
|||||||
const countTiers = normalizePositiveIntegers(form.countTiers);
|
const countTiers = normalizePositiveIntegers(form.countTiers);
|
||||||
const amountTiers = normalizePositiveIntegers(form.amountTiers);
|
const amountTiers = normalizePositiveIntegers(form.amountTiers);
|
||||||
const delayedOpenSeconds = Number(form.delayedOpenSeconds || 0);
|
const delayedOpenSeconds = Number(form.delayedOpenSeconds || 0);
|
||||||
const expireSeconds = 86400;
|
const expireSeconds = Number(form.expireSeconds || 0);
|
||||||
const dailySendLimit = Number(form.dailySendLimit || 0);
|
const dailySendLimit = Number(form.dailySendLimit || 0);
|
||||||
if (!countTiers.length) {
|
if (!countTiers.length) {
|
||||||
throw new Error("红包数量档位不能为空");
|
throw new Error("红包数量档位不能为空");
|
||||||
@ -240,7 +220,6 @@ function payloadFromForm(form) {
|
|||||||
delayed_open_seconds: delayedOpenSeconds,
|
delayed_open_seconds: delayedOpenSeconds,
|
||||||
expire_seconds: expireSeconds,
|
expire_seconds: expireSeconds,
|
||||||
daily_send_limit: dailySendLimit,
|
daily_send_limit: dailySendLimit,
|
||||||
rule_url: String(form.ruleUrl || "").trim(),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import VisibilityOutlined from "@mui/icons-material/VisibilityOutlined";
|
import VisibilityOutlined from "@mui/icons-material/VisibilityOutlined";
|
||||||
import ReplayOutlined from "@mui/icons-material/ReplayOutlined";
|
|
||||||
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 { RedPacketConfigDrawer } from "@/features/red-packets/components/RedPacketConfigDrawer.jsx";
|
import { RedPacketConfigDrawer } from "@/features/red-packets/components/RedPacketConfigDrawer.jsx";
|
||||||
@ -21,8 +20,8 @@ import { formatMillis } from "@/shared/utils/time.js";
|
|||||||
|
|
||||||
const packetTypeOptions = [
|
const packetTypeOptions = [
|
||||||
["", "全部类型"],
|
["", "全部类型"],
|
||||||
["IMMEDIATE", "即时红包"],
|
["normal", "普通红包"],
|
||||||
["DELAYED", "延时红包"],
|
["delayed", "延迟红包"],
|
||||||
];
|
];
|
||||||
|
|
||||||
const statusOptions = [
|
const statusOptions = [
|
||||||
@ -84,17 +83,6 @@ export function RedPacketPage() {
|
|||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
key: "claimed",
|
|
||||||
label: "已领",
|
|
||||||
width: "minmax(145px, 0.6fr)",
|
|
||||||
render: (packet) => (
|
|
||||||
<div className={styles.stack}>
|
|
||||||
<span>{formatNumber(packet.totalAmount - packet.remainingAmount)} 金币</span>
|
|
||||||
<span className={styles.meta}>{packet.packetCount - packet.remainingCount} 个</span>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
key: "status",
|
key: "status",
|
||||||
label: "状态",
|
label: "状态",
|
||||||
@ -113,26 +101,12 @@ export function RedPacketPage() {
|
|||||||
width: "minmax(180px, 0.75fr)",
|
width: "minmax(180px, 0.75fr)",
|
||||||
render: (packet) => formatMillis(packet.expiresAtMs),
|
render: (packet) => formatMillis(packet.expiresAtMs),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
key: "refund",
|
|
||||||
label: "退款",
|
|
||||||
width: "minmax(120px, 0.55fr)",
|
|
||||||
render: (packet) =>
|
|
||||||
packet.refundStatus
|
|
||||||
? `${packetStatusLabel(packet.refundStatus)} ${formatNumber(packet.refundAmount)}`
|
|
||||||
: "-",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
key: "actions",
|
key: "actions",
|
||||||
label: "操作",
|
label: "操作",
|
||||||
width: "128px",
|
width: "88px",
|
||||||
render: (packet) => (
|
render: (packet) => (
|
||||||
<AdminRowActions>
|
<AdminRowActions>
|
||||||
{page.abilities.canUpdate && canRetryRefund(packet) ? (
|
|
||||||
<AdminActionIconButton label="重试退款" onClick={() => page.retryRefund(packet.packetId)}>
|
|
||||||
<ReplayOutlined fontSize="small" />
|
|
||||||
</AdminActionIconButton>
|
|
||||||
) : null}
|
|
||||||
<AdminActionIconButton label="查看详情" onClick={() => page.openPacketDetail(packet.packetId)}>
|
<AdminActionIconButton label="查看详情" onClick={() => page.openPacketDetail(packet.packetId)}>
|
||||||
<VisibilityOutlined fontSize="small" />
|
<VisibilityOutlined fontSize="small" />
|
||||||
</AdminActionIconButton>
|
</AdminActionIconButton>
|
||||||
@ -236,20 +210,13 @@ export function RedPacketPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function packetTypeLabel(type) {
|
function packetTypeLabel(type) {
|
||||||
const normalized = String(type || "").toLowerCase();
|
return type === "delayed" ? "延迟红包" : "普通红包";
|
||||||
return normalized === "delayed" ? "延时红包" : "即时红包";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function packetStatusLabel(status) {
|
function packetStatusLabel(status) {
|
||||||
const normalized = String(status || "").toLowerCase();
|
return statusOptions.find(([value]) => value === status)?.[1] || status || "-";
|
||||||
return statusOptions.find(([value]) => value === normalized)?.[1] || status || "-";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatNumber(value) {
|
function formatNumber(value) {
|
||||||
return new Intl.NumberFormat("zh-CN").format(Number(value || 0));
|
return new Intl.NumberFormat("zh-CN").format(Number(value || 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
function canRetryRefund(packet) {
|
|
||||||
const status = String(packet.status || "").toLowerCase();
|
|
||||||
return status === "active" || status === "waiting_open";
|
|
||||||
}
|
|
||||||
|
|||||||
@ -131,7 +131,6 @@ export const API_OPERATIONS = {
|
|||||||
listRegionBlocks: "listRegionBlocks",
|
listRegionBlocks: "listRegionBlocks",
|
||||||
listRegions: "listRegions",
|
listRegions: "listRegions",
|
||||||
listRegistrationRewardClaims: "listRegistrationRewardClaims",
|
listRegistrationRewardClaims: "listRegistrationRewardClaims",
|
||||||
listReports: "listReports",
|
|
||||||
listResourceGrants: "listResourceGrants",
|
listResourceGrants: "listResourceGrants",
|
||||||
listResourceGroups: "listResourceGroups",
|
listResourceGroups: "listResourceGroups",
|
||||||
listResources: "listResources",
|
listResources: "listResources",
|
||||||
@ -155,7 +154,6 @@ export const API_OPERATIONS = {
|
|||||||
replaceRoleDataScopes: "replaceRoleDataScopes",
|
replaceRoleDataScopes: "replaceRoleDataScopes",
|
||||||
replaceRolePermissions: "replaceRolePermissions",
|
replaceRolePermissions: "replaceRolePermissions",
|
||||||
resetUserPassword: "resetUserPassword",
|
resetUserPassword: "resetUserPassword",
|
||||||
retryRedPacketRefund: "retryRedPacketRefund",
|
|
||||||
search: "search",
|
search: "search",
|
||||||
setAgencyJoinEnabled: "setAgencyJoinEnabled",
|
setAgencyJoinEnabled: "setAgencyJoinEnabled",
|
||||||
setBDLeaderStatus: "setBDLeaderStatus",
|
setBDLeaderStatus: "setBDLeaderStatus",
|
||||||
@ -713,14 +711,14 @@ export const API_ENDPOINTS: Record<ApiOperationId, ApiEndpoint> = {
|
|||||||
getRedPacket: {
|
getRedPacket: {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
operationId: API_OPERATIONS.getRedPacket,
|
operationId: API_OPERATIONS.getRedPacket,
|
||||||
path: "/v1/resident-activity/voice-room-red-packet/records/{packet_id}",
|
path: "/v1/admin/activity/red-packets/{packet_id}",
|
||||||
permission: "red-packet:view",
|
permission: "red-packet:view",
|
||||||
permissions: ["red-packet:view"]
|
permissions: ["red-packet:view"]
|
||||||
},
|
},
|
||||||
getRedPacketConfig: {
|
getRedPacketConfig: {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
operationId: API_OPERATIONS.getRedPacketConfig,
|
operationId: API_OPERATIONS.getRedPacketConfig,
|
||||||
path: "/v1/resident-activity/voice-room-red-packet/config",
|
path: "/v1/admin/activity/red-packets/config",
|
||||||
permission: "red-packet:view",
|
permission: "red-packet:view",
|
||||||
permissions: ["red-packet:view"]
|
permissions: ["red-packet:view"]
|
||||||
},
|
},
|
||||||
@ -1005,7 +1003,7 @@ export const API_ENDPOINTS: Record<ApiOperationId, ApiEndpoint> = {
|
|||||||
listRedPackets: {
|
listRedPackets: {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
operationId: API_OPERATIONS.listRedPackets,
|
operationId: API_OPERATIONS.listRedPackets,
|
||||||
path: "/v1/resident-activity/voice-room-red-packet/records",
|
path: "/v1/admin/activity/red-packets",
|
||||||
permission: "red-packet:view",
|
permission: "red-packet:view",
|
||||||
permissions: ["red-packet:view"]
|
permissions: ["red-packet:view"]
|
||||||
},
|
},
|
||||||
@ -1030,13 +1028,6 @@ export const API_ENDPOINTS: Record<ApiOperationId, ApiEndpoint> = {
|
|||||||
permission: "registration-reward:view",
|
permission: "registration-reward:view",
|
||||||
permissions: ["registration-reward:view"]
|
permissions: ["registration-reward:view"]
|
||||||
},
|
},
|
||||||
listReports: {
|
|
||||||
method: "GET",
|
|
||||||
operationId: API_OPERATIONS.listReports,
|
|
||||||
path: "/v1/admin/operations/reports",
|
|
||||||
permission: "report:view",
|
|
||||||
permissions: ["report:view"]
|
|
||||||
},
|
|
||||||
listResourceGrants: {
|
listResourceGrants: {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
operationId: API_OPERATIONS.listResourceGrants,
|
operationId: API_OPERATIONS.listResourceGrants,
|
||||||
@ -1188,13 +1179,6 @@ export const API_ENDPOINTS: Record<ApiOperationId, ApiEndpoint> = {
|
|||||||
permission: "user:reset-password",
|
permission: "user:reset-password",
|
||||||
permissions: ["user:reset-password"]
|
permissions: ["user:reset-password"]
|
||||||
},
|
},
|
||||||
retryRedPacketRefund: {
|
|
||||||
method: "POST",
|
|
||||||
operationId: API_OPERATIONS.retryRedPacketRefund,
|
|
||||||
path: "/v1/resident-activity/voice-room-red-packet/refund/retry",
|
|
||||||
permission: "red-packet:update",
|
|
||||||
permissions: ["red-packet:update"]
|
|
||||||
},
|
|
||||||
search: {
|
search: {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
operationId: API_OPERATIONS.search,
|
operationId: API_OPERATIONS.search,
|
||||||
@ -1376,9 +1360,9 @@ export const API_ENDPOINTS: Record<ApiOperationId, ApiEndpoint> = {
|
|||||||
permissions: ["payment-product:update"]
|
permissions: ["payment-product:update"]
|
||||||
},
|
},
|
||||||
updateRedPacketConfig: {
|
updateRedPacketConfig: {
|
||||||
method: "POST",
|
method: "PUT",
|
||||||
operationId: API_OPERATIONS.updateRedPacketConfig,
|
operationId: API_OPERATIONS.updateRedPacketConfig,
|
||||||
path: "/v1/resident-activity/voice-room-red-packet/config",
|
path: "/v1/admin/activity/red-packets/config",
|
||||||
permission: "red-packet:update",
|
permission: "red-packet:update",
|
||||||
permissions: ["red-packet:update"]
|
permissions: ["red-packet:update"]
|
||||||
},
|
},
|
||||||
|
|||||||
66
src/shared/api/generated/schema.d.ts
vendored
66
src/shared/api/generated/schema.d.ts
vendored
@ -100,7 +100,7 @@ export interface paths {
|
|||||||
patch?: never;
|
patch?: never;
|
||||||
trace?: never;
|
trace?: never;
|
||||||
};
|
};
|
||||||
"/resident-activity/voice-room-red-packet/records": {
|
"/admin/activity/red-packets": {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
header?: never;
|
header?: never;
|
||||||
@ -116,7 +116,7 @@ export interface paths {
|
|||||||
patch?: never;
|
patch?: never;
|
||||||
trace?: never;
|
trace?: never;
|
||||||
};
|
};
|
||||||
"/resident-activity/voice-room-red-packet/records/{packet_id}": {
|
"/admin/activity/red-packets/{packet_id}": {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
header?: never;
|
header?: never;
|
||||||
@ -132,7 +132,7 @@ export interface paths {
|
|||||||
patch?: never;
|
patch?: never;
|
||||||
trace?: never;
|
trace?: never;
|
||||||
};
|
};
|
||||||
"/resident-activity/voice-room-red-packet/config": {
|
"/admin/activity/red-packets/config": {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
header?: never;
|
header?: never;
|
||||||
@ -140,24 +140,8 @@ export interface paths {
|
|||||||
cookie?: never;
|
cookie?: never;
|
||||||
};
|
};
|
||||||
get: operations["getRedPacketConfig"];
|
get: operations["getRedPacketConfig"];
|
||||||
put?: never;
|
put: operations["updateRedPacketConfig"];
|
||||||
post: operations["updateRedPacketConfig"];
|
post?: never;
|
||||||
delete?: never;
|
|
||||||
options?: never;
|
|
||||||
head?: never;
|
|
||||||
patch?: never;
|
|
||||||
trace?: never;
|
|
||||||
};
|
|
||||||
"/resident-activity/voice-room-red-packet/refund/retry": {
|
|
||||||
parameters: {
|
|
||||||
query?: never;
|
|
||||||
header?: never;
|
|
||||||
path?: never;
|
|
||||||
cookie?: never;
|
|
||||||
};
|
|
||||||
get?: never;
|
|
||||||
put?: never;
|
|
||||||
post: operations["retryRedPacketRefund"];
|
|
||||||
delete?: never;
|
delete?: never;
|
||||||
options?: never;
|
options?: never;
|
||||||
head?: never;
|
head?: never;
|
||||||
@ -1028,22 +1012,6 @@ export interface paths {
|
|||||||
patch?: never;
|
patch?: never;
|
||||||
trace?: never;
|
trace?: never;
|
||||||
};
|
};
|
||||||
"/admin/operations/reports": {
|
|
||||||
parameters: {
|
|
||||||
query?: never;
|
|
||||||
header?: never;
|
|
||||||
path?: never;
|
|
||||||
cookie?: never;
|
|
||||||
};
|
|
||||||
get: operations["listReports"];
|
|
||||||
put?: never;
|
|
||||||
post?: never;
|
|
||||||
delete?: never;
|
|
||||||
options?: never;
|
|
||||||
head?: never;
|
|
||||||
patch?: never;
|
|
||||||
trace?: never;
|
|
||||||
};
|
|
||||||
"/admin/payment/recharge-bills": {
|
"/admin/payment/recharge-bills": {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
@ -2750,18 +2718,6 @@ export interface operations {
|
|||||||
200: components["responses"]["EmptyResponse"];
|
200: components["responses"]["EmptyResponse"];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
retryRedPacketRefund: {
|
|
||||||
parameters: {
|
|
||||||
query?: never;
|
|
||||||
header?: never;
|
|
||||||
path?: never;
|
|
||||||
cookie?: never;
|
|
||||||
};
|
|
||||||
requestBody?: never;
|
|
||||||
responses: {
|
|
||||||
200: components["responses"]["EmptyResponse"];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
getLuckyGiftConfig: {
|
getLuckyGiftConfig: {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
@ -3829,18 +3785,6 @@ export interface operations {
|
|||||||
200: components["responses"]["EmptyResponse"];
|
200: components["responses"]["EmptyResponse"];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
listReports: {
|
|
||||||
parameters: {
|
|
||||||
query?: never;
|
|
||||||
header?: never;
|
|
||||||
path?: never;
|
|
||||||
cookie?: never;
|
|
||||||
};
|
|
||||||
requestBody?: never;
|
|
||||||
responses: {
|
|
||||||
200: components["responses"]["EmptyResponse"];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
listRechargeBills: {
|
listRechargeBills: {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
|
|||||||
@ -245,43 +245,6 @@ export interface CoinAdjustmentCreateDto {
|
|||||||
user: CoinLedgerUserDto;
|
user: CoinLedgerUserDto;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ReportUserDto {
|
|
||||||
avatar?: string;
|
|
||||||
defaultDisplayUserId?: string;
|
|
||||||
displayUserId?: string;
|
|
||||||
userId?: string;
|
|
||||||
username?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ReportRoomDto {
|
|
||||||
coverUrl?: string;
|
|
||||||
roomId?: string;
|
|
||||||
roomShortId?: string;
|
|
||||||
title?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ReportTargetDto {
|
|
||||||
room?: ReportRoomDto;
|
|
||||||
type: "user" | "room" | string;
|
|
||||||
user?: ReportUserDto;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ReportDto {
|
|
||||||
createdAtMs?: number;
|
|
||||||
imageUrls?: string[];
|
|
||||||
reason?: string;
|
|
||||||
reportId: string;
|
|
||||||
reporterUserId?: string;
|
|
||||||
reportType?: string;
|
|
||||||
requestId?: string;
|
|
||||||
roomId?: string;
|
|
||||||
status?: string;
|
|
||||||
target: ReportTargetDto;
|
|
||||||
targetType: "user" | "room" | string;
|
|
||||||
updatedAtMs?: number;
|
|
||||||
userId?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RechargeBillDto {
|
export interface RechargeBillDto {
|
||||||
appCode?: string;
|
appCode?: string;
|
||||||
coinAmount: number;
|
coinAmount: number;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user