2 Commits 8a2a096252 ... d760ad0444

Autore SHA1 Messaggio Data
  reghao d760ad0444 更新 CompilerController 接口 1 giorno fa
  reghao 91770a48d6 AppConfig.vue 中采用分步表单方式添加 AppConfig 1 giorno fa

+ 1 - 1
src/api/devops.js

@@ -266,7 +266,7 @@ export function getAppBindDomain(appId) {
 }
 
 export function addAppConfig(formData) {
-  return postForm(devopsApi.getAppConfigList, formData)
+  return post(devopsApi.getAppConfigList, formData)
 }
 
 export function copyAppConfig(formData) {

+ 154 - 130
src/views/devops/app/AppConfig.vue

@@ -39,10 +39,6 @@
         height="480"
         style="width: 100%"
       >
-        <el-table-column
-          prop="appName"
-          label="应用名"
-        />
         <el-table-column
           prop="appId"
           label="应用 ID"
@@ -52,8 +48,8 @@
           label="分支"
         />
         <el-table-column
-          prop="bindPorts"
-          label="监听端口"
+          prop="httpPort"
+          label="HTTP 端口"
         />
         <el-table-column
           prop="totalDeployNodes"
@@ -202,17 +198,14 @@
           <el-form-item label="应用 ID" style="width: 70%; padding-right: 2px">
             <el-input v-model="editForm.appId" style="width: 70%; padding-right: 2px" readonly />
           </el-form-item>
-          <el-form-item label="应用名" style="width: 70%; padding-right: 2px">
-            <el-input v-model="editForm.appName" style="width: 70%; padding-right: 2px" />
-          </el-form-item>
           <el-form-item label="应用仓库" style="width: 70%; padding-right: 2px">
             <el-input v-model="editForm.appRepo" style="width: 70%; padding-right: 2px" readonly />
           </el-form-item>
           <el-form-item label="仓库分支" style="width: 70%; padding-right: 2px">
             <el-input v-model="editForm.repoBranch" style="width: 70%; padding-right: 2px" />
           </el-form-item>
-          <el-form-item label="监听端口" style="width: 70%; padding-right: 2px">
-            <el-input v-model="editForm.bindPorts" style="width: 70%; padding-right: 2px" />
+          <el-form-item label="HTTP 端口" style="width: 70%; padding-right: 2px">
+            <el-input v-model="editForm.httpPort" style="width: 70%; padding-right: 2px" />
           </el-form-item>
           <el-form-item label="仓库认证">
             <el-select v-model="editForm.repoAuthConfig" placeholder="选择仓库认证">
@@ -264,20 +257,14 @@
           <el-form-item label="应用 ID" style="width: 70%; padding-right: 2px">
             <el-input v-model="appConfigDetail.appId" style="width: 70%; padding-right: 2px" readonly />
           </el-form-item>
-          <el-form-item label="应用名" style="width: 70%; padding-right: 2px">
-            <el-input v-model="appConfigDetail.appName" style="width: 70%; padding-right: 2px" readonly />
-          </el-form-item>
           <el-form-item label="应用仓库" style="width: 70%; padding-right: 2px">
             <el-input v-model="appConfigDetail.appRepo" style="width: 70%; padding-right: 2px" readonly />
           </el-form-item>
           <el-form-item label="仓库分支" style="width: 70%; padding-right: 2px">
             <el-input v-model="appConfigDetail.repoBranch" style="width: 70%; padding-right: 2px" readonly />
           </el-form-item>
-          <el-form-item label="应用路径" style="width: 70%; padding-right: 2px">
-            <el-input v-model="appConfigDetail.appRootPath" style="width: 70%; padding-right: 2px" readonly />
-          </el-form-item>
-          <el-form-item label="监听端口" style="width: 70%; padding-right: 2px">
-            <el-input v-model="appConfigDetail.bindPorts" style="width: 70%; padding-right: 2px" readonly />
+          <el-form-item label="HTTP 端口" style="width: 70%; padding-right: 2px">
+            <el-input v-model="appConfigDetail.httpPort" style="width: 70%; padding-right: 2px" readonly />
           </el-form-item>
           <el-form-item label="仓库认证">
             <el-input v-model="appConfigDetail.repoAuthConfig" style="width: 70%; padding-right: 2px" readonly />
@@ -301,82 +288,67 @@
       center
     >
       <template>
-        <el-form :model="addForm" label-width="80px">
-          <el-form-item label="应用类型">
-            <el-select v-model="addForm.appType" placeholder="选择类型">
-              <el-option
-                v-for="(item, index) in appTypeList"
-                :key="index"
-                :label="item.label"
-                :value="item.value"
-              />
-            </el-select>
-          </el-form-item>
-          <el-form-item label="所属环境">
-            <el-select v-model="addForm.env" placeholder="选择环境">
-              <el-option
-                v-for="(item, index) in envList"
-                :key="index"
-                :label="item.label"
-                :value="item.value"
-              />
-            </el-select>
-          </el-form-item>
-          <el-form-item label="应用 ID" style="width: 70%; padding-right: 2px">
-            <el-input v-model="addForm.appId" style="width: 70%; padding-right: 2px" />
-          </el-form-item>
-          <el-form-item label="应用名" style="width: 70%; padding-right: 2px">
-            <el-input v-model="addForm.appName" style="width: 70%; padding-right: 2px" />
-          </el-form-item>
-          <el-form-item label="应用仓库" style="width: 70%; padding-right: 2px">
-            <el-input v-model="addForm.appRepo" style="width: 70%; padding-right: 2px" />
-          </el-form-item>
-          <el-form-item label="仓库分支" style="width: 70%; padding-right: 2px">
-            <el-input v-model="addForm.repoBranch" style="width: 70%; padding-right: 2px" />
-          </el-form-item>
-          <el-form-item label="应用路径" style="width: 70%; padding-right: 2px">
-            <el-input v-model="addForm.appRootPath" style="width: 70%; padding-right: 2px" />
-          </el-form-item>
-          <el-form-item label="监听端口" style="width: 70%; padding-right: 2px">
-            <el-input v-model="addForm.bindPorts" style="width: 70%; padding-right: 2px" />
-          </el-form-item>
-          <el-form-item label="仓库认证">
-            <el-select v-model="addForm.repoAuthConfig" placeholder="选择仓库认证">
-              <el-option
-                v-for="(item, index) in repoAuthList"
-                :key="index"
-                :label="item.label"
-                :value="item.label"
-              />
-            </el-select>
-          </el-form-item>
-          <el-form-item label="编译工具">
-            <el-select v-model="addForm.compilerConfig" placeholder="选择编译工具">
-              <el-option
-                v-for="(item, index) in compilerList"
-                :key="index"
-                :label="item.label"
-                :value="item.label"
-              />
-            </el-select>
-          </el-form-item>
-          <el-form-item label="打包工具">
-            <el-select v-model="addForm.packerConfig" placeholder="选择打包工具">
-              <el-option
-                v-for="(item, index) in packerList"
-                :key="index"
-                :label="item.label"
-                :value="item.label"
-              />
-            </el-select>
-          </el-form-item>
-          <el-form-item label="Dockerfile" style="width: 70%; padding-right: 2px">
-            <el-input v-model="addForm.dockerfile" type="textarea" :rows="10" style="width: 70%; padding-right: 2px" />
-          </el-form-item>
-          <el-form-item>
-            <el-button type="primary" @click="onAdd">确定</el-button>
-          </el-form-item>
-        </el-form>
+        <div class="app-config-container">
+          <el-steps :active="active" finish-status="success" simple style="margin-bottom: 20px">
+            <el-step title="基础信息"></el-step>
+            <el-step title="构建环境"></el-step>
+            <el-step title="详细配置"></el-step>
+          </el-steps>
+
+          <el-form :model="appForm" :rules="appFormRules" ref="appFormRef" label-width="120px">
+            <div v-show="active === 0">
+              <el-form-item label="应用仓库" prop="appRepo">
+                <el-input v-model="appForm.appRepo" placeholder="e.g. user-service"></el-input>
+              </el-form-item>
+              <el-form-item label="仓库分支" prop="repoBranch">
+                <el-input v-model="appForm.repoBranch"></el-input>
+              </el-form-item>
+              <el-form-item label="仓库认证" prop="repoAuthConfigId">
+                <el-select v-model="appForm.repoAuthConfigId" placeholder="选择认证">
+                  <el-option v-for="item in repoAuthList" :key="item.value" :label="item.label" :value="item.value"></el-option>
+                </el-select>
+              </el-form-item>
+            </div>
+
+            <div v-show="active === 1">
+              <el-form-item label="应用类型" prop="appType">
+                <el-select v-model="appForm.appType" placeholder="选择应用类型">
+                  <el-option v-for="item in appTypeList" :key="item.value" :label="item.label" :value="item.value"></el-option>
+                </el-select>
+              </el-form-item>
+              <el-form-item label="环境" prop="env">
+                <el-radio-group v-model="appForm.env">
+                  <el-radio v-for="item in envList" :key="item.value" :label="item.label" :value="item.value"></el-radio>
+                </el-radio-group>
+              </el-form-item>
+              <el-form-item label="编译器配置" prop="compilerConfigId">
+                <el-select v-model="appForm.compilerConfigId">
+                  <el-option v-for="item in compilerList" :key="item.value" :label="item.label" :value="item.value"></el-option>
+                </el-select>
+              </el-form-item>
+              <el-form-item label="打包配置" prop="packerConfigId">
+                <el-select v-model="appForm.packerConfigId">
+                  <el-option v-for="item in packerList" :key="item.value" :label="item.label" :value="item.value"></el-option>
+                </el-select>
+              </el-form-item>
+            </div>
+
+            <div v-show="active === 2">
+              <el-form-item label="HTTP 端口" prop="httpPort">
+                <el-input v-model="appForm.httpPort" placeholder="8080"></el-input>
+              </el-form-item>
+              <el-form-item label="Dockerfile" prop="dockerfile">
+                <el-input type="textarea" :rows="8" v-model="appForm.dockerfile" placeholder="FROM ..."></el-input>
+              </el-form-item>
+            </div>
+
+            <div class="footer-btns" style="text-align: center; margin-top: 20px">
+              <el-button v-if="active > 0" @click="prev">上一步</el-button>
+              <el-button v-if="active < 2" type="primary" @click="next">下一步</el-button>
+              <el-button v-if="active === 2" type="success" @click="submit">提交配置</el-button>
+            </div>
+          </el-form>
+        </div>
       </template>
     </el-dialog>
     <el-dialog
@@ -394,10 +366,6 @@
           height="480"
           style="width: 100%"
         >
-          <el-table-column
-            prop="appName"
-            label="应用名"
-          />
           <el-table-column
             prop="machineIpv4"
             label="机器地址"
@@ -528,6 +496,29 @@ export default {
       totalSize: 0,
       dataList: [],
       // **********************************************************************
+      active: 0,
+      appForm: {
+        appRepo: '',
+        repoBranch: '',
+        env: '',
+        appType: '',
+        repoAuthConfigId: null,
+        compilerConfigId: null,
+        packerConfigId: null,
+        dockerfile: '',
+        appId: '',
+      },
+      appFormRules: {
+        appRepo: [{ required: true, message: '代码仓库不能为空', trigger: 'blur' }],
+        repoBranch: [{ required: true, message: '仓库分支不能为空', trigger: 'blur' }],
+        'repoAuthConfigId': [{ required: true, message: '请选择仓库认证', trigger: 'blur' }],
+        appType: [{ required: true, message: '应用类型不能为空', trigger: 'blur' }],
+        env: [{ required: true, message: '应用环境不能为空', trigger: 'blur' }],
+        'compilerConfigId': [{ required: true, message: '请选择编译器', trigger: 'blur' }],
+        'packerConfigId': [{ required: true, message: '请选择打包配置', trigger: 'blur' }],
+        dockerfile: [{ required: true, message: '请填写 Dockerfile', trigger: 'blur' }],
+      },
+      // **********************************************************************
       showBindDomainDialog: false,
       domainList: [],
       showElInput: false,
@@ -544,11 +535,9 @@ export default {
         appType: null,
         env: null,
         appId: null,
-        appName: null,
         appRepo: null,
         repoBranch: null,
-        appRootPath: null,
-        bindPorts: null,
+        httpPort: null,
         repoAuthConfig: null,
         compilerConfig: null,
         packerConfig: null,
@@ -566,7 +555,6 @@ export default {
       showEditDialog: false,
       editForm: {
         appId: null,
-        appName: null,
         repoBranch: null,
         repoAuthConfig: null,
         compilerConfig: null,
@@ -744,7 +732,6 @@ export default {
     onEdit() {
       const formData = new FormData()
       formData.append('appId', this.editForm.appId)
-      formData.append('appName', this.editForm.appName)
       formData.append('repoBranch', this.editForm.repoBranch)
       formData.append('repoAuthConfig', this.editForm.repoAuthConfig)
       formData.append('compilerConfig', this.editForm.compilerConfig)
@@ -788,31 +775,68 @@ export default {
         this.$message.error(error.message)
       })
     },
-    onAdd() {
-      const formData = new FormData()
-      formData.append('appId', this.addForm.appId)
-      formData.append('appName', this.addForm.appName)
-      formData.append('appType', this.addForm.appType)
-      formData.append('env', this.addForm.env)
-      formData.append('appRepo', this.addForm.appRepo)
-      formData.append('repoBranch', this.addForm.repoBranch)
-      formData.append('appRootPath', this.addForm.appRootPath)
-      formData.append('bindPorts', this.addForm.bindPorts)
-      formData.append('repoAuthConfig', this.addForm.repoAuthConfig)
-      formData.append('compilerConfig', this.addForm.compilerConfig)
-      formData.append('packerConfig', this.addForm.packerConfig)
-      formData.append('dockerfile', this.addForm.dockerfile)
-      addAppConfig(formData).then(resp => {
-        if (resp.code === 0) {
-          this.getData()
-        } else {
-          this.$message.info(resp.msg)
+    // ****************************************************************************************************************
+    // 添加 AppConfig
+    // ****************************************************************************************************************
+    async next() {
+      const fieldsForSteps = [
+        ['appRepo', 'repoBranch', 'repoAuthConfigId'],
+        ['appType', 'env', 'compilerConfigId', 'packerConfigId'],
+        ['dockerfile']
+      ];
+
+      const currentFields = fieldsForSteps[this.active];
+
+      // 1. 手动封装校验逻辑,确保所有字段校验完才继续
+      try {
+        const validations = currentFields.map(field => {
+          return new Promise((resolve, reject) => {
+            this.$refs.appFormRef.validateField(field, (errorMessage) => {
+              if (errorMessage) {
+                reject(errorMessage);
+              } else {
+                resolve();
+              }
+            });
+          });
+        });
+
+        await Promise.all(validations);
+
+        // 2. 只有全部成功才会走到这里
+        if (this.active < 2) {
+          this.active++;
         }
-      }).catch(error => {
-        this.$message.error(error.message)
-      }).finally(() => {
-        this.showAddDialog = false
-      })
+      } catch (error) {
+        this.$message.error('校验未通过:', error);
+        // 校验失败,保持在当前页
+      }
+    },
+    prev() {
+      if (this.active > 0) this.active--;
+    },
+    submit() {
+      this.$refs.appFormRef.validate((valid) => {
+        if (valid) {
+          addAppConfig(this.appForm).then(resp => {
+            console.log('提交数据:', this.form);
+            this.$message.success('配置已保存');
+            if (resp.code === 0) {
+              this.active = 0;
+              this.$refs.appFormRef.resetFields();
+              this.appForm.repoAuthConfig = { id: null };
+              this.appForm.compilerConfig = { id: null };
+              this.getData()
+            } else {
+              this.$message.info(resp.msg)
+            }
+          }).catch(error => {
+            this.$message.error(error.message)
+          }).finally(() => {
+            this.showAddDialog = false
+          })
+        }
+      });
     },
     onSelectChange() {
       this.currentPage = 1
@@ -824,7 +848,7 @@ export default {
       this.getData()
     },
     handleDelete(index, row) {
-      this.$confirm('确定要删除 ' + row.appName + '?', '提示', {
+      this.$confirm('确定要删除 ' + row.appId + '?', '提示', {
         confirmButtonText: '确定',
         cancelButtonText: '取消',
         type: 'warning'
@@ -920,7 +944,7 @@ export default {
       })
     },
     handleDeleteDeployConfig(index, row) {
-      this.$confirm('确定要删除 ' + row.appName + '?', '提示', {
+      this.$confirm('确定要删除 ' + row.appId + '?', '提示', {
         confirmButtonText: '确定',
         cancelButtonText: '取消',
         type: 'warning'

+ 1 - 5
src/views/devops/app/AppStat.vue

@@ -39,11 +39,7 @@
         style="width: 100%"
       >
         <el-table-column
-          prop="appName"
-          label="应用"
-        />
-        <el-table-column
-          prop="bindPorts"
+          prop="httpPort"
           label="监听端口"
         />
         <el-table-column

+ 4 - 4
src/views/devops/app/BuildDeploy.vue

@@ -30,7 +30,7 @@
           />
         </el-select>
         <el-input
-          v-model="queryInfo.appName"
+          v-model="queryInfo.appId"
           style="margin :5px; width: 20%"
           clearable
           placeholder="输入应用名(回车搜索)"
@@ -69,7 +69,7 @@
           label="分支"
         />
         <el-table-column
-          prop="bindPorts"
+          prop="httpPort"
           label="监听端口"
         />
         <el-table-column
@@ -630,7 +630,7 @@ export default {
       queryInfo: {
         env: 'test',
         appType: '',
-        appName: '',
+        appId: '',
         pn: 1
       },
       // 屏幕宽度, 为了控制分页条的大小
@@ -946,7 +946,7 @@ export default {
       this.getData()
     },
     onRefresh() {
-      this.queryInfo.appName = ''
+      this.queryInfo.appId = ''
       this.getData()
     },
     onSelectChange() {

+ 146 - 54
src/views/devops/build/Compiler.vue

@@ -22,34 +22,21 @@
           label="编译名字"
         />
         <el-table-column
-          prop="homePath"
-          label="编译器主目录"
-        />
-        <el-table-column
-          prop="compilerImage"
-          label="编译器镜像"
-        />
-        <el-table-column
-          prop="compileCmd"
-          label="编译命令"
-        />
-        <el-table-column
-          prop="versionCmd"
-          label="编译器版本命令"
+          prop="compilerBinds"
+          label="dockerfile 变量"
         >
           <template slot-scope="scope">
-            <span style="margin: 5px">{{ scope.row.versionCmd }}</span>
             <el-button
-              style="margin: 5px"
               size="mini"
               type="success"
-              @click="handleGetVersion(scope.$index, scope.row)"
-            >查看</el-button>
+              icon="el-icon-edit"
+              @click="handleShowImage(scope.$index, scope.row)"
+            >编辑</el-button>
           </template>
         </el-table-column>
         <el-table-column
           prop="compilerBinds"
-          label="镜像映射"
+          label="容器目录映射"
         >
           <template slot-scope="scope">
             <el-button
@@ -62,7 +49,7 @@
         </el-table-column>
         <el-table-column
           prop="compilerBinds"
-          label="环境变量"
+          label="容器环境变量"
         >
           <template slot-scope="scope">
             <el-button
@@ -76,9 +63,14 @@
         <el-table-column
           fixed="right"
           label="操作"
-          width="120"
+          width="240"
         >
           <template slot-scope="scope">
+            <el-button
+              size="mini"
+              type="info"
+              @click="handleDetail(scope.row)"
+            >详情</el-button>
             <el-button
               size="mini"
               type="danger"
@@ -108,36 +100,62 @@
       center
     >
       <template>
-        <el-form ref="form" :model="form" label-width="80px">
-          <el-form-item label="编译类型">
-            <el-select v-model="form.type" placeholder="选择编译类型">
-              <el-option
-                v-for="(item, index) in compileTypes"
-                :key="index"
-                :label="item.label"
-                :value="item.value"
-              />
-            </el-select>
-          </el-form-item>
-          <el-form-item label="编译名字">
-            <el-input v-model="form.name" style="width: 70%; padding-right: 2px" />
-          </el-form-item>
-          <el-form-item label="编译主目录" style="width: 70%; padding-right: 2px">
-            <el-input v-model="form.homePath" style="width: 70%; padding-right: 2px" />
-          </el-form-item>
-          <el-form-item label="编译命令">
-            <el-input v-model="form.compileCmd" type="textarea" autosize style="padding-right: 1px;" />
-          </el-form-item>
-          <el-form-item label="编译器版本命令" style="width: 70%; padding-right: 2px">
-            <el-input v-model="form.versionCmd" type="textarea" autosize style="padding-right: 1px;" />
-          </el-form-item>
-          <el-form-item label="编译镜像">
-            <el-input v-model="form.compilerImage" style="width: 70%; padding-right: 2px" />
-          </el-form-item>
-          <el-form-item>
-            <el-button type="primary" @click="onAddCompiler">确定</el-button>
-          </el-form-item>
-        </el-form>
+        <div class="compiler-config-steps">
+          <el-steps :active="active" finish-status="success" simple style="margin-bottom: 30px">
+            <el-step title="配置基础环境" icon="el-icon-monitor" />
+            <el-step title="编写编译脚本" icon="el-icon-edit" />
+          </el-steps>
+
+          <el-form ref="form" :model="form" :rules="rules" label-width="130px">
+            <div v-show="active === 0">
+              <el-form-item label="编译类型" prop="type">
+                <el-select v-model="form.type" placeholder="请选择编译类型" style="width: 100%">
+                  <el-option
+                    v-for="(item, index) in compileTypes"
+                    :key="index"
+                    :label="item.label"
+                    :value="item.value"
+                  />
+                </el-select>
+              </el-form-item>
+              <el-form-item label="配置名称" prop="name">
+                <el-input v-model="form.name" placeholder="例如:Dotnet8-Standard-Build" />
+              </el-form-item>
+            </div>
+
+            <div v-show="active === 1">
+              <el-form-item v-if="form.type === 'dockerRun'" label="编译镜像" prop="compilerImage">
+                <el-input v-model="form.compilerImage" placeholder="e.g. mcr.microsoft.com/dotnet/sdk:8.0" />
+                <div class="form-tip">该镜像将作为构建时的 Runtime 容器</div>
+              </el-form-item>
+              <el-form-item v-if="form.type === 'shell'" label="编译主目录" prop="homePath">
+                <el-input v-model="form.homePath" placeholder="例如:/src 或 /app" />
+              </el-form-item>
+              <el-form-item v-if="form.type !== 'dockerBuild'" label="编译器版本命令" prop="versionCmd">
+                <el-input
+                  v-model="form.versionCmd"
+                  type="textarea"
+                  :autosize="{ minRows: 2 }"
+                  placeholder="例如:dotnet --version"
+                />
+              </el-form-item>
+              <el-form-item v-if="form.type !== 'dockerBuild'" label="编译命令" prop="compileCmd">
+                <el-input
+                  v-model="form.compileCmd"
+                  type="textarea"
+                  :autosize="{ minRows: 4 }"
+                  placeholder="例如:dotnet publish -c Release -o ./publish"
+                />
+              </el-form-item>
+            </div>
+
+            <el-form-item style="margin-top: 40px">
+              <el-button v-if="active > 0" @click="active--">上一步</el-button>
+              <el-button v-if="active < 1" type="primary" @click="nextStep">下一步</el-button>
+              <el-button v-if="active === 1" type="success" @click="onAddCompiler">提交保存</el-button>
+            </el-form-item>
+          </el-form>
+        </div>
       </template>
     </el-dialog>
     <!-- 查看 docker 目录映射对话框 -->
@@ -235,9 +253,18 @@ export default {
       totalSize: 0,
       dataList: [],
       // **********************************************************************
+      active: 0,
+      rules: {
+        type: [{ required: true, message: '请选择类型', trigger: 'change' }],
+        name: [{ required: true, message: '请输入配置名字', trigger: 'blur' }],
+        compilerImage: [{ required: true, message: '必须指定编译镜像', trigger: 'blur' }],
+        homePath: [{ required: true, message: '主目录不能为空', trigger: 'blur' }],
+        compileCmd: [{ required: true, message: '编译命令是核心,必须填写', trigger: 'blur' }]
+      },
+      // **********************************************************************
       showAddDialog: false,
       form: {
-        type: 'none',
+        type: '',
         name: '',
         homePath: '',
         compileCmd: '',
@@ -297,7 +324,7 @@ export default {
         this.$message.error(error.message)
       })
     },
-    onAddCompiler() {
+    onAddCompiler1() {
       const formData = new FormData()
       formData.append('type', this.form.type)
       formData.append('name', this.form.name)
@@ -314,6 +341,39 @@ export default {
         this.showAddDialog = false
       })
     },
+    // 异步校验并跳转
+    async nextStep() {
+      const stepFields = ['type', 'name'] // 第一步需要校验的字段
+
+      try {
+        // 关键:将所有字段的校验转为 Promise 数组
+        const checkActions = stepFields.map(field => {
+          return new Promise((resolve, reject) => {
+            this.$refs.form.validateField(field, error => {
+              if (error) reject(error)
+              else resolve()
+            })
+          })
+        })
+
+        await Promise.all(checkActions)
+        this.active++ // 只有全部校验成功才加 1
+      } catch (e) {
+        this.$message.warning('请检查基础配置是否填写完整')
+      }
+    },
+    onAddCompiler() {
+      this.$refs.form.validate((valid) => {
+        if (valid) {
+          // 这里发起 API 请求
+          console.log('Final Data:', this.form)
+          this.$message.success('编译器配置添加成功!')
+          // 逻辑处理,如关闭弹窗或跳转列表
+        } else {
+          return false
+        }
+      })
+    },
     handleGetVersion(index, row) {
       getCompilerVersion(row.id).then(resp => {
         if (resp.code === 0) {
@@ -379,6 +439,9 @@ export default {
         this.dockerBindForm.containerPath = ''
       })
     },
+    handleDetail(row) {
+      this.$message.info('get ' + row.name + ' detail')
+    },
     handleEdit(index, row) {
       this.$confirm('确定要删除 ' + row.name + '?', '提示', {
         confirmButtonText: '确定',
@@ -404,5 +467,34 @@ export default {
 }
 </script>
 
-<style>
+<style scoped>
+.compiler-config-steps {
+  max-width: 800px;
+  margin: 0 auto;
+  padding: 20px;
+  background: #fff;
+  border-radius: 8px;
+}
+
+.form-tip {
+  font-size: 12px;
+  color: #909399;
+  line-height: 2;
+}
+
+/* 使用 ::v-deep 穿透 scoped 限制
+  这是 Vue 2 中兼容性最好的写法,支持 Less/Sass
+*/
+::v-deep .el-textarea__inner {
+  font-family: 'Courier New', Courier, monospace;
+  background-color: #f8f9fa;
+  color: #2c3e50;
+  border: 1px solid #dcdfe6;
+}
+
+/* 鼠标悬停和聚焦时的效果优化 */
+::v-deep .el-textarea__inner:focus {
+  background-color: #fff;
+  border-color: #409EFF;
+}
 </style>