优化靓号页面展示
This commit is contained in:
parent
8c9f5d35b2
commit
63d4ffbacd
@ -60,6 +60,68 @@ test("listPrettyDisplayIDs uses admin endpoint and normalizes snake case fields"
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("listPrettyDisplayIDs normalizes assigned user and operator snapshots", async () => {
|
||||||
|
vi.stubGlobal(
|
||||||
|
"fetch",
|
||||||
|
vi.fn(
|
||||||
|
async () =>
|
||||||
|
new Response(
|
||||||
|
JSON.stringify({
|
||||||
|
code: 0,
|
||||||
|
data: {
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
assigned_user: {
|
||||||
|
avatar: "https://cdn.example/u.png",
|
||||||
|
default_display_user_id: "10001",
|
||||||
|
display_user_id: "777777",
|
||||||
|
user_id: "9001",
|
||||||
|
username: "pretty owner",
|
||||||
|
},
|
||||||
|
assigned_user_id: "9001",
|
||||||
|
created_by_admin_id: "7",
|
||||||
|
display_user_id: "777777",
|
||||||
|
operator: {
|
||||||
|
account: "ops",
|
||||||
|
admin_id: "8",
|
||||||
|
name: "运营",
|
||||||
|
type: "admin",
|
||||||
|
},
|
||||||
|
pretty_id: "pretty-2",
|
||||||
|
source: "admin_grant",
|
||||||
|
status: "assigned",
|
||||||
|
updated_by_admin_id: "8",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
page: 1,
|
||||||
|
page_size: 20,
|
||||||
|
total: 1,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await listPrettyDisplayIDs({ page: 1, page_size: 20 });
|
||||||
|
|
||||||
|
expect(result.items[0]).toMatchObject({
|
||||||
|
assignedUser: {
|
||||||
|
avatar: "https://cdn.example/u.png",
|
||||||
|
defaultDisplayUserId: "10001",
|
||||||
|
displayUserId: "777777",
|
||||||
|
userId: "9001",
|
||||||
|
username: "pretty owner",
|
||||||
|
},
|
||||||
|
operator: {
|
||||||
|
account: "ops",
|
||||||
|
adminId: "8",
|
||||||
|
name: "运营",
|
||||||
|
type: "admin",
|
||||||
|
},
|
||||||
|
updatedByAdminId: "8",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test("listAppUsers sends country region and time filters", async () => {
|
test("listAppUsers sends country region and time filters", async () => {
|
||||||
vi.stubGlobal(
|
vi.stubGlobal(
|
||||||
"fetch",
|
"fetch",
|
||||||
|
|||||||
@ -386,6 +386,8 @@ interface RawPrettyDisplayID {
|
|||||||
assignedAtMs?: number;
|
assignedAtMs?: number;
|
||||||
assigned_lease_id?: string;
|
assigned_lease_id?: string;
|
||||||
assignedLeaseId?: string;
|
assignedLeaseId?: string;
|
||||||
|
assigned_user?: RawAppUserBrief;
|
||||||
|
assignedUser?: RawAppUserBrief;
|
||||||
assigned_user_id?: EntityId;
|
assigned_user_id?: EntityId;
|
||||||
assignedUserId?: EntityId;
|
assignedUserId?: EntityId;
|
||||||
created_at_ms?: number;
|
created_at_ms?: number;
|
||||||
@ -396,6 +398,7 @@ interface RawPrettyDisplayID {
|
|||||||
displayUserId?: string;
|
displayUserId?: string;
|
||||||
generated_batch_id?: string;
|
generated_batch_id?: string;
|
||||||
generatedBatchId?: string;
|
generatedBatchId?: string;
|
||||||
|
operator?: RawPrettyDisplayIDOperator | null;
|
||||||
pool?: RawPrettyDisplayIDPool | null;
|
pool?: RawPrettyDisplayIDPool | null;
|
||||||
pool_id?: string;
|
pool_id?: string;
|
||||||
poolId?: string;
|
poolId?: string;
|
||||||
@ -409,6 +412,17 @@ interface RawPrettyDisplayID {
|
|||||||
status?: string;
|
status?: string;
|
||||||
updated_at_ms?: number;
|
updated_at_ms?: number;
|
||||||
updatedAtMs?: number;
|
updatedAtMs?: number;
|
||||||
|
updated_by_admin_id?: EntityId;
|
||||||
|
updatedByAdminId?: EntityId;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RawPrettyDisplayIDOperator extends RawAppUserBrief {
|
||||||
|
account?: string;
|
||||||
|
admin_id?: EntityId;
|
||||||
|
adminId?: EntityId;
|
||||||
|
name?: string;
|
||||||
|
source?: string;
|
||||||
|
type?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface RawPrettyDisplayIDGenerationBatch {
|
interface RawPrettyDisplayIDGenerationBatch {
|
||||||
@ -572,6 +586,37 @@ function normalizeAppUserBanOperator(item?: RawAppUserBanOperator | null) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizePrettyDisplayIDOperator(item?: RawPrettyDisplayIDOperator | null, fallbackAdminId?: EntityId) {
|
||||||
|
if (!item) {
|
||||||
|
const adminId = stringValue(fallbackAdminId);
|
||||||
|
return adminId
|
||||||
|
? {
|
||||||
|
adminId,
|
||||||
|
name: "后台管理员",
|
||||||
|
type: "admin",
|
||||||
|
}
|
||||||
|
: undefined;
|
||||||
|
}
|
||||||
|
const user = normalizeAppUserBrief(item);
|
||||||
|
const adminId = stringValue(item.adminId ?? item.admin_id ?? fallbackAdminId);
|
||||||
|
return {
|
||||||
|
...user,
|
||||||
|
account: stringValue(item.account),
|
||||||
|
adminId,
|
||||||
|
name: stringValue(item.name),
|
||||||
|
source: stringValue(item.source),
|
||||||
|
type: stringValue(item.type || (adminId ? "admin" : "app_user")),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeOptionalAppUserBrief(item?: RawAppUserBrief | null) {
|
||||||
|
if (!item) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const user = normalizeAppUserBrief(item);
|
||||||
|
return user.userId || user.displayUserId || user.username ? user : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
function normalizeAppUserBanRecord(item: RawAppUserBanRecord = {}): AppUserBanRecordDto {
|
function normalizeAppUserBanRecord(item: RawAppUserBanRecord = {}): AppUserBanRecordDto {
|
||||||
return {
|
return {
|
||||||
createdAtMs: numberValue(item.createdAtMs ?? item.created_at_ms),
|
createdAtMs: numberValue(item.createdAtMs ?? item.created_at_ms),
|
||||||
@ -640,15 +685,19 @@ function numberValue(value: unknown): number {
|
|||||||
|
|
||||||
// 靓号明细包含池号和后台发放两类来源,pool 可能为空,归一化后页面只判断 source/status 即可。
|
// 靓号明细包含池号和后台发放两类来源,pool 可能为空,归一化后页面只判断 source/status 即可。
|
||||||
function normalizePrettyDisplayID(item: RawPrettyDisplayID): PrettyDisplayIDDto {
|
function normalizePrettyDisplayID(item: RawPrettyDisplayID): PrettyDisplayIDDto {
|
||||||
|
const createdByAdminId = item.createdByAdminId ?? item.created_by_admin_id ?? "";
|
||||||
|
const updatedByAdminId = item.updatedByAdminId ?? item.updated_by_admin_id ?? "";
|
||||||
return {
|
return {
|
||||||
appCode: item.appCode ?? item.app_code ?? "",
|
appCode: item.appCode ?? item.app_code ?? "",
|
||||||
assignedAtMs: item.assignedAtMs ?? item.assigned_at_ms ?? 0,
|
assignedAtMs: item.assignedAtMs ?? item.assigned_at_ms ?? 0,
|
||||||
assignedLeaseId: item.assignedLeaseId ?? item.assigned_lease_id ?? "",
|
assignedLeaseId: item.assignedLeaseId ?? item.assigned_lease_id ?? "",
|
||||||
|
assignedUser: normalizeOptionalAppUserBrief(item.assignedUser ?? item.assigned_user),
|
||||||
assignedUserId: item.assignedUserId ?? item.assigned_user_id ?? "",
|
assignedUserId: item.assignedUserId ?? item.assigned_user_id ?? "",
|
||||||
createdAtMs: item.createdAtMs ?? item.created_at_ms ?? 0,
|
createdAtMs: item.createdAtMs ?? item.created_at_ms ?? 0,
|
||||||
createdByAdminId: item.createdByAdminId ?? item.created_by_admin_id ?? "",
|
createdByAdminId,
|
||||||
displayUserId: item.displayUserId ?? item.display_user_id ?? "",
|
displayUserId: item.displayUserId ?? item.display_user_id ?? "",
|
||||||
generatedBatchId: item.generatedBatchId ?? item.generated_batch_id ?? "",
|
generatedBatchId: item.generatedBatchId ?? item.generated_batch_id ?? "",
|
||||||
|
operator: normalizePrettyDisplayIDOperator(item.operator, updatedByAdminId || createdByAdminId),
|
||||||
pool: item.pool ? normalizePrettyDisplayIDPool(item.pool) : null,
|
pool: item.pool ? normalizePrettyDisplayIDPool(item.pool) : null,
|
||||||
poolId: item.poolId ?? item.pool_id ?? "",
|
poolId: item.poolId ?? item.pool_id ?? "",
|
||||||
prettyId: item.prettyId ?? item.pretty_id ?? "",
|
prettyId: item.prettyId ?? item.pretty_id ?? "",
|
||||||
@ -657,6 +706,7 @@ function normalizePrettyDisplayID(item: RawPrettyDisplayID): PrettyDisplayIDDto
|
|||||||
source: item.source ?? "",
|
source: item.source ?? "",
|
||||||
status: item.status ?? "",
|
status: item.status ?? "",
|
||||||
updatedAtMs: item.updatedAtMs ?? item.updated_at_ms ?? 0,
|
updatedAtMs: item.updatedAtMs ?? item.updated_at_ms ?? 0,
|
||||||
|
updatedByAdminId,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -34,6 +34,39 @@
|
|||||||
gap: 0;
|
gap: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.prettyTabsBar {
|
||||||
|
display: flex;
|
||||||
|
min-height: 48px;
|
||||||
|
align-items: center;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
padding: 0 var(--space-4);
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
background: var(--bg-card);
|
||||||
|
}
|
||||||
|
|
||||||
|
.prettyTabsBar :global(.MuiTabs-indicator) {
|
||||||
|
height: 3px;
|
||||||
|
border-radius: 999px 999px 0 0;
|
||||||
|
background: var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.prettyTab.prettyTab {
|
||||||
|
min-height: 48px;
|
||||||
|
color: var(--text-tertiary);
|
||||||
|
font-size: var(--admin-font-size);
|
||||||
|
font-weight: 750;
|
||||||
|
letter-spacing: 0;
|
||||||
|
text-transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prettyTab.prettyTab:hover {
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.prettyTab.prettyTab:global(.Mui-selected) {
|
||||||
|
color: var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
.prettyWorkspace {
|
.prettyWorkspace {
|
||||||
display: grid;
|
display: grid;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
@ -637,6 +670,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 760px) {
|
@media (max-width: 760px) {
|
||||||
|
.prettyTabsBar {
|
||||||
|
padding: 0 var(--space-3);
|
||||||
|
}
|
||||||
|
|
||||||
.prettyWorkspace {
|
.prettyWorkspace {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -46,6 +46,7 @@ export function usePrettyIdsPage() {
|
|||||||
const [idSource, setIdSource] = useState("");
|
const [idSource, setIdSource] = useState("");
|
||||||
const [idPoolId, setIdPoolId] = useState("");
|
const [idPoolId, setIdPoolId] = useState("");
|
||||||
const [assignedUserId, setAssignedUserId] = useState("");
|
const [assignedUserId, setAssignedUserId] = useState("");
|
||||||
|
const [activeTab, setActiveTab] = useState("ids");
|
||||||
const [activeDialog, setActiveDialog] = useState("");
|
const [activeDialog, setActiveDialog] = useState("");
|
||||||
const [activePool, setActivePool] = useState(null);
|
const [activePool, setActivePool] = useState(null);
|
||||||
const [activePrettyID, setActivePrettyID] = useState(null);
|
const [activePrettyID, setActivePrettyID] = useState(null);
|
||||||
@ -299,6 +300,7 @@ export function usePrettyIdsPage() {
|
|||||||
activeDialog,
|
activeDialog,
|
||||||
activePool,
|
activePool,
|
||||||
activePrettyID,
|
activePrettyID,
|
||||||
|
activeTab,
|
||||||
assignedUserId,
|
assignedUserId,
|
||||||
changeAssignedUserId,
|
changeAssignedUserId,
|
||||||
changeIDKeyword,
|
changeIDKeyword,
|
||||||
@ -331,6 +333,7 @@ export function usePrettyIdsPage() {
|
|||||||
reloadAll,
|
reloadAll,
|
||||||
resetIDFilters,
|
resetIDFilters,
|
||||||
resetPoolFilters,
|
resetPoolFilters,
|
||||||
|
setActiveTab,
|
||||||
setGenerateForm,
|
setGenerateForm,
|
||||||
setGrantForm,
|
setGrantForm,
|
||||||
setIdPage,
|
setIdPage,
|
||||||
|
|||||||
@ -5,14 +5,16 @@ import RestartAltOutlined from "@mui/icons-material/RestartAltOutlined";
|
|||||||
import SendOutlined from "@mui/icons-material/SendOutlined";
|
import SendOutlined from "@mui/icons-material/SendOutlined";
|
||||||
import SettingsOutlined from "@mui/icons-material/SettingsOutlined";
|
import SettingsOutlined from "@mui/icons-material/SettingsOutlined";
|
||||||
import MenuItem from "@mui/material/MenuItem";
|
import MenuItem from "@mui/material/MenuItem";
|
||||||
|
import Tab from "@mui/material/Tab";
|
||||||
|
import Tabs from "@mui/material/Tabs";
|
||||||
import TextField from "@mui/material/TextField";
|
import TextField from "@mui/material/TextField";
|
||||||
import Tooltip from "@mui/material/Tooltip";
|
import Tooltip from "@mui/material/Tooltip";
|
||||||
import { AdminFormDialog, AdminFormSection } from "@/shared/ui/AdminFormDialog.jsx";
|
import { AdminFormDialog, AdminFormSection } from "@/shared/ui/AdminFormDialog.jsx";
|
||||||
|
import { AdminUserIdentity } from "@/shared/ui/AdminUserIdentity.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";
|
||||||
import { DataTable } from "@/shared/ui/DataTable.jsx";
|
import { DataTable } from "@/shared/ui/DataTable.jsx";
|
||||||
import { IconButton } from "@/shared/ui/IconButton.jsx";
|
import { IconButton } from "@/shared/ui/IconButton.jsx";
|
||||||
import { useAppUserDetail } from "@/features/app-users/components/AppUserDetailContext.jsx";
|
|
||||||
import { formatMillis } from "@/shared/utils/time.js";
|
import { formatMillis } from "@/shared/utils/time.js";
|
||||||
import {
|
import {
|
||||||
prettyIdSourceLabels,
|
prettyIdSourceLabels,
|
||||||
@ -31,199 +33,22 @@ export function AppUserPrettyIdsPage() {
|
|||||||
const page = usePrettyIdsPage();
|
const page = usePrettyIdsPage();
|
||||||
const pools = page.poolQuery.data.items || [];
|
const pools = page.poolQuery.data.items || [];
|
||||||
const prettyIds = page.idQuery.data.items || [];
|
const prettyIds = page.idQuery.data.items || [];
|
||||||
|
const showingPools = page.activeTab === "pools";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className={styles.root}>
|
<section className={styles.root}>
|
||||||
<div className={`${styles.contentPanel} ${styles.prettyContentPanel}`}>
|
<div className={`${styles.contentPanel} ${styles.prettyContentPanel}`}>
|
||||||
<div className={styles.prettyWorkspace}>
|
<div className={styles.prettyTabsBar}>
|
||||||
<section className={`${styles.prettyPane} ${styles.prettyPoolPane}`}>
|
<Tabs value={page.activeTab} onChange={(_, value) => page.setActiveTab(value)}>
|
||||||
<PaneHeader
|
<Tab className={styles.prettyTab} label={`靓号列表 ${Number(page.idQuery.data.total || 0)}`} value="ids" />
|
||||||
actions={
|
<Tab className={styles.prettyTab} label={`靓号池 ${Number(page.poolQuery.data.total || 0)}`} value="pools" />
|
||||||
<>
|
</Tabs>
|
||||||
{page.abilities.canPrettyGenerate ? (
|
|
||||||
<Button
|
|
||||||
startIcon={<AutoAwesomeOutlined fontSize="small" />}
|
|
||||||
onClick={() => page.openGenerate(null)}
|
|
||||||
>
|
|
||||||
批量生成
|
|
||||||
</Button>
|
|
||||||
) : null}
|
|
||||||
{page.abilities.canPrettyUpdate ? (
|
|
||||||
<Button
|
|
||||||
startIcon={<AddCircleOutlineOutlined fontSize="small" />}
|
|
||||||
variant="primary"
|
|
||||||
onClick={page.openCreatePool}
|
|
||||||
>
|
|
||||||
新增池
|
|
||||||
</Button>
|
|
||||||
) : null}
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
count={page.poolQuery.data.total}
|
|
||||||
title="靓号池"
|
|
||||||
/>
|
|
||||||
<div className={styles.prettyFilters}>
|
|
||||||
<TextField
|
|
||||||
className={styles.compactControl}
|
|
||||||
label="等级轨道"
|
|
||||||
select
|
|
||||||
size="small"
|
|
||||||
value={page.poolTrack}
|
|
||||||
onChange={(event) => page.changePoolTrack(event.target.value)}
|
|
||||||
>
|
|
||||||
<MenuItem value="">全部等级轨道</MenuItem>
|
|
||||||
{prettyLevelTrackOptions.map(([value, label]) => (
|
|
||||||
<MenuItem key={value} value={value}>
|
|
||||||
{label}
|
|
||||||
</MenuItem>
|
|
||||||
))}
|
|
||||||
</TextField>
|
|
||||||
<TextField
|
|
||||||
className={styles.compactControl}
|
|
||||||
label="状态"
|
|
||||||
select
|
|
||||||
size="small"
|
|
||||||
value={page.poolStatus}
|
|
||||||
onChange={(event) => page.changePoolStatus(event.target.value)}
|
|
||||||
>
|
|
||||||
{prettyPoolStatusOptions.map(([value, label]) => (
|
|
||||||
<MenuItem key={value || "all"} value={value}>
|
|
||||||
{label}
|
|
||||||
</MenuItem>
|
|
||||||
))}
|
|
||||||
</TextField>
|
|
||||||
<Button startIcon={<RestartAltOutlined fontSize="small" />} onClick={page.resetPoolFilters}>
|
|
||||||
重置
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<DataState
|
|
||||||
error={page.poolQuery.error}
|
|
||||||
loading={page.poolQuery.loading}
|
|
||||||
onRetry={page.poolQuery.reload}
|
|
||||||
>
|
|
||||||
<DataTable
|
|
||||||
className={styles.compactTableFrame}
|
|
||||||
columns={poolColumns(page)}
|
|
||||||
items={pools}
|
|
||||||
minWidth="720px"
|
|
||||||
pagination={
|
|
||||||
page.poolQuery.data.total > 0
|
|
||||||
? {
|
|
||||||
page: page.poolPage,
|
|
||||||
pageSize: page.poolQuery.data.pageSize || 20,
|
|
||||||
total: page.poolQuery.data.total,
|
|
||||||
onPageChange: page.setPoolPage,
|
|
||||||
}
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
rowKey={(pool) => pool.poolId}
|
|
||||||
/>
|
|
||||||
</DataState>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section className={`${styles.prettyPane} ${styles.prettyIDPane}`}>
|
|
||||||
<PaneHeader
|
|
||||||
actions={
|
|
||||||
page.abilities.canPrettyGrant ? (
|
|
||||||
<Button
|
|
||||||
startIcon={<SendOutlined fontSize="small" />}
|
|
||||||
variant="primary"
|
|
||||||
onClick={page.openGrant}
|
|
||||||
>
|
|
||||||
后台发放
|
|
||||||
</Button>
|
|
||||||
) : null
|
|
||||||
}
|
|
||||||
count={page.idQuery.data.total}
|
|
||||||
title="靓号列表"
|
|
||||||
/>
|
|
||||||
<div className={styles.prettyFilters}>
|
|
||||||
<TextField
|
|
||||||
className={styles.search}
|
|
||||||
label="搜索"
|
|
||||||
size="small"
|
|
||||||
value={page.idKeyword}
|
|
||||||
onChange={(event) => page.changeIDKeyword(event.target.value)}
|
|
||||||
/>
|
|
||||||
<TextField
|
|
||||||
className={styles.compactControl}
|
|
||||||
label="靓号池"
|
|
||||||
select
|
|
||||||
size="small"
|
|
||||||
value={page.idPoolId}
|
|
||||||
onChange={(event) => page.changeIDPool(event.target.value)}
|
|
||||||
>
|
|
||||||
<MenuItem value="">全部靓号池</MenuItem>
|
|
||||||
{page.poolOptions.map((option) => (
|
|
||||||
<MenuItem key={option.value} value={option.value}>
|
|
||||||
{option.label}
|
|
||||||
</MenuItem>
|
|
||||||
))}
|
|
||||||
</TextField>
|
|
||||||
<TextField
|
|
||||||
className={styles.compactControl}
|
|
||||||
label="来源"
|
|
||||||
select
|
|
||||||
size="small"
|
|
||||||
value={page.idSource}
|
|
||||||
onChange={(event) => page.changeIDSource(event.target.value)}
|
|
||||||
>
|
|
||||||
{prettyIdSourceOptions.map(([value, label]) => (
|
|
||||||
<MenuItem key={value || "all"} value={value}>
|
|
||||||
{label}
|
|
||||||
</MenuItem>
|
|
||||||
))}
|
|
||||||
</TextField>
|
|
||||||
<TextField
|
|
||||||
className={styles.compactControl}
|
|
||||||
label="状态"
|
|
||||||
select
|
|
||||||
size="small"
|
|
||||||
value={page.idStatus}
|
|
||||||
onChange={(event) => page.changeIDStatus(event.target.value)}
|
|
||||||
>
|
|
||||||
{prettyIdStatusOptions.map(([value, label]) => (
|
|
||||||
<MenuItem key={value || "all"} value={value}>
|
|
||||||
{label}
|
|
||||||
</MenuItem>
|
|
||||||
))}
|
|
||||||
</TextField>
|
|
||||||
<TextField
|
|
||||||
className={styles.userIDControl}
|
|
||||||
label="占用用户"
|
|
||||||
size="small"
|
|
||||||
value={page.assignedUserId}
|
|
||||||
onChange={(event) => page.changeAssignedUserId(event.target.value)}
|
|
||||||
/>
|
|
||||||
<Button startIcon={<RestartAltOutlined fontSize="small" />} onClick={page.resetIDFilters}>
|
|
||||||
重置
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<DataState
|
|
||||||
error={page.idQuery.error}
|
|
||||||
loading={page.idQuery.loading}
|
|
||||||
onRetry={page.idQuery.reload}
|
|
||||||
>
|
|
||||||
<DataTable
|
|
||||||
className={styles.compactTableFrame}
|
|
||||||
columns={idColumns(page)}
|
|
||||||
items={prettyIds}
|
|
||||||
minWidth="980px"
|
|
||||||
pagination={
|
|
||||||
page.idQuery.data.total > 0
|
|
||||||
? {
|
|
||||||
page: page.idPage,
|
|
||||||
pageSize: page.idQuery.data.pageSize || 20,
|
|
||||||
total: page.idQuery.data.total,
|
|
||||||
onPageChange: page.setIdPage,
|
|
||||||
}
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
rowKey={(item) => item.prettyId}
|
|
||||||
/>
|
|
||||||
</DataState>
|
|
||||||
</section>
|
|
||||||
</div>
|
</div>
|
||||||
|
{showingPools ? (
|
||||||
|
<PrettyPoolPanel page={page} pools={pools} />
|
||||||
|
) : (
|
||||||
|
<PrettyIDPanel page={page} prettyIds={prettyIds} />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<PoolDialog page={page} />
|
<PoolDialog page={page} />
|
||||||
@ -234,6 +59,190 @@ export function AppUserPrettyIdsPage() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function PrettyPoolPanel({ page, pools }) {
|
||||||
|
return (
|
||||||
|
<section className={styles.prettyPane}>
|
||||||
|
<PaneHeader
|
||||||
|
actions={
|
||||||
|
<>
|
||||||
|
{page.abilities.canPrettyGenerate ? (
|
||||||
|
<Button
|
||||||
|
startIcon={<AutoAwesomeOutlined fontSize="small" />}
|
||||||
|
onClick={() => page.openGenerate(null)}
|
||||||
|
>
|
||||||
|
批量生成
|
||||||
|
</Button>
|
||||||
|
) : null}
|
||||||
|
{page.abilities.canPrettyUpdate ? (
|
||||||
|
<Button
|
||||||
|
startIcon={<AddCircleOutlineOutlined fontSize="small" />}
|
||||||
|
variant="primary"
|
||||||
|
onClick={page.openCreatePool}
|
||||||
|
>
|
||||||
|
新增池
|
||||||
|
</Button>
|
||||||
|
) : null}
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
count={page.poolQuery.data.total}
|
||||||
|
title="靓号池"
|
||||||
|
/>
|
||||||
|
<div className={styles.prettyFilters}>
|
||||||
|
<TextField
|
||||||
|
className={styles.compactControl}
|
||||||
|
label="等级轨道"
|
||||||
|
select
|
||||||
|
size="small"
|
||||||
|
value={page.poolTrack}
|
||||||
|
onChange={(event) => page.changePoolTrack(event.target.value)}
|
||||||
|
>
|
||||||
|
<MenuItem value="">全部等级轨道</MenuItem>
|
||||||
|
{prettyLevelTrackOptions.map(([value, label]) => (
|
||||||
|
<MenuItem key={value} value={value}>
|
||||||
|
{label}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</TextField>
|
||||||
|
<TextField
|
||||||
|
className={styles.compactControl}
|
||||||
|
label="状态"
|
||||||
|
select
|
||||||
|
size="small"
|
||||||
|
value={page.poolStatus}
|
||||||
|
onChange={(event) => page.changePoolStatus(event.target.value)}
|
||||||
|
>
|
||||||
|
{prettyPoolStatusOptions.map(([value, label]) => (
|
||||||
|
<MenuItem key={value || "all"} value={value}>
|
||||||
|
{label}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</TextField>
|
||||||
|
<Button startIcon={<RestartAltOutlined fontSize="small" />} onClick={page.resetPoolFilters}>
|
||||||
|
重置
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<DataState error={page.poolQuery.error} loading={page.poolQuery.loading} onRetry={page.poolQuery.reload}>
|
||||||
|
<DataTable
|
||||||
|
className={styles.compactTableFrame}
|
||||||
|
columns={poolColumns(page)}
|
||||||
|
items={pools}
|
||||||
|
minWidth="860px"
|
||||||
|
pagination={
|
||||||
|
page.poolQuery.data.total > 0
|
||||||
|
? {
|
||||||
|
page: page.poolPage,
|
||||||
|
pageSize: page.poolQuery.data.pageSize || 20,
|
||||||
|
total: page.poolQuery.data.total,
|
||||||
|
onPageChange: page.setPoolPage,
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
rowKey={(pool) => pool.poolId}
|
||||||
|
/>
|
||||||
|
</DataState>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function PrettyIDPanel({ page, prettyIds }) {
|
||||||
|
return (
|
||||||
|
<section className={styles.prettyPane}>
|
||||||
|
<PaneHeader
|
||||||
|
actions={
|
||||||
|
page.abilities.canPrettyGrant ? (
|
||||||
|
<Button startIcon={<SendOutlined fontSize="small" />} variant="primary" onClick={page.openGrant}>
|
||||||
|
后台发放
|
||||||
|
</Button>
|
||||||
|
) : null
|
||||||
|
}
|
||||||
|
count={page.idQuery.data.total}
|
||||||
|
title="靓号列表"
|
||||||
|
/>
|
||||||
|
<div className={styles.prettyFilters}>
|
||||||
|
<TextField
|
||||||
|
className={styles.search}
|
||||||
|
label="搜索"
|
||||||
|
size="small"
|
||||||
|
value={page.idKeyword}
|
||||||
|
onChange={(event) => page.changeIDKeyword(event.target.value)}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
className={styles.compactControl}
|
||||||
|
label="靓号池"
|
||||||
|
select
|
||||||
|
size="small"
|
||||||
|
value={page.idPoolId}
|
||||||
|
onChange={(event) => page.changeIDPool(event.target.value)}
|
||||||
|
>
|
||||||
|
<MenuItem value="">全部靓号池</MenuItem>
|
||||||
|
{page.poolOptions.map((option) => (
|
||||||
|
<MenuItem key={option.value} value={option.value}>
|
||||||
|
{option.label}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</TextField>
|
||||||
|
<TextField
|
||||||
|
className={styles.compactControl}
|
||||||
|
label="来源"
|
||||||
|
select
|
||||||
|
size="small"
|
||||||
|
value={page.idSource}
|
||||||
|
onChange={(event) => page.changeIDSource(event.target.value)}
|
||||||
|
>
|
||||||
|
{prettyIdSourceOptions.map(([value, label]) => (
|
||||||
|
<MenuItem key={value || "all"} value={value}>
|
||||||
|
{label}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</TextField>
|
||||||
|
<TextField
|
||||||
|
className={styles.compactControl}
|
||||||
|
label="状态"
|
||||||
|
select
|
||||||
|
size="small"
|
||||||
|
value={page.idStatus}
|
||||||
|
onChange={(event) => page.changeIDStatus(event.target.value)}
|
||||||
|
>
|
||||||
|
{prettyIdStatusOptions.map(([value, label]) => (
|
||||||
|
<MenuItem key={value || "all"} value={value}>
|
||||||
|
{label}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</TextField>
|
||||||
|
<TextField
|
||||||
|
className={styles.userIDControl}
|
||||||
|
label="占用用户"
|
||||||
|
size="small"
|
||||||
|
value={page.assignedUserId}
|
||||||
|
onChange={(event) => page.changeAssignedUserId(event.target.value)}
|
||||||
|
/>
|
||||||
|
<Button startIcon={<RestartAltOutlined fontSize="small" />} onClick={page.resetIDFilters}>
|
||||||
|
重置
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<DataState error={page.idQuery.error} loading={page.idQuery.loading} onRetry={page.idQuery.reload}>
|
||||||
|
<DataTable
|
||||||
|
className={styles.compactTableFrame}
|
||||||
|
columns={idColumns(page)}
|
||||||
|
items={prettyIds}
|
||||||
|
minWidth="1360px"
|
||||||
|
pagination={
|
||||||
|
page.idQuery.data.total > 0
|
||||||
|
? {
|
||||||
|
page: page.idPage,
|
||||||
|
pageSize: page.idQuery.data.pageSize || 20,
|
||||||
|
total: page.idQuery.data.total,
|
||||||
|
onPageChange: page.setIdPage,
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
rowKey={(item) => item.prettyId}
|
||||||
|
/>
|
||||||
|
</DataState>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function PaneHeader({ actions, count, title }) {
|
function PaneHeader({ actions, count, title }) {
|
||||||
return (
|
return (
|
||||||
<div className={styles.prettyPaneHeader}>
|
<div className={styles.prettyPaneHeader}>
|
||||||
@ -306,13 +315,12 @@ function idColumns(page) {
|
|||||||
{
|
{
|
||||||
key: "display",
|
key: "display",
|
||||||
label: "靓号",
|
label: "靓号",
|
||||||
width: "minmax(190px, 1fr)",
|
width: "minmax(130px, 0.7fr)",
|
||||||
render: (item) => (
|
render: (item) => (
|
||||||
<div className={styles.stack}>
|
<div className={styles.stack}>
|
||||||
<div className={styles.nameLine}>
|
<div className={styles.nameLine}>
|
||||||
<span className={styles.name}>{item.displayUserId || "-"}</span>
|
<span className={styles.name}>{item.displayUserId || "-"}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.meta}>{item.prettyId}</div>
|
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@ -334,21 +342,25 @@ function idColumns(page) {
|
|||||||
render: (item) => <StatusBadge labels={prettyIdStatusLabels} status={item.status} />,
|
render: (item) => <StatusBadge labels={prettyIdStatusLabels} status={item.status} />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "assigned",
|
key: "assignedUser",
|
||||||
label: "占用 / 时间",
|
label: "占用人",
|
||||||
width: "minmax(190px, 0.9fr)",
|
width: "minmax(240px, 1.2fr)",
|
||||||
render: (item) => (
|
render: (item) => (
|
||||||
<div className={styles.stack}>
|
<AssignedUserCell item={item} />
|
||||||
<AssignedUserCell item={item} />
|
|
||||||
<span className={styles.meta}>
|
|
||||||
{item.assignedAtMs ? formatMillis(item.assignedAtMs) : formatMillis(item.createdAtMs)}
|
|
||||||
</span>
|
|
||||||
<span className={styles.meta}>
|
|
||||||
{item.releasedAtMs ? `释放 ${formatMillis(item.releasedAtMs)}` : "-"}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: "operator",
|
||||||
|
label: "操作人",
|
||||||
|
width: "minmax(210px, 1fr)",
|
||||||
|
render: (item) => <OperatorCell item={item} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "time",
|
||||||
|
label: "占用 / 释放时间",
|
||||||
|
width: "minmax(210px, 1fr)",
|
||||||
|
render: (item) => <PrettyIDTimeCell item={item} />,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: "actions",
|
key: "actions",
|
||||||
label: "操作",
|
label: "操作",
|
||||||
@ -368,31 +380,51 @@ function idColumns(page) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function AssignedUserCell({ item }) {
|
function AssignedUserCell({ item }) {
|
||||||
const { openAppUserDetail } = useAppUserDetail();
|
|
||||||
const assignedUserId = formatEntityID(item.assignedUserId);
|
const assignedUserId = formatEntityID(item.assignedUserId);
|
||||||
if (!item.assignedUserId || !openAppUserDetail) {
|
if (!item.assignedUserId) {
|
||||||
return <span>{assignedUserId}</span>;
|
return <span className={styles.meta}>-</span>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return <AdminUserIdentity openInAppUserDetail rows={[assignedUserId]} user={assignedUser(item)} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function OperatorCell({ item }) {
|
||||||
|
const operator = item.operator || {};
|
||||||
|
const adminId = operator.adminId || item.updatedByAdminId || item.createdByAdminId;
|
||||||
|
if (!operator.userId && !operator.adminId && !operator.name && !operator.account && !adminId) {
|
||||||
|
return <span className={styles.meta}>-</span>;
|
||||||
|
}
|
||||||
|
if ((operator.type || "admin") === "app_user") {
|
||||||
|
return <AdminUserIdentity openInAppUserDetail rows={[operator.displayUserId, operator.userId]} user={operator} />;
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<button
|
<AdminUserIdentity
|
||||||
className={styles.countryButton}
|
name={operator.account || operator.name || "后台管理员"}
|
||||||
type="button"
|
rows={[operator.name, adminId ? `Admin ID ${adminId}` : "后台管理员"]}
|
||||||
onClick={(event) => {
|
user={{ ...operator, adminId }}
|
||||||
event.stopPropagation();
|
/>
|
||||||
openAppUserDetail({
|
|
||||||
displayUserId: assignedUserId,
|
|
||||||
prettyDisplayUserId: item.displayUserId,
|
|
||||||
prettyId: item.prettyId,
|
|
||||||
userId: item.assignedUserId,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{assignedUserId}
|
|
||||||
</button>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function PrettyIDTimeCell({ item }) {
|
||||||
|
return (
|
||||||
|
<div className={styles.stack}>
|
||||||
|
<span>{item.assignedAtMs ? formatMillis(item.assignedAtMs) : "-"}</span>
|
||||||
|
<span className={styles.meta}>{item.releasedAtMs ? `释放 ${formatMillis(item.releasedAtMs)}` : "-"}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function assignedUser(item) {
|
||||||
|
return {
|
||||||
|
...(item.assignedUser || {}),
|
||||||
|
displayUserId: item.assignedUser?.displayUserId || item.assignedUser?.defaultDisplayUserId || item.assignedUserId,
|
||||||
|
prettyDisplayUserId: item.displayUserId,
|
||||||
|
prettyId: item.prettyId,
|
||||||
|
userId: item.assignedUser?.userId || item.assignedUserId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function PoolDialog({ page }) {
|
function PoolDialog({ page }) {
|
||||||
const editing = Boolean(page.activePool);
|
const editing = Boolean(page.activePool);
|
||||||
return (
|
return (
|
||||||
|
|||||||
169
src/features/app-users/pages/AppUserPrettyIdsPage.test.jsx
Normal file
169
src/features/app-users/pages/AppUserPrettyIdsPage.test.jsx
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
import { render, screen } from "@testing-library/react";
|
||||||
|
import { afterEach, expect, test, vi } from "vitest";
|
||||||
|
import { AppUserPrettyIdsPage } from "./AppUserPrettyIdsPage.jsx";
|
||||||
|
import { usePrettyIdsPage } from "@/features/app-users/hooks/usePrettyIdsPage.js";
|
||||||
|
|
||||||
|
vi.mock("@/features/app-users/hooks/usePrettyIdsPage.js", () => ({
|
||||||
|
usePrettyIdsPage: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("pretty id page uses tabs and hides internal pretty record ids", () => {
|
||||||
|
vi.mocked(usePrettyIdsPage).mockReturnValue(pageFixture());
|
||||||
|
|
||||||
|
render(<AppUserPrettyIdsPage />);
|
||||||
|
|
||||||
|
expect(screen.getByRole("tab", { name: "靓号列表 1" })).toHaveAttribute("aria-selected", "true");
|
||||||
|
expect(screen.getByRole("tab", { name: "靓号池 1" })).toBeInTheDocument();
|
||||||
|
expect(screen.getAllByText("777777").length).toBeGreaterThan(0);
|
||||||
|
expect(screen.queryByText("pretty_1782492525506_0b70bb6c6dbc63cf")).not.toBeInTheDocument();
|
||||||
|
expect(screen.getByText("占用人 A")).toBeInTheDocument();
|
||||||
|
expect(screen.getByText("Admin ID 8")).toBeInTheDocument();
|
||||||
|
expect(screen.getByText("释放 2026-06-27 01:00:00")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("pretty id page can switch to pool tab", () => {
|
||||||
|
const page = pageFixture({ activeTab: "pools" });
|
||||||
|
vi.mocked(usePrettyIdsPage).mockReturnValue(page);
|
||||||
|
|
||||||
|
render(<AppUserPrettyIdsPage />);
|
||||||
|
|
||||||
|
expect(screen.getByRole("tab", { name: "靓号池 1" })).toHaveAttribute("aria-selected", "true");
|
||||||
|
expect(screen.getByText("≥LV.10")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
function pageFixture(patch = {}) {
|
||||||
|
return {
|
||||||
|
abilities: {
|
||||||
|
canPrettyGenerate: true,
|
||||||
|
canPrettyGrant: true,
|
||||||
|
canPrettyUpdate: true,
|
||||||
|
},
|
||||||
|
activeDialog: "",
|
||||||
|
activePool: null,
|
||||||
|
activePrettyID: null,
|
||||||
|
activeTab: "ids",
|
||||||
|
assignedUserId: "",
|
||||||
|
changeAssignedUserId: vi.fn(),
|
||||||
|
changeIDKeyword: vi.fn(),
|
||||||
|
changeIDPool: vi.fn(),
|
||||||
|
changeIDSource: vi.fn(),
|
||||||
|
changeIDStatus: vi.fn(),
|
||||||
|
changePoolStatus: vi.fn(),
|
||||||
|
changePoolTrack: vi.fn(),
|
||||||
|
closeDialog: vi.fn(),
|
||||||
|
generateForm: { count: 100, poolId: "", ruleConfigJson: "", ruleType: "aabbcc" },
|
||||||
|
grantForm: { displayUserId: "", durationDays: "", reason: "", targetUserId: "" },
|
||||||
|
idKeyword: "",
|
||||||
|
idPage: 1,
|
||||||
|
idPoolId: "",
|
||||||
|
idQuery: {
|
||||||
|
data: {
|
||||||
|
items: [prettyIdFixture()],
|
||||||
|
page: 1,
|
||||||
|
pageSize: 20,
|
||||||
|
total: 1,
|
||||||
|
},
|
||||||
|
error: null,
|
||||||
|
loading: false,
|
||||||
|
reload: vi.fn(),
|
||||||
|
},
|
||||||
|
idSource: "",
|
||||||
|
idStatus: "",
|
||||||
|
loadingAction: "",
|
||||||
|
openCreatePool: vi.fn(),
|
||||||
|
openEditPool: vi.fn(),
|
||||||
|
openGenerate: vi.fn(),
|
||||||
|
openGrant: vi.fn(),
|
||||||
|
openStatus: vi.fn(),
|
||||||
|
poolForm: {
|
||||||
|
levelTrack: "wealth",
|
||||||
|
maxLevel: 19,
|
||||||
|
minLevel: 10,
|
||||||
|
name: "",
|
||||||
|
ruleConfigJson: "",
|
||||||
|
ruleType: "aabbcc",
|
||||||
|
sortOrder: 0,
|
||||||
|
status: "active",
|
||||||
|
},
|
||||||
|
poolOptions: [{ label: "≥LV.10 · 10-19", value: "pool-1" }],
|
||||||
|
poolPage: 1,
|
||||||
|
poolQuery: {
|
||||||
|
data: {
|
||||||
|
items: [poolFixture()],
|
||||||
|
page: 1,
|
||||||
|
pageSize: 20,
|
||||||
|
total: 1,
|
||||||
|
},
|
||||||
|
error: null,
|
||||||
|
loading: false,
|
||||||
|
reload: vi.fn(),
|
||||||
|
},
|
||||||
|
poolStatus: "",
|
||||||
|
poolTrack: "",
|
||||||
|
reloadAll: vi.fn(),
|
||||||
|
resetIDFilters: vi.fn(),
|
||||||
|
resetPoolFilters: vi.fn(),
|
||||||
|
setActiveTab: vi.fn(),
|
||||||
|
setGenerateForm: vi.fn(),
|
||||||
|
setGrantForm: vi.fn(),
|
||||||
|
setIdPage: vi.fn(),
|
||||||
|
setPoolForm: vi.fn(),
|
||||||
|
setPoolPage: vi.fn(),
|
||||||
|
setStatusForm: vi.fn(),
|
||||||
|
statusForm: { prettyId: "", reason: "", status: "available" },
|
||||||
|
submitGenerate: vi.fn(),
|
||||||
|
submitGrant: vi.fn(),
|
||||||
|
submitPool: vi.fn(),
|
||||||
|
submitStatus: vi.fn(),
|
||||||
|
...patch,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function poolFixture() {
|
||||||
|
return {
|
||||||
|
createdAtMs: 1781249886288,
|
||||||
|
levelTrack: "wealth",
|
||||||
|
maxLevel: 19,
|
||||||
|
minLevel: 10,
|
||||||
|
name: "≥LV.10",
|
||||||
|
poolId: "pretty_pool_1781249886288_301d1a449c51fbc0",
|
||||||
|
ruleType: "abccba",
|
||||||
|
status: "active",
|
||||||
|
updatedAtMs: 1781249886288,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function prettyIdFixture() {
|
||||||
|
return {
|
||||||
|
assignedAtMs: 1782499200000,
|
||||||
|
assignedUser: {
|
||||||
|
avatar: "",
|
||||||
|
defaultDisplayUserId: "10001",
|
||||||
|
displayUserId: "777777",
|
||||||
|
userId: "9001",
|
||||||
|
username: "占用人 A",
|
||||||
|
},
|
||||||
|
assignedUserId: "9001",
|
||||||
|
createdAtMs: 1782499100000,
|
||||||
|
createdByAdminId: "7",
|
||||||
|
displayUserId: "777777",
|
||||||
|
operator: {
|
||||||
|
account: "ops",
|
||||||
|
adminId: "8",
|
||||||
|
name: "运营",
|
||||||
|
type: "admin",
|
||||||
|
},
|
||||||
|
pool: poolFixture(),
|
||||||
|
poolId: "pool-1",
|
||||||
|
prettyId: "pretty_1782492525506_0b70bb6c6dbc63cf",
|
||||||
|
releasedAtMs: 1782493200000,
|
||||||
|
source: "admin_grant",
|
||||||
|
status: "assigned",
|
||||||
|
updatedAtMs: 1782499300000,
|
||||||
|
updatedByAdminId: "8",
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -933,11 +933,31 @@ export interface PrettyDisplayIDDto {
|
|||||||
appCode?: string;
|
appCode?: string;
|
||||||
assignedAtMs?: number;
|
assignedAtMs?: number;
|
||||||
assignedLeaseId?: string;
|
assignedLeaseId?: string;
|
||||||
|
assignedUser?: {
|
||||||
|
avatar?: string;
|
||||||
|
defaultDisplayUserId?: string;
|
||||||
|
displayUserId?: string;
|
||||||
|
prettyDisplayUserId?: string;
|
||||||
|
prettyId?: string;
|
||||||
|
userId?: EntityId;
|
||||||
|
username?: string;
|
||||||
|
};
|
||||||
assignedUserId?: EntityId;
|
assignedUserId?: EntityId;
|
||||||
createdAtMs?: number;
|
createdAtMs?: number;
|
||||||
createdByAdminId?: EntityId;
|
createdByAdminId?: EntityId;
|
||||||
displayUserId: string;
|
displayUserId: string;
|
||||||
generatedBatchId?: string;
|
generatedBatchId?: string;
|
||||||
|
operator?: {
|
||||||
|
account?: string;
|
||||||
|
adminId?: EntityId;
|
||||||
|
avatar?: string;
|
||||||
|
displayUserId?: string;
|
||||||
|
name?: string;
|
||||||
|
source?: string;
|
||||||
|
type?: string;
|
||||||
|
userId?: EntityId;
|
||||||
|
username?: string;
|
||||||
|
};
|
||||||
pool?: PrettyDisplayIDPoolDto | null;
|
pool?: PrettyDisplayIDPoolDto | null;
|
||||||
poolId?: string;
|
poolId?: string;
|
||||||
prettyId: string;
|
prettyId: string;
|
||||||
@ -946,6 +966,7 @@ export interface PrettyDisplayIDDto {
|
|||||||
source: string;
|
source: string;
|
||||||
status: string;
|
status: string;
|
||||||
updatedAtMs?: number;
|
updatedAtMs?: number;
|
||||||
|
updatedByAdminId?: EntityId;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PrettyDisplayIDGenerationBatchDto {
|
export interface PrettyDisplayIDGenerationBatchDto {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user