1752 lines
60 KiB
Markdown
1752 lines
60 KiB
Markdown
# Code Template Bundles 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:** Add selectable code template bundles so `qing` remains the default template while future templates can be added through data and metadata instead of hardcoded IDs.
|
|
|
|
**Architecture:** Introduce a `sys_template_bundle` aggregate that owns the enabled target types for a code template bundle. Persist the selected bundle on front projects as `codeTemplate`, carry it into `GenProject`, and update generation, preview, download, and one-click flows to resolve templates by `bundleCode + templateType`.
|
|
|
|
**Tech Stack:** Java 8, Spring Boot 2.5, MyBatis XML mappers, JUnit 4, Mockito, Vue 3, Element Plus, Node `node:test`, MySQL SQL seed scripts.
|
|
|
|
---
|
|
|
|
## File Map
|
|
|
|
- Create `ruoyi-generator/src/main/java/com/ruoyi/generator/domain/TemplateBundle.java`: domain object for `sys_template_bundle`.
|
|
- Create `ruoyi-generator/src/main/java/com/ruoyi/generator/mapper/TemplateBundleMapper.java`: MyBatis mapper contract.
|
|
- Create `ruoyi-generator/src/main/resources/mapper/generator/TemplateBundleMapper.xml`: SQL queries for bundles.
|
|
- Create `ruoyi-generator/src/main/java/com/ruoyi/generator/service/ITemplateBundleService.java`: service contract for bundle lookup and supported target types.
|
|
- Create `ruoyi-generator/src/main/java/com/ruoyi/generator/service/TemplateBundleServiceImpl.java`: default bundle fallback and enabled type logic.
|
|
- Create `ruoyi-generator/src/test/java/com/ruoyi/generator/service/TemplateBundleServiceImplTest.java`: bundle service unit coverage.
|
|
- Modify `ruoyi-generator/src/main/java/com/ruoyi/generator/domain/Template.java`: add `bundleCode`.
|
|
- Modify `ruoyi-generator/src/main/resources/mapper/generator/TemplateMapper.xml`: select, filter, insert, and update `bundle_code`.
|
|
- Modify `ruoyi-generator/src/main/java/com/ruoyi/generator/domain/front/FrontProject.java`: add `codeTemplate`.
|
|
- Modify `ruoyi-generator/src/main/java/com/ruoyi/generator/domain/front/dto/FrontProjectCreateRequest.java`: add `codeTemplate`.
|
|
- Modify `ruoyi-generator/src/main/java/com/ruoyi/generator/domain/front/dto/FrontProjectUpdateRequest.java`: add `codeTemplate`.
|
|
- Modify `ruoyi-generator/src/main/java/com/ruoyi/generator/domain/front/dto/AiGenerationTaskCreateRequest.java`: add `codeTemplate`.
|
|
- Modify `ruoyi-generator/src/main/java/com/ruoyi/generator/domain/GenProject.java`: add transient `codeTemplate`.
|
|
- Modify `ruoyi-generator/src/main/resources/mapper/front/FrontProjectMapper.xml`: map and persist `code_template`.
|
|
- Modify `ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/FrontProjectServiceImpl.java`: validate/default selected code template.
|
|
- Modify `ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/FrontProjectConverter.java`: copy `codeTemplate` into `GenProject`.
|
|
- Modify `ruoyi-generator/src/main/java/com/ruoyi/generator/service/GenProjectServiceImpl.java`: resolve active template by bundle code and supported type.
|
|
- Modify `ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/IFrontProjectPreviewService.java`: expose supported template types.
|
|
- Modify `ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/FrontProjectPreviewServiceImpl.java`: use supported types for validation and download-all.
|
|
- Modify `ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/OneClickProjectGenerationServiceImpl.java`: preview only supported types.
|
|
- Modify `ruoyi-admin/src/main/java/com/ruoyi/web/controller/front/FrontProjectController.java`: expose bundle options and project template types.
|
|
- Modify `sql/code_template_bundles.sql`: create template bundle migration and qing seed.
|
|
- Modify `sql/db.sql`: fold migration into full initialization.
|
|
- Modify `sql/qing_templates.sql`: set `bundle_code = 'qing'` for qing templates.
|
|
- Modify `sql/front_project_upgrade.sql`: add front project column for older databases.
|
|
- Modify `ruoyi-generator/src/test/java/com/ruoyi/generator/service/front/FrontProjectServiceImplTest.java`: project default and persistence tests.
|
|
- Modify `ruoyi-generator/src/test/java/com/ruoyi/generator/service/GenProjectServiceImplTest.java`: bundle selection tests.
|
|
- Modify `ruoyi-generator/src/test/java/com/ruoyi/generator/util/QingTemplateSupportTest.java`: SQL seed assertions.
|
|
- Modify `easycode-web/src/api/project.js`: add bundle and supported-type APIs.
|
|
- Modify `easycode-web/src/views/GenerateView.vue`: show code template select and submit `codeTemplate`.
|
|
- Modify `easycode-web/src/views/PreviewView.vue`: fetch and render supported template types dynamically.
|
|
- Modify `easycode-web/src/views/generateView.test.mjs`: source tests for create form payload.
|
|
- Modify `easycode-web/src/views/previewView.test.mjs`: source tests for dynamic preview tabs.
|
|
|
|
---
|
|
|
|
### Task 1: Add Template Bundle Domain And Service
|
|
|
|
**Files:**
|
|
- Create: `ruoyi-generator/src/main/java/com/ruoyi/generator/domain/TemplateBundle.java`
|
|
- Create: `ruoyi-generator/src/main/java/com/ruoyi/generator/mapper/TemplateBundleMapper.java`
|
|
- Create: `ruoyi-generator/src/main/resources/mapper/generator/TemplateBundleMapper.xml`
|
|
- Create: `ruoyi-generator/src/main/java/com/ruoyi/generator/service/ITemplateBundleService.java`
|
|
- Create: `ruoyi-generator/src/main/java/com/ruoyi/generator/service/TemplateBundleServiceImpl.java`
|
|
- Create: `ruoyi-generator/src/test/java/com/ruoyi/generator/service/TemplateBundleServiceImplTest.java`
|
|
|
|
- [ ] **Step 1: Write failing service tests**
|
|
|
|
Create `ruoyi-generator/src/test/java/com/ruoyi/generator/service/TemplateBundleServiceImplTest.java`:
|
|
|
|
```java
|
|
package com.ruoyi.generator.service;
|
|
|
|
import static org.junit.Assert.assertEquals;
|
|
import static org.junit.Assert.assertTrue;
|
|
import static org.junit.Assert.fail;
|
|
import static org.mockito.Mockito.when;
|
|
|
|
import java.lang.reflect.Field;
|
|
import java.util.Arrays;
|
|
import java.util.List;
|
|
import org.junit.Before;
|
|
import org.junit.Test;
|
|
import org.mockito.Mock;
|
|
import org.mockito.MockitoAnnotations;
|
|
import com.ruoyi.common.exception.ServiceException;
|
|
import com.ruoyi.generator.domain.TemplateBundle;
|
|
import com.ruoyi.generator.mapper.TemplateBundleMapper;
|
|
|
|
public class TemplateBundleServiceImplTest
|
|
{
|
|
private TemplateBundleServiceImpl service;
|
|
|
|
@Mock
|
|
private TemplateBundleMapper templateBundleMapper;
|
|
|
|
@Before
|
|
public void setUp() throws Exception
|
|
{
|
|
MockitoAnnotations.initMocks(this);
|
|
service = new TemplateBundleServiceImpl();
|
|
Field field = TemplateBundleServiceImpl.class.getDeclaredField("templateBundleMapper");
|
|
field.setAccessible(true);
|
|
field.set(service, templateBundleMapper);
|
|
}
|
|
|
|
@Test
|
|
public void getDefaultBundleReturnsEnabledDefault()
|
|
{
|
|
TemplateBundle qing = bundle("qing", "0", "1", true, true, true, true);
|
|
when(templateBundleMapper.selectDefaultTemplateBundle()).thenReturn(qing);
|
|
|
|
assertEquals("qing", service.getDefaultBundle().getBundleCode());
|
|
}
|
|
|
|
@Test
|
|
public void getDefaultBundleFallsBackToQingWhenDatabaseIsEmpty()
|
|
{
|
|
when(templateBundleMapper.selectDefaultTemplateBundle()).thenReturn(null);
|
|
|
|
TemplateBundle fallback = service.getDefaultBundle();
|
|
|
|
assertEquals("qing", fallback.getBundleCode());
|
|
assertEquals("Qing Default Template", fallback.getBundleName());
|
|
assertEquals("0", fallback.getStatus());
|
|
}
|
|
|
|
@Test
|
|
public void getRequiredEnabledBundleRejectsDisabledBundle()
|
|
{
|
|
when(templateBundleMapper.selectTemplateBundleByCode("vue3-plus"))
|
|
.thenReturn(bundle("vue3-plus", "1", "0", true, true, true, true));
|
|
|
|
try
|
|
{
|
|
service.getRequiredEnabledBundle("vue3-plus");
|
|
fail("Expected disabled bundle to fail");
|
|
}
|
|
catch (ServiceException e)
|
|
{
|
|
assertEquals("Code template bundle is disabled: vue3-plus", e.getMessage());
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void getSupportedTemplateTypesHonorsFrontendSwitch()
|
|
{
|
|
TemplateBundle qing = bundle("qing", "0", "1", true, true, true, true);
|
|
|
|
List<String> enabled = service.getSupportedTemplateTypes(qing, "1");
|
|
List<String> frontendOff = service.getSupportedTemplateTypes(qing, "0");
|
|
|
|
assertEquals(Arrays.asList("backend", "frontend", "admin_frontend", "sql"), enabled);
|
|
assertEquals(Arrays.asList("backend", "admin_frontend", "sql"), frontendOff);
|
|
}
|
|
|
|
@Test
|
|
public void assertTemplateTypeSupportedRejectsUnsupportedType()
|
|
{
|
|
TemplateBundle backendOnly = bundle("backend-only", "0", "0", true, false, false, true);
|
|
|
|
try
|
|
{
|
|
service.assertTemplateTypeSupported(backendOnly, "frontend", "1");
|
|
fail("Expected unsupported type to fail");
|
|
}
|
|
catch (ServiceException e)
|
|
{
|
|
assertEquals("Code template bundle backend-only does not support template type: frontend", e.getMessage());
|
|
}
|
|
}
|
|
|
|
private TemplateBundle bundle(String code, String status, String isDefault,
|
|
boolean backend, boolean frontend, boolean adminFrontend, boolean sql)
|
|
{
|
|
TemplateBundle bundle = new TemplateBundle();
|
|
bundle.setBundleCode(code);
|
|
bundle.setBundleName("Qing Default Template");
|
|
bundle.setStatus(status);
|
|
bundle.setIsDefault(isDefault);
|
|
bundle.setSupportBackend(backend ? "1" : "0");
|
|
bundle.setSupportFrontend(frontend ? "1" : "0");
|
|
bundle.setSupportAdminFrontend(adminFrontend ? "1" : "0");
|
|
bundle.setSupportSql(sql ? "1" : "0");
|
|
return bundle;
|
|
}
|
|
}
|
|
```
|
|
|
|
- [ ] **Step 2: Run test to verify it fails**
|
|
|
|
Run: `mvn -pl ruoyi-generator -Dtest=TemplateBundleServiceImplTest test`
|
|
|
|
Expected: compilation fails because `TemplateBundle`, `TemplateBundleMapper`, and `TemplateBundleServiceImpl` do not exist.
|
|
|
|
- [ ] **Step 3: Add domain object**
|
|
|
|
Create `ruoyi-generator/src/main/java/com/ruoyi/generator/domain/TemplateBundle.java`:
|
|
|
|
```java
|
|
package com.ruoyi.generator.domain;
|
|
|
|
import com.ruoyi.common.core.domain.BaseEntity;
|
|
|
|
public class TemplateBundle extends BaseEntity
|
|
{
|
|
private static final long serialVersionUID = 1L;
|
|
|
|
private Long bundleId;
|
|
private String bundleCode;
|
|
private String bundleName;
|
|
private String description;
|
|
private String status;
|
|
private String isDefault;
|
|
private String supportBackend;
|
|
private String supportFrontend;
|
|
private String supportAdminFrontend;
|
|
private String supportSql;
|
|
private Integer sortOrder;
|
|
|
|
public Long getBundleId() { return bundleId; }
|
|
public void setBundleId(Long bundleId) { this.bundleId = bundleId; }
|
|
public String getBundleCode() { return bundleCode; }
|
|
public void setBundleCode(String bundleCode) { this.bundleCode = bundleCode; }
|
|
public String getBundleName() { return bundleName; }
|
|
public void setBundleName(String bundleName) { this.bundleName = bundleName; }
|
|
public String getDescription() { return description; }
|
|
public void setDescription(String description) { this.description = description; }
|
|
public String getStatus() { return status; }
|
|
public void setStatus(String status) { this.status = status; }
|
|
public String getIsDefault() { return isDefault; }
|
|
public void setIsDefault(String isDefault) { this.isDefault = isDefault; }
|
|
public String getSupportBackend() { return supportBackend; }
|
|
public void setSupportBackend(String supportBackend) { this.supportBackend = supportBackend; }
|
|
public String getSupportFrontend() { return supportFrontend; }
|
|
public void setSupportFrontend(String supportFrontend) { this.supportFrontend = supportFrontend; }
|
|
public String getSupportAdminFrontend() { return supportAdminFrontend; }
|
|
public void setSupportAdminFrontend(String supportAdminFrontend) { this.supportAdminFrontend = supportAdminFrontend; }
|
|
public String getSupportSql() { return supportSql; }
|
|
public void setSupportSql(String supportSql) { this.supportSql = supportSql; }
|
|
public Integer getSortOrder() { return sortOrder; }
|
|
public void setSortOrder(Integer sortOrder) { this.sortOrder = sortOrder; }
|
|
}
|
|
```
|
|
|
|
- [ ] **Step 4: Add mapper contract and XML**
|
|
|
|
Create `ruoyi-generator/src/main/java/com/ruoyi/generator/mapper/TemplateBundleMapper.java`:
|
|
|
|
```java
|
|
package com.ruoyi.generator.mapper;
|
|
|
|
import java.util.List;
|
|
import com.ruoyi.generator.domain.TemplateBundle;
|
|
|
|
public interface TemplateBundleMapper
|
|
{
|
|
TemplateBundle selectTemplateBundleByCode(String bundleCode);
|
|
|
|
TemplateBundle selectDefaultTemplateBundle();
|
|
|
|
List<TemplateBundle> selectTemplateBundleList(TemplateBundle templateBundle);
|
|
}
|
|
```
|
|
|
|
Create `ruoyi-generator/src/main/resources/mapper/generator/TemplateBundleMapper.xml`:
|
|
|
|
```xml
|
|
<?xml version="1.0" encoding="UTF-8" ?>
|
|
<!DOCTYPE mapper
|
|
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
|
<mapper namespace="com.ruoyi.generator.mapper.TemplateBundleMapper">
|
|
|
|
<resultMap type="com.ruoyi.generator.domain.TemplateBundle" id="TemplateBundleResult">
|
|
<id property="bundleId" column="bundle_id" />
|
|
<result property="bundleCode" column="bundle_code" />
|
|
<result property="bundleName" column="bundle_name" />
|
|
<result property="description" column="description" />
|
|
<result property="status" column="status" />
|
|
<result property="isDefault" column="is_default" />
|
|
<result property="supportBackend" column="support_backend" />
|
|
<result property="supportFrontend" column="support_frontend" />
|
|
<result property="supportAdminFrontend" column="support_admin_frontend" />
|
|
<result property="supportSql" column="support_sql" />
|
|
<result property="sortOrder" column="sort_order" />
|
|
<result property="createBy" column="create_by" />
|
|
<result property="createTime" column="create_time" />
|
|
<result property="updateBy" column="update_by" />
|
|
<result property="updateTime" column="update_time" />
|
|
<result property="remark" column="remark" />
|
|
</resultMap>
|
|
|
|
<sql id="selectTemplateBundleVo">
|
|
select bundle_id, bundle_code, bundle_name, description, status, is_default,
|
|
support_backend, support_frontend, support_admin_frontend, support_sql,
|
|
sort_order, create_by, create_time, update_by, update_time, remark
|
|
from sys_template_bundle
|
|
</sql>
|
|
|
|
<select id="selectTemplateBundleByCode" parameterType="String" resultMap="TemplateBundleResult">
|
|
<include refid="selectTemplateBundleVo"/>
|
|
where bundle_code = #{bundleCode}
|
|
limit 1
|
|
</select>
|
|
|
|
<select id="selectDefaultTemplateBundle" resultMap="TemplateBundleResult">
|
|
<include refid="selectTemplateBundleVo"/>
|
|
where status = '0' and is_default = '1'
|
|
order by sort_order asc, bundle_id asc
|
|
limit 1
|
|
</select>
|
|
|
|
<select id="selectTemplateBundleList" parameterType="com.ruoyi.generator.domain.TemplateBundle" resultMap="TemplateBundleResult">
|
|
<include refid="selectTemplateBundleVo"/>
|
|
<where>
|
|
<if test="bundleCode != null and bundleCode != ''">and bundle_code = #{bundleCode}</if>
|
|
<if test="bundleName != null and bundleName != ''">and bundle_name like concat('%', #{bundleName}, '%')</if>
|
|
<if test="status != null and status != ''">and status = #{status}</if>
|
|
</where>
|
|
order by sort_order asc, bundle_id asc
|
|
</select>
|
|
</mapper>
|
|
```
|
|
|
|
- [ ] **Step 5: Add service contract and implementation**
|
|
|
|
Create `ruoyi-generator/src/main/java/com/ruoyi/generator/service/ITemplateBundleService.java`:
|
|
|
|
```java
|
|
package com.ruoyi.generator.service;
|
|
|
|
import java.util.List;
|
|
import com.ruoyi.generator.domain.TemplateBundle;
|
|
|
|
public interface ITemplateBundleService
|
|
{
|
|
String DEFAULT_BUNDLE_CODE = "qing";
|
|
|
|
List<TemplateBundle> listEnabledBundles();
|
|
|
|
TemplateBundle getDefaultBundle();
|
|
|
|
TemplateBundle getRequiredEnabledBundle(String bundleCode);
|
|
|
|
List<String> getSupportedTemplateTypes(TemplateBundle bundle, String frontendEnabled);
|
|
|
|
List<String> getSupportedTemplateTypes(String bundleCode, String frontendEnabled);
|
|
|
|
void assertTemplateTypeSupported(TemplateBundle bundle, String templateType, String frontendEnabled);
|
|
}
|
|
```
|
|
|
|
Create `ruoyi-generator/src/main/java/com/ruoyi/generator/service/TemplateBundleServiceImpl.java`:
|
|
|
|
```java
|
|
package com.ruoyi.generator.service;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
import org.springframework.stereotype.Service;
|
|
import com.ruoyi.common.exception.ServiceException;
|
|
import com.ruoyi.common.utils.StringUtils;
|
|
import com.ruoyi.generator.domain.TemplateBundle;
|
|
import com.ruoyi.generator.mapper.TemplateBundleMapper;
|
|
|
|
@Service
|
|
public class TemplateBundleServiceImpl implements ITemplateBundleService
|
|
{
|
|
@Autowired
|
|
private TemplateBundleMapper templateBundleMapper;
|
|
|
|
@Override
|
|
public List<TemplateBundle> listEnabledBundles()
|
|
{
|
|
TemplateBundle query = new TemplateBundle();
|
|
query.setStatus("0");
|
|
return templateBundleMapper.selectTemplateBundleList(query);
|
|
}
|
|
|
|
@Override
|
|
public TemplateBundle getDefaultBundle()
|
|
{
|
|
TemplateBundle bundle = templateBundleMapper.selectDefaultTemplateBundle();
|
|
return bundle == null ? qingFallbackBundle() : bundle;
|
|
}
|
|
|
|
@Override
|
|
public TemplateBundle getRequiredEnabledBundle(String bundleCode)
|
|
{
|
|
String code = StringUtils.defaultIfEmpty(bundleCode, getDefaultBundle().getBundleCode());
|
|
TemplateBundle bundle = templateBundleMapper.selectTemplateBundleByCode(code);
|
|
if (bundle == null && DEFAULT_BUNDLE_CODE.equals(code))
|
|
{
|
|
bundle = qingFallbackBundle();
|
|
}
|
|
if (bundle == null)
|
|
{
|
|
throw new ServiceException("Code template bundle does not exist: " + code);
|
|
}
|
|
if (!"0".equals(bundle.getStatus()))
|
|
{
|
|
throw new ServiceException("Code template bundle is disabled: " + code);
|
|
}
|
|
return bundle;
|
|
}
|
|
|
|
@Override
|
|
public List<String> getSupportedTemplateTypes(String bundleCode, String frontendEnabled)
|
|
{
|
|
return getSupportedTemplateTypes(getRequiredEnabledBundle(bundleCode), frontendEnabled);
|
|
}
|
|
|
|
@Override
|
|
public List<String> getSupportedTemplateTypes(TemplateBundle bundle, String frontendEnabled)
|
|
{
|
|
List<String> types = new ArrayList<String>();
|
|
if (isSupported(bundle == null ? null : bundle.getSupportBackend()))
|
|
{
|
|
types.add("backend");
|
|
}
|
|
if (!"0".equals(frontendEnabled) && isSupported(bundle == null ? null : bundle.getSupportFrontend()))
|
|
{
|
|
types.add("frontend");
|
|
}
|
|
if (isSupported(bundle == null ? null : bundle.getSupportAdminFrontend()))
|
|
{
|
|
types.add("admin_frontend");
|
|
}
|
|
if (isSupported(bundle == null ? null : bundle.getSupportSql()))
|
|
{
|
|
types.add("sql");
|
|
}
|
|
return types;
|
|
}
|
|
|
|
@Override
|
|
public void assertTemplateTypeSupported(TemplateBundle bundle, String templateType, String frontendEnabled)
|
|
{
|
|
if (!getSupportedTemplateTypes(bundle, frontendEnabled).contains(templateType))
|
|
{
|
|
String code = bundle == null ? DEFAULT_BUNDLE_CODE : bundle.getBundleCode();
|
|
throw new ServiceException("Code template bundle " + code
|
|
+ " does not support template type: " + templateType);
|
|
}
|
|
}
|
|
|
|
private boolean isSupported(String value)
|
|
{
|
|
return !"0".equals(value);
|
|
}
|
|
|
|
private TemplateBundle qingFallbackBundle()
|
|
{
|
|
TemplateBundle bundle = new TemplateBundle();
|
|
bundle.setBundleCode(DEFAULT_BUNDLE_CODE);
|
|
bundle.setBundleName("Qing Default Template");
|
|
bundle.setStatus("0");
|
|
bundle.setIsDefault("1");
|
|
bundle.setSupportBackend("1");
|
|
bundle.setSupportFrontend("1");
|
|
bundle.setSupportAdminFrontend("1");
|
|
bundle.setSupportSql("1");
|
|
bundle.setSortOrder(0);
|
|
return bundle;
|
|
}
|
|
}
|
|
```
|
|
|
|
- [ ] **Step 6: Run test to verify it passes**
|
|
|
|
Run: `mvn -pl ruoyi-generator -Dtest=TemplateBundleServiceImplTest test`
|
|
|
|
Expected: PASS with all 5 tests green.
|
|
|
|
- [ ] **Step 7: Commit**
|
|
|
|
```bash
|
|
git add ruoyi-generator/src/main/java/com/ruoyi/generator/domain/TemplateBundle.java \
|
|
ruoyi-generator/src/main/java/com/ruoyi/generator/mapper/TemplateBundleMapper.java \
|
|
ruoyi-generator/src/main/resources/mapper/generator/TemplateBundleMapper.xml \
|
|
ruoyi-generator/src/main/java/com/ruoyi/generator/service/ITemplateBundleService.java \
|
|
ruoyi-generator/src/main/java/com/ruoyi/generator/service/TemplateBundleServiceImpl.java \
|
|
ruoyi-generator/src/test/java/com/ruoyi/generator/service/TemplateBundleServiceImplTest.java
|
|
git commit -m "feat: add template bundle service"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 2: Persist Bundle Codes On Templates And Projects
|
|
|
|
**Files:**
|
|
- Modify: `ruoyi-generator/src/main/java/com/ruoyi/generator/domain/Template.java`
|
|
- Modify: `ruoyi-generator/src/main/resources/mapper/generator/TemplateMapper.xml`
|
|
- Modify: `ruoyi-generator/src/main/java/com/ruoyi/generator/domain/front/FrontProject.java`
|
|
- Modify: `ruoyi-generator/src/main/java/com/ruoyi/generator/domain/front/dto/FrontProjectCreateRequest.java`
|
|
- Modify: `ruoyi-generator/src/main/java/com/ruoyi/generator/domain/front/dto/FrontProjectUpdateRequest.java`
|
|
- Modify: `ruoyi-generator/src/main/java/com/ruoyi/generator/domain/front/dto/AiGenerationTaskCreateRequest.java`
|
|
- Modify: `ruoyi-generator/src/main/java/com/ruoyi/generator/domain/GenProject.java`
|
|
- Modify: `ruoyi-generator/src/main/resources/mapper/front/FrontProjectMapper.xml`
|
|
- Modify: `ruoyi-generator/src/test/java/com/ruoyi/generator/service/front/FrontProjectServiceImplTest.java`
|
|
|
|
- [ ] **Step 1: Write failing project persistence tests**
|
|
|
|
Add these tests to `FrontProjectServiceImplTest`:
|
|
|
|
```java
|
|
@Mock
|
|
private com.ruoyi.generator.service.ITemplateBundleService templateBundleService;
|
|
```
|
|
|
|
In `setUp`, add:
|
|
|
|
```java
|
|
setField("templateBundleService", templateBundleService);
|
|
when(templateBundleService.getDefaultBundle()).thenReturn(templateBundle("qing"));
|
|
when(templateBundleService.getRequiredEnabledBundle("qing")).thenReturn(templateBundle("qing"));
|
|
when(templateBundleService.getRequiredEnabledBundle("vue3-plus")).thenReturn(templateBundle("vue3-plus"));
|
|
```
|
|
|
|
Add tests:
|
|
|
|
```java
|
|
@Test
|
|
public void createProjectDefaultsToQingCodeTemplate()
|
|
{
|
|
FrontProjectCreateRequest request = new FrontProjectCreateRequest();
|
|
request.setProjectName("Demo");
|
|
|
|
FrontProject project = service.createProject(7L, request);
|
|
|
|
assertEquals("qing", project.getCodeTemplate());
|
|
verify(frontProjectMapper).insertFrontProject(project);
|
|
}
|
|
|
|
@Test
|
|
public void createProjectPersistsSelectedCodeTemplate()
|
|
{
|
|
FrontProjectCreateRequest request = new FrontProjectCreateRequest();
|
|
request.setProjectName("Demo");
|
|
request.setCodeTemplate("vue3-plus");
|
|
|
|
FrontProject project = service.createProject(7L, request);
|
|
|
|
assertEquals("vue3-plus", project.getCodeTemplate());
|
|
verify(templateBundleService).getRequiredEnabledBundle("vue3-plus");
|
|
}
|
|
|
|
@Test
|
|
public void updateProjectPersistsSelectedCodeTemplate()
|
|
{
|
|
when(frontProjectMapper.selectFrontProjectByUserAndId(7L, 10L)).thenReturn(project());
|
|
FrontProjectUpdateRequest request = new FrontProjectUpdateRequest();
|
|
request.setProjectName("Demo");
|
|
request.setCodeTemplate("vue3-plus");
|
|
|
|
service.updateProject(7L, 10L, request);
|
|
|
|
ArgumentCaptor<FrontProject> projectCaptor = ArgumentCaptor.forClass(FrontProject.class);
|
|
verify(frontProjectMapper).updateFrontProject(projectCaptor.capture());
|
|
assertEquals("vue3-plus", projectCaptor.getValue().getCodeTemplate());
|
|
}
|
|
|
|
private com.ruoyi.generator.domain.TemplateBundle templateBundle(String code)
|
|
{
|
|
com.ruoyi.generator.domain.TemplateBundle bundle = new com.ruoyi.generator.domain.TemplateBundle();
|
|
bundle.setBundleCode(code);
|
|
bundle.setBundleName(code);
|
|
bundle.setStatus("0");
|
|
bundle.setSupportBackend("1");
|
|
bundle.setSupportFrontend("1");
|
|
bundle.setSupportAdminFrontend("1");
|
|
bundle.setSupportSql("1");
|
|
return bundle;
|
|
}
|
|
```
|
|
|
|
- [ ] **Step 2: Run tests to verify they fail**
|
|
|
|
Run: `mvn -pl ruoyi-generator -Dtest=FrontProjectServiceImplTest test`
|
|
|
|
Expected: compilation fails because `codeTemplate` accessors and `templateBundleService` field do not exist.
|
|
|
|
- [ ] **Step 3: Add fields and accessors**
|
|
|
|
In `Template.java`, add after `templateType`:
|
|
|
|
```java
|
|
private String bundleCode;
|
|
|
|
public String getBundleCode()
|
|
{
|
|
return bundleCode;
|
|
}
|
|
|
|
public void setBundleCode(String bundleCode)
|
|
{
|
|
this.bundleCode = bundleCode;
|
|
}
|
|
```
|
|
|
|
Also add to `toString()`:
|
|
|
|
```java
|
|
.append("bundleCode", getBundleCode())
|
|
```
|
|
|
|
In `FrontProject.java`, add after `industryTemplate`:
|
|
|
|
```java
|
|
private String codeTemplate;
|
|
|
|
public String getCodeTemplate() { return codeTemplate; }
|
|
public void setCodeTemplate(String codeTemplate) { this.codeTemplate = codeTemplate; }
|
|
```
|
|
|
|
In `FrontProjectCreateRequest.java`, `FrontProjectUpdateRequest.java`, and `AiGenerationTaskCreateRequest.java`, add:
|
|
|
|
```java
|
|
private String codeTemplate;
|
|
|
|
public String getCodeTemplate() { return codeTemplate; }
|
|
public void setCodeTemplate(String codeTemplate) { this.codeTemplate = codeTemplate; }
|
|
```
|
|
|
|
In `GenProject.java`, add near transient generation fields:
|
|
|
|
```java
|
|
private transient String codeTemplate;
|
|
|
|
public String getCodeTemplate() {
|
|
return codeTemplate;
|
|
}
|
|
|
|
public void setCodeTemplate(String codeTemplate) {
|
|
this.codeTemplate = codeTemplate;
|
|
}
|
|
```
|
|
|
|
- [ ] **Step 4: Map `bundle_code` and `code_template`**
|
|
|
|
In `TemplateMapper.xml`, update `TemplateResult`:
|
|
|
|
```xml
|
|
<result property="bundleCode" column="bundle_code" />
|
|
```
|
|
|
|
Update `selectTemplateVo`:
|
|
|
|
```xml
|
|
select t.template_id, t.template_name, t.template_path, t.template_desc, t.template_type, t.bundle_code, t.template_status
|
|
from sys_template t
|
|
```
|
|
|
|
Add filter:
|
|
|
|
```xml
|
|
<if test="bundleCode != null and bundleCode != ''">
|
|
AND t.bundle_code = #{bundleCode}
|
|
</if>
|
|
```
|
|
|
|
Update `selectTemplateByTemplateId` selected columns:
|
|
|
|
```xml
|
|
select t.template_id, t.template_name, t.template_path, t.template_desc, t.template_type, t.bundle_code, t.template_status,
|
|
tf.template_file_id, tf.file_name, tf.file_path, tf.file_content, tf.create_by, tf.create_time, tf.update_by, tf.update_time
|
|
```
|
|
|
|
Add `bundle_code` to insert and update trims:
|
|
|
|
```xml
|
|
<if test="bundleCode != null and bundleCode != ''">bundle_code,</if>
|
|
```
|
|
|
|
```xml
|
|
<if test="bundleCode != null and bundleCode != ''">#{bundleCode},</if>
|
|
```
|
|
|
|
```xml
|
|
<if test="bundleCode != null and bundleCode != ''">bundle_code = #{bundleCode},</if>
|
|
```
|
|
|
|
In `FrontProjectMapper.xml`, add result:
|
|
|
|
```xml
|
|
<result property="codeTemplate" column="code_template" />
|
|
```
|
|
|
|
Update `selectFrontProjectVo` to include `code_template` after `industry_template`:
|
|
|
|
```xml
|
|
select project_id, user_id, project_name, project_file_name, package_name, version, project_desc,
|
|
requirement_keyword, industry_template, code_template, style_preset, front_framework, back_framework,
|
|
frontend_enabled, generate_status, preview_status, app_blueprint, business_loop_plan, er_diagram,
|
|
business_blueprint, status, create_time, update_time, remark
|
|
from front_project
|
|
```
|
|
|
|
Add insert column/value:
|
|
|
|
```xml
|
|
<if test="codeTemplate != null">code_template,</if>
|
|
```
|
|
|
|
```xml
|
|
<if test="codeTemplate != null">#{codeTemplate},</if>
|
|
```
|
|
|
|
Add update:
|
|
|
|
```xml
|
|
<if test="codeTemplate != null">code_template = #{codeTemplate},</if>
|
|
```
|
|
|
|
- [ ] **Step 5: Default and validate project `codeTemplate`**
|
|
|
|
In `FrontProjectServiceImpl`, add import:
|
|
|
|
```java
|
|
import com.ruoyi.generator.service.ITemplateBundleService;
|
|
```
|
|
|
|
Add field:
|
|
|
|
```java
|
|
@Autowired
|
|
private ITemplateBundleService templateBundleService;
|
|
```
|
|
|
|
In `createProject`, after `project.setIndustryTemplate(request.getIndustryTemplate());`, add:
|
|
|
|
```java
|
|
project.setCodeTemplate(resolveCodeTemplate(request.getCodeTemplate()));
|
|
```
|
|
|
|
In `updateProject`, after `project.setIndustryTemplate(request.getIndustryTemplate());`, add:
|
|
|
|
```java
|
|
project.setCodeTemplate(resolveNullableCodeTemplate(request.getCodeTemplate()));
|
|
```
|
|
|
|
Add helper methods:
|
|
|
|
```java
|
|
private String resolveCodeTemplate(String codeTemplate)
|
|
{
|
|
String code = StringUtils.defaultIfEmpty(codeTemplate, templateBundleService.getDefaultBundle().getBundleCode());
|
|
return templateBundleService.getRequiredEnabledBundle(code).getBundleCode();
|
|
}
|
|
|
|
private String resolveNullableCodeTemplate(String codeTemplate)
|
|
{
|
|
if (codeTemplate == null)
|
|
{
|
|
return null;
|
|
}
|
|
return resolveCodeTemplate(codeTemplate);
|
|
}
|
|
```
|
|
|
|
In `FrontProjectConverter.toGenProject`, after `genProject.setStylePreset(project.getStylePreset());`, add:
|
|
|
|
```java
|
|
genProject.setCodeTemplate(project.getCodeTemplate());
|
|
```
|
|
|
|
- [ ] **Step 6: Run tests to verify they pass**
|
|
|
|
Run: `mvn -pl ruoyi-generator -Dtest=FrontProjectServiceImplTest test`
|
|
|
|
Expected: PASS.
|
|
|
|
- [ ] **Step 7: Commit**
|
|
|
|
```bash
|
|
git add ruoyi-generator/src/main/java/com/ruoyi/generator/domain/Template.java \
|
|
ruoyi-generator/src/main/resources/mapper/generator/TemplateMapper.xml \
|
|
ruoyi-generator/src/main/java/com/ruoyi/generator/domain/front/FrontProject.java \
|
|
ruoyi-generator/src/main/java/com/ruoyi/generator/domain/front/dto/FrontProjectCreateRequest.java \
|
|
ruoyi-generator/src/main/java/com/ruoyi/generator/domain/front/dto/FrontProjectUpdateRequest.java \
|
|
ruoyi-generator/src/main/java/com/ruoyi/generator/domain/front/dto/AiGenerationTaskCreateRequest.java \
|
|
ruoyi-generator/src/main/java/com/ruoyi/generator/domain/GenProject.java \
|
|
ruoyi-generator/src/main/resources/mapper/front/FrontProjectMapper.xml \
|
|
ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/FrontProjectServiceImpl.java \
|
|
ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/FrontProjectConverter.java \
|
|
ruoyi-generator/src/test/java/com/ruoyi/generator/service/front/FrontProjectServiceImplTest.java
|
|
git commit -m "feat: persist selected code template bundle"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 3: Resolve Generation Templates By Bundle
|
|
|
|
**Files:**
|
|
- Modify: `ruoyi-generator/src/main/java/com/ruoyi/generator/service/GenProjectServiceImpl.java`
|
|
- Modify: `ruoyi-generator/src/test/java/com/ruoyi/generator/service/GenProjectServiceImplTest.java`
|
|
|
|
- [ ] **Step 1: Write failing bundle selection tests**
|
|
|
|
In `GenProjectServiceImplTest`, add mock field:
|
|
|
|
```java
|
|
@Mock
|
|
private ITemplateBundleService templateBundleService;
|
|
```
|
|
|
|
In `setUp`, add:
|
|
|
|
```java
|
|
setField("templateBundleService", templateBundleService);
|
|
when(templateBundleService.getDefaultBundle()).thenReturn(bundle("qing"));
|
|
when(templateBundleService.getRequiredEnabledBundle("qing")).thenReturn(bundle("qing"));
|
|
when(templateBundleService.getRequiredEnabledBundle("vue3-plus")).thenReturn(bundle("vue3-plus"));
|
|
```
|
|
|
|
Add tests:
|
|
|
|
```java
|
|
@Test
|
|
public void frontDraftTemplateSelectionUsesProjectCodeTemplate()
|
|
{
|
|
GenProject project = project();
|
|
project.setFrontDraft(true);
|
|
project.setCodeTemplate("vue3-plus");
|
|
Template vue3Backend = template(9301L, 0L);
|
|
vue3Backend.setBundleCode("vue3-plus");
|
|
Template qingBackend = template(9201L, 0L);
|
|
qingBackend.setBundleCode("qing");
|
|
|
|
when(templateService.selectTemplateList(any(Template.class))).thenReturn(Arrays.asList(qingBackend, vue3Backend));
|
|
when(sysProjectStructureService.selectSysProjectStructureList(any(SysProjectStructure.class))).thenReturn(Arrays.asList(
|
|
structure(1L, 0L, "{projectName}", "folder", null, null)
|
|
));
|
|
|
|
service.getProjectStructure(project, "backend");
|
|
|
|
ArgumentCaptor<Template> filterCaptor = ArgumentCaptor.forClass(Template.class);
|
|
verify(templateService).selectTemplateList(filterCaptor.capture());
|
|
assertEquals("backend", filterCaptor.getValue().getTemplateType());
|
|
assertEquals("vue3-plus", filterCaptor.getValue().getBundleCode());
|
|
}
|
|
|
|
@Test
|
|
public void frontDraftTemplateSelectionFallsBackToDefaultBundle()
|
|
{
|
|
GenProject project = project();
|
|
project.setFrontDraft(true);
|
|
project.setCodeTemplate("");
|
|
Template qingBackend = template(9201L, 0L);
|
|
qingBackend.setBundleCode("qing");
|
|
|
|
when(templateService.selectTemplateList(any(Template.class))).thenReturn(Arrays.asList(qingBackend));
|
|
when(sysProjectStructureService.selectSysProjectStructureList(any(SysProjectStructure.class))).thenReturn(Arrays.asList(
|
|
structure(1L, 0L, "{projectName}", "folder", null, null)
|
|
));
|
|
|
|
service.getProjectStructure(project, "backend");
|
|
|
|
ArgumentCaptor<Template> filterCaptor = ArgumentCaptor.forClass(Template.class);
|
|
verify(templateService).selectTemplateList(filterCaptor.capture());
|
|
assertEquals("qing", filterCaptor.getValue().getBundleCode());
|
|
}
|
|
|
|
@Test
|
|
public void boundProjectTemplateStillWinsOverBundleDefault()
|
|
{
|
|
GenProject project = project();
|
|
project.setFrontDraft(false);
|
|
project.setCodeTemplate("vue3-plus");
|
|
SysProjectTemplate bound = projectTemplate(100L);
|
|
|
|
when(sysProjectTemplateService.selectSysProjectTemplateByProjectAndType(1L, "backend")).thenReturn(bound);
|
|
when(sysProjectStructureService.selectProjectStructureListByModule(1L, 100L)).thenReturn(Arrays.asList(
|
|
structure(1L, 0L, "{projectName}", "folder", null, null)
|
|
));
|
|
|
|
service.getProjectStructure(project, "backend");
|
|
|
|
verify(templateService, never()).selectTemplateList(any(Template.class));
|
|
}
|
|
|
|
private com.ruoyi.generator.domain.TemplateBundle bundle(String code)
|
|
{
|
|
com.ruoyi.generator.domain.TemplateBundle bundle = new com.ruoyi.generator.domain.TemplateBundle();
|
|
bundle.setBundleCode(code);
|
|
bundle.setStatus("0");
|
|
bundle.setSupportBackend("1");
|
|
bundle.setSupportFrontend("1");
|
|
bundle.setSupportAdminFrontend("1");
|
|
bundle.setSupportSql("1");
|
|
return bundle;
|
|
}
|
|
```
|
|
|
|
- [ ] **Step 2: Run tests to verify they fail**
|
|
|
|
Run: `mvn -pl ruoyi-generator -Dtest=GenProjectServiceImplTest test`
|
|
|
|
Expected: compilation fails until Task 2 exists, then assertion fails because `resolveActiveTemplate` ignores `bundleCode`.
|
|
|
|
- [ ] **Step 3: Inject template bundle service**
|
|
|
|
In `GenProjectServiceImpl`, add imports:
|
|
|
|
```java
|
|
import com.ruoyi.generator.domain.TemplateBundle;
|
|
```
|
|
|
|
Add field:
|
|
|
|
```java
|
|
@Autowired
|
|
private ITemplateBundleService templateBundleService;
|
|
```
|
|
|
|
- [ ] **Step 4: Replace hardcoded runnable template preference**
|
|
|
|
Remove constants:
|
|
|
|
```java
|
|
private static final Long RUNNABLE_BACKEND_TEMPLATE_ID = 9201L;
|
|
private static final Long RUNNABLE_ADMIN_FRONTEND_TEMPLATE_ID = 9202L;
|
|
private static final Long RUNNABLE_FRONTEND_TEMPLATE_ID = 9203L;
|
|
```
|
|
|
|
Replace `resolveActiveTemplate` and remove `preferredRunnableTemplateId`:
|
|
|
|
```java
|
|
private SysProjectTemplate resolveActiveTemplate(Long projectId, String type) {
|
|
GenProject project = null;
|
|
if (projectId != null) {
|
|
project = genProjectMapper.selectGenProjectById(projectId);
|
|
}
|
|
String bundleCode = project == null ? null : project.getCodeTemplate();
|
|
return resolveActiveTemplate(projectId, type, bundleCode);
|
|
}
|
|
|
|
private SysProjectTemplate resolveActiveTemplate(Long projectId, String type, String bundleCode) {
|
|
TemplateBundle bundle = templateBundleService.getRequiredEnabledBundle(
|
|
StringUtils.defaultIfEmpty(bundleCode, templateBundleService.getDefaultBundle().getBundleCode()));
|
|
templateBundleService.assertTemplateTypeSupported(bundle, type, null);
|
|
Template templateFilter = new Template();
|
|
templateFilter.setTemplateType(type);
|
|
templateFilter.setBundleCode(bundle.getBundleCode());
|
|
List<Template> templates = templateService.selectTemplateList(templateFilter);
|
|
if (StringUtils.isNotEmpty(templates)) {
|
|
for (Template template : templates) {
|
|
if (template != null && template.getTemplateId() != null
|
|
&& Long.valueOf(0L).equals(template.getTemplateStatus())) {
|
|
return toProjectTemplate(projectId, type, template);
|
|
}
|
|
}
|
|
}
|
|
throw new ServiceException("No enabled template configured for bundle "
|
|
+ bundle.getBundleCode() + " and type " + type);
|
|
}
|
|
```
|
|
|
|
Then update `resolveProjectTemplate` fallback:
|
|
|
|
```java
|
|
return resolveActiveTemplate(project.getProjectId(), type, project.getCodeTemplate());
|
|
```
|
|
|
|
Keep the existing bound project-template branch before this fallback.
|
|
|
|
- [ ] **Step 5: Avoid database reload for front draft projects**
|
|
|
|
If the Step 4 overload still calls `genProjectMapper` during front draft tests, adjust the method so the active fallback receives `project.getCodeTemplate()` directly:
|
|
|
|
```java
|
|
private SysProjectTemplate resolveProjectTemplate(GenProject project, String type) {
|
|
if (!project.isFrontDraft()) {
|
|
SysProjectTemplate projectTemplate = sysProjectTemplateService.selectSysProjectTemplateByProjectAndType(project.getProjectId(), type);
|
|
if (projectTemplate != null && projectTemplate.getTemplateId() != null) {
|
|
return projectTemplate;
|
|
}
|
|
}
|
|
|
|
return resolveActiveTemplate(project.getProjectId(), type, project.getCodeTemplate());
|
|
}
|
|
```
|
|
|
|
- [ ] **Step 6: Run tests to verify they pass**
|
|
|
|
Run: `mvn -pl ruoyi-generator -Dtest=GenProjectServiceImplTest test`
|
|
|
|
Expected: PASS.
|
|
|
|
- [ ] **Step 7: Commit**
|
|
|
|
```bash
|
|
git add ruoyi-generator/src/main/java/com/ruoyi/generator/service/GenProjectServiceImpl.java \
|
|
ruoyi-generator/src/test/java/com/ruoyi/generator/service/GenProjectServiceImplTest.java
|
|
git commit -m "feat: resolve generation templates by bundle"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 4: Expose Supported Types And Use Them In Preview Downloads
|
|
|
|
**Files:**
|
|
- Modify: `ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/IFrontProjectPreviewService.java`
|
|
- Modify: `ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/FrontProjectPreviewServiceImpl.java`
|
|
- Modify: `ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/OneClickProjectGenerationServiceImpl.java`
|
|
- Modify: `ruoyi-admin/src/main/java/com/ruoyi/web/controller/front/FrontProjectController.java`
|
|
- Modify: `ruoyi-generator/src/test/java/com/ruoyi/generator/service/front/FrontProjectPreviewServiceImplTest.java`
|
|
|
|
- [ ] **Step 1: Write failing preview service test**
|
|
|
|
In `FrontProjectPreviewServiceImplTest`, add a mock:
|
|
|
|
```java
|
|
@Mock
|
|
private com.ruoyi.generator.service.ITemplateBundleService templateBundleService;
|
|
```
|
|
|
|
In `setUp`, inject it:
|
|
|
|
```java
|
|
setField("templateBundleService", templateBundleService);
|
|
```
|
|
|
|
Add test:
|
|
|
|
```java
|
|
@Test
|
|
public void supportedTemplateTypesComeFromSelectedBundleAndFrontendSwitch()
|
|
{
|
|
FrontProject project = project();
|
|
project.setCodeTemplate("backend-only");
|
|
project.setFrontendEnabled("0");
|
|
when(frontProjectService.getProject(7L, 10L)).thenReturn(project);
|
|
when(templateBundleService.getSupportedTemplateTypes("backend-only", "0"))
|
|
.thenReturn(Arrays.asList("backend", "sql"));
|
|
|
|
List<String> types = service.getSupportedTemplateTypes(7L, 10L);
|
|
|
|
assertEquals(Arrays.asList("backend", "sql"), types);
|
|
}
|
|
```
|
|
|
|
- [ ] **Step 2: Run test to verify it fails**
|
|
|
|
Run: `mvn -pl ruoyi-generator -Dtest=FrontProjectPreviewServiceImplTest test`
|
|
|
|
Expected: compilation fails because `getSupportedTemplateTypes` is missing.
|
|
|
|
- [ ] **Step 3: Add service method**
|
|
|
|
In `IFrontProjectPreviewService`, add:
|
|
|
|
```java
|
|
public List<String> getSupportedTemplateTypes(Long userId, Long projectId);
|
|
```
|
|
|
|
In `FrontProjectPreviewServiceImpl`, add field:
|
|
|
|
```java
|
|
@Autowired
|
|
private com.ruoyi.generator.service.ITemplateBundleService templateBundleService;
|
|
```
|
|
|
|
Add implementation:
|
|
|
|
```java
|
|
@Override
|
|
public List<String> getSupportedTemplateTypes(Long userId, Long projectId)
|
|
{
|
|
FrontProject frontProject = frontProjectService.getProject(userId, projectId);
|
|
return templateBundleService.getSupportedTemplateTypes(frontProject.getCodeTemplate(),
|
|
frontProject.getFrontendEnabled());
|
|
}
|
|
```
|
|
|
|
Update `assertTemplateTypeEnabled`:
|
|
|
|
```java
|
|
private void assertTemplateTypeEnabled(GenProject project, String templateType)
|
|
{
|
|
com.ruoyi.generator.domain.TemplateBundle bundle = templateBundleService.getRequiredEnabledBundle(
|
|
project == null ? null : project.getCodeTemplate());
|
|
templateBundleService.assertTemplateTypeSupported(bundle, templateType,
|
|
project == null ? null : project.getFrontendEnabled());
|
|
}
|
|
```
|
|
|
|
- [ ] **Step 4: Update download-all**
|
|
|
|
Replace fixed calls in `downloadAll`:
|
|
|
|
```java
|
|
appendZip(zip, genProjectService.downloadStructure(genProject, "backend"), zipEntries);
|
|
if (isFrontendEnabled(genProject))
|
|
{
|
|
appendZip(zip, genProjectService.downloadStructure(genProject, "frontend"), zipEntries);
|
|
}
|
|
appendZip(zip, genProjectService.downloadStructure(genProject, "admin_frontend"), zipEntries);
|
|
appendZip(zip, genProjectService.downloadStructure(genProject, "sql"), zipEntries);
|
|
```
|
|
|
|
with:
|
|
|
|
```java
|
|
List<String> types = templateBundleService.getSupportedTemplateTypes(genProject.getCodeTemplate(),
|
|
genProject.getFrontendEnabled());
|
|
for (String type : types)
|
|
{
|
|
appendZip(zip, genProjectService.downloadStructure(genProject, type), zipEntries);
|
|
}
|
|
```
|
|
|
|
- [ ] **Step 5: Update controller endpoints**
|
|
|
|
In `FrontProjectController`, add field:
|
|
|
|
```java
|
|
@Autowired
|
|
private com.ruoyi.generator.service.ITemplateBundleService templateBundleService;
|
|
```
|
|
|
|
Add endpoints near `/options/modules`:
|
|
|
|
```java
|
|
@GetMapping("/options/code-templates")
|
|
public AjaxResult codeTemplates()
|
|
{
|
|
return AjaxResult.success(templateBundleService.listEnabledBundles());
|
|
}
|
|
|
|
@GetMapping("/{projectId}/template-types")
|
|
public AjaxResult templateTypes(@PathVariable Long projectId)
|
|
{
|
|
Long userId = SecurityUtils.getUserId();
|
|
return AjaxResult.success(frontProjectPreviewService.getSupportedTemplateTypes(userId, projectId));
|
|
}
|
|
```
|
|
|
|
Update `preview` action to use supported types:
|
|
|
|
```java
|
|
@PostMapping("/{projectId}/preview")
|
|
public AjaxResult preview(@PathVariable Long projectId)
|
|
{
|
|
Long userId = SecurityUtils.getUserId();
|
|
for (String type : frontProjectPreviewService.getSupportedTemplateTypes(userId, projectId))
|
|
{
|
|
frontProjectPreviewService.getStructure(userId, projectId, type);
|
|
}
|
|
return toAjax(frontProjectPreviewService.markPreviewReady(userId, projectId));
|
|
}
|
|
```
|
|
|
|
- [ ] **Step 6: Update one-click source preview**
|
|
|
|
In `OneClickProjectGenerationServiceImpl`, replace fixed calls:
|
|
|
|
```java
|
|
frontProjectPreviewService.getStructure(task.getUserId(), task.getProjectId(), "backend");
|
|
if (frontendEnabled)
|
|
{
|
|
frontProjectPreviewService.getStructure(task.getUserId(), task.getProjectId(), "frontend");
|
|
}
|
|
frontProjectPreviewService.getStructure(task.getUserId(), task.getProjectId(), "admin_frontend");
|
|
frontProjectPreviewService.getStructure(task.getUserId(), task.getProjectId(), "sql");
|
|
```
|
|
|
|
with:
|
|
|
|
```java
|
|
for (String type : frontProjectPreviewService.getSupportedTemplateTypes(task.getUserId(), task.getProjectId()))
|
|
{
|
|
frontProjectPreviewService.getStructure(task.getUserId(), task.getProjectId(), type);
|
|
}
|
|
```
|
|
|
|
- [ ] **Step 7: Run tests**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
mvn -pl ruoyi-generator -Dtest=FrontProjectPreviewServiceImplTest test
|
|
mvn -pl ruoyi-generator -Dtest=OneClickProjectGenerationServiceImplTest test
|
|
```
|
|
|
|
Expected: PASS.
|
|
|
|
- [ ] **Step 8: Commit**
|
|
|
|
```bash
|
|
git add ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/IFrontProjectPreviewService.java \
|
|
ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/FrontProjectPreviewServiceImpl.java \
|
|
ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/OneClickProjectGenerationServiceImpl.java \
|
|
ruoyi-admin/src/main/java/com/ruoyi/web/controller/front/FrontProjectController.java \
|
|
ruoyi-generator/src/test/java/com/ruoyi/generator/service/front/FrontProjectPreviewServiceImplTest.java
|
|
git commit -m "feat: expose supported code template types"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 5: Add SQL Migration And Qing Seed Updates
|
|
|
|
**Files:**
|
|
- Create: `sql/code_template_bundles.sql`
|
|
- Modify: `sql/front_project_upgrade.sql`
|
|
- Modify: `sql/qing_templates.sql`
|
|
- Modify: `sql/db.sql`
|
|
- Modify: `ruoyi-generator/src/test/java/com/ruoyi/generator/util/QingTemplateSupportTest.java`
|
|
|
|
- [ ] **Step 1: Write failing SQL seed test**
|
|
|
|
Add to `QingTemplateSupportTest`:
|
|
|
|
```java
|
|
@Test
|
|
public void databaseSeedsCodeTemplateBundleMetadata() throws Exception
|
|
{
|
|
String dbSql = read(repoRoot().resolve("sql/db.sql"));
|
|
String qingSql = read(repoRoot().resolve("sql/qing_templates.sql"));
|
|
String bundleSql = read(repoRoot().resolve("sql/code_template_bundles.sql"));
|
|
|
|
assertTrue(dbSql.contains("create table sys_template_bundle"));
|
|
assertTrue(dbSql.contains("bundle_code"));
|
|
assertTrue(dbSql.contains("'qing', 'Qing Default Template'"));
|
|
assertTrue(dbSql.contains("alter table sys_template add column bundle_code"));
|
|
assertTrue(dbSql.contains("alter table front_project add column code_template"));
|
|
assertTrue(qingSql.contains("bundle_code"));
|
|
assertTrue(qingSql.contains("'qing'"));
|
|
assertTrue(bundleSql.contains("sys_template_bundle"));
|
|
}
|
|
```
|
|
|
|
- [ ] **Step 2: Run test to verify it fails**
|
|
|
|
Run: `mvn -pl ruoyi-generator -Dtest=QingTemplateSupportTest#databaseSeedsCodeTemplateBundleMetadata test`
|
|
|
|
Expected: FAIL because `sql/code_template_bundles.sql` is missing and SQL files do not contain bundle metadata.
|
|
|
|
- [ ] **Step 3: Create standalone migration**
|
|
|
|
Create `sql/code_template_bundles.sql`:
|
|
|
|
```sql
|
|
drop table if exists sys_template_bundle;
|
|
create table sys_template_bundle (
|
|
bundle_id bigint(20) not null auto_increment comment 'template bundle id',
|
|
bundle_code varchar(64) not null comment 'template bundle code',
|
|
bundle_name varchar(100) not null comment 'template bundle name',
|
|
description varchar(500) default null comment 'description',
|
|
status char(1) not null default '0' comment 'status 0 normal 1 disabled',
|
|
is_default char(1) not null default '0' comment 'default bundle 1 yes 0 no',
|
|
support_backend char(1) not null default '1' comment 'supports backend',
|
|
support_frontend char(1) not null default '1' comment 'supports frontend',
|
|
support_admin_frontend char(1) not null default '1' comment 'supports admin frontend',
|
|
support_sql char(1) not null default '1' comment 'supports sql',
|
|
sort_order int default 0 comment 'sort order',
|
|
create_by varchar(64) default '' comment 'creator',
|
|
create_time datetime comment 'create time',
|
|
update_by varchar(64) default '' comment 'updater',
|
|
update_time datetime comment 'update time',
|
|
remark varchar(500) default null comment 'remark',
|
|
primary key (bundle_id),
|
|
unique key uk_template_bundle_code (bundle_code)
|
|
) engine=innodb auto_increment=100 comment='code template bundle table';
|
|
|
|
insert into sys_template_bundle
|
|
(bundle_id, bundle_code, bundle_name, description, status, is_default, support_backend,
|
|
support_frontend, support_admin_frontend, support_sql, sort_order, create_by, create_time, remark)
|
|
values
|
|
(1, 'qing', 'Qing Default Template', 'Spring Boot + MyBatis Plus + Vue2 runnable template', '0', '1',
|
|
'1', '1', '1', '1', 1, 'admin', sysdate(), 'default code template bundle');
|
|
|
|
alter table sys_template add column bundle_code varchar(64) default 'qing' comment 'template bundle code';
|
|
update sys_template set bundle_code = 'qing' where template_id in (9201, 9202, 9203);
|
|
|
|
alter table front_project add column code_template varchar(64) default 'qing' comment 'code template bundle';
|
|
update front_project set code_template = 'qing' where code_template is null or code_template = '';
|
|
```
|
|
|
|
For MySQL scripts that may run repeatedly, if the project already uses non-idempotent migration scripts, keep this style. If existing scripts use guarded changes, wrap `alter table` with the repository's established guard pattern.
|
|
|
|
- [ ] **Step 4: Update qing template inserts**
|
|
|
|
In `sql/qing_templates.sql`, change the `INSERT INTO sys_template` column list from:
|
|
|
|
```sql
|
|
INSERT INTO sys_template (template_id, template_name, template_path, template_desc, template_type, template_status, create_by, create_time, remark) VALUES
|
|
```
|
|
|
|
to:
|
|
|
|
```sql
|
|
INSERT INTO sys_template (template_id, template_name, template_path, template_desc, template_type, bundle_code, template_status, create_by, create_time, remark) VALUES
|
|
```
|
|
|
|
Change row values:
|
|
|
|
```sql
|
|
(9201, 'Runnable Backend Template', '/runnable/backend', 'Spring Boot + MyBatis Plus runnable backend', 'backend', 'qing', 0, 'admin', sysdate(), 'three-tier runnable template'),
|
|
(9202, 'Runnable Admin Frontend Template', '/runnable/admin_frontend', 'Vue2 + Element UI runnable admin frontend', 'admin_frontend', 'qing', 0, 'admin', sysdate(), 'three-tier runnable template'),
|
|
(9203, 'Runnable Portal Frontend Template', '/runnable/frontend', 'Vue2 + Element UI runnable portal frontend', 'frontend', 'qing', 0, 'admin', sysdate(), 'three-tier runnable template');
|
|
```
|
|
|
|
- [ ] **Step 5: Update full initialization SQL**
|
|
|
|
In `sql/db.sql`:
|
|
|
|
1. Add `sys_template_bundle` table near `sys_template`.
|
|
2. Add `bundle_code varchar(64) default 'qing' comment 'template bundle code'` to `sys_template`.
|
|
3. Add `code_template varchar(64) default 'qing' comment 'code template bundle'` to `front_project`.
|
|
4. Insert the qing bundle seed before qing templates.
|
|
5. Update all qing `sys_template` inserts with `bundle_code = 'qing'`.
|
|
|
|
- [ ] **Step 6: Update front project upgrade script**
|
|
|
|
In `sql/front_project_upgrade.sql`, add:
|
|
|
|
```sql
|
|
alter table front_project add column code_template varchar(64) default 'qing' comment 'code template bundle';
|
|
update front_project set code_template = 'qing' where code_template is null or code_template = '';
|
|
```
|
|
|
|
- [ ] **Step 7: Run SQL seed test**
|
|
|
|
Run: `mvn -pl ruoyi-generator -Dtest=QingTemplateSupportTest#databaseSeedsCodeTemplateBundleMetadata test`
|
|
|
|
Expected: PASS.
|
|
|
|
- [ ] **Step 8: Commit**
|
|
|
|
```bash
|
|
git add sql/code_template_bundles.sql \
|
|
sql/front_project_upgrade.sql \
|
|
sql/qing_templates.sql \
|
|
sql/db.sql \
|
|
ruoyi-generator/src/test/java/com/ruoyi/generator/util/QingTemplateSupportTest.java
|
|
git commit -m "feat: seed qing code template bundle"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 6: Add Frontend Bundle APIs And Project Form Selection
|
|
|
|
**Files:**
|
|
- Modify: `easycode-web/src/api/project.js`
|
|
- Modify: `easycode-web/src/views/GenerateView.vue`
|
|
- Modify: `easycode-web/src/views/generateView.test.mjs`
|
|
|
|
- [ ] **Step 1: Write failing frontend source tests**
|
|
|
|
Add to `easycode-web/src/views/generateView.test.mjs`:
|
|
|
|
```javascript
|
|
test('generate page exposes code template bundle selection and submits it', () => {
|
|
const source = readSource('views/GenerateView.vue')
|
|
const apiSource = readSource('api/project.js')
|
|
const projectPayload = source.match(/function projectPayload\(\) \{([\s\S]*?)\n\}/)
|
|
const oneClickPayload = source.match(/function oneClickTaskPayload\(\) \{([\s\S]*?)\n\}/)
|
|
|
|
assert.match(apiSource, /export function listCodeTemplateBundles\(\)/)
|
|
assert.match(apiSource, /url: '\/front\/project\/options\/code-templates'/)
|
|
assert.match(apiSource, /export function getProjectSupportedTemplateTypes\(projectId\)/)
|
|
assert.match(source, /const CODE_TEMPLATE_FALLBACK = 'qing'/)
|
|
assert.match(source, /const codeTemplateBundles = ref\(\[\]\)/)
|
|
assert.match(source, /projectForm\.codeTemplate/)
|
|
assert.match(source, /loadCodeTemplateBundles/)
|
|
assert.notEqual(projectPayload, null)
|
|
assert.match(projectPayload[1], /codeTemplate: projectForm\.codeTemplate \|\| CODE_TEMPLATE_FALLBACK/)
|
|
assert.notEqual(oneClickPayload, null)
|
|
assert.match(oneClickPayload[1], /codeTemplate: projectForm\.codeTemplate \|\| CODE_TEMPLATE_FALLBACK/)
|
|
})
|
|
```
|
|
|
|
- [ ] **Step 2: Run frontend test to verify it fails**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
cd easycode-web
|
|
node --test src/views/generateView.test.mjs
|
|
```
|
|
|
|
Expected: FAIL because APIs and form state are missing.
|
|
|
|
- [ ] **Step 3: Add API functions**
|
|
|
|
In `easycode-web/src/api/project.js`, add:
|
|
|
|
```javascript
|
|
export function listCodeTemplateBundles() {
|
|
return request({
|
|
url: '/front/project/options/code-templates',
|
|
method: 'get'
|
|
}).then(unwrap)
|
|
}
|
|
|
|
export function getProjectSupportedTemplateTypes(projectId) {
|
|
return request({
|
|
url: `/front/project/${projectId}/template-types`,
|
|
method: 'get'
|
|
}).then(unwrap)
|
|
}
|
|
```
|
|
|
|
- [ ] **Step 4: Add GenerateView state and loading**
|
|
|
|
In `GenerateView.vue`, add imports:
|
|
|
|
```javascript
|
|
import { listCodeTemplateBundles } from '@/api/project'
|
|
```
|
|
|
|
If there is already a grouped import from `@/api/project`, add `listCodeTemplateBundles` to that list.
|
|
|
|
Add constants and state:
|
|
|
|
```javascript
|
|
const CODE_TEMPLATE_FALLBACK = 'qing'
|
|
const codeTemplateBundles = ref([])
|
|
```
|
|
|
|
Add `codeTemplate` to `projectForm`:
|
|
|
|
```javascript
|
|
codeTemplate: CODE_TEMPLATE_FALLBACK,
|
|
```
|
|
|
|
Add loader:
|
|
|
|
```javascript
|
|
async function loadCodeTemplateBundles() {
|
|
try {
|
|
const result = await listCodeTemplateBundles()
|
|
const bundles = Array.isArray(result) ? result : []
|
|
codeTemplateBundles.value = bundles
|
|
if (!projectForm.codeTemplate && bundles.length) {
|
|
const defaultBundle = bundles.find((bundle) => bundle.isDefault === '1')
|
|
projectForm.codeTemplate = defaultBundle?.bundleCode || bundles[0].bundleCode || CODE_TEMPLATE_FALLBACK
|
|
}
|
|
} catch (error) {
|
|
codeTemplateBundles.value = [{
|
|
bundleCode: CODE_TEMPLATE_FALLBACK,
|
|
bundleName: 'Qing Default Template',
|
|
description: 'Spring Boot + Vue2 runnable template'
|
|
}]
|
|
projectForm.codeTemplate = projectForm.codeTemplate || CODE_TEMPLATE_FALLBACK
|
|
}
|
|
}
|
|
```
|
|
|
|
Call it from `onMounted` next to existing option loaders:
|
|
|
|
```javascript
|
|
await loadCodeTemplateBundles()
|
|
```
|
|
|
|
- [ ] **Step 5: Add project payload fields**
|
|
|
|
In `projectPayload()`, add:
|
|
|
|
```javascript
|
|
codeTemplate: projectForm.codeTemplate || CODE_TEMPLATE_FALLBACK,
|
|
```
|
|
|
|
In `oneClickTaskPayload()`, add:
|
|
|
|
```javascript
|
|
codeTemplate: projectForm.codeTemplate || CODE_TEMPLATE_FALLBACK,
|
|
```
|
|
|
|
If `aiTaskPayload(generateType)` sends common project metadata, add the same field there too:
|
|
|
|
```javascript
|
|
codeTemplate: projectForm.codeTemplate || CODE_TEMPLATE_FALLBACK,
|
|
```
|
|
|
|
- [ ] **Step 6: Add select control**
|
|
|
|
Place this near the visual theme selector in the one-click form:
|
|
|
|
```vue
|
|
<el-form-item label="代码模板">
|
|
<el-select v-model="projectForm.codeTemplate">
|
|
<el-option
|
|
v-for="bundle in codeTemplateBundles"
|
|
:key="bundle.bundleCode"
|
|
:label="bundle.bundleName || bundle.bundleCode"
|
|
:value="bundle.bundleCode"
|
|
>
|
|
<div class="code-template-option">
|
|
<strong>{{ bundle.bundleName || bundle.bundleCode }}</strong>
|
|
<span>{{ bundle.description || bundle.bundleCode }}</span>
|
|
</div>
|
|
</el-option>
|
|
</el-select>
|
|
</el-form-item>
|
|
```
|
|
|
|
Add scoped style if the file already has style blocks:
|
|
|
|
```scss
|
|
.code-template-option {
|
|
display: grid;
|
|
gap: 2px;
|
|
line-height: 1.35;
|
|
}
|
|
|
|
.code-template-option strong {
|
|
color: #303133;
|
|
font-size: 13px;
|
|
}
|
|
|
|
.code-template-option span {
|
|
color: #909399;
|
|
font-size: 12px;
|
|
}
|
|
```
|
|
|
|
- [ ] **Step 7: Run frontend test**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
cd easycode-web
|
|
node --test src/views/generateView.test.mjs
|
|
```
|
|
|
|
Expected: PASS.
|
|
|
|
- [ ] **Step 8: Commit**
|
|
|
|
```bash
|
|
git add easycode-web/src/api/project.js \
|
|
easycode-web/src/views/GenerateView.vue \
|
|
easycode-web/src/views/generateView.test.mjs
|
|
git commit -m "feat: choose code template bundle in generator"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 7: Make Preview Tabs Dynamic
|
|
|
|
**Files:**
|
|
- Modify: `easycode-web/src/views/PreviewView.vue`
|
|
- Modify: `easycode-web/src/views/previewView.test.mjs`
|
|
|
|
- [ ] **Step 1: Write failing preview source test**
|
|
|
|
Add to `previewView.test.mjs`:
|
|
|
|
```javascript
|
|
test('preview page loads template types from backend', () => {
|
|
const source = readView('PreviewView.vue')
|
|
const apiSource = readSource('api', 'project.js')
|
|
|
|
assert.match(apiSource, /getProjectSupportedTemplateTypes/)
|
|
assert.match(source, /getProjectSupportedTemplateTypes/)
|
|
assert.match(source, /const supportedTemplateTypes = ref\(\[\]\)/)
|
|
assert.match(source, /function normalizeTemplateTypes/)
|
|
assert.match(source, /await getProjectSupportedTemplateTypes\(projectId\.value\)/)
|
|
assert.doesNotMatch(source, /const types = \[\{ label: '.*', value: 'backend' \}\]/)
|
|
})
|
|
```
|
|
|
|
- [ ] **Step 2: Run test to verify it fails**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
cd easycode-web
|
|
node --test src/views/previewView.test.mjs
|
|
```
|
|
|
|
Expected: FAIL because preview tabs are still locally fixed.
|
|
|
|
- [ ] **Step 3: Import backend API and add state**
|
|
|
|
In `PreviewView.vue`, add `getProjectSupportedTemplateTypes` to the existing API import:
|
|
|
|
```javascript
|
|
import { downloadProject, getFileContent, getProject, getProjectStructure, getProjectSupportedTemplateTypes, getRunPreviewStatus, startRunPreview, stopRunPreview } from '@/api/project'
|
|
```
|
|
|
|
Add:
|
|
|
|
```javascript
|
|
const supportedTemplateTypes = ref([])
|
|
```
|
|
|
|
Replace fixed `templateTypes` computed with:
|
|
|
|
```javascript
|
|
const TEMPLATE_TYPE_LABELS = {
|
|
backend: '后端',
|
|
frontend: '前台前端',
|
|
admin_frontend: '后台前端',
|
|
sql: 'SQL'
|
|
}
|
|
|
|
const templateTypes = computed(() => normalizeTemplateTypes(supportedTemplateTypes.value))
|
|
|
|
function normalizeTemplateTypes(types) {
|
|
const values = Array.isArray(types) && types.length ? types : ['backend', 'frontend', 'admin_frontend']
|
|
return values
|
|
.filter((value) => value !== 'frontend' || projectConfig.value.frontendEnabled !== '0')
|
|
.map((value) => ({
|
|
label: TEMPLATE_TYPE_LABELS[value] || value,
|
|
value
|
|
}))
|
|
}
|
|
```
|
|
|
|
- [ ] **Step 4: Load supported types with project config**
|
|
|
|
In `loadProjectConfig`, after setting `projectConfig.value`, add:
|
|
|
|
```javascript
|
|
const supportedResult = await getProjectSupportedTemplateTypes(projectId.value)
|
|
supportedTemplateTypes.value = unwrap(supportedResult) || []
|
|
```
|
|
|
|
Replace hard reset:
|
|
|
|
```javascript
|
|
structures.backend = []
|
|
structures.frontend = []
|
|
structures.admin_frontend = []
|
|
```
|
|
|
|
with:
|
|
|
|
```javascript
|
|
Object.keys(structures).forEach((key) => {
|
|
structures[key] = []
|
|
})
|
|
templateTypes.value.forEach((type) => {
|
|
if (!Object.prototype.hasOwnProperty.call(structures, type.value)) {
|
|
structures[type.value] = []
|
|
}
|
|
})
|
|
```
|
|
|
|
Initialize `structures` with known defaults and allow dynamic keys:
|
|
|
|
```javascript
|
|
const structures = reactive({
|
|
backend: [],
|
|
frontend: [],
|
|
admin_frontend: [],
|
|
sql: []
|
|
})
|
|
```
|
|
|
|
- [ ] **Step 5: Keep active tab valid**
|
|
|
|
Keep existing active-type correction, but make sure it runs after supported types are loaded:
|
|
|
|
```javascript
|
|
if (!templateTypes.value.some((type) => type.value === activeType.value)) {
|
|
activeType.value = templateTypes.value[0]?.value || 'backend'
|
|
}
|
|
```
|
|
|
|
- [ ] **Step 6: Run frontend preview tests**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
cd easycode-web
|
|
node --test src/views/previewView.test.mjs
|
|
```
|
|
|
|
Expected: PASS.
|
|
|
|
- [ ] **Step 7: Commit**
|
|
|
|
```bash
|
|
git add easycode-web/src/views/PreviewView.vue easycode-web/src/views/previewView.test.mjs
|
|
git commit -m "feat: load preview template types dynamically"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 8: Full Verification
|
|
|
|
**Files:**
|
|
- No new source files.
|
|
- Use prior task files only if failures reveal missed integration points.
|
|
|
|
- [ ] **Step 1: Run backend focused tests**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
mvn -pl ruoyi-generator -Dtest=TemplateBundleServiceImplTest,FrontProjectServiceImplTest,GenProjectServiceImplTest,FrontProjectPreviewServiceImplTest,OneClickProjectGenerationServiceImplTest,QingTemplateSupportTest test
|
|
```
|
|
|
|
Expected: BUILD SUCCESS.
|
|
|
|
- [ ] **Step 2: Run frontend focused tests**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
cd easycode-web
|
|
node --test src/views/generateView.test.mjs src/views/previewView.test.mjs
|
|
```
|
|
|
|
Expected: both test files pass.
|
|
|
|
- [ ] **Step 3: Run SQL text checks**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
rg -n "sys_template_bundle|bundle_code|code_template" sql ruoyi-generator/src/main/resources/mapper
|
|
```
|
|
|
|
Expected:
|
|
|
|
- `sql/db.sql` contains table and columns.
|
|
- `sql/code_template_bundles.sql` contains table and qing seed.
|
|
- `sql/qing_templates.sql` contains qing `bundle_code`.
|
|
- `TemplateMapper.xml` maps `bundle_code`.
|
|
- `FrontProjectMapper.xml` maps `code_template`.
|
|
|
|
- [ ] **Step 4: Run git diff review**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
git diff --stat
|
|
git diff --check
|
|
```
|
|
|
|
Expected: no whitespace errors. Stat should show only files listed in this plan plus already existing unrelated worktree changes that were not staged by these tasks.
|
|
|
|
- [ ] **Step 5: Commit verification fixes if any**
|
|
|
|
If Step 1 through Step 4 required small fixes, commit only those files:
|
|
|
|
```bash
|
|
git add <files-fixed-during-verification>
|
|
git commit -m "test: verify code template bundle integration"
|
|
```
|
|
|
|
If no fixes were needed, do not create an empty commit.
|
|
|
|
---
|
|
|
|
## Self-Review
|
|
|
|
- Spec coverage: The plan covers bundle metadata, qing defaulting, project-level selection, generation template resolution, preview/download supported types, SQL migration, frontend create form, preview tabs, and verification.
|
|
- Red-flag scan: No task leaves an unnamed implementation step. Each code-changing step names concrete files and snippets.
|
|
- Type consistency: The selected project field is consistently named `codeTemplate` in Java and JavaScript, persisted as `code_template`, and the template metadata field is consistently named `bundleCode` / `bundle_code`.
|