当前位置: 首页 > 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.lryc.cn/news/612983.html

相关文章:

  • python数据结构与算法(基础)
  • DrissionPage自动化:高效Web操作新选择
  • 怎么在本地引入字体
  • 深入解析嵌套事务:原理与应用
  • 基于langchain的两个实际应用:[MCP多服务器聊天系统]和[解析PDF文档的RAG问答]
  • HTTP 协议升级(HTTP Upgrade)机制
  • 自动驾驶控制算法——滑模控制(SMC)原理与建模
  • TCP 如何保证可靠性
  • FluentUI-main的详解
  • 多账号管理方案:解析一款免Root的App分身工具
  • B-树与B+树
  • 动力电池点焊机:效率质量双提升,驱动新能源制造升级
  • Dify 从入门到精通(第 20/100 篇):Dify 的自动化测试与 CI/CD
  • Oracle exp imp expdp impdp 命令详解
  • PCB制造中压接孔、插接孔、沉头孔、台阶孔的区别及生产流程
  • 《C语言》函数练习题--1
  • 基于大数据的美食视频播放数据可视化系统 Python+Django+Vue.js
  • Vscode Data Wrangler 数据查看和处理工具
  • GitHub 上 Star 数量前 20 的开源 AI 项目
  • 中国MCP市场:腾讯、阿里、百度的本土化实践
  • 医疗人效管理新标杆:盖雅工场如何赋能健康服务企业提质增效
  • Java 大视界 -- Java 大数据在智能教育在线课程互动优化与学习体验提升中的应用(386)
  • 一篇文章用大白话带初学者搞清训练集、测试集及验证集关系及场景逻辑(包清楚)
  • LLMs api价格对比平台
  • --- Eureka 服务注册发现 ---
  • 【第7话:相机模型3】自动驾驶IPM图像投影拼接技术详解及代码示例
  • TikTok Shop冷启动破局战:亚矩阵云手机打造爆款账号矩阵
  • AWS RDS自定义终端节点深度分析工具:Python脚本详解
  • 手机控制断路器:智能家居安全用电的新篇章
  • STM32HAL 快速入门(一):点灯前的准备 —— 从软件安装到硬件原理