为Internal页面添加一个Unit Test的运行入口

本文介绍了一种将单元测试集成到Web应用的方法,通过点击Web页面上的链接即可触发单元测试,并将测试结果以网页形式展示。使用Mocha作为测试框架,并通过自定义批处理文件和HTML报告器实现。

昨天把内部页面建立起来了,那么今天就把Unit Test的运行入口赛到里面去,就是说,点击某一个链接,就运行Unit Test,并且把Unit Test的结果,通过网页显示出来。这个想法还是蛮不错的。做起来也容易,当然其中有些小挫折。


这里就是全部的源代码。Unit Test是mocha做的,我原来的思路是,运行mocha命令,然后把stdout的内容输出。但是有问题,那就是我死活拿不到完整的stdout输出,这个应该是mocha的一个bug,不高兴去研究了。所以我找了个work round,把mocha的结果重定向到一个html文件,然后,把文件内容读出来,再显示:

exports.integrationTest = function (req, res) {

    var resultFile = "./tmp/test_result.html";

    async.waterfall([

        // delete the old test result
        function (callback) {
            fs.unlink(resultFile);
            callback();
        },

        // generate new test result
        function (callback) {
            var cmd = 'IntegrationTest.bat';
            var opt = {
                cwd: "./utilities"
            };

            var process = spawn(cmd, [], opt);
            var content = "";
            process.on('close', function () {
                callback();
            });
        },

        // render test result
        function (callback) {
            fs.readFile(resultFile, callback);
        },
    ], function (error, content) {
        if (error) res.send(500);
        else {
            res.render("page_integration_test",
                {
                    title: "Integration Test",
                    header: "Integration Test Result",
                    content: content
                });
        }
    });
}

因为,Unit Test的文件,和Web Server在同一个目录,所以只能写一个bat来运行,下面是bat的内容(IntegrationTest.bat):

@ECHO OFF
cd ..\..\Test
mocha -R mocha-html-reporter>"..\WebSite\tmp\test_result.html"

注意,mocha-html-reporter不是标准的mocha reporter,所以需要先全局安装下。他会以html的格式输出整个测试结果。测试结果很丑,所以需要美化下,这个是page_integration_test.ejs和CSS文件(注意页面背景要设置成黑的):

page_integration_test.ejs:

<!DOCTYPE html>
<html>
<head>
    <% include header_title_meta %>
    <% include header_css %>
    <link rel="stylesheet" type="text/css" href="/stylesheets/integration_test_style.css" />
</head>
<body>
    <% include fragment_header %>
    <div><%- content %></div>
</body>
</html>

integration_test_style.css:

header {
    color: white;
    text-align: center;
    font-size: 26px;
}

body {
    font-family: Calibri, Segoe UI,Arial,sans-serif;
}

#stats li.progress {
    visibility: hidden;
    float: right;
    width: 0px;
    height: 0px;
}

#stats li.passes {
    font-size: 24px;
    color: #00ff21;
}

#stats li.failures {
    font-size: 24px;
    color: red;
}

#stats li.duration {
    font-size: 24px;
    color: white;
}

#report {
    margin-top: 50px;
}

    #report span {
        font-size: 14px;
        color: white;
        margin-left: 10px;
    }

    #report h1 {
        font-size: 18px;
        color: white;
    }

    #report li {
        font-size: 11px;
        color: yellow;
    }

这个是截图:




更新!


今天碰到一个问题,用iisnode托管运行的话,会报错!后来发现,是因为全局安装mocha的话,mocha库是自动安装到npm的appData下面的,所以可能 IIS 没有权限访问。那么,索性我就别全局安装了,就本地安装算了:

npm install mocha --save

npm install mocha-html-reporter --save

然后还要修改一下IntegrationTest.bat):

@ECHO OFF
node "..\node_modules\mocha\bin\mocha" "..\..\Test\test\*.js" -R mocha-html-reporter>"..\tmp\test_result.html"





e: file:///D:/AndroidStudioProjects/MyApplication/app/src/main/java/com/example/myapplication/MainActivity.kt:19:20 Unresolved reference 'calculator'. e: file:///D:/AndroidStudioProjects/MyApplication/app/src/main/java/com/example/myapplication/MainActivity.kt:27:13 Unresolved reference 'CalculatorTheme'. e: file:///D:/AndroidStudioProjects/MyApplication/app/src/main/java/com/example/myapplication/MainActivity.kt:28:17 @Composable invocations can only happen from the context of a @Composable function e: file:///D:/AndroidStudioProjects/MyApplication/app/src/main/java/com/example/myapplication/MainActivity.kt:30:43 @Composable invocations can only happen from the context of a @Composable function FAILURE: Build failed with an exception. * What went wrong: Execution failed for task ':app:compileDebugKotlin'. > A failure occurred while executing org.jetbrains.kotlin.compilerRunner.GradleCompilerRunnerWithWorkers$GradleKotlinCompilerWorkAction > Compilation error. See log for more details * Try: > Run with --info or --debug option to get more log output. > Run with --scan to get full insights. > Get more help at https://help.gradle.org. * Exception is: org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':app:compileDebugKotlin'. at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.lambda$executeIfValid$1(ExecuteActionsTaskExecuter.java:130) at org.gradle.internal.Try$Failure.ifSuccessfulOrElse(Try.java:293) at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeIfValid(ExecuteActionsTaskExecuter.java:128) at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.execute(ExecuteActionsTaskExecuter.java:116) at org.gradle.api.internal.tasks.execution.FinalizePropertiesTaskExecuter.execute(FinalizePropertiesTaskExecuter.java:46) at org.gradle.api.internal.tasks.execution.ResolveTaskExecutionModeExecuter.execute(ResolveTaskExecutionModeExecuter.java:51) at org.gradle.api.internal.tasks.execution.SkipTaskWithNoActionsExecuter.execute(SkipTaskWithNoActionsExecuter.java:57) at org.gradle.api.internal.tasks.execution.SkipOnlyIfTaskExecuter.execute(SkipOnlyIfTaskExecuter.java:74) at org.gradle.api.internal.tasks.execution.CatchExceptionTaskExecuter.execute(CatchExceptionTaskExecuter.java:36) at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.executeTask(EventFiringTaskExecuter.java:77) at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:55) at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:52) at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:209) at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:204) at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66) at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59) at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:166) at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59) at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:53) at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter.execute(EventFiringTaskExecuter.java:52) at org.gradle.execution.plan.LocalTaskNodeExecutor.execute(LocalTaskNodeExecutor.java:42) at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:331) at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:318) at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.lambda$execute$0(DefaultTaskExecutionGraph.java:314) at org.gradle.internal.operations.CurrentBuildOperationRef.with(CurrentBuildOperationRef.java:85) at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:314) at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:303) at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.execute(DefaultPlanExecutor.java:459) at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.run(DefaultPlanExecutor.java:376) at org.gradle.execution.plan.DefaultPlanExecutor.process(DefaultPlanExecutor.java:111) at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph.executeWithServices(DefaultTaskExecutionGraph.java:138) at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph.execute(DefaultTaskExecutionGraph.java:123) at org.gradle.execution.SelectedTaskExecutionAction.execute(SelectedTaskExecutionAction.java:35) at org.gradle.execution.DryRunBuildExecutionAction.execute(DryRunBuildExecutionAction.java:51) at org.gradle.execution.BuildOperationFiringBuildWorkerExecutor$ExecuteTasks.call(BuildOperationFiringBuildWorkerExecutor.java:54) at org.gradle.execution.BuildOperationFiringBuildWorkerExecutor$ExecuteTasks.call(BuildOperationFiringBuildWorkerExecutor.java:43) at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:209) at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:204) at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66) at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59) at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:166) at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59) at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:53) at org.gradle.execution.BuildOperationFiringBuildWorkerExecutor.execute(BuildOperationFiringBuildWorkerExecutor.java:40) at org.gradle.internal.build.DefaultBuildLifecycleController.lambda$executeTasks$10(DefaultBuildLifecycleController.java:313) at org.gradle.internal.model.StateTransitionController.doTransition(StateTransitionController.java:266) at org.gradle.internal.model.StateTransitionController.lambda$tryTransition$8(StateTransitionController.java:177) at org.gradle.internal.work.DefaultSynchronizer.withLock(DefaultSynchronizer.java:46) at org.gradle.internal.model.StateTransitionController.tryTransition(StateTransitionController.java:177) at org.gradle.internal.build.DefaultBuildLifecycleController.executeTasks(DefaultBuildLifecycleController.java:304) at org.gradle.internal.build.DefaultBuildWorkGraphController$DefaultBuildWorkGraph.runWork(DefaultBuildWorkGraphController.java:220) at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:263) at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:127) at org.gradle.composite.internal.DefaultBuildController.doRun(DefaultBuildController.java:181) at org.gradle.composite.internal.DefaultBuildController.access$000(DefaultBuildController.java:50) at org.gradle.composite.internal.DefaultBuildController$BuildOpRunnable.lambda$run$0(DefaultBuildController.java:198) at org.gradle.internal.operations.CurrentBuildOperationRef.with(CurrentBuildOperationRef.java:85) at org.gradle.composite.internal.DefaultBuildController$BuildOpRunnable.run(DefaultBuildController.java:198) at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64) at org.gradle.internal.concurrent.AbstractManagedExecutor$1.run(AbstractManagedExecutor.java:48) Caused by: org.gradle.workers.internal.DefaultWorkerExecutor$WorkExecutionException: A failure occurred while executing org.jetbrains.kotlin.compilerRunner.GradleCompilerRunnerWithWorkers$GradleKotlinCompilerWorkAction at org.gradle.workers.internal.DefaultWorkerExecutor$WorkItemExecution.waitForCompletion(DefaultWorkerExecutor.java:287) at org.gradle.internal.work.DefaultAsyncWorkTracker.lambda$waitForItemsAndGatherFailures$2(DefaultAsyncWorkTracker.java:130) at org.gradle.internal.Factories$1.create(Factories.java:31) at org.gradle.internal.work.DefaultWorkerLeaseService.withoutLocks(DefaultWorkerLeaseService.java:335) at org.gradle.internal.work.DefaultWorkerLeaseService.withoutLocks(DefaultWorkerLeaseService.java:318) at org.gradle.internal.work.DefaultWorkerLeaseService.withoutLock(DefaultWorkerLeaseService.java:323) at org.gradle.internal.work.DefaultAsyncWorkTracker.waitForItemsAndGatherFailures(DefaultAsyncWorkTracker.java:126) at org.gradle.internal.work.DefaultAsyncWorkTracker.waitForItemsAndGatherFailures(DefaultAsyncWorkTracker.java:92) at org.gradle.internal.work.DefaultAsyncWorkTracker.waitForAll(DefaultAsyncWorkTracker.java:78) at org.gradle.internal.work.DefaultAsyncWorkTracker.waitForCompletion(DefaultAsyncWorkTracker.java:66) at org.gradle.api.internal.tasks.execution.TaskExecution$3.run(TaskExecution.java:252) at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:29) at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:26) at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66) at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59) at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:166) at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59) at org.gradle.internal.operations.DefaultBuildOperationRunner.run(DefaultBuildOperationRunner.java:47) at org.gradle.api.internal.tasks.execution.TaskExecution.executeAction(TaskExecution.java:229) at org.gradle.api.internal.tasks.execution.TaskExecution.executeActions(TaskExecution.java:212) at org.gradle.api.internal.tasks.execution.TaskExecution.executeWithPreviousOutputFiles(TaskExecution.java:195) at org.gradle.api.internal.tasks.execution.TaskExecution.execute(TaskExecution.java:162) at org.gradle.internal.execution.steps.ExecuteStep.executeInternal(ExecuteStep.java:105) at org.gradle.internal.execution.steps.ExecuteStep.access$000(ExecuteStep.java:44) at org.gradle.internal.execution.steps.ExecuteStep$1.call(ExecuteStep.java:59) at org.gradle.internal.execution.steps.ExecuteStep$1.call(ExecuteStep.java:56) at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:209) at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:204) at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66) at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59) at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:166) at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59) at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:53) at org.gradle.internal.execution.steps.ExecuteStep.execute(ExecuteStep.java:56) at org.gradle.internal.execution.steps.ExecuteStep.execute(ExecuteStep.java:44) at org.gradle.internal.execution.steps.CancelExecutionStep.execute(CancelExecutionStep.java:42) at org.gradle.internal.execution.steps.TimeoutStep.executeWithoutTimeout(TimeoutStep.java:75) at org.gradle.internal.execution.steps.TimeoutStep.execute(TimeoutStep.java:55) at org.gradle.internal.execution.steps.PreCreateOutputParentsStep.execute(PreCreateOutputParentsStep.java:50) at org.gradle.internal.execution.steps.PreCreateOutputParentsStep.execute(PreCreateOutputParentsStep.java:28) at org.gradle.internal.execution.steps.RemovePreviousOutputsStep.execute(RemovePreviousOutputsStep.java:67) at org.gradle.internal.execution.steps.RemovePreviousOutputsStep.execute(RemovePreviousOutputsStep.java:37) at org.gradle.internal.execution.steps.BroadcastChangingOutputsStep.execute(BroadcastChangingOutputsStep.java:61) at org.gradle.internal.execution.steps.BroadcastChangingOutputsStep.execute(BroadcastChangingOutputsStep.java:26) at org.gradle.internal.execution.steps.CaptureOutputsAfterExecutionStep.execute(CaptureOutputsAfterExecutionStep.java:69) at org.gradle.internal.execution.steps.CaptureOutputsAfterExecutionStep.execute(CaptureOutputsAfterExecutionStep.java:46) at org.gradle.internal.execution.steps.ResolveInputChangesStep.execute(ResolveInputChangesStep.java:40) at org.gradle.internal.execution.steps.ResolveInputChangesStep.execute(ResolveInputChangesStep.java:29) at org.gradle.internal.execution.steps.BuildCacheStep.executeWithoutCache(BuildCacheStep.java:189) at org.gradle.internal.execution.steps.BuildCacheStep.lambda$execute$1(BuildCacheStep.java:75) at org.gradle.internal.Either$Right.fold(Either.java:175) at org.gradle.internal.execution.caching.CachingState.fold(CachingState.java:62) at org.gradle.internal.execution.steps.BuildCacheStep.execute(BuildCacheStep.java:73) at org.gradle.internal.execution.steps.BuildCacheStep.execute(BuildCacheStep.java:48) at org.gradle.internal.execution.steps.StoreExecutionStateStep.execute(StoreExecutionStateStep.java:46) at org.gradle.internal.execution.steps.StoreExecutionStateStep.execute(StoreExecutionStateStep.java:35) at org.gradle.internal.execution.steps.SkipUpToDateStep.executeBecause(SkipUpToDateStep.java:75) at org.gradle.internal.execution.steps.SkipUpToDateStep.lambda$execute$2(SkipUpToDateStep.java:53) at org.gradle.internal.execution.steps.SkipUpToDateStep.execute(SkipUpToDateStep.java:53) at org.gradle.internal.execution.steps.SkipUpToDateStep.execute(SkipUpToDateStep.java:35) at org.gradle.internal.execution.steps.legacy.MarkSnapshottingInputsFinishedStep.execute(MarkSnapshottingInputsFinishedStep.java:37) at org.gradle.internal.execution.steps.legacy.MarkSnapshottingInputsFinishedStep.execute(MarkSnapshottingInputsFinishedStep.java:27) at org.gradle.internal.execution.steps.ResolveIncrementalCachingStateStep.executeDelegate(ResolveIncrementalCachingStateStep.java:49) at org.gradle.internal.execution.steps.ResolveIncrementalCachingStateStep.executeDelegate(ResolveIncrementalCachingStateStep.java:27) at org.gradle.internal.execution.steps.AbstractResolveCachingStateStep.execute(AbstractResolveCachingStateStep.java:71) at org.gradle.internal.execution.steps.AbstractResolveCachingStateStep.execute(AbstractResolveCachingStateStep.java:39) at org.gradle.internal.execution.steps.ResolveChangesStep.execute(ResolveChangesStep.java:65) at org.gradle.internal.execution.steps.ResolveChangesStep.execute(ResolveChangesStep.java:36) at org.gradle.internal.execution.steps.ValidateStep.execute(ValidateStep.java:107) at org.gradle.internal.execution.steps.ValidateStep.execute(ValidateStep.java:56) at org.gradle.internal.execution.steps.AbstractCaptureStateBeforeExecutionStep.execute(AbstractCaptureStateBeforeExecutionStep.java:64) at org.gradle.internal.execution.steps.AbstractCaptureStateBeforeExecutionStep.execute(AbstractCaptureStateBeforeExecutionStep.java:43) at org.gradle.internal.execution.steps.AbstractSkipEmptyWorkStep.executeWithNonEmptySources(AbstractSkipEmptyWorkStep.java:125) at org.gradle.internal.execution.steps.AbstractSkipEmptyWorkStep.execute(AbstractSkipEmptyWorkStep.java:61) at org.gradle.internal.execution.steps.AbstractSkipEmptyWorkStep.execute(AbstractSkipEmptyWorkStep.java:36) at org.gradle.internal.execution.steps.legacy.MarkSnapshottingInputsStartedStep.execute(MarkSnapshottingInputsStartedStep.java:38) at org.gradle.internal.execution.steps.LoadPreviousExecutionStateStep.execute(LoadPreviousExecutionStateStep.java:36) at org.gradle.internal.execution.steps.LoadPreviousExecutionStateStep.execute(LoadPreviousExecutionStateStep.java:23) at org.gradle.internal.execution.steps.HandleStaleOutputsStep.execute(HandleStaleOutputsStep.java:75) at org.gradle.internal.execution.steps.HandleStaleOutputsStep.execute(HandleStaleOutputsStep.java:41) at org.gradle.internal.execution.steps.AssignMutableWorkspaceStep.lambda$execute$0(AssignMutableWorkspaceStep.java:35) at org.gradle.api.internal.tasks.execution.TaskExecution$4.withWorkspace(TaskExecution.java:289) at org.gradle.internal.execution.steps.AssignMutableWorkspaceStep.execute(AssignMutableWorkspaceStep.java:31) at org.gradle.internal.execution.steps.AssignMutableWorkspaceStep.execute(AssignMutableWorkspaceStep.java:22) at org.gradle.internal.execution.steps.ChoosePipelineStep.execute(ChoosePipelineStep.java:40) at org.gradle.internal.execution.steps.ChoosePipelineStep.execute(ChoosePipelineStep.java:23) at org.gradle.internal.execution.steps.ExecuteWorkBuildOperationFiringStep.lambda$execute$2(ExecuteWorkBuildOperationFiringStep.java:67) at org.gradle.internal.execution.steps.ExecuteWorkBuildOperationFiringStep.execute(ExecuteWorkBuildOperationFiringStep.java:67) at org.gradle.internal.execution.steps.ExecuteWorkBuildOperationFiringStep.execute(ExecuteWorkBuildOperationFiringStep.java:39) at org.gradle.internal.execution.steps.IdentityCacheStep.execute(IdentityCacheStep.java:46) at org.gradle.internal.execution.steps.IdentityCacheStep.execute(IdentityCacheStep.java:34) at org.gradle.internal.execution.steps.IdentifyStep.execute(IdentifyStep.java:48) at org.gradle.internal.execution.steps.IdentifyStep.execute(IdentifyStep.java:35) at org.gradle.internal.execution.impl.DefaultExecutionEngine$1.execute(DefaultExecutionEngine.java:61) at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeIfValid(ExecuteActionsTaskExecuter.java:127) at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.execute(ExecuteActionsTaskExecuter.java:116) at org.gradle.api.internal.tasks.execution.FinalizePropertiesTaskExecuter.execute(FinalizePropertiesTaskExecuter.java:46) at org.gradle.api.internal.tasks.execution.ResolveTaskExecutionModeExecuter.execute(ResolveTaskExecutionModeExecuter.java:51) at org.gradle.api.internal.tasks.execution.SkipTaskWithNoActionsExecuter.execute(SkipTaskWithNoActionsExecuter.java:57) at org.gradle.api.internal.tasks.execution.SkipOnlyIfTaskExecuter.execute(SkipOnlyIfTaskExecuter.java:74) at org.gradle.api.internal.tasks.execution.CatchExceptionTaskExecuter.execute(CatchExceptionTaskExecuter.java:36) at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.executeTask(EventFiringTaskExecuter.java:77) at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:55) at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:52) at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:209) at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:204) at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66) at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59) at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:166) at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59) at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:53) at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter.execute(EventFiringTaskExecuter.java:52) at org.gradle.execution.plan.LocalTaskNodeExecutor.execute(LocalTaskNodeExecutor.java:42) at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:331) at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:318) at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.lambda$execute$0(DefaultTaskExecutionGraph.java:314) at org.gradle.internal.operations.CurrentBuildOperationRef.with(CurrentBuildOperationRef.java:85) at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:314) at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:303) at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.execute(DefaultPlanExecutor.java:459) at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.run(DefaultPlanExecutor.java:376) at org.gradle.execution.plan.DefaultPlanExecutor.process(DefaultPlanExecutor.java:111) at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph.executeWithServices(DefaultTaskExecutionGraph.java:138) at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph.execute(DefaultTaskExecutionGraph.java:123) at org.gradle.execution.SelectedTaskExecutionAction.execute(SelectedTaskExecutionAction.java:35) at org.gradle.execution.DryRunBuildExecutionAction.execute(DryRunBuildExecutionAction.java:51) at org.gradle.execution.BuildOperationFiringBuildWorkerExecutor$ExecuteTasks.call(BuildOperationFiringBuildWorkerExecutor.java:54) at org.gradle.execution.BuildOperationFiringBuildWorkerExecutor$ExecuteTasks.call(BuildOperationFiringBuildWorkerExecutor.java:43) at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:209) at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:204) at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66) at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59) at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:166) at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59) at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:53) at org.gradle.execution.BuildOperationFiringBuildWorkerExecutor.execute(BuildOperationFiringBuildWorkerExecutor.java:40) at org.gradle.internal.build.DefaultBuildLifecycleController.lambda$executeTasks$10(DefaultBuildLifecycleController.java:313) at org.gradle.internal.model.StateTransitionController.doTransition(StateTransitionController.java:266) at org.gradle.internal.model.StateTransitionController.lambda$tryTransition$8(StateTransitionController.java:177) at org.gradle.internal.work.DefaultSynchronizer.withLock(DefaultSynchronizer.java:46) at org.gradle.internal.model.StateTransitionController.tryTransition(StateTransitionController.java:177) at org.gradle.internal.build.DefaultBuildLifecycleController.executeTasks(DefaultBuildLifecycleController.java:304) at org.gradle.internal.build.DefaultBuildWorkGraphController$DefaultBuildWorkGraph.runWork(DefaultBuildWorkGraphController.java:220) at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:263) at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:127) at org.gradle.composite.internal.DefaultBuildController.doRun(DefaultBuildController.java:181) at org.gradle.composite.internal.DefaultBuildController.access$000(DefaultBuildController.java:50) at org.gradle.composite.internal.DefaultBuildController$BuildOpRunnable.lambda$run$0(DefaultBuildController.java:198) at org.gradle.internal.operations.CurrentBuildOperationRef.with(CurrentBuildOperationRef.java:85) at org.gradle.composite.internal.DefaultBuildController$BuildOpRunnable.run(DefaultBuildController.java:198) at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64) at org.gradle.internal.concurrent.AbstractManagedExecutor$1.run(AbstractManagedExecutor.java:48) Caused by: org.jetbrains.kotlin.gradle.tasks.CompilationErrorException: Compilation error. See log for more details at org.jetbrains.kotlin.gradle.tasks.TasksUtilsKt.throwExceptionIfCompilationFailed(tasksUtils.kt:21) at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.run(GradleKotlinCompilerWork.kt:119) at org.jetbrains.kotlin.compilerRunner.GradleCompilerRunnerWithWorkers$GradleKotlinCompilerWorkAction.execute(GradleCompilerRunnerWithWorkers.kt:76) at org.gradle.workers.internal.DefaultWorkerServer.execute(DefaultWorkerServer.java:63) at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:66) at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:62) at org.gradle.internal.classloader.ClassLoaderUtils.executeInClassloader(ClassLoaderUtils.java:100) at org.gradle.workers.internal.NoIsolationWorkerFactory$1.lambda$execute$0(NoIsolationWorkerFactory.java:62) at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:44) at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:41) at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:209) at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:204) at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66) at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59) at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:166) at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59) at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:53) at org.gradle.workers.internal.AbstractWorker.executeWrappedInBuildOperation(AbstractWorker.java:41) at org.gradle.workers.internal.NoIsolationWorkerFactory$1.execute(NoIsolationWorkerFactory.java:59) at org.gradle.workers.internal.DefaultWorkerExecutor.lambda$submitWork$0(DefaultWorkerExecutor.java:174) at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runExecution(DefaultConditionalExecutionQueue.java:194) at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.access$700(DefaultConditionalExecutionQueue.java:127) at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner$1.run(DefaultConditionalExecutionQueue.java:169) at org.gradle.internal.Factories$1.create(Factories.java:31) at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:263) at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:127) at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:132) at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runBatch(DefaultConditionalExecutionQueue.java:164) at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.run(DefaultConditionalExecutionQueue.java:133) ... 2 more BUILD FAILED in 48s
07-17
<think>我们遇到了编译错误,错误信息如下: e: file:///D:/AndroidStudioProjects/MyApplication/app/src/main/java/com/example/myapplication/MainActivity.kt:19:20 Unresolved reference 'calculator'. e: file:///D:/AndroidStudioProjects/MyApplication/app/src/main/java/com/example/myapplication/MainActivity.kt:27:13 Unresolved reference 'CalculatorTheme'. e: file:///D:/AndroidStudioProjects/MyApplication/app/src/main/java/com/example/myapplication/MainActivity.kt:28:17 @Composable invocations can only happen from the context of a @Composable function e: file:///D:/AndroidStudioProjects/MyApplication/app/src/main/java/com/example/myapplication/MainActivity.kt:30:43 @Composable invocations can only happen from the context of a @Composable function 分析错误: 1. "Unresolved reference 'calculator'":可能是因为我们使用了`com.example.calculator`包名,但实际项目包名是`com.example.myapplication`。我们需要将包名改为实际包名,或者修改导入。 2. "Unresolved reference 'CalculatorTheme'":这个主题可能是我们自定义的,但代码中并没有定义。通常,在Android Studio创建Compose项目时会自动生成一个以项目名命名的Theme,比如`MyApplicationTheme`。我们需要使用正确的主题。 3. 两个关于`@Composable`的错误:这是因为在非Composable函数中调用了Composable函数。检查代码,确保在Composable函数中调用其他Composable函数。 解决方案: 1. 修改包名为实际包名,或者将代码移动到正确的包中。 2. 将`CalculatorTheme`改为自动生成的主题,例如`MyApplicationTheme`(根据实际项目生成的主题类名)。 3. 确保在`setContent`中调用的函数是Composable函数(使用`@Composable`注解)。 另外,注意我们的代码中有一个`CalculatorTheme`,但实际项目可能没有这个主题。我们可以使用默认生成的Theme,或者创建一个。 由于错误信息中显示包名为`com.example.myapplication`,我们假设项目名为MyApplication,那么主题应该是`MyApplicationTheme`。 修改步骤: 1. 将`import com.example.calculator.ui.theme.CalculatorTheme` 改为 `import com.example.myapplication.ui.theme.MyApplicationTheme`(如果存在)或者删除并使用默认主题。 2. 如果没有自定义主题,我们可以直接使用`MaterialTheme`,或者创建一个主题。 但是,在新建的Compose项目中,Android Studio会自动生成一个主题,位于`ui.theme`包下。因此,我们可能需要调整导入。 另外,注意在`MainActivity`中,`setContent`里面调用了`CalculatorApp`,而`CalculatorApp`是一个Composable函数,所以需要`@Composable`注解。 检查代码: - `MainActivity`中的`onCreate`方法中调用了`setContent`,这是Compose的入口,里面调用了`CalculatorApp()`,而`CalculatorApp`是一个Composable函数,所以没有问题。但是错误信息提示在`MainActivity`的第28行和30行有错误,我们检查一下: 第27行:`CalculatorTheme {` -> 这里因为CalculatorTheme无法解析,所以报错 第28行:`Surface(...) {` -> 由于在非Composable函数中?实际上,我们在`setContent`的lambda中,这个lambda是Composable的,所以应该没问题。但前提是`CalculatorTheme`要存在。 因此,我们主要解决两个问题:包名和主题。 修改后的`MainActivity`: 1. 修改导入的主题为实际主题(假设项目名为MyApplication,则主题为MyApplicationTheme)。 2. 如果没有,则创建一个简单的主题。 由于我们无法确定实际生成的主题名称,这里我们假设使用`MyApplicationTheme`,并修改导入语句。 同时,注意错误中提到的第19行:`import com.example.calculator.ui.theme.CalculatorTheme`,这个导入不存在,所以改为`import com.example.myapplication.ui.theme.MyApplicationTheme`。 如果项目中没有自定义主题,我们可以直接使用`MaterialTheme`。 修改后的代码: ```kotlin package com.example.myapplication // 确保包名正确 import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp // 导入自动生成的主题,如果不存在,请使用MaterialTheme // 如果Android Studio自动生成了主题,它通常在ui.theme包下 import com.example.myapplication.ui.theme.MyApplicationTheme class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { // 使用自动生成的主题,如果不存在,请替换为MaterialTheme MyApplicationTheme { // 或者直接使用 MaterialTheme Surface( modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background ) { CalculatorApp() } } } } } // 其余代码保持不变... ``` 如果项目中没有`MyApplicationTheme`,我们可以暂时用`MaterialTheme`代替: ```kotlin setContent { MaterialTheme { Surface( modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background ) { CalculatorApp() } } } ``` 另外,确保在`build.gradle`中正确引入了Compose依赖。 如果以上修改后仍然有错误,请检查主题是否在对应的包中定义。如果不存在,可以创建一个: 在`ui/theme`包下,通常有`Theme.kt`文件,定义如下: ```kotlin package com.example.myapplication.ui.theme import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable @Composable fun MyApplicationTheme( content: @Composable () -> Unit ) { MaterialTheme( content = content ) } ``` 这样,我们就可以使用`MyApplicationTheme`了。 总结修改步骤: 1. 修改包名为实际包名。 2. 修改主题为项目生成的主题(如`MyApplicationTheme`)或者使用`MaterialTheme`。 3. 确保所有Composable函数都被正确注解。 修改后,重新构建项目。</think>根据您提供的错误日志,问题主要集中在以下几个方面: ```plaintext e: file:///D:/AndroidStudioProjects/MyApplication/app/src/main/java/com/example/myapplication/MainActivity.kt:19:20 Unresolved reference 'calculator'. e: file:///D:/AndroidStudioProjects/MyApplication/app/src/main/java/com/example/myapplication/MainActivity.kt:27:13 Unresolved reference 'CalculatorTheme'. e: file:///D:/AndroidStudioProjects/MyApplication/app/src/main/java/com/example/myapplication/MainActivity.kt:28:17 @Composable invocations can only happen from the context of a @Composable function e: file:///D:/AndroidStudioProjects/MyApplication/app/src/main/java/com/example/myapplication/MainActivity.kt:30:43 @Composable invocations can only happen from the context of a @Composable function ``` ### 问题分析与解决方案 #### 1. **未解析的引用 'calculator'** 错误发生在第19行:`import com.example.calculator.ui.theme.CalculatorTheme` **解决方案**: - 确保项目包名正确 - 删除或更正错误的导入语句 修改后的`MainActivity.kt`文件开头: ```kotlin package com.example.myapplication // 确保使用正确的包名 import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp // 删除错误的导入: import com.example.calculator.ui.theme.CalculatorTheme import com.example.myapplication.ui.theme.MyApplicationTheme // 使用自动生成的主题名 ``` #### 2. **未解析的引用 'CalculatorTheme'** 错误发生在第27行:`CalculatorTheme {` **解决方案**: - 使用Android Studio自动生成的主题名(通常是`<项目名>Theme`) - 或者使用通用的MaterialTheme 修改后的主题设置: ```kotlin class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { // 使用自动生成的主题名 MyApplicationTheme { // 或者直接使用 MaterialTheme MaterialTheme { Surface( modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background ) { CalculatorApp() } } } } } } ``` #### 3. **@Composable 函数调用错误** 错误发生在第28行和30行,表明在非Composable上下文中调用了Composable函数 **解决方案**: - 确保所有UI组件都被正确的`@Composable`注解标记 - 检查函数调用层级 完整修复后的`CalculatorApp`组件: ```kotlin @Composable fun CalculatorApp() { var expression by remember { mutableStateOf("") } var result by remember { mutableStateOf("0") } var errorMessage by remember { mutableStateOf("") } Column( modifier = Modifier .fillMaxSize() .background(Color(0xFF1D1D1D)) .padding(16.dp), verticalArrangement = Arrangement.SpaceBetween ) { // 显示区域 DisplayArea(expression, result, errorMessage) // 按钮区域 ButtonsArea( onInput = { expression += it }, onCalculate = { try { val calcResult = calculateExpression(expression) result = formatResult(calcResult) errorMessage = "" } catch (e: Exception) { errorMessage = e.message ?: "计算错误" } }, onClear = { expression = "" result = "0" errorMessage = "" }, onDelete = { expression = expression.dropLast(1) errorMessage = "" } ) } } @Composable fun DisplayArea(expression: String, result: String, errorMessage: String) { Column( modifier = Modifier .fillMaxWidth() .weight(1f) .padding(bottom = 16.dp), verticalArrangement = Arrangement.Bottom ) { if (errorMessage.isNotEmpty()) { Text( text = errorMessage, color = Color.Red, fontSize = 18.sp, modifier = Modifier.fillMaxWidth(), textAlign = TextAlign.End ) } Text( text = expression, color = Color(0xFFA0A0A0), fontSize = 24.sp, modifier = Modifier.fillMaxWidth(), textAlign = TextAlign.End ) Text( text = result, color = Color.White, fontSize = 48.sp, fontWeight = FontWeight.Bold, modifier = Modifier.fillMaxWidth(), textAlign = TextAlign.End ) } } @Composable fun ButtonsArea( onInput: (String) -> Unit, onCalculate: () -> Unit, onClear: () -> Unit, onDelete: () -> Unit ) { Column( modifier = Modifier .fillMaxWidth() .weight(1.5f), verticalArrangement = Arrangement.spacedBy(12.dp) ) { // 第一行:清除按钮 Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(12.dp) ) { CalculatorButton( text = "C", color = Color(0xFFA5A5A5), textColor = Color.Black, modifier = Modifier.weight(1f), onClick = onClear ) CalculatorButton( text = "DEL", color = Color(0xFFA5A5A5), textColor = Color.Black, modifier = Modifier.weight(1f), onClick = onDelete ) CalculatorButton( text = "%", color = Color(0xFFA5A5A5), textColor = Color.Black, modifier = Modifier.weight(1f), onClick = { onInput("%") } ) CalculatorButton( text = "÷", color = Color(0xFFFF9500), modifier = Modifier.weight(1f), onClick = { onInput("/") } ) } // 其余按钮行类似实现... // 注意:确保所有按钮行都使用正确的Composable组件 } } ``` ### 完整修复后的MainActivity.kt ```kotlin package com.example.myapplication import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import java.lang.NumberFormatException import kotlin.math.pow class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { // 使用自动生成的主题名或MaterialTheme MaterialTheme { Surface( modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background ) { CalculatorApp() } } } } } @Composable fun CalculatorApp() { var expression by remember { mutableStateOf("") } var result by remember { mutableStateOf("0") } var errorMessage by remember { mutableStateOf("") } Column( modifier = Modifier .fillMaxSize() .background(Color(0xFF1D1D1D)) .padding(16.dp), verticalArrangement = Arrangement.SpaceBetween ) { // 显示区域 DisplayArea(expression, result, errorMessage) // 按钮区域 ButtonsArea( onInput = { expression += it }, onCalculate = { try { val calcResult = calculateExpression(expression) result = formatResult(calcResult) errorMessage = "" } catch (e: Exception) { errorMessage = e.message ?: "计算错误" } }, onClear = { expression = "" result = "0" errorMessage = "" }, onDelete = { expression = expression.dropLast(1) errorMessage = "" } ) } } @Composable fun DisplayArea(expression: String, result: String, errorMessage: String) { Column( modifier = Modifier .fillMaxWidth() .weight(1f) .padding(bottom = 16.dp), verticalArrangement = Arrangement.Bottom ) { if (errorMessage.isNotEmpty()) { Text( text = errorMessage, color = Color.Red, fontSize = 18.sp, modifier = Modifier.fillMaxWidth(), textAlign = TextAlign.End ) } Text( text = expression, color = Color(0xFFA0A0A0), fontSize = 24.sp, modifier = Modifier.fillMaxWidth(), textAlign = TextAlign.End ) Text( text = result, color = Color.White, fontSize = 48.sp, fontWeight = FontWeight.Bold, modifier = Modifier.fillMaxWidth(), textAlign = TextAlign.End ) } } @Composable fun ButtonsArea( onInput: (String) -> Unit, onCalculate: () -> Unit, onClear: () -> Unit, onDelete: () -> Unit ) { Column( modifier = Modifier .fillMaxWidth() .weight(1.5f), verticalArrangement = Arrangement.spacedBy(12.dp) ) { // 第一行:清除按钮 Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(12.dp) ) { CalculatorButton( text = "C", color = Color(0xFFA5A5A5), textColor = Color.Black, modifier = Modifier.weight(1f), onClick = onClear ) CalculatorButton( text = "DEL", color = Color(0xFFA5A5A5), textColor = Color.Black, modifier = Modifier.weight(1f), onClick = onDelete ) CalculatorButton( text = "%", color = Color(0xFFA5A5A5), textColor = Color.Black, modifier = Modifier.weight(1f), onClick = { onInput("%") } ) CalculatorButton( text = "÷", color = Color(0xFFFF9500), modifier = Modifier.weight(1f), onClick = { onInput("/") } ) } // 第二行:数字7-9和乘号 Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(12.dp) ) { CalculatorButton( text = "7", modifier = Modifier.weight(1f), onClick = { onInput("7") } ) CalculatorButton( text = "8", modifier = Modifier.weight(1f), onClick = { onInput("8") } ) CalculatorButton( text = "9", modifier = Modifier.weight(1f), onClick = { onInput("9") } ) CalculatorButton( text = "×", color = Color(0xFFFF9500), modifier = Modifier.weight(1f), onClick = { onInput("*") } ) } // 第三行:数字4-6和减号 Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(12.dp) ) { CalculatorButton( text = "4", modifier = Modifier.weight(1f), onClick = { onInput("4") } ) CalculatorButton( text = "5", modifier = Modifier.weight(1f), onClick = { onInput("5") } ) CalculatorButton( text = "6", modifier = Modifier.weight(1f), onClick = { onInput("6") } ) CalculatorButton( text = "-", color = Color(0xFFFF9500), modifier = Modifier.weight(1f), onClick = { onInput("-") } ) } // 第四行:数字1-3和号 Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(12.dp) ) { CalculatorButton( text = "1", modifier = Modifier.weight(1f), onClick = { onInput("1") } ) CalculatorButton( text = "2", modifier = Modifier.weight(1f), onClick = { onInput("2") } ) CalculatorButton( text = "3", modifier = Modifier.weight(1f), onClick = { onInput("3") } ) CalculatorButton( text = "+", color = Color(0xFFFF9500), modifier = Modifier.weight(1f), onClick = { onInput("+") } ) } // 第五行:0、小数点、等号 Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(12.dp) ) { CalculatorButton( text = "0", modifier = Modifier.weight(2.2f), onClick = { onInput("0") } ) CalculatorButton( text = ".", modifier = Modifier.weight(1f), onClick = { onInput(".") } ) CalculatorButton( text = "=", color = Color(0xFFFF9500), modifier = Modifier.weight(1f), onClick = onCalculate ) } } } @Composable fun CalculatorButton( text: String, modifier: Modifier = Modifier, color: Color = Color(0xFF333333), textColor: Color = Color.White, onClick: () -> Unit ) { Button( onClick = onClick, shape = RoundedCornerShape(16.dp), colors = ButtonDefaults.buttonColors( containerColor = color, contentColor = textColor ), modifier = modifier .aspectRatio(1f) .height(72.dp) ) { Text( text = text, fontSize = 24.sp, fontWeight = FontWeight.Bold ) } } // 结果格式化函数 fun formatResult(result: Double): String { return if (result % 1 == 0.0) { result.toInt().toString() } else { String.format("%.6f", result).trimEnd('0').trimEnd('.') } } // 计算核心逻辑(保持不变) fun calculateExpression(expr: String): Double { val processed = expr.replace(" ", "") .replace(Regex("(?<=\\d)\\("), "*(") .replace(Regex("\\)(?=\\d)"), ")*") .replace("%", "/100") val tokens = tokenize(processed) val rpn = shuntingYard(tokens) return evaluateRPN(rpn) } // 其余计算函数(tokenize, shuntingYard, evaluateRPN等)保持不变... ``` ### 关键修复点说明 1. **包名和导入修复**: - 移除了错误的`import com.example.calculator.ui.theme.CalculatorTheme` - 使用`MaterialTheme`作为替代方案 2. **Composable函数结构优化**: - 将大组件拆分为`DisplayArea`和`ButtonsArea`等小组件 - 确保所有UI函数都正确添加了`@Composable`注解 - 使用回调函数处理按钮点击事件 3. **主题设置简化**: - 使用`MaterialTheme`替代自定义主题 - 移除了对不存在主题的引用 4. **代码结构优化**: - 添加了结果格式化函数`formatResult` - 优化了状态管理逻辑 - 增强了错误处理机制 ### 验证步骤 1. 清理并重建项目: - 在Android Studio中选择 `Build > Clean Project` - 然后选择 `Build > Rebuild Project` 2. 确保Gradle依赖正确: ```gradle // app/build.gradle dependencies { implementation 'androidx.core:core-ktx:1.10.0' implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.1' implementation 'androidx.activity:activity-compose:1.7.1' implementation "androidx.compose.ui:ui:$compose_version" implementation "androidx.compose.ui:ui-tooling-preview:$compose_version" implementation 'androidx.compose.material3:material3:1.1.0' testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.5' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version" debugImplementation "androidx.compose.ui:ui-tooling:$compose_version" debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_version" } ``` 3. 检查AndroidManifest.xml: ```xml <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.myapplication"> ``` 如果问题仍然存在,请尝试创建一个全新的Compose Activity,然后逐步迁移代码。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值