零基础快速开发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

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
8天前
|
JavaScript 前端开发
如何在 Vue 项目中配置 Tree Shaking?
通过以上针对 Webpack 或 Rollup 的配置方法,就可以在 Vue 项目中有效地启用 Tree Shaking,从而优化项目的打包体积,提高项目的性能和加载速度。在实际配置过程中,需要根据项目的具体情况和需求,对配置进行适当的调整和优化。
|
8天前
|
存储 缓存 JavaScript
在 Vue 中使用 computed 和 watch 时,性能问题探讨
本文探讨了在 Vue.js 中使用 computed 计算属性和 watch 监听器时可能遇到的性能问题,并提供了优化建议,帮助开发者提高应用性能。
|
8天前
|
存储 缓存 JavaScript
如何在大型 Vue 应用中有效地管理计算属性和侦听器
在大型 Vue 应用中,合理管理计算属性和侦听器是优化性能和维护性的关键。本文介绍了如何通过模块化、状态管理和避免冗余计算等方法,有效提升应用的响应性和可维护性。
|
8天前
|
存储 缓存 JavaScript
Vue 中 computed 和 watch 的差异
Vue 中的 `computed` 和 `watch` 都用于处理数据变化,但使用场景不同。`computed` 用于计算属性,依赖于其他数据自动更新;`watch` 用于监听数据变化,执行异步或复杂操作。
|
7天前
|
JavaScript 前端开发 UED
vue学习第二章
欢迎来到我的博客!我是一名自学了2年半前端的大一学生,熟悉JavaScript与Vue,目前正在向全栈方向发展。如果你从我的博客中有所收获,欢迎关注我,我将持续更新更多优质文章。你的支持是我最大的动力!🎉🎉🎉
|
9天前
|
存储 JavaScript 开发者
Vue 组件间通信的最佳实践
本文总结了 Vue.js 中组件间通信的多种方法,包括 props、事件、Vuex 状态管理等,帮助开发者选择最适合项目需求的通信方式,提高开发效率和代码可维护性。
|
7天前
|
JavaScript 前端开发 开发者
vue学习第一章
欢迎来到我的博客!我是瑞雨溪,一名热爱JavaScript和Vue的大一学生。自学前端2年半,熟悉JavaScript与Vue,正向全栈方向发展。博客内容涵盖Vue基础、列表展示及计数器案例等,希望能对你有所帮助。关注我,持续更新中!🎉🎉🎉
|
22天前
|
数据采集 监控 JavaScript
在 Vue 项目中使用预渲染技术
【10月更文挑战第23天】在 Vue 项目中使用预渲染技术是提升 SEO 效果的有效途径之一。通过选择合适的预渲染工具,正确配置和运行预渲染操作,结合其他 SEO 策略,可以实现更好的搜索引擎优化效果。同时,需要不断地监控和优化预渲染效果,以适应不断变化的搜索引擎环境和用户需求。
|
9天前
|
存储 JavaScript
Vue 组件间如何通信
Vue组件间通信是指在Vue应用中,不同组件之间传递数据和事件的方法。常用的方式有:props、自定义事件、$emit、$attrs、$refs、provide/inject、Vuex等。掌握这些方法可以实现父子组件、兄弟组件及跨级组件间的高效通信。
|
14天前
|
JavaScript
Vue基础知识总结 4:vue组件化开发
Vue基础知识总结 4:vue组件化开发