<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.5, user-scalable=yes">
<title>�� 班级积分乐园 · 班主任系统</title>
<style>
* {
box-sizing: border-box;
font-family: 'Segoe UI', 'Comic Sans MS', 'Chalkboard SE', cursive, sans-serif;
margin: 0;
padding: 0;
}
body {
background: linear-gradient(145deg, #fdeff2 0%, #fff5f7 100%);
min-height: 100vh;
padding: 1.5rem 1rem 3rem;
}
.container {
max-width: 1400px;
margin: 0 auto;
background-color: rgba(255, 248, 245, 0.7);
backdrop-filter: blur(2px);
border-radius: 60px 60px 40px 40px;
padding: 1.8rem 1.5rem 2.5rem;
box-shadow: 0 15px 30px rgba(255, 150, 180, 0.2), 0 5px 15px rgba(255, 120, 150, 0.1);
border: 2px solid #ffe2e9;
}
h1 {
font-size: 2.2rem;
font-weight: 600;
color: #a64b6b;
text-shadow: 3px 3px 0 #fcd1dc;
letter-spacing: 2px;
display: flex;
align-items: center;
justify-content: center;
gap: 12px;
flex-wrap: wrap;
margin-bottom: 0.5rem;
}
h1 small {
font-size: 1rem;
background: #ffd9e2;
padding: 0.3rem 1.2rem;
border-radius: 40px;
color: #7a3f54;
font-weight: 400;
box-shadow: inset 0 1px 4px #ffeef2;
}
.toolbar {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: center;
gap: 12px 18px;
margin: 1.2rem 0 2rem;
background: #ffeef2;
padding: 0.8rem 1.8rem;
border-radius: 50px;
border: 1px solid #ffd0db;
}
.toolbar button {
background: white;
border: none;
padding: 0.6rem 1.4rem;
border-radius: 40px;
font-size: 1rem;
font-weight: 600;
color: #5d3f4a;
box-shadow: 0 4px 0 #dbb6c2;
cursor: pointer;
transition: 0.08s linear;
display: inline-flex;
align-items: center;
gap: 6px;
border: 1px solid #ffc1d0;
background: #fffaff;
}
.toolbar button:active {
transform: translateY(4px);
box-shadow: 0 1px 0 #dbb6c2;
}
.toolbar .batch-group {
display: flex;
gap: 6px;
align-items: center;
background: #ffdce5;
padding: 0.2rem 0.8rem 0.2rem 1.2rem;
border-radius: 40px;
}
.toolbar .batch-group span {
font-weight: 500;
color: #6a4352;
}
.badge {
background: #b35d7a;
color: white;
padding: 0.2rem 1rem;
border-radius: 30px;
font-size: 0.9rem;
}
.student-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(210px, 1fr));
gap: 14px;
margin: 1.8rem 0 1.5rem;
}
.student-card {
background: #ffffffdd;
backdrop-filter: blur(4px);
background: #fff9fb;
border-radius: 40px 40px 30px 30px;
padding: 0.9rem 0.6rem 0.7rem;
box-shadow: 0 8px 0 #f0ced9, 0 8px 18px #fccfdb;
border: 1px solid #ffdae4;
transition: 0.1s ease;
display: flex;
flex-direction: column;
align-items: center;
position: relative;
}
.student-card:hover {
transform: scale(1.01);
background: #fff6f9;
}
.student-id {
background: #fccfdb;
color: #572d3e;
font-weight: 700;
font-size: 0.8rem;
padding: 0.15rem 0.9rem;
border-radius: 30px;
display: inline-block;
margin-bottom: 4px;
letter-spacing: 0.5px;
}
.student-name {
font-size: 1.2rem;
font-weight: 600;
color: #38222b;
background: transparent;
border: none;
text-align: center;
width: 100%;
padding: 2px 0;
border-bottom: 2px dashed #ffb7cb;
margin: 0 0 4px 0;
outline: none;
transition: 0.1s;
}
.student-name:focus {
border-bottom: 2px solid #c95d7e;
background: #fff0f4;
border-radius: 20px;
}
.stats {
display: flex;
justify-content: center;
gap: 12px 18px;
flex-wrap: wrap;
width: 100%;
margin: 5px 0 8px;
background: #ffecf0;
padding: 6px 8px;
border-radius: 40px;
}
.stats span {
background: white;
padding: 0.15rem 0.9rem;
border-radius: 30px;
font-size: 0.9rem;
font-weight: 600;
color: #4c2d39;
box-shadow: inset 0 1px 4px #fccfdb;
}
.stats .coin {
background: #fce1b0;
color: #7a5427;
}
.btn-group {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 6px;
margin: 6px 0 4px;
width: 100%;
}
.btn-group button {
border: none;
background: white;
padding: 0.25rem 0.9rem;
border-radius: 40px;
font-weight: 600;
font-size: 0.85rem;
box-shadow: 0 3px 0 #dbb6c2;
cursor: pointer;
transition: 0.05s linear;
border: 1px solid #fec9d6;
flex: 1 0 auto;
min-width: 44px;
background: #fef6f8;
}
.btn-group .add {
background: #d9f0d9;
border-color: #b1d8b1;
color: #1d5a1d;
box-shadow: 0 3px 0 #a3c9a3;
}
.btn-group .sub {
background: #fdd9d9;
border-color: #f5bebe;
color: #8f2f2f;
box-shadow: 0 3px 0 #dba6a6;
}
.btn-group .coin-btn {
background: #f7e4b0;
border-color: #eacb8a;
color: #6f4d1c;
box-shadow: 0 3px 0 #cfb070;
}
.btn-group button:active {
transform: translateY(3px);
box-shadow: 0 1px 0 #dbb6c2;
}
.btn-group .redeem {
background: #f7d0f0;
border-color: #dbaad0;
color: #5d2d5a;
box-shadow: 0 3px 0 #c99abb;
}
.rank-section {
margin-top: 2rem;
background: #fff2f5;
border-radius: 60px;
padding: 0.8rem 1.2rem 1.8rem;
border: 2px solid #ffd0db;
}
.rank-header {
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 10px;
}
.rank-header h2 {
color: #732c47;
font-weight: 600;
display: flex;
align-items: center;
gap: 8px;
}
#rankBtn {
background: #ffb7cb;
border: none;
padding: 0.5rem 2rem;
border-radius: 40px;
font-weight: 700;
color: #3b1f29;
box-shadow: 0 5px 0 #b47189;
cursor: pointer;
transition: 0.05s linear;
font-size: 1rem;
border: 1px solid #ffa5bd;
}
#rankBtn:active {
transform: translateY(5px);
box-shadow: 0 1px 0 #b47189;
}
#rankList {
margin-top: 15px;
max-height: 240px;
overflow-y: auto;
padding: 0 4px;
display: flex;
flex-direction: column;
gap: 5px;
background: #ffeef2;
border-radius: 30px;
padding: 10px 12px;
}
.rank-item {
display: flex;
justify-content: space-between;
background: white;
padding: 0.4rem 1.2rem;
border-radius: 50px;
border: 1px solid #fbcad6;
font-weight: 500;
color: #3b222c;
}
.rank-item span:first-child {
font-weight: 600;
color: #b1456a;
}
.hidden {
display: none;
}
.footer-tip {
margin: 1rem 0 0;
text-align: center;
color: #a86b7c;
font-weight: 400;
}
/* 可爱滚动条 */
#rankList::-webkit-scrollbar {
width: 8px;
}
#rankList::-webkit-scrollbar-track {
background: #fddae4;
border-radius: 20px;
}
#rankList::-webkit-scrollbar-thumb {
background: #e7a6bb;
border-radius: 20px;
}
@media (max-width: 580px) {
.container { padding: 1rem 0.8rem; }
.student-grid { grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); }
}
</style>
</head>
<body>
<div class="container" id="app">
<h1>
�� 班级积分乐园
<small>1班 · 100位小可爱</small>
</h1>
<div class="toolbar">
<div class="batch-group">
<span>�� 批量</span>
<button @click="batchAdd(1)">➕全+1</button>
<button @click="batchAdd(-1)">➖全-1</button>
<button @click="batchAdd(5)">��全+5</button>
<button @click="batchAdd(-5)">��️全-5</button>
</div>
<button @click="resetAll">�� 重置所有</button>
</div>
<!-- 学生卡片网格 -->
<div class="student-grid">
<div class="student-card" v-for="(stu, index) in students" :key="stu.id">
<span class="student-id">#{{ stu.id }}</span>
<input class="student-name" type="text" v-model="stu.name" placeholder="输入名字" maxlength="6">
<div class="stats">
<span>⭐ {{ stu.score }}</span>
<span class="coin">�� {{ stu.coin }}</span>
</div>
<div class="btn-group">
<button class="add" @click="changeScore(stu.id, 1)">+1</button>
<button class="sub" @click="changeScore(stu.id, -1)">-1</button>
<button class="add" @click="changeScore(stu.id, 5)">+5</button>
<button class="sub" @click="changeScore(stu.id, -5)">-5</button>
<button class="coin-btn" @click="changeCoinOnly(stu.id, -1)">��-1</button>
<button class="redeem" @click="redeemReward(stu.id)">��兑换</button>
</div>
</div>
</div>
<!-- 排行榜区域 -->
<div class="rank-section">
<div class="rank-header">
<h2>�� 积分排行榜</h2>
<button id="rankBtn" @click="showRank = !showRank">{{ showRank ? '�� 收起' : '�� 显示排行榜' }}</button>
</div>
<div id="rankList" :class="{ hidden: !showRank }">
<div v-if="showRank && sortedRank.length === 0" style="padding: 12px; text-align: center;">还没有积分呢~ 加油哦 ��</div>
<div class="rank-item" v-for="(item, idx) in sortedRank" :key="item.id">
<span>{{ idx+1 }}. #{{ item.id }} {{ item.name || '小可爱' }}</span>
<span>⭐ {{ item.score }} �� {{ item.coin }}</span>
</div>
</div>
</div>
<div class="footer-tip">✨ 积分自动1:1转换兑换币 · 兑换仅扣币不影响积分</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
(function(){
// 生成100个学生数据 (学号1~100)
function buildStudents() {
const arr = [];
for (let i = 1; i <= 100; i++) {
arr.push({
id: i,
name: '',
score: 0,
coin: 0
});
}
return arr;
}
// 本地存储 key
const STORAGE_KEY = 'classroom_100_integral_system';
// 读取存储,若没有则初始化
function loadData() {
const stored = localStorage.getItem(STORAGE_KEY);
if (stored) {
try {
const parsed = JSON.parse(stored);
// 保证100个,如果长度不对则重建 (防止意外)
if (Array.isArray(parsed) && parsed.length === 100) {
return parsed;
}
} catch (e) {}
}
return buildStudents();
}
// 保存
function saveData(data) {
localStorage.setItem(STORAGE_KEY, JSON.stringify(data));
}
new Vue({
el: '#app',
data: {
students: loadData(),
showRank: false
},
computed: {
// 排序: 按积分降序,积分相同按学号
sortedRank() {
const copy = [...this.students];
copy.sort((a, b) => {
if (b.score !== a.score) return b.score - a.score;
return a.id - b.id;
});
return copy;
}
},
watch: {
students: {
deep: true,
handler(newVal) {
saveData(newVal);
}
}
},
methods: {
// 改变积分 (同时自动改变兑换币 1:1)
changeScore(id, delta) {
const stu = this.students.find(s => s.id === id);
if (!stu) return;
// 积分变化
stu.score = Math.max(0, stu.score + delta);
// 兑换币同步变化 (1:1)
stu.coin = Math.max(0, stu.coin + delta);
// 确保兑换币不会因为积分减少而变成负数,上面已经Math.max处理
// 但如果有额外情况,保证coin >=0
if (stu.coin < 0) stu.coin = 0;
// 自动保存由watch触发
},
// 仅改变兑换币 (独立操作,不影响积分) 用于兑换奖励或者扣除币
changeCoinOnly(id, delta) {
const stu = this.students.find(s => s.id === id);
if (!stu) return;
const newCoin = stu.coin + delta;
if (newCoin < 0) {
alert('�� 兑换币不够啦~ 当前 ��' + stu.coin);
return;
}
stu.coin = newCoin;
},
// 兑换奖励:消耗兑换币,积分不变
redeemReward(id) {
const stu = this.students.find(s => s.id === id);
if (!stu) return;
if (stu.coin <= 0) {
alert('�� 兑换币不足,无法兑换奖励。当前�� ' + stu.coin);
return;
}
// 可以自定义兑换消耗,这里设定每次兑换消耗1个兑换币 (可调整)
// 但根据需求“兑换奖励之后兑换币相应减少”,扣1个币作为示例。
// 也可改为扣除指定数量,但需求没有明确,常见是1币兑换一次奖励。
// 添加一个更灵活的:可输入扣几个? 但设计简洁,用默认1,并提供“扣除兑换币按钮”单独扣除。
// 此处兑换默认扣1币 (也可改变)
const cost = 1;
if (stu.coin < cost) {
alert('�� 兑换币不足 ' + cost + ' 个,当前只有 ' + stu.coin);
return;
}
stu.coin -= cost;
alert('�� 兑换成功! 消耗 ' + cost + ' 兑换币,获得小奖励~ 积分不变 ��');
},
// 批量加减分 (同时影响积分和兑换币)
batchAdd(delta) {
if (delta === 0) return;
if (delta > 0) {
if (!confirm(`确定全班每位同学 +${delta} 积分 & 兑换币吗?`)) return;
} else {
if (!confirm(`确定全班每位同学 ${delta} 积分 & 兑换币吗? (减分将同步减少兑换币)`)) return;
}
for (let stu of this.students) {
// 确保不出现负数
const newScore = Math.max(0, stu.score + delta);
const newCoin = Math.max(0, stu.coin + delta);
stu.score = newScore;
stu.coin = newCoin;
}
},
// 重置所有数据
resetAll() {
if (!confirm('⚠️ 重置所有积分、兑换币、名字? (不可撤销)')) return;
this.students = buildStudents();
saveData(this.students);
}
},
mounted() {
// 当数据初始化后,确保所有学生都有名字占位 (但保留空)
// 如果有需要可以预置一些名字?但让班主任自己输入更可爱。
}
});
})();
</script>
</body>
</html>
console