Files
yidaima/RuoYi-Vue/docs/superpowers/plans/2026-06-18-qing-business-action-dialog.md
2026-06-18 17:56:46 +08:00

28 KiB

Qing Unified Business Action Dialog Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Replace chained business-action prompts with one generated dynamic form, automatically resolve record/page parameters, and inject authenticated-user parameters on the backend for every Qing business button.

Architecture: Add transient render metadata to each BusinessActionDesign: trusted current-user fields and user-editable input-field definitions. Both Qing generation paths enrich actions from the saved database schema. The Vue template resolves available values first, opens one Element UI dialog for unresolved editable fields, and submits once; the controller overwrites trusted user fields from the authenticated portal user before the service validates requestFields.

Tech Stack: Java 8, Spring Boot 2.5, Apache Velocity, Vue 2, Element UI, JUnit 4, Mockito, Maven.


File Map

  • Create ruoyi-generator/src/main/java/com/ruoyi/generator/domain/front/dto/BusinessActionInputFieldDesign.java
    • Render-only metadata for one dynamic business-action form field.
  • Create ruoyi-generator/src/main/java/com/ruoyi/generator/util/BusinessActionRenderSupport.java
    • Infers authenticated-user fields and database-backed input metadata.
  • Create ruoyi-generator/src/test/java/com/ruoyi/generator/util/BusinessActionRenderSupportTest.java
    • Unit tests inference and schema metadata lookup independently of templates.
  • Modify ruoyi-generator/src/main/java/com/ruoyi/generator/domain/front/dto/BusinessActionDesign.java
    • Stores transient currentUserFields and inputFields.
  • Modify ruoyi-generator/src/main/java/com/ruoyi/generator/service/GenProjectServiceImpl.java
    • Enriches normal Qing backend/frontend actions and upgrades stale installed templates.
  • Modify ruoyi-generator/src/main/java/com/ruoyi/generator/service/BusinessBlockGenerationService.java
    • Enriches business actions rendered through the business-block path.
  • Modify ruoyi-generator/src/main/resources/qing/controller.java.vm
    • Overwrites trusted request fields using the authenticated portal user.
  • Modify ruoyi-generator/src/main/resources/qing/index.vue.vm
    • Replaces chained $prompt calls with one reusable dynamic dialog.
  • Modify ruoyi-generator/src/test/java/com/ruoyi/generator/util/QingTemplateSupportTest.java
    • Covers generated dialog behavior and trusted backend injection.
  • Modify ruoyi-generator/src/test/java/com/ruoyi/generator/service/GenProjectServiceImplTest.java
    • Covers stale-template fallback and normal preview metadata.
  • Modify ruoyi-generator/src/test/java/com/ruoyi/generator/service/BusinessBlockGenerationServiceTest.java
    • Covers metadata in the secondary generation path.
  • Modify sql/qing_templates.sql
    • Synchronizes bundled controller and frontend-index template records.
  • Modify sql/db.sql
    • Synchronizes the same runnable-template seed records.

Because the worktree already contains unrelated edits in several target files, inspect each path before staging. Commit only task-owned hunks; if a clean partial stage cannot be produced safely, leave the implementation uncommitted and report that explicitly.

Task 1: Derive Render Metadata for Every Business Action

Files:

  • Create: ruoyi-generator/src/main/java/com/ruoyi/generator/domain/front/dto/BusinessActionInputFieldDesign.java

  • Create: ruoyi-generator/src/main/java/com/ruoyi/generator/util/BusinessActionRenderSupport.java

  • Create: ruoyi-generator/src/test/java/com/ruoyi/generator/util/BusinessActionRenderSupportTest.java

  • Modify: ruoyi-generator/src/main/java/com/ruoyi/generator/domain/front/dto/BusinessActionDesign.java

  • Step 1: Write the failing metadata test

Create BusinessActionRenderSupportTest with a business action containing book_id, user_id, due_date, and reason. Give it an INSERT_ROW effect whose user_id value is ${current_user.id} and schema columns with comments/control types.

@Test
public void enrichesTrustedUserFieldsAndEditableInputMetadata()
{
    BusinessActionDesign action = new BusinessActionDesign();
    action.setRequestFields(Arrays.asList("book_id", "user_id", "due_date", "reason"));

    BusinessActionEffectDesign effect = new BusinessActionEffectDesign();
    effect.setType("INSERT_ROW");
    Map<String, String> values = new LinkedHashMap<String, String>();
    values.put("user_id", "${current_user.id}");
    values.put("book_id", "${param.book_id}");
    effect.setValues(values);
    action.setEffects(Arrays.asList(effect));

    GenTable book = table("book");
    book.setColumns(Arrays.asList(
            column("book_id", "bookId", "图书", "input", ""),
            column("due_date", "dueDate", "应还日期", "datetime", ""),
            column("reason", "reason", "申请说明", "textarea", "")));

    BusinessActionRenderSupport.enrich(action, Arrays.asList(book));

    assertEquals(Arrays.asList("user_id"), action.getCurrentUserFields());
    assertEquals(Arrays.asList("book_id", "due_date", "reason"),
            action.getInputFields().stream()
                    .map(BusinessActionInputFieldDesign::getField)
                    .collect(Collectors.toList()));
    assertEquals("应还日期", action.getInputFields().get(1).getLabel());
    assertEquals("datetime", action.getInputFields().get(1).getHtmlType());
    assertEquals("textarea", action.getInputFields().get(2).getHtmlType());
}

Add a second test proving ${current_user.id} used as an effect value, rule expectedValue, or rule compareValue marks the corresponding targetField as trusted.

  • Step 2: Run the test and verify RED

Run:

mvn -pl ruoyi-generator -Dtest=BusinessActionRenderSupportTest test

Expected: compilation fails because BusinessActionInputFieldDesign, BusinessActionRenderSupport, and the new getters do not exist.

  • Step 3: Add the render-only input DTO

Implement:

package com.ruoyi.generator.domain.front.dto;

public class BusinessActionInputFieldDesign
{
    private String field;
    private String label;
    private String htmlType;
    private String dictType;

    public String getField() { return field; }
    public void setField(String field) { this.field = field; }
    public String getLabel() { return label; }
    public void setLabel(String label) { this.label = label; }
    public String getHtmlType() { return htmlType; }
    public void setHtmlType(String htmlType) { this.htmlType = htmlType; }
    public String getDictType() { return dictType; }
    public void setDictType(String dictType) { this.dictType = dictType; }
}

Add to BusinessActionDesign:

private transient List<String> currentUserFields;
private transient List<BusinessActionInputFieldDesign> inputFields;

public List<String> getCurrentUserFields() { return currentUserFields; }
public void setCurrentUserFields(List<String> currentUserFields) { this.currentUserFields = currentUserFields; }
public List<BusinessActionInputFieldDesign> getInputFields() { return inputFields; }
public void setInputFields(List<BusinessActionInputFieldDesign> inputFields) { this.inputFields = inputFields; }
  • Step 4: Implement deterministic inference

Implement BusinessActionRenderSupport.enrich(action, tables) so it:

  1. Uses a LinkedHashSet to preserve request-field order.
  2. Marks map keys whose effect value is exactly ${current_user.id}.
  3. Marks targetField when an effect value, rule expectedValue, rule compareValue, or any rule expectedValues entry is ${current_user.id}.
  4. Builds inputFields from requestFields - currentUserFields.
  5. Resolves schema metadata by exact columnName or javaField, preferring the action owner table and then other project tables.
  6. Falls back to a humanized field name and input control when no schema column matches.

Core methods:

public static void enrich(BusinessActionDesign action, List<GenTable> tables)
{
    if (action == null) {
        return;
    }
    LinkedHashSet<String> trusted = collectCurrentUserFields(action);
    action.setCurrentUserFields(new ArrayList<String>(trusted));

    List<BusinessActionInputFieldDesign> inputs = new ArrayList<BusinessActionInputFieldDesign>();
    for (String field : safeList(action.getRequestFields())) {
        if (!trusted.contains(field)) {
            inputs.add(toInputField(field, action.getOwnerTable(), tables));
        }
    }
    action.setInputFields(inputs);
}

The fallback label must use field.replace('_', ' '); the template can display it unchanged without embedding action-specific labels.

  • Step 5: Run the metadata tests and verify GREEN

Run:

mvn -pl ruoyi-generator -Dtest=BusinessActionRenderSupportTest test

Expected: all metadata tests pass.

  • Step 6: Commit the isolated new files if staging is clean
git add -- ruoyi-generator/src/main/java/com/ruoyi/generator/domain/front/dto/BusinessActionInputFieldDesign.java ruoyi-generator/src/main/java/com/ruoyi/generator/util/BusinessActionRenderSupport.java ruoyi-generator/src/test/java/com/ruoyi/generator/util/BusinessActionRenderSupportTest.java
git add -p -- ruoyi-generator/src/main/java/com/ruoyi/generator/domain/front/dto/BusinessActionDesign.java
git commit -m "Add Qing business action render metadata"

Before committing, inspect git diff --cached and omit the commit if it includes pre-existing unrelated hunks.

Task 2: Feed Metadata into Both Qing Generation Paths

Files:

  • Modify: ruoyi-generator/src/main/java/com/ruoyi/generator/service/GenProjectServiceImpl.java

  • Modify: ruoyi-generator/src/main/java/com/ruoyi/generator/service/BusinessBlockGenerationService.java

  • Modify: ruoyi-generator/src/test/java/com/ruoyi/generator/service/GenProjectServiceImplTest.java

  • Modify: ruoyi-generator/src/test/java/com/ruoyi/generator/service/BusinessBlockGenerationServiceTest.java

  • Step 1: Add failing preview-generation assertions

In GenProjectServiceImplTest, create a borrow_book action with:

action.setRequestFields(Arrays.asList("book_id", "user_id", "due_date"));
effectValues.put("user_id", "${current_user.id}");

Render frontend-index.vue.vm and assert:

assertTrue(content.contains("currentUserFields: [\"user_id\"]"));
assertTrue(content.contains("field: \"due_date\""));
assertTrue(content.contains("label: \"应还日期\""));
assertTrue(content.contains("htmlType: \"datetime\""));

Add equivalent assertions to BusinessBlockGenerationServiceTest so that rendering through putPageDesignBusinessActions receives the same metadata.

  • Step 2: Run both tests and verify RED

Run:

mvn -pl ruoyi-generator -Dtest=GenProjectServiceImplTest,BusinessBlockGenerationServiceTest test

Expected: metadata strings are absent.

  • Step 3: Enrich backend and page actions

In GenProjectServiceImpl.putTableBusinessActions, enrich every filtered table action before placing it in Velocity:

for (BusinessActionDesign action : tableBusinessActions) {
    BusinessActionRenderSupport.enrich(action, project.getTables());
}

In applyBusinessButtonConfig, enrich the copied action after applying visual configuration:

BusinessActionRenderSupport.enrich(action, projectTables);

Pass project.getTables() through filterPageDesignBusinessActions and applyBusinessButtonConfig rather than looking up global state.

Apply the same explicit table-list parameter in BusinessBlockGenerationService. Do not duplicate inference logic there.

When copying actions, also copy the transient metadata if it is already present:

copy.setCurrentUserFields(source.getCurrentUserFields());
copy.setInputFields(source.getInputFields());
  • Step 4: Run both tests and verify GREEN

Run:

mvn -pl ruoyi-generator -Dtest=GenProjectServiceImplTest,BusinessBlockGenerationServiceTest test

Expected: both test classes pass.

  • Step 5: Review the service diffs before staging

Run:

git diff -- ruoyi-generator/src/main/java/com/ruoyi/generator/service/GenProjectServiceImpl.java ruoyi-generator/src/main/java/com/ruoyi/generator/service/BusinessBlockGenerationService.java

Stage only metadata-enrichment hunks; do not absorb existing worktree changes.

Task 3: Make Authenticated User Fields Server-Trusted

Files:

  • Modify: ruoyi-generator/src/main/resources/qing/controller.java.vm

  • Modify: ruoyi-generator/src/test/java/com/ruoyi/generator/util/QingTemplateSupportTest.java

  • Modify: ruoyi-generator/src/test/java/com/ruoyi/generator/service/GenProjectServiceImplTest.java

  • Step 1: Write the failing controller-template assertions

Set action.currentUserFields to ["user_id"] in the existing backend-action rendering test, then assert:

assertTrue(controller.contains(
        "prepareBusinessActionParams(params, request, new String[] {\"user_id\"})"));
assertTrue(controller.contains(
        "actionParams.put(field, currentUser.getUserId())"));

Add a stale controller-template test using content that contains the old two-argument prepareBusinessActionParams but not currentUserFields. Assert preview fallback emits the new trusted-field loop.

  • Step 2: Run the tests and verify RED

Run:

mvn -pl ruoyi-generator -Dtest=QingTemplateSupportTest,GenProjectServiceImplTest test

Expected: trusted-field assertions fail.

  • Step 3: Pass action-specific trusted fields to the controller helper

Render each action endpoint call as:

Map<String, Object> actionParams = prepareBusinessActionParams(
        params,
        request,
        new String[] {#foreach($field in $action.currentUserFields)"${field}"#if($foreach.hasNext), #end#end});

Change the helper to:

private Map<String, Object> prepareBusinessActionParams(Map<String, Object> params,
                                                        HttpServletRequest request,
                                                        String[] currentUserFields)
{
    Map<String, Object> actionParams = params == null
            ? new LinkedHashMap<String, Object>()
            : new LinkedHashMap<String, Object>(params);
    PortalUser currentUser = tokenStore.getPortalUser(resolveToken(request));
    if (currentUser == null) {
        throw new IllegalArgumentException("请先登录");
    }
    actionParams.put("__currentUserId", currentUser.getUserId());
    if (currentUserFields != null) {
        for (String field : currentUserFields) {
            if (StringUtils.isNotBlank(field)) {
                actionParams.put(field, currentUser.getUserId());
            }
        }
    }
    return actionParams;
}

This order intentionally overwrites any client-supplied user_id.

  • Step 4: Update stale-template detection

Require the new marker in shouldUseBundledBackendControllerTemplate:

|| !templateContent.contains("currentUserFields")
|| !templateContent.contains("actionParams.put(field, currentUser.getUserId())")
  • Step 5: Run the tests and verify GREEN

Run:

mvn -pl ruoyi-generator -Dtest=QingTemplateSupportTest,GenProjectServiceImplTest test

Expected: controller rendering and stale-template fallback tests pass.

Task 4: Replace Chained Prompts with One Dynamic Dialog

Files:

  • Modify: ruoyi-generator/src/main/resources/qing/index.vue.vm

  • Modify: ruoyi-generator/src/test/java/com/ruoyi/generator/util/QingTemplateSupportTest.java

  • Modify: ruoyi-generator/src/test/java/com/ruoyi/generator/service/GenProjectServiceImplTest.java

  • Step 1: Replace the old prompt test with failing dialog expectations

Change qingFrontendCollectsMissingBusinessActionFields to assert:

assertTrue(page.contains("businessActionDialogVisible"));
assertTrue(page.contains("businessActionInputFields"));
assertTrue(page.contains("<el-dialog"));
assertTrue(page.contains("submitBusinessAction"));
assertTrue(page.contains("openBusinessActionDialog"));
assertTrue(page.contains("currentUserFields"));
assertTrue(page.contains("inputFields"));
assertTrue(page.contains("v-for=\"field in businessActionInputFields\""));
assertTrue(page.contains("field.htmlType === 'datetime'"));
assertTrue(page.contains("field.htmlType === 'textarea'"));
assertTrue(page.contains("getBusinessActionFieldOptions(field)"));
assertFalse(page.contains("this.$prompt"));
assertFalse(page.contains("collectBusinessActionPayload"));

Also assert the generated action map serializes:

currentUserFields: ["user_id"],
inputFields: [
  { field: "due_date", label: "应还日期", htmlType: "datetime", dictType: "" }
]
  • Step 2: Run the template test and verify RED

Run:

mvn -pl ruoyi-generator -Dtest=QingTemplateSupportTest test

Expected: the new dialog markers are absent and $prompt is still present.

  • Step 3: Add one reusable Element UI dialog

Place one dialog next to the existing CRUD dialog:

<el-dialog
  :title="businessActionDialogTitle"
  :visible.sync="businessActionDialogVisible"
  width="520px"
  append-to-body
  :close-on-click-modal="false"
  @closed="resetBusinessActionDialog">
  <p v-if="activeBusinessAction && activeBusinessAction.confirmMessage"
     class="business-action-hint">
    {{ activeBusinessAction.confirmMessage }}
  </p>
  <el-form ref="businessActionForm" :model="businessActionForm" label-width="110px">
    <el-form-item
      v-for="field in businessActionInputFields"
      :key="field.field"
      :label="field.label"
      :prop="field.field"
      :rules="[{ required: true, message: field.label + '不能为空', trigger: field.htmlType === 'select' ? 'change' : 'blur' }]">
      <el-date-picker
        v-if="field.htmlType === 'datetime'"
        v-model="businessActionForm[field.field]"
        type="date"
        value-format="yyyy-MM-dd"
        :placeholder="'请选择' + field.label" />
      <el-select
        v-else-if="field.htmlType === 'select' && field.dictType"
        v-model="businessActionForm[field.field]"
        clearable
        :placeholder="'请选择' + field.label">
        <el-option
          v-for="option in getBusinessActionFieldOptions(field)"
          :key="option.value"
          :label="option.label"
          :value="option.value" />
      </el-select>
      <el-input
        v-else-if="field.htmlType === 'textarea'"
        v-model="businessActionForm[field.field]"
        type="textarea"
        :rows="3"
        :placeholder="'请输入' + field.label" />
      <el-input
        v-else
        v-model="businessActionForm[field.field]"
        :placeholder="'请输入' + field.label" />
    </el-form-item>
  </el-form>
  <span slot="footer">
    <el-button @click="businessActionDialogVisible = false" :disabled="businessActionSubmitting">取消</el-button>
    <el-button type="primary" :loading="businessActionSubmitting" @click="submitBusinessAction">
      {{ activeBusinessAction && activeBusinessAction.buttonLabel ? activeBusinessAction.buttonLabel : '确认执行' }}
    </el-button>
  </span>
</el-dialog>
  • Step 4: Serialize render metadata and add dialog state

For each businessActionMap item add:

buttonLabel: "${action.buttonLabel}",
currentUserFields: [#foreach($field in $action.currentUserFields)"${field}"#if($foreach.hasNext), #end#end],
inputFields: [
#foreach($field in $action.inputFields)
  {
    field: "${field.field}",
    label: "${field.label}",
    htmlType: "${field.htmlType}",
    dictType: "${field.dictType}"
  }#if($foreach.hasNext),#end
#end
]

Add component state:

businessActionDialogVisible: false,
businessActionSubmitting: false,
activeBusinessAction: null,
businessActionBasePayload: {},
businessActionForm: {},
businessActionInputFields: []

Add computed title:

businessActionDialogTitle() {
  return this.activeBusinessAction
    ? (this.activeBusinessAction.buttonLabel || this.activeBusinessAction.name || "业务操作")
    : "业务操作"
}
  • Step 5: Replace the chained collection flow

Use one routing method:

handleBusinessAction(code, row) {
  if (this.handleConfiguredRoute("business:" + code, row)
      || this.handleConfiguredRoute("toolbar:" + code, row)
      || this.handleConfiguredRoute("table:" + code, row)) {
    return
  }
  const action = this.businessActionMap[code]
  if (!action || !action.path) {
    this.$message.warning("动作未配置")
    return
  }

  const payload = this.buildBusinessActionPayload(action, row)
  const missingFields = (action.inputFields || []).filter(field => !this.hasBusinessActionValue(payload[field.field]))
  if (missingFields.length) {
    this.openBusinessActionDialog(action, payload, missingFields)
    return
  }
  if (action.confirmMessage) {
    this.$confirm(action.confirmMessage, "提示", {
      confirmButtonText: "确定",
      cancelButtonText: "取消",
      type: "warning"
    }).then(() => this.executeBusinessAction(action, payload)).catch(() => {})
    return
  }
  this.executeBusinessAction(action, payload)
}

Implement dialog methods:

openBusinessActionDialog(action, payload, fields) {
  this.activeBusinessAction = action
  this.businessActionBasePayload = Object.assign({}, payload)
  this.businessActionInputFields = fields
  this.businessActionForm = fields.reduce((form, field) => {
    form[field.field] = payload[field.field] === undefined ? "" : payload[field.field]
    return form
  }, {})
  this.businessActionDialogVisible = true
  this.$nextTick(() => this.$refs.businessActionForm && this.$refs.businessActionForm.clearValidate())
},
submitBusinessAction() {
  this.$refs.businessActionForm.validate(valid => {
    if (!valid || !this.activeBusinessAction) {
      return
    }
    const payload = Object.assign({}, this.businessActionBasePayload, this.businessActionForm)
    this.businessActionSubmitting = true
    this.executeBusinessAction(this.activeBusinessAction, payload)
      .then(() => {
        this.businessActionDialogVisible = false
      })
      .finally(() => {
        this.businessActionSubmitting = false
      })
  })
},
resetBusinessActionDialog() {
  if (this.businessActionSubmitting) {
    return
  }
  this.activeBusinessAction = null
  this.businessActionBasePayload = {}
  this.businessActionForm = {}
  this.businessActionInputFields = []
},
hasBusinessActionValue(value) {
  return value !== undefined && value !== null && String(value).trim() !== ""
},
getBusinessActionFieldOptions(field) {
  if (!field || !field.dictType) {
    return []
  }
  return this._dict.getDictDataByType(field.dictType).map(item => ({
    label: item.name,
    value: item.value
  }))
}

buildBusinessActionPayload must iterate action.inputFields plus requestFields, but must skip action.currentUserFields so trusted identity is never read from local storage or sent by the client.

  • Step 6: Keep dialog values on backend failure

Make executeBusinessAction(action, payload) return a Promise. Resolve only for code === "200" and reject with an Error for a business failure:

executeBusinessAction(action, payload) {
  const method = String(action.method || "POST").toLowerCase()
  const request = method === "get"
    ? this.request.get(action.path, { params: payload })
    : this.request[method]
      ? this.request[method](action.path, payload)
      : this.request.post(action.path, payload)

  return request.then(res => {
    if (res.code !== "200") {
      throw new Error(res.msg || "操作失败")
    }
    this.$message.success(action.successMessage || "操作成功")
    this.refreshBusinessActionTarget(action.refreshTarget)
    return res
  }).catch(error => {
    const data = error && error.response ? error.response.data : null
    this.$message.error((data && data.msg) || (error && error.message) || "操作失败")
    return Promise.reject(error)
  })
}

The dialog closes only in submitBusinessAction().then(...); failures therefore preserve entered values.

  • Step 7: Update stale frontend-template detection

Replace the obsolete collectBusinessActionPayload marker with:

|| !templateContent.contains("businessActionDialogVisible")
|| !templateContent.contains("submitBusinessAction")
|| !templateContent.contains("currentUserFields")
|| templateContent.contains("this.$prompt")

Update the stale-template test to assert the bundled output contains the dialog and does not contain $prompt.

  • Step 8: Run template and fallback tests

Run:

mvn -pl ruoyi-generator -Dtest=QingTemplateSupportTest,GenProjectServiceImplTest test

Expected: all tests pass; generated frontend contains one dynamic dialog and no chained prompt.

Task 5: Synchronize Runnable SQL Template Seeds

Files:

  • Modify: sql/qing_templates.sql

  • Modify: sql/db.sql

  • Test: ruoyi-generator/src/test/java/com/ruoyi/generator/util/QingTemplateSupportTest.java

  • Step 1: Run the seed consistency test and verify RED

Run:

mvn -pl ruoyi-generator -Dtest=QingTemplateSupportTest#qingDatabaseSeedsContainCurrentBusinessActionTemplates test

Expected: failure because bundled controller.java.vm and index.vue.vm no longer match the SQL copies.

  • Step 2: Mechanically replace the two SQL records

For both SQL files, replace:

  • controller.java.vm with escaped content from ruoyi-generator/src/main/resources/qing/controller.java.vm
  • frontend-index.vue.vm with escaped content from ruoyi-generator/src/main/resources/qing/index.vue.vm

Preserve the existing record IDs, template IDs, file names, file paths, author, and sysdate() suffix. Escape every single quote in template content as '' and normalize line endings to LF before insertion.

Do not hand-edit the embedded template bodies; use a deterministic bulk replacement because the test compares the complete escaped resource.

  • Step 3: Run the exact seed test and verify GREEN

Run:

mvn -pl ruoyi-generator -Dtest=QingTemplateSupportTest#qingDatabaseSeedsContainCurrentBusinessActionTemplates test

Expected: PASS.

  • Step 4: Run all targeted generator tests

Run:

mvn -pl ruoyi-generator -Dtest=BusinessActionRenderSupportTest,QingTemplateSupportTest,GenProjectServiceImplTest,BusinessBlockGenerationServiceTest test

Expected: BUILD SUCCESS.

Task 6: Build and Browser-Verify Generated Business Buttons

Files:

  • No additional source files unless verification exposes a defect.

  • Step 1: Run the full generator module

Run:

mvn -pl ruoyi-generator test

Expected: BUILD SUCCESS with no new test failures.

  • Step 2: Build EasyCode

Run from easycode-web:

npm run build

Expected: Vite build completes successfully.

  • Step 3: Regenerate/restart the current runnable preview

Use the existing run-preview flow for the project backing http://127.0.0.1:57320. Wait until the backend and frontend preview status reports ready, then reload /books/1 in the in-app browser.

  • Step 4: Verify the borrowing interaction

In the in-app browser:

  1. Click 借书.
  2. Confirm there is at most one business dialog.
  3. Confirm there is no user id field.
  4. Confirm the current book identifier is not requested.
  5. If due_date is unresolved, confirm it appears as a date picker in the same dialog.
  6. Cancel and verify no request is sent.
  7. Reopen, submit once, and verify exactly one business-action request.
  8. Confirm success/error feedback comes from the configured or backend message.
  • Step 5: Verify a second multi-parameter business action

Open another generated action such as reservation, return, approval, or rejection:

  • all unresolved editable fields appear together;

  • row/detail values are not requested again;

  • the authenticated user is not editable;

  • failure leaves the dialog and entered values available for retry;

  • success applies the configured refreshTarget.

  • Step 6: Final diff and completion audit

Run:

git diff --check
git status --short

Confirm:

  • no $prompt remains in the Qing frontend template or SQL seed copies;
  • no user-specific action names are hard-coded into runtime behavior;
  • controller injection overwrites trusted fields;
  • both generation paths enrich metadata;
  • resource templates and SQL seed copies match;
  • unrelated worktree changes remain untouched.