refactor(admin-web): 简化授权管理页面,独立共管钱包功能

将授权管理页面的共管钱包功能独立成单独页面,并简化授权管理页面:

授权管理页面简化:
- 移除共管钱包部分(已独立)
- 移除后端不支持的复杂配置表单(考核规则、阶梯目标等)
- 保留核心功能:授权列表、筛选、创建授权、撤销授权
- 添加创建授权对话框(用户+类型+地区+跳过考核期)
- 添加撤销授权对话框(带原因输入)
- 支持5种授权类型:社区、省团队、正式省公司、市团队、正式市公司

共管钱包独立页面:
- 新建 /co-managed-wallet 页面
- 复用现有 CoManagedWalletSection 组件
- 侧边栏添加"共管钱包"菜单项

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-01-03 18:09:19 -08:00
parent dcd6f2ce18
commit d81e230639
4 changed files with 765 additions and 401 deletions

View File

@ -671,3 +671,273 @@
min-width: 108px;
}
}
/* 卡片头部 */
.authorization__cardHeader {
align-self: stretch;
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 16px;
}
/* 创建按钮 */
.authorization__createBtn {
cursor: pointer;
border: none;
border-radius: 6px;
background-color: #005a9c;
padding: 8px 16px;
font-size: 14px;
line-height: 20px;
font-weight: 500;
color: #fff;
font-family: inherit;
@include transition-fast;
&:hover {
background-color: darken(#005a9c, 5%);
}
}
/* 类型徽章 */
.authorization__typeBadge {
display: inline-block;
padding: 2px 8px;
border-radius: 4px;
font-size: 12px;
font-weight: 500;
background-color: #e0e7ff;
color: #3730a3;
}
/* 类型和地区列 */
.authorization__tableCell--type {
width: 100px;
flex-shrink: 0;
}
.authorization__tableCell--region {
width: 140px;
flex-shrink: 0;
}
/* 空数据行 */
.authorization__emptyRow {
align-self: stretch;
padding: 40px 24px;
text-align: center;
color: #6b7280;
font-size: 14px;
}
/* 已撤销状态 */
.authorization__badge--revoked {
background-color: #fef2f2;
color: #991b1b;
}
/* 模态框样式 */
.modal__overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.modal__content {
background-color: #fff;
border-radius: 12px;
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
width: 100%;
max-width: 480px;
max-height: 90vh;
overflow-y: auto;
padding: 24px;
}
.modal__title {
margin: 0 0 20px 0;
font-size: 18px;
font-weight: 700;
color: #1e293b;
}
.modal__form {
display: flex;
flex-direction: column;
gap: 16px;
}
.modal__formGroup {
display: flex;
flex-direction: column;
gap: 6px;
}
.modal__label {
font-size: 14px;
font-weight: 500;
color: #1e293b;
}
.modal__input {
width: 100%;
height: 40px;
border-radius: 6px;
background-color: #f3f4f6;
border: 1px solid #e5e7eb;
padding: 8px 12px;
font-size: 14px;
color: #1e293b;
outline: none;
font-family: inherit;
box-sizing: border-box;
&::placeholder {
color: #9ca3af;
}
&:focus {
border-color: #005a9c;
background-color: #fff;
}
}
.modal__select {
width: 100%;
height: 40px;
border-radius: 6px;
background-color: #f3f4f6;
border: 1px solid #e5e7eb;
padding: 8px 12px;
font-size: 14px;
color: #1e293b;
cursor: pointer;
font-family: inherit;
appearance: none;
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");
background-position: right 8px center;
background-repeat: no-repeat;
background-size: 16px;
}
.modal__textarea {
width: 100%;
border-radius: 6px;
background-color: #f3f4f6;
border: 1px solid #e5e7eb;
padding: 10px 12px;
font-size: 14px;
color: #1e293b;
outline: none;
font-family: inherit;
resize: vertical;
box-sizing: border-box;
&::placeholder {
color: #9ca3af;
}
&:focus {
border-color: #005a9c;
background-color: #fff;
}
}
.modal__toggleRow {
display: flex;
align-items: center;
justify-content: space-between;
padding: 4px 0;
}
.modal__toggleLabel {
font-size: 14px;
font-weight: 500;
color: #1e293b;
}
.modal__hint {
margin: 0;
font-size: 12px;
color: #6b7280;
}
.modal__warning {
margin: 0;
padding: 12px;
background-color: #fef2f2;
border-radius: 6px;
font-size: 14px;
color: #991b1b;
line-height: 1.5;
strong {
font-weight: 600;
}
}
.modal__footer {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 12px;
margin-top: 24px;
padding-top: 16px;
border-top: 1px solid #e5e7eb;
}
.modal__cancelBtn {
cursor: pointer;
border: 1px solid #e5e7eb;
border-radius: 6px;
background-color: #fff;
padding: 8px 16px;
font-size: 14px;
font-weight: 500;
color: #6b7280;
font-family: inherit;
@include transition-fast;
&:hover {
background-color: #f3f4f6;
}
}
.modal__confirmBtn {
cursor: pointer;
border: none;
border-radius: 6px;
background-color: #005a9c;
padding: 8px 20px;
font-size: 14px;
font-weight: 500;
color: #fff;
font-family: inherit;
@include transition-fast;
&:hover:not(:disabled) {
background-color: darken(#005a9c, 5%);
}
&:disabled {
opacity: 0.5;
cursor: not-allowed;
}
&--danger {
background-color: #dc2626;
&:hover:not(:disabled) {
background-color: darken(#dc2626, 5%);
}
}
}

View File

@ -3,449 +3,526 @@
import { useState } from 'react';
import { PageContainer } from '@/components/layout';
import { cn } from '@/utils/helpers';
import { CoManagedWalletSection } from '@/components/features/co-managed-wallet';
import styles from './authorization.module.scss';
/**
* /
*
*/
interface CompanyItem {
type AuthorizationType =
| 'COMMUNITY'
| 'AUTH_PROVINCE_COMPANY'
| 'PROVINCE_COMPANY'
| 'AUTH_CITY_COMPANY'
| 'CITY_COMPANY';
/**
*
*/
const authorizationTypeLabels: Record<AuthorizationType, string> = {
COMMUNITY: '社区',
AUTH_PROVINCE_COMPANY: '省团队',
PROVINCE_COMPANY: '正式省公司',
AUTH_CITY_COMPANY: '市团队',
CITY_COMPANY: '正式市公司',
};
/**
*
*/
interface AuthorizationItem {
id: string;
avatar: string;
userId: string;
accountSequence: string;
nickname: string;
accountId: string;
province: string;
city?: string;
teamAdoptions: number;
authStatus: 'authorized' | 'pending';
avatar?: string;
type: AuthorizationType;
region?: string; // 如: "广东省" 或 "广东省深圳市"
skipAssessment: boolean;
createdAt: string;
status: 'active' | 'revoked';
}
/**
*
*/
interface TargetItem {
month: string;
provinceMonthly: number;
provinceTotal: number;
cityMonthly: number;
cityTotal: number;
}
/**
*
*/
interface SpecialCityRule {
city: string;
enabled: boolean;
}
// 模拟省公司数据
const mockProvinceCompanies: CompanyItem[] = [
{ id: '1', avatar: '', nickname: '用户昵称一', accountId: '123456789', province: '广东省', teamAdoptions: 1234, authStatus: 'authorized' },
];
// 模拟市公司数据
const mockCityCompanies: CompanyItem[] = [
{ id: '1', avatar: '', nickname: '用户昵称二', accountId: '987654321', province: '广东省', city: '深圳市', teamAdoptions: 567, authStatus: 'pending' },
];
// 阶梯性考核目标数据
const targetData: TargetItem[] = [
{ month: '第 1 个月', provinceMonthly: 500, provinceTotal: 500, cityMonthly: 100, cityTotal: 100 },
{ month: '第 2 个月', provinceMonthly: 500, provinceTotal: 1000, cityMonthly: 100, cityTotal: 200 },
{ month: '...', provinceMonthly: 0, provinceTotal: 0, cityMonthly: 0, cityTotal: 0 },
{ month: '第 9 个月', provinceMonthly: 1000, provinceTotal: 6000, cityMonthly: 200, cityTotal: 1200 },
];
// 特殊城市规则
const specialCities: SpecialCityRule[] = [
{ city: '北京市', enabled: false },
{ city: '上海市', enabled: true },
// 模拟授权数据
const mockAuthorizations: AuthorizationItem[] = [
{
id: '1',
userId: '12345',
accountSequence: 'D25122700001',
nickname: '张三',
type: 'PROVINCE_COMPANY',
region: '广东省',
skipAssessment: false,
createdAt: '2025-01-01',
status: 'active',
},
{
id: '2',
userId: '12346',
accountSequence: 'D25122700002',
nickname: '李四',
type: 'AUTH_CITY_COMPANY',
region: '广东省深圳市',
skipAssessment: true,
createdAt: '2025-01-02',
status: 'active',
},
{
id: '3',
userId: '12347',
accountSequence: 'D25122700003',
nickname: '王五',
type: 'COMMUNITY',
region: '榴莲社区A',
skipAssessment: false,
createdAt: '2025-01-03',
status: 'active',
},
];
/**
*
* UIPro Figma
* -
*/
export default function AuthorizationPage() {
// 省公司规则状态
const [provinceThreshold, setProvinceThreshold] = useState('500');
const [provinceBenefit, setProvinceBenefit] = useState('20');
const [provinceAfterBenefit, setProvinceAfterBenefit] = useState('20');
const [provinceResetEnabled, setProvinceResetEnabled] = useState(true);
// 筛选状态
const [filterType, setFilterType] = useState<AuthorizationType | ''>('');
const [filterStatus, setFilterStatus] = useState<'active' | 'revoked' | ''>('');
const [searchKeyword, setSearchKeyword] = useState('');
// 市公司规则状态
const [cityThreshold, setCityThreshold] = useState('100');
const [cityBenefit, setCityBenefit] = useState('40');
const [cityAfterBenefit, setCityAfterBenefit] = useState('40');
const [cityResetEnabled, setCityResetEnabled] = useState(true);
// 创建授权对话框状态
const [showCreateModal, setShowCreateModal] = useState(false);
const [createForm, setCreateForm] = useState({
accountSequence: '',
type: 'COMMUNITY' as AuthorizationType,
region: '',
skipAssessment: false,
});
// 授权限制规则
const [topRankLimit, setTopRankLimit] = useState('10');
const [specialRules, setSpecialRules] = useState(specialCities);
// 取消授权对话框状态
const [showRevokeModal, setShowRevokeModal] = useState(false);
const [revokeTarget, setRevokeTarget] = useState<AuthorizationItem | null>(null);
const [revokeReason, setRevokeReason] = useState('');
// 筛选后的数据
const filteredData = mockAuthorizations.filter((item) => {
if (filterType && item.type !== filterType) return false;
if (filterStatus && item.status !== filterStatus) return false;
if (searchKeyword) {
const keyword = searchKeyword.toLowerCase();
return (
item.nickname.toLowerCase().includes(keyword) ||
item.accountSequence.toLowerCase().includes(keyword) ||
(item.region && item.region.toLowerCase().includes(keyword))
);
}
return true;
});
// 处理创建授权
const handleCreate = () => {
// TODO: 调用API创建授权
console.log('创建授权:', createForm);
setShowCreateModal(false);
setCreateForm({
accountSequence: '',
type: 'COMMUNITY',
region: '',
skipAssessment: false,
});
};
// 处理取消授权
const handleRevoke = () => {
// TODO: 调用API取消授权
console.log('取消授权:', revokeTarget, '原因:', revokeReason);
setShowRevokeModal(false);
setRevokeTarget(null);
setRevokeReason('');
};
// 打开取消授权对话框
const openRevokeModal = (item: AuthorizationItem) => {
setRevokeTarget(item);
setShowRevokeModal(true);
};
// 判断是否需要地区选择
const needsRegion = (type: AuthorizationType) => {
return type !== 'COMMUNITY' || type === 'COMMUNITY';
};
// 获取地区输入提示
const getRegionPlaceholder = (type: AuthorizationType) => {
switch (type) {
case 'COMMUNITY':
return '输入社区名称';
case 'AUTH_PROVINCE_COMPANY':
case 'PROVINCE_COMPANY':
return '选择省份 (如: 广东省)';
case 'AUTH_CITY_COMPANY':
case 'CITY_COMPANY':
return '选择城市 (如: 广东省深圳市)';
default:
return '';
}
};
// 渲染开关组件
const renderToggle = (checked: boolean, onChange: (checked: boolean) => void) => (
<div
className={cn(styles.authorization__toggle, checked ? styles['authorization__toggle--on'] : styles['authorization__toggle--off'])}
className={cn(
styles.authorization__toggle,
checked ? styles['authorization__toggle--on'] : styles['authorization__toggle--off']
)}
onClick={() => onChange(!checked)}
role="switch"
aria-checked={checked}
tabIndex={0}
onKeyDown={(e) => e.key === 'Enter' && onChange(!checked)}
>
<div className={cn(styles.authorization__toggleHandle, checked ? styles['authorization__toggleHandle--on'] : styles['authorization__toggleHandle--off'])} />
</div>
);
// 渲染省公司表格
const renderProvinceTable = () => (
<div className={styles.authorization__table}>
<div className={styles.authorization__tableHeader}>
<div className={cn(styles.authorization__tableCell, styles['authorization__tableCell--header'], styles['authorization__tableCell--avatar'])}></div>
<div className={cn(styles.authorization__tableCell, styles['authorization__tableCell--header'], styles['authorization__tableCell--nickname'])}></div>
<div className={cn(styles.authorization__tableCell, styles['authorization__tableCell--header'], styles['authorization__tableCell--accountId'])}></div>
<div className={cn(styles.authorization__tableCell, styles['authorization__tableCell--header'], styles['authorization__tableCell--province'])}></div>
<div className={cn(styles.authorization__tableCell, styles['authorization__tableCell--header'], styles['authorization__tableCell--teamAdoptions'])}></div>
<div className={cn(styles.authorization__tableCell, styles['authorization__tableCell--header'], styles['authorization__tableCell--status'])}></div>
<div className={cn(styles.authorization__tableCell, styles['authorization__tableCell--header'], styles['authorization__tableCell--actions'])}></div>
</div>
{mockProvinceCompanies.map((item) => (
<div key={item.id} className={styles.authorization__tableRow}>
<div className={cn(styles.authorization__tableCell, styles['authorization__tableCell--avatar'])}>
<div
className={styles.authorization__avatar}
style={item.avatar ? { backgroundImage: `url(${item.avatar})` } : undefined}
/>
</div>
<div className={cn(styles.authorization__tableCell, styles['authorization__tableCell--nickname'])}>{item.nickname}</div>
<div className={cn(styles.authorization__tableCell, styles['authorization__tableCell--accountId'])}>{item.accountId}</div>
<div className={cn(styles.authorization__tableCell, styles['authorization__tableCell--province'])}>{item.province}</div>
<div className={cn(styles.authorization__tableCell, styles['authorization__tableCell--teamAdoptions'])}>{item.teamAdoptions.toLocaleString()}</div>
<div className={cn(styles.authorization__tableCell, styles['authorization__tableCell--status'])}>
<span className={cn(styles.authorization__badge, item.authStatus === 'authorized' ? styles['authorization__badge--authorized'] : styles['authorization__badge--pending'])}>
{item.authStatus === 'authorized' ? '已授权' : '待授权'}
</span>
</div>
<div className={cn(styles.authorization__tableCell, styles['authorization__tableCell--actions'])}>
<button className={cn(styles.authorization__actionBtn, styles['authorization__actionBtn--authorize'])}></button>
<button className={cn(styles.authorization__actionBtn, styles['authorization__actionBtn--revoke'])}></button>
</div>
</div>
))}
</div>
);
// 渲染市公司表格
const renderCityTable = () => (
<div className={styles.authorization__table}>
<div className={styles.authorization__tableHeader}>
<div className={cn(styles.authorization__tableCell, styles['authorization__tableCell--header'], styles['authorization__tableCell--avatar'])}></div>
<div className={cn(styles.authorization__tableCell, styles['authorization__tableCell--header'], styles['authorization__tableCell--nickname'])}></div>
<div className={cn(styles.authorization__tableCell, styles['authorization__tableCell--header'], styles['authorization__tableCell--accountId'])}></div>
<div className={cn(styles.authorization__tableCell, styles['authorization__tableCell--header'], styles['authorization__tableCell--city'])}>/</div>
<div className={cn(styles.authorization__tableCell, styles['authorization__tableCell--header'], styles['authorization__tableCell--teamAdoptions'])}></div>
<div className={cn(styles.authorization__tableCell, styles['authorization__tableCell--header'], styles['authorization__tableCell--status'])}></div>
<div className={cn(styles.authorization__tableCell, styles['authorization__tableCell--header'], styles['authorization__tableCell--actions'])}></div>
</div>
{mockCityCompanies.map((item) => (
<div key={item.id} className={styles.authorization__tableRow}>
<div className={cn(styles.authorization__tableCell, styles['authorization__tableCell--avatar'])}>
<div
className={styles.authorization__avatar}
style={item.avatar ? { backgroundImage: `url(${item.avatar})` } : undefined}
/>
</div>
<div className={cn(styles.authorization__tableCell, styles['authorization__tableCell--nickname'])}>{item.nickname}</div>
<div className={cn(styles.authorization__tableCell, styles['authorization__tableCell--accountId'])}>{item.accountId}</div>
<div className={cn(styles.authorization__tableCell, styles['authorization__tableCell--city'])}>{item.province} / {item.city}</div>
<div className={cn(styles.authorization__tableCell, styles['authorization__tableCell--teamAdoptions'])}>{item.teamAdoptions}</div>
<div className={cn(styles.authorization__tableCell, styles['authorization__tableCell--status'])}>
<span className={cn(styles.authorization__badge, item.authStatus === 'authorized' ? styles['authorization__badge--authorized'] : styles['authorization__badge--pending'])}>
{item.authStatus === 'authorized' ? '已授权' : '待授权'}
</span>
</div>
<div className={cn(styles.authorization__tableCell, styles['authorization__tableCell--actions'])}>
<button className={cn(styles.authorization__actionBtn, styles['authorization__actionBtn--authorize'])}></button>
<button className={cn(styles.authorization__actionBtn, styles['authorization__actionBtn--revoke'])}></button>
</div>
</div>
))}
<div
className={cn(
styles.authorization__toggleHandle,
checked
? styles['authorization__toggleHandle--on']
: styles['authorization__toggleHandle--off']
)}
/>
</div>
);
return (
<PageContainer title="授权管理">
<div className={styles.authorization}>
{/* 共管钱包管理 */}
<CoManagedWalletSection />
{/* 授权省公司管理 */}
{/* 授权列表 */}
<section className={styles.authorization__card}>
<h3 className={styles.authorization__cardTitle}></h3>
<div className={styles.authorization__filters}>
<select className={styles.authorization__select} aria-label="选择省份">
<option value=""></option>
<option value="guangdong">广</option>
<option value="zhejiang"></option>
<option value="jiangsu"></option>
</select>
<select className={styles.authorization__select} aria-label="授权状态">
<option value=""></option>
<option value="authorized"></option>
<option value="pending"></option>
</select>
<input className={styles.authorization__input} placeholder="关键词搜索" type="text" aria-label="关键词搜索" />
<button className={styles.authorization__searchBtn}></button>
</div>
{renderProvinceTable()}
<p className={styles.authorization__help}></p>
</section>
<div className={styles.authorization__cardHeader}>
<h3 className={styles.authorization__cardTitle}></h3>
<button
className={styles.authorization__createBtn}
onClick={() => setShowCreateModal(true)}
>
+
</button>
</div>
{/* 授权省公司团队权益考核规则 */}
<section className={styles.authorization__card}>
<h3 className={styles.authorization__cardTitle}></h3>
<div className={styles.authorization__form}>
<div className={styles.authorization__formRow}>
<div className={styles.authorization__formGroup}>
<label className={styles.authorization__formLabel}></label>
<input
className={styles.authorization__formInput}
type="text"
value={provinceThreshold}
onChange={(e) => setProvinceThreshold(e.target.value)}
placeholder="500"
/>
</div>
<div className={styles.authorization__formGroup}>
<label className={styles.authorization__formLabel}></label>
<input
className={styles.authorization__formInput}
type="text"
value={provinceBenefit}
onChange={(e) => setProvinceBenefit(e.target.value)}
placeholder="20"
/>
<span className={styles.authorization__formHint}> {provinceThreshold} 1 </span>
</div>
</div>
<div className={styles.authorization__formRow}>
<div className={styles.authorization__formGroup}>
<label className={styles.authorization__formLabel}> 1 </label>
<input
className={styles.authorization__formInput}
type="text"
value={provinceAfterBenefit}
onChange={(e) => setProvinceAfterBenefit(e.target.value)}
placeholder="20"
/>
</div>
<div className={styles.authorization__formGroup}>
<label className={styles.authorization__formLabel}></label>
<select className={styles.authorization__select} style={{ width: '100%' }} aria-label="考核周期">
<option value="monthly"></option>
<option value="quarterly"></option>
</select>
</div>
</div>
</div>
<div className={styles.authorization__toggleRow}>
<span className={styles.authorization__toggleLabel}> 0 {provinceThreshold} </span>
{renderToggle(provinceResetEnabled, setProvinceResetEnabled)}
</div>
<div className={styles.authorization__saveWrapper}>
<button className={styles.authorization__saveBtn}></button>
</div>
</section>
{/* 授权市公司管理 */}
<section className={styles.authorization__card}>
<h3 className={styles.authorization__cardTitle}></h3>
<div className={styles.authorization__filters}>
<select className={styles.authorization__select} aria-label="选择省份">
<option value=""></option>
<option value="guangdong">广</option>
<option value="zhejiang"></option>
</select>
<select className={styles.authorization__select} aria-label="选择城市">
<option value=""></option>
<option value="shenzhen"></option>
<option value="guangzhou">广</option>
</select>
<select className={styles.authorization__select} aria-label="授权状态">
<option value=""></option>
<option value="authorized"></option>
<option value="pending"></option>
</select>
<input className={styles.authorization__input} placeholder="关键词搜索" type="text" aria-label="关键词搜索" />
<button className={styles.authorization__searchBtn}></button>
</div>
{renderCityTable()}
<p className={styles.authorization__help}></p>
</section>
{/* 授权市公司团队权益考核规则 */}
<section className={styles.authorization__card}>
<h3 className={styles.authorization__cardTitle}></h3>
<div className={styles.authorization__form}>
<div className={styles.authorization__formRow}>
<div className={styles.authorization__formGroup}>
<label className={styles.authorization__formLabel}></label>
<input
className={styles.authorization__formInput}
type="text"
value={cityThreshold}
onChange={(e) => setCityThreshold(e.target.value)}
placeholder="100"
/>
</div>
<div className={styles.authorization__formGroup}>
<label className={styles.authorization__formLabel}></label>
<input
className={styles.authorization__formInput}
type="text"
value={cityBenefit}
onChange={(e) => setCityBenefit(e.target.value)}
placeholder="40"
/>
<span className={styles.authorization__formHint}> {cityThreshold} 1 </span>
</div>
</div>
<div className={styles.authorization__formRow}>
<div className={styles.authorization__formGroup}>
<label className={styles.authorization__formLabel}> 1 </label>
<input
className={styles.authorization__formInput}
type="text"
value={cityAfterBenefit}
onChange={(e) => setCityAfterBenefit(e.target.value)}
placeholder="40"
/>
</div>
<div className={styles.authorization__formGroup}>
<label className={styles.authorization__formLabel}></label>
<select className={styles.authorization__select} style={{ width: '100%' }} aria-label="考核周期">
<option value="monthly"></option>
<option value="quarterly"></option>
</select>
</div>
</div>
</div>
<div className={styles.authorization__toggleRow}>
<span className={styles.authorization__toggleLabel}> 0 {cityThreshold} </span>
{renderToggle(cityResetEnabled, setCityResetEnabled)}
</div>
<div className={styles.authorization__saveWrapper}>
<button className={styles.authorization__saveBtn}></button>
</div>
</section>
{/* 正式省公司/市公司授权管理 */}
<div className={styles.authorization__officialSection}>
<section className={styles.authorization__officialCard}>
<h3 className={styles.authorization__officialTitle}></h3>
<p className={styles.authorization__officialDesc}></p>
<div className={styles.authorization__officialTable}>
<div className={styles.authorization__officialRow}>
<div className={styles.authorization__officialCell}></div>
<div className={styles.authorization__officialCell}></div>
<div className={styles.authorization__officialCell}></div>
<div className={styles.authorization__officialCell}></div>
<div className={styles.authorization__officialCell}></div>
<div className={styles.authorization__officialCell}></div>
</div>
</div>
</section>
<section className={styles.authorization__officialCard}>
<h3 className={styles.authorization__officialTitle}></h3>
<p className={styles.authorization__officialDesc}></p>
<div className={styles.authorization__officialTable}>
<div className={styles.authorization__officialRow}>
<div className={styles.authorization__officialCell}></div>
<div className={styles.authorization__officialCell}></div>
<div className={styles.authorization__officialCell}></div>
<div className={styles.authorization__officialCell}>/</div>
<div className={styles.authorization__officialCell}></div>
<div className={styles.authorization__officialCell}></div>
</div>
</div>
</section>
</div>
{/* 省公司 / 市公司 授权限制规则 */}
<section className={styles.authorization__card}>
<h3 className={styles.authorization__cardTitle}> / </h3>
<div className={styles.authorization__rules}>
<div className={styles.authorization__ruleItem}> 1 </div>
<div className={styles.authorization__ruleItem}>
<div className={styles.authorization__ruleInline}>
<span> 1 </span>
<span className={styles.authorization__ruleNote}>(广)</span>
</div>
</div>
<div className={styles.authorization__ruleItem}>
<div className={styles.authorization__ruleInline}>
<span></span>
<input
className={styles.authorization__ruleInput}
type="text"
value={topRankLimit}
onChange={(e) => setTopRankLimit(e.target.value)}
aria-label="排名限制"
/>
<span></span>
</div>
</div>
<div className={styles.authorization__specialRules}>
<div className={styles.authorization__specialTitle}> ()</div>
<div className={styles.authorization__specialTable}>
{specialRules.map((rule, index) => (
<div key={rule.city} className={styles.authorization__specialRow}>
<div className={styles.authorization__specialCity}>{rule.city}</div>
<div className={styles.authorization__specialToggle}>
{renderToggle(rule.enabled, (enabled) => {
const newRules = [...specialRules];
newRules[index] = { ...rule, enabled };
setSpecialRules(newRules);
})}
<span className={styles.authorization__specialLabel}></span>
</div>
</div>
{/* 筛选区域 */}
<div className={styles.authorization__filters}>
<select
className={styles.authorization__select}
value={filterType}
onChange={(e) => setFilterType(e.target.value as AuthorizationType | '')}
aria-label="授权类型"
>
<option value=""></option>
{Object.entries(authorizationTypeLabels).map(([value, label]) => (
<option key={value} value={value}>
{label}
</option>
))}
</div>
</select>
<select
className={styles.authorization__select}
value={filterStatus}
onChange={(e) => setFilterStatus(e.target.value as 'active' | 'revoked' | '')}
aria-label="授权状态"
>
<option value=""></option>
<option value="active"></option>
<option value="revoked"></option>
</select>
<input
className={styles.authorization__input}
placeholder="搜索昵称、账户序列号、地区..."
type="text"
value={searchKeyword}
onChange={(e) => setSearchKeyword(e.target.value)}
aria-label="关键词搜索"
/>
<button className={styles.authorization__searchBtn}></button>
</div>
</div>
</section>
{/* 省公司 / 市公司 阶梯性考核目标 */}
<section className={styles.authorization__card}>
<div className={styles.authorization__targetHeader}>
<h3 className={styles.authorization__targetTitle}> / </h3>
<button className={styles.authorization__editBtn}></button>
</div>
<div className={styles.authorization__targetTable}>
<div className={styles.authorization__targetHeader2}>
<div className={cn(styles.authorization__targetCell, styles['authorization__targetCell--header'], styles['authorization__targetCell--month'])}></div>
<div className={cn(styles.authorization__targetCell, styles['authorization__targetCell--header'], styles['authorization__targetCell--provinceMonthly'])}></div>
<div className={cn(styles.authorization__targetCell, styles['authorization__targetCell--header'], styles['authorization__targetCell--provinceTotal'])}></div>
<div className={cn(styles.authorization__targetCell, styles['authorization__targetCell--header'], styles['authorization__targetCell--cityMonthly'])}></div>
<div className={cn(styles.authorization__targetCell, styles['authorization__targetCell--header'], styles['authorization__targetCell--cityTotal'])}></div>
</div>
<div className={styles.authorization__targetBody}>
{targetData.map((row, index) => (
<div key={row.month} className={cn(styles.authorization__targetRow, index < targetData.length - 1 && styles['authorization__targetRow--bordered'])}>
<div className={cn(styles.authorization__targetCell, styles['authorization__targetCell--month'])}>{row.month}</div>
<div className={cn(styles.authorization__targetCell, styles['authorization__targetCell--provinceMonthly'])}>{row.month === '...' ? '...' : row.provinceMonthly}</div>
<div className={cn(styles.authorization__targetCell, styles['authorization__targetCell--provinceTotal'])}>{row.month === '...' ? '...' : row.provinceTotal}</div>
<div className={cn(styles.authorization__targetCell, styles['authorization__targetCell--cityMonthly'])}>{row.month === '...' ? '...' : row.cityMonthly}</div>
<div className={cn(styles.authorization__targetCell, styles['authorization__targetCell--cityTotal'])}>{row.month === '...' ? '...' : row.cityTotal}</div>
{/* 授权表格 */}
<div className={styles.authorization__table}>
<div className={styles.authorization__tableHeader}>
<div
className={cn(
styles.authorization__tableCell,
styles['authorization__tableCell--header'],
styles['authorization__tableCell--avatar']
)}
>
</div>
<div
className={cn(
styles.authorization__tableCell,
styles['authorization__tableCell--header'],
styles['authorization__tableCell--nickname']
)}
>
</div>
<div
className={cn(
styles.authorization__tableCell,
styles['authorization__tableCell--header'],
styles['authorization__tableCell--accountId']
)}
>
</div>
<div
className={cn(
styles.authorization__tableCell,
styles['authorization__tableCell--header'],
styles['authorization__tableCell--type']
)}
>
</div>
<div
className={cn(
styles.authorization__tableCell,
styles['authorization__tableCell--header'],
styles['authorization__tableCell--region']
)}
>
/
</div>
<div
className={cn(
styles.authorization__tableCell,
styles['authorization__tableCell--header'],
styles['authorization__tableCell--status']
)}
>
</div>
<div
className={cn(
styles.authorization__tableCell,
styles['authorization__tableCell--header'],
styles['authorization__tableCell--actions']
)}
>
</div>
</div>
{filteredData.map((item) => (
<div key={item.id} className={styles.authorization__tableRow}>
<div
className={cn(
styles.authorization__tableCell,
styles['authorization__tableCell--avatar']
)}
>
<div
className={styles.authorization__avatar}
style={item.avatar ? { backgroundImage: `url(${item.avatar})` } : undefined}
/>
</div>
<div
className={cn(
styles.authorization__tableCell,
styles['authorization__tableCell--nickname']
)}
>
{item.nickname}
</div>
<div
className={cn(
styles.authorization__tableCell,
styles['authorization__tableCell--accountId']
)}
>
{item.accountSequence}
</div>
<div
className={cn(
styles.authorization__tableCell,
styles['authorization__tableCell--type']
)}
>
<span className={styles.authorization__typeBadge}>
{authorizationTypeLabels[item.type]}
</span>
</div>
<div
className={cn(
styles.authorization__tableCell,
styles['authorization__tableCell--region']
)}
>
{item.region || '-'}
</div>
<div
className={cn(
styles.authorization__tableCell,
styles['authorization__tableCell--status']
)}
>
<span
className={cn(
styles.authorization__badge,
item.status === 'active'
? styles['authorization__badge--authorized']
: styles['authorization__badge--revoked']
)}
>
{item.status === 'active' ? '有效' : '已撤销'}
</span>
</div>
<div
className={cn(
styles.authorization__tableCell,
styles['authorization__tableCell--actions']
)}
>
{item.status === 'active' && (
<button
className={cn(
styles.authorization__actionBtn,
styles['authorization__actionBtn--revoke']
)}
onClick={() => openRevokeModal(item)}
>
</button>
)}
</div>
</div>
))}
{filteredData.length === 0 && (
<div className={styles.authorization__emptyRow}></div>
)}
</div>
</div>
<p className={styles.authorization__help}> 9 /</p>
<p className={styles.authorization__help}>
</p>
</section>
</div>
{/* 创建授权对话框 */}
{showCreateModal && (
<div className={styles.modal__overlay} onClick={() => setShowCreateModal(false)}>
<div className={styles.modal__content} onClick={(e) => e.stopPropagation()}>
<h3 className={styles.modal__title}></h3>
<div className={styles.modal__form}>
<div className={styles.modal__formGroup}>
<label className={styles.modal__label}></label>
<input
className={styles.modal__input}
type="text"
placeholder="输入用户账户序列号 (如: D25122700001)"
value={createForm.accountSequence}
onChange={(e) =>
setCreateForm({ ...createForm, accountSequence: e.target.value })
}
/>
</div>
<div className={styles.modal__formGroup}>
<label className={styles.modal__label}></label>
<select
className={styles.modal__select}
value={createForm.type}
onChange={(e) =>
setCreateForm({ ...createForm, type: e.target.value as AuthorizationType })
}
>
{Object.entries(authorizationTypeLabels).map(([value, label]) => (
<option key={value} value={value}>
{label}
</option>
))}
</select>
</div>
{needsRegion(createForm.type) && (
<div className={styles.modal__formGroup}>
<label className={styles.modal__label}>
{createForm.type === 'COMMUNITY' ? '社区名称' : '地区'}
</label>
<input
className={styles.modal__input}
type="text"
placeholder={getRegionPlaceholder(createForm.type)}
value={createForm.region}
onChange={(e) => setCreateForm({ ...createForm, region: e.target.value })}
/>
</div>
)}
<div className={styles.modal__formGroup}>
<div className={styles.modal__toggleRow}>
<span className={styles.modal__toggleLabel}></span>
{renderToggle(createForm.skipAssessment, (checked) =>
setCreateForm({ ...createForm, skipAssessment: checked })
)}
</div>
<p className={styles.modal__hint}>
</p>
</div>
</div>
<div className={styles.modal__footer}>
<button
className={styles.modal__cancelBtn}
onClick={() => setShowCreateModal(false)}
>
</button>
<button
className={styles.modal__confirmBtn}
onClick={handleCreate}
disabled={!createForm.accountSequence || !createForm.region}
>
</button>
</div>
</div>
</div>
)}
{/* 撤销授权对话框 */}
{showRevokeModal && revokeTarget && (
<div className={styles.modal__overlay} onClick={() => setShowRevokeModal(false)}>
<div className={styles.modal__content} onClick={(e) => e.stopPropagation()}>
<h3 className={styles.modal__title}></h3>
<div className={styles.modal__form}>
<p className={styles.modal__warning}>
<strong>{revokeTarget.nickname}</strong> (
{revokeTarget.accountSequence}) {' '}
<strong>{authorizationTypeLabels[revokeTarget.type]}</strong>
</p>
<div className={styles.modal__formGroup}>
<label className={styles.modal__label}></label>
<textarea
className={styles.modal__textarea}
placeholder="请输入撤销原因..."
value={revokeReason}
onChange={(e) => setRevokeReason(e.target.value)}
rows={3}
/>
</div>
</div>
<div className={styles.modal__footer}>
<button
className={styles.modal__cancelBtn}
onClick={() => setShowRevokeModal(false)}
>
</button>
<button
className={cn(styles.modal__confirmBtn, styles['modal__confirmBtn--danger'])}
onClick={handleRevoke}
disabled={!revokeReason}
>
</button>
</div>
</div>
</div>
)}
</PageContainer>
);
}

View File

@ -0,0 +1,16 @@
'use client';
import { PageContainer } from '@/components/layout';
import { CoManagedWalletSection } from '@/components/features/co-managed-wallet';
/**
*
*
*/
export default function CoManagedWalletPage() {
return (
<PageContainer title="共管钱包管理">
<CoManagedWalletSection />
</PageContainer>
);
}

View File

@ -27,6 +27,7 @@ const topMenuItems: MenuItem[] = [
{ key: 'users', icon: '/images/Container2.svg', label: '用户管理', path: '/users' },
{ key: 'leaderboard', icon: '/images/Container3.svg', label: '龙虎榜', path: '/leaderboard' },
{ key: 'authorization', icon: '/images/Container4.svg', label: '授权管理', path: '/authorization' },
{ key: 'co-managed-wallet', icon: '/images/Container4.svg', label: '共管钱包', path: '/co-managed-wallet' },
{ key: 'notifications', icon: '/images/Container3.svg', label: '通知管理', path: '/notifications' },
{ key: 'pending-actions', icon: '/images/Container3.svg', label: '待办操作', path: '/pending-actions' },
{ key: 'withdrawals', icon: '/images/Container5.svg', label: '提现审核', path: '/withdrawals' },