当前位置: 首页 > news >正文

利用vue.js2X写前端搜索页面,express写后端API接口展现搜索数据

这里用的数据库是mongodb
第1先做一个数据库类
db.js

const {MongoClient}=require('mongodb');class ClientDB{#urlPath='mongodb://ychj:123456@localhost:27017/?authSource=employees';constructor(dbName,collectionName){this.dbName=dbName;this.collName=collectionName;this.client=new MongoClient(this.#urlPath);this.connection=null;}async connect(){if(!this.connection){this.connection=await this.client.connect();console.log('Connected to MongoDB.');const db=this.client.db(this.dbName);this.collection=db.collection(this.collName);}}async disconnect(){if(this.connection){await this.client.close();this.connection=null;console.log('Connection closed');}}async insertOne(obj){await this.connect();try{let result=await this.collection.insertOne(obj);if(result.acknowledged){return result.insertedId;}}catch(err){console.log('Insert Error:',err);throw err;}}async deleteOne(obj){await this.connect();try{let result=await this.collection.deleteOne(obj);if(result.deletedCount===1){return true;}else{return false;}}catch(err){console.error('Delete Error:',err);throw err;}}/*** 统计查询对象有多少条结果* 这里只要大于0则返回true* @param {object} obj //查询对象 * @returns */async countDocuments(obj){await this.connect();try{let result=await this.collection.countDocuments(obj);console.log(result);if(result>0){return true;}else{return false;}}catch(err){console.error("Count documents Error:",err);throw err;}}/*** 查找多结果* @param {Object} obj //查询对象* @returns */async find(obj){await this.connect();try{let result=await this.collection.find(obj).toArray();if(result.length>=1){return result;}else{return false;}}catch(err){console.error("find documents Error:",err);throw new Error(err);}}/*** * @param {Object} upCondition 更新的条件对象* @param {Object} updateData 要更新的数据对象*/async updateOne(upCondition,updateData){await this.connect();try{let result=await this.collection.updateOne(upCondition,updateData);if(result.acknowledged && result.modifiedCount===1){return true;}else{return false;}}catch(err){console.error(err);throw new Error(err);}}
}
module.exports=ClientDB;

第2写一个根据搜索词,从数据库里面找出相关词,并生成一个临时的json文件,用数据流的方式将所有搜索到的json文件写入临时文件内
注意:数据库里面的文档都是进行了分词,用了jieba这个Nodejs库进行的分词,搜索出结果后要把文档内的空格全删除掉

searchJieba.js

const fs=require("fs");
const ClientDB=require('../db');
const db=new ClientDB("employees","employees");async function searchDocuments(words)
{console.log(words);try{const docs=await db.find({$text:{"$search":words}});if(docs===false){return;}//创建随机的临时文件名const  outputFile=getRandomFileName();//对大文件的数据流,为什么要用数据流,因为搜索出来的结果如果非常大,如上千条,我们不能存储在内存中,而是存在一个临时的随机文件中//避免占用或撑爆我们的内存,所以直接写入临时文件当中,然后显示 的时候再读取const writeableStream=fs.createWriteStream(outputFile);//将读取到的搜索结果存储为json文件格式,用一个数组将其包含当中writeableStream.write("[");let isFile=true;docs.forEach((doc)=>{if(!isFile){writeableStream.write(",\n");}const formattitle=doc.title.replace(/\s+/g,'');const formatcontent=doc.content.split('\n').map(paragraph=>paragraph.replace(/\s+/g,'')).join("\n");const id=doc.id;//将各属性id,title,content组合成对象形式,然后再转化为json格式写入临时的json文件中const result={id:id,title:formattitle,content:formatcontent};writeableStream.write(JSON.stringify(result));isFile=false;});writeableStream.write("]");writeableStream.end();//返回临时文件名字return outputFile;}finally{await db.disconnect();}
}//生成临时的随机json文件
function getRandomFileName(){return `output_${Math.random().toString(36).substring(2,9)}.json`
}exports.searchDocuments=searchDocuments;

第3做接口文件,方便前端调用

const express=require("express");
const app=express();
const {searchDocuments}=require("./searchJieba");
const fs=require("fs");
const readline=require("readline");
const cors=require("cors");
const path=require("path");app.use(cors({origin:"http://localhost:5173"
}))
app.use(express.json());app.post("/api/search",async (req,res)=>{let {words}=req.body;console.log(words);try{const outputFile=await searchDocuments(words);console.log("文件名:",outputFile);if(outputFile===undefined){const msg={stat:404,message:"未查询到相关结果"};const msgJson=JSON.stringify(msg);res.send(msgJson);}else{const fileStream=fs.createReadStream(path.join(__dirname,outputFile));const rl=readline.createInterface({input:fileStream,crlfDelay:Infinity});//按行读取所有的json文件内容let jsonData='';rl.on('line',line=>{jsonData+=line;});rl.on('close',()=>{try{console.log(jsonData);res.send(jsonData);//延时删除临时文件,避免文件数据还未传输完,这边已经删除了临时文件if(fs.existsSync(outputFile)){new Promise((resolve,reject)=>{setTimeout(()=>{fs.unlink(outputFile,(err)=>{if(err)reject(err);else resolve(`文件${outputFile}已经删除`);});},5000);//延时5秒}).then(console.log).catch(console.error);}}catch(err){console.error('发送json数据给前端出错:',err);res.status(400).end('向前端发送json数据出错');}})}}catch(err){console.error('查询搜索词出错:',err);res.status(400).end('查询出错');}
});app.listen(1337,"localhost",()=>{console.log('服务端口已经开启监听');
});

第4步是用vue写前端文件

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0"><title>测试用vite显示搜索前端的VUE页面,然后调用后端API搜索出来的数据</title><script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.13/vue.js"></script></head><body><div id="app"><my-search></my-search></div><script>const showChild={props:["responseData"],template:`<div><ul><li v-for="item in responseData" :key="item.id"><div>ID: {{item.id}}</div> <div>标题:{{item.title}}</div><div>内容:{{item.content}}</div>  </li>      </ul></div>`,};const Search={template:`<div><form @submit.prevent="handleSubmit"><div><label for="searchPage">搜索</label><input type="text" id="searchPage" v-model="searchData"><button :disabled="isLoading">{{isLoading ? "搜索中..." : "搜索"}}</button>   </div></form><div v-if="!isValied" style="color:red;font-size:14px;">{{searchWordsErr}}</div> <div v-if="!isSearchResult">{{searchWordsNull}} </div> <my-child :responseData="responseData" v-if="isSearchResult"></my-child></div>`,data(){return{searchData:'',isLoading:false,searchWordsErr:'',responseData:'',searchWordsNull:'',isSearchResult:true}},computed:{isValied(){return this.searchData.length >= 2;}},methods:{handleSubmit(){this.valiedSearchWords(this.searchData);if(!this.isValied)return;this.isLoading=true;let searchWords={words:this.searchData};console.log(searchWords);try{fetch("http://localhost:1337/api/search",{method:"post",body:JSON.stringify(searchWords),headers:{"Content-Type":"application/json"}}).then(response=>{if(response.ok){return response.json();}}).then(data=>{if(data.stat===404){console.log(data.message);this.isSearchResult=false;this.searchWordsNull=data.message;return;}this.isSearchResult=true;this.responseData=data;}).catch(error=>{console.log('搜索出错:',error);}).finally(()=>{this.isLoading=false;})}catch(err){console.error("获取后端数据出错:",err);}},valiedSearchWords(words){if(words.length<2){this.searchWordsErr="搜索必须大于2个字符";}else{this.searchWordsErr='';}}},watch:{searchData(newVal){this.valiedSearchWords(newVal);}},components:{'my-child':showChild}   };new Vue({el:"#app",components:{"mySearch":Search}});</script></body>
</html>        

这样就是一个用vue写的搜索页面,后端是一个express调用接口
顺便也把插入文档的代码也贴出来,数据库类和上面的db.js 是一样的
第1步是写插入数据库的文件代码
insertJieba.js

const {Jieba}=require("@node-rs/jieba")
const {dict}=require("@node-rs/jieba/dict");
const ClientDB=require("../db");
const db=new ClientDB("employees","employees");async function insertDocument(titledata,contentdata)
{ try{const jieba=Jieba.withDict(dict);const titleWords=jieba.cut(titledata).join(' ');const contentWords=contentdata.split("\n").map(paragraph=>jieba.cut(paragraph).join(" "));const randomId=RandomId(1,10000);const result=await db.insertOne({id:randomId,title:titleWords,content:contentWords.join("\n")});if(result){return true;}else{return false;}}catch(error){console.error(error);throw new Error(error);}        
}/*** 用洗牌方法随机生成一个不重复的数字ID* @param {num} min * @param {num} max * @returns */
function RandomId(min, max) {// 创建一个从 min 到 max 的数组const numbers = Array.from({ length: max - min + 1 }, (_, i) => i + min);// 使用 Fisher-Yates 洗牌算法打乱数组for (let i = numbers.length - 1; i > 0; i--) {const j = Math.floor(Math.random() * (i + 1));[numbers[i], numbers[j]] = [numbers[j], numbers[i]];}// 返回第一个元素作为IDreturn numbers[0];
}exports.insertDocument=insertDocument;

第2步是用express写接口文件,接口文件是接收前端数据,然后传给后端的insertJieba.js写入数据库的中间件
150.js

const express=require("express");
const cors=require("cors");
const app=express();
const {insertDocument}=require('./insertJieba');app.use(cors({origin:"http://localhost:5173"
}))
app.use(express.json());app.post("/api/participle",async (req,res)=>{let {title,content}=req.body;try{let result=await insertDocument(title,content);let resData={};if(result){resData={msg:'数据插入成功',status:true};}else{resData={msg:"数据插入失败",status:false};}res.send(JSON.stringify(resData));}catch(error){console.error('Insert Documents Error:',error);res.status(404).end('插入分词发生错误');}
});app.listen(1337,"localhost",()=>{console.log("服务端口已经开启监听");
});

第3步是前端文件,即vue写的插入title和content
150.html

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0"><title>测试用vite显示vue前端页面,然后调用后端API数据</title><script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.13/vue.js"></script></head><body><div id="app"><my-content></my-content></div><script>const Content={template:`<div><form @submit.prevent="handleSubmit"><div><label for="addTitle">标题</label><input type="text" name="title" id="addTitle" v-model="title" @input="valiedTitle" required><span v-show="!titleErr.stat" :style="titleStyle">{{titleErr.msg}}</span></div> <div><label for="addContent">内容</label><textarea id="addContent" rows="10" cols="45" v-model="content" @input="valiedContent" placeholder="请输入内容" ></textarea><span v-show="!contentErr.stat" :style="contentStyle">{{contentErr.msg}}</span></div><div><button id="btn" :disabled="isLoading">{{isLoading ? "提交中..." : "提交"}}</button></div>   </form><div id="output">{{responseData}}</div>    </div>`,data(){return{title:'',content:'',isLoading:false,responseData:'',titleErr:{stat:true,msg:''},contentErr:{stat:true,msg:''},titleStyle:{color:"red",fontSize:"14px",marignLeft:"10px"},contentStyle:{color:"red",fontSize:"14px",marignLeft:"10px"}}},    methods:{handleSubmit(){this.valiedTitle(this.title);this.valiedContent(this.content);if(!this.titleErr.stat || !this.contentErr.stat){return;}this.isLoading=true;let obj={title:this.title,content:this.content};fetch("http://localhost:1337/api/participle",{method:"post",body:JSON.stringify(obj),headers:{"Content-Type":"application/json"}}).then(response=>response.json()).then(data=>{const {status,msg}=data;if(status){this.responseData=msg;this.title='';this.content='';}else{this.responseData=msg;}}).catch(error=>{console.error(error);this.responseData="提交失败:"+error;}).finally(()=>{this.isLoading=false;})},valiedTitle(newVal){if(newVal.length<5){this.titleErr.msg="标题至少4个字符";this.titleErr.stat=false;}else{this.titleErr.msg='';this.titleErr.stat=true;}},valiedContent(newVal){if(newVal.length<5){this.contentErr.msg='内容至少4个字符';this.contentErr.stat=false;}else{this.contentErr.msg='';this.contentErr.stat=true;}}},watch:{title(newVal){this.valiedTitle(newVal);},content(newVal){this.valiedContent(newVal);}}};new Vue({el:"#app",components:{"myContent":Content}})</script></body>
</html>        

运行方式:在vite中运行前端文件,html后缀的文件,在node中运行express写的文件

http://www.dtcms.com/a/319198.html

相关文章:

  • SymPy 中 atan2(y, x)函数的深度解析
  • vue3对比vue2的性能优化和提升 :Vue 3 vs Vue 2
  • ArkTS: McPointChart
  • 【Redis面试精讲 Day 16】Redis性能监控与分析工具
  • 从Web2.0到Web3.0——用户体验如何演进
  • 树莓派安装中文输入法
  • Day09 Tlisa登录认证
  • Linux总线,设备和驱动关系以及匹配机制解析
  • FPGA学习笔记——VGA显示静态图片(ROM IP核)
  • 【博弈 拓扑序 缩点】P9220 「TAOI-1」椎名真昼|省选-
  • Bosco-and-Mancuso Filter for CFA Image Denoising
  • 如何快速掌握Excel公式?14天轻松通关
  • LeetCode 面试经典 150_数组/字符串_除自身以外数组的乘积(13_238_C++_中等)(前缀积)
  • Swift 实战:高效设计 Tic-Tac-Toe 游戏逻辑(LeetCode 348)
  • 解决chrome下载crx文件被自动删除,加载未打包的扩展程序时提示“无法安装扩展程序,因为它使用了不受支持的清单版本解决方案”
  • 冷库温湿度物联网监控系统解决方案:冷链智能化
  • 普通冷库如何升级物联网冷库?工业智能网关赋能冷链智能化转型
  • AI摄像机如何为煤矿减少90%违规事故?
  • HarmonyOS 页面跳转新方案:HMRouter 路由框架全方位使用指南与实践案例
  • Axure 高阶设计:打造“以假乱真”的多图片上传组件
  • 如何使用vLLM运行gpt-oss
  • Nodejs》》MySql
  • 单链表专题---暴力算法美学(1)(有视频演示)
  • Keil MDK-ARM V5.42a 完整安装教程
  • 如何使用Ollama在本地运行gpt-oss
  • 09-netty基础-手写rpc-原理-01
  • 上位机知识篇---aptapt-get
  • 全栈:怎么把sql导入SQLserver里面
  • [特殊字符] 2025年生成式大模型部署与推理优化全景解析
  • STM32 串口控制电机运行系统