为了账号安全,请及时绑定邮箱和手机立即绑定

使用vue3+springboot构建简单Web应用教程

概述

本文将指导你使用Vue3和Spring Boot构建一个简单的Web应用,通过实际操作帮助你理解这两种技术的基本应用和集成方式。Vue3作为前端框架,提供了高效且灵活的开发体验,而Spring Boot则简化了Java应用程序的开发和部署流程。通过具体示例,你将学会如何安装和配置环境、创建Vue3项目和Spring Boot后端服务,以及实现前后端的通信。

引入Vue3和Spring Boot

在现代Web开发中,Vue.js 和 Spring Boot 作为前后端开发的流行技术,广泛应用于Web应用构建。本教程将引导你使用Vue3和Spring Boot构建一个简单的Web应用,通过实际操作,帮助你理解这两种技术的基本应用和集成方式。

介绍Vue3和Spring Boot

Vue.js 是一个前端JavaScript框架,它通过代码片段的方式组织视图逻辑,易于上手且维护性高。Vue3 是 Vue.js 的最新稳定版本,它在性能、开发体验和开发者工具方面都有了很大改进。Spring Boot 是一个基于Spring框架的Java库,它简化了Java应用程序的开发和部署,允许开发者快速创建独立的、生产级别的基于Spring的应用程序。

Vue3的特点

  • 组合式APIVue3引入了组合式API,提高了代码的可重用性和可维护性。
  • 响应式系统Vue3的响应式系统进行了重构,性能提升明显。
  • TeleportsVue3新增了Teleports,可以在DOM树的任何位置插入组件,提供了更灵活的布局选项。
  • FragmentsVue3允许组件拥有多个根节点,提高了组件的灵活性。

Spring Boot的特点

  • 自动配置:Spring Boot可以自动配置大多数应用程序的常见场景,大大简化了开发流程。
  • 独立运行:Spring Boot应用可以独立运行,不需要外部容器,支持嵌入式的Tomcat、Jetty或Undertow。
  • 外部配置:支持外部化配置,可以使用properties文件、YAML文件、环境变量、命令行参数来配置应用属性。
安装和配置环境

安装Node.js和NPM

首先,需要安装Node.js和NPM(Node.js的包管理器)。你可以从官网(Node.js官网)下载最新的长期支持版本(LTS),并按照安装指南进行安装。

# 检查Node.js和NPM是否安装成功
node -v
npm -v

安装Vue CLI

Vue CLI 是一个命令行工具,可以用来快速搭建 Vue.js 项目。通过以下命令安装Vue CLI:

npm install -g @vue/cli

安装Java和Maven

为了使用Spring Boot,你需要Java开发工具包(JDK)和Maven。你可以从Oracle官网下载Java,并从Maven官网下载Maven。

# 检查Java和Maven是否安装成功
java -version
mvn -v

安装IDE

建议使用IDEA或Eclipse进行Java开发。安装完成后,确保IDE已经配置好Java和Maven环境。


创建Vue3项目

我们将使用Vue CLI创建一个新的Vue3项目,并简要介绍项目的基本结构。

使用Vue CLI创建Vue3项目

创建项目

在安装了Vue CLI后,你可以使用以下命令来创建一个新项目:

vue create my-vue3-app

在提示选择预设时,选择Manually select features,然后选择你需要的特性,如Vue3

进入项目目录

cd my-vue3-app

运行项目

npm run serve

这将启动开发服务器,你可以在浏览器中访问http://localhost:8080查看应用。

项目结构介绍

文件结构

  • public:存放静态文件,如index.html、favicon.ico等。
  • src:存放应用的主要逻辑文件。
    • assets:存放静态资源。
    • components:存放可复用的Vue组件。
    • views:存放路由视图组件。
    • App.vue:根组件。
    • main.js:应用入口文件。
    • router.js:路由配置文件。
    • store.js:状态管理文件。

示例代码

src/main.js中,你可以看到默认的入口文件:

import { createApp } from 'vue';
import App from './App.vue';

createApp(App).mount('#app');

src/App.vue中,你可以看到默认的根组件:

<template>
  <div id="app">
    <img alt="Vue logo" class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="./assets/logo.png">
    <HelloWorld msg="Welcome to Your Vue.js App"/>
  </div>
</template>

<script>
import HelloWorld from './components/HelloWorld.vue';

export default {
  name: 'App',
  components: {
    HelloWorld
  }
}
</script>

<style scoped>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

创建组件

创建一个新的Vue组件,例如src/components/MyComponent.vue

<template>
  <div>
    <h1>Hello from MyComponent</h1>
  </div>
</template>

<script>
export default {
  name: 'MyComponent'
}
</script>

<style scoped>
h1 {
  color: red;
}
</style>

使用组件

App.vue中引入并使用新创建的组件。

<template>
  <div id="app">
    <img alt="Vue logo" class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="./assets/logo.png">
    <HelloWorld msg="Welcome to Your Vue.js App"/>
    <MyComponent />
  </div>
</template>

<script>
import HelloWorld from './components/HelloWorld.vue';
import MyComponent from './components/MyComponent.vue';

export default {
  name: 'App',
  components: {
    HelloWorld,
    MyComponent
  }
}
</script>

状态管理

src/store.js中,可以使用vuex来管理应用的状态。

import { createStore } from 'vuex';

export default createStore({
  state: {
    count: 0
  },
  mutations: {
    increment(state) {
      state.count++;
    }
  },
  actions: {
    incrementCount({ commit }) {
      commit('increment');
    }
  },
  getters: {
    count: state => state.count
  }
});

路由

src/router.js中,配置路由规则。

import { createRouter, createWebHistory } from 'vue-router';
import Home from '../views/Home.vue';
import About from '../views/About.vue';

const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home
  },
  {
    path: '/about',
    name: 'About',
    component: About
  }
];

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

export default router;

静态资源

src/assets中存放静态资源,例如图片、字体等。

配置文件

vue.config.js中,可以进行一些定制的配置。

module.exports = {
  publicPath: process.env.NODE_ENV === 'production' ? '/my-vue3-app/' : '/'
};

创建Spring Boot后端服务

我们将使用Spring Boot创建一个简单的API服务,以便与Vue前端进行通信。

使用Spring Initializr创建Spring Boot项目

创建项目

访问Spring Initializr网站(https://start.spring.io/)创建一个新的Spring Boot项目。选择Maven项目,并选择Java版本、项目名称、依赖等。

下载和导入项目

下载创建的项目,解压后导入IDE中。

运行项目

在IDE中,右键项目,选择Run As -> Spring Boot App,启动服务。

示例代码

src/main/java/com/example/myapp目录下,创建一个简单的Spring Boot应用。

package com.example.myapp;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class MyappApplication {
  public static void main(String[] args) {
    SpringApplication.run(MyappApplication.class, args);
  }
}

添加必要的依赖包

pom.xml中添加Spring Boot相关依赖。

<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
  </dependency>
</dependencies>

创建RESTful API

src/main/java/com/example/myapp/controller目录下,创建一个RESTful API控制器。

package com.example.myapp.controller;

import com.example.myapp.model.Item;
import com.example.myapp.service.ItemService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/api/items")
public class ItemController {

  @Autowired
  private ItemService itemService;

  @GetMapping
  public List<Item> getAllItems() {
    return itemService.getAllItems();
  }

  @PostMapping
  public Item createItem(@RequestBody Item item) {
    return itemService.createItem(item);
  }

  @GetMapping("/{id}")
  public Item getItemById(@PathVariable Long id) {
    return itemService.getItemById(id);
  }

  @PutMapping("/{id}")
  public Item updateItem(@PathVariable Long id, @RequestBody Item item) {
    return itemService.updateItem(id, item);
  }

  @DeleteMapping("/{id}")
  public void deleteItem(@PathVariable Long id) {
    itemService.deleteItem(id);
  }
}

示例数据模型

src/main/java/com/example/myapp/model目录下,创建一个简单的数据模型。

package com.example.myapp.model;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
public class Item {

  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  private Long id;
  private String name;
  private String description;

  // getters and setters

  public Long getId() {
    return id;
  }

  public void setId(Long id) {
    this.id = id;
  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public String getDescription() {
    return description;
  }

  public void setDescription(String description) {
    this.description = description;
  }
}

示例服务层

src/main/java/com/example/myapp/service目录下,创建服务层来处理业务逻辑。

package com.example.myapp.service;

import com.example.myapp.model.Item;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

@Service
public class ItemService {

  private List<Item> items = new ArrayList<>();

  public List<Item> getAllItems() {
    return items;
  }

  public Item createItem(Item item) {
    items.add(item);
    return item;
  }

  public Item getItemById(Long id) {
    return items.stream()
        .filter(item -> item.getId().equals(id))
        .findFirst()
        .orElse(null);
  }

  public Item updateItem(Long id, Item item) {
    Item existingItem = getItemById(id);
    if (existingItem != null) {
      existingItem.setName(item.getName());
      existingItem.setDescription(item.getDescription());
    }
    return existingItem;
  }

  public void deleteItem(Long id) {
    Item item = getItemById(id);
    if (item != null) {
      items.remove(item);
    }
  }
}

配置数据源

src/main/resources/application.properties中配置数据源。

spring.datasource.url=jdbc:mysql://localhost:3306/myapp?useSSL=false&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=root
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true

启动应用

在IDE中运行项目,启动Spring Boot应用。

mvn spring-boot:run

实现前端与后端的通信

我们将在Vue项目中使用axios发送HTTP请求,在Spring Boot项目中创建RESTful API,以实现前后端的通信。

在Vue项目中使用axios发送HTTP请求

安装axios

在Vue项目中安装axios。

npm install axios

示例代码

src/services/ItemService.js中,创建一个API服务类。

import axios from 'axios';

const API_URL = 'http://localhost:8080/api/items';

export const getAllItems = () => axios.get(API_URL);
export const createItem = (item) => axios.post(API_URL, item);
export const getItemById = (id) => axios.get(`${API_URL}/${id}`);
export const updateItem = (id, item) => axios.put(`${API_URL}/${id}`, item);
export const deleteItem = (id) => axios.delete(`${API_URL}/${id}`);

src/views/ItemList.vue中,使用API服务类来操作数据。

<template>
  <div>
    <h1>Items</h1>
    <button @click="addItem">Add Item</button>
    <table>
      <thead>
        <tr>
          <th>ID</th>
          <th>Name</th>
          <th>Description</th>
          <th>Actions</th>
        </tr>
      </thead>
      <tbody>
        <tr v-for="item in items" :key="item.id">
          <td>{{ item.id }}</td>
          <td>{{ item.name }}</td>
          <td>{{ item.description }}</td>
          <td>
            <button @click="editItem(item)">Edit</button>
            <button @click="deleteItem(item.id)">Delete</button>
          </td>
        </tr>
      </tbody>
    </table>
  </div>
</template>

<script>
import { getAllItems, createItem, getItemById, updateItem, deleteItem } from '@/services/ItemService';

export default {
  data() {
    return {
      items: [],
      newItem: {
        name: '',
        description: ''
      },
      editingItem: null
    };
  },
  methods: {
    addItem() {
      this.newItem = {
        name: '',
        description: ''
      };
      this.editingItem = null;
    },
    saveItem() {
      if (this.editingItem) {
        updateItem(this.editingItem.id, this.editingItem).then(() => {
          this.getItems();
          this.editingItem = null;
        });
      } else {
        createItem(this.newItem).then(() => {
          this.getItems();
          this.newItem = {
            name: '',
            description: ''
          };
        });
      }
    },
    editItem(item) {
      this.editingItem = { ...item };
    },
    deleteItem(id) {
      deleteItem(id).then(() => {
        this.getItems();
      });
    },
    getItems() {
      getAllItems().then((response) => {
        this.items = response.data;
      });
    }
  },
  mounted() {
    this.getItems();
  }
};
</script>

使用axios进行请求

在Vue组件中,调用getAllItemscreateItemgetItemByIdupdateItemdeleteItem方法来获取和操作数据。

import { getAllItems, createItem, getItemById, updateItem, deleteItem } from '@/services/ItemService';

export default {
  data() {
    return {
      items: [],
      newItem: {
        name: '',
        description: ''
      },
      editingItem: null
    };
  },
  methods: {
    addItem() {
      this.newItem = {
        name: '',
        description: ''
      };
      this.editingItem = null;
    },
    saveItem() {
      if (this.editingItem) {
        updateItem(this.editingItem.id, this.editingItem).then(() => {
          this.getItems();
          this.editingItem = null;
        });
      } else {
        createItem(this.newItem).then(() => {
          this.getItems();
          this.newItem = {
            name: '',
            description: ''
          };
        });
      }
    },
    editItem(item) {
      this.editingItem = { ...item };
    },
    deleteItem(id) {
      deleteItem(id).then(() => {
        this.getItems();
      });
    },
    getItems() {
      getAllItems().then((response) => {
        this.items = response.data;
      });
    }
  },
  mounted() {
    this.getItems();
  }
};

获取数据

在组件的mounted生命周期钩子中,调用getItems方法获取数据。

export default {
  // ...
  mounted() {
    this.getItems();
  }
};

增加数据

addItem方法中,调用createItem方法增加数据。

addItem() {
  this.newItem = {
    name: '',
    description: ''
  };
  this.editingItem = null;
},

saveItem() {
  if (this.editingItem) {
    updateItem(this.editingItem.id, this.editingItem).then(() => {
      this.getItems();
      this.editingItem = null;
    });
  } else {
    createItem(this.newItem).then(() => {
      this.getItems();
      this.newItem = {
        name: '',
        description: ''
      };
    });
  }
},

编辑数据

editItem方法中,编辑数据。

editItem(item) {
  this.editingItem = { ...item };
},

删除数据

deleteItem方法中,调用deleteItem方法删除数据。

deleteItem(id) {
  deleteItem(id).then(() => {
    this.getItems();
  });
}
在Spring Boot项目中创建RESTful API

在前面的部分,我们已经创建了一个简单的RESTful API。现在,我们将更深入地理解如何创建和测试这些API。

模型层

根据需求,你可能需要创建不同的数据模型。例如,我们创建了一个Item模型,它代表了商品信息。

package com.example.myapp.model;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
public class Item {

  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  private Long id;
  private String name;
  private String description;

  // getters and setters
}

服务层

服务层负责处理业务逻辑。我们定义了ItemService类来处理数据相关的操作。

package com.example.myapp.service;

import com.example.myapp.model.Item;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

@Service
public class ItemService {

  private List<Item> items = new ArrayList<>();

  public List<Item> getAllItems() {
    return items;
  }

  public Item createItem(Item item) {
    items.add(item);
    return item;
  }

  public Item getItemById(Long id) {
    return items.stream()
        .filter(item -> item.getId().equals(id))
        .findFirst()
       .
        orElse(null);
  }

  public Item updateItem(Long id, Item item) {
    Item existingItem = getItemById(id);
    if (existingItem != null) {
      existingItem.setName(item.getName());
      existingItem.setDescription(item.getDescription());
    }
    return existingItem;
  }

  public void deleteItem(Long id) {
    Item item = getItemById(id);
    if (item != null) {
      items.remove(item);
    }
  }
}

控制器层

控制器层负责处理HTTP请求,并将请求转发给服务层。

package com.example.myapp.controller;

import com.example.myapp.model.Item;
import com.example.myapp.service.ItemService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/api/items")
public class ItemController {

  @Autowired
  private ItemService itemService;

  @GetMapping
  public List<Item> getAllItems() {
    return itemService.getAllItems();
  }

  @PostMapping
  public Item createItem(@RequestBody Item item) {
    return itemService.createItem(item);
  }

  @GetMapping("/{id}")
  public Item getItemById(@PathVariable Long id) {
    return itemService.getItemById(id);
  }

  @PutMapping("/{id}")
  public Item updateItem(@PathVariable Long id, @RequestBody Item item) {
    return itemService.updateItem(id, item);
  }

  @DeleteMapping("/{id}")
  public void deleteItem(@PathVariable Long id) {
    itemService.deleteItem(id);
  }
}

测试API

你可以使用Postman或curl来测试这些API。

# 获取全部商品
curl -X GET "http://localhost:8080/api/items"

# 创建商品
curl -X POST "http://localhost:8080/api/items" \
     -H "Content-Type: application/json" \
     -d '{"name": "New Item", "description": "This is a new item"}'

# 获取商品ID为1的商品
curl -X GET "http://localhost:8080/api/items/1"

# 更新商品ID为1的商品
curl -X PUT "http://localhost:8080/api/items/1" \
     -H "Content-Type: application/json" \
     -d '{"name": "Updated Item", "description": "This is an updated item"}'

# 删除商品ID为1的商品
curl -X DELETE "http://localhost:8080/api/items/1"

通过以上步骤,我们已经实现了前后端的通信,确保了前后端能够顺畅地协作。


部署和运行应用

最后,我们将学习如何打包Vue应用,并部署Spring Boot应用。

打包Vue应用

打包命令

使用npm run build命令,将Vue应用打包成一个静态资源。

npm run build

项目结构

打包后,会在dist目录下生成一个静态资源文件夹,其中包含包括index.html和静态资源。

部署Spring Boot应用

打包Spring Boot应用

使用Maven命令将Spring Boot应用打包成一个可运行的JAR文件。

mvn clean package

项目结构

打包后,可以在target目录下找到生成的JAR文件。

运行JAR文件

使用以下命令运行生成的JAR文件:

java -jar target/myapp-0.0.1-SNAPSHOT.jar

部署到服务器

你可以将打包后的JAR文件部署到任何支持Java的服务器上,如Linux服务器、Docker容器等。

# 在Linux服务器上运行
java -jar myapp-0.0.1-SNAPSHOT.jar

部署到云服务器

使用云服务商提供的服务,如AWS、阿里云等,可以将JAR文件部署到云服务器上。

# 使用阿里云ECS部署
scp myapp-0.0.1-SNAPSHOT.jar root@your_ecs_ip:/path/to/deploy
ssh root@your_ecs_ip
java -jar /path/to/deploy/myapp-0.0.1-SNAPSHOT.jar

部署到Docker容器

使用Docker,可以将Spring Boot应用容器化部署。

# Dockerfile
FROM openjdk:8-jdk-alpine
VOLUME /tmp
COPY target/myapp-0.0.1-SNAPSHOT.jar myapp.jar
ENTRYPOINT ["java","-jar","/myapp.jar"]
# 构建Docker镜像
docker build -t myapp .
# 运行Docker容器
docker run -p 8080:8080 myapp

通过以上步骤,你已经将Vue应用和Spring Boot应用分别打包,并部署到相应的服务器上。


常见问题与解决办法

在开发过程中,你可能会遇到各种各样的问题,下面是一些常见的问题及其解决方法。

常见错误与解决方法

Vue项目错误

1. Module not found

错误信息

Module not found: Error: Can't resolve 'module-name' in 'path'

解决方法
确保模块已正确安装,检查路径是否正确。

npm install module-name

2. Property 'xxx' is missing in type

错误信息

Type '{ yyy: string; }' is not assignable to type '{ xxx: string; yyy: number; }'

解决方法
检查类型定义是否正确,确保属性类型一致。

interface MyType {
  xxx: string;
  yyy: number;
}

const myObject: MyType = {
  xxx: 'string',
  yyy: 123
};

Spring Boot项目错误

1. DataSource not configured

错误信息

org.springframework.boot.context.properties.source.InvalidConfigurationPropertyValueException: Invalid property 'url' of type 'java.lang.String' in class 'com.example.demo.DataSourceProperties'

解决方法
检查配置文件中数据源是否正确配置。

spring.datasource.url=jdbc:mysql://localhost:3306/myapp
spring.datasource.username=root
spring.datasource.password=root

2. No qualifying bean of type 'XXXX' available

错误信息

org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.example.demo.XXXX' available

解决方法
确保对应的类已经添加了@Component@Service注解。

package com.example.demo;

import org.springframework.stereotype.Component;

@Component
public class XXXX {
  //...
}
运行时问题排查

Vue项目运行时问题

1. Failed to mount app: template or render function not defined

错误信息

TypeError: Cannot read properties of undefined (reading 'data')

解决方法
确保组件的templaterender函数已被定义。

export default {
  template: `<div>Hello World</div>`
};

2. Vue warn: Error in render function

错误信息

[Vue warn]: Error in render function: "TypeError: Cannot read properties of undefined (reading 'name')"

解决方法
检查数据绑定是否正确。

<template>
  <div>{{ item.name }}</div>
</template>

<script>
export default {
  data() {
    return {
      item: {
        name: ''
      }
    };
  }
};
</script>

Spring Boot项目运行时问题

1. No qualifying bean of type 'XXXX' available

错误信息

org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.example.demo.XXXX' available

解决方法
检查是否已经导入了对应类的包。

package com.example.demo;

import org.springframework.stereotype.Component;

@Component
public class XXXX {
  //...
}

2. Cannot create inner bean

错误信息

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'XXXX': Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.example.demo.YYYY' available

解决方法
确保依赖的类已添加@Component@Service注解。

package com.example.demo;

import org.springframework.stereotype.Component;

@Component
public class YYYYY {
  //...
}

@Component
public class XXXX {
  public XXXX(YYYYY yyyyy) {
    //...
  }
}

通过以上方法,你可以在遇到问题时快速找到解决方案。希望这些信息对你有所帮助。

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消