Commit 11dd407e1870ed19774036df9da7747001413641

Authored by YunaiV
1 parent a426cc2f

refactor: 移动端代码生成器列表页迁移为 z-paging

- 将 uniapp 列表模板切换为 z-paging 分页
- 支持返回列表自动刷新、顶部下拉刷新和底部自动加载更多
- 表单提交、详情删除后统一发送 reload 事件
- 详情页监听 reload 事件刷新数据,并增加删除中刷新守卫
- 新增 uniapp 代码生成器快照单测
Showing 24 changed files with 1621 additions and 77 deletions
yudao-module-infra/src/main/resources/codegen/vue3_admin_uniapp/components/search-form.vue.vm
... ... @@ -259,7 +259,7 @@ function handle${AttrName}1Confirm() {
259 259 #end
260 260 #end
261 261  
262   -/** 搜索 */
  262 +/** 搜索按钮操作 */
263 263 function handleSearch() {
264 264 visible.value = false
265 265 emit('search', {
... ... @@ -276,7 +276,7 @@ function handleSearch() {
276 276 })
277 277 }
278 278  
279   -/** 重置 */
  279 +/** 重置按钮操作 */
280 280 function handleReset() {
281 281 #foreach($column in $columns)
282 282 #if ($column.listOperation)
... ...
yudao-module-infra/src/main/resources/codegen/vue3_admin_uniapp/views/detail/index.vue.vm
... ... @@ -62,6 +62,7 @@
62 62 #end
63 63 #end
64 64 import type { ${simpleClassName} } from '@/api/${table.moduleName}/${table.businessName}'
  65 +import { onUnload } from '@dcloudio/uni-app'
65 66 import { onMounted, ref } from 'vue'
66 67 import { useToast } from 'wot-design-uni'
67 68 import { delete${simpleClassName}, get${simpleClassName} } from '@/api/${table.moduleName}/${table.businessName}'
... ... @@ -97,7 +98,7 @@ function handleBack() {
97 98  
98 99 /** 加载${table.classComment}详情 */
99 100 async function getDetail() {
100   - if (!props.id) {
  101 + if (!props.id || deleting.value) {
101 102 return
102 103 }
103 104 try {
... ... @@ -131,6 +132,7 @@ function handleDelete() {
131 132 try {
132 133 await delete${simpleClassName}(props.id)
133 134 toast.success('删除成功')
  135 + uni.$emit('${table.moduleName}:${table.businessName}:reload')
134 136 setTimeout(() => {
135 137 handleBack()
136 138 }, 500)
... ... @@ -143,8 +145,14 @@ function handleDelete() {
143 145  
144 146 /** 初始化 */
145 147 onMounted(() => {
  148 + uni.$on('${table.moduleName}:${table.businessName}:reload', getDetail)
146 149 getDetail()
147 150 })
  151 +
  152 +/** 卸载 */
  153 +onUnload(() => {
  154 + uni.$off('${table.moduleName}:${table.businessName}:reload', getDetail)
  155 +})
148 156 </script>
149 157  
150 158 <style lang="scss" scoped>
... ...
yudao-module-infra/src/main/resources/codegen/vue3_admin_uniapp/views/form/index.vue.vm
... ... @@ -236,6 +236,7 @@ async function handleSubmit() {
236 236 await create${simpleClassName}(formData.value)
237 237 toast.success('新增成功')
238 238 }
  239 + uni.$emit('${table.moduleName}:${table.businessName}:reload')
239 240 setTimeout(() => {
240 241 handleBack()
241 242 }, 500)
... ...
yudao-module-infra/src/main/resources/codegen/vue3_admin_uniapp/views/index.vue.vm
1 1 <template>
2   - <view class="yd-page-container">
  2 + <view class="yd-page-container yd-page-container-paging">
3 3 <!-- 顶部导航栏 -->
4 4 <wd-navbar
5 5 title="${table.classComment}管理"
... ... @@ -11,14 +11,26 @@
11 11 <SearchForm @search="handleQuery" @reset="handleReset" />
12 12  
13 13 <!-- ${table.classComment}列表 -->
14   - <view class="p-24rpx">
15   - <view
16   - v-for="item in list"
17   - :key="item.${primaryColumn.javaField}"
18   - class="mb-24rpx overflow-hidden rounded-12rpx bg-white shadow-sm"
19   - @click="handleDetail(item)"
20   - >
21   - <view class="p-24rpx">
  14 + <z-paging
  15 + ref="pagingRef"
  16 + v-model="list"
  17 + :fixed="false"
  18 + class="min-h-0 flex-1"
  19 + :default-page-size="10"
  20 + :refresher-enabled="true"
  21 + :inside-more="true"
  22 + :loading-more-default-as-loading="true"
  23 + empty-view-text="暂无${table.classComment}数据"
  24 + @query="queryList"
  25 + >
  26 + <view class="p-24rpx">
  27 + <view
  28 + v-for="item in list"
  29 + :key="item.${primaryColumn.javaField}"
  30 + class="mb-24rpx overflow-hidden rounded-12rpx bg-white shadow-sm"
  31 + @click="handleDetail(item)"
  32 + >
  33 + <view class="p-24rpx">
22 34 #set ($titleField = "")
23 35 #set ($statusField = "")
24 36 #set ($statusDictType = "")
... ... @@ -35,14 +47,14 @@
35 47 #if ($titleField == "")
36 48 #set ($titleField = $primaryColumn.javaField)
37 49 #end
38   - <view class="mb-16rpx flex items-center justify-between">
39   - <view class="text-32rpx text-[#333] font-semibold">
40   - {{ item.${titleField} }}
41   - </view>
  50 + <view class="mb-16rpx flex items-center justify-between">
  51 + <view class="text-32rpx text-[#333] font-semibold">
  52 + {{ item.${titleField} }}
  53 + </view>
42 54 #if($statusField != "")
43   - <dict-tag :type="DICT_TYPE.${statusDictType.toUpperCase()}" :value="item.${statusField}" />
  55 + <dict-tag :type="DICT_TYPE.${statusDictType.toUpperCase()}" :value="item.${statusField}" />
44 56 #end
45   - </view>
  57 + </view>
46 58 #foreach($column in $columns)
47 59 #if ($column.listOperationResult && !$column.primaryKey && $column.javaField != $titleField && $column.javaField != $statusField)
48 60 #set ($javaField = $column.javaField)
... ... @@ -50,36 +62,27 @@
50 62 #set ($dictType = $column.dictType)
51 63 #set ($javaType = $column.javaType)
52 64 #if ($dictType && "" != $dictType)
53   - <view class="mb-12rpx flex items-center text-28rpx text-[#666]">
54   - <text class="mr-8rpx text-[#999]">${comment}:</text>
55   - <dict-tag :type="DICT_TYPE.${dictType.toUpperCase()}" :value="item.${javaField}" />
56   - </view>
  65 + <view class="mb-12rpx flex items-center text-28rpx text-[#666]">
  66 + <text class="mr-8rpx text-[#999]">${comment}:</text>
  67 + <dict-tag :type="DICT_TYPE.${dictType.toUpperCase()}" :value="item.${javaField}" />
  68 + </view>
57 69 #elseif ($javaType == "LocalDateTime")
58   - <view class="mb-12rpx flex items-center text-28rpx text-[#666]">
59   - <text class="mr-8rpx text-[#999]">${comment}:</text>
60   - <text class="line-clamp-1">{{ formatDateTime(item.${javaField}) || '-' }}</text>
61   - </view>
  70 + <view class="mb-12rpx flex items-center text-28rpx text-[#666]">
  71 + <text class="mr-8rpx text-[#999]">${comment}:</text>
  72 + <text class="line-clamp-1">{{ formatDateTime(item.${javaField}) || '-' }}</text>
  73 + </view>
62 74 #else
63   - <view class="mb-12rpx flex items-center text-28rpx text-[#666]">
64   - <text class="mr-8rpx text-[#999]">${comment}:</text>
65   - <text class="line-clamp-1">{{ item.${javaField} }}</text>
66   - </view>
  75 + <view class="mb-12rpx flex items-center text-28rpx text-[#666]">
  76 + <text class="mr-8rpx text-[#999]">${comment}:</text>
  77 + <text class="line-clamp-1">{{ item.${javaField} }}</text>
  78 + </view>
67 79 #end
68 80 #end
69 81 #end
  82 + </view>
70 83 </view>
71 84 </view>
72   -
73   - <!-- 加载更多 -->
74   - <view v-if="loadMoreState !== 'loading' && list.length === 0" class="py-100rpx text-center">
75   - <wd-status-tip image="content" tip="暂无${table.classComment}数据" />
76   - </view>
77   - <wd-loadmore
78   - v-if="list.length > 0"
79   - :state="loadMoreState"
80   - @reload="loadMore"
81   - />
82   - </view>
  85 + </z-paging>
83 86  
84 87 <!-- 新增按钮 -->
85 88 <wd-fab
... ... @@ -108,8 +111,7 @@
108 111 #end
109 112 #end
110 113 import type { ${simpleClassName} } from '@/api/${table.moduleName}/${table.businessName}'
111   -import type { LoadMoreState } from '@/http/types'
112   -import { onReachBottom } from '@dcloudio/uni-app'
  114 +import { onUnload } from '@dcloudio/uni-app'
113 115 import { onMounted, ref } from 'vue'
114 116 import { get${simpleClassName}Page } from '@/api/${table.moduleName}/${table.businessName}'
115 117 import { useAccess } from '@/hooks/useAccess'
... ... @@ -130,13 +132,9 @@ definePage({
130 132 })
131 133  
132 134 const { hasAccessByCodes } = useAccess()
133   -const total = ref(0) // 列表总数
134 135 const list = ref<${simpleClassName}[]>([]) // 列表数据
135   -const loadMoreState = ref<LoadMoreState>('loading') // 分页加载状态
136   -const queryParams = ref({
137   - pageNo: 1,
138   - pageSize: 10,
139   -}) // 查询参数
  136 +const pagingRef = ref<any>() // 分页组件引用
  137 +const queryParams = ref<Record<string, any>>({}) // 查询参数
140 138  
141 139 /** 返回上一页 */
142 140 function handleBack() {
... ... @@ -144,28 +142,24 @@ function handleBack() {
144 142 }
145 143  
146 144 /** 查询${table.classComment}列表 */
147   -async function getList() {
148   - loadMoreState.value = 'loading'
  145 +async function queryList(pageNo: number, pageSize: number) {
149 146 try {
150   - const data = await get${simpleClassName}Page(queryParams.value)
151   - list.value = [...list.value, ...data.list]
152   - total.value = data.total
153   - loadMoreState.value = list.value.length >= total.value ? 'finished' : 'loading'
  147 + const params = {
  148 + ...queryParams.value,
  149 + pageNo,
  150 + pageSize,
  151 + }
  152 + const data = await get${simpleClassName}Page(params)
  153 + pagingRef.value?.completeByTotal(data.list, data.total)
154 154 } catch {
155   - queryParams.value.pageNo = queryParams.value.pageNo > 1 ? queryParams.value.pageNo - 1 : 1
156   - loadMoreState.value = 'error'
  155 + pagingRef.value?.complete(false)
157 156 }
158 157 }
159 158  
160 159 /** 搜索按钮操作 */
161 160 function handleQuery(data?: Record<string, any>) {
162   - queryParams.value = {
163   - ...data,
164   - pageNo: 1,
165   - pageSize: queryParams.value.pageSize,
166   - }
167   - list.value = []
168   - getList()
  161 + queryParams.value = { ...data }
  162 + reload()
169 163 }
170 164  
171 165 /** 重置按钮操作 */
... ... @@ -173,13 +167,9 @@ function handleReset() {
173 167 handleQuery()
174 168 }
175 169  
176   -/** 加载更多 */
177   -function loadMore() {
178   - if (loadMoreState.value === 'finished') {
179   - return
180   - }
181   - queryParams.value.pageNo++
182   - getList()
  170 +/** 重新加载 */
  171 +function reload() {
  172 + pagingRef.value?.reload()
183 173 }
184 174  
185 175 /** 新增${table.classComment} */
... ... @@ -196,14 +186,14 @@ function handleDetail(item: ${simpleClassName}) {
196 186 })
197 187 }
198 188  
199   -/** 触底加载更多 */
200   -onReachBottom(() => {
201   - loadMore()
202   -})
203   -
204 189 /** 初始化 */
205 190 onMounted(() => {
206   - getList()
  191 + uni.$on('${table.moduleName}:${table.businessName}:reload', reload)
  192 +})
  193 +
  194 +/** 卸载 */
  195 +onUnload(() => {
  196 + uni.$off('${table.moduleName}:${table.businessName}:reload', reload)
207 197 })
208 198 </script>
209 199  
... ...
yudao-module-infra/src/test/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenEngineUniappTest.java 0 → 100644
  1 +package cn.iocoder.yudao.module.infra.service.codegen.inner;
  2 +
  3 +import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenColumnDO;
  4 +import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenTableDO;
  5 +import cn.iocoder.yudao.module.infra.enums.codegen.CodegenFrontTypeEnum;
  6 +import cn.iocoder.yudao.module.infra.enums.codegen.CodegenTemplateTypeEnum;
  7 +import com.baomidou.mybatisplus.annotation.DbType;
  8 +import org.junit.jupiter.api.Test;
  9 +
  10 +import java.util.List;
  11 +import java.util.Map;
  12 +
  13 +/**
  14 + * {@link CodegenEngine} 的 Vue3 Admin Uniapp + Wot UI 单元测试
  15 + *
  16 + * @author 芋道源码
  17 + */
  18 +public class CodegenEngineUniappTest extends CodegenEngineAbstractTest {
  19 +
  20 + @Test
  21 + public void testExecute_one() {
  22 + // 准备参数
  23 + CodegenTableDO table = getTable("student")
  24 + .setFrontType(CodegenFrontTypeEnum.VUE3_ADMIN_UNIAPP_WOT.getType())
  25 + .setTemplateType(CodegenTemplateTypeEnum.ONE.getType());
  26 + List<CodegenColumnDO> columns = getColumnList("student");
  27 +
  28 + // 调用
  29 + Map<String, String> result = codegenEngine.execute(DbType.MYSQL, table, columns, null, null);
  30 + // 断言
  31 + assertResult(result, "/vue3_admin_uniapp_one");
  32 + }
  33 +
  34 +}
... ...
yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_one/assert.json 0 → 100644
  1 +[ {
  2 + "contentPath" : "java/InfraStudentPageReqVO",
  3 + "filePath" : "yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/vo/InfraStudentPageReqVO.java"
  4 +}, {
  5 + "contentPath" : "java/InfraStudentRespVO",
  6 + "filePath" : "yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/vo/InfraStudentRespVO.java"
  7 +}, {
  8 + "contentPath" : "java/InfraStudentSaveReqVO",
  9 + "filePath" : "yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/vo/InfraStudentSaveReqVO.java"
  10 +}, {
  11 + "contentPath" : "java/InfraStudentController",
  12 + "filePath" : "yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/InfraStudentController.java"
  13 +}, {
  14 + "contentPath" : "java/InfraStudentDO",
  15 + "filePath" : "yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/demo/InfraStudentDO.java"
  16 +}, {
  17 + "contentPath" : "java/InfraStudentMapper",
  18 + "filePath" : "yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/demo/InfraStudentMapper.java"
  19 +}, {
  20 + "contentPath" : "xml/InfraStudentMapper",
  21 + "filePath" : "yudao-module-infra/src/main/resources/mapper/demo/InfraStudentMapper.xml"
  22 +}, {
  23 + "contentPath" : "java/InfraStudentServiceImpl",
  24 + "filePath" : "yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/service/demo/InfraStudentServiceImpl.java"
  25 +}, {
  26 + "contentPath" : "java/InfraStudentService",
  27 + "filePath" : "yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/service/demo/InfraStudentService.java"
  28 +}, {
  29 + "contentPath" : "java/InfraStudentServiceImplTest",
  30 + "filePath" : "yudao-module-infra/src/test/java/cn/iocoder/yudao/module/infra/service/demo/InfraStudentServiceImplTest.java"
  31 +}, {
  32 + "contentPath" : "java/ErrorCodeConstants_手动操作",
  33 + "filePath" : "yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/enums/ErrorCodeConstants_手动操作.java"
  34 +}, {
  35 + "contentPath" : "sql/sql",
  36 + "filePath" : "sql/sql.sql"
  37 +}, {
  38 + "contentPath" : "sql/h2",
  39 + "filePath" : "sql/h2.sql"
  40 +}, {
  41 + "contentPath" : "ts/index",
  42 + "filePath" : "yudao-ui-admin-uniapp/src/api/infra/demo/index.ts"
  43 +}, {
  44 + "contentPath" : "vue/index",
  45 + "filePath" : "yudao-ui-admin-uniapp/src/pages-infra/demo/index.vue"
  46 +}, {
  47 + "contentPath" : "vue/search-form",
  48 + "filePath" : "yudao-ui-admin-uniapp/src/pages-infra/demo/components/search-form.vue"
  49 +}, {
  50 + "contentPath" : "vue/form/index",
  51 + "filePath" : "yudao-ui-admin-uniapp/src/pages-infra/demo/form/index.vue"
  52 +}, {
  53 + "contentPath" : "vue/detail/index",
  54 + "filePath" : "yudao-ui-admin-uniapp/src/pages-infra/demo/detail/index.vue"
  55 +} ]
0 56 \ No newline at end of file
... ...
yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_one/java/ErrorCodeConstants_手动操作 0 → 100644
  1 +// TODO 待办:请将下面的错误码复制到 yudao-module-infra 模块的 ErrorCodeConstants 类中。注意,请给“TODO 补充编号”设置一个错误码编号!!!
  2 +// ========== 学生 TODO 补充编号 ==========
  3 +ErrorCode STUDENT_NOT_EXISTS = new ErrorCode(TODO 补充编号, "学生不存在");
0 4 \ No newline at end of file
... ...
yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_one/java/InfraStudentController 0 → 100644
  1 +package cn.iocoder.yudao.module.infra.controller.admin.demo;
  2 +
  3 +import org.springframework.web.bind.annotation.*;
  4 +import jakarta.annotation.Resource;
  5 +import org.springframework.validation.annotation.Validated;
  6 +import org.springframework.security.access.prepost.PreAuthorize;
  7 +import io.swagger.v3.oas.annotations.tags.Tag;
  8 +import io.swagger.v3.oas.annotations.Parameter;
  9 +import io.swagger.v3.oas.annotations.Operation;
  10 +
  11 +import jakarta.validation.constraints.*;
  12 +import jakarta.validation.*;
  13 +import jakarta.servlet.http.*;
  14 +import java.util.*;
  15 +import java.io.IOException;
  16 +
  17 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  18 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  19 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  20 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  21 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  22 +
  23 +import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
  24 +
  25 +import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
  26 +import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.*;
  27 +
  28 +import cn.iocoder.yudao.module.infra.controller.admin.demo.vo.*;
  29 +import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentDO;
  30 +import cn.iocoder.yudao.module.infra.service.demo.InfraStudentService;
  31 +
  32 +@Tag(name = "管理后台 - 学生")
  33 +@RestController
  34 +@RequestMapping("/infra/student")
  35 +@Validated
  36 +public class InfraStudentController {
  37 +
  38 + @Resource
  39 + private InfraStudentService studentService;
  40 +
  41 + @PostMapping("/create")
  42 + @Operation(summary = "创建学生")
  43 + @PreAuthorize("@ss.hasPermission('infra:student:create')")
  44 + public CommonResult<Long> createStudent(@Valid @RequestBody InfraStudentSaveReqVO createReqVO) {
  45 + return success(studentService.createStudent(createReqVO));
  46 + }
  47 +
  48 + @PutMapping("/update")
  49 + @Operation(summary = "更新学生")
  50 + @PreAuthorize("@ss.hasPermission('infra:student:update')")
  51 + public CommonResult<Boolean> updateStudent(@Valid @RequestBody InfraStudentSaveReqVO updateReqVO) {
  52 + studentService.updateStudent(updateReqVO);
  53 + return success(true);
  54 + }
  55 +
  56 + @DeleteMapping("/delete")
  57 + @Operation(summary = "删除学生")
  58 + @Parameter(name = "id", description = "编号", required = true)
  59 + @PreAuthorize("@ss.hasPermission('infra:student:delete')")
  60 + public CommonResult<Boolean> deleteStudent(@RequestParam("id") Long id) {
  61 + studentService.deleteStudent(id);
  62 + return success(true);
  63 + }
  64 +
  65 + @DeleteMapping("/delete-list")
  66 + @Parameter(name = "ids", description = "编号", required = true)
  67 + @Operation(summary = "批量删除学生")
  68 + @PreAuthorize("@ss.hasPermission('infra:student:delete')")
  69 + public CommonResult<Boolean> deleteStudentList(@RequestParam("ids") List<Long> ids) {
  70 + studentService.deleteStudentListByIds(ids);
  71 + return success(true);
  72 + }
  73 +
  74 + @GetMapping("/get")
  75 + @Operation(summary = "获得学生")
  76 + @Parameter(name = "id", description = "编号", required = true, example = "1024")
  77 + @PreAuthorize("@ss.hasPermission('infra:student:query')")
  78 + public CommonResult<InfraStudentRespVO> getStudent(@RequestParam("id") Long id) {
  79 + InfraStudentDO student = studentService.getStudent(id);
  80 + return success(BeanUtils.toBean(student, InfraStudentRespVO.class));
  81 + }
  82 +
  83 + @GetMapping("/page")
  84 + @Operation(summary = "获得学生分页")
  85 + @PreAuthorize("@ss.hasPermission('infra:student:query')")
  86 + public CommonResult<PageResult<InfraStudentRespVO>> getStudentPage(@Valid InfraStudentPageReqVO pageReqVO) {
  87 + PageResult<InfraStudentDO> pageResult = studentService.getStudentPage(pageReqVO);
  88 + return success(BeanUtils.toBean(pageResult, InfraStudentRespVO.class));
  89 + }
  90 +
  91 + @GetMapping("/export-excel")
  92 + @Operation(summary = "导出学生 Excel")
  93 + @PreAuthorize("@ss.hasPermission('infra:student:export')")
  94 + @ApiAccessLog(operateType = EXPORT)
  95 + public void exportStudentExcel(@Valid InfraStudentPageReqVO pageReqVO,
  96 + HttpServletResponse response) throws IOException {
  97 + pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
  98 + List<InfraStudentDO> list = studentService.getStudentPage(pageReqVO).getList();
  99 + // 导出 Excel
  100 + ExcelUtils.write(response, "学生.xls", "数据", InfraStudentRespVO.class,
  101 + BeanUtils.toBean(list, InfraStudentRespVO.class));
  102 + }
  103 +
  104 +}
0 105 \ No newline at end of file
... ...
yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_one/java/InfraStudentDO 0 → 100644
  1 +package cn.iocoder.yudao.module.infra.dal.dataobject.demo;
  2 +
  3 +import lombok.*;
  4 +import java.util.*;
  5 +import java.time.LocalDateTime;
  6 +import java.time.LocalDateTime;
  7 +import com.baomidou.mybatisplus.annotation.*;
  8 +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
  9 +
  10 +/**
  11 + * 学生 DO
  12 + *
  13 + * @author 芋道源码
  14 + */
  15 +@TableName("infra_student")
  16 +@KeySequence("infra_student_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
  17 +@Data
  18 +@EqualsAndHashCode(callSuper = true)
  19 +@ToString(callSuper = true)
  20 +@Builder
  21 +@NoArgsConstructor
  22 +@AllArgsConstructor
  23 +public class InfraStudentDO extends BaseDO {
  24 +
  25 + /**
  26 + * 编号
  27 + */
  28 + @TableId
  29 + private Long id;
  30 + /**
  31 + * 名字
  32 + */
  33 + private String name;
  34 + /**
  35 + * 简介
  36 + */
  37 + private String description;
  38 + /**
  39 + * 出生日期
  40 + */
  41 + private LocalDateTime birthday;
  42 + /**
  43 + * 性别
  44 + *
  45 + * 枚举 {@link TODO system_user_sex 对应的类}
  46 + */
  47 + private Integer sex;
  48 + /**
  49 + * 是否有效
  50 + *
  51 + * 枚举 {@link TODO infra_boolean_string 对应的类}
  52 + */
  53 + private Boolean enabled;
  54 + /**
  55 + * 头像
  56 + */
  57 + private String avatar;
  58 + /**
  59 + * 附件
  60 + */
  61 + private String video;
  62 + /**
  63 + * 备注
  64 + */
  65 + private String memo;
  66 +
  67 +
  68 +}
0 69 \ No newline at end of file
... ...
yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_one/java/InfraStudentMapper 0 → 100644
  1 +package cn.iocoder.yudao.module.infra.dal.mysql.demo;
  2 +
  3 +import java.util.*;
  4 +
  5 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  6 +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
  7 +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
  8 +import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentDO;
  9 +import org.apache.ibatis.annotations.Mapper;
  10 +import cn.iocoder.yudao.module.infra.controller.admin.demo.vo.*;
  11 +
  12 +/**
  13 + * 学生 Mapper
  14 + *
  15 + * @author 芋道源码
  16 + */
  17 +@Mapper
  18 +public interface InfraStudentMapper extends BaseMapperX<InfraStudentDO> {
  19 +
  20 + default PageResult<InfraStudentDO> selectPage(InfraStudentPageReqVO reqVO) {
  21 + return selectPage(reqVO, new LambdaQueryWrapperX<InfraStudentDO>()
  22 + .likeIfPresent(InfraStudentDO::getName, reqVO.getName())
  23 + .eqIfPresent(InfraStudentDO::getBirthday, reqVO.getBirthday())
  24 + .eqIfPresent(InfraStudentDO::getSex, reqVO.getSex())
  25 + .eqIfPresent(InfraStudentDO::getEnabled, reqVO.getEnabled())
  26 + .betweenIfPresent(InfraStudentDO::getCreateTime, reqVO.getCreateTime())
  27 + .orderByDesc(InfraStudentDO::getId));
  28 + }
  29 +
  30 +}
0 31 \ No newline at end of file
... ...
yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_one/java/InfraStudentPageReqVO 0 → 100644
  1 +package cn.iocoder.yudao.module.infra.controller.admin.demo.vo;
  2 +
  3 +import lombok.*;
  4 +import java.util.*;
  5 +import io.swagger.v3.oas.annotations.media.Schema;
  6 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  7 +import org.springframework.format.annotation.DateTimeFormat;
  8 +import java.time.LocalDateTime;
  9 +
  10 +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
  11 +
  12 +@Schema(description = "管理后台 - 学生分页 Request VO")
  13 +@Data
  14 +public class InfraStudentPageReqVO extends PageParam {
  15 +
  16 + @Schema(description = "名字", example = "芋头")
  17 + private String name;
  18 +
  19 + @Schema(description = "出生日期")
  20 + private LocalDateTime birthday;
  21 +
  22 + @Schema(description = "性别", example = "1")
  23 + private Integer sex;
  24 +
  25 + @Schema(description = "是否有效", example = "true")
  26 + private Boolean enabled;
  27 +
  28 + @Schema(description = "创建时间")
  29 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  30 + private LocalDateTime[] createTime;
  31 +
  32 +}
0 33 \ No newline at end of file
... ...
yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_one/java/InfraStudentRespVO 0 → 100644
  1 +package cn.iocoder.yudao.module.infra.controller.admin.demo.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.*;
  5 +import java.util.*;
  6 +import org.springframework.format.annotation.DateTimeFormat;
  7 +import java.time.LocalDateTime;
  8 +import cn.idev.excel.annotation.*;
  9 +import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
  10 +import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
  11 +
  12 +@Schema(description = "管理后台 - 学生 Response VO")
  13 +@Data
  14 +@ExcelIgnoreUnannotated
  15 +public class InfraStudentRespVO {
  16 +
  17 + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  18 + @ExcelProperty("编号")
  19 + private Long id;
  20 +
  21 + @Schema(description = "名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋头")
  22 + @ExcelProperty("名字")
  23 + private String name;
  24 +
  25 + @Schema(description = "简介", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是介绍")
  26 + @ExcelProperty("简介")
  27 + private String description;
  28 +
  29 + @Schema(description = "出生日期", requiredMode = Schema.RequiredMode.REQUIRED)
  30 + @ExcelProperty("出生日期")
  31 + private LocalDateTime birthday;
  32 +
  33 + @Schema(description = "性别", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  34 + @ExcelProperty(value = "性别", converter = DictConvert.class)
  35 + @DictFormat("system_user_sex") // TODO 代码优化:建议设置到对应的 DictTypeConstants 枚举类中
  36 + private Integer sex;
  37 +
  38 + @Schema(description = "是否有效", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
  39 + @ExcelProperty(value = "是否有效", converter = DictConvert.class)
  40 + @DictFormat("infra_boolean_string") // TODO 代码优化:建议设置到对应的 DictTypeConstants 枚举类中
  41 + private Boolean enabled;
  42 +
  43 + @Schema(description = "头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/1.png")
  44 + @ExcelProperty("头像")
  45 + private String avatar;
  46 +
  47 + @Schema(description = "附件", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/1.mp4")
  48 + @ExcelProperty("附件")
  49 + private String video;
  50 +
  51 + @Schema(description = "备注", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是备注")
  52 + @ExcelProperty("备注")
  53 + private String memo;
  54 +
  55 + @Schema(description = "创建时间")
  56 + @ExcelProperty("创建时间")
  57 + private LocalDateTime createTime;
  58 +
  59 +}
0 60 \ No newline at end of file
... ...
yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_one/java/InfraStudentSaveReqVO 0 → 100644
  1 +package cn.iocoder.yudao.module.infra.controller.admin.demo.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.*;
  5 +import java.util.*;
  6 +import jakarta.validation.constraints.*;
  7 +import org.springframework.format.annotation.DateTimeFormat;
  8 +import java.time.LocalDateTime;
  9 +
  10 +@Schema(description = "管理后台 - 学生新增/修改 Request VO")
  11 +@Data
  12 +public class InfraStudentSaveReqVO {
  13 +
  14 + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  15 + private Long id;
  16 +
  17 + @Schema(description = "名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋头")
  18 + @NotEmpty(message = "名字不能为空")
  19 + private String name;
  20 +
  21 + @Schema(description = "简介", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是介绍")
  22 + @NotEmpty(message = "简介不能为空")
  23 + private String description;
  24 +
  25 + @Schema(description = "出生日期", requiredMode = Schema.RequiredMode.REQUIRED)
  26 + @NotNull(message = "出生日期不能为空")
  27 + private LocalDateTime birthday;
  28 +
  29 + @Schema(description = "性别", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  30 + @NotNull(message = "性别不能为空")
  31 + private Integer sex;
  32 +
  33 + @Schema(description = "是否有效", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
  34 + @NotNull(message = "是否有效不能为空")
  35 + private Boolean enabled;
  36 +
  37 + @Schema(description = "头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/1.png")
  38 + @NotEmpty(message = "头像不能为空")
  39 + private String avatar;
  40 +
  41 + @Schema(description = "附件", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/1.mp4")
  42 + @NotEmpty(message = "附件不能为空")
  43 + private String video;
  44 +
  45 + @Schema(description = "备注", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是备注")
  46 + @NotEmpty(message = "备注不能为空")
  47 + private String memo;
  48 +
  49 +}
0 50 \ No newline at end of file
... ...
yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_one/java/InfraStudentService 0 → 100644
  1 +package cn.iocoder.yudao.module.infra.service.demo;
  2 +
  3 +import java.util.*;
  4 +import jakarta.validation.*;
  5 +import cn.iocoder.yudao.module.infra.controller.admin.demo.vo.*;
  6 +import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentDO;
  7 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  8 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  9 +
  10 +/**
  11 + * 学生 Service 接口
  12 + *
  13 + * @author 芋道源码
  14 + */
  15 +public interface InfraStudentService {
  16 +
  17 + /**
  18 + * 创建学生
  19 + *
  20 + * @param createReqVO 创建信息
  21 + * @return 编号
  22 + */
  23 + Long createStudent(@Valid InfraStudentSaveReqVO createReqVO);
  24 +
  25 + /**
  26 + * 更新学生
  27 + *
  28 + * @param updateReqVO 更新信息
  29 + */
  30 + void updateStudent(@Valid InfraStudentSaveReqVO updateReqVO);
  31 +
  32 + /**
  33 + * 删除学生
  34 + *
  35 + * @param id 编号
  36 + */
  37 + void deleteStudent(Long id);
  38 +
  39 + /**
  40 + * 批量删除学生
  41 + *
  42 + * @param ids 编号
  43 + */
  44 + void deleteStudentListByIds(List<Long> ids);
  45 +
  46 + /**
  47 + * 获得学生
  48 + *
  49 + * @param id 编号
  50 + * @return 学生
  51 + */
  52 + InfraStudentDO getStudent(Long id);
  53 +
  54 + /**
  55 + * 获得学生分页
  56 + *
  57 + * @param pageReqVO 分页查询
  58 + * @return 学生分页
  59 + */
  60 + PageResult<InfraStudentDO> getStudentPage(InfraStudentPageReqVO pageReqVO);
  61 +
  62 +}
0 63 \ No newline at end of file
... ...
yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_one/java/InfraStudentServiceImpl 0 → 100644
  1 +package cn.iocoder.yudao.module.infra.service.demo;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import org.springframework.stereotype.Service;
  5 +import jakarta.annotation.Resource;
  6 +import org.springframework.validation.annotation.Validated;
  7 +import org.springframework.transaction.annotation.Transactional;
  8 +
  9 +import java.util.*;
  10 +import cn.iocoder.yudao.module.infra.controller.admin.demo.vo.*;
  11 +import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentDO;
  12 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  13 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  14 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  15 +
  16 +import cn.iocoder.yudao.module.infra.dal.mysql.demo.InfraStudentMapper;
  17 +
  18 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  19 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
  20 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.diffList;
  21 +import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.*;
  22 +
  23 +/**
  24 + * 学生 Service 实现类
  25 + *
  26 + * @author 芋道源码
  27 + */
  28 +@Service
  29 +@Validated
  30 +public class InfraStudentServiceImpl implements InfraStudentService {
  31 +
  32 + @Resource
  33 + private InfraStudentMapper studentMapper;
  34 +
  35 + @Override
  36 + public Long createStudent(InfraStudentSaveReqVO createReqVO) {
  37 + // 插入
  38 + InfraStudentDO student = BeanUtils.toBean(createReqVO, InfraStudentDO.class);
  39 + studentMapper.insert(student);
  40 +
  41 + // 返回
  42 + return student.getId();
  43 + }
  44 +
  45 + @Override
  46 + public void updateStudent(InfraStudentSaveReqVO updateReqVO) {
  47 + // 校验存在
  48 + validateStudentExists(updateReqVO.getId());
  49 + // 更新
  50 + InfraStudentDO updateObj = BeanUtils.toBean(updateReqVO, InfraStudentDO.class);
  51 + studentMapper.updateById(updateObj);
  52 + }
  53 +
  54 + @Override
  55 + public void deleteStudent(Long id) {
  56 + // 校验存在
  57 + validateStudentExists(id);
  58 + // 删除
  59 + studentMapper.deleteById(id);
  60 + }
  61 +
  62 + @Override
  63 + public void deleteStudentListByIds(List<Long> ids) {
  64 + // 删除
  65 + studentMapper.deleteByIds(ids);
  66 + }
  67 +
  68 +
  69 + private void validateStudentExists(Long id) {
  70 + if (studentMapper.selectById(id) == null) {
  71 + throw exception(STUDENT_NOT_EXISTS);
  72 + }
  73 + }
  74 +
  75 + @Override
  76 + public InfraStudentDO getStudent(Long id) {
  77 + return studentMapper.selectById(id);
  78 + }
  79 +
  80 + @Override
  81 + public PageResult<InfraStudentDO> getStudentPage(InfraStudentPageReqVO pageReqVO) {
  82 + return studentMapper.selectPage(pageReqVO);
  83 + }
  84 +
  85 +}
0 86 \ No newline at end of file
... ...
yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_one/java/InfraStudentServiceImplTest 0 → 100644
  1 +package cn.iocoder.yudao.module.infra.service.demo;
  2 +
  3 +import org.junit.jupiter.api.Disabled;
  4 +import org.junit.jupiter.api.Test;
  5 +
  6 +import jakarta.annotation.Resource;
  7 +
  8 +import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
  9 +
  10 +import cn.iocoder.yudao.module.infra.controller.admin.demo.vo.*;
  11 +import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentDO;
  12 +import cn.iocoder.yudao.module.infra.dal.mysql.demo.InfraStudentMapper;
  13 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  14 +
  15 +import jakarta.annotation.Resource;
  16 +import org.springframework.context.annotation.Import;
  17 +import java.util.*;
  18 +import java.time.LocalDateTime;
  19 +
  20 +import static cn.hutool.core.util.RandomUtil.*;
  21 +import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.*;
  22 +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.*;
  23 +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
  24 +import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.*;
  25 +import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.*;
  26 +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.*;
  27 +import static org.junit.jupiter.api.Assertions.*;
  28 +import static org.mockito.Mockito.*;
  29 +
  30 +/**
  31 + * {@link InfraStudentServiceImpl} 的单元测试类
  32 + *
  33 + * @author 芋道源码
  34 + */
  35 +@Import(InfraStudentServiceImpl.class)
  36 +public class InfraStudentServiceImplTest extends BaseDbUnitTest {
  37 +
  38 + @Resource
  39 + private InfraStudentServiceImpl studentService;
  40 +
  41 + @Resource
  42 + private InfraStudentMapper studentMapper;
  43 +
  44 + @Test
  45 + public void testCreateStudent_success() {
  46 + // 准备参数
  47 + InfraStudentSaveReqVO createReqVO = randomPojo(InfraStudentSaveReqVO.class).setId(null);
  48 +
  49 + // 调用
  50 + Long studentId = studentService.createStudent(createReqVO);
  51 + // 断言
  52 + assertNotNull(studentId);
  53 + // 校验记录的属性是否正确
  54 + InfraStudentDO student = studentMapper.selectById(studentId);
  55 + assertPojoEquals(createReqVO, student, "id");
  56 + }
  57 +
  58 + @Test
  59 + public void testUpdateStudent_success() {
  60 + // mock 数据
  61 + InfraStudentDO dbStudent = randomPojo(InfraStudentDO.class);
  62 + studentMapper.insert(dbStudent);// @Sql: 先插入出一条存在的数据
  63 + // 准备参数
  64 + InfraStudentSaveReqVO updateReqVO = randomPojo(InfraStudentSaveReqVO.class, o -> {
  65 + o.setId(dbStudent.getId()); // 设置更新的 ID
  66 + });
  67 +
  68 + // 调用
  69 + studentService.updateStudent(updateReqVO);
  70 + // 校验是否更新正确
  71 + InfraStudentDO student = studentMapper.selectById(updateReqVO.getId()); // 获取最新的
  72 + assertPojoEquals(updateReqVO, student);
  73 + }
  74 +
  75 + @Test
  76 + public void testUpdateStudent_notExists() {
  77 + // 准备参数
  78 + InfraStudentSaveReqVO updateReqVO = randomPojo(InfraStudentSaveReqVO.class);
  79 +
  80 + // 调用, 并断言异常
  81 + assertServiceException(() -> studentService.updateStudent(updateReqVO), STUDENT_NOT_EXISTS);
  82 + }
  83 +
  84 + @Test
  85 + public void testDeleteStudent_success() {
  86 + // mock 数据
  87 + InfraStudentDO dbStudent = randomPojo(InfraStudentDO.class);
  88 + studentMapper.insert(dbStudent);// @Sql: 先插入出一条存在的数据
  89 + // 准备参数
  90 + Long id = dbStudent.getId();
  91 +
  92 + // 调用
  93 + studentService.deleteStudent(id);
  94 + // 校验数据不存在了
  95 + assertNull(studentMapper.selectById(id));
  96 + }
  97 +
  98 + @Test
  99 + public void testDeleteStudent_notExists() {
  100 + // 准备参数
  101 + Long id = randomLongId();
  102 +
  103 + // 调用, 并断言异常
  104 + assertServiceException(() -> studentService.deleteStudent(id), STUDENT_NOT_EXISTS);
  105 + }
  106 +
  107 + @Test
  108 + @Disabled // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解
  109 + public void testGetStudentPage() {
  110 + // mock 数据
  111 + InfraStudentDO dbStudent = randomPojo(InfraStudentDO.class, o -> { // 等会查询到
  112 + o.setName(null);
  113 + o.setBirthday(null);
  114 + o.setSex(null);
  115 + o.setEnabled(null);
  116 + o.setCreateTime(null);
  117 + });
  118 + studentMapper.insert(dbStudent);
  119 + // 测试 name 不匹配
  120 + studentMapper.insert(cloneIgnoreId(dbStudent, o -> o.setName(null)));
  121 + // 测试 birthday 不匹配
  122 + studentMapper.insert(cloneIgnoreId(dbStudent, o -> o.setBirthday(null)));
  123 + // 测试 sex 不匹配
  124 + studentMapper.insert(cloneIgnoreId(dbStudent, o -> o.setSex(null)));
  125 + // 测试 enabled 不匹配
  126 + studentMapper.insert(cloneIgnoreId(dbStudent, o -> o.setEnabled(null)));
  127 + // 测试 createTime 不匹配
  128 + studentMapper.insert(cloneIgnoreId(dbStudent, o -> o.setCreateTime(null)));
  129 + // 准备参数
  130 + InfraStudentPageReqVO reqVO = new InfraStudentPageReqVO();
  131 + reqVO.setName(null);
  132 + reqVO.setBirthday(null);
  133 + reqVO.setSex(null);
  134 + reqVO.setEnabled(null);
  135 + reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28));
  136 +
  137 + // 调用
  138 + PageResult<InfraStudentDO> pageResult = studentService.getStudentPage(reqVO);
  139 + // 断言
  140 + assertEquals(1, pageResult.getTotal());
  141 + assertEquals(1, pageResult.getList().size());
  142 + assertPojoEquals(dbStudent, pageResult.getList().get(0));
  143 + }
  144 +
  145 +}
0 146 \ No newline at end of file
... ...
yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_one/sql/h2 0 → 100644
  1 +-- 将该建表 SQL 语句,添加到 yudao-module-infra-biz 模块的 test/resources/sql/create_tables.sql 文件里
  2 +CREATE TABLE IF NOT EXISTS "infra_student" (
  3 + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
  4 + "name" varchar NOT NULL,
  5 + "description" varchar NOT NULL,
  6 + "birthday" varchar NOT NULL,
  7 + "sex" int NOT NULL,
  8 + "enabled" bit NOT NULL,
  9 + "avatar" varchar NOT NULL,
  10 + "video" varchar NOT NULL,
  11 + "memo" varchar NOT NULL,
  12 + "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  13 + PRIMARY KEY ("id")
  14 +) COMMENT '学生表';
  15 +
  16 +-- 将该删表 SQL 语句,添加到 yudao-module-infra-biz 模块的 test/resources/sql/clean.sql 文件里
  17 +DELETE FROM "infra_student";
0 18 \ No newline at end of file
... ...
yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_one/sql/sql 0 → 100644
  1 +-- 菜单 SQL
  2 +INSERT INTO system_menu(
  3 + name, permission, type, sort, parent_id,
  4 + path, icon, component, status, component_name
  5 +)
  6 +VALUES (
  7 + '学生管理', '', 2, 0, 888,
  8 + 'student', '', 'infra/demo/index', 0, 'InfraStudent'
  9 +);
  10 +
  11 +-- 按钮父菜单ID
  12 +SELECT @parentId := LAST_INSERT_ID();
  13 +
  14 +-- 按钮 SQL
  15 +INSERT INTO system_menu(
  16 + name, permission, type, sort, parent_id,
  17 + path, icon, component, status
  18 +)
  19 +VALUES (
  20 + '学生查询', 'infra:student:query', 3, 1, @parentId,
  21 + '', '', '', 0
  22 +);
  23 +INSERT INTO system_menu(
  24 + name, permission, type, sort, parent_id,
  25 + path, icon, component, status
  26 +)
  27 +VALUES (
  28 + '学生创建', 'infra:student:create', 3, 2, @parentId,
  29 + '', '', '', 0
  30 +);
  31 +INSERT INTO system_menu(
  32 + name, permission, type, sort, parent_id,
  33 + path, icon, component, status
  34 +)
  35 +VALUES (
  36 + '学生更新', 'infra:student:update', 3, 3, @parentId,
  37 + '', '', '', 0
  38 +);
  39 +INSERT INTO system_menu(
  40 + name, permission, type, sort, parent_id,
  41 + path, icon, component, status
  42 +)
  43 +VALUES (
  44 + '学生删除', 'infra:student:delete', 3, 4, @parentId,
  45 + '', '', '', 0
  46 +);
  47 +INSERT INTO system_menu(
  48 + name, permission, type, sort, parent_id,
  49 + path, icon, component, status
  50 +)
  51 +VALUES (
  52 + '学生导出', 'infra:student:export', 3, 5, @parentId,
  53 + '', '', '', 0
  54 +);
0 55 \ No newline at end of file
... ...
yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_one/ts/index 0 → 100644
  1 +import type { PageParam, PageResult } from '@/http/types'
  2 +import { http } from '@/http/http'
  3 +
  4 +/** 学生信息 */
  5 +export interface Student {
  6 + id?: number
  7 + name: string
  8 + description: string
  9 + birthday: Date
  10 + sex: number
  11 + enabled: boolean
  12 + avatar: string
  13 + video: string
  14 + memo: string
  15 + createTime?: Date
  16 +}
  17 +
  18 +/** 获取学生分页列表 */
  19 +export function getStudentPage(params: PageParam) {
  20 + return http.get<PageResult<Student>>('/infra/student/page', params)
  21 +}
  22 +
  23 +/** 获取学生详情 */
  24 +export function getStudent(id: number) {
  25 + return http.get<Student>(`/infra/student/get?id=${id}`)
  26 +}
  27 +
  28 +/** 创建学生 */
  29 +export function createStudent(data: Student) {
  30 + return http.post<number>('/infra/student/create', data)
  31 +}
  32 +
  33 +/** 更新学生 */
  34 +export function updateStudent(data: Student) {
  35 + return http.put<boolean>('/infra/student/update', data)
  36 +}
  37 +
  38 +/** 删除学生 */
  39 +export function deleteStudent(id: number) {
  40 + return http.delete<boolean>(`/infra/student/delete?id=${id}`)
  41 +}
0 42 \ No newline at end of file
... ...
yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_one/vue/detail/index 0 → 100644
  1 +<template>
  2 + <view class="yd-page-container">
  3 + <!-- 顶部导航栏 -->
  4 + <wd-navbar
  5 + title="学生详情"
  6 + left-arrow placeholder safe-area-inset-top fixed
  7 + @click-left="handleBack"
  8 + />
  9 +
  10 + <!-- 详情内容 -->
  11 + <view>
  12 + <wd-cell-group border>
  13 + <wd-cell title="编号" :value="formData?.id ?? '-'" />
  14 + <wd-cell title="名字" :value="formData?.name ?? '-'" />
  15 + <wd-cell title="简介" :value="formData?.description ?? '-'" />
  16 + <wd-cell title="出生日期" :value="formatDateTime(formData?.birthday) || '-'" />
  17 + <wd-cell title="性别">
  18 + <dict-tag :type="DICT_TYPE.SYSTEM_USER_SEX" :value="formData?.sex" />
  19 + </wd-cell>
  20 + <wd-cell title="是否有效">
  21 + <dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="formData?.enabled" />
  22 + </wd-cell>
  23 + <wd-cell title="头像" :value="formData?.avatar ?? '-'" />
  24 + <wd-cell title="附件" :value="formData?.video ?? '-'" />
  25 + <wd-cell title="备注" :value="formData?.memo ?? '-'" />
  26 + <wd-cell title="创建时间" :value="formatDateTime(formData?.createTime) || '-'" />
  27 + </wd-cell-group>
  28 + </view>
  29 +
  30 + <!-- 底部操作按钮 -->
  31 + <view class="yd-detail-footer">
  32 + <view class="yd-detail-footer-actions">
  33 + <wd-button
  34 + v-if="hasAccessByCodes(['infra:student:update'])"
  35 + class="flex-1" type="warning" @click="handleEdit"
  36 + >
  37 + 编辑
  38 + </wd-button>
  39 + <wd-button
  40 + v-if="hasAccessByCodes(['infra:student:delete'])"
  41 + class="flex-1" type="error" :loading="deleting" @click="handleDelete"
  42 + >
  43 + 删除
  44 + </wd-button>
  45 + </view>
  46 + </view>
  47 + </view>
  48 +</template>
  49 +
  50 +<script lang="ts" setup>
  51 +import type { Student } from '@/api/infra/demo'
  52 +import { onUnload } from '@dcloudio/uni-app'
  53 +import { onMounted, ref } from 'vue'
  54 +import { useToast } from 'wot-design-uni'
  55 +import { deleteStudent, getStudent } from '@/api/infra/demo'
  56 +import { useAccess } from '@/hooks/useAccess'
  57 +import { navigateBackPlus } from '@/utils'
  58 +import { DICT_TYPE } from '@/utils/constants'
  59 +import { formatDateTime } from '@/utils/date'
  60 +
  61 +const props = defineProps<{
  62 + id?: number | any
  63 +}>()
  64 +
  65 +definePage({
  66 + style: {
  67 + navigationBarTitleText: '',
  68 + navigationStyle: 'custom',
  69 + },
  70 +})
  71 +
  72 +const { hasAccessByCodes } = useAccess()
  73 +const toast = useToast()
  74 +const formData = ref<Student>() // 详情数据
  75 +const deleting = ref(false) // 删除状态
  76 +
  77 +/** 返回上一页 */
  78 +function handleBack() {
  79 + navigateBackPlus('/pages-infra/demo/index')
  80 +}
  81 +
  82 +/** 加载学生详情 */
  83 +async function getDetail() {
  84 + if (!props.id || deleting.value) {
  85 + return
  86 + }
  87 + try {
  88 + toast.loading('加载中...')
  89 + formData.value = await getStudent(props.id)
  90 + } finally {
  91 + toast.close()
  92 + }
  93 +}
  94 +
  95 +/** 编辑学生 */
  96 +function handleEdit() {
  97 + uni.navigateTo({
  98 + url: `/pages-infra/demo/form/index?id=${props.id}`,
  99 + })
  100 +}
  101 +
  102 +/** 删除学生 */
  103 +function handleDelete() {
  104 + if (!props.id) {
  105 + return
  106 + }
  107 + uni.showModal({
  108 + title: '提示',
  109 + content: '确定要删除该学生吗?',
  110 + success: async (res) => {
  111 + if (!res.confirm) {
  112 + return
  113 + }
  114 + deleting.value = true
  115 + try {
  116 + await deleteStudent(props.id)
  117 + toast.success('删除成功')
  118 + uni.$emit('infra:demo:reload')
  119 + setTimeout(() => {
  120 + handleBack()
  121 + }, 500)
  122 + } finally {
  123 + deleting.value = false
  124 + }
  125 + },
  126 + })
  127 +}
  128 +
  129 +/** 初始化 */
  130 +onMounted(() => {
  131 + uni.$on('infra:demo:reload', getDetail)
  132 + getDetail()
  133 +})
  134 +
  135 +/** 卸载 */
  136 +onUnload(() => {
  137 + uni.$off('infra:demo:reload', getDetail)
  138 +})
  139 +</script>
  140 +
  141 +<style lang="scss" scoped>
  142 +</style>
... ...
yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_one/vue/form/index 0 → 100644
  1 +<template>
  2 + <view class="yd-page-container">
  3 + <!-- 顶部导航栏 -->
  4 + <wd-navbar
  5 + :title="getTitle"
  6 + left-arrow placeholder safe-area-inset-top fixed
  7 + @click-left="handleBack"
  8 + />
  9 +
  10 + <!-- 表单区域 -->
  11 + <view>
  12 + <wd-form ref="formRef" :model="formData" :rules="formRules">
  13 + <wd-cell-group border>
  14 + <wd-input
  15 + v-model="formData.name"
  16 + label="名字"
  17 + label-width="180rpx"
  18 + prop="name"
  19 + clearable
  20 + placeholder="请输入名字"
  21 + />
  22 + <wd-textarea
  23 + v-model="formData.description"
  24 + label="简介"
  25 + label-width="180rpx"
  26 + placeholder="请输入简介"
  27 + :maxlength="200"
  28 + show-word-limit
  29 + clearable
  30 + />
  31 + <wd-datetime-picker
  32 + v-model="formData.birthday"
  33 + type="datetime"
  34 + label="出生日期"
  35 + label-width="180rpx"
  36 + prop="birthday"
  37 + />
  38 + <wd-cell title="性别" title-width="180rpx" prop="sex" center>
  39 + <wd-radio-group v-model="formData.sex" shape="button">
  40 + <wd-radio
  41 + v-for="dict in getIntDictOptions(DICT_TYPE.SYSTEM_USER_SEX)"
  42 + :key="dict.value"
  43 + :value="dict.value"
  44 + >
  45 + {{ dict.label }}
  46 + </wd-radio>
  47 + </wd-radio-group>
  48 + </wd-cell>
  49 + <wd-cell title="是否有效" title-width="180rpx" prop="enabled" center>
  50 + <wd-radio-group v-model="formData.enabled" shape="button">
  51 + <wd-radio
  52 + v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
  53 + :key="dict.value"
  54 + :value="dict.value"
  55 + >
  56 + {{ dict.label }}
  57 + </wd-radio>
  58 + </wd-radio-group>
  59 + </wd-cell>
  60 + <wd-input
  61 + v-model="formData.avatar"
  62 + label="头像"
  63 + label-width="180rpx"
  64 + prop="avatar"
  65 + clearable
  66 + placeholder="请输入头像"
  67 + />
  68 + <wd-input
  69 + v-model="formData.video"
  70 + label="附件"
  71 + label-width="180rpx"
  72 + prop="video"
  73 + clearable
  74 + placeholder="请输入附件"
  75 + />
  76 + <wd-input
  77 + v-model="formData.memo"
  78 + label="备注"
  79 + label-width="180rpx"
  80 + prop="memo"
  81 + clearable
  82 + placeholder="请输入备注"
  83 + />
  84 + </wd-cell-group>
  85 + </wd-form>
  86 + </view>
  87 +
  88 + <!-- 底部保存按钮 -->
  89 + <view class="yd-detail-footer">
  90 + <wd-button
  91 + type="primary"
  92 + block
  93 + :loading="formLoading"
  94 + @click="handleSubmit"
  95 + >
  96 + 保存
  97 + </wd-button>
  98 + </view>
  99 + </view>
  100 +</template>
  101 +
  102 +<script lang="ts" setup>
  103 +import type { FormInstance } from 'wot-design-uni/components/wd-form/types'
  104 +import type { Student } from '@/api/infra/demo'
  105 +import { computed, onMounted, ref } from 'vue'
  106 +import { useToast } from 'wot-design-uni'
  107 +import { createStudent, getStudent, updateStudent } from '@/api/infra/demo'
  108 +import { getIntDictOptions, getBoolDictOptions } from '@/hooks/useDict'
  109 +import { navigateBackPlus } from '@/utils'
  110 +import { DICT_TYPE } from '@/utils/constants'
  111 +
  112 +const props = defineProps<{
  113 + id?: number | any
  114 +}>()
  115 +
  116 +definePage({
  117 + style: {
  118 + navigationBarTitleText: '',
  119 + navigationStyle: 'custom',
  120 + },
  121 +})
  122 +
  123 +const toast = useToast()
  124 +const getTitle = computed(() => props.id ? '编辑学生' : '新增学生')
  125 +const formLoading = ref(false) // 表单提交状态
  126 +const formData = ref<Student>({
  127 + id: undefined,
  128 + name: '',
  129 + description: '',
  130 + birthday: undefined,
  131 + sex: 0,
  132 + enabled: false,
  133 + avatar: '',
  134 + video: '',
  135 + memo: '',
  136 +}) // 表单数据
  137 +const formRules = {
  138 + name: [{ required: true, message: '名字不能为空' }],
  139 + description: [{ required: true, message: '简介不能为空' }],
  140 + birthday: [{ required: true, message: '出生日期不能为空' }],
  141 + sex: [{ required: true, message: '性别不能为空' }],
  142 + enabled: [{ required: true, message: '是否有效不能为空' }],
  143 + avatar: [{ required: true, message: '头像不能为空' }],
  144 + video: [{ required: true, message: '附件不能为空' }],
  145 + memo: [{ required: true, message: '备注不能为空' }],
  146 +} // 表单校验规则
  147 +const formRef = ref<FormInstance>() // 表单组件引用
  148 +
  149 +/** 返回上一页 */
  150 +function handleBack() {
  151 + navigateBackPlus('/pages-infra/demo/index')
  152 +}
  153 +
  154 +/** 加载学生详情 */
  155 +async function getDetail() {
  156 + if (!props.id) {
  157 + return
  158 + }
  159 + formData.value = await getStudent(props.id)
  160 +}
  161 +
  162 +/** 提交表单 */
  163 +async function handleSubmit() {
  164 + const { valid } = await formRef.value.validate()
  165 + if (!valid) {
  166 + return
  167 + }
  168 +
  169 + formLoading.value = true
  170 + try {
  171 + if (props.id) {
  172 + await updateStudent(formData.value)
  173 + toast.success('修改成功')
  174 + } else {
  175 + await createStudent(formData.value)
  176 + toast.success('新增成功')
  177 + }
  178 + uni.$emit('infra:demo:reload')
  179 + setTimeout(() => {
  180 + handleBack()
  181 + }, 500)
  182 + } finally {
  183 + formLoading.value = false
  184 + }
  185 +}
  186 +
  187 +/** 初始化 */
  188 +onMounted(() => {
  189 + getDetail()
  190 +})
  191 +</script>
  192 +
  193 +<style lang="scss" scoped>
  194 +</style>
... ...
yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_one/vue/index 0 → 100644
  1 +<template>
  2 + <view class="yd-page-container yd-page-container-paging">
  3 + <!-- 顶部导航栏 -->
  4 + <wd-navbar
  5 + title="学生管理"
  6 + left-arrow placeholder safe-area-inset-top fixed
  7 + @click-left="handleBack"
  8 + />
  9 +
  10 + <!-- 搜索组件 -->
  11 + <SearchForm @search="handleQuery" @reset="handleReset" />
  12 +
  13 + <!-- 学生列表 -->
  14 + <z-paging
  15 + ref="pagingRef"
  16 + v-model="list"
  17 + :fixed="false"
  18 + class="min-h-0 flex-1"
  19 + :default-page-size="10"
  20 + :refresher-enabled="true"
  21 + :inside-more="true"
  22 + :loading-more-default-as-loading="true"
  23 + empty-view-text="暂无学生数据"
  24 + @query="queryList"
  25 + >
  26 + <view class="p-24rpx">
  27 + <view
  28 + v-for="item in list"
  29 + :key="item.id"
  30 + class="mb-24rpx overflow-hidden rounded-12rpx bg-white shadow-sm"
  31 + @click="handleDetail(item)"
  32 + >
  33 + <view class="p-24rpx">
  34 + <view class="mb-16rpx flex items-center justify-between">
  35 + <view class="text-32rpx text-[#333] font-semibold">
  36 + {{ item.name }}
  37 + </view>
  38 + <dict-tag :type="DICT_TYPE.SYSTEM_USER_SEX" :value="item.sex" />
  39 + </view>
  40 + <view class="mb-12rpx flex items-center text-28rpx text-[#666]">
  41 + <text class="mr-8rpx text-[#999]">简介:</text>
  42 + <text class="line-clamp-1">{{ item.description }}</text>
  43 + </view>
  44 + <view class="mb-12rpx flex items-center text-28rpx text-[#666]">
  45 + <text class="mr-8rpx text-[#999]">出生日期:</text>
  46 + <text class="line-clamp-1">{{ formatDateTime(item.birthday) || '-' }}</text>
  47 + </view>
  48 + <view class="mb-12rpx flex items-center text-28rpx text-[#666]">
  49 + <text class="mr-8rpx text-[#999]">是否有效:</text>
  50 + <dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="item.enabled" />
  51 + </view>
  52 + <view class="mb-12rpx flex items-center text-28rpx text-[#666]">
  53 + <text class="mr-8rpx text-[#999]">头像:</text>
  54 + <text class="line-clamp-1">{{ item.avatar }}</text>
  55 + </view>
  56 + <view class="mb-12rpx flex items-center text-28rpx text-[#666]">
  57 + <text class="mr-8rpx text-[#999]">附件:</text>
  58 + <text class="line-clamp-1">{{ item.video }}</text>
  59 + </view>
  60 + <view class="mb-12rpx flex items-center text-28rpx text-[#666]">
  61 + <text class="mr-8rpx text-[#999]">备注:</text>
  62 + <text class="line-clamp-1">{{ item.memo }}</text>
  63 + </view>
  64 + <view class="mb-12rpx flex items-center text-28rpx text-[#666]">
  65 + <text class="mr-8rpx text-[#999]">创建时间:</text>
  66 + <text class="line-clamp-1">{{ formatDateTime(item.createTime) || '-' }}</text>
  67 + </view>
  68 + </view>
  69 + </view>
  70 + </view>
  71 + </z-paging>
  72 +
  73 + <!-- 新增按钮 -->
  74 + <wd-fab
  75 + v-if="hasAccessByCodes(['infra:student:create'])"
  76 + position="right-bottom"
  77 + type="primary"
  78 + :expandable="false"
  79 + @click="handleAdd"
  80 + />
  81 + </view>
  82 +</template>
  83 +
  84 +<script lang="ts" setup>
  85 +import type { Student } from '@/api/infra/demo'
  86 +import { onUnload } from '@dcloudio/uni-app'
  87 +import { onMounted, ref } from 'vue'
  88 +import { getStudentPage } from '@/api/infra/demo'
  89 +import { useAccess } from '@/hooks/useAccess'
  90 +import { navigateBackPlus } from '@/utils'
  91 +import { DICT_TYPE } from '@/utils/constants'
  92 +import { formatDateTime } from '@/utils/date'
  93 +import SearchForm from './components/search-form.vue'
  94 +
  95 +definePage({
  96 + style: {
  97 + navigationBarTitleText: '',
  98 + navigationStyle: 'custom',
  99 + },
  100 +})
  101 +
  102 +const { hasAccessByCodes } = useAccess()
  103 +const list = ref<Student[]>([]) // 列表数据
  104 +const pagingRef = ref<any>() // 分页组件引用
  105 +const queryParams = ref<Record<string, any>>({}) // 查询参数
  106 +
  107 +/** 返回上一页 */
  108 +function handleBack() {
  109 + navigateBackPlus()
  110 +}
  111 +
  112 +/** 查询学生列表 */
  113 +async function queryList(pageNo: number, pageSize: number) {
  114 + try {
  115 + const params = {
  116 + ...queryParams.value,
  117 + pageNo,
  118 + pageSize,
  119 + }
  120 + const data = await getStudentPage(params)
  121 + pagingRef.value?.completeByTotal(data.list, data.total)
  122 + } catch {
  123 + pagingRef.value?.complete(false)
  124 + }
  125 +}
  126 +
  127 +/** 搜索按钮操作 */
  128 +function handleQuery(data?: Record<string, any>) {
  129 + queryParams.value = { ...data }
  130 + reload()
  131 +}
  132 +
  133 +/** 重置按钮操作 */
  134 +function handleReset() {
  135 + handleQuery()
  136 +}
  137 +
  138 +/** 重新加载 */
  139 +function reload() {
  140 + pagingRef.value?.reload()
  141 +}
  142 +
  143 +/** 新增学生 */
  144 +function handleAdd() {
  145 + uni.navigateTo({
  146 + url: '/pages-infra/demo/form/index',
  147 + })
  148 +}
  149 +
  150 +/** 查看详情 */
  151 +function handleDetail(item: Student) {
  152 + uni.navigateTo({
  153 + url: `/pages-infra/demo/detail/index?id=${item.id}`,
  154 + })
  155 +}
  156 +
  157 +/** 初始化 */
  158 +onMounted(() => {
  159 + uni.$on('infra:demo:reload', reload)
  160 +})
  161 +
  162 +/** 卸载 */
  163 +onUnload(() => {
  164 + uni.$off('infra:demo:reload', reload)
  165 +})
  166 +</script>
  167 +
  168 +<style lang="scss" scoped>
  169 +</style>
... ...
yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_one/vue/search-form 0 → 100644
  1 +<template>
  2 + <!-- 搜索框入口 -->
  3 + <view @click="visible = true">
  4 + <wd-search :placeholder="placeholder" hide-cancel disabled />
  5 + </view>
  6 +
  7 + <!-- 搜索弹窗 -->
  8 + <wd-popup v-model="visible" position="top" @close="visible = false">
  9 + <view class="yd-search-form-container" :style="{ paddingTop: `${getNavbarHeight()}px` }">
  10 + <view class="yd-search-form-item">
  11 + <view class="yd-search-form-label">
  12 + 名字
  13 + </view>
  14 + <wd-input
  15 + v-model="formData.name"
  16 + placeholder="请输入名字"
  17 + clearable
  18 + />
  19 + </view>
  20 + <view class="yd-search-form-item">
  21 + <view class="yd-search-form-label">
  22 + 出生日期
  23 + </view>
  24 + <wd-input
  25 + v-model="formData.birthday"
  26 + placeholder="请输入出生日期"
  27 + clearable
  28 + />
  29 + </view>
  30 + <view class="yd-search-form-item">
  31 + <view class="yd-search-form-label">
  32 + 性别
  33 + </view>
  34 + <wd-radio-group v-model="formData.sex" shape="button">
  35 + <wd-radio :value="-1">
  36 + 全部
  37 + </wd-radio>
  38 + <wd-radio
  39 + v-for="dict in getIntDictOptions(DICT_TYPE.SYSTEM_USER_SEX)"
  40 + :key="dict.value"
  41 + :value="dict.value"
  42 + >
  43 + {{ dict.label }}
  44 + </wd-radio>
  45 + </wd-radio-group>
  46 + </view>
  47 + <view class="yd-search-form-item">
  48 + <view class="yd-search-form-label">
  49 + 是否有效
  50 + </view>
  51 + <wd-radio-group v-model="formData.enabled" shape="button">
  52 + <wd-radio :value="-1">
  53 + 全部
  54 + </wd-radio>
  55 + <wd-radio
  56 + v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
  57 + :key="dict.value"
  58 + :value="dict.value"
  59 + >
  60 + {{ dict.label }}
  61 + </wd-radio>
  62 + </wd-radio-group>
  63 + </view>
  64 + <view class="yd-search-form-item">
  65 + <view class="yd-search-form-label">
  66 + 创建时间
  67 + </view>
  68 + <view class="yd-search-form-date-range-container">
  69 + <view @click="visibleCreateTime[0] = true">
  70 + <view class="yd-search-form-date-range-picker">
  71 + {{ formatDate(formData.createTime?.[0]) || '开始日期' }}
  72 + </view>
  73 + </view>
  74 + -
  75 + <view @click="visibleCreateTime[1] = true">
  76 + <view class="yd-search-form-date-range-picker">
  77 + {{ formatDate(formData.createTime?.[1]) || '结束日期' }}
  78 + </view>
  79 + </view>
  80 + </view>
  81 + <wd-datetime-picker-view v-if="visibleCreateTime[0]" v-model="tempCreateTime[0]" type="date" />
  82 + <view v-if="visibleCreateTime[0]" class="yd-search-form-date-range-actions">
  83 + <wd-button size="small" plain @click="visibleCreateTime[0] = false">
  84 + 取消
  85 + </wd-button>
  86 + <wd-button size="small" type="primary" @click="handleCreateTime0Confirm">
  87 + 确定
  88 + </wd-button>
  89 + </view>
  90 + <wd-datetime-picker-view v-if="visibleCreateTime[1]" v-model="tempCreateTime[1]" type="date" />
  91 + <view v-if="visibleCreateTime[1]" class="yd-search-form-date-range-actions">
  92 + <wd-button size="small" plain @click="visibleCreateTime[1] = false">
  93 + 取消
  94 + </wd-button>
  95 + <wd-button size="small" type="primary" @click="handleCreateTime1Confirm">
  96 + 确定
  97 + </wd-button>
  98 + </view>
  99 + </view>
  100 + <view class="yd-search-form-actions">
  101 + <wd-button class="flex-1" plain @click="handleReset">
  102 + 重置
  103 + </wd-button>
  104 + <wd-button class="flex-1" type="primary" @click="handleSearch">
  105 + 搜索
  106 + </wd-button>
  107 + </view>
  108 + </view>
  109 + </wd-popup>
  110 +</template>
  111 +
  112 +<script lang="ts" setup>
  113 +import { computed, reactive, ref } from 'vue'
  114 +import { getNavbarHeight } from '@/utils'
  115 +import { formatDate, formatDateRange } from '@/utils/date'
  116 +import { getDictLabel, getIntDictOptions, getBoolDictOptions } from '@/hooks/useDict'
  117 +import { DICT_TYPE } from '@/utils/constants'
  118 +
  119 +const emit = defineEmits<{
  120 + search: [data: Record<string, any>]
  121 + reset: []
  122 +}>()
  123 +
  124 +const visible = ref(false) // 搜索弹窗显示状态
  125 +const formData = reactive({
  126 + name: undefined as string | undefined,
  127 + birthday: undefined as string | undefined,
  128 + sex: -1, // -1 表示全部
  129 + enabled: -1 as -1 | boolean, // -1 表示全部
  130 + createTime: [undefined, undefined] as [number | undefined, number | undefined],
  131 +}) // 搜索表单数据
  132 +
  133 +/** 搜索条件 placeholder 拼接 */
  134 +const placeholder = computed(() => {
  135 + const conditions: string[] = []
  136 + if (formData.name) {
  137 + conditions.push(`名字:${formData.name}`)
  138 + }
  139 + if (formData.birthday) {
  140 + conditions.push(`出生日期:${formData.birthday}`)
  141 + }
  142 + if (formData.sex !== -1) {
  143 + conditions.push(`性别:${getDictLabel(DICT_TYPE.SYSTEM_USER_SEX, formData.sex)}`)
  144 + }
  145 + if (formData.enabled !== -1) {
  146 + conditions.push(`是否有效:${getDictLabel(DICT_TYPE.INFRA_BOOLEAN_STRING, formData.enabled)}`)
  147 + }
  148 + if (formData.createTime?.[0] && formData.createTime?.[1]) {
  149 + conditions.push(`创建时间:${formatDate(formData.createTime[0])}~${formatDate(formData.createTime[1])}`)
  150 + }
  151 + return conditions.length > 0 ? conditions.join(' | ') : '搜索学生'
  152 +})
  153 +
  154 +const visibleCreateTime = ref<[boolean, boolean]>([false, false]) // 创建时间选择器状态
  155 +const tempCreateTime = ref<[number, number]>([Date.now(), Date.now()]) // 创建时间临时值
  156 +
  157 +/** 确认创建时间开始日期 */
  158 +function handleCreateTime0Confirm() {
  159 + formData.createTime = [tempCreateTime.value[0], formData.createTime?.[1]]
  160 + visibleCreateTime.value[0] = false
  161 +}
  162 +
  163 +/** 确认创建时间结束日期 */
  164 +function handleCreateTime1Confirm() {
  165 + formData.createTime = [formData.createTime?.[0], tempCreateTime.value[1]]
  166 + visibleCreateTime.value[1] = false
  167 +}
  168 +
  169 +/** 搜索按钮操作 */
  170 +function handleSearch() {
  171 + visible.value = false
  172 + emit('search', {
  173 + ...formData,
  174 + sex: formData.sex === -1 ? undefined : formData.sex,
  175 + enabled: formData.enabled === -1 ? undefined : formData.enabled,
  176 + createTime: formatDateRange(formData.createTime),
  177 + })
  178 +}
  179 +
  180 +/** 重置按钮操作 */
  181 +function handleReset() {
  182 + formData.name = undefined
  183 + formData.birthday = undefined
  184 + formData.sex = -1
  185 + formData.enabled = -1
  186 + formData.createTime = [undefined, undefined]
  187 + visible.value = false
  188 + emit('reset')
  189 +}
  190 +</script>
... ...
yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_one/xml/InfraStudentMapper 0 → 100644
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  3 +<mapper namespace="cn.iocoder.yudao.module.infra.dal.mysql.demo.InfraStudentMapper">
  4 +
  5 + <!--
  6 + 一般情况下,尽可能使用 Mapper 进行 CRUD 增删改查即可。
  7 + 无法满足的场景,例如说多表关联查询,才使用 XML 编写 SQL。
  8 + 代码生成器暂时只生成 Mapper XML 文件本身,更多推荐 MybatisX 快速开发插件来生成查询。
  9 + 文档可见:https://www.iocoder.cn/MyBatis/x-plugins/
  10 + -->
  11 +
  12 +</mapper>
0 13 \ No newline at end of file
... ...