// 删除处理 const handleDelete = async (id) => { try { await ElMessageBox.confirm(‘确认删除该分类及其所有子分类?’, ‘警告’, { type: ‘warning’, confirmButtonText: ‘确认’, cancelButtonText: ‘取消’ }) const res = await request.delete(/category/deleteById/${id}) if (res.code === ‘200’) { ElMessage.success(‘删除成功’) await loadData() } } catch (error) { if (error !== ‘cancel’) { console.error(‘删除失败:’, error) ElMessage.error(error.response?.data?.message || ‘删除失败’) } } }已经知道前端删除的代码如上所展示,数据库表代码为CREATE TABLE category ( id int(11) NOT NULL AUTO_INCREMENT COMMENT ‘课程分类id’, title varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT ‘课程分类标题’, parent_id int(11) DEFAULT NULL COMMENT ‘父ID’, sort int(10) unsigned DEFAULT ‘0’ COMMENT ‘排序字段’, PRIMARY KEY (id), KEY parent_id (parent_id) ) ENGINE=MyISAM AUTO_INCREMENT=23 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT=‘课程分类’;根据该前端代码给出Spring boot3结合Mybatis且数据库为Mysql5.7版本的删除操作的全部后端代码,删除的时候要求能够成功删除而且要求删除父级时全部的子级包括父级本身都要删除,删除子级时只删除该子级本身后端只有,CategoryController层,CategoryService层,CategoryMapper层和CategoryMapper.xml

时间: 2025-03-22 17:09:56 浏览: 75
<think>好的,我现在需要根据用户提供的前端代码和数据库结构,编写一个Spring Boot 3的后端删除功能。用户要求当删除父级分类时,同时删除所有子级分类和父级本身;删除子级时只删除该子级。后端需要包含Controller、Service、Mapper层以及对应的XML文件。 首先,我需要理解数据库表结构。表category有id、title、parent_id和sort字段。其中parent_id表示父分类的ID,如果为NULL则表示它是顶级分类。因此,这是一个典型的树形结构,使用邻接表模型存储。 接下来,用户的删除逻辑是:当删除一个分类时,如果是父级(即有子分类),需要删除所有子分类及其自身;如果是子级(没有子分类),则只删除自己。因此,后端需要能够递归删除所有子节点。 考虑到MySQL 5.7不支持递归查询(如WITH RECURSIVE),传统的做法是在应用层或使用存储过程来处理递归删除。这里可能需要用MyBatis在Mapper中实现递归查询,或者通过多次查询来获取所有子节点。 首先,我需要设计Mapper的方法。可能需要一个方法用来根据当前节点ID查询所有子节点ID,包括所有层级的子节点。然后,另一个方法用来批量删除这些ID。或者,使用MySQL的DELETE语句结合JOIN来删除所有子节点。 但用户指定使用MyBatis,所以可能需要先查询所有子节点ID,再执行删除。例如,在Mapper中写一个递归查询,获取当前节点及其所有子节点的ID列表,然后执行批量删除。 不过,递归查询在MySQL 5.7中可能需要使用存储过程,但这样可能不够灵活。另一种方法是在Service层递归查询子节点,收集所有需要删除的ID,然后一次性删除。这种方法可能在性能上有问题,如果层级很深的话,但实现起来比较简单。 或者,可以考虑在Mapper.xml中使用自定义的递归查询,例如通过多次自我连接,但这可能不够高效,特别是当层级不确定时。 这里可能需要一个SQL语句来获取某个节点的所有子节点。例如,使用类似如下的方法: SELECT id FROM category WHERE parent_id = #{id} -- 第一层子节点 UNION ALL SELECT c.id FROM category c INNER JOIN (上面的结果) t ON c.parent_id = t.id -- 以此类推,但MySQL 5.7不支持递归CTE,所以可能需要用存储过程或者在代码中递归处理。 因此,在代码中处理可能更可行。具体来说,可以编写一个递归方法,在Service层中,先查询当前节点的所有子节点,然后递归查询每个子节点的子节点,直到没有更多子节点,收集所有ID,最后连同当前节点ID一起删除。 但是,这样可能会有多次数据库查询,可能影响性能,但考虑到分类层级通常不会太深,可能可以接受。 现在,开始编写各个层的代码: 1. Controller层:处理HTTP DELETE请求,接收分类ID,调用Service层的删除方法。 2. Service层:实现删除逻辑。首先检查该分类是否有子分类。如果有,则递归获取所有子分类ID,连同当前ID一起删除。如果没有,只删除当前ID。 但这里有个问题:如何判断该分类是否有子分类?可以通过查询parent_id等于当前ID的记录是否存在。如果有,则需要递归删除所有子节点。 或者,不管是否是父节点,直接递归删除所有子节点和自身。这样,即使删除子节点,也会检查它是否有子节点,如果有则一并删除。但根据用户的需求,删除子级时应该只删除自身,但如果该子级还有子节点,应该如何处理呢?用户的需求描述是“删除子级时只删除该子级本身”,所以如果该子级还有子节点,是否应该不允许删除?或者用户可能希望删除该子级及其所有子节点?需要明确需求。 根据用户的问题描述:“删除父级时全部的子级包括父级本身都要删除,删除子级时只删除该子级本身”。这里的“子级”可能指的是没有子节点的叶子节点。但如果有子节点的子级,可能也应该视为父级,需要递归删除。因此,可能需要明确:当删除任何一个节点时,如果它有子节点,则必须删除所有子节点;否则只删除自己。或者,可能用户希望只有当删除的是顶级父节点时才删除所有子节点,而子节点的删除仅自身。这可能不太合理,需要仔细分析。 根据用户的前端代码,当点击删除时,会弹出确认对话框,提示“确认删除该分类及其所有子分类?”,这说明无论当前分类是父级还是子级,只要它有子分类,都会删除所有子分类。或者,可能前端根据是否有子分类来显示不同的提示信息。但根据代码中的提示信息是固定的,即不管是否有子分类,都会提示删除该分类及其所有子分类。因此,后端需要确保当删除某个分类时,无论它是否有子分类,都删除它本身以及所有子分类。因此,无论该分类是父级还是子级,只要有子分类,都递归删除所有子分类。 但根据用户的问题描述,可能用户希望当删除父级时删除所有子级,而删除子级时仅删除自己。这可能需要根据当前分类是否有子分类来决定删除方式。因此,在Service层中,需要判断当前分类是否有子分类,如果有,则递归删除所有子分类和自身;如果没有,只删除自身。 这样,删除操作分为两种情况: - 如果该分类有子节点:删除所有子节点及自身。 - 如果没有子节点:仅删除自身。 因此,Service层的逻辑应该是: 1. 根据当前分类ID,查询是否存在子节点(parent_id = 当前ID)。 2. 如果有子节点,则递归获取所有子节点的ID,连同当前ID一起删除。 3. 如果没有子节点,直接删除当前ID。 但如何高效地获取所有子节点的ID呢?这可能需要递归查询,或者使用存储过程。 在MySQL 5.7中,无法使用递归CTE,所以只能通过多次查询或者存储过程。在应用中处理的话,可以通过递归方法查询每个层级的子节点。 例如,在Service层: private List<Integer> getAllChildIds(Integer parentId) { List<Integer> childIds = categoryMapper.selectChildrenIdsByParentId(parentId); List<Integer> allChildIds = new ArrayList<>(childIds); for (Integer childId : childIds) { allChildIds.addAll(getAllChildIds(childId)); } return allChildIds; } 然后,收集所有子ID,加上当前ID,执行批量删除。 但这样会产生多次数据库查询,可能效率较低,但对于层级不深的情况是可以接受的。 另一种方法是,在Mapper.xml中编写一个递归查询,通过连接查询获取所有子节点。但这在MySQL 5.7中可能需要使用存储过程。 或者,可以设计一个SQL语句,通过自连接多次查询子节点,但层级深度有限。例如,假设分类层级最多有3层,可以写多个UNION ALL,但这种方法不够灵活,不推荐。 因此,可能在Service层中使用递归方法获取所有子节点ID是可行的。 现在,具体到代码: 首先,Controller层: @DeleteMapping("/deleteById/{id}") public ResultVO deleteById(@PathVariable Integer id) { categoryService.deleteCategoryAndChildren(id); return ResultVO.success(); } 这里假设ResultVO是自定义的返回对象。 Service层: @Service public class CategoryService { @Autowired private CategoryMapper categoryMapper; public void deleteCategoryAndChildren(Integer id) { List<Integer> idsToDelete = new ArrayList<>(); idsToDelete.add(id); // 递归获取所有子节点ID getAllChildIds(id, idsToDelete); // 执行批量删除 categoryMapper.deleteBatchIds(idsToDelete); } private void getAllChildIds(Integer parentId, List<Integer> ids) { List<Integer> childIds = categoryMapper.selectChildrenIdsByParentId(parentId); if (!childIds.isEmpty()) { ids.addAll(childIds); for (Integer childId : childIds) { getAllChildIds(childId, ids); } } } } 这里,getAllChildIds方法会递归查询每个parentId的子节点,并将所有子节点ID收集到ids列表中。 然后,Mapper层: public interface CategoryMapper { void deleteBatchIds(@Param("ids") List<Integer> ids); List<Integer> selectChildrenIdsByParentId(@Param("parentId") Integer parentId); } 在Mapper.xml中: <!-- 查询子节点ID列表 --> <select id="selectChildrenIdsByParentId" resultType="java.lang.Integer"> SELECT id FROM category WHERE parent_id = #{parentId} </select> <!-- 批量删除 --> <delete id="deleteBatchIds"> DELETE FROM category WHERE id IN <foreach collection="ids" item="id" open="(" separator="," close=")"> #{id} </foreach> </delete> 这样,当调用deleteCategoryAndChildren时,会先收集所有需要删除的ID(包括自身和所有子节点),然后通过批量删除操作一次性删除。 需要注意的是,在MySQL中使用MyISAM引擎,该引擎不支持事务和外键约束。因此,删除操作没有事务管理,但如果需要保证删除的原子性,可能需要考虑使用InnoDB引擎,并添加事务注解。但用户提供的表结构使用的是MyISAM,所以这里可能不需要处理事务,或者用户可能需要调整存储引擎。 另外,关于外键的问题,由于表结构中没有外键约束,删除父节点时不会自动删除子节点,所以必须手动处理,这正是上述代码所做的。 现在,总结各层代码: CategoryController:处理删除请求,调用Service方法。 CategoryService:递归获取所有子节点ID,执行批量删除。 CategoryMapper:定义查询子节点和批量删除的方法。 Mapper.xml:实现对应的SQL语句。 此外,需要注意MyBatis的配置是否正确,比如是否启用了批量操作,参数是否正确传递等。 可能的改进点: - 使用事务:在Service方法上添加@Transactional,确保删除操作的原子性。即使MyISAM不支持事务,但如果有其他操作需要事务,可能需要更改存储引擎为InnoDB。 - 性能优化:对于层级很深的分类,递归查询可能导致多次数据库访问,可以考虑在数据库层使用存储过程来一次性获取所有子节点ID,但这可能增加维护成本。 - 错误处理:在Service层处理异常,比如分类不存在等情况,但前端已经做了确认提示,可能不需要特别处理。 综上,这样设计的代码应该能够满足用户的需求:删除父级时删除所有子级和自身,删除子级时仅删除自身(如果该子级没有子节点的话)。如果有子级还有子节点,则递归删除所有后代。</think>以下是基于Spring Boot 3 + MyBatis + MySQL 5.7的后端删除实现,实现递归删除所有子分类功能: ### 1. CategoryController 层 ```java @RestController @RequestMapping("/category") public class CategoryController { @Autowired private CategoryService categoryService; @DeleteMapping("/deleteById/{id}") public ResultVO<String> deleteById(@PathVariable Integer id) { categoryService.deleteCategoryWithChildren(id); return ResultVO.success("删除成功"); } } ``` ### 2. CategoryService 层 ```java @Service public class CategoryService { @Autowired private CategoryMapper categoryMapper; @Transactional public void deleteCategoryWithChildren(Integer id) { // 获取所有需要删除的ID(包含自身及所有子节点) List<Integer> deleteIds = new ArrayList<>(); deleteIds.add(id); // 添加当前节点 findChildIds(id, deleteIds); // 递归获取子节点 // 执行批量删除 if (!deleteIds.isEmpty()) { categoryMapper.deleteBatchIds(deleteIds); } } // 递归查找所有子节点ID private void findChildIds(Integer parentId, List<Integer> deleteIds) { List<Integer> childIds = categoryMapper.selectChildrenIds(parentId); for (Integer childId : childIds) { deleteIds.add(childId); findChildIds(childId, deleteIds); // 递归查询子节点 } } } ``` ### 3. CategoryMapper 接口层 ```java @Mapper public interface CategoryMapper { // 根据父ID查询子节点ID列表 List<Integer> selectChildrenIds(@Param("parentId") Integer parentId); // 批量删除操作 int deleteBatchIds(@Param("ids") List<Integer> ids); } ``` ### 4. CategoryMapper.xml ```xml <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.example.mapper.CategoryMapper"> <!-- 查询子节点ID列表 --> <select id="selectChildrenIds" resultType="java.lang.Integer"> SELECT id FROM category WHERE parent_id = #{parentId} </select> <!-- 批量删除 --> <delete id="deleteBatchIds"> DELETE FROM category WHERE id IN <foreach collection="ids" item="id" open="(" separator="," close=")"> #{id} </foreach> </delete> </mapper> ``` ### 实现逻辑说明 1. **递归查询子节点**:通过`findChildIds`方法递归查询所有子节点ID,时间复杂度为$O(n)$,适用于常规树形结构深度 2. **批量删除**:使用`DELETE ... IN`语句实现高效批量删除,时间复杂度$O(1)$ 3. **事务控制**:`@Transactional`注解保证删除操作的原子性 4. **树形结构处理**:当删除父节点时,会连带删除其所有子节点;删除叶子节点时仅删除自身 ### 性能优化建议 1. **索引优化**:确保`parent_id`字段已建立索引(已有`KEY parent_id (parent_id)`) 2. **批量操作**:使用`<foreach>`标签实现批量SQL,减少数据库交互次数 3. **异步删除**:如需处理超大层级,可通过@Async实现异步删除 ### 数据库兼容性说明 由于使用标准的SQL语法: - 完全兼容MySQL 5.7版本 - 支持MyISAM存储引擎(但建议改用InnoDB以保证事务完整性) - 无递归CTE语法依赖 > 注意:ResultVO需要自定义实现,包含`success()`和错误处理等方法。
阅读全文

相关推荐

//提交添加帧 const submitForm = async () => { try { await formRef.value.validate() const message = frameArray.value.length === 0 ? '帧结构与协议之后无法切换,确认提交?' : '确认提交?' ElMessageBox.confirm(message, '添加帧', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { // 设置自动填充的值 console.log(currentProtocol.value) frameFrom.value.frameheadId = currentFramehead.value.frameheadId frameFrom.value.protocolId = currentProtocol.value.nprotocolId //frameFrom拼接到frame上 frame.value = { ...frame.value, ...frameFrom.value } // 添加到数组 frameArray.value.push(frame.value) // 这里处理提交逻辑 console.log('提交数据frameArray:', frameArray.value) addFrameDialogVisible.value = false }) } catch (error) { console.error('表单验证失败:', error) } } <el-button type="primary" :icon="Edit" v-if="selectedFrame" @click="frameEdit" circle style="margin-bottom: 0.125rem" /> <el-select v-model="selectedFrameIdcode" placeholder="选择帧" class="frame-select" clearable > <el-option v-for="item in frameArray" :key="item.frameIdcode" :label="item.name" :value="item.frameIdcode" /> </el-select>const selectedFrame = computed(() => { return frameArray.value.find( (item) => item.frameIdcode === selectedFrameIdcode.value ) }) //选择的帧的详细信息 <el-dialog title="新增帧" v-model="addFrameDialogVisible" width="30%" @close="resetForm" ><el-form :model="frameFrom" :rules="rules" ref="formRef" label-width="100px" > <el-form-item label="帧编码" prop="frameIdcode"> <el-input v-model="frameFrom.frameIdcode" placeholder="请输入16进制编码" ><template #prepend>0x</template></el-input > </el-form-item> <el-form-item label="名称" prop="name"> <el-input v-model="frameFrom.name" placeholder="请输入名称" maxlength="20" show-word-limit /> </el-form-item> <el-form-item label="描述" prop="desc"> <el-input v-model="frameFrom.desc" type="textarea" :rows="3" placeholder="请输入描述" maxlength="255" show-word-limit /> </el-form-item></el-form ><template #footer> <el-button @click="addFrameDialogVisible = false">取消</el-button> <el-button type="primary" @click="submitForm">确定</el-button> </template></el-dialog > 实现frameEdit

<template> <el-tree :data=“treeData” :props=“defaultProps” node-key=“id” :highlight-current=“true” :expand-on-click-node=“false” > <template #default=“{ node, data }”> {{ node.label }} (ID: {{ data.id }}) </template> </el-tree> </template> <script setup> import { ref, onMounted } from ‘vue’ import request from ‘@/utils/request’ // 根据实际路径调整 import { ElTree } from ‘element-plus’ // 树形数据存储 const treeData = ref([]) // 树形组件配置 const defaultProps = ref({ children: ‘children’, label: ‘title’ }) // 加载树形数据 const loadTreeData = async () => { try { const res = await request.get(‘/category/tree’) // 注意接口路径需要与后端一致 if (res.code === ‘200’) { treeData.value = res.data } else { console.error(‘数据加载失败:’, res.msg) } } catch (error) { console.error(‘请求异常:’, error) } } // 组件挂载时加载数据 onMounted(() => { loadTreeData() }) </script> <style scoped> .category-container { padding: 20px; background: #fff; min-height: 500px; } .custom-node { font-size: 14px; } .el-tree { margin-top: 15px; } </style>以下面这种格式美化树状结构展示,要求给出所有代码<template> <el-button type=“primary” @click=“showFormDialog()”>新增分类</el-button> <el-table :data="tableData" row-key="id" :tree-props="{children: 'children'}" border default-expand-all style="width: 100%" > <el-table-column prop="id" label="ID" width="80" /> <el-table-column prop="title" label="分类名称" /> <el-table-column label="操作" width="180" align="center"> <template #default="{ row }"> <el-button type="primary" size="small" @click="showFormDialog(row.id)" >修改</el-button> <el-button type="danger" size="small" @click="handleDelete(row.id)" >删除</el-button> </template> </el-table-column> </el-table> <el-dialog v-model="formVisible" :title="currentId ? '修改分类' : '新增分类" width="30%" > <el-form ref="formRef" :model="form" :rules="rules" label-width="80px" > <el-form-item label="分类名称" prop="title"> <el-input v-model="form.title" placeholder="请输入分类名称" /> </el-form-item> </el-form> <template #footer> <el-button @click="formVisible = false">取消</el-button> <el-button type="primary" @click="submitForm">确认</el-button> </template> </el-dialog> </template> <style scoped> .app-container { padding: 20px; } .mb-4 { margin-bottom: 16px; } </style>

// 提交表单 const submitForm = () => { if (checkItemApp() && checkName() && checkRange()) { frameFormRef.value .validate((valid) => { if (valid) { ElMessageBox.confirm('确认保存数据吗?', '保存帧结构', { confirmButtonText: '保存', cancelButtonText: '取消', type: 'warning' }) .then(async () => { // console.log('确认了') if (valid) { try { // 预处理数据 HeadItemData.value.splice(HeadItemData.value.length - 1, 1) HeadItemData.value.forEach((item) => { // delete item.endRange // delete item.startRange delete item.isWarning delete item.isNew //把每个数据项的defaultValue从16进制转为10进制 if (item.defaultValue) { item.defaultValue = parseInt(item.defaultValue, 16) } }) // 开启加载状态 fullscreenLoading.value = true // 提交帧结构(第一层校验) const fhRes = await frameheadAPI(frameForm.value) if (fhRes.code !== 200) { throw new Error(帧头提交失败: ${fhRes.msg}) } // 添加关联ID(第二层处理) const frameheadId = fhRes.data HeadItemData.value.forEach((item) => { item.frameheadId = frameheadId }) console.log('HeadItemData.value', HeadItemData.value) // 提交头项数据(第二层校验) const hiRes = await headitemAPI(HeadItemData.value) if (hiRes.code !== 200) { throw new Error(头项提交失败: ${hiRes.msg}) } // 双重校验通过 ElMessage.success('提交成功') //初始化 init() isCreating.value = false showContent.value = false frameForm.value = { type: '', desc: '' } } catch (error) { console.error('提交异常:', error) ElMessage.error( error.message || '提交失败,请检查网络或数据' ) } finally { // 确保关闭加载状态 fullscreenLoading.value = false } } }) .catch(() => { }) } }) .catch(() => { }) } }优化代码

你是一个vue前端工程师,有着多年的研发经验,现在需要优化以下代码: 1、开始时间为今天向前一个自然月的第一天; 2、结束时间为今天日期; 3、开始时间选择和结束时间选择区间最大2个月; 4、要给开始日期与结束日期一个初始值; <template> <dt-location :data="[{title:'报告管理'},{title:'车辆报告'}]"></dt-location> <dt-toolbar-box> <template #default> <el-button-group> <el-button icon="Plus" @click="$common.linkUrl('/vinReport/edit')">查询</el-button> <el-button icon="Download" @click="exportExcel">导出Excel</el-button> </el-button-group> <el-input placeholder="输入关健字" v-model="datas.keyword" @clear="initData" @keyup.enter.native="initData" clearable> <template #append> <el-button icon="Search" @click="initData"></el-button> </template> </el-input> </template> <template #more>
报告类型
<el-select v-model="datas.reportType" placeholder="请选择..." @change="initData"> <el-option label="全部类型" :value="-1"></el-option> <el-option label="精选-A" :value="1"></el-option> <el-option label="优选-A" :value="2"></el-option> <el-option label="优选-B" :value="3"></el-option> <el-option label="优选-D" :value="4"></el-option> <el-option label="优选-C" :value="5"></el-option> <el-option label="优选-E" :value="6"></el-option> <el-option label="精选-C" :value="7"></el-option> <el-option label="优选-F" :value="8"></el-option> <el-option label="优选-G" :value="9"></el-option> <el-option label="精选-D" :value="10"></el-option> <el-option label="优选-H" :value="11"></el-option> <el-option label="优选-I" :value="12"></el-option> <el-option label="精选-E" :value="13"></el-option> </el-select>
状态
<el-select v-model="datas.reportStatus" placeholder="请选择..." @change="initData"> <el-option label="全部状态" :value="-1"></el-option> <el-option label="待确认" :value="100"></el-option> <el-option label="待付款" :value="110"></el-option> <el-option label="生成中" :value="120"></el-option> <el-option label="已生效" :value="130"></el-option> <el-option label="已退单" :value="190"></el-option> <el-option label="查询失败" :value="199"></el-option> </el-select>
查询日期
<el-date-picker style="width:280px" v-model="datas.dateRange" type="daterange" value-format="YYYY-MM-DD" start-placeholder="开始时间" end-placeholder="结束时间" @change="initData"> </el-date-picker>
</template> </dt-toolbar-box> <el-card class="table-card"> <el-table ref="tableRef" v-loading="datas.loading" :data="datas.listData" stripe class="table-list" @selection-change="handleSelectionChange"> <el-table-column fixed="left" type="selection" width="45"></el-table-column> <el-table-column fixed="left" prop="reportNo" label="报告编号" min-width="200" show-overflow-tooltip></el-table-column> <el-table-column prop="userName" label="用户名" width="100" show-overflow-tooltip></el-table-column> <el-table-column label="报告类型" width="95"> <template #default="scope"> <el-tag size="small" type="info" effect="plain" v-if="scope.row.reportType==1">精选-A</el-tag> <el-tag size="small" type="info" effect="plain" v-else-if="scope.row.reportType==2">优选-A</el-tag> <el-tag size="small" type="info" effect="plain" v-else-if="scope.row.reportType==3">优选-B</el-tag> <el-tag size="small" type="info" effect="plain" v-else-if="scope.row.reportType==4">优选-D</el-tag> <el-tag size="small" type="info" effect="plain" v-else-if="scope.row.reportType==5">优选-C</el-tag> <el-tag size="small" type="info" effect="plain" v-else-if="scope.row.reportType==6">优选-E</el-tag> <el-tag size="small" type="info" effect="plain" v-else-if="scope.row.reportType==7">精选-C</el-tag> <el-tag size="small" type="info" effect="plain" v-else-if="scope.row.reportType==8">优选-F</el-tag> <el-tag size="small" type="info" effect="plain" v-else-if="scope.row.reportType==9">优选-G</el-tag> <el-tag size="small" type="info" effect="plain" v-else-if="scope.row.reportType==10">精选-D</el-tag> <el-tag size="small" type="info" effect="plain" v-else-if="scope.row.reportType==11">优选-H</el-tag> <el-tag size="small" type="info" effect="plain" v-else-if="scope.row.reportType==12">优选-I</el-tag> <el-tag size="small" type="info" effect="plain" v-else-if="scope.row.reportType==13">精选-E</el-tag> <el-tag size="small" type="info" effect="plain" v-else-if="scope.row.reportType==22">甄选-B</el-tag> <el-tag size="small" effect="info" v-else>未定义</el-tag> </template> </el-table-column> <el-table-column label="状态" width="95"> <template #default="scope"> <el-tag size="small" type="dark" effect="dark" v-if="scope.row.reportStatus==100">待确认</el-tag> <el-tag size="small" type="dark" effect="dark" v-else-if="scope.row.reportStatus==110">待付款</el-tag> <el-tag size="small" type="danger" effect="dark" v-else-if="scope.row.reportStatus==120">生成中</el-tag> <el-tag size="small" type="success" effect="dark" v-else-if="scope.row.reportStatus==130">已生效</el-tag> <el-tag size="small" type="info" effect="dark" v-else-if="scope.row.reportStatus==190">已退单</el-tag> <el-tag size="small" type="info" effect="dark" v-else-if="scope.row.reportStatus==199">查询失败</el-tag> <el-tag size="small" type="info" effect="dark" v-else>未知</el-tag> </template> </el-table-column> <el-table-column prop="vin" label="Vin" width="180" show-overflow-tooltip></el-table-column> <el-table-column label="报告内容" width="260" show-overflow-tooltip> <template #default="scope"> {{scope.row.reportContent}} </template> </el-table-column> <el-table-column label="报告链接" width="260" show-overflow-tooltip> <template #default="scope"> {{scope.row.reportUrl}} </template> </el-table-column> <el-table-column label="查询日期" width="160"> <template #default="scope"> {{scope.row.addTime}} </template> </el-table-column> <el-table-column fixed="right" label="操作" width="120"> </el-table-column> </el-table> </el-card> <el-pagination background @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-page="datas.pageIndex" :page-sizes="[10, 20, 50, 100]" :page-size="datas.pageSize" layout="total, sizes, prev, pager, next, jumper" :total="datas.totalCount"> </el-pagination>
</template> <script setup> import { ref, reactive, getCurrentInstance } from "vue" import DtToolbarBox from '../../components/layout/DtToolbarBox.vue' //获取全局属性 const { proxy } = getCurrentInstance() const tableRef = ref(null) //定义属性 const datas = reactive({ loading: false, keyword: '', totalCount: 0, pageSize: 10, pageIndex: 1, listData: [], multipleSelection: [], reportType: -1, reportStatus: -1, dateRange: [], }); // 计算自然月的第一天 const getFirstDayOfPreviousMonth = () => { const today = new Date(); const firstDay = new Date(today.getFullYear(), today.getMonth() - 1, 1); return firstDay.toISOString().split("T")[0]; // 格式化为 YYYY-MM-DD }; // 初始化日期范围 const initDateRange = () => { const today = new Date().toISOString().split("T")[0]; const firstDayPrevMonth = calculateFirstDayOfPreviousMonth(); console.log("Start Date:", firstDayPrevMonth); console.log("End Date:", today); datas.dateRange = [firstDayPrevMonth, today]; }; // 日期选择器配置 const pickerOptions = reactive({ disabledDate(time) { const [start, end] = datas.dateRange; if (!start) return false; const maxEndDate = proxy.$dayjs(start).add(1, 'month').toDate(); return time.getTime() > maxEndDate || time.getTime() > Date.now(); }, onPick({ maxDate, minDate }) { if (maxDate && minDate) { const diff = proxy.$dayjs(maxDate).diff(minDate, 'month', true); if (diff >= 1) { proxy.$nextTick(() => { datas.dateRange = [ minDate, proxy.$dayjs(minDate).add(1, 'month').toDate() ]; proxy.$message.warning('时间选择范围不能超过1个月'); }); } } } }); // 修改日期范围时的处理 const handleDateChange = (val) => { if (!val || val.length !== 2) return; const [start, end] = val; const monthDiff = proxy.$dayjs(end).diff(start, 'month', true); if (monthDiff > 1) { proxy.$message.warning('时间选择范围不能超过1个月'); initDateRange(); // 重置为默认范围 return; } datas.dateRange = val; initData(); }; //初始化数据 const initData = async () => { if (!ValidateDateRange(datas.dateRange)) { proxy.$message.warning("日期范围不能超过2个月,请重新选择!"); return; } let sendUrl = /vehicleReport/reports?pageSize=${datas.pageSize}&pageIndex=${datas.pageIndex} + &reportType=${datas.reportType} + &reportStatus=${datas.reportStatus} if (datas.dateRange.length > 0) { sendUrl += &startTime=${datas.dateRange[0]} sendUrl += &endTime=${datas.dateRange[1]} } if (datas.keyword.length > 0) { sendUrl += &keyword=${encodeURI(datas.keyword)} } //获取分页列表 await proxy.$api.request({ url: sendUrl, beforeSend() { datas.loading = true }, success(res) { datas.listData = res.data let pageInfo = JSON.parse(res.headers["x-pagination"]) datas.totalCount = pageInfo.totalCount datas.pageSize = pageInfo.pageSize datas.pageIndex = pageInfo.pageIndex }, error(err) { datas.listData = [] }, complete() { datas.loading = false } }) } //多选删除 const deleteCheckAll = () => { //拿到选中的数据 let list = datas.multipleSelection //检查是否有选中 if (!list.length) { proxy.$message({ type: 'warning', message: '请选择要删除的记录' }) return false } //执行删除操作 proxy.$confirm('确认要删除该记录吗?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { let ids = list.map(obj => obj.id) proxy.$api.request({ method: 'delete', url: /vehicleReport/reports?ids=${ids.toString()}, loading: true, successMsg: '已删除成功', success(res) { tableRef.value.clearSelection() //清除选中状态 initData() //重新加载列表 } }) }).catch(() => {}) } //单项删除 const deleteItem = (val) => { //执行删除操作 proxy.$confirm('确认要删除该记录吗?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { proxy.$api.request({ method: 'delete', url: /vehicleReport/reports/${val}, loading: true, successMsg: '已删除成功', success(res) { initData() //重新加载列表 } }) }).catch(() => {}) } //选中第几行 const handleSelectionChange = (val) => { datas.multipleSelection = val } //每页显示数量 const handleSizeChange = (val) => { if (datas.pageSize != val) { datas.pageSize = val initData() } } //跳转到第几页 const handleCurrentChange = (val) => { if (datas.pageIndex != val) { datas.pageIndex = val initData() } } // 导出处理方法 const exportExcel = async () => { try { const params = { StartTime: datas.dateRange[0], EndTime: datas.dateRange[1], ReportType: datas.reportType, ReportStatus: datas.reportStatus, Keyword: datas.keyword, Format: "xlsx" // 支持 csv 或 xlsx }; // 显示加载动画 if (proxy && proxy.$loading) { proxy.$loading.show(); } const response = await proxy.$api.request({ url: "/https/wenku.csdn.net/vehicleReport/ExportExcel", method: "POST", data: params, responseType: 'arraybuffer', // 确保以二进制形式接收数据 headers: { 'Content-Type': 'application/json' }, success(res) { console.log(res); // 检查 response 是否有效 if (!res || !res.data) { throw new Error("服务器未返回有效数据"); } // 创建Blob对象并下载文件 const blob = new Blob([res.data], { type: res.headers['content-type'] || "application/octet-stream" }); const url = window.URL.createObjectURL(blob); console.log(url); // 隐藏加载动画 if (proxy && proxy.$loading) { proxy.$loading.hide(); } // 创建下载链接 const link = document.createElement("a"); link.href = url; link.download = 车辆报告_${new Date().toISOString().slice(0, 10)}.${params.Format}; link.click(); // 释放 URL 对象 window.URL.revokeObjectURL(url); }, error(err) { throw new Error("服务器未返回有效数据"); }, complete() { datas.loading = false } }); } catch (error) { // 隐藏加载动画 if (proxy && proxy.$loading) { proxy.$loading.hide(); } console.error("导出失败:", error.message || error); proxy.$message.error("导出失败,请检查网络或联系管理员!"); } }; // 计算自然月的第一天 const getFirstDayOfPreviousMonth = () => { const today = new Date() const firstDay = new Date(today.getFullYear(), today.getMonth() - 1, 1) return proxy.$dayjs(firstDay).format('YYYY-MM-DD') } // 初始化日期范围 const initDateRange = () => { const today = proxy.$dayjs().format('YYYY-MM-DD') const firstDayPrevMonth = getFirstDayOfPreviousMonth() datas.dateRange = [firstDayPrevMonth, today] } // 日期选择器配置 const pickerOptions = reactive({ disabledDate: (time) => { if (!datas.dateRange || datas.dateRange.length === 0) return false const [start, end] = datas.dateRange const limitDate = start ? proxy.$dayjs(start).add(1, 'month').toDate() : proxy.$dayjs(end).subtract(1, 'month').toDate() return start ? time.getTime() > limitDate.getTime() || time.getTime() > Date.now() : time.getTime() < limitDate.getTime() }, onPick: ({ maxDate, minDate }) => { // 自动限制选择范围 if (maxDate && minDate) { const diff = proxy.$dayjs(maxDate).diff(minDate, 'month') if (diff >= 1) { proxy.$nextTick(() => { datas.dateRange = [ minDate, proxy.$dayjs(minDate).add(1, 'month').toDate() ] proxy.$message.warning('时间选择范围不能超过1个月') }) } } } }); //初始化工具栏时设置默认日期范围 initLoad(); //执行初始化方法 initData(); // 页面加载时初始化日期范围 onMounted(() => { initDateRange(); initData(); }); </script> // 样式 <style scoped> .el-tooltip { max-width: 600px; } </style>

根据上述描述调整这部分代码<template> <view class="container"> <view class="section"> <view class="section-title">集装器信息</view> <view class="input-group"> <input type="text" v-model="containerCode" placeholder="集装器箱板号" @confirm="confirmContainer" /> <button @click="confirmContainer" type="default" size="mini">确认</button> <button @click="scanContainerCode" type="primary">扫描集装器</button> </view> <view class="pallet-list" v-if="containerCode"> <view class="list-title">已确认集装器</view> <view class="pallet-item"> <text>{{containerCode}}</text> <text class="delete" @click="clearContainer">删除</text> </view> </view> </view> <view class="section"> <view class="section-title">托盘信息</view> <view class="input-group"> <input type="text" v-model="palletCode" placeholder="托盘号" @confirm="addPallet" /> <button @click="addPallet" type="default" size="mini">确认</button> <button @click="scanPalletCode" type="primary">扫描托盘</button> </view> <view class="pallet-list" v-if="palletList.length > 0"> <view class="list-title">已关联托盘</view> <view v-for="(pallet, index) in palletList" :key="index" class="pallet-item"> <text>{{pallet.code}}</text> <text class="delete" @click="removePallet(index)">删除</text> </view> </view> </view> </view> <view class="index"> <sp-html2pdf-render domId="render-dom" ref="renderRef" pdfFileName="这是一个pdf文件" :isPagingEnabled="true" @beforeSavePDF="beforeSavePDF" @successSavePDF="successSavePDF" @renderOver="renderOver" ></sp-html2pdf-render> <view class="render-main"> <view id="render-dom" class="render-content"> <view class="photo-section">

已经知道树形结构数据展示的前端代码import { http } from “@/utils/http”; import { R, baseUrlApi } from “./utils”; export class UnitEntity { id: Number; unit: String; pid: Number; children: Array<UnitEntity>; } /** 获取全部的unit数据 */ export const getData = () => { return http.request<R<Array<UnitEntity>>>(“get”, baseUrlApi(“unit/list”)); };export const baseUrlApi = (url: string) => /api_demo/${url}; /** 后端返回通用数据类型 */ export type R<T> = { code: Number; msg: String; data: T; }; /** 同步休眠函数, 参数为毫秒 */ export const sleep = (ms: number): Promise<void> => { return new Promise(resolve => setTimeout(resolve, ms)); }; /** 分页数据类型 / export type PageUtils<T> = { /* 总记录数 / totalCount: number; /* 每页记录数 / pageSize: number; /* 总页数 / totalPage: number; /* 当前页数 / currPage: number; /* 列表数据 */ list: Array<T>; }; export const getStoreUser = () => { const res = sessionStorage.getItem(“user-info”); // const res = sessionStorage.getItem(“user-info”); console.log(res); return JSON.parse(res); }; <script setup lang=“ts”> import { ref, onMounted } from “vue”; import { UnitEntity, getData } from “@/api/tree.ts”; defineOptions({ name: “Welcome” }); const tableData = ref<Array<UnitEntity>>(); onMounted(() => { getData().then(res => { console.log(res); if (res.code === 0) { tableData.value = res.data; } }); }); </script> <template> <el-table :data=“tableData” style=“width: 100%; margin-bottom: 20px” row-key=“id” border default-expand-all <el-table-column prop=“id” label=“序号” sortable /> <el-table-column prop=“unit” label=“单位” sortable /> <el-table-column fixed=“right” header-align=“center” align=“center” width=“150” label=“操作” <template v-slot=“scope”> <el-button type=“text” size=“small” @click=“addOrUpdateHandle(scope.row.id)” 修改</el-button </template> </el-table-column> </el-table> </template>如何改写为vue3的前端代码已经知道后端 CategoryController为 @GetMapping(“/tree”) public Result getCategoryTree() { List<Category> treeData = categoryService.getCategoryTree(); return Result.success(); }且已经知道前端代码的统一错误返回结果写法如下所示const add=()=>{ formRef.value.validate((valid)=>{ if(valid){//验证通过的话再新增 request.post(‘/category/add’,data.form).then(res=>{ if(res.code===‘200’){ data.formVisible=false ElMessage.success(‘新增成功’) load()//新增后重新加载最新的数据 }else{ ElMessage.error(res.msg) } }) } })给出vue3的全部完整代码全部在Category.vue中,实现前端树形结构数据展示

最新推荐

recommend-type

python 点云ply文件

python
recommend-type

竞争对比.xlsx

竞争对比.xlsx
recommend-type

2025年的Java程序员面试三件套:多线程+算法+微服务.docx

2025年的Java程序员面试三件套:多线程+算法+微服务.docx
recommend-type

2025年跳槽首秀,面试阿里P6,让我见识到了什么叫天花板。。。.md

2025年跳槽首秀,面试阿里P6,让我见识到了什么叫天花板。。。.md
recommend-type

2025年Java春招高级面试指南(1到5年Java面试者必备).zip

2025年Java春招高级面试指南(1到5年Java面试者必备).zip
recommend-type

省市县三级联动实现与应用

省市县三级联动是一种常见的基于地理位置的联动选择功能,广泛应用于电子政务、电子商务、物流配送等系统的用户界面中。它通过用户在省份、城市、县三个层级之间进行选择,并实时显示下一级别的有效选项,为用户提供便捷的地理位置选择体验。本知识点将深入探讨省市县三级联动的概念、实现原理及相关的JavaScript技术。 1. 概念理解: 省市县三级联动是一种动态联动的下拉列表技术,用户在一个下拉列表中选择省份后,系统根据所选的省份动态更新城市列表;同理,当用户选择了某个城市后,系统会再次动态更新县列表。整个过程中,用户不需要手动刷新页面或点击额外的操作按钮,选中的结果可以直接用于表单提交或其他用途。 2. 实现原理: 省市县三级联动的实现涉及前端界面设计和后端数据处理两个部分。前端通常使用HTML、CSS和JavaScript来实现用户交互界面,后端则需要数据库支持,并提供API接口供前端调用。 - 前端实现: 前端通过JavaScript监听用户的选择事件,一旦用户选择了一个选项(省份、城市或县),相应的事件处理器就会被触发,并通过AJAX请求向服务器发送最新的选择值。服务器响应请求并返回相关数据后,JavaScript代码会处理这些数据,动态更新后续的下拉列表选项。 - 后端实现: 后端需要准备一套完整的省市区数据,这些数据通常存储在数据库中,并提供API接口供前端进行数据查询。当API接口接收到前端的请求后,会根据请求中包含的参数(当前选中的省份或城市)查询数据库,并将查询结果格式化为JSON或其他格式的数据返回给前端。 3. JavaScript实现细节: - HTML结构设计:创建三个下拉列表,分别对应省份、城市和县的选项。 - CSS样式设置:对下拉列表进行样式美化,确保良好的用户体验。 - JavaScript逻辑编写:监听下拉列表的变化事件,通过AJAX(如使用jQuery的$.ajax方法)向后端请求数据,并根据返回的数据更新其他下拉列表的选项。 - 数据处理:在JavaScript中处理从服务器返回的数据格式,如JSON,解析数据并动态地更新下拉列表的内容。 4. 技术选型: - AJAX:用于前后端数据交换,无需重新加载整个页面即可更新部分页面的内容。 - jQuery:简化DOM操作和事件处理,提升开发效率。 - Bootstrap或其他CSS框架:帮助快速搭建响应式和美观的界面。 - JSON:数据交换格式,易于阅读,也易于JavaScript解析。 5. 注意事项: - 数据的一致性:在省市县三级联动中,必须确保数据的准确性和一致性,避免出现数据错误或不匹配的问题。 - 用户体验:在数据加载过程中,应该给予用户明确的反馈,比如加载指示器,以免用户对操作过程感到困惑。 - 网络和性能优化:对联动数据进行合理的分页、缓存等处理,确保数据加载的流畅性和系统的响应速度。 6. 可能遇到的问题及解决方案: - 数据量大时的性能问题:通过分页、延迟加载等技术减少一次性加载的数据量。 - 用户输入错误:提供输入校验,例如正则表达式校验省份名称的正确性。 - 兼容性问题:确保前端代码兼容主流的浏览器,对不支持JavaScript的环境提供回退方案。 通过上述知识点的介绍,我们可以了解到省市县三级联动的实现原理、前端与后端如何协作以及在实施过程中需要关注的技术细节和用户体验。实际开发中,结合具体需求和项目条件,开发者需要灵活运用各种技术和方法来构建一个高效、易用的省市县三级联动功能。
recommend-type

【性能测试基准】:为RK3588选择合适的NVMe性能测试工具指南

# 1. NVMe性能测试基础 ## 1.1 NVMe协议简介 NVMe,全称为Non-Volatile Memory Express,是专为固态驱动器设计的逻辑设备接口规范。与传统的SATA接口相比,NVMe通过使用PCI Express(PCIe)总线,大大提高了存储设备的数据吞吐量和IOPS(每秒输入输出操作次数),特别适合于高速的固态存储设备。
recommend-type

软件工程题目补充5:求解杨辉三角形系数

<think>我们正在讨论杨辉三角形及其在计算二项式系数中的应用。用户要求提供生成杨辉三角形系数的算法或编程实现。根据引用内容,我们可以使用递推方法,利用杨辉三角形的性质:每个数等于它肩上的两个数之和。同时,我们注意到杨辉三角形的第n行(n从0开始)对应n次二项式展开的系数。算法设计:1.我们可以用一个一维数组来存储当前行的系数,并利用前一行的数据计算当前行。2.为了节省空间,我们可以从后往前计算,这样不会覆盖还需要使用的上一行的数据。3.第i行(0-indexed)有i+1个元素,其中第一个和最后一个元素都是1。4.对于第i行,从第i-1个元素开始往前计算,直到第1个元素(0-indexed
recommend-type

YOYOPlayer1.1.3版发布,功能更新与源码分享

YOYOPlayer是一款基于Java开发的音频播放器,它具备了丰富的功能,并且源代码完全开放,用户可以在遵循相应许可的前提下自由下载和修改。根据提供的信息,我们可以探讨YOYOPlayer开发中涉及的诸多知识点: 1. Java编程与开发环境 YOYOPlayer是使用Java语言编写的,这表明开发者需要对Java开发环境非常熟悉,包括Java语法、面向对象编程、异常处理等。同时,还可能使用了Java开发工具包(JDK)以及集成开发环境(IDE),比如Eclipse或IntelliJ IDEA进行开发。 2. 网络编程与搜索引擎API YOYOPlayer使用了百度的filetype:lrc搜索API来获取歌词,这涉及到Java网络编程的知识,需要使用URL、URLConnection等类来发送网络请求并处理响应。开发者需要熟悉如何解析和使用搜索引擎提供的API。 3. 文件操作与管理 YOYOPlayer提供了多种文件操作功能,比如设置歌词搜索目录、保存目录、以及文件关联等,这需要开发者掌握Java中的文件I/O操作,例如使用File类、RandomAccessFile类等进行文件的读写和目录管理。 4. 多线程编程 YOYOPlayer在进行歌词搜索和下载时,需要同时处理多个任务,这涉及到多线程编程。Java中的Thread类和Executor框架等是实现多线程的关键。 5. 用户界面设计 YOYOPlayer具有图形用户界面(GUI),这意味着开发者需要使用Java图形界面API,例如Swing或JavaFX来设计和实现用户界面。此外,GUI的设计还需要考虑用户体验和交互设计的原则。 6. 音频处理 YOYOPlayer是一个音频播放器,因此需要处理音频文件的解码、播放、音量控制等音频处理功能。Java中与音频相关的API,如javax.sound.sampled可能被用于实现这些功能。 7. 跨平台兼容性 YOYOPlayer支持在Windows和Linux系统下运行,这意味着它的代码需要对操作系统的差异进行处理,确保在不同平台上的兼容性和性能。跨平台编程是Java的一个显著优势,利用Java虚拟机(JVM)可以在不同操作系统上运行相同的应用程序。 8. 配置文件和偏好设置 YOYOPlayer允许用户通过首选项设置来配置播放器的行为,这通常涉及到配置文件的读写操作,例如使用java.util.Properties类来处理用户设置的持久化。 9. 软件版本控制 YOYOPlayer的版本更新记录显示了软件开发中的版本控制概念。开发者可能使用Git或SVN等版本控制工具来管理源代码的版本和迭代。 10. 社区支持与开源项目管理 YOYOPlayer作为一个开源项目,其管理和维护涉及社区协作。这包括通过论坛、博客和社交媒体等渠道与用户沟通,获取反馈并提供支持。 YOYOPlayer1.0版本的发布及之后的1.1.3更新记录了程序在功能和性能上的改进,以及对用户反馈的响应。更新记录中的变更点涉及对搜索功能的优化、用户界面的改进、标签支持的增强、音频设备选择的添加、代理服务器连接的支持,以及一些用户界面细节的调整。 通过上述知识点的讲解,我们可以看到YOYOPlayer不仅是一个功能全面的音频播放器,而且还是一个展示Java编程能力、多线程处理、网络编程、图形用户界面设计和跨平台开发能力的综合性项目。
recommend-type

【固态硬盘寿命延长】:RK3588平台NVMe维护技巧大公开

# 1. 固态硬盘寿命延长的基础知识 ## 1.1 固态硬盘的基本概念 固态硬盘(SSD)是现代计算设备中不可或缺的存储设备之一。与传统的机械硬盘(HDD)相比,SSD拥有更快的读写速度、更小的体积和更低的功耗。但是,SSD也有其生命周期限制,主要受限于NAND闪存的写入次数。 ## 1.2 SSD的写入次数和寿命 每块SSD中的NAND闪存单元都有有限的写入次数。这意味着,随着时间的推移,SSD的