diff --git a/frontend/admin-web/src/app/(dashboard)/authorization/authorization.module.scss b/frontend/admin-web/src/app/(dashboard)/authorization/authorization.module.scss index f80ac729..998d2162 100644 --- a/frontend/admin-web/src/app/(dashboard)/authorization/authorization.module.scss +++ b/frontend/admin-web/src/app/(dashboard)/authorization/authorization.module.scss @@ -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%); + } + } +} diff --git a/frontend/admin-web/src/app/(dashboard)/authorization/page.tsx b/frontend/admin-web/src/app/(dashboard)/authorization/page.tsx index 07630aa7..9f31afc0 100644 --- a/frontend/admin-web/src/app/(dashboard)/authorization/page.tsx +++ b/frontend/admin-web/src/app/(dashboard)/authorization/page.tsx @@ -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 = { + 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(''); + 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(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) => (
onChange(!checked)} role="switch" aria-checked={checked} tabIndex={0} onKeyDown={(e) => e.key === 'Enter' && onChange(!checked)} > -
-
- ); - - // 渲染省公司表格 - const renderProvinceTable = () => ( -
-
-
头像
-
昵称
-
账户序列号
-
所属省份
-
伞下团队认种总量(棵)
-
授权状态
-
操作
-
- {mockProvinceCompanies.map((item) => ( -
-
-
-
-
{item.nickname}
-
{item.accountId}
-
{item.province}
-
{item.teamAdoptions.toLocaleString()}
-
- - {item.authStatus === 'authorized' ? '已授权' : '待授权'} - -
-
- - -
-
- ))} -
- ); - - // 渲染市公司表格 - const renderCityTable = () => ( -
-
-
头像
-
昵称
-
账户序列号
-
所属省/市
-
伞下团队认种总量(棵)
-
授权状态
-
操作
-
- {mockCityCompanies.map((item) => ( -
-
-
-
-
{item.nickname}
-
{item.accountId}
-
{item.province} / {item.city}
-
{item.teamAdoptions}
-
- - {item.authStatus === 'authorized' ? '已授权' : '待授权'} - -
-
- - -
-
- ))} +
); return (
- {/* 共管钱包管理 */} - - - {/* 授权省公司管理 */} + {/* 授权列表 */}
-

授权省公司管理

-
- - - - -
- {renderProvinceTable()} -

帮助:在此管理和筛选具备省公司资格的账号,并发起授权操作。

-
+
+

授权列表

+ +
- {/* 授权省公司团队权益考核规则 */} -
-

授权省公司团队权益考核规则

-
-
-
- - setProvinceThreshold(e.target.value)} - placeholder="500" - /> -
-
- - setProvinceBenefit(e.target.value)} - placeholder="20" - /> - 即,累计完成 {provinceThreshold} 棵目标前,每新增 1 棵获得的权益 -
-
-
-
- - setProvinceAfterBenefit(e.target.value)} - placeholder="20" - /> -
-
- - -
-
-
-
- 未达成本月考核目标时,权益失效并从 0 重新累计 {provinceThreshold} 棵 - {renderToggle(provinceResetEnabled, setProvinceResetEnabled)} -
-
- -
-
- - {/* 授权市公司管理 */} -
-

授权市公司管理

-
- - - - - -
- {renderCityTable()} -

帮助:在此管理和筛选具备市公司资格的账号,并发起授权操作。

-
- - {/* 授权市公司团队权益考核规则 */} -
-

授权市公司团队权益考核规则

-
-
-
- - setCityThreshold(e.target.value)} - placeholder="100" - /> -
-
- - setCityBenefit(e.target.value)} - placeholder="40" - /> - 即,累计完成 {cityThreshold} 棵目标前,每新增 1 棵获得的权益 -
-
-
-
- - setCityAfterBenefit(e.target.value)} - placeholder="40" - /> -
-
- - -
-
-
-
- 未达成本月考核目标时,权益失效并从 0 重新累计 {cityThreshold} 棵 - {renderToggle(cityResetEnabled, setCityResetEnabled)} -
-
- -
-
- - {/* 正式省公司/市公司授权管理 */} -
-
-

正式省公司授权管理

-

当省公司完成全部阶梯性考核后,可在此授权为正式省公司。

-
-
-
头像
-
昵称
-
序列号
-
省份
-
状态
-
操作
-
-
-
-
-

正式市公司授权管理

-

当市公司完成全部阶梯性考核后,可在此授权为正式市公司。

-
-
-
头像
-
昵称
-
序列号
-
省/市
-
状态
-
操作
-
-
-
-
- - {/* 省公司 / 市公司 授权限制规则 */} -
-

省公司 / 市公司 授权限制规则

-
-
每个省份仅可授权 1 个省公司账号
-
-
- 每个城市仅可授权 1 个市公司账号 - (例如:广东省深圳市) -
-
-
-
- 伞下团队认种总量排名前 - setTopRankLimit(e.target.value)} - aria-label="排名限制" - /> - 的账号具备授权资格 -
-
-
-
特殊城市授权规则 (如直辖市、特别行政区)
-
- {specialRules.map((rule, index) => ( -
-
{rule.city}
-
- {renderToggle(rule.enabled, (enabled) => { - const newRules = [...specialRules]; - newRules[index] = { ...rule, enabled }; - setSpecialRules(newRules); - })} - 启用特例 -
-
+ {/* 筛选区域 */} +
+ + + setSearchKeyword(e.target.value)} + aria-label="关键词搜索" + /> +
-
-
- {/* 省公司 / 市公司 阶梯性考核目标 */} -
-
-

省公司 / 市公司 阶梯性考核目标

- -
-
-
-
考核月
-
省代当月目标(棵)
-
省代累计目标(棵)
-
市代当月目标(棵)
-
市代累计目标(棵)
-
-
- {targetData.map((row, index) => ( -
-
{row.month}
-
{row.month === '...' ? '...' : row.provinceMonthly}
-
{row.month === '...' ? '...' : row.provinceTotal}
-
{row.month === '...' ? '...' : row.cityMonthly}
-
{row.month === '...' ? '...' : row.cityTotal}
+ {/* 授权表格 */} +
+
+
+ 头像 +
+
+ 昵称 +
+
+ 账户序列号 +
+
+ 授权类型 +
+
+ 地区/名称 +
+
+ 状态 +
+
+ 操作 +
+
+ {filteredData.map((item) => ( +
+
+
+
+
+ {item.nickname} +
+
+ {item.accountSequence} +
+
+ + {authorizationTypeLabels[item.type]} + +
+
+ {item.region || '-'} +
+
+ + {item.status === 'active' ? '有效' : '已撤销'} + +
+
+ {item.status === 'active' && ( + + )} +
))} + {filteredData.length === 0 && ( +
暂无授权记录
+ )}
-
-

帮助:完成全部 9 个月阶梯考核后,可升级为正式省/市公司。

+ +

+ 帮助:在此管理所有授权用户,包括社区、省团队、市团队、正式省公司和正式市公司。 +

+ + {/* 创建授权对话框 */} + {showCreateModal && ( +
setShowCreateModal(false)}> +
e.stopPropagation()}> +

创建授权

+
+
+ + + setCreateForm({ ...createForm, accountSequence: e.target.value }) + } + /> +
+
+ + +
+ {needsRegion(createForm.type) && ( +
+ + setCreateForm({ ...createForm, region: e.target.value })} + /> +
+ )} +
+
+ 跳过考核期 + {renderToggle(createForm.skipAssessment, (checked) => + setCreateForm({ ...createForm, skipAssessment: checked }) + )} +
+

+ 开启后,用户将直接获得完整权益,无需经过考核期 +

+
+
+
+ + +
+
+
+ )} + + {/* 撤销授权对话框 */} + {showRevokeModal && revokeTarget && ( +
setShowRevokeModal(false)}> +
e.stopPropagation()}> +

撤销授权

+
+

+ 确定要撤销用户 {revokeTarget.nickname} ( + {revokeTarget.accountSequence}) 的{' '} + {authorizationTypeLabels[revokeTarget.type]} 授权吗? +

+
+ +