|
|
@@ -0,0 +1,289 @@
|
|
|
+<template>
|
|
|
+ <div class="pet-box">
|
|
|
+ <h2>萌宠乐园 🐱🐶</h2>
|
|
|
+
|
|
|
+ <!-- 筛选区域 -->
|
|
|
+ <div class="search-bar">
|
|
|
+ <input
|
|
|
+ v-model="keyword"
|
|
|
+ placeholder="搜索宠物名称"
|
|
|
+ class="search-input"
|
|
|
+ />
|
|
|
+ <span>颜值分值:</span>
|
|
|
+ <input
|
|
|
+ v-model.number="minLevel"
|
|
|
+ type="number"
|
|
|
+ placeholder="最低分"
|
|
|
+ class="num-input"
|
|
|
+ />
|
|
|
+ -
|
|
|
+ <input
|
|
|
+ v-model.number="maxLevel"
|
|
|
+ type="number"
|
|
|
+ placeholder="最高分"
|
|
|
+ class="num-input"
|
|
|
+ />
|
|
|
+ <button @click="resetFilter" class="btn reset-btn">重置筛选</button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 功能按钮 -->
|
|
|
+ <div class="btn-group">
|
|
|
+ <label class="check-label">
|
|
|
+ <input type="checkbox" v-model="isAllCheck" /> 全选萌宠
|
|
|
+ </label>
|
|
|
+ <button @click="reverseCheck" class="btn">反选</button>
|
|
|
+ <button @click="clearCheck" class="btn">清空选中</button>
|
|
|
+ <button @click="sortAsc" class="btn">分值升序</button>
|
|
|
+ <button @click="sortDesc" class="btn">分值降序</button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 萌宠列表 + 动画卡片 -->
|
|
|
+ <div class="pet-list">
|
|
|
+ <!-- 过渡动画:列表切换自动生效 -->
|
|
|
+ <transition-group name="pet" tag="div" class="list-wrap">
|
|
|
+ <div
|
|
|
+ class="pet-card"
|
|
|
+ :class="{ active: pet.checked }"
|
|
|
+ v-for="pet in filterList"
|
|
|
+ :key="pet.id"
|
|
|
+ >
|
|
|
+ <input type="checkbox" v-model="pet.checked" class="card-check" />
|
|
|
+ <div class="pet-avatar">{{ pet.emoji }}</div>
|
|
|
+ <div class="pet-name">{{ pet.name }}</div>
|
|
|
+ <div class="pet-type">品类:{{ pet.type }}</div>
|
|
|
+ <div class="pet-level">颜值:{{ pet.level }} 分</div>
|
|
|
+ </div>
|
|
|
+ </transition-group>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 数据统计 -->
|
|
|
+ <div class="stat">
|
|
|
+ <p>当前萌宠总数:{{ totalNum }}</p>
|
|
|
+ <p>已挑选萌宠:{{ checkedNum }} 只</p>
|
|
|
+ <p>选中萌宠平均颜值:{{ avgLevel.toFixed(1) }} 分</p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script lang="ts" setup>
|
|
|
+import { ref, computed } from 'vue'
|
|
|
+
|
|
|
+// 响应式数据
|
|
|
+const keyword = ref('')
|
|
|
+const minLevel = ref(0)
|
|
|
+const maxLevel = ref(100)
|
|
|
+
|
|
|
+// 萌宠数据源,搭配表情更有趣
|
|
|
+const petList = ref([
|
|
|
+ { id: 1, name: '橘猫', type: '猫咪', level: 92, emoji: '🐱', checked: false },
|
|
|
+ { id: 2, name: '柴犬', type: '狗狗', level: 88, emoji: '🐶', checked: false },
|
|
|
+ { id: 3, name: '小白兔', type: '萌宠', level: 95, emoji: '🐰', checked: false },
|
|
|
+ { id: 4, name: '小仓鼠', type: '萌宠', level: 80, emoji: '🐹', checked: false },
|
|
|
+ { id: 5, name: '布偶猫', type: '猫咪', level: 98, emoji: '😺', checked: false },
|
|
|
+ { id: 6, name: '柯基', type: '狗狗', level: 85, emoji: '🐕', checked: false },
|
|
|
+])
|
|
|
+
|
|
|
+// 1. 综合筛选:名称 + 颜值区间
|
|
|
+const filterList = computed(() => {
|
|
|
+ return petList.value.filter(pet => {
|
|
|
+ const nameMatch = pet.name.includes(keyword.value)
|
|
|
+ const levelMatch = pet.level >= minLevel.value && pet.level <= maxLevel.value
|
|
|
+ return nameMatch && levelMatch
|
|
|
+ })
|
|
|
+})
|
|
|
+
|
|
|
+// 2. 筛选后总数
|
|
|
+const totalNum = computed(() => filterList.value.length)
|
|
|
+
|
|
|
+// 3. 已选中数量
|
|
|
+const checkedNum = computed(() => filterList.value.filter(pet => pet.checked).length)
|
|
|
+
|
|
|
+// 4. 选中平均颜值
|
|
|
+const avgLevel = computed(() => {
|
|
|
+ const checkedArr = filterList.value.filter(pet => pet.checked)
|
|
|
+ if (checkedArr.length === 0) return 0
|
|
|
+ const total = checkedArr.reduce((sum, pet) => sum + pet.level, 0)
|
|
|
+ return total / checkedArr.length
|
|
|
+})
|
|
|
+
|
|
|
+// 5. 可写计算属性 - 全选
|
|
|
+const isAllCheck = computed({
|
|
|
+ get() {
|
|
|
+ return filterList.value.length > 0 && filterList.value.every(pet => pet.checked)
|
|
|
+ },
|
|
|
+ set(val: boolean) {
|
|
|
+ filterList.value.forEach(pet => pet.checked = val)
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+// 重置筛选
|
|
|
+const resetFilter = () => {
|
|
|
+ keyword.value = ''
|
|
|
+ minLevel.value = 0
|
|
|
+ maxLevel.value = 100
|
|
|
+}
|
|
|
+
|
|
|
+// 反选
|
|
|
+const reverseCheck = () => {
|
|
|
+ filterList.value.forEach(pet => pet.checked = !pet.checked)
|
|
|
+}
|
|
|
+
|
|
|
+// 清空选中
|
|
|
+const clearCheck = () => {
|
|
|
+ filterList.value.forEach(pet => pet.checked = false)
|
|
|
+}
|
|
|
+
|
|
|
+// 分值升序
|
|
|
+const sortAsc = () => {
|
|
|
+ petList.value.sort((a, b) => a.level - b.level)
|
|
|
+}
|
|
|
+
|
|
|
+// 分值降序
|
|
|
+const sortDesc = () => {
|
|
|
+ petList.value.sort((a, b) => b.level - a.level)
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.pet-box {
|
|
|
+ width: 90%;
|
|
|
+ margin: 20px auto;
|
|
|
+ font-family: "Microsoft Yahei", sans-serif;
|
|
|
+}
|
|
|
+h2 {
|
|
|
+ text-align: center;
|
|
|
+ color: #ff7875;
|
|
|
+}
|
|
|
+
|
|
|
+/* 筛选栏样式 */
|
|
|
+.search-bar {
|
|
|
+ text-align: center;
|
|
|
+ margin: 15px 0;
|
|
|
+}
|
|
|
+.search-input, .num-input {
|
|
|
+ padding: 6px 8px;
|
|
|
+ margin: 0 6px;
|
|
|
+ border: 1px solid #ccc;
|
|
|
+ border-radius: 6px;
|
|
|
+ outline: none;
|
|
|
+ transition: all 0.3s;
|
|
|
+}
|
|
|
+.search-input:focus, .num-input:focus {
|
|
|
+ border-color: #ff9494;
|
|
|
+ box-shadow: 0 0 4px #ffc8c8;
|
|
|
+}
|
|
|
+
|
|
|
+/* 按钮通用样式 + 动效 */
|
|
|
+.btn-group {
|
|
|
+ text-align: center;
|
|
|
+ margin: 10px 0;
|
|
|
+}
|
|
|
+.check-label {
|
|
|
+ margin-right: 10px;
|
|
|
+ cursor: pointer;
|
|
|
+}
|
|
|
+.btn {
|
|
|
+ padding: 6px 12px;
|
|
|
+ margin: 0 4px;
|
|
|
+ border: none;
|
|
|
+ border-radius: 6px;
|
|
|
+ background: #ff9494;
|
|
|
+ color: #fff;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.2s ease;
|
|
|
+}
|
|
|
+.btn:hover {
|
|
|
+ background: #ff7875;
|
|
|
+ transform: translateY(-2px);
|
|
|
+ box-shadow: 0 3px 6px rgba(255, 120, 117, 0.4);
|
|
|
+}
|
|
|
+.btn:active {
|
|
|
+ transform: translateY(0);
|
|
|
+}
|
|
|
+.reset-btn {
|
|
|
+ background: #74b9ff;
|
|
|
+}
|
|
|
+.reset-btn:hover {
|
|
|
+ background: #0984e3;
|
|
|
+}
|
|
|
+
|
|
|
+/* 萌宠卡片布局 */
|
|
|
+.pet-list {
|
|
|
+ margin-top: 20px;
|
|
|
+}
|
|
|
+.list-wrap {
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ gap: 20px;
|
|
|
+ justify-content: center;
|
|
|
+}
|
|
|
+
|
|
|
+/* 卡片基础样式 + hover 动画 */
|
|
|
+.pet-card {
|
|
|
+ width: 160px;
|
|
|
+ padding: 15px;
|
|
|
+ border-radius: 12px;
|
|
|
+ background: #fef5f5;
|
|
|
+ text-align: center;
|
|
|
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
|
|
+ transition: all 0.3s ease;
|
|
|
+ position: relative;
|
|
|
+}
|
|
|
+.pet-card:hover {
|
|
|
+ transform: translateY(-6px);
|
|
|
+ box-shadow: 0 8px 16px rgba(255, 120, 117, 0.25);
|
|
|
+}
|
|
|
+
|
|
|
+/* 选中状态动画:放大+变色 */
|
|
|
+.pet-card.active {
|
|
|
+ border: 2px solid #ff7875;
|
|
|
+ transform: scale(1.05);
|
|
|
+ background: #ffe8e8;
|
|
|
+}
|
|
|
+
|
|
|
+/* 宠物表情 */
|
|
|
+.pet-avatar {
|
|
|
+ font-size: 48px;
|
|
|
+ margin-bottom: 8px;
|
|
|
+}
|
|
|
+.pet-name {
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: bold;
|
|
|
+ color: #333;
|
|
|
+}
|
|
|
+.pet-type, .pet-level {
|
|
|
+ font-size: 14px;
|
|
|
+ color: #666;
|
|
|
+ margin: 4px 0;
|
|
|
+}
|
|
|
+.card-check {
|
|
|
+ position: absolute;
|
|
|
+ top: 8px;
|
|
|
+ left: 8px;
|
|
|
+ cursor: pointer;
|
|
|
+}
|
|
|
+
|
|
|
+/* Vue 列表过渡动画 入场/离场 */
|
|
|
+.pet-enter-active {
|
|
|
+ transition: all 0.4s ease;
|
|
|
+}
|
|
|
+.pet-leave-active {
|
|
|
+ transition: all 0.3s ease;
|
|
|
+}
|
|
|
+.pet-enter-from {
|
|
|
+ opacity: 0;
|
|
|
+ transform: scale(0.7) translateY(20px);
|
|
|
+}
|
|
|
+.pet-leave-to {
|
|
|
+ opacity: 0;
|
|
|
+ transform: scale(0.7) translateY(-20px);
|
|
|
+}
|
|
|
+
|
|
|
+/* 统计区域 */
|
|
|
+.stat {
|
|
|
+ margin-top: 30px;
|
|
|
+ text-align: center;
|
|
|
+ font-size: 15px;
|
|
|
+ color: #555;
|
|
|
+}
|
|
|
+</style>
|