4.1蓝桥杯训练

第一题:键盘-css

键盘小按钮间隔8px---对应容器加gap

位置

justify-content:space-between两端对齐,项目间等距;around两侧等距;evenly间距完全相等

flex-start左对齐/上对齐,end,center

align-item:baseline(第一行文字基线对齐),stretch项目拉伸占满高度,center

align-content:只有项目换行后才生效

第三题:电子签名

考点:

元素位置的获取,XX.getBoundingClientRect()--包括元素位置和大小

鼠标相对画布位置--鼠标相对浏览器视口的坐标clientX/Y-画布在视口中的位置

设置获取鼠标相对画布的函数,每次触发事件都需要用

绘制:

第四题:外卖餐具-vue,js

考点:

出现const XX=ref..说明XX.value才是真正的数据---setup 内部读写 ref 必须用 .value

双向绑定 v-model='某个变量或有返回值的函数'

一改变就触发@change="运行的函数名"

空数据兜底||0

使用组件《XX》《/XX》

javascript 复制代码
const { createApp, ref, computed } = Vue;

const app = createApp({
    setup() {
        // 菜品数据
        const menuItems = ref([
            {
                id: 1,
                name: "米饭",
                desc: "月售 125  香软可口的白米饭",
                price: 2.0,
                image: "./images/mifan.png",
                category: "主食"
            },
            {
                id: 2,
                name: "蛋炒饭",
                desc: "月售 89  经典蛋炒饭",
                price: 12.0,
                image: "./images/danchaofan.png",
                category: "主食"
            },
            {
                id: 3,
                name: "鱼香肉丝",
                desc: "月售 156  招牌菜品",
                price: 28.0,
                image: "./images/yuxiangrousi.png",
                category: "热菜"
            },
            {
                id: 4,
                name: "宫保鸡丁",
                desc: "月售 134  麻辣鲜香",
                price: 32.0,
                image: "./images/gongbaojiding.png",
                category: "热菜"
            },
            {
                id: 5,
                name: "红烧肉",
                desc: "月售 98  肥而不腻",
                price: 38.0,
                image: "./images/hongshaorou.png",
                category: "热菜"
            },
            {
                id: 6,
                name: "麻婆豆腐",
                desc: "月售 112  经典川菜",
                price: 18.0,
                image: "./images/mapodoufu.png",
                category: "热菜"
            }
        ]);

        // 购物车数据
        const cartItems = ref([]);

        // 当前选中分类
        const activeCategory = ref("热销榜");

        // 购物车相关
        const cartVisible = ref(false);
        const utensilsDialogVisible = ref(false);
        const orderSuccessDialogVisible = ref(false);
        const needUtensils = ref(true);
        const utensilsMode = ref("auto"); // auto, manual, custom
        const utensilsCount = ref(1);
        const customUtensilsCount = ref(4);
        const finalUtensilsCount = ref(0);

        // 切换分类
        const switchCategory = (category) => {
            activeCategory.value = category;
        };

        // 过滤菜品(热销榜显示所有)
        const filteredMenuItems = computed(() => {
            if (activeCategory.value === "热销榜") {
                return menuItems.value;
            }
            return menuItems.value.filter((item) => item.category === activeCategory.value);
        });

        // 计算总数量
        const totalCount = computed(() => {
            return cartItems.value.reduce((sum, item) => sum + item.count, 0);
        });

        // 计算总价格
        const totalPrice = computed(() => {
            return cartItems.value.reduce((sum, item) => sum + item.price * item.count, 0);
        });
        // 智能推荐餐具数量
        const recommendedCount = computed(() => {
            // TODO :目标 1 待补充代码 Start
            let mainTotal=0
            let dishTotal=0
            //等同于i=0遍历的效果
            //记住cartItems是Vue的ref响应式变量
            //cartItems.value[i]才是当前项
            for(let item of cartItems.value){
                if(item.category==='主食'){
                    mainTotal+=item.count
                }
                else{
                    dishTotal+=item.count
                }
            }
            return Math.max(mainTotal,dishTotal)||0
            // TODO :目标 1 待补充代码 End
        });

        // 获取商品在购物车中的数量
        const getItemCount = (item) => {
            const cartItem = cartItems.value.find((cartItem) => cartItem.id === item.id);
            return cartItem ? cartItem.count : 0;
        };

        // 增加商品
        const increaseItem = (item) => {
            const existingItem = cartItems.value.find((cartItem) => cartItem.id === item.id);
            if (existingItem) {
                existingItem.count++;
            } else {
                cartItems.value.push({
                    ...item,
                    count: 1
                });
            }
        };

        // 减少商品
        const decreaseItem = (item) => {
            const existingItem = cartItems.value.find((cartItem) => cartItem.id === item.id);
            if (existingItem) {
                if (existingItem.count > 1) {
                    existingItem.count--;
                } else {
                    // 如果数量为1,从购物车中移除
                    const index = cartItems.value.findIndex((cartItem) => cartItem.id === item.id);
                    cartItems.value.splice(index, 1);
                }
            }
        };

        // 清空购物车
        const clearCart = () => {
            cartItems.value = [];
            cartVisible.value = false;
        };

        // 切换购物车显示
        const toggleCart = () => {
            if (totalCount.value > 0) {
                cartVisible.value = !cartVisible.value;
            }
        };

        // 显示餐具对话框
        const showUtensilsDialog = () => {
            if (totalCount.value > 0) {
                // 显示餐具选择对话框
                utensilsDialogVisible.value = true;
            }
        };

        // 选择是否需要餐具
        const selectNeedUtensils = (need) => {
            needUtensils.value = need;
            if (!need) {
                utensilsMode.value = "auto";
                utensilsCount.value = 0;
            } else {
                utensilsMode.value = "auto";
                console.log(recommendedCount.value, '---');
                utensilsCount.value = recommendedCount.value;
            }
        };

        // 选择餐具模式
        const selectUtensilsMode = (mode) => {
            // TODO :目标 2 待补充代码 Start
            //用户选择的模式是 mode--赋值
            utensilsMode.value=mode
            //如果是auto(智能推荐)--把之前推荐数量赋值
            if(mode==='auto'){
                utensilsCount.value=recommendedCount.value
            }
            else if(mode='custom'){
                utensilsCount.value=customUtensilsCount.value
            }
            
            // TODO :目标 2 待补充代码 End
        };

        // 选择具体餐具数量
        const selectUtensilsCount = (count) => {
            // TODO :目标 2 待补充代码 Start
            utensilsMode.value='manual'
            utensilsCount.value=count
            // TODO :目标 2 待补充代码 End
        };

        // 确认餐具选择
        const confirmUtensils = () => {
            finalUtensilsCount.value = needUtensils.value ? utensilsCount.value : 0;
            utensilsDialogVisible.value = false;

            // 显示下单成功对话框
            setTimeout(() => {
                orderSuccessDialogVisible.value = true;
            }, 300);
        };

        // 关闭订单成功对话框
        const closeOrderSuccess = () => {
            orderSuccessDialogVisible.value = false;
            // 清空购物车
            cartItems.value = [];
            // 重置餐具选择
            needUtensils.value = true;
            utensilsMode.value = "auto";
        };

        return {
            menuItems,
            activeCategory,
            switchCategory,
            filteredMenuItems,
            cartVisible,
            totalCount,
            totalPrice,
            cartItems,
            recommendedCount,
            getItemCount,
            increaseItem,
            decreaseItem,
            clearCart,
            toggleCart,
            showUtensilsDialog,
            utensilsDialogVisible,
            orderSuccessDialogVisible,
            needUtensils,
            selectNeedUtensils,
            utensilsMode,
            selectUtensilsMode,
            utensilsCount,
            selectUtensilsCount,
            customUtensilsCount,
            finalUtensilsCount,
            confirmUtensils,
            closeOrderSuccess
        };
    }
});

app.use(ElementPlus);
const vm = app.mount('#app');

// 暴露Vue实例供检测脚本使用
window.__VUE_APP__ = vm;

易错易漏:

1.cartItems.value才是真正的内容--出现const XX=ref..,就得.value

2.没有考虑空购物车 ||0 不然就infinity 最好使用Math.max(mainTotal, dishTotal) || 0这个写法

3.selectUtensilsMode没有考虑到共有三种情况

4.模板不用 .value,但 setup 里必须加!

5.utensilsCountcustomUtensilsCount 的关系前者是最终真实数量,后者只是输入框临时值,必须同步。

改进方法:

使用**数组的filter来筛选数据,**就不用多次遍历

第五题:学习热度

考点:

XX.splice(index,个数)--删掉数组里从第index+1个开始往后删几个

创造对象数组

sort排序

sort((a, b) => b.value - a.value) sort:排序 b.value - a.value从大到小排

appendChild()只能一个一个加,但是append可以一起加

拼html:

legendItem.innerHTML = `

<span class="legend-color" style="background:${colorList[index]}"></span>

<span class="legend-name">${item.name}</span>

`;

javascript 复制代码
let colorList = ["#ff4d4f","#ffc53d","#69c0ff","#95de64","#d3adf7"]
function createTop3Chart({
  chartId = "chart",
  title = "学习热度-TOP3",
  combinedData = [],
 
}) {
  const chartDom = document.getElementById(chartId);
  const chartInstance = echarts.init(chartDom);

  const option = {
    tooltip: {
      trigger: 'item',
      backgroundColor: "#fff",
      formatter: function(params){
        const itemData = combinedData.find(item => item.name === params.name) || {};
        const top3 = itemData.top3 || [];
        let tooltipContent = `<div class="tool-tip-contaner"><h1>${title}</h1>`;
        tooltipContent += `<table class="tooltip-table"><tr><th>排序</th><th>名称</th><th>值</th></tr>`;
        top3.forEach((item,index)=>{
          tooltipContent += `<tr>
            <td><span class="tooltip-color" style="background:${colorList[index]}"></span>${index+1}</td>
            <td>${item.name}</td>
            <td>${item.value}</td>
          </tr>`;
        });
        tooltipContent += "</table></div>";
        return tooltipContent;
      }
    },
    series:[
      {
        type:'pie', // TODO: 待补充代码  目标 1
        radius:["30%","45%"],
        center:["50%","50%"],
        data: combinedData.sort((a,b)=>b.value-a.value).map((item,index)=>({ ...item, itemStyle:{color:colorList[index ]} })),
        label:{
          show:true,
          fontSize: 14,
          formatter:"{b}: {c} ({d}%)"
        }
      }
    ]
  };
  chartInstance.setOption(option);

 
}

const mainData = [
  { name: "Java语言", value: 80 },
  { name: "Python语言", value: 95 },
  { name: "JavaScript语言", value: 90 },
  { name: "C++语言", value: 70 },
  { name: "Go语言", value: 65 }
];
const top3Data = [
  { parentName: "Java语言", name: "Spring Boot框架", value: 35 },
  { parentName: "Java语言", name: "Maven工具", value: 25 },
  { parentName: "Java语言", name: "Hibernate框架", value: 20 },

  { parentName: "Python语言", name: "Django框架", value: 10 },
  { parentName: "Python语言", name: "Flask框架", value: 25 },
  { parentName: "Python语言", name: "Pandas库", value: 20 },

  { parentName: "JavaScript语言", name: "React框架", value: 40 },
  { parentName: "JavaScript语言", name: "Vue框架", value: 30 },
  { parentName: "JavaScript语言", name: "Node.js环境", value: 15 },

  { parentName: "C++语言", name: "STL库", value: 30 },
  { parentName: "C++语言", name: "Qt框架", value: 20 },
  { parentName: "C++语言", name: "Boost库", value: 15 },

  { parentName: "Go语言", name: "Gin框架", value: 25 },
  { parentName: "Go语言", name: "Beego框架", value: 20 },
  { parentName: "Go语言", name: "Gorm库", value: 15 }
];

var combinedData = mergeData(mainData, top3Data);


/**
  * @param {Array} mainData 主数据数组,包含语言名称和对应值
  * @param {Array} top3Data Top3 数据数组,包含每个语言的前三名及其值
  * @returns {Array} 返回合并后的数据数组,每个元素包含语言名称、值和对应的 Top3 数组
 */
function mergeData(mainData, top3Data) {
  // TODO: 待补充代码  目标 1
  let combinedData=[]
  //combined的内容分为两个对象
  //根据mainData的长度,添加n个对象
  for(let i=0;i<mainData.length;i++){
    const Object={
      name:'',
      value:'',
      top3:[]
    }
    combinedData.push(Object)
  }
  //每个对象的属性name,value,top3数据从mainData里获取
  for(i=0;i<mainData.length;i++){
    combinedData[i].name=mainData[i].name
    combinedData[i].value=mainData[i].value
  }
  //而top3又是一个数组包裹对象,对象里是name,value
  for(i=0;i<mainData.length;i++){
    const top3Content=[]
    const Name=mainData[i].name
    for(j=0;j<top3Data.length;j++){
      if(top3Data[j].parentName===Name){
        const top3Item={name:top3Data[j].name,value:top3Data[j].value}
        top3Content.push(top3Item)
      }
    }
    //不理解怎么把数组放到top3:的后面
    combinedData[i].top3=top3Content
  }
  return combinedData
}



 // 渲染自定义图例
// TODO: 待补充代码  目标 2
function renderCustomLegend(){
  const legendContainer=document.getElementById('customLegend')

  //洗牌,把mainData里面从大到小放,然后再给ItemColor和ItemName
  const Query=[]
  for(let i=0;i<mainData.length;i++){
    let Max=mainData[i].value
    // 应该让n初始化为i 
    let n=i
    for(let j=i+1;j<mainData.length;j++){
      if(mainData[j].value>Max){
        n=j
        Max=mainData[j].value
      }
    }
    const MaxObject={name:mainData[n].name,value:mainData[n].value}
    Query.push(MaxObject)
    //移除了当时最大的
    //错误写法: mainData[n].remove
    //正确写法:mainData[n].splice(index,1)
  }



  //添加div到legendContainer
  for(let i=0;i<Query.length;i++){
    const legendItem=document.createElement('div')
    //className 或者classList.add都可以
    legendItem.className='legend-item'

    const ItemColor=document.createElement('span')
    ItemColor.classList.add('legend-color')
    ItemColor.style.backgroundColor=colorList[i]
    //序号=i+1
    ItemColor.innerHTML=i+1

    const ItemName=document.createElement('span')
    ItemName.classList.add('legend-name')
    //textContent或者innerHtml都可以
    ItemName.textContent=Query[i].name
    //分别加上去!!
    legendItem.appendChild(ItemColor)
    legendItem.appendChild(ItemName)
    
    legendContainer.appendChild(legendItem)
  }
}
//记得调用渲染函数!!!
renderCustomLegend()

// 调用图表函数
createTop3Chart({
  combinedData
});

易错易漏:

获取元素id大小写错误

n应该设置为i

颜色color=XX就行,不需要""

忘记调用函数

动态数字应该是i+1

改进方法:

直接 在mainData里的内容添加top:top3的内容---item0.top3=top3

先筛选filter 出类型一样的,再对数组内容的形式做改变--map

目标二需要根据value排序,复制combinedData,通过sort((a,b)=>{b.value-a.value>0}) 属性来排序,再设置空字符串html,将固定内容添加在html,通过${元素}来填充动态数据,而html又是container的内容

第八题:路由记录

考点:

vue Router配置路由:

一般流程:

1.引入VueRouter所需方法:const {createRouter,createWebHashHistory}=VueRouter

2.定义路由规则 const routes=[ {path:"路径",component:组件,name:"路由名字"}, ]

3.创建路由实例 const router=createRouter({ history:模式;routes:routes})

4.把路由挂载到Vue应用:app.use(router)

历史记录栈:XX.historyStack(这是个数组)

添加数组内容:XX.push()

从头开始删一个:数组.shift()

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>路由记录</title>
    <script src="./lib/vue.global.js"></script>
    <script src="./lib/vue-router.global.js"></script>
   <link rel="stylesheet" href="./css/styles.css">
</head>

<body>
    <div id="app">
        <div class="app-container">
            <!-- 左侧菜单 -->
            <div class="sidebar">
                <div class="logo">
                    <div class="logo-icon">🚀</div>
                    <div class="logo-text">管理系统</div>
                </div>

                <div class="menu-section">
                    <div class="menu-title">主要功能</div>
                    <ul class="menu-items">
                        <li v-for="item in mainMenu" :key="item.path" class="menu-item"
                            :class="{ active: $route.path === item.path }" @click="navigateTo(item.path)">
                            <div class="menu-icon">{{ item.icon }}</div>
                            <div>{{ item.name }}</div>
                        </li>
                    </ul>
                </div>

                <div class="menu-section">
                    <div class="menu-title">系统设置</div>
                    <ul class="menu-items">
                        <li v-for="item in settingsMenu" :key="item.path" class="menu-item"
                            :class="{ active: $route.path === item.path }" @click="navigateTo(item.path)">
                            <div class="menu-icon">{{ item.icon }}</div>
                            <div>{{ item.name }}</div>
                        </li>
                    </ul>
                </div>
            </div>

            <!-- 右侧内容区域 -->
            <div class="main-content">
                <div class="top-bar">
                    <div class="breadcrumb">
                        当前位置: <span class="current">{{ currentPageTitle }}</span>
                    </div>
                    <div class="nav-controls">
                        <button class="nav-btn" @click="goBack" :disabled="!canGoBack">
                            <span>◀</span> 后退
                        </button>
                        <button class="nav-btn" @click="goForward" :disabled="!canGoForward">
                            前进 <span>▶</span>
                        </button>
                        <button class="nav-btn" @click="goHome">
                            <span>🏠</span> 首页
                        </button>
                    </div>
                </div>

                <div class="content-area">
                    <router-view></router-view>
                </div>

                <div class="debug-panel">
                    <div class="debug-title">路由状态监控</div>
                    <div class="debug-info">
                        <div class="debug-item">
                            <span class="status-indicator status-ok"></span>
                            <strong>当前路由:</strong> {{ $route.path }}
                        </div>
                        <div class="debug-item">
                            <span class="status-indicator status-ok"></span>
                            <strong>历史记录:</strong> {{ historyCount }} 条
                        </div>
                    </div>
                    <div class="history-stack">
                        <div v-for="(item, index) in historyStack" :key="index" class="history-item"
                            :class="{ current: index === historyStack.length - 1 }">
                            {{ item }}
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>

      <script src="./components/Home.js"></script>
      <script src="./components/Users.js"></script>
      <script src="./components/Products.js"></script>
      <script src="./components/Orders.js"></script>
      <script src="./components/Settings.js"></script>
      <script src="./components/About.js"></script>
    <script>
        const { createApp, ref, computed, onMounted, watch } = Vue;
        const { createRouter, createWebHashHistory } = VueRouter;

        // TODO 目标1 待补充代码1 Start

        //1.定义路由数组
        const routes=[
            {path:'/',component:Home,name:'home'},
            {path:'/users',component:Users,name:'user'},
            {path:'/products',component:Products,name:'products'},
            {path:'/orders',component:Orders,name:'orders'},
            {path:'/settings',component:Settings,name:'setting'},
            {path:'/about',component:About,name:'about'},
        ]

        // TODO 目标1 待补充代码1  End

        // 创建Vue应用
        const app = createApp({
            data() {
                return {
                    mainMenu: [
                        { name: '仪表盘', path: '/', icon: '📊' },
                        { name: '用户管理', path: '/users', icon: '👥' },
                        { name: '产品管理', path: '/products', icon: '📦' },
                        { name: '订单管理', path: '/orders', icon: '📋' }
                    ],
                    settingsMenu: [
                        { name: '系统设置', path: '/settings', icon: '⚙️' },
                        { name: '关于我们', path: '/about', icon: 'ℹ️' }
                    ],
                    historyStack: [],
                    maxHistorySize: 10,
                    canGoBack: false,
                    canGoForward: false
                };
            },
            computed: {
                currentPageTitle() {
                    const routeName = this.$route.name;
                    const allMenuItems = [...this.mainMenu, ...this.settingsMenu];
                    const currentItem = allMenuItems.find(item => item.path === this.$route.path);
                    return currentItem ? currentItem.name : '未知页面';
                },
                historyCount() {
                    return this.historyStack.length;
                }
            },
            methods: {
                navigateTo(path) {          
                    this.$router.push(path);
                    this.updateHistoryStack();
                   
                },
                   updateHistoryStack() {
                    // TODO 目标2 待补充代码 Start
                    //获取当前的路由路径
                    const currentPath=this.$route.path;
                    //如果用户重复点了同一菜单----历史记录栈有内容,且历史记录最后一条就是当前要跳转的路径
                    if(this.historyStack.length>0&&this.historyStack[this.historyStack.length-1]===currentPath){
                        //那就不处理
                    }else{
                        //否则历史记录栈增加当前路径
                        this.historyStack.push(currentPath)
                        //历史记录不超过最大值
                        if(this.historyStack.length>this.maxHistorySize){
                            //超过了就移除最旧的记录
                            this.historyStack.shift()
                        }
                    }
                    
                    // TODO 目标2 待补充代码 End
                    // 更新前进后退按钮状态
                    this.updateNavButtons();
                },
                
                goBack() {
                    window.history.back();
                },
                goForward() {
                    window.history.forward();
                },
                goHome() {
                    this.navigateTo('/');
                },
                
             
                updateNavButtons() {
                    // 简化实现,实际应用中可能需要更复杂的逻辑
                    this.canGoBack = this.historyStack.length > 1;
                    this.canGoForward = false; // 在实际应用中需要跟踪forward状态
                }
            },
            mounted() {
                this.updateHistoryStack();
            }
        });

        // TODO 目标1 待补充代码2 Start 

        //2.创建路由实例,指定hash模式并传入路由规则
        const router=createRouter({
            //设置路由用 # 模式显示
            //路由模式(路径显示方式):哈希模式
            history:createWebHashHistory(),
            //路由规则表:前面定义的路由数组----写的路由规则,交给路由实例使用
            routes:routes
        })
        //把路由实例放进Vue应用里
        app.use(router)

        // TODO 目标1 待补充代码2 End 


        // 挂载应用
     let vm = app.mount('#app');
    </script>
</body>

</html>

第十题:优居选

考点:

v-model双向绑定

子传父 emit:{}

父传子 props

子使用父的方法,@事件="方法"

router.push('地址')---不刷新页面,只是切换组件

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>优居选</title>
  <script src="./lib/vue.global.js"></script>
  <script src="./lib/vue-router.global.js"></script>
  <link rel="stylesheet" href="./css/styles.css">
</head>
<body>
  <div id="app"></div>

  <script type="module">
    const { createApp, ref, computed, onMounted } = Vue;
    const { createRouter, createWebHashHistory } = VueRouter;

    // 模拟租房数据
    const mockHouses = [
      {
        id: 1,
        title: "朝阳区双井豪华一居室",
        price: 6500,
        area: 65,
        bedrooms: 1,
        livingrooms: 1,
        bathrooms: 1,
        floor: "12/24",
        orientation: "南",
        district: "朝阳区",
        location: "双井",
        tags: ["近地铁", "精装修", "拎包入住"],
        image: "./images/house1.jpg",
        description: "位于朝阳区双井核心地段,交通便利,周边配套设施齐全。房屋精装修,南北通透,采光良好,家电齐全,拎包即可入住。"
      },
      {
        id: 2,
        title: "海淀区中关村温馨两居",
        price: 8500,
        area: 85,
        bedrooms: 2,
        livingrooms: 1,
        bathrooms: 1,
        floor: "8/18",
        orientation: "南北",
        district: "海淀区",
        location: "中关村",
        tags: ["学区房", "近地铁", "精装修"],
        image: "./images/house2.jpg",
        description: "位于海淀区中关村,优质学区房,周边有多所重点中小学。房屋温馨舒适,南北通透,采光充足,适合家庭居住。"
      },
      {
        id: 3,
        title: "东城区王府井精装开间",
        price: 5500,
        area: 45,
        bedrooms: 1,
        livingrooms: 1,
        bathrooms: 1,
        floor: "5/12",
        orientation: "东",
        district: "东城区",
        location: "王府井",
        tags: ["近商圈", "精装修", "独立卫浴"],
        image: "./images/house3.jpg",
        description: "位于东城区王府井商圈,生活便利,购物餐饮一应俱全。房屋精装修,独立卫浴,适合单身或情侣居住。"
      },
      {
        id: 4,
        title: "西城区金融街豪华三居",
        price: 12000,
        area: 120,
        bedrooms: 3,
        livingrooms: 2,
        bathrooms: 2,
        floor: "15/28",
        orientation: "南北",
        district: "西城区",
        location: "金融街",
        tags: ["豪华装修", "视野开阔", "近商圈"],
        image: "./images/house4.jpg",
        description: "位于西城区金融街核心区域,豪华装修,视野开阔,南北通透。周边配套设施完善,交通便利,适合高端人士居住。"
      },
      {
        id: 5,
        title: "丰台区方庄舒适两居",
        price: 5800,
        area: 75,
        bedrooms: 2,
        livingrooms: 1,
        bathrooms: 1,
        floor: "6/15",
        orientation: "南",
        district: "丰台区",
        location: "方庄",
        tags: ["性价比高", "精装修", "近地铁"],
        image: "./images/house5.jpg",
        description: "位于丰台区方庄,性价比高,精装修,近地铁。房屋采光良好,布局合理,适合年轻家庭居住。"
      },
      {
        id: 6,
        title: "通州区运河商务区一居室",
        price: 4200,
        area: 55,
        bedrooms: 1,
        livingrooms: 1,
        bathrooms: 1,
        floor: "10/20",
        orientation: "南",
        district: "通州区",
        location: "运河商务区",
        tags: ["新小区", "精装修", "近地铁"],
        image: "./images/house6.jpg",
        description: "位于通州区运河商务区,新小区,精装修,近地铁。房屋干净整洁,配套设施完善,适合上班族居住。"
      }
    ];

    // 合并后的筛选和列表组件
    const HouseListWithFilter = {
      template: `
        <div>
          <!-- 筛选部分 -->
          <div class="filter-section">
            <div class="filter-row">
              <div class="filter-group">
                <label>区域</label>
                <!-- TODO: 待补充代码1 目标1   -->
                <select id="district" v-model="filters.district">
                  <option value="">全部</option>
                  <option v-for="district in districts" :key="district" :value="district">{{ district }}</option>
                </select>
              </div>
              <div class="filter-group">
                <label>价格范围</label>
                <!-- TODO: 待补充代码2 目标1   -->
                <select id="priceRange"  v-model="filters.priceRange">
                  <option value="">全部</option>
                  <option value="0-4000">4000元以下</option>
                  <option value="4000-6000">4000-6000元</option>
                  <option value="6000-8000">6000-8000元</option>
                  <option value="8000-10000">8000-10000元</option>
                  <option value="10000-0">10000元以上</option>
                </select>
              </div>
              <div class="filter-group">
                <label>户型</label>
                <!-- TODO: 待补充代码3 目标1   -->
                <select id="bedrooms"  v-mobel="filters.bedrooms">
                  <option value="">全部</option>
                  <option value="1">一室</option>
                  <option value="2">两室</option>
                  <option value="3">三室</option>
                  <option value="4">四室及以上</option>
                </select>

              </div>
              <div class="filter-group">
                <label>面积</label>
                <!-- TODO: 待补充代码4 目标1   -->
                <select id="areaRange"  v-mobel="filters.areaRange">
                  <option value="">全部</option>
                  <option value="0-50">50㎡以下</option>
                  <option value="50-70">50-70㎡</option>
                  <option value="70-90">70-90㎡</option>
                  <option value="90-120">90-120㎡</option>
                  <option value="120-0">120㎡以上</option>
                </select>
              </div>
            </div>
            <div class="filter-row">
              <div class="filter-group">
                <label>关键词</label>
                <!-- TODO: 待补充代码5 目标1   -->
                <input id="keyword" type="text" placeholder="输入小区或位置关键词"  v-mobel="filters.keywordc">
              </div>
            </div>
          </div>

          <!-- 排序和列表部分 -->
          <div class="sort-section">
            <div class="sort-options">
              <button 
                v-for="option in sortOptions" 
                :key="option.value"
                :class="{ active: sortBy === option.value }"
                @click="updateSort(option.value)"
              >
                {{ option.label }}
              </button>
            </div>
            <div>共找到 {{ filteredHouses.length }} 套房源</div>
          </div>
          
          <div v-if="filteredHouses.length > 0" class="house-list">
            <div 
              v-for="house in filteredHouses" 
              :key="house.id" 
              class="house-card"
              @click="viewDetail(house.id)"
            >
              <img :src="house.image" :alt="house.title" class="house-img">
              <div class="house-info">
                <h3 class="house-title">{{ house.title }}</h3>
                <div class="house-details">
                  <span>{{ house.area }}㎡</span>
                  <span>{{ house.bedrooms }}室{{ house.livingrooms }}厅</span>
                  <span>{{ house.floor }}层</span>
                  <span>{{ house.orientation }}向</span>
                </div>
                <div class="house-price">{{ house.price }}元/月</div>
                <div class="house-tags">
                  <span v-for="tag in house.tags" :key="tag" class="tag">{{ tag }}</span>
                </div>
              </div>
            </div>
          </div>
          <div v-else class="no-results">
            没有找到符合条件的房源,请尝试调整筛选条件
          </div>
        </div>
      `,
      // TODO 待补充代码1  目标2 Start
      //声明子组件会向父组件触发(emit)自定义事件
      emits:['view-detail'],
      // TODO 待补充代码1  目标2 End
      setup(props, { emit }) {
        const districts = [...new Set(mockHouses.map(house => house.district))];
        
        const sortOptions = [
          { label: '默认排序', value: 'default' },
          { label: '价格从低到高', value: 'price-asc' },
          { label: '价格从高到低', value: 'price-desc' },
          { label: '面积从大到小', value: 'area-desc' }
        ];
        
        const filters = ref({
          district: '',
          priceRange: '',
          bedrooms: '',
          areaRange: '',
          keyword: ''
        });
        
        const sortBy = ref('default');
        
        const filteredHouses = computed(() => {
          let result = [...mockHouses];
          
          // 区域筛选
          if (filters.value.district) {
            result = result.filter(house => house.district === filters.value.district);
          }
          
          // 价格范围筛选
          if (filters.value.priceRange) {
            const [min, max] = filters.value.priceRange.split('-').map(Number);
            result = result.filter(house => {
              if (min === 0) return house.price <= max;
              if (max === 0) return house.price >= min;
              return house.price >= min && house.price <= max;
            });
          }
          
          // 户型筛选
          if (filters.value.bedrooms) {
            const bedrooms = parseInt(filters.value.bedrooms);
            result = result.filter(house => house.bedrooms === bedrooms);
          }
          
          // 面积范围筛选
          if (filters.value.areaRange) {
            const [min, max] = filters.value.areaRange.split('-').map(Number);
            result = result.filter(house => {
              if (min === 0) return house.area <= max;
              if (max === 0) return house.area >= min;
              return house.area >= min && house.area <= max;
            });
          }
          
          // 关键词筛选
          if (filters.value.keyword) {
            const keyword = filters.value.keyword.toLowerCase();
            result = result.filter(house => 
              house.title.toLowerCase().includes(keyword) || 
              house.location.toLowerCase().includes(keyword)
            );
          }
          
          // 排序
          if (sortBy.value === 'price-asc') {
            result.sort((a, b) => a.price - b.price);
          } else if (sortBy.value === 'price-desc') {
            result.sort((a, b) => b.price - a.price);
          } else if (sortBy.value === 'area-desc') {
            result.sort((a, b) => b.area - a.area);
          }
          
          return result;
        });
        
        const viewDetail = (id) => {
          // TODO 待补充代码2 目标2
          // viewDetail 函数中向父组件触发自定义事件
          //view-detail动作就传递id
          emits:['view-detail',id]
        };
        
        const updateSort = (sortValue) => {
          sortBy.value = sortValue;
        };
        
        return {
          districts,
          sortOptions,
          filters,
          sortBy,
          filteredHouses,
          viewDetail,
          updateSort
        };
      }
    };

    // 房屋详情组件
    const HouseDetail = {
      template: `
        <div class="detail-page">
          <div class="detail-header">
            <img :src="house.image" :alt="house.title" class="detail-img">
          </div>
          <div class="detail-content">
            <h1 class="detail-title">{{ house.title }}</h1>
            <div class="detail-price">{{ house.price }}元/月</div>
            <div class="detail-info">
              <div class="info-item">
                <span class="info-label">面积</span>
                <span class="info-value">{{ house.area }}㎡</span>
              </div>
              <div class="info-item">
                <span class="info-label">户型</span>
                <span class="info-value">{{ house.bedrooms }}室{{ house.livingrooms }}厅{{ house.bathrooms }}卫</span>
              </div>
              <div class="info-item">
                <span class="info-label">楼层</span>
                <span class="info-value">{{ house.floor }}</span>
              </div>
              <div class="info-item">
                <span class="info-label">朝向</span>
                <span class="info-value">{{ house.orientation }}</span>
              </div>
              <div class="info-item">
                <span class="info-label">区域</span>
                <span class="info-value">{{ house.district }}</span>
              </div>
              <div class="info-item">
                <span class="info-label">位置</span>
                <span class="info-value">{{ house.location }}</span>
              </div>
            </div>
            <div class="house-tags">
              <span v-for="tag in house.tags" :key="tag" class="tag">{{ tag }}</span>
            </div>
            <div class="detail-description">
              <h3>房源描述</h3>
              <p>{{ house.description }}</p>
            </div>
            <router-link to="/" class="back-button">返回列表</router-link>
          </div>
        </div>
      `,
      // TODO 待补充代码2 目标3 Start
        props:{id: Number},
       // TODO 待补充代码2 目标3 End
      setup(props) {
        const house = ref({});
        
        onMounted(() => {
          // TODO: 待补充代码3 目标3
          house.value=mockHouses.find(h=>h.id===props.id||{})
        });
        
        return {
          house
        };
      }
    };

    // 首页组件
    const HomePage = {
      template: `
        <div>
          <!-- TODO: 待补充代码3 目标2 -->
           <HouseListWithFilter @view-detail="goToDetail"/>
        </div>
      `,
      components: {
        HouseListWithFilter
      },
      setup() {
        const router = VueRouter.useRouter();
        
        const goToDetail = (id) => {
          // TODO 待补充代码4 目标2
          //补充函数
          router.push(`/house/${id}`)
        };
        
        return {
          goToDetail
        };
      }
    };

    // 路由配置
    const routes = [
      { path: '/', component: HomePage },
      // TODO 待补充代码1  目标3
      { 
        path:'/house/:id',
        component:HouseDetail,
        props:route=>({id:Number(route.params.id)})
      }
    ];

    // 创建路由
    const router = createRouter({
      history: createWebHashHistory(),
      routes
    });

    // 创建Vue应用
    const app = createApp({
      template: `
        <div>
          <header>
            <div class="container">
              <div class="header-content">
                <div class="logo">优居选</div>
                <nav>
                  <ul>
                    <li id="toIndex"><router-link to="/">首页</router-link></li>
                    <li><a href="#">二手房</a></li>
                    <li><a href="#">新房</a></li>
                    <li><a href="#">小区</a></li>
                  </ul>
                </nav>
              </div>
            </div>
          </header>
          <main class="container">
            <router-view></router-view>
          </main>
        </div>
      `
    });

    // 使用路由
    app.use(router);
    
    // 挂载应用
  let vm =   app.mount('#app');
  </script>
</body>
</html>

https://blog.csdn.net/double_eggm/article/details/159995650?sharetype=blogdetail&sharerId=159995650&sharerefer=PC&sharesource=double_eggm&sharefrom=mp_from_link

相关推荐
Little At Air4 小时前
LeetCode 30. 串联所有单词的子串 | 困难 C++实现
算法·leetcode·职场和发展
a里啊里啊5 小时前
常见面试题目集合
linux·数据库·c++·面试·职场和发展·操作系统
生信研究猿5 小时前
leetcode 121.买卖股票的最佳时机
算法·leetcode·职场和发展
And_Ii6 小时前
[蓝桥杯 2023 省 A] 更小的数
蓝桥杯
aqiu1111116 小时前
【算法日记 09】蓝桥杯实战:突破整数极限,拥抱“字符串思维”
算法·职场和发展·蓝桥杯
And_Ii6 小时前
[蓝桥杯 2023 省 A] 平方差
蓝桥杯
一轮弯弯的明月1 天前
贝尔数求集合划分方案总数
java·笔记·蓝桥杯·学习心得
Ricky111zzz1 天前
leetcode学python记录1
python·算法·leetcode·职场和发展
liu****1 天前
第16届省赛蓝桥杯大赛C/C++大学B组(京津冀)
开发语言·数据结构·c++·算法·蓝桥杯