Prechádzať zdrojové kódy

初步添加服务分类

Huanyi 2 týždňov pred
rodič
commit
8bccc5ce00
21 zmenil súbory, kde vykonal 2184 pridanie a 567 odobranie
  1. 2 33
      pom.xml
  2. 7 1
      ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/CacheNames.java
  3. 6 6
      ruoyi-modules/ruoyi-gen/src/main/resources/vm/java/controller.java.vm
  4. 6 6
      ruoyi-modules/ruoyi-gen/src/main/resources/vm/sql/sql.vm
  5. 499 0
      ruoyi-modules/ruoyi-gen/src/main/resources/vm/vue-bak/index-tree.vue.vm
  6. 459 0
      ruoyi-modules/ruoyi-gen/src/main/resources/vm/vue-bak/index.vue.vm
  7. 560 491
      ruoyi-modules/ruoyi-gen/src/main/resources/vm/vue/index-tree.vue.vm
  8. 95 29
      ruoyi-modules/ruoyi-gen/src/main/resources/vm/vue/index.vue.vm
  9. 105 0
      ruoyi-modules/yingpaipay-service/src/main/java/org/dromara/service/controller/SysServiceClassificationController.java
  10. 6 0
      ruoyi-modules/yingpaipay-service/src/main/java/org/dromara/service/domain/SysService.java
  11. 46 0
      ruoyi-modules/yingpaipay-service/src/main/java/org/dromara/service/domain/SysServiceClassification.java
  12. 7 0
      ruoyi-modules/yingpaipay-service/src/main/java/org/dromara/service/domain/bo/SysServiceBo.java
  13. 47 0
      ruoyi-modules/yingpaipay-service/src/main/java/org/dromara/service/domain/bo/SysServiceClassificationBo.java
  14. 58 0
      ruoyi-modules/yingpaipay-service/src/main/java/org/dromara/service/domain/vo/SysServiceClassificationVo.java
  15. 6 0
      ruoyi-modules/yingpaipay-service/src/main/java/org/dromara/service/domain/vo/SysServiceVo.java
  16. 15 0
      ruoyi-modules/yingpaipay-service/src/main/java/org/dromara/service/mapper/SysServiceClassificationMapper.java
  17. 71 0
      ruoyi-modules/yingpaipay-service/src/main/java/org/dromara/service/service/ISysServiceClassificationService.java
  18. 146 0
      ruoyi-modules/yingpaipay-service/src/main/java/org/dromara/service/service/impl/SysServiceClassificationServiceImpl.java
  19. 5 1
      ruoyi-modules/yingpaipay-service/src/main/java/org/dromara/service/service/impl/SysServiceServiceImpl.java
  20. 12 0
      script/sql/business/v2/create.sql
  21. 26 0
      script/sql/business/v2/update.sql

+ 2 - 33
pom.xml

@@ -67,7 +67,7 @@
         <profile>
             <id>Huanyi</id>
             <properties>
-                <profiles.active>dev</profiles.active>
+                <profiles.active>prod</profiles.active>
                 <nacos.server>127.0.0.1:8848</nacos.server>
                 <nacos.discovery.group>DEFAULT_GROUP</nacos.discovery.group>
                 <nacos.config.group>DEFAULT_GROUP</nacos.config.group>
@@ -78,38 +78,8 @@
                 <nacos.ip>127.0.0.1</nacos.ip>
             </properties>
         </profile>
-<!--        <profile>-->
-<!--            <id>Huanyi-Company</id>-->
-<!--            <properties>-->
-<!--                &lt;!&ndash; 环境标识,需要与配置文件的名称相对应 &ndash;&gt;-->
-<!--                <profiles.active>dev</profiles.active>-->
-<!--                <nacos.server>192.168.1.118:8848</nacos.server>-->
-<!--                <nacos.discovery.group>DEFAULT_GROUP</nacos.discovery.group>-->
-<!--                <nacos.config.group>DEFAULT_GROUP</nacos.config.group>-->
-<!--                <nacos.username>nacos</nacos.username>-->
-<!--                <nacos.password>nacos</nacos.password>-->
-<!--                <logstash.address>127.0.0.1:4560</logstash.address>-->
-<!--                <discovery.ip>192.168.1.118</discovery.ip>-->
-<!--                <nacos.ip>192.168.1.118</nacos.ip>-->
-<!--            </properties>-->
-<!--        </profile>-->
-        <profile>
-            <id>Steelwei</id>
-            <properties>
-                <!-- 环境标识,需要与配置文件的名称相对应 -->
-                <profiles.active>test</profiles.active>
-                <nacos.server>127.0.0.1:8848</nacos.server>
-                <nacos.discovery.group>DEFAULT_GROUP</nacos.discovery.group>
-                <nacos.config.group>DEFAULT_GROUP</nacos.config.group>
-                <nacos.username>nacos</nacos.username>
-                <nacos.password>nacos</nacos.password>
-                <logstash.address>127.0.0.1:4560</logstash.address>
-                <discovery.ip>127.0.0.1</discovery.ip>
-                <mysql.password>1234</mysql.password>
-            </properties>
-        </profile>
         <profile>
-            <id>prod</id>
+            <id>Production</id>
             <properties>
                 <profiles.active>prod</profiles.active>
                 <nacos.server>127.0.0.1:8848</nacos.server>
@@ -119,7 +89,6 @@
                 <nacos.password>nacos</nacos.password>
                 <logstash.address>127.0.0.1:4560</logstash.address>
                 <discovery.ip>127.0.0.1</discovery.ip>
-                <mysql.password>Yr7777777</mysql.password>
                 <nacos.ip>127.0.0.1</nacos.ip>
             </properties>
         </profile>

+ 7 - 1
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/CacheNames.java

@@ -176,9 +176,15 @@ public interface CacheNames {
      */
     String SYS_ORDER_SETTING = "sys_order_setting#30d";
 
-    /***
+    /**
      * 客服配置
      * @Author: Huanyi
      */
     String SYS_CUSTOMER_SERVICE_SETTING = "sys_customer_service_setting#30d";
+
+    /**
+     * 服务类型分类
+     * @Author: Huanyi
+     */
+    String SYS_SERVICE_CLASSIFICATION = "sys_service_classification#30d";
 }

+ 6 - 6
ruoyi-modules/ruoyi-gen/src/main/resources/vm/java/controller.java.vm

@@ -43,7 +43,7 @@ public class ${ClassName}Controller extends BaseController {
     /**
      * 查询${functionName}列表
      */
-    @SaCheckPermission("${permissionPrefix}:list")
+    // @SaCheckPermission("${permissionPrefix}:list")
     @GetMapping("/list")
 #if($table.crud)
     public TableDataInfo<${ClassName}Vo> list(${ClassName}Bo bo, PageQuery pageQuery) {
@@ -59,7 +59,7 @@ public class ${ClassName}Controller extends BaseController {
     /**
      * 导出${functionName}列表
      */
-    @SaCheckPermission("${permissionPrefix}:export")
+    // @SaCheckPermission("${permissionPrefix}:export")
     @Log(title = "${functionName}", businessType = BusinessType.EXPORT)
     @PostMapping("/export")
     public void export(${ClassName}Bo bo, HttpServletResponse response) {
@@ -72,7 +72,7 @@ public class ${ClassName}Controller extends BaseController {
      *
      * @param ${pkColumn.javaField} 主键
      */
-    @SaCheckPermission("${permissionPrefix}:query")
+    // @SaCheckPermission("${permissionPrefix}:query")
     @GetMapping("/{${pkColumn.javaField}}")
     public R<${ClassName}Vo> getInfo(@NotNull(message = "主键不能为空")
                                      @PathVariable("${pkColumn.javaField}") ${pkColumn.javaType} ${pkColumn.javaField}) {
@@ -82,7 +82,7 @@ public class ${ClassName}Controller extends BaseController {
     /**
      * 新增${functionName}
      */
-    @SaCheckPermission("${permissionPrefix}:add")
+    // @SaCheckPermission("${permissionPrefix}:add")
     @Log(title = "${functionName}", businessType = BusinessType.INSERT)
     @RepeatSubmit()
     @PostMapping()
@@ -93,7 +93,7 @@ public class ${ClassName}Controller extends BaseController {
     /**
      * 修改${functionName}
      */
-    @SaCheckPermission("${permissionPrefix}:edit")
+    // @SaCheckPermission("${permissionPrefix}:edit")
     @Log(title = "${functionName}", businessType = BusinessType.UPDATE)
     @RepeatSubmit()
     @PutMapping()
@@ -106,7 +106,7 @@ public class ${ClassName}Controller extends BaseController {
      *
      * @param ${pkColumn.javaField}s 主键串
      */
-    @SaCheckPermission("${permissionPrefix}:remove")
+    // @SaCheckPermission("${permissionPrefix}:remove")
     @Log(title = "${functionName}", businessType = BusinessType.DELETE)
     @DeleteMapping("/{${pkColumn.javaField}s}")
     public R<Void> remove(@NotEmpty(message = "主键不能为空")

+ 6 - 6
ruoyi-modules/ruoyi-gen/src/main/resources/vm/sql/sql.vm

@@ -1,19 +1,19 @@
 -- 菜单 SQL
-insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark, platform_id)
+insert into `pet_system`.`sys_menu` (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark, platform_id)
 values(${table.menuIds[0]}, '${functionName}', '${parentMenuId}', '1', '${businessName}', '${moduleName}/${businessName}/index', 1, 0, 'C', '0', '0', '${permissionPrefix}:list', '#', 103, 1, sysdate(), null, null, '${functionName}菜单', ${platformId});
 
 -- 按钮 SQL
-insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark, platform_id)
+insert into `pet_system`.`sys_menu` (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark, platform_id)
 values(${table.menuIds[1]}, '${functionName}查询', ${table.menuIds[0]}, '1',  '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:query',        '#', 103, 1, sysdate(), null, null, '', ${platformId});
 
-insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark, platform_id)
+insert into `pet_system`.`sys_menu` (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark, platform_id)
 values(${table.menuIds[2]}, '${functionName}新增', ${table.menuIds[0]}, '2',  '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:add',          '#', 103, 1, sysdate(), null, null, '', ${platformId});
 
-insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark, platform_id)
+insert into `pet_system`.`sys_menu` (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark, platform_id)
 values(${table.menuIds[3]}, '${functionName}修改', ${table.menuIds[0]}, '3',  '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:edit',         '#', 103, 1, sysdate(), null, null, '', ${platformId});
 
-insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark, platform_id)
+insert into `pet_system`.`sys_menu` (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark, platform_id)
 values(${table.menuIds[4]}, '${functionName}删除', ${table.menuIds[0]}, '4',  '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:remove',       '#', 103, 1, sysdate(), null, null, '', ${platformId});
 
-insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark, platform_id)
+insert into `pet_system`.`sys_menu` (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark, platform_id)
 values(${table.menuIds[5]}, '${functionName}导出', ${table.menuIds[0]}, '5',  '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:export',       '#', 103, 1, sysdate(), null, null, '', ${platformId});

+ 499 - 0
ruoyi-modules/ruoyi-gen/src/main/resources/vm/vue-bak/index-tree.vue.vm

@@ -0,0 +1,499 @@
+<template>
+  <div class="p-2">
+    <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
+      <div v-show="showSearch" class="mb-[10px]">
+        <el-card shadow="hover">
+          <el-form ref="queryFormRef" :model="queryParams" :inline="true">
+#foreach($column in $columns)
+#if($column.query)
+#set($dictType=$column.dictType)
+#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+#set($parentheseIndex=$column.columnComment.indexOf("("))
+#if($parentheseIndex != -1)
+#set($comment=$column.columnComment.substring(0, $parentheseIndex))
+#else
+#set($comment=$column.columnComment)
+#end
+#if($column.htmlType == "input" || $column.htmlType == "textarea")
+            <el-form-item label="${comment}" prop="${column.javaField}">
+              <el-input v-model="queryParams.${column.javaField}" placeholder="请输入${comment}" clearable @keyup.enter="handleQuery" />
+            </el-form-item>
+#elseif(($column.htmlType == "select" || $column.htmlType == "radio") && "" != $dictType)
+            <el-form-item label="${comment}" prop="${column.javaField}">
+              <el-select v-model="queryParams.${column.javaField}" placeholder="请选择${comment}" clearable>
+                <el-option v-for="dict in ${dictType}" :key="dict.value" :label="dict.label" :value="dict.value"/>
+              </el-select>
+            </el-form-item>
+#elseif(($column.htmlType == "select" || $column.htmlType == "radio") && $dictType)
+            <el-form-item label="${comment}" prop="${column.javaField}">
+              <el-select v-model="queryParams.${column.javaField}" placeholder="请选择${comment}" clearable>
+                <el-option label="请选择字典生成" value="" />
+              </el-select>
+            </el-form-item>
+#elseif($column.htmlType == "datetime" && $column.queryType != "BETWEEN")
+            <el-form-item label="${comment}" prop="${column.javaField}">
+              <el-date-picker clearable
+                v-model="queryParams.${column.javaField}"
+                type="date"
+                value-format="YYYY-MM-DD"
+                placeholder="选择${comment}"
+              />
+            </el-form-item>
+#elseif($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
+            <el-form-item label="${comment}" style="width: 308px">
+              <el-date-picker
+                v-model="dateRange${AttrName}"
+                value-format="YYYY-MM-DD HH:mm:ss"
+                type="daterange"
+                range-separator="-"
+                start-placeholder="开始日期"
+                end-placeholder="结束日期"
+                :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
+              />
+            </el-form-item>
+#end
+#end
+#end
+            <el-form-item>
+              <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
+              <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+            </el-form-item>
+          </el-form>
+        </el-card>
+      </div>
+    </transition>
+
+    <el-card shadow="never">
+      <template #header>
+        <el-row :gutter="10" class="mb8">
+          <el-col :span="1.5">
+            <el-button type="primary" plain icon="Plus" @click="handleAdd()" v-hasPermi="['${moduleName}:${businessName}:add']">新增</el-button>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="info" plain icon="Sort" @click="handleToggleExpandAll">展开/折叠</el-button>
+          </el-col>
+          <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
+        </el-row>
+      </template>
+      <el-table
+        ref="${businessName}TableRef"
+        v-loading="loading"
+        :data="${businessName}List"
+        row-key="${treeCode}"
+        border
+        :default-expand-all="isExpandAll"
+        :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
+      >
+#foreach($column in $columns)
+#set($javaField=$column.javaField)
+#set($parentheseIndex=$column.columnComment.indexOf("("))
+#if($parentheseIndex != -1)
+#set($comment=$column.columnComment.substring(0, $parentheseIndex))
+#else
+#set($comment=$column.columnComment)
+#end
+#if($column.pk)
+#elseif($column.list && $column.htmlType == "datetime")
+        <el-table-column label="${comment}" align="center" prop="${javaField}" width="180">
+          <template #default="scope">
+            <span>{{ parseTime(scope.row.${javaField}, '{y}-{m}-{d}') }}</span>
+          </template>
+        </el-table-column>
+#elseif($column.list && $column.htmlType == "imageUpload")
+        <el-table-column label="${comment}" align="center" prop="${javaField}Url" width="100">
+          <template #default="scope">
+            <image-preview :src="scope.row.${javaField}Url" :width="50" :height="50"/>
+          </template>
+        </el-table-column>
+#elseif($column.list && $column.dictType && "" != $column.dictType)
+        <el-table-column label="${comment}" align="center" prop="${javaField}">
+          <template #default="scope">
+#if($column.htmlType == "checkbox")
+            <dict-tag :options="${column.dictType}" :value="scope.row.${javaField} ? scope.row.${javaField}.split(',') : []"/>
+#else
+            <dict-tag :options="${column.dictType}" :value="scope.row.${javaField}"/>
+#end
+          </template>
+        </el-table-column>
+#elseif($column.list && "" != $javaField)
+#if(${foreach.index} == 1)
+        <el-table-column label="${comment}" prop="${javaField}" />
+#else
+        <el-table-column label="${comment}" align="center" prop="${javaField}" />
+#end
+#end
+#end
+        <el-table-column label="操作" align="center" fixed="right" class-name="small-padding fixed-width">
+          <template #default="scope">
+            <el-tooltip content="修改" placement="top">
+              <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['${moduleName}:${businessName}:edit']" />
+            </el-tooltip>
+            <el-tooltip content="新增" placement="top">
+              <el-button link type="primary" icon="Plus" @click="handleAdd(scope.row)" v-hasPermi="['${moduleName}:${businessName}:add']" />
+            </el-tooltip>
+            <el-tooltip content="删除" placement="top">
+              <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['${moduleName}:${businessName}:remove']" />
+            </el-tooltip>
+          </template>
+        </el-table-column>
+      </el-table>
+    </el-card>
+    <!-- 添加或修改${functionName}对话框 -->
+    <el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
+      <el-form ref="${businessName}FormRef" :model="form" :rules="rules" label-width="80px">
+#foreach($column in $columns)
+#set($field=$column.javaField)
+#if(($column.insert || $column.edit) && !$column.pk)
+#set($parentheseIndex=$column.columnComment.indexOf("("))
+#if($parentheseIndex != -1)
+#set($comment=$column.columnComment.substring(0, $parentheseIndex))
+#else
+#set($comment=$column.columnComment)
+#end
+#set($dictType=$column.dictType)
+#if("" != $treeParentCode && $column.javaField == $treeParentCode)
+        <el-form-item label="${comment}" prop="${treeParentCode}">
+          <el-tree-select
+            v-model="form.${treeParentCode}"
+            :data="${businessName}Options"
+            :props="{ value: '${treeCode}', label: '${treeName}', children: 'children' }"
+            value-key="${treeCode}"
+            placeholder="请选择${comment}"
+            check-strictly
+          />
+        </el-form-item>
+#elseif($column.htmlType == "input")
+        <el-form-item label="${comment}" prop="${field}">
+          <el-input v-model="form.${field}" placeholder="请输入${comment}" />
+        </el-form-item>
+#elseif($column.htmlType == "imageUpload")
+        <el-form-item label="${comment}" prop="${field}">
+          <image-upload v-model="form.${field}"/>
+        </el-form-item>
+#elseif($column.htmlType == "fileUpload")
+        <el-form-item label="${comment}" prop="${field}">
+          <file-upload v-model="form.${field}"/>
+        </el-form-item>
+#elseif($column.htmlType == "editor")
+        <el-form-item label="${comment}">
+          <editor v-model="form.${field}" :min-height="192"/>
+        </el-form-item>
+#elseif($column.htmlType == "select" && "" != $dictType)
+        <el-form-item label="${comment}" prop="${field}">
+          <el-select v-model="form.${field}" placeholder="请选择${comment}">
+            <el-option
+              v-for="dict in ${dictType}"
+              :key="dict.value"
+              :label="dict.label"
+#if($column.javaType == "Integer" || $column.javaType == "Long")
+              :value="parseInt(dict.value)"
+#else
+              :value="dict.value"
+#end
+            ></el-option>
+          </el-select>
+        </el-form-item>
+#elseif($column.htmlType == "select" && $dictType)
+        <el-form-item label="${comment}" prop="${field}">
+          <el-select v-model="form.${field}" placeholder="请选择${comment}">
+            <el-option label="请选择字典生成" value="" />
+          </el-select>
+        </el-form-item>
+#elseif($column.htmlType == "checkbox" && "" != $dictType)
+        <el-form-item label="${comment}" prop="${field}">
+          <el-checkbox-group v-model="form.${field}">
+            <el-checkbox
+              v-for="dict in ${dictType}"
+              :key="dict.value"
+              :label="dict.value">
+              {{dict.label}}
+            </el-checkbox>
+          </el-checkbox-group>
+        </el-form-item>
+#elseif($column.htmlType == "checkbox" && $dictType)
+        <el-form-item label="${comment}" prop="${field}">
+          <el-checkbox-group v-model="form.${field}">
+            <el-checkbox>请选择字典生成</el-checkbox>
+          </el-checkbox-group>
+        </el-form-item>
+#elseif($column.htmlType == "radio" && "" != $dictType)
+        <el-form-item label="${comment}" prop="${field}">
+          <el-radio-group v-model="form.${field}">
+            <el-radio
+              v-for="dict in ${dictType}"
+              :key="dict.value"
+#if($column.javaType == "Integer" || $column.javaType == "Long")
+              :value="parseInt(dict.value)"
+#else
+              :value="dict.value"
+#end
+            >{{dict.label}}</el-radio>
+          </el-radio-group>
+        </el-form-item>
+#elseif($column.htmlType == "radio" && $dictType)
+        <el-form-item label="${comment}" prop="${field}">
+          <el-radio-group v-model="form.${field}">
+            <el-radio value="1">请选择字典生成</el-radio>
+          </el-radio-group>
+        </el-form-item>
+#elseif($column.htmlType == "datetime")
+        <el-form-item label="${comment}" prop="${field}">
+          <el-date-picker clearable
+            v-model="form.${field}"
+            type="datetime"
+            value-format="YYYY-MM-DD HH:mm:ss"
+            placeholder="选择${comment}"
+          />
+        </el-form-item>
+#elseif($column.htmlType == "textarea")
+        <el-form-item label="${comment}" prop="${field}">
+          <el-input v-model="form.${field}" type="textarea" placeholder="请输入内容" />
+        </el-form-item>
+#end
+#end
+#end
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>
+          <el-button @click="cancel">取 消</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup name="${BusinessName}" lang="ts">
+import { list${BusinessName}, get${BusinessName}, del${BusinessName}, add${BusinessName}, update${BusinessName} } from "@/api/${moduleName}/${businessName}";
+import { ${BusinessName}VO, ${BusinessName}Query, ${BusinessName}Form } from '@/api/${moduleName}/${businessName}/types';
+
+type ${BusinessName}Option = {
+  ${treeCode}: number;
+  ${treeName}: string;
+  children?: ${BusinessName}Option[];
+}
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;;
+
+#if(${dicts} != '')
+#set($dictsNoSymbol=$dicts.replace("'", ""))
+const { ${dictsNoSymbol} } = toRefs<any>(proxy?.useDict(${dicts}));
+#end
+
+const ${businessName}List = ref<${BusinessName}VO[]>([]);
+const ${businessName}Options = ref<${BusinessName}Option[]>([]);
+const buttonLoading = ref(false);
+const showSearch = ref(true);
+const isExpandAll = ref(true);
+const loading = ref(false);
+
+const queryFormRef = ref<ElFormInstance>();
+const ${businessName}FormRef = ref<ElFormInstance>();
+const ${businessName}TableRef = ref<ElTableInstance>()
+
+const dialog = reactive<DialogOption>({
+    visible: false,
+    title: ''
+});
+
+#foreach ($column in $columns)
+#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
+#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+const dateRange${AttrName} = ref<[DateModelType, DateModelType]>(['', '']);
+#end
+#end
+
+const initFormData: ${BusinessName}Form = {
+#foreach ($column in $columns)
+#if($column.insert || $column.edit)
+#if($column.htmlType == "checkbox")
+    $column.javaField: []#if($foreach.count != $columns.size()),#end
+#else
+    $column.javaField: undefined#if($foreach.count != $columns.size()),#end
+#end
+#end
+#end
+}
+
+const data = reactive<PageData<${BusinessName}Form, ${BusinessName}Query>>({
+  form: {...initFormData},
+  queryParams: {
+#foreach ($column in $columns)
+#if($column.query)
+#if($column.htmlType != "datetime" || $column.queryType != "BETWEEN")
+    $column.javaField: undefined,
+#end
+#end
+#end
+    params: {
+#foreach ($column in $columns)
+#if($column.query)
+#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
+      $column.javaField: undefined#if($foreach.count != $columns.size()),#end
+#end
+#end
+#end
+    }
+  },
+  rules: {
+#foreach ($column in $columns)
+#if($column.insert || $column.edit)
+#if($column.required)
+#set($parentheseIndex=$column.columnComment.indexOf("("))
+#if($parentheseIndex != -1)
+#set($comment=$column.columnComment.substring(0, $parentheseIndex))
+#else
+#set($comment=$column.columnComment)
+#end
+    $column.javaField: [
+      { required: true, message: "$comment不能为空", trigger: #if($column.htmlType == "select" || $column.htmlType == "radio")"change"#else"blur"#end }
+    ]#if($foreach.count != $columns.size()),#end
+#end
+#end
+#end
+  }
+});
+
+const { queryParams, form, rules } = toRefs(data);
+
+/** 查询${functionName}列表 */
+const getList = async () => {
+  loading.value = true;
+#foreach ($column in $columns)
+#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
+  queryParams.value.params = {};
+#break
+#end
+#end
+#foreach ($column in $columns)
+#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
+#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+  proxy?.addDateRange(queryParams.value, dateRange${AttrName}.value, '${AttrName}');
+#end
+#end
+  const res = await list${BusinessName}(queryParams.value);
+  const data = proxy?.handleTree<${BusinessName}VO>(res.data, "${treeCode}", "${treeParentCode}");
+  if (data) {
+    ${businessName}List.value = data;
+    loading.value = false;
+  }
+}
+
+/** 查询${functionName}下拉树结构 */
+const getTreeselect = async () => {
+  const res = await list${BusinessName}();
+  ${businessName}Options.value = [];
+  const data: ${BusinessName}Option = { ${treeCode}: 0, ${treeName}: '顶级节点', children: [] };
+  data.children = proxy?.handleTree<${BusinessName}Option>(res.data, "${treeCode}", "${treeParentCode}");
+  ${businessName}Options.value.push(data);
+}
+
+// 取消按钮
+const cancel = () => {
+  reset();
+  dialog.visible = false;
+}
+
+// 表单重置
+const reset = () => {
+  form.value = {...initFormData}
+  ${businessName}FormRef.value?.resetFields();
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  getList();
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+#foreach ($column in $columns)
+#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
+#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+  dateRange${AttrName}.value = ['', ''];
+#end
+#end
+  queryFormRef.value?.resetFields();
+  handleQuery();
+}
+
+/** 新增按钮操作 */
+const handleAdd = (row?: ${BusinessName}VO) => {
+  reset();
+  getTreeselect();
+  if (row != null && row.${treeCode}) {
+    form.value.${treeParentCode} = row.${treeCode};
+  } else {
+    form.value.${treeParentCode} = 0;
+  }
+  dialog.visible = true;
+  dialog.title = "添加${functionName}";
+}
+
+/** 展开/折叠操作 */
+const handleToggleExpandAll = () => {
+  isExpandAll.value = !isExpandAll.value;
+  toggleExpandAll(${businessName}List.value, isExpandAll.value)
+}
+
+/** 展开/折叠操作 */
+const toggleExpandAll = (data: ${BusinessName}VO[], status: boolean) => {
+  data.forEach((item) => {
+    ${businessName}TableRef.value?.toggleRowExpansion(item, status)
+    if (item.children && item.children.length > 0) toggleExpandAll(item.children, status)
+  })
+}
+
+/** 修改按钮操作 */
+const handleUpdate = async (row: ${BusinessName}VO) => {
+  reset();
+  await getTreeselect();
+  if (row != null) {
+    form.value.${treeParentCode} = row.${treeParentCode};
+  }
+  const res = await get${BusinessName}(row.${pkColumn.javaField});
+  Object.assign(form.value, res.data);
+#foreach ($column in $columns)
+  #if($column.htmlType == "checkbox")
+  form.value.$column.javaField = form.value.${column.javaField}.split(",");
+  #end
+#end
+  dialog.visible = true;
+  dialog.title = "修改${functionName}";
+}
+
+/** 提交按钮 */
+const submitForm = () => {
+  ${businessName}FormRef.value?.validate(async (valid: boolean) => {
+    if (valid) {
+      buttonLoading.value = true;
+#foreach ($column in $columns)
+#if($column.htmlType == "checkbox")
+      form.value.$column.javaField = form.value.${column.javaField}.join(",");
+#end
+#end
+      if (form.value.${pkColumn.javaField}) {
+        await update${BusinessName}(form.value).finally(() => buttonLoading.value = false);
+      } else {
+        await add${BusinessName}(form.value).finally(() => buttonLoading.value = false);
+      }
+      proxy?.#[[$modal]]#.msgSuccess("操作成功");
+      dialog.visible = false;
+      getList();
+    }
+  });
+}
+
+/** 删除按钮操作 */
+const handleDelete = async (row: ${BusinessName}VO) => {
+  await proxy?.#[[$modal]]#.confirm('是否确认删除${functionName}编号为"' + row.${pkColumn.javaField} + '"的数据项?');
+  loading.value = true;
+  await del${BusinessName}(row.${pkColumn.javaField}).finally(() => loading.value = false);
+  await getList();
+  proxy?.#[[$modal]]#.msgSuccess("删除成功");
+}
+
+onMounted(() => {
+  getList();
+});
+</script>

+ 459 - 0
ruoyi-modules/ruoyi-gen/src/main/resources/vm/vue-bak/index.vue.vm

@@ -0,0 +1,459 @@
+<template>
+  <div class="p-2">
+    <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
+      <div v-show="showSearch" class="mb-[10px]">
+        <el-card shadow="hover">
+          <el-form ref="queryFormRef" :model="queryParams" :inline="true">
+#foreach($column in $columns)
+#if($column.query)
+#set($dictType=$column.dictType)
+#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+#set($parentheseIndex=$column.columnComment.indexOf("("))
+#if($parentheseIndex != -1)
+#set($comment=$column.columnComment.substring(0, $parentheseIndex))
+#else
+#set($comment=$column.columnComment)
+#end
+#if($column.htmlType == "input" || $column.htmlType == "textarea")
+            <el-form-item label="${comment}" prop="${column.javaField}">
+              <el-input v-model="queryParams.${column.javaField}" placeholder="请输入${comment}" clearable @keyup.enter="handleQuery" />
+            </el-form-item>
+#elseif(($column.htmlType == "select" || $column.htmlType == "radio") && "" != $dictType)
+            <el-form-item label="${comment}" prop="${column.javaField}">
+              <el-select v-model="queryParams.${column.javaField}" placeholder="请选择${comment}" clearable >
+                <el-option v-for="dict in ${dictType}" :key="dict.value" :label="dict.label" :value="dict.value"/>
+              </el-select>
+            </el-form-item>
+#elseif(($column.htmlType == "select" || $column.htmlType == "radio") && $dictType)
+            <el-form-item label="${comment}" prop="${column.javaField}">
+              <el-select v-model="queryParams.${column.javaField}" placeholder="请选择${comment}" clearable >
+                <el-option label="请选择字典生成" value="" />
+              </el-select>
+            </el-form-item>
+#elseif($column.htmlType == "datetime" && $column.queryType != "BETWEEN")
+            <el-form-item label="${comment}" prop="${column.javaField}">
+              <el-date-picker clearable
+                v-model="queryParams.${column.javaField}"
+                type="date"
+                value-format="YYYY-MM-DD"
+                placeholder="请选择${comment}"
+              />
+            </el-form-item>
+#elseif($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
+            <el-form-item label="${comment}" style="width: 308px">
+              <el-date-picker
+                v-model="dateRange${AttrName}"
+                value-format="YYYY-MM-DD HH:mm:ss"
+                type="daterange"
+                range-separator="-"
+                start-placeholder="开始日期"
+                end-placeholder="结束日期"
+                :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
+              />
+            </el-form-item>
+#end
+#end
+#end
+            <el-form-item>
+              <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
+              <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+            </el-form-item>
+          </el-form>
+        </el-card>
+      </div>
+    </transition>
+
+    <el-card shadow="never">
+      <template #header>
+        <el-row :gutter="10" class="mb8">
+          <el-col :span="1.5">
+            <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['${moduleName}:${businessName}:add']">新增</el-button>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['${moduleName}:${businessName}:edit']">修改</el-button>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['${moduleName}:${businessName}:remove']">删除</el-button>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['${moduleName}:${businessName}:export']">导出</el-button>
+          </el-col>
+          <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
+        </el-row>
+      </template>
+
+      <el-table v-loading="loading" border :data="${businessName}List" @selection-change="handleSelectionChange">
+        <el-table-column type="selection" width="55" align="center" />
+#foreach($column in $columns)
+#set($javaField=$column.javaField)
+#set($parentheseIndex=$column.columnComment.indexOf("("))
+#if($parentheseIndex != -1)
+#set($comment=$column.columnComment.substring(0, $parentheseIndex))
+#else
+#set($comment=$column.columnComment)
+#end
+#if($column.pk)
+        <el-table-column label="${comment}" align="center" prop="${javaField}" v-if="${column.list}" />
+#elseif($column.list && $column.htmlType == "datetime")
+        <el-table-column label="${comment}" align="center" prop="${javaField}" width="180">
+          <template #default="scope">
+            <span>{{ parseTime(scope.row.${javaField}, '{y}-{m}-{d}') }}</span>
+          </template>
+        </el-table-column>
+#elseif($column.list && $column.htmlType == "imageUpload")
+        <el-table-column label="${comment}" align="center" prop="${javaField}Url" width="100">
+          <template #default="scope">
+            <image-preview :src="scope.row.${javaField}Url" :width="50" :height="50"/>
+          </template>
+        </el-table-column>
+#elseif($column.list && $column.dictType && "" != $column.dictType)
+        <el-table-column label="${comment}" align="center" prop="${javaField}">
+          <template #default="scope">
+#if($column.htmlType == "checkbox")
+            <dict-tag :options="${column.dictType}" :value="scope.row.${javaField} ? scope.row.${javaField}.split(',') : []"/>
+#else
+            <dict-tag :options="${column.dictType}" :value="scope.row.${javaField}"/>
+#end
+          </template>
+        </el-table-column>
+#elseif($column.list && "" != $javaField)
+        <el-table-column label="${comment}" align="center" prop="${javaField}" />
+#end
+#end
+        <el-table-column label="操作" align="center" fixed="right" class-name="small-padding fixed-width">
+          <template #default="scope">
+            <el-tooltip content="修改" placement="top">
+              <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['${moduleName}:${businessName}:edit']"></el-button>
+            </el-tooltip>
+            <el-tooltip content="删除" placement="top">
+              <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['${moduleName}:${businessName}:remove']"></el-button>
+            </el-tooltip>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
+    </el-card>
+    <!-- 添加或修改${functionName}对话框 -->
+    <el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
+      <el-form ref="${businessName}FormRef" :model="form" :rules="rules" label-width="80px">
+#foreach($column in $columns)
+#set($field=$column.javaField)
+#if(($column.insert || $column.edit) && !$column.pk)
+#set($parentheseIndex=$column.columnComment.indexOf("("))
+#if($parentheseIndex != -1)
+#set($comment=$column.columnComment.substring(0, $parentheseIndex))
+#else
+#set($comment=$column.columnComment)
+#end
+#set($dictType=$column.dictType)
+#if($column.htmlType == "input")
+        <el-form-item label="${comment}" prop="${field}">
+          <el-input v-model="form.${field}" placeholder="请输入${comment}" />
+        </el-form-item>
+#elseif($column.htmlType == "imageUpload")
+        <el-form-item label="${comment}" prop="${field}">
+          <image-upload v-model="form.${field}"/>
+        </el-form-item>
+#elseif($column.htmlType == "fileUpload")
+        <el-form-item label="${comment}" prop="${field}">
+          <file-upload v-model="form.${field}"/>
+        </el-form-item>
+#elseif($column.htmlType == "editor")
+        <el-form-item label="${comment}">
+          <editor v-model="form.${field}" :min-height="192"/>
+        </el-form-item>
+#elseif($column.htmlType == "select" && "" != $dictType)
+        <el-form-item label="${comment}" prop="${field}">
+          <el-select v-model="form.${field}" placeholder="请选择${comment}">
+            <el-option
+                v-for="dict in ${dictType}"
+                :key="dict.value"
+                :label="dict.label"
+#if($column.javaType == "Integer" || $column.javaType == "Long")
+                :value="parseInt(dict.value)"
+#else
+                :value="dict.value"
+#end
+            ></el-option>
+          </el-select>
+        </el-form-item>
+#elseif($column.htmlType == "select" && $dictType)
+        <el-form-item label="${comment}" prop="${field}">
+          <el-select v-model="form.${field}" placeholder="请选择${comment}">
+            <el-option label="请选择字典生成" value="" />
+          </el-select>
+        </el-form-item>
+#elseif($column.htmlType == "checkbox" && "" != $dictType)
+        <el-form-item label="${comment}" prop="${field}">
+          <el-checkbox-group v-model="form.${field}">
+            <el-checkbox
+                v-for="dict in ${dictType}"
+                :key="dict.value"
+                :label="dict.value">
+                {{dict.label}}
+            </el-checkbox>
+          </el-checkbox-group>
+        </el-form-item>
+#elseif($column.htmlType == "checkbox" && $dictType)
+        <el-form-item label="${comment}" prop="${field}">
+          <el-checkbox-group v-model="form.${field}">
+            <el-checkbox>请选择字典生成</el-checkbox>
+          </el-checkbox-group>
+        </el-form-item>
+#elseif($column.htmlType == "radio" && "" != $dictType)
+        <el-form-item label="${comment}" prop="${field}">
+          <el-radio-group v-model="form.${field}">
+            <el-radio
+              v-for="dict in ${dictType}"
+              :key="dict.value"
+#if($column.javaType == "Integer" || $column.javaType == "Long")
+              :value="parseInt(dict.value)"
+#else
+              :value="dict.value"
+#end
+            >{{dict.label}}</el-radio>
+          </el-radio-group>
+        </el-form-item>
+#elseif($column.htmlType == "radio" && $dictType)
+        <el-form-item label="${comment}" prop="${field}">
+          <el-radio-group v-model="form.${field}">
+            <el-radio value="1">请选择字典生成</el-radio>
+          </el-radio-group>
+        </el-form-item>
+#elseif($column.htmlType == "datetime")
+        <el-form-item label="${comment}" prop="${field}">
+          <el-date-picker clearable
+            v-model="form.${field}"
+            type="datetime"
+            value-format="YYYY-MM-DD HH:mm:ss"
+            placeholder="请选择${comment}">
+          </el-date-picker>
+        </el-form-item>
+#elseif($column.htmlType == "textarea")
+        <el-form-item label="${comment}" prop="${field}">
+            <el-input v-model="form.${field}" type="textarea" placeholder="请输入内容" />
+        </el-form-item>
+#end
+#end
+#end
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>
+          <el-button @click="cancel">取 消</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup name="${BusinessName}" lang="ts">
+import { list${BusinessName}, get${BusinessName}, del${BusinessName}, add${BusinessName}, update${BusinessName} } from '@/api/${moduleName}/${businessName}';
+import { ${BusinessName}VO, ${BusinessName}Query, ${BusinessName}Form } from '@/api/${moduleName}/${businessName}/types';
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+#if(${dicts} != '')
+#set($dictsNoSymbol=$dicts.replace("'", ""))
+const { ${dictsNoSymbol} } = toRefs<any>(proxy?.useDict(${dicts}));
+#end
+
+const ${businessName}List = ref<${BusinessName}VO[]>([]);
+const buttonLoading = ref(false);
+const loading = ref(true);
+const showSearch = ref(true);
+const ids = ref<Array<string | number>>([]);
+const single = ref(true);
+const multiple = ref(true);
+const total = ref(0);
+#foreach ($column in $columns)
+#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
+#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+const dateRange${AttrName} = ref<[DateModelType, DateModelType]>(['', '']);
+#end
+#end
+
+const queryFormRef = ref<ElFormInstance>();
+const ${businessName}FormRef = ref<ElFormInstance>();
+
+const dialog = reactive<DialogOption>({
+  visible: false,
+  title: ''
+});
+
+const initFormData: ${BusinessName}Form = {
+#foreach ($column in $columns)
+#if($column.insert || $column.edit)
+#if($column.htmlType == "checkbox")
+  $column.javaField: []#if($foreach.count != $columns.size()),#end
+#else
+  $column.javaField: undefined#if($foreach.count != $columns.size()),#end
+#end
+#end
+#end
+}
+const data = reactive<PageData<${BusinessName}Form, ${BusinessName}Query>>({
+  form: {...initFormData},
+  queryParams: {
+    pageNum: 1,
+    pageSize: 10,
+#foreach ($column in $columns)
+#if($column.query)
+#if($column.htmlType != "datetime" || $column.queryType != "BETWEEN")
+    $column.javaField: undefined,
+#end
+#end
+#end
+    params: {
+#foreach ($column in $columns)
+#if($column.query)
+#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
+      $column.javaField: undefined#if($foreach.count != $columns.size()),#end
+#end
+#end
+#end
+    }
+  },
+  rules: {
+#foreach ($column in $columns)
+#if($column.insert || $column.edit)
+#if($column.required)
+#set($parentheseIndex=$column.columnComment.indexOf("("))
+#if($parentheseIndex != -1)
+#set($comment=$column.columnComment.substring(0, $parentheseIndex))
+#else
+#set($comment=$column.columnComment)
+#end
+    $column.javaField: [
+      { required: true, message: "$comment不能为空", trigger: #if($column.htmlType == "select" || $column.htmlType == "radio")"change"#else"blur"#end }
+    ]#if($foreach.count != $columns.size()),#end
+#end
+#end
+#end
+  }
+});
+
+const { queryParams, form, rules } = toRefs(data);
+
+/** 查询${functionName}列表 */
+const getList = async () => {
+  loading.value = true;
+#foreach ($column in $columns)
+#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
+  queryParams.value.params = {};
+#break
+#end
+#end
+#foreach ($column in $columns)
+#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
+#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+  proxy?.addDateRange(queryParams.value, dateRange${AttrName}.value, '${AttrName}');
+#end
+#end
+  const res = await list${BusinessName}(queryParams.value);
+  ${businessName}List.value = res.rows;
+  total.value = res.total;
+  loading.value = false;
+}
+
+/** 取消按钮 */
+const cancel = () => {
+  reset();
+  dialog.visible = false;
+}
+
+/** 表单重置 */
+const reset = () => {
+  form.value = {...initFormData};
+  ${businessName}FormRef.value?.resetFields();
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.value.pageNum = 1;
+  getList();
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+#foreach ($column in $columns)
+#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
+#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+  dateRange${AttrName}.value = ['', ''];
+#end
+#end
+  queryFormRef.value?.resetFields();
+  handleQuery();
+}
+
+/** 多选框选中数据 */
+const handleSelectionChange = (selection: ${BusinessName}VO[]) => {
+  ids.value = selection.map(item => item.${pkColumn.javaField});
+  single.value = selection.length != 1;
+  multiple.value = !selection.length;
+}
+
+/** 新增按钮操作 */
+const handleAdd = () => {
+  reset();
+  dialog.visible = true;
+  dialog.title = "添加${functionName}";
+}
+
+/** 修改按钮操作 */
+const handleUpdate = async (row?: ${BusinessName}VO) => {
+  reset();
+  const _${pkColumn.javaField} = row?.${pkColumn.javaField} || ids.value[0]
+  const res = await get${BusinessName}(_${pkColumn.javaField});
+  Object.assign(form.value, res.data);
+#foreach ($column in $columns)
+  #if($column.htmlType == "checkbox")
+  form.value.$column.javaField = form.value.${column.javaField}.split(",");
+  #end
+#end
+  dialog.visible = true;
+  dialog.title = "修改${functionName}";
+}
+
+/** 提交按钮 */
+const submitForm = () => {
+  ${businessName}FormRef.value?.validate(async (valid: boolean) => {
+    if (valid) {
+      buttonLoading.value = true;
+      #foreach ($column in $columns)
+        #if($column.htmlType == "checkbox")
+        form.value.$column.javaField = form.value.${column.javaField}.join(",");
+        #end
+      #end
+      if (form.value.${pkColumn.javaField}) {
+        await update${BusinessName}(form.value).finally(() =>  buttonLoading.value = false);
+      } else {
+        await add${BusinessName}(form.value).finally(() =>  buttonLoading.value = false);
+      }
+      proxy?.#[[$modal]]#.msgSuccess("操作成功");
+      dialog.visible = false;
+      await getList();
+    }
+  });
+}
+
+/** 删除按钮操作 */
+const handleDelete = async (row?: ${BusinessName}VO) => {
+  const _${pkColumn.javaField}s = row?.${pkColumn.javaField} || ids.value;
+  await proxy?.#[[$modal]]#.confirm('是否确认删除${functionName}编号为"' + _${pkColumn.javaField}s + '"的数据项?').finally(() => loading.value = false);
+  await del${BusinessName}(_${pkColumn.javaField}s);
+  proxy?.#[[$modal]]#.msgSuccess("删除成功");
+  await getList();
+}
+
+/** 导出按钮操作 */
+const handleExport = () => {
+  proxy?.download('${moduleName}/${businessName}/export', {
+    ...queryParams.value
+  }, `${businessName}_#[[${new Date().getTime()}]]#.xlsx`)
+}
+
+onMounted(() => {
+  getList();
+});
+</script>

+ 560 - 491
ruoyi-modules/ruoyi-gen/src/main/resources/vm/vue/index-tree.vue.vm

@@ -1,499 +1,568 @@
 <template>
-  <div class="p-2">
-    <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
-      <div v-show="showSearch" class="mb-[10px]">
-        <el-card shadow="hover">
-          <el-form ref="queryFormRef" :model="queryParams" :inline="true">
-#foreach($column in $columns)
-#if($column.query)
-#set($dictType=$column.dictType)
-#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
-#set($parentheseIndex=$column.columnComment.indexOf("("))
-#if($parentheseIndex != -1)
-#set($comment=$column.columnComment.substring(0, $parentheseIndex))
-#else
-#set($comment=$column.columnComment)
-#end
-#if($column.htmlType == "input" || $column.htmlType == "textarea")
-            <el-form-item label="${comment}" prop="${column.javaField}">
-              <el-input v-model="queryParams.${column.javaField}" placeholder="请输入${comment}" clearable @keyup.enter="handleQuery" />
-            </el-form-item>
-#elseif(($column.htmlType == "select" || $column.htmlType == "radio") && "" != $dictType)
-            <el-form-item label="${comment}" prop="${column.javaField}">
-              <el-select v-model="queryParams.${column.javaField}" placeholder="请选择${comment}" clearable>
-                <el-option v-for="dict in ${dictType}" :key="dict.value" :label="dict.label" :value="dict.value"/>
-              </el-select>
-            </el-form-item>
-#elseif(($column.htmlType == "select" || $column.htmlType == "radio") && $dictType)
-            <el-form-item label="${comment}" prop="${column.javaField}">
-              <el-select v-model="queryParams.${column.javaField}" placeholder="请选择${comment}" clearable>
-                <el-option label="请选择字典生成" value="" />
-              </el-select>
-            </el-form-item>
-#elseif($column.htmlType == "datetime" && $column.queryType != "BETWEEN")
-            <el-form-item label="${comment}" prop="${column.javaField}">
-              <el-date-picker clearable
-                v-model="queryParams.${column.javaField}"
-                type="date"
-                value-format="YYYY-MM-DD"
-                placeholder="选择${comment}"
-              />
-            </el-form-item>
-#elseif($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
-            <el-form-item label="${comment}" style="width: 308px">
-              <el-date-picker
-                v-model="dateRange${AttrName}"
-                value-format="YYYY-MM-DD HH:mm:ss"
-                type="daterange"
-                range-separator="-"
-                start-placeholder="开始日期"
-                end-placeholder="结束日期"
-                :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
-              />
-            </el-form-item>
-#end
-#end
-#end
-            <el-form-item>
-              <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
-              <el-button icon="Refresh" @click="resetQuery">重置</el-button>
-            </el-form-item>
-          </el-form>
+    <div class="gen-container">
+        <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
+            <div v-show="showSearch" class="search-wrapper">
+                <el-card shadow="hover" class="search-card">
+                    <el-form ref="queryFormRef" :model="queryParams" :inline="true">
+                        #foreach($column in $columns)
+                            #if($column.query)
+                                #set($dictType=$column.dictType)
+                                #set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+                                #set($parentheseIndex=$column.columnComment.indexOf("("))
+                                #if($parentheseIndex != -1)
+                                    #set($comment=$column.columnComment.substring(0, $parentheseIndex))
+                                #else
+                                    #set($comment=$column.columnComment)
+                                #end
+                                #if($column.htmlType == "input" || $column.htmlType == "textarea")
+                                    <el-form-item label="${comment}" prop="${column.javaField}">
+                                        <el-input v-model="queryParams.${column.javaField}" placeholder="请输入${comment}" clearable @keyup.enter="handleQuery" />
+                                    </el-form-item>
+                                #elseif(($column.htmlType == "select" || $column.htmlType == "radio") && "" != $dictType)
+                                    <el-form-item label="${comment}" prop="${column.javaField}">
+                                        <el-select v-model="queryParams.${column.javaField}" placeholder="请选择${comment}" clearable>
+                                            <el-option v-for="dict in ${dictType}" :key="dict.value" :label="dict.label" :value="dict.value"/>
+                                        </el-select>
+                                    </el-form-item>
+                                #elseif(($column.htmlType == "select" || $column.htmlType == "radio") && $dictType)
+                                    <el-form-item label="${comment}" prop="${column.javaField}">
+                                        <el-select v-model="queryParams.${column.javaField}" placeholder="请选择${comment}" clearable>
+                                            <el-option label="请选择字典生成" value="" />
+                                        </el-select>
+                                    </el-form-item>
+                                #elseif($column.htmlType == "datetime" && $column.queryType != "BETWEEN")
+                                    <el-form-item label="${comment}" prop="${column.javaField}">
+                                        <el-date-picker clearable
+                                                        v-model="queryParams.${column.javaField}"
+                                                        type="date"
+                                                        value-format="YYYY-MM-DD"
+                                                        placeholder="选择${comment}"
+                                        />
+                                    </el-form-item>
+                                #elseif($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
+                                    <el-form-item label="${comment}" style="width: 308px">
+                                        <el-date-picker
+                                            v-model="dateRange${AttrName}"
+                                            value-format="YYYY-MM-DD HH:mm:ss"
+                                            type="daterange"
+                                            range-separator="-"
+                                            start-placeholder="开始日期"
+                                            end-placeholder="结束日期"
+                                            :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
+                                        />
+                                    </el-form-item>
+                                #end
+                            #end
+                        #end
+                        <el-form-item>
+                            <el-button type="primary" @click="handleQuery">搜索</el-button>
+                            <el-button @click="resetQuery">重置</el-button>
+                        </el-form-item>
+                    </el-form>
+                </el-card>
+            </div>
+        </transition>
+
+        <el-card shadow="hover" class="table-card">
+            <template #header>
+                <div class="card-header">
+                    <div class="header-left">
+                        <span class="header-title">${functionName}列表</span>
+                    </div>
+                    <div class="header-right">
+                        <el-button type="primary" size="small" @click="handleAdd()" v-hasPermi="['${moduleName}:${businessName}:add']">新增</el-button>
+                        <el-button type="info" size="small" @click="handleToggleExpandAll">展开/折叠</el-button>
+                        <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
+                    </div>
+                </div>
+            </template>
+            <el-table
+                ref="${businessName}TableRef"
+                v-loading="loading"
+                :data="${businessName}List"
+                row-key="${treeCode}"
+                stripe
+                :default-expand-all="isExpandAll"
+                :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
+                class="gen-table"
+            >
+                #foreach($column in $columns)
+                    #set($javaField=$column.javaField)
+                    #set($parentheseIndex=$column.columnComment.indexOf("("))
+                    #if($parentheseIndex != -1)
+                        #set($comment=$column.columnComment.substring(0, $parentheseIndex))
+                    #else
+                        #set($comment=$column.columnComment)
+                    #end
+                    #if($column.pk)
+                    #elseif($column.list && $column.htmlType == "datetime")
+                        <el-table-column label="${comment}" align="center" prop="${javaField}" width="180">
+                            <template #default="scope">
+                                <span>{{ parseTime(scope.row.${javaField}, '{y}-{m}-{d}') }}</span>
+                            </template>
+                        </el-table-column>
+                    #elseif($column.list && $column.htmlType == "imageUpload")
+                        <el-table-column label="${comment}" align="center" prop="${javaField}Url" width="100">
+                            <template #default="scope">
+                                <image-preview :src="scope.row.${javaField}Url" :width="50" :height="50"/>
+                            </template>
+                        </el-table-column>
+                    #elseif($column.list && $column.dictType && "" != $column.dictType)
+                        <el-table-column label="${comment}" align="center" prop="${javaField}">
+                            <template #default="scope">
+                                #if($column.htmlType == "checkbox")
+                                    <dict-tag :options="${column.dictType}" :value="scope.row.${javaField} ? scope.row.${javaField}.split(',') : []"/>
+                                #else
+                                    <dict-tag :options="${column.dictType}" :value="scope.row.${javaField}"/>
+                                #end
+                            </template>
+                        </el-table-column>
+                    #elseif($column.list && "" != $javaField)
+                        #if(${foreach.index} == 1)
+                            <el-table-column label="${comment}" prop="${javaField}" />
+                        #else
+                            <el-table-column label="${comment}" align="center" prop="${javaField}" />
+                        #end
+                    #end
+                #end
+                <el-table-column label="操作" align="center" width="200" fixed="right">
+                    <template #default="scope">
+                        <el-button link type="primary" size="small" @click="handleUpdate(scope.row)" v-hasPermi="['${moduleName}:${businessName}:edit']">编辑</el-button>
+                        <el-button link type="primary" size="small" @click="handleAdd(scope.row)" v-hasPermi="['${moduleName}:${businessName}:add']">新增</el-button>
+                        <el-button link type="danger" size="small" @click="handleDelete(scope.row)" v-hasPermi="['${moduleName}:${businessName}:remove']">删除</el-button>
+                    </template>
+                </el-table-column>
+            </el-table>
         </el-card>
-      </div>
-    </transition>
-
-    <el-card shadow="never">
-      <template #header>
-        <el-row :gutter="10" class="mb8">
-          <el-col :span="1.5">
-            <el-button type="primary" plain icon="Plus" @click="handleAdd()" v-hasPermi="['${moduleName}:${businessName}:add']">新增</el-button>
-          </el-col>
-          <el-col :span="1.5">
-            <el-button type="info" plain icon="Sort" @click="handleToggleExpandAll">展开/折叠</el-button>
-          </el-col>
-          <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
-        </el-row>
-      </template>
-      <el-table
-        ref="${businessName}TableRef"
-        v-loading="loading"
-        :data="${businessName}List"
-        row-key="${treeCode}"
-        border
-        :default-expand-all="isExpandAll"
-        :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
-      >
-#foreach($column in $columns)
-#set($javaField=$column.javaField)
-#set($parentheseIndex=$column.columnComment.indexOf("("))
-#if($parentheseIndex != -1)
-#set($comment=$column.columnComment.substring(0, $parentheseIndex))
-#else
-#set($comment=$column.columnComment)
-#end
-#if($column.pk)
-#elseif($column.list && $column.htmlType == "datetime")
-        <el-table-column label="${comment}" align="center" prop="${javaField}" width="180">
-          <template #default="scope">
-            <span>{{ parseTime(scope.row.${javaField}, '{y}-{m}-{d}') }}</span>
-          </template>
-        </el-table-column>
-#elseif($column.list && $column.htmlType == "imageUpload")
-        <el-table-column label="${comment}" align="center" prop="${javaField}Url" width="100">
-          <template #default="scope">
-            <image-preview :src="scope.row.${javaField}Url" :width="50" :height="50"/>
-          </template>
-        </el-table-column>
-#elseif($column.list && $column.dictType && "" != $column.dictType)
-        <el-table-column label="${comment}" align="center" prop="${javaField}">
-          <template #default="scope">
-#if($column.htmlType == "checkbox")
-            <dict-tag :options="${column.dictType}" :value="scope.row.${javaField} ? scope.row.${javaField}.split(',') : []"/>
-#else
-            <dict-tag :options="${column.dictType}" :value="scope.row.${javaField}"/>
-#end
-          </template>
-        </el-table-column>
-#elseif($column.list && "" != $javaField)
-#if(${foreach.index} == 1)
-        <el-table-column label="${comment}" prop="${javaField}" />
-#else
-        <el-table-column label="${comment}" align="center" prop="${javaField}" />
-#end
-#end
-#end
-        <el-table-column label="操作" align="center" fixed="right" class-name="small-padding fixed-width">
-          <template #default="scope">
-            <el-tooltip content="修改" placement="top">
-              <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['${moduleName}:${businessName}:edit']" />
-            </el-tooltip>
-            <el-tooltip content="新增" placement="top">
-              <el-button link type="primary" icon="Plus" @click="handleAdd(scope.row)" v-hasPermi="['${moduleName}:${businessName}:add']" />
-            </el-tooltip>
-            <el-tooltip content="删除" placement="top">
-              <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['${moduleName}:${businessName}:remove']" />
-            </el-tooltip>
-          </template>
-        </el-table-column>
-      </el-table>
-    </el-card>
-    <!-- 添加或修改${functionName}对话框 -->
-    <el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
-      <el-form ref="${businessName}FormRef" :model="form" :rules="rules" label-width="80px">
-#foreach($column in $columns)
-#set($field=$column.javaField)
-#if(($column.insert || $column.edit) && !$column.pk)
-#set($parentheseIndex=$column.columnComment.indexOf("("))
-#if($parentheseIndex != -1)
-#set($comment=$column.columnComment.substring(0, $parentheseIndex))
-#else
-#set($comment=$column.columnComment)
-#end
-#set($dictType=$column.dictType)
-#if("" != $treeParentCode && $column.javaField == $treeParentCode)
-        <el-form-item label="${comment}" prop="${treeParentCode}">
-          <el-tree-select
-            v-model="form.${treeParentCode}"
-            :data="${businessName}Options"
-            :props="{ value: '${treeCode}', label: '${treeName}', children: 'children' }"
-            value-key="${treeCode}"
-            placeholder="请选择${comment}"
-            check-strictly
-          />
-        </el-form-item>
-#elseif($column.htmlType == "input")
-        <el-form-item label="${comment}" prop="${field}">
-          <el-input v-model="form.${field}" placeholder="请输入${comment}" />
-        </el-form-item>
-#elseif($column.htmlType == "imageUpload")
-        <el-form-item label="${comment}" prop="${field}">
-          <image-upload v-model="form.${field}"/>
-        </el-form-item>
-#elseif($column.htmlType == "fileUpload")
-        <el-form-item label="${comment}" prop="${field}">
-          <file-upload v-model="form.${field}"/>
-        </el-form-item>
-#elseif($column.htmlType == "editor")
-        <el-form-item label="${comment}">
-          <editor v-model="form.${field}" :min-height="192"/>
-        </el-form-item>
-#elseif($column.htmlType == "select" && "" != $dictType)
-        <el-form-item label="${comment}" prop="${field}">
-          <el-select v-model="form.${field}" placeholder="请选择${comment}">
-            <el-option
-              v-for="dict in ${dictType}"
-              :key="dict.value"
-              :label="dict.label"
-#if($column.javaType == "Integer" || $column.javaType == "Long")
-              :value="parseInt(dict.value)"
-#else
-              :value="dict.value"
-#end
-            ></el-option>
-          </el-select>
-        </el-form-item>
-#elseif($column.htmlType == "select" && $dictType)
-        <el-form-item label="${comment}" prop="${field}">
-          <el-select v-model="form.${field}" placeholder="请选择${comment}">
-            <el-option label="请选择字典生成" value="" />
-          </el-select>
-        </el-form-item>
-#elseif($column.htmlType == "checkbox" && "" != $dictType)
-        <el-form-item label="${comment}" prop="${field}">
-          <el-checkbox-group v-model="form.${field}">
-            <el-checkbox
-              v-for="dict in ${dictType}"
-              :key="dict.value"
-              :label="dict.value">
-              {{dict.label}}
-            </el-checkbox>
-          </el-checkbox-group>
-        </el-form-item>
-#elseif($column.htmlType == "checkbox" && $dictType)
-        <el-form-item label="${comment}" prop="${field}">
-          <el-checkbox-group v-model="form.${field}">
-            <el-checkbox>请选择字典生成</el-checkbox>
-          </el-checkbox-group>
-        </el-form-item>
-#elseif($column.htmlType == "radio" && "" != $dictType)
-        <el-form-item label="${comment}" prop="${field}">
-          <el-radio-group v-model="form.${field}">
-            <el-radio
-              v-for="dict in ${dictType}"
-              :key="dict.value"
-#if($column.javaType == "Integer" || $column.javaType == "Long")
-              :value="parseInt(dict.value)"
-#else
-              :value="dict.value"
-#end
-            >{{dict.label}}</el-radio>
-          </el-radio-group>
-        </el-form-item>
-#elseif($column.htmlType == "radio" && $dictType)
-        <el-form-item label="${comment}" prop="${field}">
-          <el-radio-group v-model="form.${field}">
-            <el-radio value="1">请选择字典生成</el-radio>
-          </el-radio-group>
-        </el-form-item>
-#elseif($column.htmlType == "datetime")
-        <el-form-item label="${comment}" prop="${field}">
-          <el-date-picker clearable
-            v-model="form.${field}"
-            type="datetime"
-            value-format="YYYY-MM-DD HH:mm:ss"
-            placeholder="选择${comment}"
-          />
-        </el-form-item>
-#elseif($column.htmlType == "textarea")
-        <el-form-item label="${comment}" prop="${field}">
-          <el-input v-model="form.${field}" type="textarea" placeholder="请输入内容" />
-        </el-form-item>
-#end
-#end
-#end
-      </el-form>
-      <template #footer>
-        <div class="dialog-footer">
-          <el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>
-          <el-button @click="cancel">取 消</el-button>
-        </div>
-      </template>
-    </el-dialog>
-  </div>
+        <!-- 添加或修改${functionName}对话框 -->
+        <el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
+            <el-form ref="${businessName}FormRef" :model="form" :rules="rules" label-width="80px">
+                #foreach($column in $columns)
+                    #set($field=$column.javaField)
+                    #if(($column.insert || $column.edit) && !$column.pk)
+                        #set($parentheseIndex=$column.columnComment.indexOf("("))
+                        #if($parentheseIndex != -1)
+                            #set($comment=$column.columnComment.substring(0, $parentheseIndex))
+                        #else
+                            #set($comment=$column.columnComment)
+                        #end
+                        #set($dictType=$column.dictType)
+                        #if("" != $treeParentCode && $column.javaField == $treeParentCode)
+                            <el-form-item label="${comment}" prop="${treeParentCode}">
+                                <el-tree-select
+                                    v-model="form.${treeParentCode}"
+                                    :data="${businessName}Options"
+                                    :props="{ value: '${treeCode}', label: '${treeName}', children: 'children' }"
+                                    value-key="${treeCode}"
+                                    placeholder="请选择${comment}"
+                                    check-strictly
+                                />
+                            </el-form-item>
+                        #elseif($column.htmlType == "input")
+                            <el-form-item label="${comment}" prop="${field}">
+                                <el-input v-model="form.${field}" placeholder="请输入${comment}" />
+                            </el-form-item>
+                        #elseif($column.htmlType == "imageUpload")
+                            <el-form-item label="${comment}" prop="${field}">
+                                <image-upload v-model="form.${field}"/>
+                            </el-form-item>
+                        #elseif($column.htmlType == "fileUpload")
+                            <el-form-item label="${comment}" prop="${field}">
+                                <file-upload v-model="form.${field}"/>
+                            </el-form-item>
+                        #elseif($column.htmlType == "editor")
+                            <el-form-item label="${comment}">
+                                <editor v-model="form.${field}" :min-height="192"/>
+                            </el-form-item>
+                        #elseif($column.htmlType == "select" && "" != $dictType)
+                            <el-form-item label="${comment}" prop="${field}">
+                                <el-select v-model="form.${field}" placeholder="请选择${comment}">
+                                    <el-option
+                                        v-for="dict in ${dictType}"
+                                        :key="dict.value"
+                                        :label="dict.label"
+                                        #if($column.javaType == "Integer" || $column.javaType == "Long")
+                                        :value="parseInt(dict.value)"
+                                        #else
+                                        :value="dict.value"
+                                        #end
+                                    ></el-option>
+                                </el-select>
+                            </el-form-item>
+                        #elseif($column.htmlType == "select" && $dictType)
+                            <el-form-item label="${comment}" prop="${field}">
+                                <el-select v-model="form.${field}" placeholder="请选择${comment}">
+                                    <el-option label="请选择字典生成" value="" />
+                                </el-select>
+                            </el-form-item>
+                        #elseif($column.htmlType == "checkbox" && "" != $dictType)
+                            <el-form-item label="${comment}" prop="${field}">
+                                <el-checkbox-group v-model="form.${field}">
+                                    <el-checkbox
+                                        v-for="dict in ${dictType}"
+                                        :key="dict.value"
+                                        :label="dict.value">
+                                        {{dict.label}}
+                                    </el-checkbox>
+                                </el-checkbox-group>
+                            </el-form-item>
+                        #elseif($column.htmlType == "checkbox" && $dictType)
+                            <el-form-item label="${comment}" prop="${field}">
+                                <el-checkbox-group v-model="form.${field}">
+                                    <el-checkbox>请选择字典生成</el-checkbox>
+                                </el-checkbox-group>
+                            </el-form-item>
+                        #elseif($column.htmlType == "radio" && "" != $dictType)
+                            <el-form-item label="${comment}" prop="${field}">
+                                <el-radio-group v-model="form.${field}">
+                                    <el-radio
+                                        v-for="dict in ${dictType}"
+                                        :key="dict.value"
+                                        #if($column.javaType == "Integer" || $column.javaType == "Long")
+                                        :value="parseInt(dict.value)"
+                                        #else
+                                        :value="dict.value"
+                                        #end
+                                    >{{dict.label}}</el-radio>
+                                </el-radio-group>
+                            </el-form-item>
+                        #elseif($column.htmlType == "radio" && $dictType)
+                            <el-form-item label="${comment}" prop="${field}">
+                                <el-radio-group v-model="form.${field}">
+                                    <el-radio value="1">请选择字典生成</el-radio>
+                                </el-radio-group>
+                            </el-form-item>
+                        #elseif($column.htmlType == "datetime")
+                            <el-form-item label="${comment}" prop="${field}">
+                                <el-date-picker clearable
+                                                v-model="form.${field}"
+                                                type="datetime"
+                                                value-format="YYYY-MM-DD HH:mm:ss"
+                                                placeholder="选择${comment}"
+                                />
+                            </el-form-item>
+                        #elseif($column.htmlType == "textarea")
+                            <el-form-item label="${comment}" prop="${field}">
+                                <el-input v-model="form.${field}" type="textarea" placeholder="请输入内容" />
+                            </el-form-item>
+                        #end
+                    #end
+                #end
+            </el-form>
+            <template #footer>
+                <div class="dialog-footer">
+                    <el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>
+                    <el-button @click="cancel">取 消</el-button>
+                </div>
+            </template>
+        </el-dialog>
+    </div>
 </template>
 
 <script setup name="${BusinessName}" lang="ts">
-import { list${BusinessName}, get${BusinessName}, del${BusinessName}, add${BusinessName}, update${BusinessName} } from "@/api/${moduleName}/${businessName}";
-import { ${BusinessName}VO, ${BusinessName}Query, ${BusinessName}Form } from '@/api/${moduleName}/${businessName}/types';
-
-type ${BusinessName}Option = {
-  ${treeCode}: number;
-  ${treeName}: string;
-  children?: ${BusinessName}Option[];
-}
-
-const { proxy } = getCurrentInstance() as ComponentInternalInstance;;
-
-#if(${dicts} != '')
-#set($dictsNoSymbol=$dicts.replace("'", ""))
-const { ${dictsNoSymbol} } = toRefs<any>(proxy?.useDict(${dicts}));
-#end
-
-const ${businessName}List = ref<${BusinessName}VO[]>([]);
-const ${businessName}Options = ref<${BusinessName}Option[]>([]);
-const buttonLoading = ref(false);
-const showSearch = ref(true);
-const isExpandAll = ref(true);
-const loading = ref(false);
-
-const queryFormRef = ref<ElFormInstance>();
-const ${businessName}FormRef = ref<ElFormInstance>();
-const ${businessName}TableRef = ref<ElTableInstance>()
-
-const dialog = reactive<DialogOption>({
-    visible: false,
-    title: ''
-});
-
-#foreach ($column in $columns)
-#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
-#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
-const dateRange${AttrName} = ref<[DateModelType, DateModelType]>(['', '']);
-#end
-#end
-
-const initFormData: ${BusinessName}Form = {
-#foreach ($column in $columns)
-#if($column.insert || $column.edit)
-#if($column.htmlType == "checkbox")
-    $column.javaField: []#if($foreach.count != $columns.size()),#end
-#else
-    $column.javaField: undefined#if($foreach.count != $columns.size()),#end
-#end
-#end
-#end
-}
-
-const data = reactive<PageData<${BusinessName}Form, ${BusinessName}Query>>({
-  form: {...initFormData},
-  queryParams: {
-#foreach ($column in $columns)
-#if($column.query)
-#if($column.htmlType != "datetime" || $column.queryType != "BETWEEN")
-    $column.javaField: undefined,
-#end
-#end
-#end
-    params: {
-#foreach ($column in $columns)
-#if($column.query)
-#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
-      $column.javaField: undefined#if($foreach.count != $columns.size()),#end
-#end
-#end
-#end
+    import { list${BusinessName}, get${BusinessName}, del${BusinessName}, add${BusinessName}, update${BusinessName} } from "@/api/${moduleName}/${businessName}";
+    import { ${BusinessName}VO, ${BusinessName}Query, ${BusinessName}Form } from '@/api/${moduleName}/${businessName}/types';
+
+    type ${BusinessName}Option = {
+            ${treeCode}: number;
+            ${treeName}: string;
+        children?: ${BusinessName}Option[];
+    }
+
+    const { proxy } = getCurrentInstance() as ComponentInternalInstance;;
+
+        #if(${dicts} != '')
+            #set($dictsNoSymbol=$dicts.replace("'", ""))
+        const { ${dictsNoSymbol} } = toRefs<any>(proxy?.useDict(${dicts}));
+        #end
+
+    const ${businessName}List = ref<${BusinessName}VO[]>([]);
+    const ${businessName}Options = ref<${BusinessName}Option[]>([]);
+    const buttonLoading = ref(false);
+    const showSearch = ref(true);
+    const isExpandAll = ref(true);
+    const loading = ref(false);
+
+    const queryFormRef = ref<ElFormInstance>();
+    const ${businessName}FormRef = ref<ElFormInstance>();
+    const ${businessName}TableRef = ref<ElTableInstance>()
+
+    const dialog = reactive<DialogOption>({
+        visible: false,
+        title: ''
+    });
+
+        #foreach ($column in $columns)
+            #if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
+                #set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+            const dateRange${AttrName} = ref<[DateModelType, DateModelType]>(['', '']);
+            #end
+        #end
+
+    const initFormData: ${BusinessName}Form = {
+        #foreach ($column in $columns)
+            #if($column.insert || $column.edit)
+                #if($column.htmlType == "checkbox")
+                        $column.javaField: []#if($foreach.count != $columns.size()),#end
+                #else
+                        $column.javaField: undefined#if($foreach.count != $columns.size()),#end
+                #end
+            #end
+        #end
+    }
+
+    const data = reactive<PageData<${BusinessName}Form, ${BusinessName}Query>>({
+        form: {...initFormData},
+        queryParams: {
+            #foreach ($column in $columns)
+                #if($column.query)
+                    #if($column.htmlType != "datetime" || $column.queryType != "BETWEEN")
+                            $column.javaField: undefined,
+                    #end
+                #end
+            #end
+            params: {
+                #foreach ($column in $columns)
+                    #if($column.query)
+                        #if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
+                                $column.javaField: undefined#if($foreach.count != $columns.size()),#end
+                        #end
+                    #end
+                #end
+            }
+        },
+        rules: {
+            #foreach ($column in $columns)
+                #if($column.insert || $column.edit)
+                    #if($column.required)
+                        #set($parentheseIndex=$column.columnComment.indexOf("("))
+                        #if($parentheseIndex != -1)
+                            #set($comment=$column.columnComment.substring(0, $parentheseIndex))
+                        #else
+                            #set($comment=$column.columnComment)
+                        #end
+                            $column.javaField: [
+                            { required: true, message: "$comment不能为空", trigger: #if($column.htmlType == "select" || $column.htmlType == "radio")"change"#else"blur"#end }
+                        ]#if($foreach.count != $columns.size()),#end
+                    #end
+                #end
+            #end
+        }
+    });
+
+    const { queryParams, form, rules } = toRefs(data);
+
+    /** 查询${functionName}列表 */
+    const getList = async () => {
+        loading.value = true;
+        #foreach ($column in $columns)
+            #if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
+                queryParams.value.params = {};
+                #break
+            #end
+        #end
+        #foreach ($column in $columns)
+            #if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
+                #set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+                proxy?.addDateRange(queryParams.value, dateRange${AttrName}.value, '${AttrName}');
+            #end
+        #end
+        const res = await list${BusinessName}(queryParams.value);
+        const data = proxy?.handleTree<${BusinessName}VO>(res.data, "${treeCode}", "${treeParentCode}");
+        if (data) {
+                ${businessName}List.value = data;
+            loading.value = false;
+        }
     }
-  },
-  rules: {
-#foreach ($column in $columns)
-#if($column.insert || $column.edit)
-#if($column.required)
-#set($parentheseIndex=$column.columnComment.indexOf("("))
-#if($parentheseIndex != -1)
-#set($comment=$column.columnComment.substring(0, $parentheseIndex))
-#else
-#set($comment=$column.columnComment)
-#end
-    $column.javaField: [
-      { required: true, message: "$comment不能为空", trigger: #if($column.htmlType == "select" || $column.htmlType == "radio")"change"#else"blur"#end }
-    ]#if($foreach.count != $columns.size()),#end
-#end
-#end
-#end
-  }
-});
-
-const { queryParams, form, rules } = toRefs(data);
-
-/** 查询${functionName}列表 */
-const getList = async () => {
-  loading.value = true;
-#foreach ($column in $columns)
-#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
-  queryParams.value.params = {};
-#break
-#end
-#end
-#foreach ($column in $columns)
-#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
-#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
-  proxy?.addDateRange(queryParams.value, dateRange${AttrName}.value, '${AttrName}');
-#end
-#end
-  const res = await list${BusinessName}(queryParams.value);
-  const data = proxy?.handleTree<${BusinessName}VO>(res.data, "${treeCode}", "${treeParentCode}");
-  if (data) {
-    ${businessName}List.value = data;
-    loading.value = false;
-  }
-}
-
-/** 查询${functionName}下拉树结构 */
-const getTreeselect = async () => {
-  const res = await list${BusinessName}();
-  ${businessName}Options.value = [];
-  const data: ${BusinessName}Option = { ${treeCode}: 0, ${treeName}: '顶级节点', children: [] };
-  data.children = proxy?.handleTree<${BusinessName}Option>(res.data, "${treeCode}", "${treeParentCode}");
-  ${businessName}Options.value.push(data);
-}
-
-// 取消按钮
-const cancel = () => {
-  reset();
-  dialog.visible = false;
-}
-
-// 表单重置
-const reset = () => {
-  form.value = {...initFormData}
-  ${businessName}FormRef.value?.resetFields();
-}
-
-/** 搜索按钮操作 */
-const handleQuery = () => {
-  getList();
-}
-
-/** 重置按钮操作 */
-const resetQuery = () => {
-#foreach ($column in $columns)
-#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
-#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
-  dateRange${AttrName}.value = ['', ''];
-#end
-#end
-  queryFormRef.value?.resetFields();
-  handleQuery();
-}
-
-/** 新增按钮操作 */
-const handleAdd = (row?: ${BusinessName}VO) => {
-  reset();
-  getTreeselect();
-  if (row != null && row.${treeCode}) {
-    form.value.${treeParentCode} = row.${treeCode};
-  } else {
-    form.value.${treeParentCode} = 0;
-  }
-  dialog.visible = true;
-  dialog.title = "添加${functionName}";
-}
-
-/** 展开/折叠操作 */
-const handleToggleExpandAll = () => {
-  isExpandAll.value = !isExpandAll.value;
-  toggleExpandAll(${businessName}List.value, isExpandAll.value)
-}
-
-/** 展开/折叠操作 */
-const toggleExpandAll = (data: ${BusinessName}VO[], status: boolean) => {
-  data.forEach((item) => {
-    ${businessName}TableRef.value?.toggleRowExpansion(item, status)
-    if (item.children && item.children.length > 0) toggleExpandAll(item.children, status)
-  })
-}
-
-/** 修改按钮操作 */
-const handleUpdate = async (row: ${BusinessName}VO) => {
-  reset();
-  await getTreeselect();
-  if (row != null) {
-    form.value.${treeParentCode} = row.${treeParentCode};
-  }
-  const res = await get${BusinessName}(row.${pkColumn.javaField});
-  Object.assign(form.value, res.data);
-#foreach ($column in $columns)
-  #if($column.htmlType == "checkbox")
-  form.value.$column.javaField = form.value.${column.javaField}.split(",");
-  #end
-#end
-  dialog.visible = true;
-  dialog.title = "修改${functionName}";
-}
-
-/** 提交按钮 */
-const submitForm = () => {
-  ${businessName}FormRef.value?.validate(async (valid: boolean) => {
-    if (valid) {
-      buttonLoading.value = true;
-#foreach ($column in $columns)
-#if($column.htmlType == "checkbox")
-      form.value.$column.javaField = form.value.${column.javaField}.join(",");
-#end
-#end
-      if (form.value.${pkColumn.javaField}) {
-        await update${BusinessName}(form.value).finally(() => buttonLoading.value = false);
-      } else {
-        await add${BusinessName}(form.value).finally(() => buttonLoading.value = false);
-      }
-      proxy?.#[[$modal]]#.msgSuccess("操作成功");
-      dialog.visible = false;
-      getList();
+
+    /** 查询${functionName}下拉树结构 */
+    const getTreeselect = async () => {
+        const res = await list${BusinessName}();
+            ${businessName}Options.value = [];
+        const data: ${BusinessName}Option = { ${treeCode}: 0, ${treeName}: '顶级节点', children: [] };
+        data.children = proxy?.handleTree<${BusinessName}Option>(res.data, "${treeCode}", "${treeParentCode}");
+            ${businessName}Options.value.push(data);
+    }
+
+    // 取消按钮
+    const cancel = () => {
+        reset();
+        dialog.visible = false;
+    }
+
+    // 表单重置
+    const reset = () => {
+        form.value = {...initFormData}
+            ${businessName}FormRef.value?.resetFields();
+    }
+
+    /** 搜索按钮操作 */
+    const handleQuery = () => {
+        getList();
+    }
+
+    /** 重置按钮操作 */
+    const resetQuery = () => {
+        #foreach ($column in $columns)
+            #if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
+                #set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+                dateRange${AttrName}.value = ['', ''];
+            #end
+        #end
+        queryFormRef.value?.resetFields();
+        handleQuery();
+    }
+
+    /** 新增按钮操作 */
+    const handleAdd = (row?: ${BusinessName}VO) => {
+        reset();
+        getTreeselect();
+        if (row != null && row.${treeCode}) {
+            form.value.${treeParentCode} = row.${treeCode};
+        } else {
+            form.value.${treeParentCode} = 0;
+        }
+        dialog.visible = true;
+        dialog.title = "添加${functionName}";
+    }
+
+    /** 展开/折叠操作 */
+    const handleToggleExpandAll = () => {
+        isExpandAll.value = !isExpandAll.value;
+        toggleExpandAll(${businessName}List.value, isExpandAll.value)
     }
-  });
-}
-
-/** 删除按钮操作 */
-const handleDelete = async (row: ${BusinessName}VO) => {
-  await proxy?.#[[$modal]]#.confirm('是否确认删除${functionName}编号为"' + row.${pkColumn.javaField} + '"的数据项?');
-  loading.value = true;
-  await del${BusinessName}(row.${pkColumn.javaField}).finally(() => loading.value = false);
-  await getList();
-  proxy?.#[[$modal]]#.msgSuccess("删除成功");
-}
-
-onMounted(() => {
-  getList();
-});
+
+    /** 展开/折叠操作 */
+    const toggleExpandAll = (data: ${BusinessName}VO[], status: boolean) => {
+        data.forEach((item) => {
+                ${businessName}TableRef.value?.toggleRowExpansion(item, status)
+            if (item.children && item.children.length > 0) toggleExpandAll(item.children, status)
+        })
+    }
+
+    /** 修改按钮操作 */
+    const handleUpdate = async (row: ${BusinessName}VO) => {
+        reset();
+        await getTreeselect();
+        if (row != null) {
+            form.value.${treeParentCode} = row.${treeParentCode};
+        }
+        const res = await get${BusinessName}(row.${pkColumn.javaField});
+        Object.assign(form.value, res.data);
+        #foreach ($column in $columns)
+            #if($column.htmlType == "checkbox")
+                form.value.$column.javaField = form.value.${column.javaField}.split(",");
+            #end
+        #end
+        dialog.visible = true;
+        dialog.title = "修改${functionName}";
+    }
+
+    /** 提交按钮 */
+    const submitForm = () => {
+            ${businessName}FormRef.value?.validate(async (valid: boolean) => {
+            if (valid) {
+                buttonLoading.value = true;
+                #foreach ($column in $columns)
+                    #if($column.htmlType == "checkbox")
+                        form.value.$column.javaField = form.value.${column.javaField}.join(",");
+                    #end
+                #end
+                if (form.value.${pkColumn.javaField}) {
+                    await update${BusinessName}(form.value).finally(() => buttonLoading.value = false);
+                } else {
+                    await add${BusinessName}(form.value).finally(() => buttonLoading.value = false);
+                }
+                proxy?.#[[$modal]]#.msgSuccess("操作成功");
+                dialog.visible = false;
+                getList();
+            }
+        });
+    }
+
+    /** 删除按钮操作 */
+    const handleDelete = async (row: ${BusinessName}VO) => {
+        await proxy?.#[[$modal]]#.confirm('是否确认删除${functionName}编号为"' + row.${pkColumn.javaField} + '"的数据项?');
+        loading.value = true;
+        await del${BusinessName}(row.${pkColumn.javaField}).finally(() => loading.value = false);
+        await getList();
+        proxy?.#[[$modal]]#.msgSuccess("删除成功");
+    }
+
+    onMounted(() => {
+        getList();
+    });
 </script>
+
+<style lang="scss" scoped>
+    .gen-container {
+        padding: 20px;
+        background-color: #f5f7fa;
+        min-height: calc(100vh - 84px);
+    }
+
+    .search-wrapper {
+        margin-bottom: 20px;
+    }
+
+    .search-card {
+        :deep(.el-card__body) {
+            padding: 20px;
+        }
+    }
+
+    .table-card {
+        :deep(.el-card__header) {
+            padding: 16px 20px;
+            border-bottom: 1px solid #f0f0f0;
+        }
+
+        :deep(.el-card__body) {
+            padding: 20px;
+        }
+    }
+
+    .card-header {
+        display: flex;
+        justify-content: space-between;
+        align-items: center;
+
+        .header-left {
+            .header-title {
+                font-size: 16px;
+                font-weight: 600;
+                color: #303133;
+            }
+        }
+
+        .header-right {
+            display: flex;
+            gap: 8px;
+            align-items: center;
+            flex-wrap: nowrap;
+        }
+    }
+
+    .gen-table {
+        :deep(.el-table__header) {
+            th {
+                background-color: #f5f7fa;
+                color: #606266;
+                font-weight: 600;
+                border-bottom: 1px solid #ebeef5;
+            }
+        }
+
+        :deep(.el-table__body) {
+            td {
+                border-bottom: 1px solid #ebeef5;
+            }
+        }
+
+        :deep(.el-table__row) {
+            &:last-child td {
+                border-bottom: none;
+            }
+        }
+    }
+</style>

+ 95 - 29
ruoyi-modules/ruoyi-gen/src/main/resources/vm/vue/index.vue.vm

@@ -1,8 +1,8 @@
 <template>
-  <div class="p-2">
+  <div class="gen-container">
     <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
-      <div v-show="showSearch" class="mb-[10px]">
-        <el-card shadow="hover">
+      <div v-show="showSearch" class="search-wrapper">
+        <el-card shadow="hover" class="search-card">
           <el-form ref="queryFormRef" :model="queryParams" :inline="true">
 #foreach($column in $columns)
 #if($column.query)
@@ -55,34 +55,31 @@
 #end
 #end
             <el-form-item>
-              <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
-              <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+              <el-button type="primary" @click="handleQuery">搜索</el-button>
+              <el-button @click="resetQuery">重置</el-button>
             </el-form-item>
           </el-form>
         </el-card>
       </div>
     </transition>
 
-    <el-card shadow="never">
+    <el-card shadow="hover" class="table-card">
       <template #header>
-        <el-row :gutter="10" class="mb8">
-          <el-col :span="1.5">
-            <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['${moduleName}:${businessName}:add']">新增</el-button>
-          </el-col>
-          <el-col :span="1.5">
-            <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['${moduleName}:${businessName}:edit']">修改</el-button>
-          </el-col>
-          <el-col :span="1.5">
-            <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['${moduleName}:${businessName}:remove']">删除</el-button>
-          </el-col>
-          <el-col :span="1.5">
-            <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['${moduleName}:${businessName}:export']">导出</el-button>
-          </el-col>
-          <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
-        </el-row>
+        <div class="card-header">
+          <div class="header-left">
+            <span class="header-title">${functionName}列表</span>
+          </div>
+          <div class="header-right">
+            <el-button type="primary" size="small" @click="handleAdd" v-hasPermi="['${moduleName}:${businessName}:add']">新增</el-button>
+            <el-button type="success" size="small" :disabled="single" @click="handleUpdate()" v-hasPermi="['${moduleName}:${businessName}:edit']">修改</el-button>
+            <el-button type="danger" size="small" :disabled="multiple" @click="handleDelete()" v-hasPermi="['${moduleName}:${businessName}:remove']">删除</el-button>
+            <el-button type="warning" size="small" @click="handleExport" v-hasPermi="['${moduleName}:${businessName}:export']">导出</el-button>
+            <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
+          </div>
+        </div>
       </template>
 
-      <el-table v-loading="loading" border :data="${businessName}List" @selection-change="handleSelectionChange">
+      <el-table v-loading="loading" stripe :data="${businessName}List" @selection-change="handleSelectionChange" class="gen-table">
         <el-table-column type="selection" width="55" align="center" />
 #foreach($column in $columns)
 #set($javaField=$column.javaField)
@@ -120,14 +117,10 @@
         <el-table-column label="${comment}" align="center" prop="${javaField}" />
 #end
 #end
-        <el-table-column label="操作" align="center" fixed="right" class-name="small-padding fixed-width">
+        <el-table-column label="操作" align="center" width="160" fixed="right">
           <template #default="scope">
-            <el-tooltip content="修改" placement="top">
-              <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['${moduleName}:${businessName}:edit']"></el-button>
-            </el-tooltip>
-            <el-tooltip content="删除" placement="top">
-              <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['${moduleName}:${businessName}:remove']"></el-button>
-            </el-tooltip>
+            <el-button link type="primary" size="small" @click="handleUpdate(scope.row)" v-hasPermi="['${moduleName}:${businessName}:edit']">编辑</el-button>
+            <el-button link type="danger" size="small" @click="handleDelete(scope.row)" v-hasPermi="['${moduleName}:${businessName}:remove']">删除</el-button>
           </template>
         </el-table-column>
       </el-table>
@@ -457,3 +450,76 @@ onMounted(() => {
   getList();
 });
 </script>
+
+<style lang="scss" scoped>
+.gen-container {
+  padding: 20px;
+  background-color: #f5f7fa;
+  min-height: calc(100vh - 84px);
+}
+
+.search-wrapper {
+  margin-bottom: 20px;
+}
+
+.search-card {
+  :deep(.el-card__body) {
+    padding: 20px;
+  }
+}
+
+.table-card {
+  :deep(.el-card__header) {
+    padding: 16px 20px;
+    border-bottom: 1px solid #f0f0f0;
+  }
+
+  :deep(.el-card__body) {
+    padding: 20px;
+  }
+}
+
+.card-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+
+  .header-left {
+    .header-title {
+      font-size: 16px;
+      font-weight: 600;
+      color: #303133;
+    }
+  }
+
+  .header-right {
+    display: flex;
+    gap: 8px;
+    align-items: center;
+    flex-wrap: nowrap;
+  }
+}
+
+.gen-table {
+  :deep(.el-table__header) {
+    th {
+      background-color: #f5f7fa;
+      color: #606266;
+      font-weight: 600;
+      border-bottom: 1px solid #ebeef5;
+    }
+  }
+
+  :deep(.el-table__body) {
+    td {
+      border-bottom: 1px solid #ebeef5;
+    }
+  }
+
+  :deep(.el-table__row) {
+    &:last-child td {
+      border-bottom: none;
+    }
+  }
+}
+</style>

+ 105 - 0
ruoyi-modules/yingpaipay-service/src/main/java/org/dromara/service/controller/SysServiceClassificationController.java

@@ -0,0 +1,105 @@
+package org.dromara.service.controller;
+
+import java.util.List;
+
+import lombok.RequiredArgsConstructor;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.constraints.*;
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.validation.annotation.Validated;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.excel.utils.ExcelUtil;
+import org.dromara.service.domain.vo.SysServiceClassificationVo;
+import org.dromara.service.domain.bo.SysServiceClassificationBo;
+import org.dromara.service.service.ISysServiceClassificationService;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+
+/**
+ * 服务分类
+ * 前端访问路由地址为:/service/classification
+ *
+ * @author Huanyi
+ * @date 2026-03-27
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/classification")
+public class SysServiceClassificationController extends BaseController {
+
+    private final ISysServiceClassificationService sysServiceClassificationService;
+
+    /**
+     * 查询服务分类列表
+     */
+    @GetMapping("/list")
+    public TableDataInfo<SysServiceClassificationVo> list(SysServiceClassificationBo bo, PageQuery pageQuery) {
+        return sysServiceClassificationService.queryPageList(bo, pageQuery);
+    }
+
+    /**
+     * 导出服务分类列表
+     */
+    @Log(title = "服务分类", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(SysServiceClassificationBo bo, HttpServletResponse response) {
+        List<SysServiceClassificationVo> list = sysServiceClassificationService.queryList(bo);
+        ExcelUtil.exportExcel(list, "服务分类", SysServiceClassificationVo.class, response);
+    }
+
+    /**
+     * 获取服务分类详细信息
+     *
+     * @param id 主键
+     */
+    @GetMapping("/{id}")
+    public R<SysServiceClassificationVo> getInfo(@NotNull(message = "主键不能为空")
+                                     @PathVariable("id") Long id) {
+        return R.ok(sysServiceClassificationService.queryById(id));
+    }
+
+    /**
+     * 新增服务分类
+     */
+    @Log(title = "服务分类", businessType = BusinessType.INSERT)
+    @RepeatSubmit()
+    @PostMapping()
+    public R<Void> add(@Validated(AddGroup.class) @RequestBody SysServiceClassificationBo bo) {
+        return toAjax(sysServiceClassificationService.insertByBo(bo));
+    }
+
+    /**
+     * 修改服务分类
+     */
+    @Log(title = "服务分类", businessType = BusinessType.UPDATE)
+    @RepeatSubmit()
+    @PutMapping()
+    public R<Void> edit(@Validated(EditGroup.class) @RequestBody SysServiceClassificationBo bo) {
+        return toAjax(sysServiceClassificationService.updateByBo(bo));
+    }
+
+    /**
+     * 删除服务分类
+     *
+     * @param ids 主键串
+     */
+    @Log(title = "服务分类", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public R<Void> remove(@NotEmpty(message = "主键不能为空")
+                          @PathVariable("ids") Long[] ids) {
+        return toAjax(sysServiceClassificationService.deleteWithValidByIds(List.of(ids), true));
+    }
+
+    @GetMapping("/listAll")
+    public R<List<SysServiceClassificationVo>> listAll() {
+        return R.ok(sysServiceClassificationService.listAll());
+    }
+}

+ 6 - 0
ruoyi-modules/yingpaipay-service/src/main/java/org/dromara/service/domain/SysService.java

@@ -77,4 +77,10 @@ public class SysService extends BaseEntity {
      */
     private String clockInRemark;
 
+    private String introduction;
+
+    private String orderInstruction;
+
+    private Long classificationId;
+
 }

+ 46 - 0
ruoyi-modules/yingpaipay-service/src/main/java/org/dromara/service/domain/SysServiceClassification.java

@@ -0,0 +1,46 @@
+package org.dromara.service.domain;
+
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serial;
+
+/**
+ * 服务分类对象 sys_service_classification
+ *
+ * @author Huanyi
+ * @date 2026-03-27
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("sys_service_classification")
+public class SysServiceClassification extends BaseEntity {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 序号
+     */
+    @TableId(value = "id")
+    private Long id;
+
+    /**
+     * 类型名称
+     */
+    private String name;
+
+    /**
+     * 排序
+     */
+    private Integer sort;
+
+    /**
+     * 备注
+     */
+    private String remark;
+
+
+}

+ 7 - 0
ruoyi-modules/yingpaipay-service/src/main/java/org/dromara/service/domain/bo/SysServiceBo.java

@@ -61,4 +61,11 @@ public class SysServiceBo extends BaseEntity {
      */
     private String clockInRemark;
 
+    private String introduction;
+
+    private String orderInstruction;
+
+    @NotNull(message = "所属分类不能为空", groups = { AddGroup.class, EditGroup.class })
+    private Long classificationId;
+
 }

+ 47 - 0
ruoyi-modules/yingpaipay-service/src/main/java/org/dromara/service/domain/bo/SysServiceClassificationBo.java

@@ -0,0 +1,47 @@
+package org.dromara.service.domain.bo;
+
+import org.dromara.service.domain.SysServiceClassification;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import jakarta.validation.constraints.*;
+
+/**
+ * 服务分类业务对象 sys_service_classification
+ *
+ * @author Huanyi
+ * @date 2026-03-27
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@AutoMapper(target = SysServiceClassification.class, reverseConvertGenerate = false)
+public class SysServiceClassificationBo extends BaseEntity {
+
+    /**
+     * 序号
+     */
+    @NotNull(message = "序号不能为空", groups = { EditGroup.class })
+    private Long id;
+
+    /**
+     * 类型名称
+     */
+    @NotBlank(message = "类型名称不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String name;
+
+    /**
+     * 排序
+     */
+    @NotNull(message = "排序不能为空", groups = { AddGroup.class, EditGroup.class })
+    private Integer sort;
+
+    /**
+     * 备注
+     */
+    private String remark;
+
+
+}

+ 58 - 0
ruoyi-modules/yingpaipay-service/src/main/java/org/dromara/service/domain/vo/SysServiceClassificationVo.java

@@ -0,0 +1,58 @@
+package org.dromara.service.domain.vo;
+
+import org.dromara.service.domain.SysServiceClassification;
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import org.dromara.common.excel.annotation.ExcelDictFormat;
+import org.dromara.common.excel.convert.ExcelDictConvert;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+
+
+/**
+ * 服务分类视图对象 sys_service_classification
+ *
+ * @author Huanyi
+ * @date 2026-03-27
+ */
+@Data
+@ExcelIgnoreUnannotated
+@AutoMapper(target = SysServiceClassification.class)
+public class SysServiceClassificationVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    private Long id;
+
+    /**
+     * 类型名称
+     */
+    @ExcelProperty(value = "类型名称")
+    private String name;
+
+    /**
+     * 排序
+     */
+    @ExcelProperty(value = "排序")
+    private Integer sort;
+
+    /**
+     * 备注
+     */
+    @ExcelProperty(value = "备注")
+    private String remark;
+
+    /**
+     * 创建时间
+     */
+    @ExcelProperty(value = "创建时间")
+    private Date createTime;
+
+
+}

+ 6 - 0
ruoyi-modules/yingpaipay-service/src/main/java/org/dromara/service/domain/vo/SysServiceVo.java

@@ -83,4 +83,10 @@ public class SysServiceVo implements Serializable {
      */
     private String clockInRemark;
 
+    private String introduction;
+
+    private String orderInstruction;
+
+    private Long classificationId;
+
 }

+ 15 - 0
ruoyi-modules/yingpaipay-service/src/main/java/org/dromara/service/mapper/SysServiceClassificationMapper.java

@@ -0,0 +1,15 @@
+package org.dromara.service.mapper;
+
+import org.dromara.service.domain.SysServiceClassification;
+import org.dromara.service.domain.vo.SysServiceClassificationVo;
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+
+/**
+ * 服务分类Mapper接口
+ *
+ * @author Huanyi
+ * @date 2026-03-27
+ */
+public interface SysServiceClassificationMapper extends BaseMapperPlus<SysServiceClassification, SysServiceClassificationVo> {
+
+}

+ 71 - 0
ruoyi-modules/yingpaipay-service/src/main/java/org/dromara/service/service/ISysServiceClassificationService.java

@@ -0,0 +1,71 @@
+package org.dromara.service.service;
+
+import org.dromara.service.domain.SysServiceClassification;
+import org.dromara.service.domain.vo.SysServiceClassificationVo;
+import org.dromara.service.domain.bo.SysServiceClassificationBo;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.mybatis.core.page.PageQuery;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 服务分类Service接口
+ *
+ * @author Huanyi
+ * @date 2026-03-27
+ */
+public interface ISysServiceClassificationService {
+
+    /**
+     * 查询服务分类
+     *
+     * @param id 主键
+     * @return 服务分类
+     */
+    SysServiceClassificationVo queryById(Long id);
+
+    /**
+     * 分页查询服务分类列表
+     *
+     * @param bo        查询条件
+     * @param pageQuery 分页参数
+     * @return 服务分类分页列表
+     */
+    TableDataInfo<SysServiceClassificationVo> queryPageList(SysServiceClassificationBo bo, PageQuery pageQuery);
+
+    /**
+     * 查询符合条件的服务分类列表
+     *
+     * @param bo 查询条件
+     * @return 服务分类列表
+     */
+    List<SysServiceClassificationVo> queryList(SysServiceClassificationBo bo);
+
+    /**
+     * 新增服务分类
+     *
+     * @param bo 服务分类
+     * @return 是否新增成功
+     */
+    Boolean insertByBo(SysServiceClassificationBo bo);
+
+    /**
+     * 修改服务分类
+     *
+     * @param bo 服务分类
+     * @return 是否修改成功
+     */
+    Boolean updateByBo(SysServiceClassificationBo bo);
+
+    /**
+     * 校验并批量删除服务分类信息
+     *
+     * @param ids     待删除的主键集合
+     * @param isValid 是否进行有效性校验
+     * @return 是否删除成功
+     */
+    Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
+
+    List<SysServiceClassificationVo> listAll();
+}

+ 146 - 0
ruoyi-modules/yingpaipay-service/src/main/java/org/dromara/service/service/impl/SysServiceClassificationServiceImpl.java

@@ -0,0 +1,146 @@
+package org.dromara.service.service.impl;
+
+import org.dromara.common.core.constant.CacheNames;
+import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.redis.utils.CacheUtils;
+import org.springframework.cache.annotation.CacheEvict;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.stereotype.Service;
+import org.dromara.service.domain.bo.SysServiceClassificationBo;
+import org.dromara.service.domain.vo.SysServiceClassificationVo;
+import org.dromara.service.domain.SysServiceClassification;
+import org.dromara.service.mapper.SysServiceClassificationMapper;
+import org.dromara.service.service.ISysServiceClassificationService;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Collection;
+
+/**
+ * 服务分类Service业务层处理
+ *
+ * @author Huanyi
+ * @date 2026-03-27
+ */
+@Slf4j
+@RequiredArgsConstructor
+@Service
+public class SysServiceClassificationServiceImpl implements ISysServiceClassificationService {
+
+    private final SysServiceClassificationMapper baseMapper;
+
+    /**
+     * 查询服务分类
+     *
+     * @param id 主键
+     * @return 服务分类
+     */
+    @Override
+    public SysServiceClassificationVo queryById(Long id){
+        return baseMapper.selectVoById(id);
+    }
+
+    /**
+     * 分页查询服务分类列表
+     *
+     * @param bo        查询条件
+     * @param pageQuery 分页参数
+     * @return 服务分类分页列表
+     */
+    @Override
+    public TableDataInfo<SysServiceClassificationVo> queryPageList(SysServiceClassificationBo bo, PageQuery pageQuery) {
+        LambdaQueryWrapper<SysServiceClassification> lqw = buildQueryWrapper(bo);
+        Page<SysServiceClassificationVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
+        return TableDataInfo.build(result);
+    }
+
+    /**
+     * 查询符合条件的服务分类列表
+     *
+     * @param bo 查询条件
+     * @return 服务分类列表
+     */
+    @Override
+    public List<SysServiceClassificationVo> queryList(SysServiceClassificationBo bo) {
+        LambdaQueryWrapper<SysServiceClassification> lqw = buildQueryWrapper(bo);
+        return baseMapper.selectVoList(lqw);
+    }
+
+    private LambdaQueryWrapper<SysServiceClassification> buildQueryWrapper(SysServiceClassificationBo bo) {
+        Map<String, Object> params = bo.getParams();
+        LambdaQueryWrapper<SysServiceClassification> lqw = Wrappers.lambdaQuery();
+        lqw.orderByAsc(SysServiceClassification::getSort);
+        lqw.like(StringUtils.isNotBlank(bo.getName()), SysServiceClassification::getName, bo.getName());
+        lqw.eq(bo.getSort() != null, SysServiceClassification::getSort, bo.getSort());
+        return lqw;
+    }
+
+    /**
+     * 新增服务分类
+     *
+     * @param bo 服务分类
+     * @return 是否新增成功
+     */
+    @CacheEvict(cacheNames = CacheNames.SYS_SERVICE_CLASSIFICATION, key = "'all'")
+    @Override
+    public Boolean insertByBo(SysServiceClassificationBo bo) {
+        SysServiceClassification add = MapstructUtils.convert(bo, SysServiceClassification.class);
+        validEntityBeforeSave(add);
+        boolean flag = baseMapper.insert(add) > 0;
+        if (flag) {
+            bo.setId(add.getId());
+        }
+        return flag;
+    }
+
+    /**
+     * 修改服务分类
+     *
+     * @param bo 服务分类
+     * @return 是否修改成功
+     */
+    @CacheEvict(cacheNames = CacheNames.SYS_SERVICE_CLASSIFICATION, key = "'all'")
+    @Override
+    public Boolean updateByBo(SysServiceClassificationBo bo) {
+        SysServiceClassification update = MapstructUtils.convert(bo, SysServiceClassification.class);
+        validEntityBeforeSave(update);
+        return baseMapper.updateById(update) > 0;
+    }
+
+    /**
+     * 保存前的数据校验
+     */
+    private void validEntityBeforeSave(SysServiceClassification entity){
+        //TODO 做一些数据校验,如唯一约束
+    }
+
+    /**
+     * 校验并批量删除服务分类信息
+     *
+     * @param ids     待删除的主键集合
+     * @param isValid 是否进行有效性校验
+     * @return 是否删除成功
+     */
+    @CacheEvict(cacheNames = CacheNames.SYS_SERVICE_CLASSIFICATION, key = "'all'")
+    @Override
+    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
+        if(isValid){
+            //TODO 做一些业务上的校验,判断是否需要校验
+        }
+        return baseMapper.deleteByIds(ids) > 0;
+    }
+
+    @Cacheable(cacheNames = CacheNames.SYS_SERVICE_CLASSIFICATION, key = "'all'")
+    @Override
+    public List<SysServiceClassificationVo> listAll() {
+        return baseMapper.selectVoList();
+    }
+}

+ 5 - 1
ruoyi-modules/yingpaipay-service/src/main/java/org/dromara/service/service/impl/SysServiceServiceImpl.java

@@ -28,6 +28,7 @@ import org.dromara.service.domain.SysService;
 import org.dromara.service.mapper.SysServiceMapper;
 import org.dromara.service.service.ISysServiceService;
 
+import java.nio.charset.StandardCharsets;
 import java.util.*;
 
 /**
@@ -52,7 +53,6 @@ public class SysServiceServiceImpl implements ISysServiceService {
      * @param id 主键
      * @return 服务列表
      */
-    @Cacheable(cacheNames = CacheNames.SYS_SERVICE, key = "'all'")
     @Override
     public SysServiceVo queryById(Long id){
         return baseMapper.selectVoById(id);
@@ -101,6 +101,8 @@ public class SysServiceServiceImpl implements ISysServiceService {
     @Override
     public SysServiceVo insertByBo(SysServiceBo bo) {
         SysService add = MapstructUtils.convert(bo, SysService.class);
+        add.setIntroduction(new String(Base64.getDecoder().decode(bo.getIntroduction()), StandardCharsets.UTF_8));
+        add.setOrderInstruction(new String(Base64.getDecoder().decode(bo.getOrderInstruction()), StandardCharsets.UTF_8));
         validEntityBeforeSave(add);
         boolean flag = baseMapper.insert(add) > 0;
         if (flag) {
@@ -119,6 +121,8 @@ public class SysServiceServiceImpl implements ISysServiceService {
     @Override
     public SysServiceVo updateByBo(SysServiceBo bo) {
         SysService update = MapstructUtils.convert(bo, SysService.class);
+        update.setIntroduction(new String(Base64.getDecoder().decode(bo.getIntroduction()), StandardCharsets.UTF_8));
+        update.setOrderInstruction(new String(Base64.getDecoder().decode(bo.getOrderInstruction()), StandardCharsets.UTF_8));
         validEntityBeforeSave(update);
         boolean flag = baseMapper.updateById(update) > 0;
         if (flag) {}

+ 12 - 0
script/sql/business/v2/create.sql

@@ -0,0 +1,12 @@
+CREATE TABLE `pet_system`.`sys_service_classification`
+(
+    `id`          bigint PRIMARY KEY NOT NULL COMMENT '序号',
+    `name`        varchar(128)       NOT NULL COMMENT '类型名称',
+    `sort`        int                NOT NULL DEFAULT 0 COMMENT '排序',
+    `remark`      varchar(512) COMMENT '备注',
+    `create_dept` bigint(20) COMMENT '创建部门',
+    `create_by`   bigint(20) COMMENT '创建者',
+    `create_time` datetime COMMENT '创建时间',
+    `update_by`   bigint(20) COMMENT '更新者',
+    `update_time` datetime COMMENT '更新时间'
+) ENGINE = innoDB COMMENT = '服务分类信息表';

+ 26 - 0
script/sql/business/v2/update.sql

@@ -0,0 +1,26 @@
+ALTER TABLE sys_service ADD COLUMN `introduction` text COMMENT '服务介绍';
+ALTER TABLE sys_service ADD COLUMN `order_instruction` text COMMENT '下单须知';
+
+-- 菜单 SQL
+insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark, platform_id)
+values(2037451324848697345, '服务分类', '2020736413590941698', '1', 'classification', 'service/classification/index', 1, 0, 'C', '0', '0', 'service:classification:list', '#', 103, 1, sysdate(), null, null, '服务分类菜单', 0);
+
+-- 按钮 SQL
+insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark, platform_id)
+values(2037451324848697346, '服务分类查询', 2037451324848697345, '1',  '#', '', 1, 0, 'F', '0', '0', 'service:classification:query',        '#', 103, 1, sysdate(), null, null, '', 0);
+
+insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark, platform_id)
+values(2037451324848697347, '服务分类新增', 2037451324848697345, '2',  '#', '', 1, 0, 'F', '0', '0', 'service:classification:add',          '#', 103, 1, sysdate(), null, null, '', 0);
+
+insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark, platform_id)
+values(2037451324848697348, '服务分类修改', 2037451324848697345, '3',  '#', '', 1, 0, 'F', '0', '0', 'service:classification:edit',         '#', 103, 1, sysdate(), null, null, '', 0);
+
+insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark, platform_id)
+values(2037451324848697349, '服务分类删除', 2037451324848697345, '4',  '#', '', 1, 0, 'F', '0', '0', 'service:classification:remove',       '#', 103, 1, sysdate(), null, null, '', 0);
+
+insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark, platform_id)
+values(2037451324848697350, '服务分类导出', 2037451324848697345, '5',  '#', '', 1, 0, 'F', '0', '0', 'service:classification:export',       '#', 103, 1, sysdate(), null, null, '', 0);
+
+ALTER TABLE sys_service ADD COLUMN classification_id bigint COMMENT '所属分类ID';
+
+UPDATE sys_menu SET parent_id = 2020736413590941698, menu_name = '服务列表', order_num = 2, path = 'list', component = 'service/list/index', is_frame = '1', is_cache = '0', menu_type = 'C', visible = '0', status = '0', perms = 'service:list:list', icon = 'service-list', remark = '服务列表菜单', platform_id = 0, create_dept = 103, create_time = '2026-02-25 13:44:44', update_by = 1, update_time = '2026-03-27 17:55:14' WHERE menu_id = 2026533580326912001