vue3setup标签语法 + vite + delfin 递归组件实现无限评论功能
1、 功能效果
在线预览:https://szhihao.gitee.io/comment/
gitee仓库地址:https://gitee.com/szhihao/comment
2、实现的具体技术点
-
根据不同的人名可以进行评论(tap切换)
-
对进行的评论可以无限进行回复(递归组件和数据tree处理)
-
判断评论是否为空
-
localStorage 本地存储的使用
-
vuex 的使用
-
使用的插件
-
{"name": "vite-project","private": true,"version": "0.0.0","type": "module","scripts": {"dev": "vite","build": "vite build","preview": "vite preview"},"dependencies": {"delfin": "^1.2.5","vue": "^3.3.4"},"devDependencies": {"@types/node": "^20.5.0","@vitejs/plugin-vue": "^4.2.3","vite": "^4.4.5"} }
-
vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'// 使用path 需要安装 npm install @types/node --save-dev
import path from "path";//这个path用到了上面安装的@types/node
// https://vitejs.dev/config/
export default defineConfig({plugins: [vue()],resolve:{alias:{'@': path.resolve('./src') // @代替src},extensions:['.js','.vue'] // 可以省略.js 和.vue 后缀名}
})
app.vue文件
1、定义tab结构作为评论的人名切换
<template><div><h1>{{ useUserInfoStore.userInfo.userName }}</h1></div><button:class="{bg:item.id === useUserInfoStore.userInfo.id }"v-for="(item,index) in tapList":key="item.id"@click="useUserInfoStore.setUserInfo(item)">{{item.userName}}</button><hr><comment-one></comment-one>
</template>
<script setup>
import useUserInfo from './stort/index.js'
import {reactive,ref} from "vue";
const tabindex = ref(-1) //tab的默认值 -1 什么也不显示
import commentOne from './components/commentOne.vue'
const useUserInfoStore = useUserInfo()
// 定义人名字数据处理
const tapList = reactive([{id:1,userName:'孙志豪'},{id:2,userName: '王五',},{id:3,userName: '小明',}
])</script>
<style scoped>
.bg {background-color: skyblue;
}
</style>
2、components/commentOne.vue
- 当前组件是显示评论的组件,里面是 formatTree 数据处理的操作
<template><div><p><textarea v-model="textValue"></textarea></p><p><button @click="addComment">提交评论</button></p></div><div><comment-comp :commentData="data.commentData" @add-reply="addReply" @remove="removeHandler"></comment-comp></div>
</template><script setup>
import {ref,reactive} from "vue";
import useUserInfo from '@/stort/index.js'
// 使用vuex
const useUserInfoStore = useUserInfo()
const textValue = ref('')
import commentComp from './commentComp.vue'
let data = reactive({commentData:formatTree(getComment('one')) // 数据tree处理
})const addComment = () => {//判空处理if(textValue.value === undefined || textValue.value == null || textValue.value.trim() === ''){alert('请输入评论')return}// 读取vuex中的数据const {id, userName} = useUserInfoStore.userInfolet commentInfo = {id: new Date().getTime(),pid: 0,uid: id,userName,comment: textValue.value}setComment('one', commentInfo)// 第一次追加数据,解决本地存上数据,刷新才会显示data.commentData.unshift(commentInfo)textValue.value = ''
}const addReply = (item, replyText) => {if(replyText === undefined || replyText == null || replyText.trim() === '') {alert('请输入评论')return}const {id, userName} = useUserInfoStore.userInfo;const replyInfo = {id: new Date().getTime(),pid: item.id,uid: id,userName,comment: replyText,recoverName:item.userName}// 有子级就将当前数据放进去;(item.children || (item.children = [])).unshift(replyInfo)setComment('one', replyInfo);
}// 本地数据的存储,
// 1、将本地的数据先取出
// 2、追加新的数据
// 3、将数据转换字符串存入本地function setComment(field, comment) {const commentList = JSON.parse(localStorage.getItem(field) || '[]')commentList.unshift(comment)localStorage.setItem(field, JSON.stringify(commentList))
}// 本地取出数据
function getComment(field) {let arr = JSON.parse(localStorage.getItem(field) || '[]')return arr.reverse()
}// 处理数据处理成tree的结构
function formatTree(data) {const result = []const map = {}// 以id为属性名作为每一项data.forEach(item => {map[item.id] = item})data.forEach(item => {// 再次循环data数据,通过pid可以读取到map中父级的数据,因为子级的pid 是父级的 id 给的;子级的pid == 父级的 idconst parent = map[item.pid]if(parent) {//如果是父级其它数据就是子级// 如果是父级的话,并且有 children字段将当前的子级的数据放到children里面,没有的话开一个新数组;(parent.children || (parent .children = [])).unshift(item)}else {// 循环完了,当找找没有子级的数据就将数据放到 result 结果中最后返回result.unshift(item)}})return result
}
</script><style scoped></style>
3、components/commentComp.vue
- 该组件主要作用是递归组件,无限评论的核心,回复功能的实现;
<template><liv-for="(item,index) in commentData":key="item.id"><p> {{index + 1}}楼 pid:{{item.pid}} --- {{handlerText(item)}} : {{ item.comment }} <a href="javascript:;" @click="setRepFlag(item)">回复</a></p><div v-if="item.flag"><p><textarea v-model="item.replyText"></textarea></p><p><button @click="addReply(item)">提交评论</button></p></div><div v-if="item.children"><ul><!-- 在组件中自己使用自己--><comment-comp v-if="item.children" :commentData="item.children" @add-reply="addReply" ></comment-comp></ul></div></li></template><script setup>
const props = defineProps({commentData: Array
})// 子穿父 vue3 新语法 Emits
const emit = defineEmits(['addReply','remove'])const setRepFlag = (item) => {item.flag = !item.flag
}
// 提交评论
const addReply = (item) => {let replyText = item.replyTextemit('addReply',item,replyText)item.flag = falseitem.replyText = ''
}
// 回复人处理
function handlerText(item) {let recoverName = item.recoverName ? '@' + item.recoverName : ''return item.recoverName === item.userName ? '我说' : item.userName + recoverName + '说'
}</script><style scoped>
li{width: 100%;height: 100%;background-color: skyblue;
}
</style>
4、vite-project/src/stort/index.js
import {createStore} from 'delfin'
// delfin 插件使用vuex
export default createStore({state:{userInfo:{id:1,userName:'孙志豪'}},actions:{setUserInfo(store,userInfo) {store.userInfo = userInfo}}
})