微信小程序 | 借ChatGPT之手重构社交聊天小程序

2023-10-03 15:24

本文主要是介绍微信小程序 | 借ChatGPT之手重构社交聊天小程序,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

请添加图片描述

一、 ChatGPT效果分析

在这里插入图片描述

体验过ChatGPT这一产品的小伙伴对于GPT模型的恢复效果不知道有没有一种让人感觉到真的在和真人交流的感觉。不管你的问题有多么的刁钻,它总是能以一种宠辱不惊的状态回复你。

但是对于一些很无理的要求,它有的时候也是很果断的😂
在这里插入图片描述

没有体验过的小伙伴也可以直接从效果图中看出,AI的每一句回答都是一个字一个字或者一小段一小段地给予回复,给人一种无比地丝滑感,这才是真的真的聊天啊!

那么这个时候,如果可以把ChatGPT这个AI的丝滑聊天动效直接迁移到我们现在使用的聊天场景中来,把这些死板的、一次性的消息框效果直接全量优化!让我们的社交更加具有趣味性!😜


二、关键技术点

针对这一效果我们静下心来思考一下你会发现:ChatGPT的这个聊天框的响应反馈不仅仅是有一个动态光标的存在,更重要的是它返回的真的有够快的。
试想一下,按照我们在日常开发中的发起Http请求业务开发过程中,都是在三次握手之后客户端与服务端才开始交流感情!而且都是要到后端处理完全部逻辑之后才进行数据的返回,然后前端再拿这些数据进行渲染操作,所以要做到这么快就有两种设想:

  • (1)后端处理完全部逻辑后速度返回,前端速度解析,然后配以光标效果进行渲染。(Bug:数据量一爆炸,前端的响应速度并不能保证!
  • (2)后端一边处理一边返回数据,前端同时接收并渲染。后端服务采用流式数据响应,从而实现不等待式实时渲染

2.1 前端动效的支持

ChatGPT中对话框进行文字输入的时候,我们可以明显看到,在每个文字的后面都有一个闪烁的光标,正是这一效果可以给予用户一种真在动态输入的感觉,让体验倍加丝滑!

在这里插入图片描述

要实现这一效果,我们可以使用定时器,每100毫秒逐个渲染出文本内容,并在文本后面添加了一个闪烁的光标。注意要在组件中设置ref属性来获取span元素的引用。

<template><div><span ref="text"></span><span ref="cursor" class="blink">_</span></div>
</template><script>
export default {mounted() {const text = this.$refs.text;const cursor = this.$refs.cursor;const textContent = "这是一段需要逐个渲染的文字";let index = 0;setInterval(() => {if (index <= textContent.length) {text.textContent = textContent.slice(0, index);cursor.style.opacity = index % 2 === 0 ? 1 : 0;index++;}}, 100);},
};
</script><style>
.blink {animation: blink-animation 1s steps(1) infinite;
}@keyframes blink-animation {0% {opacity: 0;}50% {opacity: 1;}100% {opacity: 0;}
}
</style>

2.2 消息数据的实时响应

在这里插入图片描述
在前端中,可以使用流式处理(Streaming)的方式,实时加载从 HTTP 请求返回的 JSON 数据。这种方式可以避免一次性加载大量数据所造成的性能问题,而是在数据流到达时逐步处理数据。

以下是使用流式处理加载 JSON 数据的示例代码:


function loadJSON(url, onData) {let xhr = new XMLHttpRequest()xhr.open('GET', url, true)xhr.responseType = 'json'xhr.onprogress = function() {let chunk = xhr.response.slice(xhr.loaded, xhr.response.length)onData(chunk)}xhr.onload = function() {if (xhr.status === 200) {onData(xhr.response)}}xhr.send()
}

在上面的代码中,定义了一个 loadJSON 函数,该函数使用 XMLHttpRequest 对象发送 GET 请求,并指定 responseType:json 参数。然后,在 onprogress 事件中,获取从服务器返回的 JSON 数据的最新一块数据,并通过 onData 回调函数将数据传递给客户端。在 onload 事件中,将最后一块数据发送给客户端。


三、丝滑聊天功能实现

3.1 小程序端

  • 光标元素

在这里插入图片描述


  • 完整代码

<template><view class="content"><view class="content-box" @touchstart="touchstart" id="content-box" :class="{'content-showfn':showFunBtn}"><!-- 背景图- 定位方式 --><image class="content-box-bg" :src="_user_info.chatBgImg" :style="{ height: imgHeight }"></image><view class="content-box-loading" v-if="!loading"><u-loading mode="flower"></u-loading></view><view class="message" v-for="(item, index) in messageList" :key="index" :id="`msg-${item.hasBeenSentId}`"><view class="message-item " :class="item.isItMe ? 'right' : 'left'"><image class="img" :src="item.fromUserHeadImg" mode="" @tap="linkToBusinessCard(item.fromUserId)"></image><!-- contentType = 1 文本 --><view class="content" v-if="item.contentType == 1"><!-- <span ref="text" value="item.content"></span><span ref="cursor" class="blink">_</span> --><!-- 		{{ generateTextSpan(item,index) }}<span :ref="'text'+index" :value="item.content" :index="index"></span><span ref="cursor" class="blink">_</span> --><chat-record :content="item.content"></chat-record></view><!-- <view class="content" v-if="item.contentType == 1">{{ item.content }}</view> --><!-- contentType = 2 语音 --><viewclass="content contentType2":class="[{ 'content-type-right': item.isItMe }]"v-if="item.contentType == 2"@tap="handleAudio(item)"hover-class="contentType2-hover-class":style="{width:`${130+(item.contentDuration*2)}rpx`}"><viewclass="voice_icon":class="[{ voice_icon_right: item.isItMe },{ voice_icon_left: !item.isItMe },{ voice_icon_right_an: item.anmitionPlay && item.isItMe },{ voice_icon_left_an: item.anmitionPlay && !item.isItMe }]"></view><view class="">{{ item.contentDuration }}''</view></view><!-- contentType = 3 图片 --><view class="content contentType3" 	v-if="item.contentType == 3"@tap="viewImg([item.content])"><image :src="item.content" class="img" mode="widthFix"></image></view></view></view> </view><!-- 底部聊天输入框 --><view class="input-box" :class="{ 'input-box-mpInputMargin': mpInputMargin }"><view class="input-box-flex"><!-- #ifndef H5 --><image v-if="chatType === 'voice'" class="icon_img" :src="require('@/static/voice.png')"  @click="switchChatType('keyboard')"></image><image v-if="chatType === 'keyboard'" class="icon_img" :src="require('@/static/keyboard.png')"  @click="switchChatType('voice')"></image><!-- #endif --><view class="input-box-flex-grow"> <inputv-if="chatType === 'voice'"type="text"class="content"id="input"v-model="formData.content":hold-keyboard="true":confirm-type="'send'":confirm-hold="true"placeholder-style="color:#DDDDDD;":cursor-spacing="10"@confirm="sendMsg(null)"/><viewclass="voice_title"v-if="chatType === 'keyboard'":style="{ background: recording ? '#c7c6c6' : '#FFFFFF' }"@touchstart.stop.prevent="startVoice"@touchmove.stop.prevent="moveVoice"@touchend.stop="endVoice"@touchcancel.stop="cancelVoice">{{ voiceTitle }}</view></view><!-- 功能性按钮 --><image class=" icon_btn_add" :src="require('@/static/add.png')" @tap="switchFun"></image><!-- #ifdef H5 --> <button class="btn" type="primary" size="mini" @touchend.prevent="sendMsg(null)">发送</button><!-- #endif --></view><view class="fun-box" :class="{'show-fun-box':showFunBtn}"><u-grid :col="4"  hover-class="contentType2-hover-class" :border="false" @click="clickGrid"><u-grid-item v-for="(item, index) in funList" :index="index" :key="index" bg-color="#eaeaea"><u-icon :name="item.icon" :size="52"></u-icon><view class="grid-text">{{ item.title }}</view></u-grid-item></u-grid></view></view><!-- //语音动画 --><view class="voice_an"  v-if="recording"><view class="voice_an_icon"><view id="one" class="wave"></view><view id="two" class="wave"></view><view id="three" class="wave"></view><view id="four" class="wave"></view><view id="five" class="wave"></view><view id="six" class="wave"></view><view id="seven" class="wave"></view></view><view class="text">{{voiceIconText}}</view></view></view>
</template><script>import chatRecord from '@/components/chatRecord/index.vue'
export default {components:{chatRecord},data() {return {lines:[],fromUserInfo: {},formData: {content: '',limit: 15,index: 1},messageList: [],loading: true, //标识是否正在获取数据imgHeight: '1000px',mpInputMargin: false, //适配微信小程序 底部输入框高度被顶起的问题chatType:"voice",  // 图标类型 'voice'语音 'keyboard'键盘voiceTitle: '按住 说话',Recorder: uni.getRecorderManager(),Audio: uni.createInnerAudioContext(),recording: false, //标识是否正在录音isStopVoice: false, //加锁 防止点击过快引起的当录音正在准备(还没有开始录音)的时候,却调用了stop方法但并不能阻止录音的问题voiceInterval:null,voiceTime:0, //总共录音时长canSend:true, //是否可以发送PointY:0, //坐标位置voiceIconText:"正在录音...",showFunBtn:false, //是否展示功能型按钮AudioExam:null, //正在播放音频的实例funList: [{ icon:"photo-fill",title:"照片",uploadType:["album"] },{ icon:"camera-fill",title:"拍摄",uploadType:["camera"] },],};},updated() {},methods: {//拼接消息 处理滚动async joinData() {if (!this.loading) {//如果没有获取数据 即loading为false时,return 避免用户重复上拉触发加载return;}this.loading = false;const data = await this.getMessageData();//获取节点信息const { index } = this.formData;const sel = `#msg-${index > 1 ? this.messageList[0].hasBeenSentId : data[data.length - 1].hasBeenSentId}`;this.messageList = [...data, ...this.messageList];console.log(this.messageList)//填充数据后,视图会自动滚动到最上面一层然后瞬间再跳回bindScroll的指定位置 ---体验不是很好,后期优化this.$nextTick(() => {this.bindScroll(sel);//如果还有数据if (this.formData.limit >= data.length) {this.formData.index++;setTimeout(() => {this.loading = true;}, 200);}});},//处理滚动bindScroll(sel, duration = 0) {const query = uni.createSelectorQuery().in(this);query.select(sel).boundingClientRect(data => {uni.pageScrollTo({scrollTop: data && data.top - 40,duration});}).exec();},generateTextSpan(item,index){var name = 'text'+indexconsole.log('== text ==',this.$refs.text1)},//获取消息getMessageData() {let getData = () => {let arr = [];let startIndex = (this.formData.index - 1) * this.formData.limit;let endIndex = startIndex + this.formData.limit;return arr;};return new Promise((resolve, reject) => {const data = getData();setTimeout(() => {resolve(data);}, 500);});},getPersonMsgData(text) {let getData = () => {let arr = [];const isItMe = false;let startIndex = (this.formData.index - 1) * this.formData.limit;let endIndex = startIndex + this.formData.limit;arr.push({hasBeenSentId: startIndex, //已发送过去消息的idcontent: text,fromUserHeadImg: isItMe ? this._user_info.headImg : this.fromUserInfo.fromUserHeadImg, //用户头像fromUserId: isItMe ? this._user_info.id : this.fromUserInfo.fromUserId,isItMe, //true此条信息是我发送的 false别人发送的createTime: Date.now(),contentType: 1, // 1文字文本 2语音anmitionPlay: false //标识音频是否在播放});console.log('==arr==',arr)return arr;};return new Promise((resolve, reject) => {const data = getData();setTimeout(() => {resolve(data);}, 500);});},async joinPersonData(text) {if (!this.loading) {//如果没有获取数据 即loading为false时,return 避免用户重复上拉触发加载return;}this.loading = false;const data = await this.getPersonMsgData(text);//获取节点信息const { index } = this.formData;const sel = `#msg-${index > 1 ? this.messageList[0].hasBeenSentId : data[data.length - 1].hasBeenSentId}`;this.messageList = [...data, ...this.messageList];console.log(this.messageList)//填充数据后,视图会自动滚动到最上面一层然后瞬间再跳回bindScroll的指定位置 ---体验不是很好,后期优化this.$nextTick(() => {this.bindScroll(sel);//如果还有数据if (this.formData.limit >= data.length) {this.formData.index++;setTimeout(() => {this.loading = true;}, 200);}});},//切换语音或者键盘方式switchChatType(type) {this.chatType = type;this.showFunBtn =false;},//切换功能性按钮switchFun(){this.chatType = 'keyboard'this.showFunBtn = !this.showFunBtn;uni.hideKeyboard()},//发送消息sendMsg(data) {var that = thisconst params = {hasBeenSentId: Date.now(), //已发送过去消息的idcontent: this.formData.content,fromUserHeadImg: this._user_info.headImg, //用户头像fromUserId: this._user_info.id,isItMe: true, //true此条信息是我发送的  false别人发送的createTime: Date.now(),contentType: 1};if (data) {if(data.contentType == 2){//说明是发送语音params.content = data.content;params.contentType = data.contentType;params.contentDuration = data.contentDuration;params.anmitionPlay = false;}else if(data.contentType == 3){//发送图片params.content = data.content;params.contentType = data.contentType;}} else if (!this.$u.trim(this.formData.content)) {//验证输入框书否为空字符传return;}this.messageList.push(params);let msg = that.formData.contentuni.request({url: 'http://127.0.0.1:8099/chat?msg='+msg,responseType: 'text',success: res => {console.log('==res==',res)const reader = res.data.getReader();const decoder = new TextDecoder();const read = () => {reader.read().then(({ done, value }) => {if (done) {return;}that.messageList.push({hasBeenSentId: 1, //已发送过去消息的idcontent: decoder.decode(value),fromUserHeadImg:  that.fromUserInfo.fromUserHeadImg, //用户头像fromUserId: that.fromUserInfo.fromUserId,isItMe: false, //true此条信息是我发送的 false别人发送的createTime: Date.now(),contentType: 1, // 1文字文本 2语音anmitionPlay: false //标识音频是否在播放});read();});};read();},fail: err => {console.log('Request failed', err)}})this.$nextTick(() => {this.formData.content = '';// #ifdef MP-WEIXINif(params.contentType == 1){uni.pageScrollTo({scrollTop: 99999,duration: 0, //小程序如果有滚动效果 input的焦点也会随着页面滚动...});}else{setTimeout(()=>{uni.pageScrollTo({scrollTop: 99999,duration: 0, //小程序如果有滚动效果 input的焦点也会随着页面滚动...});},150)}// #endif// #ifndef MP-WEIXINuni.pageScrollTo({scrollTop: 99999,duration: 100});// #endifif(this.showFunBtn){this.showFunBtn = false;}// #ifdef MP-WEIXIN if (params.contentType == 1) {this.mpInputMargin = true;} // #endif//h5浏览器并没有很好的办法控制键盘一直处于唤起状态 而且会有样式性的问题// #ifdef H5uni.hideKeyboard();// #endif});},//用户触摸屏幕的时候隐藏键盘touchstart() {uni.hideKeyboard();},// userid 用户idlinkToBusinessCard(userId) {this.$u.route({url: 'pages/businessCard/businessCard',params: {userId}});},//准备开始录音startVoice(e) {if(!this.Audio.paused){//如果音频正在播放 先暂停。this.stopAudio(this.AudioExam)}this.recording = true;this.isStopVoice = false;this.canSend = true;this.voiceIconText = "正在录音..."this.PointY = e.touches[0].clientY;this.Recorder.start({format: 'mp3'});},//录音已经开始beginVoice(){if (this.isStopVoice) {this.Recorder.stop();return;}this.voiceTitle = '松开 结束'this.voiceInterval =  setInterval(()=>{this.voiceTime ++;},1000)},//move 正在录音中moveVoice(e){const PointY = e.touches[0].clientYconst slideY = this.PointY - PointY;if(slideY > uni.upx2px(120)){this.canSend = false;this.voiceIconText = '松开手指 取消发送 '}else if(slideY > uni.upx2px(60)){this.canSend = true;this.voiceIconText = '手指上滑 取消发送 '}else{this.voiceIconText = '正在录音... '}},//结束录音endVoice() {this.isStopVoice = true; //加锁 确保已经结束录音并不会录制this.Recorder.stop();this.voiceTitle = '按住 说话'},//录音被打断cancelVoice(e){this.voiceTime = 0;this.voiceTitle = '按住 说话';this.canSend = false;this.Recorder.stop();},//处理录音文件handleRecorder({ tempFilePath,duration }) {let contentDuration;// #ifdef MP-WEIXINthis.voiceTime = 0;if (duration < 600) {this.voiceIconText="说话时间过短";setTimeout(()=>{this.recording = false;},200)return;} contentDuration = duration/1000;// #endif// #ifdef APP-PLUScontentDuration = this.voiceTime +1;this.voiceTime = 0;if(contentDuration <= 0) {this.voiceIconText="说话时间过短";setTimeout(()=>{this.recording = false;},200)return;};// #endifthis.recording = false;const params = {contentType: 2,content: tempFilePath,contentDuration: Math.ceil(contentDuration)};this.canSend && this.sendMsg(params);},//控制播放还是暂停音频文件handleAudio(item) {this.AudioExam = item;this.Audio.paused ? this.playAudio(item) : this.stopAudio(item);},//播放音频playAudio(item) {this.Audio.src = item.content;this.Audio.hasBeenSentId = item.hasBeenSentId;this.Audio.play();item.anmitionPlay = true;},//停止音频stopAudio(item) {item.anmitionPlay = false;this.Audio.src = '';this.Audio.stop();},//关闭动画closeAnmition() {const hasBeenSentId = this.Audio.hasBeenSentId;const item = this.messageList.find(it => it.hasBeenSentId == hasBeenSentId);item.anmitionPlay = false;},//点击宫格时触发clickGrid(index){if(index == 0){this.chooseImage(['album'])}else if(index == 1){this.chooseImage(['camera'])}},//发送图片chooseImage(sourceType){uni.chooseImage({sourceType,sizeType:['compressed'], success:res=>{ this.showFunBtn = false;for(let i = 0;i<res.tempFilePaths.length;i++){const params = {contentType: 3,content: res.tempFilePaths[i],};this.sendMsg(params)}}})},//查看大图viewImg(imgList){uni.previewImage({urls: imgList,// #ifndef MP-WEIXINindicator: 'number'// #endif});},},onPageScroll(e) {if (e.scrollTop < 50) {this.joinData();}},onNavigationBarButtonTap({ index }) {if (index == 0) {//用户详情 设置} else if (index == 1) {//返回按钮this.$u.route({type: 'switchTab',url: 'pages/home/home'});}},//返回按钮事件onBackPress(e) {//以下内容对h5不生效//--所以如果用浏览器自带的返回按钮进行返回的时候页面不会重定向 正在寻找合适的解决方案this.$u.route({type: 'switchTab',url: 'pages/home/home'});return true;},onLoad(info) {// { messageId,fromUserName,fromUserHeadImg } = infoconst userInfo = this.firendList.filter(item => item.userId == info.fromUserId)[0];this.fromUserInfo = {fromUserName: userInfo.userName,fromUserHeadImg: userInfo.headImg,fromUserId: userInfo.userId,messageId: info.messageId};//录音开始事件this.Recorder.onStart(e => {this.beginVoice();});//录音结束事件this.Recorder.onStop(res => {clearInterval(this.voiceInterval);this.handleRecorder(res);});//音频停止事件this.Audio.onStop(e => {this.closeAnmition();});//音频播放结束事件this.Audio.onEnded(e => {this.closeAnmition();});},onReady() {//自定义返回按钮 因为原生的返回按钮不可阻止默认事件// #ifdef H5const icon = document.getElementsByClassName('uni-page-head-btn')[0];icon.style.display = 'none';// #endifuni.setNavigationBarTitle({title: this.fromUserInfo.fromUserName});// this.joinData();uni.getSystemInfo({success: res => {this.imgHeight = res.windowHeight + 'px';}});uni.onKeyboardHeightChange(res => {if (res.height == 0) {// #ifdef MP-WEIXINthis.mpInputMargin = false;// #endif}else{this.showFunBtn = false;}});}
};
</script><style lang="scss" scoped>@import './index.scss'</style><style>.blink {animation: blink-animation 1s steps(1) infinite;}@keyframes blink-animation {0% {opacity: 0;}50% {opacity: 1;}100% {opacity: 0;}}</style>

3.2 服务端

3.2.1 Pthon Flask版

Flask框架提供了一个 Response 对象,可以将流式输出的数据返回给客户端。可以使用 yield 语句逐步生成数据,并将其传递给 Response 对象,以实现流式输出的效果。

以下是一个简单的使用 Flask 框架实现流式输出的示例代码:


from flask import Flask, Response
import timeapp = Flask(__name__)@app.route('/')
def stream():def generate():for i in range(10):yield str(i)time.sleep(1)return Response(generate(), mimetype='text/plain')if __name__ == '__main__':app.run(debug=True)

在上面的代码中,定义了一个 / 路由,当客户端访问该路由时,将执行 stream() 函数。generate() 函数使用 yield 语句逐步生成数字,并使用 time.sleep() 方法暂停一秒钟,以模拟生成数据的过程。最后,将生成的数据传递给 Response 对象,并将数据类型设置为文本类型(text/plain)。通过这种方式,实现了流式输出的效果。

3.2.2 Java SpringBoot版

(1)使用ResponseBodyEmitter对象

package com.example.streaming;import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitter;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;import java.io.IOException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;@Controller
public class StreamingController {private ExecutorService executor = Executors.newCachedThreadPool();@GetMapping(value = "/streaming", produces = MediaType.TEXT_PLAIN_VALUE)public ResponseBodyEmitter streaming() throws IOException {ResponseBodyEmitter emitter = new ResponseBodyEmitter();executor.execute(() -> {try {emitter.send("Hello\n");Thread.sleep(1000);emitter.send("World\n");emitter.complete();} catch (Exception e) {emitter.completeWithError(e);}});return emitter;}
}

在上面的代码中,我们定义了一个名为“StreamingController”的类,用于处理流式数据请求。在这个类中,我们定义了一个名为“streaming”的方法,该方法返回一个ResponseBodyEmitter对象,该对象用于向前端发送流式数据。在“streaming”方法中,我们创建了一个新的线程来模拟生成流式数据,通过调用ResponseBodyEmitter对象send方法将数据发送给前端。需要注意的是,我们需要将该方法的produces属性设置为“text/plain”,以指定返回的数据类型为文本类型,方便前端的数据解析。

(2)使用PrintWriter对象
@RestController
public class ExampleController {@GetMapping("/stream")public void streamData(HttpServletResponse response) throws Exception {response.setContentType("text/plain");response.setCharacterEncoding("UTF-8");PrintWriter writer = response.getWriter();for(int i=1; i<=10; i++) {writer.write("This is line " + i + "\n");writer.flush();Thread.sleep(1000); // 模拟耗时操作}writer.close();}
}

在这个示例中,我们使用@RestController注解将一个Java类声明为Spring MVC控制器,然后在该类中声明一个处理GET请求的方法streamData。在该方法中,我们首先设置响应的内容类型和字符编码,然后通过response.getWriter()方法获取PrintWriter对象,将数据写入响应并使用flush()方法刷新输出流,最后关闭PrintWriter对象


四、推荐阅读

🥇入门和进阶小程序开发,不可错误的精彩内容🥇 :

  • 《小程序开发必备功能的吐血整理【个人中心界面样式大全】》
  • 《微信小程序 | 动手实现双十一红包雨》
  • 《微信小程序 | 人脸识别的最终解决方案》
  • 《微信小程序 |基于百度AI从零实现人脸识别小程序》
  • 《吐血整理的几十款小程序登陆界面【附完整代码】》

这篇关于微信小程序 | 借ChatGPT之手重构社交聊天小程序的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/521

相关文章

W外链微信推广短连接怎么做?

制作微信推广链接的难点分析 一、内容创作难度 制作微信推广链接时,首先需要创作有吸引力的内容。这不仅要求内容本身有趣、有价值,还要能够激起人们的分享欲望。对于许多企业和个人来说,尤其是那些缺乏创意和写作能力的人来说,这是制作微信推广链接的一大难点。 二、精准定位难度 微信用户群体庞大,不同用户的需求和兴趣各异。因此,制作推广链接时需要精准定位目标受众,以便更有效地吸引他们点击并分享链接

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

EMLOG程序单页友链和标签增加美化

单页友联效果图: 标签页面效果图: 源码介绍 EMLOG单页友情链接和TAG标签,友链单页文件代码main{width: 58%;是设置宽度 自己把设置成与您的网站宽度一样,如果自适应就填写100%,TAG文件不用修改 安装方法:把Links.php和tag.php上传到网站根目录即可,访问 域名/Links.php、域名/tag.php 所有模板适用,代码就不粘贴出来,已经打

跨系统环境下LabVIEW程序稳定运行

在LabVIEW开发中,不同电脑的配置和操作系统(如Win11与Win7)可能对程序的稳定运行产生影响。为了确保程序在不同平台上都能正常且稳定运行,需要从兼容性、驱动、以及性能优化等多个方面入手。本文将详细介绍如何在不同系统环境下,使LabVIEW开发的程序保持稳定运行的有效策略。 LabVIEW版本兼容性 LabVIEW各版本对不同操作系统的支持存在差异。因此,在开发程序时,尽量使用

CSP 2023 提高级第一轮 CSP-S 2023初试题 完善程序第二题解析 未完

一、题目阅读 (最大值之和)给定整数序列 a0,⋯,an−1,求该序列所有非空连续子序列的最大值之和。上述参数满足 1≤n≤105 和 1≤ai≤108。 一个序列的非空连续子序列可以用两个下标 ll 和 rr(其中0≤l≤r<n0≤l≤r<n)表示,对应的序列为 al,al+1,⋯,ar​。两个非空连续子序列不同,当且仅当下标不同。 例如,当原序列为 [1,2,1,2] 时,要计算子序列 [

这些心智程序你安装了吗?

原文题目:《为什么聪明人也会做蠢事(四)》 心智程序 大脑有两个特征导致人类不够理性,一个是处理信息方面的缺陷,一个是心智程序出了问题。前者可以称为“认知吝啬鬼”,前几篇文章已经讨论了。本期主要讲心智程序这个方面。 心智程序这一概念由哈佛大学认知科学家大卫•帕金斯提出,指个体可以从记忆中提取出的规则、知识、程序和策略,以辅助我们决策判断和解决问题。如果把人脑比喻成计算机,那心智程序就是人脑的

uniapp设置微信小程序的交互反馈

链接:uni.showToast(OBJECT) | uni-app官网 (dcloud.net.cn) 设置操作成功的弹窗: title是我们弹窗提示的文字 showToast是我们在加载的时候进入就会弹出的提示。 2.设置失败的提示窗口和标签 icon:'error'是设置我们失败的logo 设置的文字上限是7个文字,如果需要设置的提示文字过长就需要设置icon并给

基于SpringBoot的宠物服务系统+uniapp小程序+LW参考示例

系列文章目录 1.基于SSM的洗衣房管理系统+原生微信小程序+LW参考示例 2.基于SpringBoot的宠物摄影网站管理系统+LW参考示例 3.基于SpringBoot+Vue的企业人事管理系统+LW参考示例 4.基于SSM的高校实验室管理系统+LW参考示例 5.基于SpringBoot的二手数码回收系统+原生微信小程序+LW参考示例 6.基于SSM的民宿预订管理系统+LW参考示例 7.基于

Spring Roo 实站( 一 )部署安装 第一个示例程序

转自:http://blog.csdn.net/jun55xiu/article/details/9380213 一:安装 注:可以参与官网spring-roo: static.springsource.org/spring-roo/reference/html/intro.html#intro-exploring-sampleROO_OPTS http://stati

H5漂流瓶社交系统源码

一个非常有创意的H5漂流瓶社交系统源码,带完整前端h5和后台管理系统。 环境:Nginx 1.20.1-MySQL 5.6.50-PHP-7.3 代码下载