SpringBoot框架之KOB项目 -创建菜单与游戏界面(上)

实现项目前端
前端模仿几次就会了

项目前端总体设计

导航栏的实现

组件

创建组件,在components文件夹下面创建文件-表示组件。
例如在components文件夹下创建NavBar.vue(vue规定创建的项目名称要有两个大写字母组成)
对于每一个组件,包含三个部分:html, css, js
scoped的作用是加入一个随机字符串,使得该组件不会影响到组件以外的内容

<template>

</template>

<script>


</script>

<style scoped>


</style>

BootStrap

可以让程序员很轻松的做美工的工作

进入BootStrap官网,可以对样式进行搜索,这里以实现导航栏为例,搜索NavBar,然后选择一个合适的样式,复制到NavBar.vue

<template>
  <nav class="navbar navbar-expand-lg bg-body-tertiary">
    <div class="container-fluid">
      <a class="navbar-brand" href="#">Navbar w/ text</a>
      <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarText" aria-controls="navbarText" aria-expanded="false" aria-label="Toggle navigation">
        <span class="navbar-toggler-icon"></span>

      </button>

      <div class="collapse navbar-collapse" id="navbarText">
        <ul class="navbar-nav me-auto mb-2 mb-lg-0">
          <li class="nav-item">
            <a class="nav-link active" aria-current="page" href="#">Home</a>

          </li>

          <li class="nav-item">
            <a class="nav-link" href="#">Features</a>

          </li>

          <li class="nav-item">
            <a class="nav-link" href="#">Pricing</a>

          </li>

        </ul>

        <span class="navbar-text">
          Navbar text with an inline element
        </span>

      </div>

    </div>

  </nav>

</template>

<script>


</script>

<style scoped>
  //scoped的作用是加入一个随机字符串,使得该组件不会影响到组件以外的内容

</style>

App.vue中导入组件

安装@popperjs/core依赖,导入BootStrap的依赖,导入NavBar.vue,并且使用components关键字,并且在<templements>中写入<NavBar/>
image.png

<template>
  <NavBar/>
  <router-view></router-view>
</template>
<script>
import NavBar from './components/NavBar.vue'
import "bootstrap/dist/css/bootstrap.min.css"
import "bootstrap/dist/js/bootstrap"
export default{
  components:{
    NavBar
  }
}
</script>
<style>
  body{
    background-image: url("@/assets/background.png");
    background-size: cover;
  }
</style>

实现效果:
image.png
对样式进行微调,下拉菜单依然到bootstrap找样式(有没有想过为啥网站的导航栏都一样啊,都是自己写的吗,都不是~,都是bootstrap)

<template>
  <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
    <div class="container">
      <a class="navbar-brand" href="#">King of Bots</a>
      <div class="collapse navbar-collapse" id="navbarText">
        <ul class="navbar-nav me-auto mb-2 mb-lg-0">
          <li class="nav-item">
            <a class="nav-link " aria-current="page" href="#">对战</a>
          </li>
          <li class="nav-item">
            <a class="nav-link" href="#">对局列表</a>
          </li>
          <li class="nav-item">
            <a class="nav-link" href="#">排行榜</a>
          </li>
        </ul>
        <ul class="navbar-nav">
          <li class="nav-item dropdown">
            <a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
              hxw
            </a>
            <ul class="dropdown-menu">
              <li><a class="dropdown-item" href="#">我的Bot</a></li>
              <li><a class="dropdown-item" href="#">退出</a></li>
            </ul>
          </li>
        </ul>
      </div>
    </div>
  </nav>
</template>

<script>


</script>

<style scoped>


</style>

image.png

实现页面跳转

image.png
页面的组件可以放到components文件夹下面,也可以放在views文件夹下面,一般习惯于放在views文件夹下面。由于每一个页面又包含多个组件,所以每一个页面可以用一个文件夹表示。分别在文件夹下创建组件。取名方式如下图所示。
image.png
可以先在每个页面中写入文字区分,搭建完框架之后,再进一步完善。这样每一个页面的内容就完成了,下面考虑如何点击导航栏按钮,跳转到具体的页面。
router文件夹下的index.js文件中添加所有页面

  • 导入所有页面组件文件
  • 地址和组件的绑定:定义路径对象,实现页面组件和地址的绑定,可以实现在指定路径下,显示对应页面组件的内容
  • 导航栏的按钮和地址的绑定(不刷新):通过修改NavBar.vue中用router-link标签替换<a>标签
<template>
  <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
    <div class="container">
      <router-link class="navbar-brand" :to="{name: 'home'}">King of Bots</router-link>
      <div class="collapse navbar-collapse" id="navbarText">
        <ul class="navbar-nav me-auto mb-2 mb-lg-0">
          <li class="nav-item">
            <router-link class="nav-link " :to="{name: 'pk_index'}" >对战</router-link>
          </li>
          <li class="nav-item">
            <router-link class="nav-link" :to="{name: 'record_index'}">对局列表</router-link>
          </li>
          <li class="nav-item">
            <router-link class="nav-link" :to="{name:'ranklist_index'}">排行榜</router-link>
          </li>
        </ul>
        <ul class="navbar-nav">
          <li class="nav-item dropdown">
            <a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
              hxw
            </a>
            <ul class="dropdown-menu">
              <li><router-link class="dropdown-item" :to="{name:'user_bot_index'}">我的Bot</router-link></li>
              <li><router-link class="dropdown-item" href="#">退出</router-link></li>
            </ul>
          </li>
        </ul>
      </div>
    </div>
  </nav>
</template>

<script>


</script>

<style scoped>


</style>
import { createRouter, createWebHistory } from 'vue-router'
import ErrorView from  '../views/error/NotFound.vue'
import PkIndexView from '../views/pk/PkIndexView.vue'
import RankListView from '../views/ranklist/RankListIndexView.vue'
import RecordView from '../views/record/RecordIndexView.vue'
import UserBotIndexView from  '../views/user/bot/UserBotIndexView.vue'
const routes = [
  {
    path:"/",
    name:"home",
    redirect:"/pk/"
    // 重定向,当输入根目录的时候,自动跳转到pk页面
  },
  {
    path: "/404/",
    name: "not_found_index",
    component: ErrorView,
  },
  {
    path: "/pk/",
    name: "pk_index",
    component: PkIndexView,
  },
  {
    path: "/ranklist/",
    name: "ranklist_index",
    component: RankListView,
  },
  {
    path: "/record/",
    name: "record_index",
    component: RecordView,
  },
  {
    path: "/user/bot/",
    name: "user_bot_index",
    component: UserBotIndexView,
  },
  {
    //输入其他乱七八糟的路径,自动跳转到404
    path: "/:catchAll()",
    redirect: "/404/",
  }
]

const router = createRouter({
  history: createWebHistory(),
  routes
})

export default router

完善页面

  • 给页面添加一个框框框起来
  • 每一个页面都需要用一个框框,所以将框框独立出来作为一个单独的组件,在componets文件夹下创建ContentField.vue
  • 在每一个页面中引入ContentField.vue组件,这里给出PkIndexView.vue其余页面组件引入方式类似。
  • 导航栏按钮点击指定按钮,实现点击高亮:判断当前在哪一个页面,对该页面添加active
<template>
  <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
    <div class="container">
      <router-link class="navbar-brand" :to="{name: 'home'}">King of Bots</router-link>
      <div class="collapse navbar-collapse" id="navbarText">
        <ul class="navbar-nav me-auto mb-2 mb-lg-0">
          <li class="nav-item">
            <router-link :class="rout_name === 'pk_index' ? 'nav-link active' : 'nav-link'" :to="{name: 'pk_index'}" >对战</router-link>
          </li>
          <li class="nav-item">
            <router-link :class="rout_name === 'record_index' ? 'nav-link active' : 'nav-link'" :to="{name: 'record_index'}">对局列表</router-link>
          </li>
          <li class="nav-item">
            <router-link :class="rout_name === 'ranklist_index' ? 'nav-link active' : 'nav-link'" :to="{name:'ranklist_index'}">排行榜</router-link>
          </li>
        </ul>
        <ul class="navbar-nav">
          <li class="nav-item dropdown">
            <a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
              hxw
            </a>
            <ul class="dropdown-menu">
              <li><router-link class="dropdown-item" :to="{name:'user_bot_index'}">我的Bot</router-link></li>
              <li><router-link class="dropdown-item" href="#">退出</router-link></li>
            </ul>
          </li>
        </ul>
      </div>
    </div>
  </nav>
</template>

<script>
import { useRouter } from "vue-router";
import {computed} from "vue";

export default {
  setup(){
    const route = useRouter();
    let rout_name = computed(() => route.name)
    return{
      rout_name
    }
  }
}
</script>

<style scoped>


</style>
<template>
  <div class="container content-field">
    <div class="card">
      <div class="card-body">
        <slot></slot>
      </div>
    </div>
  </div>
</template>
<script>
</script>

<style scoped>
div.content-field{
  margin-top: 20px;
}
</style>

<template>
  <ContentField>对战</ContentField>
</template>

<script>
import ContentField from '../../components/ContentField.vue'
export default {
  components: {
    ContentField
  }
}
</script>

<style scoped>

</style>

导航栏功能实现完成,效果如下
image.png

游戏页面的实现

实现地图(13*13)功能

image.png
设计思路:

  • 周围一圈是墙壁,空地中间设置随机障碍物
  • 为了公平,实现障碍物的轴对称或者中心对称
  • 能够从左上角走到右上角

游戏中,物体是如何动起来的
一秒钟刷新60张图片(60帧),当前帧把上一帧覆盖掉,实现视觉上的运动。
所有物体每秒钟都需要刷新60次,所以将其抽象出来,设计一个基类AcGameObject.js
代码脚本通常放在asset文件夹中。在asset文件夹中创建scripts文件夹和images文件夹,将背景文件加入到images

先实现游戏基类

const AC_GAME_OBJECTS = [];
export class AcGameObject{
    constructor() {
        AC_GAME_OBJECTS.push(this);
        this.timedelta = 0;
        this.has_called_start = false;
    }

    //只执行一次
    start(){

    }
    //每帧执行一次,除了第一帧之外
    update(){

    }
    //删除之前执行
    on_destory(){

    }
    //删除
    destory(){
        this.on_destory()
        for(let i in AC_GAME_OBJECTS){
            const obj = AC_GAME_OBJECTS[i];
            if(obj === this){
                AC_GAME_OBJECTS.splice(i);
                break;
            }
        }
    }
}

let last_timestamp;
const step = timestamp => {//传递当前执行的时刻
    for(let obj of AC_GAME_OBJECTS){
        if(!obj.has_called_start){//如果没有执行过start函数,就执行start函数
            obj.has_called_start = true;// 表示已经执行过了
            obj.start();
        }else{
            obj.timedelta = timestamp - last_timestamp;
            obj.update();
        }
    }
    last_timestamp = timestamp;
    //执行完step,又会在下一帧执行step
    requestAnimationFrame(step)
}
//第一次调用会在下一帧执行step
requestAnimationFrame(step)

游戏中每一组件的都是一个类,一个class
pk页面需要展示地图,所以pk页面的背景可以删掉,单独给pk页面写一个游戏页面组件PlayGround.vue,在游戏页面中在写一个游戏板子组件GameMap.vue,所以可以看出PlayGround.vue包含着GameMap.vue
现在就是要求PlayGround.vue中包含的面积最大的row * col 的矩形。

画地图的实现方式,奇数画浅色,偶数画深色。
复制鼠标点击位置的颜色的方式:

  • 鼠标悬浮在指定颜色区域的上方
  • 打开QQ
  • 键盘同时按住Ctrl + Alt + A随后按住 C实现复制色号

image.png

接着实现地图中的障碍物,在script包下创建Wall.js,墙是在地图的后面,所以墙会将地图覆盖。

最终代码总结:

js脚本

const AC_GAME_OBJECTS = [];
export class AcGameObject{
    constructor(){
        AC_GAME_OBJECTS.push(this);
        this.timedelata = 0;//记录时间差
        this.has_called_start = false;//用于标记是否执行过
    }

    //start执行一次
    start(){

    }
    update(){

    }

    //删除之前执行
    on_destory(){

    }

    destroy(){
        this.on_destory();//删除之前执行
        for(let i in AC_GAME_OBJECTS){
            const obj = AC_GAME_OBJECTS[i];
            if(obj === this){
                AC_GAME_OBJECTS.splice(i);
                break;
            }
        }
    }

}
let last_timestamp;
const step = timstamp => {
    for(let obj of AC_GAME_OBJECTS){
        if(!obj.has_called_start){
            obj.has_called_start = true;
            obj.start();
        }else{
            obj.timedelata = timstamp - last_timestamp;
            obj.update();
        }
    }
    last_timestamp = timstamp;
    requestAnimationFrame(step);
}
//第一次调用会在下一帧执行step
requestAnimationFrame(step);
import {AcGameObject}  from "@/assets/scripts/AcGameObject";
import {Wall} from "@/assets/scripts/Wall";

export class GameMap extends AcGameObject{
    constructor(ctx,parent){//画布,画布的父元素(动态修改画布的长宽)
        super();
        this.ctx = ctx;
        this.parent = parent;
        this.L = 0;// L 用来存储绝对距离,L表示一个单位的长度

        //设置内部地图的行数和列数
        this.rows = 13;
        this.cols = 13;

        //设置障碍物的数量
        this.inner_walls_count = 20;
        //开一个数组,用来存储墙
        this.walls = [];
    }
    //编写一个函数判断是否连通,从左下角到右上角
    check_connectivity(g, sx, sy, tx, ty){
        if(sx == tx && sy == ty){
            return true;
        }else{
            //将当前位置标记为已经走过
            g[sx][sy] = true;
        }
        //定义上下左右四个方向的偏移量
        const dx = [-1, 0, 1, 0], dy = [0, 1, 0, -1];
        for(let i = 0 ; i < 4; i ++){
            let nx = sx + dx[i], ny = sy + dy[i];
            if(!g[nx][ny] && this.check_connectivity(g, nx, ny,tx,ty)){
                return true;
            }
        }
        return false;

    }

    creat_walls(){
        const g = [];
        for(let r = 0; r < this.rows; r ++){
            g[r] = [];
            for(let c = 0; c < this.cols; c++){
                g[r][c] = false;
            }
        }

        //给四周加上障碍物
        for(let r = 0; r < this.rows; r ++){
            g[r][0] = g[r][this.cols - 1] = true;
        }
        for(let  c = 0; c < this.cols; c ++){
            g[0][c] = g[this.rows - 1][c] = true;
        }

        // 随机生成障碍物, 每次放两个
        for(let i = 0; i < this.inner_walls_count / 2; i ++){
            //每次随机一个位置,重复了继续随机,直到随机到不同位置指定数量的障碍物
            for(let j = 0; j < 1000; j ++){
                let r = parseInt(Math.random() * this.rows);
                let c = parseInt(Math.random() * this.cols);
                if(g[r][c] || g[c][r]){
                    continue;
                }
                //确保不覆盖左下角和右上角
                if((r == this.rows - 2 && c == 1) || (r == 1 && c == this.cols - 2)){
                    continue;
                }
                g[r][c] = g[c][r] = true;
                break;
            }
        }
        //数组的复制
        const copy_g = JSON.parse(JSON.stringify(g));

        if(!this.check_connectivity(copy_g, this.rows - 2, 1, 1, this.cols - 2)){
            return false;
        }
        //枚举数组
        for(let r = 0; r < this.rows; r ++){
            for(let c = 0; c < this.cols; c ++){
                if(g[r][c]){
                    this.walls.push(new Wall(r, c, this));
                }
            }
        }
        return true;
    }
    start(){
        //不建议写死循环,可以循环足够多的次数
        for(let i = 0; i < 1000; i ++){
            if(this.creat_walls()){
                break;
            }
        }
        this.creat_walls();
    }
    update_size(){
        // 单位格的距离取成整个像素,防止浮点数造成格子与格子之间有微小缝隙
        this.L = parseInt(Math.min(this.parent.clientWidth / this.cols, this.parent.clientHeight / this.rows));
        this.ctx.canvas.width = this.L * this.cols;
        this.ctx.canvas.height = this.L * this.rows;
    }
    update() {
        this.update_size();
        this.render();

    }
    render(){
        //将当前的地图画出来
       const color_even = "#AAD751";//偶数颜色
       const color_odd = "#A2D149";//奇数颜色
       for(let r = 0; r < this.rows; r++ ){
           for(let c = 0; c < this.cols; c++){
               if((c + r) % 2 === 0){
                   this.ctx.fillStyle = color_even;
               } else {
                   this.ctx.fillStyle = color_odd;
               }
               this.ctx.fillRect(c * this.L, r * this.L, this.L, this.L);
           }
       }
    }

}

import {AcGameObject} from "@/assets/scripts/AcGameObject";
export class Wall extends AcGameObject{
    constructor(r, c, gamemap) {
        super();
        this.r = r;
        this.c = c;
        this.gamemap = gamemap;
        this.color = "#B37226"
    }

    update() {
        this.render();
    }
    render(){
        const L = this.gamemap.L; // L会动态变化,需要动态取
        const ctx = this.gamemap.ctx;

        ctx.fillStyle = this.color;
        ctx.fillRect(this.c * L, this.r * L, L, L);
    }

}

组件

<template>
  <div class="container content-field">
    <div class="card">
      <div class="card-body">
        <slot></slot>
      </div>
    </div>
  </div>
</template>
<script>
</script>

<style scoped>
div.content-field{
  margin-top: 20px;
}
</style>

<template>
  <div ref = "parent" class="gamemap">
    <canvas ref="canvas">
    </canvas>
  </div>
</template>

<script>
import {GameMap} from "@/assets/scripts/GameMap";
import {ref, onMounted} from "vue";

export default {
  setup(){
    let parent = ref(null);
    let canvas = ref(null);

    onMounted(() => {
      new GameMap(canvas.value.getContext('2d'),parent.value)
    });
    return {
      parent,
      canvas
    }
  }
}
</script>

<style scoped>
  div.gamemap{
    width: 100%;
    height: 100%;
    display: flex;
    justify-content: center;
    alien-items: center;
  }
</style>
<template>
  <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
    <div class="container">
      <router-link class="navbar-brand" :to="{name: 'home'}">King of Bots</router-link>
      <div class="collapse navbar-collapse" id="navbarText">
        <ul class="navbar-nav me-auto mb-2 mb-lg-0">
          <li class="nav-item">
            <router-link :class="rout_name === 'pk_index' ? 'nav-link active' : 'nav-link'" :to="{name: 'pk_index'}" >对战</router-link>
          </li>
          <li class="nav-item">
            <router-link :class="rout_name === 'record_index' ? 'nav-link active' : 'nav-link'" :to="{name: 'record_index'}">对局列表</router-link>
          </li>
          <li class="nav-item">
            <router-link :class="rout_name === 'ranklist_index' ? 'nav-link active' : 'nav-link'" :to="{name:'ranklist_index'}">排行榜</router-link>
          </li>
        </ul>
        <ul class="navbar-nav">
          <li class="nav-item dropdown">
            <a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
              hxw
            </a>
            <ul class="dropdown-menu">
              <li><router-link class="dropdown-item" :to="{name:'user_bot_index'}">我的Bot</router-link></li>
              <li><router-link class="dropdown-item" href="#">退出</router-link></li>
            </ul>
          </li>
        </ul>
      </div>
    </div>
  </nav>
</template>

<script>
import { useRouter } from "vue-router";
import {computed} from "vue";

export default {
  setup(){
    const route = useRouter();
    let rout_name = computed(() => route.name)
    return{
      rout_name
    }
  }
}
</script>

<style scoped>


</style>
<template>

  <div class="playground">
    <GameMap/>
  </div>

</template>

<script>
import GameMap from  "./GameMap.vue"

export default {
  components:{
    GameMap,
  }
}

</script>

<style scoped>
div.playground{
  width: 60vw;
  height: 70vh;
  margin: 50px auto;
}

</style>

跟换网址图标以及最终效果展示

更换图标,点击图片另存为,到指定目录,打开所有文件,双击替换的图片,实现替换
image.png
image.png

总结

本次写的是前端代码,每次用户刷新浏览器都会将页面加载出来,每次加载,会先创建游戏地图,游戏地图每秒钟刷新60次。创建完地图之后,创建障碍物,障碍物是先创建四周,然后随机创建内部障碍物,这里需要判断创建的障碍物是否满足连通性,如不满足,重新创建。
最后,记得上传git哦~

  • 12
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: kob kt f360 是一款功能强大的家用电风扇,下面是对其说明书的简要介绍: 该电风扇具有三档风速调节功能,可以根据个人需求选择合适的风速,提供舒适的风感体验。同时,还配备有90度自动摇头功能,可以帮助实现更广泛的风向散布,使整个房间都能受到凉爽的风。 kob kt f360 具有创新的静音设计,采用先进的静音技术,减少了噪音干扰,更适合安静的环境使用,不会打扰到您的休息或工作。 该电风扇还内置倒计时功能,可以按照您的需求设置运行时间,设定后自动关闭,方便省电节能。此外,该电风扇还带有室温显示功能,可以让您了解当前环境温度,方便调整风速等。 kob kt f360 采用便捷的遥控器操作,让您可以轻松控制风速、风向等功能,无需离开座位就能自由调节。 此外,kob kt f360 的外观简约大方,采用优质材料制造,耐用性强,易于清洁维护。同时,其紧凑的设计,占据空间小,方便携带和存储。 总之,kob kt f360 是一款功能齐全、操作便捷、静音效果好的电风扇。无论是在家中、办公室还是其他场所使用,都能为您提供舒适、凉爽的环境。同时,其节能环保的设计,也符合现代人的节能理念。 ### 回答2: KOB KT F360是一款先进的电子产品,它具有多种功能和特点。首先,说明书对产品的外观和构造进行了详细描述。你可以从说明书中了解到KOB KT F360采用了高品质的材料和精致的工艺,使其外观时尚而耐用。同时,它的紧凑设计使得携带和使用变得更加方便。 说明书还介绍了KOB KT F360的主要功能和操作方法。它具有多种模式,如音乐播放、视频观看、游戏等,可以满足用户的多样化需求。此外,说明书还详细介绍了产品的操作界面和各个按钮的功能,帮助用户快速上手。 说明书还详细列出了KOB KT F360的技术规格和性能参数。例如,它支持高清屏幕显示,拥有超长续航时间,配置强大的处理器等。这些技术特点的介绍有助于用户了解产品的性能和优势,同时也可以选择最适合自己的使用方法。 最后,说明书还对KOB KT F360的使用注意事项进行了说明。它包括如何避免产品的损坏和电池寿命延长的建议,以及如何正确储存和清洁产品等。 总之,KOB KT F360说明书提供了全面而详细的介绍,帮助用户了解产品的各个方面,并在使用中提供必要的指导和建议。无论是初次使用还是需要更多技术支持的用户,都可以通过说明书获得满意的答案和帮助。 ### 回答3: kob kt f360是一款多功能电子产品,下面为您提供其说明书。 kob kt f360是一款具有多种功能的设备,可以用于各种场景。它具有时尚的外观设计和高品质的制造工艺,给用户带来了极佳的使用体验。 首先,kob kt f360配备了高清显示屏,显示效果清晰细腻。用户可以通过触摸屏幕进行各种操作,非常方便。同时,其内置了强大的处理器,运行速度快,反应灵敏。 其次,kob kt f360还具备丰富的功能。它支持多种文件格式的阅读,包括文档、图片、音频和视频等。用户可以轻松地在这款设备上阅读电子书、浏览照片、收听音乐和观看视频。 此外,kob kt f360还内置了无线网络功能,用户可以轻松地连接到互联网。无论是查找资讯、浏览社交媒体还是收发电子邮件,都非常方便。 值得一提的是,kob kt f360还具备优秀的电池续航能力。它配备了高容量的电池,可以满足用户长时间使用的需求。 最后,kob kt f360还支持外部存储扩展。用户可以通过插入存储卡,扩展设备的存储空间,更方便地存储和分享文件。 总而言之,kob kt f360是一款功能强大、外观时尚的多功能电子产品。它能够满足用户的各种需求,无论是娱乐、办公还是学习,都能轻松应对。拥有这款设备,用户可以更加便捷地享受数字化生活。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值