零基础快速开发Vue图书管理系统—主体列表实现篇(四)

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 服务端相关代码如下

零基础快速开发Vue图书管理系统—主体列表实现篇(四)

一、书籍编辑操作

2345_image_file_copy_555.jpg

2345_image_file_copy_556.jpg

2345_image_file_copy_557.jpg

服务端相关代码

2345_image_file_copy_558.jpg

const Router = require('@koa/router');
const mongoose = require('mongoose');
const { getBody } = require('../../helpers/utils')
const BOOK_CONST = {
    IN: 'IN_COUNT',
    OUT: 'OUT_COUNT',
}
const Book = mongoose.model('Book');
const router = new Router({
    prefix: '/book',
});
router.post('/add', async(ctx) => {
    const {
        name,
        price,
        author,
        publishDate,
        classify,
        count
    } = getBody(ctx);
    const book = new Book({
        name,
        price,
        author,
        publishDate,
        classify,
        count
    });
    const res = await book.save();
    ctx.body = {
        data: res,
        code: 1,
        msg: '添加成功'
    };
});
router.get('/list', async(ctx) => {
    const {
        page = 1,
            size = 10,
            keyword = ''
    } = ctx.query;
    //1 20
    //2 20
    //3 40 
    const query = {};
    if (keyword) {
        query.name = keyword;
    }
    const list = await Book
        .find(query)
        //实现分页效果
        .skip((page - 1) * size)
        .limit(size)
        .exec();
    const total = await Book.countDocuments();
    ctx.body = {
        data: {
            list,
            total,
            page,
            size,
        },
        code: 1,
        msg: '获取列表成功'
    };
});
router.delete('/:id', async(ctx) => {
    const { id } = ctx.params;
    const delMsg = await Book.deleteOne({
        _id: id,
    });
    ctx.body = {
        data: delMsg,
        msg: '删除成功',
        code: 1,
    };
});
router.post('/update/count', async(ctx) => {
    const { id, type } = ctx.request.body;
    let { num, } = ctx.request.body;
    num = Number(num);
    const book = await Book.findOne({
        _id: id,
    }).exec();
    if (!book) {
        ctx.body = {
            code: 0,
            msg: '没有找到书籍'
        };
        return;
    }
    //找到了书
    if (type === BOOK_CONST.IN) {
        //入库操作
        num = Math.abs(num);
    } else {
        //出库操作
        num = -Math.abs(num);
    }
    book.count = book.count + num;
    if (book.count < 0) {
        ctx.body = {
            code: 0,
            msg: '剩下的量不足以出库'
        };
        return;
    }
    const res = await book.save();
    ctx.body = {
        data: res,
        code: 1,
        msg: '操作成功'
    };
});
router.post('/update', async(ctx) => {
    const {
        id,
        // name,
        // price,
        // author,
        // publishDate,
        // classify
        //剩余参数运算符
        ...others
    } = ctx.request.body;
    const one = await Book.findOne({
        _id: id,
    }).exec();
    //没有找到书
    if (!one) {
        ctx.body = {
            msg: '没有找到书籍',
            code: 0,
        }
        return;
    }
    const newQuery = {};
    Object.entries(others).forEach(([key, value]) => {
        if (value) {
            newQuery[key] = value;
        }
    });
    Object.assign(one, newQuery);
    const res = await one.save();
    ctx.body = {
        data: res,
        code: 1,
        msg: '保存成功'
    };
});
module.exports = router;

客户端相关代码:

<template>
    <div>
        <a-modal title="添加书籍" 
        :visible="props.show" 
        @ok="submit" 
        @cancel="close">
            <a-form :label-col="{span:6}">
                <a-form-item label="书名">
                    <a-input v-model:value="editForm.name" />
                </a-form-item>
                <a-form-item label="价格">
                    <a-input-number v-model:value="editForm.price" :min="0" :max="9999999" />
                </a-form-item>
                <a-form-item label="作者">
                    <a-input v-model:value="editForm.author" />
                </a-form-item>
                <a-form-item label="出版日期">
                    <a-date-picker v-model:value="editForm.publishDate" />
                </a-form-item>
                <a-form-item label="分类">
                    <a-input v-model:value="editForm.classify" />
                </a-form-item>
            </a-form>
        </a-modal>
    </div>
</template>
<script src="./index.js">
</script>
<style lang="scss" scoped>
    @import './index.scss'
</style>
import { defineComponent, reactive, watch } from 'vue'
import { book } from '@/service';
import { message } from 'ant-design-vue'
import { result, clone } from '../../../helpers/utils/index';
import moment from 'moment';
export default defineComponent({
    props: {
        show: Boolean,
        book: Object,
    },
    setup(props, context) {
        const editForm = reactive({
            name: '',
            price: 0,
            author: '',
            publishDate: 0,
            classify: '',
        })
        const close = () => {
            context.emit('update:show', false);
        };
        watch(() => props.book, (current) => {
            Object.assign(editForm, current);
            editForm.publishDate = moment(Number(editForm.publishDate));
        });
        const submit = async() => {
            const res = await book.update({
                id: props.book._id,
                //…扩展运算符
                name: editForm.name,
                price: editForm.price,
                author: editForm.author,
                publishDate: editForm.publishDate.valueOf(),
                classify: editForm.classify,
            });
            result(res)
                .success(({ data, msg }) => {
                    context.emit('update', data);
                    message.success(msg);
                    close();
                })
        }
        return {
            editForm,
            submit,
            props,
            close,
        }
    }
})

二、书籍详情页面开发

2345_image_file_copy_559.jpg

<template>
    <div>
        <a-card>
            <space-between>
                <h2>书籍的名字</h2>
                <div>
                    <a-button size="small" type="primary">编辑</a-button>
                    &nbsp;
                    <a-button size="small" type="danger">删除</a-button>
                </div>
            </space-between>
            <a-divider></a-divider>
            <div class="base-info">
                <div class="items">
                    <div class="item">
                        <div class="title">价格</div>
                        <div class="content">1</div>
                    </div>
                    <div class="item">
                        <div class="title">作者</div>
                        <div class="content">2</div>
                    </div>
                    <div class="item">
                        <div class="title">分类</div>
                        <div class="content">3</div>
                    </div>
                </div>
                <div class="items">
                    <div class="item">
                        <div class="title">出版日期</div>
                        <div class="content">4</div>
                    </div>
                </div>
            </div>
        </a-card>
        <div class="log">
            <a-card title="出入库日志">
                <template #extra>
                    <span>
                        <a href="javascript:;">出库日志</a>
                    </span>
                    <span style="margin-left:12px">
                        <a href="javascript:;">入库日志</a>
                    </span>
                </template>
                <div>
                    <a-table bordered :pagination="false"></a-table>
                </div>
                <space-between style="margin-top:24px">
                    <div />
                    <a-pagination></a-pagination>
                </space-between>
            </a-card>
        </div>
    </div>
</template>
<script src="./index.js">
</script>
<style lang="scss" scoped>
    @import './index.scss'
</style>

三、书籍详情接口实现

2345_image_file_copy_560.jpg

四、库存日志相关内容实现

2345_image_file_copy_561.jpg

2345_image_file_copy_562.jpg

import { defineComponent, onMounted, ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { book, inventoryLog } from '@/service'
import { result, formatTimestamp } from '@/helpers/utils';
import { CheckOutlined } from '@ant-design/icons-vue'
import { message } from 'ant-design-vue'
import Update from '@/views/Books/Update/index'
const columns = [{
        title: '数量',
        dataIndex: 'num',
    },
    {
        title: '操作时间',
        slots: {
            customRender: 'createdAt'
        }
    }
]
export default defineComponent({
    components: {
        Update,
        CheckOutlined
    },
    setup() {
        const route = useRoute();
        const router = useRouter();
        const showUpdateModal = ref(false);
        const { id } = route.params;
        const detailInfo = ref({});
        const log = ref([]);
        const logTotal = ref(0);
        const logCurPage = ref(1);
        const curLogType = ref('IN_COUNT')
        const getDetail = async() => {
            const res = await book.detail(id);
            result(res)
                .success(({ data }) => {
                    detailInfo.value = data;
                })
        };
        //获取出库入库日志
        const getInventoryLog = async() => {
            const res = await inventoryLog.list(
                curLogType.value,
                logCurPage.value,
                10);
            result(res)
                .success(({ data: { list, total } }) => {
                    log.value = list;
                    logTotal.value = total;
                })
        };
        onMounted(() => {
            getDetail();
            getInventoryLog();
        });
        const remove = async() => {
            const res = await book.remove(id);
            result(res)
                .success(({ msg }) => {
                    message.success(msg);
                    router.replace('/books')
                });
        };
        //更新操作
        const update = (book) => {
            Object.assign(detailInfo.value, book)
        };
        //日志分页切换的时候
        const setLogPage = (page) => {
            logCurPage.value = page;
            getInventoryLog();
        };
        //筛选日志
        const logFilter = (type) => {
            curLogType.value = type;
            getInventoryLog();
        };
        return {
            d: detailInfo,
            formatTimestamp,
            remove,
            showUpdateModal,
            update,
            log,
            logTotal,
            setLogPage,
            columns,
            logFilter,
            curLogType,
            logCurPage
        }
    }
});
<template>
    <div>
        <a-card>
            <space-between>
                <h2>{{d.name}}</h2>
                <div>
                    <a-button size="small" type="primary" @click="showUpdateModal=true">编辑</a-button>
                    &nbsp;
                    <a-button size="small" type="danger" @click="remove">删除</a-button>
                </div>
            </space-between>
            <a-divider />
            <div class="base-info">
                <div class="items">
                    <div class="item">
                        <div class="title">价格</div>
                        <div class="content">{{d.price}}</div>
                    </div>
                    <div class="item">
                        <div class="title">作者</div>
                        <div class="content">{{d.author}}</div>
                    </div>
                    <div class="item">
                        <div class="title">分类</div>
                        <div class="content">{{d.classify}}</div>
                    </div>
                </div>
                <div class="items">
                    <div class="item">
                        <div class="title">出版日期</div>
                        <div class="content">{{formatTimestamp(d.publishDate)}}</div>
                    </div>
                </div>
            </div>
        </a-card>
        <div class="log">
            <a-card title="出入库日志">
                <template #extra>
                    <span>
                        <a href="javascript:;" @click="logFilter('IN_COUNT')">
                            <CheckOutlined v-if="curLogType==='IN_COUNT'" />
                            入库日志</a>
                    </span>
                    <span style="margin-left:12px">
                        <a href="javascript:;" @click="logFilter('OUT_COUNT')">
                            <CheckOutlined v-if="curLogType==='OUT_COUNT'" />
                            出库日志</a>
                    </span>
                </template>
                <div>
                    <a-table :data-source="log" :columns="columns" bordered :pagination="false">
                        <template #createdAt="{record}">
                            {{ formatTimestamp(record.meta.createdAt) }}
                        </template>
                    </a-table>
                </div>
                <space-between style="margin-top:24px">
                    <div />
                    <a-pagination 
                    v-model:current="logCurPage" 
                    :total="logTotal" 
                    :page-size="10" 
                    @change="setLogPage">
                    </a-pagination>
                </space-between>
            </a-card>
        </div>
        <update v-model:show="showUpdateModal" :book="d" @update="update" />
    </div>
</template>
<script src="./index.js">
</script>
<style lang="scss" scoped>
    @import './index.scss'
</style>

五、书籍相关内容的优化

2345_image_file_copy_563.jpg

给列表增加排序功能

2345_image_file_copy_564.jpg

2345_image_file_copy_565.jpg

相关实践学习
【涂鸦即艺术】基于云应用开发平台CAP部署AI实时生图绘板
【涂鸦即艺术】基于云应用开发平台CAP部署AI实时生图绘板
相关文章
|
2月前
|
JavaScript
Vue中如何实现兄弟组件之间的通信
在Vue中,兄弟组件可通过父组件中转、事件总线、Vuex/Pinia或provide/inject实现通信。小型项目推荐父组件中转或事件总线,大型项目建议使用Pinia等状态管理工具,确保数据流清晰可控,避免内存泄漏。
275 2
|
22天前
|
缓存 JavaScript
vue中的keep-alive问题(2)
vue中的keep-alive问题(2)
246 137
|
5月前
|
人工智能 JavaScript 算法
Vue 中 key 属性的深入解析:改变 key 导致组件销毁与重建
Vue 中 key 属性的深入解析:改变 key 导致组件销毁与重建
696 0
|
3月前
|
缓存 前端开发 大数据
虚拟列表在Vue3中的具体应用场景有哪些?
虚拟列表在 Vue3 中通过仅渲染可视区域内容,显著提升大数据列表性能,适用于 ERP 表格、聊天界面、社交媒体、阅读器、日历及树形结构等场景,结合 `vue-virtual-scroller` 等工具可实现高效滚动与交互体验。
392 1
|
4月前
|
人工智能 JSON JavaScript
VTJ.PRO 首发 MasterGo 设计智能识别引擎,秒级生成 Vue 代码
VTJ.PRO发布「AI MasterGo设计稿识别引擎」,成为全球首个支持解析MasterGo原生JSON文件并自动生成Vue组件的AI工具。通过双引擎架构,实现设计到代码全流程自动化,效率提升300%,助力企业降本增效,引领“设计即生产”新时代。
369 1
|
4月前
|
JavaScript 安全
在 Vue 中,如何在回调函数中正确使用 this?
在 Vue 中,如何在回调函数中正确使用 this?
200 0
|
7月前
|
JavaScript
vue实现任务周期cron表达式选择组件
vue实现任务周期cron表达式选择组件
953 4
|
5月前
|
JavaScript UED
用组件懒加载优化Vue应用性能
用组件懒加载优化Vue应用性能
|
6月前
|
JavaScript 数据可视化 前端开发
基于 Vue 与 D3 的可拖拽拓扑图技术方案及应用案例解析
本文介绍了基于Vue和D3实现可拖拽拓扑图的技术方案与应用实例。通过Vue构建用户界面和交互逻辑,结合D3强大的数据可视化能力,实现了力导向布局、节点拖拽、交互事件等功能。文章详细讲解了数据模型设计、拖拽功能实现、组件封装及高级扩展(如节点类型定制、连接样式优化等),并提供了性能优化方案以应对大数据量场景。最终,展示了基础网络拓扑、实时更新拓扑等应用实例,为开发者提供了一套完整的实现思路和实践经验。
749 77
|
7月前
|
缓存 JavaScript 前端开发
Vue 基础语法介绍
Vue 基础语法介绍