feat(pre-planting): 重命名预种持仓→预种明细 + 购买协议弹窗
- mobile-app: "预种持仓"按钮和页面标题改为"预种明细" - admin-service: 新增预种协议文本 API (GET/PUT agreement),存储于 system_configs 表 - admin-service: 公开 config API 响应增加 agreementText 字段 - planting-service: 新建 PrePlantingPublicController (无需 JWT),暴露 GET /pre-planting/config - admin-web: 预种管理页面新增协议文本编辑器(textarea + 保存按钮) - mobile-app: 购买流程增加协议弹窗,用户需勾选同意后才能继续 - mobile-app: 协议文本优先使用后台配置,未配置时使用默认文本 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
92054e776e
commit
c0ac63d40a
|
|
@ -27,6 +27,11 @@ class TogglePrePlantingConfigDto {
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class UpdatePrePlantingAgreementDto {
|
||||||
|
@IsString()
|
||||||
|
text: string;
|
||||||
|
}
|
||||||
|
|
||||||
@ApiTags('预种计划配置')
|
@ApiTags('预种计划配置')
|
||||||
@Controller('admin/pre-planting')
|
@Controller('admin/pre-planting')
|
||||||
export class PrePlantingConfigController {
|
export class PrePlantingConfigController {
|
||||||
|
|
@ -124,6 +129,26 @@ export class PrePlantingConfigController {
|
||||||
async getStats() {
|
async getStats() {
|
||||||
return this.proxyService.getStats();
|
return this.proxyService.getStats();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// [2026-02-28] 新增:预种协议管理
|
||||||
|
// ============================================
|
||||||
|
|
||||||
|
@Get('agreement')
|
||||||
|
@ApiOperation({ summary: '获取预种协议文本' })
|
||||||
|
@ApiResponse({ status: HttpStatus.OK, description: '协议文本' })
|
||||||
|
async getAgreement() {
|
||||||
|
const text = await this.configService.getAgreement();
|
||||||
|
return { text };
|
||||||
|
}
|
||||||
|
|
||||||
|
@Put('agreement')
|
||||||
|
@HttpCode(HttpStatus.OK)
|
||||||
|
@ApiOperation({ summary: '更新预种协议文本' })
|
||||||
|
@ApiResponse({ status: HttpStatus.OK, description: '更新成功' })
|
||||||
|
async updateAgreement(@Body() dto: UpdatePrePlantingAgreementDto) {
|
||||||
|
return this.configService.updateAgreement(dto.text);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -137,8 +162,10 @@ export class PublicPrePlantingConfigController {
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@Get('config')
|
@Get('config')
|
||||||
@ApiOperation({ summary: '获取预种计划开关状态(内部API)' })
|
@ApiOperation({ summary: '获取预种计划开关状态(内部API,含协议文本)' })
|
||||||
async getConfig() {
|
async getConfig() {
|
||||||
return this.configService.getConfig();
|
const config = await this.configService.getConfig();
|
||||||
|
const agreementText = await this.configService.getAgreement();
|
||||||
|
return { ...config, agreementText };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,38 @@ export class PrePlantingConfigService {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取预种协议文本(从 system_configs 表读取)
|
||||||
|
*/
|
||||||
|
async getAgreement(): Promise<string | null> {
|
||||||
|
const config = await this.prisma.systemConfig.findUnique({
|
||||||
|
where: { key: 'pre_planting_agreement' },
|
||||||
|
});
|
||||||
|
return config?.value ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新预种协议文本(upsert 到 system_configs 表)
|
||||||
|
*/
|
||||||
|
async updateAgreement(text: string, updatedBy?: string): Promise<{ text: string }> {
|
||||||
|
await this.prisma.systemConfig.upsert({
|
||||||
|
where: { key: 'pre_planting_agreement' },
|
||||||
|
create: {
|
||||||
|
key: 'pre_planting_agreement',
|
||||||
|
value: text,
|
||||||
|
description: '预种计划购买协议文本',
|
||||||
|
updatedBy: updatedBy || null,
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
value: text,
|
||||||
|
updatedBy: updatedBy || null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
this.logger.log(`[PRE-PLANTING] Agreement text updated by ${updatedBy || 'unknown'}`);
|
||||||
|
return { text };
|
||||||
|
}
|
||||||
|
|
||||||
async updateConfig(
|
async updateConfig(
|
||||||
isActive: boolean,
|
isActive: boolean,
|
||||||
updatedBy?: string,
|
updatedBy?: string,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
import { Controller, Get, HttpStatus } from '@nestjs/common';
|
||||||
|
import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
|
||||||
|
import { PrePlantingAdminClient } from '../../infrastructure/external/pre-planting-admin.client';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [2026-02-28] 预种计划公开 API(无需 JWT)
|
||||||
|
*
|
||||||
|
* 供 mobile-app 在未登录或登录前获取预种计划配置信息(开关状态 + 协议文本)。
|
||||||
|
* 与 PrePlantingController(需 JWT)分离,避免在类级 @UseGuards 中打洞。
|
||||||
|
*/
|
||||||
|
@ApiTags('预种计划-公开')
|
||||||
|
@Controller('pre-planting')
|
||||||
|
export class PrePlantingPublicController {
|
||||||
|
constructor(
|
||||||
|
private readonly adminClient: PrePlantingAdminClient,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
@Get('config')
|
||||||
|
@ApiOperation({ summary: '获取预种计划配置(公开,含协议文本)' })
|
||||||
|
@ApiResponse({ status: HttpStatus.OK, description: '配置信息' })
|
||||||
|
async getConfig() {
|
||||||
|
return this.adminClient.getPrePlantingConfig();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,6 +6,7 @@ import { firstValueFrom } from 'rxjs';
|
||||||
export interface PrePlantingConfig {
|
export interface PrePlantingConfig {
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
activatedAt: Date | null;
|
activatedAt: Date | null;
|
||||||
|
agreementText?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import { HttpModule } from '@nestjs/axios';
|
||||||
// Controllers
|
// Controllers
|
||||||
import { PrePlantingController } from './api/controllers/pre-planting.controller';
|
import { PrePlantingController } from './api/controllers/pre-planting.controller';
|
||||||
import { InternalPrePlantingController } from './api/controllers/internal-pre-planting.controller';
|
import { InternalPrePlantingController } from './api/controllers/internal-pre-planting.controller';
|
||||||
|
import { PrePlantingPublicController } from './api/controllers/pre-planting-public.controller';
|
||||||
|
|
||||||
// Application Services
|
// Application Services
|
||||||
import { PrePlantingApplicationService } from './application/services/pre-planting-application.service';
|
import { PrePlantingApplicationService } from './application/services/pre-planting-application.service';
|
||||||
|
|
@ -55,6 +56,7 @@ import { PrePlantingAuthorizationClient } from './infrastructure/external/pre-pl
|
||||||
],
|
],
|
||||||
controllers: [
|
controllers: [
|
||||||
PrePlantingController,
|
PrePlantingController,
|
||||||
|
PrePlantingPublicController,
|
||||||
InternalPrePlantingController,
|
InternalPrePlantingController,
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
|
|
|
||||||
|
|
@ -20,11 +20,12 @@
|
||||||
* 侧边栏入口在"数据统计"和"系统维护"之间。
|
* 侧边栏入口在"数据统计"和"系统维护"之间。
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useState } from 'react';
|
import { useState, useEffect, useCallback } from 'react';
|
||||||
import { Button } from '@/components/common';
|
import { Button } from '@/components/common';
|
||||||
import { PageContainer } from '@/components/layout';
|
import { PageContainer } from '@/components/layout';
|
||||||
import { cn } from '@/utils/helpers';
|
import { cn } from '@/utils/helpers';
|
||||||
import { formatDateTime } from '@/utils/formatters';
|
import { formatDateTime } from '@/utils/formatters';
|
||||||
|
import { prePlantingService } from '@/services/prePlantingService';
|
||||||
import {
|
import {
|
||||||
usePrePlantingConfig,
|
usePrePlantingConfig,
|
||||||
usePrePlantingStats,
|
usePrePlantingStats,
|
||||||
|
|
@ -88,6 +89,36 @@ export default function PrePlantingPage() {
|
||||||
keyword: activeTab === 'merges' ? keyword : undefined,
|
keyword: activeTab === 'merges' ? keyword : undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// === 协议文本管理 ===
|
||||||
|
const [agreementText, setAgreementText] = useState('');
|
||||||
|
const [agreementSaving, setAgreementSaving] = useState(false);
|
||||||
|
const [agreementMsg, setAgreementMsg] = useState('');
|
||||||
|
|
||||||
|
const loadAgreement = useCallback(async () => {
|
||||||
|
try {
|
||||||
|
const res = await prePlantingService.getAgreement();
|
||||||
|
setAgreementText(res.text ?? '');
|
||||||
|
} catch {
|
||||||
|
// 未配置时使用空文本
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => { loadAgreement(); }, [loadAgreement]);
|
||||||
|
|
||||||
|
const handleSaveAgreement = async () => {
|
||||||
|
setAgreementSaving(true);
|
||||||
|
setAgreementMsg('');
|
||||||
|
try {
|
||||||
|
await prePlantingService.updateAgreement(agreementText);
|
||||||
|
setAgreementMsg('协议已保存');
|
||||||
|
setTimeout(() => setAgreementMsg(''), 3000);
|
||||||
|
} catch {
|
||||||
|
setAgreementMsg('保存失败,请重试');
|
||||||
|
} finally {
|
||||||
|
setAgreementSaving(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// === 开关切换 ===
|
// === 开关切换 ===
|
||||||
const handleToggle = () => {
|
const handleToggle = () => {
|
||||||
if (!config || toggleConfig.isPending) return;
|
if (!config || toggleConfig.isPending) return;
|
||||||
|
|
@ -163,6 +194,49 @@ export default function PrePlantingPage() {
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* 预种协议管理 */}
|
||||||
|
<div className={styles.prePlanting__card} style={{ marginBottom: 16 }}>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 12 }}>
|
||||||
|
<h3 style={{ margin: 0, fontSize: 16, fontWeight: 600, color: '#5D4037' }}>预种协议管理</h3>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||||
|
{agreementMsg && (
|
||||||
|
<span style={{ fontSize: 13, color: agreementMsg.includes('失败') ? '#d32f2f' : '#2e7d32' }}>
|
||||||
|
{agreementMsg}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
<Button
|
||||||
|
variant="primary"
|
||||||
|
size="sm"
|
||||||
|
onClick={handleSaveAgreement}
|
||||||
|
disabled={agreementSaving}
|
||||||
|
>
|
||||||
|
{agreementSaving ? '保存中...' : '保存协议'}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<textarea
|
||||||
|
value={agreementText}
|
||||||
|
onChange={(e) => setAgreementText(e.target.value)}
|
||||||
|
placeholder={'预种协议\n\n1. 用户成功购买预种计划后,自动获得分享权。\n2. 每一份预种计划,对应享有1棵猫山王榴莲树40%产果分红权的1/5份额,即单份预种计划可享受该果树8%的产果分红权。'}
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
minHeight: 120,
|
||||||
|
padding: 12,
|
||||||
|
border: '1px solid #d7ccc8',
|
||||||
|
borderRadius: 8,
|
||||||
|
fontSize: 14,
|
||||||
|
lineHeight: 1.6,
|
||||||
|
fontFamily: 'inherit',
|
||||||
|
resize: 'vertical',
|
||||||
|
backgroundColor: '#fffaf5',
|
||||||
|
color: '#5D4037',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<p style={{ margin: '8px 0 0', fontSize: 12, color: '#8d6e63' }}>
|
||||||
|
留空时,App 将显示默认协议内容。修改后需保存才能生效。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* 统计卡片 */}
|
{/* 统计卡片 */}
|
||||||
<div className={styles.prePlanting__statsGrid}>
|
<div className={styles.prePlanting__statsGrid}>
|
||||||
<div className={styles.prePlanting__statCard}>
|
<div className={styles.prePlanting__statCard}>
|
||||||
|
|
|
||||||
|
|
@ -303,6 +303,8 @@ export const API_ENDPOINTS = {
|
||||||
MERGES: '/v1/admin/pre-planting/merges',
|
MERGES: '/v1/admin/pre-planting/merges',
|
||||||
// 预种统计汇总(总份数、总金额、合并树数等)
|
// 预种统计汇总(总份数、总金额、合并树数等)
|
||||||
STATS: '/v1/admin/pre-planting/stats',
|
STATS: '/v1/admin/pre-planting/stats',
|
||||||
|
// [2026-02-28] 预种协议文本(管理员可编辑)
|
||||||
|
AGREEMENT: '/v1/admin/pre-planting/agreement',
|
||||||
},
|
},
|
||||||
// [2026-02-19] 纯新增:树转让管理 (transfer-service)
|
// [2026-02-19] 纯新增:树转让管理 (transfer-service)
|
||||||
// Saga 编排的树转让订单管理:列表/详情/统计/强制取消
|
// Saga 编排的树转让订单管理:列表/详情/统计/强制取消
|
||||||
|
|
|
||||||
|
|
@ -158,6 +158,20 @@ export const prePlantingService = {
|
||||||
async getMerges(params: PrePlantingListParams = {}): Promise<PaginatedResponse<PrePlantingAdminMerge>> {
|
async getMerges(params: PrePlantingListParams = {}): Promise<PaginatedResponse<PrePlantingAdminMerge>> {
|
||||||
return apiClient.get(API_ENDPOINTS.PRE_PLANTING.MERGES, { params });
|
return apiClient.get(API_ENDPOINTS.PRE_PLANTING.MERGES, { params });
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [2026-02-28] 获取预种协议文本
|
||||||
|
*/
|
||||||
|
async getAgreement(): Promise<{ text: string | null }> {
|
||||||
|
return apiClient.get(API_ENDPOINTS.PRE_PLANTING.AGREEMENT);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [2026-02-28] 更新预种协议文本
|
||||||
|
*/
|
||||||
|
async updateAgreement(text: string): Promise<{ text: string }> {
|
||||||
|
return apiClient.put(API_ENDPOINTS.PRE_PLANTING.AGREEMENT, { text });
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default prePlantingService;
|
export default prePlantingService;
|
||||||
|
|
|
||||||
|
|
@ -42,10 +42,12 @@ enum PrePlantingContractStatus {
|
||||||
class PrePlantingConfig {
|
class PrePlantingConfig {
|
||||||
final bool isActive; // 预种功能是否开启
|
final bool isActive; // 预种功能是否开启
|
||||||
final DateTime? activatedAt; // 开启时间
|
final DateTime? activatedAt; // 开启时间
|
||||||
|
final String? agreementText; // 预种协议文本(null 时使用默认)
|
||||||
|
|
||||||
PrePlantingConfig({
|
PrePlantingConfig({
|
||||||
required this.isActive,
|
required this.isActive,
|
||||||
this.activatedAt,
|
this.activatedAt,
|
||||||
|
this.agreementText,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory PrePlantingConfig.fromJson(Map<String, dynamic> json) {
|
factory PrePlantingConfig.fromJson(Map<String, dynamic> json) {
|
||||||
|
|
@ -54,6 +56,7 @@ class PrePlantingConfig {
|
||||||
activatedAt: json['activatedAt'] != null
|
activatedAt: json['activatedAt'] != null
|
||||||
? DateTime.parse(json['activatedAt'])
|
? DateTime.parse(json['activatedAt'])
|
||||||
: null,
|
: null,
|
||||||
|
agreementText: json['agreementText'] as String?,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import '../../../../core/services/pre_planting_service.dart';
|
||||||
import '../../../../routes/route_paths.dart';
|
import '../../../../routes/route_paths.dart';
|
||||||
|
|
||||||
// ============================================
|
// ============================================
|
||||||
// [2026-02-17] 预种持仓页面
|
// [2026-02-17] 预种明细页面
|
||||||
// ============================================
|
// ============================================
|
||||||
//
|
//
|
||||||
// 显示用户的预种计划持仓信息,包括:
|
// 显示用户的预种计划持仓信息,包括:
|
||||||
|
|
@ -25,9 +25,9 @@ import '../../../../routes/route_paths.dart';
|
||||||
// 本页面纯展示,不含购买操作。
|
// 本页面纯展示,不含购买操作。
|
||||||
// 购买入口在 Profile 页面或本页顶部按钮跳转到 PrePlantingPurchasePage。
|
// 购买入口在 Profile 页面或本页顶部按钮跳转到 PrePlantingPurchasePage。
|
||||||
|
|
||||||
/// 预种持仓页面
|
/// 预种明细页面
|
||||||
///
|
///
|
||||||
/// 展示用户的预种持仓概览、合并进度、订单列表和合并记录。
|
/// 展示用户的预种明细概览、合并进度、订单列表和合并记录。
|
||||||
class PrePlantingPositionPage extends ConsumerStatefulWidget {
|
class PrePlantingPositionPage extends ConsumerStatefulWidget {
|
||||||
const PrePlantingPositionPage({super.key});
|
const PrePlantingPositionPage({super.key});
|
||||||
|
|
||||||
|
|
@ -203,7 +203,7 @@ class _PrePlantingPositionPageState
|
||||||
const SizedBox(width: 42),
|
const SizedBox(width: 42),
|
||||||
const Expanded(
|
const Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
'预种持仓',
|
'预种明细',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
fontFamily: 'Inter',
|
fontFamily: 'Inter',
|
||||||
|
|
|
||||||
|
|
@ -323,11 +323,22 @@ class _PrePlantingPurchasePageState
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 默认预种协议文本
|
||||||
|
static const String _defaultAgreementText =
|
||||||
|
'预种协议\n\n'
|
||||||
|
'1. 用户成功购买预种计划后,自动获得分享权。\n'
|
||||||
|
'2. 每一份预种计划,对应享有1棵猫山王榴莲树40%产果分红权的1/5份额,'
|
||||||
|
'即单份预种计划可享受该果树8%的产果分红权。';
|
||||||
|
|
||||||
/// 确认购买
|
/// 确认购买
|
||||||
Future<void> _confirmPurchase() async {
|
Future<void> _confirmPurchase() async {
|
||||||
if (!_canPurchase) return;
|
if (!_canPurchase) return;
|
||||||
|
|
||||||
// 显示确认弹窗
|
// 第一步:显示协议弹窗
|
||||||
|
final agreed = await _showAgreementDialog();
|
||||||
|
if (agreed != true || !mounted) return;
|
||||||
|
|
||||||
|
// 第二步:显示确认弹窗
|
||||||
final confirmed = await _showConfirmDialog();
|
final confirmed = await _showConfirmDialog();
|
||||||
if (confirmed != true || !mounted) return;
|
if (confirmed != true || !mounted) return;
|
||||||
|
|
||||||
|
|
@ -382,6 +393,19 @@ class _PrePlantingPurchasePageState
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 显示预种协议弹窗(需勾选同意才能继续)
|
||||||
|
Future<bool?> _showAgreementDialog() {
|
||||||
|
final agreementText = (_config?.agreementText != null && _config!.agreementText!.isNotEmpty)
|
||||||
|
? _config!.agreementText!
|
||||||
|
: _defaultAgreementText;
|
||||||
|
|
||||||
|
return showDialog<bool>(
|
||||||
|
context: context,
|
||||||
|
barrierDismissible: false,
|
||||||
|
builder: (ctx) => _AgreementDialog(agreementText: agreementText),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// 显示购买确认弹窗
|
/// 显示购买确认弹窗
|
||||||
Future<bool?> _showConfirmDialog() {
|
Future<bool?> _showConfirmDialog() {
|
||||||
final totalAmount = _quantity * _pricePerPortion;
|
final totalAmount = _quantity * _pricePerPortion;
|
||||||
|
|
@ -1420,3 +1444,150 @@ class _PrePlantingPurchasePageState
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// [2026-02-28] 预种协议弹窗组件
|
||||||
|
// ============================================
|
||||||
|
|
||||||
|
/// 预种购买协议弹窗
|
||||||
|
///
|
||||||
|
/// 展示协议文本,用户需勾选"我已经知晓并完全同意协议中的内容"后
|
||||||
|
/// 才能点击"下一步"继续购买流程。
|
||||||
|
class _AgreementDialog extends StatefulWidget {
|
||||||
|
final String agreementText;
|
||||||
|
|
||||||
|
const _AgreementDialog({required this.agreementText});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<_AgreementDialog> createState() => _AgreementDialogState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AgreementDialogState extends State<_AgreementDialog> {
|
||||||
|
bool _isAgreed = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return AlertDialog(
|
||||||
|
backgroundColor: const Color(0xFFFFF7E6),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
),
|
||||||
|
title: const Text(
|
||||||
|
'预种协议',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
color: Color(0xFF5D4037),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
content: SizedBox(
|
||||||
|
width: double.maxFinite,
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
// 协议文本(可滚动)
|
||||||
|
Container(
|
||||||
|
constraints: const BoxConstraints(maxHeight: 300),
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: const Color(0xFFFFF5E6),
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
border: Border.all(
|
||||||
|
color: const Color(0xFFD7CCC8),
|
||||||
|
width: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Scrollbar(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: Text(
|
||||||
|
widget.agreementText,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontFamily: 'Inter',
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
height: 1.8,
|
||||||
|
color: Color(0xFF5D4037),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
// 勾选区域
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
setState(() {
|
||||||
|
_isAgreed = !_isAgreed;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
width: 22,
|
||||||
|
height: 22,
|
||||||
|
margin: const EdgeInsets.only(top: 1),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: _isAgreed
|
||||||
|
? const Color(0xFFD4A84B)
|
||||||
|
: const Color(0xFFFFF5E6),
|
||||||
|
borderRadius: BorderRadius.circular(4),
|
||||||
|
border: Border.all(
|
||||||
|
color: const Color(0xFFD4A84B),
|
||||||
|
width: 1.5,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: _isAgreed
|
||||||
|
? const Icon(
|
||||||
|
Icons.check,
|
||||||
|
color: Colors.white,
|
||||||
|
size: 16,
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
const Expanded(
|
||||||
|
child: Text(
|
||||||
|
'我已经知晓并完全同意协议中的内容',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontFamily: 'Inter',
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: Color(0xFF5D4037),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.of(context).pop(false),
|
||||||
|
child: const Text(
|
||||||
|
'取消',
|
||||||
|
style: TextStyle(color: Color(0xFF745D43), fontSize: 16),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: _isAgreed ? () => Navigator.of(context).pop(true) : null,
|
||||||
|
child: Text(
|
||||||
|
'下一步',
|
||||||
|
style: TextStyle(
|
||||||
|
color: _isAgreed
|
||||||
|
? const Color(0xFFD4AF37)
|
||||||
|
: const Color(0xFFD4AF37).withValues(alpha: 0.4),
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1998,7 +1998,7 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
|
||||||
),
|
),
|
||||||
SizedBox(width: 6),
|
SizedBox(width: 6),
|
||||||
Text(
|
Text(
|
||||||
'预种持仓',
|
'预种明细',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontFamily: 'Inter',
|
fontFamily: 'Inter',
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue