feat(client): 授权管理独立一级屏——授权/购买续费/订单管理三 tab(桌面+移动)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,193 @@
|
||||
// screens/license/license_screen.dart — 授权管理独立一级屏(原型 license.html /
|
||||
// m-license.html),三 tab:授权信息 / 在线购买续费 / 订单管理。
|
||||
// 桌面 `/license`、移动 `/me/license` 复用同一 Widget,内部按 context.isMobile
|
||||
// 切换两种形态(桌面:头部+DsSeg+卡片区;移动:通栏 DsSeg+滚动内容)。
|
||||
//
|
||||
// tab 可见性:
|
||||
// - 「在线购买续费」iOS App Store 合规态(hideExternalPurchaseUi)整 tab 隐藏;
|
||||
// - 「订单管理」GET /license/purchases 仅管理员可用(后端判权),非管理员隐藏该 tab。
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:lucide_icons_flutter/lucide_icons.dart';
|
||||
|
||||
import '../../core/auth/auth_state.dart';
|
||||
import '../../core/config/store_compliance.dart';
|
||||
import '../../core/responsive/responsive.dart';
|
||||
import '../../core/theme/app_dims.g.dart';
|
||||
import '../../core/theme/context_tokens.dart';
|
||||
import '../../core/utils/money.dart';
|
||||
import '../../core/utils/web_launch.dart';
|
||||
import '../../providers/license_purchase_provider.dart';
|
||||
import '../../widgets/ds/ds_atoms.dart';
|
||||
import 'license_orders_tab.dart';
|
||||
import '../settings/license_panel.dart';
|
||||
import '../settings/purchase_card.dart';
|
||||
import '../settings/settings_card.dart';
|
||||
|
||||
enum _LicSection { info, buy, orders }
|
||||
|
||||
class LicenseScreen extends ConsumerStatefulWidget {
|
||||
const LicenseScreen({super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<LicenseScreen> createState() => _LicenseScreenState();
|
||||
}
|
||||
|
||||
class _LicenseScreenState extends ConsumerState<LicenseScreen> {
|
||||
_LicSection _section = _LicSection.info;
|
||||
|
||||
void _goInfo() => setState(() => _section = _LicSection.info);
|
||||
void _goBuy() => setState(() => _section = _LicSection.buy);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final t = context.tokens;
|
||||
final role = ref.watch(authStateProvider.select((s) => s.user?.role));
|
||||
final isAdmin = role == 'admin' || role == 'superadmin';
|
||||
final showBuy = !hideExternalPurchaseUi;
|
||||
final showOrders = isAdmin;
|
||||
|
||||
// 当前 tab 若因权限/合规态变得不可见,回退到「授权信息」。
|
||||
if ((_section == _LicSection.buy && !showBuy) ||
|
||||
(_section == _LicSection.orders && !showOrders)) {
|
||||
_section = _LicSection.info;
|
||||
}
|
||||
|
||||
final sections = [
|
||||
_LicSection.info,
|
||||
if (showBuy) _LicSection.buy,
|
||||
if (showOrders) _LicSection.orders,
|
||||
];
|
||||
|
||||
final mobile = context.isMobile;
|
||||
final segLabels = {
|
||||
_LicSection.info: '授权信息',
|
||||
_LicSection.buy: mobile ? '购买 / 续费' : '在线购买 / 续费',
|
||||
_LicSection.orders: '订单管理',
|
||||
};
|
||||
|
||||
Widget seg = DsSeg(
|
||||
items: [for (final s in sections) segLabels[s]!],
|
||||
index: sections.indexOf(_section).clamp(0, sections.length - 1),
|
||||
onChanged: (i) => setState(() => _section = sections[i]),
|
||||
);
|
||||
|
||||
Widget body = switch (_section) {
|
||||
_LicSection.info => _infoBody(mobile),
|
||||
_LicSection.buy => _buyBody(mobile, isAdmin),
|
||||
_LicSection.orders => LicenseOrdersTab(
|
||||
onGoInfo: _goInfo,
|
||||
onGoBuy: showBuy ? _goBuy : null,
|
||||
),
|
||||
};
|
||||
|
||||
if (mobile) {
|
||||
return Container(
|
||||
color: t.bg,
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(14, 12, 14, 0),
|
||||
child: Align(alignment: Alignment.centerLeft, child: seg),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Expanded(child: body),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Container(
|
||||
color: t.bg,
|
||||
padding: const EdgeInsets.fromLTRB(26, 22, 26, 22),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
_buildHeader(context, ref, t),
|
||||
const SizedBox(height: 18),
|
||||
Align(alignment: Alignment.centerLeft, child: seg),
|
||||
const SizedBox(height: 18),
|
||||
Expanded(child: body),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHeader(BuildContext context, WidgetRef ref, dynamic t) {
|
||||
final sub = switch (_section) {
|
||||
_LicSection.info => '授权状态 · 兑换券 · 降级规则',
|
||||
_LicSection.buy => '选择套餐支付宝支付 · 到账自动续期',
|
||||
_LicSection.orders => () {
|
||||
final result = ref.watch(purchaseListProvider).valueOrNull;
|
||||
if (result == null) return '订单流水';
|
||||
return '共 ${result.total} 笔 · '
|
||||
'累计 ¥${yuanFromMinor(result.summary.paidTotalMinor)}';
|
||||
}(),
|
||||
};
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text('授权管理',
|
||||
style: TextStyle(
|
||||
fontSize: AppDims.fsH1,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: t.heading)),
|
||||
const SizedBox(width: AppDims.sp3),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 2),
|
||||
child: Text(sub, style: TextStyle(fontSize: AppDims.fsSm, color: t.muted)),
|
||||
),
|
||||
const Spacer(),
|
||||
if (_section == _LicSection.orders)
|
||||
DsButton('刷新',
|
||||
icon: LucideIcons.refreshCw,
|
||||
onPressed: () =>
|
||||
ref.read(purchaseListProvider.notifier).reload()),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _infoBody(bool mobile) {
|
||||
final child = mobile
|
||||
? const LicensePanel()
|
||||
: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 720),
|
||||
child: const LicensePanel(),
|
||||
);
|
||||
return SingleChildScrollView(
|
||||
padding: mobile ? const EdgeInsets.fromLTRB(14, 0, 14, 14) : EdgeInsets.zero,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buyBody(bool mobile, bool isAdmin) {
|
||||
final card = SettingsCard(
|
||||
title: '在线购买 / 续费',
|
||||
desc: isAdmin ? '选择套餐支付宝支付,到账后授权自动续期' : '在线购买仅门店管理员可操作',
|
||||
child: isAdmin
|
||||
? const PurchaseCard()
|
||||
: Builder(builder: (ctx) {
|
||||
final t = ctx.tokens;
|
||||
return Row(children: [
|
||||
Text('在线购买请联系门店管理员 · ',
|
||||
style: TextStyle(fontSize: AppDims.fsSm, color: t.muted)),
|
||||
InkWell(
|
||||
onTap: () => launchAuthedWebPath(ref, '/#pricing'),
|
||||
child: Text('查看套餐价格 →',
|
||||
style: TextStyle(
|
||||
fontSize: AppDims.fsSm,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: t.primary)),
|
||||
),
|
||||
]);
|
||||
}),
|
||||
);
|
||||
final child = mobile
|
||||
? card
|
||||
: ConstrainedBox(constraints: const BoxConstraints(maxWidth: 560), child: card);
|
||||
return SingleChildScrollView(
|
||||
padding: mobile ? const EdgeInsets.fromLTRB(14, 0, 14, 14) : EdgeInsets.zero,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user