09. 开发物品申请交换功能
SQL
CREATE TABLE `charge` (
`id` int NOT NULL AUTO_INCREMENT COMMENT 'ID',
`item_id` int DEFAULT NULL COMMENT '被交换物品ID',
`content` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '交换物品',
`remark` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '交换理由',
`user_id` int DEFAULT NULL COMMENT '申请人',
`time` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '申请时间',
`status` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '审核状态',
`reason` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '审核理由',
`location` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '交换地点',
`share_time` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '交换时间',
`item_userid` int DEFAULT NULL COMMENT '物品主人ID',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='物品交换信息';
ELement-Plus 笔记
https://www.yuque.com/xiaqing-en2ii/skflxg/hzi02h8qfizne3yv
后端的接口
Charge.java
package com.example.entity;
public class Charge {
/**ID */
private Integer id;
/**被交换物品ID */
private Integer itemId;
private String itemName;
private String requirement;
/**交换物品 */
private String content;
/**交换理由 */
private String remark;
/**申请人 */
private Integer userId;
private String userName;
/**申请时间 */
private String time;
/**审核状态 */
private String status;
/**审核理由 */
private String reason;
/**交换地点 */
private String location;
/**交换时间 */
private String shareTime;
/**物品主人ID */
private Integer itemUserid;
private String itemUserName;
private String comment;
public String getComment() {
return comment;
}
public void setComment(String comment) {
this.comment = comment;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getItemId() {
return itemId;
}
public void setItemId(Integer itemId) {
this.itemId = itemId;
}
public String getItemName() {
return itemName;
}
public void setItemName(String itemName) {
this.itemName = itemName;
}
public String getRequirement() {
return requirement;
}
public void setRequirement(String requirement) {
this.requirement = requirement;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getRemark() {
return remark;
}
public void setRemark(String remark) {
this.remark = remark;
}
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getTime() {
return time;
}
public void setTime(String time) {
this.time = time;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getReason() {
return reason;
}
public void setReason(String reason) {
this.reason = reason;
}
public String getLocation() {
return location;
}
public void setLocation(String location) {
this.location = location;
}
public String getShareTime() {
return shareTime;
}
public void setShareTime(String shareTime) {
this.shareTime = shareTime;
}
public Integer getItemUserid() {
return itemUserid;
}
public void setItemUserid(Integer itemUserid) {
this.itemUserid = itemUserid;
}
public String getItemUserName() {
return itemUserName;
}
public void setItemUserName(String itemUserName) {
this.itemUserName = itemUserName;
}
}
ChargeService.java
package com.example.service;
import cn.hutool.core.date.DateUtil;
import com.example.entity.Charge;
import com.example.entity.Items;
import com.example.mapper.ChargeMapper;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
/**
* 业务处理
**/
@Service
public class ChargeService {
@Resource
private ChargeMapper chargeMapper;
@Resource
ItemsService itemsService;
/**
* 新增
*/
public void add(Charge charge) {
charge.setTime(DateUtil.now());
charge.setStatus("待审核");
chargeMapper.insert(charge);
}
/**
* 删除
*/
public void deleteById(Integer id) {
chargeMapper.deleteById(id);
}
/**
* 修改
*/
@Transactional
public void updateById(Charge charge) {
if ("通过".equals(charge.getStatus())) {
Items items = itemsService.selectById(charge.getItemId());
items.setStatus(false);
itemsService.updateById(items);
}
chargeMapper.updateById(charge);
}
/**
* 根据ID查询
*/
public Charge selectById(Integer id) {
return chargeMapper.selectById(id);
}
/**
* 查询所有
*/
public List<Charge> selectAll(Charge charge) {
return chargeMapper.selectAll(charge);
}
/**
* 分页查询
*/
public PageInfo<Charge> selectPage(Charge charge, Integer pageNum, Integer pageSize) {
PageHelper.startPage(pageNum, pageSize);
List<Charge> list = chargeMapper.selectAll(charge);
return PageInfo.of(list);
}
}
ChargeMapper.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.ChargeMapper">
<select id="selectAll" resultType="com.example.entity.Charge">
select charge.*, user1.name as userName, user2.name as itemUserName,
items.name as itemName, items.requirement
from `charge`
left join user user1 on charge.user_id = user1.id
left join user user2 on charge.item_userid = user2.id
left join items on charge.item_id = items.id
<where>
<if test="itemName != null"> and items.name like concat('%', #{itemName}, '%')</if>
<if test="userId != null"> and charge.user_id = #{userId}</if>
<if test="itemUserid != null"> and charge.item_userid = #{itemUserid}</if>
</where>
order by charge.id desc
</select>
<insert id="insert" parameterType="com.example.entity.Charge" useGeneratedKeys="true">
insert into `charge`
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="id != null">id,</if>
<if test="itemId != null">item_id,</if>
<if test="content != null">content,</if>
<if test="remark != null">remark,</if>
<if test="userId != null">user_id,</if>
<if test="time != null">time,</if>
<if test="status != null">status,</if>
<if test="reason != null">reason,</if>
<if test="location != null">location,</if>
<if test="shareTime != null">share_time,</if>
<if test="itemUserid != null">item_userid,</if>
<if test="comment != null">comment,</if>
</trim>
values
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="id != null">#{id},</if>
<if test="itemId != null">#{itemId},</if>
<if test="content != null">#{content},</if>
<if test="remark != null">#{remark},</if>
<if test="userId != null">#{userId},</if>
<if test="time != null">#{time},</if>
<if test="status != null">#{status},</if>
<if test="reason != null">#{reason},</if>
<if test="location != null">#{location},</if>
<if test="shareTime != null">#{shareTime},</if>
<if test="itemUserid != null">#{itemUserid},</if>
<if test="comment != null">#{comment},</if>
</trim>
</insert>
<update id="updateById" parameterType="com.example.entity.Charge">
update `charge`
<set>
<if test="id != null">
id = #{id},
</if>
<if test="itemId != null">
item_id = #{itemId},
</if>
<if test="content != null">
content = #{content},
</if>
<if test="remark != null">
remark = #{remark},
</if>
<if test="userId != null">
user_id = #{userId},
</if>
<if test="time != null">
time = #{time},
</if>
<if test="status != null">
status = #{status},
</if>
<if test="reason != null">
reason = #{reason},
</if>
<if test="location != null">
location = #{location},
</if>
<if test="shareTime != null">
share_time = #{shareTime},
</if>
<if test="itemUserid != null">
item_userid = #{itemUserid},
</if>
</set>
where id = #{id}
</update>
</mapper>
前端页面
物品申请信息 Charge.vue
<template>
<div>
<div class="card" style="margin-bottom: 5px;">
<el-input v-model="data.itemName" style="width: 300px; margin-right: 10px" placeholder="请输入物品名称查询"></el-input>
<el-button type="primary" @click="load">查询</el-button>
<el-button type="info" style="margin: 0 10px" @click="reset">重置</el-button>
</div>
<div class="card" style="margin-bottom: 5px">
<el-table :data="data.tableData" stripe>
<el-table-column prop="itemName" label="被交换物品"></el-table-column>
<el-table-column prop="requirement" label="交换条件"></el-table-column>
<el-table-column prop="content" label="交换物品"></el-table-column>
<el-table-column prop="remark" label="交换理由"></el-table-column>
<el-table-column prop="time" label="申请时间"></el-table-column>
<el-table-column prop="status" label="审核状态">
<template v-slot="scope">
<el-tag type="warning" v-if="scope.row.status === '待审核'">待审核</el-tag>
<el-tag type="success" v-if="scope.row.status === '通过'">通过</el-tag>
<el-tag type="danger" v-if="scope.row.status === '拒绝'">拒绝</el-tag>
</template>
</el-table-column>
<el-table-column prop="reason" label="审核理由"></el-table-column>
<el-table-column prop="location" label="交换地点"></el-table-column>
<el-table-column prop="shareTime" label="交换时间"></el-table-column>
<el-table-column prop="itemUserName" label="物品主人"></el-table-column>
<el-table-column prop="comment" label="物品主人留言"></el-table-column>
<el-table-column label="操作" align="center" width="160">
<template #default="scope">
<el-button :disabled="scope.row.status === '通过'" type="danger" @click="handleDelete(scope.row.id)">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
<div class="card">
<el-pagination @current-change="load" background layout="total, prev, pager, next" v-model:page-size="data.pageSize" v-model:current-page="data.pageNum" :total="data.total"/>
</div>
</div>
</template>
<script setup>
import request from "@/utils/request";
import {reactive, ref} from "vue";
import {ElMessageBox, ElMessage} from "element-plus";
const formRef = ref()
const data = reactive({
user: JSON.parse(localStorage.getItem('system-user') || '{}'),
pageNum: 1,
pageSize: 10,
total: 0,
formVisible: false,
form: {},
tableData: [],
itemName: null,
})
// 分页查询
const load = () => {
request.get('/charge/selectPage', {
params: {
pageNum: data.pageNum,
pageSize: data.pageSize,
itemName: data.itemName,
userId: data.user.id
}
}).then(res => {
if (res.code === '200') {
data.tableData = res.data?.list
data.total = res.data?.total
} else {
ElMessage.error(res.msg)
}
})
}
// 新增
const handleAdd = () => {
data.form = {}
data.formVisible = true
}
// 编辑
const handleEdit = (row) => {
data.form = JSON.parse(JSON.stringify(row))
data.formVisible = true
}
// 新增保存
const add = () => {
request.post('/charge/add', data.form).then(res => {
if (res.code === '200') {
load()
ElMessage.success('操作成功')
data.formVisible = false
} else {
ElMessage.error(res.msg)
}
})
}
// 编辑保存
const update = () => {
request.put('/charge/update', data.form).then(res => {
if (res.code === '200') {
load()
ElMessage.success('操作成功')
data.formVisible = false
} else {
ElMessage.error(res.msg)
}
})
}
// 弹窗保存
const save = () => {
formRef.value.validate(valid => {
if (valid) {
// data.form有id就是更新,没有就是新增
data.form.id ? update() : add()
}
})
}
// 删除
const handleDelete = (id) => {
ElMessageBox.confirm('删除后数据无法恢复,您确定删除吗?', '删除确认', { type: 'warning' }).then(res => {
request.delete('/charge/delete/' + id).then(res => {
if (res.code === '200') {
load()
ElMessage.success('操作成功')
} else {
ElMessage.error(res.msg)
}
})
}).catch(err => {})
}
// 重置
const reset = () => {
data.title = null
load()
}
load()
</script>
<style scoped>
:deep(.el-tag--warning) {
color: #ff5900;
}
</style>
申请审核 Charge1.vue
<template>
<div>
<div class="card" style="margin-bottom: 5px;">
<el-input v-model="data.itemName" style="width: 300px; margin-right: 10px" placeholder="请输入物品名称查询"></el-input>
<el-button type="primary" @click="load">查询</el-button>
<el-button type="info" style="margin: 0 10px" @click="reset">重置</el-button>
</div>
<div class="card" style="margin-bottom: 5px">
<el-table :data="data.tableData" stripe>
<el-table-column prop="itemName" label="被交换物品"></el-table-column>
<el-table-column prop="requirement" label="交换条件"></el-table-column>
<el-table-column prop="content" label="交换物品"></el-table-column>
<el-table-column prop="remark" label="交换理由"></el-table-column>
<el-table-column prop="userName" label="申请人"></el-table-column>
<el-table-column prop="time" label="申请时间"></el-table-column>
<el-table-column prop="status" label="审核状态">
<template v-slot="scope">
<el-tag type="warning" v-if="scope.row.status === '待审核'">待审核</el-tag>
<el-tag type="success" v-if="scope.row.status === '通过'">通过</el-tag>
<el-tag type="danger" v-if="scope.row.status === '拒绝'">拒绝</el-tag>
</template>
</el-table-column>
<el-table-column prop="reason" label="审核理由"></el-table-column>
<el-table-column prop="location" label="交换地点"></el-table-column>
<el-table-column prop="shareTime" label="交换时间"></el-table-column>
<el-table-column prop="comment" label="物品主人留言"></el-table-column>
<el-table-column label="操作" align="center" width="120">
<template #default="scope">
<el-button :disabled="scope.row.status !== '待审核'" type="primary" @click="handleEdit(scope.row)">审核</el-button>
</template>
</el-table-column>
</el-table>
</div>
<div class="card">
<el-pagination @current-change="load" background layout="total, prev, pager, next" v-model:page-size="data.pageSize" v-model:current-page="data.pageNum" :total="data.total"/>
</div>
<el-dialog title="审核信息" width="40%" v-model="data.formVisible" :close-on-click-modal="false" destroy-on-close>
<el-form ref="formRef" :model="data.form" :rules="data.rules" label-width="100px" style="padding-right: 50px">
<el-form-item label="审核状态" prop="status">
<el-radio-group v-model="data.form.status">
<el-radio-button label="通过" value="通过"></el-radio-button>
<el-radio-button label="拒绝" value="拒绝"></el-radio-button>
</el-radio-group>
</el-form-item>
<div v-if="data.form.status === '通过'">
<el-form-item label="交换地点" prop="location">
<el-input placeholder="请输入交换地点" v-model="data.form.location" autocomplete="off" />
</el-form-item>
<el-form-item label="交换时间" prop="shareTime">
<el-date-picker style="width: 100%" v-model="data.form.shareTime" type="datetime" placeholder="交换时间" format="YYYY-MM-DD HH:mm:ss" value-format="YYYY-MM-DD HH:mm:ss" />
</el-form-item>
</div>
<div v-if="data.form.status === '拒绝'">
<el-form-item label="拒绝理由" prop="reason">
<el-input placeholder="请输入拒绝理由" v-model="data.form.reason" autocomplete="off" />
</el-form-item>
</div>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="data.formVisible = false">取 消</el-button>
<el-button type="primary" @click="save">保 存</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup>
import request from "@/utils/request";
import {reactive, ref} from "vue";
import {ElMessageBox, ElMessage} from "element-plus";
const formRef = ref()
const data = reactive({
user: JSON.parse(localStorage.getItem('system-user') || '{}'),
pageNum: 1,
pageSize: 10,
total: 0,
formVisible: false,
form: {},
tableData: [],
itemName: null,
rules: {
status: [
{ required: true, message: '请选择审核状态', trigger: 'change' }
],
location: [
{ required: true, message: '请输入交换地点', trigger: 'blur' }
],
shareTime: [
{ required: true, message: '请输入交换时间', trigger: 'change' }
],
reason: [
{ required: true, message: '请输入拒绝理由', trigger: 'blur' }
],
}
})
// 分页查询
const load = () => {
request.get('/charge/selectPage', {
params: {
pageNum: data.pageNum,
pageSize: data.pageSize,
itemName: data.itemName,
itemUserid: data.user.id
}
}).then(res => {
data.tableData = res.data?.list
data.total = res.data?.total
})
}
// 新增
const handleAdd = () => {
data.form = {}
data.formVisible = true
}
// 编辑
const handleEdit = (row) => {
data.form = JSON.parse(JSON.stringify(row))
data.formVisible = true
}
// 新增保存
const add = () => {
request.post('/charge/add', data.form).then(res => {
if (res.code === '200') {
load()
ElMessage.success('操作成功')
data.formVisible = false
} else {
ElMessage.error(res.msg)
}
})
}
// 编辑保存
const update = () => {
request.put('/charge/update', data.form).then(res => {
if (res.code === '200') {
load()
ElMessage.success('操作成功')
data.formVisible = false
} else {
ElMessage.error(res.msg)
}
})
}
// 弹窗保存
const save = () => {
formRef.value.validate(valid => {
if (valid) {
// data.form有id就是更新,没有就是新增
data.form.id ? update() : add()
}
})
}
// 删除
const handleDelete = (id) => {
ElMessageBox.confirm('删除后数据无法恢复,您确定删除吗?', '删除确认', { type: 'warning' }).then(res => {
request.delete('/charge/delete/' + id).then(res => {
if (res.code === '200') {
load()
ElMessage.success('操作成功')
} else {
ElMessage.error(res.msg)
}
})
}).catch(err => {})
}
// 重置
const reset = () => {
data.title = null
load()
}
load()
</script>
<style scoped>
:deep(.el-tag--warning) {
color: #ff5900;
}
</style>
ItemsView.vue
<template>
<div>
<div class="card" style="margin-bottom: 10px">
<el-button @click="changeCategoryItem(null)" :type="data.categoryId === null ? 'primary' : 'default'">全部</el-button>
<el-button @click="changeCategoryItem(item.id)" :type="data.categoryId === item.id ? 'primary' : 'default'" v-for="item in data.categoryList" :key="item.id">{{ item.name }}</el-button>
</div>
<div v-if="data.total > 0">
<el-row :gutter="10">
<el-col :span="6" v-for="item in data.tableData" :key="item.id">
<div class="card" style="padding: 0">
<img :src="item.img" alt="" style="width: 100%; height:350px; display: block; border-radius: 5px 5px 0 0">
<div style="padding: 10px">
<div style="margin: 10px 0; font-size: 20px; font-weight: 400">物品:{{ item.name }}</div>
<div class="ellipsis2" style="margin: 10px 0; text-align: justify; color: #666;" ><span style="color: #333">描述信息:</span>{{ item.description }}</div>
<div class="ellipsis2" style="margin: 10px 0; text-align: justify; color: #666;" ><span style="color: #333">交换条件:</span>{{ item.requirement }}</div>
<div style="margin: 10px 0; display: flex; color: #666">
<div style="flex: 1"><span style="color: #333">上传人:</span>{{item.userName}}</div>
<div><span style="color: #333">上传时间:</span>{{ item.time }}</div>
</div>
<div style="text-align: right">
<el-button @click="handleCharge(item)" type="primary" :disabled="item.userId === data.user.id">申请交换</el-button>
</div>
</div>
</div>
</el-col>
</el-row>
</div>
<div v-else>
<div class="card" style="padding: 50px; display: flex; justify-content: center; align-items: center; font-size: 20px; color: #666">暂无物品...</div>
</div>
<el-dialog title="申请信息" width="40%" v-model="data.formVisible" :close-on-click-modal="false" destroy-on-close>
<el-form ref="formRef" :model="data.form" :rules="data.rules" label-width="100px" style="padding-right: 50px">
<div style="padding-left: 30px; margin-bottom: 20px; color: #1890ff">当前申请交换:<b>{{ data.form.itemName }}</b></div>
<el-form-item label="交换物品" prop="content">
<el-input placeholder="请输入您提供的交换物品" v-model="data.form.content" autocomplete="off" />
</el-form-item>
<el-form-item label="交换理由" prop="remark">
<el-input type="textarea" :rows="3" maxlength="200" placeholder="请输入交换理由" v-model="data.form.remark" autocomplete="off" />
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="data.formVisible = false">取 消</el-button>
<el-button type="primary" @click="saveCharge">保 存</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { reactive, ref } from "vue";
import request from "@/utils/request";
import {ElMessage} from "element-plus";
const formRef = ref()
const data = reactive({
user: JSON.parse(localStorage.getItem('system-user') || '{}'),
categoryList: [],
categoryId: null, // 当前选中的分类ID
pageNum: 1,
pageSize: 10,
total: 0,
tableData: [],
form: {},
rules: {
content: [
{ required: true, message: '请输入交换物品', trigger: 'blur' }
],
}
})
const handleCharge = (item) => {
data.formVisible = true
data.form = { itemName: item.name, itemId: item.id, itemUserid: item.userId }
}
const saveCharge = () => {
formRef.value.validate(valid => {
if (valid) {
data.form.userId = data.user.id
request.post('/charge/add', data.form).then(res => {
if (res.code === '200') {
load()
ElMessage.success('操作成功')
data.formVisible = false
} else {
ElMessage.error(res.msg)
}
})
}
})
}
// 查询分类数据
request.get('/category/selectAll').then(res => {
data.categoryList = res.data
})
const changeCategoryItem = (categoryId) => {
data.categoryId = categoryId
load()
}
// 分页查询
const load = () => {
request.get('/items/selectPage', {
params: {
pageNum: data.pageNum,
pageSize: data.pageSize,
status: true,
checkStatus: '通过',
categoryId: data.categoryId
}
}).then(res => {
if (res.code === '500') {
ElMessage.error(res.msg)
return
}
data.tableData = res.data?.list
data.total = res.data?.total
})
}
load()
</script>
<style>
.ellipsis2 {
word-break: break-all;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 1; /* 超出几行省略 */
overflow: hidden;
}
</style>