fix: 修复API接口规范和数据获取问题
1. 修复前端从res.rows改为res.data获取数据 2. 更新API接口基础规范文档: - 添加系统基础模块规范 - 添加租户过滤规范 - 统一返回值规范 - 完善代码生成规范
This commit is contained in:
1 parent
9b8e437c31
commit
a4247ea901
2 files changed
+382
-33
No files matched your search
@@ -346,3 +346,196 @@
|
||||
- 次要按钮: 使用轮廓式设计
|
||||
- 危险按钮: 使用错误色
|
||||
- 禁用状态: 降低透明度
|
||||
|
||||
# API接口基础规范(2025-07-01更新)
|
||||
|
||||
## 一、系统基础模块规范
|
||||
|
||||
### 1. 系统模块代码位置
|
||||
|
||||
所有涉及以下功能的API接口代码必须统一写在 `ruoyi-system` 工程中:
|
||||
- 用户管理相关(SysUser)
|
||||
- 组织机构相关(SysDept)
|
||||
- 角色权限相关(SysRole)
|
||||
- 菜单权限相关(SysMenu)
|
||||
- 字典管理相关(SysDict)
|
||||
- 系统配置相关(SysConfig)
|
||||
- 租户管理相关(SysTenant)
|
||||
|
||||
#### 代码组织结构
|
||||
```
|
||||
ruoyi-system/
|
||||
├── src/main/java/org/dromara/system/
|
||||
│ ├── controller/system/ # 控制器(注意是system子包)
|
||||
│ ├── service/ # 服务接口
|
||||
│ ├── service/impl/ # 服务实现
|
||||
│ ├── mapper/ # 数据访问层
|
||||
│ └── domain/ # 实体类和VO对象
|
||||
```
|
||||
|
||||
### 2. 租户过滤规范
|
||||
|
||||
#### 2.1 Controller层
|
||||
```java
|
||||
@RestController
|
||||
@RequestMapping("/system/xxx")
|
||||
public class XxxController {
|
||||
|
||||
@GetMapping("/list")
|
||||
public R<List<XxxVO>> list(@RequestParam(value = "tenantId", required = false) String tenantId) {
|
||||
// 如果tenantId为空,会自动使用当前用户的租户ID
|
||||
return R.ok(xxxService.selectXxxList(tenantId));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.2 Service层
|
||||
```java
|
||||
@Service
|
||||
public class XxxServiceImpl implements IXxxService {
|
||||
|
||||
@Override
|
||||
public List<XxxVO> selectXxxList(String tenantId) {
|
||||
if (StringUtils.isEmpty(tenantId)) {
|
||||
tenantId = SecurityUtils.getTenantId();
|
||||
}
|
||||
return baseMapper.selectXxxList(tenantId);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.3 Mapper层
|
||||
```java
|
||||
@Mapper
|
||||
public interface XxxMapper {
|
||||
|
||||
@Select("""
|
||||
SELECT
|
||||
x.*
|
||||
FROM xxx x
|
||||
WHERE x.tenant_id = #{tenantId}
|
||||
AND x.del_flag = '0'
|
||||
ORDER BY x.create_time DESC
|
||||
""")
|
||||
List<Xxx> selectXxxList(@Param("tenantId") String tenantId);
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 统一返回值规范
|
||||
|
||||
#### 3.1 后端返回格式
|
||||
```java
|
||||
// 统一返回格式
|
||||
public class R<T> {
|
||||
private int code; // 状态码
|
||||
private String msg; // 消息
|
||||
private T data; // 数据对象
|
||||
}
|
||||
|
||||
// 分页返回格式
|
||||
public class TableDataInfo<T> {
|
||||
private int code; // 状态码
|
||||
private String msg; // 消息
|
||||
private List<T> data; // 数据对象
|
||||
private long total; // 总记录数
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.2 前端获取数据规范
|
||||
```typescript
|
||||
// 标准数据获取
|
||||
const getList = async () => {
|
||||
try {
|
||||
const res = await listApi();
|
||||
// 正确:从data字段获取数据
|
||||
dataList.value = res.data || [];
|
||||
} catch (error) {
|
||||
console.error('获取数据失败:', error);
|
||||
dataList.value = [];
|
||||
}
|
||||
};
|
||||
|
||||
// 分页数据获取
|
||||
const getPageList = async (query: PageQuery) => {
|
||||
try {
|
||||
const res = await pageApi(query);
|
||||
// 正确:从data字段获取列表数据
|
||||
dataList.value = res.data || [];
|
||||
// 获取总数
|
||||
total.value = res.total || 0;
|
||||
} catch (error) {
|
||||
console.error('获取分页数据失败:', error);
|
||||
dataList.value = [];
|
||||
total.value = 0;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## 二、代码生成规范
|
||||
|
||||
### 1. 代码生成器配置
|
||||
|
||||
#### 1.1 系统模块配置
|
||||
- 当生成的代码涉及用户、组织、角色等基础功能时:
|
||||
- 包名:`org.dromara.system`
|
||||
- 模块名:`ruoyi-system`
|
||||
- 表前缀:`sys_`
|
||||
|
||||
#### 1.2 租户字段配置
|
||||
- 必须包含租户字段:`tenant_id`
|
||||
- 字段类型:`varchar(20)`
|
||||
- 默认值:`'000000'`
|
||||
- 注释:`租户编号`
|
||||
|
||||
### 2. 模板调整建议
|
||||
|
||||
#### 2.1 Controller模板
|
||||
```java
|
||||
@ApiOperation("查询${functionName}列表")
|
||||
@SaCheckPermission("${permissionPrefix}:list")
|
||||
@GetMapping("/list")
|
||||
public R<List<${ClassName}VO>> list(${ClassName}BO bo) {
|
||||
// 确保设置租户ID
|
||||
if (StringUtils.isEmpty(bo.getTenantId())) {
|
||||
bo.setTenantId(SecurityUtils.getTenantId());
|
||||
}
|
||||
return R.ok(${className}Service.queryList(bo));
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.2 Service模板
|
||||
```java
|
||||
@Override
|
||||
public List<${ClassName}VO> queryList(${ClassName}BO bo) {
|
||||
// 租户过滤
|
||||
LambdaQueryWrapper<${ClassName}> lqw = Wrappers.lambdaQuery();
|
||||
lqw.eq(${ClassName}::getTenantId, bo.getTenantId());
|
||||
// ... 其他查询条件
|
||||
return baseMapper.selectVoList(lqw);
|
||||
}
|
||||
```
|
||||
|
||||
## 三、注意事项
|
||||
|
||||
### 1. 租户ID处理
|
||||
- 前端可以选择性传入租户ID
|
||||
- 如果未传入,后端使用当前用户的租户ID
|
||||
- 禁止跨租户查询(除非特殊说明)
|
||||
|
||||
### 2. 返回值处理
|
||||
- 前端统一从data字段获取数据
|
||||
- 禁止使用rows或其他字段名
|
||||
- 空值处理使用空数组或空对象
|
||||
|
||||
### 3. 系统模块开发
|
||||
- 涉及基础功能必须在ruoyi-system中开发
|
||||
- 保持代码结构统一
|
||||
- 遵循权限命名规范
|
||||
|
||||
---
|
||||
|
||||
**📝 维护说明**:
|
||||
- 更新时间:2025-07-01
|
||||
- 更新人:wuzilong
|
||||
- 基于实际项目开发经验总结
|
||||
- 持续补充和优化
|
||||
@@ -37,23 +37,26 @@
|
||||
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['hq:hallReservation:add']">新增</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['hq:hallReservation:edit']"
|
||||
>修改</el-button
|
||||
>
|
||||
<el-button icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['hq:hallReservation:edit']">修改</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['hq:hallReservation:remove']"
|
||||
>删除</el-button
|
||||
>
|
||||
<el-button icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['hq:hallReservation:remove']">删除</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button icon="Download" @click="handleExport" v-hasPermi="['hq:hallReservation:export']">导出</el-button>
|
||||
<el-button icon="Download" @click="handleExport" v-hasPermi="['hq:hallReservation: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="hallReservationList" @selection-change="handleSelectionChange" style="margin-bottom: 0">
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
border
|
||||
:data="hallReservationList"
|
||||
@selection-change="handleSelectionChange"
|
||||
style="margin-bottom: 0"
|
||||
@row-dblclick="handleView"
|
||||
>
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="报告厅ID" align="center" prop="hallId" />
|
||||
<el-table-column label="报告厅名称" align="center" prop="hallName" />
|
||||
@@ -78,13 +81,9 @@
|
||||
:hide-after="100"
|
||||
effect="dark"
|
||||
>
|
||||
<span class="purpose-text">
|
||||
{{
|
||||
scope.row.reservationPurpose && scope.row.reservationPurpose.length > 100
|
||||
? scope.row.reservationPurpose.substring(0, 100) + '...'
|
||||
: scope.row.reservationPurpose
|
||||
}}
|
||||
</span>
|
||||
<div class="purpose-text">
|
||||
{{ scope.row.reservationPurpose }}
|
||||
</div>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -110,17 +109,13 @@
|
||||
:hide-after="100"
|
||||
effect="dark"
|
||||
>
|
||||
<span class="audit-remark-text">
|
||||
{{
|
||||
scope.row.auditRemark && scope.row.auditRemark.length > 100
|
||||
? scope.row.auditRemark.substring(0, 100) + '...'
|
||||
: scope.row.auditRemark
|
||||
}}
|
||||
</span>
|
||||
<div class="audit-remark-text">
|
||||
{{ scope.row.auditRemark }}
|
||||
</div>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="220">
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="220" fixed="right">
|
||||
<template #default="scope">
|
||||
<!-- 查看按钮,所有记录都可以查看 -->
|
||||
<el-tooltip content="查看" placement="top">
|
||||
@@ -206,9 +201,9 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<!-- 报告厅图片展示区域 -->
|
||||
<el-form-item label="报告厅图片" v-if="selectedHallImages.length > 0">
|
||||
<el-form-item label="报告厅图片">
|
||||
<div class="selected-hall-images">
|
||||
<div class="image-gallery">
|
||||
<div class="image-gallery" v-if="selectedHallImages.length > 0">
|
||||
<div v-for="(image, index) in selectedHallImages" :key="index" class="image-item" @click="previewImage(image, index)">
|
||||
<img :src="image" class="gallery-image" @error="handleImageError" />
|
||||
<div class="image-overlay">
|
||||
@@ -216,6 +211,10 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="no-images-placeholder">
|
||||
<el-icon><Picture /></el-icon>
|
||||
<span>暂无图片</span>
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="预约开始时间" prop="startTime">
|
||||
@@ -226,6 +225,7 @@
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
placeholder="请选择预约开始时间"
|
||||
style="width: 100%"
|
||||
@change="handleStartTimeChange"
|
||||
>
|
||||
</el-date-picker>
|
||||
</el-form-item>
|
||||
@@ -269,9 +269,9 @@
|
||||
<span class="form-text">{{ viewForm.hallName }}</span>
|
||||
</el-form-item>
|
||||
<!-- 报告厅图片展示区域 -->
|
||||
<el-form-item label="报告厅图片" v-if="viewHallImages.length > 0">
|
||||
<el-form-item label="报告厅图片">
|
||||
<div class="view-hall-images">
|
||||
<div class="image-gallery">
|
||||
<div class="image-gallery" v-if="viewHallImages.length > 0">
|
||||
<div v-for="(image, index) in viewHallImages" :key="index" class="image-item" @click="previewImage(image, index)">
|
||||
<img :src="image" class="gallery-image" @error="handleImageError" />
|
||||
<div class="image-overlay">
|
||||
@@ -279,6 +279,10 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="no-images-placeholder">
|
||||
<el-icon><Picture /></el-icon>
|
||||
<span>暂无图片</span>
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="预约开始时间">
|
||||
@@ -301,6 +305,19 @@
|
||||
<el-form-item label="预约状态">
|
||||
<dict-tag :options="hall_reserv_status" :value="viewForm.status" />
|
||||
</el-form-item>
|
||||
<!-- 当状态为审核中时显示可审核人员列表 -->
|
||||
<el-form-item label="可审核人员" v-if="viewForm.status === '0' && auditorList.length > 0">
|
||||
<div class="auditor-list">
|
||||
<div v-for="auditor in auditorList" :key="auditor.userId" class="auditor-item">
|
||||
<el-avatar :size="32" :src="auditor.avatar">{{ auditor.nickName?.substring(0, 1) }}</el-avatar>
|
||||
<div class="auditor-info">
|
||||
<div class="auditor-name">{{ auditor.nickName || auditor.userName }}</div>
|
||||
<div class="auditor-dept">{{ auditor.dept?.deptName }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="auditorList.length === 0" class="no-auditor">暂无可审核人员</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="审核人" v-if="viewForm.auditName">
|
||||
<span class="form-text">{{ viewForm.auditName }}</span>
|
||||
</el-form-item>
|
||||
@@ -325,7 +342,7 @@
|
||||
<script setup name="HallReservation" lang="ts">
|
||||
import { listHallReservation, getHallReservation, delHallReservation, addHallReservation, updateHallReservation } from '@/api/hq/hallReservation';
|
||||
import { listHallInfo } from '@/api/hq/hallInfo';
|
||||
import { getUserProfile } from '@/api/system/user';
|
||||
import { getUserProfile, listUsersByPermission } from '@/api/system/user';
|
||||
import { HallReservationVO, HallReservationQuery, HallReservationForm } from '@/api/hq/hallReservation/types';
|
||||
import { HallInfoVO } from '@/api/hq/hallInfo/types';
|
||||
import { useUserStore } from '@/store/modules/user';
|
||||
@@ -375,6 +392,44 @@ const viewForm = ref<HallReservationForm>({
|
||||
auditRemark: undefined
|
||||
});
|
||||
|
||||
// 添加审核人员列表
|
||||
const auditorList = ref<any[]>([]);
|
||||
|
||||
/** 获取具有审核权限的人员列表 */
|
||||
const getAuditorList = async () => {
|
||||
try {
|
||||
const res = await listUsersByPermission('hq:hallAudit:list');
|
||||
auditorList.value = res.data || [];
|
||||
console.log('审核人员列表:', auditorList.value);
|
||||
} catch (error) {
|
||||
console.error('获取审核人员列表失败:', error);
|
||||
auditorList.value = [];
|
||||
}
|
||||
};
|
||||
|
||||
/** 验证结束时间 */
|
||||
const validateEndTime = (rule: any, value: any, callback: any) => {
|
||||
if (value && form.value.startTime) {
|
||||
const startTime = new Date(form.value.startTime).getTime();
|
||||
const endTime = new Date(value).getTime();
|
||||
if (endTime <= startTime) {
|
||||
callback(new Error('结束时间必须晚于开始时间'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
/** 开始时间变化时触发结束时间验证 */
|
||||
const handleStartTimeChange = () => {
|
||||
// 如果结束时间已经填写,则触发验证
|
||||
if (data.form.endTime) {
|
||||
hallReservationFormRef.value?.validateField('endTime');
|
||||
}
|
||||
};
|
||||
|
||||
const initFormData: HallReservationForm = {
|
||||
id: undefined,
|
||||
hallId: undefined,
|
||||
@@ -390,6 +445,7 @@ const initFormData: HallReservationForm = {
|
||||
auditTime: undefined,
|
||||
auditRemark: undefined
|
||||
};
|
||||
|
||||
const data = reactive<PageData<HallReservationForm, HallReservationQuery>>({
|
||||
form: { ...initFormData },
|
||||
queryParams: {
|
||||
@@ -409,7 +465,25 @@ const data = reactive<PageData<HallReservationForm, HallReservationQuery>>({
|
||||
rules: {
|
||||
hallName: [{ required: true, message: '报告厅名称不能为空', trigger: 'change' }],
|
||||
startTime: [{ required: true, message: '预约开始时间不能为空', trigger: 'blur' }],
|
||||
endTime: [{ required: true, message: '预约结束时间不能为空', trigger: 'blur' }],
|
||||
endTime: [
|
||||
{ required: true, message: '预约结束时间不能为空', trigger: 'blur' },
|
||||
{
|
||||
validator: (rule: any, value: any, callback: any) => {
|
||||
if (value && data.form.startTime) {
|
||||
const startTime = new Date(data.form.startTime).getTime();
|
||||
const endTime = new Date(value).getTime();
|
||||
if (endTime <= startTime) {
|
||||
callback(new Error('结束时间必须晚于开始时间'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
reservationPurpose: [{ required: true, message: '预约用途不能为空', trigger: 'blur' }]
|
||||
}
|
||||
});
|
||||
@@ -635,6 +709,11 @@ const handleView = async (row: HallReservationVO) => {
|
||||
const res = await getHallReservation(row.id);
|
||||
Object.assign(viewForm.value, res.data);
|
||||
|
||||
// 如果状态是审核中,获取审核人员列表
|
||||
if (viewForm.value.status === '0') {
|
||||
await getAuditorList();
|
||||
}
|
||||
|
||||
// 查找对应的报告厅并显示图片
|
||||
if (viewForm.value.hallName) {
|
||||
const selectedHall = hallList.value.find((hall) => hall.hallName === viewForm.value.hallName);
|
||||
@@ -849,22 +928,28 @@ onMounted(() => {
|
||||
|
||||
/* 表格中预约用途文本样式 */
|
||||
.purpose-text {
|
||||
display: inline-block;
|
||||
max-width: 100%;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 3;
|
||||
overflow: hidden;
|
||||
word-break: break-all;
|
||||
word-wrap: break-word;
|
||||
line-height: 1.4;
|
||||
cursor: pointer;
|
||||
max-height: 4.2em;
|
||||
}
|
||||
|
||||
/* 表格中审核备注文本样式 */
|
||||
.audit-remark-text {
|
||||
display: inline-block;
|
||||
max-width: 100%;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 3;
|
||||
overflow: hidden;
|
||||
word-break: break-all;
|
||||
word-wrap: break-word;
|
||||
line-height: 1.4;
|
||||
cursor: pointer;
|
||||
max-height: 4.2em;
|
||||
}
|
||||
|
||||
/* 查看详情表单样式 */
|
||||
@@ -923,6 +1008,7 @@ onMounted(() => {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
max-width: 450px; /* 与预约用途保持一致 */
|
||||
}
|
||||
|
||||
.image-item {
|
||||
@@ -970,6 +1056,76 @@ onMounted(() => {
|
||||
color: white;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
/* 暂无图片占位样式 */
|
||||
.no-images-placeholder {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 450px; /* 与预约用途保持一致 */
|
||||
height: 120px;
|
||||
background-color: #f5f7fa;
|
||||
border: 2px dashed #dcdfe6;
|
||||
border-radius: 8px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.no-images-placeholder .el-icon {
|
||||
font-size: 32px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.no-images-placeholder span {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* 审核人员列表样式 */
|
||||
.auditor-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px;
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.auditor-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px 16px;
|
||||
background-color: #f5f7fa;
|
||||
border-radius: 8px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.auditor-item:hover {
|
||||
background-color: #ecf5ff;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.auditor-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.auditor-name {
|
||||
font-size: 14px;
|
||||
color: #303133;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.auditor-dept {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.no-auditor {
|
||||
color: #909399;
|
||||
font-size: 14px;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* 页面容器样式 */
|
||||
</style>
|
||||
|
||||
<style>
|
||||
|
||||
Reference in new issue
Block a user