演示地址:https://codepen.io/kissbackboard/pen/oNExNKL
目前实现了增删改查、排序、趣味提示信息、回收站。
右边的猫头鹰是使用Spline3D设计工具制作,我拿官方模型库里的加工了一下
HTML:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, addial-scale=1.0">
<title></title>
<!-- 主要的CSS -->
<link rel="stylesheet" href="css/main.css">
</head>
<body>
<div id="root">
<!-- 用于输入新增用户信息的表单 -->
<transition name="modalBox" appear>
<div v-show="showOperate" class="operate" @click.self="showOperate = ifOwlSpeak = false">
<div @keyup.enter.stop="affirm">
<label>学号:</label>
<input type="number" ref="addCode" v-model="addCode" @input="OwlSpeakShow('若学号重复会无法提交哦')"><br>
<label>姓名:</label>
<input type="text" @keydown="banSpace" v-model="addName"><br>
<label>性别:</label>
<select class="addSex" v-model="addSex" @change="OwlSpeakShow('原来是一位' + addSex + '同学啊')">
<option value="男">男</option>
<option value="女">女</option>
</select><br>
<label>年龄:</label>
<input type="number" v-model="addAge" @input="OwlSpeakShow('按回车也可以提交哦')"><br>
<button @click="affirm" class="affirmBtn">确定</button>
<button @click="showOperate = ifOwlSpeak = false" class="cancelBtn">取消</button>
</div>
</div>
</transition>
<!-- 新增、查询、回收站功能 -->
<button @click="add" class="addBtn" @mouseenter="OwlSpeakShow('是有新同学要来吗')" @mouseleave="OwlSpeakHide">添加学生</button>
<input type="text" placeholder="按姓名查询" class="inquireName" @focus="inquireAlone('name')" v-model="inquireNameValue" @input="inquireName" @mouseenter="OwlSpeakShow('输入后会自动查询')" @mouseleave="OwlSpeakHide">
<input type="number" placeholder="按学号查询" class="inquireCode" @focus="inquireAlone('code')" v-model="inquireCodeValue" @input="inquireCode" @mouseenter="OwlSpeakShow('用完整学号查询最准确了')" @mouseleave="OwlSpeakHide">
<button class="recycleBtn" @click="showRecycle = true" @mouseenter="OwlSpeakShow('可以查看被删除的学生')" @mouseleave="OwlSpeakHide">回收站</button>
<!-- 回收站表格 -->
<transition name="modalBox" appear>
<div class="recycleInfoTable" @click.self="showRecycle = false" v-show="showRecycle">
<h1>回收站</h1>
<table cellspacing='1' cellpadding='0' class="infoTable">
<thead>
<tr>
<th @click="recycleVariousSort('code')" @mouseenter="OwlSpeakShow('点击可以按学号排序')" @mouseleave="OwlSpeakHide">学号</th>
<th @click="recycleVariousSort('name')" @mouseenter="OwlSpeakShow('点击可以按姓名排序')" @mouseleave="OwlSpeakHide">姓名</th>
<th @click="recycleVariousSort('date')" @mouseenter="OwlSpeakShow('点击可以按日期排序')" @mouseleave="OwlSpeakHide">删除时间</th>
<th @dblclick="allEmpty" @mouseenter="OwlSpeakShow('双击清空回收站所有信息')" @mouseleave="OwlSpeakHide">操作</th>
</tr>
</thead>
<tbody is="transition-group" name="fade" appear>
<tr v-for="user in recycleInfo" v-show="user.satisfy" :key="user.id">
<td>{{user.code}}</td>
<td>{{user.name}}</td>
<td>{{user.date | timeformater}}</td>
<!--还原和彻底删除操作 -->
<td>
<button class="saveBtn" @click="reductionInfo(user)" @mouseenter="OwlSpeakShow('点击还原 ' + user.name + ' 的信息')" @mouseleave="OwlSpeakHide">还原</button>
<button class="deleteBtn" @click="thoroughDeleteInfo(user)" @mouseenter="OwlSpeakShow('彻底删除 ' + user.name + ' 的信息')" @mouseleave="OwlSpeakHide">彻底删除</button>
</td>
</tr>
</tbody>
</table>
</div>
</transition>
<!-- 学生信息表格 -->
<table cellspacing='1' cellpadding='0' class="infoTable">
<thead>
<tr>
<th @click="variousSort('code')" @mouseenter="OwlSpeakShow('点击可以按学号排序')" @mouseleave="OwlSpeakHide">学号</th>
<th @click="variousSort('name')" @mouseenter="OwlSpeakShow('点击按姓氏首字母排序')" @mouseleave="OwlSpeakHide">姓名</th>
<th @click="variousSort('sex')" @mouseenter="OwlSpeakShow('点击可以按性别排序')" @mouseleave="OwlSpeakHide">性别</th>
<th @click="variousSort('age')" @mouseenter="OwlSpeakShow('点击可以按年龄排序')" @mouseleave="OwlSpeakHide">年龄</th>
<th @mouseenter="OwlSpeakShow('目前一共有 ' + info.length + ' 位学生')" @mouseleave="OwlSpeakHide">操作</th>
</tr>
</thead>
<tbody is="transition-group" name="fade" appear>
<tr v-for="user in info" v-show="user.satisfy" @keyup.enter.stop="currentCodeIfRepeat(user)" :key="user.id">
<td>
<span v-show="!user.isEdit">{{user.code}}</span><input ref="editFirst" v-show="user.isEdit" type="number" v-model="user.code" @input="OwlSpeakShow('若学号重复会无法保存哦')">
</td>
<td>
<span v-show="!user.isEdit">{{user.name}}</span><input v-show="user.isEdit" type="text" v-model="user.name" @keydown="banSpace">
</td>
<td>
<span v-show="!user.isEdit">{{user.sex}}</span>
<select v-show="user.isEdit" v-model="user.sex">
<option value="男">男</option>
<option value="女">女</option>
</select>
</td>
<td>
<span v-show="!user.isEdit">{{user.age}}</span><input v-show="user.isEdit" type="number" v-model="user.age">
</td>
<!-- 编辑和删除操作 -->
<td>
<button v-show="user.isEdit" @click="edit(user)" class="saveBtn" @mouseenter="OwlSpeakShow('按回车键也可以保存哦')" @mouseleave="OwlSpeakHide">保存</button>
<button v-show="!user.isEdit" @click="edit(user)" class="editBtn" @mouseenter="OwlSpeakShow(user.name +' 的信息有误吗')" @mouseleave="OwlSpeakHide">编辑</button>
<button @click="deleteInfo(user)" class="deleteBtn" @mouseenter="OwlSpeakShow('你要删除 '+ user.name +' 吗?')" @mouseleave="OwlSpeakHide">删除</button>
</td>
</tr>
</tbody>
</table>
<!-- 猫头鹰 webGL -->
<div class="owl" @mouseenter="OwlSpeakShow('应该不会有bug了吧...')" @mouseleave="OwlSpeakHide" v-show="ifOwl">
<div :class="[{ cover: !owlLoadIfSucceed }, { part: owlLoadIfSucceed }]" @click="ifOwl = false" @mouseenter="OwlSpeakShow('点击后将把我隐藏')" @mouseleave="OwlSpeakHide">{{owlLoadInfo}}</div>
<p v-show="ifOwlSpeak">{{owlSpeak}}</p>
<!-- 使用Spline3D设计工具制作 -->
<iframe src='https://my.spline.design/copy-15a6b025c7aac1efc79c2de6c4aac93a/' frameborder='0' width='180px' height='180px' scrolling="no" @load="owlLoad"></iframe>
</div>
</div>
<!-- JS部分 -->
<script src="js/dayjs.min.js"></script>
<script src="js/vue.js"></script>
<!-- 默认的学生信息,方便测试各个功能(可删除) -->
<script src="js/defaultInfo.js"></script>
<!-- 主要的JS -->
<script src="js/main.js"></script>
</body>
</html>
CSS:
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
display: flex;
justify-content: center;
}
#root {
padding-top: 30px;
position: relative;
}
button {
cursor: pointer;
user-select: none;
}
.infoTable {
margin-top: 20px;
background-color: rgb(255, 255, 255);
border-radius: 5px;
overflow: hidden;
}
.infoTable td,
.infoTable th {
padding: 6px;
min-width: 180px;
max-width: 25vw;
height: 50px;
overflow: auto;
text-align: center;
background-color: #3d559d;
color: white;
}
.infoTable th {
height: 40px;
background-color: #6472b8;
cursor: pointer;
user-select: none;
}
.infoTable tr td:last-child button {
width: 40%;
height: 30px;
background-color: white;
border: none;
border-radius: 2px;
}
.infoTable tbody tr td .editBtn {
color: #032ebc;
}
.infoTable tbody tr td .saveBtn {
color: #032ebc;
font-weight: 900;
}
.infoTable tbody tr td .deleteBtn {
color: rgb(255, 57, 57);
}
.infoTable tbody tr:hover {
opacity: 0.85;
}
.recycleInfoTable {
width: 100vw;
height: 100vh;
position: fixed;
z-index: 2;
top: 0px;
left: 0px;
background-color: #ffffffaf;
display: flex;
justify-content: center;
padding-top: 60px;
}
.recycleInfoTable table {
height: 1px;
}
.recycleInfoTable td:nth-child(3),
.recycleInfoTable th:nth-child(3) {
padding: 6px;
width: 360px;
height: 30px;
}
.recycleInfoTable h1 {
position: absolute;
top: 10px;
color: rgb(255, 57, 57);
}
.operate {
display: grid;
place-content: center;
width: 100vw;
height: 100vh;
background-color: #3d559d41;
position: fixed;
z-index: 9;
top: 0px;
left: 0px;
}
.operate div {
width: 500px;
height: 280px;
background-color: #ffffff;
text-align: center;
padding-top: 20px;
box-shadow: 1px 6px 15px 1px rgba(0, 0, 0, 0.4);
border-radius: 5px;
}
.operate div input,
.addSex {
width: 50%;
height: 30px;
margin-top: 15px;
padding-left: 5px;
}
.operate div input:focus {
outline-color: #3d6bff;
}
.operate div button {
margin-top: 20px;
margin-left: 30px;
margin-right: 30px;
width: 22%;
height: 15%;
background-color: #6776ff;
border: none;
color: white;
font-size: 16px;
border-radius: 3px;
}
.operate div label {
font-weight: 900;
letter-spacing: 5px;
}
/* 学生信息表格中的表单 */
.infoTable tbody tr td input,
.infoTable tbody tr td select {
text-align: center;
width: 150px;
height: 33px;
font-size: 15px;
}
.infoTable tbody tr td input:focus {
outline-color: #2e5eff;
}
.addBtn {
width: 120px;
height: 40px;
background-color: #4d5ffd;
border: none;
color: white;
font-size: 16px;
border-radius: 3px;
/* word-spacing: 1px; */
}
.recycleBtn {
width: 120px;
height: 40px;
background-color: #fd4b37;
border: none;
color: white;
font-size: 16px;
border-radius: 3px;
position: fixed;
right: 10px;
bottom: 10px;
}
/* 用于查询的表单 */
.inquireCode,
.inquireName {
width: 150px;
height: 37px;
position: absolute;
z-index: 99;
right: 0px;
top: 30px;
text-align: center;
border: 1px solid #5b84ff;
border-radius: 2px;
}
.inquireCode:focus,
.inquireName:focus {
outline-color: #5b84ff;
}
.inquireName {
right: 160px;
}
.owl {
position: fixed;
z-index: 99;
top: 100px;
right: 0px;
color: #4364c7;
/* background-color: #032ebc; */
}
.owl .cover {
width: 100%;
height: 100%;
background-color: transparent;
position: absolute;
right: 30px;
bottom: -30px;
font-size: 22px;
font-weight: 900;
-webkit-text-stroke: .3px rgb(255, 255, 255);
}
.owl .part {
width: 33px;
height: 33px;
background-color: white;
border-radius: 100%;
position: absolute;
right: 15px;
bottom: 19px;
cursor: pointer;
font-size: 6px;
text-align: center;
line-height: 33px;
transition: all .1s;
}
.owl .part:hover {
background-color: #1f46c5;
color: white;
}
.owl p {
width: auto;
height: auto;
text-align: center;
position: absolute;
z-index: 2;
bottom: 190px;
left: -45px;
user-select: none;
background-color: #476cff;
color: white;
padding: 10px;
border-radius: 5px;
}
.owl p::before {
content: '';
width: 30px;
height: 30px;
background-color: #476cff;
position: absolute;
z-index: -1;
bottom: -25px;
right: 55px;
clip-path: polygon(0 0, 50% 50%, 100% 0);
}
.fade-enter-active {
animation: fadeAnimate 0.3s ease-in-out;
}
.fade-leave-active {
animation: fadeAnimate 0.3s ease-in-out reverse;
}
@keyframes fadeAnimate {
from {
opacity: 0;
transform: translateY(-50%);
}
to {
transform: translateY(0%);
opacity: 1;
}
}
.modalBox-enter-active {
animation: modalBox 0.3s ease-in-out;
}
.modalBox-leave-active {
animation: modalBox 0.3s ease-in-out reverse;
}
@keyframes modalBox {
0% {
transform: translateY(-100px) scale(2);
opacity: 0;
}
100% {
transform: translateY(0px) scale(1);
opacity: 1;
}
}
JS:
// 实例化Vue
const vm = new Vue({
el: '#root',
data: {
info: JSON.parse(localStorage.getItem("persistence_user_info")) || [],
recycleInfo: JSON.parse(localStorage.getItem("recycle_user_info")) || [],
showOperate: false,
addCode: '',
addName: '',
addSex: '',
addAge: '',
inquireCodeValue: '',
inquireNameValue: '',
ifOwl: true,
ifOwlSpeak: true,
owlSpeak: '可以持久化存储信息哦',
owlLoadIfSucceed: false,
owlLoadInfo: 'canvas加载中...',
ascOrDesc: 'asc',
// 猫头鹰随机说的句子
owlRandomSpeak: [
'可以持久化存储信息哦',
'JavaScript是最好的语言',
'没有对象? new一个',
'本人面向百度、CSDN编程',
'我以后在给代码加注释',
'见鬼了,昨天还好好的',
'启动! Ctrl+C大法',
'它在我电脑上能运行',
],
showRecycle: false
},
methods: {
add() {
// 使所有用于增加操作的表单值为空
this.addCode = this.addName = this.addSex = this.addAge = '';
this.showOperate = true;
// 下一次DOM更新结束后执行指定的回调函数
this.$nextTick(function () {
this.$refs.addCode.focus();
});
},
affirm() {
let code = this.addCode;
let name = this.addName;
let sex = this.addSex;
let age = this.addAge;
// 判断所有用于输入新增用户信息的表单 值是否为空
if (code == '' || name == '' || sex == '' || age == '') return alert('信息不完整');
// 判断是否有学号重复
for (let i = 0; i < this.info.length; i++) {
if (this.info[i].code == this.addCode) return alert('学号重复!');
}
// 新建一个学生对象
let obj = {
id: Math.random(),
code,
name,
sex,
age,
satisfy: true,
isEdit: false
}
// 把这个学生对象添加到info
this.info.push(obj);
this.showOperate = false;
this.OwlSpeakShow('欢迎新同学:' + name);
},
deleteInfo(user) {
// 添加到回收站
let obj = {
id: user.id,
code: user.code,
name: user.name,
sex: user.sex,
age: user.age,
satisfy: user.satisfy,
isEdit: user.isEdit,
date: +new Date(),
};
this.recycleInfo.unshift(obj);
// 删除一个学生对象
this.info = this.info.filter(item => item.id != user.id);
this.OwlSpeakShow(user.name + ' 的信息删除成功');
},
edit(user) {
// 判断是否为编辑状态
if (user.isEdit) {
// 非编辑状态
// 判断当前行的学号是否与其他学号重复
this.currentCodeIfRepeat(user);
} else {
// 编辑状态
user.isEdit = true;
// 下一次DOM更新结束后执行指定的回调函数
this.$nextTick(function () {
// 点击编辑后使第一个文本框赋值
this.$refs.editFirst.forEach(item => {
if (item.value == user.code) return item.focus();
});
});
}
},
// 按学号查询
inquireCode() {
let meetsNum = 0;
let inquireInfo;
// 判断是查询回收站的表格还是学生信息的表格
this.showRecycle ? inquireInfo = this.recycleInfo : inquireInfo = this.info;
inquireInfo.forEach(element => {
element.code.toString().indexOf(this.inquireCodeValue) != -1 ? element.satisfy = true : element.satisfy = false;
element.satisfy && meetsNum++;
});
this.inquireCodeValue ? this.OwlSpeakShow('有 ' + meetsNum + ' 位同学符合查询条件') : this.OwlSpeakShow('输入后会自动查询');
},
// 按姓名查询
inquireName() {
this.banSpace();
let meetsNum = 0;
let inquireInfo;
// 判断是查询回收站的表格还是学生信息的表格
this.showRecycle ? inquireInfo = this.recycleInfo : inquireInfo = this.info;
inquireInfo.forEach(element => {
element.name.indexOf(this.inquireNameValue) != -1 ? element.satisfy = true : element.satisfy = false;
element.satisfy && meetsNum++;
});
this.inquireNameValue ? this.OwlSpeakShow('有 ' + meetsNum + ' 位同学符合查询条件') : this.OwlSpeakShow('输入后会自动查询');
},
inquireAlone(mark) {
if (mark == 'code') {
this.inquireCode();
this.inquireNameValue = '';
} else if (mark == 'name') {
this.inquireName();
this.inquireCodeValue = '';
}
},
// 禁止输入空格
banSpace() {
window.event.target.value = window.event.target.value.replace(/\s+/g, ''); //适用于keyup事件
if (window.event.keyCode == 32 || window.event.code == 'Space' || window.event.key == ' ') return window.event.returnValue = false; //适用于keydown事件
},
// 判断所有学号是否重复,并把重复学号所在的行设为编辑状态
allCodeIfRepeat() {
let flag = false;
for (let i = 0; i < this.info.length; i++) {
let frequency = 0;
let element = this.info[i];
for (let j = 0; j < this.info.length; j++) {
let elements = this.info[j];
if (element.code == elements.code) {
frequency++;
if (frequency > 1) {
element.isEdit = true;
flag = true;
}
}
}
}
flag && alert('学号重复!');
return flag;
},
// 判断当前行的学号是否与其他学号重复
currentCodeIfRepeat(user) {
let frequency = 0;
for (let i = 0; i < this.info.length; i++) {
if (this.info[i].code == user.code) {
frequency++;
if (frequency > 1) {
alert('学号重复!');
return true;
}
}
}
user.isEdit = false;
return false;
},
OwlSpeakShow(speak) {
this.ifOwlSpeak = true;
this.owlSpeak = speak;
},
OwlSpeakHide() {
this.ifOwlSpeak = false;
},
// 学生信息表格排序
variousSort(mark) {
// 根据学号排序
if (mark == 'code') {
this.ascOrDesc == 'asc' ? this.info.sort((a, b) => a.code - b.code) : this.info.sort((a, b) => b.code - a.code);
}
// 根据姓名排序
else if (mark == 'name') {
this.ascOrDesc == 'asc' ? this.info.sort((a, b) => a.name.localeCompare(b.name)) : this.info.sort((a, b) => b.name.localeCompare(a.name));
}
// 根据性别排序
else if (mark == 'sex') {
this.ascOrDesc == 'asc' ? this.info.sort((a, b) => a.sex.localeCompare(b.sex)) : this.info.sort((a, b) => b.sex.localeCompare(a.sex));
}
// 根据年龄排序
else if (mark == 'age') {
this.ascOrDesc == 'asc' ? this.info.sort((a, b) => a.age - b.age) : this.info.sort((a, b) => b.age - a.age);
}
this.ascOrDesc == 'asc' ? this.ascOrDesc = 'desc' : this.ascOrDesc = 'asc';
},
// 回收站表格排序
recycleVariousSort(mark) {
if (mark == 'code') {
this.ascOrDesc == 'asc' ? this.recycleInfo.sort((a, b) => a.code - b.code) : this.recycleInfo.sort((a, b) => b.code - a.code);
}
// 根据姓名排序
else if (mark == 'name') {
this.ascOrDesc == 'asc' ? this.recycleInfo.sort((a, b) => a.name.localeCompare(b.name)) : this.recycleInfo.sort((a, b) => b.name.localeCompare(a.name));
}
// 根据日期排序
else if (mark == 'date') {
this.ascOrDesc == 'asc' ? this.recycleInfo.sort((a, b) => a.date - b.date) : this.recycleInfo.sort((a, b) => b.date - a.date);
}
this.ascOrDesc == 'asc' ? this.ascOrDesc = 'desc' : this.ascOrDesc = 'asc';
},
// 对于iframe加载成功的处理
owlLoad() {
this.owlLoadIfSucceed = true;
this.owlLoadInfo = '隐藏';
},
// 在回收站彻底删除
thoroughDeleteInfo(user) {
// 彻底删除一个学生对象
this.recycleInfo = this.recycleInfo.filter(item => item.id != user.id);
},
// 在回收站还原一个学生
reductionInfo(user) {
let repeat = false;
for (let i = 0; i < this.info.length; i++) {
if (this.info[i].code == user.code) {
alert('学号重复!');
repeat = true;
this.info[i].isEdit = repeat;
user.isEdit = repeat;
}
}
// 添加到学生信息表
let obj = {
id: user.id,
code: user.code,
name: user.name,
sex: user.sex,
age: user.age,
satisfy: user.satisfy,
isEdit: repeat
};
this.info.unshift(obj);
this.thoroughDeleteInfo(user);
},
allEmpty() {
this.recycleInfo = [];
}
},
filters: {
timeformater(value, str = 'YYYY年MM月DD日 HH:mm:ss') {
return dayjs(value).format(str);
},
},
watch: {
// 深度监视info
info: {
deep: true,
handler(value) {
localStorage.setItem("persistence_user_info", JSON.stringify(value));
},
},
// 深度监视recycleInfo
recycleInfo: {
deep: true,
handler(value) {
localStorage.setItem("recycle_user_info", JSON.stringify(value));
},
},
},
created() {
// 恢复各个属性的默认值(防止刷新网页后还处于编辑、查询等状态)
this.info.forEach(element => {
element.isEdit = false;
element.satisfy = true;
});
// 对于(在编辑状态并且有学号重复的情况下刷新网页)的措施
// 判断所有学号是否重复,并把重复学号所在的行设为编辑状态
this.allCodeIfRepeat();
// 让猫头鹰随机说话
setInterval(() => {
!this.ifOwlSpeak && this.OwlSpeakShow(this.owlRandomSpeak[Math.floor(Math.random() * this.owlRandomSpeak.length)]);
}, 6000);
}
});
记得引入vue哦