springboot+vue小白升级之路06-实现文件上传和下载

661 篇文章 4 订阅
131 篇文章 0 订阅

接着上一个的内容,代码我就不全部贴了,

有变化的部分发一下

springboot后端

pojo

book.java

package com.shrimpking.pojo;

import java.math.BigDecimal;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import java.io.Serializable;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * <p>
 * 图书表
 * </p>
 *
 * @author shrimpking
 * @since 2023-11-12
 */
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("an_book")
@ApiModel(value="Book对象", description="图书表")
public class Book implements Serializable {

    private static final long serialVersionUID = 1L;

    @ApiModelProperty(value = "主键id")
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    @ApiModelProperty(value = "图书名称")
    private String name;

    @ApiModelProperty(value = "价格")
    private Double price;

    @ApiModelProperty(value = "作者")
    private String author;

    @ApiModelProperty(value = "出版社")
    private String express;

    @ApiModelProperty(value = "图书封面")
    private String img;


}

mapper

BookMapper.java

package com.shrimpking.mapper;

import com.shrimpking.pojo.Book;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;

/**
 * <p>
 * 图书表 Mapper 接口
 * </p>
 *
 * @author shrimpking
 * @since 2023-11-12
 */
public interface BookMapper extends BaseMapper<Book> {

}

 MapperXml

BookMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.shrimpking.mapper.BookMapper">

    <!-- 通用查询映射结果 -->
    <resultMap id="BaseResultMap" type="com.shrimpking.pojo.Book">
        <id column="id" property="id" />
        <result column="name" property="name" />
        <result column="price" property="price" />
        <result column="author" property="author" />
        <result column="express" property="express" />
        <result column="img" property="img" />
    </resultMap>

    <!-- 通用查询结果列 -->
    <sql id="Base_Column_List">
        id, name, price, author, express, img
    </sql>

</mapper>

 service

BookService.java

package com.shrimpking.service;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.shrimpking.pojo.Book;
import com.baomidou.mybatisplus.extension.service.IService;
import com.shrimpking.req.BookParams;
import com.shrimpking.req.QueryParams;

/**
 * <p>
 * 图书表 服务类
 * </p>
 *
 * @author shrimpking
 * @since 2023-11-12
 */
public interface BookService extends IService<Book> {

    IPage<Book> findBySearchPage(BookParams bookParams);
}

 serviceImpl

BookServiceImpl.java

package com.shrimpking.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.shrimpking.pojo.Book;
import com.shrimpking.mapper.BookMapper;
import com.shrimpking.pojo.User;
import com.shrimpking.req.BookParams;
import com.shrimpking.req.QueryParams;
import com.shrimpking.service.BookService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;

/**
 * <p>
 * 图书表 服务实现类
 * </p>
 *
 * @author shrimpking
 * @since 2023-11-12
 */
@Service
public class BookServiceImpl extends ServiceImpl<BookMapper, Book> implements BookService {

    @Override
    public IPage<Book> findBySearchPage(BookParams bookParams)
    {
        //声明分页
        IPage<Book> page = new Page<>(bookParams.getCurrentPage(), bookParams.getPageSize());
        //声明查询条件
        LambdaQueryWrapper<Book> queryWrapper = new LambdaQueryWrapper<>();
        //图书名称不为空,有条件值时,加入此条件
        queryWrapper.like(
                StringUtils.isNotBlank(bookParams.getName()),
                Book::getName, bookParams.getName())
                //作者不为空,有条件值时,加入此条件
                .or().like(
                StringUtils.isNotBlank(bookParams.getAuthor()),
                Book::getAuthor,bookParams.getAuthor())
                //出版社不为空,加入此条件
                .or().like(
                StringUtils.isNotBlank(bookParams.getExpress()),
                Book::getExpress,bookParams.getExpress())
                .orderByDesc(Book::getId);
        //返回结果
        return this.baseMapper.selectPage(page, queryWrapper);
    }
}

controller

bookController.java

package com.shrimpking.controller;


import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.shrimpking.pojo.Book;
import com.shrimpking.pojo.User;
import com.shrimpking.req.BookParams;
import com.shrimpking.req.QueryParams;
import com.shrimpking.res.Result;
import com.shrimpking.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
 * <p>
 * 图书表 前端控制器
 * </p>
 *
 * @author shrimpking
 * @since 2023-11-12
 */
@RestController
@RequestMapping("/book")
public class BookController {

    @Autowired
    private BookService bookService;

    /**
     * 有查询条件时,获取分页数据
     * @param queryParams
     * @return
     */
    @GetMapping("/searchPage")
    public Result findBySearchPage(BookParams bookParams){
        IPage<Book> list = this.bookService.findBySearchPage(bookParams);
        return Result.success(list);
    }

    @PostMapping("/save")
    public Result save(@RequestBody Book book){
        //先查询有无同名图书
        LambdaQueryWrapper<Book> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Book::getName,book.getName());
        int count = this.bookService.count(queryWrapper);
        if(count > 0) return Result.error("此图书已存在");

        boolean save = this.bookService.save(book);
        if(!save) return Result.error("保存失败");
        return Result.success("保存成功");
    }

    @PostMapping("/update")
    public Result update(@RequestBody Book book){
        //先查询有无同名图书
        LambdaQueryWrapper<Book> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Book::getName,book.getName());
        Book one = this.bookService.getOne(queryWrapper);
        if(one != null && !one.getId().equals(book.getId())){
            return Result.error("此图书已存在");
        }

        boolean save = this.bookService.updateById(book);
        if(!save) return Result.error("更新失败");
        return Result.success("更新成功");
    }

    @DeleteMapping("/delete")
    public Result delete(@RequestParam("id") Integer id){
        boolean remove = this.bookService.removeById(id);
        if(!remove) return Result.error("删除失败");
        return Result.success("删除成功");
    }


}

fileController.java

package com.shrimpking.controller;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import com.shrimpking.res.Result;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.util.List;

/**
 * Created by IntelliJ IDEA.
 *
 * @Author : Shrimpking
 * @create 2023/11/13 11:07
 */
@RestController
@RequestMapping("/files")
public class FileController
{
    /**
     * 文件上传存储路径
     */
    private static final String filePath = System.getProperty("user.dir") + "/file/";

    /**
     * 上传文件
     * @param file
     * @return
     */
    @PostMapping("/upload")
    public Result upload(MultipartFile file){

        synchronized (FileController.class){
            //获取当前时间戳
            String flag = System.currentTimeMillis() + "";
            //获取原始文件名
            String fileName = file.getOriginalFilename();
            try
            {
                //如果文件夹不存在,在项目的根目录中创建文件夹
                if(!FileUtil.isDirectory(filePath)){
                    FileUtil.mkdir(filePath);
                }
                //文件存储形式: 时间戳 + 文件名
                FileUtil.writeBytes(file.getBytes(),filePath + flag + "-" + fileName);
                System.out.println(fileName + "--上传成功");
                Thread.sleep(1L);
            }catch (Exception e){
                System.err.println(fileName + "--上传失败");
            }
            return Result.success(flag);
        }
    }


    /**
     * 下载文件
     * @param flag
     * @param response
     */
    @GetMapping("/down/{flag}")
    public void avatarPath(@PathVariable String flag, HttpServletResponse response){
        //创建目录
        if(!FileUtil.isDirectory(filePath)){
            FileUtil.mkdir(filePath);
        }

        OutputStream os;
        //获取目录下全部文件列表
        List<String> fileNames = FileUtil.listFileNames(filePath);
        //逐一核对,获取该文件名称
        String avatar = fileNames.stream().filter(item -> item.contains(flag)).findAny().orElse("");

        try
        {
            if(StrUtil.isNotEmpty(avatar)){
                response.addHeader("Content-Disposition","attachment;filename=" + URLEncoder.encode(avatar,"UTF-8"));
                response.setContentType("application/octet-stream");
                byte[] bytes = FileUtil.readBytes(filePath + avatar);
                os = response.getOutputStream();
                os.write(bytes);
                os.flush();
                os.close();
            }
        }catch (Exception e){
            System.out.println("文件下载失败");
        }
    }
}

pom.xml

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.1</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>3.4.1</version>
        </dependency>
        <dependency>
            <groupId>org.freemarker</groupId>
            <artifactId>freemarker</artifactId>
            <version>2.3.30</version>
        </dependency>

        <!--swagger依赖-->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
        </dependency>
        <!--swagger ui-->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.9.2</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>
        <!-- jwt验证       -->
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.10.3</version>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.3.7</version>
        </dependency>
    </dependencies>

application.prpperties

server.port=8089

spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimeZone=UTC
spring.datasource.username=root
spring.datasource.password=mysql123
#日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
#配置别名
mybatis-plus.type-aliases-package=com.shrimpking.pojo
#开启逻辑删除,标识字段
mybatis-plus.global-config.db-config.logic-delete-field=is_deleted
#删除
mybatis-plus.global-config.db-config.logic-delete-value=1
#未删除
mybatis-plus.global-config.db-config.logic-not-delete-value=0
#swagger
spring.mvc.pathmatch.matching-strategy=ant_path_matcher

Vue前端

router

import Vue from 'vue'
import VueRouter from 'vue-router'


Vue.use(VueRouter)

const routes = [
  {
    path: '/login',
    name:'LoginView',
    component: ()=> import('@/views/LoginView.vue'),
  },
  {
    path:'/register',
    name: 'Register',
    component: ()=> import('@/views/RegisterView.vue'),
  },
  {
    path: '/',
    redirect: '/home',
    name: 'Layout',
    component: ()=> import('@/views/Layout.vue'),
    children:[
      {
        path: 'home',
        name: 'HomeView',
        component: ()=> import('@/views/HomeView.vue')
      },
      {
        path: 'admin',
        name: 'AdminView',
        component: ()=> import('@/views/User/AdminView.vue'),
      },
      {
        path:'user',
        name:'UserView',
        component: ()=> import('@/views/User/UserView.vue'),
      },
      {
        path:'book',
        name:'BookView',
        component: ()=> import('@/views/Info/BookView.vue'),
      },
    ]
  },
]

const router = new VueRouter({
  routes
})

//白名单
const IGNORE_URLS = ['/login','/register'];

//前置守卫
router.beforeEach((to, from, next) => {
  //在白名单中,放行
  if(IGNORE_URLS.includes(to.path)){
    next();
  }
  //获取用户
  let admin = JSON.parse(window.localStorage.getItem('access-admin'));
  if(!admin && !IGNORE_URLS.includes(to.path)){
    //没有登录 ,没有在白名单中,跳转登录
    return next('/login');
  }

  next();
});

export default router

request.js

import axios from 'axios'

const request  = axios.create({
    baseURL: 'http://localhost:8089/api',  //
    timeout: 5000,
});

//request 拦截器
request.interceptors.request.use( config =>{
    config.headers['Content-Type'] =  'application/json;charset=utf-8';
    //获取token
    const admin = JSON.parse(window.localStorage.getItem('access-admin'));
    if(admin){
        config.headers['z-token'] = admin.token;
    }
    return config;
},error => {
    return Promise.reject(error);
});

//respose 拦截器
request.interceptors.response.use( response => {
    //response.data即为后端返回的result, 也就是脱壳
    let res = response.data;

    //兼容服务端返回的字符串数据
    if(typeof res === 'string'){
        res = res ? JSON.parse(res) : res;
    }
    return res;
},error => {
    console.log('error:' + error);
    return Promise.reject(error);
});

export default request;

BookView.vue

<template>
    <div>
        <!-- 搜索区域       -->
        <div style="margin-bottom:15px;">
            <el-input
                    v-model="searchForm.name"
                    style="width:200px;"
                    placeholder="请输入图书名"
                    @clear="doSearch"
                    @keypress.native.enter="doSearch"
                    clearable>
            </el-input>
            <el-input
                    v-model="searchForm.author"
                    style="width:200px;margin-left: 10px;"
                    placeholder="请输入作者"
                    @clear="doSearch"
                    @keypress.native.enter="doSearch"
                    clearable>
            </el-input>
            <el-input
                    v-model="searchForm.express"
                    style="width:200px;margin-left: 10px;"
                    placeholder="请输入出版社"
                    @clear="doSearch"
                    @keypress.native.enter="doSearch"
                    clearable>
            </el-input>
            <el-button
                    type="warning"
                    style="margin-left: 10px;"
                    icon="el-icon-search"
                    @click="doSearch">查询</el-button>
            <el-button
                    type="primary"
                    style="margin-left: 10px;"
                    icon="el-icon-toilet-paper"
                    @click="clearSearch">清空</el-button>
            <el-button
                    type="primary"
                    style="margin-left: 10px;"
                    icon="el-icon-plus" @click="addBtn">新增</el-button>
        </div>

        <!-- 表格区域       -->
        <el-table
                :data="tableData"
                style="width: 100%">
            <el-table-column
                    prop="id"
                    label="ID">
            </el-table-column>
            <el-table-column
                    prop="name"
                    label="图书名称">
            </el-table-column>
            <el-table-column
                    prop="author"
                    label="作者">
            </el-table-column>
            <el-table-column
                    prop="express"
                    label="出版社">
            </el-table-column>
            <el-table-column
                    prop="price"
                    label="价格">
            </el-table-column>
            <el-table-column
                    prop="img"
                    label="图书封面">
                <template slot-scope="scope">
                    <el-image
                            :src="scope.row.img"
                            :preview-src-list="[scope.row.img]"
                            style="width:60px;height:60px;border-radius: 50%;"></el-image>
                </template>
            </el-table-column>
            <el-table-column label="操作" width="260px">
                <template slot-scope="scope">
                    <el-button type="primary" icon="el-icon-edit" @click="editBtn(scope.row)">编辑</el-button>
                    <el-button type="danger" icon="el-icon-delete" @click="deleteBtn(scope.row)">删除</el-button>
                    <el-button icon="el-icon-download" @click="downloadBtn(scope.row)">下载</el-button>
                </template>
            </el-table-column>
        </el-table>
        <!-- 分页区域       -->
        <div style="margin-top:15px;">
            <el-pagination
                    @size-change="handleSizeChange"
                    @current-change="handleCurrentChange"
                    :current-page="searchForm.currentPage"
                    :page-sizes="[2, 5, 10, 20]"
                    :page-size="searchForm.pageSize"
                    layout="total, sizes, prev, pager, next, jumper"
                    :total="total">
            </el-pagination>
        </div>
        <!--  对话框      -->
        <div>
            <el-dialog
                    :title="dialogTitle"
                    :visible.sync="dialogFormVisible"
                    :close-on-click-modal="false"
                    @close="closeDialog"
                    width="35%">
                <el-form
                        :model="addForm"
                        :rules="rules"
                        ref="addForm"
                        :label-width="formLabelWidth"
                        label-postion="left">
                    <el-form-item label="图书名称" prop="name">
                        <el-input v-model="addForm.name" clearable></el-input>
                    </el-form-item>
                    <el-form-item label="作者" prop="author">
                        <el-input v-model="addForm.author" clearable></el-input>
                    </el-form-item>
                    <el-form-item label="出版社" prop="express">
                        <el-input v-model="addForm.express" clearable></el-input>
                    </el-form-item>
                    <el-form-item label="价格" prop="price">
                        <el-input-number v-model="addForm.price" :max="9999.9" :min="0.1" :step="0.5"></el-input-number>
                    </el-form-item>
                    <el-form-item label="图片封面" prop="img">
<!--                        <el-input v-model="addForm.img" clearable></el-input>-->
                        <el-upload :action="uploadPath" :on-success="successUpload">
                            <el-button size="small" type="primary">点击上传</el-button>
                        </el-upload>
                    </el-form-item>
                </el-form>
                <div slot="footer" class="dialog-footer">
                    <el-button @click="resetBtn" v-show="dialogTitle === '新增图书'">重 置</el-button>
                    <el-button type="primary" @click="submitBtn">确 定</el-button>
                </div>
            </el-dialog>
        </div>
    </div>
</template>

<script>
    import request from "@/utils/request";

    export default {
        name: "BookView",
        data() {
            return {
                //上传路径
                uploadPath:'http://localhost:8089/api/files/upload/',
                //下载路径
                downloadPath:'http://localhost:8089/api/files/down/',
                //添加表单
                addForm:{
                    name:'',
                    author:'',
                    express:'',
                    price:'',
                    img:''
                },
                rules:{
                    name:[{required: true, message: '请输入图书名称', trigger: 'blur'}],
                    author:[{required: true, message: '请输入作者', trigger: 'blur'}],
                    express:[{required: true, message: '请输入出版社', trigger: 'blur'}],
                    price:[{required: true, message: '请输入价格', trigger: 'blur'}],
                    img:[{required: true, message: '请输入封面地址', trigger: 'blur'}],
                },
                //表单标题宽度
                formLabelWidth:'80px',
                //对话框标题
                dialogTitle:'',
                //对话框
                dialogFormVisible: false,
                //搜索条件
                searchForm:{
                    name: '',
                    author: '',
                    express:'',
                    currentPage: 1,
                    pageSize: 5
                },
                tableData: [],
                total:0
            }
        },
        methods: {
            //下载按钮
            downloadBtn(row){
                window.location.href = row.img;
            },
            //文件上传成功的回调
            successUpload(res){
                this.addForm.img = this.downloadPath + res.data;
            },
            //删除
            deleteBtn(row){
                this.$confirm(`您确定要删除【${row.name}】吗`,'删除提示',{
                    confirmButtonText:'删除',
                    cancelButtonText:'取消',
                    type:'warning',
                }).then(()=>{
                    request.delete('/book/delete',{
                        params:{ id : row.id}
                    }).then(res => {
                        if(res.code === '200'){
                            this.$message.success(res.data);
                            this.doSearch();
                        }
                    })
                }).catch(_=>{
                    this.$message.warning('已取消删除');
                })
            },
            //编辑
            editBtn(row){
                let obj = JSON.parse(JSON.stringify(row));
                this.addForm = obj;
                this.dialogTitle = "编辑图书";
                this.dialogFormVisible = true;
            },
            //关闭对话框
            closeDialog(){
                this.resetBtn();
                this.dialogFormVisible = false;
            },
            //新增保存
            submitBtn(){
                this.$refs.addForm.validate((valid)=>{
                    if(valid){
                        //校验通过
                        //有id,编辑,没有id是新增
                        request.post(this.addForm.id ? '/book/update':'/book/save',this.addForm)
                            .then(res=>{
                                if(res.code === '200'){
                                    this.$message.success(res.data);
                                    this.resetBtn();
                                    this.dialogFormVisible = false;
                                    this.doSearch();
                                }else {
                                    this.$message.error(res.msg);
                                }
                            })
                    }
                })
            },
            //新增重置
            resetBtn(){
                this.$refs.addForm.resetFields();
                //修复bug
                this.addForm = {};
            },
            addBtn(){
                this.dialogTitle = '新增图书';
                this.dialogFormVisible = true;
            },
            clearSearch(){
                this.searchForm.name = '';
                this.searchForm.author = '';
                this.searchForm.express = '';
                this.doSearch();
            },
            //搜索
            doSearch(){
                //修复bug
                this.searchForm.currentPage = 1;
                this.getData();

            },
            handleSizeChange(val) {
                this.searchForm.pageSize = val;
                this.getData();
            },
            handleCurrentChange(val) {
                this.searchForm.currentPage = val;
                this.getData();
            },
            //获取数据
            getData(){
                request.get('/book/searchPage',{
                    params: this.searchForm
                }).then(res=>{
                    if(res.code === '200'){
                        this.tableData = res.data.records; //数据
                        this.searchForm.currentPage = res.data.current; //当前页
                        this.searchForm.pageSize = res.data.size; //页条数
                        this.total = res.data.total; //总条数
                    }else {
                        this.$message.error(res.msg);
                    }
                });
            }
        },
        created(){
            //获取数据
            this.getData();
        }
    }
</script>

<style scoped>

</style>

测试

登录

 首页

 图书信息

 新增图书

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

虾米大王

有你的支持,我会更有动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值