经理相关
This commit is contained in:
parent
ec252cec2c
commit
28051f2acd
@ -21,6 +21,7 @@ import {
|
|||||||
setCoinSellerStatus,
|
setCoinSellerStatus,
|
||||||
updateBDLeaderPositionAlias,
|
updateBDLeaderPositionAlias,
|
||||||
updateCountry,
|
updateCountry,
|
||||||
|
updateManager,
|
||||||
} from "./api";
|
} from "./api";
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@ -60,7 +61,8 @@ test("host org list APIs use generated admin paths and filters", async () => {
|
|||||||
region_id: 7,
|
region_id: 7,
|
||||||
status: "active",
|
status: "active",
|
||||||
});
|
});
|
||||||
await createManager({ contact: "+63", targetUserId: "1003" });
|
await createManager({ canBlockUser: false, contact: "+63", targetUserId: "1003" });
|
||||||
|
await updateManager("1003", { canBlockUser: false, canGrantVehicle: true, contact: "+63" });
|
||||||
await listHosts({ agency_id: 41, page: 1, page_size: 10, sort_by: "diamond", sort_direction: "desc" });
|
await listHosts({ agency_id: 41, page: 1, page_size: 10, sort_by: "diamond", sort_direction: "desc" });
|
||||||
await listCoinSellers({
|
await listCoinSellers({
|
||||||
page: 1,
|
page: 1,
|
||||||
@ -98,15 +100,16 @@ test("host org list APIs use generated admin paths and filters", async () => {
|
|||||||
const [agencyUrl] = vi.mocked(fetch).mock.calls[1];
|
const [agencyUrl] = vi.mocked(fetch).mock.calls[1];
|
||||||
const [managerUrl, managerInit] = vi.mocked(fetch).mock.calls[2];
|
const [managerUrl, managerInit] = vi.mocked(fetch).mock.calls[2];
|
||||||
const [managerCreateUrl, managerCreateInit] = vi.mocked(fetch).mock.calls[3];
|
const [managerCreateUrl, managerCreateInit] = vi.mocked(fetch).mock.calls[3];
|
||||||
const [hostUrl] = vi.mocked(fetch).mock.calls[4];
|
const [managerUpdateUrl, managerUpdateInit] = vi.mocked(fetch).mock.calls[4];
|
||||||
const [coinSellerUrl, coinSellerInit] = vi.mocked(fetch).mock.calls[5];
|
const [hostUrl] = vi.mocked(fetch).mock.calls[5];
|
||||||
const [bdLeaderCreateUrl, bdLeaderCreateInit] = vi.mocked(fetch).mock.calls[6];
|
const [coinSellerUrl, coinSellerInit] = vi.mocked(fetch).mock.calls[6];
|
||||||
const [bdLeaderAliasUrl, bdLeaderAliasInit] = vi.mocked(fetch).mock.calls[7];
|
const [bdLeaderCreateUrl, bdLeaderCreateInit] = vi.mocked(fetch).mock.calls[7];
|
||||||
const [coinSellerCreateUrl, coinSellerCreateInit] = vi.mocked(fetch).mock.calls[8];
|
const [bdLeaderAliasUrl, bdLeaderAliasInit] = vi.mocked(fetch).mock.calls[8];
|
||||||
const [coinSellerStatusUrl, coinSellerStatusInit] = vi.mocked(fetch).mock.calls[9];
|
const [coinSellerCreateUrl, coinSellerCreateInit] = vi.mocked(fetch).mock.calls[9];
|
||||||
const [coinSellerStockUrl, coinSellerStockInit] = vi.mocked(fetch).mock.calls[10];
|
const [coinSellerStatusUrl, coinSellerStatusInit] = vi.mocked(fetch).mock.calls[10];
|
||||||
const [coinSellerStockDebitUrl, coinSellerStockDebitInit] = vi.mocked(fetch).mock.calls[11];
|
const [coinSellerStockUrl, coinSellerStockInit] = vi.mocked(fetch).mock.calls[11];
|
||||||
const [agencyDeleteUrl, agencyDeleteInit] = vi.mocked(fetch).mock.calls[12];
|
const [coinSellerStockDebitUrl, coinSellerStockDebitInit] = vi.mocked(fetch).mock.calls[12];
|
||||||
|
const [agencyDeleteUrl, agencyDeleteInit] = vi.mocked(fetch).mock.calls[13];
|
||||||
|
|
||||||
expect(String(bdUrl)).toContain("/api/v1/admin/bds?");
|
expect(String(bdUrl)).toContain("/api/v1/admin/bds?");
|
||||||
expect(String(bdUrl)).toContain("parent_leader_user_id=21");
|
expect(String(bdUrl)).toContain("parent_leader_user_id=21");
|
||||||
@ -127,7 +130,18 @@ test("host org list APIs use generated admin paths and filters", async () => {
|
|||||||
expect(managerInit?.method).toBe("GET");
|
expect(managerInit?.method).toBe("GET");
|
||||||
expect(String(managerCreateUrl)).toContain("/api/v1/admin/managers");
|
expect(String(managerCreateUrl)).toContain("/api/v1/admin/managers");
|
||||||
expect(managerCreateInit?.method).toBe("POST");
|
expect(managerCreateInit?.method).toBe("POST");
|
||||||
expect(JSON.parse(String(managerCreateInit?.body))).toEqual({ contact: "+63", targetUserId: "1003" });
|
expect(JSON.parse(String(managerCreateInit?.body))).toEqual({
|
||||||
|
canBlockUser: false,
|
||||||
|
contact: "+63",
|
||||||
|
targetUserId: "1003",
|
||||||
|
});
|
||||||
|
expect(String(managerUpdateUrl)).toContain("/api/v1/admin/managers/1003");
|
||||||
|
expect(managerUpdateInit?.method).toBe("PUT");
|
||||||
|
expect(JSON.parse(String(managerUpdateInit?.body))).toEqual({
|
||||||
|
canBlockUser: false,
|
||||||
|
canGrantVehicle: true,
|
||||||
|
contact: "+63",
|
||||||
|
});
|
||||||
expect(String(hostUrl)).toContain("/api/v1/admin/hosts?");
|
expect(String(hostUrl)).toContain("/api/v1/admin/hosts?");
|
||||||
expect(String(hostUrl)).toContain("agency_id=41");
|
expect(String(hostUrl)).toContain("agency_id=41");
|
||||||
expect(String(hostUrl)).toContain("sort_by=diamond");
|
expect(String(hostUrl)).toContain("sort_by=diamond");
|
||||||
|
|||||||
@ -38,6 +38,7 @@ import type {
|
|||||||
SalaryWalletAdjustPayload,
|
SalaryWalletAdjustPayload,
|
||||||
SalaryWalletAdjustResultDto,
|
SalaryWalletAdjustResultDto,
|
||||||
SalaryWalletHistoryDto,
|
SalaryWalletHistoryDto,
|
||||||
|
UpdateManagerPayload,
|
||||||
} from "@/shared/api/types";
|
} from "@/shared/api/types";
|
||||||
|
|
||||||
export function listCountries(query: PageQuery = {}): Promise<ApiList<CountryDto>> {
|
export function listCountries(query: PageQuery = {}): Promise<ApiList<CountryDto>> {
|
||||||
@ -186,6 +187,13 @@ export function createManager(payload: CreateManagerPayload): Promise<ManagerDto
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function updateManager(userId: EntityId, payload: UpdateManagerPayload): Promise<ManagerDto> {
|
||||||
|
return apiRequest<ManagerDto, UpdateManagerPayload>(`/v1/admin/managers/${encodeURIComponent(String(userId))}`, {
|
||||||
|
body: payload,
|
||||||
|
method: "PUT",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function listHosts(query: PageQuery = {}): Promise<ApiPage<HostProfileDto>> {
|
export function listHosts(query: PageQuery = {}): Promise<ApiPage<HostProfileDto>> {
|
||||||
const endpoint = API_ENDPOINTS.listHosts;
|
const endpoint = API_ENDPOINTS.listHosts;
|
||||||
return apiRequest<ApiPage<HostProfileDto>>(apiEndpointPath(API_OPERATIONS.listHosts), {
|
return apiRequest<ApiPage<HostProfileDto>>(apiEndpointPath(API_OPERATIONS.listHosts), {
|
||||||
@ -210,10 +218,13 @@ export function listSalaryWalletHistory(query: PageQuery = {}): Promise<ApiPage<
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function adjustSalaryWallet(payload: SalaryWalletAdjustPayload): Promise<SalaryWalletAdjustResultDto> {
|
export function adjustSalaryWallet(payload: SalaryWalletAdjustPayload): Promise<SalaryWalletAdjustResultDto> {
|
||||||
return apiRequest<SalaryWalletAdjustResultDto, SalaryWalletAdjustPayload>("/v1/admin/host-org/salary-wallets/adjust", {
|
return apiRequest<SalaryWalletAdjustResultDto, SalaryWalletAdjustPayload>(
|
||||||
body: payload,
|
"/v1/admin/host-org/salary-wallets/adjust",
|
||||||
method: "POST",
|
{
|
||||||
});
|
body: payload,
|
||||||
|
method: "POST",
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createBDLeader(payload: CreateBDLeaderPayload): Promise<BDProfileDto> {
|
export function createBDLeader(payload: CreateBDLeaderPayload): Promise<BDProfileDto> {
|
||||||
|
|||||||
@ -2,20 +2,42 @@ import { useMemo, useState } from "react";
|
|||||||
import { parseForm } from "@/shared/forms/validation";
|
import { parseForm } from "@/shared/forms/validation";
|
||||||
import { usePaginatedQuery } from "@/shared/hooks/usePaginatedQuery.js";
|
import { usePaginatedQuery } from "@/shared/hooks/usePaginatedQuery.js";
|
||||||
import { useRegionOptions } from "@/shared/hooks/useRegionOptions.js";
|
import { useRegionOptions } from "@/shared/hooks/useRegionOptions.js";
|
||||||
import { createManager, listManagers } from "@/features/host-org/api";
|
import { createManager, listManagers, updateManager } from "@/features/host-org/api";
|
||||||
import { useBDAbilities } from "@/features/host-org/permissions.js";
|
import { useBDAbilities } from "@/features/host-org/permissions.js";
|
||||||
import { createManagerSchema } from "@/features/host-org/schema";
|
import { createManagerSchema } from "@/features/host-org/schema";
|
||||||
import { useToast } from "@/shared/ui/ToastProvider.jsx";
|
import { useToast } from "@/shared/ui/ToastProvider.jsx";
|
||||||
|
|
||||||
const pageSize = 50;
|
const pageSize = 50;
|
||||||
const emptyData = { items: [], page: 1, pageSize, total: 0 };
|
const emptyData = { items: [], page: 1, pageSize, total: 0 };
|
||||||
const emptyManagerForm = () => ({ contact: "", targetUserId: "" });
|
export const managerPermissionFields = [
|
||||||
|
"canGrantAvatarFrame",
|
||||||
|
"canGrantVehicle",
|
||||||
|
"canUpdateUserLevel",
|
||||||
|
"canAddBdLeader",
|
||||||
|
"canAddAdmin",
|
||||||
|
"canAddSuperadmin",
|
||||||
|
"canBlockUser",
|
||||||
|
];
|
||||||
|
|
||||||
|
const emptyPermissions = () => Object.fromEntries(managerPermissionFields.map((field) => [field, true]));
|
||||||
|
|
||||||
|
const emptyManagerForm = () => ({ contact: "", targetUserId: "", ...emptyPermissions() });
|
||||||
|
|
||||||
|
const managerPermissionPayload = (form) =>
|
||||||
|
Object.fromEntries(managerPermissionFields.map((field) => [field, form[field] !== false]));
|
||||||
|
|
||||||
|
const managerFormFromItem = (item) => ({
|
||||||
|
contact: item?.contact || "",
|
||||||
|
targetUserId: String(item?.userId || ""),
|
||||||
|
...Object.fromEntries(managerPermissionFields.map((field) => [field, item?.[field] !== false])),
|
||||||
|
});
|
||||||
|
|
||||||
export function useHostManagersPage() {
|
export function useHostManagersPage() {
|
||||||
const abilities = useBDAbilities();
|
const abilities = useBDAbilities();
|
||||||
const { showToast } = useToast();
|
const { showToast } = useToast();
|
||||||
const { loadingRegions, regionOptions } = useRegionOptions();
|
const { loadingRegions, regionOptions } = useRegionOptions();
|
||||||
const [activeAction, setActiveAction] = useState("");
|
const [activeAction, setActiveAction] = useState("");
|
||||||
|
const [editingManager, setEditingManager] = useState(null);
|
||||||
const [loadingAction, setLoadingAction] = useState("");
|
const [loadingAction, setLoadingAction] = useState("");
|
||||||
const [managerForm, setManagerForm] = useState(emptyManagerForm);
|
const [managerForm, setManagerForm] = useState(emptyManagerForm);
|
||||||
const [query, setQuery] = useState("");
|
const [query, setQuery] = useState("");
|
||||||
@ -68,16 +90,30 @@ export function useHostManagersPage() {
|
|||||||
|
|
||||||
const submitManager = async (event) => {
|
const submitManager = async (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
setLoadingAction("manager-create");
|
const isEdit = activeAction === "manager-edit" && editingManager?.userId;
|
||||||
|
setLoadingAction(isEdit ? "manager-update" : "manager-create");
|
||||||
try {
|
try {
|
||||||
const payload = parseForm(createManagerSchema, managerForm);
|
const payload = parseForm(createManagerSchema, managerForm);
|
||||||
await createManager(payload);
|
if (isEdit) {
|
||||||
|
await updateManager(editingManager.userId, {
|
||||||
|
contact: payload.contact,
|
||||||
|
...managerPermissionPayload(payload),
|
||||||
|
});
|
||||||
|
showToast("经理权限已更新", "success");
|
||||||
|
} else {
|
||||||
|
await createManager({
|
||||||
|
contact: payload.contact,
|
||||||
|
targetUserId: payload.targetUserId,
|
||||||
|
...managerPermissionPayload(payload),
|
||||||
|
});
|
||||||
|
showToast("经理已添加", "success");
|
||||||
|
}
|
||||||
setManagerForm(emptyManagerForm());
|
setManagerForm(emptyManagerForm());
|
||||||
setActiveAction("");
|
setActiveAction("");
|
||||||
showToast("经理已添加", "success");
|
setEditingManager(null);
|
||||||
await reload();
|
await reload();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
showToast(err.message || "添加经理失败", "error");
|
showToast(err.message || (isEdit ? "更新经理权限失败" : "添加经理失败"), "error");
|
||||||
} finally {
|
} finally {
|
||||||
setLoadingAction("");
|
setLoadingAction("");
|
||||||
}
|
}
|
||||||
@ -85,9 +121,22 @@ export function useHostManagersPage() {
|
|||||||
|
|
||||||
const closeAction = () => {
|
const closeAction = () => {
|
||||||
setActiveAction("");
|
setActiveAction("");
|
||||||
|
setEditingManager(null);
|
||||||
setManagerForm(emptyManagerForm());
|
setManagerForm(emptyManagerForm());
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const openManagerForm = () => {
|
||||||
|
setEditingManager(null);
|
||||||
|
setManagerForm(emptyManagerForm());
|
||||||
|
setActiveAction("manager");
|
||||||
|
};
|
||||||
|
|
||||||
|
const openManagerEdit = (item) => {
|
||||||
|
setEditingManager(item);
|
||||||
|
setManagerForm(managerFormFromItem(item));
|
||||||
|
setActiveAction("manager-edit");
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
abilities,
|
abilities,
|
||||||
activeAction,
|
activeAction,
|
||||||
@ -96,12 +145,14 @@ export function useHostManagersPage() {
|
|||||||
changeStatus,
|
changeStatus,
|
||||||
closeAction,
|
closeAction,
|
||||||
data,
|
data,
|
||||||
|
editingManager,
|
||||||
error,
|
error,
|
||||||
loadingAction,
|
loadingAction,
|
||||||
loading,
|
loading,
|
||||||
loadingRegions,
|
loadingRegions,
|
||||||
managerForm,
|
managerForm,
|
||||||
openManagerForm: () => setActiveAction("manager"),
|
openManagerEdit,
|
||||||
|
openManagerForm,
|
||||||
page,
|
page,
|
||||||
query,
|
query,
|
||||||
regionId,
|
regionId,
|
||||||
|
|||||||
@ -104,6 +104,69 @@
|
|||||||
gap: var(--space-2);
|
gap: var(--space-2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.permissionGrid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
gap: var(--space-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.permissionSwitch {
|
||||||
|
display: flex;
|
||||||
|
min-width: 0;
|
||||||
|
min-height: 40px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: var(--space-3);
|
||||||
|
padding: 0 var(--space-3);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius-control);
|
||||||
|
background: var(--bg-input);
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-size: var(--admin-font-size);
|
||||||
|
font-weight: 650;
|
||||||
|
}
|
||||||
|
|
||||||
|
.permissionSwitch span {
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.permissionList {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.permissionPill {
|
||||||
|
display: inline-flex;
|
||||||
|
min-height: 24px;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
padding: 0 8px;
|
||||||
|
border: 1px solid var(--success-border);
|
||||||
|
border-radius: var(--radius-pill);
|
||||||
|
background: var(--success-surface);
|
||||||
|
color: var(--success);
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 720;
|
||||||
|
line-height: 1;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.permissionPill span {
|
||||||
|
color: inherit;
|
||||||
|
font-weight: 780;
|
||||||
|
}
|
||||||
|
|
||||||
|
.permissionPillOff {
|
||||||
|
border-color: var(--border);
|
||||||
|
background: var(--bg-input);
|
||||||
|
color: var(--text-tertiary);
|
||||||
|
}
|
||||||
|
|
||||||
.rateDrawerForm {
|
.rateDrawerForm {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
@ -1,7 +1,11 @@
|
|||||||
import Add from "@mui/icons-material/Add";
|
import Add from "@mui/icons-material/Add";
|
||||||
|
import EditOutlined from "@mui/icons-material/EditOutlined";
|
||||||
import TextField from "@mui/material/TextField";
|
import TextField from "@mui/material/TextField";
|
||||||
|
import Tooltip from "@mui/material/Tooltip";
|
||||||
import { formatMillis } from "@/shared/utils/time.js";
|
import { formatMillis } from "@/shared/utils/time.js";
|
||||||
|
import { AdminSwitch } from "@/shared/ui/AdminSwitch.jsx";
|
||||||
import { DataState } from "@/shared/ui/DataState.jsx";
|
import { DataState } from "@/shared/ui/DataState.jsx";
|
||||||
|
import { IconButton } from "@/shared/ui/IconButton.jsx";
|
||||||
import { managerStatusFilters, statusLabels } from "@/features/host-org/constants.js";
|
import { managerStatusFilters, statusLabels } from "@/features/host-org/constants.js";
|
||||||
import { HostOrgActionModal } from "@/features/host-org/components/HostOrgActionModal.jsx";
|
import { HostOrgActionModal } from "@/features/host-org/components/HostOrgActionModal.jsx";
|
||||||
import { HostOrgPerson, hostOrgRegionName } from "@/features/host-org/components/HostOrgIdentity.jsx";
|
import { HostOrgPerson, hostOrgRegionName } from "@/features/host-org/components/HostOrgIdentity.jsx";
|
||||||
@ -11,6 +15,16 @@ import { useHostManagersPage } from "@/features/host-org/hooks/useHostManagersPa
|
|||||||
import { createOptionsColumnFilter, createTextColumnFilter } from "@/shared/ui/tableFilters.js";
|
import { createOptionsColumnFilter, createTextColumnFilter } from "@/shared/ui/tableFilters.js";
|
||||||
import styles from "@/features/host-org/host-org.module.css";
|
import styles from "@/features/host-org/host-org.module.css";
|
||||||
|
|
||||||
|
const managerPermissionOptions = [
|
||||||
|
{ key: "canGrantAvatarFrame", label: "头像框" },
|
||||||
|
{ key: "canGrantVehicle", label: "Car" },
|
||||||
|
{ key: "canUpdateUserLevel", label: "升级等级" },
|
||||||
|
{ key: "canAddBdLeader", label: "BD Leader" },
|
||||||
|
{ key: "canAddAdmin", label: "Admin" },
|
||||||
|
{ key: "canAddSuperadmin", label: "Superadmin" },
|
||||||
|
{ key: "canBlockUser", label: "Block User" },
|
||||||
|
];
|
||||||
|
|
||||||
const managerColumns = [
|
const managerColumns = [
|
||||||
{
|
{
|
||||||
key: "userId",
|
key: "userId",
|
||||||
@ -30,6 +44,12 @@ const managerColumns = [
|
|||||||
render: (item) => item.contact || "-",
|
render: (item) => item.contact || "-",
|
||||||
width: "minmax(150px, 0.85fr)",
|
width: "minmax(150px, 0.85fr)",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: "permissions",
|
||||||
|
label: "权限详情",
|
||||||
|
render: (item) => <ManagerPermissions item={item} />,
|
||||||
|
width: "minmax(270px, 1.35fr)",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: "status",
|
key: "status",
|
||||||
label: "状态",
|
label: "状态",
|
||||||
@ -54,11 +74,19 @@ const managerColumns = [
|
|||||||
render: (item) => formatMillis(item.updatedAtMs),
|
render: (item) => formatMillis(item.updatedAtMs),
|
||||||
width: "minmax(170px, 1fr)",
|
width: "minmax(170px, 1fr)",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: "actions",
|
||||||
|
label: "操作",
|
||||||
|
render: (item, _index, context) => <ManagerActions item={item} page={context?.page} />,
|
||||||
|
resizable: false,
|
||||||
|
width: "88px",
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export function HostManagersPage() {
|
export function HostManagersPage() {
|
||||||
const page = useHostManagersPage();
|
const page = useHostManagersPage();
|
||||||
const createDisabled = !page.abilities.canCreate;
|
const isEditing = page.activeAction === "manager-edit";
|
||||||
|
const formDisabled = isEditing ? !page.abilities.canUpdate : !page.abilities.canCreate;
|
||||||
const items = page.data.items || [];
|
const items = page.data.items || [];
|
||||||
const total = page.data.total || 0;
|
const total = page.data.total || 0;
|
||||||
const toolbarActions = [
|
const toolbarActions = [
|
||||||
@ -101,7 +129,7 @@ export function HostManagersPage() {
|
|||||||
columns={columns}
|
columns={columns}
|
||||||
context={{ page, regionOptions: page.regionOptions }}
|
context={{ page, regionOptions: page.regionOptions }}
|
||||||
items={items}
|
items={items}
|
||||||
minWidth="1240px"
|
minWidth="1640px"
|
||||||
pagination={
|
pagination={
|
||||||
total > 0
|
total > 0
|
||||||
? {
|
? {
|
||||||
@ -118,16 +146,16 @@ export function HostManagersPage() {
|
|||||||
</DataState>
|
</DataState>
|
||||||
</div>
|
</div>
|
||||||
<HostOrgActionModal
|
<HostOrgActionModal
|
||||||
disabled={createDisabled}
|
disabled={formDisabled}
|
||||||
loading={page.loadingAction === "manager-create"}
|
loading={["manager-create", "manager-update"].includes(page.loadingAction)}
|
||||||
open={page.activeAction === "manager"}
|
open={page.activeAction === "manager" || page.activeAction === "manager-edit"}
|
||||||
sectionTitle="经理信息"
|
sectionTitle="经理信息"
|
||||||
onClose={page.closeAction}
|
onClose={page.closeAction}
|
||||||
onSubmit={page.submitManager}
|
onSubmit={page.submitManager}
|
||||||
title="添加经理"
|
title={isEditing ? "编辑经理权限" : "添加经理"}
|
||||||
>
|
>
|
||||||
<TextField
|
<TextField
|
||||||
disabled={createDisabled}
|
disabled={formDisabled || isEditing}
|
||||||
label="用户短 ID"
|
label="用户短 ID"
|
||||||
required
|
required
|
||||||
type="number"
|
type="number"
|
||||||
@ -135,11 +163,12 @@ export function HostManagersPage() {
|
|||||||
onChange={(event) => page.setManagerForm({ ...page.managerForm, targetUserId: event.target.value })}
|
onChange={(event) => page.setManagerForm({ ...page.managerForm, targetUserId: event.target.value })}
|
||||||
/>
|
/>
|
||||||
<TextField
|
<TextField
|
||||||
disabled={createDisabled}
|
disabled={formDisabled}
|
||||||
label="联系方式"
|
label="联系方式"
|
||||||
value={page.managerForm.contact}
|
value={page.managerForm.contact}
|
||||||
onChange={(event) => page.setManagerForm({ ...page.managerForm, contact: event.target.value })}
|
onChange={(event) => page.setManagerForm({ ...page.managerForm, contact: event.target.value })}
|
||||||
/>
|
/>
|
||||||
|
<PermissionSwitchGrid disabled={formDisabled} form={page.managerForm} onChange={page.setManagerForm} />
|
||||||
</HostOrgActionModal>
|
</HostOrgActionModal>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
@ -164,3 +193,59 @@ function ManagerStatus({ item }) {
|
|||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ManagerPermissions({ item }) {
|
||||||
|
return (
|
||||||
|
<div className={styles.permissionList}>
|
||||||
|
{managerPermissionOptions.map((permission) => {
|
||||||
|
const enabled = item?.[permission.key] !== false;
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
className={
|
||||||
|
enabled ? styles.permissionPill : `${styles.permissionPill} ${styles.permissionPillOff}`
|
||||||
|
}
|
||||||
|
key={permission.key}
|
||||||
|
>
|
||||||
|
{permission.label}
|
||||||
|
<span>{enabled ? "开" : "关"}</span>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ManagerActions({ item, page }) {
|
||||||
|
const disabled = !page?.abilities?.canUpdate;
|
||||||
|
return (
|
||||||
|
<div className={styles.rowActions}>
|
||||||
|
<Tooltip title={disabled ? "无编辑权限" : "编辑权限"}>
|
||||||
|
<span>
|
||||||
|
<IconButton disabled={disabled} label="编辑权限" onClick={() => page.openManagerEdit(item)}>
|
||||||
|
<EditOutlined fontSize="small" />
|
||||||
|
</IconButton>
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function PermissionSwitchGrid({ disabled, form, onChange }) {
|
||||||
|
return (
|
||||||
|
<div className={styles.permissionGrid}>
|
||||||
|
{managerPermissionOptions.map((permission) => (
|
||||||
|
<label className={styles.permissionSwitch} key={permission.key}>
|
||||||
|
<span>{permission.label}</span>
|
||||||
|
<AdminSwitch
|
||||||
|
checked={form?.[permission.key] !== false}
|
||||||
|
checkedLabel="开"
|
||||||
|
disabled={disabled}
|
||||||
|
label={permission.label}
|
||||||
|
uncheckedLabel="关"
|
||||||
|
onChange={(event) => onChange({ ...form, [permission.key]: event.target.checked })}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@ -18,7 +18,10 @@ const userIdSchema = z.preprocess(
|
|||||||
}
|
}
|
||||||
return String(value).trim();
|
return String(value).trim();
|
||||||
},
|
},
|
||||||
z.string().min(1, "请输入有效用户短 ID").regex(/^[1-9]\d*$/, "请输入有效用户短 ID"),
|
z
|
||||||
|
.string()
|
||||||
|
.min(1, "请输入有效用户短 ID")
|
||||||
|
.regex(/^[1-9]\d*$/, "请输入有效用户短 ID"),
|
||||||
);
|
);
|
||||||
const entityIdSchema = z.preprocess(
|
const entityIdSchema = z.preprocess(
|
||||||
(value) => {
|
(value) => {
|
||||||
@ -106,6 +109,13 @@ export const createCoinSellerSchema = commandContactSchema.extend({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const createManagerSchema = z.object({
|
export const createManagerSchema = z.object({
|
||||||
|
canAddAdmin: z.boolean().optional().default(true),
|
||||||
|
canAddBdLeader: z.boolean().optional().default(true),
|
||||||
|
canAddSuperadmin: z.boolean().optional().default(true),
|
||||||
|
canBlockUser: z.boolean().optional().default(true),
|
||||||
|
canGrantAvatarFrame: z.boolean().optional().default(true),
|
||||||
|
canGrantVehicle: z.boolean().optional().default(true),
|
||||||
|
canUpdateUserLevel: z.boolean().optional().default(true),
|
||||||
contact: z.string().trim().max(128, "联系方式不能超过 128 个字符").optional().default(""),
|
contact: z.string().trim().max(128, "联系方式不能超过 128 个字符").optional().default(""),
|
||||||
targetUserId: userIdSchema,
|
targetUserId: userIdSchema,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -561,6 +561,13 @@ export interface AgencyDto {
|
|||||||
export interface ManagerDto {
|
export interface ManagerDto {
|
||||||
avatar?: string;
|
avatar?: string;
|
||||||
bdLeaderCount?: number;
|
bdLeaderCount?: number;
|
||||||
|
canAddAdmin?: boolean;
|
||||||
|
canAddBdLeader?: boolean;
|
||||||
|
canAddSuperadmin?: boolean;
|
||||||
|
canBlockUser?: boolean;
|
||||||
|
canGrantAvatarFrame?: boolean;
|
||||||
|
canGrantVehicle?: boolean;
|
||||||
|
canUpdateUserLevel?: boolean;
|
||||||
contact?: string;
|
contact?: string;
|
||||||
createdAtMs?: number;
|
createdAtMs?: number;
|
||||||
displayUserId?: string;
|
displayUserId?: string;
|
||||||
@ -574,10 +581,28 @@ export interface ManagerDto {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface CreateManagerPayload {
|
export interface CreateManagerPayload {
|
||||||
|
canAddAdmin?: boolean;
|
||||||
|
canAddBdLeader?: boolean;
|
||||||
|
canAddSuperadmin?: boolean;
|
||||||
|
canBlockUser?: boolean;
|
||||||
|
canGrantAvatarFrame?: boolean;
|
||||||
|
canGrantVehicle?: boolean;
|
||||||
|
canUpdateUserLevel?: boolean;
|
||||||
contact?: string;
|
contact?: string;
|
||||||
targetUserId: string;
|
targetUserId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface UpdateManagerPayload {
|
||||||
|
canAddAdmin?: boolean;
|
||||||
|
canAddBdLeader?: boolean;
|
||||||
|
canAddSuperadmin?: boolean;
|
||||||
|
canBlockUser?: boolean;
|
||||||
|
canGrantAvatarFrame?: boolean;
|
||||||
|
canGrantVehicle?: boolean;
|
||||||
|
canUpdateUserLevel?: boolean;
|
||||||
|
contact?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface HostProfileDto {
|
export interface HostProfileDto {
|
||||||
avatar?: string;
|
avatar?: string;
|
||||||
createdAtMs?: number;
|
createdAtMs?: number;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user