Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ffad461d67 | |||
| f950bcd841 |
@@ -22,10 +22,6 @@ function genPassword(len = 10): string {
|
||||
|
||||
type View = 'list' | 'person' | 'roles' | 'roleForm';
|
||||
|
||||
// 「自定义」哨兵角色:不是共享角色,选它时可见页由本人逐页勾选(存 pages_override),与后端一致。
|
||||
const CUSTOM_ROLE = 'custom';
|
||||
const CUSTOM_LABEL = '自定义';
|
||||
|
||||
// ===== 只读权限矩阵:角色详情 / 人员「可见页面」预览共用(深色=有,灰色=无)=====
|
||||
function PermMatrix({ catalog, pages }: { catalog: PermissionGroup[]; pages: string[] }) {
|
||||
const has = useMemo(() => new Set(pages), [pages]);
|
||||
@@ -113,11 +109,6 @@ export default function AdminsPage() {
|
||||
return m;
|
||||
}, [roles]);
|
||||
const roleOptions = roles.map((r) => ({ value: r.name, label: r.label }));
|
||||
// 人员表单角色下拉:真实角色 + 末尾「自定义」。选「自定义」→ 下方可见页改为逐页勾选。
|
||||
const personRoleOptions = [...roleOptions, { value: CUSTOM_ROLE, label: CUSTOM_LABEL }];
|
||||
// 角色 key → 展示名(custom 显示「自定义」;查不到回退 key 原文)
|
||||
const roleLabelOf = (name: string) =>
|
||||
(name === CUSTOM_ROLE ? CUSTOM_LABEL : roleByName[name]?.label ?? name);
|
||||
|
||||
const loadAll = async () => {
|
||||
setLoading(true);
|
||||
@@ -143,14 +134,11 @@ export default function AdminsPage() {
|
||||
const [personName, setPersonName] = useState('');
|
||||
const [personRole, setPersonRole] = useState('');
|
||||
const [initPw, setInitPw] = useState('');
|
||||
// 「自定义」角色时逐页勾选的可见页集(role==custom 才用到)
|
||||
const [personPages, setPersonPages] = useState<string[]>([]);
|
||||
|
||||
const openCreatePerson = () => {
|
||||
setPersonEditing(null);
|
||||
setPersonName('');
|
||||
setPersonRole(roles.find((r) => !r.is_builtin)?.name ?? roles[0]?.name ?? 'operator');
|
||||
setPersonPages([]);
|
||||
setInitPw(genPassword());
|
||||
setView('person');
|
||||
};
|
||||
@@ -158,8 +146,6 @@ export default function AdminsPage() {
|
||||
setPersonEditing(a);
|
||||
setPersonName(a.username);
|
||||
setPersonRole(a.role);
|
||||
// 编辑「自定义」用户时,回填其已勾选的可见页(供继续增删)
|
||||
setPersonPages(a.role === CUSTOM_ROLE ? (a.pages_override ?? []) : []);
|
||||
setInitPw(''); // 编辑态密码只读展示 a.password(已确定的登录密码),不用 initPw
|
||||
setView('person');
|
||||
};
|
||||
@@ -174,30 +160,18 @@ export default function AdminsPage() {
|
||||
<Button onClick={() => copyText(value)}>复制</Button>
|
||||
</div>
|
||||
);
|
||||
const isCustom = personRole === CUSTOM_ROLE;
|
||||
const submitPerson = async () => {
|
||||
if (isCustom && personPages.length === 0) { message.error('自定义角色请至少勾选一个可见页面'); return; }
|
||||
try {
|
||||
if (personEditing) {
|
||||
// 密码只读、不改;提交变更的角色 +(自定义时)勾选的可见页
|
||||
const roleChanged = personRole !== personEditing.role;
|
||||
const pagesChanged = isCustom
|
||||
&& JSON.stringify(personPages) !== JSON.stringify(personEditing.pages_override ?? []);
|
||||
if (roleChanged || pagesChanged) {
|
||||
const payload: Record<string, unknown> = {};
|
||||
if (roleChanged) payload.role = personRole;
|
||||
// 自定义:带上勾选集(切到 custom 或改现有 custom 勾选都要);切回普通角色不带,后端会清空 override
|
||||
if (isCustom) payload.pages_override = personPages;
|
||||
await api.patch(`/admin/api/admins/${personEditing.id}`, payload);
|
||||
// 密码只读、不改;只提交变更的角色
|
||||
if (personRole !== personEditing.role) {
|
||||
await api.patch(`/admin/api/admins/${personEditing.id}`, { role: personRole });
|
||||
}
|
||||
message.success('已保存');
|
||||
} else {
|
||||
if (personName.trim().length < 3) { message.error('用户名至少 3 位'); return; }
|
||||
await api.post('/admin/api/admins', {
|
||||
username: personName.trim(),
|
||||
password: initPw,
|
||||
role: personRole,
|
||||
...(isCustom ? { pages_override: personPages } : {}),
|
||||
username: personName.trim(), password: initPw, role: personRole,
|
||||
});
|
||||
message.success('已创建,请把初始密码转交本人');
|
||||
}
|
||||
@@ -210,10 +184,8 @@ export default function AdminsPage() {
|
||||
// ===== 列表操作 =====
|
||||
const changeRole = (a: AdminInfo, role: string) => {
|
||||
if (role === a.role) return;
|
||||
// 改成「自定义」需要逐页勾选 → 转去编辑页(列表里没有页选择器,直接切会得到空可见页)
|
||||
if (role === CUSTOM_ROLE) { openEditPerson({ ...a, role: CUSTOM_ROLE }); return; }
|
||||
modal.confirm({
|
||||
title: `把 ${a.username} 的角色改为 ${roleLabelOf(role)}?`,
|
||||
title: `把 ${a.username} 的角色改为 ${role}?`,
|
||||
onOk: async () => {
|
||||
try { await api.patch(`/admin/api/admins/${a.id}`, { role }); message.success('已更新'); loadAll(); }
|
||||
catch (e) { message.error(errMsg(e)); }
|
||||
@@ -294,9 +266,8 @@ export default function AdminsPage() {
|
||||
{ title: '用户名', dataIndex: 'username' },
|
||||
{
|
||||
title: '角色', dataIndex: 'role', width: 170,
|
||||
// options 含「自定义」→ custom 用户在列表里显示为「自定义」;选它会转去编辑页勾选(见 changeRole)
|
||||
render: (r: string, a: AdminInfo) => (
|
||||
<Select size="small" value={r} style={{ width: 150 }} options={personRoleOptions} onChange={(v) => changeRole(a, v)} />
|
||||
<Select size="small" value={r} style={{ width: 150 }} options={roleOptions} onChange={(v) => changeRole(a, v)} />
|
||||
),
|
||||
},
|
||||
{ title: '状态', dataIndex: 'status', width: 90, render: (s: string) => <Tag color={s === 'active' ? 'green' : 'default'}>{s === 'active' ? 'active' : '已禁用'}</Tag> },
|
||||
@@ -370,26 +341,16 @@ export default function AdminsPage() {
|
||||
<span>选择角色<span style={{ color: '#cf1322' }}> *</span></span>
|
||||
{isSuper && <a onClick={openRoles}>设置角色权限 ›</a>}
|
||||
</div>
|
||||
<Select value={personRole} options={personRoleOptions} onChange={setPersonRole} style={{ width: '100%' }} />
|
||||
<div style={{ color: '#999', fontSize: 12, marginTop: 6 }}>
|
||||
{isCustom
|
||||
? '「自定义」:下方逐页勾选该成员可见的页面,仅对本人生效(不影响任何角色)'
|
||||
: '可见页面完全由所选角色决定;如需调整请到「角色设置」修改该角色'}
|
||||
</div>
|
||||
<Select value={personRole} options={roleOptions} onChange={setPersonRole} style={{ width: '100%' }} />
|
||||
<div style={{ color: '#999', fontSize: 12, marginTop: 6 }}>可见页面完全由所选角色决定;如需调整请到「角色设置」修改该角色</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 8 }}>
|
||||
<span>可见页面</span>
|
||||
<span style={{ color: '#999', fontSize: 12 }}>
|
||||
{isCustom ? '勾选本成员可见的页面' : '跟随所选角色,此处只读(深色=可见)'}
|
||||
</span>
|
||||
<span style={{ color: '#999', fontSize: 12 }}>跟随所选角色,此处只读(深色=可见)</span>
|
||||
</div>
|
||||
{isCustom ? (
|
||||
<PermEditor catalog={catalog} value={personPages} onChange={setPersonPages} />
|
||||
) : (
|
||||
<PermMatrix catalog={catalog} pages={personRole === 'super_admin' ? catalog.flatMap((g) => g.pages.map((p) => p.key)) : rolePagesPreview} />
|
||||
)}
|
||||
<PermMatrix catalog={catalog} pages={personRole === 'super_admin' ? catalog.flatMap((g) => g.pages.map((p) => p.key)) : rolePagesPreview} />
|
||||
</div>
|
||||
|
||||
<div style={{ marginTop: 24, textAlign: 'right' }}>
|
||||
|
||||
@@ -624,27 +624,12 @@ export default function WithdrawsPage() {
|
||||
const rejectAmount = rejectTargets.reduce((sum, item) => sum + item.amount_cents, 0);
|
||||
const ledgerIssues = ledger
|
||||
? [
|
||||
// 普通现金账(coin_cash)
|
||||
ledger.balance_diff_cents ? `现金余额差额 ${yuan(ledger.balance_diff_cents)}` : '',
|
||||
ledger.missing_withdraw_txn_count ? `现金缺扣款流水 ${ledger.missing_withdraw_txn_count} 笔` : '',
|
||||
ledger.missing_refund_txn_count ? `现金缺退款流水 ${ledger.missing_refund_txn_count} 笔` : '',
|
||||
ledger.duplicate_refund_txn_count ? `现金重复退款流水 ${ledger.duplicate_refund_txn_count} 类` : '',
|
||||
ledger.balance_diff_cents ? `余额差额 ${yuan(ledger.balance_diff_cents)}` : '',
|
||||
ledger.missing_withdraw_txn_count ? `缺扣款流水 ${ledger.missing_withdraw_txn_count} 笔` : '',
|
||||
ledger.missing_refund_txn_count ? `缺退款流水 ${ledger.missing_refund_txn_count} 笔` : '',
|
||||
ledger.duplicate_refund_txn_count ? `重复退款流水 ${ledger.duplicate_refund_txn_count} 类` : '',
|
||||
ledger.refund_txn_on_non_terminal_count
|
||||
? `现金非退款终态存在退款流水 ${ledger.refund_txn_on_non_terminal_count} 笔`
|
||||
: '',
|
||||
// 邀请奖励金账(invite_cash)
|
||||
ledger.invite_balance_diff_cents ? `邀请金余额差额 ${yuan(ledger.invite_balance_diff_cents)}` : '',
|
||||
ledger.invite_missing_withdraw_txn_count
|
||||
? `邀请金缺扣款流水 ${ledger.invite_missing_withdraw_txn_count} 笔`
|
||||
: '',
|
||||
ledger.invite_missing_refund_txn_count
|
||||
? `邀请金缺退款流水 ${ledger.invite_missing_refund_txn_count} 笔`
|
||||
: '',
|
||||
ledger.invite_duplicate_refund_txn_count
|
||||
? `邀请金重复退款流水 ${ledger.invite_duplicate_refund_txn_count} 类`
|
||||
: '',
|
||||
ledger.invite_refund_txn_on_non_terminal_count
|
||||
? `邀请金非退款终态存在退款流水 ${ledger.invite_refund_txn_on_non_terminal_count} 笔`
|
||||
? `非退款终态存在退款流水 ${ledger.refund_txn_on_non_terminal_count} 笔`
|
||||
: '',
|
||||
].filter(Boolean)
|
||||
: [];
|
||||
@@ -684,7 +669,7 @@ export default function WithdrawsPage() {
|
||||
message={ledger.ok ? '现金账本校验正常' : '现金账本存在异常'}
|
||||
description={
|
||||
ledger.ok
|
||||
? `现金 余额 ${yuan(ledger.cash_balance_total_cents)} / 流水 ${yuan(ledger.cash_transaction_total_cents)};邀请金 余额 ${yuan(ledger.invite_cash_balance_total_cents)} / 流水 ${yuan(ledger.invite_cash_transaction_total_cents)}`
|
||||
? `账户余额合计 ${yuan(ledger.cash_balance_total_cents)},现金流水净额 ${yuan(ledger.cash_transaction_total_cents)}`
|
||||
: ledgerIssues.join(';')
|
||||
}
|
||||
action={
|
||||
|
||||
+2
-12
@@ -3,12 +3,11 @@
|
||||
export interface AdminInfo {
|
||||
id: number;
|
||||
username: string;
|
||||
role: string; // super_admin / finance / operator / custom(自定义) / 自定义角色名
|
||||
role: string; // super_admin / finance / operator / 自定义角色名
|
||||
status: string;
|
||||
created_at: string;
|
||||
last_login_at: string | null;
|
||||
pages?: string[]; // 当前有效可见页 key(登录 / /me 下发);super_admin = 全部页;role==custom = pages_override。左侧导航按此过滤
|
||||
pages_override?: string[] | null; // 「自定义」勾选的可见页原始集(role==custom 时非空);列表接口下发,供编辑回填勾选
|
||||
pages?: string[]; // 当前角色有效可见页 key(登录 / /me 下发);super_admin = 全部页。左侧导航按此过滤
|
||||
password?: string | null; // 明文登录密码(仅账号列表下发);无留存(脚本建的超管)为 null → 不显示
|
||||
}
|
||||
|
||||
@@ -234,7 +233,6 @@ export interface WithdrawBulkResult {
|
||||
|
||||
export interface WithdrawLedgerCheck {
|
||||
ok: boolean;
|
||||
// 普通现金账(coin_cash)
|
||||
cash_balance_total_cents: number;
|
||||
cash_transaction_total_cents: number;
|
||||
balance_diff_cents: number;
|
||||
@@ -242,14 +240,6 @@ export interface WithdrawLedgerCheck {
|
||||
missing_refund_txn_count: number;
|
||||
duplicate_refund_txn_count: number;
|
||||
refund_txn_on_non_terminal_count: number;
|
||||
// 邀请奖励金账(invite_cash:与普通现金物理隔离,各自对账)
|
||||
invite_cash_balance_total_cents: number;
|
||||
invite_cash_transaction_total_cents: number;
|
||||
invite_balance_diff_cents: number;
|
||||
invite_missing_withdraw_txn_count: number;
|
||||
invite_missing_refund_txn_count: number;
|
||||
invite_duplicate_refund_txn_count: number;
|
||||
invite_refund_txn_on_non_terminal_count: number;
|
||||
}
|
||||
|
||||
export interface WxpayHealthCheck {
|
||||
|
||||
Reference in New Issue
Block a user