使用HBuilderX 开发微信小程序的日志(5)

2025-02-14

前言:这一篇日志中包含了我实现录入书籍这一功能的过程。当时第一次写这个功能的时候很折腾,写了好几天。这一次一个下午就搞定了。

要实现这个功能,就是要将用户输入的数据传入到页面本地的变量中,然后再向云数据库上传表单中的数据。

不过我没有使用 <form> 这个组件。<form> 绑定的提交函数按理来说会传入子组件中表单组件的 value 值,但我的提交函数传入的内容是空的,仿佛里面没有组件一样。难道我自定义的组件不能作为表单组件被识别?

添加录入书籍功能

之前我在个人页面中添加了“录入书籍”的功能图标,点击这个图标需要能够跳转到“书籍录入”的页面:

我使用 <navigator> 包裹 <uni-grid-item>,实现了点击图标跳转到相应页面的功能。

<navigator url="/pages/admin/add/add" hover-class="none">  <!-- 将 hover-class 设置为none,避免了点击 navigator 的样式与点击 uni-grid-item 的样式重合 -->
    <uni-grid-item>
        <view class="cm-grid-item-box">
            <uni-icons type="plusempty" :size="30"></uni-icons>
            <text class="text">录入书籍</text>
        </view>
    </uni-grid-item>
</navigator>
解决了一个关于 v-model 的报错

跳转到书籍录入页面之后发现有一个报错:

[Vue warn]: Property "imageValue" was accessed during render but is not defined on instance. \n  at <CmFilePicker title="封面" > \n  at <CmInput >

看起来和之前添加的“图片上传器”有关:

<view class="cm-form-input">
    <view class="title">{{title}}</view>
    <uni-file-picker
        v-model="imageValue"
        fileMediatype="image"
        mode="grid"
    />
</view>

我之前从官网复制了 uni-file-picker 的示例过来,还没细想 v-model 的作用……

v-model 是 Vue.js 中的一个指令,用于在表单控件元素(如 <input><textarea><select>)和 Vue 实例的数据之间创建双向数据绑定。这意味着当表单控件的值发生变化时,Vue 实例中的数据也会相应地更新,反之亦然。

原来是用于数据绑定的。我并没有定义 imageValue 这个变量,不报错就怪了。

我接下来要给表单添加功能了,也许 v-model 很快就会用到。

添加扫码功能

参考官网的这篇文档:https://uniapp.dcloud.net.cn/api/system/barcode.html

给 cm-input 组件补充了右侧图标的点击事件 scan。

scan: function() {
    console.log('启动扫码...');
    uni.scanCode({
        scanType: ["barCode"],
        success: function(res) {
        	console.log("扫码结果:", res.result);
        }
    })
}

在 scanCode 函数内,this 不会指向在页面的 data() 中定义的变量。因此要对本地的变量进行操作,就要使用 var that = this 的写法:

scan: function() {
    console.log('启动扫码...');
    var that = this;  // 1
    uni.scanCode({
        scanType: ["barCode"],
        success: function(res) {
        	console.log("扫码结果:", res.result);
        	that.isbn = res.result;  // 2
        }
    })
}

扫码之后,会将得到的书籍 ISBN 号码填写到表单中的 ISBN 这一项。

要注意的是,显示在表单上的数据其实是自定义组件 <cm-input> 中的 <input> 组件中的参数—— value

<cm-input title="ISBN" placeholder="扫描或输入ISBN" :value="isbn" r_icon="scan" icon_color="#A00000" :on_icon_click="bindScanIconClicked"></cm-input>

我使用 v-bind,将 value 绑定到了页面中的变量 isbn 上。这个绑定是单向的,也就是 isbn 的值的修改会同步改变 value 值,但是修改 value 值不会影响 isbn

信息的传递路径如下:

  • 扫码得到的数据 => isbn => value => 显示在页面上的数据

但是会有这样一种情况

  • 用户手动输入 ISBN 数据 => value => 不会更新 isbn => 点击提交表单的按钮 => 向数据库中的 ISBN 一项添加或修改数据

这种情况下, isbn 的数据不会更新。

这样看来,页面中的局部变量 isbn 只是修改数据 value 的一个接口。

关于通过 ISBN 查询书籍信息的问题

用户在扫描书籍条码成功,或者手动输入 ISBN 并按确定键之后,会自动触发 ISBN 的查询。

// 绑定扫码图标
bindScanIconClicked: function() {
    console.log('启动扫码...');
    var that = this;
    uni.scanCode({
        scanType: ["barCode"],
        success: (res) => {
        	console.log("扫码结果:", res.result);
            // 通过更新 isbn 来更新表单所显示的数据
            that.isbn = res.result;
            // 在扫码成功后自动进行 ISBN 搜索
            that.searchISBN(that.isbn);
		}
    })
},

对于 <input> 组件,点击确定键后会触发的事件可以在 @confirm 中设置:

// 绑定在输入 ISBN 后点击确定键
bindISBNInputConfirmed: function(e) {
    this.isbn = e.detail.value
    console.log('用户输入了 ISBN:', this.isbn)

    // 在用户按确认键后自动进行 ISBN 搜索
    this.searchISBN(this.isbn)
},

进行 ISBN 搜索后,如果成功了,就更新页面显示的数据;如果失败了,就弹出提示,不更新数据。

关于图片文件上传

我之前自定义了一个组件 cm-file-picker ,这个组件其实就是给 uni-file-picker 添加了一点格式。 uni-file-picker 可以使用 @success 来设置在文件上传成功时执行的事件。uni-file-picker 会在文件上传成功时返回文件的地址。而在开通 uni-app 服务空间的情况下,这些文件会默认上传到服务空间中。

因此,我只需要获取所有图片文件的地址,并在随后将这些地址上传到数据库就行了。

这些地址,我使用一个本地变量 images 数组进行存放。

// 使用文件选择器上传图片成功后触发
onImageUploadSuccess: function(res) {
    console.log('图片上传成功:', res.tempFilePaths);
    this.images.push(...res.tempFilePaths);  // 每次图片上传后,将 uni-file-picker 返回的图片地址数组拼接到 images 数组之后
    console.log("当前图片组:", this.images);
}

同时,通过 ISBN 搜索到的书籍图片也可以添加到 images 数组中,预备上传到数据库。

// 通过isbn数据库查找当前isbn对应的书籍数据
searchISBN: function(isbn) {
    console.log("查找当前isbn对应的书籍数据...")
    var that = this;
    uni.request({
        url: "https://api.tanshuapi.com/api/isbn_base/v1/index",
        data: {
            key: that.apikey,
            isbn: isbn
        },
        success: (res) => {
            var bookdata = res.data.data;
            console.log("请求成功:", bookdata);

            that.title = bookdata.title
            that.author = bookdata.author
            that.publisher = bookdata.publisher
            that.images.push(bookdata.img)  // 通过网络搜索到图片之后,同样将其地址添加到 images 数组中
        },
        fail: () => {
	        console.log("请求失败!");
        }
    })
},
关于通过 ISBN 搜索到的书籍图片的显示

我们通过 ISBN 搜索到的书籍图片应该显示在哪里呢?我觉得还是“封面”这一栏里面好。我在自定义的 cm-file-picker 组件里面添加了一个 slotslot 中的组件仍然会受到父组件 display: flex 的影响,从而自动横向排列。

<template>
	<view class="cm-form-input">
		<view class="title">{{title}}</view>
		<slot></slot>
		<uni-file-picker
			fileMediatype="image"
			mode="grid"
			@success="on_upload_success"
		/>
	</view>
</template>

效果是这样的:

在这里插入图片描述

然后我在 cm-file-picker 组件中添加了一个 image 组件用于显示网络图片。我新建了一个本地变量 image_search_res 作为图片资源地址,这个变量的默认值是默认显示的书籍图片。之后我稍微写了一下 image 组件的 CSS 来调整大小和排版。最后我添加了一个绑定图片点击行为的事件 onImageTapped() ,用来设置图片的预览。

<cm-file-picker title="封面" :on_upload_success="onImageUploadSuccess">
    <image @tap="onImageTapped(image_search_res)" style="height: 132rpx; width: 132rpx; margin-right: 1rem;" :src="image_search_res">
    </image>
</cm-file-picker>

onImageTapped() 函数可以接受当前图片的资源地址,并调用 uni.previewImage() 进行预览:

// 点击单张图片触发预览
onImageTapped: function(src) {
    console.log("图片被点击:", src);
    uni.previewImage({
        current: 0,
        urls: [src]
    })
}
扩大了 cm-input 组件的右侧图标范围以防止误触

在手机上操作时发现点击扫码时会误触到旁边的输入框,所以我决定把 cm-input 的图标范围扩大一点:

<view class="r-icon">
	<uni-icons :type="r_icon" :size="icon_size" :color="icon_color" @click="on_icon_click"></uni-icons>
</view>

...

<style scoped>
.r-icon {
	/* background-color: yellow; */
	display: flex;
	justify-content: center;
	width: 20%;
}
</style>

在这里插入图片描述

将表单中的数据上传到数据库

我没有使用 form 组件来获取表单数据,因为我发现用不了,目前还不清楚原因……

不过我将所有需要添加到数据库的数据包装成了一个 JS 对象,在点击”录入“按钮之后,这个对象会作为上传函数的参数:

// 点击“录入”按钮提交书籍数据
onFormSubmit: function() {
	console.log("提交表单")
	var bookdata = {
		storage: this.addition,
		isbn: this.isbn,
	// ...
	}
	console.log("表单数据:", bookdata);
	uniCloud.callFunction({
		name: 'addBookData',
		data: bookdata
	}).then(res => {
		console.log('addBookData 云函数返回信息:', res);
		if (res.result.code === 200) {
			console.log('添加成功');
		} else {
			console.error('添加失败:', res.result.msg);
		}
	}).catch(err => {
		console.log('调用云函数失败:', err);
	})
}

我使用云函数作为上传数据的函数。在项目的 uniCloud/cloudfunctions 文件夹图标上右键,可以选择”新建云函数/云对象“。

在这里插入图片描述

然后写好云函数。这个函数只包含一个向 library 数据库中添加一条数据的功能:

'use strict';
exports.main = async (event, context) => {
	console.log('调用云函数 addBookData 传入参数:', event)
	const db = uniCloud.database();
	try {
		const res = await db.collection('library').add(event);
		return {
			code: 200,
			msg: '添加成功',
			data: res
		};
	} catch (err) {
		return {
			code: 500,
			msg: '添加失败',
			error: err
		};
	}
};

写好之后记得上传云函数。

到此,录入书籍(而且是新书)的功能就基本实现了。之后我还会完善一些细节。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值