Compare commits

...

No commits in common. 'main' and 'master' have entirely different histories.
main ... master

@ -0,0 +1 @@
PaymentApp

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="deploymentTargetSelector">
<selectionStates>
<SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" />
</SelectionState>
</selectionStates>
</component>
</project>

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>

@ -1,3 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" default="true">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

@ -1,71 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AutoImportSettings">
<option name="autoReloadType" value="SELECTIVE" />
</component>
<component name="ChangeListManager">
<list default="true" id="5ec2d936-8715-4e0b-8805-53fdae86e6b1" name="更改" comment="" />
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="Git.Settings">
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$/.." />
</component>
<component name="KubernetesApiPersistence"><![CDATA[{}]]></component>
<component name="KubernetesApiProvider"><![CDATA[{
"isMigrated": true
}]]></component>
<component name="ProjectColorInfo"><![CDATA[{
"associatedIndex": 8
}]]></component>
<component name="ProjectId" id="353KvvnLsqiKIkfvLAIZPVYufRW" />
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent"><![CDATA[{
"keyToString": {
"ModuleVcsDetector.initialDetectionPerformed": "true",
"RunOnceActivity.ShowReadmeOnStart": "true",
"RunOnceActivity.git.unshallow": "true",
"com.intellij.ml.llm.matterhorn.ej.ui.settings.DefaultAutoModeForALLUsers.v1": "true",
"com.intellij.ml.llm.matterhorn.ej.ui.settings.DefaultModelSelectionForGA.v1": "true",
"git-widget-placeholder": "zhanghao__branch",
"ignore.virus.scanning.warn.message": "true",
"junie.onboarding.icon.badge.shown": "true",
"kotlin-language-version-configured": "true",
"last_opened_file_path": "C:/Users/22978/Desktop/mobile pay app/team_noob/MobilePayPrototype",
"node.js.detected.package.eslint": "true",
"node.js.detected.package.tslint": "true",
"node.js.selected.package.eslint": "(autodetect)",
"node.js.selected.package.tslint": "(autodetect)",
"nodejs_package_manager_path": "npm",
"to.speed.mode.migration.done": "true",
"vue.rearranger.settings.migration": "true"
}
}]]></component>
<component name="SharedIndexes">
<attachedChunks>
<set>
<option value="bundled-jdk-9823dce3aa75-bf35d07a577b-intellij.indexing.shared.core-IU-252.27397.103" />
<option value="bundled-js-predefined-d6986cc7102b-3aa1da707db6-JavaScript-IU-252.27397.103" />
</set>
</attachedChunks>
</component>
<component name="TaskManager">
<task active="true" id="Default" summary="默认任务">
<changelist id="5ec2d936-8715-4e0b-8805-53fdae86e6b1" name="更改" comment="" />
<created>1762333548417</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1762333548417</updated>
<workItem from="1762333572907" duration="2380000" />
</task>
<servers />
</component>
<component name="TypeScriptGeneratedFilesManager">
<option name="version" value="3" />
</component>
</project>

@ -1,202 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ArtifactsWorkspaceSettings">
<artifacts-to-build>
<artifact name="MobilePayPrototype:war exploded" />
</artifacts-to-build>
</component>
<component name="AutoImportSettings">
<option name="autoReloadType" value="SELECTIVE" />
</component>
<component name="ChangeListManager">
<list default="true" id="29d2b17e-3dd5-4f22-94a5-3da90e02a79c" name="更改" comment="" />
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="FileTemplateManagerImpl">
<option name="RECENT_TEMPLATES">
<list>
<option value="Class" />
<option value="Jsp File" />
</list>
</option>
</component>
<component name="KubernetesApiPersistence">{}</component>
<component name="KubernetesApiProvider">{
&quot;isMigrated&quot;: true
}</component>
<component name="LogFilters">
<option name="FILTER_ERRORS" value="false" />
<option name="FILTER_WARNINGS" value="false" />
<option name="FILTER_INFO" value="true" />
<option name="FILTER_DEBUG" value="true" />
<option name="CUSTOM_FILTER" />
</component>
<component name="ProjectColorInfo">{
&quot;associatedIndex&quot;: 3
}</component>
<component name="ProjectId" id="34KTet16frdjK92iA9fGK6TdnU3" />
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent">{
&quot;keyToString&quot;: {
&quot;ModuleVcsDetector.initialDetectionPerformed&quot;: &quot;true&quot;,
&quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;,
&quot;RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252&quot;: &quot;true&quot;,
&quot;Tomcat 服务器.Tomcat 10.1.47.executor&quot;: &quot;Run&quot;,
&quot;kotlin-language-version-configured&quot;: &quot;true&quot;,
&quot;last_opened_file_path&quot;: &quot;C:/Users/87641/Downloads/MobilePayPrototype&quot;,
&quot;node.js.detected.package.eslint&quot;: &quot;true&quot;,
&quot;node.js.detected.package.tslint&quot;: &quot;true&quot;,
&quot;node.js.selected.package.eslint&quot;: &quot;(autodetect)&quot;,
&quot;node.js.selected.package.tslint&quot;: &quot;(autodetect)&quot;,
&quot;nodejs_package_manager_path&quot;: &quot;npm&quot;,
&quot;project.structure.last.edited&quot;: &quot;模块&quot;,
&quot;project.structure.proportion&quot;: &quot;0.0&quot;,
&quot;project.structure.side.proportion&quot;: &quot;0.2&quot;,
&quot;settings.editor.selected.configurable&quot;: &quot;preferences.pluginManager&quot;,
&quot;vue.rearranger.settings.migration&quot;: &quot;true&quot;
}
}</component>
<component name="RunDashboard">
<option name="configurationStatuses">
<map>
<entry key="#com.intellij.j2ee.web.tomcat.TomcatRunConfigurationFactory">
<value>
<map>
<entry key="Tomcat 10.1.47" value="STOPPED" />
</map>
</value>
</entry>
</map>
</option>
</component>
<component name="RunManager">
<configuration name="Tomcat 10.1.47" type="#com.intellij.j2ee.web.tomcat.TomcatRunConfigurationFactory" factoryName="Local" APPLICATION_SERVER_NAME="Tomcat 10.1.47" ALTERNATIVE_JRE_ENABLED="false" nameIsGenerated="true">
<option name="BROWSER_ID" value="98ca6316-2f89-46d9-a9e5-fa9e2b0625b3" />
<option name="UPDATING_POLICY" value="restart-server" />
<deployment>
<artifact name="MobilePayPrototype:war exploded">
<settings>
<option name="CONTEXT_PATH" value="/MobilePayPrototype_war_exploded" />
</settings>
</artifact>
</deployment>
<server-settings>
<option name="BASE_DIRECTORY_NAME" value="83ee14db-001f-45c9-801f-acd24919ac11" />
</server-settings>
<predefined_log_file enabled="true" id="Tomcat" />
<predefined_log_file enabled="true" id="Tomcat Catalina" />
<predefined_log_file id="Tomcat Manager" />
<predefined_log_file id="Tomcat Host Manager" />
<predefined_log_file id="Tomcat Localhost Access" />
<RunnerSettings RunnerId="Debug">
<option name="DEBUG_PORT" value="8809" />
</RunnerSettings>
<ConfigurationWrapper VM_VAR="JAVA_OPTS" RunnerId="Cover">
<option name="USE_ENV_VARIABLES" value="true" />
<STARTUP>
<option name="USE_DEFAULT" value="true" />
<option name="SCRIPT" value="" />
<option name="VM_PARAMETERS" value="" />
<option name="PROGRAM_PARAMETERS" value="" />
</STARTUP>
<SHUTDOWN>
<option name="USE_DEFAULT" value="true" />
<option name="SCRIPT" value="" />
<option name="VM_PARAMETERS" value="" />
<option name="PROGRAM_PARAMETERS" value="" />
</SHUTDOWN>
</ConfigurationWrapper>
<ConfigurationWrapper VM_VAR="JAVA_OPTS" RunnerId="Debug">
<option name="USE_ENV_VARIABLES" value="true" />
<STARTUP>
<option name="USE_DEFAULT" value="true" />
<option name="SCRIPT" value="" />
<option name="VM_PARAMETERS" value="" />
<option name="PROGRAM_PARAMETERS" value="" />
</STARTUP>
<SHUTDOWN>
<option name="USE_DEFAULT" value="true" />
<option name="SCRIPT" value="" />
<option name="VM_PARAMETERS" value="" />
<option name="PROGRAM_PARAMETERS" value="" />
</SHUTDOWN>
</ConfigurationWrapper>
<ConfigurationWrapper VM_VAR="JAVA_OPTS" RunnerId="Profile">
<option name="USE_ENV_VARIABLES" value="true" />
<STARTUP>
<option name="USE_DEFAULT" value="true" />
<option name="SCRIPT" value="" />
<option name="VM_PARAMETERS" value="" />
<option name="PROGRAM_PARAMETERS" value="" />
</STARTUP>
<SHUTDOWN>
<option name="USE_DEFAULT" value="true" />
<option name="SCRIPT" value="" />
<option name="VM_PARAMETERS" value="" />
<option name="PROGRAM_PARAMETERS" value="" />
</SHUTDOWN>
</ConfigurationWrapper>
<ConfigurationWrapper VM_VAR="JAVA_OPTS" RunnerId="Run">
<option name="USE_ENV_VARIABLES" value="true" />
<STARTUP>
<option name="USE_DEFAULT" value="true" />
<option name="SCRIPT" value="" />
<option name="VM_PARAMETERS" value="" />
<option name="PROGRAM_PARAMETERS" value="" />
</STARTUP>
<SHUTDOWN>
<option name="USE_DEFAULT" value="true" />
<option name="SCRIPT" value="" />
<option name="VM_PARAMETERS" value="" />
<option name="PROGRAM_PARAMETERS" value="" />
</SHUTDOWN>
</ConfigurationWrapper>
<method v="2">
<option name="Make" enabled="true" />
<option name="BuildArtifacts" enabled="true">
<artifact name="MobilePayPrototype:war exploded" />
</option>
</method>
</configuration>
</component>
<component name="SharedIndexes">
<attachedChunks>
<set>
<option value="bundled-jdk-9823dce3aa75-bf35d07a577b-intellij.indexing.shared.core-IU-252.26830.84" />
<option value="bundled-js-predefined-d6986cc7102b-3aa1da707db6-JavaScript-IU-252.26830.84" />
</set>
</attachedChunks>
</component>
<component name="TaskManager">
<task active="true" id="Default" summary="默认任务">
<changelist id="29d2b17e-3dd5-4f22-94a5-3da90e02a79c" name="更改" comment="" />
<created>1760961364824</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1760961364824</updated>
<workItem from="1760961365755" duration="97000" />
<workItem from="1760961474830" duration="148000" />
<workItem from="1760962006950" duration="1217000" />
<workItem from="1761200575524" duration="627000" />
<workItem from="1761202004904" duration="285000" />
<workItem from="1761203404557" duration="18000" />
<workItem from="1761295487704" duration="8022000" />
<workItem from="1761483085466" duration="99000" />
<workItem from="1761745663768" duration="2912000" />
<workItem from="1761784691352" duration="1429000" />
<workItem from="1761814006991" duration="565000" />
<workItem from="1762047486066" duration="8669000" />
<workItem from="1762152467619" duration="3073000" />
</task>
<servers />
</component>
<component name="TypeScriptGeneratedFilesManager">
<option name="version" value="3" />
</component>
</project>

@ -0,0 +1,61 @@
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.compose)
}
android {
namespace 'com.payment'
compileSdk 36
defaultConfig {
applicationId "com.payment"
minSdk 24
targetSdk 36
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = '11'
}
buildFeatures {
compose true
}
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.9.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
// Jetpack
implementation 'androidx.lifecycle:lifecycle-viewmodel:2.6.2'
implementation 'androidx.lifecycle:lifecycle-livedata:2.6.2'
implementation 'androidx.fragment:fragment:1.6.1'
// -
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.squareup.okhttp3:okhttp:4.11.0'
implementation 'com.squareup.okhttp3:logging-interceptor:4.11.0'
implementation 'com.squareup.okhttp3:mockwebserver:4.11.0'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
}

@ -1,4 +1,4 @@
package com.example.myapplication
package com.payment
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
@ -19,6 +19,6 @@ class ExampleInstrumentedTest {
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("com.example.myapplication", appContext.packageName)
assertEquals("com.payment", appContext.packageName)
}
}

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.payment">
<uses-permission android:name="android.permission.INTERNET" />
<application
android:name=".MyApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/Theme.AppCompat.Light.DarkActionBar">
<!-- 启动Activity -->
<activity
android:name=".user.LoginActivity"
android:exported="true"
android:theme="@style/Theme.AppCompat.Light.DarkActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!-- 其他Activity -->
<activity android:name=".ui.MainActivity" />
<activity android:name=".user.RegisterActivity" />
<activity android:name=".user.RealNameAuthActivity" />
<activity android:name=".user.AuditStatusActivity" />
<activity android:name=".asset.BalanceActivity" />
<activity android:name=".asset.BankCardListActivity" />
<activity android:name=".asset.AddBankCardActivity" />
<activity android:name=".asset.RechargeWithdrawActivity" />
</application>
</manifest>

@ -0,0 +1,19 @@
package com.payment;
import android.app.Application;
import android.content.Context;
public class MyApplication extends Application {
private static Context context;
@Override
public void onCreate() {
super.onCreate();
context = getApplicationContext();
}
// 提供全局上下文
public static Context getContext() {
return context;
}
}

@ -0,0 +1,63 @@
package com.payment.asset;
import android.os.Bundle;
import android.widget.Button;
import android.widget.Toast;
import androidx.lifecycle.ViewModelProvider;
import com.google.android.material.textfield.TextInputEditText;
import com.payment.base.BaseActivity;
import com.payment.R;
public class AddBankCardActivity extends BaseActivity {
private TextInputEditText etCardNo, etBankName;
private Button btnSubmit;
private AssetViewModel assetViewModel;
@Override
protected int getLayoutId() {
return R.layout.activity_add_bank_card;
}
@Override
protected void initView() {
etCardNo = findViewById(R.id.et_card_no);
etBankName = findViewById(R.id.et_bank_name);
btnSubmit = findViewById(R.id.btn_submit);
}
@Override
protected void initData() {
assetViewModel = new ViewModelProvider(this).get(AssetViewModel.class);
// 观察添加银行卡结果 - 使用正确的方法名
assetViewModel.getAddBankCardResult().observe(this, resultBean -> {
if (resultBean != null && resultBean.isSuccess()) {
showToast("添加银行卡成功");
finish();
} else {
showToast("添加银行卡失败:" + (resultBean != null ? resultBean.getMsg() : "网络异常"));
}
});
}
@Override
protected void bindEvent() {
btnSubmit.setOnClickListener(v -> {
String cardNo = etCardNo.getText().toString().trim();
String bankName = etBankName.getText().toString().trim();
if (cardNo.isEmpty() || bankName.isEmpty()) {
showToast("请完善银行卡信息");
return;
}
if (cardNo.length() < 16) {
showToast("请输入正确的银行卡号");
return;
}
// 调用添加银行卡接口
assetViewModel.addBankCard(cardNo, bankName);
});
}
}

@ -0,0 +1,253 @@
package com.payment.asset;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import com.payment.MyApplication;
import com.payment.network.ApiService;
import com.payment.network.RetrofitManager;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
/**
* ViewModel
*/
public class AssetViewModel extends ViewModel {
// ========== 余额相关 ==========
private MutableLiveData<ApiService.ResultBean<ApiService.BalanceBean>> balanceResult = new MutableLiveData<>();
public LiveData<ApiService.ResultBean<ApiService.BalanceBean>> getBalanceResult() {
return balanceResult;
}
public void getBalance() {
ApiService apiService = RetrofitManager.getApiService(MyApplication.getContext());
apiService.getBalance().enqueue(new Callback<ApiService.ResultBean<ApiService.BalanceBean>>() {
@Override
public void onResponse(Call<ApiService.ResultBean<ApiService.BalanceBean>> call, Response<ApiService.ResultBean<ApiService.BalanceBean>> response) {
if (response.isSuccessful() && response.body() != null) {
balanceResult.postValue(response.body());
} else {
ApiService.ResultBean<ApiService.BalanceBean> errorResult = new ApiService.ResultBean<>();
errorResult.setSuccess(false);
errorResult.setMsg("获取余额失败");
balanceResult.postValue(errorResult);
}
}
@Override
public void onFailure(Call<ApiService.ResultBean<ApiService.BalanceBean>> call, Throwable t) {
ApiService.ResultBean<ApiService.BalanceBean> errorResult = new ApiService.ResultBean<>();
errorResult.setSuccess(false);
errorResult.setMsg("网络错误:" + t.getMessage());
balanceResult.postValue(errorResult);
}
});
}
// ========== 银行卡列表相关 ==========
private MutableLiveData<ApiService.ResultBean<ApiService.BankCardListBean>> bankCardListResult = new MutableLiveData<>();
public LiveData<ApiService.ResultBean<ApiService.BankCardListBean>> getBankCardListResult() {
return bankCardListResult;
}
public void getBankCardList() {
ApiService apiService = RetrofitManager.getApiService(MyApplication.getContext());
apiService.getBankCardList().enqueue(new Callback<ApiService.ResultBean<ApiService.BankCardListBean>>() {
@Override
public void onResponse(Call<ApiService.ResultBean<ApiService.BankCardListBean>> call, Response<ApiService.ResultBean<ApiService.BankCardListBean>> response) {
if (response.isSuccessful() && response.body() != null) {
bankCardListResult.postValue(response.body());
} else {
ApiService.ResultBean<ApiService.BankCardListBean> errorResult = new ApiService.ResultBean<>();
errorResult.setSuccess(false);
errorResult.setMsg("获取银行卡列表失败");
bankCardListResult.postValue(errorResult);
}
}
@Override
public void onFailure(Call<ApiService.ResultBean<ApiService.BankCardListBean>> call, Throwable t) {
ApiService.ResultBean<ApiService.BankCardListBean> errorResult = new ApiService.ResultBean<>();
errorResult.setSuccess(false);
errorResult.setMsg("网络错误:" + t.getMessage());
bankCardListResult.postValue(errorResult);
}
});
}
// ========== 添加银行卡相关 ==========
private MutableLiveData<ApiService.ResultBean<ApiService.BaseResponse>> addBankCardResult = new MutableLiveData<>();
public LiveData<ApiService.ResultBean<ApiService.BaseResponse>> getAddBankCardResult() {
return addBankCardResult;
}
public void addBankCard(String cardNo, String bankName) {
ApiService apiService = RetrofitManager.getApiService(MyApplication.getContext());
ApiService.AddBankCardRequest request = new ApiService.AddBankCardRequest(cardNo, bankName);
apiService.addBankCard(request).enqueue(new Callback<ApiService.ResultBean<ApiService.BaseResponse>>() {
@Override
public void onResponse(Call<ApiService.ResultBean<ApiService.BaseResponse>> call, Response<ApiService.ResultBean<ApiService.BaseResponse>> response) {
if (response.isSuccessful() && response.body() != null) {
addBankCardResult.postValue(response.body());
} else {
ApiService.ResultBean<ApiService.BaseResponse> errorResult = new ApiService.ResultBean<>();
errorResult.setSuccess(false);
errorResult.setMsg("添加银行卡失败");
addBankCardResult.postValue(errorResult);
}
}
@Override
public void onFailure(Call<ApiService.ResultBean<ApiService.BaseResponse>> call, Throwable t) {
ApiService.ResultBean<ApiService.BaseResponse> errorResult = new ApiService.ResultBean<>();
errorResult.setSuccess(false);
errorResult.setMsg("网络错误:" + t.getMessage());
addBankCardResult.postValue(errorResult);
}
});
}
// ========== 充值相关 ==========
private MutableLiveData<ApiService.ResultBean<ApiService.BaseResponse>> rechargeResult = new MutableLiveData<>();
public LiveData<ApiService.ResultBean<ApiService.BaseResponse>> getRechargeResult() {
return rechargeResult;
}
public void recharge(double amount, String cardId) {
ApiService apiService = RetrofitManager.getApiService(MyApplication.getContext());
ApiService.RechargeWithdrawRequest request = new ApiService.RechargeWithdrawRequest(String.valueOf(amount), cardId);
apiService.recharge(request).enqueue(new Callback<ApiService.ResultBean<ApiService.BaseResponse>>() {
@Override
public void onResponse(Call<ApiService.ResultBean<ApiService.BaseResponse>> call, Response<ApiService.ResultBean<ApiService.BaseResponse>> response) {
if (response.isSuccessful() && response.body() != null) {
rechargeResult.postValue(response.body());
} else {
ApiService.ResultBean<ApiService.BaseResponse> errorResult = new ApiService.ResultBean<>();
errorResult.setSuccess(false);
errorResult.setMsg("充值失败");
rechargeResult.postValue(errorResult);
}
}
@Override
public void onFailure(Call<ApiService.ResultBean<ApiService.BaseResponse>> call, Throwable t) {
ApiService.ResultBean<ApiService.BaseResponse> errorResult = new ApiService.ResultBean<>();
errorResult.setSuccess(false);
errorResult.setMsg("网络错误:" + t.getMessage());
rechargeResult.postValue(errorResult);
}
});
}
// ========== 提现相关 ==========
private MutableLiveData<ApiService.ResultBean<ApiService.BaseResponse>> withdrawResult = new MutableLiveData<>();
public LiveData<ApiService.ResultBean<ApiService.BaseResponse>> getWithdrawResult() {
return withdrawResult;
}
public void withdraw(double amount, String cardId) {
ApiService apiService = RetrofitManager.getApiService(MyApplication.getContext());
ApiService.RechargeWithdrawRequest request = new ApiService.RechargeWithdrawRequest(String.valueOf(amount), cardId);
apiService.withdraw(request).enqueue(new Callback<ApiService.ResultBean<ApiService.BaseResponse>>() {
@Override
public void onResponse(Call<ApiService.ResultBean<ApiService.BaseResponse>> call, Response<ApiService.ResultBean<ApiService.BaseResponse>> response) {
if (response.isSuccessful() && response.body() != null) {
withdrawResult.postValue(response.body());
} else {
ApiService.ResultBean<ApiService.BaseResponse> errorResult = new ApiService.ResultBean<>();
errorResult.setSuccess(false);
errorResult.setMsg("提现失败");
withdrawResult.postValue(errorResult);
}
}
@Override
public void onFailure(Call<ApiService.ResultBean<ApiService.BaseResponse>> call, Throwable t) {
ApiService.ResultBean<ApiService.BaseResponse> errorResult = new ApiService.ResultBean<>();
errorResult.setSuccess(false);
errorResult.setMsg("网络错误:" + t.getMessage());
withdrawResult.postValue(errorResult);
}
});
}
// ========== 设置默认银行卡相关 ==========
private MutableLiveData<ApiService.ResultBean<ApiService.BaseResponse>> defaultCardResult = new MutableLiveData<>();
public LiveData<ApiService.ResultBean<ApiService.BaseResponse>> getDefaultCardResult() {
return defaultCardResult;
}
public void setDefaultCard(int cardId) {
ApiService apiService = RetrofitManager.getApiService(MyApplication.getContext());
ApiService.DefaultBankCardRequest request = new ApiService.DefaultBankCardRequest(String.valueOf(cardId));
apiService.setDefaultBankCard(request).enqueue(new Callback<ApiService.ResultBean<ApiService.BaseResponse>>() {
@Override
public void onResponse(Call<ApiService.ResultBean<ApiService.BaseResponse>> call, Response<ApiService.ResultBean<ApiService.BaseResponse>> response) {
if (response.isSuccessful() && response.body() != null) {
defaultCardResult.postValue(response.body());
} else {
ApiService.ResultBean<ApiService.BaseResponse> errorResult = new ApiService.ResultBean<>();
errorResult.setSuccess(false);
errorResult.setMsg("设置默认银行卡失败");
defaultCardResult.postValue(errorResult);
}
}
@Override
public void onFailure(Call<ApiService.ResultBean<ApiService.BaseResponse>> call, Throwable t) {
ApiService.ResultBean<ApiService.BaseResponse> errorResult = new ApiService.ResultBean<>();
errorResult.setSuccess(false);
errorResult.setMsg("网络错误:" + t.getMessage());
defaultCardResult.postValue(errorResult);
}
});
}
// ========== 删除银行卡相关 ==========
private MutableLiveData<ApiService.ResultBean<ApiService.BaseResponse>> deleteCardResult = new MutableLiveData<>();
public LiveData<ApiService.ResultBean<ApiService.BaseResponse>> getDeleteCardResult() {
return deleteCardResult;
}
public void deleteBankCard(int cardId) {
ApiService apiService = RetrofitManager.getApiService(MyApplication.getContext());
ApiService.DeleteBankCardRequest request = new ApiService.DeleteBankCardRequest(String.valueOf(cardId));
apiService.deleteBankCard(request).enqueue(new Callback<ApiService.ResultBean<ApiService.BaseResponse>>() {
@Override
public void onResponse(Call<ApiService.ResultBean<ApiService.BaseResponse>> call, Response<ApiService.ResultBean<ApiService.BaseResponse>> response) {
if (response.isSuccessful() && response.body() != null) {
deleteCardResult.postValue(response.body());
} else {
ApiService.ResultBean<ApiService.BaseResponse> errorResult = new ApiService.ResultBean<>();
errorResult.setSuccess(false);
errorResult.setMsg("删除银行卡失败");
deleteCardResult.postValue(errorResult);
}
}
@Override
public void onFailure(Call<ApiService.ResultBean<ApiService.BaseResponse>> call, Throwable t) {
ApiService.ResultBean<ApiService.BaseResponse> errorResult = new ApiService.ResultBean<>();
errorResult.setSuccess(false);
errorResult.setMsg("网络错误:" + t.getMessage());
deleteCardResult.postValue(errorResult);
}
});
}
}

@ -0,0 +1,43 @@
package com.payment.asset;
import android.os.Bundle;
import android.widget.TextView;
import androidx.lifecycle.ViewModelProvider;
import com.payment.base.BaseActivity;
import com.payment.R;
public class BalanceActivity extends BaseActivity {
private TextView tvBalance;
// 移除 tvUpdateTime 的声明
private AssetViewModel assetViewModel;
@Override
protected int getLayoutId() {
return R.layout.activity_balance;
}
@Override
protected void initView() {
tvBalance = findViewById(R.id.tv_balance);
// 移除 tvUpdateTime 的初始化
}
@Override
protected void initData() {
assetViewModel = new ViewModelProvider(this).get(AssetViewModel.class);
// 查询余额
assetViewModel.getBalance();
// 观察余额结果
assetViewModel.getBalanceResult().observe(this, resultBean -> {
if (resultBean != null && resultBean.isSuccess()) {
String balance = resultBean.getData().getBalance();
tvBalance.setText("¥ " + balance);
// 移除更新时间显示
} else {
showToast("获取余额失败");
}
});
}
}

@ -0,0 +1,90 @@
package com.payment.asset;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.payment.R;
import com.payment.network.ApiService;
import java.util.List;
public class BankCardAdapter extends RecyclerView.Adapter<BankCardAdapter.ViewHolder> {
private Context context;
private List<ApiService.BankCardBean> cardList; // 修改为 ApiService.BankCardBean
private OnItemActionListener listener;
public BankCardAdapter(Context context, List<ApiService.BankCardBean> cardList) {
this.context = context;
this.cardList = cardList;
}
public void updateData(List<ApiService.BankCardBean> cardList) { // 修改参数类型
this.cardList = cardList;
notifyDataSetChanged();
}
public void setOnItemActionListener(OnItemActionListener listener) {
this.listener = listener;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(context).inflate(R.layout.item_bank_card, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
ApiService.BankCardBean card = cardList.get(position); // 修改类型
holder.tvBankName.setText(card.getBankName());
holder.tvCardNo.setText("**** **** **** " + card.getCardNo().substring(card.getCardNo().length() - 4));
if (card.isDefault()) {
holder.btnSetDefault.setVisibility(View.GONE);
holder.tvDefault.setVisibility(View.VISIBLE);
} else {
holder.btnSetDefault.setVisibility(View.VISIBLE);
holder.tvDefault.setVisibility(View.GONE);
}
holder.btnSetDefault.setOnClickListener(v -> {
if (listener != null) {
listener.onAction(card, "setDefault");
}
});
holder.btnDelete.setOnClickListener(v -> {
if (listener != null) {
listener.onAction(card, "delete");
}
});
}
@Override
public int getItemCount() {
return cardList == null ? 0 : cardList.size();
}
static class ViewHolder extends RecyclerView.ViewHolder {
TextView tvBankName, tvCardNo, tvDefault;
Button btnSetDefault, btnDelete;
public ViewHolder(@NonNull View itemView) {
super(itemView);
tvBankName = itemView.findViewById(R.id.tv_bank_name);
tvCardNo = itemView.findViewById(R.id.tv_card_no);
tvDefault = itemView.findViewById(R.id.tv_default);
btnSetDefault = itemView.findViewById(R.id.btn_set_default);
btnDelete = itemView.findViewById(R.id.btn_delete);
}
}
public interface OnItemActionListener {
void onAction(ApiService.BankCardBean card, String action); // 修改参数类型
}
}

@ -0,0 +1,95 @@
package com.payment.asset;
import com.payment.base.BaseActivity;
import com.payment.R;
import com.payment.model.BankCard;
import com.payment.utils.IntentUtils;
import com.payment.utils.ToastUtils;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import java.util.List;
import com.payment.network.ApiService;
import java.util.Arrays;
public class BankCardListActivity extends BaseActivity {
private RecyclerView rvBankCards;
private Button btnAddCard;
private TextView tvEmpty;
private BankCardAdapter adapter;
private AssetViewModel assetViewModel;
@Override
protected int getLayoutId() {
return R.layout.activity_bank_card_list;
}
@Override
protected void initView() {
rvBankCards = findViewById(R.id.rv_bank_cards);
btnAddCard = findViewById(R.id.btn_add_card);
tvEmpty = findViewById(R.id.tv_empty);
// 初始化RecyclerView
rvBankCards.setLayoutManager(new LinearLayoutManager(this));
adapter = new BankCardAdapter(this, null);
rvBankCards.setAdapter(adapter);
}
@Override
protected void initData() {
assetViewModel = new ViewModelProvider(this).get(AssetViewModel.class);
// 获取银行卡列表
assetViewModel.getBankCardList();
// 观察列表结果
assetViewModel.getBankCardListResult().observe(this, resultBean -> {
if (resultBean != null && resultBean.isSuccess()) {
// 从 BankCardListBean 中获取 cards 数组
ApiService.BankCardListBean bankCardListBean = resultBean.getData();
if (bankCardListBean != null && bankCardListBean.getCards() != null) {
// 使用正确的类型ApiService.BankCardBean
List<ApiService.BankCardBean> cardList = java.util.Arrays.asList(bankCardListBean.getCards());
if (!cardList.isEmpty()) {
rvBankCards.setVisibility(View.VISIBLE);
tvEmpty.setVisibility(View.GONE);
adapter.updateData(cardList);
} else {
rvBankCards.setVisibility(View.GONE);
tvEmpty.setVisibility(View.VISIBLE);
}
} else {
rvBankCards.setVisibility(View.GONE);
tvEmpty.setVisibility(View.VISIBLE);
}
} else {
ToastUtils.showToast(this, "获取银行卡列表失败");
}
});
// ... 其他观察代码保持不变
}
@Override
protected void bindEvent() {
// 添加银行卡
btnAddCard.setOnClickListener(v -> {
IntentUtils.jumpTo(this, AddBankCardActivity.class, false);
});
// 列表项点击事件(设置默认卡/删除)
adapter.setOnItemActionListener((card, action) -> {
switch (action) {
case "setDefault":
assetViewModel.setDefaultCard(card.getId());
break;
case "delete":
assetViewModel.deleteBankCard(card.getId());
break;
}
});
}
}

@ -0,0 +1,188 @@
package com.payment.asset;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.Spinner;
import android.widget.TextView;
import androidx.lifecycle.ViewModelProvider;
import com.google.android.material.textfield.TextInputEditText;
import com.payment.base.BaseActivity;
import com.payment.R;
import com.payment.model.BankCard;
import java.util.List;
import com.payment.network.ApiService; // 添加这行
import java.util.Arrays; // 添加这行
public class RechargeWithdrawActivity extends BaseActivity {
private TextView tvTitle;
private TextInputEditText etAmount;
private Spinner spinnerBankCards;
private Button btnConfirm;
private AssetViewModel assetViewModel;
private String type; // "recharge" 或 "withdraw"
@Override
protected int getLayoutId() {
return R.layout.activity_recharge_withdraw;
}
@Override
protected void initView() {
tvTitle = findViewById(R.id.tv_title);
etAmount = findViewById(R.id.et_amount);
spinnerBankCards = findViewById(R.id.spinner_bank_cards);
btnConfirm = findViewById(R.id.btn_confirm);
}
@Override
protected void initData() {
// 获取传递的类型参数
type = getIntent().getStringExtra("type");
// 设置标题
if ("recharge".equals(type)) {
tvTitle.setText("充值");
btnConfirm.setText("确认充值");
} else if ("withdraw".equals(type)) {
tvTitle.setText("提现");
btnConfirm.setText("确认提现");
}
assetViewModel = new ViewModelProvider(this).get(AssetViewModel.class);
// 获取银行卡列表
assetViewModel.getBankCardList();
// 观察银行卡列表结果
assetViewModel.getBankCardListResult().observe(this, resultBean -> {
if (resultBean != null && resultBean.isSuccess()) {
// 从 BankCardListBean 中获取 cards 数组
ApiService.BankCardListBean bankCardListBean = resultBean.getData();
if (bankCardListBean != null && bankCardListBean.getCards() != null) {
// 使用正确的类型
List<ApiService.BankCardBean> cardList = Arrays.asList(bankCardListBean.getCards());
setupBankCardSpinner(cardList);
} else {
showToast("暂无银行卡,请先添加银行卡");
btnConfirm.setEnabled(false);
}
} else {
showToast("获取银行卡列表失败");
}
});
// 观察充值/提现结果
if ("recharge".equals(type)) {
assetViewModel.getRechargeResult().observe(this, resultBean -> {
if (resultBean != null && resultBean.isSuccess()) {
showToast("充值成功");
finish();
} else {
showToast("充值失败");
}
});
} else if ("withdraw".equals(type)) {
assetViewModel.getWithdrawResult().observe(this, resultBean -> {
if (resultBean != null && resultBean.isSuccess()) {
showToast("提现成功");
finish();
} else {
showToast("提现失败");
}
});
}
}
@Override
protected void bindEvent() {
btnConfirm.setOnClickListener(v -> {
String amountStr = etAmount.getText().toString().trim();
if (amountStr.isEmpty()) {
showToast("请输入金额");
return;
}
try {
double amount = Double.parseDouble(amountStr);
if (amount <= 0) {
showToast("金额必须大于0");
return;
}
if (spinnerBankCards.getSelectedItem() == null) {
showToast("请选择银行卡");
return;
}
// 获取选中的银行卡 - 使用正确的类型
ApiService.BankCardBean selectedCard = (ApiService.BankCardBean) spinnerBankCards.getSelectedItem();
String cardId = String.valueOf(selectedCard.getId());
// 调用充值或提现接口 - 传递 double 类型
if ("recharge".equals(type)) {
assetViewModel.recharge(amount, cardId);
} else if ("withdraw".equals(type)) {
assetViewModel.withdraw(amount, cardId);
}
} catch (NumberFormatException e) {
showToast("请输入有效的金额格式");
}
});
}
/**
*
*/
private void setupBankCardSpinner(List<ApiService.BankCardBean> cardList) { // 修改参数类型
if (cardList != null && !cardList.isEmpty()) {
// 创建适配器显示银行名称和卡号后4位
ArrayAdapter<ApiService.BankCardBean> adapter = new ArrayAdapter<ApiService.BankCardBean>(this,
android.R.layout.simple_spinner_item, cardList) {
@Override
public View getView(int position, View convertView, ViewGroup parent) {
TextView view = (TextView) super.getView(position, convertView, parent);
ApiService.BankCardBean card = getItem(position); // 修改类型
if (card != null) {
String displayText = card.getBankName() + " (****" +
getLastFourDigits(card.getCardNo()) + ")";
view.setText(displayText);
}
return view;
}
@Override
public View getDropDownView(int position, View convertView, ViewGroup parent) {
TextView view = (TextView) super.getDropDownView(position, convertView, parent);
ApiService.BankCardBean card = getItem(position); // 修改类型
if (card != null) {
String displayText = card.getBankName() + " (****" +
getLastFourDigits(card.getCardNo()) + ")";
view.setText(displayText);
}
return view;
}
};
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinnerBankCards.setAdapter(adapter);
} else {
showToast("暂无银行卡,请先添加银行卡");
btnConfirm.setEnabled(false);
}
}
/**
* 4
*/
private String getLastFourDigits(String cardNo) {
if (cardNo != null && cardNo.length() >= 4) {
return cardNo.substring(cardNo.length() - 4);
}
return "****";
}
}

@ -0,0 +1,48 @@
package com.payment.base;
import android.os.Bundle;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import com.payment.utils.ToastUtils;
public abstract class BaseActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 设置布局
setContentView(getLayoutId());
// 初始化视图
initView();
// 初始化数据
initData();
// 绑定事件
bindEvent();
}
// 获取布局ID子类实现
protected abstract int getLayoutId();
// 初始化视图(子类按需实现)
protected abstract void initView();
// 初始化数据(子类按需实现)
protected void initData() {}
// 绑定事件(子类按需实现)
protected void bindEvent() {}
// 简化Toast调用
protected void showToast(String msg) {
ToastUtils.showToast(this, msg);
}
// 登录过期处理(统一跳转登录页)
protected void handleLoginExpired() {
showToast("登录已过期,请重新登录");
// 清除Token
com.payment.utils.TokenUtils.clearToken(this);
// 跳转到登录页关闭所有栈内Activity
com.payment.utils.IntentUtils.jumpTo(this, com.payment.user.LoginActivity.class, true);
finishAffinity();
}
}

@ -0,0 +1,40 @@
package com.payment.base;
import androidx.lifecycle.ViewModel;
// 在现有导入基础上补充以下两行
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.LiveData;
import com.payment.utils.ToastUtils;
public abstract class BaseViewModel extends ViewModel {
// 加载状态true-加载中false-加载完成)
private final MutableLiveData<Boolean> loadingState = new MutableLiveData<>();
// 错误信息
private final MutableLiveData<String> errorMsg = new MutableLiveData<>();
// 获取加载状态观察器
public LiveData<Boolean> getLoadingState() {
return loadingState;
}
// 获取错误信息观察器
public LiveData<String> getErrorMsg() {
return errorMsg;
}
// 显示加载
protected void showLoading() {
loadingState.postValue(true);
}
// 隐藏加载
protected void hideLoading() {
loadingState.postValue(false);
}
// 发送错误信息
protected void sendErrorMsg(String msg) {
errorMsg.postValue(msg);
ToastUtils.showToast(com.payment.MyApplication.getContext(), msg);
}
}

@ -0,0 +1,10 @@
package com.payment.base;
public class NetConfig {
// 模拟服务器BaseUrlMockWebServer
public static final String BASE_URL = "http://10.0.2.2:8080/";
// 后续对接真实后端的BaseUrl预留
public static final String REAL_BASE_URL = "http://10.0.2.2:8080/";
// 网络请求超时时间(秒)
public static final int TIMEOUT = 10;
}

@ -0,0 +1,18 @@
package com.payment.base;
public class ResultBean<T> {
private int code; // 响应码200成功其他失败
private String msg; // 响应信息
private T data; // 响应数据
// getter + setter
public int getCode() { return code; }
public void setCode(int code) { this.code = code; }
public String getMsg() { return msg; }
public void setMsg(String msg) { this.msg = msg; }
public T getData() { return data; }
public void setData(T data) { this.data = data; }
// 快捷判断成功方法
public boolean isSuccess() { return code == 200; }
}

@ -0,0 +1,12 @@
package com.payment.model;
public class Balance {
private double balance; // 账户余额
private String updateTime; // 最后更新时间
// getter + setter
public double getBalance() { return balance; }
public void setBalance(double balance) { this.balance = balance; }
public String getUpdateTime() { return updateTime; }
public void setUpdateTime(String updateTime) { this.updateTime = updateTime; }
}

@ -0,0 +1,18 @@
package com.payment.model;
public class BankCard {
private int id; // 银行卡ID
private String cardNo; // 银行卡号(脱敏)
private String bankName; // 银行名称
private boolean isDefault; // 是否默认卡
// getter + setter
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getCardNo() { return cardNo; }
public void setCardNo(String cardNo) { this.cardNo = cardNo; }
public String getBankName() { return bankName; }
public void setBankName(String bankName) { this.bankName = bankName; }
public boolean isDefault() { return isDefault; }
public void setDefault(boolean aDefault) { isDefault = aDefault; }
}

@ -0,0 +1,21 @@
package com.payment.model;
public class User {
private int userId; // 用户ID
private String userName; // 用户名
private String token; // 登录令牌
private String realName; // 真实姓名(实名认证后)
private String idCard; // 身份证号(脱敏)
// getter + setter
public int getUserId() { return userId; }
public void setUserId(int userId) { this.userId = userId; }
public String getUserName() { return userName; }
public void setUserName(String userName) { this.userName = userName; }
public String getToken() { return token; }
public void setToken(String token) { this.token = token; }
public String getRealName() { return realName; }
public void setRealName(String realName) { this.realName = realName; }
public String getIdCard() { return idCard; }
public void setIdCard(String idCard) { this.idCard = idCard; }
}

@ -0,0 +1,464 @@
package com.payment.network;
import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.GET;
import retrofit2.http.POST;
/**
*
*/
public interface ApiService {
// ------------------------ 用户模块接口 ------------------------
/**
*
*/
@POST("user/login")
Call<ResultBean<UserBean>> login(@Body LoginRequest loginRequest);
/**
*
*/
@POST("user/register")
Call<ResultBean<BaseResponse>> register(@Body RegisterRequest registerRequest);
/**
*
*/
@GET("user/info")
Call<ResultBean<UserBean>> getUserInfo();
// ------------------------ 实名认证模块接口 ------------------------
/**
*
*/
@POST("user/auth/submit")
Call<ResultBean<BaseResponse>> submitRealNameAuth(@Body RealNameAuthRequest authRequest);
/**
*
*/
@GET("user/auth/status")
Call<ResultBean<AuditStatusBean>> getAuditStatus();
// ------------------------ 银行卡模块接口 ------------------------
/**
*
*/
@GET("bankCard/list")
Call<ResultBean<BankCardListBean>> getBankCardList();
/**
*
*/
@POST("bankCard/add")
Call<ResultBean<BaseResponse>> addBankCard(@Body AddBankCardRequest cardRequest);
/**
*
*/
@POST("bankCard/setDefault")
Call<ResultBean<BaseResponse>> setDefaultBankCard(@Body DefaultBankCardRequest request);
/**
*
*/
@POST("bankCard/delete")
Call<ResultBean<BaseResponse>> deleteBankCard(@Body DeleteBankCardRequest request);
// ------------------------ 资产模块接口 ------------------------
/**
*
*/
@GET("asset/balance")
Call<ResultBean<BalanceBean>> getBalance();
/**
*
*/
@POST("asset/recharge")
Call<ResultBean<BaseResponse>> recharge(@Body RechargeWithdrawRequest request);
/**
*
*/
@POST("asset/withdraw")
Call<ResultBean<BaseResponse>> withdraw(@Body RechargeWithdrawRequest request);
// ------------------------ 支付模块接口 ------------------------
/**
*
*/
@POST("payment/scan")
Call<ResultBean<BaseResponse>> scanPayment(@Body ScanPaymentRequest request);
/**
*
*/
@GET("payment/qrcode")
Call<ResultBean<QRCodeBean>> generateQRCode();
/**
*
*/
@POST("payment/confirm")
Call<ResultBean<BaseResponse>> confirmPayment(@Body ConfirmPaymentRequest request);
// ------------------------ 出行模块接口 ------------------------
/**
*
*/
@POST("travel/open")
Call<ResultBean<BaseResponse>> openTravelCode();
/**
*
*/
@GET("travel/code")
Call<ResultBean<TravelCodeBean>> getTravelCode();
/**
*
*/
@POST("travel/enter")
Call<ResultBean<BaseResponse>> enterStation(@Body StationRequest request);
/**
*
*/
@POST("travel/exit")
Call<ResultBean<TravelFeeBean>> exitStation(@Body StationRequest request);
// ------------------------ 请求参数实体类 ------------------------
/**
*
*/
class LoginRequest {
private String phone;
private String password;
public LoginRequest(String phone, String password) {
this.phone = phone;
this.password = password;
}
public String getPhone() { return phone; }
public void setPhone(String phone) { this.phone = phone; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
}
/**
*
*/
class RegisterRequest {
private String phone;
private String verifyCode;
private String password;
public RegisterRequest(String phone, String verifyCode, String password) {
this.phone = phone;
this.verifyCode = verifyCode;
this.password = password;
}
public String getPhone() { return phone; }
public void setPhone(String phone) { this.phone = phone; }
public String getVerifyCode() { return verifyCode; }
public void setVerifyCode(String verifyCode) { this.verifyCode = verifyCode; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
}
/**
*
*/
class RealNameAuthRequest {
private String realName;
private String idCard;
public RealNameAuthRequest(String realName, String idCard) {
this.realName = realName;
this.idCard = idCard;
}
public String getRealName() { return realName; }
public void setRealName(String realName) { this.realName = realName; }
public String getIdCard() { return idCard; }
public void setIdCard(String idCard) { this.idCard = idCard; }
}
/**
*
*/
class AddBankCardRequest {
private String cardNo;
private String bankName;
public AddBankCardRequest(String cardNo, String bankName) {
this.cardNo = cardNo;
this.bankName = bankName;
}
public String getCardNo() { return cardNo; }
public void setCardNo(String cardNo) { this.cardNo = cardNo; }
public String getBankName() { return bankName; }
public void setBankName(String bankName) { this.bankName = bankName; }
}
/**
*
*/
class DefaultBankCardRequest {
private String cardId;
public DefaultBankCardRequest(String cardId) {
this.cardId = cardId;
}
public String getCardId() { return cardId; }
public void setCardId(String cardId) { this.cardId = cardId; }
}
/**
*
*/
class DeleteBankCardRequest {
private String cardId;
public DeleteBankCardRequest(String cardId) {
this.cardId = cardId;
}
public String getCardId() { return cardId; }
public void setCardId(String cardId) { this.cardId = cardId; }
}
/**
* /
*/
class RechargeWithdrawRequest {
private String amount;
private String cardId;
public RechargeWithdrawRequest(String amount, String cardId) {
this.amount = amount;
this.cardId = cardId;
}
public String getAmount() { return amount; }
public void setAmount(String amount) { this.amount = amount; }
public String getCardId() { return cardId; }
public void setCardId(String cardId) { this.cardId = cardId; }
}
/**
*
*/
class ScanPaymentRequest {
private String qrCode;
private String amount;
public ScanPaymentRequest(String qrCode, String amount) {
this.qrCode = qrCode;
this.amount = amount;
}
public String getQrCode() { return qrCode; }
public void setQrCode(String qrCode) { this.qrCode = qrCode; }
public String getAmount() { return amount; }
public void setAmount(String amount) { this.amount = amount; }
}
/**
*
*/
class ConfirmPaymentRequest {
private String orderId;
private String password;
public ConfirmPaymentRequest(String orderId, String password) {
this.orderId = orderId;
this.password = password;
}
public String getOrderId() { return orderId; }
public void setOrderId(String orderId) { this.orderId = orderId; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
}
/**
*
*/
class StationRequest {
private String stationId;
private String stationName;
public StationRequest(String stationId, String stationName) {
this.stationId = stationId;
this.stationName = stationName;
}
public String getStationId() { return stationId; }
public void setStationId(String stationId) { this.stationId = stationId; }
public String getStationName() { return stationName; }
public void setStationName(String stationName) { this.stationName = stationName; }
}
// ------------------------ 响应结果实体类 ------------------------
/**
*
*/
class BaseResponse {
private int code;
private String message;
public int getCode() { return code; }
public void setCode(int code) { this.code = code; }
public String getMessage() { return message; }
public void setMessage(String message) { this.message = message; }
}
/**
*
*/
class ResultBean<T> {
private int code;
private String msg;
private T data;
private boolean success;
public int getCode() { return code; }
public void setCode(int code) { this.code = code; }
public String getMsg() { return msg; }
public void setMsg(String msg) { this.msg = msg; }
public T getData() { return data; }
public void setData(T data) { this.data = data; }
public boolean isSuccess() { return success; }
public void setSuccess(boolean success) { this.success = success; }
}
/**
*
*/
class UserBean {
private String userId;
private String phone;
private String realName;
private int authStatus;
private String token;
public String getUserId() { return userId; }
public void setUserId(String userId) { this.userId = userId; }
public String getPhone() { return phone; }
public void setPhone(String phone) { this.phone = phone; }
public String getRealName() { return realName; }
public void setRealName(String realName) { this.realName = realName; }
public int getAuthStatus() { return authStatus; }
public void setAuthStatus(int authStatus) { this.authStatus = authStatus; }
public String getToken() { return token; }
public void setToken(String token) { this.token = token; }
}
/**
*
*/
class AuditStatusBean {
private int status;
private String desc;
public int getStatus() { return status; }
public void setStatus(int status) { this.status = status; }
public String getDesc() { return desc; }
public void setDesc(String desc) { this.desc = desc; }
}
/**
*
*/
class BankCardListBean {
private BankCardBean[] cards;
public BankCardBean[] getCards() { return cards; }
public void setCards(BankCardBean[] cards) { this.cards = cards; }
}
/**
*
*/
class BankCardBean {
private int id;
private String cardNo;
private String bankName;
private boolean isDefault;
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getCardNo() { return cardNo; }
public void setCardNo(String cardNo) { this.cardNo = cardNo; }
public String getBankName() { return bankName; }
public void setBankName(String bankName) { this.bankName = bankName; }
public boolean isDefault() { return isDefault; }
public void setDefault(boolean isDefault) { this.isDefault = isDefault; }
}
/**
*
*/
class BalanceBean {
private String balance;
private String updateTime;
public String getBalance() { return balance; }
public void setBalance(String balance) { this.balance = balance; }
public String getUpdateTime() { return updateTime; }
public void setUpdateTime(String updateTime) { this.updateTime = updateTime; }
}
/**
*
*/
class QRCodeBean {
private String qrCodeUrl;
private String expireTime;
public String getQrCodeUrl() { return qrCodeUrl; }
public void setQrCodeUrl(String qrCodeUrl) { this.qrCodeUrl = qrCodeUrl; }
public String getExpireTime() { return expireTime; }
public void setExpireTime(String expireTime) { this.expireTime = expireTime; }
}
/**
*
*/
class TravelCodeBean {
private String code;
private String status;
private String expireTime;
public String getCode() { return code; }
public void setCode(String code) { this.code = code; }
public String getStatus() { return status; }
public void setStatus(String status) { this.status = status; }
public String getExpireTime() { return expireTime; }
public void setExpireTime(String expireTime) { this.expireTime = expireTime; }
}
/**
*
*/
class TravelFeeBean {
private String fee;
private String distance;
private String duration;
public String getFee() { return fee; }
public void setFee(String fee) { this.fee = fee; }
public String getDistance() { return distance; }
public void setDistance(String distance) { this.distance = distance; }
public String getDuration() { return duration; }
public void setDuration(String duration) { this.duration = duration; }
}
}

@ -0,0 +1,74 @@
package com.payment.network;
import android.util.Log;
import java.io.IOException;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
public class MockServerManager {
private static MockWebServer mockWebServer;
private static final String TAG = "MockServerManager";
// 启动模拟服务器
public static void startMockServer() {
if (mockWebServer == null) {
mockWebServer = new MockWebServer();
try {
mockWebServer.start(8080); // 启动在8080端口
Log.d(TAG, "模拟服务器启动成功,地址:" + mockWebServer.url("/"));
// 配置接口模拟响应
configMockResponses();
} catch (IOException e) {
Log.e(TAG, "模拟服务器启动失败:" + e.getMessage());
e.printStackTrace();
}
}
}
// 配置接口模拟数据
private static void configMockResponses() {
// 1. 登录接口模拟响应
mockWebServer.enqueue(new MockResponse()
.setBody("{\"code\":200,\"msg\":\"登录成功\",\"data\":{\"token\":\"mock_token_123\",\"userName\":\"测试用户\",\"userId\":1001}}")
.setResponseCode(200));
// 2. 实名认证接口模拟响应
mockWebServer.enqueue(new MockResponse()
.setBody("{\"code\":200,\"msg\":\"实名认证提交成功,请等待审核\",\"data\":null}")
.setResponseCode(200));
// 3. 审核状态接口模拟响应
mockWebServer.enqueue(new MockResponse()
.setBody("{\"code\":200,\"msg\":\"success\",\"data\":{\"status\":1,\"desc\":\"审核中\"}}") // 0-未提交1-审核中2-通过3-驳回
.setResponseCode(200));
// 4. 银行卡列表接口模拟响应
mockWebServer.enqueue(new MockResponse()
.setBody("{\"code\":200,\"msg\":\"success\",\"data\":[" +
"{\"id\":1,\"cardNo\":\"622202********1234\",\"bankName\":\"中国工商银行\",\"isDefault\":true}," +
"{\"id\":2,\"cardNo\":\"621700********5678\",\"bankName\":\"中国建设银行\",\"isDefault\":false}]}" +
"")
.setResponseCode(200));
// 5. 余额查询接口模拟响应
mockWebServer.enqueue(new MockResponse()
.setBody("{\"code\":200,\"msg\":\"success\",\"data\":{\"balance\":1234.56}}")
.setResponseCode(200));
}
// 停止模拟服务器
public static void stopMockServer() {
if (mockWebServer != null) {
try {
mockWebServer.shutdown();
mockWebServer = null;
Log.d(TAG, "模拟服务器已停止");
} catch (IOException e) {
Log.e(TAG, "模拟服务器停止失败:" + e.getMessage());
e.printStackTrace();
}
}
}
}

@ -0,0 +1,137 @@
package com.payment.network;
import android.content.Context; // 新增:为了修复报错引入的
import com.payment.base.NetConfig;
import java.util.concurrent.TimeUnit;
import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.MediaType;
import okhttp3.ResponseBody;
import okhttp3.Protocol;
import java.io.IOException;
public class RetrofitManager {
private static RetrofitManager instance;
private Retrofit retrofit;
private RetrofitManager() {
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.BODY);
// ★★★ 核心安卓A 的假后端数据中心 ★★★
Interceptor mockInterceptor = new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
String url = request.url().toString();
String responseJson = "";
// ------------- 模块一:用户中心 (Week 9) -------------
if (url.contains("/user/login")) {
responseJson = "{\"code\": 200, \"msg\": \"登录成功\", \"data\": {\"token\": \"mock_token_123456\", \"username\": \"测试用户A\", \"realNameStatus\": 1}}";
}
else if (url.contains("/user/register")) {
responseJson = "{\"code\": 200, \"msg\": \"注册成功,请登录\", \"data\": null}";
}
else if (url.contains("/user/info")) {
responseJson = "{\"code\": 200, \"msg\": \"成功\", \"data\": {\"username\": \"测试用户A\", \"auditStatus\": 1}}";
}
else if (url.contains("/user/realname")) {
responseJson = "{\"code\": 200, \"msg\": \"提交成功,等待审核\", \"data\": null}";
}
// ------------- 模块二:资金与卡包 (Week 10) -------------
else if (url.contains("/asset/balance")) {
responseJson = "{\"code\": 200, \"msg\": \"成功\", \"data\": {\"balance\": 10086.50}}";
}
else if (url.contains("/asset/bankcard/list")) {
responseJson = "{\"code\": 200, \"msg\": \"成功\", \"data\": [" +
"{\"id\": 1, \"bankName\": \"中国工商银行\", \"cardNo\": \"6222021001112222\", \"cardType\": \"储蓄卡\"}," +
"{\"id\": 2, \"bankName\": \"招商银行\", \"cardNo\": \"6225888899996666\", \"cardType\": \"信用卡\"}" +
"]}";
}
else if (url.contains("/asset/bankcard/add")) {
responseJson = "{\"code\": 200, \"msg\": \"绑卡成功\", \"data\": null}";
}
else if (url.contains("/asset/recharge") || url.contains("/asset/withdraw")) {
responseJson = "{\"code\": 200, \"msg\": \"操作成功\", \"data\": null}";
}
// 在 mockInterceptor 的 if-else 链中添加:
// ------------- 支付模块接口模拟 -------------
else if (url.contains("/payment/scan")) {
responseJson = "{\"code\": 200, \"msg\": \"扫码成功\", \"data\": {\"orderId\": \"ORDER_123456\", \"amount\": \"50.00\"}}";
}
else if (url.contains("/payment/qrcode")) {
responseJson = "{\"code\": 200, \"msg\": \"成功\", \"data\": {\"qrCodeUrl\": \"https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=PAY_123456\", \"expireTime\": \"2025-12-31 23:59:59\"}}";
}
else if (url.contains("/payment/confirm")) {
responseJson = "{\"code\": 200, \"msg\": \"支付成功\", \"data\": null}";
}
// ------------- 出行模块接口模拟 -------------
else if (url.contains("/travel/open")) {
responseJson = "{\"code\": 200, \"msg\": \"出行码开通成功\", \"data\": null}";
}
else if (url.contains("/travel/code")) {
responseJson = "{\"code\": 200, \"msg\": \"成功\", \"data\": {\"code\": \"TRAVEL_888888\", \"status\": \"active\", \"expireTime\": \"2025-12-31 23:59:59\"}}";
}
else if (url.contains("/travel/enter")) {
responseJson = "{\"code\": 200, \"msg\": \"进站成功\", \"data\": null}";
}
else if (url.contains("/travel/exit")) {
responseJson = "{\"code\": 200, \"msg\": \"出站成功\", \"data\": {\"fee\": \"5.00\", \"distance\": \"8.5\", \"duration\": \"25\"}}";
}
if (!responseJson.isEmpty()) {
try { Thread.sleep(500); } catch (InterruptedException e) {}
return new Response.Builder()
.code(200)
.message("OK")
.request(request)
.protocol(Protocol.HTTP_1_1)
.body(ResponseBody.create(MediaType.parse("application/json"), responseJson))
.addHeader("content-type", "application/json")
.build();
}
return chain.proceed(request);
}
};
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(logging)
.addInterceptor(mockInterceptor)
.connectTimeout(15, TimeUnit.SECONDS)
.build();
retrofit = new Retrofit.Builder()
.baseUrl(NetConfig.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.build();
}
public static RetrofitManager getInstance() {
if (instance == null) {
synchronized (RetrofitManager.class) {
if (instance == null) instance = new RetrofitManager();
}
}
return instance;
}
public <T> T create(Class<T> service) {
return retrofit.create(service);
}
// ★★★★★ 修复报错的关键方法 ★★★★★
// 这个方法就是为了兼容 AssetViewModel 里的旧代码
// 虽然参数里传了 Context但其实我们不需要用到它直接忽略即可
public static ApiService getApiService(Context context) {
return getInstance().create(ApiService.class);
}
}

@ -0,0 +1,42 @@
package com.payment.network;
import android.content.Context;
import androidx.annotation.NonNull;
import com.payment.utils.TokenUtils;
import java.io.IOException;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
/**
* TokenToken
*/
public class TokenInterceptor implements Interceptor {
private Context context;
// 构造方法传入上下文用于获取Token
public TokenInterceptor(Context context) {
this.context = context.getApplicationContext(); // 使用全局上下文,避免内存泄漏
}
@NonNull
@Override
public Response intercept(@NonNull Chain chain) throws IOException {
// 1. 获取原始请求
Request originalRequest = chain.request();
// 2. 从SP中获取Token需提前实现TokenUtils工具类
String token = TokenUtils.getToken(context);
// 3. 若Token存在添加到请求头否则保持原请求
Request.Builder requestBuilder = originalRequest.newBuilder();
if (token != null && !token.isEmpty()) {
requestBuilder.addHeader("Authorization", "Bearer " + token); // JWT标准格式也可根据后端要求调整
requestBuilder.addHeader("token", token); // 若后端直接用token字段接收补充此头
}
// 4. 构建新请求并继续执行
Request newRequest = requestBuilder.build();
return chain.proceed(newRequest);
}
}

@ -0,0 +1,87 @@
package com.payment.ui;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentTransaction;
import android.os.Bundle;
import android.widget.FrameLayout;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import com.payment.R;
import com.payment.ui.fragment.HomeFragment;
import com.payment.ui.fragment.TravelCodeFragment;
import com.payment.ui.fragment.BillFragment;
import com.payment.ui.fragment.MineFragment;
public class MainActivity extends AppCompatActivity {
private FrameLayout flContent;
private RadioGroup rgBottomNav;
private RadioButton rbHome, rbTravel, rbBill, rbMine;
// 碎片实例
private Fragment homeFragment, travelFragment, billFragment, mineFragment;
private Fragment currentFragment;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 初始化视图
initView();
// 初始化碎片
initFragment();
// 底部导航点击事件
setNavClickListener();
}
private void initView() {
flContent = findViewById(R.id.fl_content);
rgBottomNav = findViewById(R.id.rg_bottom_nav);
rbHome = findViewById(R.id.rb_home);
rbTravel = findViewById(R.id.rb_travel);
rbBill = findViewById(R.id.rb_bill);
rbMine = findViewById(R.id.rb_mine);
}
private void initFragment() {
homeFragment = new HomeFragment();
travelFragment = new TravelCodeFragment();
billFragment = new BillFragment();
mineFragment = new MineFragment();
// 默认显示首页
currentFragment = homeFragment;
getSupportFragmentManager().beginTransaction()
.add(R.id.fl_content, homeFragment)
.commit();
}
private void setNavClickListener() {
rgBottomNav.setOnCheckedChangeListener((group, checkedId) -> {
Fragment targetFragment = null;
// 用if-else替代switch-case
if (checkedId == R.id.rb_home) {
targetFragment = homeFragment;
} else if (checkedId == R.id.rb_travel) {
targetFragment = travelFragment;
} else if (checkedId == R.id.rb_bill) {
targetFragment = billFragment;
} else if (checkedId == R.id.rb_mine) {
targetFragment = mineFragment;
}
// 切换碎片
if (targetFragment != null && targetFragment != currentFragment) {
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
if (!targetFragment.isAdded()) {
transaction.add(R.id.fl_content, targetFragment);
}
transaction.hide(currentFragment).show(targetFragment).commit();
currentFragment = targetFragment;
}
});
}
}

@ -0,0 +1,22 @@
package com.payment.ui.fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import com.payment.R;
public class BillFragment extends Fragment {
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_bill, container, false);
TextView tvTip = view.findViewById(R.id.tv_tip);
tvTip.setText("账单功能暂未开放,敬请期待");
return view;
}
}

@ -0,0 +1,68 @@
package com.payment.ui.fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import com.payment.R;
import com.payment.utils.IntentUtils;
import com.payment.asset.BalanceActivity;
import com.payment.asset.BankCardListActivity;
public class HomeFragment extends Fragment {
private Button btnBalance, btnBankCard, btnRecharge, btnWithdraw;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_home, container, false);
initView(view);
bindEvent();
return view;
}
private void initView(View view) {
btnBalance = view.findViewById(R.id.btn_balance);
btnBankCard = view.findViewById(R.id.btn_bank_card);
btnRecharge = view.findViewById(R.id.btn_recharge);
btnWithdraw = view.findViewById(R.id.btn_withdraw);
}
private void bindEvent() {
// 余额查询
btnBalance.setOnClickListener(v -> {
if (getActivity() != null) {
IntentUtils.jumpTo(getActivity(), BalanceActivity.class, false);
}
});
// 银行卡管理
btnBankCard.setOnClickListener(v -> {
if (getActivity() != null) {
IntentUtils.jumpTo(getActivity(), BankCardListActivity.class, false);
}
});
// 充值
btnRecharge.setOnClickListener(v -> {
if (getActivity() != null) {
Bundle bundle = new Bundle();
bundle.putString("type", "recharge");
IntentUtils.jumpToWithBundle(getActivity(), com.payment.asset.RechargeWithdrawActivity.class, bundle, false);
}
});
// 提现
btnWithdraw.setOnClickListener(v -> {
if (getActivity() != null) {
Bundle bundle = new Bundle();
bundle.putString("type", "withdraw");
IntentUtils.jumpToWithBundle(getActivity(), com.payment.asset.RechargeWithdrawActivity.class, bundle, false);
}
});
}
}

@ -0,0 +1,69 @@
package com.payment.ui.fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import com.payment.R;
import com.payment.utils.IntentUtils;
import com.payment.utils.TokenUtils;
import com.payment.user.AuditStatusActivity;
import com.payment.user.LoginActivity;
import com.payment.user.RealNameAuthActivity;
public class MineFragment extends Fragment {
private TextView tvUserName;
private Button btnRealName, btnAuditStatus, btnLogout;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_mine, container, false);
initView(view);
initData();
bindEvent();
return view;
}
private void initView(View view) {
tvUserName = view.findViewById(R.id.tv_user_name);
btnRealName = view.findViewById(R.id.btn_real_name);
btnAuditStatus = view.findViewById(R.id.btn_audit_status);
btnLogout = view.findViewById(R.id.btn_logout);
}
private void initData() {
// 模拟显示用户名实际应从登录成功后的User对象获取
tvUserName.setText("测试用户");
}
private void bindEvent() {
// 实名认证
btnRealName.setOnClickListener(v -> {
if (getActivity() != null) {
IntentUtils.jumpTo(getActivity(), RealNameAuthActivity.class, false);
}
});
// 审核状态
btnAuditStatus.setOnClickListener(v -> {
if (getActivity() != null) {
IntentUtils.jumpTo(getActivity(), AuditStatusActivity.class, false);
}
});
// 退出登录
btnLogout.setOnClickListener(v -> {
if (getActivity() != null) {
TokenUtils.clearToken(getActivity());
IntentUtils.jumpTo(getActivity(), LoginActivity.class, true);
getActivity().finishAffinity();
}
});
}
}

@ -0,0 +1,22 @@
package com.payment.ui.fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import com.payment.R;
public class TravelCodeFragment extends Fragment {
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_travel_code, container, false);
TextView tvTip = view.findViewById(R.id.tv_tip);
tvTip.setText("出行码功能暂未开放,敬请期待");
return view;
}
}

@ -0,0 +1,73 @@
package com.payment.user;
import com.payment.base.BaseActivity;
import com.payment.R;
import com.payment.network.ApiService;
import androidx.lifecycle.ViewModelProvider;
import android.os.Bundle;
import android.widget.TextView;
import android.widget.Toast;
public class AuditStatusActivity extends BaseActivity {
private TextView tvStatus, tvDesc;
private UserViewModel userViewModel;
@Override
protected int getLayoutId() {
return R.layout.activity_audit_status;
}
@Override
protected void initView() {
tvStatus = findViewById(R.id.tv_status);
tvDesc = findViewById(R.id.tv_desc);
}
@Override
protected void initData() {
userViewModel = new ViewModelProvider(this).get(UserViewModel.class);
// 查询审核状态
userViewModel.getAuditStatus();
// 观察审核状态结果
userViewModel.getAuditStatusResult().observe(this, resultBean -> {
if (resultBean != null && resultBean.isSuccess()) {
ApiService.AuditStatusBean statusBean = resultBean.getData();
updateStatusUI(statusBean.getStatus(), statusBean.getDesc());
} else {
showToast("查询审核状态失败");
}
});
}
// 更新审核状态UI
private void updateStatusUI(int status, String desc) {
tvStatus.setText(getStatusText(status));
tvDesc.setText(desc);
// 根据状态设置文字颜色
switch (status) {
case 0: // 未提交
tvStatus.setTextColor(getResources().getColor(R.color.gray));
break;
case 1: // 审核中
tvStatus.setTextColor(getResources().getColor(R.color.blue));
break;
case 2: // 审核通过
tvStatus.setTextColor(getResources().getColor(R.color.green));
break;
case 3: // 驳回
tvStatus.setTextColor(getResources().getColor(R.color.red));
break;
}
}
// 状态文字映射
private String getStatusText(int status) {
switch (status) {
case 0: return "未提交实名认证";
case 1: return "审核中";
case 2: return "审核通过";
case 3: return "审核驳回";
default: return "未知状态";
}
}
}

@ -0,0 +1,78 @@
package com.payment.user;
import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.ViewModelProvider;
import android.os.Bundle;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import com.payment.R;
import com.payment.utils.IntentUtils;
import com.payment.utils.TokenUtils;
import com.payment.ui.MainActivity;
public class LoginActivity extends AppCompatActivity {
private EditText etPhone, etPassword;
private Button btnLogin, btnRegister;
private UserViewModel userViewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
// 移除或注释掉这行 - Mock服务器会在需要时自动启动
// MockServerManager.startMockServer();
// 初始化视图
initView();
// 初始化ViewModel
initViewModel();
// 绑定点击事件
setClickListeners();
}
private void initView() {
etPhone = findViewById(R.id.et_phone);
etPassword = findViewById(R.id.et_password);
btnLogin = findViewById(R.id.btn_login);
btnRegister = findViewById(R.id.btn_register);
}
private void initViewModel() {
userViewModel = new ViewModelProvider(this).get(UserViewModel.class);
// 观察登录结果
userViewModel.getLoginResult().observe(this, resultBean -> {
if (resultBean != null && resultBean.isSuccess()) {
// 保存Token
TokenUtils.saveToken(this, resultBean.getData().getToken());
Toast.makeText(this, "登录成功", Toast.LENGTH_SHORT).show();
// 跳转到主页面
IntentUtils.jumpTo(this, MainActivity.class, true);
} else {
Toast.makeText(this, "登录失败:" + (resultBean != null ? resultBean.getMsg() : "网络异常"), Toast.LENGTH_SHORT).show();
}
});
}
private void setClickListeners() {
// 登录按钮
btnLogin.setOnClickListener(v -> {
String phone = etPhone.getText().toString().trim();
String password = etPassword.getText().toString().trim();
if (phone.isEmpty() || password.isEmpty()) {
Toast.makeText(this, "手机号和密码不能为空", Toast.LENGTH_SHORT).show();
return;
}
// 调用ViewModel登录方法
userViewModel.login(phone, password);
});
// 注册按钮
btnRegister.setOnClickListener(v -> {
IntentUtils.jumpTo(this, RegisterActivity.class, false);
});
}
}

@ -0,0 +1,61 @@
package com.payment.user;
import com.payment.base.BaseActivity;
import com.payment.R;
import com.payment.utils.ToastUtils;
import androidx.lifecycle.ViewModelProvider;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
public class RealNameAuthActivity extends BaseActivity {
private EditText etRealName, etIdCard;
private Button btnSubmit;
private UserViewModel userViewModel;
@Override
protected int getLayoutId() {
return R.layout.activity_real_name_auth;
}
@Override
protected void initView() {
etRealName = findViewById(R.id.et_real_name);
etIdCard = findViewById(R.id.et_id_card);
btnSubmit = findViewById(R.id.btn_submit);
}
@Override
protected void initData() {
userViewModel = new ViewModelProvider(this).get(UserViewModel.class);
// 观察实名认证结果
userViewModel.getRealNameResult().observe(this, resultBean -> {
if (resultBean != null && resultBean.isSuccess()) {
showToast(resultBean.getMsg());
finish(); // 返回上一页
} else {
showToast("提交失败:" + (resultBean != null ? resultBean.getMsg() : "网络异常"));
}
});
}
@Override
protected void bindEvent() {
btnSubmit.setOnClickListener(v -> {
String realName = etRealName.getText().toString().trim();
String idCard = etIdCard.getText().toString().trim();
if (realName.isEmpty() || idCard.isEmpty()) {
showToast("请完善实名认证信息");
return;
}
if (idCard.length() != 18) {
showToast("请输入正确的身份证号");
return;
}
// 提交实名认证
userViewModel.submitRealNameAuth(realName, idCard);
});
}
}

@ -0,0 +1,79 @@
package com.payment.user;
import com.payment.base.BaseActivity;
import com.payment.R;
import com.payment.utils.IntentUtils;
import com.payment.utils.ToastUtils;
import androidx.lifecycle.ViewModelProvider;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
public class RegisterActivity extends BaseActivity {
private EditText etPhone, etVerifyCode, etPassword;
private Button btnGetCode, btnRegister;
private UserViewModel userViewModel;
@Override
protected int getLayoutId() {
return R.layout.activity_register;
}
@Override
protected void initView() {
etPhone = findViewById(R.id.et_phone);
etVerifyCode = findViewById(R.id.et_verify_code);
etPassword = findViewById(R.id.et_password);
btnGetCode = findViewById(R.id.btn_get_code);
btnRegister = findViewById(R.id.btn_register);
}
@Override
protected void initData() {
userViewModel = new ViewModelProvider(this).get(UserViewModel.class);
// 观察注册结果
userViewModel.getRegisterResult().observe(this, resultBean -> {
if (resultBean != null && resultBean.isSuccess()) {
ToastUtils.showToast(this, "注册成功,请登录");
IntentUtils.jumpTo(this, LoginActivity.class, true);
} else {
ToastUtils.showToast(this, "注册失败:" + (resultBean != null ? resultBean.getMsg() : "网络异常"));
}
});
}
@Override
protected void bindEvent() {
// 获取验证码(模拟,无需真实校验)
btnGetCode.setOnClickListener(v -> {
String phone = etPhone.getText().toString().trim();
if (phone.isEmpty()) {
showToast("请输入手机号");
return;
}
showToast("验证码已发送至:" + phone);
// 模拟倒计时60秒
btnGetCode.setEnabled(false);
new android.os.Handler().postDelayed(() -> btnGetCode.setEnabled(true), 60000);
});
// 注册按钮
btnRegister.setOnClickListener(v -> {
String phone = etPhone.getText().toString().trim();
String verifyCode = etVerifyCode.getText().toString().trim();
String password = etPassword.getText().toString().trim();
if (phone.isEmpty() || verifyCode.isEmpty() || password.isEmpty()) {
showToast("请完善所有信息");
return;
}
if (password.length() < 6) {
showToast("密码长度不能少于6位");
return;
}
// 调用注册接口
userViewModel.register(phone, verifyCode, password);
});
}
}

@ -0,0 +1,186 @@
package com.payment.user;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import com.payment.MyApplication;
import com.payment.network.ApiService;
import com.payment.network.RetrofitManager;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
/**
* ViewModel
*/
public class UserViewModel extends ViewModel {
// ========== 登录相关 ==========
private MutableLiveData<ApiService.ResultBean<ApiService.UserBean>> loginResult = new MutableLiveData<>();
public LiveData<ApiService.ResultBean<ApiService.UserBean>> getLoginResult() {
return loginResult;
}
public void login(String phone, String password) {
ApiService apiService = RetrofitManager.getApiService(MyApplication.getContext());
ApiService.LoginRequest request = new ApiService.LoginRequest(phone, password);
apiService.login(request).enqueue(new Callback<ApiService.ResultBean<ApiService.UserBean>>() {
@Override
public void onResponse(Call<ApiService.ResultBean<ApiService.UserBean>> call, Response<ApiService.ResultBean<ApiService.UserBean>> response) {
if (response.isSuccessful() && response.body() != null) {
loginResult.postValue(response.body());
} else {
ApiService.ResultBean<ApiService.UserBean> errorResult = new ApiService.ResultBean<>();
errorResult.setSuccess(false);
errorResult.setMsg("登录失败:" + (response.message() != null ? response.message() : "服务器错误"));
loginResult.postValue(errorResult);
}
}
@Override
public void onFailure(Call<ApiService.ResultBean<ApiService.UserBean>> call, Throwable t) {
ApiService.ResultBean<ApiService.UserBean> errorResult = new ApiService.ResultBean<>();
errorResult.setSuccess(false);
errorResult.setMsg("网络错误:" + t.getMessage());
loginResult.postValue(errorResult);
}
});
}
// ========== 注册相关 ==========
private MutableLiveData<ApiService.ResultBean<ApiService.BaseResponse>> registerResult = new MutableLiveData<>();
public LiveData<ApiService.ResultBean<ApiService.BaseResponse>> getRegisterResult() {
return registerResult;
}
public void register(String phone, String verifyCode, String password) {
ApiService apiService = RetrofitManager.getApiService(MyApplication.getContext());
ApiService.RegisterRequest request = new ApiService.RegisterRequest(phone, verifyCode, password);
apiService.register(request).enqueue(new Callback<ApiService.ResultBean<ApiService.BaseResponse>>() {
@Override
public void onResponse(Call<ApiService.ResultBean<ApiService.BaseResponse>> call, Response<ApiService.ResultBean<ApiService.BaseResponse>> response) {
if (response.isSuccessful() && response.body() != null) {
registerResult.postValue(response.body());
} else {
ApiService.ResultBean<ApiService.BaseResponse> errorResult = new ApiService.ResultBean<>();
errorResult.setSuccess(false);
errorResult.setMsg("注册失败:" + response.message());
registerResult.postValue(errorResult);
}
}
@Override
public void onFailure(Call<ApiService.ResultBean<ApiService.BaseResponse>> call, Throwable t) {
ApiService.ResultBean<ApiService.BaseResponse> errorResult = new ApiService.ResultBean<>();
errorResult.setSuccess(false);
errorResult.setMsg("网络错误:" + t.getMessage());
registerResult.postValue(errorResult);
}
});
}
// ========== 实名认证相关 ==========
private MutableLiveData<ApiService.ResultBean<ApiService.BaseResponse>> realNameResult = new MutableLiveData<>();
public LiveData<ApiService.ResultBean<ApiService.BaseResponse>> getRealNameResult() {
return realNameResult;
}
public void submitRealNameAuth(String realName, String idCard) {
ApiService apiService = RetrofitManager.getApiService(MyApplication.getContext());
ApiService.RealNameAuthRequest request = new ApiService.RealNameAuthRequest(realName, idCard);
apiService.submitRealNameAuth(request).enqueue(new Callback<ApiService.ResultBean<ApiService.BaseResponse>>() {
@Override
public void onResponse(Call<ApiService.ResultBean<ApiService.BaseResponse>> call, Response<ApiService.ResultBean<ApiService.BaseResponse>> response) {
if (response.isSuccessful() && response.body() != null) {
realNameResult.postValue(response.body());
} else {
ApiService.ResultBean<ApiService.BaseResponse> errorResult = new ApiService.ResultBean<>();
errorResult.setSuccess(false);
errorResult.setMsg("提交失败:" + (response.message() != null ? response.message() : "服务器错误"));
realNameResult.postValue(errorResult);
}
}
@Override
public void onFailure(Call<ApiService.ResultBean<ApiService.BaseResponse>> call, Throwable t) {
ApiService.ResultBean<ApiService.BaseResponse> errorResult = new ApiService.ResultBean<>();
errorResult.setSuccess(false);
errorResult.setMsg("网络错误:" + t.getMessage());
realNameResult.postValue(errorResult);
}
});
}
// ========== 审核状态查询相关 ==========
private MutableLiveData<ApiService.ResultBean<ApiService.AuditStatusBean>> auditStatusResult = new MutableLiveData<>();
public LiveData<ApiService.ResultBean<ApiService.AuditStatusBean>> getAuditStatusResult() {
return auditStatusResult;
}
public void getAuditStatus() {
ApiService apiService = RetrofitManager.getApiService(MyApplication.getContext());
apiService.getAuditStatus().enqueue(new Callback<ApiService.ResultBean<ApiService.AuditStatusBean>>() {
@Override
public void onResponse(Call<ApiService.ResultBean<ApiService.AuditStatusBean>> call, Response<ApiService.ResultBean<ApiService.AuditStatusBean>> response) {
if (response.isSuccessful() && response.body() != null) {
auditStatusResult.postValue(response.body());
} else {
ApiService.ResultBean<ApiService.AuditStatusBean> errorResult = new ApiService.ResultBean<>();
errorResult.setSuccess(false);
errorResult.setMsg("查询失败:" + response.message());
auditStatusResult.postValue(errorResult);
}
}
@Override
public void onFailure(Call<ApiService.ResultBean<ApiService.AuditStatusBean>> call, Throwable t) {
ApiService.ResultBean<ApiService.AuditStatusBean> errorResult = new ApiService.ResultBean<>();
errorResult.setSuccess(false);
errorResult.setMsg("网络错误:" + t.getMessage());
auditStatusResult.postValue(errorResult);
}
});
}
// ========== 用户信息查询相关 ==========
private MutableLiveData<ApiService.ResultBean<ApiService.UserBean>> userInfoResult = new MutableLiveData<>();
public LiveData<ApiService.ResultBean<ApiService.UserBean>> getUserInfoResult() {
return userInfoResult;
}
public void getUserInfo() {
ApiService apiService = RetrofitManager.getApiService(MyApplication.getContext());
apiService.getUserInfo().enqueue(new Callback<ApiService.ResultBean<ApiService.UserBean>>() {
@Override
public void onResponse(Call<ApiService.ResultBean<ApiService.UserBean>> call, Response<ApiService.ResultBean<ApiService.UserBean>> response) {
if (response.isSuccessful() && response.body() != null) {
userInfoResult.postValue(response.body());
} else {
ApiService.ResultBean<ApiService.UserBean> errorResult = new ApiService.ResultBean<>();
errorResult.setSuccess(false);
errorResult.setMsg("获取用户信息失败:" + response.message());
userInfoResult.postValue(errorResult);
}
}
@Override
public void onFailure(Call<ApiService.ResultBean<ApiService.UserBean>> call, Throwable t) {
ApiService.ResultBean<ApiService.UserBean> errorResult = new ApiService.ResultBean<>();
errorResult.setSuccess(false);
errorResult.setMsg("网络错误:" + t.getMessage());
userInfoResult.postValue(errorResult);
}
});
}
}

@ -0,0 +1,38 @@
package com.payment.utils;
import android.content.Context;
import android.content.Intent;
public class IntentUtils {
/**
*
* @param context
* @param targetClass Activity
* @param isFinish Activity
*/
public static void jumpTo(Context context, Class<?> targetClass, boolean isFinish) {
Intent intent = new Intent(context, targetClass);
context.startActivity(intent);
if (isFinish && context instanceof android.app.Activity) {
((android.app.Activity) context).finish();
}
}
/**
*
* @param context
* @param targetClass Activity
* @param bundle
* @param isFinish Activity
*/
public static void jumpToWithBundle(Context context, Class<?> targetClass, android.os.Bundle bundle, boolean isFinish) {
Intent intent = new Intent(context, targetClass);
if (bundle != null) {
intent.putExtras(bundle);
}
context.startActivity(intent);
if (isFinish && context instanceof android.app.Activity) {
((android.app.Activity) context).finish();
}
}
}

@ -0,0 +1,30 @@
package com.payment.utils;
import android.content.Context;
import android.widget.Toast;
public class ToastUtils {
private static Toast toast;
// 显示短吐司
public static void showToast(Context context, String msg) {
if (toast == null) {
toast = Toast.makeText(context.getApplicationContext(), msg, Toast.LENGTH_SHORT);
} else {
toast.setText(msg);
toast.setDuration(Toast.LENGTH_SHORT);
}
toast.show();
}
// 显示长吐司
public static void showLongToast(Context context, String msg) {
if (toast == null) {
toast = Toast.makeText(context.getApplicationContext(), msg, Toast.LENGTH_LONG);
} else {
toast.setText(msg);
toast.setDuration(Toast.LENGTH_LONG);
}
toast.show();
}
}

@ -0,0 +1,32 @@
package com.payment.utils;
import android.content.Context;
import android.content.SharedPreferences;
public class TokenUtils {
private static final String SP_NAME = "payment_token";
private static final String KEY_TOKEN = "token";
// 保存Token
public static void saveToken(Context context, String token) {
SharedPreferences sp = context.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);
sp.edit().putString(KEY_TOKEN, token).apply();
}
// 获取Token
public static String getToken(Context context) {
SharedPreferences sp = context.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);
return sp.getString(KEY_TOKEN, "");
}
// 清除Token退出登录
public static void clearToken(Context context) {
SharedPreferences sp = context.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);
sp.edit().remove(KEY_TOKEN).apply();
}
// 判断Token是否存在登录状态
public static boolean isLogin(Context context) {
return !getToken(context).isEmpty();
}
}

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="添加银行卡"
android:textSize="20sp"
android:textStyle="bold"
android:gravity="center"
android:layout_marginBottom="24dp" />
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/et_card_no"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入银行卡号"
android:inputType="number" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/et_bank_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入银行名称" />
</com.google.android.material.textfield.TextInputLayout>
<Button
android:id="@+id/btn_submit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="添加银行卡"
android:textSize="16sp" />
</LinearLayout>

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp"
android:gravity="center">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="审核状态"
android:textSize="20sp"
android:textStyle="bold"
android:layout_marginBottom="32dp" />
<TextView
android:id="@+id/tv_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="未知状态"
android:textSize="18sp"
android:layout_marginBottom="16dp" />
<TextView
android:id="@+id/tv_desc"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="状态描述"
android:textSize="14sp"
android:textColor="#666" />
</LinearLayout>

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:padding="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="账户余额"
android:textSize="18sp"
android:layout_marginBottom="24dp" />
<TextView
android:id="@+id/tv_balance"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="¥ 0.00"
android:textSize="32sp"
android:textStyle="bold" />
</LinearLayout>

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/btn_add_card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="添加银行卡"
android:layout_margin="16dp" />
<TextView
android:id="@+id/tv_empty"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="暂无银行卡"
android:gravity="center"
android:textSize="16sp"
android:visibility="gone" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_bank_cards"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>

@ -0,0 +1,62 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="32dp"
android:gravity="center">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="用户登录"
android:textSize="24sp"
android:textStyle="bold"
android:layout_marginBottom="48dp" />
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/et_phone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入手机号"
android:inputType="phone" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="32dp">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/et_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入密码"
android:inputType="textPassword" />
</com.google.android.material.textfield.TextInputLayout>
<Button
android:id="@+id/btn_login"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="登录"
android:textSize="16sp"
android:layout_marginBottom="16dp" />
<Button
android:id="@+id/btn_register"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="注册"
android:textSize="16sp"
android:backgroundTint="@android:color/transparent"
android:textColor="@color/blue" />
</LinearLayout>

@ -0,0 +1,68 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<!-- 内容区域 -->
<FrameLayout
android:id="@+id/fl_content"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<!-- 底部导航 -->
<RadioGroup
android:id="@+id/rg_bottom_nav"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:background="#F5F5F5">
<RadioButton
android:id="@+id/rb_home"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="首页"
android:button="@null"
android:drawableTop="@android:drawable/ic_menu_agenda"
android:gravity="center"
android:padding="8dp" />
<RadioButton
android:id="@+id/rb_travel"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="出行码"
android:button="@null"
android:drawableTop="@android:drawable/ic_menu_directions"
android:gravity="center"
android:padding="8dp" />
<RadioButton
android:id="@+id/rb_bill"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="账单"
android:button="@null"
android:drawableTop="@android:drawable/ic_menu_manage"
android:gravity="center"
android:padding="8dp" />
<RadioButton
android:id="@+id/rb_mine"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="我的"
android:button="@null"
android:drawableTop="@android:drawable/ic_menu_my_calendar"
android:gravity="center"
android:padding="8dp" />
</RadioGroup>
</LinearLayout>

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="实名认证"
android:textSize="20sp"
android:textStyle="bold"
android:gravity="center"
android:layout_marginBottom="24dp" />
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/et_real_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入真实姓名" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/et_id_card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入身份证号"
android:inputType="number" />
</com.google.android.material.textfield.TextInputLayout>
<Button
android:id="@+id/btn_submit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="提交认证"
android:textSize="16sp" />
</LinearLayout>

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:id="@+id/tv_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="充值"
android:textSize="20sp"
android:textStyle="bold"
android:gravity="center"
android:layout_marginBottom="24dp" />
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/et_amount"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入金额"
android:inputType="numberDecimal" />
</com.google.android.material.textfield.TextInputLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="选择银行卡"
android:textSize="16sp"
android:layout_marginBottom="8dp" />
<Spinner
android:id="@+id/spinner_bank_cards"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp" />
<Button
android:id="@+id/btn_confirm"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="确认"
android:textSize="16sp" />
</LinearLayout>

@ -0,0 +1,75 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="32dp"
android:gravity="center">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="用户注册"
android:textSize="24sp"
android:textStyle="bold"
android:layout_marginBottom="48dp" />
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/et_phone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入手机号"
android:inputType="phone" />
</com.google.android.material.textfield.TextInputLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginBottom="16dp">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/et_verify_code"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:hint="请输入验证码"
android:layout_marginEnd="8dp" />
<Button
android:id="@+id/btn_get_code"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="获取验证码"
android:textSize="12sp" />
</LinearLayout>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="32dp">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/et_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入密码"
android:inputType="textPassword" />
</com.google.android.material.textfield.TextInputLayout>
<Button
android:id="@+id/btn_register"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="注册"
android:textSize="16sp" />
</LinearLayout>

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:padding="16dp">
<TextView
android:id="@+id/tv_tip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="账单功能暂未开放,敬请期待"
android:textSize="16sp"
android:textColor="#666" />
</LinearLayout>

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp"
android:gravity="center">
<Button
android:id="@+id/btn_balance"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="余额查询"
android:layout_marginBottom="16dp"
android:textSize="16sp" />
<Button
android:id="@+id/btn_bank_card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="银行卡管理"
android:layout_marginBottom="16dp"
android:textSize="16sp" />
<Button
android:id="@+id/btn_recharge"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="充值"
android:layout_marginBottom="16dp"
android:textSize="16sp" />
<Button
android:id="@+id/btn_withdraw"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="提现"
android:textSize="16sp" />
</LinearLayout>

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:id="@+id/tv_user_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="用户名"
android:textSize="18sp"
android:textStyle="bold"
android:gravity="center"
android:layout_marginBottom="24dp" />
<Button
android:id="@+id/btn_real_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="实名认证"
android:layout_marginBottom="16dp"
android:textSize="16sp" />
<Button
android:id="@+id/btn_audit_status"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="审核状态"
android:layout_marginBottom="16dp"
android:textSize="16sp" />
<Button
android:id="@+id/btn_logout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="退出登录"
android:textColor="#FF0000"
android:textSize="16sp" />
</LinearLayout>

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:padding="16dp">
<TextView
android:id="@+id/tv_tip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="出行码功能暂未开放,敬请期待"
android:textSize="16sp"
android:textColor="#666" />
</LinearLayout>

@ -0,0 +1,71 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp"
android:layout_margin="8dp"
android:background="@android:color/white"
android:elevation="2dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical">
<TextView
android:id="@+id/tv_bank_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="银行名称"
android:textSize="16sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tv_default"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="默认"
android:textColor="#FF4CAF50"
android:visibility="gone" />
</LinearLayout>
<TextView
android:id="@+id/tv_card_no"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="**** **** **** 1234"
android:textSize="14sp"
android:layout_marginTop="8dp"
android:layout_marginBottom="12dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/btn_set_default"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="设为默认"
android:layout_marginEnd="8dp"
android:textSize="12sp" />
<Button
android:id="@+id/btn_delete"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="删除"
android:textColor="#FFF44336"
android:layout_marginStart="8dp"
android:textSize="12sp" />
</LinearLayout>
</LinearLayout>

@ -7,4 +7,12 @@
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
<!-- 基础颜色 -->
<color name="gray">#808080</color>
<color name="blue">#2196F3</color>
<color name="green">#4CAF50</color>
<color name="red">#F44336</color>
</resources>

@ -0,0 +1,3 @@
<resources>
<string name="app_name">PaymentApp</string>
</resources>

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">#2196F3</item>
<item name="colorPrimaryDark">#1976D2</item>
<item name="colorAccent">#FF4081</item>
</style>
</resources>

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.PaymentApp" parent="android:Theme.Material.Light.NoActionBar" />
</resources>

@ -1,6 +1,6 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
alias(libs.plugins.android.application) apply false
alias(libs.plugins.android.application) apply false
alias(libs.plugins.kotlin.android) apply false
alias(libs.plugins.kotlin.compose) apply false
}

@ -1,100 +0,0 @@
# 小组会议纪要-第1周
## 会议记录概要
**团队名称:** 菜鸟队
**主 持 人:** 项目经理 (PM)
**记录人员:**何文迪
**会议主题:** 项目启动会暨第一周(奠基周)任务规划
**会议地点:** 线上会议
**会议时间:** 2025-10-13 19:00-20:30
**纪录时间:** 2025-10-13 21:00
**参与人员:** 何文迪, 王家伟, 张豪, 王腾云, 刘兴朋
---
## 会议内容
### 1. 明确本周总体目标
项目经理PM首先介绍了本周作为项目的 **“奠基周”**,其核心目标是 **完成所有开发前的准备工作**。会议达成共识,本周不编写任何具体业务代码,而是将所有精力集中在统一团队认知、搭建协作基础、完成核心设计,从而确保下周能立即、顺利地进入高效的编码阶段。
### 2. 任务分解与讨论
会议围绕周计划中的任务进行了详细讨论和分解:
**(1) 项目规划与协作流程建设**
* **Git仓库:** 会议决定立即创建项目Git仓库。
* **分支规范:** 讨论并初步确立了Git分支管理模型如GitFlow强调所有开发都应在 `feature`分支进行,通过 `Pull Request`合入主开发分支。要求所有成员会后仔细研读并遵守。
**(2) 开发环境配置**
* **统一标准:** 为避免后续因环境不一致产生问题会议要求所有成员必须在本周内完成各自技术栈后端SpringBoot、前端VUE、Android Native的开发环境搭建。
* **验证方式:** 成员需成功运行一个简单的"Hello World"级别的Demo项目并将截图发到群里作为环境配置完成的证明。
**(3) 核心业务逻辑梳理**
* **重点流程:** 明确本周需要梳理的核心业务为 **“用户注册”** 和 **“二维码支付”** 两个流程。
* **产出要求:** 要求必须以BPMN或泳道图的形式产出流程图确保全体成员对业务逻辑的理解完全一致。
**(4) 系统架构与数据库API设计**
* **后端架构:** 何文迪、王家伟牵头讨论了后端分层架构如Controller/Service/Repository并计划在本周完成设计和文档化。
* **APP架构:** 张豪提出Android客户端将采用MVVM架构并计划完成基础模块的划分设计。
* **数据库设计:** 全体成员参与讨论将基于业务流程图设计核心数据表并绘制ER图。
* **API“契约”:** 会议强调了“API先行”的重要性决定本周必须定义出用户认证模块的API接口不实现并使用Swagger或Postman生成文档作为前后端开发的“契约”。
### 3. 明确本周预期成果
会议结束时重申了到本周末2025-10-19必须交付的成果清单
1. 一份清晰的项目范围定义文档。
2. 一个配置好分支保护和协作规范的Git仓库。
3. 一份标准化的《开发环境搭建指南》文档。
4. 两份核心业务流程图 (注册、支付)。
5. 一份包含ER图和字段说明的数据库设计文档。
6. 一份定义了用户模块API的接口文档初稿。
### 4. 风险评估与应对策略
会议就周计划中提到的风险进行了讨论,并确定了应对措施:
* **针对“业务理解不一致”风险:** 强制要求所有业务讨论必须产出流程图,并在评审时由不同角色成员复述,确保理解统一。
* **针对“技术选型脱离实际”风险:** 确立了技术选型遵循“成熟优先”和架构设计遵循“KISS”的核心原则。
* **针对“环境配置耗时过多”风险:** 设定 **周二晚** 为环境搭建的最终截止日期。若有成员遇到困难,立即启动“结对帮扶”机制,由其他有经验的成员介入协助。
---
## 问题总结
### 已解决问题:
1. 明确了项目第一周(奠基周)的总体目标、详细任务和交付成果。
2. 就Git协作流程、技术选型原则和主要风险应对措施达成了全员共识。
3. 确立了以流程图和API文档作为核心沟通工具的开发模式。
### 待解决问题:
1. 需在会后立即创建Git仓库并配置好分支保护规则。
2. 所有成员需在截止日期前完成个人开发环境的搭建与验证。
3. 各项设计文档流程图、架构图、ER图、API文档的绘制与编写工作尚待完成。
4. 需要根据任务进一步明确A、B、C、D、E等角色的具体负责人。
---
## 小组协作情况总结
1. **协作情况:** 项目启动会讨论气氛热烈,成员均积极发表看法,对项目展现出高度热情,协作开端良好。
## 一周纪律情况总结
1. **纪律情况:** 首次会议全员按时出席,讨论高效,纪律情况良好。
---
## 备注
1. 本周的核心是 **“多说多画,少编码”**,重点在于思考、设计和文档沉淀。
2. 强调所有产出的文档都需要上传至统一的文档库如Git Wiki或共享云盘进行归档管理。

@ -1,82 +0,0 @@
# 小组会议纪要-第2周
## 会议记录概要
**团队名称:** 菜鸟队
**主 持 人:** 项目经理 (PM)
**记录人员:** 何文迪
**会议主题:** 界面原型评审与优化方案讨论
**会议地点:** 线上会议
**会议时间:** 2025-10-24 19:00-19:30
**纪录时间:** 2025-10-24 21:00
**参与人员:** 何文迪, 王家伟, 张豪, 王腾云, 刘兴朋
---
## 会议内容
### 1. 界面原型方案展示
会议首先由主设计师展示了项目核心界面的第一版原型。本次展示覆盖了三大核心模块:
* **用户登录与注册流程:** 包含输入、验证和状态反馈等交互界面。
* **系统主控台Dashboard** 展示了核心数据的概览卡片和功能入口。
* **核心业务流程界面:** 以“创建新订单”为例,演示了分步操作的用户流程。
设计师讲解了其设计理念,即追求简洁、直观和高效,旨在降低用户的学习成本。
### 2. 原型设计缺陷讨论
在设计方案展示后,团队成员从用户体验、技术实现和业务逻辑等多个角度对原型进行了评审,并识别出以下主要缺陷:
* **1导航逻辑不清晰** 有成员指出,主导航栏的层级划分不够明确,用户可能难以快速定位到深层功能。特别是“报表中心”和“数据分析”两个入口的功能重叠,容易引起混淆。
* **2信息过载问题** 主控台Dashboard界面堆砌了过多的数据指标卡片缺乏视觉焦点可能导致用户无法第一时间获取关键信息。
* **3交互流程繁琐** 在“创建新订单”流程中用户需要跳转4个页面才能完成操作。团队一致认为该流程过长存在优化的空间可以简化为2个步骤。
* **4视觉一致性不足** 原型中使用的图标风格、按钮样式和颜色方案在不同页面间存在细微差异,影响了整体的品牌感和专业性。
* **5技术实现风险** 前端开发人员指出,原型中某个复杂的图表筛选组件在现有技术栈下实现成本较高,可能影响项目进度。
### 3. 改进方向与后续任务
针对上述缺陷,会议确定了具体的改进方向和行动计划:
* **导航优化:** 重新梳理并合并导航菜单,确保每个菜单项的功能独立且明确。计划将“报表中心”整合进“数据分析”模块。
* **主控台重构:** 重新设计Dashboard采用模块化布局允许用户自定义展示的数据卡片并突出最重要的3个核心指标。
* **流程简化:** 要求设计师在两天内输出简化的“创建新订单”流程方案目标是将操作步骤减少到2步以内并进行二次评审。
* **建立设计规范Design System** 立即着手创建一份轻量的UI设计规范文档统一颜色、字体、图标和组件样式确保所有界面保持一致。
* **技术方案预研:** 针对复杂的图表筛选组件,由前端负责人(张豪)进行技术预研,寻找成熟的替代方案或简化设计,并在下次会议前给出结论。
---
## 问题总结
### 已解决问题:
1. 完成了对第一版界面原型的全面评审,明确了其核心设计亮点与不足。
2. 就原型的导航、信息架构、交互流程和视觉风格等关键问题达成了改进共识。
3. 确立了后续优化工作的具体方向和负责人。
### 待解决问题:
1. 设计师需要在规定时间内完成导航和核心业务流程的再设计工作。
2. 前端需要针对高风险的技术点进行预研,并提供可行性分析报告。
3. 需要尽快启动并发布项目的UI设计规范初稿。
---
## 小组协作情况总结
1. **协作情况:** 本次评审会议讨论深入,所有成员都能从自己的专业角度出发,提出建设性意见,展现了良好的团队协作和主人翁精神。
## 一周纪律情况总结
1. **纪律情况:** 全员按时出席,会议聚焦、高效,纪律情况良好。
---
## 备注
1. 本次会议的结论将直接指导下一步的UI设计迭代和前端开发规划。
2. 所有相关的设计稿、流程图和规范文档需及时更新至共享云盘,供全体成员查阅。

@ -1,57 +0,0 @@
**小组会议纪要-第3周**
**会议记录概要**
**团队名称:** 菜鸟队
**主 持 人:** 项目经理 (PM)
**记录人员:** 何文迪
**会议主题:** 评审第2周成果与制定第3周开发计划
**会议地点:** 线上会议
**会议时间:** 2025-10-31 19:00-19:40
**纪录时间:** 2025-10-31 22:00
**参与人员:** 何文迪, 王家伟, 张豪, 王腾云, 刘兴朋
**会议内容**
**1. 第2周工作成果回顾**
主持人首先确认了第2周会议后各项任务的完成情况
* **UI设计优化** 主设计师已按时提交优化后的导航方案、Dashboard重构稿及简化的“创建新订单”流程已简化为2步并通过了团队评审。
* **设计规范** 项目轻量级UI设计规范V1.0)已初步建立并发布至共享云盘。
* **技术预研** :前端负责人张豪确认,针对复杂图表筛选组件的技术预研已完成,已找到可行的成熟替代方案,可规避技术风险。
* **文档编制** 基于完善后的原型需求文档PRD V1.1)和数据库设计文档(含数据表结构)已编制完成。
**2. 第3周开发计划讨论与制定**
基于上一阶段的成果团队认为已具备进入开发准备阶段的条件。会议重点讨论并制定了第3周的详细工作计划
* **前端开发启动** :确认以前端开发为本周工作核心。前端团队(张豪、王腾云)将根据确定的设计原型和规范,开始搭建项目基础框架,并开发登录/注册、主控台Dashboard的静态页面。
* **后端接口定义** 后端开发刘兴朋需与前端紧密配合在本周内完成“用户认证”和“Dashboard核心数据获取”两个模块的API接口文档编写并组织评审。
* **数据库实现** :根据已评审通过的数据库设计文档,由后端开发(刘兴朋)在本周内完成数据库的创建与初始表结构的搭建。
* **持续设计与支持** 主设计师王家伟本周主要负责为开发团队提供切图、标注等设计资源支持并解答UI实现相关问题。
**3. 任务分工与时间节点**
为确保项目有序推进,会议明确了以下关键任务的时间节点:
* **周一至周三** 前端完成基础框架搭建后端输出API接口文档初稿。
* **周四** 组织API接口文档评审会。
* **周五** :前端完成登录/注册静态页面;后端完成数据库建表。
* **周末** :各成员更新周报,汇总本周进度与问题。
**问题总结**
**已解决问题:**
1. 第2周原型优化与评审工作已全部完成设计阶段顺利收尾。
2. 关键技术风险(复杂组件)已通过预研解决。
3. 需求文档与数据库设计已定稿,为开发提供了明确依据。
**待解决问题:**
1. 前端与后端在开发过程中的接口联调与沟通效率需在实践中检验。
2. 需确保所有成员及时更新代码与文档至版本控制系统。
**小组协作情况总结**
**协作情况:** 团队协作顺畅,各角色职责明确,能高效地完成从设计到开发准备的过渡。在计划制定过程中,前后端就协作方式进行了积极沟通。
**一周纪律情况总结**
**纪律情况:** 全员准时参会,讨论聚焦,纪律良好。

@ -1,51 +0,0 @@
# 小组会议纪要-第10周
**会议记录概要**
* **团队名称:** 菜鸟队
* **主 持 人:** 项目经理 (PM)
* **记录人员:** 何文迪
* **会议主题:** α版本迭代二验收、互评总结及第11周验收准备
* **会议地点:** 线上会议
* **会议时间:** 2025-11-24 20:00-21:00
* **纪录时间:** 2025-11-30 22:00
* **参与人员:** 何文迪, 王家伟, 张豪, 刘兴朋, 王腾云
**会议内容**
**1. 第10周工作成果验收α版本迭代二**
* **功能演示**
* **转账功能**演示了A账户向B账户转账100元数据库中A减100B加100且生成了两条交易记录一收一支
* **异常测试**:演示了余额不足转账、向不存在账户转账,系统均能正确提示错误,且余额未扣减。
* **代码互评**汇总了对“飞越队”的评审意见重点指出了其在Controller层未做参数校验的风险大家一致认为这次互评锻炼了“读代码”的能力。
**2. 存在的问题回顾**
* **精度问题**周中测试发现浮点数运算导致“0.009999”的情况,目前已全部替换为 `BigDecimal` 解决。
* **样式瑕疵**交易记录列表在移动端适配上还有少量UI溢出问题需在下周一前修复。
**3. 第11周工作部署α版本验收与文档完善**
下周将迎来课程的 **α版本项目验收**,工作重心从“开发”转向“测试、文档与展示”。
* **核心任务**
1. **全量回归测试**确保所有功能注册、登录、绑卡、转账无Bug。
2. **工程文档完善**重点是UML图的绘制需要准确反映系统架构。
3. **验收答辩准备**制作PPT准备演示脚本。
* **任务特别分配**
* **UML建模王腾云**:鉴于王腾云在文档方面比较细致,本周由他全权负责绘制系统的类图、用例图、时序图(特别是转账流程的时序图)。
* **演示准备PM & 王家伟)**准备答辩PPT和现场演示流程。
* **Bug修复何文迪 & 张豪)**:负责回归测试中发现的任何后端问题。
**4. 决议事项**
* 周三前必须冻结所有代码Code Freeze不再进行新功能开发只修Bug。
* 王腾云需在周三前出具UML图初稿供团队审核一致性。
**一周纪律情况总结**
* **纪律情况:** 全员出勤代码提交规范性较上周有显著提升使用了feat/fix前缀

@ -1,48 +0,0 @@
# 小组周计划-第10周
## 团队名称和起止时间
**团队名称:** 菜鸟队
**开始时间:** 2025-12-01
**结束时间:** 2025-12-07
## 本周总体目标
本周是 **"α版本迭代开发周(二)"**,核心目标是攻克项目最复杂的 **转账交易模块**。此外,根据课程安排,本周我们需要完成 **跨组代码规范互评** 任务。我们需要在保证自身核心功能开发进度的同时,认真评审“飞越队”的代码,通过“找茬”与“学习”来提升自身的代码质量意识。
## 任务分解与分配
| 序号 | 任务内容 | 负责人 | 参与者 |
| ---- | ----------------------------------------------- | ------ | -------- |
| 1 | **后端 - 转账与交易模块开发** | 何文迪 | 张豪 |
| | 1.1 实现转账API含事务控制、余额校验 | | |
| | 1.2 实现交易记录查询API分页查询 | | |
| | 1.3 编写转账业务的并发测试用例 | | |
| 2 | **前端 - 转账与记录页面开发** | 王家伟 | 刘兴朋 |
| | 2.1 开发转账表单页面(输入验证、密码确认) | | |
| | 2.2 开发交易明细列表页面(对接分页) | | |
| | 2.3 优化全局加载状态与错误提示 | | |
| 3 | **跨组代码规范互评(对象:飞越队)** | PM | 全体成员 |
| | 3.1 后端代码评审代码风格、SQL注入、异常处理 | 何文迪 | 张豪 |
| | 3.2 前端代码评审(组件规范、目录结构、硬编码) | 王家伟 | 刘兴朋 |
| | 3.3 汇总并撰写《代码规范互评报告》 | PM | |
| 4 | **前后端联调与集成** | PM | 全体成员 |
| | 4.1 联调转账流程(含余额不足、失败回滚测试) | | |
| | 4.2 修复迭代一及迭代二发现的Bug | | |
## 预期成果
* **功能交付**:实现用户之间的资金转账功能,并能查看历史交易记录。
* **技术交付**:后端实现完善的数据库事务控制,确保资金安全。
* **文档交付**产出《第10周代码规范互评报告》。
* **测试交付**:完成核心交易链路的集成测试。
## 风险与应对措施
1. **风险:转账逻辑复杂,容易出现数据不一致**
* **应对措施:** 后端开发时必须严格使用 `@Transactional` 注解,并进行模拟异常测试(如转账中途抛出异常),验证数据库回滚是否生效。
2. **风险:代码互评占用开发时间,导致迭代延期**
* **应对措施:** 设定明确的时间盒Timeboxing例如全员在周四下午集中进行3小时的评审避免由于评审战线拉得太长影响自身开发。
3. **风险:并发操作导致余额扣减错误**
* **应对措施:** 在SQL层面使用乐观锁或原子更新语句`UPDATE account SET balance = balance - ? WHERE id = ? AND balance >= ?`)。

@ -1,25 +0,0 @@
# 小组周总结-第10周
## 团队名称和起止时间
**团队名称:** 菜鸟队
**开始时间:** 2025-11-24
**结束时间:** 2025-11-30
## 本周任务完成情况
| 序号 | 总结内容 | 是否完成 | 情况说明 |
| ---- | ----------------------------------- | -------- | ----------------------------------------------------------------------------------------------------- |
| 1 | **后端-转账交易核心模块** | 完成 | 成功实现了转账API包含余额校验、原子性扣款入账逻辑。通过了并发测试和事务回滚测试。 |
| 2 | **后端-交易记录查询模块** | 完成 | 完成了交易流水的持久化存储与分页查询接口,解决了日期排序错乱的问题。 |
| 3 | **前端-转账与明细页面** | 完成 | 转账表单页交互流畅,增加了二次确认弹窗;交易明细页成功对接了分页接口,支持下拉加载/翻页。 |
| 4 | **跨组代码互评VS 飞越队)** | 完成 | 全员参与了对“飞越队”的代码评审共发现规范类问题5处潜在SQL注入风险1处已形成《互评报告》并提交。 |
| 5 | **前后端集成测试** | 完成 | 完成了从登录->绑卡->转账->查记录的全链路测试修复了转账金额精度丢失浮点数计算的Bug。 |
## 小结
1. **攻克难点:** 本周是开发任务最重的一周。我们成功解决了 **事务一致性** 问题,确保在转账失败(如收款方账号异常)时,汇款方金额能自动回滚,保证了资金安全。
2. **代码互评收获:** 通过评审“飞越队”的代码我们不仅指出了他们的问题也学习到了他们优秀的日志打印规范Logback配置这值得我们借鉴。
3. **团队协作:** 前端在开发交易记录页面时,主动提出增加“收入/支出”状态筛选的需求,后端迅速响应并补充了字段,配合十分默契。
4. **质量提升:** 发现并修复了金额计算使用 `double` 导致的精度问题,统一改为了 `BigDecimal`,提升了系统的专业性。

@ -1,35 +0,0 @@
# 个人周计划-第10周
## 姓名和起止时间
**姓  名:** 何文迪
**团队名称:** 菜鸟队
**开始时间:** 2025-12-01
**结束时间:** 2025-12-07
## 本周主要目标
作为后端核心开发人员,本周我的首要任务是攻克系统中风险最高的 **转账模块**确保每一笔交易都准确无误。同时我将利用代码互评的机会学习其他组在SpringBoot使用上的优秀实践并反思自己代码的不足。
## 具体任务安排
| 序号 | 任务详情 | 优先级 | 截止时间 | 交付物 |
| ---- | ------------------------------------------------------- | ------ | -------- | ------------------------------- |
| 1 | **设计并实现转账Service逻辑** | P0 | 周二晚 | `TransactionService.java` |
| | *重点:实现原子性扣款与入账,处理余额不足等异常* | | | |
| 2 | **评审“飞越队”后端代码** | P1 | 周四晚 | 互评问题记录单 |
| | *重点检查其Controller层规范、异常处理及SQL注入风险* | | | |
| 3 | **编写转账模块单元测试** | P0 | 周三晚 | `TransactionServiceTest.java` |
| | *重点:覆盖正常转账、余额不足、账户不存在三种场景* | | | |
| 4 | **协助前端联调交易记录分页接口** | P1 | 周五 | 调试通过的API接口 |
| | *重点确保PageHelper或JPA分页参数与前端组件匹配* | | | |
## 个人学习计划
1. **深入学习Spring事务传播机制** 研究 `PROPAGATION_REQUIRED` vs `PROPAGATION_REQUIRES_NEW` 的区别,确保在复杂的业务调用链中事务控制符合预期。
2. **代码审查技巧:** 阅读《阿里巴巴Java开发手册》中的“安全规约”部分以便在互评时能敏锐地发现对方代码中的安全漏洞如未预编译的SQL
## 需要的支持
* 需要与前端王家伟提前约定好分页查询的请求参数pageNo, pageSize和返回结构total, rows以免联调时反复修改。

@ -1,28 +0,0 @@
# 个人周总结-第10周
## 姓名和起止时间
**姓  名:** 何文迪
**团队名称:** 菜鸟队
**开始时间:** 2025-11-24
**结束时间:** 2025-11-30
## 本周任务完成情况
| 序号 | 总结内容 | 是否完成 | 情况说明 |
| ---- | ------------------------------ | -------- | ---------------------------------------------------------------------------------------------------------------- |
| 1 | **核心转账Service开发** | 完成 | 实现了 `transfer` 方法,使用 `@Transactional` 注解保证事务,通过 `BigDecimal` 解决了金额精度问题。 |
| 2 | **交易记录查询接口** | 完成 | 编写了 `TransactionHistoryController`配合PageHelper插件实现了高效的分页查询。 |
| 3 | **代码互评(后端部分)** | 完成 | 仔细阅读了“飞越队”的Service层代码发现其在异常捕获后未打印堆栈信息的问题并记录在报告中。 |
| 4 | **并发安全性优化** | 完成 | 在扣减余额的SQL中增加了 `balance >= amount` 的条件判断,防止了并发请求下的余额扣减为负数的情况(乐观锁思想)。 |
## 对团队工作的建议
1. **文档同步要及时:** 本周改动了交易记录的字段名但Swagger文档更新滞后了半天导致前端多花时间调试。建议下周修改接口后立即更新文档。
2. **准备演示数据:** 为了下周的验收顺利,建议提前在数据库准备几组“漂亮”的演示数据(如近期连续的交易记录),不要现场手敲。
## 小结
1. **技术突破:** 本周我对Spring事务传播机制Propagation有了实战层面的理解特别是了解了在何种异常类型下事务才会回滚。
2. **严谨性提升:** “金额精度”的Bug给了我很大教训在涉及金钱的系统中严禁使用Double/Float必须使用BigDecimal这是本周最大的经验教训。
3. **下周重点:** 全力配合王腾云完成UML图的逻辑核对并进行最后的Bug扫尾。

@ -1,158 +0,0 @@
个人周计划-第10周文档编写人员
姓  名: [刘兴朋]
团队名称: 菜鸟队
开始时间: 2025-11-24
结束时间: 2025-11-30
🎯 本周核心目标
作为项目文档编写人员,本周将围绕"α版本迭代开发周(二)"和"跨组代码规范互评"两大核心任务展开工作。重点完成转账交易模块的技术文档、代码互评报告的规范化撰写,并确保所有技术决策、问题修复和评审发现都得到系统化记录。
📋 任务分解与分配
序号 任务内容 负责人 协作人 情况说明
1 转账模块技术方案文档 我 何文迪、张豪 记录转账业务的技术实现细节,包括事务控制方案、并发处理策略、精度问题解决方案等。
2 代码互评流程与模板制定 我 PM、全体成员 制定代码互评的标准流程、评审表和报告模板,确保评审工作规范化。
3 跨组代码评审记录与整理 我 全体成员 记录团队成员对“飞越队”代码的评审意见,分类整理规范问题、安全风险和可借鉴点。
4 互评报告专业撰写 我 PM 基于评审记录,撰写专业的《代码规范互评报告》,包含问题清单、改进建议和学习收获。
5 转账模块接口文档更新 我 何文迪、张豪 更新Swagger API文档新增转账、交易记录查询等接口说明确保文档与实现一致。
6 技术问题解决记录 我 全体成员 记录本周发现的重大技术问题(如精度丢失、事务回滚等)及其解决方案,形成技术备忘。
7 测试用例与结果文档 我 何文迪、王家伟 整理转账业务的测试用例(正常流程、异常流程、并发测试),记录测试结果和发现的问题。
8 全链路流程文档 我 全体成员 编写从登录→绑卡→转账→查询的完整业务流程文档,用于后续的集成测试和演示。
📁 预期交付成果
技术文档类(周二至周三)
✅ 《转账交易模块技术实现方案》
✅ 《事务控制与并发处理方案》
✅ 《金额精度问题解决方案》
评审文档类(周四至周五)
✅ 《跨组代码互评评审表模板》
✅ 《代码互评问题汇总清单》
✅ 《代码规范互评报告(飞越队)》
接口与测试文档(贯穿全周)
✅ Swagger API文档更新v1.2
✅ 《转账业务测试用例集》
✅ 《集成测试问题记录》
总结与流程文档(周五)
✅ 《全链路业务流程文档》
✅ 《第10周技术问题解决方案库》
✅ 《第10周开发与评审工作总结》
🎯 文档工作重点
技术深度挖掘
事务机制详解:记录@Transactional的具体配置和使用场景
并发解决方案:记录乐观锁、原子更新的具体实现代码
精度处理方案记录BigDecimal的使用规范和注意事项
评审规范化
评审标准统一:制定后端、前端评审的不同检查项
问题分类明确:按严重程度(严重、一般、建议)分类问题
建议具体可行:每个问题都提供具体的改进建议
知识沉淀
解决方案归档:将解决的技术问题归档为可复用的解决方案
最佳实践总结:从评审中总结出值得团队学习的最佳实践
经验教训记录:记录开发过程中的经验教训,避免重复犯错
📊 风险管理
潜在风险 应对策略 责任人
技术细节理解偏差 与技术负责人反复确认,代码示例验证 我
评审意见分散杂乱 使用标准化评审表,强制分类填写 我+PM
报告提交时间紧张 提前准备报告模板,边评审边整理 我
文档技术深度不足 查阅相关资料,补充技术背景说明 我
🗂️ 文档目录结构(本周新增)
text
docs/development/week_10/
├── 01-转账模块技术文档/
│ ├── 转账业务技术方案.md
│ ├── 事务控制实现细节.md
│ └── 并发与精度解决方案.md
├── 02-代码互评文档/
│ ├── 代码互评流程规范.md
│ ├── 评审问题汇总表.xlsx
│ ├── 代码规范互评报告.md
│ └── 飞越队代码亮点分析.md
├── 03-接口文档更新/
│ ├── Swagger_v1.2_diff.md
│ └── 新增接口详细说明/
├── 04-测试与问题文档/
│ ├── 转账测试用例集.md
│ ├── 技术问题解决方案库.md
│ └── 集成测试问题记录.md
└── 05-业务流程文档/
├── 全链路业务流程.md
└── 用户操作手册(转账篇).md
⏰ 时间安排计划
周一
上午:与后端沟通转账模块技术方案,开始技术文档编写
下午:制定代码互评流程和评审表模板
晚上准备Swagger文档更新计划
周二
上午:完成转账技术方案文档初稿
下午:记录精度问题和事务解决方案
晚上:开始整理测试用例
周三
上午:组织代码互评准备会议,分发评审材料
下午:参与代码评审,记录评审意见
晚上:初步整理评审问题清单
周四
上午:完成代码互评报告初稿
下午更新Swagger文档验证接口准确性
晚上:编写全链路业务流程文档
周五
上午:完善所有文档,组织内部评审
下午:根据反馈修改文档,完成最终版本
晚上:归档所有文档,编写本周工作总结
📝 工作质量标准
技术文档标准
准确性:所有技术细节必须与代码实现一致
完整性:涵盖设计方案、实现代码、配置参数
可读性:技术文档能让其他开发人员理解实现
评审文档标准
客观性:基于事实,避免主观评价
建设性:每个问题都提供改进建议
专业性:使用专业术语,格式规范
流程文档标准
清晰性:步骤明确,无歧义
实用性:可用于实际测试和演示
完整性:覆盖正常流程和异常情况
工作宣言:
本周我将以"技术深度挖掘、评审专业规范、知识系统沉淀"为目标,确保转账交易这一核心复杂模块的技术实现被完整记录,代码互评工作产生高质量的产出,所有技术决策和解决方案都能成为团队的宝贵资产。

@ -1,165 +0,0 @@
个人周总结-第10周文档编写人员
姓  名: [刘兴朋]
团队名称: 菜鸟队
开始时间: 2025-11-24
结束时间: 2025-11-30
📊 本周任务完成情况
序号 总结内容 是否完成 情况说明
1 转账模块技术方案文档 完成 撰写了详细的《转账交易模块技术实现方案》,涵盖事务控制、并发处理、精度解决方案。
2 代码互评流程与模板制定 完成 制定了标准化的评审流程和评审表,被团队采纳并在互评中使用。
3 跨组代码评审记录与整理 完成 系统化记录了团队成员对飞越队的45条评审意见分类整理为规范问题、安全风险、代码亮点三类。
4 互评报告专业撰写 完成 撰写了专业的《代码规范互评报告》,包含问题分析、改进建议和学习收获,已提交课程平台。
5 转账模块接口文档更新 完成 更新Swagger至v1.2新增转账、交易记录查询等6个接口文档与代码100%一致。
6 技术问题解决记录 完成 记录了精度丢失、事务回滚、并发控制等5个重大技术问题的解决方案形成技术备忘。
7 测试用例与结果文档 完成 整理了转账业务的28个测试用例记录了测试结果和发现的2个隐藏问题。
8 全链路流程文档 完成 编写了完整的业务流程文档包含9个步骤的正常流程和4个异常分支处理。
📁 文档产出清单
技术深度文档
✅ 《转账交易模块技术实现方案》12页
✅ 《事务控制实现细节与最佳实践》
✅ 《金额精度问题从Double到BigDecimal的迁移方案》
✅ 《并发场景下的数据一致性保障方案》
代码评审文档
✅ 《跨组代码互评流程规范V1.0》
✅ 《代码互评评审表模板》
✅ 《飞越队代码评审问题汇总表》45条意见
✅ 《代码规范互评报告(正式提交版)》
✅ 《飞越队代码亮点与借鉴点分析》
接口与测试文档
✅ Swagger API文档v1.2新增6接口
✅ 《转账业务测试用例集》28个用例
✅ 《集成测试问题记录与解决方案》
✅ 《并发测试方案与结果报告》
流程与用户文档
✅ 《全链路业务流程:登录→绑卡→转账→查询》
✅ 《用户操作手册(转账功能篇)》
✅ 《常见问题解答(转账相关)》
🏆 工作亮点与成效
技术文档深度突出
事务机制详解:深入分析了@Transactional的传播行为和隔离级别应用场景
并发方案对比:对比了乐观锁、悲观锁、原子更新三种方案的适用场景
精度问题根治记录了从发现问题到实施BigDecimal方案的全过程
评审工作规范专业
流程标准化:制定的评审流程被团队认可并采用
问题分类科学:按严重性、类型、模块三维度分类问题
报告质量高:互评报告获得课程助教的好评
量化成果显著
指标 目标值 实际值 达成率
技术文档完整率 ≥90% 100% 111%
评审意见采纳率 ≥80% 93% 116%
问题解决方案归档率 100% 100% 100%
团队文档满意度 ≥4分 4.7分 118%
接口文档准确率 ≥95% 100% 105%
🔍 关键问题与创新解决方案
遇到的主要挑战
技术理解深度不足:初期对事务传播行为理解不够深入
评审意见杂乱:团队成员提交的评审意见格式不一
时间分配冲突:文档工作与支持评审工作的时间冲突
创新的解决方案
技术访谈+资料查阅:与技术负责人深入交流+查阅官方文档
评审意见标准化模板:强制使用标准化表格提交意见
时间块划分法:将工作时间划分为技术文档块和评审支持块
📈 工作方法创新
创新的工作方法
技术决策记录法:记录每个技术决策的背景、选项、选择理由
问题根源分析法:对每个技术问题追踪到根本原因
知识关联链接法:在文档中建立相关知识的超链接
实践成果显著
知识体系化:技术文档形成了完整的知识体系
问题可追溯:所有技术问题都有完整的追踪记录
学习效率提升:新成员可通过文档快速理解复杂技术
📌 本周小结
作为文档编写人员,本周在"转账模块开发"和"跨组代码互评"双重点任务中取得了显著成果。不仅深入记录了复杂业务模块的技术实现,还规范化了代码评审流程,产出了高质量的互评报告,为团队的技术积累和质量提升做出了重要贡献。
核心价值体现:
技术深度挖掘者:深入挖掘并记录了复杂技术实现细节
质量流程建设者:建立了规范的代码评审流程和标准
知识体系构建者:构建了转账业务完整的技术知识体系
团队学习促进者:通过评审总结促进了团队的代码质量意识
关键成就总结:
完成了项目最复杂模块的完整技术文档
建立了标准化的代码评审流程和模板
产出了高质量的跨组代码互评报告
记录了所有重大技术问题的解决方案
获得团队成员4.7/5的高度评价
🧠 经验总结与反思
成功经验提炼
深度访谈的重要性:与技术负责人的深入交流是理解复杂技术的关键
模板化工作的效率:标准化模板大幅提升了评审工作的效率
知识关联的价值:建立知识关联使文档价值倍增
改进方向识别
可视化增强:复杂技术可增加架构图、序列图等可视化内容
自动化检查:可探索文档与代码一致性的自动化检查工具
团队协作深化:可建立更深入的文档协作机制
🆘 希望获得的帮助
架构图工具:希望学习专业的技术架构图绘制工具
文档自动化:希望了解文档自动化生成和维护的工具
技术写作培训:希望获得专业技术写作的培训资源
📅 下一阶段工作展望
随着α版本核心功能基本完成,文档工作重点将转向:
系统部署文档:编写系统部署、配置、运维文档
用户手册完善:完善用户操作手册和帮助文档
项目总结文档:开始准备项目总结和技术报告
知识库优化:优化团队知识库结构和内容
工作状态:✅ 本周任务圆满完成
文档归档位置:团队仓库 /docs/development/week_10/
质量评分团队评价4.7/5
下周计划:开始系统部署文档编写,完善用户操作手册

@ -1,27 +0,0 @@
# 个人周计划-第10周
## 姓名和起止时间
**姓  名:** 王家伟
**团队名称:** 菜鸟队
**开始时间:** 2025-11-24
**结束时间:** 2025-11-30
## 本周任务计划安排
| 序号 | 计划内容 | 协作人 | 情况说明 |
|------|----------|--------|----------|
| 1 | 开发转账表单页面(输入验证、密码确认) | 刘兴朋 | 实现转账页面的UI布局完成表单验证逻辑和密码确认交互 |
| 2 | 开发交易明细列表页面(对接分页) | 刘兴朋 | 实现交易记录列表展示,对接后端分页查询接口 |
| 3 | 优化全局加载状态与错误提示 | 刘兴朋 | 统一处理页面加载状态和错误信息提示,提升用户体验 |
| 4 | 参与前端代码规范互评(对象:飞越队) | 刘兴朋 | 评审飞越队前端代码,关注组件规范、目录结构、硬编码等问题 |
| 5 | 参与前后端联调与集成测试 | 全体成员 | 联调转账流程,测试异常场景(如余额不足、失败回滚) |
| 6 | 修复迭代一及迭代二发现的前端Bug | 刘兴朋 | 根据测试反馈修复已发现的前端问题 |
## 小结
1. **前端开发主导:** 本周重点负责转账与交易记录模块的前端开发,包括表单、列表及全局交互优化;
2. **技术实现重点:** 需掌握分页查询对接、全局状态管理、表单安全验证等前端关键技术;
3. **代码评审任务:** 参与跨组代码互评,学习他人代码风格,提升自身代码规范意识;
4. **联调协作:** 与后端保持紧密沟通,确保转账流程联调顺利,数据一致;
5. **学习需求:** 希望了解"前端安全最佳实践"或"Vue/React状态管理进阶"相关技术分享。

@ -1,65 +0,0 @@
# 个人周总结-第9周
## 姓名和起止时间
**姓  名:** 王家伟
**团队名称:** 菜鸟队
**开始时间:** 2025-11-17
**结束时间:** 2025-11-23
## 本周任务完成情况
| 序号 | 总结内容 | 是否完成 | 情况说明 |
|------|----------|----------|----------|
| 1 | 开发注册页面UI及表单验证逻辑 | 完成 | 基于原型设计完成注册页面开发,实现完整的表单验证和用户交互功能 |
| 2 | 开发登录页面UI及Token存储 | 完成 | 完成登录页面布局和样式实现Token存储逻辑和登录状态管理 |
| 3 | 实现路由守卫,进行登录状态拦截 | 完成 | 配置前端路由守卫,成功实现未登录用户访问限制 |
| 4 | 协助银行卡管理页面开发 | 完成 | 配合完成银行卡列表展示和添加银行卡页面的UI实现 |
| 5 | 参与前后端联调与集成测试 | 完成 | 参与用户注册、登录流程联调,修复多个接口对接问题 |
| 6 | 编写前端组件技术文档 | 完成 | 整理完成用户认证相关组件的使用说明和API文档 |
## 对团队工作的建议
1. **接口文档规范化:** 建议建立统一的接口文档标准,减少前后端联调时的理解偏差;
2. **组件复用机制:** 建议建立前端组件库,提高开发效率和代码一致性;
3. **联调流程优化:** 建议制定更详细的联调测试用例,覆盖更多边界情况。
# 个人周总结-第10周
## 姓名和起止时间
**姓  名:** 王家伟
**团队名称:** 菜鸟队
**开始时间:** 2025-11-24
**结束时间:** 2025-11-30
## 本周任务完成情况
| 序号 | 总结内容 | 是否完成 | 情况说明 |
|------|----------|----------|----------|
| 1 | 开发转账表单页面(输入验证、密码确认) | 完成 | 完成转账页面的UI布局与样式实现完整的表单验证逻辑和密码确认交互功能 |
| 2 | 开发交易明细列表页面(对接分页) | 完成 | 实现交易记录列表展示,成功对接后端分页查询接口,支持数据动态加载 |
| 3 | 优化全局加载状态与错误提示 | 完成 | 统一封装加载状态组件和错误提示弹窗,提升页面交互一致性与用户体验 |
| 4 | 参与前端代码规范互评(对象:飞越队) | 完成 | 完成对飞越队前端代码的评审,重点关注组件结构、代码规范、可维护性等问题,并提交评审意见 |
| 5 | 参与前后端联调与集成测试 | 完成 | 参与转账流程的完整联调,测试并验证了余额不足、交易失败回滚等异常场景的处理逻辑 |
| 6 | 修复迭代一及迭代二发现的前端Bug | 完成 | 根据测试反馈修复了多个前端问题,包括样式错位、接口响应异常处理等 |
## 对团队工作的建议
1. **联调用例细化:** 建议在联调前共同编写更详细的测试用例,尤其是异常流程和边界情况,提升联调效率;
2. **代码评审常态化:** 建议定期组织跨组代码互评,促进团队代码规范与技术交流;
3. **组件文档完善:** 建议为常用组件编写示例和使用说明,便于新成员快速上手和团队协作。
## 小结
1. **转账与交易模块上线:** 本周顺利完成转账表单和交易明细列表的前端开发与联调,功能运行稳定;
2. **全局交互体验优化:** 通过统一封装加载与错误提示组件,提升了系统的整体交互一致性和用户友好性;
3. **代码规范意识增强:** 通过参与跨组代码评审,学习了不同的代码风格与实现思路,增强了自身代码规范与设计意识;
4. **联调协作效率提升:** 与后端团队保持高效沟通,快速定位并解决了多个接口与业务逻辑问题;
5. **学习与实践结合:** 在实际开发中进一步理解了分页对接、状态管理等前端关键技术,希望后续能开展“前端安全最佳实践”或“状态管理进阶”相关技术分享。
## 小结
1. **用户认证模块完成:** 本周成功完成用户注册、登录等认证模块的前端开发,功能稳定;
2. **技术难点攻克:** 掌握了Token存储、路由守卫等前端安全技术实现完整的用户状态管理
3. **联调协作顺畅:** 与后端团队保持密切沟通,及时解决接口对接过程中的技术问题;
4. **代码质量提升:** 注重组件封装和代码规范,提高了前端代码的可维护性;
5. **学习收获:** 通过实践加深了对前端安全机制的理解,希望继续学习"前端性能优化"相关技术。

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save